From 478458d404eb8663d247e04ff12cb39a5c5f8558 Mon Sep 17 00:00:00 2001 From: Michael Marineau Date: Sat, 18 Oct 2014 18:21:07 -0700 Subject: [PATCH] gpt: add new repair function to sync up primary and backup tables. --- grub-core/lib/gpt.c | 90 ++++++++++++++++++++++++++++++++++++ include/grub/gpt_partition.h | 3 ++ tests/gpt_unit_test.c | 49 ++++++++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/grub-core/lib/gpt.c b/grub-core/lib/gpt.c index 43a150942..2d61df488 100644 --- a/grub-core/lib/gpt.c +++ b/grub-core/lib/gpt.c @@ -31,6 +31,20 @@ GRUB_MOD_LICENSE ("GPLv3+"); static grub_uint8_t grub_gpt_magic[] = GRUB_GPT_HEADER_MAGIC; +static grub_uint64_t +grub_gpt_size_to_sectors (grub_gpt_t gpt, grub_size_t size) +{ + unsigned int sector_size; + grub_uint64_t sectors; + + sector_size = 1U << gpt->log_sector_size; + sectors = size / sector_size; + if (size % sector_size) + sectors++; + + return sectors; +} + static grub_err_t grub_gpt_lecrc32 (void *data, grub_size_t len, grub_uint32_t *crc) { @@ -275,6 +289,82 @@ fail: return NULL; } +grub_err_t +grub_gpt_repair (grub_disk_t disk, grub_gpt_t gpt) +{ + grub_uint64_t backup_header, backup_entries; + grub_uint32_t crc; + + if (disk->log_sector_size != gpt->log_sector_size) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "GPT sector size must match disk sector size"); + + if (!(gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID || + gpt->status & GRUB_GPT_BACKUP_ENTRIES_VALID)) + return grub_error (GRUB_ERR_BUG, "No valid GPT entries"); + + if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) + { + backup_header = grub_le_to_cpu64 (gpt->primary.alternate_lba); + grub_memcpy (&gpt->backup, &gpt->primary, sizeof (gpt->backup)); + } + else if (gpt->status & GRUB_GPT_BACKUP_HEADER_VALID) + { + backup_header = grub_le_to_cpu64 (gpt->backup.header_lba); + grub_memcpy (&gpt->primary, &gpt->backup, sizeof (gpt->primary)); + } + else + return grub_error (GRUB_ERR_BUG, "No valid GPT header"); + + /* Relocate backup to end if disk whenever possible. */ + if (disk->total_sectors != GRUB_DISK_SIZE_UNKNOWN) + backup_header = disk->total_sectors - 1; + + backup_entries = backup_header - + grub_gpt_size_to_sectors (gpt, gpt->entries_size); + + /* Update/fixup header and partition table locations. */ + gpt->primary.header_lba = grub_cpu_to_le64_compile_time (1); + gpt->primary.alternate_lba = grub_cpu_to_le64 (backup_header); + gpt->primary.partitions = grub_cpu_to_le64_compile_time (2); + gpt->backup.header_lba = gpt->primary.alternate_lba; + gpt->backup.alternate_lba = gpt->primary.header_lba; + gpt->backup.partitions = grub_cpu_to_le64 (backup_entries); + + /* Writing headers larger than our header structure are unsupported. */ + gpt->primary.headersize = + grub_cpu_to_le32_compile_time (sizeof (gpt->primary)); + gpt->backup.headersize = + grub_cpu_to_le32_compile_time (sizeof (gpt->backup)); + + /* Recompute checksums. */ + if (grub_gpt_lecrc32 (gpt->entries, gpt->entries_size, &crc)) + return grub_errno; + + gpt->primary.partentry_crc32 = crc; + gpt->backup.partentry_crc32 = crc; + + if (grub_gpt_header_lecrc32 (&gpt->primary, &gpt->primary.crc32)) + return grub_errno; + + if (grub_gpt_header_lecrc32 (&gpt->backup, &gpt->backup.crc32)) + return grub_errno; + + /* Sanity check. */ + if (grub_gpt_header_check (&gpt->primary, gpt->log_sector_size)) + return grub_error (GRUB_ERR_BUG, "Generated invalid GPT primary header"); + + if (grub_gpt_header_check (&gpt->backup, gpt->log_sector_size)) + return grub_error (GRUB_ERR_BUG, "Generated invalid GPT backup header"); + + gpt->status |= (GRUB_GPT_PRIMARY_HEADER_VALID | + GRUB_GPT_PRIMARY_ENTRIES_VALID | + GRUB_GPT_BACKUP_HEADER_VALID | + GRUB_GPT_BACKUP_ENTRIES_VALID); + + return GRUB_ERR_NONE; +} + void grub_gpt_free (grub_gpt_t gpt) { diff --git a/include/grub/gpt_partition.h b/include/grub/gpt_partition.h index 7f41e22dd..62d027e4e 100644 --- a/include/grub/gpt_partition.h +++ b/include/grub/gpt_partition.h @@ -141,6 +141,9 @@ grub_gpt_sector_to_addr (grub_gpt_t gpt, grub_uint64_t sector) /* Allocates and fills new grub_gpt structure, free with grub_gpt_free. */ grub_gpt_t grub_gpt_read (grub_disk_t disk); +/* Sync up primary and backup headers, recompute checksums. */ +grub_err_t grub_gpt_repair (grub_disk_t disk, grub_gpt_t gpt); + void grub_gpt_free (grub_gpt_t gpt); grub_err_t grub_gpt_pmbr_check (struct grub_msdos_partition_mbr *mbr); diff --git a/tests/gpt_unit_test.c b/tests/gpt_unit_test.c index 4d70868af..83198bebf 100644 --- a/tests/gpt_unit_test.c +++ b/tests/gpt_unit_test.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -442,6 +443,52 @@ read_fallback_test (void) close_disk (&data); } +static void +repair_test (void) +{ + struct test_data data; + grub_gpt_t gpt; + + open_disk (&data); + + /* Erase/Repair primary. */ + memset (&data.raw->primary_header, 0, sizeof (data.raw->primary_header)); + sync_disk (&data); + gpt = read_disk (&data); + grub_gpt_repair (data.dev->disk, gpt); + grub_test_assert (grub_errno == GRUB_ERR_NONE, + "repair failed: %s", grub_errmsg); + if (memcmp (&gpt->primary, &example_primary, sizeof (gpt->primary))) + { + printf ("Invalid restored primary header:\n"); + hexdump (16, (char*)&gpt->primary, sizeof (gpt->primary)); + printf ("Expected primary header:\n"); + hexdump (16, (char*)&example_primary, sizeof (example_primary)); + grub_test_assert (0, "repair did not restore primary header"); + } + grub_gpt_free (gpt); + reset_disk (&data); + + /* Erase/Repair backup. */ + memset (&data.raw->backup_header, 0, sizeof (data.raw->backup_header)); + sync_disk (&data); + gpt = read_disk (&data); + grub_gpt_repair (data.dev->disk, gpt); + grub_test_assert (grub_errno == GRUB_ERR_NONE, + "repair failed: %s", grub_errmsg); + if (memcmp (&gpt->backup, &example_backup, sizeof (gpt->backup))) + { + printf ("Invalid restored backup header:\n"); + hexdump (16, (char*)&gpt->backup, sizeof (gpt->backup)); + printf ("Expected backup header:\n"); + hexdump (16, (char*)&example_backup, sizeof (example_backup)); + grub_test_assert (0, "repair did not restore backup header"); + } + grub_gpt_free (gpt); + reset_disk (&data); + + close_disk (&data); +} void grub_unit_test_init (void) { @@ -453,6 +500,7 @@ grub_unit_test_init (void) grub_test_register ("gpt_read_valid_test", read_valid_test); grub_test_register ("gpt_read_invalid_test", read_invalid_entries_test); grub_test_register ("gpt_read_fallback_test", read_fallback_test); + grub_test_register ("gpt_repair_test", repair_test); } void @@ -463,5 +511,6 @@ grub_unit_test_fini (void) grub_test_unregister ("gpt_read_valid_test"); grub_test_unregister ("gpt_read_invalid_test"); grub_test_unregister ("gpt_read_fallback_test"); + grub_test_unregister ("gpt_repair_test"); grub_fini_all (); }