#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <architecture/byte_order.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Kernel/libsa/mkext.h>
__private_extern__ u_int8_t *
encodeLZSS(u_int8_t *dstP, long dstLen, u_int8_t *srcP, long srcLen);
__private_extern__ void
checkLZSS(u_int8_t *codeP, u_int8_t *srcEnd, u_int8_t *textP, u_int32_t tLen);
__private_extern__ void
find_arch(u_int8_t **dataP, off_t *sizeP, cpu_type_t in_cpu,
cpu_subtype_t in_cpu_subtype, u_int8_t *data_ptr, off_t filesize);
__private_extern__ int
get_arch_from_flag(char *name, cpu_type_t *cpuP, cpu_subtype_t *subcpuP);
#define KEXT_PATH "/System/Library/Extensions"
#define KEXT_BUNDLEEXTN ".kext"
#define KEXT_PACKAGETYPE "KEXT"
#define INFO_PATH "Contents/Info.plist"
#define kMkxtDefaultSize (6 * 1024 * 1024)
#define kKEXTPathStr CFSTR(KEXT_PATH)
#define kKEXTBundleExtnStr CFSTR(KEXT_BUNDLEEXTN)
#define kKEXTPackageTypeStr CFSTR(KEXT_PACKAGETYPE)
#define kKEXTPackageTypeKey CFSTR("CFBundlePackageType")
#define CFDynamicCast(type, ref) \
((type ## Ref) ((CFGetTypeID(ref) == type ## GetTypeID())? ref : 0))
static volatile void bomb(int code, const char *msg, ...);
#define checkFatal(expr, msg) do { \
if ( !(expr) ) { \
bomb msg; \
} \
} while(0)
#define ZERO_POINT ((UInt8 *) sKEXTs)
#define ADLER_POINT ((UInt8 *) &sKEXTs->version)
static const char *sCmdName = NULL;
static int sVerbose = 0;
static mkext_header *sKEXTs = 0;
static u_int8_t *sKEXTEnd = 0;
static cpu_type_t sCPU;
static cpu_subtype_t sSubType;
static const char *sOutFilename;
static Boolean sLocalRoot = FALSE;
static Boolean sNetworkRoot = FALSE;
struct validationContext {
CFMutableDictionaryRef dict;
Boolean isChild;
};
struct compressContext {
mkext_kext *curkext;
u_int8_t *dst;
};
static volatile void bomb(int code, const char *msg, ...)
{
char buf[1024];
va_list ap;
va_start(ap, msg);
vsnprintf(buf, sizeof(buf), msg, ap);
va_end(ap);
fprintf(stderr, "%s: %s\n", sCmdName, buf);
fflush(stderr);
exit(code);
}
static volatile void verbosePrintf(int level, const char *msg, ...)
{
if (level <= sVerbose) {
va_list ap;
va_start(ap, msg);
vprintf(msg, ap);
va_end(ap);
fflush(stdout);
}
}
static volatile void usage(void)
{
static const char *usageMessage =
"Usage: %s [-v [1-3]] [-t arch] [-l] [-n] [-o <filename>]\n"
" [-d <directory> ...] [<kextlist> ...]\n"
" -v run in verbose mode (may specify level 1, 2, or 3)\n"
" -t thin KEXT executables to one architecture only\n"
" -l include only KEXTs required for local disk boot\n"
" -n include only KEXTs required for network boot\n"
" (-l and -n may be combined)\n"
" -d directory to scan for kernel extensions\n"
" -o write archive to filename (.mkext or .mke extension)\n";
fprintf(stderr, usageMessage, sCmdName);
exit(EX_USAGE);
}
static CFURLRef URLCreateWithFileSystemPath(const char *dirname)
{
CFURLRef relURL, absURL;
relURL = CFURLCreateFromFileSystemRepresentation
(NULL, dirname, strlen(dirname), TRUE);
if (!relURL)
return relURL;
absURL = CFURLCopyAbsoluteURL(relURL);
CFRelease(relURL);
return absURL;
}
static void ArrayAppendDirPath(CFMutableArrayRef dirList, const char *dirArg)
{
CFURLRef url;
url = URLCreateWithFileSystemPath(dirArg);
checkFatal(url, (EX_TEMPFAIL, "Couldn't create url - no memory?"));
CFArrayAppendValue(dirList, url);
CFRelease(url);
}
static void URLAbsoluteGetPath(CFURLRef url, char *buf, int len)
{
CFURLRef absURL;
CFStringRef urlPath;
absURL = CFURLCopyAbsoluteURL(url); assert(absURL);
urlPath = CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle); assert(urlPath);
CFRelease(absURL);
CFStringGetCString(urlPath, buf, len, kCFStringEncodingMacRoman);
CFRelease(urlPath);
}
static void BundleGetInfoPlistPath(CFBundleRef bundle, char *buf, int len)
{
CFURLRef dataURL;
int baseLen;
dataURL = CFBundleCopyBundleURL(bundle); assert(dataURL);
URLAbsoluteGetPath(dataURL, buf, len - 1);
CFRelease(dataURL);
baseLen = strlen(buf);
buf[baseLen] = '/';
baseLen++;
buf[baseLen] = '\0';
strncpy(buf + baseLen, INFO_PATH, len - baseLen);
}
static CFArrayRef createBundleList(int argc, const char *argv[])
{
CFMutableArrayRef bundleList;
CFMutableArrayRef baseDirList;
char c;
int i;
Boolean baseDirSpecified = FALSE;
baseDirList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
checkFatal(baseDirList,
(EX_TEMPFAIL, "Couldn't create base dir array - no memory?"));
bundleList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
checkFatal(bundleList,
(EX_TEMPFAIL, "Couldn't Bundle array - no memory?"));
sCPU = CPU_TYPE_ANY;
sSubType = CPU_SUBTYPE_MULTIPLE;
sOutFilename = NULL;
while ((c = getopt(argc, argv, "vt:d:o:ln?")) != -1) {
switch(c) {
case 'd':
ArrayAppendDirPath(baseDirList, optarg);
baseDirSpecified = TRUE;
break;
case 'l':
sLocalRoot = TRUE;
break;
case 'n':
sNetworkRoot = TRUE;
break;
case 'o': sOutFilename = optarg; break;
case 't': get_arch_from_flag(optarg, &sCPU, &sSubType); break;
case 'v':
{
const char * next = argv[optind];
if ((next[0] == '1' || next[0] == '2' || next[0] == '3') &&
next[1] == '\0') {
sVerbose = atoi(argv[optind]);
optind++;
} else if (next[0] == '-') {
sVerbose = 1;
} else {
usage();
}
break;
}
case '?':
default:
usage();
}
}
argc -= optind;
argv += optind;
for (i = 0; i < argc; i++) {
CFURLRef url;
CFBundleRef kext;
url = URLCreateWithFileSystemPath(argv[i]);
checkFatal(url, (EX_TEMPFAIL, "Couldn't create url - no memory?"));
kext = CFBundleCreate(NULL, url); CFRelease(url);
checkFatal(kext, (EX_TEMPFAIL, "%s does not exist", argv[i]));
CFArrayAppendValue(bundleList, kext);
CFRelease(kext);
}
if (!baseDirSpecified && !CFArrayGetCount(bundleList)) {
ArrayAppendDirPath(baseDirList, KEXT_PATH);
baseDirSpecified = TRUE;
}
if (baseDirSpecified) {
int count = CFArrayGetCount(baseDirList);
for (i = 0; i < count; i++) {
CFTypeRef obj = CFArrayGetValueAtIndex(baseDirList, i);
CFURLRef dirUrl = CFDynamicCast(CFURL, obj);
CFArrayRef kextList = 0;
if (dirUrl) {
kextList = CFBundleCreateBundlesFromDirectory
(NULL, dirUrl, kKEXTBundleExtnStr);
}
if (kextList) {
CFArrayAppendArray(bundleList, kextList,
CFRangeMake(0, CFArrayGetCount(kextList)));
CFRelease(kextList);
}
}
CFRelease(baseDirList);
}
checkFatal(CFArrayGetCount(bundleList),
(EX_NOINPUT, "No kernel extensions found"));
return bundleList;
}
static void validateBundle(const void *val, void *context)
{
CFBundleRef kext = CFDynamicCast(CFBundle, (CFTypeRef) val);
CFTypeRef rawValue; CFStringRef bundleIdent = 0;
CFStringRef bundleType = 0;
char bundle_ident[120];
CFStringRef execString = 0;
CFURLRef execUrl, pluginDir;
CFArrayRef childKexts;
CFIndex childCount;
struct validationContext *c = context;
char buf[MAXPATHLEN+1];
int fd;
if (!kext)
return;
bundleIdent = CFBundleGetIdentifier(kext);
if (!bundleIdent || CFDictionaryGetValue(c->dict, bundleIdent)) {
fprintf(stderr, "Error: Can't get bundle identifier.\n");
return; }
if (!CFStringGetCString(bundleIdent, bundle_ident, sizeof(bundle_ident) / sizeof(char),
kCFStringEncodingASCII)) {
strcpy(bundle_ident, "(unknown)");
}
if (sLocalRoot || sNetworkRoot) {
do {
CFStringRef requiredString = 0;
rawValue = CFBundleGetValueForInfoDictionaryKey(kext,
CFSTR("OSBundleRequired"));
if (rawValue) {
requiredString = CFDynamicCast(CFString, rawValue);
}
if (!requiredString) {
verbosePrintf(3,
"Skipping bundle %s; no OSBundleRequired key.\n",
bundle_ident);
goto do_children;
} else if (CFStringCompare(requiredString,
CFSTR("Root"), 0) == kCFCompareEqualTo ||
CFStringCompare(requiredString,
CFSTR("Console"), 0) == kCFCompareEqualTo) {
continue;
} else if (sLocalRoot &&
CFStringCompare(requiredString, CFSTR("Local-Root"), 0) ==
kCFCompareEqualTo) {
continue;
} else if (sNetworkRoot &&
CFStringCompare(requiredString, CFSTR("Network-Root"), 0) ==
kCFCompareEqualTo) {
continue;
} else {
char required_string[120];
if (!CFStringGetCString(requiredString, required_string,
sizeof(required_string) / sizeof(char),
kCFStringEncodingASCII)) {
strcpy(required_string, "(unknown)");
}
verbosePrintf(3,
"Skipping bundle %s; OSBundleRequired key is \"%s\".\n",
bundle_ident, required_string);
goto do_children;
}
} while (0);
}
rawValue = CFBundleGetValueForInfoDictionaryKey(kext, kKEXTPackageTypeKey);
if (rawValue) {
bundleType = CFDynamicCast(CFString, rawValue);
}
if (!bundleType || !CFEqual(bundleType, kKEXTPackageTypeStr)) {
return; }
BundleGetInfoPlistPath(kext, buf, sizeof(buf));
fd = open(buf, O_RDONLY, 0);
if (-1 == fd) {
goto do_children;
}
close(fd);
rawValue = CFBundleGetValueForInfoDictionaryKey(kext,
CFSTR("CFBundleExecutable"));
if (rawValue) {
execString = CFDynamicCast(CFString, rawValue);
}
execUrl = CFBundleCopyExecutableURL(kext);
if (execString && !execUrl) {
fprintf(stderr,
"Error: Bundle %s claims an executable but has none.\n",
bundle_ident);
goto do_children;
}
if (execUrl) {
URLAbsoluteGetPath(execUrl, buf, sizeof(buf) - 1);
CFRelease(execUrl);
if (buf && *buf) {
int cc;
off_t size;
fd = open(buf, O_RDONLY, 0);
if (-1 == fd) { goto do_children;
}
cc = read(fd, buf, 512); close(fd);
if (512 != cc) { goto do_children;
}
find_arch(NULL, &size, sCPU, sSubType, buf, 512);
if (!size) { goto do_children;
}
}
}
CFDictionarySetValue(c->dict, bundleIdent, kext);
do_children:
if (c->isChild)
return;
pluginDir = CFBundleCopyBuiltInPlugInsURL(kext);
if (!pluginDir)
return;
childKexts = CFBundleCreateBundlesFromDirectory
(NULL, pluginDir, kKEXTBundleExtnStr);
CFRelease(pluginDir);
if (!childKexts)
return;
if ( (childCount = CFArrayGetCount(childKexts)) ) {
struct validationContext childContext;
childContext.dict = c->dict;
childContext.isChild = TRUE;
CFArrayApplyFunction(childKexts,
CFRangeMake(0, childCount),
validateBundle,
&childContext);
}
CFRelease(childKexts);
return;
}
static CFDictionaryRef validateBundleList(CFArrayRef bundleList)
{
struct validationContext context;
CFMutableDictionaryRef bundleDict;
bundleDict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
checkFatal(bundleDict,
(EX_TEMPFAIL, "Couldn't create bundle array - no memory?"));
context.dict = bundleDict;
context.isChild = FALSE;
CFArrayApplyFunction(bundleList,
CFRangeMake(0, CFArrayGetCount(bundleList)),
validateBundle,
&context);
checkFatal(CFDictionaryGetCount(bundleDict),
(EX_TEMPFAIL, "Couldn't find any valid bundles to cache"));
return bundleDict;
}
static u_int8_t *
compressFile(u_int8_t *dst, mkext_file *file, char *fileName)
{
struct stat statbuf;
off_t size;
u_int8_t *dstend, *src, *data;
int fd;
memset(file, '\0', sizeof(*file));
if (!fileName || !*fileName)
return dst;
fd = open(fileName, O_RDONLY, 0);
if (-1 == fd)
return dst;
checkFatal(-1 != fstat(fd, &statbuf),
(EX_NOINPUT, "Can't stat file %s - %s", fileName, strerror(errno)));
if (!statbuf.st_size) {
close(fd);
return dst;
}
src = mmap(0, (size_t) statbuf.st_size, PROT_READ, MAP_FILE, fd, 0);
checkFatal(-1 != (int) src,
(EX_SOFTWARE, "Can't map file %s - %s", fileName, strerror(errno)));
find_arch(&data, &size, sCPU, sSubType, src, statbuf.st_size);
if (!size) {
verbosePrintf(1, "Couldn't find valid architecture for %s\n", fileName);
munmap(src, statbuf.st_size);
close(fd);
return dst;
}
verbosePrintf(2, "Compressing %s %ld => ", fileName, (size_t) size);
dstend = compress_lzss(dst, sKEXTEnd - dst, data, size);
checkFatal(dstend,
(EX_SOFTWARE, "Too many kexts to fit in mkext"));
file->offset = NXSwapHostLongToBig(dst - ZERO_POINT);
file->realsize = NXSwapHostLongToBig((size_t) size);
file->compsize = NXSwapHostLongToBig(dstend - dst);
file->modifiedsecs = NXSwapHostLongToBig(statbuf.st_mtimespec.tv_sec);
if (dstend >= dst + size) {
file->compsize = 0;
memcpy(dst, data, (size_t) size);
dstend = dst + size;
}
if (file->compsize && 3 <= sVerbose) {
u_int8_t *chkbuf = (u_int8_t *)malloc((size_t) size);
size_t chklen;
chklen = decompress_lzss(chkbuf, dst, dstend - dst);
checkFatal(chklen == (size_t) size, (EX_SOFTWARE,
"Internal error, decompressed size expected %ld got %d",
(size_t) size, chklen));
checkFatal(0 == memcmp(chkbuf, data, (size_t) size),
(EX_SOFTWARE, "Internal error, decompressed not same as input"));
free(chkbuf);
}
verbosePrintf(2, "%ld\n", NXSwapBigLongToHost(file->compsize));
checkFatal(-1 != munmap(src, (size_t) statbuf.st_size),
(EX_SOFTWARE, "Can't unmap memory - %s", strerror(errno)));
close(fd);
return dstend;
}
static void
compressAndSaveBundle(const void *key, const void *val, void *context)
{
char curfile[MAXPATHLEN+1];
CFBundleRef kext;
CFURLRef dataURL;
struct compressContext *current = context;
u_int8_t *dst = current->dst;
kext = CFDynamicCast(CFBundle, (CFTypeRef) val); assert(kext);
BundleGetInfoPlistPath(kext, curfile, sizeof(curfile));
dst = compressFile(dst, ¤t->curkext->plist, curfile);
checkFatal(dst, (EX_NOPERM,
"Can't open info file %s - %s", curfile, strerror(errno)));
dataURL = CFBundleCopyExecutableURL(kext);
if (dataURL) {
URLAbsoluteGetPath(dataURL, curfile, sizeof(curfile) - 1);
CFRelease(dataURL);
}
else
*curfile = '\0'; dst = compressFile(dst, ¤t->curkext->module, curfile);
current->dst = dst;
current->curkext++;
}
static void compressBundleDict(CFDictionaryRef validBundles)
{
CFIndex bundleCount = CFDictionaryGetCount(validBundles);
struct compressContext context;
kern_return_t ret;
vm_address_t driverVM;
checkFatal(bundleCount,
(EX_NOINPUT, "Couldn't find any valid bundles to bundle up"));
ret = vm_allocate(mach_task_self(),
&driverVM, kMkxtDefaultSize, VM_FLAGS_ANYWHERE);
checkFatal(ret == KERN_SUCCESS, (EX_OSERR,
"Failed to allocate address space - %s", mach_error_string(ret)));
sKEXTs = (mkext_header *) driverVM;
sKEXTEnd = (u_int8_t *) (driverVM + kMkxtDefaultSize);
sKEXTs->magic = NXSwapHostIntToBig(MKEXT_MAGIC);
sKEXTs->signature = NXSwapHostIntToBig(MKEXT_SIGN);
sKEXTs->version = NXSwapHostIntToBig(0x01008000); sKEXTs->numkexts = NXSwapHostIntToBig(bundleCount);
sKEXTs->cputype = NXSwapHostIntToBig(sCPU);
sKEXTs->cpusubtype = NXSwapHostIntToBig(sSubType);
context.curkext = &sKEXTs->kext[0];
context.dst = (u_int8_t *) &sKEXTs->kext[bundleCount];
CFDictionaryApplyFunction(validBundles, compressAndSaveBundle, &context);
sKEXTs->length = NXSwapHostIntToBig(context.dst - ZERO_POINT);
sKEXTs->adler32 =
NXSwapHostIntToBig(adler32(ADLER_POINT, context.dst - ADLER_POINT));
}
static void outputFile(void)
{
size_t length = NXSwapBigIntToHost(sKEXTs->length);
char filename[MAXPATHLEN];
if (sOutFilename && sOutFilename[0] != '\0') {
int fd;
ssize_t res;
strncpy(filename, sOutFilename, sizeof(filename));
fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
checkFatal(-1 != fd,
(EX_CANTCREAT, "Can't create %s - %s", filename, strerror(errno)));
res = write(fd, sKEXTs, length);
close(fd);
checkFatal(-1 != res,
(EX_IOERR, "Can't write %s - %s", filename, strerror(errno)));
}
else
strcpy(filename, "(file unspecified)");
verbosePrintf(1, "%s: %s contains %d kexts for %d bytes with crc 0x%x\n",
sCmdName,
filename,
NXSwapBigIntToHost(sKEXTs->numkexts),
length,
NXSwapBigIntToHost(sKEXTs->adler32));
}
int main (int argc, const char *argv[])
{
CFArrayRef bundleList;
CFDictionaryRef bundleDict;
if ( (sCmdName = strrchr(argv[0], '/')) )
sCmdName++;
else
sCmdName = argv[0];
bundleList = createBundleList(argc, argv); assert(bundleList);
bundleDict = validateBundleList(bundleList); assert(bundleDict);
CFRelease(bundleList);
compressBundleDict(bundleDict); assert(sKEXTs);
CFRelease(bundleDict);
outputFile();
verbosePrintf(3, "%s: struct File = %d, KEXT = %d, header = %d\n",
sCmdName, sizeof(mkext_file), sizeof(mkext_kext), sizeof(mkext_header));
exit(0);
}