/* * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include // Linux and FreeBSD support #ifdef HAIKU_HOST_PLATFORM_LINUX # include # include # include # include # define USE_PARTITION_MAP 1 #elif HAIKU_HOST_PLATFORM_FREEBSD # include # include # include # include # define USE_PARTITION_MAP 1 #elif HAIKU_HOST_PLATFORM_DARWIN # include # include # include # define USE_PARTITION_MAP 1 #endif #ifdef HAIKU_TARGET_PLATFORM_HAIKU # include # include # include # include # include # include # include "bfs_control.h" #endif #if USE_PARTITION_MAP # include "guid.h" # include "gpt_known_guids.h" # include "Header.h" # include "PartitionMap.h" # include "PartitionMapParser.h" #endif static const char *kCommandName = "makebootable"; static const int kBootCodeSize = 1024; static const int kFirstBootCodePartSize = 512; static const int kSecondBootCodePartOffset = 676; static const int kSecondBootCodePartSize = kBootCodeSize - kSecondBootCodePartOffset; static const int kPartitionOffsetOffset = 506; static int kArgc; static const char *const *kArgv; // usage const char *kUsage = "Usage: %s [ options ] ...\n" "\n" "Makes the specified BFS partitions/devices bootable by writing boot code\n" "into the first two sectors. It doesn't mark the partition(s) active.\n" "\n" "If a given refers to a directory, the partition/device on which the\n" "directory resides will be made bootable. If it refers to a regular file,\n" "the file is considered a disk image and the boot code will be written to\n" "it.\n" "\n" "Options:\n" " -h, --help - Print this help text and exit.\n" " --dry-run - Do everything but actually writing the boot block to disk.\n" ; // print_usage static void print_usage(bool error) { // get command name const char *commandName = NULL; if (kArgc > 0) { if (const char *lastSlash = strchr(kArgv[0], '/')) commandName = lastSlash + 1; else commandName = kArgv[0]; } if (!commandName || strlen(commandName) == 0) commandName = kCommandName; // print usage fprintf((error ? stderr : stdout), kUsage, commandName, commandName, commandName); } // print_usage_and_exit static void print_usage_and_exit(bool error) { print_usage(error); exit(error ? 1 : 0); } // read_boot_code_data static uint8 * read_boot_code_data(const char* programPath) { // open our executable BFile executableFile; status_t error = executableFile.SetTo(programPath, B_READ_ONLY); if (error != B_OK) { fprintf(stderr, "Error: Failed to open my executable file (\"%s\": " "%s\n", programPath, strerror(error)); exit(1); } uint8 *bootCodeData = new uint8[kBootCodeSize]; // open our resources BResources resources; error = resources.SetTo(&executableFile); const void *resourceData = NULL; if (error == B_OK) { // read the boot block from the resources size_t resourceSize; resourceData = resources.LoadResource(B_RAW_TYPE, 666, &resourceSize); if (resourceData && resourceSize != (size_t)kBootCodeSize) { resourceData = NULL; printf("Warning: Something is fishy with my resources! The boot " "code doesn't have the correct size. Trying the attribute " "instead ...\n"); } } if (resourceData) { // found boot data in the resources memcpy(bootCodeData, resourceData, kBootCodeSize); } else { // no boot data in the resources; try the attribute ssize_t bytesRead = executableFile.ReadAttr("BootCode", B_RAW_TYPE, 0, bootCodeData, kBootCodeSize); if (bytesRead < 0) { fprintf(stderr, "Error: Failed to read boot code from resources " "or attribute.\n"); exit(1); } if (bytesRead != kBootCodeSize) { fprintf(stderr, "Error: Failed to read boot code from resources, " "and the boot code in the attribute has the wrong size!\n"); exit(1); } } return bootCodeData; } // write_boot_code_part static void write_boot_code_part(const char *fileName, int fd, off_t imageOffset, const uint8 *bootCodeData, int offset, int size, bool dryRun) { if (!dryRun) { ssize_t bytesWritten = write_pos(fd, imageOffset + offset, bootCodeData + offset, size); if (bytesWritten != size) { fprintf(stderr, "Error: Failed to write to \"%s\": %s\n", fileName, strerror(bytesWritten < 0 ? errno : B_ERROR)); } } } #ifdef HAIKU_TARGET_PLATFORM_HAIKU static status_t find_own_image(image_info *info) { int32 cookie = 0; while (get_next_image_info(B_CURRENT_TEAM, &cookie, info) == B_OK) { if (((addr_t)info->text <= (addr_t)find_own_image && (addr_t)info->text + info->text_size > (addr_t)find_own_image)) { return B_OK; } } return B_NAME_NOT_FOUND; } #endif #if USE_PARTITION_MAP static void dump_partition_map(const PartitionMap& map) { fprintf(stderr, "partitions:\n"); int32 count = map.CountPartitions(); for (int i = 0; i < count; i++) { const Partition* partition = map.PartitionAt(i); fprintf(stderr, "%2d: ", i); if (partition == NULL) { fprintf(stderr, "\n"); continue; } if (partition->IsEmpty()) { fprintf(stderr, "\n"); continue; } fprintf(stderr, "offset: %16" B_PRIdOFF ", size: %16" B_PRIdOFF ", type: %x%s\n", partition->Offset(), partition->Size(), partition->Type(), partition->IsExtended() ? " (extended)" : ""); } } static void get_partition_offset(int deviceFD, off_t deviceStart, off_t deviceSize, int blockSize, int partitionIndex, char *deviceName, int64 &_partitionOffset) { PartitionMapParser parser(deviceFD, deviceStart, deviceSize, blockSize); PartitionMap map; status_t error = parser.Parse(NULL, &map); if (error == B_OK) { Partition *partition = map.PartitionAt(partitionIndex - 1); if (!partition || partition->IsEmpty()) { fprintf(stderr, "Error: Invalid partition index %d.\n", partitionIndex); dump_partition_map(map); exit(1); } if (partition->IsExtended()) { fprintf(stderr, "Error: Partition %d is an extended " "partition.\n", partitionIndex); dump_partition_map(map); exit(1); } _partitionOffset = partition->Offset(); } else { // try again using GPT instead EFI::Header gptHeader(deviceFD, deviceSize, blockSize); error = gptHeader.InitCheck(); if (error == B_OK && partitionIndex < gptHeader.EntryCount()) { gpt_partition_entry partition = gptHeader.EntryAt(partitionIndex - 1); static_guid bfs_uuid = {0x42465331, 0x3BA3, 0x10F1, 0x802A4861696B7521LL}; if (!(bfs_uuid == partition.partition_type)) { fprintf(stderr, "Error: Partition %d does not have the " "BFS UUID.\n", partitionIndex); exit(1); } _partitionOffset = partition.StartBlock() * blockSize; } else { fprintf(stderr, "Error: Parsing partition table on device " "\"%s\" failed: %s\n", deviceName, strerror(error)); exit(1); } } close(deviceFD); } #endif // main int main(int argc, const char *const *argv) { kArgc = argc; kArgv = argv; if (argc < 2) print_usage_and_exit(true); // parameters const char **files = new const char*[argc]; int fileCount = 0; bool dryRun = false; off_t startOffset = 0; // parse arguments for (int argi = 1; argi < argc;) { const char *arg = argv[argi++]; if (arg[0] == '-') { if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) { print_usage_and_exit(false); } else if (strcmp(arg, "--dry-run") == 0) { dryRun = true; } else if (strcmp(arg, "--start-offset") == 0) { if (argi >= argc) print_usage_and_exit(true); startOffset = strtoll(argv[argi++], NULL, 0); } else { print_usage_and_exit(true); } } else { files[fileCount++] = arg; } } // we need at least one file if (fileCount == 0) print_usage_and_exit(true); // read the boot code uint8 *bootCodeData = NULL; #ifndef HAIKU_TARGET_PLATFORM_HAIKU bootCodeData = read_boot_code_data(argv[0]); #else image_info info; if (find_own_image(&info) == B_OK) bootCodeData = read_boot_code_data(info.name); #endif if (!bootCodeData) { fprintf(stderr, "Error: Failed to read \n"); exit(1); } // iterate through the files and make them bootable status_t error; for (int i = 0; i < fileCount; i++) { const char *fileName = files[i]; BEntry entry; error = entry.SetTo(fileName, true); if (error != B_OK) { fprintf(stderr, "Error: Failed to open \"%s\": %s\n", fileName, strerror(error)); exit(1); } // get stat to check the type of the file struct stat st; error = entry.GetStat(&st); if (error != B_OK) { fprintf(stderr, "Error: Failed to stat \"%s\": %s\n", fileName, strerror(error)); exit(1); } bool noPartition = false; int64 partitionOffset = 0; fs_info info; // needs to be here (we use the device name later) if (S_ISDIR(st.st_mode)) { #ifdef HAIKU_TARGET_PLATFORM_HAIKU // a directory: get the device error = fs_stat_dev(st.st_dev, &info); if (error != B_OK) { fprintf(stderr, "Error: Failed to determine device for " "\"%s\": %s\n", fileName, strerror(error)); exit(1); } fileName = info.device_name; #else (void)info; fprintf(stderr, "Error: Specifying directories not supported " "on this platform!\n"); exit(1); #endif } else if (S_ISREG(st.st_mode)) { // a regular file: fine noPartition = true; } else if (S_ISCHR(st.st_mode)) { // character special: a device or partition under BeOS // or under FreeBSD #if !defined(HAIKU_TARGET_PLATFORM_HAIKU) \ && !defined(HAIKU_HOST_PLATFORM_FREEBSD) fprintf(stderr, "Error: Character special devices not " "supported on this platform.\n"); exit(1); #endif #ifdef HAIKU_HOST_PLATFORM_FREEBSD // chop off the trailing number int fileNameLen = strlen(fileName); int baseNameLen = -1; for (int k = fileNameLen - 1; k >= 0; k--) { if (!isdigit(fileName[k])) { baseNameLen = k + 1; break; } } // Remove de 's' from 'ad2s2' slice device (partition for DOS // users) to get 'ad2' base device baseNameLen--; if (baseNameLen < 0) { // only digits? fprintf(stderr, "Error: Failed to get base device name.\n"); exit(1); } if (baseNameLen < fileNameLen) { // get base device name and partition index char baseDeviceName[B_PATH_NAME_LENGTH]; int partitionIndex = atoi(fileName + baseNameLen + 1); // Don't forget the 's' of slice :) memcpy(baseDeviceName, fileName, baseNameLen); baseDeviceName[baseNameLen] = '\0'; // open base device int baseFD = open(baseDeviceName, O_RDONLY); if (baseFD < 0) { fprintf(stderr, "Error: Failed to open \"%s\": %s\n", baseDeviceName, strerror(errno)); exit(1); } // get device size int64 deviceSize; if (ioctl(baseFD, DIOCGMEDIASIZE, &deviceSize) == -1) { fprintf(stderr, "Error: Failed to get device geometry " "for \"%s\": %s\n", baseDeviceName, strerror(errno)); exit(1); } // parse the partition map // TODO: block size! get_partition_offset(baseFD, 0, deviceSize, 512, partitionIndex, baseDeviceName, partitionOffset); } else { // The given device is the base device. We'll write at // offset 0. } #endif // HAIKU_HOST_PLATFORM_FREEBSD } else if (S_ISBLK(st.st_mode)) { // block device: a device or partition under Linux or Darwin #ifdef HAIKU_HOST_PLATFORM_LINUX // chop off the trailing number int fileNameLen = strlen(fileName); int baseNameLen = -1; for (int k = fileNameLen - 1; k >= 0; k--) { if (!isdigit(fileName[k])) { baseNameLen = k + 1; break; } } if (baseNameLen < 0) { // only digits? fprintf(stderr, "Error: Failed to get base device name.\n"); exit(1); } if (baseNameLen < fileNameLen) { // get base device name and partition index char baseDeviceName[B_PATH_NAME_LENGTH]; int partitionIndex = atoi(fileName + baseNameLen); memcpy(baseDeviceName, fileName, baseNameLen); baseDeviceName[baseNameLen] = '\0'; // open base device int baseFD = open(baseDeviceName, O_RDONLY); if (baseFD < 0) { fprintf(stderr, "Error: Failed to open \"%s\": %s\n", baseDeviceName, strerror(errno)); exit(1); } // get device size -- try BLKGETSIZE64, but, if it doesn't // work, fall back to the obsolete HDIO_GETGEO int64 deviceSize; hd_geometry geometry; if (ioctl(baseFD, BLKGETSIZE64, &deviceSize) == 0 && deviceSize > 0) { // looks good } else if (ioctl(baseFD, HDIO_GETGEO, &geometry) == 0) { deviceSize = (int64)geometry.heads * geometry.sectors * geometry.cylinders * 512; } else { fprintf(stderr, "Error: Failed to get device geometry " "for \"%s\": %s\n", baseDeviceName, strerror(errno)); exit(1); } // parse the partition map // TODO: block size! get_partition_offset(baseFD, 0, deviceSize, 512, partitionIndex, baseDeviceName, partitionOffset); } else { // The given device is the base device. We'll write at // offset 0. } #elif defined(HAIKU_HOST_PLATFORM_DARWIN) // chop off the trailing number int fileNameLen = strlen(fileName); int baseNameLen = fileNameLen - 2; // get base device name and partition index char baseDeviceName[B_PATH_NAME_LENGTH]; int partitionIndex = atoi(fileName + baseNameLen + 1); memcpy(baseDeviceName, fileName, baseNameLen); baseDeviceName[baseNameLen] = '\0'; // open base device int baseFD = open(baseDeviceName, O_RDONLY); if (baseFD < 0) { fprintf(stderr, "Error: Failed to open \"%s\": %s\n", baseDeviceName, strerror(errno)); exit(1); } // get device size int64 blockSize; int64 blockCount; int64 deviceSize; if (ioctl(baseFD, DKIOCGETBLOCKSIZE, &blockSize) == -1) { fprintf(stderr, "Error: Failed to get block size " "for \"%s\": %s\n", baseDeviceName, strerror(errno)); exit(1); } if (ioctl(baseFD, DKIOCGETBLOCKCOUNT, &blockCount) == -1) { fprintf(stderr, "Error: Failed to get block count " "for \"%s\": %s\n", baseDeviceName, strerror(errno)); exit(1); } deviceSize = blockSize * blockCount; // parse the partition map get_partition_offset(baseFD, 0, deviceSize, 512, partitionIndex, baseDeviceName, partitionOffset); #else // partitions are block devices under Haiku, but not under BeOS #ifdef HAIKU_TARGET_PLATFORM_HAIKU fprintf(stderr, "Error: Block devices not supported on this " "platform!\n"); exit(1); #endif // HAIKU_TARGET_PLATFORM_HAIKU #endif } else { fprintf(stderr, "Error: File type of \"%s\" is not supported.\n", fileName); exit(1); } // open the file int fd = open(fileName, O_RDWR); if (fd < 0) { fprintf(stderr, "Error: Failed to open \"%s\": %s\n", fileName, strerror(errno)); exit(1); } #ifdef HAIKU_TARGET_PLATFORM_HAIKU // get a partition info if (!noPartition && strlen(fileName) >= 3 && strncmp("raw", fileName + strlen(fileName) - 3, 3)) { partition_info partitionInfo; if (ioctl(fd, B_GET_PARTITION_INFO, &partitionInfo, sizeof(partitionInfo)) == 0) { partitionOffset = partitionInfo.offset; } else { fprintf(stderr, "Error: Failed to get partition info: %s\n", strerror(errno)); exit(1); } } #endif // HAIKU_TARGET_PLATFORM_HAIKU // adjust the partition offset in the boot code data // hard coded sector size: 512 bytes *(uint32*)(bootCodeData + kPartitionOffsetOffset) = B_HOST_TO_LENDIAN_INT32((uint32)(partitionOffset / 512)); // write the boot code printf("Writing boot code to \"%s\" (partition offset: %" B_PRId64 " bytes, start offset = %" B_PRIdOFF ") " "...\n", fileName, partitionOffset, startOffset); write_boot_code_part(fileName, fd, startOffset, bootCodeData, 0, kFirstBootCodePartSize, dryRun); write_boot_code_part(fileName, fd, startOffset, bootCodeData, kSecondBootCodePartOffset, kSecondBootCodePartSize, dryRun); #ifdef HAIKU_TARGET_PLATFORM_HAIKU // check if this partition is mounted BDiskDeviceRoster roster; BPartition* partition; BDiskDevice device; status_t status = roster.GetPartitionForPath(fileName, &device, &partition); if (status != B_OK) { status = roster.GetFileDeviceForPath(fileName, &device); if (status == B_OK) partition = &device; } if (status == B_OK && partition->IsMounted() && !dryRun) { // This partition is mounted, we need to tell BFS to update its // boot block (we are using part of the same logical block). BPath path; status = partition->GetMountPoint(&path); if (status == B_OK) { update_boot_block update; update.offset = kSecondBootCodePartOffset - 512; update.data = bootCodeData + kSecondBootCodePartOffset; update.length = kSecondBootCodePartSize; int mountFD = open(path.Path(), O_RDONLY); if (ioctl(mountFD, BFS_IOCTL_UPDATE_BOOT_BLOCK, &update, sizeof(update_boot_block)) != 0) { fprintf(stderr, "Could not update BFS boot block: %s\n", strerror(errno)); } close(mountFD); } else { fprintf(stderr, "Could not update BFS boot code while the " "partition is mounted!\n"); } } #endif // HAIKU_TARGET_PLATFORM_HAIKU close(fd); } delete[] files; return 0; }