e6cc6f328a
Rename grub_gpt_part_type to grub_gpt_part_guid and update grub_gpt_partentry to use this type for both the partition type GUID string and the partition GUID string entries. This change ensures that the two GUID fields are handled more consistently and helps to simplify the changes needed to add Linux partition GUID support. Signed-off-by: Nicholas Vinson <nvinson234@gmail.com> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
777 lines
23 KiB
C
777 lines
23 KiB
C
/* gpt.c - Read/Verify/Write GUID Partition Tables (GPT). */
|
|
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2002,2005,2006,2007,2008 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/charset.h>
|
|
#include <grub/crypto.h>
|
|
#include <grub/device.h>
|
|
#include <grub/disk.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/dl.h>
|
|
#include <grub/msdos_partition.h>
|
|
#include <grub/gpt_partition.h>
|
|
|
|
GRUB_MOD_LICENSE ("GPLv3+");
|
|
|
|
static grub_uint8_t grub_gpt_magic[] = GRUB_GPT_HEADER_MAGIC;
|
|
|
|
static grub_err_t
|
|
grub_gpt_read_entries (grub_disk_t disk, grub_gpt_t gpt,
|
|
struct grub_gpt_header *header,
|
|
void **ret_entries,
|
|
grub_size_t *ret_entries_size);
|
|
|
|
char *
|
|
grub_gpt_guid_to_str (grub_gpt_part_guid_t *guid)
|
|
{
|
|
return grub_xasprintf ("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
grub_le_to_cpu32 (guid->data1),
|
|
grub_le_to_cpu16 (guid->data2),
|
|
grub_le_to_cpu16 (guid->data3),
|
|
guid->data4[0], guid->data4[1],
|
|
guid->data4[2], guid->data4[3],
|
|
guid->data4[4], guid->data4[5],
|
|
guid->data4[6], guid->data4[7]);
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_gpt_device_partentry (grub_device_t device,
|
|
struct grub_gpt_partentry *entry)
|
|
{
|
|
grub_disk_t disk = device->disk;
|
|
grub_partition_t p;
|
|
grub_err_t err;
|
|
|
|
if (!disk || !disk->partition)
|
|
return grub_error (GRUB_ERR_BUG, "not a partition");
|
|
|
|
if (grub_strcmp (disk->partition->partmap->name, "gpt"))
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, "not a GPT partition");
|
|
|
|
p = disk->partition;
|
|
disk->partition = p->parent;
|
|
err = grub_disk_read (disk, p->offset, p->index, sizeof (*entry), entry);
|
|
disk->partition = p;
|
|
|
|
return err;
|
|
}
|
|
|
|
grub_err_t
|
|
grub_gpt_part_label (grub_device_t device, char **label)
|
|
{
|
|
struct grub_gpt_partentry entry;
|
|
const grub_size_t name_len = ARRAY_SIZE (entry.name);
|
|
const grub_size_t label_len = name_len * GRUB_MAX_UTF8_PER_UTF16 + 1;
|
|
grub_size_t i;
|
|
grub_uint8_t *end;
|
|
|
|
if (grub_gpt_device_partentry (device, &entry))
|
|
return grub_errno;
|
|
|
|
*label = grub_malloc (label_len);
|
|
if (!*label)
|
|
return grub_errno;
|
|
|
|
for (i = 0; i < name_len; i++)
|
|
entry.name[i] = grub_le_to_cpu16 (entry.name[i]);
|
|
|
|
end = grub_utf16_to_utf8 ((grub_uint8_t *) *label, entry.name, name_len);
|
|
*end = '\0';
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
grub_err_t
|
|
grub_gpt_part_uuid (grub_device_t device, char **uuid)
|
|
{
|
|
struct grub_gpt_partentry entry;
|
|
|
|
if (grub_gpt_device_partentry (device, &entry))
|
|
return grub_errno;
|
|
|
|
*uuid = grub_gpt_guid_to_str (&entry.guid);
|
|
if (!*uuid)
|
|
return grub_errno;
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static struct grub_gpt_header *
|
|
grub_gpt_get_header (grub_gpt_t gpt)
|
|
{
|
|
if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID)
|
|
return &gpt->primary;
|
|
else if (gpt->status & GRUB_GPT_BACKUP_HEADER_VALID)
|
|
return &gpt->backup;
|
|
|
|
grub_error (GRUB_ERR_BUG, "No valid GPT header");
|
|
return NULL;
|
|
}
|
|
|
|
grub_err_t
|
|
grub_gpt_disk_uuid (grub_device_t device, char **uuid)
|
|
{
|
|
struct grub_gpt_header *header;
|
|
|
|
grub_gpt_t gpt = grub_gpt_read (device->disk);
|
|
if (!gpt)
|
|
goto done;
|
|
|
|
header = grub_gpt_get_header (gpt);
|
|
if (!header)
|
|
goto done;
|
|
|
|
*uuid = grub_gpt_guid_to_str (&header->guid);
|
|
|
|
done:
|
|
grub_gpt_free (gpt);
|
|
return grub_errno;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Copied from grub-core/kern/disk_common.c grub_disk_adjust_range so we can
|
|
* avoid attempting to use disk->total_sectors when GRUB won't let us.
|
|
* TODO: Why is disk->total_sectors not set to GRUB_DISK_SIZE_UNKNOWN? */
|
|
static int
|
|
grub_gpt_disk_size_valid (grub_disk_t disk)
|
|
{
|
|
grub_disk_addr_t total_sectors;
|
|
|
|
/* Transform total_sectors to number of 512B blocks. */
|
|
total_sectors = disk->total_sectors << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS);
|
|
|
|
/* Some drivers have problems with disks above reasonable.
|
|
Treat unknown as 1EiB disk. While on it, clamp the size to 1EiB.
|
|
Just one condition is enough since GRUB_DISK_UNKNOWN_SIZE << ls is always
|
|
above 9EiB.
|
|
*/
|
|
if (total_sectors > (1ULL << 51))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
grub_gpt_lecrc32 (grub_uint32_t *crc, const void *data, grub_size_t len)
|
|
{
|
|
grub_uint32_t crc32_val;
|
|
|
|
grub_crypto_hash (GRUB_MD_CRC32, &crc32_val, data, len);
|
|
|
|
/* GRUB_MD_CRC32 always uses big endian, gpt is always little. */
|
|
*crc = grub_swap_bytes32 (crc32_val);
|
|
}
|
|
|
|
static void
|
|
grub_gpt_header_lecrc32 (grub_uint32_t *crc, struct grub_gpt_header *header)
|
|
{
|
|
grub_uint32_t old, new;
|
|
|
|
/* crc32 must be computed with the field cleared. */
|
|
old = header->crc32;
|
|
header->crc32 = 0;
|
|
grub_gpt_lecrc32 (&new, header, sizeof (*header));
|
|
header->crc32 = old;
|
|
|
|
*crc = new;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (mbr->signature !=
|
|
grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE))
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid MBR signature");
|
|
|
|
for (i = 0; i < sizeof (mbr->entries); i++)
|
|
if (mbr->entries[i].type == GRUB_PC_PARTITION_TYPE_GPT_DISK)
|
|
return GRUB_ERR_NONE;
|
|
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid protective MBR");
|
|
}
|
|
|
|
static grub_uint64_t
|
|
grub_gpt_entries_size (struct grub_gpt_header *gpt)
|
|
{
|
|
return (grub_uint64_t) grub_le_to_cpu32 (gpt->maxpart) *
|
|
(grub_uint64_t) grub_le_to_cpu32 (gpt->partentry_size);
|
|
}
|
|
|
|
static grub_uint64_t
|
|
grub_gpt_entries_sectors (struct grub_gpt_header *gpt,
|
|
unsigned int log_sector_size)
|
|
{
|
|
grub_uint64_t sector_bytes, entries_bytes;
|
|
|
|
sector_bytes = 1ULL << log_sector_size;
|
|
entries_bytes = grub_gpt_entries_size (gpt);
|
|
return grub_divmod64(entries_bytes + sector_bytes - 1, sector_bytes, NULL);
|
|
}
|
|
|
|
static int
|
|
is_pow2 (grub_uint32_t n)
|
|
{
|
|
return (n & (n - 1)) == 0;
|
|
}
|
|
|
|
grub_err_t
|
|
grub_gpt_header_check (struct grub_gpt_header *gpt,
|
|
unsigned int log_sector_size)
|
|
{
|
|
grub_uint32_t crc = 0, size;
|
|
grub_uint64_t start, end;
|
|
|
|
if (grub_memcmp (gpt->magic, grub_gpt_magic, sizeof (grub_gpt_magic)) != 0)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT signature");
|
|
|
|
if (gpt->version != GRUB_GPT_HEADER_VERSION)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "unknown GPT version");
|
|
|
|
grub_gpt_header_lecrc32 (&crc, gpt);
|
|
if (gpt->crc32 != crc)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT header crc32");
|
|
|
|
/* The header size "must be greater than or equal to 92 and must be less
|
|
* than or equal to the logical block size." */
|
|
size = grub_le_to_cpu32 (gpt->headersize);
|
|
if (size < 92U || size > (1U << log_sector_size))
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT header size");
|
|
|
|
/* The partition entry size must be "a value of 128*(2^n) where n is an
|
|
* integer greater than or equal to zero (e.g., 128, 256, 512, etc.)." */
|
|
size = grub_le_to_cpu32 (gpt->partentry_size);
|
|
if (size < 128U || size % 128U || !is_pow2 (size / 128U))
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry size");
|
|
|
|
/* The minimum entries table size is specified in terms of bytes,
|
|
* regardless of how large the individual entry size is. */
|
|
if (grub_gpt_entries_size (gpt) < GRUB_GPT_DEFAULT_ENTRIES_SIZE)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry table size");
|
|
|
|
/* And of course there better be some space for partitions! */
|
|
start = grub_le_to_cpu64 (gpt->start);
|
|
end = grub_le_to_cpu64 (gpt->end);
|
|
if (start > end)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid usable sectors");
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static int
|
|
grub_gpt_headers_equal (grub_gpt_t gpt)
|
|
{
|
|
/* Assume headers passed grub_gpt_header_check so skip magic and version.
|
|
* Individual fields must be checked instead of just using memcmp because
|
|
* crc32, header, alternate, and partitions will all normally differ. */
|
|
|
|
if (gpt->primary.headersize != gpt->backup.headersize ||
|
|
gpt->primary.header_lba != gpt->backup.alternate_lba ||
|
|
gpt->primary.alternate_lba != gpt->backup.header_lba ||
|
|
gpt->primary.start != gpt->backup.start ||
|
|
gpt->primary.end != gpt->backup.end ||
|
|
gpt->primary.maxpart != gpt->backup.maxpart ||
|
|
gpt->primary.partentry_size != gpt->backup.partentry_size ||
|
|
gpt->primary.partentry_crc32 != gpt->backup.partentry_crc32)
|
|
return 0;
|
|
|
|
return grub_memcmp(&gpt->primary.guid, &gpt->backup.guid,
|
|
sizeof(grub_gpt_part_guid_t)) == 0;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_gpt_check_primary (grub_gpt_t gpt)
|
|
{
|
|
grub_uint64_t backup, primary, entries, entries_len, start, end;
|
|
|
|
primary = grub_le_to_cpu64 (gpt->primary.header_lba);
|
|
backup = grub_le_to_cpu64 (gpt->primary.alternate_lba);
|
|
entries = grub_le_to_cpu64 (gpt->primary.partitions);
|
|
entries_len = grub_gpt_entries_sectors(&gpt->primary, gpt->log_sector_size);
|
|
start = grub_le_to_cpu64 (gpt->primary.start);
|
|
end = grub_le_to_cpu64 (gpt->primary.end);
|
|
|
|
grub_dprintf ("gpt", "Primary GPT layout:\n"
|
|
"primary header = 0x%llx backup header = 0x%llx\n"
|
|
"entries location = 0x%llx length = 0x%llx\n"
|
|
"first usable = 0x%llx last usable = 0x%llx\n",
|
|
(unsigned long long) primary,
|
|
(unsigned long long) backup,
|
|
(unsigned long long) entries,
|
|
(unsigned long long) entries_len,
|
|
(unsigned long long) start,
|
|
(unsigned long long) end);
|
|
|
|
if (grub_gpt_header_check (&gpt->primary, gpt->log_sector_size))
|
|
return grub_errno;
|
|
if (primary != 1)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid primary GPT LBA");
|
|
if (entries <= 1 || entries+entries_len > start)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid entries location");
|
|
if (backup <= end)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid backup GPT LBA");
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_gpt_check_backup (grub_gpt_t gpt)
|
|
{
|
|
grub_uint64_t backup, primary, entries, entries_len, start, end;
|
|
|
|
backup = grub_le_to_cpu64 (gpt->backup.header_lba);
|
|
primary = grub_le_to_cpu64 (gpt->backup.alternate_lba);
|
|
entries = grub_le_to_cpu64 (gpt->backup.partitions);
|
|
entries_len = grub_gpt_entries_sectors(&gpt->backup, gpt->log_sector_size);
|
|
start = grub_le_to_cpu64 (gpt->backup.start);
|
|
end = grub_le_to_cpu64 (gpt->backup.end);
|
|
|
|
grub_dprintf ("gpt", "Backup GPT layout:\n"
|
|
"primary header = 0x%llx backup header = 0x%llx\n"
|
|
"entries location = 0x%llx length = 0x%llx\n"
|
|
"first usable = 0x%llx last usable = 0x%llx\n",
|
|
(unsigned long long) primary,
|
|
(unsigned long long) backup,
|
|
(unsigned long long) entries,
|
|
(unsigned long long) entries_len,
|
|
(unsigned long long) start,
|
|
(unsigned long long) end);
|
|
|
|
if (grub_gpt_header_check (&gpt->backup, gpt->log_sector_size))
|
|
return grub_errno;
|
|
if (primary != 1)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid primary GPT LBA");
|
|
if (entries <= end || entries+entries_len > backup)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid entries location");
|
|
if (backup <= end)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid backup GPT LBA");
|
|
|
|
/* If both primary and backup are valid but differ prefer the primary. */
|
|
if ((gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) &&
|
|
!grub_gpt_headers_equal (gpt))
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "backup GPT out of sync");
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_gpt_read_primary (grub_disk_t disk, grub_gpt_t gpt)
|
|
{
|
|
grub_disk_addr_t addr;
|
|
|
|
/* TODO: The gpt partmap module searches for the primary header instead
|
|
* of relying on the disk's sector size. For now trust the disk driver
|
|
* but eventually this code should match the existing behavior. */
|
|
gpt->log_sector_size = disk->log_sector_size;
|
|
|
|
grub_dprintf ("gpt", "reading primary GPT from sector 0x1\n");
|
|
|
|
addr = grub_gpt_sector_to_addr (gpt, 1);
|
|
if (grub_disk_read (disk, addr, 0, sizeof (gpt->primary), &gpt->primary))
|
|
return grub_errno;
|
|
|
|
if (grub_gpt_check_primary (gpt))
|
|
return grub_errno;
|
|
|
|
gpt->status |= GRUB_GPT_PRIMARY_HEADER_VALID;
|
|
|
|
if (grub_gpt_read_entries (disk, gpt, &gpt->primary,
|
|
&gpt->entries, &gpt->entries_size))
|
|
return grub_errno;
|
|
|
|
gpt->status |= GRUB_GPT_PRIMARY_ENTRIES_VALID;
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_gpt_read_backup (grub_disk_t disk, grub_gpt_t gpt)
|
|
{
|
|
void *entries = NULL;
|
|
grub_size_t entries_size;
|
|
grub_uint64_t sector;
|
|
grub_disk_addr_t addr;
|
|
|
|
/* Assumes gpt->log_sector_size == disk->log_sector_size */
|
|
if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID)
|
|
{
|
|
sector = grub_le_to_cpu64 (gpt->primary.alternate_lba);
|
|
if (grub_gpt_disk_size_valid (disk) && sector >= disk->total_sectors)
|
|
return grub_error (GRUB_ERR_OUT_OF_RANGE,
|
|
"backup GPT located at 0x%llx, "
|
|
"beyond last disk sector at 0x%llx",
|
|
(unsigned long long) sector,
|
|
(unsigned long long) disk->total_sectors - 1);
|
|
}
|
|
else if (grub_gpt_disk_size_valid (disk))
|
|
sector = disk->total_sectors - 1;
|
|
else
|
|
return grub_error (GRUB_ERR_OUT_OF_RANGE,
|
|
"size of disk unknown, cannot locate backup GPT");
|
|
|
|
grub_dprintf ("gpt", "reading backup GPT from sector 0x%llx\n",
|
|
(unsigned long long) sector);
|
|
|
|
addr = grub_gpt_sector_to_addr (gpt, sector);
|
|
if (grub_disk_read (disk, addr, 0, sizeof (gpt->backup), &gpt->backup))
|
|
return grub_errno;
|
|
|
|
if (grub_gpt_check_backup (gpt))
|
|
return grub_errno;
|
|
|
|
/* Ensure the backup header thinks it is located where we found it. */
|
|
if (grub_le_to_cpu64 (gpt->backup.header_lba) != sector)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid backup GPT LBA");
|
|
|
|
gpt->status |= GRUB_GPT_BACKUP_HEADER_VALID;
|
|
|
|
if (grub_gpt_read_entries (disk, gpt, &gpt->backup,
|
|
&entries, &entries_size))
|
|
return grub_errno;
|
|
|
|
if (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID)
|
|
{
|
|
if (entries_size != gpt->entries_size ||
|
|
grub_memcmp (entries, gpt->entries, entries_size) != 0)
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "backup GPT out of sync");
|
|
|
|
grub_free (entries);
|
|
}
|
|
else
|
|
{
|
|
gpt->entries = entries;
|
|
gpt->entries_size = entries_size;
|
|
}
|
|
|
|
gpt->status |= GRUB_GPT_BACKUP_ENTRIES_VALID;
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_gpt_read_entries (grub_disk_t disk, grub_gpt_t gpt,
|
|
struct grub_gpt_header *header,
|
|
void **ret_entries,
|
|
grub_size_t *ret_entries_size)
|
|
{
|
|
void *entries = NULL;
|
|
grub_uint32_t count, size, crc;
|
|
grub_uint64_t sector;
|
|
grub_disk_addr_t addr;
|
|
grub_size_t entries_size;
|
|
|
|
/* Grub doesn't include calloc, hence the manual overflow check. */
|
|
count = grub_le_to_cpu32 (header->maxpart);
|
|
size = grub_le_to_cpu32 (header->partentry_size);
|
|
entries_size = count *size;
|
|
if (size && entries_size / size != count)
|
|
{
|
|
grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
|
|
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;
|
|
|
|
sector = grub_le_to_cpu64 (header->partitions);
|
|
grub_dprintf ("gpt", "reading GPT %lu entries from sector 0x%llx\n",
|
|
(unsigned long) count,
|
|
(unsigned long long) sector);
|
|
|
|
addr = grub_gpt_sector_to_addr (gpt, sector);
|
|
if (grub_disk_read (disk, addr, 0, entries_size, entries))
|
|
goto fail;
|
|
|
|
grub_gpt_lecrc32 (&crc, entries, entries_size);
|
|
if (crc != header->partentry_crc32)
|
|
{
|
|
grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry crc32");
|
|
goto fail;
|
|
}
|
|
|
|
*ret_entries = entries;
|
|
*ret_entries_size = entries_size;
|
|
return GRUB_ERR_NONE;
|
|
|
|
fail:
|
|
grub_free (entries);
|
|
return grub_errno;
|
|
}
|
|
|
|
grub_gpt_t
|
|
grub_gpt_read (grub_disk_t disk)
|
|
{
|
|
grub_gpt_t gpt;
|
|
|
|
grub_dprintf ("gpt", "reading GPT from %s\n", disk->name);
|
|
|
|
gpt = grub_zalloc (sizeof (*gpt));
|
|
if (!gpt)
|
|
goto fail;
|
|
|
|
if (grub_disk_read (disk, 0, 0, sizeof (gpt->mbr), &gpt->mbr))
|
|
goto fail;
|
|
|
|
/* Check the MBR but errors aren't reported beyond the status bit. */
|
|
if (grub_gpt_pmbr_check (&gpt->mbr))
|
|
grub_errno = GRUB_ERR_NONE;
|
|
else
|
|
gpt->status |= GRUB_GPT_PROTECTIVE_MBR;
|
|
|
|
/* If both the primary and backup fail report the primary's error. */
|
|
if (grub_gpt_read_primary (disk, gpt))
|
|
{
|
|
grub_error_push ();
|
|
grub_gpt_read_backup (disk, gpt);
|
|
grub_error_pop ();
|
|
}
|
|
else
|
|
grub_gpt_read_backup (disk, gpt);
|
|
|
|
/* If either succeeded clear any possible error from the other. */
|
|
if (grub_gpt_primary_valid (gpt) || grub_gpt_backup_valid (gpt))
|
|
grub_errno = GRUB_ERR_NONE;
|
|
else
|
|
goto fail;
|
|
|
|
return gpt;
|
|
|
|
fail:
|
|
grub_gpt_free (gpt);
|
|
return NULL;
|
|
}
|
|
|
|
struct grub_gpt_partentry *
|
|
grub_gpt_get_partentry (grub_gpt_t gpt, grub_uint32_t n)
|
|
{
|
|
struct grub_gpt_header *header;
|
|
grub_size_t offset;
|
|
|
|
header = grub_gpt_get_header (gpt);
|
|
if (!header)
|
|
return NULL;
|
|
|
|
if (n >= grub_le_to_cpu32 (header->maxpart))
|
|
return NULL;
|
|
|
|
offset = (grub_size_t) grub_le_to_cpu32 (header->partentry_size) * n;
|
|
return (struct grub_gpt_partentry *) ((char *) gpt->entries + offset);
|
|
}
|
|
|
|
grub_err_t
|
|
grub_gpt_repair (grub_disk_t disk, grub_gpt_t gpt)
|
|
{
|
|
/* Skip if there is nothing to do. */
|
|
if (grub_gpt_both_valid (gpt))
|
|
return GRUB_ERR_NONE;
|
|
|
|
grub_dprintf ("gpt", "repairing GPT for %s\n", disk->name);
|
|
|
|
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 (grub_gpt_primary_valid (gpt))
|
|
{
|
|
grub_uint64_t backup_header;
|
|
|
|
grub_dprintf ("gpt", "primary GPT is valid\n");
|
|
|
|
/* Relocate backup to end if disk if the disk has grown. */
|
|
backup_header = grub_le_to_cpu64 (gpt->primary.alternate_lba);
|
|
if (grub_gpt_disk_size_valid (disk) &&
|
|
disk->total_sectors - 1 > backup_header)
|
|
{
|
|
backup_header = disk->total_sectors - 1;
|
|
grub_dprintf ("gpt", "backup GPT header relocated to 0x%llx\n",
|
|
(unsigned long long) backup_header);
|
|
|
|
gpt->primary.alternate_lba = grub_cpu_to_le64 (backup_header);
|
|
}
|
|
|
|
grub_memcpy (&gpt->backup, &gpt->primary, sizeof (gpt->backup));
|
|
gpt->backup.header_lba = gpt->primary.alternate_lba;
|
|
gpt->backup.alternate_lba = gpt->primary.header_lba;
|
|
gpt->backup.partitions = grub_cpu_to_le64 (backup_header -
|
|
grub_gpt_size_to_sectors (gpt, gpt->entries_size));
|
|
}
|
|
else if (grub_gpt_backup_valid (gpt))
|
|
{
|
|
grub_dprintf ("gpt", "backup GPT is valid\n");
|
|
|
|
grub_memcpy (&gpt->primary, &gpt->backup, sizeof (gpt->primary));
|
|
gpt->primary.header_lba = gpt->backup.alternate_lba;
|
|
gpt->primary.alternate_lba = gpt->backup.header_lba;
|
|
gpt->primary.partitions = grub_cpu_to_le64_compile_time (2);
|
|
}
|
|
else
|
|
return grub_error (GRUB_ERR_BUG, "No valid GPT");
|
|
|
|
if (grub_gpt_update (gpt))
|
|
return grub_errno;
|
|
|
|
grub_dprintf ("gpt", "repairing GPT for %s successful\n", disk->name);
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
grub_err_t
|
|
grub_gpt_update (grub_gpt_t gpt)
|
|
{
|
|
grub_uint32_t crc;
|
|
|
|
/* Clear status bits, require revalidation of everything. */
|
|
gpt->status &= ~(GRUB_GPT_PRIMARY_HEADER_VALID |
|
|
GRUB_GPT_PRIMARY_ENTRIES_VALID |
|
|
GRUB_GPT_BACKUP_HEADER_VALID |
|
|
GRUB_GPT_BACKUP_ENTRIES_VALID);
|
|
|
|
/* 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));
|
|
|
|
grub_gpt_lecrc32 (&crc, gpt->entries, gpt->entries_size);
|
|
gpt->primary.partentry_crc32 = crc;
|
|
gpt->backup.partentry_crc32 = crc;
|
|
|
|
grub_gpt_header_lecrc32 (&gpt->primary.crc32, &gpt->primary);
|
|
grub_gpt_header_lecrc32 (&gpt->backup.crc32, &gpt->backup);
|
|
|
|
if (grub_gpt_check_primary (gpt))
|
|
{
|
|
grub_error_push ();
|
|
return grub_error (GRUB_ERR_BUG, "Generated invalid GPT primary header");
|
|
}
|
|
|
|
gpt->status |= (GRUB_GPT_PRIMARY_HEADER_VALID |
|
|
GRUB_GPT_PRIMARY_ENTRIES_VALID);
|
|
|
|
if (grub_gpt_check_backup (gpt))
|
|
{
|
|
grub_error_push ();
|
|
return grub_error (GRUB_ERR_BUG, "Generated invalid GPT backup header");
|
|
}
|
|
|
|
gpt->status |= (GRUB_GPT_BACKUP_HEADER_VALID |
|
|
GRUB_GPT_BACKUP_ENTRIES_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 (addr == 0)
|
|
return grub_error (GRUB_ERR_BUG,
|
|
"Refusing to write GPT header to address 0x0");
|
|
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 (addr < 2)
|
|
return grub_error (GRUB_ERR_BUG,
|
|
"Refusing to write GPT entries to address 0x%llx",
|
|
(unsigned long long) addr);
|
|
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)
|
|
{
|
|
grub_uint64_t backup_header;
|
|
|
|
/* TODO: update/repair protective MBRs too. */
|
|
|
|
if (!grub_gpt_both_valid (gpt))
|
|
return grub_error (GRUB_ERR_BAD_PART_TABLE, "Invalid GPT data");
|
|
|
|
/* Write the backup GPT first so if writing fails the update is aborted
|
|
* and the primary is left intact. However if the backup location is
|
|
* inaccessible we have to just skip and hope for the best, the backup
|
|
* will need to be repaired in the OS. */
|
|
backup_header = grub_le_to_cpu64 (gpt->backup.header_lba);
|
|
if (grub_gpt_disk_size_valid (disk) &&
|
|
backup_header >= disk->total_sectors)
|
|
{
|
|
grub_printf ("warning: backup GPT located at 0x%llx, "
|
|
"beyond last disk sector at 0x%llx\n",
|
|
(unsigned long long) backup_header,
|
|
(unsigned long long) disk->total_sectors - 1);
|
|
grub_printf ("warning: only writing primary GPT, "
|
|
"the backup GPT must be repaired from the OS\n");
|
|
}
|
|
else
|
|
{
|
|
grub_dprintf ("gpt", "writing backup GPT to %s\n", disk->name);
|
|
if (grub_gpt_write_table (disk, gpt, &gpt->backup))
|
|
return grub_errno;
|
|
}
|
|
|
|
grub_dprintf ("gpt", "writing primary GPT to %s\n", disk->name);
|
|
if (grub_gpt_write_table (disk, gpt, &gpt->primary))
|
|
return grub_errno;
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
void
|
|
grub_gpt_free (grub_gpt_t gpt)
|
|
{
|
|
if (!gpt)
|
|
return;
|
|
|
|
grub_free (gpt->entries);
|
|
grub_free (gpt);
|
|
}
|