1/*
2 * Copyright (c) 2011-2012 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25#include <CoreFoundation/CoreFoundation.h>
26#include <IOKit/IOKitLib.h>
27
28#include "bless.h"
29#include "bless_private.h"
30
31// public entry points (bless.h)
32kern_return_t
33BLSetEFIBootDevice(BLContextPtr context, char *bsdName)
34{
35    if (0 == setefidevice(context, bsdName, false /* next */,
36                          false, NULL, NULL, false)) {
37        return KERN_SUCCESS;
38    } else {
39        return KERN_FAILURE;
40    }
41}
42
43kern_return_t
44BLSetEFIBootDeviceOnce(BLContextPtr context, char *bsdName)
45{
46    if (0 == setefidevice(context, bsdName, true /* next */,
47                          false, NULL, NULL, false)) {
48        return KERN_SUCCESS;
49    } else {
50        return KERN_FAILURE;
51    }
52}
53
54kern_return_t
55BLSetEFIBootFileOnce(BLContextPtr context, char *path)
56{
57    if (0 == setefifilepath(context, path, true, NULL, NULL)) {
58        return KERN_SUCCESS;
59    } else {
60        return KERN_FAILURE;
61    }
62}
63
64/*
65 * private entry points (bless_private.h)
66 */
67
68// one static helper (defined at the bottom of this file)
69static int setefibootargs(BLContextPtr context, mach_port_t masterPort);
70
71int setefidevice(BLContextPtr context, const char * bsdname, int bootNext,
72				 int bootLegacy, const char *legacyHint, const char *optionalData, bool shortForm)
73{
74    int ret;
75
76    CFStringRef xmlString = NULL;
77    const char *bootString = NULL;
78
79	if(bootLegacy) {
80        if(legacyHint) {
81            ret = BLCreateEFIXMLRepresentationForDevice(context,
82                                                legacyHint+5,
83                                                NULL,
84                                                &xmlString,
85                                                false);
86
87            if(ret) {
88                return 1;
89            }
90
91            ret = setit(context, kIOMasterPortDefault, "efi-legacy-drive-hint", xmlString);
92            if(ret) return ret;
93
94            ret = _forwardNVRAM(context, CFSTR("efi-legacy-drive-hint-data"), CFSTR("BootCampHD"));
95            if(ret) return ret;
96
97            ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-legacy-drive-hint"));
98            if(ret) return ret;
99
100        }
101
102        ret = BLCreateEFIXMLRepresentationForLegacyDevice(context,
103                                                  bsdname,
104                                                  &xmlString);
105	} else {
106        // the given device may be pointing at a RAID
107        CFDictionaryRef dict = NULL;
108        CFArrayRef      array = NULL;
109        char            newBSDName[MAXPATHLEN];
110
111        CFStringRef firstBooter = NULL;
112        CFStringRef firstData = NULL;
113        int         uefiDiscBootEntry = 0;
114        int         uefiDiscPartitionStart = 0;
115        int         uefiDiscPartitionSize = 0;
116
117        strlcpy(newBSDName, bsdname, sizeof newBSDName);
118
119        // first check to see if we are dealing with a disk that has the following properties:
120        // is optical AND is DVD AND has an El Torito Boot Catalog AND has an UEFI-bootable entry.
121        // get yes/no on that, and if yes, also get some facts about where on disc it is.
122        bool isUEFIDisc = isDVDWithElToritoWithUEFIBootableOS(context, newBSDName, &uefiDiscBootEntry, &uefiDiscPartitionStart, &uefiDiscPartitionSize);
123
124        if (isUEFIDisc) {
125            contextprintf(context, kBLLogLevelVerbose, "Disk is DVD disc with BootCatalog with UEFIBootableOS\n");
126        } else {
127            contextprintf(context, kBLLogLevelVerbose, "Checking if disk is complex (if it is associated with booter partitions)\n");
128            ret = BLCreateBooterInformationDictionary(context, newBSDName,
129                                                      &dict);
130            if(ret) {
131                return 1;
132            }
133
134            // check to see if there's a booter partition. If so, use it
135            array = CFDictionaryGetValue(dict, kBLAuxiliaryPartitionsKey);
136            if(array) {
137                if(CFArrayGetCount(array) > 0) {
138                    firstBooter = CFArrayGetValueAtIndex(array, 0);
139                    if(!CFStringGetCString(firstBooter, newBSDName, sizeof(newBSDName),
140                                           kCFStringEncodingUTF8)) {
141                        return 1;
142                    }
143                    contextprintf(context, kBLLogLevelVerbose, "Substituting booter %s\n", newBSDName);
144                }
145            }
146
147            // check to see if there's a data partition (without auxiliary) that
148            // we should boot from
149            if (!firstBooter) {
150                array = CFDictionaryGetValue(dict, kBLDataPartitionsKey);
151                if(array) {
152                    if(CFArrayGetCount(array) > 0) {
153                        firstData = CFArrayGetValueAtIndex(array, 0);
154                        if(!CFStringGetCString(firstData, newBSDName, sizeof(newBSDName),
155                                               kCFStringEncodingUTF8)) {
156                            return 1;
157                        }
158                        if (0 == strcmp(newBSDName, bsdname)) {
159                            /* same as before, no substitution */
160                            firstData = NULL;
161                        } else {
162                            contextprintf(context, kBLLogLevelVerbose, "Substituting bootable data partition %s\n", newBSDName);
163                        }
164                    }
165                }
166            }
167        }
168
169        // we have the real entity we want to boot from: whether the "direct" disk, an actual underlying
170        // boot helper disk, or ElTorito offset information. create EFI boot settings depending on case.
171
172        if (false == isUEFIDisc) {
173            ret = BLCreateEFIXMLRepresentationForDevice(context,
174                                                        newBSDName,
175                                                        optionalData,
176                                                        &xmlString,
177                                                        shortForm);
178        } else {
179            ret = BLCreateEFIXMLRepresentationForElToritoEntry(context,
180                                                               newBSDName,
181                                                               uefiDiscBootEntry,
182                                                               uefiDiscPartitionStart,
183                                                               uefiDiscPartitionSize,
184                                                               &xmlString);
185        }
186
187        if (dict)
188            CFRelease(dict);
189	}
190
191    if(ret) {
192        return 1;
193    }
194
195	// TODO merge this code with BLSetEFIBootDevice() [9300208]
196    if(bootNext) {
197        bootString = "efi-boot-next";
198    } else {
199        bootString = "efi-boot-device";
200    }
201
202    ret = setit(context, kIOMasterPortDefault, bootString, xmlString);
203    CFRelease(xmlString);
204    if(ret) return ret;
205
206	ret = efinvramcleanup(context);
207	if(ret) return ret;
208
209    return ret;
210}
211
212int setefifilepath(BLContextPtr context, const char * path, int bootNext,
213				   const char *optionalData, bool shortForm)
214{
215    CFStringRef xmlString = NULL;
216    const char *bootString = NULL;
217    int ret;
218    struct statfs sb;
219    if(0 != blsustatfs(path, &sb)) {
220        contextprintf(context, kBLLogLevelError,  "Can't statfs %s\n" ,
221                           path);
222        return 1;
223    }
224
225    // first try to get booter information for the block device.
226    // if there is none, we can do our normal path
227    // the given device may be pointing at a RAID
228    CFDictionaryRef dict = NULL;
229    CFArrayRef array = NULL;
230    char    newBSDName[MAXPATHLEN];
231
232    CFStringRef firstBooter = NULL;
233    CFStringRef firstData = NULL;
234    int         uefiDiscBootEntry = 0;
235    int         uefiDiscPartitionStart = 0;
236    int         uefiDiscPartitionSize = 0;
237
238    strlcpy(newBSDName, sb.f_mntfromname + 5, sizeof newBSDName);
239
240    // first check to see if we are dealing with a disk that has the following properties:
241    // is optical AND is DVD AND has an El Torito Boot Catalog AND has an UEFI-bootable entry.
242    // get yes/no on that, and if yes, also get some facts about where on disc it is.
243    bool isUEFIDisc = isDVDWithElToritoWithUEFIBootableOS(context, newBSDName, &uefiDiscBootEntry, &uefiDiscPartitionStart, &uefiDiscPartitionSize);
244
245    if (isUEFIDisc) {
246        contextprintf(context, kBLLogLevelVerbose, "Disk is DVD disc with BootCatalog with UEFIBootableOS\n");
247    } else {
248        contextprintf(context, kBLLogLevelVerbose, "Checking if disk is complex (if it is associated with booter partitions)\n");
249
250        ret = BLCreateBooterInformationDictionary(context, newBSDName,
251                                                  &dict);
252        if(ret) {
253            return 1;
254        }
255
256        // check to see if there's a booter partition. If so, use it
257        array = CFDictionaryGetValue(dict, kBLAuxiliaryPartitionsKey);
258        if(array) {
259            if(CFArrayGetCount(array) > 0) {
260                firstBooter = CFArrayGetValueAtIndex(array, 0);
261                if(!CFStringGetCString(firstBooter, newBSDName, sizeof(newBSDName),
262                                       kCFStringEncodingUTF8)) {
263                    return 1;
264                }
265                contextprintf(context, kBLLogLevelVerbose, "Substituting booter %s\n", newBSDName);
266            }
267        }
268
269        // check to see if there's a data partition (without auxiliary) that
270        // we should boot from
271        if (!firstBooter) {
272            array = CFDictionaryGetValue(dict, kBLDataPartitionsKey);
273            if(array) {
274                if(CFArrayGetCount(array) > 0) {
275                    firstData = CFArrayGetValueAtIndex(array, 0);
276                    if(!CFStringGetCString(firstData, newBSDName, sizeof(newBSDName),
277                                           kCFStringEncodingUTF8)) {
278                        return 1;
279                    }
280                    if (0 == strcmp(newBSDName, sb.f_mntfromname + 5)) {
281                        /* same as before, no substitution */
282                        firstData = NULL;
283                    } else {
284                        contextprintf(context, kBLLogLevelVerbose, "Substituting bootable data partition %s\n", newBSDName);
285                    }
286                }
287            }
288        }
289    }
290
291    // we have the real entity we want to boot from: whether the "direct" disk, an actual underlying
292    // boot helper disk, or ElTorito offset information. create EFI boot settings depending on case.
293
294    if(firstBooter || firstData) {
295        // so this is probably a RAID. Validate that we were passed a mountpoint
296        if(0 != strncmp(sb.f_mntonname, path, MAXPATHLEN)) {
297            contextprintf(context, kBLLogLevelError,  "--file not supported for %s\n" ,
298                               sb.f_mntonname);
299            return 2;
300        }
301        ret = BLCreateEFIXMLRepresentationForDevice(context,
302                                                    newBSDName,
303                                                    optionalData,
304                                                    &xmlString,
305                                                    shortForm);
306    } else if(isUEFIDisc) {
307        ret = BLCreateEFIXMLRepresentationForElToritoEntry(context,
308                                                           newBSDName,
309                                                           uefiDiscBootEntry,
310                                                           uefiDiscPartitionStart,
311                                                           uefiDiscPartitionSize,
312                                                           &xmlString);
313    } else {
314        ret = BLCreateEFIXMLRepresentationForPath(context,
315                                                  path,
316                                                  optionalData,
317                                                  &xmlString,
318                                                  shortForm);
319    }
320
321    if (dict)
322        CFRelease (dict);
323
324    if(ret) {
325        return 1;
326    }
327
328    if(bootNext) {
329        bootString = "efi-boot-next";
330    } else {
331        bootString = "efi-boot-device";
332    }
333
334    ret = setit(context, kIOMasterPortDefault, bootString, xmlString);
335    CFRelease(xmlString);
336    if(ret) {
337        return 2;
338    }
339
340	ret = efinvramcleanup(context);
341	if(ret) return ret;
342
343    return 0;
344}
345
346// no BLSet...() wrapper yet
347int setefinetworkpath(BLContextPtr context, CFStringRef booterXML,
348					  CFStringRef kernelXML, CFStringRef mkextXML,
349					  CFStringRef kernelcacheXML, int bootNext)
350{
351    const char *bootString = NULL;
352    int ret;
353
354    if(bootNext) {
355        bootString = "efi-boot-next";
356    } else {
357        bootString = "efi-boot-device";
358    }
359
360    ret = setit(context, kIOMasterPortDefault, bootString, booterXML);
361    if(ret) return ret;
362
363	if(kernelXML) {
364		ret = setit(context, kIOMasterPortDefault, "efi-boot-file", kernelXML);
365	} else {
366		ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-file"));
367	}
368    if(ret) return ret;
369
370	if(mkextXML) {
371		ret = setit(context, kIOMasterPortDefault, "efi-boot-mkext", mkextXML);
372	} else {
373		ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-mkext"));
374	}
375    if(ret) return ret;
376
377    if(kernelcacheXML) {
378		ret = setit(context, kIOMasterPortDefault, "efi-boot-kernelcache", kernelcacheXML);
379	} else {
380		ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-kernelcache"));
381	}
382    if(ret) return ret;
383
384
385    ret = setefibootargs(context, kIOMasterPortDefault);
386    if(ret) return ret;
387
388    return 0;
389}
390
391int efinvramcleanup(BLContextPtr context)
392{
393	int ret;
394
395	ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-file"));
396    if(ret) return ret;
397
398    ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-mkext"));
399    if(ret) return ret;
400
401    ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-kernelcache"));
402    if(ret) return ret;
403
404    ret = setefibootargs(context, kIOMasterPortDefault);
405    if(ret) return ret;
406
407    return 0;
408}
409
410
411// shared helpers (used by setboot.c)
412
413int _forwardNVRAM(BLContextPtr context, CFStringRef from, CFStringRef to)
414{
415
416    io_registry_entry_t optionsNode = 0;
417    CFTypeRef       valRef;
418    kern_return_t   kret;
419
420    optionsNode = IORegistryEntryFromPath(kIOMasterPortDefault, kIODeviceTreePlane ":/options");
421
422    if(IO_OBJECT_NULL == optionsNode) {
423        contextprintf(context, kBLLogLevelError,  "Could not find " kIODeviceTreePlane ":/options\n");
424        return 1;
425    }
426
427    valRef = IORegistryEntryCreateCFProperty(optionsNode, from, kCFAllocatorDefault, 0);
428
429    if(valRef == NULL) {
430        contextprintf(context, kBLLogLevelError,  "Could not find variable '%s'\n",
431                                                    BLGetCStringDescription(from));
432        return 2;
433    }
434
435    contextprintf(context, kBLLogLevelVerbose,  "Setting EFI NVRAM:\n" );
436    contextprintf(context, kBLLogLevelVerbose,  "\t%s='...'\n", BLGetCStringDescription(to) );
437
438    kret = IORegistryEntrySetCFProperty(optionsNode, to, valRef);
439    if(kret) {
440        CFRelease(valRef);
441        IOObjectRelease(optionsNode);
442        contextprintf(context, kBLLogLevelError,  "Could not set boot property '%s': %#x\n",
443                                                        BLGetCStringDescription(to),
444                                                        kret);
445        return 3;
446    }
447
448    CFRelease(valRef);
449    IOObjectRelease(optionsNode);
450
451    return 0;
452}
453
454int setit(BLContextPtr context, mach_port_t masterPort, const char *bootvar, CFStringRef xmlstring)
455{
456
457    io_registry_entry_t optionsNode = 0;
458    CFStringRef bootName = NULL;
459    kern_return_t kret;
460    char    cStr[1024];
461
462    optionsNode = IORegistryEntryFromPath(masterPort, kIODeviceTreePlane ":/options");
463
464    if(IO_OBJECT_NULL == optionsNode) {
465        contextprintf(context, kBLLogLevelError,  "Could not find " kIODeviceTreePlane ":/options\n");
466        return 1;
467    }
468
469    bootName = CFStringCreateWithCString(kCFAllocatorDefault, bootvar, kCFStringEncodingUTF8);
470    if(bootName == NULL) {
471        IOObjectRelease(optionsNode);
472        return 2;
473    }
474
475    CFStringGetCString(xmlstring, cStr, sizeof(cStr), kCFStringEncodingUTF8);
476
477    contextprintf(context, kBLLogLevelVerbose,  "Setting EFI NVRAM:\n" );
478    contextprintf(context, kBLLogLevelVerbose,  "\t%s='%s'\n", bootvar, cStr );
479
480    kret = IORegistryEntrySetCFProperty(optionsNode, bootName, xmlstring);
481    if(kret) {
482        CFRelease(bootName);
483        IOObjectRelease(optionsNode);
484        contextprintf(context, kBLLogLevelError,  "Could not set boot device property: %#x\n", kret);
485        return 2;
486    }
487
488    CFRelease(bootName);
489    IOObjectRelease(optionsNode);
490
491    return 0;
492}
493
494// truly private helper?
495// fetch old args. If set, filter them and reset
496static int setefibootargs(BLContextPtr context, mach_port_t masterPort)
497{
498
499    int             ret;
500    char        cStr[1024], newArgs[1024];
501    CFStringRef     newString;
502
503    ret = BLCopyEFINVRAMVariableAsString(context,
504                                   CFSTR("boot-args"),
505                                   &newString);
506
507    if(ret) {
508        contextprintf(context, kBLLogLevelError,  "Error getting NVRAM variable \"boot-args\"\n");
509        return 1;
510    }
511
512    if(newString == NULL) {
513        // nothing set. that's OK
514        contextprintf(context, kBLLogLevelVerbose,  "NVRAM variable \"boot-args\" not set.\n");
515        return 0;
516    }
517
518    if(!CFStringGetCString(newString, cStr, sizeof(cStr), kCFStringEncodingUTF8)) {
519        contextprintf(context, kBLLogLevelError,  "Could not interpret boot-args as string. Ignoring...\n");
520        cStr[0] = '\0';
521    }
522
523    CFRelease(newString);
524
525    ret = BLPreserveBootArgs(context, cStr, newArgs, sizeof newArgs);
526    if(ret) {
527        return ret;
528    }
529
530    newString = CFStringCreateWithCString(kCFAllocatorDefault, newArgs, kCFStringEncodingUTF8);
531    if(newString == NULL) {
532        return 2;
533    }
534
535    ret = setit(context, masterPort, "boot-args", newString);
536    CFRelease(newString);
537    if(ret)
538        return ret;
539
540    return 0;
541}
542
543