1/*
2 * Copyright (c) 2006 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
26#include <IOKit/kext/OSKext.h>
27#include <IOKit/kext/OSKextPrivate.h>
28
29#include "kextfind_main.h"
30#include "kextfind_tables.h"
31#include "kextfind_query.h"
32#include "kextfind_commands.h"
33#include "kextfind_report.h"
34#include "QEQuery.h"
35
36/*******************************************************************************
37* Misc. macros.
38*******************************************************************************/
39
40#define kKextSuffix            ".kext"
41
42/*******************************************************************************
43* Global variables (non-static referenced by utility.c).
44*******************************************************************************/
45
46const char * progname = "(unknown)";
47
48#pragma mark Main Routine
49/*******************************************************************************
50* Global variables.
51*******************************************************************************/
52int main(int argc, char * const *argv)
53{
54    int result = EX_OSERR;
55
56    CFIndex count, i;
57
58    QEQueryRef          query            = NULL;
59    struct querySetup * queryCallback    = queryCallbackList;
60
61    QEQueryRef          reportQuery      = NULL;
62    struct querySetup * reportCallback   = reportCallbackList;
63    uint32_t            reportStartIndex;
64
65    QueryContext        queryContext;
66    Boolean             queryStarted     = false;
67    uint32_t            numArgsUsed      = 0;
68
69    OSKextRef           theKext          = NULL;  // don't release
70    CFArrayRef          allKexts         = NULL;  // must release
71
72    bzero(&queryContext, sizeof(queryContext));
73
74   /*****
75    * Find out what the program was invoked as.
76    */
77    progname = rindex(argv[0], '/');
78    if (progname) {
79        progname++;   // go past the '/'
80    } else {
81        progname = (char *)argv[0];
82    }
83
84   /* Set the OSKext log callback right away.
85    */
86    OSKextSetLogOutputFunction(&tool_log);
87
88    result = readArgs(argc, argv, &queryContext);
89    if (result != EX_OK) {
90        if (result == kKextfindExitHelp) {
91            result = EX_OK;
92        }
93        goto finish;
94    }
95
96    result = checkArgs(&queryContext);
97    if (result != EX_OK) {
98        goto finish;
99    }
100
101    if (queryContext.defaultArch) {
102        OSKextSetArchitecture(queryContext.defaultArch);
103    }
104
105   /*****
106    * Set up the query.
107    */
108    query = QEQueryCreate(&queryContext);
109    if (!query) {
110        OSKextLog(/* kext */ NULL,
111            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
112            "Can't create query");
113        goto finish;
114    }
115
116    while (queryCallback->longName) {
117        if (queryCallback->parseCallback) {
118            QEQuerySetParseCallbackForPredicate(query, queryCallback->longName,
119                queryCallback->parseCallback);
120            if (queryCallback->shortName) {
121                QEQuerySetSynonymForPredicate(query, queryCallback->shortName,
122                    queryCallback->longName);
123            }
124        }
125        if (queryCallback->evalCallback) {
126            QEQuerySetEvaluationCallbackForPredicate(query,
127                queryCallback->longName,
128                queryCallback->evalCallback);
129        }
130        queryCallback++;
131    }
132    QEQuerySetSynonymForPredicate(query, CFSTR("!"), CFSTR(kQEQueryTokenNot));
133
134    numArgsUsed = optind;
135
136   /* If we're not immediately doing a report spec, parse the query.
137    */
138    if (argv[numArgsUsed] && strcmp(argv[numArgsUsed], kKeywordReport)) {
139        while (QEQueryAppendElementFromArgs(query, argc - numArgsUsed,
140            &argv[numArgsUsed], &numArgsUsed)) {
141
142            queryStarted = true;
143            if (argv[numArgsUsed] && !strcmp(argv[numArgsUsed], kKeywordReport)) {
144                break;
145            }
146        }
147
148    }
149
150    if (QEQueryLastError(query) != kQEQueryErrorNone) {
151        switch (QEQueryLastError(query)) {
152          case kQEQueryErrorNoMemory:
153            OSKextLogMemError();
154            break;
155          case kQEQueryErrorEmptyGroup:
156            OSKextLog(/* kext */ NULL,
157                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
158                "Empty group near arg #%d.", numArgsUsed);
159            break;
160          case kQEQueryErrorSyntax:
161            OSKextLog(/* kext */ NULL,
162                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
163                "Query syntax error near '%s' (arg #%d).",
164                argv[numArgsUsed], numArgsUsed);
165            break;
166          case kQEQueryErrorNoParseCallback:
167            if (queryStarted) {
168                OSKextLog(/* kext */ NULL,
169                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
170                    "Expected query predicate, found '%s' (arg #%d).",
171                    argv[numArgsUsed], numArgsUsed);
172            } else {
173                OSKextLog(/* kext */ NULL,
174                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
175                    "Unknown option/query predicate '%s' (arg #%d).",
176                    argv[numArgsUsed], numArgsUsed);
177                usage(kUsageLevelBrief);
178            }
179            break;
180          case kQEQueryErrorInvalidOrMissingArgument:
181            OSKextLog(/* kext */ NULL,
182                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
183                "Invalid/missing option or argument for '%s' (arg #%d).",
184                argv[numArgsUsed], numArgsUsed);
185            break;
186          case kQEQueryErrorParseCallbackFailed:
187            OSKextLog(/* kext */ NULL,
188                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
189                "Query parsing callback failed.");
190            break;
191          default:
192            break;
193        }
194        goto finish;
195    }
196
197    if (!QEQueryIsComplete(query)) {
198        OSKextLog(/* kext */ NULL,
199            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
200            "Unbalanced groups or trailing operator.");
201        goto finish;
202    }
203
204   /****************************************
205    */
206    if (argv[numArgsUsed] && !strcmp(argv[numArgsUsed], kKeywordReport)) {
207
208        numArgsUsed++;
209
210        if (argv[numArgsUsed] && !strcmp(argv[numArgsUsed], kNoReportHeader)) {
211            numArgsUsed++;
212            queryContext.reportStarted = true; // cause header to be skipped
213        }
214
215        if (queryContext.commandSpecified) {
216            OSKextLog(/* kext */ NULL,
217                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
218                "Can't do report; query has commands.");
219            goto finish;
220        }
221
222        reportQuery = QEQueryCreate(&queryContext);
223        if (!reportQuery) {
224            OSKextLog(/* kext */ NULL,
225                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
226                "Can't create report engine.");
227            goto finish;
228        }
229        QEQuerySetShortCircuits(reportQuery, false);
230
231        while (reportCallback->longName) {
232            if (reportCallback->parseCallback) {
233                QEQuerySetParseCallbackForPredicate(reportQuery,
234                    reportCallback->longName,
235                    reportCallback->parseCallback);
236                if (reportCallback->shortName) {
237                    QEQuerySetSynonymForPredicate(reportQuery,
238                        reportCallback->shortName,
239                        reportCallback->longName);
240                }
241            }
242            if (reportCallback->evalCallback) {
243                QEQuerySetEvaluationCallbackForPredicate(reportQuery,
244                    reportCallback->longName,
245                    reportCallback->evalCallback);
246            }
247            reportCallback++;
248        }
249
250        reportStartIndex = numArgsUsed;
251
252        while (QEQueryAppendElementFromArgs(reportQuery, argc - numArgsUsed,
253            &argv[numArgsUsed], &numArgsUsed)) {
254        }
255
256        if (reportStartIndex == numArgsUsed) {
257            OSKextLog(/* kext */ NULL,
258                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
259                "No report predicates specified.");
260            usage(kUsageLevelBrief);
261            goto finish;
262        }
263
264        if (QEQueryLastError(reportQuery) != kQEQueryErrorNone) {
265            switch (QEQueryLastError(reportQuery)) {
266              case kQEQueryErrorNoMemory:
267                OSKextLogMemError();
268                break;
269              case kQEQueryErrorEmptyGroup:
270                OSKextLog(/* kext */ NULL,
271                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
272                    "Empty group near arg #%d.", numArgsUsed);
273                break;
274              case kQEQueryErrorSyntax:
275                OSKextLog(/* kext */ NULL,
276                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
277                    "Report syntax error near '%s' (arg #%d).",
278                    argv[numArgsUsed], numArgsUsed);
279                break;
280              case kQEQueryErrorNoParseCallback:
281                if (1) {
282                    OSKextLog(/* kext */ NULL,
283                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
284                    "Expected report predicate, found '%s' (arg #%d).",
285                        argv[numArgsUsed], numArgsUsed);
286                } else {
287                    OSKextLog(/* kext */ NULL,
288                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
289                    "Unknown option/report predicate '%s' (arg #%d).",
290                        argv[numArgsUsed], numArgsUsed);
291                }
292                break;
293              case kQEQueryErrorInvalidOrMissingArgument:
294                OSKextLog(/* kext */ NULL,
295                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
296                    "Invalid/missing option or argument for '%s' (arg #%d).",
297                    argv[numArgsUsed], numArgsUsed);
298                break;
299              case kQEQueryErrorParseCallbackFailed:
300                OSKextLog(/* kext */ NULL,
301                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
302                    "Query parsing callback failed.");
303                break;
304              default:
305                break;
306            }
307            goto finish;
308        }
309
310        if (!QEQueryIsComplete(reportQuery)) {
311            OSKextLog(/* kext */ NULL,
312                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
313                "Unbalanced groups or trailing operator.");
314            goto finish;
315        }
316    }
317
318    if ((int)numArgsUsed < argc) {
319        OSKextLog(/* kext */ NULL,
320            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
321            "Leftover elements '%s'... (arg #%d).",
322            argv[numArgsUsed], numArgsUsed);
323        goto finish;
324    }
325
326   /*****
327    * Create the set of kexts we'll be searching/reporting.
328    */
329    OSKextSetRecordsDiagnostics(kOSKextDiagnosticsFlagAll);
330    OSKextSetUsesCaches(false);
331    allKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
332        queryContext.searchURLs);
333    if (!allKexts || !CFArrayGetCount(allKexts)) {
334        // see comment below about clang not understanding exit() :P
335        OSKextLog(/* kext */ NULL,
336            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
337            "No kernel extensions found.");
338        result = EX_SOFTWARE;
339        goto finish;
340    }
341
342    if (queryContext.checkLoaded) {
343        if (kOSReturnSuccess != OSKextReadLoadedKextInfo(
344            /* kextIdentifiers (all kexts) */ NULL,
345            /* flushDependencies? */ false)) {
346
347            result = EX_OSERR;
348            goto finish;
349        }
350    }
351
352    if (result != EX_OK) {
353        goto finish;
354    }
355
356   /*****
357    * Run the query!
358    */
359    count = CFArrayGetCount(allKexts);
360    for (i = 0; i < count; i++) {
361
362        theKext = (OSKextRef)CFArrayGetValueAtIndex(allKexts, i);
363
364        if (QEQueryEvaluate(query, theKext)) {
365            if (!queryContext.commandSpecified) {
366                if (!reportQuery) {
367                    printKext(theKext, queryContext.pathSpec,
368                        queryContext.extraInfo, '\n');
369                } else {
370                    if (!queryContext.reportStarted) {
371                        queryContext.reportRowStarted = false;
372                        QEQueryEvaluate(reportQuery, theKext);
373                        printf("\n");
374                        if ((QEQueryLastError(reportQuery) != kQEQueryErrorNone)) {
375                            OSKextLog(/* kext */ NULL,
376                                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
377                                "Report evaluation error; aborting.");
378                            goto finish;
379                        }
380                        queryContext.reportStarted = true;
381                    }
382                    queryContext.reportRowStarted = false;
383                    QEQueryEvaluate(reportQuery, theKext);
384                    printf("\n");
385                    if ((QEQueryLastError(reportQuery) != kQEQueryErrorNone)) {
386                        OSKextLog(/* kext */ NULL,
387                            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
388                            "Report evaluation error; aborting.");
389                        goto finish;
390                    }
391                }
392            }
393        } else if (QEQueryLastError(query) != kQEQueryErrorNone) {
394            OSKextLog(/* kext */ NULL,
395                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
396                "Query evaluation error; aborting.");
397            goto finish;
398        }
399    }
400
401    result = EX_OK;
402
403finish:
404    // clang's analyzer now knows exit() never returns but doesn't realize
405    // it frees resources. :P
406    exit(result);  // we don't need to do the cleanup when exiting.
407
408    if (query)                 QEQueryFree(query);
409    if (allKexts)              CFRelease(allKexts);
410
411    exit(result);
412    return result;
413}
414
415#pragma mark Major Subroutines
416/*******************************************************************************
417* Major Subroutines
418*******************************************************************************/
419ExitStatus readArgs(
420    int            argc,
421    char * const * argv,
422    QueryContext * toolArgs)
423{
424    ExitStatus result          = EX_USAGE;
425    int        opt_char        = 0;
426    int        last_optind;  // for recovering from getopt failures
427    Boolean    readingOptions  = true;
428    CFURLRef   scratchURL      = NULL;  // must release
429    uint32_t   i;
430
431    bzero(toolArgs, sizeof(*toolArgs));
432
433   /*****
434    * Allocate collection objects needed for command line argument processing.
435    */
436    if (!createCFMutableArray(&toolArgs->searchURLs, &kCFTypeArrayCallBacks)) {
437        OSKextLogMemError();
438        result = EX_OSERR;
439        goto finish;
440    }
441
442    toolArgs->assertiveness = kKextfindPicky;
443
444   /*****
445    * Process command-line arguments.
446    */
447    opterr = 0;
448    last_optind = optind;
449    while (readingOptions &&
450        -1 != (opt_char = getopt_long_only(argc, argv, kOPT_CHARS,
451        opt_info, NULL))) {
452
453        switch (opt_char) {
454
455            case kOptHelp:
456                usage(kUsageLevelFull);
457                result = kKextfindExitHelp;
458                goto finish;
459                break;
460
461            case kOptCaseInsensitive:
462                toolArgs->caseInsensitive = true;
463                break;
464
465            case kOptSearchItem:
466                if (!checkSearchItem(optarg, /* log? */ true)) {
467                    goto finish;
468                }
469
470                SAFE_RELEASE_NULL(scratchURL);
471                scratchURL = CFURLCreateFromFileSystemRepresentation(
472                    kCFAllocatorDefault,
473                    (const UInt8 *)optarg, strlen(optarg), true);
474                if (!scratchURL) {
475                    result = EX_OSERR;
476                    OSKextLogMemError();
477                    goto finish;
478                }
479                CFArrayAppendValue(toolArgs->searchURLs, scratchURL);
480                break;
481
482            case kOptSubstring:
483                toolArgs->substrings = true;
484                break;
485
486            case kOptSystemExtensions:
487              {
488                    CFArrayRef sysExtFolders =
489                        OSKextGetSystemExtensionsFolderURLs();
490                    CFArrayAppendArray(toolArgs->searchURLs,
491                        sysExtFolders, RANGE_ALL(sysExtFolders));
492                }
493                break;
494
495            case 0:
496                switch (longopt) {
497
498                    case kLongOptQueryPredicate:
499                        optind = last_optind;
500                        readingOptions = false;
501                        if (argv[last_optind] && (argv[last_optind][0] != '-')) {
502                            // probably a directory; stupid getopt_long_only()
503                            break;
504                        }
505                        break;
506
507#ifdef EXTRA_INFO
508                    case kLongOptExtraInfo:
509                        toolArgs->extraInfo = true;
510                        break;
511#endif
512                    case kLongOptRelativePaths:
513                       /* Last one specified wins! */
514                        toolArgs->pathSpec = kPathsRelative;
515                        break;
516
517                    case kLongOptDefaultArch:
518                       /* Last one specified wins! */
519                        toolArgs->defaultArch = NXGetArchInfoFromName(optarg);
520                        if (!toolArgs->defaultArch) {
521                            OSKextLog(/* kext */ NULL,
522                                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
523                                "Unknown architecture %s.", optarg);
524                            goto finish;
525                        }
526                        break;
527
528                    case kLongOptNoPaths:
529                       /* Last one specified wins! */
530                        toolArgs->pathSpec = kPathsNone;
531                        break;
532
533#ifdef MEEK_PICKY
534                    case kLongOptMeek:
535                        toolArgs->assertiveness = kKextfindMeek;
536                        break;
537
538                    case kLongOptPicky:
539                        toolArgs->assertiveness = kKextfindPicky;
540                        break;
541#endif
542
543                    default:
544                        OSKextLog(/* kext */ NULL,
545                            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
546                            "Internal argument processing error.");
547                        result = EX_SOFTWARE;
548                        goto finish;
549                        break;
550
551                }
552                longopt = 0;
553                break;
554
555            default:
556               /* getopt_long() gives us '?' if we turn off errors, so just
557                * move on to query parsing. Sometimes optind jumps ahead too
558                * far, so we restore it to the value before the call to
559                * getopt_long_only() -- we can't just decrement it.
560                */
561                optind = last_optind;
562                readingOptions = false;
563                break;
564        }
565
566        last_optind = optind;
567    }
568
569   /*****
570    * Record the kext & directory names from the command line.
571    */
572    for (i = optind; (int)i < argc; i++) {
573        SAFE_RELEASE_NULL(scratchURL);
574
575       /* If the arg isn't a directory, break from the loop, and we'll
576        * process remaining args as query elements.
577        */
578        if (!checkSearchItem(argv[i], /* log? */ false)) {
579            break;
580        }
581        scratchURL = CFURLCreateFromFileSystemRepresentation(
582            kCFAllocatorDefault,
583            (const UInt8 *)argv[i], strlen(argv[i]), true);
584        if (!scratchURL) {
585            result = EX_OSERR;
586            OSKextLogMemError();
587            goto finish;
588        }
589        CFArrayAppendValue(toolArgs->searchURLs, scratchURL);
590        optind++;
591    }
592
593    if (!CFArrayGetCount(toolArgs->searchURLs)) {
594        CFArrayRef sysExtFolders =
595            OSKextGetSystemExtensionsFolderURLs();
596        CFArrayAppendArray(toolArgs->searchURLs,
597            sysExtFolders, RANGE_ALL(sysExtFolders));
598    }
599
600    result = EX_OK;
601
602finish:
603    SAFE_RELEASE(scratchURL);
604
605    if (result == EX_USAGE) {
606        usage(kUsageLevelBrief);
607    }
608    return result;
609}
610
611/*******************************************************************************
612*******************************************************************************/
613ExitStatus checkArgs(QueryContext * toolArgs __unused)
614{
615    ExitStatus result = EX_USAGE;
616
617    result = EX_OK;
618
619    if (result == EX_USAGE) {
620        usage(kUsageLevelBrief);
621    }
622    return result;
623}
624
625/*******************************************************************************
626* checkSearchItem()
627*
628* This function makes sure that a given directory exists, and is writeable.
629*******************************************************************************/
630Boolean checkSearchItem(const char * pathname, Boolean logFlag)
631{
632    int result = false;
633    struct stat stat_buf;
634
635    if (stat(pathname, &stat_buf) != 0) {
636        if (logFlag) {
637            OSKextLog(/* kext */ NULL,
638                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
639                "Can't stat %s - %s.", pathname, strerror(errno));
640        }
641        goto finish;
642    }
643
644    if ((stat_buf.st_mode & S_IFMT) != S_IFDIR) {
645        if (logFlag) {
646            OSKextLog(/* kext */ NULL,
647                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
648                "%s - not a kext or directory.",
649                pathname);
650        }
651        goto finish;
652    }
653
654    result = true;
655
656finish:
657    return result;
658}
659
660/*******************************************************************************
661*******************************************************************************/
662fat_iterator createFatIteratorForKext(OSKextRef aKext)
663{
664    fat_iterator result        = NULL;
665    CFURLRef     kextURL       = NULL;  // do not release
666    CFURLRef     executableURL = NULL;  // must release
667    char         executablePath[PATH_MAX];
668
669    kextURL = OSKextGetURL(aKext);
670    if (!kextURL) {
671        // xxx - log it?
672        goto finish;
673    }
674    executableURL = _CFBundleCopyExecutableURLInDirectory(kextURL);
675    if (!executableURL) {
676        goto finish;
677    }
678    if (!CFURLGetFileSystemRepresentation(executableURL,
679        /* resolveToBase? */ true, (UInt8 *)executablePath,
680        sizeof(executablePath))) {
681
682        OSKextLogStringError(aKext);
683        goto finish;
684    }
685    result = fat_iterator_open(executablePath, /* macho_only? */ true);
686
687finish:
688    SAFE_RELEASE(executableURL);
689    return result;
690}
691
692/*******************************************************************************
693* usage()
694*******************************************************************************/
695void usage(UsageLevel usageLevel)
696{
697    FILE * stream = stderr;
698
699    fprintf(stream,
700      "usage: %s [options] [directory or extension ...] [query]\n"
701      "    [-report [-no-header] report_predicate...]"
702      "\n",
703      progname);
704
705    if (usageLevel == kUsageLevelBrief) {
706        fprintf(stream, "use %s -%s for a list of options\n",
707            progname, kOptNameHelp);
708        return;
709    }
710
711    fprintf(stream, "Options\n");
712
713    fprintf(stream, "    -%s                        -%s\n",
714        kOptNameHelp, kOptNameCaseInsensitive);
715#ifdef EXTRA_INFO
716    fprintf(stream, "    -%s                  -%s\n",
717        kOptNameExtraInfo, kOptNameNulTerminate);
718#endif
719    fprintf(stream, "    -%s              -%s\n",
720        kOptNameRelativePaths, kOptNameSubstring);
721    fprintf(stream, "    -%s\n",
722        kOptNameNoPaths);
723
724    fprintf(stream, "\n");
725
726    fprintf(stream, "Handy Query Predicates\n");
727
728    fprintf(stream, "    %s [-s] [-i] id\n", kPredNameBundleID);
729    fprintf(stream, "    %s [-s] [-i] id\n", kPredNameBundleName);
730    fprintf(stream, "    %s [-s] [-i] name value\n", kPredNameMatchProperty);
731    fprintf(stream, "    %s [-s] [-i] name value\n", kPredNameProperty);
732
733    fprintf(stream, "\n");
734
735    fprintf(stream, "    %s                      %s\n",
736        kPredNameLoaded, kPredNameNonloadable);
737    fprintf(stream, "    %s                     %s\n",
738        kPredNameInvalid, kPredNameInauthentic);
739    fprintf(stream, "    %s        %s\n",
740        kPredNameDependenciesMissing, kPredNameWarnings);
741
742    fprintf(stream, "\n");
743
744    fprintf(stream, "    %s arch1[,arch2...]       %s arch1[,arch2...]\n",
745        kPredNameArch, kPredNameArchExact);
746    fprintf(stream, "    %s                  %s\n",
747        kPredNameExecutable, kPredNameIsLibrary);
748    fprintf(stream, "    %s symbol       %s symbol\n",
749        kPredNameDefinesSymbol, kPredNameReferencesSymbol);
750
751    fprintf(stream, "\n");
752
753    fprintf(stream, "See the man page for the full list.\n");
754
755    return;
756}
757