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 "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_TARGET_PLATFORM_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: %16" B_PRIdOFF ", size: %16" B_PRIdOFF 241 ", type: %x%s\n", partition->Offset(), 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_TARGET_PLATFORM_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 #ifdef HAIKU_TARGET_PLATFORM_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(HAIKU_TARGET_PLATFORM_HAIKU) \ 365 && !defined(HAIKU_HOST_PLATFORM_FREEBSD) 366 367 fprintf(stderr, "Error: Character special devices not " 368 "supported on this platform.\n"); 369 exit(1); 370 371 #endif 372 373 #ifdef HAIKU_HOST_PLATFORM_FREEBSD 374 375 // chop off the trailing number 376 int fileNameLen = strlen(fileName); 377 int baseNameLen = -1; 378 for (int k = fileNameLen - 1; k >= 0; k--) { 379 if (!isdigit(fileName[k])) { 380 baseNameLen = k + 1; 381 break; 382 } 383 } 384 385 // Remove de 's' from 'ad2s2' slice device (partition for DOS 386 // users) to get 'ad2' base device 387 baseNameLen--; 388 389 if (baseNameLen < 0) { 390 // only digits? 391 fprintf(stderr, "Error: Failed to get base device name.\n"); 392 exit(1); 393 } 394 395 if (baseNameLen < fileNameLen) { 396 // get base device name and partition index 397 char baseDeviceName[B_PATH_NAME_LENGTH]; 398 int partitionIndex = atoi(fileName + baseNameLen + 1); 399 // Don't forget the 's' of slice :) 400 memcpy(baseDeviceName, fileName, baseNameLen); 401 baseDeviceName[baseNameLen] = '\0'; 402 403 // open base device 404 int baseFD = open(baseDeviceName, O_RDONLY); 405 if (baseFD < 0) { 406 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 407 baseDeviceName, strerror(errno)); 408 exit(1); 409 } 410 411 // get device size 412 int64 deviceSize; 413 if (ioctl(baseFD, DIOCGMEDIASIZE, &deviceSize) == -1) { 414 fprintf(stderr, "Error: Failed to get device geometry " 415 "for \"%s\": %s\n", baseDeviceName, 416 strerror(errno)); 417 exit(1); 418 } 419 420 // parse the partition map 421 // TODO: block size! 422 PartitionMapParser parser(baseFD, 0, deviceSize, 512); 423 PartitionMap map; 424 error = parser.Parse(NULL, &map); 425 if (error != B_OK) { 426 fprintf(stderr, "Error: Parsing partition table on " 427 "device \"%s\" failed: %s\n", baseDeviceName, 428 strerror(error)); 429 exit(1); 430 } 431 432 close(baseFD); 433 434 // check the partition we are supposed to write at 435 Partition *partition = map.PartitionAt(partitionIndex - 1); 436 if (!partition || partition->IsEmpty()) { 437 fprintf(stderr, "Error: Invalid partition index %d.\n", 438 partitionIndex); 439 dump_partition_map(map); 440 exit(1); 441 } 442 443 if (partition->IsExtended()) { 444 fprintf(stderr, "Error: Partition %d is an extended " 445 "partition.\n", partitionIndex); 446 dump_partition_map(map); 447 exit(1); 448 } 449 450 partitionOffset = partition->Offset(); 451 452 } else { 453 // The given device is the base device. We'll write at 454 // offset 0. 455 } 456 457 #endif // HAIKU_HOST_PLATFORM_FREEBSD 458 459 } else if (S_ISBLK(st.st_mode)) { 460 // block device: a device or partition under Linux or Darwin 461 #ifdef HAIKU_HOST_PLATFORM_LINUX 462 463 // chop off the trailing number 464 int fileNameLen = strlen(fileName); 465 int baseNameLen = -1; 466 for (int k = fileNameLen - 1; k >= 0; k--) { 467 if (!isdigit(fileName[k])) { 468 baseNameLen = k + 1; 469 break; 470 } 471 } 472 473 if (baseNameLen < 0) { 474 // only digits? 475 fprintf(stderr, "Error: Failed to get base device name.\n"); 476 exit(1); 477 } 478 479 if (baseNameLen < fileNameLen) { 480 // get base device name and partition index 481 char baseDeviceName[B_PATH_NAME_LENGTH]; 482 int partitionIndex = atoi(fileName + baseNameLen); 483 memcpy(baseDeviceName, fileName, baseNameLen); 484 baseDeviceName[baseNameLen] = '\0'; 485 486 // open base device 487 int baseFD = open(baseDeviceName, O_RDONLY); 488 if (baseFD < 0) { 489 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 490 baseDeviceName, strerror(errno)); 491 exit(1); 492 } 493 494 // get device size -- try BLKGETSIZE64, but, if it doesn't 495 // work, fall back to the obsolete HDIO_GETGEO 496 int64 deviceSize; 497 hd_geometry geometry; 498 if (ioctl(baseFD, BLKGETSIZE64, &deviceSize) == 0 499 && deviceSize > 0) { 500 // looks good 501 } else if (ioctl(baseFD, HDIO_GETGEO, &geometry) == 0) { 502 deviceSize = (int64)geometry.heads * geometry.sectors 503 * geometry.cylinders * 512; 504 } else { 505 fprintf(stderr, "Error: Failed to get device geometry " 506 "for \"%s\": %s\n", baseDeviceName, 507 strerror(errno)); 508 exit(1); 509 } 510 511 // parse the partition map 512 // TODO: block size! 513 PartitionMapParser parser(baseFD, 0, deviceSize, 512); 514 PartitionMap map; 515 error = parser.Parse(NULL, &map); 516 if (error != B_OK) { 517 fprintf(stderr, "Error: Parsing partition table on " 518 "device \"%s\" failed: %s\n", baseDeviceName, 519 strerror(error)); 520 exit(1); 521 } 522 523 close(baseFD); 524 525 // check the partition we are supposed to write at 526 Partition *partition = map.PartitionAt(partitionIndex - 1); 527 if (!partition || partition->IsEmpty()) { 528 fprintf(stderr, "Error: Invalid partition index %d.\n", 529 partitionIndex); 530 dump_partition_map(map); 531 exit(1); 532 } 533 534 if (partition->IsExtended()) { 535 fprintf(stderr, "Error: Partition %d is an extended " 536 "partition.\n", partitionIndex); 537 dump_partition_map(map); 538 exit(1); 539 } 540 541 partitionOffset = partition->Offset(); 542 } else { 543 // The given device is the base device. We'll write at 544 // offset 0. 545 } 546 547 #elif defined(HAIKU_HOST_PLATFORM_DARWIN) 548 // chop off the trailing number 549 int fileNameLen = strlen(fileName); 550 int baseNameLen = fileNameLen - 2; 551 552 // get base device name and partition index 553 char baseDeviceName[B_PATH_NAME_LENGTH]; 554 int partitionIndex = atoi(fileName + baseNameLen + 1); 555 memcpy(baseDeviceName, fileName, baseNameLen); 556 baseDeviceName[baseNameLen] = '\0'; 557 558 // open base device 559 int baseFD = open(baseDeviceName, O_RDONLY); 560 if (baseFD < 0) { 561 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 562 baseDeviceName, strerror(errno)); 563 exit(1); 564 } 565 566 // get device size 567 int64 blockSize; 568 int64 blockCount; 569 int64 deviceSize; 570 if (ioctl(baseFD, DKIOCGETBLOCKSIZE, &blockSize) == -1) { 571 fprintf(stderr, "Error: Failed to get block size " 572 "for \"%s\": %s\n", baseDeviceName, 573 strerror(errno)); 574 exit(1); 575 } 576 if (ioctl(baseFD, DKIOCGETBLOCKCOUNT, &blockCount) == -1) { 577 fprintf(stderr, "Error: Failed to get block count " 578 "for \"%s\": %s\n", baseDeviceName, 579 strerror(errno)); 580 exit(1); 581 } 582 583 deviceSize = blockSize * blockCount; 584 585 // parse the partition map 586 PartitionMapParser parser(baseFD, 0, deviceSize, blockSize); 587 PartitionMap map; 588 error = parser.Parse(NULL, &map); 589 if (error != B_OK) { 590 fprintf(stderr, "Error: Parsing partition table on " 591 "device \"%s\" failed: %s\n", baseDeviceName, 592 strerror(error)); 593 exit(1); 594 } 595 596 close(baseFD); 597 598 // check the partition we are supposed to write at 599 Partition *partition = map.PartitionAt(partitionIndex - 1); 600 if (!partition || partition->IsEmpty()) { 601 fprintf(stderr, "Error: Invalid partition index %d.\n", 602 partitionIndex); 603 dump_partition_map(map); 604 exit(1); 605 } 606 607 if (partition->IsExtended()) { 608 fprintf(stderr, "Error: Partition %d is an extended " 609 "partition.\n", partitionIndex); 610 dump_partition_map(map); 611 exit(1); 612 } 613 partitionOffset = partition->Offset(); 614 #else 615 // partitions are block devices under Haiku, but not under BeOS 616 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 617 fprintf(stderr, "Error: Block devices not supported on this " 618 "platform!\n"); 619 exit(1); 620 #endif // HAIKU_TARGET_PLATFORM_HAIKU 621 622 #endif 623 } else { 624 fprintf(stderr, "Error: File type of \"%s\" is not supported.\n", 625 fileName); 626 exit(1); 627 } 628 629 // open the file 630 int fd = open(fileName, O_RDWR); 631 if (fd < 0) { 632 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", fileName, 633 strerror(errno)); 634 exit(1); 635 } 636 637 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 638 639 // get a partition info 640 if (!noPartition 641 && strlen(fileName) >= 3 642 && strncmp("raw", fileName + strlen(fileName) - 3, 3)) { 643 partition_info partitionInfo; 644 if (ioctl(fd, B_GET_PARTITION_INFO, &partitionInfo, 645 sizeof(partitionInfo)) == 0) { 646 partitionOffset = partitionInfo.offset; 647 } else { 648 fprintf(stderr, "Error: Failed to get partition info: %s\n", 649 strerror(errno)); 650 exit(1); 651 } 652 } 653 654 #endif // __BEOS__ 655 656 // adjust the partition offset in the boot code data 657 // hard coded sector size: 512 bytes 658 *(uint32*)(bootCodeData + kPartitionOffsetOffset) 659 = B_HOST_TO_LENDIAN_INT32((uint32)(partitionOffset / 512)); 660 661 // write the boot code 662 printf("Writing boot code to \"%s\" (partition offset: %" B_PRId64 663 " bytes, start offset = %" B_PRIdOFF ") " 664 "...\n", fileName, partitionOffset, startOffset); 665 666 write_boot_code_part(fileName, fd, startOffset, bootCodeData, 0, 667 kFirstBootCodePartSize, dryRun); 668 write_boot_code_part(fileName, fd, startOffset, bootCodeData, 669 kSecondBootCodePartOffset, kSecondBootCodePartSize, 670 dryRun); 671 672 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 673 // check if this partition is mounted 674 BDiskDeviceRoster roster; 675 BPartition* partition; 676 BDiskDevice device; 677 status_t status = roster.GetPartitionForPath(fileName, &device, 678 &partition); 679 if (status != B_OK) { 680 status = roster.GetFileDeviceForPath(fileName, &device); 681 if (status == B_OK) 682 partition = &device; 683 } 684 if (status == B_OK && partition->IsMounted() && !dryRun) { 685 // This partition is mounted, we need to tell BFS to update its 686 // boot block (we are using part of the same logical block). 687 BPath path; 688 status = partition->GetMountPoint(&path); 689 if (status == B_OK) { 690 update_boot_block update; 691 update.offset = kSecondBootCodePartOffset - 512; 692 update.data = bootCodeData + kSecondBootCodePartOffset; 693 update.length = kSecondBootCodePartSize; 694 695 int mountFD = open(path.Path(), O_RDONLY); 696 if (ioctl(mountFD, BFS_IOCTL_UPDATE_BOOT_BLOCK, &update, 697 sizeof(update_boot_block)) != 0) { 698 fprintf(stderr, "Could not update BFS boot block: %s\n", 699 strerror(errno)); 700 } 701 close(mountFD); 702 } else { 703 fprintf(stderr, "Could not update BFS boot code while the " 704 "partition is mounted!\n"); 705 } 706 } 707 #endif // HAIKU_TARGET_PLATFORM_HAIKU 708 709 close(fd); 710 } 711 712 return 0; 713 } 714