Updated to version 0.4.1

Adds relative partition sizing and placement options for both start and
end sectors; improves hybrid MBR synchronization (deletes matching MBR
partition when a GPT partition is deleted, warns about inconsistencies
when verifying or writing a partition table).
diff --git a/gpt.cc b/gpt.cc
index af3da2a..9498a86 100644
--- a/gpt.cc
+++ b/gpt.cc
@@ -1,7 +1,7 @@
 /* gpt.cc -- Functions for loading, saving, and manipulating legacy MBR and GPT partition
    data. */
 
-/* By Rod Smith, January to February, 2009 */
+/* By Rod Smith, initial coding January to February, 2009 */
 
 /* This program is copyright (c) 2009 by Roderick W. Smith. It is distributed
   under the terms of the GNU GPL version 2, as detailed in the COPYING file. */
@@ -33,6 +33,7 @@
  *                                      *
  ****************************************/
 
+// Default constructor
 GPTData::GPTData(void) {
    blockSize = SECTOR_SIZE; // set a default
    diskSize = 0;
@@ -66,66 +67,139 @@
    LoadPartitions(filename);
 } // GPTData(char* filename) constructor
 
+// Destructor
 GPTData::~GPTData(void) {
    free(partitions);
 } // GPTData destructor
 
-// Resizes GPT to specified number of entries. Creates a new table if
-// necessary, copies data if it already exists.
-int GPTData::SetGPTSize(uint32_t numEntries) {
-   struct GPTPart* newParts;
-   struct GPTPart* trash;
-   uint32_t i, high, copyNum;
-   int allOK = 1;
+/*********************************************************************
+ *                                                                   *
+ * Begin functions that verify data, or that adjust the verification *
+ * information (compute CRCs, rebuild headers)                       *
+ *                                                                   *
+ *********************************************************************/
 
-   // First, adjust numEntries upward, if necessary, to get a number
-   // that fills the allocated sectors
-   i = blockSize / GPT_SIZE;
-   if ((numEntries % i) != 0) {
-      printf("Adjusting GPT size from %lu ", (unsigned long) numEntries);
-      numEntries = ((numEntries / i) + 1) * i;
-      printf("to %lu to fill the sector\n", (unsigned long) numEntries);
+// Perform detailed verification, reporting on any problems found, but
+// do *NOT* recover from these problems. Returns the total number of
+// problems identified.
+int GPTData::Verify(void) {
+   int problems = 0, numSegments, i, j;
+   uint64_t totalFree, largestSegment;
+   char tempStr[255], siTotal[255], siLargest[255];
+
+   // First, check for CRC errors in the GPT data....
+   if (!mainCrcOk) {
+      problems++;
+      printf("\nProblem: The CRC for the main GPT header is invalid. The main GPT header may\n"
+            "be corrupt. Consider loading the backup GPT header to rebuild the main GPT\n"
+            "header\n");
+   } // if
+   if (!mainPartsCrcOk) {
+      problems++;
+      printf("\nProblem: The CRC for the main partition table is invalid. This table may be\n"
+            "corrupt. Consider loading the backup partition table.\n");
+   } // if
+   if (!secondCrcOk) {
+      problems++;
+      printf("\nProblem: The CRC for the backup GPT header is invalid. The backup GPT header\n"
+            "may be corrupt. Consider using the main GPT header to rebuild the backup GPT\n"
+            "header.\n");
+   } // if
+   if (!secondPartsCrcOk) {
+      problems++;
+      printf("\nCaution: The CRC for the backup partition table is invalid. This table may\n"
+            "be corrupt. This program will automatically create a new backup partition\n"
+            "table when you save your partitions.\n");
    } // if
 
-   newParts = (GPTPart*) calloc(numEntries, sizeof (GPTPart));
-   if (newParts != NULL) {
-      if (partitions != NULL) { // existing partitions; copy them over
-         GetPartRange(&i, &high);
-         if (numEntries < (high + 1)) { // Highest entry too high for new #
-            printf("The highest-numbered partition is %lu, which is greater than the requested\n"
-                   "partition table size of %d; cannot resize. Perhaps sorting will help.\n",
-                   (unsigned long) (high + 1), numEntries);
-            allOK = 0;
-         } else { // go ahead with copy
-            if (numEntries < mainHeader.numParts)
-               copyNum = numEntries;
-            else
-               copyNum = mainHeader.numParts;
-            for (i = 0; i < copyNum; i++) {
-               newParts[i] = partitions[i];
-            } // for
-            trash = partitions;
-            partitions = newParts;
-            free(trash);
-         } // if
-      } else { // No existing partition table; just create it
-         partitions = newParts;
-      } // if/else existing partitions
-      mainHeader.numParts = numEntries;
-      secondHeader.numParts = numEntries;
-      mainHeader.firstUsableLBA = ((numEntries * GPT_SIZE) / blockSize) + 2 ;
-      secondHeader.firstUsableLBA = mainHeader.firstUsableLBA;
-      mainHeader.lastUsableLBA = diskSize - mainHeader.firstUsableLBA;
-      secondHeader.lastUsableLBA = mainHeader.lastUsableLBA;
-      secondHeader.partitionEntriesLBA = secondHeader.lastUsableLBA + UINT64_C(1);
-      if (diskSize > 0)
-         CheckGPTSize();
-   } else { // Bad memory allocation
-      fprintf(stderr, "Error allocating memory for partition table!\n");
-      allOK = 0;
+   // Now check that critical main and backup GPT entries match
+   if (mainHeader.currentLBA != secondHeader.backupLBA) {
+      problems++;
+      printf("\nProblem: main GPT header's current LBA pointer (%llu) doesn't\n"
+            "match the backup GPT header's LBA pointer(%llu)\n",
+            (unsigned long long) mainHeader.currentLBA,
+             (unsigned long long) secondHeader.backupLBA);
+   } // if
+   if (mainHeader.backupLBA != secondHeader.currentLBA) {
+      problems++;
+      printf("\nProblem: main GPT header's backup LBA pointer (%llu) doesn't\n"
+            "match the backup GPT header's current LBA pointer (%llu)\n",
+            (unsigned long long) mainHeader.backupLBA,
+             (unsigned long long) secondHeader.currentLBA);
+   } // if
+   if (mainHeader.firstUsableLBA != secondHeader.firstUsableLBA) {
+      problems++;
+      printf("\nProblem: main GPT header's first usable LBA pointer (%llu) doesn't\n"
+            "match the backup GPT header's first usable LBA pointer (%llu)\n",
+            (unsigned long long) mainHeader.firstUsableLBA,
+             (unsigned long long) secondHeader.firstUsableLBA);
+   } // if
+   if (mainHeader.lastUsableLBA != secondHeader.lastUsableLBA) {
+      problems++;
+      printf("\nProblem: main GPT header's last usable LBA pointer (%llu) doesn't\n"
+            "match the backup GPT header's last usable LBA pointer (%llu)\n",
+            (unsigned long long) mainHeader.lastUsableLBA,
+             (unsigned long long) secondHeader.lastUsableLBA);
+   } // if
+   if ((mainHeader.diskGUID.data1 != secondHeader.diskGUID.data1) ||
+        (mainHeader.diskGUID.data2 != secondHeader.diskGUID.data2)) {
+      problems++;
+      printf("\nProblem: main header's disk GUID (%s) doesn't\n",
+             GUIDToStr(mainHeader.diskGUID, tempStr));
+      printf("match the backup GPT header's disk GUID (%s)\n",
+             GUIDToStr(secondHeader.diskGUID, tempStr));
+   } // if
+   if (mainHeader.numParts != secondHeader.numParts) {
+      problems++;
+      printf("\nProblem: main GPT header's number of partitions (%lu) doesn't\n"
+            "match the backup GPT header's number of partitions (%lu)\n",
+            (unsigned long) mainHeader.numParts,
+            (unsigned long) secondHeader.numParts);
+   } // if
+   if (mainHeader.sizeOfPartitionEntries != secondHeader.sizeOfPartitionEntries) {
+      problems++;
+      printf("\nProblem: main GPT header's size of partition entries (%lu) doesn't\n"
+            "match the backup GPT header's size of partition entries (%lu)\n",
+            (unsigned long) mainHeader.sizeOfPartitionEntries,
+            (unsigned long) secondHeader.sizeOfPartitionEntries);
+   } // if
+
+   // Now check for a few other miscellaneous problems...
+   // Check that the disk size will hold the data...
+   if (mainHeader.backupLBA > diskSize) {
+      problems++;
+      printf("\nProblem: Disk is too small to hold all the data!\n");
+      printf("(Disk size is %llu sectors, needs to be %llu sectors.)\n",
+            (unsigned long long) diskSize,
+               (unsigned long long) mainHeader.backupLBA);
+   } // if
+
+   // Check for overlapping partitions....
+   problems += FindOverlaps();
+
+   // Check for mismatched MBR and GPT partitions...
+   problems += FindHybridMismatches();
+
+   // Verify that partitions don't run into GPT data areas....
+   problems += CheckGPTSize();
+
+   // Now compute available space, but only if no problems found, since
+   // problems could affect the results
+   if (problems == 0) {
+      totalFree = FindFreeBlocks(&numSegments, &largestSegment);
+      BytesToSI(totalFree * (uint64_t) blockSize, siTotal);
+      BytesToSI(largestSegment * (uint64_t) blockSize, siLargest);
+      printf("No problems found. %llu free sectors (%s) available in %u\n"
+             "segments, the largest of which is %llu sectors (%s) in size\n",
+             (unsigned long long) totalFree,
+              siTotal, numSegments, (unsigned long long) largestSegment,
+                                     siLargest);
+   } else {
+      printf("\nIdentified %d problems!\n", problems);
    } // if/else
-   return (allOK);
-} // GPTData::SetGPTSize()
+
+   return (problems);
+} // GPTData::Verify()
 
 // Checks to see if the GPT tables overrun existing partitions; if they
 // do, issues a warning but takes no action. Returns number of problems
@@ -140,7 +214,7 @@
    lastUsedBlock = 0;
    for (i = 0; i < mainHeader.numParts; i++) {
       if ((partitions[i].GetFirstLBA() < firstUsedBlock) &&
-          (partitions[i].GetFirstLBA() != 0))
+           (partitions[i].GetFirstLBA() != 0))
          firstUsedBlock = partitions[i].GetFirstLBA();
       if (partitions[i].GetLastLBA() > lastUsedBlock)
          lastUsedBlock = partitions[i].GetLastLBA();
@@ -180,845 +254,6 @@
    return numProbs;
 } // GPTData::CheckGPTSize()
 
-// Tell user whether Apple Partition Map (APM) was discovered....
-void GPTData::ShowAPMState(void) {
-   if (apmFound)
-      printf("  APM: present\n");
-   else
-      printf("  APM: not present\n");
-} // GPTData::ShowAPMState()
-
-// Tell user about the state of the GPT data....
-void GPTData::ShowGPTState(void) {
-   switch (state) {
-      case gpt_invalid:
-         printf("  GPT: not present\n");
-         break;
-      case gpt_valid:
-         printf("  GPT: present\n");
-         break;
-      case gpt_corrupt:
-         printf("  GPT: damaged\n");
-         break;
-      default:
-         printf("\a  GPT: unknown -- bug!\n");
-         break;
-   } // switch
-} // GPTData::ShowGPTState()
-
-// Scan for partition data. This function loads the MBR data (regular MBR or
-// protective MBR) and loads BSD disklabel data (which is probably invalid).
-// It also looks for APM data, forces a load of GPT data, and summarizes
-// the results.
-void GPTData::PartitionScan(int fd) {
-   BSDData bsdDisklabel;
-//   int bsdFound;
-
-   printf("Partition table scan:\n");
-
-   // Read the MBR & check for BSD disklabel
-   protectiveMBR.ReadMBRData(fd);
-   protectiveMBR.ShowState();
-   bsdDisklabel.ReadBSDData(fd, 0, diskSize - 1);
-   bsdFound = bsdDisklabel.ShowState();
-//   bsdDisklabel.DisplayBSDData();
-
-   // Load the GPT data, whether or not it's valid
-   ForceLoadGPTData(fd);
-   ShowAPMState(); // Show whether there's an Apple Partition Map present
-   ShowGPTState(); // Show GPT status
-   printf("\n");
-
-   if (apmFound) {
-      printf("\n*******************************************************************\n");
-      printf("This disk appears to contain an Apple-format (APM) partition table!\n");
-      printf("It will be destroyed if you continue!\n");
-      printf("*******************************************************************\n\n\a");
-   } // if
-/*   if (bsdFound) {
-      printf("\n*************************************************************************\n");
-      printf("This disk appears to contain a BSD disklabel! It will be destroyed if you\n"
-            "continue!\n");
-      printf("*************************************************************************\n\n\a");
-   } // if */
-} // GPTData::PartitionScan()
-
-// Read GPT data from a disk.
-int GPTData::LoadPartitions(char* deviceFilename) {
-   int fd, err;
-   int allOK = 1, i;
-   uint64_t firstBlock, lastBlock;
-   BSDData bsdDisklabel;
-
-   if ((fd = open(deviceFilename, O_RDONLY)) != -1) {
-      // store disk information....
-      diskSize = disksize(fd, &err);
-      blockSize = (uint32_t) GetBlockSize(fd);
-      strcpy(device, deviceFilename);
-      PartitionScan(fd); // Check for partition types & print summary
-
-      switch (UseWhichPartitions()) {
-         case use_mbr:
-            XFormPartitions();
-            break;
-         case use_bsd:
-            bsdDisklabel.ReadBSDData(fd, 0, diskSize - 1);
-//            bsdDisklabel.DisplayBSDData();
-            ClearGPTData();
-            protectiveMBR.MakeProtectiveMBR(1); // clear boot area (option 1)
-            XFormDisklabel(&bsdDisklabel, 0);
-            break;
-         case use_gpt:
-            break;
-         case use_new:
-            ClearGPTData();
-            protectiveMBR.MakeProtectiveMBR();
-            break;
-      } // switch
-
-      // Now find the first and last sectors used by partitions...
-      if (allOK) {
-         firstBlock = mainHeader.backupLBA; // start high
-	 lastBlock = 0; // start low
-         for (i = 0; i < mainHeader.numParts; i++) {
-	    if ((partitions[i].GetFirstLBA() < firstBlock) &&
-                (partitions[i].GetFirstLBA() > 0))
-	       firstBlock = partitions[i].GetFirstLBA();
-            if (partitions[i].GetLastLBA() > lastBlock)
-               lastBlock = partitions[i].GetLastLBA();
-	 } // for
-      } // if
-      CheckGPTSize();
-   } else {
-      allOK = 0;
-      fprintf(stderr, "Problem opening %s for reading! Error is %d\n",
-              deviceFilename, errno);
-      if (errno == EACCES) { // User is probably not running as root
-         fprintf(stderr, "You must run this program as root or use sudo!\n");
-      } // if
-   } // if/else
-   return (allOK);
-} // GPTData::LoadPartitions()
-
-// Loads the GPT, as much as possible. Returns 1 if this seems to have
-// succeeded, 0 if there are obvious problems....
-int GPTData::ForceLoadGPTData(int fd) {
-   int allOK = 1, validHeaders;
-   off_t seekTo;
-   char* storage;
-   uint32_t newCRC, sizeOfParts;
-
-   // Seek to and read the main GPT header
-   lseek64(fd, 512, SEEK_SET);
-   read(fd, &mainHeader, 512); // read main GPT header
-   mainCrcOk = CheckHeaderCRC(&mainHeader);
-   if (IsLittleEndian() == 0) // big-endian system; adjust header byte order....
-      ReverseHeaderBytes(&mainHeader);
-
-   // Load backup header, check its CRC, and store the results of
-   // the check for future reference
-   seekTo = (diskSize * blockSize) - UINT64_C(512);
-   if (lseek64(fd, seekTo, SEEK_SET) != (off_t) -1) {
-      read(fd, &secondHeader, 512); // read secondary GPT header
-      secondCrcOk = CheckHeaderCRC(&secondHeader);
-      if (IsLittleEndian() == 0) // big-endian system; adjust header byte order....
-         ReverseHeaderBytes(&secondHeader);
-   } else {
-      allOK = 0;
-      state = gpt_invalid;
-      fprintf(stderr, "Unable to seek to secondary GPT at sector %llu!\n",
-              diskSize - (UINT64_C(1)));
-   } // if/else lseek
-
-   // Return valid headers code: 0 = both headers bad; 1 = main header
-   // good, backup bad; 2 = backup header good, main header bad;
-   // 3 = both headers good. Note these codes refer to valid GPT
-   // signatures and version numbers; more subtle problems will elude
-   // this check!
-   validHeaders = CheckHeaderValidity();
-
-   // Read partitions (from primary array)
-   if (validHeaders > 0) { // if at least one header is OK....
-      // GPT appears to be valid....
-      state = gpt_valid;
-
-      // We're calling the GPT valid, but there's a possibility that one
-      // of the two headers is corrupt. If so, use the one that seems to
-      // be in better shape to regenerate the bad one
-      if (validHeaders == 2) { // valid backup header, invalid main header
-         printf("Caution: invalid main GPT header, but valid backup; regenerating main header\n"
-                "from backup!\n");
-         RebuildMainHeader();
-         mainCrcOk = secondCrcOk; // Since copied, use CRC validity of backup
-      } else if (validHeaders == 1) { // valid main header, invalid backup
-         printf("Caution: invalid backup GPT header, but valid main header; regenerating\n"
-                "backup header from main header.\n");
-         RebuildSecondHeader();
-         secondCrcOk = mainCrcOk; // Since regenerated, use CRC validity of main
-      } // if/else/if
-
-      // Load the main partition table, including storing results of its
-      // CRC check
-      if (LoadMainTable() == 0)
-         allOK = 0;
-
-      // Load backup partition table into temporary storage to check
-      // its CRC and store the results, then discard this temporary
-      // storage, since we don't use it in any but recovery operations
-      seekTo = secondHeader.partitionEntriesLBA * (off_t) blockSize;
-      if ((lseek64(fd, seekTo, SEEK_SET) != (off_t) -1) && (secondCrcOk)) {
-         sizeOfParts = secondHeader.numParts * secondHeader.sizeOfPartitionEntries;
-         storage = (char*) malloc(sizeOfParts);
-         read(fd, storage, sizeOfParts);
-         newCRC = chksum_crc32((unsigned char*) storage,  sizeOfParts);
-         free(storage);
-         secondPartsCrcOk = (newCRC == secondHeader.partitionEntriesCRC);
-      } // if
-
-      // Check for valid CRCs and warn if there are problems
-      if ((mainCrcOk == 0) || (secondCrcOk == 0) || (mainPartsCrcOk == 0) ||
-          (secondPartsCrcOk == 0)) {
-         printf("Warning! One or more CRCs don't match. You should repair the disk!\n");
-	 state = gpt_corrupt;
-      } // if
-   } else {
-      state = gpt_invalid;
-   } // if/else
-   return allOK;
-} // GPTData::ForceLoadGPTData()
-
-// Loads the partition tables pointed to by the main GPT header. The
-// main GPT header in memory MUST be valid for this call to do anything
-// sensible!
-int GPTData::LoadMainTable(void) {
-   int fd, retval = 0;
-   uint32_t newCRC, sizeOfParts;
-
-   if ((fd = open(device, O_RDONLY)) != -1) {
-      // Set internal data structures for number of partitions on the disk
-      SetGPTSize(mainHeader.numParts);
-
-      // Load main partition table, and record whether its CRC
-      // matches the stored value
-      lseek64(fd, mainHeader.partitionEntriesLBA * blockSize, SEEK_SET);
-      sizeOfParts = mainHeader.numParts * mainHeader.sizeOfPartitionEntries;
-      read(fd, partitions, sizeOfParts);
-      newCRC = chksum_crc32((unsigned char*) partitions, sizeOfParts);
-      mainPartsCrcOk = (newCRC == mainHeader.partitionEntriesCRC);
-      if (IsLittleEndian() == 0)
-         ReversePartitionBytes();
-      retval = 1;
-   } // if
-   return retval;
-} // GPTData::LoadMainTable()
-
-// Examines the MBR & GPT data, and perhaps asks the user questions, to
-// determine which set of data to use: the MBR (use_mbr), the GPT (use_gpt),
-// or create a new set of partitions (use_new)
-WhichToUse GPTData::UseWhichPartitions(void) {
-   WhichToUse which = use_new;
-   MBRValidity mbrState;
-   int answer;
-
-   mbrState = protectiveMBR.GetValidity();
-
-   if ((state == gpt_invalid) && ((mbrState == mbr) || (mbrState == hybrid))) {
-      printf("\n\a***************************************************************\n"
-             "Found invalid GPT and valid MBR; converting MBR to GPT format.\n"
-             "THIS OPERATON IS POTENTIALLY DESTRUCTIVE! Exit by typing 'q' if\n"
-             "you don't want to convert your MBR partitions to GPT format!\n"
-             "***************************************************************\n\n");
-      which = use_mbr;
-   } // if
-
-   if ((state == gpt_invalid) && bsdFound) {
-      printf("\n\a**********************************************************************\n"
-             "Found invalid GPT and valid BSD disklabel; converting BSD disklabel\n"
-             "to GPT format. THIS OPERATON IS POTENTIALLY DESTRUCTIVE! Your first\n"
-             "BSD partition will likely be unusable. Exit by typing 'q' if you don't\n"
-             "want to convert your BSD partitions to GPT format!\n"
-             "**********************************************************************\n\n");
-      which = use_bsd;
-   } // if
-
-   if ((state == gpt_valid) && (mbrState == gpt)) {
-      printf("Found valid GPT with protective MBR; using GPT.\n");
-      which = use_gpt;
-   } // if
-   if ((state == gpt_valid) && (mbrState == hybrid)) {
-      printf("Found valid GPT with hybrid MBR; using GPT.\n");
-      printf("\aIf you change GPT partitions, you may need to re-create the hybrid MBR!\n");
-      which = use_gpt;
-   } // if
-   if ((state == gpt_valid) && (mbrState == invalid)) {
-      printf("\aFound valid GPT with corrupt MBR; using GPT and will create new\nprotective MBR on save.\n");
-      which = use_gpt;
-      protectiveMBR.MakeProtectiveMBR();
-   } // if
-   if ((state == gpt_valid) && (mbrState == mbr)) {
-      printf("Found valid MBR and GPT. Which do you want to use?\n");
-      answer = GetNumber(1, 3, 2, (char*) " 1 - MBR\n 2 - GPT\n 3 - Create blank GPT\n\nYour answer: ");
-      if (answer == 1) {
-         which = use_mbr;
-      } else if (answer == 2) {
-         which = use_gpt;
-	 protectiveMBR.MakeProtectiveMBR();
-         printf("Using GPT and creating fresh protective MBR.\n");
-      } else which = use_new;
-   } // if
-
-   // Nasty decisions here -- GPT is present, but corrupt (bad CRCs or other
-   // problems)
-   if (state == gpt_corrupt) {
-      if ((mbrState == mbr) || (mbrState == hybrid)) {
-         printf("Found valid MBR and corrupt GPT. Which do you want to use? (Using the\n"
-                "GPT MAY permit recovery of GPT data.)\n");
-         answer = GetNumber(1, 3, 2, (char*) " 1 - MBR\n 2 - GPT\n 3 - Create blank GPT\n\nYour answer: ");
-         if (answer == 1) {
-            which = use_mbr;
-//            protectiveMBR.MakeProtectiveMBR();
-         } else if (answer == 2) {
-            which = use_gpt;
-         } else which = use_new;
-      } else if (mbrState == invalid) {
-         printf("Found invalid MBR and corrupt GPT. What do you want to do? (Using the\n"
-                "GPT MAY permit recovery of GPT data.)\n");
-         answer = GetNumber(1, 2, 1, (char*) " 1 - GPT\n 2 - Create blank GPT\n\nYour answer: ");
-         if (answer == 1) {
-            which = use_gpt;
-         } else which = use_new;
-      } else { // corrupt GPT, MBR indicates it's a GPT disk....
-         printf("\a\a****************************************************************************\n"
-                "Caution: Found protective or hybrid MBR and corrupt GPT. Using GPT, but disk\n"
-                "verification and recovery are STRONGLY recommended.\n"
-                "****************************************************************************\n");
-      } // if/else/else
-   } // if (corrupt GPT)
-
-   if (which == use_new)
-      printf("Creating new GPT entries.\n");
-
-   return which;
-} // UseWhichPartitions()
-
-void GPTData::ResizePartitionTable(void) {
-   int newSize;
-   char prompt[255];
-   uint32_t curLow, curHigh;
-
-   printf("Current partition table size is %lu.\n",
-          (unsigned long) mainHeader.numParts);
-   GetPartRange(&curLow, &curHigh);
-   curHigh++; // since GetPartRange() returns numbers starting from 0...
-   // There's no point in having fewer than four partitions....
-   if (curHigh < 4)
-      curHigh = 4;
-   sprintf(prompt, "Enter new size (%d up, default %d): ", (int) curHigh,
-           (int) NUM_GPT_ENTRIES);
-   newSize = GetNumber(4, 65535, 128, prompt);
-   if (newSize < 128) {
-      printf("Caution: The partition table size should officially be 16KB or larger,\n"
-             "which works out to 128 entries. In practice, smaller tables seem to\n"
-	     "work with most OSes, but this practice is risky. I'm proceeding with\n"
-             "the resize, but you may want to reconsider this action and undo it.\n\n");
-   } // if
-   SetGPTSize(newSize);
-} // GPTData::ResizePartitionTable()
-
-// Find the low and high used partition numbers (numbered from 0).
-// Return value is the number of partitions found. Note that the
-// *low and *high values are both set to 0 when no partitions
-// are found, as well as when a single partition in the first
-// position exists. Thus, the return value is the only way to
-// tell when no partitions exist.
-int GPTData::GetPartRange(uint32_t *low, uint32_t *high) {
-   uint32_t i;
-   int numFound = 0;
-
-   *low = mainHeader.numParts + 1; // code for "not found"
-   *high = 0;
-   if (mainHeader.numParts > 0) { // only try if partition table exists...
-      for (i = 0; i < mainHeader.numParts; i++) {
-         if (partitions[i].GetFirstLBA() != UINT64_C(0)) { // it exists
-            *high = i; // since we're counting up, set the high value
-	    // Set the low value only if it's not yet found...
-            if (*low == (mainHeader.numParts + 1)) *low = i;
-            numFound++;
-         } // if
-      } // for
-   } // if
-
-   // Above will leave *low pointing to its "not found" value if no partitions
-   // are defined, so reset to 0 if this is the case....
-   if (*low == (mainHeader.numParts + 1))
-      *low = 0;
-   return numFound;
-} // GPTData::GetPartRange()
-
-// Display the basic GPT data
-void GPTData::DisplayGPTData(void) {
-   int i, j;
-   char sizeInSI[255]; // String to hold size of disk in SI units
-   char tempStr[255];
-   uint64_t temp, totalFree;
-
-   BytesToSI(diskSize * blockSize, sizeInSI);
-   printf("Disk %s: %lu sectors, %s\n", device,
-          (unsigned long) diskSize, sizeInSI);
-   printf("Disk identifier (GUID): %s\n", GUIDToStr(mainHeader.diskGUID, tempStr));
-   printf("Partition table holds up to %lu entries\n", (unsigned long) mainHeader.numParts);
-   printf("First usable sector is %lu, last usable sector is %lu\n",
-          (unsigned long) mainHeader.firstUsableLBA,
-          (unsigned long) mainHeader.lastUsableLBA);
-   totalFree = FindFreeBlocks(&i, &temp);
-   printf("Total free space is %llu sectors (%s)\n", totalFree,
-          BytesToSI(totalFree * (uint64_t) blockSize, sizeInSI));
-   printf("\nNumber  Start (sector)    End (sector)  Size       Code  Name\n");
-   for (i = 0; i < mainHeader.numParts; i++) {
-      partitions[i].ShowSummary(i, blockSize, sizeInSI);
-   } // for
-} // GPTData::DisplayGPTData()
-
-// Get partition number from user and then call ShowPartDetails(partNum)
-// to show its detailed information
-void GPTData::ShowDetails(void) {
-   int partNum;
-   uint32_t low, high;
-
-   if (GetPartRange(&low, &high) > 0) {
-      partNum = GetPartNum();
-      ShowPartDetails(partNum);
-   } else {
-      printf("No partitions\n");
-   } // if/else
-} // GPTData::ShowDetails()
-
-// Show detailed information on the specified partition
-void GPTData::ShowPartDetails(uint32_t partNum) {
-   if (partitions[partNum].GetFirstLBA() != 0) {
-      partitions[partNum].ShowDetails(blockSize);
-   } else {
-      printf("Partition #%d does not exist.", (int) (partNum + 1));
-   } // if
-} // GPTData::ShowPartDetails()
-
-// Interactively create a partition
-void GPTData::CreatePartition(void) {
-   uint64_t firstBlock, lastBlock, sector;
-   char prompt[255];
-   int partNum, firstFreePart = 0;
-
-   // Find first free partition...
-   while (partitions[firstFreePart].GetFirstLBA() != 0) {
-      firstFreePart++;
-   } // while
-
-   if (((firstBlock = FindFirstAvailable()) != 0) &&
-       (firstFreePart < mainHeader.numParts)) {
-      lastBlock = FindLastAvailable(firstBlock);
-
-      // Get partition number....
-      do {
-         sprintf(prompt, "Partition number (%d-%d, default %d): ", firstFreePart + 1,
-                 mainHeader.numParts, firstFreePart + 1);
-         partNum = GetNumber(firstFreePart + 1, mainHeader.numParts,
-                             firstFreePart + 1, prompt) - 1;
-	 if (partitions[partNum].GetFirstLBA() != 0)
-	    printf("partition %d is in use.\n", partNum + 1);
-      } while (partitions[partNum].GetFirstLBA() != 0);
-
-      // Get first block for new partition...
-      sprintf(prompt, "First sector (%llu-%llu, default = %llu): ", firstBlock,
-              lastBlock, firstBlock);
-      do {
-	 sector = GetNumber(firstBlock, lastBlock, firstBlock, prompt);
-      } while (IsFree(sector) == 0);
-      firstBlock = sector;
-
-      // Get last block for new partitions...
-      lastBlock = FindLastInFree(firstBlock);
-      sprintf(prompt, "Last sector or +size or +sizeM or +sizeK (%llu-%llu, default = %d): ",
-              firstBlock, lastBlock, lastBlock);
-      do {
-         sector = GetLastSector(firstBlock, lastBlock, prompt);
-      } while (IsFree(sector) == 0);
-      lastBlock = sector;
-
-      partitions[partNum].SetFirstLBA(firstBlock);
-      partitions[partNum].SetLastLBA(lastBlock);
-
-      partitions[partNum].SetUniqueGUID(1);
-      partitions[partNum].ChangeType();
-      partitions[partNum].SetName((unsigned char*) partitions[partNum].GetNameType(prompt));
-   } else {
-      printf("No free sectors available\n");
-   } // if/else
-} // GPTData::CreatePartition()
-
-// Interactively delete a partition (duh!)
-void GPTData::DeletePartition(void) {
-   int partNum;
-   uint32_t low, high;
-   char prompt[255];
-
-   if (GetPartRange(&low, &high) > 0) {
-      sprintf(prompt, "Partition number (%d-%d): ", low + 1, high + 1);
-      partNum = GetNumber(low + 1, high + 1, low, prompt);
-      partitions[partNum - 1].BlankPartition();
-   } else {
-      printf("No partitions\n");
-   } // if/else
-} // GPTData::DeletePartition()
-
-// Find the first available block after the starting point; returns 0 if
-// there are no available blocks left
-uint64_t GPTData::FindFirstAvailable(uint64_t start) {
-   uint64_t first;
-   uint32_t i;
-   int firstMoved = 0;
-
-   // Begin from the specified starting point or from the first usable
-   // LBA, whichever is greater...
-   if (start < mainHeader.firstUsableLBA)
-      first = mainHeader.firstUsableLBA;
-   else
-      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 < mainHeader.numParts; i++) {
-         if ((first >= partitions[i].GetFirstLBA()) &&
-             (first <= partitions[i].GetLastLBA())) { // in existing part.
-            first = partitions[i].GetLastLBA() + 1;
-            firstMoved = 1;
-          } // if
-      } // for
-   } while (firstMoved == 1);
-   if (first > mainHeader.lastUsableLBA)
-      first = 0;
-   return (first);
-} // GPTData::FindFirstAvailable()
-
-// Find the last available block on the disk at or after the start
-// block. Returns 0 if there are no available partitions after
-// (or including) start.
-uint64_t GPTData::FindLastAvailable(uint64_t start) {
-   uint64_t last;
-   uint32_t i;
-   int lastMoved = 0;
-
-   // Start by assuming the last usable LBA is available....
-   last = mainHeader.lastUsableLBA;
-
-   // ...now, similar to algorithm in FindFirstAvailable(), search
-   // through all partitions, moving last when it's in an existing
-   // partition. Set the lastMoved flag so we repeat to catch cases
-   // where partitions are out of logical order.
-   do {
-      lastMoved = 0;
-      for (i = 0; i < mainHeader.numParts; i++) {
-         if ((last >= partitions[i].GetFirstLBA()) &&
-             (last <= partitions[i].GetLastLBA())) { // in existing part.
-            last = partitions[i].GetFirstLBA() - 1;
-            lastMoved = 1;
-         } // if
-      } // for
-   } while (lastMoved == 1);
-   if (last < mainHeader.firstUsableLBA)
-      last = 0;
-   return (last);
-} // GPTData::FindLastAvailable()
-
-// Find the last available block in the free space pointed to by start.
-uint64_t GPTData::FindLastInFree(uint64_t start) {
-   uint64_t nearestStart;
-   uint32_t i;
-
-   nearestStart = mainHeader.lastUsableLBA;
-   for (i = 0; i < mainHeader.numParts; i++) {
-      if ((nearestStart > partitions[i].GetFirstLBA()) &&
-          (partitions[i].GetFirstLBA() > start)) {
-         nearestStart = partitions[i].GetFirstLBA() - 1;
-      } // if
-   } // for
-   return (nearestStart);
-} // GPTData::FindLastInFree()
-
-// Returns 1 if sector is unallocated, 0 if it's allocated to a partition
-int GPTData::IsFree(uint64_t sector) {
-   int isFree = 1;
-   uint32_t i;
-
-   for (i = 0; i < mainHeader.numParts; i++) {
-      if ((sector >= partitions[i].GetFirstLBA()) &&
-          (sector <= partitions[i].GetLastLBA())) {
-         isFree = 0;
-      } // if
-   } // for
-   if ((sector < mainHeader.firstUsableLBA) || 
-       (sector > mainHeader.lastUsableLBA)) {
-      isFree = 0;
-   } // if
-   return (isFree);
-} // GPTData::IsFree()
-
-int GPTData::XFormPartitions(void) {
-   int i, numToConvert;
-   uint8_t origType;
-   struct newGUID;
-   char name[NAME_SIZE];
-
-   // Clear out old data & prepare basics....
-   ClearGPTData();
-
-   // Convert the smaller of the # of GPT or MBR partitions
-   if (mainHeader.numParts > (NUM_LOGICALS + 4))
-      numToConvert = NUM_LOGICALS + 4;
-   else
-      numToConvert = mainHeader.numParts;
-
-   for (i = 0; i < numToConvert; i++) {
-      origType = protectiveMBR.GetType(i);
-      // don't waste CPU time trying to convert extended, hybrid protective, or
-      // null (non-existent) partitions
-      if ((origType != 0x05) && (origType != 0x0f) && (origType != 0x85) && 
-          (origType != 0x00) && (origType != 0xEE))
-         partitions[i] = protectiveMBR.AsGPT(i);
-   } // for
-
-   // Convert MBR into protective MBR
-   protectiveMBR.MakeProtectiveMBR();
-
-   // Record that all original CRCs were OK so as not to raise flags
-   // when doing a disk verification
-   mainCrcOk = secondCrcOk = mainPartsCrcOk = secondPartsCrcOk = 1;
-
-   return (1);
-} // GPTData::XFormPartitions()
-
-// Transforms BSD disklable on the specified partition (numbered from 0).
-// If an invalid partition number is given, the program prompts for one.
-// Returns the number of new partitions created.
-int GPTData::XFormDisklabel(int i) {
-   uint32_t low, high, partNum, startPart;
-   uint16_t hexCode;
-   int goOn = 1, numDone = 0;
-   BSDData disklabel;
-
-   if (GetPartRange(&low, &high) != 0) {
-      if ((i < low) || (i > high))
-         partNum = GetPartNum();
-      else
-         partNum = (uint32_t) i;
-
-      // Find the partition after the last used one
-      startPart = high + 1;
-
-      // Now see if the specified partition has a BSD type code....
-      hexCode = partitions[partNum].GetHexType();
-      if ((hexCode != 0xa500) && (hexCode != 0xa900)) {
-         printf("Specified partition doesn't have a disklabel partition type "
-                "code.\nContinue anyway?");
-         goOn = (GetYN() == 'Y');
-      } // if
-
-      // If all is OK, read the disklabel and convert it.
-      if (goOn) {
-         goOn = disklabel.ReadBSDData(device, partitions[partNum].GetFirstLBA(),
-                                      partitions[partNum].GetLastLBA());
-         if ((goOn) && (disklabel.IsDisklabel())) {
-            numDone = XFormDisklabel(&disklabel, startPart);
-            if (numDone == 1)
-               printf("Converted %d BSD partition.\n", numDone);
-            else
-               printf("Converted %d BSD partitions.\n", numDone);
-         } else {
-            printf("Unable to convert partitions! Unrecognized BSD disklabel.\n");
-         } // if/else
-      } // if
-      if (numDone > 0) { // converted partitions; delete carrier
-         partitions[partNum].BlankPartition();
-      } // if
-   } else {
-      printf("No partitions\n");
-   } // if/else
-   return numDone;
-} // GPTData::XFormDisklable(int i)
-
-// Transform the partitions on an already-loaded BSD disklabel...
-int GPTData::XFormDisklabel(BSDData* disklabel, int startPart) {
-   int i, numDone = 0;
-
-   if ((disklabel->IsDisklabel()) && (startPart >= 0) &&
-       (startPart < mainHeader.numParts)) {
-      for (i = 0; i < disklabel->GetNumParts(); i++) {
-         partitions[i + startPart] = disklabel->AsGPT(i);
-         if (partitions[i + startPart].GetFirstLBA() != UINT64_C(0))
-            numDone++;
-      } // for
-   } // if
-
-   // Record that all original CRCs were OK so as not to raise flags
-   // when doing a disk verification
-   mainCrcOk = secondCrcOk = mainPartsCrcOk = secondPartsCrcOk = 1;
-
-   return numDone;
-} // GPTData::XFormDisklabel(BSDData* disklabel)
-
-// Sort the GPT entries, eliminating gaps and making for a logical
-// ordering. Relies on QuickSortGPT() for the bulk of the work
-void GPTData::SortGPT(void) {
-   int i, lastPart = 0;
-   GPTPart temp;
-
-   // First, find the last partition with data, so as not to
-   // spend needless time sorting empty entries....
-   for (i = 0; i < mainHeader.numParts; i++) {
-      if (partitions[i].GetFirstLBA() > 0)
-         lastPart = i;
-   } // for
-
-   // Now swap empties with the last partitions, to simplify the logic
-   // in the Quicksort function....
-   i = 0;
-   while (i < lastPart) {
-      if (partitions[i].GetFirstLBA() == 0) {
-         temp = partitions[i];
-	 partitions[i] = partitions[lastPart];
-	 partitions[lastPart] = temp;
-	 lastPart--;
-      } // if
-      i++;
-   } // while
-
-   // Now call the recursive quick sort routine to do the real work....
-   QuickSortGPT(partitions, 0, lastPart);
-} // GPTData::SortGPT()
-
-// Blank the partition array
-void GPTData::BlankPartitions(void) {
-   uint32_t i;
-
-   for (i = 0; i < mainHeader.numParts; i++) {
-      partitions[i].BlankPartition();
-   } // for
-} // GPTData::BlankPartitions()
-
-// Set up data structures for entirely new set of partitions on the
-// specified device. Returns 1 if OK, 0 if there were problems.
-int GPTData::ClearGPTData(void) {
-   int goOn, i;
-
-   // Set up the partition table....
-   free(partitions);
-   partitions = NULL;
-   SetGPTSize(NUM_GPT_ENTRIES);
-
-   // Now initialize a bunch of stuff that's static....
-   mainHeader.signature = GPT_SIGNATURE;
-   mainHeader.revision = 0x00010000;
-   mainHeader.headerSize = (uint32_t) HEADER_SIZE;
-   mainHeader.reserved = 0;
-   mainHeader.currentLBA = UINT64_C(1);
-   mainHeader.partitionEntriesLBA = (uint64_t) 2;
-   mainHeader.sizeOfPartitionEntries = GPT_SIZE;
-   for (i = 0; i < GPT_RESERVED; i++) {
-      mainHeader.reserved2[i] = '\0';
-   } // for
-
-   // Now some semi-static items (computed based on end of disk)
-   mainHeader.backupLBA = diskSize - UINT64_C(1);
-   mainHeader.lastUsableLBA = diskSize - mainHeader.firstUsableLBA;
-
-   // Set a unique GUID for the disk, based on random numbers
-   // rand() is only 32 bits, so multiply together to fill a 64-bit value
-   mainHeader.diskGUID.data1 = (uint64_t) rand() * (uint64_t) rand();
-   mainHeader.diskGUID.data2 = (uint64_t) rand() * (uint64_t) rand();
-
-   // Copy main header to backup header
-   RebuildSecondHeader();
-
-   // Blank out the partitions array....
-   BlankPartitions();
-
-   // Flag all CRCs as being OK....
-   mainCrcOk = 1;
-   secondCrcOk = 1;
-   mainPartsCrcOk = 1;
-   secondPartsCrcOk = 1;
-
-   return (goOn);
-} // GPTData::ClearGPTData()
-
-// Prompt user for a partition number, then change its type code
-// using ChangeGPTType(struct GPTPartition*) function.
-void GPTData::ChangePartType(void) {
-   int partNum;
-   uint32_t low, high;
-
-   if (GetPartRange(&low, &high) > 0) {
-      partNum = GetPartNum();
-      partitions[partNum].ChangeType();
-   } else {
-      printf("No partitions\n");
-   } // if/else
-} // GPTData::ChangePartType()
-
-// Prompts user for partition number and returns the result.
-uint32_t GPTData::GetPartNum(void) {
-   uint32_t partNum;
-   uint32_t low, high;
-   char prompt[255];
-
-   if (GetPartRange(&low, &high) > 0) {
-      sprintf(prompt, "Partition number (%d-%d): ", low + 1, high + 1);
-      partNum = GetNumber(low + 1, high + 1, low, prompt);
-   } else partNum = 1;
-   return (partNum - 1);
-} // GPTData::GetPartNum()
-
-void GPTData::SetAttributes(uint32_t partNum) {
-   Attributes theAttr;
-
-   theAttr.SetAttributes(partitions[partNum].GetAttributes());
-   theAttr.DisplayAttributes();
-   theAttr.ChangeAttributes();
-   partitions[partNum].SetAttributes(theAttr.GetAttributes());
-} // GPTData::SetAttributes()
-
-void GPTData::SetName(uint32_t partNum, char* theName) {
-   if ((partNum >= 0) && (partNum < mainHeader.numParts))
-      if (partitions[partNum].GetFirstLBA() > 0)
-         partitions[partNum].SetName((unsigned char*) theName);
-} // GPTData::SetName
-
-// Set the disk GUID to the specified value. Note that the header CRCs must
-// be recomputed after calling this function.
-void GPTData::SetDiskGUID(GUIDData newGUID) {
-   mainHeader.diskGUID = newGUID;
-   secondHeader.diskGUID = newGUID;
-} // SetDiskGUID()
-
-// Set the unique GUID of the specified partition. Returns 1 on
-// successful completion, 0 if there were problems (invalid
-// partition number).
-int GPTData::SetPartitionGUID(uint32_t pn, GUIDData theGUID) {
-   int retval = 0;
-
-   if (pn < mainHeader.numParts) {
-      if (partitions[pn].GetFirstLBA() != UINT64_C(0)) {
-	 partitions[pn].SetUniqueGUID(theGUID);
-         retval = 1;
-      } // if
-   } // if
-   return retval;
-} // GPTData::SetPartitionGUID()
-
 // Check the validity of the GPT header. Returns 1 if the main header
 // is valid, 2 if the backup header is valid, 3 if both are valid, and
 // 0 if neither is valid. Note that this function just checks the GPT
@@ -1048,12 +283,12 @@
 
    // If MBR bad, check for an Apple disk signature
    if ((protectiveMBR.GetValidity() == invalid) && 
-       (((mainHeader.signature << 32) == APM_SIGNATURE1) ||
+        (((mainHeader.signature << 32) == APM_SIGNATURE1) ||
         (mainHeader.signature << 32) == APM_SIGNATURE2)) {
       apmFound = 1; // Will display warning message later
-   } // if
+        } // if
 
-   return valid;
+        return valid;
 } // GPTData::CheckHeaderValidity()
 
 // Check the header CRC to see if it's OK...
@@ -1117,138 +352,6 @@
    secondHeader.headerCRC = crc;
 } // GPTData::RecomputeCRCs()
 
-// Perform detailed verification, reporting on any problems found, but
-// do *NOT* recover from these problems. Returns the total number of
-// problems identified.
-int GPTData::Verify(void) {
-   int problems = 0, numSegments, i, j;
-   uint64_t totalFree, largestSegment;
-   char tempStr[255], siTotal[255], siLargest[255];
-
-   // First, check for CRC errors in the GPT data....
-   if (!mainCrcOk) {
-      problems++;
-      printf("\nProblem: The CRC for the main GPT header is invalid. The main GPT header may\n"
-             "be corrupt. Consider loading the backup GPT header to rebuild the main GPT\n"
-             "header\n");
-   } // if
-   if (!mainPartsCrcOk) {
-      problems++;
-      printf("\nProblem: The CRC for the main partition table is invalid. This table may be\n"
-             "corrupt. Consider loading the backup partition table.\n");
-   } // if
-   if (!secondCrcOk) {
-      problems++;
-      printf("\nProblem: The CRC for the backup GPT header is invalid. The backup GPT header\n"
-             "may be corrupt. Consider using the main GPT header to rebuild the backup GPT\n"
-             "header.\n");
-   } // if
-   if (!secondPartsCrcOk) {
-      problems++;
-      printf("\nCaution: The CRC for the backup partition table is invalid. This table may\n"
-             "be corrupt. This program will automatically create a new backup partition\n"
-             "table when you save your partitions.\n");
-   } // if
-
-   // Now check that critical main and backup GPT entries match
-   if (mainHeader.currentLBA != secondHeader.backupLBA) {
-      problems++;
-      printf("\nProblem: main GPT header's current LBA pointer (%llu) doesn't\n"
-             "match the backup GPT header's LBA pointer(%llu)\n",
-             (unsigned long long) mainHeader.currentLBA,
-	     (unsigned long long) secondHeader.backupLBA);
-   } // if
-   if (mainHeader.backupLBA != secondHeader.currentLBA) {
-      problems++;
-      printf("\nProblem: main GPT header's backup LBA pointer (%llu) doesn't\n"
-             "match the backup GPT header's current LBA pointer (%llu)\n",
-             (unsigned long long) mainHeader.backupLBA,
-	     (unsigned long long) secondHeader.currentLBA);
-   } // if
-   if (mainHeader.firstUsableLBA != secondHeader.firstUsableLBA) {
-      problems++;
-      printf("\nProblem: main GPT header's first usable LBA pointer (%llu) doesn't\n"
-             "match the backup GPT header's first usable LBA pointer (%llu)\n",
-             (unsigned long long) mainHeader.firstUsableLBA,
-	     (unsigned long long) secondHeader.firstUsableLBA);
-   } // if
-   if (mainHeader.lastUsableLBA != secondHeader.lastUsableLBA) {
-      problems++;
-      printf("\nProblem: main GPT header's last usable LBA pointer (%llu) doesn't\n"
-             "match the backup GPT header's last usable LBA pointer (%llu)\n",
-             (unsigned long long) mainHeader.lastUsableLBA,
-	     (unsigned long long) secondHeader.lastUsableLBA);
-   } // if
-   if ((mainHeader.diskGUID.data1 != secondHeader.diskGUID.data1) ||
-       (mainHeader.diskGUID.data2 != secondHeader.diskGUID.data2)) {
-      problems++;
-      printf("\nProblem: main header's disk GUID (%s) doesn't\n",
-             GUIDToStr(mainHeader.diskGUID, tempStr));
-      printf("match the backup GPT header's disk GUID (%s)\n",
-             GUIDToStr(secondHeader.diskGUID, tempStr));
-   } // if
-   if (mainHeader.numParts != secondHeader.numParts) {
-      problems++;
-      printf("\nProblem: main GPT header's number of partitions (%lu) doesn't\n"
-             "match the backup GPT header's number of partitions (%lu)\n",
-	     (unsigned long) mainHeader.numParts,
-             (unsigned long) secondHeader.numParts);
-   } // if
-   if (mainHeader.sizeOfPartitionEntries != secondHeader.sizeOfPartitionEntries) {
-      problems++;
-      printf("\nProblem: main GPT header's size of partition entries (%lu) doesn't\n"
-             "match the backup GPT header's size of partition entries (%lu)\n",
-	     (unsigned long) mainHeader.sizeOfPartitionEntries,
-	     (unsigned long) secondHeader.sizeOfPartitionEntries);
-   } // if
-
-   // Now check for a few other miscellaneous problems...
-   // Check that the disk size will hold the data...
-   if (mainHeader.backupLBA > diskSize) {
-      problems++;
-      printf("\nProblem: Disk is too small to hold all the data!\n");
-      printf("(Disk size is %llu sectors, needs to be %llu sectors.)\n",
-             (unsigned long long) diskSize,
-             (unsigned long long) mainHeader.backupLBA);
-   } // if
-
-   // Check for overlapping partitions....
-   for (i = 1; i < mainHeader.numParts; i++) {
-      for (j = 0; j < i; j++) {
-         if (partitions[i].DoTheyOverlap(&partitions[j])) {
-            problems++;
-            printf("\nProblem: partitions %d and %d overlap:\n", i + 1, j + 1);
-            printf("  Partition %d: %llu to %llu\n", i, 
-                   (unsigned long long) partitions[i].GetFirstLBA(),
-                   (unsigned long long) partitions[i].GetLastLBA());
-            printf("  Partition %d: %llu to %llu\n", j,
-                   (unsigned long long) partitions[j].GetFirstLBA(),
-                   (unsigned long long) partitions[j].GetLastLBA());
-	 } // if
-      } // for j...
-   } // for i...
-
-   // Verify that partitions don't run into GPT data areas....
-   problems += CheckGPTSize();
-
-   // Now compute available space, but only if no problems found, since
-   // problems could affect the results
-   if (problems == 0) {
-      totalFree = FindFreeBlocks(&numSegments, &largestSegment);
-      BytesToSI(totalFree * (uint64_t) blockSize, siTotal);
-      BytesToSI(largestSegment * (uint64_t) blockSize, siLargest);
-      printf("No problems found. %llu free sectors (%s) available in %u\n"
-             "segments, the largest of which is %llu sectors (%s) in size\n",
-             (unsigned long long) totalFree,
-             siTotal, numSegments, (unsigned long long) largestSegment,
-	     siLargest);
-   } else {
-      printf("\nIdentified %d problems!\n", problems);
-   } // if/else
-
-   return (problems);
-} // GPTData::Verify()
-
 // Rebuild the main GPT header, using the secondary header as a model.
 // Typically called when the main header has been found to be corrupt.
 void GPTData::RebuildMainHeader(void) {
@@ -1294,7 +397,279 @@
    secondHeader.partitionEntriesCRC = mainHeader.partitionEntriesCRC;
    for (i = 0 ; i < GPT_RESERVED; i++)
       secondHeader.reserved2[i] = mainHeader.reserved2[i];
-} // RebuildSecondHeader()
+} // GPTData::RebuildSecondHeader()
+
+// Search for hybrid MBR entries that have no corresponding GPT partition.
+// Returns number of such mismatches found
+int GPTData::FindHybridMismatches(void) {
+   int i, j, found, numFound = 0;
+   uint64_t mbrFirst, mbrLast;
+
+   for (i = 0; i < 4; i++) {
+      if ((protectiveMBR.GetType(i) != 0xEE) && (protectiveMBR.GetType(i) != 0x00)) {
+         j = 0;
+         found = 0;
+         do {
+            mbrFirst = (uint64_t) protectiveMBR.GetFirstSector(i);
+            mbrLast = mbrFirst + (uint64_t) protectiveMBR.GetLength(i) - UINT64_C(1);
+            if ((partitions[j].GetFirstLBA() == mbrFirst) &&
+                (partitions[j].GetLastLBA() == mbrLast))
+               found = 1;
+            j++;
+         } while ((!found) && (j < mainHeader.numParts));
+         if (!found) {
+            numFound++;
+            printf("\nWarning! Mismatched GPT and MBR partitions! MBR partition "
+                   "%d, of type 0x%02X,\nhas no corresponding GPT partition! "
+                   "You may continue, but this condition\nmight cause data loss"
+                   " in the future!\a\n", i + 1, protectiveMBR.GetType(i));
+         } // if
+      } // if
+   } // for
+   return numFound;
+} // GPTData::FindHybridMismatches
+
+// Find overlapping partitions and warn user about them. Returns number of
+// overlapping partitions.
+int GPTData::FindOverlaps(void) {
+   int i, j, problems = 0;
+
+   for (i = 1; i < mainHeader.numParts; i++) {
+      for (j = 0; j < i; j++) {
+         if (partitions[i].DoTheyOverlap(&partitions[j])) {
+            problems++;
+            printf("\nProblem: partitions %d and %d overlap:\n", i + 1, j + 1);
+            printf("  Partition %d: %llu to %llu\n", i,
+                   (unsigned long long) partitions[i].GetFirstLBA(),
+                    (unsigned long long) partitions[i].GetLastLBA());
+            printf("  Partition %d: %llu to %llu\n", j,
+                   (unsigned long long) partitions[j].GetFirstLBA(),
+                    (unsigned long long) partitions[j].GetLastLBA());
+         } // if
+      } // for j...
+   } // for i...
+   return problems;
+} // GPTData::FindOverlaps()
+
+/******************************************************************
+ *                                                                *
+ * Begin functions that load data from disk or save data to disk. *
+ *                                                                *
+ ******************************************************************/
+
+// Scan for partition data. This function loads the MBR data (regular MBR or
+// protective MBR) and loads BSD disklabel data (which is probably invalid).
+// It also looks for APM data, forces a load of GPT data, and summarizes
+// the results.
+void GPTData::PartitionScan(int fd) {
+   BSDData bsdDisklabel;
+//   int bsdFound;
+
+   printf("Partition table scan:\n");
+
+   // Read the MBR & check for BSD disklabel
+   protectiveMBR.ReadMBRData(fd);
+   protectiveMBR.ShowState();
+   bsdDisklabel.ReadBSDData(fd, 0, diskSize - 1);
+   bsdFound = bsdDisklabel.ShowState();
+//   bsdDisklabel.DisplayBSDData();
+
+   // Load the GPT data, whether or not it's valid
+   ForceLoadGPTData(fd);
+   ShowAPMState(); // Show whether there's an Apple Partition Map present
+   ShowGPTState(); // Show GPT status
+   printf("\n");
+
+   if (apmFound) {
+      printf("\n*******************************************************************\n");
+      printf("This disk appears to contain an Apple-format (APM) partition table!\n");
+      printf("It will be destroyed if you continue!\n");
+      printf("*******************************************************************\n\n\a");
+   } // if
+/*   if (bsdFound) {
+   printf("\n*************************************************************************\n");
+   printf("This disk appears to contain a BSD disklabel! It will be destroyed if you\n"
+   "continue!\n");
+   printf("*************************************************************************\n\n\a");
+} // if */
+} // GPTData::PartitionScan()
+
+// Read GPT data from a disk.
+int GPTData::LoadPartitions(char* deviceFilename) {
+   int fd, err;
+   int allOK = 1, i;
+   uint64_t firstBlock, lastBlock;
+   BSDData bsdDisklabel;
+
+   // First, do a test to see if writing will be possible later....
+   fd = OpenForWrite(deviceFilename);
+   if (fd == -1)
+      printf("\aNOTE: Write test failed with error number %d. It will be "
+             "impossible to save\nchanges to this disk's partition table!\n\n",
+             errno);
+   close(fd);
+
+   if ((fd = open(deviceFilename, O_RDONLY)) != -1) {
+      // store disk information....
+      diskSize = disksize(fd, &err);
+      blockSize = (uint32_t) GetBlockSize(fd);
+      strcpy(device, deviceFilename);
+      PartitionScan(fd); // Check for partition types & print summary
+
+      switch (UseWhichPartitions()) {
+         case use_mbr:
+            XFormPartitions();
+            break;
+         case use_bsd:
+            bsdDisklabel.ReadBSDData(fd, 0, diskSize - 1);
+//            bsdDisklabel.DisplayBSDData();
+            ClearGPTData();
+            protectiveMBR.MakeProtectiveMBR(1); // clear boot area (option 1)
+            XFormDisklabel(&bsdDisklabel, 0);
+            break;
+         case use_gpt:
+            break;
+         case use_new:
+            ClearGPTData();
+            protectiveMBR.MakeProtectiveMBR();
+            break;
+      } // switch
+
+      // Now find the first and last sectors used by partitions...
+      if (allOK) {
+         firstBlock = mainHeader.backupLBA; // start high
+         lastBlock = 0; // start low
+         for (i = 0; i < mainHeader.numParts; i++) {
+            if ((partitions[i].GetFirstLBA() < firstBlock) &&
+                 (partitions[i].GetFirstLBA() > 0))
+               firstBlock = partitions[i].GetFirstLBA();
+            if (partitions[i].GetLastLBA() > lastBlock)
+               lastBlock = partitions[i].GetLastLBA();
+         } // for
+      } // if
+      CheckGPTSize();
+   } else {
+      allOK = 0;
+      fprintf(stderr, "Problem opening %s for reading! Error is %d\n",
+              deviceFilename, errno);
+      if (errno == EACCES) { // User is probably not running as root
+         fprintf(stderr, "You must run this program as root or use sudo!\n");
+      } // if
+   } // if/else
+   return (allOK);
+} // GPTData::LoadPartitions()
+
+// Loads the GPT, as much as possible. Returns 1 if this seems to have
+// succeeded, 0 if there are obvious problems....
+int GPTData::ForceLoadGPTData(int fd) {
+   int allOK = 1, validHeaders;
+   off_t seekTo;
+   char* storage;
+   uint32_t newCRC, sizeOfParts;
+
+   // Seek to and read the main GPT header
+   lseek64(fd, 512, SEEK_SET);
+   read(fd, &mainHeader, 512); // read main GPT header
+   mainCrcOk = CheckHeaderCRC(&mainHeader);
+   if (IsLittleEndian() == 0) // big-endian system; adjust header byte order....
+      ReverseHeaderBytes(&mainHeader);
+
+   // Load backup header, check its CRC, and store the results of
+   // the check for future reference
+   seekTo = (diskSize * blockSize) - UINT64_C(512);
+   if (lseek64(fd, seekTo, SEEK_SET) != (off_t) -1) {
+      read(fd, &secondHeader, 512); // read secondary GPT header
+      secondCrcOk = CheckHeaderCRC(&secondHeader);
+      if (IsLittleEndian() == 0) // big-endian system; adjust header byte order....
+         ReverseHeaderBytes(&secondHeader);
+   } else {
+      allOK = 0;
+      state = gpt_invalid;
+      fprintf(stderr, "Unable to seek to secondary GPT at sector %llu!\n",
+              diskSize - (UINT64_C(1)));
+   } // if/else lseek
+
+   // Return valid headers code: 0 = both headers bad; 1 = main header
+   // good, backup bad; 2 = backup header good, main header bad;
+   // 3 = both headers good. Note these codes refer to valid GPT
+   // signatures and version numbers; more subtle problems will elude
+   // this check!
+   validHeaders = CheckHeaderValidity();
+
+   // Read partitions (from primary array)
+   if (validHeaders > 0) { // if at least one header is OK....
+      // GPT appears to be valid....
+      state = gpt_valid;
+
+      // We're calling the GPT valid, but there's a possibility that one
+      // of the two headers is corrupt. If so, use the one that seems to
+      // be in better shape to regenerate the bad one
+      if (validHeaders == 2) { // valid backup header, invalid main header
+         printf("Caution: invalid main GPT header, but valid backup; regenerating main header\n"
+               "from backup!\n");
+         RebuildMainHeader();
+         mainCrcOk = secondCrcOk; // Since copied, use CRC validity of backup
+      } else if (validHeaders == 1) { // valid main header, invalid backup
+         printf("Caution: invalid backup GPT header, but valid main header; regenerating\n"
+               "backup header from main header.\n");
+         RebuildSecondHeader();
+         secondCrcOk = mainCrcOk; // Since regenerated, use CRC validity of main
+      } // if/else/if
+
+      // Load the main partition table, including storing results of its
+      // CRC check
+      if (LoadMainTable() == 0)
+         allOK = 0;
+
+      // Load backup partition table into temporary storage to check
+      // its CRC and store the results, then discard this temporary
+      // storage, since we don't use it in any but recovery operations
+      seekTo = secondHeader.partitionEntriesLBA * (off_t) blockSize;
+      if ((lseek64(fd, seekTo, SEEK_SET) != (off_t) -1) && (secondCrcOk)) {
+         sizeOfParts = secondHeader.numParts * secondHeader.sizeOfPartitionEntries;
+         storage = (char*) malloc(sizeOfParts);
+         read(fd, storage, sizeOfParts);
+         newCRC = chksum_crc32((unsigned char*) storage,  sizeOfParts);
+         free(storage);
+         secondPartsCrcOk = (newCRC == secondHeader.partitionEntriesCRC);
+      } // if
+
+      // Check for valid CRCs and warn if there are problems
+      if ((mainCrcOk == 0) || (secondCrcOk == 0) || (mainPartsCrcOk == 0) ||
+           (secondPartsCrcOk == 0)) {
+         printf("Warning! One or more CRCs don't match. You should repair the disk!\n");
+         state = gpt_corrupt;
+           } // if
+   } else {
+      state = gpt_invalid;
+   } // if/else
+   return allOK;
+} // GPTData::ForceLoadGPTData()
+
+// Loads the partition tables pointed to by the main GPT header. The
+// main GPT header in memory MUST be valid for this call to do anything
+// sensible!
+int GPTData::LoadMainTable(void) {
+   int fd, retval = 0;
+   uint32_t newCRC, sizeOfParts;
+
+   if ((fd = open(device, O_RDONLY)) != -1) {
+      // Set internal data structures for number of partitions on the disk
+      SetGPTSize(mainHeader.numParts);
+
+      // Load main partition table, and record whether its CRC
+      // matches the stored value
+      lseek64(fd, mainHeader.partitionEntriesLBA * blockSize, SEEK_SET);
+      sizeOfParts = mainHeader.numParts * mainHeader.sizeOfPartitionEntries;
+      read(fd, partitions, sizeOfParts);
+      newCRC = chksum_crc32((unsigned char*) partitions, sizeOfParts);
+      mainPartsCrcOk = (newCRC == mainHeader.partitionEntriesCRC);
+      if (IsLittleEndian() == 0)
+         ReversePartitionBytes();
+      retval = 1;
+   } // if
+   return retval;
+} // GPTData::LoadMainTable()
 
 // Load the second (backup) partition table as the primary partition
 // table. Used in repair functions
@@ -1311,7 +686,7 @@
          read(fd, partitions, sizeOfParts);
          newCRC = chksum_crc32((unsigned char*) partitions, sizeOfParts);
          secondPartsCrcOk = (newCRC == secondHeader.partitionEntriesCRC);
-	 mainPartsCrcOk = secondPartsCrcOk;
+         mainPartsCrcOk = secondPartsCrcOk;
          if (IsLittleEndian() == 0)
             ReversePartitionBytes();
          if (!secondPartsCrcOk) {
@@ -1325,141 +700,6 @@
    } // if/else
 } // GPTData::LoadSecondTableAsMain()
 
-// Finds the total number of free blocks, the number of segments in which
-// they reside, and the size of the largest of those segments
-uint64_t GPTData::FindFreeBlocks(int *numSegments, uint64_t *largestSegment) {
-   uint64_t start = UINT64_C(0); // starting point for each search
-   uint64_t totalFound = UINT64_C(0); // running total
-   uint64_t firstBlock; // first block in a segment
-   uint64_t lastBlock; // last block in a segment
-   uint64_t segmentSize; // size of segment in blocks
-   int num = 0;
-
-   *largestSegment = UINT64_C(0);
-   do {
-      firstBlock = FindFirstAvailable(start);
-      if (firstBlock != UINT64_C(0)) { // something's free...
-         lastBlock = FindLastInFree(firstBlock);
-         segmentSize = lastBlock - firstBlock + UINT64_C(1);
-	 if (segmentSize > *largestSegment) {
-            *largestSegment = segmentSize;
-	 } // if
-         totalFound += segmentSize;
-	 num++;
-	 start = lastBlock + 1;
-      } // if
-   } while (firstBlock != 0);
-   *numSegments = num;
-   return totalFound;
-} // GPTData::FindFreeBlocks()
-
-// Create a hybrid MBR -- an ugly, funky thing that helps GPT work with
-// OSes that don't understand GPT.
-void GPTData::MakeHybrid(void) {
-   uint32_t partNums[3];
-   char line[255];
-   int numParts, i, j, typeCode, bootable, mbrNum;
-   uint64_t length;
-   char fillItUp = 'M'; // fill extra partition entries? (Yes/No/Maybe)
-   char eeFirst; // Whether EFI GPT (0xEE) partition comes first in table
-
-   printf("\nWARNING! Hybrid MBRs are flaky and potentially dangerous! If you decide not\n"
-          "to use one, just hit the Enter key at the below prompt and your MBR\n"
-          "partition table will be untouched.\n\n\a");
-
-   // Now get the numbers of up to three partitions to add to the
-   // hybrid MBR....
-   printf("Type from one to three GPT partition numbers, separated by spaces, to be\n"
-          "added to the hybrid MBR, in sequence: ");
-   fgets(line, 255, stdin);
-   numParts = sscanf(line, "%d %d %d", &partNums[0], &partNums[1], &partNums[2]);
-
-   if (numParts > 0) {
-      // Blank out the protective MBR, but leave the boot loader code
-      // alone....
-      protectiveMBR.EmptyMBR(0);
-      protectiveMBR.SetDiskSize(diskSize);
-      printf("Place EFI GPT (0xEE) partition first in MBR (good for GRUB)? ");
-      eeFirst = GetYN();
-   } // if
-
-   for (i = 0; i < numParts; i++) {
-      j = partNums[i] - 1;
-      printf("\nCreating entry for partition #%d\n", j + 1);
-      if ((j >= 0) && (j < mainHeader.numParts)) {
-         if (partitions[j].GetLastLBA() < UINT32_MAX) {
-            do {
-               printf("Enter an MBR hex code (default %02X): ",
-                      typeHelper.GUIDToID(partitions[j].GetType()) / 256);
-               fgets(line, 255, stdin);
-               sscanf(line, "%x", &typeCode);
-               if (line[0] == '\n')
-                 typeCode = partitions[j].GetHexType() / 256;
-            } while ((typeCode <= 0) || (typeCode > 255));
-	    printf("Set the bootable flag? ");
-	    bootable = (GetYN() == 'Y');
-            length = partitions[j].GetLengthLBA();
-            if (eeFirst == 'Y')
-               mbrNum = i + 1;
-            else
-               mbrNum = i;
-            protectiveMBR.MakePart(mbrNum, (uint32_t) partitions[j].GetFirstLBA(),
-                                   (uint32_t) length, typeCode, bootable);
-         } else { // partition out of range
-            printf("Partition %d ends beyond the 2TiB limit of MBR partitions; omitting it.\n",
-                   j + 1);
-         } // if/else
-      } else {
-         printf("Partition %d is out of range; omitting it.\n", j + 1);
-      } // if/else
-   } // for
-
-   if (numParts > 0) { // User opted to create a hybrid MBR....
-      // Create EFI protective partition that covers the start of the disk.
-      // If this location (covering the main GPT data structures) is omitted,
-      // Linux won't find any partitions on the disk. Note that this is
-      // NUMBERED AFTER the hybrid partitions, contrary to what the
-      // gptsync utility does. This is because Windows seems to choke on
-      // disks with a 0xEE partition in the first slot and subsequent
-      // additional partitions, unless it boots from the disk.
-      if (eeFirst == 'Y')
-         mbrNum = 0;
-      else
-         mbrNum = numParts;
-      protectiveMBR.MakePart(mbrNum, 1, protectiveMBR.FindLastInFree(1), 0xEE);
-
-      // ... and for good measure, if there are any partition spaces left,
-      // optionally create more protective EFI partitions to cover as much
-      // space as possible....
-      for (i = 0; i < 4; i++) {
-         if (protectiveMBR.GetType(i) == 0x00) { // unused entry....
-	    if (fillItUp == 'M') {
-	       printf("\nUnused partition space(s) found. Use one to protect more partitions? ");
-	       fillItUp = GetYN();
-	       typeCode = 0x00; // use this to flag a need to get type code
-	    } // if
-	    if (fillItUp == 'Y') {
-               while ((typeCode <= 0) || (typeCode > 255)) {
-                  printf("Enter an MBR hex code (EE is EFI GPT, but may confuse MacOS): ");
-                  // Comment on above: Mac OS treats disks with more than one
-                  // 0xEE MBR partition as MBR disks, not as GPT disks.
-                  fgets(line, 255, stdin);
-                  sscanf(line, "%x", &typeCode);
-                  if (line[0] == '\n')
-                     typeCode = 0;
-               } // while
-               protectiveMBR.MakeBiggestPart(i, typeCode); // make a partition
-            } // if (fillItUp == 'Y')
-         } // if unused entry
-      } // for (i = 0; i < 4; i++)
-   } // if (numParts > 0)
-} // GPTData::MakeHybrid()
-
-// Create a fresh protective MBR.
-void GPTData::MakeProtectiveMBR(void) {
-   protectiveMBR.MakeProtectiveMBR();
-} // GPTData::MakeProtectiveMBR(void)
-
 // Writes GPT (and protective MBR) to disk. Returns 1 on successful
 // write, 0 if there was a problem.
 int GPTData::SaveGPTData(void) {
@@ -1491,21 +731,14 @@
    } // if
 
    // Check for overlapping partitions....
-   for (i = 1; i < mainHeader.numParts; i++) {
-      for (j = 0; j < i; j++) {
-         if (partitions[i].DoTheyOverlap(&partitions[j])) {
-            fprintf(stderr, "\Error: partitions %d and %d overlap:\n", i + 1, j + 1);
-            fprintf(stderr, "  Partition %d: %llu to %llu\n", i, 
-                   (unsigned long long) partitions[i].GetFirstLBA(),
-                   (unsigned long long) partitions[i].GetLastLBA());
-            fprintf(stderr, "  Partition %d: %llu to %llu\n", j,
-                   (unsigned long long) partitions[j].GetFirstLBA(),
-                   (unsigned long long) partitions[j].GetLastLBA());
-            fprintf(stderr, "Aborting write operation!\n");
-	    allOK = 0;
-	 } // if
-      } // for j...
-   } // for i...
+   if (FindOverlaps() > 0) {
+      allOK = 0;
+      fprintf(stderr, "Aborting write operation!\n");
+   } // if
+
+   // Check for mismatched MBR and GPT data, but let it pass if found
+   // (function displays warning message)
+   FindHybridMismatches();
 
    // Pull out some data that's needed before doing byte-order reversal on
    // big-endian systems....
@@ -1536,30 +769,24 @@
 
    // Do it!
    if (allOK) {
-      fd = open(device, O_WRONLY); // try to open the device; may fail....
-#ifdef __APPLE__
-      // MacOS X requires a shared lock under some circumstances....
-      if (fd < 0) {
-         fd = open(device, O_WRONLY|O_SHLOCK);
-      } // if
-#endif
+      fd = OpenForWrite(device);
       if (fd != -1) {
          // First, write the protective MBR...
-	 protectiveMBR.WriteMBRData(fd);
+         protectiveMBR.WriteMBRData(fd);
 
          // Now write the main GPT header...
          if (allOK)
             if (write(fd, &mainHeader, 512) == -1)
-              allOK = 0;
+               allOK = 0;
 
          // Now write the main partition tables...
-	 if (allOK) {
-	    if (write(fd, partitions, GPT_SIZE * numParts) == -1)
+         if (allOK) {
+            if (write(fd, partitions, GPT_SIZE * numParts) == -1)
                allOK = 0;
          } // if
 
          // Now seek to near the end to write the secondary GPT....
-	 if (allOK) {
+         if (allOK) {
             offset = (off_t) secondTable * (off_t) (blockSize);
             if (lseek64(fd, offset, SEEK_SET) == (off_t) - 1) {
                allOK = 0;
@@ -1568,51 +795,52 @@
          } // if
 
          // Now write the secondary partition tables....
-	 if (allOK)
+         if (allOK)
             if (write(fd, partitions, GPT_SIZE * numParts) == -1)
                allOK = 0;
 
          // Now write the secondary GPT header...
-	 if (allOK)
+         if (allOK)
             if (write(fd, &secondHeader, 512) == -1)
-	       allOK = 0;
+               allOK = 0;
 
          // re-read the partition table
          if (allOK) {
             sync();
 #ifdef __APPLE__
-	    printf("Warning: The kernel may continue to use old or deleted partitions.\n"
-	           "You should reboot or remove the drive.\n");
+            printf("Warning: The kernel may continue to use old or deleted partitions.\n"
+                  "You should reboot or remove the drive.\n");
 	    /* don't know if this helps
 	     * it definitely will get things on disk though:
-	     * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
-	    i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
+            * http://topiks.org/mac-os-x/0321278542/ch12lev1sec8.html */
+            i = ioctl(fd, DKIOCSYNCHRONIZECACHE);
 #else
 #ifdef __FreeBSD__
             sleep(2);
             i = ioctl(fd, DIOCGFLUSH);
-            printf("Warning: The kernel is still using the old partition table.\n");
+            printf("Warning: The kernel may still provide disk access using old partition IDs.\n");
 #else
-	    sleep(2);
+            sleep(2);
             i = ioctl(fd, BLKRRPART);
             if (i)
                printf("Warning: The kernel is still using the old partition table.\n"
-                      "The new table will be used at the next reboot.\n");
+                     "The new table will be used at the next reboot.\n");
 #endif
 #endif
          } // if
 
          if (allOK) { // writes completed OK
-	    printf("The operation has completed successfully.\n");
+            printf("The operation has completed successfully.\n");
          } else {
-	    printf("Warning! An error was reported when writing the partition table! This error\n");
-	    printf("MIGHT be harmless, but you may have trashed the disk! Use parted and, if\n");
-	    printf("necessary, restore your original partition table.\n");
+            printf("Warning! An error was reported when writing the partition table! This error\n");
+            printf("MIGHT be harmless, but you may have trashed the disk! Use parted and, if\n");
+            printf("necessary, restore your original partition table.\n");
          } // if/else
-	 close(fd);
+         close(fd);
       } else {
-         fprintf(stderr, "Unable to open device %s for writing! Errno is %d! Aborting!\n", device, errno);
-	 allOK = 0;
+         fprintf(stderr, "Unable to open device %s for writing! Errno is %d! Aborting write!\n",
+                 device, errno);
+         allOK = 0;
       } // if/else
    } else {
       printf("Aborting write of new partition table.\n");
@@ -1659,7 +887,7 @@
       // Now write the secondary GPT header...
       if (allOK)
          if (write(fd, &secondHeader, 512) == -1)
-	    allOK = 0;
+            allOK = 0;
 
       // Now write the main partition tables...
       if (allOK) {
@@ -1732,7 +960,7 @@
       if ((val = CheckHeaderValidity()) > 0) {
          if (val == 2) { // only backup header seems to be good
             numParts = secondHeader.numParts;
-	    sizeOfEntries = secondHeader.sizeOfPartitionEntries;
+            sizeOfEntries = secondHeader.sizeOfPartitionEntries;
          } else { // main header is OK
             numParts = mainHeader.numParts;
             sizeOfEntries = mainHeader.sizeOfPartitionEntries;
@@ -1743,7 +971,7 @@
          // If current disk size doesn't match that of backup....
          if (secondHeader.currentLBA != diskSize - UINT64_C(1)) {
             printf("Warning! Current disk size doesn't match that of the backup!\n"
-	           "Adjusting sizes to match, but subsequent problems are possible!\n");
+                  "Adjusting sizes to match, but subsequent problems are possible!\n");
             secondHeader.currentLBA = mainHeader.backupLBA = diskSize - UINT64_C(1);
             mainHeader.lastUsableLBA = diskSize - mainHeader.firstUsableLBA;
             secondHeader.lastUsableLBA = mainHeader.lastUsableLBA;
@@ -1759,9 +987,9 @@
          mainPartsCrcOk = (newCRC == mainHeader.partitionEntriesCRC);
          secondPartsCrcOk = (newCRC == secondHeader.partitionEntriesCRC);
          // Reverse byte order, if necessary
-            if (littleEndian == 0) {
-               ReversePartitionBytes();
-            } // if
+         if (littleEndian == 0) {
+            ReversePartitionBytes();
+         } // if
 
       } else {
          allOK = 0;
@@ -1779,6 +1007,229 @@
    return allOK;
 } // GPTData::LoadGPTBackup()
 
+// Tell user whether Apple Partition Map (APM) was discovered....
+void GPTData::ShowAPMState(void) {
+   if (apmFound)
+      printf("  APM: present\n");
+   else
+      printf("  APM: not present\n");
+} // GPTData::ShowAPMState()
+
+// Tell user about the state of the GPT data....
+void GPTData::ShowGPTState(void) {
+   switch (state) {
+      case gpt_invalid:
+         printf("  GPT: not present\n");
+         break;
+      case gpt_valid:
+         printf("  GPT: present\n");
+         break;
+      case gpt_corrupt:
+         printf("  GPT: damaged\n");
+         break;
+      default:
+         printf("\a  GPT: unknown -- bug!\n");
+         break;
+   } // switch
+} // GPTData::ShowGPTState()
+
+// Display the basic GPT data
+void GPTData::DisplayGPTData(void) {
+   int i, j;
+   char sizeInSI[255]; // String to hold size of disk in SI units
+   char tempStr[255];
+   uint64_t temp, totalFree;
+
+   BytesToSI(diskSize * blockSize, sizeInSI);
+   printf("Disk %s: %lu sectors, %s\n", device,
+          (unsigned long) diskSize, sizeInSI);
+   printf("Disk identifier (GUID): %s\n", GUIDToStr(mainHeader.diskGUID, tempStr));
+   printf("Partition table holds up to %lu entries\n", (unsigned long) mainHeader.numParts);
+   printf("First usable sector is %lu, last usable sector is %lu\n",
+          (unsigned long) mainHeader.firstUsableLBA,
+           (unsigned long) mainHeader.lastUsableLBA);
+   totalFree = FindFreeBlocks(&i, &temp);
+   printf("Total free space is %llu sectors (%s)\n", totalFree,
+          BytesToSI(totalFree * (uint64_t) blockSize, sizeInSI));
+   printf("\nNumber  Start (sector)    End (sector)  Size       Code  Name\n");
+   for (i = 0; i < mainHeader.numParts; i++) {
+      partitions[i].ShowSummary(i, blockSize, sizeInSI);
+   } // for
+} // GPTData::DisplayGPTData()
+
+// Get partition number from user and then call ShowPartDetails(partNum)
+// to show its detailed information
+void GPTData::ShowDetails(void) {
+   int partNum;
+   uint32_t low, high;
+
+   if (GetPartRange(&low, &high) > 0) {
+      partNum = GetPartNum();
+      ShowPartDetails(partNum);
+   } else {
+      printf("No partitions\n");
+   } // if/else
+} // GPTData::ShowDetails()
+
+// Show detailed information on the specified partition
+void GPTData::ShowPartDetails(uint32_t partNum) {
+   if (partitions[partNum].GetFirstLBA() != 0) {
+      partitions[partNum].ShowDetails(blockSize);
+   } else {
+      printf("Partition #%d does not exist.", (int) (partNum + 1));
+   } // if
+} // GPTData::ShowPartDetails()
+
+/*********************************************************************
+ *                                                                   *
+ * Begin functions that obtain information from the users, and often *
+ * do something with that information (call other functions)         *
+ *                                                                   *
+ *********************************************************************/
+
+// Prompts user for partition number and returns the result.
+uint32_t GPTData::GetPartNum(void) {
+   uint32_t partNum;
+   uint32_t low, high;
+   char prompt[255];
+
+   if (GetPartRange(&low, &high) > 0) {
+      sprintf(prompt, "Partition number (%d-%d): ", low + 1, high + 1);
+      partNum = GetNumber(low + 1, high + 1, low, prompt);
+   } else partNum = 1;
+   return (partNum - 1);
+} // GPTData::GetPartNum()
+
+// What it says: Resize the partition table. (Default is 128 entries.)
+void GPTData::ResizePartitionTable(void) {
+   int newSize;
+   char prompt[255];
+   uint32_t curLow, curHigh;
+
+   printf("Current partition table size is %lu.\n",
+          (unsigned long) mainHeader.numParts);
+   GetPartRange(&curLow, &curHigh);
+   curHigh++; // since GetPartRange() returns numbers starting from 0...
+   // There's no point in having fewer than four partitions....
+   if (curHigh < 4)
+      curHigh = 4;
+   sprintf(prompt, "Enter new size (%d up, default %d): ", (int) curHigh,
+           (int) NUM_GPT_ENTRIES);
+   newSize = GetNumber(4, 65535, 128, prompt);
+   if (newSize < 128) {
+      printf("Caution: The partition table size should officially be 16KB or larger,\n"
+            "which works out to 128 entries. In practice, smaller tables seem to\n"
+            "work with most OSes, but this practice is risky. I'm proceeding with\n"
+            "the resize, but you may want to reconsider this action and undo it.\n\n");
+   } // if
+   SetGPTSize(newSize);
+} // GPTData::ResizePartitionTable()
+
+// Interactively create a partition
+void GPTData::CreatePartition(void) {
+   uint64_t firstBlock, firstInLargest, lastBlock, sector;
+   char prompt[255];
+   int partNum, firstFreePart = 0;
+
+   // Find first free partition...
+   while (partitions[firstFreePart].GetFirstLBA() != 0) {
+      firstFreePart++;
+   } // while
+
+   if (((firstBlock = FindFirstAvailable()) != 0) &&
+         (firstFreePart < mainHeader.numParts)) {
+      lastBlock = FindLastAvailable(firstBlock);
+      firstInLargest = FindFirstInLargest();
+
+      // Get partition number....
+      do {
+         sprintf(prompt, "Partition number (%d-%d, default %d): ", firstFreePart + 1,
+                 mainHeader.numParts, firstFreePart + 1);
+         partNum = GetNumber(firstFreePart + 1, mainHeader.numParts,
+                             firstFreePart + 1, prompt) - 1;
+         if (partitions[partNum].GetFirstLBA() != 0)
+            printf("partition %d is in use.\n", partNum + 1);
+      } while (partitions[partNum].GetFirstLBA() != 0);
+
+      // Get first block for new partition...
+      sprintf(prompt,
+              "First sector (%llu-%llu, default = %llu) or {+-}size{KMGT}: ",
+              firstBlock, lastBlock, firstInLargest);
+      do {
+         sector = GetSectorNum(firstBlock, lastBlock, firstInLargest, prompt);
+      } while (IsFree(sector) == 0);
+      firstBlock = sector;
+
+      // Get last block for new partitions...
+      lastBlock = FindLastInFree(firstBlock);
+      sprintf(prompt,
+              "Last sector (%llu-%llu, default = %llu) or {+-}size{KMGT}: ",
+              firstBlock, lastBlock, lastBlock);
+      do {
+         sector = GetSectorNum(firstBlock, lastBlock, lastBlock, prompt);
+      } while (IsFree(sector) == 0);
+      lastBlock = sector;
+
+      partitions[partNum].SetFirstLBA(firstBlock);
+      partitions[partNum].SetLastLBA(lastBlock);
+
+      partitions[partNum].SetUniqueGUID(1);
+      partitions[partNum].ChangeType();
+      partitions[partNum].SetName((unsigned char*) partitions[partNum].GetNameType(prompt));
+         } else {
+            printf("No free sectors available\n");
+         } // if/else
+} // GPTData::CreatePartition()
+
+// Interactively delete a partition (duh!)
+void GPTData::DeletePartition(void) {
+   int partNum;
+   uint32_t low, high;
+   uint64_t startSector, length;
+   char prompt[255];
+
+   if (GetPartRange(&low, &high) > 0) {
+      sprintf(prompt, "Partition number (%d-%d): ", low + 1, high + 1);
+      partNum = GetNumber(low + 1, high + 1, low, prompt);
+
+      // In case there's a protective MBR, look for & delete matching
+      // MBR partition....
+      startSector = partitions[partNum - 1].GetFirstLBA();
+      length = partitions[partNum - 1].GetLengthLBA();
+      protectiveMBR.DeleteByLocation(startSector, length);
+
+      // Now delete the GPT partition
+      partitions[partNum - 1].BlankPartition();
+   } else {
+      printf("No partitions\n");
+   } // if/else
+} // GPTData::DeletePartition()
+
+// Prompt user for a partition number, then change its type code
+// using ChangeGPTType(struct GPTPartition*) function.
+void GPTData::ChangePartType(void) {
+   int partNum;
+   uint32_t low, high;
+
+   if (GetPartRange(&low, &high) > 0) {
+      partNum = GetPartNum();
+      partitions[partNum].ChangeType();
+   } else {
+      printf("No partitions\n");
+   } // if/else
+} // GPTData::ChangePartType()
+
+// Partition attributes seem to be rarely used, but I want a way to
+// adjust them for completeness....
+void GPTData::SetAttributes(uint32_t partNum) {
+   Attributes theAttr;
+
+   theAttr.SetAttributes(partitions[partNum].GetAttributes());
+   theAttr.DisplayAttributes();
+   theAttr.ChangeAttributes();
+   partitions[partNum].SetAttributes(theAttr.GetAttributes());
+} // GPTData::SetAttributes()
+
 // This function destroys the on-disk GPT structures. Returns 1 if the
 // user confirms destruction, 0 if the user aborts.
 int GPTData::DestroyGPT(void) {
@@ -1810,14 +1261,14 @@
             write(fd, blankSector, 512);
          lseek64(fd, secondHeader.currentLBA * 512, SEEK_SET); // seek to GPT header
          write(fd, blankSector, 512); // blank it out
-	 printf("Blank out MBR? ");
-	 if (GetYN() == 'Y') {
+         printf("Blank out MBR? ");
+         if (GetYN() == 'Y') {
             lseek64(fd, 0, SEEK_SET);
             write(fd, blankSector, 512); // blank it out
          } // if blank MBR
          close(fd);
-	 printf("GPT data structures destroyed! You may now partition the disk using fdisk or\n"
-                "other utilities. Program will now terminate.\n");
+         printf("GPT data structures destroyed! You may now partition the disk using fdisk or\n"
+               "other utilities. Program will now terminate.\n");
       } else {
          printf("Problem opening %s for writing! Program will now terminate.\n");
       } // if/else (fd != -1)
@@ -1825,6 +1276,680 @@
    return (goOn == 'Y');
 } // GPTData::DestroyGPT()
 
+/**************************************************************************
+ *                                                                        *
+ * Partition table transformation functions (MBR or BSD disklabel to GPT) *
+ * (some of these functions may require user interaction)                 *
+ *                                                                        *
+ **************************************************************************/
+
+// Examines the MBR & GPT data, and perhaps asks the user questions, to
+// determine which set of data to use: the MBR (use_mbr), the GPT (use_gpt),
+// or create a new set of partitions (use_new)
+WhichToUse GPTData::UseWhichPartitions(void) {
+   WhichToUse which = use_new;
+   MBRValidity mbrState;
+   int answer;
+
+   mbrState = protectiveMBR.GetValidity();
+
+   if ((state == gpt_invalid) && ((mbrState == mbr) || (mbrState == hybrid))) {
+      printf("\n\a***************************************************************\n"
+            "Found invalid GPT and valid MBR; converting MBR to GPT format.\n"
+            "THIS OPERATON IS POTENTIALLY DESTRUCTIVE! Exit by typing 'q' if\n"
+            "you don't want to convert your MBR partitions to GPT format!\n"
+            "***************************************************************\n\n");
+      which = use_mbr;
+   } // if
+
+   if ((state == gpt_invalid) && bsdFound) {
+      printf("\n\a**********************************************************************\n"
+            "Found invalid GPT and valid BSD disklabel; converting BSD disklabel\n"
+            "to GPT format. THIS OPERATON IS POTENTIALLY DESTRUCTIVE! Your first\n"
+            "BSD partition will likely be unusable. Exit by typing 'q' if you don't\n"
+            "want to convert your BSD partitions to GPT format!\n"
+            "**********************************************************************\n\n");
+      which = use_bsd;
+   } // if
+
+   if ((state == gpt_valid) && (mbrState == gpt)) {
+      printf("Found valid GPT with protective MBR; using GPT.\n");
+      which = use_gpt;
+   } // if
+   if ((state == gpt_valid) && (mbrState == hybrid)) {
+      printf("Found valid GPT with hybrid MBR; using GPT.\n");
+      which = use_gpt;
+   } // if
+   if ((state == gpt_valid) && (mbrState == invalid)) {
+      printf("\aFound valid GPT with corrupt MBR; using GPT and will create new\nprotective MBR on save.\n");
+      which = use_gpt;
+      protectiveMBR.MakeProtectiveMBR();
+   } // if
+   if ((state == gpt_valid) && (mbrState == mbr)) {
+      printf("Found valid MBR and GPT. Which do you want to use?\n");
+      answer = GetNumber(1, 3, 2, (char*) " 1 - MBR\n 2 - GPT\n 3 - Create blank GPT\n\nYour answer: ");
+      if (answer == 1) {
+         which = use_mbr;
+      } else if (answer == 2) {
+         which = use_gpt;
+         protectiveMBR.MakeProtectiveMBR();
+         printf("Using GPT and creating fresh protective MBR.\n");
+      } else which = use_new;
+   } // if
+
+   // Nasty decisions here -- GPT is present, but corrupt (bad CRCs or other
+   // problems)
+   if (state == gpt_corrupt) {
+      if ((mbrState == mbr) || (mbrState == hybrid)) {
+         printf("Found valid MBR and corrupt GPT. Which do you want to use? (Using the\n"
+               "GPT MAY permit recovery of GPT data.)\n");
+         answer = GetNumber(1, 3, 2, (char*) " 1 - MBR\n 2 - GPT\n 3 - Create blank GPT\n\nYour answer: ");
+         if (answer == 1) {
+            which = use_mbr;
+//            protectiveMBR.MakeProtectiveMBR();
+         } else if (answer == 2) {
+            which = use_gpt;
+         } else which = use_new;
+      } else if (mbrState == invalid) {
+         printf("Found invalid MBR and corrupt GPT. What do you want to do? (Using the\n"
+               "GPT MAY permit recovery of GPT data.)\n");
+         answer = GetNumber(1, 2, 1, (char*) " 1 - GPT\n 2 - Create blank GPT\n\nYour answer: ");
+         if (answer == 1) {
+            which = use_gpt;
+         } else which = use_new;
+      } else { // corrupt GPT, MBR indicates it's a GPT disk....
+         printf("\a\a****************************************************************************\n"
+               "Caution: Found protective or hybrid MBR and corrupt GPT. Using GPT, but disk\n"
+               "verification and recovery are STRONGLY recommended.\n"
+               "****************************************************************************\n");
+      } // if/else/else
+   } // if (corrupt GPT)
+
+   if (which == use_new)
+      printf("Creating new GPT entries.\n");
+
+   return which;
+} // UseWhichPartitions()
+
+// Convert MBR partition table into GPT form
+int GPTData::XFormPartitions(void) {
+   int i, numToConvert;
+   uint8_t origType;
+   struct newGUID;
+   char name[NAME_SIZE];
+
+   // Clear out old data & prepare basics....
+   ClearGPTData();
+
+   // Convert the smaller of the # of GPT or MBR partitions
+   if (mainHeader.numParts > (NUM_LOGICALS + 4))
+      numToConvert = NUM_LOGICALS + 4;
+   else
+      numToConvert = mainHeader.numParts;
+
+   for (i = 0; i < numToConvert; i++) {
+      origType = protectiveMBR.GetType(i);
+      // don't waste CPU time trying to convert extended, hybrid protective, or
+      // null (non-existent) partitions
+      if ((origType != 0x05) && (origType != 0x0f) && (origType != 0x85) && 
+           (origType != 0x00) && (origType != 0xEE))
+         partitions[i] = protectiveMBR.AsGPT(i);
+   } // for
+
+   // Convert MBR into protective MBR
+   protectiveMBR.MakeProtectiveMBR();
+
+   // Record that all original CRCs were OK so as not to raise flags
+   // when doing a disk verification
+   mainCrcOk = secondCrcOk = mainPartsCrcOk = secondPartsCrcOk = 1;
+
+   return (1);
+} // GPTData::XFormPartitions()
+
+// Transforms BSD disklabel on the specified partition (numbered from 0).
+// If an invalid partition number is given, the program prompts for one.
+// Returns the number of new partitions created.
+int GPTData::XFormDisklabel(int i) {
+   uint32_t low, high, partNum, startPart;
+   uint16_t hexCode;
+   int goOn = 1, numDone = 0;
+   BSDData disklabel;
+
+   if (GetPartRange(&low, &high) != 0) {
+      if ((i < low) || (i > high))
+         partNum = GetPartNum();
+      else
+         partNum = (uint32_t) i;
+
+      // Find the partition after the last used one
+      startPart = high + 1;
+
+      // Now see if the specified partition has a BSD type code....
+      hexCode = partitions[partNum].GetHexType();
+      if ((hexCode != 0xa500) && (hexCode != 0xa900)) {
+         printf("Specified partition doesn't have a disklabel partition type "
+               "code.\nContinue anyway?");
+         goOn = (GetYN() == 'Y');
+      } // if
+
+      // If all is OK, read the disklabel and convert it.
+      if (goOn) {
+         goOn = disklabel.ReadBSDData(device, partitions[partNum].GetFirstLBA(),
+                                      partitions[partNum].GetLastLBA());
+         if ((goOn) && (disklabel.IsDisklabel())) {
+            numDone = XFormDisklabel(&disklabel, startPart);
+            if (numDone == 1)
+               printf("Converted %d BSD partition.\n", numDone);
+            else
+               printf("Converted %d BSD partitions.\n", numDone);
+         } else {
+            printf("Unable to convert partitions! Unrecognized BSD disklabel.\n");
+         } // if/else
+      } // if
+      if (numDone > 0) { // converted partitions; delete carrier
+         partitions[partNum].BlankPartition();
+      } // if
+   } else {
+      printf("No partitions\n");
+   } // if/else
+   return numDone;
+} // GPTData::XFormDisklable(int i)
+
+// Transform the partitions on an already-loaded BSD disklabel...
+int GPTData::XFormDisklabel(BSDData* disklabel, int startPart) {
+   int i, numDone = 0;
+
+   if ((disklabel->IsDisklabel()) && (startPart >= 0) &&
+        (startPart < mainHeader.numParts)) {
+      for (i = 0; i < disklabel->GetNumParts(); i++) {
+         partitions[i + startPart] = disklabel->AsGPT(i);
+         if (partitions[i + startPart].GetFirstLBA() != UINT64_C(0))
+            numDone++;
+      } // for
+   } // if
+
+   // Record that all original CRCs were OK so as not to raise flags
+   // when doing a disk verification
+   mainCrcOk = secondCrcOk = mainPartsCrcOk = secondPartsCrcOk = 1;
+
+   return numDone;
+} // GPTData::XFormDisklabel(BSDData* disklabel)
+
+// Create a hybrid MBR -- an ugly, funky thing that helps GPT work with
+// OSes that don't understand GPT.
+void GPTData::MakeHybrid(void) {
+   uint32_t partNums[3];
+   char line[255];
+   int numParts, i, j, typeCode, bootable, mbrNum;
+   uint64_t length;
+   char fillItUp = 'M'; // fill extra partition entries? (Yes/No/Maybe)
+   char eeFirst; // Whether EFI GPT (0xEE) partition comes first in table
+
+   printf("\nWARNING! Hybrid MBRs are flaky and potentially dangerous! If you decide not\n"
+         "to use one, just hit the Enter key at the below prompt and your MBR\n"
+         "partition table will be untouched.\n\n\a");
+
+   // Now get the numbers of up to three partitions to add to the
+   // hybrid MBR....
+   printf("Type from one to three GPT partition numbers, separated by spaces, to be\n"
+         "added to the hybrid MBR, in sequence: ");
+   fgets(line, 255, stdin);
+   numParts = sscanf(line, "%d %d %d", &partNums[0], &partNums[1], &partNums[2]);
+
+   if (numParts > 0) {
+      // Blank out the protective MBR, but leave the boot loader code
+      // alone....
+      protectiveMBR.EmptyMBR(0);
+      protectiveMBR.SetDiskSize(diskSize);
+      printf("Place EFI GPT (0xEE) partition first in MBR (good for GRUB)? ");
+      eeFirst = GetYN();
+   } // if
+
+   for (i = 0; i < numParts; i++) {
+      j = partNums[i] - 1;
+      printf("\nCreating entry for partition #%d\n", j + 1);
+      if ((j >= 0) && (j < mainHeader.numParts)) {
+         if (partitions[j].GetLastLBA() < UINT32_MAX) {
+            do {
+               printf("Enter an MBR hex code (default %02X): ",
+                      typeHelper.GUIDToID(partitions[j].GetType()) / 256);
+               fgets(line, 255, stdin);
+               sscanf(line, "%x", &typeCode);
+               if (line[0] == '\n')
+                  typeCode = partitions[j].GetHexType() / 256;
+            } while ((typeCode <= 0) || (typeCode > 255));
+            printf("Set the bootable flag? ");
+            bootable = (GetYN() == 'Y');
+            length = partitions[j].GetLengthLBA();
+            if (eeFirst == 'Y')
+               mbrNum = i + 1;
+            else
+               mbrNum = i;
+            protectiveMBR.MakePart(mbrNum, (uint32_t) partitions[j].GetFirstLBA(),
+                                   (uint32_t) length, typeCode, bootable);
+            protectiveMBR.SetHybrid();
+         } else { // partition out of range
+            printf("Partition %d ends beyond the 2TiB limit of MBR partitions; omitting it.\n",
+                   j + 1);
+         } // if/else
+      } else {
+         printf("Partition %d is out of range; omitting it.\n", j + 1);
+      } // if/else
+   } // for
+
+   if (numParts > 0) { // User opted to create a hybrid MBR....
+      // Create EFI protective partition that covers the start of the disk.
+      // If this location (covering the main GPT data structures) is omitted,
+      // Linux won't find any partitions on the disk. Note that this is
+      // NUMBERED AFTER the hybrid partitions, contrary to what the
+      // gptsync utility does. This is because Windows seems to choke on
+      // disks with a 0xEE partition in the first slot and subsequent
+      // additional partitions, unless it boots from the disk.
+      if (eeFirst == 'Y')
+         mbrNum = 0;
+      else
+         mbrNum = numParts;
+      protectiveMBR.MakePart(mbrNum, 1, protectiveMBR.FindLastInFree(1), 0xEE);
+
+      // ... and for good measure, if there are any partition spaces left,
+      // optionally create another protective EFI partition to cover as much
+      // space as possible....
+      for (i = 0; i < 4; i++) {
+         if (protectiveMBR.GetType(i) == 0x00) { // unused entry....
+            if (fillItUp == 'M') {
+               printf("\nUnused partition space(s) found. Use one to protect more partitions? ");
+               fillItUp = GetYN();
+               typeCode = 0x00; // use this to flag a need to get type code
+            } // if
+            if (fillItUp == 'Y') {
+               while ((typeCode <= 0) || (typeCode > 255)) {
+                  printf("Enter an MBR hex code (EE is EFI GPT, but may confuse MacOS): ");
+                  // Comment on above: Mac OS treats disks with more than one
+                  // 0xEE MBR partition as MBR disks, not as GPT disks.
+                  fgets(line, 255, stdin);
+                  sscanf(line, "%x", &typeCode);
+                  if (line[0] == '\n')
+                     typeCode = 0;
+               } // while
+               protectiveMBR.MakeBiggestPart(i, typeCode); // make a partition
+            } // if (fillItUp == 'Y')
+         } // if unused entry
+      } // for (i = 0; i < 4; i++)
+   } // if (numParts > 0)
+} // GPTData::MakeHybrid()
+
+/**********************************************************************
+ *                                                                    *
+ * Functions that adjust GPT data structures WITHOUT user interaction *
+ * (they may display information for the user's benefit, though)      *
+ *                                                                    *
+ **********************************************************************/
+
+// Resizes GPT to specified number of entries. Creates a new table if
+// necessary, copies data if it already exists.
+int GPTData::SetGPTSize(uint32_t numEntries) {
+   struct GPTPart* newParts;
+   struct GPTPart* trash;
+   uint32_t i, high, copyNum;
+   int allOK = 1;
+
+   // First, adjust numEntries upward, if necessary, to get a number
+   // that fills the allocated sectors
+   i = blockSize / GPT_SIZE;
+   if ((numEntries % i) != 0) {
+      printf("Adjusting GPT size from %lu ", (unsigned long) numEntries);
+      numEntries = ((numEntries / i) + 1) * i;
+      printf("to %lu to fill the sector\n", (unsigned long) numEntries);
+   } // if
+
+   newParts = (GPTPart*) calloc(numEntries, sizeof (GPTPart));
+   if (newParts != NULL) {
+      if (partitions != NULL) { // existing partitions; copy them over
+         GetPartRange(&i, &high);
+         if (numEntries < (high + 1)) { // Highest entry too high for new #
+            printf("The highest-numbered partition is %lu, which is greater than the requested\n"
+                  "partition table size of %d; cannot resize. Perhaps sorting will help.\n",
+                  (unsigned long) (high + 1), numEntries);
+            allOK = 0;
+         } else { // go ahead with copy
+            if (numEntries < mainHeader.numParts)
+               copyNum = numEntries;
+            else
+               copyNum = mainHeader.numParts;
+            for (i = 0; i < copyNum; i++) {
+               newParts[i] = partitions[i];
+            } // for
+            trash = partitions;
+            partitions = newParts;
+            free(trash);
+         } // if
+      } else { // No existing partition table; just create it
+         partitions = newParts;
+      } // if/else existing partitions
+      mainHeader.numParts = numEntries;
+      secondHeader.numParts = numEntries;
+      mainHeader.firstUsableLBA = ((numEntries * GPT_SIZE) / blockSize) + 2 ;
+      secondHeader.firstUsableLBA = mainHeader.firstUsableLBA;
+      mainHeader.lastUsableLBA = diskSize - mainHeader.firstUsableLBA;
+      secondHeader.lastUsableLBA = mainHeader.lastUsableLBA;
+      secondHeader.partitionEntriesLBA = secondHeader.lastUsableLBA + UINT64_C(1);
+      if (diskSize > 0)
+         CheckGPTSize();
+   } else { // Bad memory allocation
+      fprintf(stderr, "Error allocating memory for partition table!\n");
+      allOK = 0;
+   } // if/else
+   return (allOK);
+} // GPTData::SetGPTSize()
+
+// Blank the partition array
+void GPTData::BlankPartitions(void) {
+   uint32_t i;
+
+   for (i = 0; i < mainHeader.numParts; i++) {
+      partitions[i].BlankPartition();
+   } // for
+} // GPTData::BlankPartitions()
+
+// Sort the GPT entries, eliminating gaps and making for a logical
+// ordering. Relies on QuickSortGPT() for the bulk of the work
+void GPTData::SortGPT(void) {
+   int i, lastPart = 0;
+   GPTPart temp;
+
+   // First, find the last partition with data, so as not to
+   // spend needless time sorting empty entries....
+   for (i = 0; i < mainHeader.numParts; i++) {
+      if (partitions[i].GetFirstLBA() > 0)
+         lastPart = i;
+   } // for
+
+   // Now swap empties with the last partitions, to simplify the logic
+   // in the Quicksort function....
+   i = 0;
+   while (i < lastPart) {
+      if (partitions[i].GetFirstLBA() == 0) {
+         temp = partitions[i];
+         partitions[i] = partitions[lastPart];
+         partitions[lastPart] = temp;
+         lastPart--;
+      } // if
+      i++;
+   } // while
+
+   // Now call the recursive quick sort routine to do the real work....
+   QuickSortGPT(partitions, 0, lastPart);
+} // GPTData::SortGPT()
+
+// Set up data structures for entirely new set of partitions on the
+// specified device. Returns 1 if OK, 0 if there were problems.
+int GPTData::ClearGPTData(void) {
+   int goOn, i;
+
+   // Set up the partition table....
+   free(partitions);
+   partitions = NULL;
+   SetGPTSize(NUM_GPT_ENTRIES);
+
+   // Now initialize a bunch of stuff that's static....
+   mainHeader.signature = GPT_SIGNATURE;
+   mainHeader.revision = 0x00010000;
+   mainHeader.headerSize = (uint32_t) HEADER_SIZE;
+   mainHeader.reserved = 0;
+   mainHeader.currentLBA = UINT64_C(1);
+   mainHeader.partitionEntriesLBA = (uint64_t) 2;
+   mainHeader.sizeOfPartitionEntries = GPT_SIZE;
+   for (i = 0; i < GPT_RESERVED; i++) {
+      mainHeader.reserved2[i] = '\0';
+   } // for
+
+   // Now some semi-static items (computed based on end of disk)
+   mainHeader.backupLBA = diskSize - UINT64_C(1);
+   mainHeader.lastUsableLBA = diskSize - mainHeader.firstUsableLBA;
+
+   // Set a unique GUID for the disk, based on random numbers
+   // rand() is only 32 bits, so multiply together to fill a 64-bit value
+   mainHeader.diskGUID.data1 = (uint64_t) rand() * (uint64_t) rand();
+   mainHeader.diskGUID.data2 = (uint64_t) rand() * (uint64_t) rand();
+
+   // Copy main header to backup header
+   RebuildSecondHeader();
+
+   // Blank out the partitions array....
+   BlankPartitions();
+
+   // Flag all CRCs as being OK....
+   mainCrcOk = 1;
+   secondCrcOk = 1;
+   mainPartsCrcOk = 1;
+   secondPartsCrcOk = 1;
+
+   return (goOn);
+} // GPTData::ClearGPTData()
+
+void GPTData::SetName(uint32_t partNum, char* theName) {
+   if ((partNum >= 0) && (partNum < mainHeader.numParts))
+      if (partitions[partNum].GetFirstLBA() > 0)
+         partitions[partNum].SetName((unsigned char*) theName);
+} // GPTData::SetName
+
+// Set the disk GUID to the specified value. Note that the header CRCs must
+// be recomputed after calling this function.
+void GPTData::SetDiskGUID(GUIDData newGUID) {
+   mainHeader.diskGUID = newGUID;
+   secondHeader.diskGUID = newGUID;
+} // SetDiskGUID()
+
+// Set the unique GUID of the specified partition. Returns 1 on
+// successful completion, 0 if there were problems (invalid
+// partition number).
+int GPTData::SetPartitionGUID(uint32_t pn, GUIDData theGUID) {
+   int retval = 0;
+
+   if (pn < mainHeader.numParts) {
+      if (partitions[pn].GetFirstLBA() != UINT64_C(0)) {
+         partitions[pn].SetUniqueGUID(theGUID);
+         retval = 1;
+      } // if
+   } // if
+   return retval;
+} // GPTData::SetPartitionGUID()
+
+/********************************************************
+ *                                                      *
+ * Functions that return data about GPT data structures *
+ * (most of these are inline in gpt.h)                  *
+ *                                                      *
+ ********************************************************/
+
+// Find the low and high used partition numbers (numbered from 0).
+// Return value is the number of partitions found. Note that the
+// *low and *high values are both set to 0 when no partitions
+// are found, as well as when a single partition in the first
+// position exists. Thus, the return value is the only way to
+// tell when no partitions exist.
+int GPTData::GetPartRange(uint32_t *low, uint32_t *high) {
+   uint32_t i;
+   int numFound = 0;
+
+   *low = mainHeader.numParts + 1; // code for "not found"
+   *high = 0;
+   if (mainHeader.numParts > 0) { // only try if partition table exists...
+      for (i = 0; i < mainHeader.numParts; i++) {
+         if (partitions[i].GetFirstLBA() != UINT64_C(0)) { // it exists
+            *high = i; // since we're counting up, set the high value
+	    // Set the low value only if it's not yet found...
+            if (*low == (mainHeader.numParts + 1)) *low = i;
+            numFound++;
+         } // if
+      } // for
+   } // if
+
+   // Above will leave *low pointing to its "not found" value if no partitions
+   // are defined, so reset to 0 if this is the case....
+   if (*low == (mainHeader.numParts + 1))
+      *low = 0;
+   return numFound;
+} // GPTData::GetPartRange()
+
+/****************************************************
+ *                                                  *
+ * Functions that return data about disk free space *
+ *                                                  *
+ ****************************************************/
+
+// Find the first available block after the starting point; returns 0 if
+// there are no available blocks left
+uint64_t GPTData::FindFirstAvailable(uint64_t start) {
+   uint64_t first;
+   uint32_t i;
+   int firstMoved = 0;
+
+   // Begin from the specified starting point or from the first usable
+   // LBA, whichever is greater...
+   if (start < mainHeader.firstUsableLBA)
+      first = mainHeader.firstUsableLBA;
+   else
+      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 < mainHeader.numParts; i++) {
+         if ((first >= partitions[i].GetFirstLBA()) &&
+              (first <= partitions[i].GetLastLBA())) { // in existing part.
+            first = partitions[i].GetLastLBA() + 1;
+            firstMoved = 1;
+              } // if
+      } // for
+   } while (firstMoved == 1);
+   if (first > mainHeader.lastUsableLBA)
+      first = 0;
+   return (first);
+} // GPTData::FindFirstAvailable()
+
+// Finds the first available sector in the largest block of unallocated
+// space on the disk. Returns 0 if there are no available blocks left
+uint64_t GPTData::FindFirstInLargest(void) {
+   uint64_t start, firstBlock, lastBlock, segmentSize, selectedSize = 0, selectedSegment;
+
+   start = 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);
+   return selectedSegment;
+} // GPTData::FindFirstInLargest()
+
+// Find the last available block on the disk at or after the start
+// block. Returns 0 if there are no available partitions after
+// (or including) start.
+uint64_t GPTData::FindLastAvailable(uint64_t start) {
+   uint64_t last;
+   uint32_t i;
+   int lastMoved = 0;
+
+   // Start by assuming the last usable LBA is available....
+   last = mainHeader.lastUsableLBA;
+
+   // ...now, similar to algorithm in FindFirstAvailable(), search
+   // through all partitions, moving last when it's in an existing
+   // partition. Set the lastMoved flag so we repeat to catch cases
+   // where partitions are out of logical order.
+   do {
+      lastMoved = 0;
+      for (i = 0; i < mainHeader.numParts; i++) {
+         if ((last >= partitions[i].GetFirstLBA()) &&
+              (last <= partitions[i].GetLastLBA())) { // in existing part.
+            last = partitions[i].GetFirstLBA() - 1;
+            lastMoved = 1;
+              } // if
+      } // for
+   } while (lastMoved == 1);
+   if (last < mainHeader.firstUsableLBA)
+      last = 0;
+   return (last);
+} // GPTData::FindLastAvailable()
+
+// Find the last available block in the free space pointed to by start.
+uint64_t GPTData::FindLastInFree(uint64_t start) {
+   uint64_t nearestStart;
+   uint32_t i;
+
+   nearestStart = mainHeader.lastUsableLBA;
+   for (i = 0; i < mainHeader.numParts; i++) {
+      if ((nearestStart > partitions[i].GetFirstLBA()) &&
+           (partitions[i].GetFirstLBA() > start)) {
+         nearestStart = partitions[i].GetFirstLBA() - 1;
+           } // if
+   } // for
+   return (nearestStart);
+} // GPTData::FindLastInFree()
+
+// Finds the total number of free blocks, the number of segments in which
+// they reside, and the size of the largest of those segments
+uint64_t GPTData::FindFreeBlocks(int *numSegments, uint64_t *largestSegment) {
+   uint64_t start = UINT64_C(0); // starting point for each search
+   uint64_t totalFound = UINT64_C(0); // running total
+   uint64_t firstBlock; // first block in a segment
+   uint64_t lastBlock; // last block in a segment
+   uint64_t segmentSize; // size of segment in blocks
+   int num = 0;
+
+   *largestSegment = UINT64_C(0);
+   do {
+      firstBlock = FindFirstAvailable(start);
+      if (firstBlock != UINT64_C(0)) { // something's free...
+         lastBlock = FindLastInFree(firstBlock);
+         segmentSize = lastBlock - firstBlock + UINT64_C(1);
+         if (segmentSize > *largestSegment) {
+            *largestSegment = segmentSize;
+         } // if
+         totalFound += segmentSize;
+         num++;
+         start = lastBlock + 1;
+      } // if
+   } while (firstBlock != 0);
+   *numSegments = num;
+   return totalFound;
+} // GPTData::FindFreeBlocks()
+
+// Returns 1 if sector is unallocated, 0 if it's allocated to a partition
+int GPTData::IsFree(uint64_t sector) {
+   int isFree = 1;
+   uint32_t i;
+
+   for (i = 0; i < mainHeader.numParts; i++) {
+      if ((sector >= partitions[i].GetFirstLBA()) &&
+           (sector <= partitions[i].GetLastLBA())) {
+         isFree = 0;
+           } // if
+   } // for
+   if ((sector < mainHeader.firstUsableLBA) || 
+        (sector > mainHeader.lastUsableLBA)) {
+      isFree = 0;
+        } // if
+        return (isFree);
+} // GPTData::IsFree()
+
+/********************************
+ *                              *
+ * Endianness support functions *
+ *                              *
+ ********************************/
+
 void GPTData::ReverseHeaderBytes(struct GPTHeader* header) {
    ReverseBytes(&header->signature, 8);
    ReverseBytes(&header->revision, 4);
@@ -1854,7 +1979,7 @@
    // mismatch if there's data corruption.
    if ((mainHeader.signature != GPT_SIGNATURE) && (IsLittleEndian() == 0)) {
       printf("GPT signature mismatch in GPTData::ReversePartitionBytes(). This indicates\n"
-             "data corruption or a misplaced call to this function.\n");
+            "data corruption or a misplaced call to this function.\n");
    } // if signature mismatch....
    for (i = 0; i < mainHeader.numParts; i++) {
       partitions[i].ReversePartBytes();
@@ -1909,8 +2034,9 @@
 // Determine endianness; set allOK = 0 if running on big-endian hardware
    if (IsLittleEndian() == 0) {
       fprintf(stderr, "\aRunning on big-endian hardware. Big-endian support is new and poorly"
-              " tested!\nBeware!\n");
+            " tested!\nBeware!\n");
       // allOK = 0;
    } // if
    return (allOK);
 } // SizesOK()
+