From 28b0af948e5e0796781a45b7ce5151b5bda9ae5b Mon Sep 17 00:00:00 2001 From: Michael Marineau Date: Mon, 29 Sep 2014 15:30:37 -0700 Subject: [PATCH 1/2] linguas: use en_US as UTF-8 locale, C.UTF-8 is not a standard locale. Apparently some distros like Debian provide C.UTF-8 but it isn't actually fully supported by glibc and thus is not available on all systems, neither Gentoo nor Fedora provide it. https://sourceware.org/bugzilla/show_bug.cgi?id=17318 --- po/Rules-swiss | 2 +- po/Rules-translit | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) 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 From f82f65f338b5f7d4cf7a81a2e9d3dd6d0dfe0b28 Mon Sep 17 00:00:00 2001 From: Michael Marineau Date: Sun, 28 Sep 2014 21:26:21 -0700 Subject: [PATCH 2/2] gpt: start new GPT module This module is a new implementation for reading GUID Partition Tables which is much stricter than the existing part_gpt module and exports GPT data directly instead of the generic grub_partition structure. It will be the basis for modules that need to read/write/update GPT data. The current code does nothing more than read and verify the table. --- .gitignore | 1 + Makefile.util.def | 16 ++ grub-core/Makefile.core.def | 5 + grub-core/lib/gpt.c | 288 +++++++++++++++++++++ include/grub/gpt_partition.h | 60 +++++ tests/gpt_unit_test.c | 467 +++++++++++++++++++++++++++++++++++ 6 files changed, 837 insertions(+) create mode 100644 grub-core/lib/gpt.c create mode 100644 tests/gpt_unit_test.c 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/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 (); +}