1/*
2 * Copyright (c) 2006, 2012 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23#include <CoreFoundation/CoreFoundation.h>
24#include <CoreFoundation/CFBundlePriv.h>
25#include <errno.h>
26#include <libc.h>
27#include <libgen.h>     // dirname()
28#include <Kernel/libkern/mkext.h>
29#include <sys/types.h>
30#include <sys/mman.h>
31#include <fts.h>
32#include <paths.h>
33#include <mach-o/arch.h>
34#include <mach-o/fat.h>
35#include <mach-o/loader.h>
36#include <mach-o/nlist.h>
37#include <mach-o/swap.h>
38#include <mach/mach.h>
39#include <mach/mach_error.h>
40#include <mach/mach_port.h>     // mach_port_allocate()
41#include <mach/mach_types.h>
42#include <mach/machine/vm_param.h>
43#include <mach/kmod.h>
44#include <notify.h>
45#include <servers/bootstrap.h>  // bootstrap mach ports
46#include <stdlib.h>
47#include <unistd.h>             // sleep(3)
48#include <sys/types.h>
49#include <sys/stat.h>
50
51#include <DiskArbitration/DiskArbitrationPrivate.h>
52#include <IOKit/IOTypes.h>
53#include <IOKit/IOKitLib.h>
54#include <IOKit/IOKitServer.h>
55#include <IOKit/IOCFUnserialize.h>
56#include <IOKit/IOCFSerialize.h>
57#include <libkern/OSByteOrder.h>
58
59#include <IOKit/kext/OSKext.h>
60#include <IOKit/kext/OSKextPrivate.h>
61#include <IOKit/kext/kextmanager_types.h>
62#include <IOKit/kext/kextmanager_mig.h>
63
64#include <IOKit/pwr_mgt/IOPMLib.h>
65
66#include "kcgen_main.h"
67#include "compression.h"
68
69/*******************************************************************************
70* Program Globals
71*******************************************************************************/
72const char * progname = "(unknown)";
73
74/*******************************************************************************
75*******************************************************************************/
76int main(int argc, char * const * argv)
77{
78    ExitStatus      result    = EX_SOFTWARE;
79    KcgenArgs       toolArgs;
80    Boolean         fatal     = false;
81
82   /*****
83    * Find out what the program was invoked as.
84    */
85    progname = rindex(argv[0], '/');
86    if (progname) {
87        progname++;   // go past the '/'
88    } else {
89        progname = (char *)argv[0];
90    }
91
92   /* Set the OSKext log callback right away.
93    */
94    OSKextSetLogOutputFunction(&tool_log);
95
96   /* Make the library not sort opened kexts by version for bundle ID lookups.
97    */
98    _OSKextSetStrictRecordingByLastOpened(TRUE);
99
100   /*****
101    * Check if we were spawned by kextd, set up straightaway
102    * for service log filtering, and hook up to ASL.
103    */
104    if (getenv("KEXTD_SPAWNED")) {
105        OSKextSetLogFilter(kDefaultServiceLogFilter | kOSKextLogKextOrGlobalMask,
106            /* kernel? */ false);
107        OSKextSetLogFilter(kDefaultServiceLogFilter | kOSKextLogKextOrGlobalMask,
108            /* kernel? */ true);
109        tool_openlog("com.apple.kcgen");
110    }
111
112   /*****
113    * Process args & check for permission to load.
114    */
115    result = readArgs(&argc, &argv, &toolArgs);
116    if (result != EX_OK) {
117        if (result == kKcgenExitHelp) {
118            result = EX_OK;
119        }
120        goto finish;
121    }
122
123    result = checkArgs(&toolArgs);
124    if (result != EX_OK) {
125        goto finish;
126    }
127
128   /* From here on out the default exit status is ok.
129    */
130    result = EX_OK;
131
132   /* The whole point of this program is to update caches, so let's not
133    * try to read any (we'll briefly turn this back on when checking them).
134    */
135    OSKextSetUsesCaches(false);
136
137   /* If we're compressing the prelinked kernel, take care of that here
138    * and exit.
139    */
140    if (toolArgs.prelinkedKernelPath && !CFArrayGetCount(toolArgs.argURLs) &&
141        (toolArgs.compress || toolArgs.uncompress))
142    {
143        result = compressPrelinkedKernel(toolArgs.prelinkedKernelPath, toolArgs.compress);
144        goto finish;
145    }
146
147   /*****
148    * Read the kexts we'll be working with; first the set of all kexts, then
149    * the repository and named kexts for use with mkext-creation flags.
150    */
151    if (toolArgs.printTestResults) {
152        OSKextSetRecordsDiagnostics(kOSKextDiagnosticsFlagAll);
153    }
154    toolArgs.allKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault, toolArgs.argURLs);
155    if (!toolArgs.allKexts || !CFArrayGetCount(toolArgs.allKexts)) {
156        OSKextLog(/* kext */ NULL,
157            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
158            "Error - no kernel extensions found.");
159        result = EX_SOFTWARE;
160        goto finish;
161    }
162
163    toolArgs.repositoryKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
164        toolArgs.repositoryURLs);
165    toolArgs.namedKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
166        toolArgs.namedKextURLs);
167    if (!toolArgs.repositoryKexts || !toolArgs.namedKexts) {
168        OSKextLog(/* kext */ NULL,
169            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
170            "Error - failed to read extensions.");
171        result = EX_SOFTWARE;
172        goto finish;
173    }
174
175    if (result != EX_OK) {
176        goto finish;
177    }
178
179    if (toolArgs.prelinkedKernelPath) {
180        result = createPrelinkedKernel(&toolArgs);
181        if (result != EX_OK) {
182            goto finish;
183        }
184
185    }
186
187finish:
188
189   /* We're actually not going to free anything else because we're exiting!
190    */
191    exit(result);
192
193    SAFE_RELEASE(toolArgs.kextIDs);
194    SAFE_RELEASE(toolArgs.argURLs);
195    SAFE_RELEASE(toolArgs.repositoryURLs);
196    SAFE_RELEASE(toolArgs.namedKextURLs);
197    SAFE_RELEASE(toolArgs.allKexts);
198    SAFE_RELEASE(toolArgs.repositoryKexts);
199    SAFE_RELEASE(toolArgs.namedKexts);
200    SAFE_RELEASE(toolArgs.kernelFile);
201    SAFE_RELEASE(toolArgs.symbolDirURL);
202    SAFE_FREE(toolArgs.prelinkedKernelPath);
203    SAFE_FREE(toolArgs.kernelPath);
204
205    return result;
206}
207
208/*******************************************************************************
209*******************************************************************************/
210ExitStatus readArgs(
211    int            * argc,
212    char * const  ** argv,
213    KcgenArgs  * toolArgs)
214{
215    ExitStatus   result         = EX_USAGE;
216    ExitStatus   scratchResult  = EX_USAGE;
217    CFStringRef  scratchString  = NULL;  // must release
218    CFNumberRef  scratchNumber  = NULL;  // must release
219    CFURLRef     scratchURL     = NULL;  // must release
220    size_t       len            = 0;
221    int32_t      i              = 0;
222    int          optchar        = 0;
223    int          longindex      = -1;
224
225    bzero(toolArgs, sizeof(*toolArgs));
226
227   /*****
228    * Allocate collection objects.
229    */
230    if (!createCFMutableSet(&toolArgs->kextIDs, &kCFTypeSetCallBacks)             ||
231        !createCFMutableSet(&toolArgs->optionalKextIDs, &kCFTypeSetCallBacks)     ||
232        !createCFMutableArray(&toolArgs->argURLs, &kCFTypeArrayCallBacks)         ||
233        !createCFMutableArray(&toolArgs->repositoryURLs, &kCFTypeArrayCallBacks)  ||
234        !createCFMutableArray(&toolArgs->namedKextURLs, &kCFTypeArrayCallBacks)   ||
235        !createCFMutableArray(&toolArgs->targetArchs, NULL)) {
236
237        OSKextLogMemError();
238        result = EX_OSERR;
239        exit(result);
240    }
241
242    /*****
243    * Process command line arguments.
244    */
245    while ((optchar = getopt_long_only(*argc, *argv,
246        kOptChars, sOptInfo, &longindex)) != -1) {
247
248        SAFE_RELEASE_NULL(scratchString);
249        SAFE_RELEASE_NULL(scratchNumber);
250        SAFE_RELEASE_NULL(scratchURL);
251
252        /* When processing short (single-char) options, there is no way to
253         * express optional arguments.  Instead, we suppress missing option
254         * argument errors by adding a leading ':' to the option string.
255         * When getopt detects a missing argument, it will return a ':' so that
256         * we can screen for options that are not required to have an argument.
257         */
258        if (optchar == ':') {
259            switch (optopt) {
260                case kOptPrelinkedKernel:
261                    optchar = optopt;
262                    break;
263                default:
264                    OSKextLog(/* kext */ NULL,
265                        kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
266                        "Error - option requires an argument -- -%c.",
267                        optopt);
268                    break;
269            }
270        }
271
272        switch (optchar) {
273
274            case kOptArch:
275                if (!addArchForName(toolArgs, optarg)) {
276                    OSKextLog(/* kext */ NULL,
277                        kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
278                        "Error - unknown architecture %s.", optarg);
279                    goto finish;
280                }
281                break;
282
283            case kOptBundleIdentifier:
284                scratchString = CFStringCreateWithCString(kCFAllocatorDefault,
285                   optarg, kCFStringEncodingUTF8);
286                if (!scratchString) {
287                    OSKextLogMemError();
288                    result = EX_OSERR;
289                    goto finish;
290                }
291                CFSetAddValue(toolArgs->kextIDs, scratchString);
292                break;
293
294            case kOptPrelinkedKernel:
295                scratchResult = readPrelinkedKernelArgs(toolArgs, *argc, *argv,
296                    /* isLongopt */ longindex != -1);
297                if (scratchResult != EX_OK) {
298                    result = scratchResult;
299                    goto finish;
300                }
301                break;
302
303            case kOptHelp:
304                usage(kUsageLevelFull);
305                result = kKcgenExitHelp;
306                goto finish;
307
308            case kOptKernel:
309                if (toolArgs->kernelPath) {
310                    OSKextLog(/* kext */ NULL,
311                        kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
312                        "Warning - kernel file already specified; using last.");
313                } else {
314                    toolArgs->kernelPath = malloc(PATH_MAX);
315                    if (!toolArgs->kernelPath) {
316                        OSKextLogMemError();
317                        result = EX_OSERR;
318                        goto finish;
319                    }
320                }
321
322                len = strlcpy(toolArgs->kernelPath, optarg, PATH_MAX);
323                if (len >= PATH_MAX) {
324                    OSKextLog(/* kext */ NULL,
325                        kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
326                        "Error - kernel filename length exceeds PATH_MAX");
327                    goto finish;
328                }
329                break;
330
331            case kOptTests:
332                toolArgs->printTestResults = true;
333                break;
334
335            case kOptQuiet:
336                beQuiet();
337                break;
338
339            case kOptVerbose:
340                scratchResult = setLogFilterForOpt(*argc, *argv,
341                    /* forceOnFlags */ kOSKextLogKextOrGlobalMask);
342                if (scratchResult != EX_OK) {
343                    result = scratchResult;
344                    goto finish;
345                }
346                break;
347
348            case kOptNoAuthentication:
349                OSKextLog(/* kext */ NULL,
350                    kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
351                    "Note: -%s is implicitly set for %s.", kOptNameNoAuthentication, progname);
352                break;
353
354            case 0:
355                switch (longopt) {
356                    case kLongOptOptionalBundleIdentifier:
357                        scratchString = CFStringCreateWithCString(kCFAllocatorDefault,
358                           optarg, kCFStringEncodingUTF8);
359                        if (!scratchString) {
360                            OSKextLogMemError();
361                            result = EX_OSERR;
362                            goto finish;
363                        }
364                        CFSetAddValue(toolArgs->optionalKextIDs, scratchString);
365                        break;
366
367                    case kLongOptCompressed:
368                        toolArgs->compress = true;
369                        break;
370
371                    case kLongOptUncompressed:
372                        toolArgs->uncompress = true;
373                        break;
374
375                    case kLongOptSymbols:
376                        if (toolArgs->symbolDirURL) {
377                            OSKextLog(/* kext */ NULL,
378                                kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
379                                "Warning - symbol directory already specified; using last.");
380                            SAFE_RELEASE_NULL(toolArgs->symbolDirURL);
381                        }
382
383                        scratchURL = CFURLCreateFromFileSystemRepresentation(
384                            kCFAllocatorDefault,
385                            (const UInt8 *)optarg, strlen(optarg), true);
386                        if (!scratchURL) {
387                            OSKextLogStringError(/* kext */ NULL);
388                            result = EX_OSERR;
389                            goto finish;
390                        }
391
392                        toolArgs->symbolDirURL = CFRetain(scratchURL);
393                        toolArgs->generatePrelinkedSymbols = true;
394                        break;
395
396                    case kLongOptVolumeRoot:
397                        if (toolArgs->volumeRootURL) {
398                            OSKextLog(/* kext */ NULL,
399                                kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
400                                "Warning: volume root already specified; using last.");
401                            SAFE_RELEASE_NULL(toolArgs->volumeRootURL);
402                        }
403
404                        scratchURL = CFURLCreateFromFileSystemRepresentation(
405                            kCFAllocatorDefault,
406                            (const UInt8 *)optarg, strlen(optarg), true);
407                        if (!scratchURL) {
408                            OSKextLogStringError(/* kext */ NULL);
409                            result = EX_OSERR;
410                            goto finish;
411                        }
412
413                        toolArgs->volumeRootURL = CFRetain(scratchURL);
414                        break;
415
416                    case kLongOptAllPersonalities:
417                        OSKextLog(/* kext */ NULL,
418                            kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
419                            "Note: -%s is implicitly set for %s.", kOptNameAllPersonalities, progname);
420                        break;
421
422                    case kLongOptNoLinkFailures:
423                        OSKextLog(/* kext */ NULL,
424                            kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
425                            "Note: -%s is implicitly set for %s.", kOptNameNoLinkFailures, progname);
426                        break;
427
428                    case kLongOptStripSymbols:
429                        toolArgs->stripSymbols = true;
430                        break;
431
432                    case kLongOptMaxSliceSize:
433                        toolArgs->maxSliceSize = atol(optarg);
434                        break;
435
436                    default:
437                       /* Because we use ':', getopt_long doesn't print an error message.
438                        */
439                        OSKextLog(/* kext */ NULL,
440                            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
441                            "Error - unrecognized option %s", (*argv)[optind-1]);
442                        goto finish;
443                        break;
444                }
445                break;
446
447            default:
448               /* Because we use ':', getopt_long doesn't print an error message.
449                */
450                OSKextLog(/* kext */ NULL,
451                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
452                    "Error - unrecognized option %s", (*argv)[optind-1]);
453                goto finish;
454                break;
455
456        }
457
458       /* Reset longindex, because getopt_long_only() is stupid and doesn't.
459        */
460        longindex = -1;
461    }
462
463   /* Update the argc & argv seen by main().
464    */
465    *argc -= optind;
466    *argv += optind;
467
468   /*
469    * Record the kext & directory names from the command line.
470    */
471    for (i = 0; i < *argc; i++) {
472        SAFE_RELEASE_NULL(scratchURL);
473        SAFE_RELEASE_NULL(scratchString);
474
475        scratchURL = CFURLCreateFromFileSystemRepresentation(
476            kCFAllocatorDefault,
477            (const UInt8 *)(*argv)[i], strlen((*argv)[i]), true);
478        if (!scratchURL) {
479            OSKextLogMemError();
480            result = EX_OSERR;
481            goto finish;
482        }
483        CFArrayAppendValue(toolArgs->argURLs, scratchURL);
484
485        scratchString = CFURLCopyPathExtension(scratchURL);
486        if (scratchString && CFEqual(scratchString, CFSTR("kext"))) {
487            CFArrayAppendValue(toolArgs->namedKextURLs, scratchURL);
488        } else {
489            CFArrayAppendValue(toolArgs->repositoryURLs, scratchURL);
490        }
491    }
492
493    result = EX_OK;
494
495finish:
496    SAFE_RELEASE(scratchString);
497    SAFE_RELEASE(scratchNumber);
498    SAFE_RELEASE(scratchURL);
499
500    if (result == EX_USAGE) {
501        usage(kUsageLevelBrief);
502    }
503    return result;
504}
505
506/*******************************************************************************
507*******************************************************************************/
508void logUsedKexts(
509    KcgenArgs       * toolArgs,
510    CFArrayRef        prelinkKexts)
511{
512    int               fd;
513    int               ret;
514    char            * kextTracePath;
515    char              tmpBuffer[PATH_MAX + 1 + 1];
516    size_t            tmpBufLen;
517    ssize_t           writeSize;
518    CFIndex           count, i;
519
520    /* Log used kext bundle identifiers to out-of-band log file */
521    kextTracePath = getenv("KEXT_TRACE_FILE");
522    if (!kextTracePath) {
523        return;
524    }
525
526    fd = open(kextTracePath, O_WRONLY|O_APPEND|O_CREAT, DEFFILEMODE);
527    if (fd < 0) {
528        return;
529    }
530
531    snprintf(tmpBuffer, sizeof(tmpBuffer), "%s\n", toolArgs->prelinkedKernelPath);
532    tmpBufLen = strlen(tmpBuffer);
533    writeSize = write(fd, tmpBuffer, tmpBufLen);
534    if (writeSize != (ssize_t)tmpBufLen) {
535        close(fd);
536        return;
537    }
538
539    count = CFArrayGetCount(prelinkKexts);
540    for (i = 0; i < count; i++) {
541        CFStringRef kextID;
542        OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(prelinkKexts, i);
543
544        kextID = OSKextGetIdentifier(theKext);
545        if (kextID) {
546            char kextIDCString[KMOD_MAX_NAME];
547
548            CFStringGetCString(kextID, kextIDCString, sizeof(kextIDCString),
549                               kCFStringEncodingUTF8);
550
551            snprintf(tmpBuffer, sizeof(tmpBuffer), "[Logging for XBS] Used kext bundle identifier: %s\n", kextIDCString);
552            tmpBufLen = strlen(tmpBuffer);
553            writeSize = write(fd, tmpBuffer, tmpBufLen);
554            if (writeSize != (ssize_t)tmpBufLen) {
555                close(fd);
556                return;
557            }
558        }
559    }
560
561    close(fd);
562}
563
564/*******************************************************************************
565*******************************************************************************/
566ExitStatus readPrelinkedKernelArgs(
567    KcgenArgs * toolArgs,
568    int             argc,
569    char * const  * argv,
570    Boolean         isLongopt)
571{
572    char * filename = NULL;  // do not free
573
574    if (optarg) {
575        filename = optarg;
576    } else if (isLongopt && optind < argc) {
577        filename = argv[optind];
578        optind++;
579    }
580
581    if (filename && !filename[0]) {
582        filename = NULL;
583    }
584
585    return setPrelinkedKernelArgs(toolArgs, filename);
586}
587
588/*******************************************************************************
589*******************************************************************************/
590ExitStatus setPrelinkedKernelArgs(
591    KcgenArgs * toolArgs,
592    char      * filename)
593{
594    ExitStatus          result          = EX_USAGE;
595
596    if (toolArgs->prelinkedKernelPath) {
597        OSKextLog(/* kext */ NULL,
598            kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
599            "Warning - prelinked kernel already specified; using last.");
600    } else {
601        toolArgs->prelinkedKernelPath = malloc(PATH_MAX);
602        if (!toolArgs->prelinkedKernelPath) {
603            OSKextLogMemError();
604            result = EX_OSERR;
605            goto finish;
606        }
607    }
608
609   /* If we don't have a filename we construct a default one, automatically
610    * add the system extensions folders, and note that we're using default
611    * info.
612    */
613    if (!filename) {
614        OSKextLog(/* kext */ NULL,
615            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
616            "Error - prelinked kernel filename required");
617        goto finish;
618    } else {
619        size_t len = strlcpy(toolArgs->prelinkedKernelPath, filename, PATH_MAX);
620        if (len >= PATH_MAX) {
621            OSKextLog(/* kext */ NULL,
622                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
623                "Error - prelinked kernel filename length exceeds PATH_MAX");
624            goto finish;
625        }
626    }
627
628    result = EX_OK;
629finish:
630    return result;
631}
632
633/*******************************************************************************
634********************************************************************************/
635void addArch(
636    KcgenArgs * toolArgs,
637    const NXArchInfo  * arch)
638{
639    if (CFArrayContainsValue(toolArgs->targetArchs,
640        RANGE_ALL(toolArgs->targetArchs), arch))
641    {
642        return;
643    }
644
645    CFArrayAppendValue(toolArgs->targetArchs, arch);
646}
647
648/*******************************************************************************
649*******************************************************************************/
650const NXArchInfo * addArchForName(
651    KcgenArgs     * toolArgs,
652    const char    * archname)
653{
654    const NXArchInfo * result = NULL;
655
656    result = NXGetArchInfoFromName(archname);
657    if (!result) {
658        goto finish;
659    }
660
661    addArch(toolArgs, result);
662
663finish:
664    return result;
665}
666
667/*******************************************************************************
668*******************************************************************************/
669ExitStatus checkArgs(KcgenArgs * toolArgs)
670{
671    ExitStatus  result  = EX_USAGE;
672
673    if (!toolArgs->prelinkedKernelPath)
674    {
675        OSKextLog(/* kext */ NULL,
676            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
677            "Error - no work to do; check options and try again.");
678        goto finish;
679    }
680
681    if (!CFArrayGetCount(toolArgs->argURLs) &&
682        !toolArgs->compress && !toolArgs->uncompress)
683    {
684        OSKextLog(/* kext */ NULL,
685            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
686            "Error - no kexts or directories specified.");
687        goto finish;
688    }
689
690    if (!toolArgs->compress && !toolArgs->uncompress) {
691        toolArgs->compress = true;
692    } else if (toolArgs->compress && toolArgs->uncompress) {
693        OSKextLog(/* kext */ NULL,
694            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
695            "Both -%s and -%s specified; using -%s.",
696            kOptNameCompressed, kOptNameUncompressed, kOptNameCompressed);
697        toolArgs->compress = true;
698        toolArgs->uncompress = false;
699    }
700
701    result = EX_OK;
702
703finish:
704    if (result == EX_USAGE) {
705        usage(kUsageLevelBrief);
706    }
707    return result;
708}
709
710/*******************************************************************************
711*******************************************************************************/
712typedef struct {
713    KcgenArgs         * toolArgs;
714    CFMutableArrayRef   kextArray;
715    const NXArchInfo  * arch;
716    Boolean             optional;
717    Boolean             error;
718} FilterIDContext;
719
720void filterKextID(const void * vValue, void * vContext)
721{
722    CFStringRef       kextID  = (CFStringRef)vValue;
723    FilterIDContext * context = (FilterIDContext *)vContext;
724    OSKextRef         theKext = OSKextGetKextWithIdentifier(kextID);
725
726    if (!theKext) {
727        char kextIDCString[KMOD_MAX_NAME];
728
729        CFStringGetCString(kextID, kextIDCString, sizeof(kextIDCString),
730            kCFStringEncodingUTF8);
731
732        if (context->optional) {
733            OSKextLog(/* kext */ NULL,
734                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
735                    "Can't find kext with optional identifier %s; skipping.", kextIDCString);
736        } else {
737            OSKextLog(/* kext */ NULL,
738                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
739                    "Error - can't find kext with identifier %s.", kextIDCString);
740            context->error = TRUE;
741        }
742
743        goto finish;
744    }
745
746    if (checkKextForProblems(context->toolArgs, theKext, context->arch)) {
747        if (!context->optional) {
748            OSKextLog(/* kext */ NULL,
749                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
750                    "Error - a required kext was omitted");
751            context->error = true;
752        }
753        goto finish;
754    }
755
756    if (!CFArrayContainsValue(context->kextArray,
757            RANGE_ALL(context->kextArray), theKext))
758    {
759        CFArrayAppendValue(context->kextArray, theKext);
760    }
761
762finish:
763    return;
764}
765
766/*******************************************************************************
767 *******************************************************************************/
768ExitStatus checkKextForProblems(
769        KcgenArgs         * toolArgs,
770        OSKextRef           theKext,
771        const NXArchInfo  * arch)
772{
773    ExitStatus          result        = EX_SOFTWARE;
774    char                kextPath[PATH_MAX];
775
776    if (!CFURLGetFileSystemRepresentation(OSKextGetURL(theKext),
777                              /* resolveToBase */ false, (UInt8 *)kextPath, sizeof(kextPath)))
778    {
779        strlcpy(kextPath, "(unknown)", sizeof(kextPath));
780    }
781
782    /* Skip kexts we have no interest in for the current arch.
783     */
784    if (!OSKextSupportsArchitecture(theKext, arch)) {
785        OSKextLog(/* kext */ NULL,
786                  kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
787                  "%s doesn't support architecture '%s'; ommiting.", kextPath,
788                  arch->name);
789        goto finish;
790    }
791
792    if (!OSKextIsValid(theKext)) {
793        OSKextLog(/* kext */ NULL,
794                  kOSKextLogErrorLevel | kOSKextLogArchiveFlag |
795                  kOSKextLogValidationFlag | kOSKextLogGeneralFlag,
796                  "%s is not valid; omitting.", kextPath);
797        if (toolArgs->printTestResults) {
798            OSKextLogDiagnostics(theKext, kOSKextDiagnosticsFlagAll);
799        }
800        goto finish;
801    }
802
803    if (!OSKextResolveDependencies(theKext)) {
804        OSKextLog(/* kext */ NULL,
805                  kOSKextLogWarningLevel | kOSKextLogArchiveFlag |
806                  kOSKextLogDependenciesFlag | kOSKextLogGeneralFlag,
807                  "%s is missing dependencies (including anyway; "
808                  "dependencies may be available from elsewhere)", kextPath);
809        if (toolArgs->printTestResults) {
810            OSKextLogDiagnostics(theKext, kOSKextDiagnosticsFlagAll);
811        }
812    }
813
814    result = EX_OK;
815
816finish:
817    return result;
818
819}
820
821/*******************************************************************************
822*******************************************************************************/
823ExitStatus filterKextsForCache(
824        KcgenArgs         * toolArgs,
825        CFMutableArrayRef   kextArray,
826        const NXArchInfo  * arch,
827        Boolean           * fatalOut __unused)
828{
829    ExitStatus          result        = EX_SOFTWARE;
830    CFIndex             count, i;
831
832    CFArrayRemoveAllValues(kextArray);
833
834   /*****
835    * Apply filters to select the kexts.
836    *
837    * If kexts have been specified by identifier, those are the only kexts we are going to use.
838    * Otherwise run through the repository and named kexts and see which ones match the filter.
839    */
840    if (CFSetGetCount(toolArgs->kextIDs) || CFSetGetCount(toolArgs->optionalKextIDs)) {
841        FilterIDContext context;
842
843        context.toolArgs = toolArgs;
844        context.kextArray = kextArray;
845        context.arch = arch;
846        context.optional = FALSE;
847        context.error = FALSE;
848
849        CFSetApplyFunction(toolArgs->kextIDs, filterKextID, &context);
850
851        context.optional = TRUE;
852        CFSetApplyFunction(toolArgs->optionalKextIDs, filterKextID, &context);
853
854        if (context.error) {
855            goto finish;
856        }
857
858    } else {
859
860        count = CFArrayGetCount(toolArgs->repositoryKexts);
861        for (i = 0; i < count; i++) {
862            char kextPath[PATH_MAX];
863            OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(
864                    toolArgs->repositoryKexts, i);
865
866            if (!CFArrayContainsValue(kextArray, RANGE_ALL(kextArray), theKext) &&
867                !checkKextForProblems(toolArgs, theKext, arch)) {
868                CFArrayAppendValue(kextArray, theKext);
869            }
870        }
871
872        count = CFArrayGetCount(toolArgs->namedKexts);
873        for (i = 0; i < count; i++) {
874            OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(
875                    toolArgs->namedKexts, i);
876            if (!CFArrayContainsValue(kextArray, RANGE_ALL(kextArray), theKext) &&
877                !checkKextForProblems(toolArgs, theKext, arch)) {
878                CFArrayAppendValue(kextArray, theKext);
879            }
880        }
881    }
882
883    result = EX_OK;
884
885finish:
886    return result;
887}
888
889/*******************************************************************************
890 * Creates a list of architectures to generate prelinked kernel slices for by
891 * selecting the requested architectures for which the kernel has a slice.
892 * Warns when a requested architecture does not have a corresponding kernel
893 * slice.
894 *******************************************************************************/
895ExitStatus
896createPrelinkedKernelArchs(
897    KcgenArgs         * toolArgs,
898    CFMutableArrayRef * prelinkArchsOut)
899{
900    ExitStatus          result          = EX_OSERR;
901    CFMutableArrayRef   kernelArchs     = NULL;  // must release
902    CFMutableArrayRef   prelinkArchs    = NULL;  // must release
903    const NXArchInfo  * targetArch      = NULL;  // do not free
904    int                 i               = 0;
905
906    result = readFatFileArchsWithPath(toolArgs->kernelPath, &kernelArchs);
907    if (result != EX_OK) {
908        goto finish;
909    }
910
911    prelinkArchs = CFArrayCreateMutableCopy(kCFAllocatorDefault,
912        /* capacity */ 0, toolArgs->targetArchs);
913    if (!prelinkArchs) {
914        OSKextLogMemError();
915        result = EX_OSERR;
916        goto finish;
917    }
918
919    for (i = 0; i < CFArrayGetCount(prelinkArchs); ++i) {
920        targetArch = CFArrayGetValueAtIndex(prelinkArchs, i);
921        if (!CFArrayContainsValue(kernelArchs,
922            RANGE_ALL(kernelArchs), targetArch))
923        {
924            OSKextLog(/* kext */ NULL,
925                kOSKextLogWarningLevel | kOSKextLogArchiveFlag,
926                "Warning - kernel file %s does not contain requested arch: %s",
927                toolArgs->kernelPath, targetArch->name);
928            CFArrayRemoveValueAtIndex(prelinkArchs, i);
929            i--;
930            continue;
931        }
932    }
933
934    *prelinkArchsOut = (CFMutableArrayRef) CFRetain(prelinkArchs);
935    result = EX_OK;
936
937finish:
938    SAFE_RELEASE(kernelArchs);
939    SAFE_RELEASE(prelinkArchs);
940
941    return result;
942}
943
944/*******************************************************************************
945*******************************************************************************/
946ExitStatus
947createPrelinkedKernel(
948    KcgenArgs     * toolArgs)
949{
950    ExitStatus          result              = EX_OSERR;
951    struct timeval      prelinkFileTimes[2];
952    CFMutableArrayRef   generatedArchs      = NULL;  // must release
953    CFMutableArrayRef   generatedSymbols    = NULL;  // must release
954    CFMutableArrayRef   existingArchs       = NULL;  // must release
955    CFMutableArrayRef   existingSlices      = NULL;  // must release
956    CFMutableArrayRef   prelinkArchs        = NULL;  // must release
957    CFMutableArrayRef   prelinkSlices       = NULL;  // must release
958    CFDataRef           prelinkSlice        = NULL;  // must release
959    CFDictionaryRef     sliceSymbols        = NULL;  // must release
960    const NXArchInfo  * targetArch          = NULL;  // do not free
961    Boolean             updateModTime       = false;
962    u_int               numArchs            = 0;
963    u_int               i                   = 0;
964    int                 j                   = 0;
965
966    bzero(prelinkFileTimes, sizeof(prelinkFileTimes));
967
968    result = createPrelinkedKernelArchs(toolArgs, &prelinkArchs);
969    if (result != EX_OK) {
970        goto finish;
971    }
972    numArchs = (u_int)CFArrayGetCount(prelinkArchs);
973
974    prelinkSlices = CFArrayCreateMutable(kCFAllocatorDefault,
975        numArchs, &kCFTypeArrayCallBacks);
976    generatedSymbols = CFArrayCreateMutable(kCFAllocatorDefault,
977        numArchs, &kCFTypeArrayCallBacks);
978    generatedArchs = CFArrayCreateMutable(kCFAllocatorDefault,
979        numArchs, NULL);
980    if (!prelinkSlices || !generatedSymbols || !generatedArchs) {
981        OSKextLogMemError();
982        result = EX_OSERR;
983        goto finish;
984    }
985
986    for (i = 0; i < numArchs; i++) {
987        targetArch = CFArrayGetValueAtIndex(prelinkArchs, i);
988
989        SAFE_RELEASE_NULL(prelinkSlice);
990        SAFE_RELEASE_NULL(sliceSymbols);
991
992       /* We always create a new prelinked kernel for the current
993        * running architecture if asked, but we'll reuse existing slices
994        * for other architectures if possible.
995        */
996        if (existingArchs &&
997            targetArch != OSKextGetRunningKernelArchitecture())
998        {
999            j = (int)CFArrayGetFirstIndexOfValue(existingArchs,
1000                RANGE_ALL(existingArchs), targetArch);
1001            if (j != -1) {
1002                prelinkSlice = CFArrayGetValueAtIndex(existingSlices, j);
1003                CFArrayAppendValue(prelinkSlices, prelinkSlice);
1004                prelinkSlice = NULL;
1005                OSKextLog(/* kext */ NULL,
1006                    kOSKextLogDebugLevel | kOSKextLogArchiveFlag,
1007                    "Using existing prelinked slice for arch %s",
1008                    targetArch->name);
1009                continue;
1010            }
1011        }
1012
1013        OSKextLog(/* kext */ NULL,
1014            kOSKextLogDebugLevel | kOSKextLogArchiveFlag,
1015            "Generating a new prelinked slice for arch %s",
1016            targetArch->name);
1017
1018        result = createPrelinkedKernelForArch(toolArgs, &prelinkSlice,
1019            &sliceSymbols, targetArch);
1020        if (result != EX_OK) {
1021            goto finish;
1022        }
1023
1024        if (toolArgs->maxSliceSize &&
1025            (CFDataGetLength(prelinkSlice) > toolArgs->maxSliceSize)) {
1026
1027            result = EX_SOFTWARE;
1028            OSKextLog(/* kext */ NULL,
1029                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
1030                "Error - prelink slice is larger (%ld) than requested maximum %ld.",
1031                (long int)CFDataGetLength(prelinkSlice),
1032                (long int)toolArgs->maxSliceSize);
1033            goto finish;
1034        }
1035
1036        CFArrayAppendValue(prelinkSlices, prelinkSlice);
1037        CFArrayAppendValue(generatedSymbols, sliceSymbols);
1038        CFArrayAppendValue(generatedArchs, targetArch);
1039    }
1040
1041    result = writeFatFile(toolArgs->prelinkedKernelPath, prelinkSlices,
1042        prelinkArchs, (0644),  // !!! - need macro for perms
1043        (updateModTime) ? prelinkFileTimes : NULL);
1044    if (result != EX_OK) {
1045        goto finish;
1046    }
1047
1048    if (toolArgs->symbolDirURL) {
1049        result = writePrelinkedSymbols(toolArgs->symbolDirURL,
1050            generatedSymbols, generatedArchs);
1051        if (result != EX_OK) {
1052            goto finish;
1053        }
1054    }
1055
1056    OSKextLog(/* kext */ NULL,
1057        kOSKextLogBasicLevel | kOSKextLogGeneralFlag | kOSKextLogArchiveFlag,
1058        "Created prelinked kernel %s.",
1059        toolArgs->prelinkedKernelPath);
1060
1061    result = EX_OK;
1062
1063finish:
1064    SAFE_RELEASE(generatedArchs);
1065    SAFE_RELEASE(generatedSymbols);
1066    SAFE_RELEASE(existingArchs);
1067    SAFE_RELEASE(existingSlices);
1068    SAFE_RELEASE(prelinkArchs);
1069    SAFE_RELEASE(prelinkSlices);
1070    SAFE_RELEASE(prelinkSlice);
1071    SAFE_RELEASE(sliceSymbols);
1072
1073    return result;
1074}
1075
1076/*******************************************************************************
1077*******************************************************************************/
1078ExitStatus createPrelinkedKernelForArch(
1079    KcgenArgs       * toolArgs,
1080    CFDataRef           * prelinkedKernelOut,
1081    CFDictionaryRef     * prelinkedSymbolsOut,
1082    const NXArchInfo    * archInfo)
1083{
1084    ExitStatus result = EX_OSERR;
1085    CFMutableArrayRef prelinkKexts = NULL;
1086    CFDataRef kernelImage = NULL;
1087    CFDataRef prelinkedKernel = NULL;
1088    uint32_t flags = 0;
1089    Boolean fatalOut = false;
1090
1091    /* Retrieve the kernel image for the requested architecture.
1092     */
1093    kernelImage = readMachOSliceForArch(toolArgs->kernelPath, archInfo, /* checkArch */ TRUE);
1094    if (!kernelImage) {
1095        OSKextLog(/* kext */ NULL,
1096                kOSKextLogErrorLevel | kOSKextLogArchiveFlag |  kOSKextLogFileAccessFlag,
1097                "Error - failed to read kernel file.");
1098        goto finish;
1099    }
1100
1101    /* Set the architecture in the OSKext library */
1102
1103    if (!OSKextSetArchitecture(archInfo)) {
1104        OSKextLog(/* kext */ NULL,
1105            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
1106            "Error - can't set architecture %s to create prelinked kernel.",
1107            archInfo->name);
1108        result = EX_OSERR;
1109        goto finish;
1110    }
1111
1112   /*****
1113    * Figure out which kexts we're actually archiving.
1114    * This uses toolArgs->allKexts, which must already be created.
1115    */
1116    prelinkKexts = CFArrayCreateMutable(kCFAllocatorDefault, 0,
1117        &kCFTypeArrayCallBacks);
1118    if (!prelinkKexts) {
1119        OSKextLogMemError();
1120        result = EX_OSERR;
1121        goto finish;
1122    }
1123
1124    result = filterKextsForCache(toolArgs, prelinkKexts,
1125            archInfo, &fatalOut);
1126    if (result != EX_OK || fatalOut) {
1127        goto finish;
1128    }
1129
1130    result = EX_OSERR;
1131
1132    if (!CFArrayGetCount(prelinkKexts)) {
1133        OSKextLog(/* kext */ NULL,
1134            kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
1135            "Error - no kexts found for architecture %s.",
1136            archInfo->name);
1137        goto finish;
1138    }
1139
1140   /* Create the prelinked kernel from the given kernel and kexts */
1141
1142    flags |= kOSKextKernelcacheKASLRFlag;
1143    flags |= kOSKextKernelcacheNeedAllFlag;
1144    flags |= kOSKextKernelcacheSkipAuthenticationFlag;
1145    flags |= kOSKextKernelcacheIncludeAllPersonalitiesFlag;
1146
1147    flags |= (toolArgs->stripSymbols) ? kOSKextKernelcacheStripSymbolsFlag : 0;
1148    flags |= (toolArgs->printTestResults) ? kOSKextKernelcachePrintDiagnosticsFlag : 0;
1149
1150    prelinkedKernel = OSKextCreatePrelinkedKernel(kernelImage, prelinkKexts,
1151        toolArgs->volumeRootURL, flags, prelinkedSymbolsOut);
1152    if (!prelinkedKernel) {
1153        OSKextLog(/* kext */ NULL,
1154            kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
1155            "Error - failed to generate prelinked kernel.");
1156        result = EX_OSERR;
1157        goto finish;
1158    }
1159
1160    /* Log used bundle identifiers for B&I */
1161    logUsedKexts(toolArgs, prelinkKexts);
1162
1163   /* Compress the prelinked kernel if needed */
1164
1165    if (toolArgs->compress) {
1166        *prelinkedKernelOut = compressPrelinkedSlice(prelinkedKernel, true);
1167    } else {
1168        *prelinkedKernelOut = CFRetain(prelinkedKernel);
1169    }
1170
1171    if (!*prelinkedKernelOut) {
1172        goto finish;
1173    }
1174
1175    result = EX_OK;
1176
1177finish:
1178    SAFE_RELEASE(kernelImage);
1179    SAFE_RELEASE(prelinkKexts);
1180    SAFE_RELEASE(prelinkedKernel);
1181
1182    return result;
1183}
1184
1185
1186/*********************************************************************
1187 *********************************************************************/
1188ExitStatus compressPrelinkedKernel(
1189    const char        * prelinkPath,
1190    Boolean             compress)
1191{
1192    ExitStatus          result          = EX_SOFTWARE;
1193    struct timeval      prelinkedKernelTimes[2];
1194    CFMutableArrayRef   prelinkedSlices = NULL; // must release
1195    CFMutableArrayRef   prelinkedArchs  = NULL; // must release
1196    CFDataRef           prelinkedSlice  = NULL; // must release
1197    const NXArchInfo  * archInfo        = NULL; // do not free
1198    const u_char      * sliceBytes      = NULL; // do not free
1199    mode_t              fileMode        = 0;
1200    int                 i               = 0;
1201
1202    result = readMachOSlices(prelinkPath, &prelinkedSlices,
1203        &prelinkedArchs, &fileMode, prelinkedKernelTimes);
1204    if (result != EX_OK) {
1205        goto finish;
1206    }
1207
1208    /* Compress/uncompress each slice of the prelinked kernel.
1209     */
1210
1211    for (i = 0; i < CFArrayGetCount(prelinkedSlices); ++i) {
1212
1213        SAFE_RELEASE_NULL(prelinkedSlice);
1214        prelinkedSlice = CFArrayGetValueAtIndex(prelinkedSlices, i);
1215
1216        if (compress) {
1217            prelinkedSlice = compressPrelinkedSlice(prelinkedSlice, true);
1218            if (!prelinkedSlice) {
1219                result = EX_DATAERR;
1220                goto finish;
1221            }
1222        } else {
1223            prelinkedSlice = uncompressPrelinkedSlice(prelinkedSlice);
1224            if (!prelinkedSlice) {
1225                result = EX_DATAERR;
1226                goto finish;
1227            }
1228        }
1229
1230        CFArraySetValueAtIndex(prelinkedSlices, i, prelinkedSlice);
1231    }
1232    SAFE_RELEASE_NULL(prelinkedSlice);
1233
1234    /* Snow Leopard prelinked kernels are not wrapped in a fat header, so we
1235     * have to decompress the prelinked kernel and look at the mach header
1236     * to get the architecture information.
1237     */
1238
1239    if (!prelinkedArchs && CFArrayGetCount(prelinkedSlices) == 1) {
1240        if (!createCFMutableArray(&prelinkedArchs, NULL)) {
1241            OSKextLogMemError();
1242            result = EX_OSERR;
1243            goto finish;
1244        }
1245
1246        sliceBytes = CFDataGetBytePtr(CFArrayGetValueAtIndex(prelinkedSlices, 0));
1247
1248        archInfo = getThinHeaderPageArch(sliceBytes);
1249        if (archInfo) {
1250            CFArrayAppendValue(prelinkedArchs, archInfo);
1251        } else {
1252            SAFE_RELEASE_NULL(prelinkedArchs);
1253        }
1254    }
1255
1256    /* If we still don't have architecture information, then something
1257     * definitely went wrong.
1258     */
1259
1260    if (!prelinkedArchs) {
1261        OSKextLog(/* kext */ NULL,
1262            kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
1263            "Couldn't determine prelinked kernel's architecture");
1264        result = EX_SOFTWARE;
1265        goto finish;
1266    }
1267
1268    result = writeFatFile(prelinkPath, prelinkedSlices,
1269        prelinkedArchs, fileMode, prelinkedKernelTimes);
1270    if (result != EX_OK) {
1271        goto finish;
1272    }
1273
1274    result = EX_OK;
1275
1276finish:
1277    SAFE_RELEASE(prelinkedSlices);
1278    SAFE_RELEASE(prelinkedArchs);
1279    SAFE_RELEASE(prelinkedSlice);
1280
1281    return result;
1282}
1283
1284
1285/*******************************************************************************
1286* usage()
1287*******************************************************************************/
1288void usage(UsageLevel usageLevel)
1289{
1290    fprintf(stderr,
1291      "usage: %1$s -prelinked-kernel <filename> [options] [--] [kext or directory]\n"
1292      "\n",
1293      progname);
1294
1295    if (usageLevel == kUsageLevelBrief) {
1296        fprintf(stderr, "use %s -%s for an explanation of each option\n",
1297            progname, kOptNameHelp);
1298    }
1299
1300    if (usageLevel == kUsageLevelBrief) {
1301        return;
1302    }
1303
1304    fprintf(stderr, "-%s <filename> (-%c):\n"
1305        "        create/update prelinked kernel\n",
1306        kOptNamePrelinkedKernel, kOptPrelinkedKernel);
1307
1308    fprintf(stderr, "\n");
1309
1310    fprintf(stderr,
1311        "kext or directory: Consider kext or all kexts in directory for inclusion\n");
1312    fprintf(stderr, "-%s <bundle_id> (-%c):\n"
1313        "        include the kext whose CFBundleIdentifier is <bundle_id>\n",
1314        kOptNameBundleIdentifier, kOptBundleIdentifier);
1315    fprintf(stderr, "-%s <bundle_id>:\n"
1316        "        include the kext whose CFBundleIdentifier is <bundle_id> if it exists\n",
1317        kOptNameOptionalBundleIdentifier);
1318    fprintf(stderr, "-%s <kernel_filename> (-%c): Use kernel_filename for a prelinked kernel\n",
1319        kOptNameKernel, kOptKernel);
1320
1321    fprintf(stderr, "-%s <archname>:\n"
1322        "        include architecture <archname> in created cache(s)\n",
1323        kOptNameArch);
1324    fprintf(stderr, "-%s <volume>:\n"
1325        "        Save kext paths in the prelinked kernel relative to <volume>\n",
1326        kOptNameVolumeRoot);
1327    fprintf(stderr, "\n");
1328
1329    fprintf(stderr, "-%s (-%c): quiet mode: print no informational or error messages\n",
1330        kOptNameQuiet, kOptQuiet);
1331    fprintf(stderr, "-%s [ 0-6 | 0x<flags> ] (-%c):\n"
1332        "        verbose mode; print info about analysis & loading\n",
1333        kOptNameVerbose, kOptVerbose);
1334    fprintf(stderr, "\n");
1335
1336    fprintf(stderr, "-%s (-%c):\n"
1337        "        print diagnostics for kexts with problems\n",
1338        kOptNameTests, kOptTests);
1339
1340    fprintf(stderr, "-%s (-%c): print this message and exit\n",
1341        kOptNameHelp, kOptHelp);
1342
1343    return;
1344}
1345