1/* 2 * FILE: brtest.c 3 * AUTH: Soren Spies (sspies) 4 * DATE: 10 March 2011 (Copyright Apple Inc.) 5 * DESC: test libBootRoot 6 * 7 */ 8 9// CFLAGS: -lBootRoot 10 11#include "bootroot.h" 12 13#include <bootfiles.h> 14#include <err.h> 15#include <paths.h> 16#include <stdio.h> 17#include <strings.h> 18#include <sys/param.h> // MIN() 19#include <sys/stat.h> 20#include <sysexits.h> 21 22#include <CoreFoundation/CoreFoundation.h> 23 24 25// #define VERBOSE 26 27void usage(int exval) __attribute__((noreturn)); 28void usage(int exval) 29{ 30 fprintf(stderr, 31 "Usage: brtest update <vol> [-f]\n" 32 " brtest listboots <vol>\n" 33 " brtest erasefiles <srcVol> <bootDev> [-f]\n" 34 " brtest copyfiles <src> [options] <bootDev> [<BlessStyle>]\n" 35 " Options:\n" 36 " -anyboot - update <src>'s bootstamps (no UUID)\n" 37 " -pickerLabel <label> - specific text for opt-boot\n" 38 " brtest copyfiles <src> <root> /<dmg> <tgt>[/<dir>] [<BS>]\n" 39 " (/<dmg> is relative to <root>)\n" 40 41 // hopefully disable will be implicit when "stealing" an Apple_Boot 42 // " brtest disableHelperUpdates <[src]Vol> [<tgtVol>]\n" 43 ); 44 45 exit(exval); 46} 47 48int 49update(CFURLRef volURL, int argc, char *argv[]) 50{ 51 Boolean force = false; 52 53 if (argc == 2) { 54 if (argv[1][0] == '-' && argv[1][1] == 'f') { 55 force = true; 56 } else { 57 return EINVAL; 58 } 59 } 60 61 return BRUpdateBootFiles(volURL, force); 62} 63 64#define DEVMAXPATHSIZE 128 65int 66listboots(char *volpath, CFURLRef volURL) 67{ 68 int result; 69 CFArrayRef boots; 70 CFIndex i, bcount = 0; 71 72 boots = BRCopyActiveBootPartitions(volURL); 73 74 if (!boots) { 75 printf("%s: no boot partitions\n", volpath); 76 result = 0; 77 goto finish; 78 } 79 80 printf("boot support for %s:\n", volpath); 81 bcount = CFArrayGetCount(boots); 82 for (i = 0; i < bcount; i++) { 83 CFShow(CFArrayGetValueAtIndex(boots, i)); // sufficient? 84 } 85 86 result = 0; 87 88finish: 89 if (boots) CFRelease(boots); 90 91 return result; 92} 93 94int 95copyfiles(CFURLRef srcVol, int argc, char *argv[]) 96{ 97 int result = ELAST + 1; 98 char *targetSpec, *tdir, *blessarg = NULL; 99 char *hostpath, *dmgpath; 100 char helperName[DEVMAXPATHSIZE], helperDev[DEVMAXPATHSIZE] = _PATH_DEV; 101 char path[PATH_MAX]; 102 struct stat sb; 103 104 BRCopyFilesOpts opts = kBROptsNone; 105 CFStringRef pickerLabel = NULL; 106 CFArrayRef helpers = NULL; 107 CFURLRef hostVol = NULL; 108 CFStringRef bootDev = NULL; 109 CFURLRef rootDMG = NULL; 110 CFStringRef rootDMGURLStr; // no need to release? 111 CFStringRef bootArgs = NULL; 112 CFMutableDictionaryRef plistOverrides = NULL; 113 CFURLRef targetDir = NULL; 114 BRBlessStyle blessSpec = kBRBlessFSDefault; 115 116 if (argc > 3 && strcmp(argv[3], "-anyboot") == 0) { 117 opts |= kBRAnyBootStamps; 118 argv++; argc--; 119 } 120 if (argc > 4 && strcmp(argv[3], "-pickerLabel") == 0) { 121 pickerLabel = CFStringCreateWithFileSystemRepresentation(nil, argv[4]); 122 argv += 2; argc -= 2; 123 } 124 125 // argv[1-2] processed by main() 126 switch (argc) { 127 char path[PATH_MAX]; 128 case 4: 129 case 5: 130 // brtest copyfiles <src> <bootDev>[/<dir>] [<BlessStyle>] 131 hostVol = CFRetain(srcVol); 132 (void)CFURLGetFileSystemRepresentation(hostVol, true, 133 (UInt8*)path, PATH_MAX); 134 hostpath = path; 135 targetSpec = argv[3]; 136 blessarg = argv[4]; 137 break; 138 139 case 6: 140 case 7: 141 // brtest copyfiles <src> <root> /<dmg> <tgt>[/<dir>] [<BS>] 142 hostpath = argv[3]; 143 dmgpath = argv[4]; 144 targetSpec = argv[5]; 145 blessarg = argv[6]; 146 147 // make sure the dmg actually exists (at the right path) 148 (void)strlcpy(path, hostpath, PATH_MAX); 149 (void)strlcat(path, argv[4], PATH_MAX); 150 if (stat(path, &sb)) { 151 err(EX_NOINPUT, "%s", path); 152 } 153 154 hostVol = CFURLCreateFromFileSystemRepresentation(nil,(UInt8*)hostpath, 155 strlen(hostpath), true); 156 if (!hostVol) goto finish; 157 158 /* !! from CFURL.h !! 159 * Note that, strictly speaking any leading '/' is not considered part 160 * of the URL's path, although its presence or absence determines 161 * whether the path is absolute. */ 162 // an absolute URL appears a critical to get root-dmg=file:///foo.dmg 163 if (dmgpath[0] != '/') usage(EX_USAGE); 164 rootDMG = CFURLCreateFromFileSystemRepresentation(nil, (UInt8*)dmgpath, 165 strlen(dmgpath), false); 166 if (!rootDMG) goto finish; 167 168 // Soren does not understand CFRURL. An absolute URL isn't enough, 169 // it also has to have CFURLGetString() called on it. Awesome(?)ly, 170 // this value could be ignored as it apparently changes the 171 // description of rootDMG(!?). To keep the code clear, we use 172 // rootDMGURLStr when creating bootArgs. 173 rootDMGURLStr = CFURLGetString(rootDMG); 174 if (!rootDMGURLStr) goto finish; 175 176 // pass arguments to the kernel so it roots off the specified DMG 177 // root-dmg must be a URL, not a path, with or w/o /localhost/ 178 bootArgs = CFStringCreateWithFormat(nil, nil, CFSTR("root-dmg=%@"), 179 rootDMGURLStr); 180 // fputc('\t', stderr); CFShow(bootArgs); 181 // container-dmg=%@ allows another layer of disk image :P 182 if (!bootArgs) goto finish; 183 plistOverrides = CFDictionaryCreateMutable(nil, 1, 184 &kCFTypeDictionaryKeyCallBacks, 185 &kCFTypeDictionaryValueCallBacks); 186 if (!plistOverrides) goto finish; 187 CFDictionarySetValue(plistOverrides, CFSTR(kKernelFlagsKey), bootArgs); 188 break; 189 190 default: 191 usage(EX_USAGE); 192 } 193 194 // extract any target directory from argument (e.g. disk0s3/mydir) 195 if ((tdir = strchr(targetSpec, '/'))) { 196 size_t tlen = tdir-targetSpec; 197 if (*(tdir + 1) == '\0') usage(EX_USAGE); 198 (void)strlcpy(helperName, targetSpec, MIN(tlen + 1, DEVMAXPATHSIZE)); 199 bootDev = CFStringCreateWithBytes(nil, (UInt8*)targetSpec, 200 tlen, kCFStringEncodingUTF8, false); 201 targetDir = CFURLCreateFromFileSystemRepresentation(nil, (UInt8*)tdir, 202 strlen(tdir), true); 203 } else { 204 bootDev = CFStringCreateWithFileSystemRepresentation(nil, targetSpec); 205 (void)strlcpy(helperName, targetSpec, DEVMAXPATHSIZE); 206 } 207 208 // make sure the target /dev/ node exists 209 (void)strlcat(helperDev, helperName, DEVMAXPATHSIZE); 210 if (stat(helperDev, &sb)) { 211 err(EX_NOINPUT, "%s", helperDev); 212 } 213 214 // warn if hostVol requires Boot!=Root but bootDev isn't one of its helpers 215 if (hostVol && (helpers = BRCopyActiveBootPartitions(hostVol)) 216 && CFArrayGetCount(helpers) > 0) { 217 CFRange searchRange = { 0, CFArrayGetCount(helpers) }; 218 if (!CFArrayContainsValue(helpers, searchRange, bootDev)) { 219 fprintf(stderr,"%s doesn't 'belong to' %s; CSFDE might not work\n", 220 helperName, hostpath); 221 } 222 } 223 if (helpers) CFRelease(helpers); 224 225 // evaluate any bless style argument 226 if (blessarg) { 227 if (strcasestr(blessarg, "none")) { 228 blessSpec = kBRBlessNone; 229 } else if (strcasecmp(blessarg, "default") == 0) { 230 // blessSpec = kBRBlessFSDefault; 231 } else if (strcasecmp(blessarg, "full") == 0) { 232 blessSpec = kBRBlessFull; 233 } else if (strcasecmp(blessarg, "once") == 0) { 234 blessSpec = kBRBlessOnce; 235 } else if (strcasecmp(blessarg, "fsonce") == 0) { 236 blessSpec |= kBRBlessOnce; 237 } else { 238 usage(EX_USAGE); 239 } 240 } 241 242 // use the fancier function depending on how custom we are 243 if (opts || pickerLabel || targetDir || blessarg) { 244 if (targetDir && !pickerLabel) { 245 pickerLabel = CFURLCopyLastPathComponent(targetDir); 246 } 247#if LOG_ARGS 248CFShow(CFSTR("ToDir() args ...")); 249CFShow(srcVol); 250CFShow(hostVol); 251CFShow(plistOverrides); 252CFShow(bootDev); 253CFShow(targetDir); 254fprintf(stderr, "blessSpec: %d\n", blessSpec); 255CFShow(CFURLCopyLastPathComponent(targetDir)); 256#endif 257 result = BRCopyBootFilesToDir(srcVol, hostVol, plistOverrides, 258 bootDev, targetDir, blessSpec, pickerLabel, opts); 259 } else { 260 result = BRCopyBootFiles(srcVol, hostVol, bootDev, plistOverrides); 261 } 262 263finish: 264 if (pickerLabel) CFRelease(pickerLabel); 265 if (targetDir) CFRelease(targetDir); 266 if (plistOverrides) CFRelease(plistOverrides); 267 if (bootArgs) CFRelease(bootArgs); 268 if (rootDMG) CFRelease(rootDMG); 269 if (bootDev) CFRelease(bootDev); 270 if (hostVol) CFRelease(hostVol); 271 272 return result; 273} 274 275int 276erasefiles(char *volpath, CFURLRef srcVol, char *devname, char *forceArg) 277{ 278 int result; 279 CFStringRef bsdName = NULL; 280 CFArrayRef helpers = NULL; 281 Boolean force = false; 282 283 if (forceArg) { 284 if (forceArg[0] == '-' && forceArg[1] == 'f') { 285 force = true; 286 } else { 287 result = EINVAL; goto finish; 288 } 289 } 290 291 292 // build args 293 if (!strstr(devname, "disk")) { 294 usage(EX_USAGE); 295 } 296 bsdName = CFStringCreateWithFileSystemRepresentation(nil, devname); 297 298 // prevent user from erasing srcVol's Apple_Boot(s) (-f overrides) 299 // X: doesn't prevent user from whacking another volume's Apple_Boot 300 if (!force) { 301 helpers = BRCopyActiveBootPartitions(srcVol); 302 if (helpers) { 303 CFRange searchRange = { 0, CFArrayGetCount(helpers) }; 304 if (CFArrayContainsValue(helpers, searchRange, bsdName)) { 305 fprintf(stderr, "%s currently required to boot '%s'! (-f?)\n", 306 devname, volpath); 307 result = /* kPOSIXErrorBase +*/ EBUSY; 308 goto finish; 309 } 310 } 311 } 312 313 result = BREraseBootFiles(srcVol, bsdName); 314 315finish: 316 if (helpers) CFRelease(helpers); 317 if (bsdName) CFRelease(bsdName); 318 319 return result; 320} 321 322int 323main(int argc, char *argv[]) 324{ 325 int result, exval; 326 char *verb, *volpath; 327 struct stat sb; 328 CFURLRef volURL = NULL; 329 330 // check for -h or not enough args 331 if (argc >= 2 && argv[1][0] == '-' && argv[1][1] == 'h') 332 usage(EX_OK); 333 if (argc < 3) 334 usage(EX_USAGE); 335 336#ifdef USE_ASL // example code for daemon clients 337 // OSKextSetLogOutputFunction(&tool_log); 338 // tool_openlog(getprogname()); 339 // tool_openlog("brtest/libBootRoot"); 340#endif // USE_ASL 341#ifdef VERBOSE 342 OSKextSetLogFilter(kOSKextLogDetailLevel | 343 kOSKextLogVerboseFlagsMask | 344 kOSKextLogKextOrGlobalMask, 345 false); 346#endif 347 348 verb = argv[1]; 349 volpath = argv[2]; 350 if (stat(volpath, &sb) != 0) { 351 if (volpath[0] == '-') { 352 usage(EX_USAGE); 353 } else { 354 err(EX_NOINPUT, "%s", volpath); 355 } 356 } 357 volURL = CFURLCreateFromFileSystemRepresentation(nil, (UInt8*)volpath, 358 strlen(volpath), true); 359 if (!volURL) { 360 usage(EX_OSERR); 361 } 362 363 if (strcasecmp(verb, "update") == 0) { 364 if (argc < 3 || argc > 4) 365 usage(EX_USAGE); 366 result = update(volURL, argc-2, argv+2); 367 } else if (strcasecmp(verb, "listboots") == 0) { 368 if (argc != 3) 369 usage(EX_USAGE); 370 result = listboots(volpath, volURL); 371 } else if (strcasecmp(verb, "copyfiles") == 0) { 372 result = copyfiles(volURL, argc, argv); 373 } else if (strcasecmp(verb, "erasefiles") == 0) { 374 if (argc != 4 && argc != 5) 375 usage(EX_USAGE); 376 result = erasefiles(argv[2], volURL, argv[3], argv[4]); 377 /* ... other verbs ... */ 378 } else { 379 fprintf(stderr, "no recognized verb!\n"); 380 usage(EX_USAGE); 381 } 382 383 if (result < 0) { 384 printf("brtest function result = %#x\n", result); 385 } else { 386 printf("brtest function result = %d", result); 387 if (result == -1) { 388 printf(": errno %d -> %s", errno, strerror(errno)); 389 } else if (result && result <= ELAST) { 390 printf(": %s", strerror(result)); 391 } 392 printf("\n"); 393 } 394 395 if (result == -1) { 396 exval = EX_OSERR; 397 } else if (result == EINVAL) { 398 exval = EX_USAGE; 399 } else if (result) { 400 exval = EX_SOFTWARE; 401 } else { 402 exval = result; 403 } 404 405// fprintf(stderr, "check for leaks now\n"); 406// pause() 407 if (volURL) CFRelease(volURL); 408 409 return exval; 410} 411