diff --git a/Makefile.util.def b/Makefile.util.def index f3a8aec17..ab056f8ac 100644 --- a/Makefile.util.def +++ b/Makefile.util.def @@ -1150,6 +1150,12 @@ script = { common = tests/grub_cmd_tr.in; }; +script = { + testcase; + name = gptrepair_test; + common = tests/gptrepair_test.in; +}; + program = { testcase; name = example_unit_test; diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index 385f53f59..7f309b830 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -826,6 +826,11 @@ module = { common = commands/gptsync.c; }; +module = { + name = gptrepair; + common = commands/gptrepair.c; +}; + module = { name = gpt; common = lib/gpt.c; diff --git a/grub-core/commands/gptrepair.c b/grub-core/commands/gptrepair.c new file mode 100644 index 000000000..38392fd8f --- /dev/null +++ b/grub-core/commands/gptrepair.c @@ -0,0 +1,116 @@ +/* gptrepair.c - verify and restore GPT info from alternate location. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 Free Software Foundation, Inc. + * Copyright (C) 2014 CoreOS, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +static char * +trim_dev_name (char *name) +{ + grub_size_t len = grub_strlen (name); + if (len && name[0] == '(' && name[len - 1] == ')') + { + name[len - 1] = '\0'; + name = name + 1; + } + return name; +} + +static grub_err_t +grub_cmd_gptrepair (grub_command_t cmd __attribute__ ((unused)), + int argc, char **args) +{ + grub_device_t dev = NULL; + grub_gpt_t gpt = NULL; + char *dev_name; + grub_uint32_t primary_crc, backup_crc; + enum grub_gpt_status old_status; + + if (argc != 1 || !grub_strlen(args[0])) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "device name required"); + + dev_name = trim_dev_name (args[0]); + dev = grub_device_open (dev_name); + if (!dev) + goto done; + + if (!dev->disk) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "not a disk"); + goto done; + } + + gpt = grub_gpt_read (dev->disk); + if (!gpt) + goto done; + + primary_crc = gpt->primary.crc32; + backup_crc = gpt->backup.crc32; + old_status = gpt->status; + + if (grub_gpt_repair (dev->disk, gpt)) + goto done; + + if (primary_crc == gpt->primary.crc32 && + backup_crc == gpt->backup.crc32 && + old_status && gpt->status) + { + grub_printf_ (N_("GPT already valid, %s unmodified.\n"), dev_name); + goto done; + } + + if (grub_gpt_write (dev->disk, gpt)) + goto done; + + if (!(old_status & GRUB_GPT_PRIMARY_VALID)) + grub_printf_ (N_("Primary GPT for %s repaired.\n"), dev_name); + + if (!(old_status & GRUB_GPT_BACKUP_VALID)) + grub_printf_ (N_("Backup GPT for %s repaired.\n"), dev_name); + +done: + if (gpt) + grub_gpt_free (gpt); + + if (dev) + grub_device_close (dev); + + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(gptrepair) +{ + cmd = grub_register_command ("gptrepair", grub_cmd_gptrepair, + N_("DEVICE"), + N_("Verify and repair GPT on drive DEVICE.")); +} + +GRUB_MOD_FINI(gptrepair) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/lib/gpt.c b/grub-core/lib/gpt.c index a308e8537..67ffdf703 100644 --- a/grub-core/lib/gpt.c +++ b/grub-core/lib/gpt.c @@ -31,23 +31,32 @@ 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_header_crc32 (struct grub_gpt_header *gpt, grub_uint32_t *crc) +grub_gpt_lecrc32 (void *data, grub_size_t len, grub_uint32_t *crc) { grub_uint8_t *crc32_context; - grub_uint32_t old; crc32_context = grub_zalloc (GRUB_MD_CRC32->contextsize); if (!crc32_context) return grub_errno; - /* crc32 must be computed with the field cleared. */ - old = gpt->crc32; - gpt->crc32 = 0; GRUB_MD_CRC32->init (crc32_context); - GRUB_MD_CRC32->write (crc32_context, gpt, sizeof (*gpt)); + GRUB_MD_CRC32->write (crc32_context, data, len); GRUB_MD_CRC32->final (crc32_context); - gpt->crc32 = old; /* GRUB_MD_CRC32 always uses big endian, gpt is always little. */ *crc = grub_swap_bytes32 (*(grub_uint32_t *) @@ -58,6 +67,25 @@ grub_gpt_header_crc32 (struct grub_gpt_header *gpt, grub_uint32_t *crc) return GRUB_ERR_NONE; } +static grub_err_t +grub_gpt_header_lecrc32 (struct grub_gpt_header *header, grub_uint32_t *crc) +{ + grub_uint32_t old, new; + grub_err_t err; + + /* crc32 must be computed with the field cleared. */ + old = header->crc32; + header->crc32 = 0; + err = grub_gpt_lecrc32 (header, sizeof (*header), &new); + header->crc32 = old; + + if (err) + return err; + + *crc = new; + return GRUB_ERR_NONE; +} + /* Make sure the MBR is a protective MBR and not a normal MBR. */ grub_err_t grub_gpt_pmbr_check (struct grub_msdos_partition_mbr *mbr) @@ -87,7 +115,7 @@ grub_gpt_header_check (struct grub_gpt_header *gpt, if (gpt->version != GRUB_GPT_HEADER_VERSION) return grub_error (GRUB_ERR_BAD_PART_TABLE, "unknown GPT version"); - if (grub_gpt_header_crc32 (gpt, &crc)) + if (grub_gpt_header_lecrc32 (gpt, &crc)) return grub_errno; if (gpt->crc32 != crc) @@ -137,7 +165,7 @@ grub_gpt_read_backup (grub_disk_t disk, grub_gpt_t gpt) if (disk->total_sectors != GRUB_DISK_SIZE_UNKNOWN) sector = disk->total_sectors - 1; else if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) - sector = grub_le_to_cpu64 (gpt->primary.backup); + sector = grub_le_to_cpu64 (gpt->primary.alternate_lba); else return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "Unable to locate backup GPT"); @@ -153,12 +181,11 @@ grub_gpt_read_backup (grub_disk_t disk, grub_gpt_t gpt) return GRUB_ERR_NONE; } -static struct grub_gpt_partentry * +static grub_err_t grub_gpt_read_entries (grub_disk_t disk, grub_gpt_t gpt, struct grub_gpt_header *header) { struct grub_gpt_partentry *entries = NULL; - grub_uint8_t *crc32_context = NULL; grub_uint32_t count, size, crc; grub_disk_addr_t addr; grub_size_t entries_size; @@ -173,6 +200,10 @@ grub_gpt_read_entries (grub_disk_t disk, grub_gpt_t gpt, goto fail; } + /* Double check that the header was validated properly. */ + if (entries_size < GRUB_GPT_DEFAULT_ENTRIES_SIZE) + return grub_error (GRUB_ERR_BUG, "invalid GPT entries table size"); + entries = grub_malloc (entries_size); if (!entries) goto fail; @@ -181,35 +212,29 @@ grub_gpt_read_entries (grub_disk_t disk, grub_gpt_t gpt, if (grub_disk_read (disk, addr, 0, entries_size, entries)) goto fail; - crc32_context = grub_zalloc (GRUB_MD_CRC32->contextsize); - if (!crc32_context) + if (grub_gpt_lecrc32 (entries, entries_size, &crc)) goto fail; - GRUB_MD_CRC32->init (crc32_context); - GRUB_MD_CRC32->write (crc32_context, entries, entries_size); - GRUB_MD_CRC32->final (crc32_context); - - crc = *(grub_uint32_t *) GRUB_MD_CRC32->read (crc32_context); - if (grub_swap_bytes32 (crc) != header->partentry_crc32) + if (crc != header->partentry_crc32) { grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry crc32"); goto fail; } - grub_free (crc32_context); - return entries; + grub_free (gpt->entries); + gpt->entries = entries; + gpt->entries_size = entries_size; + return GRUB_ERR_NONE; fail: grub_free (entries); - grub_free (crc32_context); - return NULL; + return grub_errno; } grub_gpt_t grub_gpt_read (grub_disk_t disk) { grub_gpt_t gpt; - struct grub_gpt_partentry *backup_entries; gpt = grub_zalloc (sizeof (*gpt)); if (!gpt) @@ -241,42 +266,141 @@ grub_gpt_read (grub_disk_t disk) else goto fail; - /* Same error handling scheme for the entry tables. */ - gpt->entries = grub_gpt_read_entries (disk, gpt, &gpt->primary); - if (!gpt->entries) - { - grub_error_push (); - backup_entries = grub_gpt_read_entries (disk, gpt, &gpt->backup); - grub_error_pop (); - } - else - { - gpt->status |= GRUB_GPT_PRIMARY_ENTRIES_VALID; - backup_entries = grub_gpt_read_entries (disk, gpt, &gpt->backup); - } + /* Similarly, favor the value or error from the primary table. */ + if (gpt->status & GRUB_GPT_BACKUP_HEADER_VALID && + !grub_gpt_read_entries (disk, gpt, &gpt->backup)) + gpt->status |= GRUB_GPT_BACKUP_ENTRIES_VALID; - if (backup_entries) - { - gpt->status |= GRUB_GPT_BACKUP_ENTRIES_VALID; - - if (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID) - grub_free (backup_entries); - else - gpt->entries = backup_entries; - } + grub_errno = GRUB_ERR_NONE; + if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID && + !grub_gpt_read_entries (disk, gpt, &gpt->primary)) + gpt->status |= GRUB_GPT_PRIMARY_ENTRIES_VALID; if (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID || gpt->status & GRUB_GPT_BACKUP_ENTRIES_VALID) - { - grub_errno = GRUB_ERR_NONE; - return gpt; - } + grub_errno = GRUB_ERR_NONE; + else + goto fail; + + return gpt; fail: grub_gpt_free (gpt); 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_BOTH_VALID; + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_gpt_write_table (grub_disk_t disk, grub_gpt_t gpt, + struct grub_gpt_header *header) +{ + grub_disk_addr_t addr; + + if (grub_le_to_cpu32 (header->headersize) != sizeof (*header)) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "Header size is %u, must be %u", + grub_le_to_cpu32 (header->headersize), + sizeof (*header)); + + addr = grub_gpt_sector_to_addr (gpt, grub_le_to_cpu64 (header->header_lba)); + if (grub_disk_write (disk, addr, 0, sizeof (*header), header)) + return grub_errno; + + addr = grub_gpt_sector_to_addr (gpt, grub_le_to_cpu64 (header->partitions)); + if (grub_disk_write (disk, addr, 0, gpt->entries_size, gpt->entries)) + return grub_errno; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_gpt_write (grub_disk_t disk, grub_gpt_t gpt) +{ + /* TODO: update/repair protective MBRs too. */ + + if (!(gpt->status & GRUB_GPT_BOTH_VALID)) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "Invalid GPT data"); + + if (grub_gpt_write_table (disk, gpt, &gpt->primary)) + return grub_errno; + + if (grub_gpt_write_table (disk, gpt, &gpt->backup)) + return grub_errno; + + 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 04ed2d7f1..3cac6df32 100644 --- a/include/grub/gpt_partition.h +++ b/include/grub/gpt_partition.h @@ -64,8 +64,8 @@ struct grub_gpt_header grub_uint32_t headersize; grub_uint32_t crc32; grub_uint32_t unused1; - grub_uint64_t primary; - grub_uint64_t backup; + grub_uint64_t header_lba; + grub_uint64_t alternate_lba; grub_uint64_t start; grub_uint64_t end; grub_uint8_t guid[16]; @@ -103,10 +103,17 @@ typedef enum grub_gpt_status } grub_gpt_status_t; #define GRUB_GPT_MBR_VALID (GRUB_GPT_PROTECTIVE_MBR|GRUB_GPT_HYBRID_MBR) +#define GRUB_GPT_PRIMARY_VALID \ + (GRUB_GPT_PRIMARY_HEADER_VALID|GRUB_GPT_PRIMARY_ENTRIES_VALID) +#define GRUB_GPT_BACKUP_VALID \ + (GRUB_GPT_BACKUP_HEADER_VALID|GRUB_GPT_BACKUP_ENTRIES_VALID) +#define GRUB_GPT_BOTH_VALID (GRUB_GPT_PRIMARY_VALID|GRUB_GPT_BACKUP_VALID) /* UEFI requires the entries table to be at least 16384 bytes for a * total of 128 entries given the standard 128 byte entry size. */ -#define GRUB_GPT_DEFAULT_ENTRIES_LENGTH 128 +#define GRUB_GPT_DEFAULT_ENTRIES_SIZE 16384 +#define GRUB_GPT_DEFAULT_ENTRIES_LENGTH \ + (GRUB_GPT_DEFAULT_ENTRIES_SIZE / sizeof (struct grub_gpt_partentry)) struct grub_gpt { @@ -122,6 +129,7 @@ struct grub_gpt /* Only need one entries table, on disk both copies are identical. */ struct grub_gpt_partentry *entries; + grub_size_t entries_size; /* Logarithm of sector size, in case GPT and disk driver disagree. */ unsigned int log_sector_size; @@ -138,6 +146,12 @@ 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); + +/* Write headers and entry tables back to disk. */ +grub_err_t grub_gpt_write (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 a824cd967..83198bebf 100644 --- a/tests/gpt_unit_test.c +++ b/tests/gpt_unit_test.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -94,8 +95,8 @@ static const struct grub_gpt_header example_primary = { .version = GRUB_GPT_HEADER_VERSION, .headersize = sizeof (struct grub_gpt_header), .crc32 = grub_cpu_to_le32_compile_time (0x7cd8642c), - .primary = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR), - .backup = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR), + .header_lba = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR), + .alternate_lba = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR), .start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR), .end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR), .guid = {0xad, 0x31, 0xc1, 0x69, 0xd6, 0x67, 0xc6, 0x46, @@ -112,8 +113,8 @@ static const struct grub_gpt_header example_backup = { .version = GRUB_GPT_HEADER_VERSION, .headersize = sizeof (struct grub_gpt_header), .crc32 = grub_cpu_to_le32_compile_time (0xcfaa4a27), - .primary = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR), - .backup = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR), + .header_lba = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR), + .alternate_lba = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR), .start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR), .end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR), .guid = {0xad, 0x31, 0xc1, 0x69, 0xd6, 0x67, 0xc6, 0x46, @@ -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 (); } diff --git a/tests/gptrepair_test.in b/tests/gptrepair_test.in new file mode 100644 index 000000000..80b2de633 --- /dev/null +++ b/tests/gptrepair_test.in @@ -0,0 +1,102 @@ +#! /bin/sh +set -e + +# Copyright (C) 2010 Free Software Foundation, Inc. +# Copyright (C) 2014 CoreOS, Inc. +# +# GRUB is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GRUB is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GRUB. If not, see . + +parted=parted +grubshell=@builddir@/grub-shell + +. "@builddir@/grub-core/modinfo.sh" + +case "${grub_modinfo_target_cpu}-${grub_modinfo_platform}" in + mips-qemu_mips | mipsel-qemu_mips | i386-qemu | i386-multiboot | i386-coreboot | mipsel-loongson) + disk=ata0 + ;; + powerpc-ieee1275) + disk=ieee1275//pci@80000000/mac-io@4/ata-3@20000/disk@0 + # FIXME: QEMU firmware has bugs which prevent it from accessing hard disk w/o recognised label. + exit 0 + ;; + sparc64-ieee1275) + disk=ieee1275//pci@1fe\,0/pci-ata@5/ide0@500/disk@0 + # FIXME: QEMU firmware has bugs which prevent it from accessing hard disk w/o recognised label. + exit 0 + ;; + i386-ieee1275) + disk=ieee1275/d + # FIXME: QEMU firmware has bugs which prevent it from accessing hard disk w/o recognised label. + exit 0 + ;; + mips-arc) + # FIXME: ARC firmware has bugs which prevent it from accessing hard disk w/o dvh disklabel. + exit 0 ;; + mipsel-arc) + disk=arc/scsi0/disk0/rdisk0 + ;; + *) + disk=hd0 + ;; +esac +img1="`mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX"`" || exit 1 +img2="`mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX"`" || exit 1 +trap "rm -f '${img1}' '${ing2}'" EXIT + +create_disk_image () { + size=$1 + rm -f "${img1}" + dd if=/dev/zero of="${img1}" bs=512 count=1 seek=$((size - 1)) status=none + ${parted} -a none -s "${img1}" mklabel gpt + cp "${img1}" "${img2}" +} + +wipe_disk_area () { + sector=$1 + size=$2 + dd if=/dev/zero of="${img2}" bs=512 count=${size} seek=${sector} conv=notrunc status=none +} + +do_repair () { + output="`echo "gptrepair ($disk)" | "${grubshell}" --disk="${img2}"`" + if echo "${output}" | grep ^error; then + return 1 + fi + if echo "${output}" | grep -v GPT; then + echo "Unexpected output ${output}" + return 1 + fi + echo "${output}" +} + +echo "Nothing to repair:" +create_disk_image 100 +do_repair +cmp "${img1}" "${img2}" +echo + +echo "Repair primary (MBR left intact)" +create_disk_image 100 +wipe_disk_area 1 1 +do_repair +cmp "${img1}" "${img2}" +echo + +echo "Repair backup" +create_disk_image 100 +wipe_disk_area 99 1 +do_repair +cmp "${img1}" "${img2}" +echo