#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mount.h>
#include <IOKit/IOBSD.h>
#include <IOKit/IOCFSerialize.h>
#include <DiskArbitration/DiskArbitration.h>
#include <CoreFoundation/CoreFoundation.h>
#include "enums.h"
#include "bless.h"
extern int blesscontextprintf(BLContextPtr context, int loglevel, char const *fmt, ...)
__attribute__ ((format (printf, 3, 4)));
static int updateAppleBootIfPresent(BLContextPtr context, char *device, CFDataRef bootxData,
CFDataRef labelData);
int setefidevice(BLContextPtr context, const char * bsdname, int bootNext, const char *optionalData);
int setefifilepath(BLContextPtr context, const char * path, int bootNext, const char *optionalData);
int setefinetworkpath(BLContextPtr context, const char * interface, const char *host, const char *path, int bootNext, const char *optionalData);
static int setit(BLContextPtr context, mach_port_t masterPort, const char *bootvar, CFStringRef xmlstring);
static int setefibootargs(BLContextPtr context, mach_port_t masterPort);
int setboot(BLContextPtr context, char *device, CFDataRef bootxData,
CFDataRef labelData)
{
int err;
CFTypeRef bootData = NULL;
BLPreBootEnvType preboot;
err = BLGetPreBootEnvironmentType(context, &preboot);
if(err) {
blesscontextprintf(context, kBLLogLevelError, "Could not determine preboot environment\n");
return 1;
}
err = BLGetRAIDBootDataForDevice(context, device, &bootData);
if(err) {
blesscontextprintf(context, kBLLogLevelError, "Error while determining if %s is a RAID\n", device );
return 3;
}
if(bootData) {
err = BLUpdateRAIDBooters(context, device, bootData, bootxData, labelData);
if(err) {
blesscontextprintf(context, kBLLogLevelError, "Error while updating RAID booters for %s\n", device );
}
CFRelease(bootData);
} else {
err = updateAppleBootIfPresent(context, device, bootxData, labelData);
if(err) {
blesscontextprintf(context, kBLLogLevelError, "Error while updating booter for %s\n", device );
}
}
if(preboot == kBLPreBootEnvType_OpenFirmware) {
err = BLSetOpenFirmwareBootDevice(context, device);
if(err) {
blesscontextprintf(context, kBLLogLevelError, "Can't set Open Firmware\n" );
return 1;
} else {
blesscontextprintf(context, kBLLogLevelVerbose, "Open Firmware set successfully\n" );
}
} else if( preboot == kBLPreBootEnvType_BIOS){
err = BLSetActiveBIOSBootDevice(context, device);
if(err) {
blesscontextprintf(context, kBLLogLevelError, "Can't set active boot partition as %s\n", device);
return 4;
} else {
blesscontextprintf(context, kBLLogLevelVerbose, "%s set as active boot partition\n" , device);
}
} else if(preboot == kBLPreBootEnvType_EFI) {
err = setefidevice(context, device + 5, 0, NULL);
if(err) {
blesscontextprintf(context, kBLLogLevelError, "Can't set EFI\n" );
return 1;
} else {
blesscontextprintf(context, kBLLogLevelVerbose, "EFI set successfully\n" );
}
}
return 0;
}
static int updateAppleBootIfPresent(BLContextPtr context, char *device, CFDataRef bootxData,
CFDataRef labelData)
{
char booterDev[MAXPATHLEN];
io_service_t service = 0;
CFStringRef name = NULL;
int32_t needsBooter = 0;
int32_t isBooter = 0;
BLUpdateBooterFileSpec *spec = NULL;
int32_t specCount = 0, currentCount = 0;
int ret;
strcpy(booterDev, "/dev/");
ret = BLDeviceNeedsBooter(context, device,
&needsBooter,
&isBooter,
&service);
if(ret) {
blesscontextprintf(context, kBLLogLevelError, "Could not determine if partition needs booter\n" );
return 1;
}
if(!(needsBooter || isBooter))
return 0;
for(;;) {
DADiskRef disk = NULL;
DASessionRef session = NULL;
CFDictionaryRef props = NULL;
CFStringRef daName = NULL;
char label[MAXPATHLEN];
if(labelData) break;
session = DASessionCreate(kCFAllocatorDefault);
if(session == NULL) {
blesscontextprintf(context, kBLLogLevelVerbose, "Can't connect to DiskArb\n");
break;
}
disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, device+5);
if(disk == NULL) {
CFRelease(session);
blesscontextprintf(context, kBLLogLevelVerbose, "Can't create DADisk for %s\n",
device + 5);
break;
}
props = DADiskCopyDescription(disk);
if(props == NULL) {
CFRelease(session);
CFRelease(disk);
blesscontextprintf(context, kBLLogLevelVerbose, "Can't get properties for %s\n",
device + 5);
break;
}
daName = CFDictionaryGetValue(props, kDADiskDescriptionVolumeNameKey);
if(daName == NULL) {
CFRelease(props);
CFRelease(disk);
CFRelease(session);
blesscontextprintf(context, kBLLogLevelVerbose, "Can't get properties for %s\n",
device + 5);
break;
}
if(!CFStringGetCString(daName, label, sizeof(label),
kCFStringEncodingUTF8)) {
CFRelease(props);
CFRelease(disk);
CFRelease(session);
break;
}
CFRelease(props);
CFRelease(disk);
CFRelease(session);
ret = BLGenerateOFLabel(context, label, &labelData);
if(ret)
labelData = NULL;
break;
}
if(!(bootxData || labelData)) {
IOObjectRelease(service);
return 0;
}
name = IORegistryEntryCreateCFProperty( service, CFSTR(kIOBSDNameKey),
kCFAllocatorDefault, 0);
if(name == NULL || CFStringGetTypeID() != CFGetTypeID(name)) {
IOObjectRelease(service);
blesscontextprintf(context, kBLLogLevelError, "Could not find bsd name for %x\n" , service);
return 2;
}
IOObjectRelease(service); service = 0;
if(!CFStringGetCString(name,booterDev+5,sizeof(booterDev)-5,kCFStringEncodingUTF8)) {
CFRelease(name);
blesscontextprintf(context, kBLLogLevelError, "Could not find bsd name for %x\n" , service);
return 3;
}
CFRelease(name);
if(labelData) specCount += 2;
if(bootxData) specCount += 1;
spec = calloc(specCount, sizeof(spec[0]));
if(labelData) {
spec[currentCount+0].version = 0;
spec[currentCount+0].reqType = kBL_OSTYPE_PPC_TYPE_OFLABEL;
spec[currentCount+0].reqCreator = kBL_OSTYPE_PPC_CREATOR_CHRP;
spec[currentCount+0].reqFilename = NULL;
spec[currentCount+0].payloadData = labelData;
spec[currentCount+0].postType = 0; spec[currentCount+0].postCreator = 0; spec[currentCount+0].foundFile = 0;
spec[currentCount+0].updatedFile = 0;
spec[currentCount+1].version = 0;
spec[currentCount+1].reqType = kBL_OSTYPE_PPC_TYPE_OFLABEL_PLACEHOLDER;
spec[currentCount+1].reqCreator = kBL_OSTYPE_PPC_CREATOR_CHRP;
spec[currentCount+1].reqFilename = NULL;
spec[currentCount+1].payloadData = labelData;
spec[currentCount+1].postType = kBL_OSTYPE_PPC_TYPE_OFLABEL;
spec[currentCount+1].postCreator = 0; spec[currentCount+1].foundFile = 0;
spec[currentCount+1].updatedFile = 0;
currentCount += 2;
}
if(bootxData) {
spec[currentCount+0].version = 0;
spec[currentCount+0].reqType = kBL_OSTYPE_PPC_TYPE_BOOTX;
spec[currentCount+0].reqCreator = kBL_OSTYPE_PPC_CREATOR_CHRP;
spec[currentCount+0].reqFilename = NULL;
spec[currentCount+0].payloadData = bootxData;
spec[currentCount+0].postType = 0; spec[currentCount+0].postCreator = 0; spec[currentCount+0].foundFile = 0;
spec[currentCount+0].updatedFile = 0;
}
ret = BLUpdateBooter(context, booterDev, spec, specCount);
if(ret) {
blesscontextprintf(context, kBLLogLevelError, "Error enumerating HFS+ volume\n");
return 1;
}
if(bootxData) {
if(!(spec[currentCount].foundFile)) {
blesscontextprintf(context, kBLLogLevelError, "No pre-existing BootX found in HFS+ volume\n");
return 2;
}
if(!(spec[currentCount].updatedFile)) {
blesscontextprintf(context, kBLLogLevelError, "BootX was not updated\n");
return 3;
}
}
if(labelData) {
if(!(spec[0].foundFile || spec[1].foundFile)) {
blesscontextprintf(context, kBLLogLevelError, "No pre-existing OF label found in HFS+ volume\n");
return 2;
}
if(!(spec[0].updatedFile || spec[1].updatedFile)) {
blesscontextprintf(context, kBLLogLevelError, "OF label was not updated\n");
return 3;
}
}
free(spec);
return 0;
}
int setefidevice(BLContextPtr context, const char * bsdname, int bootNext, const char *optionalData)
{
int ret;
CFStringRef xmlString = NULL;
const char *bootString = NULL;
ret = BLCreateEFIXMLRepresentationForDevice(context,
bsdname,
optionalData,
&xmlString);
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 = 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 = setefibootargs(context, kIOMasterPortDefault);
if(ret) return ret;
return ret;
}
static 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(MACH_PORT_NULL == optionsNode) {
blesscontextprintf(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);
blesscontextprintf(context, kBLLogLevelVerbose, "Setting EFI NVRAM:\n" );
blesscontextprintf(context, kBLLogLevelVerbose, "\t%s='%s'\n", bootvar, cStr );
kret = IORegistryEntrySetCFProperty(optionsNode, bootName, xmlstring);
if(kret) {
IOObjectRelease(optionsNode);
blesscontextprintf(context, kBLLogLevelError, "Could not set boot device property: %#x\n", kret);
return 2;
}
IOObjectRelease(optionsNode);
return 0;
}
int setefifilepath(BLContextPtr context, const char * path, int bootNext, const char *optionalData)
{
CFStringRef xmlString = NULL;
const char *bootString = NULL;
int ret;
ret = BLCreateEFIXMLRepresentationForPath(context,
path,
optionalData,
&xmlString);
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 = 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 = setefibootargs(context, kIOMasterPortDefault);
if(ret) return ret;
return 0;
}
int setefinetworkpath(BLContextPtr context, const char * interface,
const char *host, const char *path, int bootNext,
const char *optionalData)
{
CFStringRef xmlString = NULL;
const char *bootString = NULL;
int ret;
ret = BLCreateEFIXMLRepresentationForNetworkPath(context,
interface,
host,
path,
optionalData,
&xmlString);
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 = 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 = setefibootargs(context, kIOMasterPortDefault);
if(ret) return ret;
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) {
blesscontextprintf(context, kBLLogLevelError, "Error getting NVRAM variable \"boot-args\"\n");
return 1;
}
if(newString == NULL) {
blesscontextprintf(context, kBLLogLevelVerbose, "NVRAM variable \"boot-args\" not set.\n");
return 0;
}
if(!CFStringGetCString(newString, cStr, sizeof(cStr), kCFStringEncodingUTF8)) {
blesscontextprintf(context, kBLLogLevelError, "Could not interpret boot-args as string. Ignoring...\n");
strcpy(cStr, "");
}
ret = BLPreserveBootArgs(context, cStr, newArgs);
if(ret) {
return ret;
}
newString = CFStringCreateWithCString(kCFAllocatorDefault, newArgs, kCFStringEncodingUTF8);
if(newString == NULL) {
return 2;
}
ret = setit(context, masterPort, "boot-args", newString);
if(ret)
return ret;
return 0;
}