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