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