/* * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include "kextlibs_main.h" #include "kext_tools_util.h" /******************************************************************************* * Local function prototypes and misc grotty bits. *******************************************************************************/ const char * progname = "(unknown)"; /******************************************************************************* *******************************************************************************/ int main(int argc, char * const * argv) { ExitStatus result = EX_OSERR; ExitStatus printResult = EX_OSERR; KextlibsArgs toolArgs; CFArrayRef kexts = NULL; // must release const NXArchInfo ** arches = NULL; // must free KextlibsInfo * libInfo = NULL; // must release contents & free Boolean libsAreArchSpecific = FALSE; CFIndex count, i; CFIndex numArches; /***** * Find out what the program was invoked as. */ progname = rindex(argv[0], '/'); if (progname) { progname++; // go past the '/' } else { progname = (char *)argv[0]; } /* Set the OSKext log callback right away. */ OSKextSetLogOutputFunction(&tool_log); result = readArgs(argc, argv, &toolArgs); if (result != EX_OK) { if (result == kKextlibsExitHelp) { result = EX_OK; } goto finish; } result = EX_OSERR; /***** * If necessary or requested, add the extensions folders to the * BEGINNING of the list of folders. */ count = CFArrayGetCount(toolArgs.repositoryURLs); if (!count || toolArgs.flagSysKexts) { CFArrayRef osExtFolders = OSKextGetSystemExtensionsFolderURLs(); // do not release if (!osExtFolders) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Library error - can't get system extensions folders."); goto finish; } count = CFArrayGetCount(osExtFolders); for (i = 0; i < count; i++) { CFTypeRef osExtFolder = CFArrayGetValueAtIndex(osExtFolders, i); CFIndex folderIndex = CFArrayGetFirstIndexOfValue( toolArgs.repositoryURLs, RANGE_ALL(toolArgs.repositoryURLs), osExtFolder); if (folderIndex == kCFNotFound) { CFArrayInsertValueAtIndex(toolArgs.repositoryURLs, i, osExtFolder); } } } kexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault, toolArgs.repositoryURLs); if (!kexts) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't read kexts from folders."); goto finish; } // see comment below about clang not understanding exit() :P toolArgs.kextURL = CFURLCreateFromFileSystemRepresentation( kCFAllocatorDefault, (u_char *)toolArgs.kextName, strlen(toolArgs.kextName), /* isDirectory */ true); if (!toolArgs.kextURL) { OSKextLogStringError(/* kext */ NULL); goto finish; } toolArgs.theKext = OSKextCreate(kCFAllocatorDefault,toolArgs. kextURL); if (!toolArgs.theKext) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't open %s.", toolArgs.kextName); goto finish; } /* A codeless kext is either a library redirect, * so we can't advise, or it doesn't need any libraries! */ if (!OSKextDeclaresExecutable(toolArgs.theKext)) { if (OSKextIsLibrary(toolArgs.theKext)) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "%s is a library without an executable; " "defining its OSBundleLibraries (if any) is up to you.", toolArgs.kextName); } else { CFDictionaryRef libs = OSKextGetValueForInfoDictionaryKey(toolArgs.theKext, CFSTR(kOSBundleLibrariesKey)); if (libs && CFDictionaryGetTypeID() == CFGetTypeID(libs) && CFDictionaryGetCount(libs)) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "%s has no executable and should not declare OSBundleLibraries.", toolArgs.kextName); } else { /* In this one case, the exit status will be EX_OK. */ OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "%s has no executable and does not need to declare OSBundleLibraries.", toolArgs.kextName); result = EX_OK; } } goto finish; } arches = OSKextCopyArchitectures(toolArgs.theKext); if (!arches || !arches[0]) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't determine architectures of %s.", toolArgs.kextName); goto finish; } result = EX_OK; for (numArches = 0; arches[numArches]; numArches++) { /* just counting */ } libInfo = (KextlibsInfo *)malloc(numArches * sizeof(KextlibsInfo)); if (!libInfo) { OSKextLogMemError(); goto finish; } /* Find libraries for the kext for each architecture in the kext. */ for (i = 0; i < numArches; i++) { OSKextSetArchitecture(arches[i]); libInfo[i].libKexts = OSKextFindLinkDependencies(toolArgs.theKext, toolArgs.flagNonKPI, toolArgs.flagAllowUnsupported, &libInfo[i].undefSymbols, &libInfo[i].onedefSymbols, &libInfo[i].multdefSymbols, &libInfo[i].multdefLibs); if (!libInfo[i].libKexts) { OSKextLogMemError(); result = EX_OSERR; goto finish; } } /* If there's more than 1 arch, see if we have to print arch-specific * results. */ if (numArches >= 2) { for (i = 0; i < numArches - 1; i++) { if (!CFEqual(libInfo[i].libKexts, libInfo[i+1].libKexts)) { libsAreArchSpecific = TRUE; break; } } } /* If all the libs are the same for all arches, then just print them * once at the top of the output. Otherwise, only when doing XML, * print the arch-specific XML declarations before the diagnostics. */ if (!libsAreArchSpecific) { printResult = printLibs(&toolArgs, NULL, libInfo[0].libKexts, /* extraNewline? */ TRUE); /* Higher exit statuses always win. */ if (printResult > result) { result = printResult; } } else if (toolArgs.flagXML) { for (i = 0; i < numArches; i++) { printResult = printLibs(&toolArgs, arches[i], libInfo[i].libKexts, /* extraNewline? */ i + 1 == numArches); if (printResult > result) { result = printResult; } } } /* Down here, for each arch, print arch-specific non-XML library declarations, * followed by any problems found for that arch. */ for (i = 0; i < numArches; i++) { if (libsAreArchSpecific && !toolArgs.flagXML) { printResult = printLibs(&toolArgs, arches[i], libInfo[i].libKexts, /* extraNewline? */ i + 1 == numArches); if (printResult > result) { result = printResult; } } printResult = printProblems(&toolArgs, arches[i], libInfo[i].undefSymbols, libInfo[i].onedefSymbols, libInfo[i].multdefSymbols, libInfo[i].multdefLibs, /* printArchFlag */ !libsAreArchSpecific || toolArgs.flagXML, /* extraNewline? */ i + i < numArches); if (printResult != EX_OK) { /* Higher exit statuses always win. */ if (printResult > result) { result = printResult; } } } finish: /* We're done so we just exit without cleaning up. clang's analyzer is now smart enough to know that exit() never returns but not smart enough to know that it frees all resources. */ exit(result); SAFE_FREE(arches); SAFE_RELEASE(toolArgs.repositoryURLs); SAFE_RELEASE(toolArgs.kextURL); SAFE_RELEASE(toolArgs.theKext); SAFE_RELEASE(kexts); if (libInfo) { for (i = 0; numArches; i++) { SAFE_RELEASE_NULL(libInfo[i].libKexts); SAFE_RELEASE_NULL(libInfo[i].undefSymbols); SAFE_RELEASE_NULL(libInfo[i].onedefSymbols); SAFE_RELEASE_NULL(libInfo[i].multdefSymbols); SAFE_RELEASE_NULL(libInfo[i].multdefLibs); } free(libInfo); } return result; } /******************************************************************************* *******************************************************************************/ ExitStatus readArgs( int argc, char * const * argv, KextlibsArgs * toolArgs) { ExitStatus result = EX_USAGE; ExitStatus scratchResult = EX_USAGE; int optChar = 0; bzero(toolArgs, sizeof(*toolArgs)); /***** * Allocate collection objects needed for command line argument processing. */ toolArgs->repositoryURLs = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!toolArgs->repositoryURLs) { OSKextLogMemError(); result = EX_OSERR; goto finish; } /***** * Process command-line arguments. */ result = EX_USAGE; while ((optChar = getopt_long_only(argc, argv, kOptChars, sOptInfo, NULL)) != -1) { switch (optChar) { case kOptHelp: usage(kUsageLevelFull); result = kKextlibsExitHelp; goto finish; break; case kOptRepository: scratchResult = addRepository(toolArgs, optarg); if (scratchResult != EX_OK) { result = scratchResult; goto finish; } break; case kOptCompatible: toolArgs->flagCompatible = true; break; case kOptSystemExtensions: toolArgs->flagSysKexts = true; break; case kOptQuiet: beQuiet(); break; case kOptVerbose: scratchResult = setLogFilterForOpt(argc, argv, /* forceOnFlags */ 0); if (scratchResult != EX_OK) { result = scratchResult; goto finish; } break; case 0: switch (longopt) { case kLongOptXML: toolArgs->flagXML = true; break; case kLongOptAllSymbols: toolArgs->flagPrintUndefSymbols = true; toolArgs->flagPrintOnedefSymbols = true; toolArgs->flagPrintMultdefSymbols = true; break; case kLongOptUndefSymbols: toolArgs->flagPrintUndefSymbols = true; break; case kLongOptOnedefSymbols: toolArgs->flagPrintOnedefSymbols = true; break; case kLongOptMultdefSymbols: toolArgs->flagPrintMultdefSymbols = true; break; case kLongOptNonKPI: toolArgs->flagNonKPI = true; break; case kLongOptUnsupported: toolArgs->flagAllowUnsupported = true; break; } break; default: usage(kUsageLevelBrief); goto finish; break; } } argc -= optind; argv += optind; if (!argv[0]) { fprintf(stderr, "No kext specified."); usage(kUsageLevelBrief); goto finish; } scratchResult = checkPath(argv[0], kOSKextBundleExtension, /* directoryRequired */ TRUE, /* writableRequired */ FALSE); if (scratchResult != EX_OK) { result = scratchResult; goto finish; } toolArgs->kextName = argv[0]; argc--; argv++; if (argc) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Too many arguments starting at '%s'.", argv[0]); usage(kUsageLevelBrief); goto finish; } result = EX_OK; finish: return result; } /******************************************************************************* *******************************************************************************/ ExitStatus addRepository( KextlibsArgs * toolArgs, const char * path) { ExitStatus result = EX_OSERR; CFURLRef url = NULL; // must release result = checkPath(path, /* suffix */ NULL, /* dirRequired? */ TRUE, /* writableRequired? */ FALSE); if (result != EX_OK) { goto finish; } url = CFURLCreateFromFileSystemRepresentation( kCFAllocatorDefault, (const UInt8 *)path, strlen(path), true); if (!url) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't create CFURL for '%s'.", path); goto finish; } addToArrayIfAbsent(toolArgs->repositoryURLs, url); result = EX_OK; finish: SAFE_RELEASE(url); return result; } /******************************************************************************* * This function prints to stdout, as it represents the only real output of * the program and we want to be able to pipe it into pbcopy. *******************************************************************************/ ExitStatus printLibs( KextlibsArgs * toolArgs, const NXArchInfo * arch, CFArrayRef libKexts, Boolean trailingNewlineFlag) { ExitStatus result = EX_OSERR; const NXArchInfo * genericArch = NULL; // do not free char * libIdentifier = NULL; // must free CFIndex count, i; /* Get the generic architecture name for the arch, except for PPC, * which we no longer support. */ if (arch && arch->cputype != CPU_TYPE_POWERPC) { genericArch = NXGetArchInfoFromCpuType(arch->cputype, CPU_SUBTYPE_MULTIPLE); if (!genericArch) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't find generic NXArchInfo for %s.", arch->name); goto finish; } } count = CFArrayGetCount(libKexts); /* For XML output, don't print anything if we found no libraries; * an empty OSBundleLibraries might look like good output to somebody. */ if (toolArgs->flagXML) { if (count) { fprintf(stdout, "\tOSBundleLibraries%s%s\n", genericArch ? "_" : "", genericArch ? genericArch->name : ""); fprintf(stdout, "\t\n"); } } else { if (arch) { fprintf(stderr, "For %s:\n", arch->name); } else { fprintf(stderr, "For all architectures:\n"); } if (!count) { fprintf(stdout, " No libraries found.\n"); } } for (i = 0; i < count; i++) { OSKextRef libKext = (OSKextRef)CFArrayGetValueAtIndex(libKexts, i); OSKextVersion version; char versCString[kOSKextVersionMaxLength]; SAFE_FREE_NULL(libIdentifier); libIdentifier = createUTF8CStringForCFString(OSKextGetIdentifier(libKext)); if (toolArgs->flagCompatible) { version = OSKextGetCompatibleVersion(libKext); } else { version = OSKextGetVersion(libKext); } if (libIdentifier && version > kOSKextVersionUndefined) { OSKextVersionGetString(version, versCString, sizeof(versCString)); if (toolArgs->flagXML) { fprintf(stdout, "\t\t%s\n", libIdentifier); fprintf(stdout, "\t\t%s\n", versCString); } else { fprintf(stdout, " %s = %s\n", libIdentifier, versCString); } } else { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Internal error generating library list."); goto finish; } } if (count && toolArgs->flagXML) { fprintf(stdout, "\t\n"); } if (trailingNewlineFlag) { fprintf(stderr, "\n"); } result = kKextlibsExitOK; finish: SAFE_FREE(libIdentifier); return result; } /******************************************************************************* *******************************************************************************/ ExitStatus printProblems( KextlibsArgs * toolArgs, const NXArchInfo * arch, CFDictionaryRef undefSymbols, CFDictionaryRef onedefSymbols, CFDictionaryRef multdefSymbols, CFArrayRef multdefLibs, Boolean printArchFlag, Boolean trailingNewlineFlag) { ExitStatus result = EX_OSERR; CFIndex undefCount, onedefCount, multdefCount; CFIndex count, i; onedefCount = CFDictionaryGetCount(onedefSymbols); undefCount = CFDictionaryGetCount(undefSymbols); multdefCount = CFDictionaryGetCount(multdefSymbols); if (!toolArgs->flagPrintOnedefSymbols && !undefCount && !multdefCount) { result = kKextlibsExitOK; goto finish; } if (printArchFlag) { fprintf(stderr, "For %s:\n", arch->name); } if (toolArgs->flagPrintOnedefSymbols) { fprintf(stderr, " %ld symbol%s found in one library kext each%s\n", onedefCount, onedefCount > 1 ? "s" : "", onedefCount && toolArgs->flagPrintOnedefSymbols ? ":" : "."); CFDictionaryApplyFunction(onedefSymbols, printOnedefSymbol, &(toolArgs->flagCompatible)); } if (undefCount) { fprintf(stderr, " %ld symbol%s not found in any library kext%s\n", undefCount, undefCount > 1 ? "s" : "", toolArgs->flagPrintUndefSymbols ? ":" : "."); if (toolArgs->flagPrintUndefSymbols) { CFDictionaryApplyFunction(undefSymbols, printUndefSymbol, NULL); } result = kKextlibsExitUndefineds; } if (multdefCount) { if (toolArgs->flagPrintMultdefSymbols) { fprintf(stderr, " %ld symbol%s found in more than one library kext:\n", multdefCount, multdefCount > 1 ? "s" : ""); CFDictionaryApplyFunction(multdefSymbols, printMultdefSymbol, &(toolArgs->flagCompatible)); } else { count = CFArrayGetCount(multdefLibs); fprintf(stderr, " Multiple symbols found among %ld libraries:\n", count); for (i = 0; i < count; i++) { OSKextRef lib = (OSKextRef)CFArrayGetValueAtIndex(multdefLibs, i); char * name = NULL; // must free name = createUTF8CStringForCFString(OSKextGetIdentifier(lib)); if (name) { fprintf(stderr, "\t%s\n", name); } SAFE_FREE(name); } } result = kKextlibsExitMultiples; } if (trailingNewlineFlag) { fprintf(stderr, "\n"); } if (undefCount || multdefCount) { goto finish; } result = kKextlibsExitOK; finish: return result; } /******************************************************************************* * *******************************************************************************/ void printUndefSymbol(const void * key, const void * value __unused, void * context __unused) { char * cSymbol = NULL; // must free cSymbol = createUTF8CStringForCFString((CFStringRef)key); if (cSymbol) { fprintf(stderr, "\t%s\n", cSymbol); } SAFE_FREE(cSymbol); return; } /******************************************************************************* * *******************************************************************************/ void printOnedefSymbol(const void * key, const void * value, void * context) { Boolean flagCompatible = *(Boolean *)context; OSKextRef libKext = (OSKextRef)value; char * cSymbol = NULL; // must free char * libVers = NULL; // must free CFURLRef libKextURL; char libKextName[PATH_MAX]; CFStringRef libVersString = NULL; // do not release cSymbol = createUTF8CStringForCFString((CFStringRef)key); if (!cSymbol) { OSKextLogStringError(/* kext */ NULL); goto finish; } libKextURL = OSKextGetURL(libKext); if (!CFURLGetFileSystemRepresentation(libKextURL, /* resolveToBase */ false, (u_char *)libKextName, PATH_MAX)) { OSKextLogStringError(/* kext */ NULL); goto finish; } if (flagCompatible) { libVersString = OSKextGetValueForInfoDictionaryKey(libKext, CFSTR("OSBundleCompatibleVersion")); } else { libVersString = OSKextGetValueForInfoDictionaryKey(libKext, kCFBundleVersionKey); } libVers = createUTF8CStringForCFString(libVersString); if (!libVers) { OSKextLogStringError(/* kext */ NULL); goto finish; } fprintf(stderr, " %s in %s (%s%s)\n", cSymbol, libKextName, flagCompatible ? "compatible version " : "", libVers); finish: SAFE_FREE(cSymbol); SAFE_FREE(libVers); return; } /******************************************************************************* * *******************************************************************************/ void printMultdefSymbol(const void * key, const void * value, void * context) { char * cSymbol = NULL; // must free char * libVers = NULL; // must free CFArrayRef libs = (CFArrayRef)value; Boolean flagCompatible = *(Boolean *)context; CFIndex count, i; cSymbol = createUTF8CStringForCFString((CFStringRef)key); if (cSymbol) { fprintf(stderr, " %s: in\n", cSymbol); } count = CFArrayGetCount(libs); for (i = 0; i < count; i++) { OSKextRef libKext = (OSKextRef)CFArrayGetValueAtIndex(libs, i); CFURLRef libKextURL; char libKextName[PATH_MAX]; CFStringRef libVersString = NULL; // do not release SAFE_FREE_NULL(libVers); libKextURL = OSKextGetURL(libKext); if (!CFURLGetFileSystemRepresentation(libKextURL, /* resolveToBase */ true, (u_char *)libKextName, PATH_MAX)) { fprintf(stderr, "string/url conversion error\n"); goto finish; } if (flagCompatible) { libVersString = OSKextGetValueForInfoDictionaryKey(libKext, CFSTR("OSBundleCompatibleVersion")); } else { libVersString = OSKextGetValueForInfoDictionaryKey(libKext, kCFBundleVersionKey); } libVers = createUTF8CStringForCFString(libVersString); if (!libVers) { fprintf(stderr, "string/url conversion error\n"); goto finish; } fprintf(stderr, " %s (%s%s)\n", libKextName, flagCompatible ? "compatible version " : "", libVers); } finish: SAFE_FREE(cSymbol); SAFE_FREE(libVers); return; } /******************************************************************************* * usage() *******************************************************************************/ static void usage(UsageLevel usageLevel) { fprintf(stderr, "usage: %s [options] kext\n", progname); if (usageLevel == kUsageLevelBrief) { fprintf(stderr, "\nuse %s -%s for a list of options\n", progname, kOptNameHelp); return; } fprintf(stderr, "\n"); // extra newline for spacing fprintf(stderr, ": the kext to find libraries for\n"); fprintf(stderr, "-%s :\n", kOptNameArch); fprintf(stderr, " resolve for architecture instead of running kernel's\n"); fprintf(stderr, "-%s: print XML fragment suitable for pasting\n", kOptNameXML); // fake out compiler for blank line fprintf(stderr, "\n"); fprintf(stderr, "-%s (-%c):\n", kOptNameSystemExtensions, kOptSystemExtensions); fprintf(stderr, " look in the system exensions folder (assumed if no other folders\n" " specified with %s)\n",kOptNameRepository); fprintf(stderr, "-%s (-%c):\n", kOptNameRepository, kOptRepository); fprintf(stderr, " look in for library kexts\n"); fprintf(stderr, "%s", "\n"); fprintf(stderr, "-%s:\n", kOptNameAllSymbols); fprintf(stderr, " list all symbols, found, not found, or found more than once\n"); fprintf(stderr, "-%s:\n", kOptNameOnedefSymbols); fprintf(stderr, " list all symbols found with the library kext they were found in\n"); fprintf(stderr, "-%s:\n", kOptNameUndefSymbols); fprintf(stderr, " list all symbols not found in any library\n"); fprintf(stderr, "-%s:\n", kOptNameMultdefSymbols); fprintf(stderr, " list all symbols found more than once with their library kexts\n"); fprintf(stderr, "%s", "\n"); fprintf(stderr, "-%s (-%c):\n", kOptNameCompatible, kOptCompatible); fprintf(stderr, " use library kext compatble versions rather than current versions\n"); fprintf(stderr, "-%s:\n", kOptNameUnsupported); fprintf(stderr, " look in unsupported kexts for symbols\n"); return; }