diff --git a/Makefile.util.def b/Makefile.util.def index f3a8aec17..ab056f8ac 100644 --- a/Makefile.util.def +++ b/Makefile.util.def @@ -1150,6 +1150,12 @@ script = { common = tests/grub_cmd_tr.in; }; +script = { + testcase; + name = gptrepair_test; + common = tests/gptrepair_test.in; +}; + program = { testcase; name = example_unit_test; diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index 385f53f59..7f309b830 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -826,6 +826,11 @@ module = { common = commands/gptsync.c; }; +module = { + name = gptrepair; + common = commands/gptrepair.c; +}; + module = { name = gpt; common = lib/gpt.c; diff --git a/grub-core/commands/gptrepair.c b/grub-core/commands/gptrepair.c new file mode 100644 index 000000000..38392fd8f --- /dev/null +++ b/grub-core/commands/gptrepair.c @@ -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 . + */ + +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/grub-core/lib/gpt.c b/grub-core/lib/gpt.c index 2d61df488..67ffdf703 100644 --- a/grub-core/lib/gpt.c +++ b/grub-core/lib/gpt.c @@ -357,10 +357,46 @@ grub_gpt_repair (grub_disk_t disk, grub_gpt_t gpt) 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_PRIMARY_HEADER_VALID | - GRUB_GPT_PRIMARY_ENTRIES_VALID | - GRUB_GPT_BACKUP_HEADER_VALID | - GRUB_GPT_BACKUP_ENTRIES_VALID); + 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; } diff --git a/include/grub/gpt_partition.h b/include/grub/gpt_partition.h index 62d027e4e..3cac6df32 100644 --- a/include/grub/gpt_partition.h +++ b/include/grub/gpt_partition.h @@ -103,6 +103,11 @@ typedef enum grub_gpt_status } grub_gpt_status_t; #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 * total of 128 entries given the standard 128 byte entry size. */ @@ -144,6 +149,9 @@ 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); grub_err_t grub_gpt_pmbr_check (struct grub_msdos_partition_mbr *mbr); diff --git a/tests/gptrepair_test.in b/tests/gptrepair_test.in new file mode 100644 index 000000000..80b2de633 --- /dev/null +++ b/tests/gptrepair_test.in @@ -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 . + +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