Merge pull request #2 from marineam/gptprio
GPT write support and repair command
This commit is contained in:
commit
6cee30ef82
7 changed files with 473 additions and 57 deletions
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
116
grub-core/commands/gptrepair.c
Normal file
116
grub-core/commands/gptrepair.c
Normal 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);
|
||||||
|
}
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
102
tests/gptrepair_test.in
Normal 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
|
Loading…
Reference in a new issue