#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFBundlePriv.h>
#include <errno.h>
#include <libc.h>
#include <libgen.h> // dirname()
#include <Kernel/libkern/mkext.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fts.h>
#include <paths.h>
#include <mach-o/arch.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach-o/swap.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <mach/mach_port.h> // mach_port_allocate()
#include <mach/mach_types.h>
#include <mach/machine/vm_param.h>
#include <mach/kmod.h>
#include <notify.h>
#include <servers/bootstrap.h> // bootstrap mach ports
#include <stdlib.h>
#include <unistd.h> // sleep(3)
#include <sys/types.h>
#include <sys/stat.h>
#include <DiskArbitration/DiskArbitrationPrivate.h>
#include <IOKit/IOTypes.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOKitServer.h>
#include <IOKit/IOCFUnserialize.h>
#include <IOKit/IOCFSerialize.h>
#include <libkern/OSByteOrder.h>
#include <IOKit/kext/OSKext.h>
#include <IOKit/kext/OSKextPrivate.h>
#include <IOKit/kext/kextmanager_types.h>
#include <IOKit/kext/kextmanager_mig.h>
#include <bootfiles.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include "kextcache_main.h"
#include "bootcaches.h"
#include "update_boot.h"
#include "mkext1_file.h"
#include "compression.h"
#define MKEXT_PERMS (0644)
#define SYSTEM_LOAD_TIMEOUT ((8 * 60) + 27)
const char * progname = "(unknown)";
static mach_port_t sLockPort = MACH_PORT_NULL;
static uuid_t s_vol_uuid;
static mach_port_t sKextdPort = MACH_PORT_NULL;
static void waitForIOKitQuiescence(void);
static ExitStatus waitForLowSystemLoad(void);
static int takeVolumeForPath(const char *path);
#define kMaxArchs 64
#define kRootPathLen 256
static u_int usecs_from_timeval(struct timeval *t);
static void timeval_from_usecs(struct timeval *t, u_int usecs);
static void timeval_difference(struct timeval *dst,
struct timeval *a, struct timeval *b);
int main(int argc, char * const * argv)
{
ExitStatus result = EX_SOFTWARE;
Boolean fatal = false;
KextcacheArgs toolArgs;
CFDataRef prelinkedKernel = NULL; CFDataRef prelinkedKernelIn = NULL; CFDictionaryRef prelinkedSymbols = NULL;
progname = rindex(argv[0], '/');
if (progname) {
progname++; } else {
progname = (char *)argv[0];
}
OSKextSetLogOutputFunction(&tool_log);
if (getenv("KEXTD_SPAWNED")) {
OSKextSetLogFilter(kDefaultServiceLogFilter | kOSKextLogKextOrGlobalMask,
false);
OSKextSetLogFilter(kDefaultServiceLogFilter | kOSKextLogKextOrGlobalMask,
true);
tool_openlog("com.apple.kextcache");
}
result = readArgs(&argc, &argv, &toolArgs);
if (result != EX_OK) {
if (result == kKextcacheExitHelp) {
result = EX_OK;
}
goto finish;
}
checkKextdSpawnedFilter( false);
checkKextdSpawnedFilter( true);
result = checkArgs(&toolArgs);
if (result != EX_OK) {
goto finish;
}
result = EX_OK;
if (toolArgs.lowPriorityFlag) {
OSKextLog( NULL,
kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Running in low-priority background mode.");
setpriority(PRIO_PROCESS, getpid(), 20); setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_THROTTLE);
if (toolArgs.prelinkedKernelURL) {
result = waitForLowSystemLoad();
if (result != EX_OK) goto finish;
}
}
OSKextSetUsesCaches(false);
if (toolArgs.updateVolumeURL) {
result = updateBoots(toolArgs.updateVolumeURL,
toolArgs.forceUpdateFlag, toolArgs.expectUpToDate);
goto finish;
}
if (toolArgs.prelinkedKernelURL && !CFArrayGetCount(toolArgs.argURLs) &&
(toolArgs.compress || toolArgs.uncompress))
{
prelinkedKernelIn = readMachOFileWithURL(toolArgs.prelinkedKernelURL, NULL);
if (!prelinkedKernelIn) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't read prelinked kernel.");
goto finish;
}
if (toolArgs.uncompress) {
prelinkedKernel = uncompressPrelinkedKernel(prelinkedKernelIn);
if (!prelinkedKernel) {
goto finish;
}
} else if (toolArgs.compress) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Prelinked kernels can't be compressed after they are created.");
goto finish;
}
result = savePrelinkedKernel(&toolArgs, prelinkedKernel, NULL);
goto finish;
}
if (toolArgs.printTestResults) {
OSKextSetRecordsDiagnostics(kOSKextDiagnosticsFlagAll);
}
toolArgs.allKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault, toolArgs.argURLs);
if (!toolArgs.allKexts || !CFArrayGetCount(toolArgs.allKexts)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No kernel extensions found.");
result = EX_SOFTWARE;
goto finish;
}
toolArgs.repositoryKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
toolArgs.repositoryURLs);
toolArgs.namedKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
toolArgs.namedKextURLs);
if (!toolArgs.repositoryKexts || !toolArgs.namedKexts) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error reading extensions.");
result = EX_SOFTWARE;
goto finish;
}
if (result != EX_OK) {
goto finish;
}
if (toolArgs.updateSystemCaches) {
result = updateSystemDirectoryCaches(&toolArgs);
}
if (toolArgs.mkextURL) {
result = createMkext(&toolArgs, &fatal);
if (fatal) {
goto finish;
}
}
if (toolArgs.prelinkedKernelURL) {
CFDictionaryRef * prelinkedSymbolsPtr = NULL; PlatformInfo platformInfo;
if (toolArgs.needDefaultPrelinkedKernelInfo &&
OSKextGetActualSafeBoot()) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't update the system prelinked kernel during safe boot.");
result = EX_OSERR;
goto finish;
}
getPlatformInfo(&toolArgs, &platformInfo);
if (toolArgs.generatePrelinkedSymbols) {
prelinkedSymbolsPtr = &prelinkedSymbols;
}
result = createPrelinkedKernel(&toolArgs, &prelinkedKernel,
prelinkedSymbolsPtr, toolArgs.archInfo[0],
toolArgs.needLoadedKextInfo ? &platformInfo : NULL);
if (result != EX_OK) {
goto finish;
}
result = savePrelinkedKernel(&toolArgs, prelinkedKernel,
prelinkedSymbols);
if (result != EX_OK) {
goto finish;
}
}
finish:
exit(result);
SAFE_RELEASE(toolArgs.kextIDs);
SAFE_RELEASE(toolArgs.argURLs);
SAFE_RELEASE(toolArgs.repositoryURLs);
SAFE_RELEASE(toolArgs.namedKextURLs);
SAFE_RELEASE(toolArgs.allKexts);
SAFE_RELEASE(toolArgs.repositoryKexts);
SAFE_RELEASE(toolArgs.namedKexts);
SAFE_RELEASE(toolArgs.mkextURL);
SAFE_RELEASE(toolArgs.prelinkedKernelURL);
SAFE_RELEASE(toolArgs.kernelURL);
SAFE_RELEASE(toolArgs.kernelFile);
SAFE_RELEASE(toolArgs.symbolDirURL);
SAFE_RELEASE(prelinkedKernel);
SAFE_RELEASE(prelinkedKernelIn);
SAFE_RELEASE(prelinkedSymbols);
return result;
}
ExitStatus readArgs(
int * argc,
char * const ** argv,
KextcacheArgs * toolArgs)
{
ExitStatus result = EX_USAGE;
int optchar;
int longindex = -1;
CFStringRef scratchString = NULL; CFNumberRef scratchNumber = NULL; CFURLRef scratchURL = NULL; uint32_t i;
bzero(toolArgs, sizeof(*toolArgs));
if (!createCFMutableSet(&toolArgs->kextIDs, &kCFTypeSetCallBacks) ||
!createCFMutableArray(&toolArgs->argURLs, &kCFTypeArrayCallBacks) ||
!createCFMutableArray(&toolArgs->repositoryURLs, &kCFTypeArrayCallBacks) ||
!createCFMutableArray(&toolArgs->namedKextURLs, &kCFTypeArrayCallBacks)) {
result = EX_OSERR;
OSKextLogMemError();
exit(result);
}
while ((optchar = getopt_long_only(*argc, *argv,
kOptChars, sOptInfo, &longindex)) != -1) {
SAFE_RELEASE_NULL(scratchString);
SAFE_RELEASE_NULL(scratchNumber);
SAFE_RELEASE_NULL(scratchURL);
if (optchar == ':') {
switch (optopt) {
case kOptPrelinkedKernel:
optchar = optopt;
break;
default:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"%s: option requires an argument -- -%c.",
progname, optopt);
break;
}
}
if (optchar == kOptMkext) {
optchar = 0;
longopt = kLongOptMkext;
}
switch (optchar) {
case kOptArch:
if (toolArgs->numArches >= kMaxNumArches) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Maximum of %d architectures supported", kMaxNumArches);
goto finish;
}
if (!addArchForName(toolArgs, optarg)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unknown architecture %s.", optarg);
goto finish;
}
toolArgs->explicitArch = true;
break;
case kOptBundleIdentifier:
scratchString = CFStringCreateWithCString(kCFAllocatorDefault,
optarg, kCFStringEncodingUTF8);
if (!scratchString) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
CFSetAddValue(toolArgs->kextIDs, scratchString);
break;
case kOptPrelinkedKernel:
result = readPrelinkedKernelArgs(toolArgs, *argc, *argv,
longindex != -1);
if (result != EX_OK) {
goto finish;
}
break;
case kOptSystemMkext:
if (toolArgs->updateSystemMkext) {
continue;
}
if (toolArgs->mkextURL && !toolArgs->updateSystemMkext) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"-%s (-%c) can't be used when saving files by name",
kOptNameSystemMkext, kOptSystemMkext);
goto finish;
}
toolArgs->updateSystemMkext = true;
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault, (const UInt8 *)_kOSKextStartupMkextPath,
strlen(_kOSKextStartupMkextPath), true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->mkextURL = CFRetain(scratchURL);
toolArgs->mkextVersion = 2;
{
CFArrayRef sysExtensionsFolders =
OSKextGetSystemExtensionsFolderURLs();
CFArrayAppendArray(toolArgs->argURLs,
sysExtensionsFolders, RANGE_ALL(sysExtensionsFolders));
CFArrayAppendArray(toolArgs->repositoryURLs,
sysExtensionsFolders, RANGE_ALL(sysExtensionsFolders));
}
break;
case kOptForce:
toolArgs->forceUpdateFlag = true;
break;
case kOptLowPriorityFork:
toolArgs->lowPriorityFlag = true;
break;
case kOptHelp:
usage(kUsageLevelFull);
result = kKextcacheExitHelp;
goto finish;
case kOptRepositoryCaches:
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"-%c is no longer used; ignoring.",
kOptRepositoryCaches);
break;
case kOptKernel:
if (toolArgs->kernelURL) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: kernel file already specified; using last.");
SAFE_RELEASE_NULL(toolArgs->kernelURL);
}
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg, strlen(optarg), true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->kernelURL = CFRetain(scratchURL);
break;
case kOptLocalRoot:
toolArgs->requiredFlagsRepositoriesOnly |=
kOSKextOSBundleRequiredLocalRootFlag;
break;
case kOptLocalRootAll:
toolArgs->requiredFlagsAll |=
kOSKextOSBundleRequiredLocalRootFlag;
break;
case kOptNetworkRoot:
toolArgs->requiredFlagsRepositoriesOnly |=
kOSKextOSBundleRequiredNetworkRootFlag;
break;
case kOptNetworkRootAll:
toolArgs->requiredFlagsAll |=
kOSKextOSBundleRequiredNetworkRootFlag;
break;
case kOptAllLoaded:
setNeededLoadedKextInfo(toolArgs);
break;
case kOptSafeBoot:
toolArgs->requiredFlagsRepositoriesOnly |=
kOSKextOSBundleRequiredSafeBootFlag;
break;
case kOptSafeBootAll:
toolArgs->requiredFlagsAll |=
kOSKextOSBundleRequiredSafeBootFlag;
break;
case kOptTests:
toolArgs->printTestResults = true;
break;
case kOptUpdate:
case kOptCheckUpdate:
if (toolArgs->updateVolumeURL) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: update volume already specified; using last.");
SAFE_RELEASE_NULL(toolArgs->updateVolumeURL);
}
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg, strlen(optarg), true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->updateVolumeURL = CFRetain(scratchURL);
if (optchar == kOptCheckUpdate) {
toolArgs->expectUpToDate = true;
}
break;
case kOptQuiet:
beQuiet();
break;
case kOptVerbose:
result = setLogFilterForOpt(*argc, *argv,
kOSKextLogKextOrGlobalMask);
break;
case kOptNoAuthentication:
toolArgs->skipAuthentication = true;
break;
case 0:
switch (longopt) {
case kLongOptMkext1:
case kLongOptMkext2:
if (toolArgs->updateSystemMkext) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"-%s (-%c) can't be used when saving files by name.",
kOptNameSystemMkext, kOptSystemMkext);
goto finish;
}
if (toolArgs->mkextURL) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: output mkext file already specified; using last.");
SAFE_RELEASE_NULL(toolArgs->mkextURL);
}
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg, strlen(optarg), true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->mkextURL = CFRetain(scratchURL);
if (longopt == kLongOptMkext1) {
toolArgs->mkextVersion = 1;
} else if (longopt == kLongOptMkext2) {
toolArgs->mkextVersion = 2;
} else {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Intenral error.");
}
break;
case kLongOptVolumeRoot:
if (toolArgs->volumeRootURL) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: volume root already specified; using last.");
SAFE_RELEASE_NULL(toolArgs->volumeRootURL);
}
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg, strlen(optarg), true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->volumeRootURL = CFRetain(scratchURL);
break;
case kLongOptSystemCaches:
toolArgs->updateSystemCaches = true;
{
CFArrayRef sysExtensionsFolders =
OSKextGetSystemExtensionsFolderURLs();
CFArrayAppendArray(toolArgs->argURLs,
sysExtensionsFolders, RANGE_ALL(sysExtensionsFolders));
CFArrayAppendArray(toolArgs->repositoryURLs,
sysExtensionsFolders, RANGE_ALL(sysExtensionsFolders));
}
break;
case kLongOptCompressed:
toolArgs->compress = true;
break;
case kLongOptUncompressed:
toolArgs->uncompress = true;
break;
case kLongOptSymbols:
if (toolArgs->symbolDirURL) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: symbol directory already specified; using last.");
SAFE_RELEASE_NULL(toolArgs->symbolDirURL);
}
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg, strlen(optarg), true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->symbolDirURL = CFRetain(scratchURL);
toolArgs->generatePrelinkedSymbols = true;
break;
case kLongOptSystemPrelinkedKernel:
result = setPrelinkedKernelArgs(toolArgs,
NULL);
if (result != EX_OK) {
goto finish;
}
setNeededLoadedKextInfo(toolArgs);
break;
default:
goto finish;
break;
}
break;
default:
goto finish;
break;
}
longindex = -1;
}
*argc -= optind;
*argv += optind;
if (!toolArgs->updateVolumeURL) {
for (i = 0; i < *argc; i++) {
SAFE_RELEASE_NULL(scratchURL);
SAFE_RELEASE_NULL(scratchString);
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)(*argv)[i], strlen((*argv)[i]), true);
if (!scratchURL) {
result = EX_OSERR;
OSKextLogMemError();
goto finish;
}
CFArrayAppendValue(toolArgs->argURLs, scratchURL);
scratchString = CFURLCopyPathExtension(scratchURL);
if (scratchString && CFEqual(scratchString, CFSTR("kext"))) {
CFArrayAppendValue(toolArgs->namedKextURLs, scratchURL);
} else {
CFArrayAppendValue(toolArgs->repositoryURLs, scratchURL);
}
}
}
result = EX_OK;
finish:
SAFE_RELEASE(scratchString);
SAFE_RELEASE(scratchNumber);
SAFE_RELEASE(scratchURL);
if (result == EX_USAGE) {
usage(kUsageLevelBrief);
}
return result;
}
ExitStatus readPrelinkedKernelArgs(
KextcacheArgs * toolArgs,
int argc,
char * const * argv,
Boolean isLongopt)
{
char * filename = NULL;
if (optarg) {
filename = optarg;
} else if (isLongopt && optind < argc) {
filename = argv[optind];
optind++;
}
if (filename && !filename[0]) {
filename = NULL;
}
return setPrelinkedKernelArgs(toolArgs, filename);
}
ExitStatus setPrelinkedKernelArgs(
KextcacheArgs * toolArgs,
char * filename)
{
ExitStatus result = EX_USAGE;
CFURLRef scratchURL = NULL;
if (toolArgs->prelinkedKernelURL) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: prelinked kernel already specified; using last.");
SAFE_RELEASE_NULL(toolArgs->prelinkedKernelURL);
toolArgs->prelinkedKernelErrorRequired = false;
}
if (!filename) {
toolArgs->needDefaultPrelinkedKernelInfo = true;
const char * prelinkedKernelDir =
_kOSKextCachesRootFolder "/" _kOSKextStartupCachesSubfolder;
scratchURL = CFURLCreateWithBytes(kCFAllocatorDefault,
(UInt8 *)prelinkedKernelDir, strlen(prelinkedKernelDir),
kCFStringEncodingMacRoman, NULL);
} else {
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault, (const UInt8 *)filename,
strlen(filename), true);
toolArgs->prelinkedKernelErrorRequired = true;
}
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->prelinkedKernelURL = CFRetain(scratchURL);
result = EX_OK;
finish:
SAFE_RELEASE(scratchURL);
return result;
}
void setNeededLoadedKextInfo(KextcacheArgs * toolArgs)
{
CFArrayRef sysExtensionsFolders = OSKextGetSystemExtensionsFolderURLs();
CFArrayAppendArray(toolArgs->argURLs,
sysExtensionsFolders, RANGE_ALL(sysExtensionsFolders));
CFArrayAppendArray(toolArgs->repositoryURLs,
sysExtensionsFolders, RANGE_ALL(sysExtensionsFolders));
toolArgs->needLoadedKextInfo = true;
return;
}
static void
waitForIOKitQuiescence(void)
{
kern_return_t kern_result = 0;;
mach_timespec_t waitTime = { 40, 0 };
OSKextLog( NULL,
kOSKextLogProgressLevel | kOSKextLogIPCFlag,
"Waiting for I/O Kit to quiesce.");
kern_result = IOKitWaitQuiet(kIOMasterPortDefault, &waitTime);
if (kern_result == kIOReturnTimeout) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"IOKitWaitQuiet() timed out.");
} else if (kern_result != kOSReturnSuccess) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"IOKitWaitQuiet() failed - %s.",
safe_mach_error_string(kern_result));
}
}
static ExitStatus
waitForLowSystemLoad(void)
{
ExitStatus result = EX_SOFTWARE;
int notifyFileDescriptor = 0, notifyToken = 0, currentToken = 0;
uint32_t notifyStatus = 0, usecs = 0;
uint64_t notifyState = 0;
struct timeval currenttime;
struct timeval endtime;
struct timeval timeout;
fd_set readfds;
fd_set tmpfds;
bzero(¤ttime, sizeof(currenttime));
bzero(&endtime, sizeof(endtime));
bzero(&timeout, sizeof(timeout));
OSKextLog( NULL,
kOSKextLogProgressLevel | kOSKextLogGeneralFlag,
"Waiting for low system load.");
notifyStatus = notify_register_file_descriptor(
kIOSystemLoadAdvisoryNotifyName, ¬ifyFileDescriptor,
0, ¬ifyToken);
if (notifyStatus != NOTIFY_STATUS_OK) {
result = EX_OSERR;
goto finish;
}
notifyStatus = notify_get_state(notifyToken, ¬ifyState);
if (notifyStatus != NOTIFY_STATUS_OK) {
result = EX_OSERR;
goto finish;
}
if (notifyState != kIOSystemLoadAdvisoryLevelGreat) {
notifyStatus = gettimeofday(¤ttime, NULL);
if (notifyStatus < 0) {
result = EX_OSERR;
goto finish;
}
endtime = currenttime;
endtime.tv_sec += SYSTEM_LOAD_TIMEOUT;
}
timeval_difference(&timeout, &endtime, ¤ttime);
usecs = usecs_from_timeval(&timeout);
FD_ZERO(&readfds);
FD_SET(notifyFileDescriptor, &readfds);
while (usecs) {
FD_COPY(&readfds, &tmpfds);
notifyStatus = select(notifyFileDescriptor + 1,
&tmpfds, NULL, NULL, &timeout);
if (notifyStatus < 0) {
result = EX_OSERR;
goto finish;
}
notifyStatus = gettimeofday(¤ttime, NULL);
if (notifyStatus < 0) {
result = EX_OSERR;
goto finish;
}
timeval_difference(&timeout, &endtime, ¤ttime);
usecs = usecs_from_timeval(&timeout);
if (!FD_ISSET(notifyFileDescriptor, &tmpfds)) continue;
notifyStatus = read(notifyFileDescriptor,
¤tToken, sizeof(currentToken));
if (notifyStatus < 0) {
result = EX_OSERR;
goto finish;
}
if (currentToken == notifyToken) {
notifyStatus = notify_get_state(notifyToken, ¬ifyState);
if (notifyStatus != NOTIFY_STATUS_OK) {
result = EX_OSERR;
goto finish;
}
if (notifyState == kIOSystemLoadAdvisoryLevelGreat) break;
}
}
result = EX_OK;
finish:
return result;
}
static u_int
usecs_from_timeval(struct timeval *t)
{
u_int usecs = 0;
if (t) {
usecs = (t->tv_sec * 1000) + t->tv_usec;
}
return usecs;
}
static void
timeval_from_usecs(struct timeval *t, u_int usecs)
{
if (t) {
if (usecs > 0) {
t->tv_sec = usecs / 1000;
t->tv_usec = usecs % 1000;
} else {
bzero(t, sizeof(*t));
}
}
}
static void
timeval_difference(struct timeval *dst, struct timeval *a, struct timeval *b)
{
u_int ausec = 0, busec = 0, dstusec = 0;
if (dst) {
ausec = usecs_from_timeval(a);
busec = usecs_from_timeval(b);
if (ausec > busec) {
dstusec = ausec - busec;
}
timeval_from_usecs(dst, dstusec);
}
}
const NXArchInfo * addArchForName(
KextcacheArgs * toolArgs,
const char * archname)
{
const NXArchInfo * result = NULL;
int i;
result = NXGetArchInfoFromName(archname);
if (!result) {
goto finish;
}
for (i = 0; i < toolArgs->numArches; i++) {
if (toolArgs->archInfo[i] == result) {
goto finish;
}
}
toolArgs->archInfo[toolArgs->numArches] = result;
++(toolArgs->numArches);
finish:
return result;
}
void addArch(
KextcacheArgs * toolArgs,
const NXArchInfo * arch)
{
int i;
for (i = 0; i < toolArgs->numArches; i++) {
if (toolArgs->archInfo[i] == arch) {
goto finish;
}
}
toolArgs->archInfo[toolArgs->numArches] = arch;
++(toolArgs->numArches);
finish:
return;
}
void checkKextdSpawnedFilter(Boolean kernelFlag)
{
const char * environmentVariable = NULL; char * environmentLogFilterString = NULL;
if (kernelFlag) {
environmentVariable = "KEXT_LOG_FILTER_KERNEL";
} else {
environmentVariable = "KEXT_LOG_FILTER_USER";
}
environmentLogFilterString = getenv(environmentVariable);
if (environmentLogFilterString) {
OSKextLogSpec toolLogSpec = OSKextGetLogFilter(kernelFlag);
OSKextLogSpec kextdLogSpec = strtoul(environmentLogFilterString, NULL, 16);
OSKextLogSpec toolLogLevel = toolLogSpec & kOSKextLogLevelMask;
OSKextLogSpec kextdLogLevel = kextdLogSpec & kOSKextLogLevelMask;
OSKextLogSpec comboLogLevel = MAX(toolLogLevel, kextdLogLevel);
OSKextLogSpec toolLogFlags = toolLogSpec & kOSKextLogFlagsMask;
OSKextLogSpec kextdLogFlags = kextdLogSpec & kOSKextLogFlagsMask;
OSKextLogSpec comboLogFlags = toolLogFlags | kextdLogFlags |
kOSKextLogKextOrGlobalMask;
OSKextSetLogFilter(comboLogLevel | comboLogFlags, kernelFlag);
} else {
char logSpecBuffer[16];
snprintf(logSpecBuffer, sizeof(logSpecBuffer), "0x%x",
OSKextGetLogFilter(kernelFlag));
setenv(environmentVariable, logSpecBuffer, 1);
}
return;
}
ExitStatus checkArgs(KextcacheArgs * toolArgs)
{
ExitStatus result = EX_USAGE;
if (!toolArgs->mkextURL && !toolArgs->prelinkedKernelURL &&
!toolArgs->updateVolumeURL && !toolArgs->updateSystemCaches) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No work to do; check options and try again.");
goto finish;
}
if (toolArgs->volumeRootURL && !toolArgs->mkextURL &&
!toolArgs->prelinkedKernelURL) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Use -%s only when creating an mkext archive or prelinked kernel.",
kOptNameVolumeRoot);
goto finish;
}
if (!toolArgs->updateVolumeURL && !CFArrayGetCount(toolArgs->argURLs) &&
!toolArgs->compress && !toolArgs->uncompress) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No kexts or directories specified.");
goto finish;
}
if (!toolArgs->compress && !toolArgs->uncompress) {
toolArgs->compress = true;
} else if (toolArgs->compress && toolArgs->uncompress) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Both -%s and -%s specified; using -%s.",
kOptNameCompressed, kOptNameUncompressed, kOptNameCompressed);
toolArgs->compress = true;
toolArgs->uncompress = false;
}
if (toolArgs->forceUpdateFlag) {
if (toolArgs->expectUpToDate || !toolArgs->updateVolumeURL) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"-%s (-%c) is allowed only with -%s (-%c).",
kOptNameForce, kOptForce, kOptNameUpdate, kOptUpdate);
goto finish;
}
}
if (toolArgs->updateVolumeURL) {
if (toolArgs->mkextURL || toolArgs->prelinkedKernelURL) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't create mkext or prelinked kernel when updating volumes.");
}
}
if (!toolArgs->explicitArch) {
if (toolArgs->prelinkedKernelURL) {
const NXArchInfo *hostArch;
hostArch = OSKextGetRunningKernelArchitecture();
if (!hostArch) {
goto finish;
}
addArch(toolArgs, hostArch);
} else {
addArchForName(toolArgs, "i386");
addArchForName(toolArgs, "x86_64");
}
} else {
if (toolArgs->prelinkedKernelURL) {
if (toolArgs->numArches != 1) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Prelinked kernel requires a single architecture.");
goto finish;
}
if (toolArgs->needLoadedKextInfo) {
const NXArchInfo *hostArch;
hostArch = OSKextGetRunningKernelArchitecture();
if (!hostArch) {
goto finish;
}
if (toolArgs->archInfo[0]->cputype != hostArch->cputype) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't request loaded kexts for non-host architecture.");
goto finish;
}
}
}
}
if (!toolArgs->haveFolderMtime && CFArrayGetCount(toolArgs->repositoryURLs)) {
CFURLRef firstURL = (CFURLRef)CFArrayGetValueAtIndex(
toolArgs->repositoryURLs, 0);
if (firstURL) {
result = statURL(firstURL, &toolArgs->firstFolderStatBuffer);
if (result != EX_OK) {
goto finish;
}
toolArgs->haveFolderMtime = true;
}
}
if (toolArgs->needDefaultPrelinkedKernelInfo && !toolArgs->kernelURL) {
toolArgs->kernelURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault, (UInt8 *) kDefaultKernelFile,
sizeof(kDefaultKernelFile) - 1, false);
if (!toolArgs->kernelURL) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
}
if (toolArgs->prelinkedKernelURL && !toolArgs->uncompress &&
!toolArgs->kernelURL) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No kernel specified for prelinked kernel generation.");
goto finish;
}
if (toolArgs->kernelURL) {
result = statURL(toolArgs->kernelURL, &toolArgs->kernelStatBuffer);
if (result != EX_OK) {
goto finish;
}
}
if (toolArgs->needDefaultPrelinkedKernelInfo ||
toolArgs->updateSystemMkext ||
toolArgs->updateSystemCaches) {
if (geteuid() != 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"You must be running as root to update system kext caches.");
result = EX_NOPERM;
goto finish;
}
}
result = EX_OK;
finish:
if (result == EX_USAGE) {
usage(kUsageLevelBrief);
}
return result;
}
ExitStatus statURL(CFURLRef anURL, struct stat * statBuffer)
{
ExitStatus result = EX_OSERR;
char dirPath[PATH_MAX];
if (!CFURLGetFileSystemRepresentation(anURL, true,
(UInt8 *)dirPath, sizeof(dirPath))) {
OSKextLogStringError( NULL);
goto finish;
}
if (stat(dirPath, statBuffer)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't stat %s - %s.", dirPath, strerror(errno));
goto finish;
}
result = EX_OK;
finish:
return result;
}
ExitStatus updateSystemDirectoryCaches(
KextcacheArgs * toolArgs)
{
ExitStatus result = EX_OSERR;
ExitStatus directoryResult = EX_OK; CFArrayRef sysExtensionsFolders = NULL; CFURLRef folderURL = NULL; char folderPath[PATH_MAX] = "";
CFIndex count, i;
sysExtensionsFolders = OSKextGetSystemExtensionsFolderURLs();
if (!sysExtensionsFolders) {
OSKextLogMemError();
goto finish;
}
count = CFArrayGetCount(sysExtensionsFolders);
for (i = 0; i < count; i++) {
folderURL = CFArrayGetValueAtIndex(sysExtensionsFolders, i);
if (!CFURLGetFileSystemRepresentation(folderURL, true,
(UInt8 *)folderPath, sizeof(folderPath))) {
OSKextLogStringError( NULL);
goto finish;
}
if (EX_OK != updateDirectoryCaches(toolArgs, folderURL)) {
directoryResult = EX_OSERR;
} else {
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogGeneralFlag,
"Directory caches updated for %s.", folderPath);
}
}
if (directoryResult == EX_OK) {
result = EX_OK;
}
finish:
return result;
}
ExitStatus updateDirectoryCaches(
KextcacheArgs * toolArgs,
CFURLRef folderURL)
{
ExitStatus result = EX_OK; CFArrayRef kexts = NULL; CFArrayRef directoryValues = NULL; CFIndex i;
kexts = OSKextCreateKextsFromURL(kCFAllocatorDefault, folderURL);
if (!kexts) {
result = EX_OSERR;
goto finish;
}
if (!_OSKextWriteIdentifierCacheForKextsInDirectory(
kexts, folderURL, true)) {
result = EX_OSERR;
}
for (i = 0; i < toolArgs->numArches; i++) {
if (kOSReturnSuccess != writePersonalitiesCache(kexts,
toolArgs->archInfo[i], folderURL)) {
result = EX_OSERR;
}
if (!readKextPropertyValuesForDirectory(folderURL,
CFSTR(kOSBundleHelperKey), toolArgs->archInfo[i],
true, NULL)) {
result = EX_OSERR;
}
}
result = EX_OK;
finish:
SAFE_RELEASE(kexts);
SAFE_RELEASE(directoryValues);
return result;
}
#define kOpenFirmwareMaxFileSize (16 * 1024 * 1024)
ExitStatus createMkext(
KextcacheArgs * toolArgs,
Boolean * fatalOut)
{
ExitStatus result = EX_SOFTWARE;
CFMutableArrayRef archiveKexts = NULL; CFMutableArrayRef mkexts = NULL; CFDataRef mkext = NULL; struct fat_header fatHeader;
uint32_t fatOffset = 0;
CFURLRef systemMkextFolderURL = NULL; char mkextPath[PATH_MAX];
char tmpPath[PATH_MAX];
int fileDescriptor = -1; mode_t real_umask;
struct timeval cacheFileTimes[2];
Boolean updateModTime = true;
int i;
if (!getenv("_com_apple_kextd_skiplocks")) {
result = takeVolumeForPath(mkextPath);
if (result != EX_OK) {
goto finish;
}
}
if (!createCFMutableArray(&mkexts, &kCFTypeArrayCallBacks)) {
OSKextLogMemError();
result = EX_OSERR;
*fatalOut = true;
goto finish;
}
if (!createCFMutableArray(&archiveKexts, &kCFTypeArrayCallBacks)) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
if (toolArgs->updateSystemMkext) {
systemMkextFolderURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault, (UInt8 *)_kOSKextStartupMkextFolderPath,
strlen(_kOSKextStartupMkextFolderPath), true);
if (!systemMkextFolderURL) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
if (!_OSKextCreateFolderForCacheURL(toolArgs->mkextURL)) {
result = EX_OSERR;
goto finish;
}
}
for (i = 0; i < toolArgs->numArches; i++) {
SAFE_RELEASE_NULL(mkext);
if (!OSKextSetArchitecture(toolArgs->archInfo[i])) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't set architecture %s to create mkext.",
toolArgs->archInfo[i]->name);
result = EX_OSERR;
goto finish;
}
result = filterKextsForMkext(toolArgs, archiveKexts,
toolArgs->archInfo[i], fatalOut);
if (result != EX_OK || *fatalOut) {
goto finish;
}
if (!CFArrayGetCount(archiveKexts)) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogArchiveFlag,
"No kexts found for architecture %s; skipping architecture.",
toolArgs->archInfo[i]->name);
continue;
}
if (toolArgs->mkextVersion == 2) {
mkext = OSKextCreateMkext(kCFAllocatorDefault, archiveKexts,
toolArgs->volumeRootURL,
kOSKextOSBundleRequiredNone, toolArgs->compress);
} else if (toolArgs->mkextVersion == 1) {
mkext = createMkext1ForArch(toolArgs->archInfo[i], archiveKexts,
toolArgs->compress);
}
if (!mkext) {
result = EX_OSERR;
goto finish;
}
if (toolArgs->archInfo[i] == NXGetArchInfoFromName("ppc")) {
if (CFDataGetLength(mkext) > kOpenFirmwareMaxFileSize) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"PPC archive is too large for Open Firmware; aborting.");
result = EX_SOFTWARE;
*fatalOut = true;
goto finish;
}
}
CFArrayAppendValue(mkexts, mkext);
}
if (!CFArrayGetCount(mkexts)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"No mkext archives created.");
goto finish;
}
if (!CFURLGetFileSystemRepresentation(toolArgs->mkextURL,
true, (UInt8 *)mkextPath, sizeof(mkextPath))) {
OSKextLogStringError( NULL);
goto finish;
}
if (!strncmp(mkextPath, _kOSKextStartupMkextFolderPath,
sizeof(_kOSKextStartupMkextFolderPath) - 1)) {
_OSKextCreateFolderForCacheURL(toolArgs->mkextURL);
}
strlcpy(tmpPath, mkextPath, sizeof(tmpPath));
if (strlcat(tmpPath, ".XXXX", sizeof(tmpPath)) >= sizeof(tmpPath)) {
OSKextLogStringError( NULL);
goto finish;
}
fileDescriptor = mkstemp(tmpPath);
if (-1 == fileDescriptor) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't create %s - %s.",
tmpPath, strerror(errno));
goto finish;
}
real_umask = umask(0);
umask(real_umask);
if (-1 == fchmod(fileDescriptor, MKEXT_PERMS & ~real_umask)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't set permissions on %s - %s.",
tmpPath, strerror(errno));
}
OSKextLog( NULL,
kOSKextLogDebugLevel | kOSKextLogFileAccessFlag,
"Saving temp mkext file %s.",
tmpPath);
fatHeader.magic = OSSwapHostToBigInt32(FAT_MAGIC);
fatHeader.nfat_arch = OSSwapHostToBigInt32(toolArgs->numArches);
result = writeToFile(fileDescriptor, (const UInt8 *)&fatHeader,
sizeof(fatHeader));
if (result != EX_OK) {
goto finish;
}
fatOffset = sizeof(struct fat_header) +
(sizeof(struct fat_arch) * toolArgs->numArches);
for (i = 0; i < toolArgs->numArches; i++) {
CFDataRef mkext = (CFDataRef)CFArrayGetValueAtIndex(mkexts, i);
CFIndex mkextLength = CFDataGetLength(mkext);
struct fat_arch fatArch;
fatArch.cputype = OSSwapHostToBigInt32(toolArgs->archInfo[i]->cputype);
fatArch.cpusubtype =
OSSwapHostToBigInt32(toolArgs->archInfo[i]->cpusubtype);
fatArch.offset = OSSwapHostToBigInt32(fatOffset);
fatArch.size = OSSwapHostToBigInt32(mkextLength);
fatArch.align = OSSwapHostToBigInt32(0);
result = writeToFile(fileDescriptor, (UInt8 *)&fatArch, sizeof(fatArch));
if (result != EX_OK) {
goto finish;
}
fatOffset += mkextLength;
}
for (i = 0; i < toolArgs->numArches; i++) {
CFDataRef mkext = (CFDataRef)CFArrayGetValueAtIndex(mkexts, i);
const UInt8 * mkextPtr = CFDataGetBytePtr(mkext);
CFIndex mkextLength = CFDataGetLength(mkext);
result = writeToFile(fileDescriptor, mkextPtr, mkextLength);
if (result != EX_OK) {
goto finish;
}
}
if (toolArgs->haveFolderMtime && CFArrayGetCount(toolArgs->repositoryURLs)) {
CFURLRef firstURL = (CFURLRef)CFArrayGetValueAtIndex(
toolArgs->repositoryURLs, 0);
struct stat newStatBuffer;
struct timespec newModTime;
struct timespec origModTime =
toolArgs->firstFolderStatBuffer.st_mtimespec;
result = statURL(firstURL, &newStatBuffer);
if (result != EX_OK) {
goto finish;
}
newModTime = newStatBuffer.st_mtimespec;
if ((newModTime.tv_sec != origModTime.tv_sec) ||
(newModTime.tv_nsec != origModTime.tv_nsec)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
"Source directory has changed since starting; "
"not saving cache file %s.",
mkextPath);
result = kKextcacheExitStale;
goto finish;
}
TIMESPEC_TO_TIMEVAL(&cacheFileTimes[0], &newStatBuffer.st_atimespec);
TIMESPEC_TO_TIMEVAL(&cacheFileTimes[1], &newStatBuffer.st_mtimespec);
cacheFileTimes[1].tv_sec++;
updateModTime = true;
}
OSKextLog( NULL,
kOSKextLogDebugLevel | kOSKextLogFileAccessFlag,
"Renaming temp mkext file to %s.",
mkextPath);
if (rename(tmpPath, mkextPath) != 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't rename temp mkext file - %s.",
strerror(errno));
result = EX_OSERR;
goto finish;
}
if (updateModTime) {
if (utimes(mkextPath, cacheFileTimes)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't update mod time of %s - %s.", mkextPath, strerror(errno));
}
}
result = EX_OK;
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogGeneralFlag | kOSKextLogArchiveFlag,
"Created mkext archive %s.", mkextPath);
finish:
SAFE_RELEASE(archiveKexts);
SAFE_RELEASE(mkexts);
SAFE_RELEASE(mkext);
SAFE_RELEASE(systemMkextFolderURL);
if (fileDescriptor != -1) {
close(fileDescriptor);
if (result != EX_OK) {
unlink(tmpPath);
}
}
putVolumeForPath(mkextPath, result);
return result;
}
typedef struct {
KextcacheArgs * toolArgs;
CFMutableArrayRef kextArray;
} FilterIDContext;
void filterKextID(const void * vValue, void * vContext)
{
CFStringRef kextID = (CFStringRef)vValue;
FilterIDContext * context = (FilterIDContext *)vContext;
OSKextRef theKext = OSKextGetKextWithIdentifier(kextID);
if (!theKext) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Internal error filtering kexts by identifier.");
goto finish;
}
if (OSKextMatchesRequiredFlags(theKext, context->toolArgs->requiredFlagsAll) &&
!CFArrayContainsValue(context->kextArray,
RANGE_ALL(context->kextArray), theKext)) {
CFArrayAppendValue(context->kextArray, theKext);
}
finish:
return;
}
ExitStatus filterKextsForMkext(
KextcacheArgs * toolArgs,
CFMutableArrayRef kextArray,
const NXArchInfo * arch,
Boolean * fatalOut)
{
ExitStatus result = EX_SOFTWARE;
OSKextRequiredFlags requiredFlags;
CFIndex count, i;
CFArrayRemoveAllValues(kextArray);
if (CFSetGetCount(toolArgs->kextIDs)) {
FilterIDContext context;
context.toolArgs = toolArgs;
context.kextArray = kextArray;
CFSetApplyFunction(toolArgs->kextIDs, filterKextID, &context);
result = EX_OK;
goto finish;
}
requiredFlags = toolArgs->requiredFlagsRepositoriesOnly |
toolArgs->requiredFlagsAll;
if (requiredFlags) {
requiredFlags |= kOSKextOSBundleRequiredRootFlag |
kOSKextOSBundleRequiredConsoleFlag;
}
count = CFArrayGetCount(toolArgs->repositoryKexts);
for (i = 0; i < count; i++) {
char kextPath[PATH_MAX];
OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(
toolArgs->repositoryKexts, i);
if (!CFURLGetFileSystemRepresentation(OSKextGetURL(theKext),
false, (UInt8 *)kextPath, sizeof(kextPath))) {
strlcpy(kextPath, "(unknown)", sizeof(kextPath));
}
if (!OSKextIsValid(theKext)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag |
kOSKextLogValidationFlag | kOSKextLogGeneralFlag,
"%s is not valid; omitting from mkext.", kextPath);
if (toolArgs->printTestResults) {
OSKextLogDiagnostics(theKext, kOSKextDiagnosticsFlagAll);
}
continue;
}
if (!OSKextSupportsArchitecture(theKext, arch)) {
OSKextLog( NULL,
kOSKextLogStepLevel | kOSKextLogArchiveFlag,
"%s doesn't support architecture '%s'; omitting from mkext.", kextPath,
arch->name);
continue;
}
if (!OSKextMatchesRequiredFlags(theKext, requiredFlags)) {
OSKextLog( NULL,
kOSKextLogStepLevel | kOSKextLogArchiveFlag,
"%s does not match OSBundleRequired conditions; omitting from mkext.",
kextPath);
continue;
}
if (!toolArgs->skipAuthentication && !OSKextIsAuthentic(theKext)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag |
kOSKextLogAuthenticationFlag | kOSKextLogGeneralFlag,
"%s is not authentic; omitting from mkext.", kextPath);
if (toolArgs->printTestResults) {
OSKextLogDiagnostics(theKext, kOSKextDiagnosticsFlagAll);
}
continue;
}
if (!OSKextResolveDependencies(theKext)) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogArchiveFlag |
kOSKextLogDependenciesFlag | kOSKextLogGeneralFlag,
"%s is missing dependencies (including anyway; "
"dependencies may be available from elsewhere)", kextPath);
if (toolArgs->printTestResults) {
OSKextLogDiagnostics(theKext, kOSKextDiagnosticsFlagAll);
}
}
if (!CFArrayContainsValue(kextArray, RANGE_ALL(kextArray), theKext)) {
CFArrayAppendValue(kextArray, theKext);
}
}
requiredFlags = toolArgs->requiredFlagsAll;
if (requiredFlags) {
requiredFlags |= kOSKextOSBundleRequiredRootFlag |
kOSKextOSBundleRequiredConsoleFlag;
}
count = CFArrayGetCount(toolArgs->namedKexts);
for (i = 0; i < count; i++) {
OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(
toolArgs->namedKexts, i);
if (OSKextMatchesRequiredFlags(theKext, requiredFlags) &&
!CFArrayContainsValue(kextArray, RANGE_ALL(kextArray), theKext)) {
CFArrayAppendValue(kextArray, theKext);
}
}
result = EX_OK;
finish:
return result;
}
ExitStatus writeToFile(
int fileDescriptor,
const UInt8 * data,
CFIndex length)
{
ExitStatus result = EX_OSERR;
int bytesWritten = 0;
int totalBytesWritten = 0;
while (totalBytesWritten < length) {
bytesWritten = write(fileDescriptor, data + totalBytesWritten,
length - totalBytesWritten);
if (bytesWritten < 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Write failed - %s", strerror(errno));
goto finish;
}
totalBytesWritten += bytesWritten;
}
result = EX_OK;
finish:
return result;
}
int getFileOffsetAndSizeForArch(
int fileDescriptor,
const NXArchInfo * archInfo,
off_t * sliceOffsetOut,
size_t * sliceSizeOut)
{
int result = -1;
void *headerPage = NULL;
struct fat_header *fatHeader = NULL;
struct fat_arch *fatArch = NULL;
off_t sliceOffset = 0;
size_t sliceSize = 0;
headerPage = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_FILE | MAP_PRIVATE,
fileDescriptor, 0);
if (!headerPage) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Failed to map file header page.");
goto finish;
}
fatHeader = (struct fat_header *) headerPage;
fatArch = (struct fat_arch *) (&fatHeader[1]);
if (fatHeader->magic == FAT_CIGAM) {
swap_fat_header(fatHeader, NXHostByteOrder());
swap_fat_arch(fatArch, fatHeader->nfat_arch, NXHostByteOrder());
}
if (fatHeader->magic == FAT_MAGIC) {
fatArch = NXFindBestFatArch(archInfo->cputype, archInfo->cpusubtype,
fatArch, fatHeader->nfat_arch);
if (!fatArch) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Fat file does not contain requested architecture %s.",
archInfo->name);
goto finish;
}
sliceOffset = fatArch->offset;
sliceSize = fatArch->size;
} else {
struct stat fileInfo;
if (fstat(fileDescriptor, &fileInfo)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Could not stat file."); goto finish;
}
sliceSize = fileInfo.st_size;
}
*sliceOffsetOut = sliceOffset;
*sliceSizeOut = sliceSize;
result = 0;
finish:
return result;
}
int readFileAtOffset(
int fileDescriptor,
off_t fileOffset,
size_t fileSize,
u_char * buf)
{
int result = -1;
off_t seekedBytes = 0;
ssize_t readBytes = 0;
size_t totalReadBytes = 0;
seekedBytes = lseek(fileDescriptor, fileOffset, SEEK_SET);
if (seekedBytes != fileOffset) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Failed to seek in file."); goto finish;
}
while (totalReadBytes < fileSize) {
readBytes = read(fileDescriptor, buf + totalReadBytes,
fileSize - totalReadBytes);
if (readBytes < 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Failed to read file.");
goto finish;
}
totalReadBytes += (size_t) readBytes;
}
result = 0;
finish:
return result;
}
int verifyMachOIsArch(
u_char * fileBuf,
size_t size,
const NXArchInfo * archInfo)
{
int result = -1;
cpu_type_t cputype = 0;
struct mach_header *checkHeader = (struct mach_header *)fileBuf;
if (!archInfo) {
result = 0;
goto finish;
}
if (size < sizeof(uint32_t)) {
goto finish;
}
if (checkHeader->magic == MH_MAGIC_64 || checkHeader->magic == MH_CIGAM_64) {
struct mach_header_64 *machHeader =
(struct mach_header_64 *) fileBuf;
if (size < sizeof(*machHeader)) {
goto finish;
}
cputype = machHeader->cputype;
if (checkHeader->magic == MH_CIGAM_64) cputype = OSSwapInt32(cputype);
} else if (checkHeader->magic == MH_MAGIC || checkHeader->magic == MH_CIGAM) {
struct mach_header *machHeader =
(struct mach_header *) fileBuf;
if (size < sizeof(*machHeader)) {
goto finish;
}
cputype = machHeader->cputype;
if (checkHeader->magic == MH_CIGAM) cputype = OSSwapInt32(cputype);
}
if (cputype != archInfo->cputype) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"File is not of expected architecture %s.", archInfo->name);
goto finish;
}
result = 0;
finish:
return result;
}
CFDataRef readMachOFileWithURL(CFURLRef fileURL, const NXArchInfo * archInfo)
{
char filePath[MAXPATHLEN];
int fileDescriptor = 0;
off_t fileSliceOffset = 0;
size_t fileSliceSize = 0;
u_char * fileBuf = NULL; CFDataRef fileImage = NULL;
struct stat statBuf;
if (!CFURLGetFileSystemRepresentation(fileURL, true,
(UInt8 *)filePath, sizeof(filePath)))
{
goto finish;
}
fileDescriptor = open(filePath, O_RDONLY);
if (fileDescriptor < 0) {
goto finish;
}
if (fstat(fileDescriptor, &statBuf)) {
goto finish;
}
if (archInfo) {
if (getFileOffsetAndSizeForArch(fileDescriptor, archInfo,
&fileSliceOffset, &fileSliceSize))
{
goto finish;
}
} else {
fileSliceOffset = 0;
fileSliceSize = statBuf.st_size;
}
fileBuf = malloc(fileSliceSize);
if (!fileBuf) {
OSKextLogMemError();
goto finish;
}
if (readFileAtOffset(fileDescriptor, fileSliceOffset,
fileSliceSize, fileBuf))
{
goto finish;
}
if (verifyMachOIsArch(fileBuf, fileSliceSize, archInfo)) {
goto finish;
}
fileImage = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
(UInt8 *) fileBuf, fileSliceSize, kCFAllocatorMalloc);
if (!fileImage) {
goto finish;
}
fileBuf = NULL;
finish:
if (fileBuf) free(fileBuf);
return fileImage;
}
void getPlatformInfo(
KextcacheArgs * toolArgs,
PlatformInfo * platformInfo)
{
io_registry_entry_t entry = 0; CFURLRef parentURL = NULL; CFStringRef prelinkedKernelFilename = NULL;
bzero(platformInfo, sizeof(*platformInfo));
entry = IORegistryEntryFromPath(kIOMasterPortDefault, kIOServicePlane ":/");
if (entry) {
if (KERN_SUCCESS !=
IORegistryEntryGetName(entry, platformInfo->platformName)) {
platformInfo->platformName[0] = 0;
}
IOObjectRelease(entry);
}
entry = IORegistryEntryFromPath(kIOMasterPortDefault,
kIODeviceTreePlane ":/chosen");
if (entry) {
CFTypeRef obj = 0;
obj = IORegistryEntryCreateCFProperty(entry, CFSTR("rootpath"),
kCFAllocatorDefault, kNilOptions);
if (obj && (CFGetTypeID(obj) == CFDataGetTypeID())) {
CFIndex len = CFDataGetLength((CFDataRef) obj);
strlcpy(platformInfo->rootPath, (char *) CFDataGetBytePtr((CFDataRef) obj),
sizeof(platformInfo->rootPath));
platformInfo->rootPath[len] = 0;
} else {
const char *data;
char *ptr = platformInfo->rootPath;
u_long len = 0;
obj = 0;
do {
obj = IORegistryEntryCreateCFProperty(entry, CFSTR("boot-device-path"),
kCFAllocatorDefault, kNilOptions);
if (!obj) {
break;
}
if (CFGetTypeID(obj) == CFDataGetTypeID()) {
data = (char *)CFDataGetBytePtr((CFDataRef) obj);
len = CFDataGetLength((CFDataRef) obj);
} else if (CFGetTypeID(obj) == CFStringGetTypeID()) {
data = CFStringGetCStringPtr((CFStringRef) obj, kCFStringEncodingUTF8);
if (!data) {
break;
}
len = strlen(data) + 1; } else {
break;
}
if (len > sizeof(platformInfo->rootPath)) {
len = sizeof(platformInfo->rootPath);
}
memcpy(ptr, data, len);
ptr += len;
SAFE_RELEASE_NULL(obj);
obj = IORegistryEntryCreateCFProperty(entry, CFSTR("boot-file"),
kCFAllocatorDefault, kNilOptions);
if (!obj) {
break;
}
if (CFGetTypeID(obj) == CFDataGetTypeID()) {
data = (char *)CFDataGetBytePtr((CFDataRef) obj);
len = CFDataGetLength((CFDataRef) obj);
} else if (CFGetTypeID(obj) == CFStringGetTypeID()) {
data = CFStringGetCStringPtr((CFStringRef) obj, kCFStringEncodingUTF8);
if (!data) {
break;
}
len = strlen(data);
} else {
break;
}
if ((ptr - platformInfo->rootPath + len) >=
sizeof(platformInfo->rootPath)) {
len = sizeof(platformInfo->rootPath) -
(ptr - platformInfo->rootPath);
}
memcpy(ptr, data, len);
} while (0);
}
SAFE_RELEASE(obj);
IOObjectRelease(entry);
}
if (!platformInfo->platformName[0] || !platformInfo->rootPath[0]) {
platformInfo->platformName[0] = platformInfo->rootPath[0] = 0;
}
if (toolArgs->needDefaultPrelinkedKernelInfo) {
uint32_t adler32 = 0;
parentURL = toolArgs->prelinkedKernelURL;
adler32 = OSSwapHostToBigInt32(local_adler32(
(u_int8_t *)platformInfo, sizeof(*platformInfo)));
prelinkedKernelFilename = CFStringCreateWithFormat(
kCFAllocatorDefault, 0, CFSTR("%s_%s.%08X"),
_kOSKextPrelinkedKernelBasename, toolArgs->archInfo[0]->name,
adler32);
if (!prelinkedKernelFilename) {
OSKextLogMemError();
goto finish;
}
toolArgs->prelinkedKernelURL = CFURLCreateCopyAppendingPathComponent(
kCFAllocatorDefault, parentURL, prelinkedKernelFilename,
false);
if (!toolArgs->prelinkedKernelURL) {
OSKextLogMemError();
goto finish;
}
}
finish:
SAFE_RELEASE(parentURL);
SAFE_RELEASE(prelinkedKernelFilename);
return;
}
CFDataRef uncompressPrelinkedKernel(
CFDataRef prelinkImage)
{
CFDataRef result = NULL;
CFMutableDataRef uncompressedImage = NULL; const PrelinkedKernelHeader * prelinkHeader = NULL; unsigned char * buf = NULL; vm_size_t bufsize = 0;
vm_size_t uncompsize = 0;
uint32_t adler32 = 0;
prelinkHeader = (PrelinkedKernelHeader *) CFDataGetBytePtr(prelinkImage);
if (prelinkHeader->signature != OSSwapHostToBigInt32('comp')) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Compressed prelinked kernel has invalid signature: 0x%x.",
prelinkHeader->signature);
goto finish;
}
if (prelinkHeader->compressType != OSSwapHostToBigInt32('lzss')) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Compressed prelinked kernel has invalid compressType: 0x%x.",
prelinkHeader->compressType);
goto finish;
}
bufsize = OSSwapBigToHostInt32(prelinkHeader->uncompressedSize);
uncompressedImage = CFDataCreateMutable(kCFAllocatorDefault, bufsize);
if (!uncompressedImage) {
goto finish;
}
CFDataSetLength(uncompressedImage, bufsize);
buf = CFDataGetMutableBytePtr(uncompressedImage);
if (!buf) {
OSKextLogMemError();
goto finish;
}
uncompsize = decompress_lzss(buf, bufsize,
((u_int8_t *)(CFDataGetBytePtr(prelinkImage))) + sizeof(*prelinkHeader),
CFDataGetLength(prelinkImage) - sizeof(*prelinkHeader));
if (uncompsize != bufsize) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Compressed prelinked kernel uncompressed to an unexpected size: %u.",
(unsigned)uncompsize);
goto finish;
}
adler32 = local_adler32((u_int8_t *) buf, bufsize);
if (prelinkHeader->adler32 != OSSwapHostToBigInt32(adler32)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Checksum error for compressed prelinked kernel.");
goto finish;
}
result = CFRetain(uncompressedImage);
finish:
SAFE_RELEASE(uncompressedImage);
return result;
}
CFDataRef compressPrelinkedKernel(
CFDataRef prelinkImage,
PlatformInfo * platformInfo)
{
CFDataRef result = NULL;
CFMutableDataRef compressedImage = NULL; PrelinkedKernelHeader * kernelHeader = NULL; const PrelinkedKernelHeader * kernelHeaderIn = NULL; unsigned char * buf = NULL; unsigned char * bufend = NULL; u_long offset = 0;
vm_size_t bufsize = 0;
vm_size_t compsize = 0;
uint32_t adler32 = 0;
kernelHeaderIn = (const PrelinkedKernelHeader *)
CFDataGetBytePtr(prelinkImage);
if (kernelHeaderIn->signature == OSSwapHostToBigInt('comp')) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Prelinked kernel is already compressed.");
goto finish;
}
offset = sizeof(*kernelHeader);
bufsize = CFDataGetLength(prelinkImage) + offset;
compressedImage = CFDataCreateMutable(kCFAllocatorDefault, bufsize);
if (!compressedImage) {
goto finish;
}
CFDataSetLength(compressedImage, bufsize);
buf = CFDataGetMutableBytePtr(compressedImage);
if (!buf) {
OSKextLogMemError();
goto finish;
}
kernelHeader = (PrelinkedKernelHeader *) buf;
bzero(kernelHeader, sizeof(*kernelHeader));
kernelHeader->signature = OSSwapHostToBigInt32('comp');
kernelHeader->compressType = OSSwapHostToBigInt32('lzss');
adler32 = local_adler32((u_int8_t *)CFDataGetBytePtr(prelinkImage),
CFDataGetLength(prelinkImage));
kernelHeader->adler32 = OSSwapHostToBigInt32(adler32);
kernelHeader->uncompressedSize =
OSSwapHostToBigInt32(CFDataGetLength(prelinkImage));
if (platformInfo) {
strlcpy(kernelHeader->platformName, platformInfo->platformName,
sizeof(kernelHeader->platformName));
memcpy(kernelHeader->rootPath, platformInfo->rootPath,
sizeof(kernelHeader->rootPath));
}
bufend = compress_lzss(buf + offset, bufsize,
(u_int8_t *)CFDataGetBytePtr(prelinkImage),
CFDataGetLength(prelinkImage));
if (!bufend) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Failed to compress prelinked kernel.");
goto finish;
}
compsize = bufend - (buf + offset);
kernelHeader->compressedSize = OSSwapHostToBigInt32(compsize);
CFDataSetLength(compressedImage, bufend - buf);
result = CFRetain(compressedImage);
finish:
SAFE_RELEASE(compressedImage);
return result;
}
ExitStatus createPrelinkedKernel(
KextcacheArgs * toolArgs,
CFDataRef * prelinkedKernelOut,
CFDictionaryRef * prelinkedSymbolsOut,
const NXArchInfo * archInfo,
PlatformInfo * platformInfo)
{
ExitStatus result = EX_OSERR;
CFArrayRef requestedIdentifiers = NULL;
CFArrayRef kextArray = NULL;
CFDataRef kernelImage = NULL;
CFDataRef prelinkedKernel = NULL;
kernelImage = readMachOFileWithURL(toolArgs->kernelURL, archInfo);
if (!kernelImage) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag | kOSKextLogFileAccessFlag,
"Failed to read kernel file.");
goto finish;
}
if (!OSKextSetArchitecture(archInfo)) {
goto finish;
}
if (toolArgs->needLoadedKextInfo) {
(void) waitForIOKitQuiescence();
requestedIdentifiers = OSKextCopyAllRequestedIdentifiers();
if (!requestedIdentifiers) {
goto finish;
}
kextArray = OSKextCopyKextsWithIdentifiers(requestedIdentifiers);
if (!kextArray) {
goto finish;
}
} else {
Boolean fatalOut = false;
CFMutableArrayRef kexts = NULL;
if (!createCFMutableArray(&kexts, &kCFTypeArrayCallBacks)) {
OSKextLogMemError();
goto finish;
}
result = filterKextsForMkext(toolArgs, kexts, archInfo, &fatalOut);
if (result != EX_OK || fatalOut) {
SAFE_RELEASE(kexts);
goto finish;
}
result = EX_OSERR;
if (!CFArrayGetCount(kexts)) {
SAFE_RELEASE(kexts);
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"No kexts found for architecture %s.",
archInfo->name);
goto finish;
}
kextArray = CFArrayCreateCopy(kCFAllocatorDefault, kexts);
if (!kextArray) {
SAFE_RELEASE(kexts);
OSKextLogMemError();
goto finish;
}
SAFE_RELEASE(kexts);
}
prelinkedKernel = OSKextCreatePrelinkedKernel(kernelImage, kextArray,
toolArgs->volumeRootURL, toolArgs->prelinkedKernelErrorRequired,
toolArgs->skipAuthentication, toolArgs->printTestResults,
prelinkedSymbolsOut);
if (!prelinkedKernel) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Failed to generate prelinked kernel.");
if (toolArgs->prelinkedKernelErrorRequired) {
result = EX_OSERR;
}
goto finish;
}
if (toolArgs->compress) {
*prelinkedKernelOut = compressPrelinkedKernel(prelinkedKernel, platformInfo);
} else {
*prelinkedKernelOut = CFRetain(prelinkedKernel);
}
if (!*prelinkedKernelOut) {
goto finish;
}
result = EX_OK;
finish:
SAFE_RELEASE(kernelImage);
SAFE_RELEASE(requestedIdentifiers);
SAFE_RELEASE(kextArray);
SAFE_RELEASE(prelinkedKernel);
return result;
}
ExitStatus savePrelinkedKernel(
KextcacheArgs * toolArgs,
CFDataRef prelinkedKernel,
CFDictionaryRef prelinkedSymbols)
{
ExitStatus result = EX_OSERR;
int fileDescriptor = 0;
mode_t real_umask = 0;
char tmpPath[MAXPATHLEN];
char finalPath[MAXPATHLEN];
struct timeval cacheFileTimes[2];
boolean_t updateModTime = false;
SaveFileContext saveFileContext;
if (toolArgs->needDefaultPrelinkedKernelInfo) {
if (!_OSKextCreateFolderForCacheURL(toolArgs->prelinkedKernelURL)) {
goto finish;
}
}
if (!CFURLGetFileSystemRepresentation(toolArgs->prelinkedKernelURL,
true, (UInt8 *)tmpPath, sizeof(finalPath))) {
OSKextLogStringError( NULL);
goto finish;
}
strlcpy(finalPath, tmpPath, sizeof(finalPath));
if (strlcat(tmpPath, ".XXXX", sizeof(tmpPath)) >= sizeof(tmpPath)) {
OSKextLogStringError( NULL);
goto finish;
}
fileDescriptor = mkstemp(tmpPath);
if (-1 == fileDescriptor) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't create %s - %s.",
tmpPath, strerror(errno));
goto finish;
}
real_umask = umask(0);
umask(real_umask);
if (-1 == fchmod(fileDescriptor, MKEXT_PERMS & ~real_umask)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't set permissions on %s - %s.",
tmpPath, strerror(errno));
}
result = writeToFile(fileDescriptor,
CFDataGetBytePtr(prelinkedKernel),
CFDataGetLength(prelinkedKernel));
if (result != EX_OK) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't save prelinked kernel - %s.",
strerror(errno));
goto finish;
}
if (close(fileDescriptor)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Failed to close prelinked kernel file - %s.",
strerror(errno));
goto finish;
}
if (toolArgs->haveFolderMtime && CFArrayGetCount(toolArgs->repositoryURLs)) {
CFURLRef firstURL = (CFURLRef)CFArrayGetValueAtIndex(
toolArgs->repositoryURLs, 0);
struct stat newStatBuffer;
struct timespec newModTime;
struct timespec origModTime =
toolArgs->firstFolderStatBuffer.st_mtimespec;
struct timeval kernelTimeval;
struct timeval kextTimeval;
result = statURL(firstURL, &newStatBuffer);
if (result != EX_OK) {
goto finish;
}
newModTime = newStatBuffer.st_mtimespec;
if ((newModTime.tv_sec != origModTime.tv_sec) ||
(newModTime.tv_nsec != origModTime.tv_nsec)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Source directory has changed since starting; "
"not saving cache file %s.",
finalPath);
result = kKextcacheExitStale;
goto finish;
}
result = statURL(toolArgs->kernelURL, &newStatBuffer);
if (result != EX_OK) {
goto finish;
}
origModTime = toolArgs->kernelStatBuffer.st_mtimespec;
newModTime = newStatBuffer.st_mtimespec;
if ((newModTime.tv_sec != origModTime.tv_sec) ||
(newModTime.tv_nsec != origModTime.tv_nsec)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Source kernel has changed since starting; "
"not saving cache file %s.",
finalPath);
result = kKextcacheExitStale;
goto finish;
}
TIMESPEC_TO_TIMEVAL(&kextTimeval, &toolArgs->firstFolderStatBuffer.st_mtimespec);
TIMESPEC_TO_TIMEVAL(&kernelTimeval, &toolArgs->kernelStatBuffer.st_mtimespec);
if (timercmp(&kextTimeval, &kernelTimeval, >)) {
newStatBuffer = toolArgs->firstFolderStatBuffer;
}
TIMESPEC_TO_TIMEVAL(&cacheFileTimes[0], &newStatBuffer.st_atimespec);
TIMESPEC_TO_TIMEVAL(&cacheFileTimes[1], &newStatBuffer.st_mtimespec);
cacheFileTimes[1].tv_sec++;
updateModTime = true;
}
if (rename(tmpPath, finalPath) != 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't rename tmp prelinked kernel file - %s.",
strerror(errno));
result = EX_OSERR;
goto finish;
}
if (updateModTime) {
if (utimes(finalPath, cacheFileTimes)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't update mod time of %s - %s.",
finalPath, strerror(errno));
}
}
if (prelinkedSymbols) {
saveFileContext.saveDirURL = toolArgs->symbolDirURL;
saveFileContext.overwrite = true;
saveFileContext.fatal = false;
CFDictionaryApplyFunction(prelinkedSymbols, &saveFile, &saveFileContext);
if (saveFileContext.fatal) {
goto finish;
}
}
result = EX_OK;
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogGeneralFlag | kOSKextLogArchiveFlag,
"Created prelinked kernel %s.", finalPath);
finish:
return result;
}
static int upstat(const char *path, struct stat *sb, struct statfs *sfs)
{
int rval = ELAST+1;
char buf[PATH_MAX], *tpath = buf;
struct stat defaultsb;
if (strlcpy(buf, path, PATH_MAX) > PATH_MAX) goto finish;
if (!sb) sb = &defaultsb;
while ((rval = stat(tpath, sb)) == -1 && errno == ENOENT) {
if (tpath[0] == '.' && tpath[1] == '\0') goto finish;
if (tpath[0] == '/' && tpath[1] == '\0') goto finish;
tpath = dirname(tpath); }
if (sfs)
rval = statfs(tpath, sfs);
finish:
if (rval) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
"Couldn't find volume for %s.", path);
}
return rval;
}
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
static int getVolumeUUID(const char *volPath, uuid_t vol_uuid)
{
int rval = ENODEV;
DADiskRef dadisk = NULL;
CFDictionaryRef dadesc = NULL;
CFUUIDRef volUUID; CFUUIDBytes uuidBytes;
COMPILE_TIME_ASSERT(sizeof(CFUUIDBytes) == sizeof(uuid_t));
dadisk = createDiskForMount(NULL, volPath);
if (!dadisk) goto finish;
dadesc = DADiskCopyDescription(dadisk);
if (!dadesc) goto finish;
volUUID = CFDictionaryGetValue(dadesc, kDADiskDescriptionVolumeUUIDKey);
if (!volUUID) goto finish;
uuidBytes = CFUUIDGetUUIDBytes(volUUID);
memcpy(vol_uuid, &uuidBytes.byte0, sizeof(uuid_t));
rval = 0;
finish:
if (dadesc) CFRelease(dadesc);
if (dadisk) CFRelease(dadisk);
return rval;
}
int takeVolumeForPaths(char *volPath)
{
int bsderr, rval = ELAST + 1;
struct stat volsb;
bsderr = stat(volPath, &volsb);
if (bsderr) goto finish;
rval = takeVolumeForPath(volPath);
finish:
if (bsderr) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
"Couldn't lock paths on volume %s.", volPath);
rval = bsderr;
}
return rval;
}
#define WAITFORLOCK 1
static int takeVolumeForPath(const char *path)
{
int rval = ELAST + 1;
kern_return_t macherr = KERN_SUCCESS;
int lckres = 0;
struct statfs sfs;
const char *volPath;
mach_port_t taskport = MACH_PORT_NULL;
if (sLockPort) {
return EALREADY; }
if (geteuid() != 0) {
rval = 0;
goto finish;
}
if (!sKextdPort) {
macherr=bootstrap_look_up(bootstrap_port,KEXTD_SERVER_NAME,&sKextdPort);
if (macherr) goto finish;
}
if ((rval = upstat(path, NULL, &sfs))) goto finish;
volPath = sfs.f_mntonname;
if ((rval = getVolumeUUID(volPath, s_vol_uuid))) {
goto finish;
}
taskport = mach_task_self();
if (taskport == MACH_PORT_NULL) goto finish;
macherr = mach_port_allocate(taskport, MACH_PORT_RIGHT_RECEIVE, &sLockPort);
if (macherr) goto finish;
macherr = kextmanager_lock_volume(sKextdPort, sLockPort, s_vol_uuid,
!WAITFORLOCK, &lckres);
if (macherr) goto finish;
while (lckres == EAGAIN) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"kextd wasn't ready; waiting 10 seconds and trying again");
sleep(10);
macherr = kextmanager_lock_volume(sKextdPort, sLockPort, s_vol_uuid,
!WAITFORLOCK, &lckres);
if (macherr) goto finish;
}
if (lckres == EBUSY || lckres == EAGAIN) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"%s locked; waiting for lock.", volPath);
macherr = kextmanager_lock_volume(sKextdPort, sLockPort, s_vol_uuid,
WAITFORLOCK, &lckres);
if (macherr) goto finish;
if (lckres == 0) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Lock acquired; proceeding.");
}
}
if (lckres == ENOENT) {
struct stat sb;
rval = stat(volPath, &sb);
} else {
rval = lckres;
}
finish:
if (sLockPort != MACH_PORT_NULL && (lckres != 0 || macherr)) {
mach_port_mod_refs(taskport, sLockPort, MACH_PORT_RIGHT_RECEIVE, -1);
sLockPort = MACH_PORT_NULL;
}
if (macherr == BOOTSTRAP_UNKNOWN_SERVICE) {
rval = 0;
} else if (macherr) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Couldn't lock %s: %s (%d).", path,
safe_mach_error_string(macherr), macherr);
rval = macherr;
} else {
if (rval == -1) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogFileAccessFlag,
"Couldn't lock %s.", path);
rval = errno;
} else if (rval) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Couldn't lock %s: %s", path, strerror(rval));
}
}
return rval;
}
int putVolumeForPath(const char *path, int status)
{
int rval = KERN_SUCCESS;
if (sLockPort == MACH_PORT_NULL)
goto finish;
rval = kextmanager_unlock_volume(sKextdPort, sLockPort, s_vol_uuid, status);
mach_port_mod_refs(mach_task_self(),sLockPort,MACH_PORT_RIGHT_RECEIVE,-1);
sLockPort = MACH_PORT_NULL;
finish:
if (rval) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Couldn't unlock volume for %s: %s (%d).",
path, safe_mach_error_string(rval), rval);
}
return rval;
}
void usage(UsageLevel usageLevel)
{
fprintf(stderr,
"usage: %1$s <mkext_flag> [options] [--] [kext or directory] ...\n"
" %1$s -system-mkext [options] [--] [kext or directory] ...\n"
" %1$s -prelinked-kernel <filename> [options] [--] [kext or directory]\n"
" %1$s -system-prelinked-kernel\n"
" %1$s [options] -prelinked-kernel\n"
" %1$s -update-volume <volume> [options]\n"
" %1$s -system-caches [options]\n"
"\n",
progname);
if (usageLevel == kUsageLevelBrief) {
fprintf(stderr, "use %s -%s for an explanation of each option\n",
progname, kOptNameHelp);
}
if (usageLevel == kUsageLevelBrief) {
return;
}
fprintf(stderr, "-%s <filename>: create an mkext (latest supported version)\n",
kOptNameMkext);
fprintf(stderr, "-%s <filename>: create an mkext (version 2)\n",
kOptNameMkext2);
fprintf(stderr, "-%s <filename> (-%c): create an mkext (version 1)\n",
kOptNameMkext1, kOptMkext);
fprintf(stderr, "-%s (-%c): update the startup mkext for the system\n",
kOptNameSystemMkext, kOptSystemMkext);
fprintf(stderr, "-%s [<filename>] (-%c):\n"
" create/update prelinked kernel (must be last if no filename given)\n",
kOptNamePrelinkedKernel, kOptPrelinkedKernel);
fprintf(stderr, "-%s:\n"
" create/update system prelinked kernel\n",
kOptNameSystemPrelinkedKernel);
fprintf(stderr, "-%s <volume> (-%c): update system kext caches for <volume>\n",
kOptNameUpdate, kOptUpdate);
#if 0
fprintf(stderr, "-%c <volume>:\n"
" check system kext caches for <volume> (nonzero exit if out of date)\n",
kOptCheckUpdate);
#endif
fprintf(stderr, "-%s: update system kext info caches for the root volume\n",
kOptNameSystemCaches);
fprintf(stderr, "\n");
fprintf(stderr,
"kext or directory: Add the kext or all kexts in directory to cache\n");
fprintf(stderr, "-%s <bundle_id> (-%c):\n"
" add the kext whose CFBundleIdentifier is <bundle_id>\n",
kOptNameBundleIdentifier, kOptBundleIdentifier);
fprintf(stderr, "-%s <volume>:\n"
" Save kext paths in an mkext archive or prelinked kernel "
" relative to <volume>\n",
kOptNameVolumeRoot);
fprintf(stderr, "-%s <kernel_filename> (-%c): Use kernel_filename for a prelinked kernel\n",
kOptNameKernel, kOptKernel);
fprintf(stderr, "-%s (-%c): Include all kexts ever loaded in prelinked kernel\n",
kOptNameAllLoaded, kOptAllLoaded);
fprintf(stderr, "-%s (-%c): Update volumes even if they look up to date\n",
kOptNameForce, kOptForce);
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c): Add 'Local-Root' kexts from directories to an mkext file\n",
kOptNameLocalRoot, kOptLocalRoot);
fprintf(stderr, "-%s (-%c): Add 'Local-Root' kexts to an mkext file\n",
kOptNameLocalRootAll, kOptLocalRootAll);
fprintf(stderr, "-%s (-%c): Add 'Network-Root' kexts from directories to an mkext file\n",
kOptNameNetworkRoot, kOptNetworkRoot);
fprintf(stderr, "-%s (-%c): Add 'Network-Root' kexts to an mkext file\n",
kOptNameNetworkRootAll, kOptNetworkRootAll);
fprintf(stderr, "-%s (-%c): Add 'Safe Boot' kexts from directories to an mkext file\n",
kOptNameSafeBoot, kOptSafeBoot);
fprintf(stderr, "-%s (-%c): Add 'Safe Boot' kexts to an mkext file\n",
kOptNameSafeBootAll, kOptSafeBootAll);
fprintf(stderr, "\n");
fprintf(stderr, "-%s <archname>:\n"
" include architecture <archname> in created cache(s)\n",
kOptNameArch);
fprintf(stderr, "-%c: run at low priority\n",
kOptLowPriorityFork);
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c): quiet mode: print no informational or error messages\n",
kOptNameQuiet, kOptQuiet);
fprintf(stderr, "-%s [ 0-6 | 0x<flags> ] (-%c):\n"
" verbose mode; print info about analysis & loading\n",
kOptNameVerbose, kOptVerbose);
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c):\n"
" print diagnostics for kexts with problems\n",
kOptNameTests, kOptTests);
fprintf(stderr, "-%s (-%c): don't authenticate kexts (for use during development)\n",
kOptNameNoAuthentication, kOptNoAuthentication);
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c): print this message and exit\n",
kOptNameHelp, kOptHelp);
return;
}