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/CHANGELOG b/CHANGELOG
index 420291b..8f6f69a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,38 @@
+0.4.1:
+------
+
+- Code cleanup/re-organization
+
+- Partition creation function ('n' on main menu) now uses the start of the
+  largest available chunk of free space rather than the first available
+  sector as the default starting sector number. This should enable easier
+  partition creation if there are small bits of free space on the disk.
+
+- You can now specify the end point of a partition by using a minus sign,
+  in which case the end point is the default value minus the specified
+  size. For instance, "-200M" creates a partition that ends 200MiB before
+  the default end point.
+
+- You can now specify the start point of a partition by using a plus or
+  minus sign, in which case the start point is the specified distance from
+  the start (+) or end (-) of free space. This is exactly the same as the
+  new rules for entry of the end point, except that the default value is
+  set differently.
+
+- Deleting a partition now checks for a matching hybrid MBR partition, and
+  if one is found, it's deleted. Any empty space that then surrounds the
+  0xEE (EFI GPT) MBR partitions is then added to the nearby 0xEE partition.
+  If no non-0xEE partitions are left, a fresh protective MBR is generated.
+
+- Added hybrid MBR consistency check to the verify ('v') option and to
+  pre-write checks. If non-0xEE/non-0x00 MBR partitions without
+  corresponding GPT partitions are found, the user is warned. This finding
+  does NOT prevent writing the partition table, though.
+
+- Added non-destructive write test when opening the device file, in order
+  to detect the problem with FreeBSD being unable to write to disks with
+  mounted partitions (or other potential problems).
+
 0.4.0:
 ------
 
@@ -92,7 +127,8 @@
 0.3.1:
 ------
 
-- Added Mac OS X support, provided as a patch by an anonymous contributor.
+- Added Mac OS X support, provided as a patch by David Hubbard
+  (david.c.hubbard@gmail.com).
 
 - Fixed bug in disksize() function on Mac OS. (Possibly dependent on the
   kernel and/or GCC version.) The disk size, of type uint64_t, was not
@@ -105,7 +141,8 @@
 
 - Fixed bug that caused display of options after a disk-write error.
 
-- Fixed several incorrect MacOS X partition type GUIDs.
+- Fixed several incorrect MacOS X partition type GUIDs, thanks to Yves
+  Blusseau (1otnwmz02@sneakemail.com).
 
 0.3.0:
 ------
diff --git a/Makefile b/Makefile
index eb17c78..a6eec4f 100644
--- a/Makefile
+++ b/Makefile
@@ -31,4 +31,3 @@
 $(OBJS):	
 
 # DO NOT DELETE
-
diff --git a/bsd.cc b/bsd.cc
index b46ea7a..4bc28f4 100644
--- a/bsd.cc
+++ b/bsd.cc
@@ -1,7 +1,7 @@
-/* bsd.cc -- Functions for loading, saving, and manipulating legacy BSD disklabel
+/* bsd.cc -- Functions for loading and manipulating legacy BSD disklabel
    data. */
 
-/* By Rod Smith, August, 2009 */
+/* By Rod Smith, initial coding August, 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. */
@@ -15,10 +15,8 @@
 #include <stdint.h>
 #include <fcntl.h>
 #include <string.h>
-//#include <time.h>
 #include <sys/stat.h>
 #include <errno.h>
-#include "crc32.h"
 #include "support.h"
 #include "bsd.h"
 
@@ -34,7 +32,6 @@
    labelFirstLBA = 0;
    labelLastLBA = 0;
    labelStart = LABEL_OFFSET1; // assume raw disk format
-//   deviceFilename[0] = '\0';
    partitions = NULL;
 } // default constructor
 
@@ -42,19 +39,23 @@
    free(partitions);
 } // destructor
 
+// Read BSD disklabel data from the specified device filename. This function
+// just opens the device file and then calls an overloaded function to do
+// the bulk of the work.
 int BSDData::ReadBSDData(char* device, uint64_t startSector, uint64_t endSector) {
    int fd, allOK = 1;
 
-   if ((fd = open(device, O_RDONLY)) != -1) {
-      ReadBSDData(fd, startSector, endSector);
+   if (device != NULL) {
+      if ((fd = open(device, O_RDONLY)) != -1) {
+         ReadBSDData(fd, startSector, endSector);
+      } else {
+         allOK = 0;
+      } // if/else
+
+      close(fd);
    } else {
       allOK = 0;
-   } // if
-
-   close(fd);
-
-//   if (allOK)
-//      strcpy(deviceFilename, device);
+   } // if/else
 
    return allOK;
 } // BSDData::ReadBSDData() (device filename version)
@@ -250,8 +251,9 @@
    sectorEnd = sectorOne + (uint64_t) partitions[i].lengthLBA;
    if (sectorEnd > 0) sectorEnd--;
    // Note on above: BSD partitions sometimes have a length of 0 and a start
-   // sector of 0. With unsigned ints, the usual (start + length - 1) to
-   // find the end will result in a huge number, which will be confusing
+   // sector of 0. With unsigned ints, the usual way (start + length - 1) to
+   // find the end will result in a huge number, which will be confusing.
+   // Thus, apply the "-1" part only if it's reasonable to do so.
 
    // Do a few sanity checks on the partition before we pass it on....
    // First, check that it falls within the bounds of its container
@@ -264,7 +266,7 @@
        (GetType(i) == 0))
       passItOn = 0;
    // If the end point is 0, it's not a valid partition.
-   if (sectorEnd == 0)
+   if ((sectorEnd == 0) || (sectorEnd == labelFirstLBA))
       passItOn = 0;
 
    if (passItOn) {
diff --git a/bsd.h b/bsd.h
index b35d921..c92cfb0 100644
--- a/bsd.h
+++ b/bsd.h
@@ -11,7 +11,7 @@
 #ifndef __BSD_STRUCTS
 #define __BSD_STRUCTS
 
-#define BSD_SIGNATURE UINT32_C(0x82564557)
+#define BSD_SIGNATURE UINT32_C(0x82564557)  /* BSD disklabel signature ("magic") */
 
 #define LABEL_OFFSET1 64   /* BSD disklabels can start at one of these two */
 #define LABEL_OFFSET2 512  /* values; check both for valid signatures */
@@ -36,33 +36,32 @@
 enum BSDValidity {unknown, bsd_invalid, bsd};
 
 // Data for a single BSD partition record
-struct  BSDRecord {     // the partition table
-   uint32_t  lengthLBA;     // number of sectors in partition
-   uint32_t  firstLBA;   // starting sector
-   uint32_t  fragSize;    // filesystem basic fragment size
-   uint8_t  fsType;    // filesystem type, see below
-   uint8_t  frag;      // filesystem fragments per block
-   uint16_t pcpg;  /* filesystem cylinders per group */ // was u_uint16_t
+// Create entries for all fields, although we only use lengthLBA, firstLBA,
+// and fsType, to simplify loading the data from disk....
+struct  BSDRecord {      // the partition table
+   uint32_t lengthLBA;   // number of sectors in partition
+   uint32_t firstLBA;    // starting sector
+   uint32_t fragSize;    // filesystem basic fragment size
+   uint8_t  fsType;      // filesystem type, see below
+   uint8_t  frag;        // filesystem fragments per block
+   uint16_t pcpg;        // filesystem cylinders per group
 };
 
 // Full data in tweaked MBR format
 class BSDData {
    protected:
       // We only need a few items from the main BSD disklabel data structure....
-      uint32_t  signature;             // the magic number
-      uint32_t  sectorSize;      // # of bytes per sector
-      uint32_t  signature2;         // the magic number (again)
-      uint16_t numParts;            // number of partitions in table
-      BSDRecord* partitions;        // partition array
+      uint32_t signature;        // the magic number
+      uint32_t sectorSize;       // # of bytes per sector
+      uint32_t signature2;       // the magic number (again)
+      uint16_t numParts;         // number of partitions in table
+      BSDRecord* partitions;     // partition array
 
       // Above are basic BSD disklabel data; now add more stuff....
-//      uint64_t offset; // starting point in blocks
-      uint64_t labelStart; // BSD disklabel start point in bytes from firstLBA
-      uint64_t labelFirstLBA; // first sector of BSD disklabel (partition or disk)
-      uint64_t labelLastLBA; // final sector of BSD disklabel
-//      char deviceFilename[256];
+      uint64_t labelFirstLBA;    // first sector of BSD disklabel (partition or disk)
+      uint64_t labelLastLBA;     // final sector of BSD disklabel
+      uint64_t labelStart;       // BSD disklabel start point in bytes from labelFirstLBA
       BSDValidity state;
-//      struct BSDRecord* GetPartition(int i); // Return BSD partition
    public:
       BSDData(void);
       ~BSDData(void);
@@ -70,7 +69,6 @@
       void ReadBSDData(int fd, uint64_t startSector, uint64_t endSector);
       void ReverseMetaBytes(void);
       void DisplayBSDData(void);
-//      int ConvertBSDParts(struct GPTPartition gptParts[]);
       int ShowState(void); // returns 1 if BSD disklabel detected
       int IsDisklabel(void) {return (state == bsd);}
 
diff --git a/gdisk.8 b/gdisk.8
index 714d300..9cdfdc8 100644
--- a/gdisk.8
+++ b/gdisk.8
@@ -1,6 +1,6 @@
 .\" Copyright 2009 Roderick W. Smith (rodsmith@rodsbooks.com)
 .\" May be distributed under the GNU General Public License
-.TH GDISK 8 "August 2009" "0.4.0" "GPT fdisk Manual"
+.TH GDISK 8 "August 2009" "0.4.1" "GPT fdisk Manual"
 .SH NAME
 gdisk \- GPT partition table manipulator for Linux and Unix
 .SH SYNOPSIS
@@ -258,7 +258,9 @@
 Some OSes' GPT utilities create some blank space (typically 128 MiB) after
 each partition. The intent is to enable future disk utilities to use this
 space. Such free space is not required of GPT disks, but creating it may
-help in future disk maintenance.
+help in future disk maintenance. You can use GPT fdisk's relative partition
+positioning option (specifying the starting sector as '+128M', for
+instance) to simplify creating such gaps.
 
 .SH OPTIONS
 .TP
@@ -296,7 +298,10 @@
 .B d
 Delete a partition. This action deletes the entry from the partition table
 but does not disturb the data within the sectors originally allocated to
-the partition on the disk.
+the partition on the disk. If a corresponding hybrid MBR partition exists,
+.B gdisk
+deletes it, as well, and expands any adjacent 0xEE (EFI GPT) MBR protective
+partition to fill the new free space.
 
 .TP
 .B i
@@ -339,9 +344,21 @@
 Create a new partition. This command is modelled after the equivalent
 .B fdisk
 option, although some differences exist. You enter a partition number,
-starting sector, and either an ending sector or increment (in integral
-multiples of sectors, kilobytes, megabytes, gigabytes, or terabytes). You
-must also set a partition type code.
+starting sector, and an ending sector. Both start and end sectors can be
+specified in absolute terms as sector numbers or as positions measured in
+kilobytes (K), megabytes (M), gigabytes (G), or terabytes (T); for
+instance,
+.BI 40M
+specifies a position 40MiB from the start of the disk. You can specify
+locations relative to the start or end of the specified range by preceding
+the number by a '+' or '-' symbol, as in
+.BI +2G
+to specify a point 2GiB after the first available sector, or
+.BI -200M
+to specify a point 200MiB before the last available sector. Pressing the
+Enter key with no input specifies the default value, which is the start of
+the largest available block for the start sector and the last available
+block for the end sector.
 
 .TP
 .B o
@@ -545,17 +562,29 @@
 usually bypasses the prompt entirely.
 
 .SH BUGS
-As of August of 2009 (version 0.3.5),
+As of August of 2009 (version 0.4.1),
 .B gdisk
-should be considered early beta software. Known bugs and
-limitations include:
+should be considered beta software. Known bugs and limitations include:
 
 .TP
 .B *
-The program compiles correctly only on Linux and Mac OS X. Both 64-bit
-(x86-64) and 32-bit (x86) versions for Linux have been tested, the former
-more thoroughly than the latter. The Mac OS X support was added with
-version 0.3.1 and has not been as thoroughly tested.
+The program compiles correctly only on Linux, FreeBSD, and Mac OS X. Both
+64-bit (x86-64) and 32-bit (x86) versions for Linux have been tested, the
+former more thoroughly than the latter. The Mac OS X support was added with
+version 0.3.1 and has not been as thoroughly tested. FreeBSD support was
+added with version 0.4.0 and has not been very thoroughly tested.
+
+.TP
+.B *
+The FreeBSD version of the program can't write changes to the partition
+table to a disk when existing partitions on that disk are mounted. (The
+same problem exists with many other FreeBSD utilities, such as
+.B "gpt"
+,
+.B "fdisk"
+, and
+.B "dd".
+
 
 .TP
 .B *
@@ -593,32 +622,34 @@
 
 .TP
 .B *
-Converting from MBR supports only one extended partition. If multiple
-extended partitions are found, only the final extended partition's logical
-partitions are guaranteed to be converted intact; some or all of the
-earlier extended partition(s) logical partitions will be lost.
-
-.TP
-.B *
 MBR conversions work only if the disk has correct LBA partition
 descriptors. These descriptors should be present on any disk over 8 GiB in
 size or on smaller disks partitioned with any but very ancient software.
 
 .TP
 .B *
-If an MBR disk contains a FreeBSD disklabel partition, it's converted
-in-place as such rather than splitting out its constituent disklabel
-partitions into GPT partitions. Other OSes' disklabel partitions may not
-get appropriate GUID type codes at all.
+BSD disklabel support can create first and/or last partitions that overlap
+with the GPT data structures. This can sometimes be compensated by
+adjusting the partition table size, but in extreme cases the affected
+partition(s) may need to be deleted.
 
 .TP
 .B *
-Booting after converting an MBR disk may be disrupted. Sometimes
-re-installing a boot loader will fix the problem, but other times you may
-need to switch boot loaders. Except on EFI-based platforms, Windows through
-at least Windows 7 RC doesn't support booting from GPT disks. Creating a
-hybrid MBR (using the 'h' option on the experts' menu) or abandoning GPT in
-favor of MBR may be your only options in this case.
+Because of the highly variable nature of BSD disklabel structures,
+conversions from this form may be unreliable -- partitions may be dropped,
+converted in a way that creates overlaps with other partitions, or
+converted with incorrect start or end values. Use this feature with
+caution!
+
+.TP
+.B *
+Booting after converting an MBR or BSD disklabel disk is likely to be
+disrupted. Sometimes re-installing a boot loader will fix the problem, but
+other times you may need to switch boot loaders. Except on EFI-based
+platforms, Windows through at least Windows 7 RC doesn't support booting
+from GPT disks. Creating a hybrid MBR (using the 'h' option on the experts'
+menu) or abandoning GPT in favor of MBR may be your only options in this
+case.
 
 .PP
 
diff --git a/gdisk.cc b/gdisk.cc
index 5baf9fe..ad2f51b 100644
--- a/gdisk.cc
+++ b/gdisk.cc
@@ -2,7 +2,7 @@
 // Program modelled after Linux fdisk, but it manipulates GPT partitions
 // rather than MBR partitions.
 //
-// by Rod Smith, February 2009
+// by Rod Smith, project began 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. */
@@ -27,7 +27,7 @@
    int doMore = 1;
    char* device = NULL;
 
-   printf("GPT fdisk (gdisk) version 0.4.0\n\n");
+   printf("GPT fdisk (gdisk) version 0.4.1\n\n");
 
     if (argc == 2) { // basic usage
       if (SizesOK()) {
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()
+
diff --git a/gpt.h b/gpt.h
index 2f405d5..758f2fb 100644
--- a/gpt.h
+++ b/gpt.h
@@ -70,62 +70,68 @@
 //   uint32_t units; // display units, in multiples of sectors
    PartTypes typeHelper;
 public:
+   // Basic necessary functions....
    GPTData(void);
    GPTData(char* deviceFilename);
    ~GPTData(void);
-   int SetGPTSize(uint32_t numEntries);
+
+   // Verify (or update) data integrity
+   int Verify(void);
    int CheckGPTSize(void);
-   void ShowAPMState(void);
-   void ShowGPTState(void);
+   int CheckHeaderValidity(void);
+   int CheckHeaderCRC(struct GPTHeader* header);
+   void RecomputeCRCs(void);
+   void RebuildMainHeader(void);
+   void RebuildSecondHeader(void);
+   int FindHybridMismatches(void);
+   int FindOverlaps(void);
+
+   // Load or save data from/to disk
    void PartitionScan(int fd);
    int LoadPartitions(char* deviceFilename);
    int ForceLoadGPTData(int fd);
    int LoadMainTable(void);
-   WhichToUse UseWhichPartitions(void);
-   void ResizePartitionTable(void);
-   int GetPartRange(uint32_t* low, uint32_t* high);
+   void LoadSecondTableAsMain(void);
+   int SaveGPTData(void);
+   int SaveGPTBackup(char* filename);
+   int LoadGPTBackup(char* filename);
+
+   // Display data....
+   void ShowAPMState(void);
+   void ShowGPTState(void);
    void DisplayGPTData(void);
    void DisplayMBRData(void) {protectiveMBR.DisplayMBRData();}
    void ShowDetails(void);
    void ShowPartDetails(uint32_t partNum);
+
+   // Request information from the user (& possibly do something with it)
+   uint32_t GetPartNum(void);
+   void ResizePartitionTable(void);
    void CreatePartition(void);
    void DeletePartition(void);
-   void BlankPartitions(void);
-   uint64_t FindFirstAvailable(uint64_t start = 0);
-   uint64_t FindLastAvailable(uint64_t start);
-   uint64_t FindLastInFree(uint64_t start);
-   int IsFree(uint64_t sector);
+   void ChangePartType(void);
+   void SetAttributes(uint32_t partNum);
+   int DestroyGPT(void); // Returns 1 if user proceeds
+
+   // Convert to GPT from other formats (may require user interaction)
+   WhichToUse UseWhichPartitions(void);
    int XFormPartitions(void);
    int XFormDisklabel(int OnGptPart = -1);
    int XFormDisklabel(BSDData* disklabel, int startPart);
+   void MakeHybrid(void);
+
+   // Adjust GPT structures WITHOUT user interaction...
+   int SetGPTSize(uint32_t numEntries);
+   void BlankPartitions(void);
    void SortGPT(void);
    int ClearGPTData(void);
-   void ChangePartType(void);
-   uint32_t GetPartNum(void);
-   void SetAttributes(uint32_t partNum);
    void SetName(uint32_t partNum, char* theName = NULL);
    void SetDiskGUID(GUIDData newGUID);
    int SetPartitionGUID(uint32_t pn, GUIDData theGUID);
-   int CheckHeaderValidity(void);
-   int CheckHeaderCRC(struct GPTHeader* header);
-   void RecomputeCRCs(void);
-   int Verify(void);
-   void RebuildMainHeader(void);
-   void RebuildSecondHeader(void);
-   void LoadSecondTableAsMain(void);
-   uint64_t FindFreeBlocks(int *numSegments, uint64_t *largestSegment);
-   void MakeHybrid(void);
-   void MakeProtectiveMBR(void);
-   int SaveGPTData(void);
-   int SaveGPTBackup(char* filename);
-   int LoadGPTBackup(char* filename);
-   int DestroyGPT(void); // Returns 1 if user proceeds
-
-   // Endianness functions
-   void ReverseHeaderBytes(struct GPTHeader* header); // for endianness
-   void ReversePartitionBytes(); // for endianness
+   void MakeProtectiveMBR(void) {protectiveMBR.MakeProtectiveMBR();}
 
    // Return data about the GPT structures....
+   int GetPartRange(uint32_t* low, uint32_t* high);
    uint32_t GetNumParts(void) {return mainHeader.numParts;}
    uint64_t GetMainHeaderLBA(void) {return mainHeader.currentLBA;}
    uint64_t GetSecondHeaderLBA(void) {return secondHeader.currentLBA;}
@@ -133,12 +139,21 @@
    uint64_t GetSecondPartsLBA(void) {return secondHeader.partitionEntriesLBA;}
    uint64_t GetBlocksInPartTable(void) {return (mainHeader.numParts *
                    mainHeader.sizeOfPartitionEntries) / blockSize;}
+
+   // Find information about free space
+   uint64_t FindFirstAvailable(uint64_t start = 0);
+   uint64_t FindFirstInLargest(void);
+   uint64_t FindLastAvailable(uint64_t start);
+   uint64_t FindLastInFree(uint64_t start);
+   uint64_t FindFreeBlocks(int *numSegments, uint64_t *largestSegment);
+   int IsFree(uint64_t sector);
+
+   // Endianness functions
+   void ReverseHeaderBytes(struct GPTHeader* header); // for endianness
+   void ReversePartitionBytes(); // for endianness
 }; // class GPTData
 
 // Function prototypes....
-void BlankPartition(struct GPTPartition* partition);
-int TheyOverlap(struct GPTPartition* first, struct GPTPartition* second);
-void ChangeGPTType(struct GPTPartition* part);
 int SizesOK(void);
 
 #endif
diff --git a/mbr.cc b/mbr.cc
index b4bc6f4..28745b3 100644
--- a/mbr.cc
+++ b/mbr.cc
@@ -496,6 +496,61 @@
    return found;
 } // MBRData::MakeBiggestPart(int i)
 
+// Delete a partition if one exists at the specified location.
+// Returns 1 if a partition was deleted, 0 otherwise....
+// Used to help keep GPT & hybrid MBR partitions in sync....
+int MBRData::DeleteByLocation(uint64_t start64, uint64_t length64) {
+   uint32_t start32, length32;
+   int i, j, deleted = 0;
+
+   if ((state == hybrid) && (start64 < UINT32_MAX) && (length64 < UINT32_MAX)) {
+      start32 = (uint32_t) start64;
+      length32 = (uint32_t) length64;
+      for (i = 0; i < 4; i++) {
+         if ((partitions[i].firstLBA == start32) && (partitions[i].lengthLBA = length32) &&
+             (partitions[i].partitionType != 0xEE)) {
+            partitions[i].firstLBA = UINT32_C(0);
+            partitions[i].lengthLBA = UINT32_C(0);
+            partitions[i].status = UINT8_C(0);
+            partitions[i].partitionType = UINT8_C(0);
+            for (j = 0; j < 3; j++) {
+               partitions[i].firstSector[j] = UINT8_C(0);
+               partitions[i].lastSector[j] = UINT8_C(0);
+            } // for j (CHS data blanking)
+            OptimizeEESize();
+            deleted = 1;
+         } // if (match found)
+      } // for i (partition scan)
+   } // if (hybrid & GPT partition < 2TiB)
+   return deleted;
+} // MBRData::DeleteByLocation()
+
+// Optimizes the size of the 0xEE (EFI GPT) partition
+void MBRData::OptimizeEESize(void) {
+   int i, typeFlag = 0;
+   uint32_t after;
+
+   for (i = 0; i < 4; i++) {
+      // Check for non-empty and non-0xEE partitions
+      if ((partitions[i].partitionType != 0xEE) && (partitions[i].partitionType != 0x00))
+         typeFlag++;
+      if (partitions[i].partitionType == 0xEE) {
+         // Blank space before this partition; fill it....
+         if (IsFree(partitions[i].firstLBA - 1)) {
+            partitions[i].firstLBA = FindFirstInFree(partitions[i].firstLBA - 1);
+         } // if
+         // Blank space after this partition; fill it....
+         after = partitions[i].firstLBA + partitions[i].lengthLBA;
+         if (IsFree(after)) {
+            partitions[i].lengthLBA = FindLastInFree(after) - partitions[i].firstLBA + 1;
+         } // if free space after
+      } // if partition is 0xEE
+      if (typeFlag == 0) { // No non-hybrid partitions found
+         MakeProtectiveMBR(); // ensure it's a fully compliant hybrid MBR.
+      } // if
+   } // for partition loop
+} // MBRData::OptimizeEESize()
+
 // Return a pointer to a primary or logical partition, or NULL if
 // the partition is out of range....
 struct MBRRecord* MBRData::GetPartition(int i) {
@@ -582,6 +637,7 @@
    return (first);
 } // MBRData::FindFirstAvailable()
 
+// Finds the last free sector on the disk from start forward.
 uint32_t MBRData::FindLastInFree(uint32_t start) {
    uint32_t nearestStart;
    uint32_t i;
@@ -597,7 +653,39 @@
       } // if
    } // for
    return (nearestStart);
-} // MBRData::FindLastInFree
+} // MBRData::FindLastInFree()
+
+// Finds the first free sector on the disk from start backward.
+uint32_t MBRData::FindFirstInFree(uint32_t start) {
+   uint32_t nearestStart, bestLastLBA, thisLastLBA;
+   int i;
+
+   bestLastLBA = 1;
+   for (i = 0; i < 4; i++) {
+      thisLastLBA = partitions[i].firstLBA + partitions[i].lengthLBA;
+      if (thisLastLBA > 0) thisLastLBA--;
+      if ((thisLastLBA > bestLastLBA) && (thisLastLBA < start)) {
+         bestLastLBA = thisLastLBA + 1;
+      } // if
+   } // for
+   return (bestLastLBA);
+} // MBRData::FindFirstInFree()
+
+// Returns 1 if the specified sector is unallocated, 0 if it's
+// allocated.
+int MBRData::IsFree(uint32_t sector) {
+   int i, isFree = 1;
+   uint32_t first, last;
+
+   for (i = 0; i < 4; i++) {
+      first = partitions[i].firstLBA;
+      last = first + partitions[i].lengthLBA;
+      if (last > 0) last--;
+      if ((first <= sector) && (last >= sector))
+         isFree = 0;
+   } // for
+   return isFree;
+} // MBRData::IsFree()
 
 uint8_t MBRData::GetStatus(int i) {
    MBRRecord* thePart;
diff --git a/mbr.h b/mbr.h
index 45159c1..3d1136b 100644
--- a/mbr.h
+++ b/mbr.h
@@ -105,10 +105,15 @@
    void MakePart(int num, uint32_t startLBA, uint32_t lengthLBA, int type = 0x07,
                  int bootable = 0);
    int MakeBiggestPart(int i, int type); // Make partition filling most space
+   int DeleteByLocation(uint64_t start64, uint64_t length64);
+   void OptimizeEESize(void);
+   void SetHybrid(void) {state = hybrid;} // Set hybrid flag
 
    // Functions to find information on free space....
    uint32_t FindFirstAvailable(uint32_t start = 1);
    uint32_t FindLastInFree(uint32_t start);
+   uint32_t FindFirstInFree(uint32_t start);
+   int IsFree(uint32_t sector);
 
    // Functions to extract data on specific partitions....
    uint8_t GetStatus(int i);
diff --git a/support.cc b/support.cc
index 7ebec9a..798aff8 100644
--- a/support.cc
+++ b/support.cc
@@ -14,6 +14,7 @@
 #include <string.h>
 #include <stdint.h>
 #include <errno.h>
+#include <fcntl.h>
 #include "support.h"
 
 #include <sys/types.h>
@@ -64,12 +65,14 @@
    return response;
 } // GetYN(void)
 
-// Obtains the final sector number, between low and high, from the
+// Obtains a sector number, between low and high, from the
 // user, accepting values prefixed by "+" to add sectors to low,
 // or the same with "K", "M", "G", or "T" as suffixes to add
 // kilobytes, megabytes, gigabytes, or terabytes, respectively.
-// Use the high value as the default if the user just hits Enter
-uint64_t GetLastSector(uint64_t low, uint64_t high, char prompt[]) {
+// If a "-" prefix is used, use the high value minus the user-
+// specified number of sectors (or KiB, MiB, etc.). Use the def
+ //value as the default if the user just hits Enter
+uint64_t GetSectorNum(uint64_t low, uint64_t high, uint64_t def, char prompt[]) {
    unsigned long long response;
    int num;
    int plusFlag = 0;
@@ -92,12 +95,18 @@
          strcpy(line, &line[1]);
       } // if
 
+      // If present, flag and remove leading minus sign
+      if (line[0] == '-') {
+         plusFlag = -1;
+         strcpy(line, &line[1]);
+      } // if
+
       // Extract numeric response and, if present, suffix
       num = sscanf(line, "%llu%c", &response, &suffix);
 
-      // If no response, use default: The high value
+      // If no response, use default (def)
       if (num <= 0) {
-         response = (unsigned long long) high;
+         response = (unsigned long long) def;
 	 suffix = ' ';
          plusFlag = 0;
       } // if
@@ -127,11 +136,14 @@
       // Adjust response based on multiplier and plus flag, if present
       response *= (unsigned long long) mult;
       if (plusFlag == 1) {
-         response = response + (unsigned long long) low - 1;
+         response = response + (unsigned long long) low - UINT64_C(1);
+      } // if
+      if (plusFlag == -1) {
+         response = (unsigned long long) high - response;
       } // if
    } // while
    return ((uint64_t) response);
-} // GetLastSector()
+} // GetSectorNum()
 
 // Takes a size in bytes (in size) and converts this to a size in
 // SI units (KiB, MiB, GiB, TiB, or PiB), returned in C++ string
@@ -354,6 +366,21 @@
    return retval;
 } // PowerOf2()
 
+// An extended file-open function. This includes some system-specific checks.
+// I want them in a function because I use these calls twice and I don't want
+// to forget to change them in one location if I need to change them in
+// the other....
+int OpenForWrite(char* deviceFilename) {
+   int fd;
+
+   fd = open(deviceFilename, 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(deviceFilename, O_WRONLY|O_SHLOCK);
+   } // if
+#endif
+} // MyOpen()
 
 /**************************************************************************************
  *                                                                                    *
diff --git a/support.h b/support.h
index d812d3c..60fcb8c 100644
--- a/support.h
+++ b/support.h
@@ -53,7 +53,7 @@
 
 int GetNumber(int low, int high, int def, const char prompt[]);
 char GetYN(void);
-uint64_t GetLastSector(uint64_t low, uint64_t high, char prompt[]);
+uint64_t GetSectorNum(uint64_t low, uint64_t high, uint64_t def, char prompt[]);
 char* BytesToSI(uint64_t size, char theValue[]);
 int GetBlockSize(int fd);
 char* GUIDToStr(struct GUIDData theGUID, char* theString);
@@ -61,6 +61,7 @@
 int IsLittleEndian(void); // Returns 1 if CPU is little-endian, 0 if it's big-endian
 void ReverseBytes(void* theValue, int numBytes); // Reverses byte-order of theValue
 uint64_t PowerOf2(int value);
+int OpenForWrite(char* deviceFilename);
 
 uint64_t disksize(int fd, int* err);