diff --git a/.gitignore b/.gitignore
index 18ab8e812..ec536004c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,7 @@ gensymlist.sh
gentrigtables
gentrigtables.exe
gettext_strings_test
+gpt_unit_test
grub-bin2h
/grub-bios-setup
/grub-bios-setup.exe
diff --git a/Makefile.util.def b/Makefile.util.def
index a286a89cd..f3a8aec17 100644
--- a/Makefile.util.def
+++ b/Makefile.util.def
@@ -1227,6 +1227,22 @@ program = {
ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
};
+program = {
+ testcase;
+ name = gpt_unit_test;
+ common = tests/gpt_unit_test.c;
+ common = tests/lib/unit_test.c;
+ common = grub-core/disk/host.c;
+ common = grub-core/kern/emu/hostfs.c;
+ common = grub-core/lib/gpt.c;
+ common = grub-core/tests/lib/test.c;
+ ldadd = libgrubmods.a;
+ ldadd = libgrubgcry.a;
+ ldadd = libgrubkern.a;
+ ldadd = grub-core/gnulib/libgnu.a;
+ ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
+};
+
program = {
name = grub-menulst2cfg;
mansection = 1;
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 42443bc00..385f53f59 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -826,6 +826,11 @@ module = {
common = commands/gptsync.c;
};
+module = {
+ name = gpt;
+ common = lib/gpt.c;
+};
+
module = {
name = halt;
nopc = commands/halt.c;
diff --git a/grub-core/lib/gpt.c b/grub-core/lib/gpt.c
new file mode 100644
index 000000000..a308e8537
--- /dev/null
+++ b/grub-core/lib/gpt.c
@@ -0,0 +1,288 @@
+/* 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 .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static grub_uint8_t grub_gpt_magic[] = GRUB_GPT_HEADER_MAGIC;
+
+
+static grub_err_t
+grub_gpt_header_crc32 (struct grub_gpt_header *gpt, 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->final (crc32_context);
+ gpt->crc32 = old;
+
+ /* GRUB_MD_CRC32 always uses big endian, gpt is always little. */
+ *crc = grub_swap_bytes32 (*(grub_uint32_t *)
+ GRUB_MD_CRC32->read (crc32_context));
+
+ grub_free (crc32_context);
+
+ 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)
+{
+ 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");
+}
+
+grub_err_t
+grub_gpt_header_check (struct grub_gpt_header *gpt,
+ unsigned int log_sector_size)
+{
+ grub_uint32_t crc = 0, size;
+
+ 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");
+
+ if (grub_gpt_header_crc32 (gpt, &crc))
+ return grub_errno;
+
+ if (gpt->crc32 != crc)
+ return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT header crc32");
+
+ /* The header size must be between 92 and the sector 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 multiple of 128. */
+ size = grub_le_to_cpu32 (gpt->partentry_size);
+ if (size < 128 || size % 128)
+ return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry size");
+
+ 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;
+
+ 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_header_check (&gpt->primary, gpt->log_sector_size))
+ return grub_errno;
+
+ gpt->status |= GRUB_GPT_PRIMARY_HEADER_VALID;
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_gpt_read_backup (grub_disk_t disk, grub_gpt_t gpt)
+{
+ grub_uint64_t sector;
+ grub_disk_addr_t addr;
+
+ /* Assumes gpt->log_sector_size == disk->log_sector_size */
+ 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);
+ else
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "Unable to locate backup GPT");
+
+ 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_header_check (&gpt->backup, gpt->log_sector_size))
+ return grub_errno;
+
+ gpt->status |= GRUB_GPT_BACKUP_HEADER_VALID;
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_gpt_partentry *
+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;
+
+ /* 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;
+ }
+
+ entries = grub_malloc (entries_size);
+ if (!entries)
+ goto fail;
+
+ addr = grub_gpt_sector_to_addr (gpt, grub_le_to_cpu64 (header->partitions));
+ if (grub_disk_read (disk, addr, 0, entries_size, entries))
+ goto fail;
+
+ crc32_context = grub_zalloc (GRUB_MD_CRC32->contextsize);
+ if (!crc32_context)
+ 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)
+ {
+ grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry crc32");
+ goto fail;
+ }
+
+ grub_free (crc32_context);
+ return entries;
+
+fail:
+ grub_free (entries);
+ grub_free (crc32_context);
+ return NULL;
+}
+
+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)
+ 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 (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID ||
+ gpt->status & GRUB_GPT_BACKUP_HEADER_VALID)
+ grub_errno = GRUB_ERR_NONE;
+ 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);
+ }
+
+ 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;
+ }
+
+ if (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID ||
+ gpt->status & GRUB_GPT_BACKUP_ENTRIES_VALID)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return gpt;
+ }
+
+fail:
+ grub_gpt_free (gpt);
+ return NULL;
+}
+
+void
+grub_gpt_free (grub_gpt_t gpt)
+{
+ if (!gpt)
+ return;
+
+ grub_free (gpt->entries);
+ grub_free (gpt);
+}
diff --git a/include/grub/gpt_partition.h b/include/grub/gpt_partition.h
index 1b32f6725..04ed2d7f1 100644
--- a/include/grub/gpt_partition.h
+++ b/include/grub/gpt_partition.h
@@ -21,6 +21,7 @@
#include
#include
+#include
struct grub_gpt_part_type
{
@@ -50,6 +51,12 @@ typedef struct grub_gpt_part_type grub_gpt_part_type_t;
{ 0x85, 0xD2, 0xE1, 0xE9, 0x04, 0x34, 0xCF, 0xB3 } \
}
+#define GRUB_GPT_HEADER_MAGIC \
+ { 0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54 }
+
+#define GRUB_GPT_HEADER_VERSION \
+ grub_cpu_to_le32_compile_time (0x00010000U)
+
struct grub_gpt_header
{
grub_uint8_t magic[8];
@@ -78,10 +85,63 @@ struct grub_gpt_partentry
char name[72];
} GRUB_PACKED;
+/* Basic GPT partmap module. */
grub_err_t
grub_gpt_partition_map_iterate (grub_disk_t disk,
grub_partition_iterate_hook_t hook,
void *hook_data);
+/* Advanced GPT library. */
+typedef enum grub_gpt_status
+ {
+ GRUB_GPT_PROTECTIVE_MBR = 0x01,
+ GRUB_GPT_HYBRID_MBR = 0x02,
+ GRUB_GPT_PRIMARY_HEADER_VALID = 0x04,
+ GRUB_GPT_PRIMARY_ENTRIES_VALID = 0x08,
+ GRUB_GPT_BACKUP_HEADER_VALID = 0x10,
+ GRUB_GPT_BACKUP_ENTRIES_VALID = 0x20,
+ } grub_gpt_status_t;
+
+#define GRUB_GPT_MBR_VALID (GRUB_GPT_PROTECTIVE_MBR|GRUB_GPT_HYBRID_MBR)
+
+/* 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
+
+struct grub_gpt
+{
+ /* Bit field indicating which structures on disk are valid. */
+ grub_gpt_status_t status;
+
+ /* Protective or hybrid MBR. */
+ struct grub_msdos_partition_mbr mbr;
+
+ /* Each of the two GPT headers. */
+ struct grub_gpt_header primary;
+ struct grub_gpt_header backup;
+
+ /* Only need one entries table, on disk both copies are identical. */
+ struct grub_gpt_partentry *entries;
+
+ /* Logarithm of sector size, in case GPT and disk driver disagree. */
+ unsigned int log_sector_size;
+};
+typedef struct grub_gpt *grub_gpt_t;
+
+/* Translate GPT sectors to GRUB's 512 byte block addresses. */
+static inline grub_disk_addr_t
+grub_gpt_sector_to_addr (grub_gpt_t gpt, grub_uint64_t sector)
+{
+ return (sector << (gpt->log_sector_size - GRUB_DISK_SECTOR_BITS));
+}
+
+/* Allocates and fills new grub_gpt structure, free with grub_gpt_free. */
+grub_gpt_t grub_gpt_read (grub_disk_t disk);
+
+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_header_check (struct grub_gpt_header *gpt,
+ unsigned int log_sector_size);
#endif /* ! GRUB_GPT_PARTITION_HEADER */
diff --git a/po/Rules-swiss b/po/Rules-swiss
index b5fbda92b..d192f4911 100644
--- a/po/Rules-swiss
+++ b/po/Rules-swiss
@@ -3,5 +3,5 @@
DISTFILES.common.extra2 = swiss.sed Rules-swiss
de_CH.po-create: de.po swiss.sed
- LC_ALL=C.UTF-8 $(MSGFILTER) -i $< -o $(srcdir)/de_CH.po sed -f $(srcdir)/swiss.sed
+ LC_ALL=en_US.UTF-8 $(MSGFILTER) -i $< -o $(srcdir)/de_CH.po sed -f $(srcdir)/swiss.sed
diff --git a/po/Rules-translit b/po/Rules-translit
index 4f9176d30..2962163bc 100644
--- a/po/Rules-translit
+++ b/po/Rules-translit
@@ -1,16 +1,16 @@
DISTFILES.common.extra4 = hebrew.sed cyrillic.sed greek.sed arabic.sed Rules-translit
de@hebrew.po-create: de.po hebrew.sed
- LC_ALL=C.UTF-8 $(MSGFILTER) -i $< -o $(srcdir)/de@hebrew.po sed -f $(srcdir)/hebrew.sed
+ LC_ALL=en_US.UTF-8 $(MSGFILTER) -i $< -o $(srcdir)/de@hebrew.po sed -f $(srcdir)/hebrew.sed
en@hebrew.po-create: $(DOMAIN).pot hebrew.sed
- LC_ALL=C.UTF-8 $(MSGINIT) -i $(srcdir)/$(DOMAIN).pot --no-translator -l en@hebrew -o - 2>/dev/null | LC_ALL=C.UTF-8 $(MSGCONV) -t UTF-8 | LC_ALL=C.UTF-8 $(MSGFILTER) -o $(srcdir)/en@hebrew.po -i - sed -f $(srcdir)/hebrew.sed
+ LC_ALL=en_US.UTF-8 $(MSGINIT) -i $(srcdir)/$(DOMAIN).pot --no-translator -l en@hebrew -o - 2>/dev/null | LC_ALL=en_US.UTF-8 $(MSGCONV) -t UTF-8 | LC_ALL=en_US.UTF-8 $(MSGFILTER) -o $(srcdir)/en@hebrew.po -i - sed -f $(srcdir)/hebrew.sed
en@cyrillic.po-create: $(DOMAIN).pot cyrillic.sed
- LC_ALL=C.UTF-8 $(MSGINIT) -i $(srcdir)/$(DOMAIN).pot --no-translator -l en@cyrillic -o - 2>/dev/null | LC_ALL=C.UTF-8 $(MSGCONV) -t UTF-8 | LC_ALL=C.UTF-8 $(MSGFILTER) -o $(srcdir)/en@cyrillic.po -i - sed -f $(srcdir)/cyrillic.sed
+ LC_ALL=en_US.UTF-8 $(MSGINIT) -i $(srcdir)/$(DOMAIN).pot --no-translator -l en@cyrillic -o - 2>/dev/null | LC_ALL=en_US.UTF-8 $(MSGCONV) -t UTF-8 | LC_ALL=en_US.UTF-8 $(MSGFILTER) -o $(srcdir)/en@cyrillic.po -i - sed -f $(srcdir)/cyrillic.sed
en@greek.po-create: $(DOMAIN).pot greek.sed
- LC_ALL=C.UTF-8 $(MSGINIT) -i $(srcdir)/$(DOMAIN).pot --no-translator -l en@greek -o - 2>/dev/null | LC_ALL=C.UTF-8 $(MSGCONV) -t UTF-8 | LC_ALL=C.UTF-8 $(MSGFILTER) -o $(srcdir)/en@greek.po -i - sed -f $(srcdir)/greek.sed
+ LC_ALL=en_US.UTF-8 $(MSGINIT) -i $(srcdir)/$(DOMAIN).pot --no-translator -l en@greek -o - 2>/dev/null | LC_ALL=en_US.UTF-8 $(MSGCONV) -t UTF-8 | LC_ALL=en_US.UTF-8 $(MSGFILTER) -o $(srcdir)/en@greek.po -i - sed -f $(srcdir)/greek.sed
en@arabic.po-create: $(DOMAIN).pot arabic.sed
- LC_ALL=C.UTF-8 $(MSGINIT) -i $(srcdir)/$(DOMAIN).pot --no-translator -l en@arabic -o - 2>/dev/null | LC_ALL=C.UTF-8 $(MSGCONV) -t UTF-8 | LC_ALL=C.UTF-8 $(MSGFILTER) -o $(srcdir)/en@arabic.po -i - sed -f $(srcdir)/arabic.sed
+ LC_ALL=en_US.UTF-8 $(MSGINIT) -i $(srcdir)/$(DOMAIN).pot --no-translator -l en@arabic -o - 2>/dev/null | LC_ALL=en_US.UTF-8 $(MSGCONV) -t UTF-8 | LC_ALL=en_US.UTF-8 $(MSGFILTER) -o $(srcdir)/en@arabic.po -i - sed -f $(srcdir)/arabic.sed
diff --git a/tests/gpt_unit_test.c b/tests/gpt_unit_test.c
new file mode 100644
index 000000000..a824cd967
--- /dev/null
+++ b/tests/gpt_unit_test.c
@@ -0,0 +1,467 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * 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
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* from gnulib */
+#include
+
+
+/* GPT section sizes. */
+#define HEADER_SIZE (sizeof (struct grub_gpt_header))
+#define HEADER_PAD (GRUB_DISK_SECTOR_SIZE - HEADER_SIZE)
+#define ENTRY_SIZE (sizeof (struct grub_gpt_partentry))
+#define TABLE_ENTRIES 0x80
+#define TABLE_SIZE (TABLE_ENTRIES * ENTRY_SIZE)
+#define TABLE_SECTORS (TABLE_SIZE / GRUB_DISK_SECTOR_SIZE)
+
+/* Double check that the table size calculation was valid. */
+verify (TABLE_SECTORS * GRUB_DISK_SECTOR_SIZE == TABLE_SIZE);
+
+/* GPT section locations for a 1MiB disk. */
+#define DISK_SECTORS 0x800
+#define DISK_SIZE (GRUB_DISK_SECTOR_SIZE * DISK_SECTORS)
+#define PRIMARY_HEADER_SECTOR 0x1
+#define PRIMARY_TABLE_SECTOR 0x2
+#define BACKUP_HEADER_SECTOR (DISK_SECTORS - 0x1)
+#define BACKUP_TABLE_SECTOR (BACKUP_HEADER_SECTOR - TABLE_SECTORS)
+
+#define DATA_START_SECTOR (PRIMARY_TABLE_SECTOR + TABLE_SECTORS)
+#define DATA_END_SECTOR (BACKUP_TABLE_SECTOR - 0x1)
+#define DATA_SECTORS (BACKUP_TABLE_SECTOR - DATA_START_SECTOR)
+#define DATA_SIZE (GRUB_DISK_SECTOR_SIZE * DATA_SECTORS)
+
+struct test_disk
+{
+ struct grub_msdos_partition_mbr mbr;
+
+ struct grub_gpt_header primary_header;
+ grub_uint8_t primary_header_pad[HEADER_PAD];
+ struct grub_gpt_partentry primary_entries[TABLE_ENTRIES];
+
+ grub_uint8_t data[DATA_SIZE];
+
+ struct grub_gpt_partentry backup_entries[TABLE_ENTRIES];
+ struct grub_gpt_header backup_header;
+ grub_uint8_t backup_header_pad[HEADER_PAD];
+} GRUB_PACKED;
+
+/* Sanity check that all the above ugly math was correct. */
+verify (sizeof (struct test_disk) == DISK_SIZE);
+
+struct test_data
+{
+ int fd;
+ grub_device_t dev;
+ struct test_disk *raw;
+};
+
+
+/* Sample primary GPT header for an empty 1MB disk. */
+static const struct grub_gpt_header example_primary = {
+ .magic = GRUB_GPT_HEADER_MAGIC,
+ .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),
+ .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,
+ 0x93, 0xc4, 0x12, 0x4c, 0x75, 0x52, 0x56, 0xac},
+ .partitions = grub_cpu_to_le64_compile_time (PRIMARY_TABLE_SECTOR),
+ .maxpart = grub_cpu_to_le32_compile_time (TABLE_ENTRIES),
+ .partentry_size = grub_cpu_to_le32_compile_time (ENTRY_SIZE),
+ .partentry_crc32 = grub_cpu_to_le32_compile_time (0xab54d286),
+};
+
+/* And the backup header. */
+static const struct grub_gpt_header example_backup = {
+ .magic = GRUB_GPT_HEADER_MAGIC,
+ .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),
+ .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,
+ 0x93, 0xc4, 0x12, 0x4c, 0x75, 0x52, 0x56, 0xac},
+ .partitions = grub_cpu_to_le64_compile_time (BACKUP_TABLE_SECTOR),
+ .maxpart = grub_cpu_to_le32_compile_time (TABLE_ENTRIES),
+ .partentry_size = grub_cpu_to_le32_compile_time (ENTRY_SIZE),
+ .partentry_crc32 = grub_cpu_to_le32_compile_time (0xab54d286),
+};
+
+/* Sample protective MBR for the same 1MB disk. Note, this matches
+ * parted and fdisk behavior. The UEFI spec uses different values. */
+static const struct grub_msdos_partition_mbr example_pmbr = {
+ .entries = {{.flag = 0x00,
+ .start_head = 0x00,
+ .start_sector = 0x01,
+ .start_cylinder = 0x00,
+ .type = 0xee,
+ .end_head = 0xfe,
+ .end_sector = 0xff,
+ .end_cylinder = 0xff,
+ .start = grub_cpu_to_le32_compile_time (0x1),
+ .length = grub_cpu_to_le32_compile_time (DISK_SECTORS - 0x1),
+ }},
+ .signature = grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE),
+};
+
+/* If errors are left in grub's error stack things can get confused. */
+static void
+assert_error_stack_empty (void)
+{
+ do
+ {
+ grub_test_assert (grub_errno == GRUB_ERR_NONE,
+ "error on stack: %s", grub_errmsg);
+ }
+ while (grub_error_pop ());
+}
+
+static grub_err_t
+execute_command2 (const char *name, const char *arg1, const char *arg2)
+{
+ grub_command_t cmd;
+ grub_err_t err;
+ char *argv[2];
+
+ cmd = grub_command_find (name);
+ if (!cmd)
+ grub_fatal ("can't find command %s", name);
+
+ argv[0] = strdup (arg1);
+ argv[1] = strdup (arg2);
+ err = (cmd->func) (cmd, 2, argv);
+ free (argv[0]);
+ free (argv[1]);
+
+ return err;
+}
+
+static void
+sync_disk (struct test_data *data)
+{
+ if (msync (data->raw, DISK_SIZE, MS_SYNC | MS_INVALIDATE) < 0)
+ grub_fatal ("Syncing disk failed: %s", strerror (errno));
+
+ grub_disk_cache_invalidate_all ();
+}
+
+static void
+reset_disk (struct test_data *data)
+{
+ memset (data->raw, 0, DISK_SIZE);
+
+ /* Initialize image with valid example tables. */
+ memcpy (&data->raw->mbr, &example_pmbr, sizeof (data->raw->mbr));
+ memcpy (&data->raw->primary_header, &example_primary,
+ sizeof (data->raw->primary_header));
+ memcpy (&data->raw->backup_header, &example_backup,
+ sizeof (data->raw->backup_header));
+
+ sync_disk (data);
+}
+
+static void
+open_disk (struct test_data *data)
+{
+ const char *loop = "loop0";
+ char template[] = "/tmp/grub_gpt_test.XXXXXX";
+ char host[sizeof ("(host)") + sizeof (template)];
+
+ data->fd = mkstemp (template);
+ if (data->fd < 0)
+ grub_fatal ("Creating %s failed: %s", template, strerror (errno));
+
+ if (ftruncate (data->fd, DISK_SIZE) < 0)
+ {
+ int err = errno;
+ unlink (template);
+ grub_fatal ("Resizing %s failed: %s", template, strerror (err));
+ }
+
+ data->raw = mmap (NULL, DISK_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED, data->fd, 0);
+ if (data->raw == MAP_FAILED)
+ {
+ int err = errno;
+ unlink (template);
+ grub_fatal ("Maping %s failed: %s", template, strerror (err));
+ }
+
+ snprintf (host, sizeof (host), "(host)%s", template);
+ if (execute_command2 ("loopback", loop, host) != GRUB_ERR_NONE)
+ {
+ unlink (template);
+ grub_fatal ("loopback %s %s failed: %s", loop, host, grub_errmsg);
+ }
+
+ if (unlink (template) < 0)
+ grub_fatal ("Unlinking %s failed: %s", template, strerror (errno));
+
+ reset_disk (data);
+
+ data->dev = grub_device_open (loop);
+ if (!data->dev)
+ grub_fatal ("Opening %s failed: %s", loop, grub_errmsg);
+}
+
+static void
+close_disk (struct test_data *data)
+{
+ char *loop;
+
+ assert_error_stack_empty ();
+
+ if (munmap (data->raw, DISK_SIZE) || close (data->fd))
+ grub_fatal ("Closing disk image failed: %s", strerror (errno));
+
+ loop = strdup (data->dev->disk->name);
+ grub_test_assert (grub_device_close (data->dev) == GRUB_ERR_NONE,
+ "Closing disk device failed: %s", grub_errmsg);
+
+ grub_test_assert (execute_command2 ("loopback", "-d", loop) ==
+ GRUB_ERR_NONE, "loopback -d %s failed: %s", loop,
+ grub_errmsg);
+
+ free (loop);
+}
+
+static grub_gpt_t
+read_disk (struct test_data *data)
+{
+ grub_gpt_t gpt;
+
+ gpt = grub_gpt_read (data->dev->disk);
+ if (gpt == NULL)
+ {
+ grub_print_error ();
+ grub_fatal ("grub_gpt_read failed");
+ }
+
+
+ return gpt;
+}
+
+static void
+pmbr_test (void)
+{
+ struct grub_msdos_partition_mbr mbr;
+
+ memset (&mbr, 0, sizeof (mbr));
+
+ /* Empty is invalid. */
+ grub_gpt_pmbr_check (&mbr);
+ grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
+ "unexpected error: %s", grub_errmsg);
+ grub_errno = GRUB_ERR_NONE;
+
+ /* A table without a protective partition is invalid. */
+ mbr.signature = grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE);
+ grub_gpt_pmbr_check (&mbr);
+ grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
+ "unexpected error: %s", grub_errmsg);
+ grub_errno = GRUB_ERR_NONE;
+
+ /* A table with a protective type is ok. */
+ memcpy (&mbr, &example_pmbr, sizeof (mbr));
+ grub_gpt_pmbr_check (&mbr);
+ grub_test_assert (grub_errno == GRUB_ERR_NONE,
+ "unexpected error: %s", grub_errmsg);
+ grub_errno = GRUB_ERR_NONE;
+}
+
+static void
+header_test (void)
+{
+ struct grub_gpt_header primary, backup;
+
+ /* Example headers should be valid. */
+ memcpy (&primary, &example_primary, sizeof (primary));
+ grub_gpt_header_check (&primary, GRUB_DISK_SECTOR_BITS);
+ grub_test_assert (grub_errno == GRUB_ERR_NONE,
+ "unexpected error: %s", grub_errmsg);
+ grub_errno = GRUB_ERR_NONE;
+
+ memcpy (&backup, &example_backup, sizeof (backup));
+ grub_gpt_header_check (&backup, GRUB_DISK_SECTOR_BITS);
+ grub_test_assert (grub_errno == GRUB_ERR_NONE,
+ "unexpected error: %s", grub_errmsg);
+ grub_errno = GRUB_ERR_NONE;
+
+ /* Twiddle the GUID to invalidate the CRC. */
+ primary.guid[0] = 0;
+ grub_gpt_header_check (&primary, GRUB_DISK_SECTOR_BITS);
+ grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
+ "unexpected error: %s", grub_errmsg);
+ grub_errno = GRUB_ERR_NONE;
+
+ backup.guid[0] = 0;
+ grub_gpt_header_check (&backup, GRUB_DISK_SECTOR_BITS);
+ grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
+ "unexpected error: %s", grub_errmsg);
+ grub_errno = GRUB_ERR_NONE;
+}
+
+static void
+read_valid_test (void)
+{
+ struct test_data data;
+ grub_gpt_t gpt;
+
+ open_disk (&data);
+ gpt = read_disk (&data);
+ grub_test_assert (gpt->status == (GRUB_GPT_PROTECTIVE_MBR |
+ GRUB_GPT_PRIMARY_HEADER_VALID |
+ GRUB_GPT_PRIMARY_ENTRIES_VALID |
+ GRUB_GPT_BACKUP_HEADER_VALID |
+ GRUB_GPT_BACKUP_ENTRIES_VALID),
+ "unexpected status: 0x%02x", gpt->status);
+ grub_gpt_free (gpt);
+ close_disk (&data);
+}
+
+static void
+read_invalid_entries_test (void)
+{
+ struct test_data data;
+ grub_gpt_t gpt;
+
+ open_disk (&data);
+
+ /* Corrupt the first entry in both tables. */
+ memset (&data.raw->primary_entries[0], 0x55,
+ sizeof (data.raw->primary_entries[0]));
+ memset (&data.raw->backup_entries[0], 0x55,
+ sizeof (data.raw->backup_entries[0]));
+ sync_disk (&data);
+
+ gpt = grub_gpt_read (data.dev->disk);
+ grub_test_assert (gpt == NULL, "no error reported for corrupt entries");
+ grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
+ "unexpected error: %s", grub_errmsg);
+ grub_errno = GRUB_ERR_NONE;
+
+ close_disk (&data);
+}
+
+static void
+read_fallback_test (void)
+{
+ struct test_data data;
+ grub_gpt_t gpt;
+
+ open_disk (&data);
+
+ /* Corrupt the primary header. */
+ memset (&data.raw->primary_header.guid, 0x55,
+ sizeof (data.raw->primary_header.guid));
+ sync_disk (&data);
+ gpt = read_disk (&data);
+ grub_test_assert ((gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) == 0,
+ "unreported corrupt primary header");
+ grub_gpt_free (gpt);
+ reset_disk (&data);
+
+ /* Corrupt the backup header. */
+ memset (&data.raw->backup_header.guid, 0x55,
+ sizeof (data.raw->backup_header.guid));
+ sync_disk (&data);
+ gpt = read_disk (&data);
+ grub_test_assert ((gpt->status & GRUB_GPT_BACKUP_HEADER_VALID) == 0,
+ "unreported corrupt backup header");
+ grub_gpt_free (gpt);
+ reset_disk (&data);
+
+ /* Corrupt the primary entry table. */
+ memset (&data.raw->primary_entries[0], 0x55,
+ sizeof (data.raw->primary_entries[0]));
+ sync_disk (&data);
+ gpt = read_disk (&data);
+ grub_test_assert ((gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID) == 0,
+ "unreported corrupt primary entries table");
+ grub_gpt_free (gpt);
+ reset_disk (&data);
+
+ /* Corrupt the backup entry table. */
+ memset (&data.raw->backup_entries[0], 0x55,
+ sizeof (data.raw->backup_entries[0]));
+ sync_disk (&data);
+ gpt = read_disk (&data);
+ grub_test_assert ((gpt->status & GRUB_GPT_BACKUP_ENTRIES_VALID) == 0,
+ "unreported corrupt backup entries table");
+ grub_gpt_free (gpt);
+ reset_disk (&data);
+
+ /* If primary is corrupt and disk size is unknown fallback fails. */
+ memset (&data.raw->primary_header.guid, 0x55,
+ sizeof (data.raw->primary_header.guid));
+ sync_disk (&data);
+ data.dev->disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN;
+ gpt = grub_gpt_read (data.dev->disk);
+ grub_test_assert (gpt == NULL, "no error reported");
+ grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
+ "unexpected error: %s", grub_errmsg);
+ grub_errno = GRUB_ERR_NONE;
+
+ close_disk (&data);
+}
+
+void
+grub_unit_test_init (void)
+{
+ grub_init_all ();
+ grub_hostfs_init ();
+ grub_host_init ();
+ grub_test_register ("gpt_pmbr_test", pmbr_test);
+ grub_test_register ("gpt_header_test", header_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_fallback_test", read_fallback_test);
+}
+
+void
+grub_unit_test_fini (void)
+{
+ grub_test_unregister ("gpt_pmbr_test");
+ grub_test_unregister ("gpt_header_test");
+ grub_test_unregister ("gpt_read_valid_test");
+ grub_test_unregister ("gpt_read_invalid_test");
+ grub_test_unregister ("gpt_read_fallback_test");
+ grub_fini_all ();
+}