1 /* 2 * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <errno.h> 8 #include <fcntl.h> 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 #include <unistd.h> 13 #include <sys/stat.h> 14 15 #include <ByteOrder.h> 16 #include <Drivers.h> 17 #include <Entry.h> 18 #include <File.h> 19 #include <fs_info.h> 20 #include <Resources.h> 21 #include <TypeConstants.h> 22 23 // Linux and FreeBSD support 24 #ifdef HAIKU_HOST_PLATFORM_LINUX 25 # include <ctype.h> 26 # include <linux/fs.h> 27 # include <linux/hdreg.h> 28 # include <sys/ioctl.h> 29 30 # define USE_PARTITION_MAP 1 31 #elif HAIKU_HOST_PLATFORM_FREEBSD 32 # include <ctype.h> 33 # include <sys/disklabel.h> 34 # include <sys/disk.h> 35 # include <sys/ioctl.h> 36 37 # define USE_PARTITION_MAP 1 38 #elif HAIKU_HOST_PLATFORM_DARWIN 39 # include <ctype.h> 40 # include <sys/disk.h> 41 # include <sys/ioctl.h> 42 43 # define USE_PARTITION_MAP 1 44 #endif 45 46 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 47 # include <image.h> 48 49 # include <DiskDevice.h> 50 # include <DiskDeviceRoster.h> 51 # include <Partition.h> 52 # include <Path.h> 53 54 # include "bfs_control.h" 55 #endif 56 57 #if USE_PARTITION_MAP 58 # include "guid.h" 59 # include "gpt_known_guids.h" 60 # include "Header.h" 61 # include "PartitionMap.h" 62 # include "PartitionMapParser.h" 63 #endif 64 65 66 static const char *kCommandName = "makebootable"; 67 68 static const int kBootCodeSize = 1024; 69 static const int kFirstBootCodePartSize = 512; 70 static const int kSecondBootCodePartOffset = 676; 71 static const int kSecondBootCodePartSize = kBootCodeSize 72 - kSecondBootCodePartOffset; 73 static const int kPartitionOffsetOffset = 506; 74 75 static int kArgc; 76 static const char *const *kArgv; 77 78 // usage 79 const char *kUsage = 80 "Usage: %s [ options ] <file> ...\n" 81 "\n" 82 "Makes the specified BFS partitions/devices bootable by writing boot code\n" 83 "into the first two sectors. It doesn't mark the partition(s) active.\n" 84 "\n" 85 "If a given <file> refers to a directory, the partition/device on which the\n" 86 "directory resides will be made bootable. If it refers to a regular file,\n" 87 "the file is considered a disk image and the boot code will be written to\n" 88 "it.\n" 89 "\n" 90 "Options:\n" 91 " -h, --help - Print this help text and exit.\n" 92 " --dry-run - Do everything but actually writing the boot block to disk.\n" 93 ; 94 95 96 // print_usage 97 static void 98 print_usage(bool error) 99 { 100 // get command name 101 const char *commandName = NULL; 102 if (kArgc > 0) { 103 if (const char *lastSlash = strchr(kArgv[0], '/')) 104 commandName = lastSlash + 1; 105 else 106 commandName = kArgv[0]; 107 } 108 109 if (!commandName || strlen(commandName) == 0) 110 commandName = kCommandName; 111 112 // print usage 113 fprintf((error ? stderr : stdout), kUsage, commandName, commandName, 114 commandName); 115 } 116 117 118 // print_usage_and_exit 119 static void 120 print_usage_and_exit(bool error) 121 { 122 print_usage(error); 123 exit(error ? 1 : 0); 124 } 125 126 127 // read_boot_code_data 128 static uint8 * 129 read_boot_code_data(const char* programPath) 130 { 131 // open our executable 132 BFile executableFile; 133 status_t error = executableFile.SetTo(programPath, B_READ_ONLY); 134 if (error != B_OK) { 135 fprintf(stderr, "Error: Failed to open my executable file (\"%s\": " 136 "%s\n", programPath, strerror(error)); 137 exit(1); 138 } 139 140 uint8 *bootCodeData = new uint8[kBootCodeSize]; 141 142 // open our resources 143 BResources resources; 144 error = resources.SetTo(&executableFile); 145 const void *resourceData = NULL; 146 if (error == B_OK) { 147 // read the boot block from the resources 148 size_t resourceSize; 149 resourceData = resources.LoadResource(B_RAW_TYPE, 666, &resourceSize); 150 151 if (resourceData && resourceSize != (size_t)kBootCodeSize) { 152 resourceData = NULL; 153 printf("Warning: Something is fishy with my resources! The boot " 154 "code doesn't have the correct size. Trying the attribute " 155 "instead ...\n"); 156 } 157 } 158 159 if (resourceData) { 160 // found boot data in the resources 161 memcpy(bootCodeData, resourceData, kBootCodeSize); 162 } else { 163 // no boot data in the resources; try the attribute 164 ssize_t bytesRead = executableFile.ReadAttr("BootCode", B_RAW_TYPE, 165 0, bootCodeData, kBootCodeSize); 166 if (bytesRead < 0) { 167 fprintf(stderr, "Error: Failed to read boot code from resources " 168 "or attribute.\n"); 169 exit(1); 170 } 171 if (bytesRead != kBootCodeSize) { 172 fprintf(stderr, "Error: Failed to read boot code from resources, " 173 "and the boot code in the attribute has the wrong size!\n"); 174 exit(1); 175 } 176 } 177 178 return bootCodeData; 179 } 180 181 182 // write_boot_code_part 183 static void 184 write_boot_code_part(const char *fileName, int fd, off_t imageOffset, 185 const uint8 *bootCodeData, int offset, int size, bool dryRun) 186 { 187 if (!dryRun) { 188 ssize_t bytesWritten = write_pos(fd, imageOffset + offset, 189 bootCodeData + offset, size); 190 if (bytesWritten != size) { 191 fprintf(stderr, "Error: Failed to write to \"%s\": %s\n", fileName, 192 strerror(bytesWritten < 0 ? errno : B_ERROR)); 193 } 194 } 195 } 196 197 198 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 199 200 static status_t 201 find_own_image(image_info *info) 202 { 203 int32 cookie = 0; 204 while (get_next_image_info(B_CURRENT_TEAM, &cookie, info) == B_OK) { 205 if (((addr_t)info->text <= (addr_t)find_own_image 206 && (addr_t)info->text + info->text_size 207 > (addr_t)find_own_image)) { 208 return B_OK; 209 } 210 } 211 212 return B_NAME_NOT_FOUND; 213 } 214 215 #endif 216 217 218 #if USE_PARTITION_MAP 219 220 static void 221 dump_partition_map(const PartitionMap& map) 222 { 223 fprintf(stderr, "partitions:\n"); 224 int32 count = map.CountPartitions(); 225 for (int i = 0; i < count; i++) { 226 const Partition* partition = map.PartitionAt(i); 227 fprintf(stderr, "%2d: ", i); 228 if (partition == NULL) { 229 fprintf(stderr, "<null>\n"); 230 continue; 231 } 232 233 if (partition->IsEmpty()) { 234 fprintf(stderr, "<empty>\n"); 235 continue; 236 } 237 238 fprintf(stderr, "offset: %16" B_PRIdOFF ", size: %16" B_PRIdOFF 239 ", type: %x%s\n", partition->Offset(), partition->Size(), 240 partition->Type(), partition->IsExtended() ? " (extended)" : ""); 241 } 242 } 243 244 245 static void 246 get_partition_offset(int deviceFD, off_t deviceStart, off_t deviceSize, 247 int blockSize, int partitionIndex, char *deviceName, 248 int64 &_partitionOffset) 249 { 250 PartitionMapParser parser(deviceFD, deviceStart, deviceSize, blockSize); 251 PartitionMap map; 252 status_t error = parser.Parse(NULL, &map); 253 if (error == B_OK) { 254 Partition *partition = map.PartitionAt(partitionIndex - 1); 255 if (!partition || partition->IsEmpty()) { 256 fprintf(stderr, "Error: Invalid partition index %d.\n", 257 partitionIndex); 258 dump_partition_map(map); 259 exit(1); 260 } 261 262 if (partition->IsExtended()) { 263 fprintf(stderr, "Error: Partition %d is an extended " 264 "partition.\n", partitionIndex); 265 dump_partition_map(map); 266 exit(1); 267 } 268 269 _partitionOffset = partition->Offset(); 270 } else { 271 // try again using GPT instead 272 EFI::Header gptHeader(deviceFD, deviceSize, blockSize); 273 error = gptHeader.InitCheck(); 274 if (error == B_OK && partitionIndex < gptHeader.EntryCount()) { 275 gpt_partition_entry partition = gptHeader.EntryAt(partitionIndex - 1); 276 277 static_guid bfs_uuid = {0x42465331, 0x3BA3, 0x10F1, 278 0x802A4861696B7521LL}; 279 280 if (!(bfs_uuid == partition.partition_type)) { 281 fprintf(stderr, "Error: Partition %d does not have the " 282 "BFS UUID.\n", partitionIndex); 283 exit(1); 284 } 285 286 _partitionOffset = partition.StartBlock() * blockSize; 287 } else { 288 fprintf(stderr, "Error: Parsing partition table on device " 289 "\"%s\" failed: %s\n", deviceName, strerror(error)); 290 exit(1); 291 } 292 } 293 294 close(deviceFD); 295 } 296 297 #endif 298 299 300 // main 301 int 302 main(int argc, const char *const *argv) 303 { 304 kArgc = argc; 305 kArgv = argv; 306 307 if (argc < 2) 308 print_usage_and_exit(true); 309 310 // parameters 311 const char **files = new const char*[argc]; 312 int fileCount = 0; 313 bool dryRun = false; 314 off_t startOffset = 0; 315 316 // parse arguments 317 for (int argi = 1; argi < argc;) { 318 const char *arg = argv[argi++]; 319 320 if (arg[0] == '-') { 321 if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) { 322 print_usage_and_exit(false); 323 } else if (strcmp(arg, "--dry-run") == 0) { 324 dryRun = true; 325 } else if (strcmp(arg, "--start-offset") == 0) { 326 if (argi >= argc) 327 print_usage_and_exit(true); 328 startOffset = strtoll(argv[argi++], NULL, 0); 329 } else { 330 print_usage_and_exit(true); 331 } 332 333 } else { 334 files[fileCount++] = arg; 335 } 336 } 337 338 // we need at least one file 339 if (fileCount == 0) 340 print_usage_and_exit(true); 341 342 // read the boot code 343 uint8 *bootCodeData = NULL; 344 #ifndef HAIKU_TARGET_PLATFORM_HAIKU 345 bootCodeData = read_boot_code_data(argv[0]); 346 #else 347 image_info info; 348 if (find_own_image(&info) == B_OK) 349 bootCodeData = read_boot_code_data(info.name); 350 #endif 351 if (!bootCodeData) { 352 fprintf(stderr, "Error: Failed to read \n"); 353 exit(1); 354 } 355 356 // iterate through the files and make them bootable 357 status_t error; 358 for (int i = 0; i < fileCount; i++) { 359 const char *fileName = files[i]; 360 BEntry entry; 361 error = entry.SetTo(fileName, true); 362 if (error != B_OK) { 363 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 364 fileName, strerror(error)); 365 exit(1); 366 } 367 368 // get stat to check the type of the file 369 struct stat st; 370 error = entry.GetStat(&st); 371 if (error != B_OK) { 372 fprintf(stderr, "Error: Failed to stat \"%s\": %s\n", 373 fileName, strerror(error)); 374 exit(1); 375 } 376 377 bool noPartition = false; 378 int64 partitionOffset = 0; 379 fs_info info; // needs to be here (we use the device name later) 380 if (S_ISDIR(st.st_mode)) { 381 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 382 383 // a directory: get the device 384 error = fs_stat_dev(st.st_dev, &info); 385 if (error != B_OK) { 386 fprintf(stderr, "Error: Failed to determine device for " 387 "\"%s\": %s\n", fileName, strerror(error)); 388 exit(1); 389 } 390 391 fileName = info.device_name; 392 393 #else 394 395 (void)info; 396 fprintf(stderr, "Error: Specifying directories not supported " 397 "on this platform!\n"); 398 exit(1); 399 400 #endif 401 402 } else if (S_ISREG(st.st_mode)) { 403 // a regular file: fine 404 noPartition = true; 405 } else if (S_ISCHR(st.st_mode)) { 406 // character special: a device or partition under BeOS 407 // or under FreeBSD 408 #if !defined(HAIKU_TARGET_PLATFORM_HAIKU) \ 409 && !defined(HAIKU_HOST_PLATFORM_FREEBSD) 410 411 fprintf(stderr, "Error: Character special devices not " 412 "supported on this platform.\n"); 413 exit(1); 414 415 #endif 416 417 #ifdef HAIKU_HOST_PLATFORM_FREEBSD 418 419 // chop off the trailing number 420 int fileNameLen = strlen(fileName); 421 int baseNameLen = -1; 422 for (int k = fileNameLen - 1; k >= 0; k--) { 423 if (!isdigit(fileName[k])) { 424 baseNameLen = k + 1; 425 break; 426 } 427 } 428 429 // Remove de 's' from 'ad2s2' slice device (partition for DOS 430 // users) to get 'ad2' base device 431 baseNameLen--; 432 433 if (baseNameLen < 0) { 434 // only digits? 435 fprintf(stderr, "Error: Failed to get base device name.\n"); 436 exit(1); 437 } 438 439 if (baseNameLen < fileNameLen) { 440 // get base device name and partition index 441 char baseDeviceName[B_PATH_NAME_LENGTH]; 442 int partitionIndex = atoi(fileName + baseNameLen + 1); 443 // Don't forget the 's' of slice :) 444 memcpy(baseDeviceName, fileName, baseNameLen); 445 baseDeviceName[baseNameLen] = '\0'; 446 447 // open base device 448 int baseFD = open(baseDeviceName, O_RDONLY); 449 if (baseFD < 0) { 450 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 451 baseDeviceName, strerror(errno)); 452 exit(1); 453 } 454 455 // get device size 456 int64 deviceSize; 457 if (ioctl(baseFD, DIOCGMEDIASIZE, &deviceSize) == -1) { 458 fprintf(stderr, "Error: Failed to get device geometry " 459 "for \"%s\": %s\n", baseDeviceName, 460 strerror(errno)); 461 exit(1); 462 } 463 464 // parse the partition map 465 // TODO: block size! 466 get_partition_offset(baseFD, 0, deviceSize, 512, 467 partitionIndex, baseDeviceName, partitionOffset); 468 } else { 469 // The given device is the base device. We'll write at 470 // offset 0. 471 } 472 473 #endif // HAIKU_HOST_PLATFORM_FREEBSD 474 475 } else if (S_ISBLK(st.st_mode)) { 476 // block device: a device or partition under Linux or Darwin 477 #ifdef HAIKU_HOST_PLATFORM_LINUX 478 479 // chop off the trailing number 480 int fileNameLen = strlen(fileName); 481 int baseNameLen = -1; 482 for (int k = fileNameLen - 1; k >= 0; k--) { 483 if (!isdigit(fileName[k])) { 484 baseNameLen = k + 1; 485 break; 486 } 487 } 488 489 if (baseNameLen < 0) { 490 // only digits? 491 fprintf(stderr, "Error: Failed to get base device name.\n"); 492 exit(1); 493 } 494 495 if (baseNameLen < fileNameLen) { 496 // get base device name and partition index 497 char baseDeviceName[B_PATH_NAME_LENGTH]; 498 int partitionIndex = atoi(fileName + baseNameLen); 499 memcpy(baseDeviceName, fileName, baseNameLen); 500 baseDeviceName[baseNameLen] = '\0'; 501 502 // open base device 503 int baseFD = open(baseDeviceName, O_RDONLY); 504 if (baseFD < 0) { 505 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 506 baseDeviceName, strerror(errno)); 507 exit(1); 508 } 509 510 // get device size -- try BLKGETSIZE64, but, if it doesn't 511 // work, fall back to the obsolete HDIO_GETGEO 512 int64 deviceSize; 513 hd_geometry geometry; 514 if (ioctl(baseFD, BLKGETSIZE64, &deviceSize) == 0 515 && deviceSize > 0) { 516 // looks good 517 } else if (ioctl(baseFD, HDIO_GETGEO, &geometry) == 0) { 518 deviceSize = (int64)geometry.heads * geometry.sectors 519 * geometry.cylinders * 512; 520 } else { 521 fprintf(stderr, "Error: Failed to get device geometry " 522 "for \"%s\": %s\n", baseDeviceName, 523 strerror(errno)); 524 exit(1); 525 } 526 527 // parse the partition map 528 // TODO: block size! 529 get_partition_offset(baseFD, 0, deviceSize, 512, 530 partitionIndex, baseDeviceName, partitionOffset); 531 } else { 532 // The given device is the base device. We'll write at 533 // offset 0. 534 } 535 536 #elif defined(HAIKU_HOST_PLATFORM_DARWIN) 537 // chop off the trailing number 538 int fileNameLen = strlen(fileName); 539 int baseNameLen = fileNameLen - 2; 540 541 // get base device name and partition index 542 char baseDeviceName[B_PATH_NAME_LENGTH]; 543 int partitionIndex = atoi(fileName + baseNameLen + 1); 544 memcpy(baseDeviceName, fileName, baseNameLen); 545 baseDeviceName[baseNameLen] = '\0'; 546 547 // open base device 548 int baseFD = open(baseDeviceName, O_RDONLY); 549 if (baseFD < 0) { 550 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 551 baseDeviceName, strerror(errno)); 552 exit(1); 553 } 554 555 // get device size 556 int64 blockSize; 557 int64 blockCount; 558 int64 deviceSize; 559 if (ioctl(baseFD, DKIOCGETBLOCKSIZE, &blockSize) == -1) { 560 fprintf(stderr, "Error: Failed to get block size " 561 "for \"%s\": %s\n", baseDeviceName, 562 strerror(errno)); 563 exit(1); 564 } 565 if (ioctl(baseFD, DKIOCGETBLOCKCOUNT, &blockCount) == -1) { 566 fprintf(stderr, "Error: Failed to get block count " 567 "for \"%s\": %s\n", baseDeviceName, 568 strerror(errno)); 569 exit(1); 570 } 571 572 deviceSize = blockSize * blockCount; 573 574 // parse the partition map 575 get_partition_offset(baseFD, 0, deviceSize, 512, 576 partitionIndex, baseDeviceName, partitionOffset); 577 #else 578 // partitions are block devices under Haiku, but not under BeOS 579 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 580 fprintf(stderr, "Error: Block devices not supported on this " 581 "platform!\n"); 582 exit(1); 583 #endif // HAIKU_TARGET_PLATFORM_HAIKU 584 585 #endif 586 } else { 587 fprintf(stderr, "Error: File type of \"%s\" is not supported.\n", 588 fileName); 589 exit(1); 590 } 591 592 // open the file 593 int fd = open(fileName, O_RDWR); 594 if (fd < 0) { 595 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", fileName, 596 strerror(errno)); 597 exit(1); 598 } 599 600 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 601 602 // get a partition info 603 if (!noPartition 604 && strlen(fileName) >= 3 605 && strncmp("raw", fileName + strlen(fileName) - 3, 3)) { 606 partition_info partitionInfo; 607 if (ioctl(fd, B_GET_PARTITION_INFO, &partitionInfo, 608 sizeof(partitionInfo)) == 0) { 609 partitionOffset = partitionInfo.offset; 610 } else { 611 fprintf(stderr, "Error: Failed to get partition info: %s\n", 612 strerror(errno)); 613 exit(1); 614 } 615 } 616 617 #endif // HAIKU_TARGET_PLATFORM_HAIKU 618 619 // adjust the partition offset in the boot code data 620 // hard coded sector size: 512 bytes 621 *(uint32*)(bootCodeData + kPartitionOffsetOffset) 622 = B_HOST_TO_LENDIAN_INT32((uint32)(partitionOffset / 512)); 623 624 // write the boot code 625 printf("Writing boot code to \"%s\" (partition offset: %" B_PRId64 626 " bytes, start offset = %" B_PRIdOFF ") " 627 "...\n", fileName, partitionOffset, startOffset); 628 629 write_boot_code_part(fileName, fd, startOffset, bootCodeData, 0, 630 kFirstBootCodePartSize, dryRun); 631 write_boot_code_part(fileName, fd, startOffset, bootCodeData, 632 kSecondBootCodePartOffset, kSecondBootCodePartSize, 633 dryRun); 634 635 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 636 // check if this partition is mounted 637 BDiskDeviceRoster roster; 638 BPartition* partition; 639 BDiskDevice device; 640 status_t status = roster.GetPartitionForPath(fileName, &device, 641 &partition); 642 if (status != B_OK) { 643 status = roster.GetFileDeviceForPath(fileName, &device); 644 if (status == B_OK) 645 partition = &device; 646 } 647 if (status == B_OK && partition->IsMounted() && !dryRun) { 648 // This partition is mounted, we need to tell BFS to update its 649 // boot block (we are using part of the same logical block). 650 BPath path; 651 status = partition->GetMountPoint(&path); 652 if (status == B_OK) { 653 update_boot_block update; 654 update.offset = kSecondBootCodePartOffset - 512; 655 update.data = bootCodeData + kSecondBootCodePartOffset; 656 update.length = kSecondBootCodePartSize; 657 658 int mountFD = open(path.Path(), O_RDONLY); 659 if (ioctl(mountFD, BFS_IOCTL_UPDATE_BOOT_BLOCK, &update, 660 sizeof(update_boot_block)) != 0) { 661 fprintf(stderr, "Could not update BFS boot block: %s\n", 662 strerror(errno)); 663 } 664 close(mountFD); 665 } else { 666 fprintf(stderr, "Could not update BFS boot code while the " 667 "partition is mounted!\n"); 668 } 669 } 670 #endif // HAIKU_TARGET_PLATFORM_HAIKU 671 672 close(fd); 673 } 674 675 delete[] files; 676 677 return 0; 678 } 679