#include "bootroot.h"
#include <bootfiles.h>
#include <err.h>
#include <stdio.h>
#include <strings.h>
#include <sys/param.h> // MIN()
#include <sys/stat.h>
#include <sysexits.h>
#include <CoreFoundation/CoreFoundation.h>
void usage(int exval) __attribute__((noreturn));
void usage(int exval)
{
fprintf(stderr,
"Usage: brtest update <vol> [-f]\n"
" brtest listboots <vol>\n"
" brtest erasefiles <srcVol> <bootDev> [-f]\n"
" brtest copyfiles <src> <bootDev>[/<dir>] [<BlessStyle>]\n"
" brtest copyfiles <src> <root> /<dmg> <tgt>[/<dir>] [<BS>]\n"
" (/<dmg> is relative to <root>)\n"
);
exit(exval);
}
int
update(CFURLRef volURL, int argc, char *argv[])
{
Boolean force = false;
if (argc == 2) {
if (argv[1][0] == '-' && argv[1][1] == 'f') {
force = true;
} else {
return EINVAL;
}
}
return BRUpdateBootFiles(volURL, force);
}
#define DEVMAXPATHSIZE 128
int
listboots(char *volpath, CFURLRef volURL)
{
int result;
CFArrayRef boots;
CFIndex i, bcount = 0;
boots = BRCopyActiveBootPartitions(volURL);
if (!boots) {
printf("%s: no boot partitions\n", volpath);
result = 0;
goto finish;
}
printf("boot support for %s:\n", volpath);
bcount = CFArrayGetCount(boots);
for (i = 0; i < bcount; i++) {
CFShow(CFArrayGetValueAtIndex(boots, i)); }
result = 0;
finish:
if (boots) CFRelease(boots);
return result;
}
int
copyfiles(CFURLRef srcVol, int argc, char *argv[])
{
int result = ELAST + 1;
char *targetSpec, *tdir, *blessarg = NULL;
char *hostpath, *dmgpath, helperName[DEVMAXPATHSIZE];
char path[PATH_MAX];
struct stat sb;
CFArrayRef helpers = NULL;
CFURLRef hostVol = NULL;
CFStringRef bootDev = NULL;
CFURLRef rootDMG = NULL;
CFStringRef rootDMGURLStr; CFStringRef bootArgs = NULL;
CFMutableDictionaryRef plistOverrides = NULL;
CFURLRef targetDir = NULL;
BRBlessStyle blessSpec = kBRBlessFSDefault;
switch (argc) {
char path[PATH_MAX];
case 4:
case 5:
hostVol = srcVol;
(void)CFURLGetFileSystemRepresentation(hostVol, true,
(UInt8*)path, PATH_MAX);
hostpath = path;
targetSpec = argv[3];
blessarg = argv[4];
break;
case 6:
case 7:
hostpath = argv[3];
dmgpath = argv[4];
targetSpec = argv[5];
blessarg = argv[6];
(void)strlcpy(path, hostpath, PATH_MAX);
(void)strlcat(path, argv[4], PATH_MAX);
if (stat(path, &sb)) {
err(EX_NOINPUT, "%s", path);
}
hostVol = CFURLCreateFromFileSystemRepresentation(nil,(UInt8*)hostpath,
strlen(hostpath), true);
if (!hostVol) goto finish;
if (dmgpath[0] != '/') usage(EX_USAGE);
rootDMG = CFURLCreateFromFileSystemRepresentation(nil, (UInt8*)dmgpath,
strlen(dmgpath), false);
if (!rootDMG) goto finish;
rootDMGURLStr = CFURLGetString(rootDMG);
if (!rootDMGURLStr) goto finish;
bootArgs = CFStringCreateWithFormat(nil, nil, CFSTR("root-dmg=%@"),
rootDMGURLStr);
if (!bootArgs) goto finish;
plistOverrides = CFDictionaryCreateMutable(nil, 1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!plistOverrides) goto finish;
CFDictionarySetValue(plistOverrides, CFSTR(kKernelFlagsKey), bootArgs);
break;
default:
usage(EX_USAGE);
}
if ((tdir = strchr(targetSpec, '/'))) {
size_t tlen = tdir-targetSpec;
if (*(tdir + 1) == '\0') usage(EX_USAGE);
strlcpy(helperName, targetSpec, MIN(tlen + 1, DEVMAXPATHSIZE));
bootDev = CFStringCreateWithBytes(nil, (UInt8*)targetSpec,
tlen, kCFStringEncodingUTF8, false);
targetDir = CFURLCreateFromFileSystemRepresentation(nil, (UInt8*)tdir,
strlen(tdir), true);
} else {
bootDev = CFStringCreateWithFileSystemRepresentation(nil, targetSpec);
(void)strlcpy(helperName, targetSpec, DEVMAXPATHSIZE);
}
if (hostVol && (helpers = BRCopyActiveBootPartitions(hostVol))
&& CFArrayGetCount(helpers) > 0) {
CFRange searchRange = { 0, CFArrayGetCount(helpers) };
if (!CFArrayContainsValue(helpers, searchRange, bootDev)) {
fprintf(stderr,"%s doesn't 'belong to' %s; CSFDE might not work\n",
helperName, hostpath);
}
CFRelease(helpers);
}
if (blessarg) {
if (strcasestr(blessarg, "none")) {
blessSpec = kBRBlessNone;
} else if (strcasestr(blessarg, "default") ||
strcasestr(blessarg, "fsdefault")) {
blessSpec = kBRBlessFull;
} else if (strcasestr(blessarg, "full")) {
blessSpec = kBRBlessFSDefault;
} else if (strcasestr(blessarg, "once")) {
blessSpec = kBRBlessOnce;
} else {
usage(EX_USAGE);
}
}
if (targetDir || blessarg) {
CFStringRef pickerLabel = NULL;
if (targetDir) {
pickerLabel = CFURLCopyLastPathComponent(targetDir);
}
#if LOG_ARGS
CFShow(CFSTR("ToDir() args ..."));
CFShow(srcVol);
CFShow(hostVol);
CFShow(plistOverrides);
CFShow(bootDev);
CFShow(targetDir);
fprintf(stderr, "blessSpec: %d\n", blessSpec);
CFShow(CFURLCopyLastPathComponent(targetDir));
#endif
result = BRCopyBootFilesToDir(srcVol, hostVol, plistOverrides,
bootDev, targetDir, blessSpec, pickerLabel);
if (pickerLabel) CFRelease(pickerLabel);
} else {
result = BRCopyBootFiles(srcVol, hostVol, bootDev, plistOverrides);
}
finish:
if (targetDir) CFRelease(targetDir);
if (plistOverrides) CFRelease(plistOverrides);
if (bootArgs) CFRelease(bootArgs);
if (rootDMG) CFRelease(rootDMG);
if (bootDev) CFRelease(bootDev);
if (hostVol) CFRelease(hostVol);
return result;
}
int
erasefiles(char *volpath, CFURLRef srcVol, char *devname, char *forceArg)
{
int result;
CFStringRef bsdName = NULL;
CFArrayRef helpers = NULL;
Boolean force = false;
if (forceArg) {
if (forceArg[0] == '-' && forceArg[1] == 'f') {
force = true;
} else {
result = EINVAL; goto finish;
}
}
if (!strstr(devname, "disk")) {
usage(EX_USAGE);
}
bsdName = CFStringCreateWithFileSystemRepresentation(nil, devname);
if (!force) {
helpers = BRCopyActiveBootPartitions(srcVol);
if (helpers) {
CFRange searchRange = { 0, CFArrayGetCount(helpers) };
if (CFArrayContainsValue(helpers, searchRange, bsdName)) {
fprintf(stderr, "%s currently required to boot '%s'! (-f?)\n",
devname, volpath);
result = EBUSY;
goto finish;
}
}
}
result = BREraseBootFiles(srcVol, bsdName);
finish:
if (helpers) CFRelease(helpers);
if (bsdName) CFRelease(bsdName);
return result;
}
int
main(int argc, char *argv[])
{
int result, exval;
char *verb, *volpath;
struct stat sb;
CFURLRef volURL = NULL;
if (2 == argc && argv[1][0] == '-' && argv[1][1] == 'h')
usage(EX_OK);
if (argc < 3)
usage(EX_USAGE);
#ifdef USE_ASL // example code for daemon clients
#endif // USE_ASL
#ifdef VERBOSE
OSKextSetLogFilter(kOSKextLogDetailLevel |
kOSKextLogVerboseFlagsMask |
kOSKextLogKextOrGlobalMask,
false);
#endif
verb = argv[1];
volpath = argv[2];
if (stat(volpath, &sb) != 0) {
err(EX_NOINPUT, "%s", volpath);
}
volURL = CFURLCreateFromFileSystemRepresentation(nil, (UInt8*)volpath,
strlen(volpath), true);
if (!volURL) {
usage(EX_OSERR);
}
if (strcasecmp(verb, "update") == 0) {
if (argc < 3 || argc > 4)
usage(EX_USAGE);
result = update(volURL, argc-2, argv+2);
} else if (strcasecmp(verb, "listboots") == 0) {
if (argc != 3)
usage(EX_USAGE);
result = listboots(volpath, volURL);
} else if (strcasecmp(verb, "copyfiles") == 0) {
result = copyfiles(volURL, argc, argv);
} else if (strcasecmp(verb, "erasefiles") == 0) {
if (argc != 4 && argc != 5)
usage(EX_USAGE);
result = erasefiles(argv[2], volURL, argv[3], argv[4]);
} else {
usage(EX_USAGE);
}
if (result < 0) {
printf("brtest function result = %#x\n", result);
} else {
printf("brtest function result = %d", result);
if (result == -1) {
printf(": errno %d -> %s", errno, strerror(errno));
} else if (result && result <= ELAST) {
printf(": %s", strerror(result));
}
printf("\n");
}
if (result == -1) {
exval = EX_OSERR;
} else if (result == EINVAL) {
exval = EX_USAGE;
} else if (result) {
exval = EX_SOFTWARE;
} else {
exval = result;
}
if (volURL) CFRelease(volURL);
return exval;
}