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