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