summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsrs5694 <srs5694@users.sourceforge.net>2009-08-31 10:13:04 -0400
committersrs5694 <srs5694@users.sourceforge.net>2009-08-31 10:13:04 -0400
commite4ac11ebee0ad586a538f49b9c0e32ad19cae665 (patch)
tree63b482ab258c59aba571032ad095eefa87fc8759
parent221e08768de7fe42ba533ca22baf671420569c07 (diff)
downloadsgdisk-e4ac11ebee0ad586a538f49b9c0e32ad19cae665.tar.gz
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).
-rw-r--r--CHANGELOG41
-rw-r--r--Makefile1
-rw-r--r--bsd.cc34
-rw-r--r--bsd.h38
-rw-r--r--gdisk.891
-rw-r--r--gdisk.cc4
-rw-r--r--gpt.cc2386
-rw-r--r--gpt.h85
-rw-r--r--mbr.cc90
-rw-r--r--mbr.h5
-rw-r--r--support.cc41
-rw-r--r--support.h3
12 files changed, 1574 insertions, 1245 deletions
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 @@ the big-endian support while developing 0.4.0.
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 @@ the big-endian support while developing 0.4.0.
- 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 @@ depend: $(SRCS)
$(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 @@ BSDData::BSDData(void) {
labelFirstLBA = 0;
labelLastLBA = 0;
labelStart = LABEL_OFFSET1; // assume raw disk format
-// deviceFilename[0] = '\0';
partitions = NULL;
} // default constructor
@@ -42,19 +39,23 @@ BSDData::~BSDData(void) {
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 @@ GPTPart BSDData::AsGPT(int i) {
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 @@ GPTPart BSDData::AsGPT(int i) {
(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 @@ using namespace std;
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 @@ class BSDData {
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 @@ for normal file storage in both Windows and Mac OS X.)
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 @@ a default name based on the partition type code.
.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 @@ see a summary of available options.
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 @@ entering data. When only one option is possible,
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, 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 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 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 @@ partition using GNU Parted or a similar tool.
.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 *
+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 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.
+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 main(int argc, char* argv[]) {
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 @@ using namespace std;
* *
****************************************/
+// Default constructor
GPTData::GPTData(void) {
blockSize = SECTOR_SIZE; // set a default
diskSize = 0;
@@ -66,66 +67,139 @@ GPTData::GPTData(char* filename) {
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 @@ int GPTData::CheckGPTSize(void) {
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,31 +254,208 @@ int GPTData::CheckGPTSize(void) {
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()
+// 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
+// signature and revision numbers, not CRCs or other data.
+int GPTData::CheckHeaderValidity(void) {
+ int valid = 3;
-// 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()
+ if (mainHeader.signature != GPT_SIGNATURE) {
+ valid -= 1;
+// printf("Main GPT signature invalid; read 0x%016llX, should be\n0x%016llX\n",
+// (unsigned long long) mainHeader.signature, (unsigned long long) GPT_SIGNATURE);
+ } else if ((mainHeader.revision != 0x00010000) && valid) {
+ valid -= 1;
+ printf("Unsupported GPT version in main header; read 0x%08lX, should be\n0x%08lX\n",
+ (unsigned long) mainHeader.revision, UINT32_C(0x00010000));
+ } // if/else/if
+
+ if (secondHeader.signature != GPT_SIGNATURE) {
+ valid -= 2;
+// printf("Secondary GPT signature invalid; read 0x%016llX, should be\n0x%016llX\n",
+// (unsigned long long) secondHeader.signature, (unsigned long long) GPT_SIGNATURE);
+ } else if ((secondHeader.revision != 0x00010000) && valid) {
+ valid -= 2;
+ printf("Unsupported GPT version in backup header; read 0x%08lX, should be\n0x%08lX\n",
+ (unsigned long) mainHeader.revision, UINT32_C(0x00010000));
+ } // if/else/if
+
+ // If MBR bad, check for an Apple disk signature
+ if ((protectiveMBR.GetValidity() == invalid) &&
+ (((mainHeader.signature << 32) == APM_SIGNATURE1) ||
+ (mainHeader.signature << 32) == APM_SIGNATURE2)) {
+ apmFound = 1; // Will display warning message later
+ } // if
+
+ return valid;
+} // GPTData::CheckHeaderValidity()
+
+// Check the header CRC to see if it's OK...
+// Note: Must be called BEFORE byte-order reversal on big-endian
+// systems!
+int GPTData::CheckHeaderCRC(struct GPTHeader* header) {
+ uint32_t oldCRC, newCRC;
+
+ // Back up old header CRC and then blank it, since it must be 0 for
+ // computation to be valid
+ oldCRC = header->headerCRC;
+ if (IsLittleEndian() == 0)
+ ReverseBytes(&oldCRC, 4);
+ header->headerCRC = UINT32_C(0);
+
+ // Initialize CRC functions...
+ chksum_crc32gentab();
+
+ // Compute CRC, restore original, and return result of comparison
+ newCRC = chksum_crc32((unsigned char*) header, HEADER_SIZE);
+ mainHeader.headerCRC = oldCRC;
+ return (oldCRC == newCRC);
+} // GPTData::CheckHeaderCRC()
+
+// Recompute all the CRCs. Must be called before saving (but after reversing
+// byte order on big-endian systems) if any changes have been made.
+void GPTData::RecomputeCRCs(void) {
+ uint32_t crc;
+ uint32_t trueNumParts, crcTemp;
+ int littleEndian = 1;
+
+ // Initialize CRC functions...
+ chksum_crc32gentab();
+
+ littleEndian = IsLittleEndian();
+
+ // Compute CRC of partition tables & store in main and secondary headers
+ trueNumParts = mainHeader.numParts;
+ if (littleEndian == 0)
+ ReverseBytes(&trueNumParts, 4); // unreverse this key piece of data....
+ crc = chksum_crc32((unsigned char*) partitions, trueNumParts * GPT_SIZE);
+ mainHeader.partitionEntriesCRC = crc;
+ secondHeader.partitionEntriesCRC = crc;
+ if (littleEndian == 0) {
+ ReverseBytes(&mainHeader.partitionEntriesCRC, 4);
+ ReverseBytes(&secondHeader.partitionEntriesCRC, 4);
+ } // if
+
+ // Zero out GPT tables' own CRCs (required for correct computation)
+ mainHeader.headerCRC = 0;
+ secondHeader.headerCRC = 0;
+
+ // Compute & store CRCs of main & secondary headers...
+ crc = chksum_crc32((unsigned char*) &mainHeader, HEADER_SIZE);
+ if (littleEndian == 0)
+ ReverseBytes(&crc, 4);
+ mainHeader.headerCRC = crc;
+ crc = chksum_crc32((unsigned char*) &secondHeader, HEADER_SIZE);
+ if (littleEndian == 0)
+ ReverseBytes(&crc, 4);
+ secondHeader.headerCRC = crc;
+} // GPTData::RecomputeCRCs()
+
+// 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) {
+ int i;
+
+ mainHeader.signature = GPT_SIGNATURE;
+ mainHeader.revision = secondHeader.revision;
+ mainHeader.headerSize = HEADER_SIZE;
+ mainHeader.headerCRC = UINT32_C(0);
+ mainHeader.reserved = secondHeader.reserved;
+ mainHeader.currentLBA = secondHeader.backupLBA;
+ mainHeader.backupLBA = secondHeader.currentLBA;
+ mainHeader.firstUsableLBA = secondHeader.firstUsableLBA;
+ mainHeader.lastUsableLBA = secondHeader.lastUsableLBA;
+ mainHeader.diskGUID.data1 = secondHeader.diskGUID.data1;
+ mainHeader.diskGUID.data2 = secondHeader.diskGUID.data2;
+ mainHeader.partitionEntriesLBA = UINT64_C(2);
+ mainHeader.numParts = secondHeader.numParts;
+ mainHeader.sizeOfPartitionEntries = secondHeader.sizeOfPartitionEntries;
+ mainHeader.partitionEntriesCRC = secondHeader.partitionEntriesCRC;
+ for (i = 0 ; i < GPT_RESERVED; i++)
+ mainHeader.reserved2[i] = secondHeader.reserved2[i];
+} // GPTData::RebuildMainHeader()
+
+// Rebuild the secondary GPT header, using the main header as a model.
+void GPTData::RebuildSecondHeader(void) {
+ int i;
+
+ secondHeader.signature = GPT_SIGNATURE;
+ secondHeader.revision = mainHeader.revision;
+ secondHeader.headerSize = HEADER_SIZE;
+ secondHeader.headerCRC = UINT32_C(0);
+ secondHeader.reserved = mainHeader.reserved;
+ secondHeader.currentLBA = mainHeader.backupLBA;
+ secondHeader.backupLBA = mainHeader.currentLBA;
+ secondHeader.firstUsableLBA = mainHeader.firstUsableLBA;
+ secondHeader.lastUsableLBA = mainHeader.lastUsableLBA;
+ secondHeader.diskGUID.data1 = mainHeader.diskGUID.data1;
+ secondHeader.diskGUID.data2 = mainHeader.diskGUID.data2;
+ secondHeader.partitionEntriesLBA = secondHeader.lastUsableLBA + UINT64_C(1);
+ secondHeader.numParts = mainHeader.numParts;
+ secondHeader.sizeOfPartitionEntries = mainHeader.sizeOfPartitionEntries;
+ secondHeader.partitionEntriesCRC = mainHeader.partitionEntriesCRC;
+ for (i = 0 ; i < GPT_RESERVED; i++)
+ secondHeader.reserved2[i] = mainHeader.reserved2[i];
+} // 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).
@@ -236,11 +487,11 @@ void GPTData::PartitionScan(int fd) {
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 */
+ 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.
@@ -250,6 +501,14 @@ int GPTData::LoadPartitions(char* deviceFilename) {
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);
@@ -279,14 +538,14 @@ int GPTData::LoadPartitions(char* deviceFilename) {
// Now find the first and last sectors used by partitions...
if (allOK) {
firstBlock = mainHeader.backupLBA; // start high
- lastBlock = 0; // start low
+ 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].GetFirstLBA() < firstBlock) &&
+ (partitions[i].GetFirstLBA() > 0))
+ firstBlock = partitions[i].GetFirstLBA();
if (partitions[i].GetLastLBA() > lastBlock)
lastBlock = partitions[i].GetLastLBA();
- } // for
+ } // for
} // if
CheckGPTSize();
} else {
@@ -347,12 +606,12 @@ int GPTData::ForceLoadGPTData(int fd) {
// 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");
+ "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");
+ "backup header from main header.\n");
RebuildSecondHeader();
secondCrcOk = mainCrcOk; // Since regenerated, use CRC validity of main
} // if/else/if
@@ -377,10 +636,10 @@ int GPTData::ForceLoadGPTData(int fd) {
// Check for valid CRCs and warn if there are problems
if ((mainCrcOk == 0) || (secondCrcOk == 0) || (mainPartsCrcOk == 0) ||
- (secondPartsCrcOk == 0)) {
+ (secondPartsCrcOk == 0)) {
printf("Warning! One or more CRCs don't match. You should repair the disk!\n");
- state = gpt_corrupt;
- } // if
+ state = gpt_corrupt;
+ } // if
} else {
state = gpt_invalid;
} // if/else
@@ -412,148 +671,367 @@ int GPTData::LoadMainTable(void) {
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;
+// Load the second (backup) partition table as the primary partition
+// table. Used in repair functions
+void GPTData::LoadSecondTableAsMain(void) {
+ int fd;
+ off_t seekTo;
+ uint32_t sizeOfParts, newCRC;
- mbrState = protectiveMBR.GetValidity();
+ if ((fd = open(device, O_RDONLY)) != -1) {
+ seekTo = secondHeader.partitionEntriesLBA * (off_t) blockSize;
+ if (lseek64(fd, seekTo, SEEK_SET) != (off_t) -1) {
+ SetGPTSize(secondHeader.numParts);
+ sizeOfParts = secondHeader.numParts * secondHeader.sizeOfPartitionEntries;
+ read(fd, partitions, sizeOfParts);
+ newCRC = chksum_crc32((unsigned char*) partitions, sizeOfParts);
+ secondPartsCrcOk = (newCRC == secondHeader.partitionEntriesCRC);
+ mainPartsCrcOk = secondPartsCrcOk;
+ if (IsLittleEndian() == 0)
+ ReversePartitionBytes();
+ if (!secondPartsCrcOk) {
+ printf("Error! After loading backup partitions, the CRC still doesn't check out!\n");
+ } // if
+ } else {
+ printf("Error! Couldn't seek to backup partition table!\n");
+ } // if/else
+ } else {
+ printf("Error! Couldn't open device %s when recovering backup partition table!\n");
+ } // if/else
+} // GPTData::LoadSecondTableAsMain()
- 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;
+// Writes GPT (and protective MBR) to disk. Returns 1 on successful
+// write, 0 if there was a problem.
+int GPTData::SaveGPTData(void) {
+ int allOK = 1, i, j;
+ char answer, line[256];
+ int fd;
+ uint64_t secondTable;
+ uint32_t numParts;
+ off_t offset;
+
+ if (strlen(device) == 0) {
+ printf("Device not defined.\n");
} // 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;
+ // First do some final sanity checks....
+ // Is there enough space to hold the GPT headers and partition tables,
+ // given the partition sizes?
+ if (CheckGPTSize() > 0) {
+ allOK = 0;
} // if
- if ((state == gpt_valid) && (mbrState == gpt)) {
- printf("Found valid GPT with protective MBR; using GPT.\n");
- which = use_gpt;
+ // Check that disk is really big enough to handle this...
+ if (mainHeader.backupLBA > diskSize) {
+ fprintf(stderr, "Error! Disk is too small -- either the original MBR is corrupt or you're\n");
+ fprintf(stderr, "working from an MBR copied to a file! Aborting!\n");
+ printf("(Disk size is %ld sectors, needs to be %ld sectors.)\n", diskSize,
+ mainHeader.backupLBA);
+ allOK = 0;
} // 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;
+
+ // Check for overlapping partitions....
+ if (FindOverlaps() > 0) {
+ allOK = 0;
+ fprintf(stderr, "Aborting write operation!\n");
} // 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();
+
+ // 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....
+ numParts = mainHeader.numParts;
+ secondTable = secondHeader.partitionEntriesLBA;
+ if (IsLittleEndian() == 0) {
+ // Reverse partition bytes first, since that function requires non-reversed
+ // data from the main header....
+ ReversePartitionBytes();
+ ReverseHeaderBytes(&mainHeader);
+ ReverseHeaderBytes(&secondHeader);
} // 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;
+ RecomputeCRCs();
+
+ if (allOK) {
+ printf("\nFinal checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING\n");
+ printf("MBR PARTITIONS!! THIS PROGRAM IS BETA QUALITY AT BEST. IF YOU LOSE ALL YOUR\n");
+ printf("DATA, YOU HAVE ONLY YOURSELF TO BLAME IF YOU ANSWER 'Y' BELOW!\n\n");
+ printf("Do you want to proceed, possibly destroying your data? (Y/N) ");
+ fgets(line, 255, stdin);
+ sscanf(line, "%c", &answer);
+ if ((answer == 'Y') || (answer == 'y')) {
+ printf("OK; writing new GPT partition table.\n");
+ } else {
+ allOK = 0;
+ } // if/else
} // 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)
+ // Do it!
+ if (allOK) {
+ fd = OpenForWrite(device);
+ if (fd != -1) {
+ // First, write the protective MBR...
+ protectiveMBR.WriteMBRData(fd);
- if (which == use_new)
- printf("Creating new GPT entries.\n");
+ // Now write the main GPT header...
+ if (allOK)
+ if (write(fd, &mainHeader, 512) == -1)
+ allOK = 0;
- return which;
-} // UseWhichPartitions()
+ // Now write the main partition tables...
+ if (allOK) {
+ if (write(fd, partitions, GPT_SIZE * numParts) == -1)
+ allOK = 0;
+ } // if
-void GPTData::ResizePartitionTable(void) {
- int newSize;
- char prompt[255];
- uint32_t curLow, curHigh;
+ // Now seek to near the end to write the secondary GPT....
+ if (allOK) {
+ offset = (off_t) secondTable * (off_t) (blockSize);
+ if (lseek64(fd, offset, SEEK_SET) == (off_t) - 1) {
+ allOK = 0;
+ printf("Unable to seek to end of disk!\n");
+ } // if
+ } // if
- 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");
+ // Now write the secondary partition tables....
+ if (allOK)
+ if (write(fd, partitions, GPT_SIZE * numParts) == -1)
+ allOK = 0;
+
+ // Now write the secondary GPT header...
+ if (allOK)
+ if (write(fd, &secondHeader, 512) == -1)
+ 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");
+ /* 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);
+#else
+#ifdef __FreeBSD__
+ sleep(2);
+ i = ioctl(fd, DIOCGFLUSH);
+ printf("Warning: The kernel may still provide disk access using old partition IDs.\n");
+#else
+ 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");
+#endif
+#endif
+ } // if
+
+ if (allOK) { // writes completed OK
+ 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");
+ } // if/else
+ close(fd);
+ } else {
+ 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");
} // 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;
+ if (IsLittleEndian() == 0) {
+ // Reverse (normalize) header bytes first, since ReversePartitionBytes()
+ // requires non-reversed data in mainHeader...
+ ReverseHeaderBytes(&mainHeader);
+ ReverseHeaderBytes(&secondHeader);
+ ReversePartitionBytes();
+ } // if
- *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++;
+ return (allOK);
+} // GPTData::SaveGPTData()
+
+// Save GPT data to a backup file. This function does much less error
+// checking than SaveGPTData(). It can therefore preserve many types of
+// corruption for later analysis; however, it preserves only the MBR,
+// the main GPT header, the backup GPT header, and the main partition
+// table; it discards the backup partition table, since it should be
+// identical to the main partition table on healthy disks.
+int GPTData::SaveGPTBackup(char* filename) {
+ int fd, allOK = 1;
+ uint32_t numParts;
+
+ if ((fd = open(filename, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH)) != -1) {
+ // Reverse the byte order, if necessary....
+ numParts = mainHeader.numParts;
+ if (IsLittleEndian() == 0) {
+ ReversePartitionBytes();
+ ReverseHeaderBytes(&mainHeader);
+ ReverseHeaderBytes(&secondHeader);
+ } // if
+
+ // Now write the protective MBR...
+ protectiveMBR.WriteMBRData(fd);
+
+ // Now write the main GPT header...
+ if (allOK)
+ if (write(fd, &mainHeader, 512) == -1)
+ allOK = 0;
+
+ // Now write the secondary GPT header...
+ if (allOK)
+ if (write(fd, &secondHeader, 512) == -1)
+ allOK = 0;
+
+ // Now write the main partition tables...
+ if (allOK) {
+ if (write(fd, partitions, GPT_SIZE * numParts) == -1)
+ allOK = 0;
+ } // if
+
+ if (allOK) { // writes completed OK
+ printf("The operation has completed successfully.\n");
+ } else {
+ printf("Warning! An error was reported when writing the backup file.\n");
+ printf("It may not be useable!\n");
+ } // if/else
+ close(fd);
+
+ // Now reverse the byte-order reversal, if necessary....
+ if (IsLittleEndian() == 0) {
+ ReverseHeaderBytes(&mainHeader);
+ ReverseHeaderBytes(&secondHeader);
+ ReversePartitionBytes();
+ } // if
+ } else {
+ fprintf(stderr, "Unable to open file %s for writing! Aborting!\n", filename);
+ allOK = 0;
+ } // if/else
+ return allOK;
+} // GPTData::SaveGPTBackup()
+
+// Load GPT data from a backup file created by SaveGPTBackup(). This function
+// does minimal error checking. It returns 1 if it completed successfully,
+// 0 if there was a problem. In the latter case, it creates a new empty
+// set of partitions.
+int GPTData::LoadGPTBackup(char* filename) {
+ int fd, allOK = 1, val;
+ uint32_t numParts, sizeOfEntries, sizeOfParts, newCRC;
+ int littleEndian = 1;
+
+ if ((fd = open(filename, O_RDONLY)) != -1) {
+ if (IsLittleEndian() == 0)
+ littleEndian = 0;
+
+ // Let the MBRData class load the saved MBR...
+ protectiveMBR.ReadMBRData(fd, 0); // 0 = don't check block size
+
+ // Load the main GPT header, check its vaility, and set the GPT
+ // size based on the data
+ read(fd, &mainHeader, 512);
+ mainCrcOk = CheckHeaderCRC(&mainHeader);
+
+ // Reverse byte order, if necessary
+ if (littleEndian == 0) {
+ ReverseHeaderBytes(&mainHeader);
+ } // if
+
+ // Load the backup GPT header in much the same way as the main
+ // GPT header....
+ read(fd, &secondHeader, 512);
+ secondCrcOk = CheckHeaderCRC(&secondHeader);
+
+ // Reverse byte order, if necessary
+ if (littleEndian == 0) {
+ ReverseHeaderBytes(&secondHeader);
+ } // if
+
+ // 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!
+ if ((val = CheckHeaderValidity()) > 0) {
+ if (val == 2) { // only backup header seems to be good
+ numParts = secondHeader.numParts;
+ sizeOfEntries = secondHeader.sizeOfPartitionEntries;
+ } else { // main header is OK
+ numParts = mainHeader.numParts;
+ sizeOfEntries = mainHeader.sizeOfPartitionEntries;
+ } // if/else
+
+ SetGPTSize(numParts);
+
+ // 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");
+ secondHeader.currentLBA = mainHeader.backupLBA = diskSize - UINT64_C(1);
+ mainHeader.lastUsableLBA = diskSize - mainHeader.firstUsableLBA;
+ secondHeader.lastUsableLBA = mainHeader.lastUsableLBA;
+ secondHeader.partitionEntriesLBA = secondHeader.lastUsableLBA + UINT64_C(1);
} // if
- } // for
+
+ // Load main partition table, and record whether its CRC
+ // matches the stored value
+ sizeOfParts = numParts * sizeOfEntries;
+ read(fd, partitions, sizeOfParts);
+
+ newCRC = chksum_crc32((unsigned char*) partitions, sizeOfParts);
+ mainPartsCrcOk = (newCRC == mainHeader.partitionEntriesCRC);
+ secondPartsCrcOk = (newCRC == secondHeader.partitionEntriesCRC);
+ // Reverse byte order, if necessary
+ if (littleEndian == 0) {
+ ReversePartitionBytes();
+ } // if
+
+ } else {
+ allOK = 0;
+ } // if/else
+ } else {
+ allOK = 0;
+ fprintf(stderr, "Unable to open file %s for reading! Aborting!\n", filename);
+ } // if/else
+
+ // Something went badly wrong, so blank out partitions
+ if (allOK == 0) {
+ ClearGPTData();
+ protectiveMBR.MakeProtectiveMBR();
} // if
+ return allOK;
+} // GPTData::LoadGPTBackup()
- // 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()
+// 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) {
@@ -569,7 +1047,7 @@ void GPTData::DisplayGPTData(void) {
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);
+ (unsigned long) mainHeader.lastUsableLBA);
totalFree = FindFreeBlocks(&i, &temp);
printf("Total free space is %llu sectors (%s)\n", totalFree,
BytesToSI(totalFree * (uint64_t) blockSize, sizeInSI));
@@ -602,9 +1080,54 @@ void GPTData::ShowPartDetails(uint32_t partNum) {
} // 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, lastBlock, sector;
+ uint64_t firstBlock, firstInLargest, lastBlock, sector;
char prompt[255];
int partNum, firstFreePart = 0;
@@ -614,8 +1137,9 @@ void GPTData::CreatePartition(void) {
} // while
if (((firstBlock = FindFirstAvailable()) != 0) &&
- (firstFreePart < mainHeader.numParts)) {
+ (firstFreePart < mainHeader.numParts)) {
lastBlock = FindLastAvailable(firstBlock);
+ firstInLargest = FindFirstInLargest();
// Get partition number....
do {
@@ -623,24 +1147,26 @@ void GPTData::CreatePartition(void) {
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);
+ 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);
+ sprintf(prompt,
+ "First sector (%llu-%llu, default = %llu) or {+-}size{KMGT}: ",
+ firstBlock, lastBlock, firstInLargest);
do {
- sector = GetNumber(firstBlock, lastBlock, firstBlock, prompt);
+ 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 or +size or +sizeM or +sizeK (%llu-%llu, default = %d): ",
+ sprintf(prompt,
+ "Last sector (%llu-%llu, default = %llu) or {+-}size{KMGT}: ",
firstBlock, lastBlock, lastBlock);
do {
- sector = GetLastSector(firstBlock, lastBlock, prompt);
+ sector = GetSectorNum(firstBlock, lastBlock, lastBlock, prompt);
} while (IsFree(sector) == 0);
lastBlock = sector;
@@ -650,123 +1176,202 @@ void GPTData::CreatePartition(void) {
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
+ } 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()
-// 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;
+// 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;
- // ...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()
+ if (GetPartRange(&low, &high) > 0) {
+ partNum = GetPartNum();
+ partitions[partNum].ChangeType();
+ } else {
+ printf("No partitions\n");
+ } // if/else
+} // GPTData::ChangePartType()
-// 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;
+// 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;
- // Start by assuming the last usable LBA is available....
- last = mainHeader.lastUsableLBA;
+ theAttr.SetAttributes(partitions[partNum].GetAttributes());
+ theAttr.DisplayAttributes();
+ theAttr.ChangeAttributes();
+ partitions[partNum].SetAttributes(theAttr.GetAttributes());
+} // GPTData::SetAttributes()
- // ...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()
+// This function destroys the on-disk GPT structures. Returns 1 if the
+// user confirms destruction, 0 if the user aborts.
+int GPTData::DestroyGPT(void) {
+ int fd, i, doMore;
+ char blankSector[512], goOn;
-// 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;
+ for (i = 0; i < 512; i++) {
+ blankSector[i] = '\0';
+ } // for
- nearestStart = mainHeader.lastUsableLBA;
- for (i = 0; i < mainHeader.numParts; i++) {
- if ((nearestStart > partitions[i].GetFirstLBA()) &&
- (partitions[i].GetFirstLBA() > start)) {
- nearestStart = partitions[i].GetFirstLBA() - 1;
+ printf("\a\aAbout to wipe out GPT on %s. Proceed? ", device);
+ goOn = GetYN();
+ if (goOn == 'Y') {
+ fd = open(device, O_WRONLY);
+#ifdef __APPLE__
+ // MacOS X requires a shared lock under some circumstances....
+ if (fd < 0) {
+ fd = open(device, O_WRONLY|O_SHLOCK);
} // if
- } // for
- return (nearestStart);
-} // GPTData::FindLastInFree()
+#endif
+ if (fd != -1) {
+ lseek64(fd, mainHeader.currentLBA * 512, SEEK_SET); // seek to GPT header
+ write(fd, blankSector, 512); // blank it out
+ lseek64(fd, mainHeader.partitionEntriesLBA * 512, SEEK_SET); // seek to partition table
+ for (i = 0; i < GetBlocksInPartTable(); i++)
+ write(fd, blankSector, 512);
+ lseek64(fd, secondHeader.partitionEntriesLBA * 512, SEEK_SET); // seek to partition table
+ for (i = 0; i < GetBlocksInPartTable(); i++)
+ 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') {
+ 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");
+ } else {
+ printf("Problem opening %s for writing! Program will now terminate.\n");
+ } // if/else (fd != -1)
+ } // if (goOn == 'Y')
+ return (goOn == 'Y');
+} // GPTData::DestroyGPT()
-// 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;
+/**************************************************************************
+ * *
+ * Partition table transformation functions (MBR or BSD disklabel to GPT) *
+ * (some of these functions may require user interaction) *
+ * *
+ **************************************************************************/
- 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;
+// 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
- return (isFree);
-} // GPTData::IsFree()
+ 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;
@@ -787,7 +1392,7 @@ int GPTData::XFormPartitions(void) {
// 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))
+ (origType != 0x00) && (origType != 0xEE))
partitions[i] = protectiveMBR.AsGPT(i);
} // for
@@ -801,7 +1406,7 @@ int GPTData::XFormPartitions(void) {
return (1);
} // GPTData::XFormPartitions()
-// Transforms BSD disklable on the specified partition (numbered from 0).
+// 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) {
@@ -823,7 +1428,7 @@ int GPTData::XFormDisklabel(int i) {
hexCode = partitions[partNum].GetHexType();
if ((hexCode != 0xa500) && (hexCode != 0xa900)) {
printf("Specified partition doesn't have a disklabel partition type "
- "code.\nContinue anyway?");
+ "code.\nContinue anyway?");
goOn = (GetYN() == 'Y');
} // if
@@ -855,7 +1460,7 @@ int GPTData::XFormDisklabel(BSDData* disklabel, int startPart) {
int i, numDone = 0;
if ((disklabel->IsDisklabel()) && (startPart >= 0) &&
- (startPart < mainHeader.numParts)) {
+ (startPart < mainHeader.numParts)) {
for (i = 0; i < disklabel->GetNumParts(); i++) {
partitions[i + startPart] = disklabel->AsGPT(i);
if (partitions[i + startPart].GetFirstLBA() != UINT64_C(0))
@@ -870,6 +1475,182 @@ int GPTData::XFormDisklabel(BSDData* disklabel, int startPart) {
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) {
@@ -889,9 +1670,9 @@ void GPTData::SortGPT(void) {
while (i < lastPart) {
if (partitions[i].GetFirstLBA() == 0) {
temp = partitions[i];
- partitions[i] = partitions[lastPart];
- partitions[lastPart] = temp;
- lastPart--;
+ partitions[i] = partitions[lastPart];
+ partitions[lastPart] = temp;
+ lastPart--;
} // if
i++;
} // while
@@ -900,15 +1681,6 @@ void GPTData::SortGPT(void) {
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) {
@@ -955,42 +1727,6 @@ int GPTData::ClearGPTData(void) {
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)
@@ -1012,318 +1748,155 @@ int GPTData::SetPartitionGUID(uint32_t pn, GUIDData theGUID) {
if (pn < mainHeader.numParts) {
if (partitions[pn].GetFirstLBA() != UINT64_C(0)) {
- partitions[pn].SetUniqueGUID(theGUID);
+ 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
-// signature and revision numbers, not CRCs or other data.
-int GPTData::CheckHeaderValidity(void) {
- int valid = 3;
-
- if (mainHeader.signature != GPT_SIGNATURE) {
- valid -= 1;
-// printf("Main GPT signature invalid; read 0x%016llX, should be\n0x%016llX\n",
-// (unsigned long long) mainHeader.signature, (unsigned long long) GPT_SIGNATURE);
- } else if ((mainHeader.revision != 0x00010000) && valid) {
- valid -= 1;
- printf("Unsupported GPT version in main header; read 0x%08lX, should be\n0x%08lX\n",
- (unsigned long) mainHeader.revision, UINT32_C(0x00010000));
- } // if/else/if
-
- if (secondHeader.signature != GPT_SIGNATURE) {
- valid -= 2;
-// printf("Secondary GPT signature invalid; read 0x%016llX, should be\n0x%016llX\n",
-// (unsigned long long) secondHeader.signature, (unsigned long long) GPT_SIGNATURE);
- } else if ((secondHeader.revision != 0x00010000) && valid) {
- valid -= 2;
- printf("Unsupported GPT version in backup header; read 0x%08lX, should be\n0x%08lX\n",
- (unsigned long) mainHeader.revision, UINT32_C(0x00010000));
- } // if/else/if
-
- // If MBR bad, check for an Apple disk signature
- if ((protectiveMBR.GetValidity() == invalid) &&
- (((mainHeader.signature << 32) == APM_SIGNATURE1) ||
- (mainHeader.signature << 32) == APM_SIGNATURE2)) {
- apmFound = 1; // Will display warning message later
- } // if
-
- return valid;
-} // GPTData::CheckHeaderValidity()
-
-// Check the header CRC to see if it's OK...
-// Note: Must be called BEFORE byte-order reversal on big-endian
-// systems!
-int GPTData::CheckHeaderCRC(struct GPTHeader* header) {
- uint32_t oldCRC, newCRC;
-
- // Back up old header CRC and then blank it, since it must be 0 for
- // computation to be valid
- oldCRC = header->headerCRC;
- if (IsLittleEndian() == 0)
- ReverseBytes(&oldCRC, 4);
- header->headerCRC = UINT32_C(0);
-
- // Initialize CRC functions...
- chksum_crc32gentab();
-
- // Compute CRC, restore original, and return result of comparison
- newCRC = chksum_crc32((unsigned char*) header, HEADER_SIZE);
- mainHeader.headerCRC = oldCRC;
- return (oldCRC == newCRC);
-} // GPTData::CheckHeaderCRC()
-
-// Recompute all the CRCs. Must be called before saving (but after reversing
-// byte order on big-endian systems) if any changes have been made.
-void GPTData::RecomputeCRCs(void) {
- uint32_t crc;
- uint32_t trueNumParts, crcTemp;
- int littleEndian = 1;
-
- // Initialize CRC functions...
- chksum_crc32gentab();
-
- littleEndian = IsLittleEndian();
-
- // Compute CRC of partition tables & store in main and secondary headers
- trueNumParts = mainHeader.numParts;
- if (littleEndian == 0)
- ReverseBytes(&trueNumParts, 4); // unreverse this key piece of data....
- crc = chksum_crc32((unsigned char*) partitions, trueNumParts * GPT_SIZE);
- mainHeader.partitionEntriesCRC = crc;
- secondHeader.partitionEntriesCRC = crc;
- if (littleEndian == 0) {
- ReverseBytes(&mainHeader.partitionEntriesCRC, 4);
- ReverseBytes(&secondHeader.partitionEntriesCRC, 4);
- } // if
-
- // Zero out GPT tables' own CRCs (required for correct computation)
- mainHeader.headerCRC = 0;
- secondHeader.headerCRC = 0;
+/********************************************************
+ * *
+ * Functions that return data about GPT data structures *
+ * (most of these are inline in gpt.h) *
+ * *
+ ********************************************************/
- // Compute & store CRCs of main & secondary headers...
- crc = chksum_crc32((unsigned char*) &mainHeader, HEADER_SIZE);
- if (littleEndian == 0)
- ReverseBytes(&crc, 4);
- mainHeader.headerCRC = crc;
- crc = chksum_crc32((unsigned char*) &secondHeader, HEADER_SIZE);
- if (littleEndian == 0)
- ReverseBytes(&crc, 4);
- 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];
+// 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;
- // 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");
+ *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
- // 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
+ // 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()
- // 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
+/****************************************************
+ * *
+ * Functions that return data about disk free space *
+ * *
+ ****************************************************/
- // 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...
+// 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;
- // Verify that partitions don't run into GPT data areas....
- problems += CheckGPTSize();
+ // 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 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
+ // ...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()
- return (problems);
-} // GPTData::Verify()
+// 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;
-// 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) {
- int i;
+ 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()
- mainHeader.signature = GPT_SIGNATURE;
- mainHeader.revision = secondHeader.revision;
- mainHeader.headerSize = HEADER_SIZE;
- mainHeader.headerCRC = UINT32_C(0);
- mainHeader.reserved = secondHeader.reserved;
- mainHeader.currentLBA = secondHeader.backupLBA;
- mainHeader.backupLBA = secondHeader.currentLBA;
- mainHeader.firstUsableLBA = secondHeader.firstUsableLBA;
- mainHeader.lastUsableLBA = secondHeader.lastUsableLBA;
- mainHeader.diskGUID.data1 = secondHeader.diskGUID.data1;
- mainHeader.diskGUID.data2 = secondHeader.diskGUID.data2;
- mainHeader.partitionEntriesLBA = UINT64_C(2);
- mainHeader.numParts = secondHeader.numParts;
- mainHeader.sizeOfPartitionEntries = secondHeader.sizeOfPartitionEntries;
- mainHeader.partitionEntriesCRC = secondHeader.partitionEntriesCRC;
- for (i = 0 ; i < GPT_RESERVED; i++)
- mainHeader.reserved2[i] = secondHeader.reserved2[i];
-} // GPTData::RebuildMainHeader()
+// 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;
-// Rebuild the secondary GPT header, using the main header as a model.
-void GPTData::RebuildSecondHeader(void) {
- int i;
+ // Start by assuming the last usable LBA is available....
+ last = mainHeader.lastUsableLBA;
- secondHeader.signature = GPT_SIGNATURE;
- secondHeader.revision = mainHeader.revision;
- secondHeader.headerSize = HEADER_SIZE;
- secondHeader.headerCRC = UINT32_C(0);
- secondHeader.reserved = mainHeader.reserved;
- secondHeader.currentLBA = mainHeader.backupLBA;
- secondHeader.backupLBA = mainHeader.currentLBA;
- secondHeader.firstUsableLBA = mainHeader.firstUsableLBA;
- secondHeader.lastUsableLBA = mainHeader.lastUsableLBA;
- secondHeader.diskGUID.data1 = mainHeader.diskGUID.data1;
- secondHeader.diskGUID.data2 = mainHeader.diskGUID.data2;
- secondHeader.partitionEntriesLBA = secondHeader.lastUsableLBA + UINT64_C(1);
- secondHeader.numParts = mainHeader.numParts;
- secondHeader.sizeOfPartitionEntries = mainHeader.sizeOfPartitionEntries;
- secondHeader.partitionEntriesCRC = mainHeader.partitionEntriesCRC;
- for (i = 0 ; i < GPT_RESERVED; i++)
- secondHeader.reserved2[i] = mainHeader.reserved2[i];
-} // RebuildSecondHeader()
+ // ...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()
-// Load the second (backup) partition table as the primary partition
-// table. Used in repair functions
-void GPTData::LoadSecondTableAsMain(void) {
- int fd;
- off_t seekTo;
- uint32_t sizeOfParts, newCRC;
+// 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;
- if ((fd = open(device, O_RDONLY)) != -1) {
- seekTo = secondHeader.partitionEntriesLBA * (off_t) blockSize;
- if (lseek64(fd, seekTo, SEEK_SET) != (off_t) -1) {
- SetGPTSize(secondHeader.numParts);
- sizeOfParts = secondHeader.numParts * secondHeader.sizeOfPartitionEntries;
- read(fd, partitions, sizeOfParts);
- newCRC = chksum_crc32((unsigned char*) partitions, sizeOfParts);
- secondPartsCrcOk = (newCRC == secondHeader.partitionEntriesCRC);
- mainPartsCrcOk = secondPartsCrcOk;
- if (IsLittleEndian() == 0)
- ReversePartitionBytes();
- if (!secondPartsCrcOk) {
- printf("Error! After loading backup partitions, the CRC still doesn't check out!\n");
- } // if
- } else {
- printf("Error! Couldn't seek to backup partition table!\n");
- } // if/else
- } else {
- printf("Error! Couldn't open device %s when recovering backup partition table!\n");
- } // if/else
-} // GPTData::LoadSecondTableAsMain()
+ 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
@@ -1341,489 +1914,41 @@ uint64_t GPTData::FindFreeBlocks(int *numSegments, uint64_t *largestSegment) {
if (firstBlock != UINT64_C(0)) { // something's free...
lastBlock = FindLastInFree(firstBlock);
segmentSize = lastBlock - firstBlock + UINT64_C(1);
- if (segmentSize > *largestSegment) {
+ if (segmentSize > *largestSegment) {
*largestSegment = segmentSize;
- } // if
+ } // if
totalFound += segmentSize;
- num++;
- start = lastBlock + 1;
+ 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) {
- int allOK = 1, i, j;
- char answer, line[256];
- int fd;
- uint64_t secondTable;
- uint32_t numParts;
- off_t offset;
-
- if (strlen(device) == 0) {
- printf("Device not defined.\n");
- } // if
-
- // First do some final sanity checks....
- // Is there enough space to hold the GPT headers and partition tables,
- // given the partition sizes?
- if (CheckGPTSize() > 0) {
- allOK = 0;
- } // if
-
- // Check that disk is really big enough to handle this...
- if (mainHeader.backupLBA > diskSize) {
- fprintf(stderr, "Error! Disk is too small -- either the original MBR is corrupt or you're\n");
- fprintf(stderr, "working from an MBR copied to a file! Aborting!\n");
- printf("(Disk size is %ld sectors, needs to be %ld sectors.)\n", diskSize,
- mainHeader.backupLBA);
- allOK = 0;
- } // 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...
-
- // Pull out some data that's needed before doing byte-order reversal on
- // big-endian systems....
- numParts = mainHeader.numParts;
- secondTable = secondHeader.partitionEntriesLBA;
- if (IsLittleEndian() == 0) {
- // Reverse partition bytes first, since that function requires non-reversed
- // data from the main header....
- ReversePartitionBytes();
- ReverseHeaderBytes(&mainHeader);
- ReverseHeaderBytes(&secondHeader);
- } // if
- RecomputeCRCs();
-
- if (allOK) {
- printf("\nFinal checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING\n");
- printf("MBR PARTITIONS!! THIS PROGRAM IS BETA QUALITY AT BEST. IF YOU LOSE ALL YOUR\n");
- printf("DATA, YOU HAVE ONLY YOURSELF TO BLAME IF YOU ANSWER 'Y' BELOW!\n\n");
- printf("Do you want to proceed, possibly destroying your data? (Y/N) ");
- fgets(line, 255, stdin);
- sscanf(line, "%c", &answer);
- if ((answer == 'Y') || (answer == 'y')) {
- printf("OK; writing new GPT partition table.\n");
- } else {
- allOK = 0;
- } // if/else
- } // if
-
- // 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
- if (fd != -1) {
- // First, write the protective MBR...
- protectiveMBR.WriteMBRData(fd);
-
- // Now write the main GPT header...
- if (allOK)
- if (write(fd, &mainHeader, 512) == -1)
- allOK = 0;
-
- // Now write the main partition tables...
- 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) {
- offset = (off_t) secondTable * (off_t) (blockSize);
- if (lseek64(fd, offset, SEEK_SET) == (off_t) - 1) {
- allOK = 0;
- printf("Unable to seek to end of disk!\n");
- } // if
- } // if
-
- // Now write the secondary partition tables....
- if (allOK)
- if (write(fd, partitions, GPT_SIZE * numParts) == -1)
- allOK = 0;
-
- // Now write the secondary GPT header...
- if (allOK)
- if (write(fd, &secondHeader, 512) == -1)
- 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");
- /* 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);
-#else
-#ifdef __FreeBSD__
- sleep(2);
- i = ioctl(fd, DIOCGFLUSH);
- printf("Warning: The kernel is still using the old partition table.\n");
-#else
- 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");
-#endif
-#endif
- } // if
-
- if (allOK) { // writes completed OK
- 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");
- } // if/else
- close(fd);
- } else {
- fprintf(stderr, "Unable to open device %s for writing! Errno is %d! Aborting!\n", device, errno);
- allOK = 0;
- } // if/else
- } else {
- printf("Aborting write of new partition table.\n");
- } // if
-
- if (IsLittleEndian() == 0) {
- // Reverse (normalize) header bytes first, since ReversePartitionBytes()
- // requires non-reversed data in mainHeader...
- ReverseHeaderBytes(&mainHeader);
- ReverseHeaderBytes(&secondHeader);
- ReversePartitionBytes();
- } // if
-
- return (allOK);
-} // GPTData::SaveGPTData()
-
-// Save GPT data to a backup file. This function does much less error
-// checking than SaveGPTData(). It can therefore preserve many types of
-// corruption for later analysis; however, it preserves only the MBR,
-// the main GPT header, the backup GPT header, and the main partition
-// table; it discards the backup partition table, since it should be
-// identical to the main partition table on healthy disks.
-int GPTData::SaveGPTBackup(char* filename) {
- int fd, allOK = 1;
- uint32_t numParts;
-
- if ((fd = open(filename, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH)) != -1) {
- // Reverse the byte order, if necessary....
- numParts = mainHeader.numParts;
- if (IsLittleEndian() == 0) {
- ReversePartitionBytes();
- ReverseHeaderBytes(&mainHeader);
- ReverseHeaderBytes(&secondHeader);
- } // if
-
- // Now write the protective MBR...
- protectiveMBR.WriteMBRData(fd);
-
- // Now write the main GPT header...
- if (allOK)
- if (write(fd, &mainHeader, 512) == -1)
- allOK = 0;
-
- // Now write the secondary GPT header...
- if (allOK)
- if (write(fd, &secondHeader, 512) == -1)
- allOK = 0;
-
- // Now write the main partition tables...
- if (allOK) {
- if (write(fd, partitions, GPT_SIZE * numParts) == -1)
- allOK = 0;
- } // if
-
- if (allOK) { // writes completed OK
- printf("The operation has completed successfully.\n");
- } else {
- printf("Warning! An error was reported when writing the backup file.\n");
- printf("It may not be useable!\n");
- } // if/else
- close(fd);
-
- // Now reverse the byte-order reversal, if necessary....
- if (IsLittleEndian() == 0) {
- ReverseHeaderBytes(&mainHeader);
- ReverseHeaderBytes(&secondHeader);
- ReversePartitionBytes();
- } // if
- } else {
- fprintf(stderr, "Unable to open file %s for writing! Aborting!\n", filename);
- allOK = 0;
- } // if/else
- return allOK;
-} // GPTData::SaveGPTBackup()
-
-// Load GPT data from a backup file created by SaveGPTBackup(). This function
-// does minimal error checking. It returns 1 if it completed successfully,
-// 0 if there was a problem. In the latter case, it creates a new empty
-// set of partitions.
-int GPTData::LoadGPTBackup(char* filename) {
- int fd, allOK = 1, val;
- uint32_t numParts, sizeOfEntries, sizeOfParts, newCRC;
- int littleEndian = 1;
-
- if ((fd = open(filename, O_RDONLY)) != -1) {
- if (IsLittleEndian() == 0)
- littleEndian = 0;
-
- // Let the MBRData class load the saved MBR...
- protectiveMBR.ReadMBRData(fd, 0); // 0 = don't check block size
-
- // Load the main GPT header, check its vaility, and set the GPT
- // size based on the data
- read(fd, &mainHeader, 512);
- mainCrcOk = CheckHeaderCRC(&mainHeader);
-
- // Reverse byte order, if necessary
- if (littleEndian == 0) {
- ReverseHeaderBytes(&mainHeader);
- } // if
-
- // Load the backup GPT header in much the same way as the main
- // GPT header....
- read(fd, &secondHeader, 512);
- secondCrcOk = CheckHeaderCRC(&secondHeader);
-
- // Reverse byte order, if necessary
- if (littleEndian == 0) {
- ReverseHeaderBytes(&secondHeader);
- } // if
-
- // 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!
- if ((val = CheckHeaderValidity()) > 0) {
- if (val == 2) { // only backup header seems to be good
- numParts = secondHeader.numParts;
- sizeOfEntries = secondHeader.sizeOfPartitionEntries;
- } else { // main header is OK
- numParts = mainHeader.numParts;
- sizeOfEntries = mainHeader.sizeOfPartitionEntries;
- } // if/else
-
- SetGPTSize(numParts);
-
- // 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");
- secondHeader.currentLBA = mainHeader.backupLBA = diskSize - UINT64_C(1);
- mainHeader.lastUsableLBA = diskSize - mainHeader.firstUsableLBA;
- secondHeader.lastUsableLBA = mainHeader.lastUsableLBA;
- secondHeader.partitionEntriesLBA = secondHeader.lastUsableLBA + UINT64_C(1);
- } // if
-
- // Load main partition table, and record whether its CRC
- // matches the stored value
- sizeOfParts = numParts * sizeOfEntries;
- read(fd, partitions, sizeOfParts);
-
- newCRC = chksum_crc32((unsigned char*) partitions, sizeOfParts);
- mainPartsCrcOk = (newCRC == mainHeader.partitionEntriesCRC);
- secondPartsCrcOk = (newCRC == secondHeader.partitionEntriesCRC);
- // Reverse byte order, if necessary
- if (littleEndian == 0) {
- ReversePartitionBytes();
- } // if
-
- } else {
- allOK = 0;
- } // if/else
- } else {
- allOK = 0;
- fprintf(stderr, "Unable to open file %s for reading! Aborting!\n", filename);
- } // if/else
-
- // Something went badly wrong, so blank out partitions
- if (allOK == 0) {
- ClearGPTData();
- protectiveMBR.MakeProtectiveMBR();
- } // if
- return allOK;
-} // GPTData::LoadGPTBackup()
-
-// This function destroys the on-disk GPT structures. Returns 1 if the
-// user confirms destruction, 0 if the user aborts.
-int GPTData::DestroyGPT(void) {
- int fd, i, doMore;
- char blankSector[512], goOn;
+// 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 < 512; i++) {
- blankSector[i] = '\0';
+ 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()
- printf("\a\aAbout to wipe out GPT on %s. Proceed? ", device);
- goOn = GetYN();
- if (goOn == 'Y') {
- fd = open(device, O_WRONLY);
-#ifdef __APPLE__
- // MacOS X requires a shared lock under some circumstances....
- if (fd < 0) {
- fd = open(device, O_WRONLY|O_SHLOCK);
- } // if
-#endif
- if (fd != -1) {
- lseek64(fd, mainHeader.currentLBA * 512, SEEK_SET); // seek to GPT header
- write(fd, blankSector, 512); // blank it out
- lseek64(fd, mainHeader.partitionEntriesLBA * 512, SEEK_SET); // seek to partition table
- for (i = 0; i < GetBlocksInPartTable(); i++)
- write(fd, blankSector, 512);
- lseek64(fd, secondHeader.partitionEntriesLBA * 512, SEEK_SET); // seek to partition table
- for (i = 0; i < GetBlocksInPartTable(); i++)
- 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') {
- 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");
- } else {
- printf("Problem opening %s for writing! Program will now terminate.\n");
- } // if/else (fd != -1)
- } // if (goOn == 'Y')
- return (goOn == 'Y');
-} // GPTData::DestroyGPT()
+/********************************
+ * *
+ * Endianness support functions *
+ * *
+ ********************************/
void GPTData::ReverseHeaderBytes(struct GPTHeader* header) {
ReverseBytes(&header->signature, 8);
@@ -1854,7 +1979,7 @@ void GPTData::ReversePartitionBytes() {
// 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 @@ int SizesOK(void) {
// 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 @@ protected:
// 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 @@ public:
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 @@ int MBRData::MakeBiggestPart(int i, int type) {
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 @@ uint32_t MBRData::FindFirstAvailable(uint32_t start) {
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 @@ uint32_t MBRData::FindLastInFree(uint32_t start) {
} // 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 @@ public:
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 @@ char GetYN(void) {
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 @@ uint64_t GetLastSector(uint64_t low, uint64_t high, char prompt[]) {
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 @@ uint64_t GetLastSector(uint64_t low, uint64_t high, char prompt[]) {
// 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 @@ uint64_t PowerOf2(int value) {
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 @@ struct GUIDData {
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 @@ GUIDData GetGUID(void);
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);