Merge pull request #2 from marineam/gptprio

GPT write support and repair command
This commit is contained in:
Michael Marineau 2014-10-21 13:52:22 -07:00
commit 6cee30ef82
7 changed files with 473 additions and 57 deletions

View file

@ -1150,6 +1150,12 @@ script = {
common = tests/grub_cmd_tr.in; common = tests/grub_cmd_tr.in;
}; };
script = {
testcase;
name = gptrepair_test;
common = tests/gptrepair_test.in;
};
program = { program = {
testcase; testcase;
name = example_unit_test; name = example_unit_test;

View file

@ -826,6 +826,11 @@ module = {
common = commands/gptsync.c; common = commands/gptsync.c;
}; };
module = {
name = gptrepair;
common = commands/gptrepair.c;
};
module = { module = {
name = gpt; name = gpt;
common = lib/gpt.c; common = lib/gpt.c;

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <grub/command.h>
#include <grub/device.h>
#include <grub/err.h>
#include <grub/gpt_partition.h>
#include <grub/i18n.h>
#include <grub/misc.h>
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);
}

View file

@ -31,23 +31,32 @@ GRUB_MOD_LICENSE ("GPLv3+");
static grub_uint8_t grub_gpt_magic[] = GRUB_GPT_HEADER_MAGIC; 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 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_uint8_t *crc32_context;
grub_uint32_t old;
crc32_context = grub_zalloc (GRUB_MD_CRC32->contextsize); crc32_context = grub_zalloc (GRUB_MD_CRC32->contextsize);
if (!crc32_context) if (!crc32_context)
return grub_errno; 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->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); GRUB_MD_CRC32->final (crc32_context);
gpt->crc32 = old;
/* GRUB_MD_CRC32 always uses big endian, gpt is always little. */ /* GRUB_MD_CRC32 always uses big endian, gpt is always little. */
*crc = grub_swap_bytes32 (*(grub_uint32_t *) *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; 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. */ /* Make sure the MBR is a protective MBR and not a normal MBR. */
grub_err_t grub_err_t
grub_gpt_pmbr_check (struct grub_msdos_partition_mbr *mbr) 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) if (gpt->version != GRUB_GPT_HEADER_VERSION)
return grub_error (GRUB_ERR_BAD_PART_TABLE, "unknown GPT 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; return grub_errno;
if (gpt->crc32 != crc) 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) if (disk->total_sectors != GRUB_DISK_SIZE_UNKNOWN)
sector = disk->total_sectors - 1; sector = disk->total_sectors - 1;
else if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) 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 else
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"Unable to locate backup GPT"); "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; 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, grub_gpt_read_entries (grub_disk_t disk, grub_gpt_t gpt,
struct grub_gpt_header *header) struct grub_gpt_header *header)
{ {
struct grub_gpt_partentry *entries = NULL; struct grub_gpt_partentry *entries = NULL;
grub_uint8_t *crc32_context = NULL;
grub_uint32_t count, size, crc; grub_uint32_t count, size, crc;
grub_disk_addr_t addr; grub_disk_addr_t addr;
grub_size_t entries_size; grub_size_t entries_size;
@ -173,6 +200,10 @@ grub_gpt_read_entries (grub_disk_t disk, grub_gpt_t gpt,
goto fail; 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); entries = grub_malloc (entries_size);
if (!entries) if (!entries)
goto fail; 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)) if (grub_disk_read (disk, addr, 0, entries_size, entries))
goto fail; goto fail;
crc32_context = grub_zalloc (GRUB_MD_CRC32->contextsize); if (grub_gpt_lecrc32 (entries, entries_size, &crc))
if (!crc32_context)
goto fail; goto fail;
GRUB_MD_CRC32->init (crc32_context); if (crc != header->partentry_crc32)
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)
{ {
grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry crc32"); grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry crc32");
goto fail; goto fail;
} }
grub_free (crc32_context); grub_free (gpt->entries);
return entries; gpt->entries = entries;
gpt->entries_size = entries_size;
return GRUB_ERR_NONE;
fail: fail:
grub_free (entries); grub_free (entries);
grub_free (crc32_context); return grub_errno;
return NULL;
} }
grub_gpt_t grub_gpt_t
grub_gpt_read (grub_disk_t disk) grub_gpt_read (grub_disk_t disk)
{ {
grub_gpt_t gpt; grub_gpt_t gpt;
struct grub_gpt_partentry *backup_entries;
gpt = grub_zalloc (sizeof (*gpt)); gpt = grub_zalloc (sizeof (*gpt));
if (!gpt) if (!gpt)
@ -241,42 +266,141 @@ grub_gpt_read (grub_disk_t disk)
else else
goto fail; goto fail;
/* Same error handling scheme for the entry tables. */ /* Similarly, favor the value or error from the primary table. */
gpt->entries = grub_gpt_read_entries (disk, gpt, &gpt->primary); if (gpt->status & GRUB_GPT_BACKUP_HEADER_VALID &&
if (!gpt->entries) !grub_gpt_read_entries (disk, gpt, &gpt->backup))
{ gpt->status |= GRUB_GPT_BACKUP_ENTRIES_VALID;
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);
}
if (backup_entries) grub_errno = GRUB_ERR_NONE;
{ if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID &&
gpt->status |= GRUB_GPT_BACKUP_ENTRIES_VALID; !grub_gpt_read_entries (disk, gpt, &gpt->primary))
gpt->status |= GRUB_GPT_PRIMARY_ENTRIES_VALID;
if (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID)
grub_free (backup_entries);
else
gpt->entries = backup_entries;
}
if (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID || if (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID ||
gpt->status & GRUB_GPT_BACKUP_ENTRIES_VALID) gpt->status & GRUB_GPT_BACKUP_ENTRIES_VALID)
{ grub_errno = GRUB_ERR_NONE;
grub_errno = GRUB_ERR_NONE; else
return gpt; goto fail;
}
return gpt;
fail: fail:
grub_gpt_free (gpt); grub_gpt_free (gpt);
return NULL; 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 void
grub_gpt_free (grub_gpt_t gpt) grub_gpt_free (grub_gpt_t gpt)
{ {

View file

@ -64,8 +64,8 @@ struct grub_gpt_header
grub_uint32_t headersize; grub_uint32_t headersize;
grub_uint32_t crc32; grub_uint32_t crc32;
grub_uint32_t unused1; grub_uint32_t unused1;
grub_uint64_t primary; grub_uint64_t header_lba;
grub_uint64_t backup; grub_uint64_t alternate_lba;
grub_uint64_t start; grub_uint64_t start;
grub_uint64_t end; grub_uint64_t end;
grub_uint8_t guid[16]; grub_uint8_t guid[16];
@ -103,10 +103,17 @@ typedef enum grub_gpt_status
} grub_gpt_status_t; } grub_gpt_status_t;
#define GRUB_GPT_MBR_VALID (GRUB_GPT_PROTECTIVE_MBR|GRUB_GPT_HYBRID_MBR) #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 /* UEFI requires the entries table to be at least 16384 bytes for a
* total of 128 entries given the standard 128 byte entry size. */ * 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 struct grub_gpt
{ {
@ -122,6 +129,7 @@ struct grub_gpt
/* Only need one entries table, on disk both copies are identical. */ /* Only need one entries table, on disk both copies are identical. */
struct grub_gpt_partentry *entries; struct grub_gpt_partentry *entries;
grub_size_t entries_size;
/* Logarithm of sector size, in case GPT and disk driver disagree. */ /* Logarithm of sector size, in case GPT and disk driver disagree. */
unsigned int log_sector_size; 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. */ /* Allocates and fills new grub_gpt structure, free with grub_gpt_free. */
grub_gpt_t grub_gpt_read (grub_disk_t disk); 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); void grub_gpt_free (grub_gpt_t gpt);
grub_err_t grub_gpt_pmbr_check (struct grub_msdos_partition_mbr *mbr); grub_err_t grub_gpt_pmbr_check (struct grub_msdos_partition_mbr *mbr);

View file

@ -24,6 +24,7 @@
#include <grub/err.h> #include <grub/err.h>
#include <grub/gpt_partition.h> #include <grub/gpt_partition.h>
#include <grub/msdos_partition.h> #include <grub/msdos_partition.h>
#include <grub/lib/hexdump.h>
#include <grub/test.h> #include <grub/test.h>
#include <errno.h> #include <errno.h>
@ -94,8 +95,8 @@ static const struct grub_gpt_header example_primary = {
.version = GRUB_GPT_HEADER_VERSION, .version = GRUB_GPT_HEADER_VERSION,
.headersize = sizeof (struct grub_gpt_header), .headersize = sizeof (struct grub_gpt_header),
.crc32 = grub_cpu_to_le32_compile_time (0x7cd8642c), .crc32 = grub_cpu_to_le32_compile_time (0x7cd8642c),
.primary = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR), .header_lba = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR),
.backup = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR), .alternate_lba = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR),
.start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR), .start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR),
.end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR), .end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR),
.guid = {0xad, 0x31, 0xc1, 0x69, 0xd6, 0x67, 0xc6, 0x46, .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, .version = GRUB_GPT_HEADER_VERSION,
.headersize = sizeof (struct grub_gpt_header), .headersize = sizeof (struct grub_gpt_header),
.crc32 = grub_cpu_to_le32_compile_time (0xcfaa4a27), .crc32 = grub_cpu_to_le32_compile_time (0xcfaa4a27),
.primary = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR), .header_lba = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR),
.backup = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR), .alternate_lba = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR),
.start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR), .start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR),
.end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR), .end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR),
.guid = {0xad, 0x31, 0xc1, 0x69, 0xd6, 0x67, 0xc6, 0x46, .guid = {0xad, 0x31, 0xc1, 0x69, 0xd6, 0x67, 0xc6, 0x46,
@ -442,6 +443,52 @@ read_fallback_test (void)
close_disk (&data); 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 void
grub_unit_test_init (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_valid_test", read_valid_test);
grub_test_register ("gpt_read_invalid_test", read_invalid_entries_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_read_fallback_test", read_fallback_test);
grub_test_register ("gpt_repair_test", repair_test);
} }
void void
@ -463,5 +511,6 @@ grub_unit_test_fini (void)
grub_test_unregister ("gpt_read_valid_test"); grub_test_unregister ("gpt_read_valid_test");
grub_test_unregister ("gpt_read_invalid_test"); grub_test_unregister ("gpt_read_invalid_test");
grub_test_unregister ("gpt_read_fallback_test"); grub_test_unregister ("gpt_read_fallback_test");
grub_test_unregister ("gpt_repair_test");
grub_fini_all (); grub_fini_all ();
} }

102
tests/gptrepair_test.in Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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