aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'gptsync/gptsync.c')
-rw-r--r--gptsync/gptsync.c470
1 files changed, 470 insertions, 0 deletions
diff --git a/gptsync/gptsync.c b/gptsync/gptsync.c
new file mode 100644
index 0000000..3ad26bf
--- /dev/null
+++ b/gptsync/gptsync.c
@@ -0,0 +1,470 @@
+/*
+ * gptsync/gptsync.c
+ * Platform-independent code for syncing GPT and MBR
+ *
+ * Copyright (c) 2006-2007 Christoph Pfisterer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Christoph Pfisterer nor the names of the
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "gptsync.h"
+
+#include "syslinux_mbr.h"
+
+//
+// MBR functions
+//
+
+static UINTN check_mbr(VOID)
+{
+ UINTN i, k;
+
+ // check each entry
+ for (i = 0; i < mbr_part_count; i++) {
+ // check for overlap
+ for (k = 0; k < mbr_part_count; k++) {
+ if (k != i && !(mbr_parts[i].start_lba > mbr_parts[k].end_lba || mbr_parts[k].start_lba > mbr_parts[i].end_lba)) {
+ Print(L"Status: MBR partition table is invalid, partitions overlap.\n");
+ return 1;
+ }
+ }
+
+ // check for extended partitions
+ if (mbr_parts[i].mbr_type == 0x05 || mbr_parts[i].mbr_type == 0x0f || mbr_parts[i].mbr_type == 0x85) {
+ Print(L"Status: Extended partition found in MBR table, will not touch this disk.\n",
+ gpt_parts[i].gpt_parttype->name);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static UINTN write_mbr(VOID)
+{
+ UINTN status;
+ UINTN i, k;
+ UINT8 active;
+ UINT64 lba;
+ MBR_PARTITION_INFO *table;
+ BOOLEAN have_bootcode;
+
+ Print(L"\nWriting new MBR...\n");
+
+ // read MBR data
+ status = read_sector(0, sector);
+ if (status != 0)
+ return status;
+
+ // write partition table
+ *((UINT16 *)(sector + 510)) = 0xaa55;
+
+ table = (MBR_PARTITION_INFO *)(sector + 446);
+ active = 0x80;
+ for (i = 0; i < 4; i++) {
+ for (k = 0; k < new_mbr_part_count; k++) {
+ if (new_mbr_parts[k].index == i)
+ break;
+ }
+ if (k >= new_mbr_part_count) {
+ // unused entry
+ table[i].flags = 0;
+ table[i].start_chs[0] = 0;
+ table[i].start_chs[1] = 0;
+ table[i].start_chs[2] = 0;
+ table[i].type = 0;
+ table[i].end_chs[0] = 0;
+ table[i].end_chs[1] = 0;
+ table[i].end_chs[2] = 0;
+ table[i].start_lba = 0;
+ table[i].size = 0;
+ } else {
+ if (new_mbr_parts[k].active) {
+ table[i].flags = active;
+ active = 0x00;
+ } else
+ table[i].flags = 0x00;
+ table[i].start_chs[0] = 0xfe;
+ table[i].start_chs[1] = 0xff;
+ table[i].start_chs[2] = 0xff;
+ table[i].type = new_mbr_parts[k].mbr_type;
+ table[i].end_chs[0] = 0xfe;
+ table[i].end_chs[1] = 0xff;
+ table[i].end_chs[2] = 0xff;
+
+ lba = new_mbr_parts[k].start_lba;
+ if (lba > 0xffffffffULL) {
+ Print(L"Warning: Partition %d starts beyond 2 TiB limit\n", i+1);
+ lba = 0xffffffffULL;
+ }
+ table[i].start_lba = (UINT32)lba;
+
+ lba = new_mbr_parts[k].end_lba + 1 - new_mbr_parts[k].start_lba;
+ if (lba > 0xffffffffULL) {
+ Print(L"Warning: Partition %d extends beyond 2 TiB limit\n", i+1);
+ lba = 0xffffffffULL;
+ }
+ table[i].size = (UINT32)lba;
+ }
+ }
+
+ // add boot code if necessary
+ have_bootcode = FALSE;
+ for (i = 0; i < MBR_BOOTCODE_SIZE; i++) {
+ if (sector[i] != 0) {
+ have_bootcode = TRUE;
+ break;
+ }
+ }
+ if (!have_bootcode) {
+ // no boot code found in the MBR, add the syslinux MBR code
+ SetMem(sector, 0, MBR_BOOTCODE_SIZE);
+ CopyMem(sector, syslinux_mbr, SYSLINUX_MBR_SIZE);
+ }
+
+ // write MBR data
+ status = write_sector(0, sector);
+ if (status != 0)
+ return status;
+
+ Print(L"MBR updated successfully!\n");
+
+ return 0;
+}
+
+//
+// GPT functions
+//
+
+static UINTN check_gpt(VOID)
+{
+ UINTN i, k;
+ BOOLEAN found_data_parts;
+
+ if (gpt_part_count == 0) {
+ Print(L"Status: No GPT partition table, no need to sync.\n");
+ return 1;
+ }
+
+ // check each entry
+ found_data_parts = FALSE;
+ for (i = 0; i < gpt_part_count; i++) {
+ // check sanity
+ if (gpt_parts[i].end_lba < gpt_parts[i].start_lba) {
+ Print(L"Status: GPT partition table is invalid.\n");
+ return 1;
+ }
+ // check for overlap
+ for (k = 0; k < gpt_part_count; k++) {
+ if (k != i && !(gpt_parts[i].start_lba > gpt_parts[k].end_lba || gpt_parts[k].start_lba > gpt_parts[i].end_lba)) {
+ Print(L"Status: GPT partition table is invalid, partitions overlap.\n");
+ return 1;
+ }
+ }
+
+ // check for partitions kind
+ if (gpt_parts[i].gpt_parttype->kind == GPT_KIND_FATAL) {
+ Print(L"Status: GPT partition of type '%s' found, will not touch this disk.\n",
+ gpt_parts[i].gpt_parttype->name);
+ return 1;
+ }
+ if (gpt_parts[i].gpt_parttype->kind == GPT_KIND_DATA ||
+ gpt_parts[i].gpt_parttype->kind == GPT_KIND_BASIC_DATA)
+ found_data_parts = TRUE;
+ }
+
+ if (!found_data_parts) {
+ Print(L"Status: GPT partition table has no data partitions, no need to sync.\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+//
+// compare GPT and MBR tables
+//
+
+#define ACTION_NONE (0)
+#define ACTION_NOP (1)
+#define ACTION_REWRITE (2)
+
+static UINTN analyze(VOID)
+{
+ UINTN action;
+ UINTN i, k, iter, count_active, detected_parttype;
+ CHARN *fsname;
+ UINT64 min_start_lba;
+ UINTN status;
+ BOOLEAN have_esp;
+
+ new_mbr_part_count = 0;
+
+ // determine correct MBR types for GPT partitions
+ if (gpt_part_count == 0) {
+ Print(L"Status: No GPT partitions defined, nothing to sync.\n");
+ return 0;
+ }
+ have_esp = FALSE;
+ for (i = 0; i < gpt_part_count; i++) {
+ gpt_parts[i].mbr_type = gpt_parts[i].gpt_parttype->mbr_type;
+ if (gpt_parts[i].gpt_parttype->kind == GPT_KIND_BASIC_DATA) {
+ // Basic Data: need to look at data in the partition
+ status = detect_mbrtype_fs(gpt_parts[i].start_lba, &detected_parttype, &fsname);
+ if (detected_parttype)
+ gpt_parts[i].mbr_type = detected_parttype;
+ else
+ gpt_parts[i].mbr_type = 0x0b; // fallback: FAT32
+ } else if (gpt_parts[i].mbr_type == 0xef) {
+ // EFI System Partition: GNU parted can put this on any partition,
+ // need to detect file systems
+ status = detect_mbrtype_fs(gpt_parts[i].start_lba, &detected_parttype, &fsname);
+ if (!have_esp && (detected_parttype == 0x01 || detected_parttype == 0x0e || detected_parttype == 0x0c))
+ ; // seems to be a legitimate ESP, don't change
+ else if (detected_parttype)
+ gpt_parts[i].mbr_type = detected_parttype;
+ else if (have_esp) // make sure there's no more than one ESP per disk
+ gpt_parts[i].mbr_type = 0x83; // fallback: Linux
+ }
+ // NOTE: mbr_type may still be 0 if content detection fails for exotic GPT types or file systems
+
+ if (gpt_parts[i].mbr_type == 0xef)
+ have_esp = TRUE;
+ }
+
+ // check for common scenarios
+ action = ACTION_NONE;
+ if (mbr_part_count == 0) {
+ // current MBR is empty
+ action = ACTION_REWRITE;
+ } else if (mbr_part_count == 1 && mbr_parts[0].mbr_type == 0xee) {
+ // MBR has just the EFI Protective partition (i.e. untouched)
+ action = ACTION_REWRITE;
+ }
+ if (action == ACTION_NONE && mbr_part_count > 0) {
+ if (mbr_parts[0].mbr_type == 0xee &&
+ gpt_parts[0].mbr_type == 0xef &&
+ mbr_parts[0].start_lba == 1 &&
+ mbr_parts[0].end_lba == gpt_parts[0].end_lba) {
+ // The Apple Way, "EFI Protective" covering the tables and the ESP
+ action = ACTION_NOP;
+ if ((mbr_part_count != gpt_part_count && gpt_part_count <= 4) ||
+ (mbr_part_count != 4 && gpt_part_count > 4)) {
+ // number of partitions has changed
+ action = ACTION_REWRITE;
+ } else {
+ // check partition ranges and types
+ for (i = 1; i < mbr_part_count; i++) {
+ if (mbr_parts[i].start_lba != gpt_parts[i].start_lba ||
+ mbr_parts[i].end_lba != gpt_parts[i].end_lba ||
+ (gpt_parts[i].mbr_type && mbr_parts[i].mbr_type != gpt_parts[i].mbr_type))
+ // position or type has changed
+ action = ACTION_REWRITE;
+ }
+ }
+ // check number of active partitions
+ count_active = 0;
+ for (i = 0; i < mbr_part_count; i++)
+ if (mbr_parts[i].active)
+ count_active++;
+ if (count_active!= 1)
+ action = ACTION_REWRITE;
+ }
+ }
+ if (action == ACTION_NONE && mbr_part_count > 0 && mbr_parts[0].mbr_type == 0xef) {
+ // The XOM Way, all partitions mirrored 1:1
+ action = ACTION_REWRITE;
+ // check partition ranges and types
+ for (i = 0; i < mbr_part_count; i++) {
+ if (mbr_parts[i].start_lba != gpt_parts[i].start_lba ||
+ mbr_parts[i].end_lba != gpt_parts[i].end_lba ||
+ (gpt_parts[i].mbr_type && mbr_parts[i].mbr_type != gpt_parts[i].mbr_type))
+ // position or type has changed -> better don't touch
+ action = ACTION_NONE;
+ }
+ }
+
+ if (action == ACTION_NOP) {
+ Print(L"Status: Tables are synchronized, no need to sync.\n");
+ return 0;
+ } else if (action == ACTION_REWRITE) {
+ Print(L"Status: MBR table must be updated.\n");
+ } else {
+ Print(L"Status: Analysis inconclusive, will not touch this disk.\n");
+ return 1;
+ }
+
+ // generate the new table
+
+ // first entry: EFI Protective
+ new_mbr_parts[0].index = 0;
+ new_mbr_parts[0].start_lba = 1;
+ new_mbr_parts[0].mbr_type = 0xee;
+ new_mbr_part_count = 1;
+
+ if (gpt_parts[0].mbr_type == 0xef) {
+ new_mbr_parts[0].end_lba = gpt_parts[0].end_lba;
+ i = 1;
+ } else {
+ min_start_lba = gpt_parts[0].start_lba;
+ for (k = 0; k < gpt_part_count; k++) {
+ if (min_start_lba > gpt_parts[k].start_lba)
+ min_start_lba = gpt_parts[k].start_lba;
+ }
+ new_mbr_parts[0].end_lba = min_start_lba - 1;
+ i = 0;
+ }
+
+ // add other GPT partitions until the table is full
+ // TODO: in the future, prioritize partitions by kind
+ for (; i < gpt_part_count && new_mbr_part_count < 4; i++) {
+ new_mbr_parts[new_mbr_part_count].index = new_mbr_part_count;
+ new_mbr_parts[new_mbr_part_count].start_lba = gpt_parts[i].start_lba;
+ new_mbr_parts[new_mbr_part_count].end_lba = gpt_parts[i].end_lba;
+ new_mbr_parts[new_mbr_part_count].mbr_type = gpt_parts[i].mbr_type;
+ new_mbr_parts[new_mbr_part_count].active = FALSE;
+
+ // find matching partition in the old MBR table
+ for (k = 0; k < mbr_part_count; k++) {
+ if (mbr_parts[k].start_lba == gpt_parts[i].start_lba) {
+ // keep type if not detected
+ if (new_mbr_parts[new_mbr_part_count].mbr_type == 0)
+ new_mbr_parts[new_mbr_part_count].mbr_type = mbr_parts[k].mbr_type;
+ // keep active flag
+ new_mbr_parts[new_mbr_part_count].active = mbr_parts[k].active;
+ break;
+ }
+ }
+
+ if (new_mbr_parts[new_mbr_part_count].mbr_type == 0)
+ // final fallback: set to a (hopefully) unused type
+ new_mbr_parts[new_mbr_part_count].mbr_type = 0xc0;
+
+ new_mbr_part_count++;
+ }
+
+ // make sure there's exactly one active partition
+ for (iter = 0; iter < 3; iter++) {
+ // check
+ count_active = 0;
+ for (i = 0; i < new_mbr_part_count; i++)
+ if (new_mbr_parts[i].active)
+ count_active++;
+ if (count_active == 1)
+ break;
+
+ // set active on the first matching partition
+ if (count_active == 0) {
+ for (i = 0; i < new_mbr_part_count; i++) {
+ if ((iter >= 0 && (new_mbr_parts[i].mbr_type == 0x07 || // NTFS
+ new_mbr_parts[i].mbr_type == 0x0b || // FAT32
+ new_mbr_parts[i].mbr_type == 0x0c)) || // FAT32 (LBA)
+ (iter >= 1 && (new_mbr_parts[i].mbr_type == 0x83)) || // Linux
+ (iter >= 2 && i > 0)) {
+ new_mbr_parts[i].active = TRUE;
+ break;
+ }
+ }
+ } else if (count_active > 1 && iter == 0) {
+ // too many active partitions, try deactivating the ESP / EFI Protective entry
+ if ((new_mbr_parts[0].mbr_type == 0xee || new_mbr_parts[0].mbr_type == 0xef) &&
+ new_mbr_parts[0].active) {
+ new_mbr_parts[0].active = FALSE;
+ }
+ } else if (count_active > 1 && iter > 0) {
+ // too many active partitions, deactivate all but the first one
+ count_active = 0;
+ for (i = 0; i < new_mbr_part_count; i++)
+ if (new_mbr_parts[i].active) {
+ if (count_active > 0)
+ new_mbr_parts[i].active = FALSE;
+ count_active++;
+ }
+ }
+ }
+
+ // dump table
+ Print(L"\nProposed new MBR partition table:\n");
+ Print(L" # A Start LBA End LBA Type\n");
+ for (i = 0; i < new_mbr_part_count; i++) {
+ Print(L" %d %s %12lld %12lld %02x %s\n",
+ new_mbr_parts[i].index + 1,
+ new_mbr_parts[i].active ? STR("*") : STR(" "),
+ new_mbr_parts[i].start_lba,
+ new_mbr_parts[i].end_lba,
+ new_mbr_parts[i].mbr_type,
+ mbr_parttype_name(new_mbr_parts[i].mbr_type));
+ }
+
+ return 0;
+}
+
+//
+// sync algorithm entry point
+//
+
+UINTN gptsync(VOID)
+{
+ UINTN status = 0;
+ UINTN status_gpt, status_mbr;
+ // BOOLEAN proceed = FALSE;
+
+ // get full information from disk
+ status_gpt = read_gpt();
+ status_mbr = read_mbr();
+ if (status_gpt != 0 || status_mbr != 0)
+ return (status_gpt || status_mbr);
+
+ // cross-check current situation
+ Print(L"\n");
+ status = check_gpt(); // check GPT for consistency
+ if (status != 0)
+ return status;
+ status = check_mbr(); // check MBR for consistency
+ if (status != 0)
+ return status;
+ status = analyze(); // analyze the situation & compose new MBR table
+ if (status != 0)
+ return status;
+ if (new_mbr_part_count == 0)
+ return status;
+
+ // offer user the choice what to do
+ // status = input_boolean(STR("\nMay I update the MBR as printed above? [y/N] "), &proceed);
+ // if (status != 0 || proceed != TRUE)
+ // return status;
+
+ // adjust the MBR and write it back
+ status = write_mbr();
+ if (status != 0)
+ return status;
+
+ return status;
+}