#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFBundlePriv.h>
#include <IOKit/kext/OSKext.h>
#include <IOKit/kext/OSKextPrivate.h>
#include "kextfind_main.h"
#include "kextfind_tables.h"
#include "kextfind_query.h"
#include "kextfind_commands.h"
#include "kextfind_report.h"
#include "QEQuery.h"
#define kKextSuffix ".kext"
const char * progname = "(unknown)";
#pragma mark Main Routine
int main(int argc, char * const *argv)
{
int result = EX_OSERR;
CFIndex count, i;
QEQueryRef query = NULL;
struct querySetup * queryCallback = queryCallbackList;
QEQueryRef reportQuery = NULL;
struct querySetup * reportCallback = reportCallbackList;
uint32_t reportStartIndex;
QueryContext queryContext;
Boolean queryStarted = false;
uint32_t numArgsUsed = 0;
OSKextRef theKext = NULL; CFArrayRef allKexts = NULL;
bzero(&queryContext, sizeof(queryContext));
progname = rindex(argv[0], '/');
if (progname) {
progname++; } else {
progname = (char *)argv[0];
}
OSKextSetLogOutputFunction(&tool_log);
result = readArgs(argc, argv, &queryContext);
if (result != EX_OK) {
if (result == kKextfindExitHelp) {
result = EX_OK;
}
goto finish;
}
result = checkArgs(&queryContext);
if (result != EX_OK) {
goto finish;
}
if (queryContext.defaultArch) {
OSKextSetArchitecture(queryContext.defaultArch);
}
query = QEQueryCreate(&queryContext);
if (!query) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't create query");
goto finish;
}
while (queryCallback->longName) {
if (queryCallback->parseCallback) {
QEQuerySetParseCallbackForPredicate(query, queryCallback->longName,
queryCallback->parseCallback);
if (queryCallback->shortName) {
QEQuerySetSynonymForPredicate(query, queryCallback->shortName,
queryCallback->longName);
}
}
if (queryCallback->evalCallback) {
QEQuerySetEvaluationCallbackForPredicate(query,
queryCallback->longName,
queryCallback->evalCallback);
}
queryCallback++;
}
QEQuerySetSynonymForPredicate(query, CFSTR("!"), CFSTR(kQEQueryTokenNot));
numArgsUsed = optind;
if (argv[numArgsUsed] && strcmp(argv[numArgsUsed], kKeywordReport)) {
while (QEQueryAppendElementFromArgs(query, argc - numArgsUsed,
&argv[numArgsUsed], &numArgsUsed)) {
queryStarted = true;
if (argv[numArgsUsed] && !strcmp(argv[numArgsUsed], kKeywordReport)) {
break;
}
}
}
if (QEQueryLastError(query) != kQEQueryErrorNone) {
switch (QEQueryLastError(query)) {
case kQEQueryErrorNoMemory:
OSKextLogMemError();
break;
case kQEQueryErrorEmptyGroup:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Empty group near arg #%d.", numArgsUsed);
break;
case kQEQueryErrorSyntax:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Query syntax error near '%s' (arg #%d).",
argv[numArgsUsed], numArgsUsed);
break;
case kQEQueryErrorNoParseCallback:
if (queryStarted) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Expected query predicate, found '%s' (arg #%d).",
argv[numArgsUsed], numArgsUsed);
} else {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unknown option/query predicate '%s' (arg #%d).",
argv[numArgsUsed], numArgsUsed);
usage(kUsageLevelBrief);
}
break;
case kQEQueryErrorInvalidOrMissingArgument:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Invalid/missing option or argument for '%s' (arg #%d).",
argv[numArgsUsed], numArgsUsed);
break;
case kQEQueryErrorParseCallbackFailed:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Query parsing callback failed.");
break;
default:
break;
}
goto finish;
}
if (!QEQueryIsComplete(query)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unbalanced groups or trailing operator.");
goto finish;
}
if (argv[numArgsUsed] && !strcmp(argv[numArgsUsed], kKeywordReport)) {
numArgsUsed++;
if (argv[numArgsUsed] && !strcmp(argv[numArgsUsed], kNoReportHeader)) {
numArgsUsed++;
queryContext.reportStarted = true; }
if (queryContext.commandSpecified) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't do report; query has commands.");
goto finish;
}
reportQuery = QEQueryCreate(&queryContext);
if (!reportQuery) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't create report engine.");
goto finish;
}
QEQuerySetShortCircuits(reportQuery, false);
while (reportCallback->longName) {
if (reportCallback->parseCallback) {
QEQuerySetParseCallbackForPredicate(reportQuery,
reportCallback->longName,
reportCallback->parseCallback);
if (reportCallback->shortName) {
QEQuerySetSynonymForPredicate(reportQuery,
reportCallback->shortName,
reportCallback->longName);
}
}
if (reportCallback->evalCallback) {
QEQuerySetEvaluationCallbackForPredicate(reportQuery,
reportCallback->longName,
reportCallback->evalCallback);
}
reportCallback++;
}
reportStartIndex = numArgsUsed;
while (QEQueryAppendElementFromArgs(reportQuery, argc - numArgsUsed,
&argv[numArgsUsed], &numArgsUsed)) {
}
if (reportStartIndex == numArgsUsed) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No report predicates specified.");
usage(kUsageLevelBrief);
goto finish;
}
if (QEQueryLastError(reportQuery) != kQEQueryErrorNone) {
switch (QEQueryLastError(reportQuery)) {
case kQEQueryErrorNoMemory:
OSKextLogMemError();
break;
case kQEQueryErrorEmptyGroup:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Empty group near arg #%d.", numArgsUsed);
break;
case kQEQueryErrorSyntax:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Report syntax error near '%s' (arg #%d).",
argv[numArgsUsed], numArgsUsed);
break;
case kQEQueryErrorNoParseCallback:
if (1) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Expected report predicate, found '%s' (arg #%d).",
argv[numArgsUsed], numArgsUsed);
} else {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unknown option/report predicate '%s' (arg #%d).",
argv[numArgsUsed], numArgsUsed);
}
break;
case kQEQueryErrorInvalidOrMissingArgument:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Invalid/missing option or argument for '%s' (arg #%d).",
argv[numArgsUsed], numArgsUsed);
break;
case kQEQueryErrorParseCallbackFailed:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Query parsing callback failed.");
break;
default:
break;
}
goto finish;
}
if (!QEQueryIsComplete(reportQuery)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unbalanced groups or trailing operator.");
goto finish;
}
}
if ((int)numArgsUsed < argc) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Leftover elements '%s'... (arg #%d).",
argv[numArgsUsed], numArgsUsed);
goto finish;
}
OSKextSetRecordsDiagnostics(kOSKextDiagnosticsFlagAll);
OSKextSetUsesCaches(false);
allKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
queryContext.searchURLs);
if (!allKexts || !CFArrayGetCount(allKexts)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No kernel extensions found.");
result = EX_SOFTWARE;
goto finish;
}
if (queryContext.checkLoaded) {
if (kOSReturnSuccess != OSKextReadLoadedKextInfo(
NULL,
false)) {
result = EX_OSERR;
goto finish;
}
}
if (result != EX_OK) {
goto finish;
}
count = CFArrayGetCount(allKexts);
for (i = 0; i < count; i++) {
theKext = (OSKextRef)CFArrayGetValueAtIndex(allKexts, i);
if (QEQueryEvaluate(query, theKext)) {
if (!queryContext.commandSpecified) {
if (!reportQuery) {
printKext(theKext, queryContext.pathSpec,
queryContext.extraInfo, '\n');
} else {
if (!queryContext.reportStarted) {
queryContext.reportRowStarted = false;
QEQueryEvaluate(reportQuery, theKext);
printf("\n");
if ((QEQueryLastError(reportQuery) != kQEQueryErrorNone)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Report evaluation error; aborting.");
goto finish;
}
queryContext.reportStarted = true;
}
queryContext.reportRowStarted = false;
QEQueryEvaluate(reportQuery, theKext);
printf("\n");
if ((QEQueryLastError(reportQuery) != kQEQueryErrorNone)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Report evaluation error; aborting.");
goto finish;
}
}
}
} else if (QEQueryLastError(query) != kQEQueryErrorNone) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Query evaluation error; aborting.");
goto finish;
}
}
result = EX_OK;
finish:
exit(result);
if (query) QEQueryFree(query);
if (allKexts) CFRelease(allKexts);
exit(result);
return result;
}
#pragma mark Major Subroutines
ExitStatus readArgs(
int argc,
char * const * argv,
QueryContext * toolArgs)
{
ExitStatus result = EX_USAGE;
int opt_char = 0;
int last_optind; Boolean readingOptions = true;
CFURLRef scratchURL = NULL; uint32_t i;
bzero(toolArgs, sizeof(*toolArgs));
if (!createCFMutableArray(&toolArgs->searchURLs, &kCFTypeArrayCallBacks)) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
toolArgs->assertiveness = kKextfindPicky;
opterr = 0;
last_optind = optind;
while (readingOptions &&
-1 != (opt_char = getopt_long_only(argc, argv, kOPT_CHARS,
opt_info, NULL))) {
switch (opt_char) {
case kOptHelp:
usage(kUsageLevelFull);
result = kKextfindExitHelp;
goto finish;
break;
case kOptCaseInsensitive:
toolArgs->caseInsensitive = true;
break;
case kOptSearchItem:
if (!checkSearchItem(optarg, true)) {
goto finish;
}
SAFE_RELEASE_NULL(scratchURL);
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg, strlen(optarg), true);
if (!scratchURL) {
result = EX_OSERR;
OSKextLogMemError();
goto finish;
}
CFArrayAppendValue(toolArgs->searchURLs, scratchURL);
break;
case kOptSubstring:
toolArgs->substrings = true;
break;
case kOptSystemExtensions:
{
CFArrayRef sysExtFolders =
OSKextGetSystemExtensionsFolderURLs();
CFArrayAppendArray(toolArgs->searchURLs,
sysExtFolders, RANGE_ALL(sysExtFolders));
}
break;
case 0:
switch (longopt) {
case kLongOptQueryPredicate:
optind = last_optind;
readingOptions = false;
if (argv[last_optind] && (argv[last_optind][0] != '-')) {
break;
}
break;
#ifdef EXTRA_INFO
case kLongOptExtraInfo:
toolArgs->extraInfo = true;
break;
#endif
case kLongOptRelativePaths:
toolArgs->pathSpec = kPathsRelative;
break;
case kLongOptDefaultArch:
toolArgs->defaultArch = NXGetArchInfoFromName(optarg);
if (!toolArgs->defaultArch) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unknown architecture %s.", optarg);
goto finish;
}
break;
case kLongOptNoPaths:
toolArgs->pathSpec = kPathsNone;
break;
#ifdef MEEK_PICKY
case kLongOptMeek:
toolArgs->assertiveness = kKextfindMeek;
break;
case kLongOptPicky:
toolArgs->assertiveness = kKextfindPicky;
break;
#endif
default:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Internal argument processing error.");
result = EX_SOFTWARE;
goto finish;
break;
}
longopt = 0;
break;
default:
optind = last_optind;
readingOptions = false;
break;
}
last_optind = optind;
}
for (i = optind; (int)i < argc; i++) {
SAFE_RELEASE_NULL(scratchURL);
if (!checkSearchItem(argv[i], false)) {
break;
}
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)argv[i], strlen(argv[i]), true);
if (!scratchURL) {
result = EX_OSERR;
OSKextLogMemError();
goto finish;
}
CFArrayAppendValue(toolArgs->searchURLs, scratchURL);
optind++;
}
if (!CFArrayGetCount(toolArgs->searchURLs)) {
CFArrayRef sysExtFolders =
OSKextGetSystemExtensionsFolderURLs();
CFArrayAppendArray(toolArgs->searchURLs,
sysExtFolders, RANGE_ALL(sysExtFolders));
}
result = EX_OK;
finish:
SAFE_RELEASE(scratchURL);
if (result == EX_USAGE) {
usage(kUsageLevelBrief);
}
return result;
}
ExitStatus checkArgs(QueryContext * toolArgs __unused)
{
ExitStatus result = EX_USAGE;
result = EX_OK;
if (result == EX_USAGE) {
usage(kUsageLevelBrief);
}
return result;
}
Boolean checkSearchItem(const char * pathname, Boolean logFlag)
{
int result = false;
struct stat stat_buf;
if (stat(pathname, &stat_buf) != 0) {
if (logFlag) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't stat %s - %s.", pathname, strerror(errno));
}
goto finish;
}
if ((stat_buf.st_mode & S_IFMT) != S_IFDIR) {
if (logFlag) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"%s - not a kext or directory.",
pathname);
}
goto finish;
}
result = true;
finish:
return result;
}
fat_iterator createFatIteratorForKext(OSKextRef aKext)
{
fat_iterator result = NULL;
CFURLRef kextURL = NULL; CFURLRef executableURL = NULL; char executablePath[PATH_MAX];
kextURL = OSKextGetURL(aKext);
if (!kextURL) {
goto finish;
}
executableURL = _CFBundleCopyExecutableURLInDirectory(kextURL);
if (!executableURL) {
goto finish;
}
if (!CFURLGetFileSystemRepresentation(executableURL,
true, (UInt8 *)executablePath,
sizeof(executablePath))) {
OSKextLogStringError(aKext);
goto finish;
}
result = fat_iterator_open(executablePath, true);
finish:
SAFE_RELEASE(executableURL);
return result;
}
void usage(UsageLevel usageLevel)
{
FILE * stream = stderr;
fprintf(stream,
"usage: %s [options] [directory or extension ...] [query]\n"
" [-report [-no-header] report_predicate...]"
"\n",
progname);
if (usageLevel == kUsageLevelBrief) {
fprintf(stream, "use %s -%s for a list of options\n",
progname, kOptNameHelp);
return;
}
fprintf(stream, "Options\n");
fprintf(stream, " -%s -%s\n",
kOptNameHelp, kOptNameCaseInsensitive);
#ifdef EXTRA_INFO
fprintf(stream, " -%s -%s\n",
kOptNameExtraInfo, kOptNameNulTerminate);
#endif
fprintf(stream, " -%s -%s\n",
kOptNameRelativePaths, kOptNameSubstring);
fprintf(stream, " -%s\n",
kOptNameNoPaths);
fprintf(stream, "\n");
fprintf(stream, "Handy Query Predicates\n");
fprintf(stream, " %s [-s] [-i] id\n", kPredNameBundleID);
fprintf(stream, " %s [-s] [-i] id\n", kPredNameBundleName);
fprintf(stream, " %s [-s] [-i] name value\n", kPredNameMatchProperty);
fprintf(stream, " %s [-s] [-i] name value\n", kPredNameProperty);
fprintf(stream, "\n");
fprintf(stream, " %s %s\n",
kPredNameLoaded, kPredNameNonloadable);
fprintf(stream, " %s %s\n",
kPredNameInvalid, kPredNameInauthentic);
fprintf(stream, " %s %s\n",
kPredNameDependenciesMissing, kPredNameWarnings);
fprintf(stream, "\n");
fprintf(stream, " %s arch1[,arch2...] %s arch1[,arch2...]\n",
kPredNameArch, kPredNameArchExact);
fprintf(stream, " %s %s\n",
kPredNameExecutable, kPredNameIsLibrary);
fprintf(stream, " %s symbol %s symbol\n",
kPredNameDefinesSymbol, kPredNameReferencesSymbol);
fprintf(stream, "\n");
fprintf(stream, "See the man page for the full list.\n");
return;
}