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