| /* mbr.cc -- Functions for loading, saving, and manipulating legacy MBR partition |
| data. */ |
| |
| /* By Rod Smith, January to February, 2009 */ |
| |
| #define __STDC_LIMIT_MACROS |
| #define __STDC_CONSTANT_MACROS |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <time.h> |
| #include <sys/stat.h> |
| #include <errno.h> |
| #include "mbr.h" |
| #include "support.h" |
| |
| using namespace std; |
| |
| /**************************************** |
| * * |
| * MBRData class and related structures * |
| * * |
| ****************************************/ |
| |
| MBRData::MBRData(void) { |
| blockSize = SECTOR_SIZE; |
| diskSize = 0; |
| strcpy(device, ""); |
| state = invalid; |
| srand((unsigned int) time(NULL)); |
| EmptyMBR(); |
| } // MBRData default constructor |
| |
| MBRData::MBRData(char *filename) { |
| blockSize = SECTOR_SIZE; |
| diskSize = 0; |
| strcpy(device, filename); |
| state = invalid; |
| |
| srand((unsigned int) time(NULL)); |
| // Try to read the specified partition table, but if it fails.... |
| if (!ReadMBRData(filename)) { |
| EmptyMBR(); |
| strcpy(device, ""); |
| } // if |
| } // MBRData(char *filename) constructor |
| |
| MBRData::~MBRData(void) { |
| } // MBRData destructor |
| |
| // Empty all data. Meant mainly for calling by constructors, but it's also |
| // used by the hybrid MBR functions in the GPTData class. |
| void MBRData::EmptyMBR(int clearBootloader) { |
| int i; |
| |
| // Zero out the boot loader section, the disk signature, and the |
| // 2-byte nulls area only if requested to do so. (This is the |
| // default.) |
| if (clearBootloader == 1) { |
| for (i = 0; i < 440; i++) |
| code[i] = 0; |
| diskSignature = (uint32_t) rand(); |
| nulls = 0; |
| } // if |
| |
| // Blank out the partitions |
| for (i = 0; i < 4; i++) { |
| partitions[i].status = UINT8_C(0); |
| partitions[i].firstSector[0] = UINT8_C(0); |
| partitions[i].firstSector[1] = UINT8_C(0); |
| partitions[i].firstSector[2] = UINT8_C(0); |
| partitions[i].partitionType = UINT8_C(0); |
| partitions[i].lastSector[0] = UINT8_C(0); |
| partitions[i].lastSector[1] = UINT8_C(0); |
| partitions[i].lastSector[2] = UINT8_C(0); |
| partitions[i].firstLBA = UINT32_C(0); |
| partitions[i].lengthLBA = UINT32_C(0); |
| } // for |
| MBRSignature = MBR_SIGNATURE; |
| |
| blockSize = SECTOR_SIZE; |
| diskSize = 0; |
| for (i = 0; i < NUM_LOGICALS; i++) { |
| logicals[i].status = UINT8_C(0); |
| logicals[i].firstSector[0] = UINT8_C(0); |
| logicals[i].firstSector[1] = UINT8_C(0); |
| logicals[i].firstSector[2] = UINT8_C(0); |
| logicals[i].partitionType = UINT8_C(0); |
| logicals[i].lastSector[0] = UINT8_C(0); |
| logicals[i].lastSector[1] = UINT8_C(0); |
| logicals[i].lastSector[2] = UINT8_C(0); |
| logicals[i].firstLBA = UINT32_C(0); |
| logicals[i].lengthLBA = UINT32_C(0); |
| } // for |
| } // MBRData::EmptyMBR() |
| |
| // Read data from MBR. Returns 1 if read was successful (even if the |
| // data isn't a valid MBR), 0 if the read failed. |
| int MBRData::ReadMBRData(char* deviceFilename) { |
| int fd, allOK = 1; |
| |
| if ((fd = open(deviceFilename, O_RDONLY)) != -1) { |
| ReadMBRData(fd); |
| } else { |
| allOK = 0; |
| } // if |
| |
| close(fd); |
| |
| if (allOK) |
| strcpy(device, deviceFilename); |
| |
| return allOK; |
| } // MBRData::ReadMBRData(char* deviceFilename) |
| |
| // Read data from MBR. |
| void MBRData::ReadMBRData(int fd) { |
| int allOK = 1, i, maxLogicals = 0; |
| int err; |
| |
| // Clear logical partition array |
| for (i = 0; i < NUM_LOGICALS; i++) { |
| logicals[i].status = UINT8_C(0); |
| logicals[i].firstSector[0] = UINT8_C(0); |
| logicals[i].firstSector[1] = UINT8_C(0); |
| logicals[i].firstSector[2] = UINT8_C(0); |
| logicals[i].partitionType = UINT8_C(0); |
| logicals[i].lastSector[0] = UINT8_C(0); |
| logicals[i].lastSector[1] = UINT8_C(0); |
| logicals[i].lastSector[2] = UINT8_C(0); |
| logicals[i].firstLBA = UINT32_C(0); |
| logicals[i].lengthLBA = UINT32_C(0); |
| } // for |
| |
| read(fd, code, 440); |
| read(fd, &diskSignature, 4); |
| read(fd, &nulls, 2); |
| read(fd, partitions, 64); |
| read(fd, &MBRSignature, 2); |
| |
| // Reverse the byte order, if necessary |
| if (IsLittleEndian() == 0) { |
| ReverseBytes((char*) &diskSignature, 4); |
| ReverseBytes((char*) &nulls, 2); |
| ReverseBytes((char*) &MBRSignature, 2); |
| for (i = 0; i < 4; i++) { |
| ReverseBytes((char*) &partitions[i].firstLBA, 4); |
| ReverseBytes((char*) &partitions[i].lengthLBA, 4); |
| } // for |
| } // if |
| |
| if (MBRSignature != MBR_SIGNATURE) { |
| allOK = 0; |
| state = invalid; |
| fprintf(stderr, "MBR signature invalid; read 0x%04X, but should be 0x%04X\n", |
| (unsigned int) MBRSignature, (unsigned int) MBR_SIGNATURE); |
| } /* if */ |
| |
| // Find disk size |
| diskSize = disksize(fd, &err); |
| |
| // Find block size |
| if ((blockSize = GetBlockSize(fd)) == -1) { |
| blockSize = SECTOR_SIZE; |
| printf("Unable to determine sector size; assuming %lu bytes!\n", |
| (unsigned long) SECTOR_SIZE); |
| } // if |
| |
| // Load logical partition data, if any is found.... |
| if (allOK) { |
| for (i = 0; i < 4; i++) { |
| if ((partitions[i].partitionType == 0x05) || (partitions[i].partitionType == 0x0f) |
| || (partitions[i].partitionType == 0x85)) { |
| // Found it, so call a recursive algorithm to load everything from them.... |
| maxLogicals = ReadLogicalPart(fd, partitions[i].firstLBA, UINT32_C(0), maxLogicals); |
| if ((maxLogicals < 0) || (maxLogicals > NUM_LOGICALS)) { |
| allOK = 0; |
| fprintf(stderr, "Error reading logical partitions! List may be truncated!\n"); |
| } // if maxLogicals valid |
| } // if primary partition is extended |
| } // for primary partition loop |
| if (allOK) { // Loaded logicals OK |
| state = mbr; |
| } else { |
| state = invalid; |
| } // if |
| } // if |
| |
| /* Check to see if it's in GPT format.... */ |
| if (allOK) { |
| for (i = 0; i < 4; i++) { |
| if (partitions[i].partitionType == UINT8_C(0xEE)) { |
| state = gpt; |
| } // if |
| } // for |
| } // if |
| |
| // If there's an EFI GPT partition, look for other partition types, |
| // to flag as hybrid |
| if (state == gpt) { |
| for (i = 0 ; i < 4; i++) { |
| if ((partitions[i].partitionType != UINT8_C(0xEE)) && |
| (partitions[i].partitionType != UINT8_C(0x00))) |
| state = hybrid; |
| } // for |
| } // if hybrid |
| |
| /* // Tell the user what the MBR state is... |
| switch (state) { |
| case invalid: |
| printf("Information: MBR appears to be empty or invalid.\n"); |
| break; |
| case gpt: |
| printf("Information: MBR holds GPT placeholder partitions.\n"); |
| break; |
| case hybrid: |
| printf("Information: MBR holds hybrid GPT/MBR data.\n"); |
| break; |
| case mbr: |
| printf("Information: MBR data appears to be valid.\n"); |
| break; |
| } // switch */ |
| } // MBRData::ReadMBRData(int fd) |
| |
| // Write the MBR data to the default defined device. |
| int MBRData::WriteMBRData(void) { |
| int allOK = 1, fd; |
| |
| if ((fd = open(device, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH)) != -1) { |
| WriteMBRData(fd); |
| } else { |
| allOK = 0; |
| } // if/else |
| close(fd); |
| return allOK; |
| } // MBRData::WriteMBRData(void) |
| |
| // Save the MBR data to a file. Note that this function writes ONLY the |
| // MBR data, not the logical partitions (if any are defined). |
| void MBRData::WriteMBRData(int fd) { |
| int i; |
| |
| // Reverse the byte order, if necessary |
| if (IsLittleEndian() == 0) { |
| ReverseBytes((char*) &diskSignature, 4); |
| ReverseBytes((char*) &nulls, 2); |
| ReverseBytes((char*) &MBRSignature, 2); |
| for (i = 0; i < 4; i++) { |
| ReverseBytes((char*) &partitions[i].firstLBA, 4); |
| ReverseBytes((char*) &partitions[i].lengthLBA, 4); |
| } // for |
| } // if |
| |
| write(fd, code, 440); |
| write(fd, &diskSignature, 4); |
| write(fd, &nulls, 2); |
| write(fd, partitions, 64); |
| write(fd, &MBRSignature, 2); |
| |
| // Reverse the byte order, if necessary |
| if (IsLittleEndian() == 0) { |
| ReverseBytes((char*) &diskSignature, 4); |
| ReverseBytes((char*) &nulls, 2); |
| ReverseBytes((char*) &MBRSignature, 2); |
| for (i = 0; i < 4; i++) { |
| ReverseBytes((char*) &partitions[i].firstLBA, 4); |
| ReverseBytes((char*) &partitions[i].lengthLBA, 4); |
| } // for |
| }// if |
| } // MBRData::WriteMBRData(int fd) |
| |
| // This is a recursive function to read all the logical partitions, following the |
| // logical partition linked list from the disk and storing the basic data in the |
| // logicals[] array. Returns last index to logicals[] uses, or -1 if there was a |
| // problem |
| int MBRData::ReadLogicalPart(int fd, uint32_t extendedStart, |
| uint32_t diskOffset, int partNum) { |
| struct EBRRecord ebr; |
| off_t offset; |
| |
| if ((partNum < NUM_LOGICALS) && (partNum >= 0)) { |
| offset = (off_t) (extendedStart + diskOffset) * blockSize; |
| if (lseek64(fd, offset, SEEK_SET) == (off_t) -1) { // seek to EBR record |
| fprintf(stderr, "Unable to seek to %lu! Aborting!\n", (unsigned long) offset); |
| partNum = -1; |
| } |
| if (read(fd, &ebr, 512) != 512) { // Load the data.... |
| fprintf(stderr, "Error seeking to or reading logical partition data from %lu!\nAborting!\n", |
| (unsigned long) offset); |
| partNum = -1; |
| } else if (IsLittleEndian() != 1) { // Reverse byte ordering of some data.... |
| ReverseBytes((char*) &ebr.MBRSignature, 2); |
| ReverseBytes((char*) &ebr.partitions[0].firstLBA, 4); |
| ReverseBytes((char*) &ebr.partitions[0].lengthLBA, 4); |
| ReverseBytes((char*) &ebr.partitions[1].firstLBA, 4); |
| ReverseBytes((char*) &ebr.partitions[1].lengthLBA, 4); |
| } // if/else/if |
| |
| if (ebr.MBRSignature != MBR_SIGNATURE) { |
| partNum = -1; |
| fprintf(stderr, "MBR signature in logical partition invalid; read 0x%04X, but should be 0x%04X\n", |
| (unsigned int) ebr.MBRSignature, (unsigned int) MBR_SIGNATURE); |
| } // if |
| |
| // Copy over the basic data.... |
| logicals[partNum].status = ebr.partitions[0].status; |
| logicals[partNum].firstLBA = ebr.partitions[0].firstLBA + diskOffset + extendedStart; |
| logicals[partNum].lengthLBA = ebr.partitions[0].lengthLBA; |
| logicals[partNum].partitionType = ebr.partitions[0].partitionType; |
| |
| // Find the next partition (if there is one) and recurse.... |
| if ((ebr.partitions[1].firstLBA != UINT32_C(0)) && (partNum >= 0) && |
| (partNum < (NUM_LOGICALS - 1))) { |
| partNum = ReadLogicalPart(fd, extendedStart, ebr.partitions[1].firstLBA, |
| partNum + 1); |
| } else { |
| partNum++; |
| } // if another partition |
| } // Not enough space for all the logicals (or previous error encountered) |
| return (partNum); |
| } // MBRData::ReadLogicalPart() |
| |
| // Show the MBR data to the user.... |
| void MBRData::DisplayMBRData(void) { |
| int i; |
| char tempStr[255]; |
| char bootCode; |
| |
| printf("MBR disk identifier: 0x%08X\n", (unsigned int) diskSignature); |
| printf("MBR partitions:\n"); |
| printf("Number\t Boot\t Start (sector)\t Length (sectors)\tType\n"); |
| for (i = 0; i < 4; i++) { |
| if (partitions[i].lengthLBA != 0) { |
| if (partitions[i].status && 0x80) // it's bootable |
| bootCode = '*'; |
| else |
| bootCode = ' '; |
| printf("%4d\t %c\t%13lu\t%15lu \t0x%02X\n", i + 1, bootCode, |
| (unsigned long) partitions[i].firstLBA, |
| (unsigned long) partitions[i].lengthLBA, partitions[i].partitionType); |
| } // if |
| } // for |
| |
| // Now display logical partition data.... |
| for (i = 0; i < NUM_LOGICALS; i++) { |
| if (logicals[i].lengthLBA != 0) { |
| printf("%4d\t%13lu\t%15lu \t0x%02X\n", i + 5, (unsigned long) logicals[i].firstLBA, |
| (unsigned long) logicals[i].lengthLBA, logicals[i].partitionType); |
| } // if |
| } // for |
| printf("\nDisk size is %lu sectors (%s)\n", (unsigned long) diskSize, |
| BytesToSI(diskSize * (uint64_t) blockSize, tempStr)); |
| } // MBRData::DisplayMBRData() |
| |
| // Create a protective MBR |
| void MBRData::MakeProtectiveMBR(void) { |
| int i; |
| |
| // Initialize variables |
| nulls = 0; |
| MBRSignature = MBR_SIGNATURE; |
| |
| partitions[0].status = UINT8_C(0); // Flag the protective part. as unbootable |
| |
| // Write CHS data. This maxes out the use of the disk, as much as |
| // possible -- even to the point of exceeding the capacity of sub-8GB |
| // disks. The EFI spec says to use 0xffffff as the ending value, |
| // although normal MBR disks max out at 0xfeffff. FWIW, both GNU Parted |
| // and Apple's Disk Utility use 0xfeffff, and the latter puts that |
| // value in for the FIRST sector, too! |
| partitions[0].firstSector[0] = UINT8_C(0); |
| partitions[0].firstSector[1] = UINT8_C(1); |
| partitions[0].firstSector[2] = UINT8_C(0); |
| partitions[0].lastSector[0] = UINT8_C(255); |
| partitions[0].lastSector[1] = UINT8_C(255); |
| partitions[0].lastSector[2] = UINT8_C(255); |
| |
| partitions[0].partitionType = UINT8_C(0xEE); |
| partitions[0].firstLBA = UINT32_C(1); |
| if (diskSize < UINT32_MAX) { // If the disk is under 2TiB |
| partitions[0].lengthLBA = diskSize - 1; |
| } else { // disk is too big to represent, so fake it... |
| partitions[0].lengthLBA = UINT32_MAX; |
| } // if/else |
| |
| // Zero out three unused primary partitions... |
| for (i = 1; i < 4; i++) { |
| partitions[i].status = UINT8_C(0); |
| partitions[i].firstSector[0] = UINT8_C(0); |
| partitions[i].firstSector[1] = UINT8_C(0); |
| partitions[i].firstSector[2] = UINT8_C(0); |
| partitions[i].partitionType = UINT8_C(0); |
| partitions[i].lastSector[0] = UINT8_C(0); |
| partitions[i].lastSector[1] = UINT8_C(0); |
| partitions[i].lastSector[2] = UINT8_C(0); |
| partitions[i].firstLBA = UINT32_C(0); |
| partitions[i].lengthLBA = UINT32_C(0); |
| } // for |
| |
| // Zero out all the logical partitions. Not necessary for data |
| // integrity on write, but eliminates stray entries if user wants |
| // to view the MBR after converting the disk |
| for (i = 0; i < NUM_LOGICALS; i++) { |
| logicals[i].status = UINT8_C(0); |
| logicals[i].firstSector[0] = UINT8_C(0); |
| logicals[i].firstSector[1] = UINT8_C(0); |
| logicals[i].firstSector[2] = UINT8_C(0); |
| logicals[i].partitionType = UINT8_C(0); |
| logicals[i].lastSector[0] = UINT8_C(0); |
| logicals[i].lastSector[1] = UINT8_C(0); |
| logicals[i].lastSector[2] = UINT8_C(0); |
| logicals[i].firstLBA = UINT32_C(0); |
| logicals[i].lengthLBA = UINT32_C(0); |
| } // for |
| |
| state = gpt; |
| } // MBRData::MakeProtectiveMBR() |
| |
| // Create a partition that fills the most available space. Returns |
| // 1 if partition was created, 0 otherwise. Intended for use in |
| // creating hybrid MBRs. |
| int MBRData::MakeBiggestPart(int i, int type) { |
| uint32_t start = UINT32_C(1); // starting point for each search |
| uint32_t firstBlock; // first block in a segment |
| uint32_t lastBlock; // last block in a segment |
| uint32_t segmentSize; // size of segment in blocks |
| uint32_t selectedSegment = UINT32_C(0); // location of largest segment |
| uint32_t selectedSize = UINT32_C(0); // size of largest segment in blocks |
| int found = 0; |
| |
| do { |
| firstBlock = FindFirstAvailable(start); |
| if (firstBlock != UINT32_C(0)) { // something's free... |
| lastBlock = FindLastInFree(firstBlock); |
| segmentSize = lastBlock - firstBlock + UINT32_C(1); |
| if (segmentSize > selectedSize) { |
| selectedSize = segmentSize; |
| selectedSegment = firstBlock; |
| } // if |
| start = lastBlock + 1; |
| } // if |
| } while (firstBlock != 0); |
| if ((selectedSize > UINT32_C(0)) && ((uint64_t) selectedSize < diskSize)) { |
| found = 1; |
| MakePart(i, selectedSegment, selectedSize, type, 0); |
| } else { |
| found = 0; |
| } // if/else |
| return found; |
| } // MBRData::MakeBiggestPart(int i) |
| |
| // Return a pointer to a primary or logical partition, or NULL if |
| // the partition is out of range.... |
| struct MBRRecord* MBRData::GetPartition(int i) { |
| MBRRecord* thePart = NULL; |
| |
| if ((i >= 0) && (i < 4)) { // primary partition |
| thePart = &partitions[i]; |
| } // if |
| if ((i >= 4) && (i < (NUM_LOGICALS + 4))) { |
| thePart = &logicals[i - 4]; |
| } // if |
| return thePart; |
| } // GetPartition() |
| |
| // Displays the state, as a word, on stdout. Used for debugging |
| void MBRData::ShowState(void) { |
| switch (state) { |
| case invalid: |
| printf("invalid"); |
| break; |
| case gpt: |
| printf("gpt"); |
| break; |
| case hybrid: |
| printf("hybrid"); |
| break; |
| case mbr: |
| printf("mbr"); |
| break; |
| default: |
| printf("unknown -- bug!"); |
| break; |
| } // switch |
| } // MBRData::ShowState() |
| |
| // Create a primary partition of the specified number, starting LBA, |
| // and length. This function does *NO* error checking, so it's possible |
| // to seriously screw up a partition table using this function! It's |
| // intended as a way to create a hybrid MBR, which is a pretty funky |
| // setup to begin with.... |
| void MBRData::MakePart(int num, uint32_t start, uint32_t length, int type, |
| int bootable) { |
| |
| partitions[num].status = (uint8_t) bootable * (uint8_t) 0x80; |
| partitions[num].firstSector[0] = UINT8_C(0); |
| partitions[num].firstSector[1] = UINT8_C(0); |
| partitions[num].firstSector[2] = UINT8_C(0); |
| partitions[num].partitionType = (uint8_t) type; |
| partitions[num].lastSector[0] = UINT8_C(0); |
| partitions[num].lastSector[1] = UINT8_C(0); |
| partitions[num].lastSector[2] = UINT8_C(0); |
| partitions[num].firstLBA = start; |
| partitions[num].lengthLBA = length; |
| } // MakePart() |
| |
| // Finds the first free space on the disk from start onward; returns 0 |
| // if none available.... |
| uint32_t MBRData::FindFirstAvailable(uint32_t start) { |
| uint32_t first; |
| uint32_t i; |
| int firstMoved = 0; |
| |
| first = start; |
| |
| // ...now search through all partitions; if first is within an |
| // existing partition, move it to the next sector after that |
| // partition and repeat. If first was moved, set firstMoved |
| // flag; repeat until firstMoved is not set, so as to catch |
| // cases where partitions are out of sequential order.... |
| do { |
| firstMoved = 0; |
| for (i = 0; i < 4; i++) { |
| // Check if it's in the existing partition |
| if ((first >= partitions[i].firstLBA) && |
| (first < (partitions[i].firstLBA + partitions[i].lengthLBA))) { |
| first = partitions[i].firstLBA + partitions[i].lengthLBA; |
| firstMoved = 1; |
| } // if |
| } // for |
| } while (firstMoved == 1); |
| if (first >= diskSize) |
| first = 0; |
| return (first); |
| } // MBRData::FindFirstAvailable() |
| |
| uint32_t MBRData::FindLastInFree(uint32_t start) { |
| uint32_t nearestStart; |
| uint32_t i; |
| |
| if (diskSize <= UINT32_MAX) |
| nearestStart = diskSize - 1; |
| else |
| nearestStart = UINT32_MAX - 1; |
| for (i = 0; i < 4; i++) { |
| if ((nearestStart > partitions[i].firstLBA) && |
| (partitions[i].firstLBA > start)) { |
| nearestStart = partitions[i].firstLBA - 1; |
| } // if |
| } // for |
| return (nearestStart); |
| } // MBRData::FindLastInFree |
| |
| uint8_t MBRData::GetStatus(int i) { |
| MBRRecord* thePart; |
| uint8_t retval; |
| |
| thePart = GetPartition(i); |
| if (thePart != NULL) |
| retval = thePart->status; |
| else |
| retval = UINT8_C(0); |
| return retval; |
| } // MBRData::GetStatus() |
| |
| uint8_t MBRData::GetType(int i) { |
| MBRRecord* thePart; |
| uint8_t retval; |
| |
| thePart = GetPartition(i); |
| if (thePart != NULL) |
| retval = thePart->partitionType; |
| else |
| retval = UINT8_C(0); |
| return retval; |
| } // MBRData::GetType() |
| |
| uint32_t MBRData::GetFirstSector(int i) { |
| MBRRecord* thePart; |
| uint32_t retval; |
| |
| thePart = GetPartition(i); |
| if (thePart != NULL) { |
| retval = thePart->firstLBA; |
| } else |
| retval = UINT32_C(0); |
| return retval; |
| } // MBRData::GetFirstSector() |
| |
| uint32_t MBRData::GetLength(int i) { |
| MBRRecord* thePart; |
| uint32_t retval; |
| |
| thePart = GetPartition(i); |
| if (thePart != NULL) { |
| retval = thePart->lengthLBA; |
| } else |
| retval = UINT32_C(0); |
| return retval; |
| } // MBRData::GetLength() |