BLSetEFIBootDevice.c [plain text]
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOMedia.h>
#include "bless.h"
#include "bless_private.h"
kern_return_t
BLSetEFIBootDevice(BLContextPtr context, char *bsdName)
{
if (0 == setefidevice(context, bsdName, false ,
false, NULL, NULL, false)) {
return KERN_SUCCESS;
} else {
return KERN_FAILURE;
}
}
kern_return_t
BLSetEFIBootDeviceOnce(BLContextPtr context, char *bsdName)
{
if (0 == setefidevice(context, bsdName, true ,
false, NULL, NULL, false)) {
return KERN_SUCCESS;
} else {
return KERN_FAILURE;
}
}
kern_return_t
BLSetEFIBootFileOnce(BLContextPtr context, char *path)
{
if (0 == setefifilepath(context, path, true, NULL, NULL)) {
return KERN_SUCCESS;
} else {
return KERN_FAILURE;
}
}
static int setefibootargs(BLContextPtr context, mach_port_t masterPort);
int setefidevice(BLContextPtr context, const char * bsdname, int bootNext,
int bootLegacy, const char *legacyHint, const char *optionalData, bool shortForm)
{
int ret;
CFStringRef xmlString = NULL;
const char *bootString = NULL;
if(bootLegacy) {
if(legacyHint) {
ret = BLCreateEFIXMLRepresentationForDevice(context,
legacyHint+5,
NULL,
&xmlString,
false);
if(ret) {
return 1;
}
ret = setit(context, kIOMasterPortDefault, "efi-legacy-drive-hint", xmlString);
if(ret) return ret;
ret = _forwardNVRAM(context, CFSTR("efi-legacy-drive-hint-data"), CFSTR("BootCampHD"));
if(ret) return ret;
ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-legacy-drive-hint"));
if(ret) return ret;
}
ret = BLCreateEFIXMLRepresentationForLegacyDevice(context,
bsdname,
&xmlString);
} else {
CFDictionaryRef dict = NULL;
CFArrayRef array = NULL;
char newBSDName[MAXPATHLEN];
CFStringRef firstBooter = NULL;
CFStringRef firstData = NULL;
int uefiDiscBootEntry = 0;
int uefiDiscPartitionStart = 0;
int uefiDiscPartitionSize = 0;
strlcpy(newBSDName, bsdname, sizeof newBSDName);
bool isUEFIDisc = isDVDWithElToritoWithUEFIBootableOS(context, newBSDName, &uefiDiscBootEntry, &uefiDiscPartitionStart, &uefiDiscPartitionSize);
if (isUEFIDisc) {
contextprintf(context, kBLLogLevelVerbose, "Disk is DVD disc with BootCatalog with UEFIBootableOS\n");
} else {
contextprintf(context, kBLLogLevelVerbose, "Checking if disk is complex (if it is associated with booter partitions)\n");
ret = BLCreateBooterInformationDictionary(context, newBSDName,
&dict);
if(ret) {
return 1;
}
array = CFDictionaryGetValue(dict, kBLAuxiliaryPartitionsKey);
if(array) {
if(CFArrayGetCount(array) > 0) {
firstBooter = CFArrayGetValueAtIndex(array, 0);
if(!CFStringGetCString(firstBooter, newBSDName, sizeof(newBSDName),
kCFStringEncodingUTF8)) {
return 1;
}
contextprintf(context, kBLLogLevelVerbose, "Substituting booter %s\n", newBSDName);
}
}
if (!firstBooter) {
array = CFDictionaryGetValue(dict, kBLDataPartitionsKey);
if(array) {
if(CFArrayGetCount(array) > 0) {
firstData = CFArrayGetValueAtIndex(array, 0);
if(!CFStringGetCString(firstData, newBSDName, sizeof(newBSDName),
kCFStringEncodingUTF8)) {
return 1;
}
if (0 == strcmp(newBSDName, bsdname)) {
firstData = NULL;
} else {
contextprintf(context, kBLLogLevelVerbose, "Substituting bootable data partition %s\n", newBSDName);
}
}
}
}
}
if (false == isUEFIDisc) {
ret = BLCreateEFIXMLRepresentationForDevice(context,
newBSDName,
optionalData,
&xmlString,
shortForm);
} else {
ret = BLCreateEFIXMLRepresentationForElToritoEntry(context,
newBSDName,
uefiDiscBootEntry,
uefiDiscPartitionStart,
uefiDiscPartitionSize,
&xmlString);
}
if (dict)
CFRelease(dict);
}
if(ret) {
return 1;
}
if(bootNext) {
bootString = "efi-boot-next";
} else {
bootString = "efi-boot-device";
}
ret = setit(context, kIOMasterPortDefault, bootString, xmlString);
CFRelease(xmlString);
if(ret) return ret;
ret = efinvramcleanup(context);
if(ret) return ret;
return ret;
}
int setefifilepath(BLContextPtr context, const char * path, int bootNext,
const char *optionalData, bool shortForm)
{
CFStringRef xmlString = NULL;
const char *bootString = NULL;
int ret;
struct statfs sb;
if(0 != blsustatfs(path, &sb)) {
contextprintf(context, kBLLogLevelError, "Can't statfs %s\n" ,
path);
return 1;
}
CFDictionaryRef dict = NULL;
CFArrayRef array = NULL;
char newBSDName[MAXPATHLEN];
CFStringRef firstBooter = NULL;
CFStringRef firstData = NULL;
CFStringRef firstVolume = NULL;
char prebootBSD[MAXPATHLEN];
char partialPath[MAXPATHLEN];
int uefiDiscBootEntry = 0;
int uefiDiscPartitionStart = 0;
int uefiDiscPartitionSize = 0;
strlcpy(newBSDName, sb.f_mntfromname + 5, sizeof newBSDName);
bool isUEFIDisc = isDVDWithElToritoWithUEFIBootableOS(context, newBSDName, &uefiDiscBootEntry, &uefiDiscPartitionStart, &uefiDiscPartitionSize);
if (isUEFIDisc) {
contextprintf(context, kBLLogLevelVerbose, "Disk is DVD disc with BootCatalog with UEFIBootableOS\n");
} else {
contextprintf(context, kBLLogLevelVerbose, "Checking if disk is complex (if it is associated with booter partitions)\n");
ret = BLCreateBooterInformationDictionary(context, newBSDName,
&dict);
if(ret) {
return 1;
}
array = CFDictionaryGetValue(dict, kBLAPFSPrebootVolumesKey);
if (array && CFArrayGetCount(array) > 0) {
io_registry_entry_t rootMedia;
CFStringRef rootUUID;
firstVolume = CFArrayGetValueAtIndex(array, 0);
rootMedia = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, newBSDName));
if (!rootMedia) {
CFRelease(dict);
return 2;
}
rootUUID = IORegistryEntryCreateCFProperty(rootMedia, CFSTR(kIOMediaUUIDKey), kCFAllocatorDefault, 0);
IOObjectRelease(rootMedia);
if (!rootUUID) {
CFRelease(dict);
return 2;
}
CFStringGetCString(firstVolume, prebootBSD, sizeof prebootBSD, kCFStringEncodingUTF8);
contextprintf(context, kBLLogLevelVerbose, "Substituting preboot volume %s\n", prebootBSD);
strlcpy(partialPath, "/", sizeof partialPath);
CFStringGetCString(rootUUID, partialPath + strlen(partialPath), sizeof partialPath - strlen(partialPath), kCFStringEncodingUTF8);
strlcat(partialPath, "/System/Library/CoreServices/boot.efi", sizeof partialPath);
}
if (!firstVolume) {
array = CFDictionaryGetValue(dict, kBLAuxiliaryPartitionsKey);
if(array) {
if(CFArrayGetCount(array) > 0) {
firstBooter = CFArrayGetValueAtIndex(array, 0);
if(!CFStringGetCString(firstBooter, newBSDName, sizeof(newBSDName),
kCFStringEncodingUTF8)) {
return 1;
}
contextprintf(context, kBLLogLevelVerbose, "Substituting booter %s\n", newBSDName);
}
}
}
if (!firstVolume && !firstBooter) {
array = CFDictionaryGetValue(dict, kBLDataPartitionsKey);
if(array) {
if(CFArrayGetCount(array) > 0) {
firstData = CFArrayGetValueAtIndex(array, 0);
if(!CFStringGetCString(firstData, newBSDName, sizeof(newBSDName),
kCFStringEncodingUTF8)) {
return 1;
}
if (0 == strcmp(newBSDName, sb.f_mntfromname + 5)) {
firstData = NULL;
} else {
contextprintf(context, kBLLogLevelVerbose, "Substituting bootable data partition %s\n", newBSDName);
}
}
}
}
}
if(firstBooter || firstData) {
if(0 != strncmp(sb.f_mntonname, path, MAXPATHLEN)) {
contextprintf(context, kBLLogLevelError, "--file not supported for %s\n" ,
sb.f_mntonname);
return 2;
}
ret = BLCreateEFIXMLRepresentationForDevice(context,
newBSDName,
optionalData,
&xmlString,
shortForm);
} else if(isUEFIDisc) {
ret = BLCreateEFIXMLRepresentationForElToritoEntry(context,
newBSDName,
uefiDiscBootEntry,
uefiDiscPartitionStart,
uefiDiscPartitionSize,
&xmlString);
} else if (firstVolume) {
ret = BLCreateEFIXMLRepresentationForPartialPath(context,
prebootBSD,
partialPath,
optionalData,
&xmlString,
shortForm);
} else {
ret = BLCreateEFIXMLRepresentationForPath(context,
path,
optionalData,
&xmlString,
shortForm);
}
if (dict)
CFRelease (dict);
if(ret) {
return 1;
}
if(bootNext) {
bootString = "efi-boot-next";
} else {
bootString = "efi-boot-device";
}
ret = setit(context, kIOMasterPortDefault, bootString, xmlString);
CFRelease(xmlString);
if(ret) {
return 2;
}
ret = efinvramcleanup(context);
if(ret) return ret;
return 0;
}
int setefinetworkpath(BLContextPtr context, CFStringRef booterXML,
CFStringRef kernelXML, CFStringRef mkextXML,
CFStringRef kernelcacheXML, int bootNext)
{
const char *bootString = NULL;
int ret;
if(bootNext) {
bootString = "efi-boot-next";
} else {
bootString = "efi-boot-device";
}
ret = setit(context, kIOMasterPortDefault, bootString, booterXML);
if(ret) return ret;
if(kernelXML) {
ret = setit(context, kIOMasterPortDefault, "efi-boot-file", kernelXML);
} else {
ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-file"));
}
if(ret) return ret;
if(mkextXML) {
ret = setit(context, kIOMasterPortDefault, "efi-boot-mkext", mkextXML);
} else {
ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-mkext"));
}
if(ret) return ret;
if(kernelcacheXML) {
ret = setit(context, kIOMasterPortDefault, "efi-boot-kernelcache", kernelcacheXML);
} else {
ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-kernelcache"));
}
if(ret) return ret;
ret = setefibootargs(context, kIOMasterPortDefault);
if(ret) return ret;
return 0;
}
int efinvramcleanup(BLContextPtr context)
{
int ret;
ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-file"));
if(ret) return ret;
ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-mkext"));
if(ret) return ret;
ret = setit(context, kIOMasterPortDefault, kIONVRAMDeletePropertyKey, CFSTR("efi-boot-kernelcache"));
if(ret) return ret;
ret = setefibootargs(context, kIOMasterPortDefault);
if(ret) return ret;
return 0;
}
int _forwardNVRAM(BLContextPtr context, CFStringRef from, CFStringRef to)
{
io_registry_entry_t optionsNode = 0;
CFTypeRef valRef;
kern_return_t kret;
optionsNode = IORegistryEntryFromPath(kIOMasterPortDefault, kIODeviceTreePlane ":/options");
if(IO_OBJECT_NULL == optionsNode) {
contextprintf(context, kBLLogLevelError, "Could not find " kIODeviceTreePlane ":/options\n");
return 1;
}
valRef = IORegistryEntryCreateCFProperty(optionsNode, from, kCFAllocatorDefault, 0);
if(valRef == NULL) {
contextprintf(context, kBLLogLevelError, "Could not find variable '%s'\n",
BLGetCStringDescription(from));
return 2;
}
contextprintf(context, kBLLogLevelVerbose, "Setting EFI NVRAM:\n" );
contextprintf(context, kBLLogLevelVerbose, "\t%s='...'\n", BLGetCStringDescription(to) );
kret = IORegistryEntrySetCFProperty(optionsNode, to, valRef);
if(kret) {
CFRelease(valRef);
IOObjectRelease(optionsNode);
contextprintf(context, kBLLogLevelError, "Could not set boot property '%s': %#x\n",
BLGetCStringDescription(to),
kret);
return 3;
}
CFRelease(valRef);
IOObjectRelease(optionsNode);
return 0;
}
int setit(BLContextPtr context, mach_port_t masterPort, const char *bootvar, CFStringRef xmlstring)
{
io_registry_entry_t optionsNode = 0;
CFStringRef bootName = NULL;
kern_return_t kret;
char cStr[1024];
optionsNode = IORegistryEntryFromPath(masterPort, kIODeviceTreePlane ":/options");
if(IO_OBJECT_NULL == optionsNode) {
contextprintf(context, kBLLogLevelError, "Could not find " kIODeviceTreePlane ":/options\n");
return 1;
}
bootName = CFStringCreateWithCString(kCFAllocatorDefault, bootvar, kCFStringEncodingUTF8);
if(bootName == NULL) {
IOObjectRelease(optionsNode);
return 2;
}
CFStringGetCString(xmlstring, cStr, sizeof(cStr), kCFStringEncodingUTF8);
contextprintf(context, kBLLogLevelVerbose, "Setting EFI NVRAM:\n" );
contextprintf(context, kBLLogLevelVerbose, "\t%s='%s'\n", bootvar, cStr );
kret = IORegistryEntrySetCFProperty(optionsNode, bootName, xmlstring);
if(kret) {
CFRelease(bootName);
IOObjectRelease(optionsNode);
contextprintf(context, kBLLogLevelError, "Could not set boot device property: %#x\n", kret);
return 2;
}
CFRelease(bootName);
IOObjectRelease(optionsNode);
return 0;
}
static int setefibootargs(BLContextPtr context, mach_port_t masterPort)
{
int ret;
char cStr[1024], newArgs[1024];
CFStringRef newString;
ret = BLCopyEFINVRAMVariableAsString(context,
CFSTR("boot-args"),
&newString);
if(ret) {
contextprintf(context, kBLLogLevelError, "Error getting NVRAM variable \"boot-args\"\n");
return 1;
}
if(newString == NULL) {
contextprintf(context, kBLLogLevelVerbose, "NVRAM variable \"boot-args\" not set.\n");
return 0;
}
if(!CFStringGetCString(newString, cStr, sizeof(cStr), kCFStringEncodingUTF8)) {
contextprintf(context, kBLLogLevelError, "Could not interpret boot-args as string. Ignoring...\n");
cStr[0] = '\0';
}
CFRelease(newString);
ret = BLPreserveBootArgs(context, cStr, newArgs, sizeof newArgs);
if(ret) {
return ret;
}
newString = CFStringCreateWithCString(kCFAllocatorDefault, newArgs, kCFStringEncodingUTF8);
if(newString == NULL) {
return 2;
}
ret = setit(context, masterPort, "boot-args", newString);
CFRelease(newString);
if(ret)
return ret;
return 0;
}