/* * Copyright 2020-2021 Haiku, Inc. All rights reserved. * All rights reserved. Distributed under the terms of the MIT License. * * Authors: * Alexander von Gluck IV */ #include #include #include #include #include #include #include #include #include #define EXIT_FAILURE 1 //#define DEBUG_MBRTOOL #ifdef DEBUG_MBRTOOL # define TRACE(x...) printf("mbrtool: " x) #else # define TRACE(x...) ; #endif #define INFO(x...) printf("mbrtool: " x) // Disk sector size, 512 assumed! static const size_t kSectorSize = 512; static void print_usage(bool error) { printf("\n"); printf("usage: mbrtool (options) \n"); printf(" Disk image to operate on\n"); printf(" Partition ID (0-3)\n"); printf(" Partition type id (hex)\n"); printf(" Partition start offset (KiB)\n"); printf(" Partition length (KiB)\n\n"); printf(" Options:\n"); printf(" -a, --active Partition boot flag\n"); printf("\nWarning: This tool requires precision!\n"); printf(" Inputs are only lightly validated!\n\n"); exit(error ? EXIT_FAILURE : 0); } static void checkError(bool failed, const char *message) { if (!failed) return; if (errno != 0) INFO("Error: %s: %s\n", message, strerror(errno)); else INFO("Error: %s\n", message); exit(1); } static ssize_t mbrWipe(int handle) { // Blow away MBR while avoiding bootloader uint8_t emptyMBR[66] = {}; // End of MBR marker emptyMBR[64] = 0x55; emptyMBR[65] = 0xAA; return pwrite(handle, emptyMBR, 66, 0x1BE); } static bool mbrValid(int handle) { // TODO: this is a really basic check and ignores invalid // partition table entries uint8_t mbrBytes[66] = {}; ssize_t read = pread(handle, mbrBytes, 66, 0x1BE); checkError(read < 0, "failed to read MBR for validation"); return (mbrBytes[64] == 0x55 && mbrBytes[65] == 0xAA); } static void createPartition(int handle, int index, bool active, uint8_t type, uint64_t offset, uint64_t size) { uint8_t bootable = active ? 0x80 : 0x0; uint8_t partition[16] = { bootable, // bootable 0xff, 0xff, 0xff, // CHS first block (default to LBA) type, // partition type 0xff, 0xff, 0xff, // CHS last block (default to LBA) 0x00, 0x00, 0x00, 0x00, // imageOffset in blocks (written below) 0x00, 0x00, 0x00, 0x00 // imageSize in blocks (written below) }; // fill in LBA values uint32_t partitionOffset = (uint32_t)(offset / kSectorSize); ((uint32_t *)partition)[2] = partitionOffset; ((uint32_t *)partition)[3] = (uint32_t)(size / kSectorSize); TRACE("%s: #%d %c bytes: %u-%u, sectors: %u-%u\n", __func__, index, active ? 'b' : '-', offset, offset + size, partitionOffset, partitionOffset + uint32_t(size / kSectorSize)); ssize_t written = pwrite(handle, partition, 16, 512 - 2 - 16 * (4 - index)); checkError(written != 16, "failed to write partition entry"); if (active) { // make it bootable written = pwrite(handle, &partitionOffset, 4, offset + 512 - 2 - 4); checkError(written != 4, "failed to make partition bootable"); } return; } int main(int argc, char *argv[]) { const char *imageFile = NULL; int partType = -1; int partIndex = -1; int64_t partStartOffset = -1; int64_t partLength = -1; bool partBootable = false; while (1) { int c; static struct option long_options[] = { {"active", no_argument, 0, 'a'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; opterr = 1; /* don't print errors */ c = getopt_long(argc, argv, "+ha", long_options, NULL); if (c == -1) break; switch (c) { case 's': print_usage(false); break; case 'a': partBootable = true; break; default: print_usage(true); } } if ((argc - optind) != 5) print_usage(true); for (int index = optind; index < argc; index++) { if (imageFile == NULL) imageFile = argv[index]; else if (partIndex < 0) partIndex = atoi(argv[index]); else if (partType < 0) partType = (int)strtol(argv[index], NULL, 0); else if (partStartOffset < 0 ) { partStartOffset = (int64_t)strtol(argv[index], NULL, 10); partStartOffset *= 1024; } else if (partLength < 0) { partLength = (int64_t)strtol(argv[index], NULL, 10); partLength *= 1024; } } checkError(partIndex < 0 || partIndex > 3, "invalid partition index, valid range is 0-3"); checkError(partType < 0, "Incorrect Partition Type!"); checkError((partStartOffset + partLength) > 2089072000000, "partitions beyond 2TiB are not accepted!"); int imageFileHandle = open(imageFile, O_RDWR); checkError(imageFileHandle < 0, "failed to open disk image file"); struct stat stat; int result = fstat(imageFileHandle, &stat); checkError(result != 0, "failed to stat image file"); off_t imageSize = stat.st_size; if (!mbrValid(imageFileHandle)) { INFO("MBR of image is invalid, creating a fresh one.\n"); mbrWipe(imageFileHandle); } // Just a warning. This is technically valid since MBR partition // definitions are purely within the first 512 bytes if (partStartOffset + partLength > imageSize) INFO("Warning: Partition extends beyond end of file!\n"); createPartition(imageFileHandle, partIndex, partBootable, partType, partStartOffset, partLength); return 0; }