qubes-installer-qubes-os/gptsync/gptsync.c

471 lines
17 KiB
C
Raw Normal View History

/*
* 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;
}