diff --git a/Makefile.util.def b/Makefile.util.def
index ab056f8ac..4d0b20410 100644
--- a/Makefile.util.def
+++ b/Makefile.util.def
@@ -1156,6 +1156,12 @@ script = {
common = tests/gptrepair_test.in;
};
+script = {
+ testcase;
+ name = gptprio_test;
+ common = tests/gptprio_test.in;
+};
+
program = {
testcase;
name = example_unit_test;
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index f6ae77fe7..ed97cb7a7 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -831,6 +831,11 @@ module = {
common = commands/gptrepair.c;
};
+module = {
+ name = gptprio;
+ common = commands/gptprio.c;
+};
+
module = {
name = gpt;
common = lib/gpt.c;
diff --git a/grub-core/commands/gptprio.c b/grub-core/commands/gptprio.c
new file mode 100644
index 000000000..29bd11d68
--- /dev/null
+++ b/grub-core/commands/gptprio.c
@@ -0,0 +1,238 @@
+/* gptprio.c - manage priority based partition selection. */
+/*
+ * 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
+#include
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static const struct grub_arg_option options_next[] = {
+ {"set-device", 'd', 0,
+ N_("Set a variable to the name of selected partition."),
+ N_("VARNAME"), ARG_TYPE_STRING},
+ {"set-uuid", 'u', 0,
+ N_("Set a variable to the GPT UUID of selected partition."),
+ N_("VARNAME"), ARG_TYPE_STRING},
+ {0, 0, 0, 0, 0, 0}
+};
+
+enum options_next
+{
+ NEXT_SET_DEVICE,
+ NEXT_SET_UUID,
+};
+
+static unsigned int
+grub_gptprio_priority (struct grub_gpt_partentry *entry)
+{
+ return (unsigned int) grub_gpt_entry_attribute
+ (entry, GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_PRIORITY, 4);
+}
+
+static unsigned int
+grub_gptprio_tries_left (struct grub_gpt_partentry *entry)
+{
+ return (unsigned int) grub_gpt_entry_attribute
+ (entry, GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_TRIES_LEFT, 4);
+}
+
+static void
+grub_gptprio_set_tries_left (struct grub_gpt_partentry *entry,
+ unsigned int tries_left)
+{
+ grub_gpt_entry_set_attribute
+ (entry, tries_left, GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_TRIES_LEFT, 4);
+}
+
+static unsigned int
+grub_gptprio_successful (struct grub_gpt_partentry *entry)
+{
+ return (unsigned int) grub_gpt_entry_attribute
+ (entry, GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_SUCCESSFUL, 1);
+}
+
+static grub_err_t
+grub_find_next (const char *disk_name,
+ const grub_gpt_part_type_t *part_type,
+ char **part_name, char **part_guid)
+{
+ struct grub_gpt_partentry *part_found = NULL;
+ grub_device_t dev = NULL;
+ grub_gpt_t gpt = NULL;
+ grub_uint32_t i, part_index;
+
+ dev = grub_device_open (disk_name);
+ if (!dev)
+ goto done;
+
+ gpt = grub_gpt_read (dev->disk);
+ if (!gpt)
+ goto done;
+
+ if (!(gpt->status & GRUB_GPT_BOTH_VALID))
+ if (grub_gpt_repair (dev->disk, gpt))
+ goto done;
+
+ for (i = 0; i < grub_le_to_cpu32 (gpt->primary.maxpart); i++)
+ {
+ struct grub_gpt_partentry *part = &gpt->entries[i];
+
+ if (grub_memcmp (part_type, &part->type, sizeof (*part_type)) == 0)
+ {
+ unsigned int priority, tries_left, successful, old_priority = 0;
+
+ priority = grub_gptprio_priority (part);
+ tries_left = grub_gptprio_tries_left (part);
+ successful = grub_gptprio_successful (part);
+
+ if (part_found)
+ old_priority = grub_gptprio_priority (part_found);
+
+ if ((tries_left || successful) && priority > old_priority)
+ {
+ part_index = i;
+ part_found = part;
+ }
+ }
+ }
+
+ if (!part_found)
+ {
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("no such partition"));
+ goto done;
+ }
+
+ if (grub_gptprio_tries_left (part_found))
+ {
+ unsigned int tries_left = grub_gptprio_tries_left (part_found);
+
+ grub_gptprio_set_tries_left (part_found, tries_left - 1);
+
+ if (grub_gpt_update_checksums (gpt))
+ goto done;
+
+ if (grub_gpt_write (dev->disk, gpt))
+ goto done;
+ }
+
+ *part_name = grub_xasprintf ("%s,gpt%u", disk_name, part_index + 1);
+ if (!*part_name)
+ goto done;
+
+ *part_guid =
+ grub_xasprintf ("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ grub_le_to_cpu32 (part_found->guid.data1),
+ grub_le_to_cpu16 (part_found->guid.data2),
+ grub_le_to_cpu16 (part_found->guid.data3),
+ part_found->guid.data4[0],
+ part_found->guid.data4[1],
+ part_found->guid.data4[2],
+ part_found->guid.data4[3],
+ part_found->guid.data4[4],
+ part_found->guid.data4[5],
+ part_found->guid.data4[6],
+ part_found->guid.data4[7]);
+ if (!*part_name)
+ goto done;
+
+ grub_errno = GRUB_ERR_NONE;
+
+done:
+ grub_gpt_free (gpt);
+
+ if (dev)
+ grub_device_close (dev);
+
+ return grub_errno;
+}
+
+
+
+static grub_err_t
+grub_cmd_next (grub_extcmd_context_t ctxt, int argc, char **args)
+{
+ struct grub_arg_list *state = ctxt->state;
+ char *p, *root = NULL, *part_name = NULL, *part_guid = NULL;
+
+ /* TODO: Add a uuid parser and a command line flag for providing type. */
+ grub_gpt_part_type_t part_type = GRUB_GPT_PARTITION_TYPE_USR_X86_64;
+
+ if (!state[NEXT_SET_DEVICE].set || !state[NEXT_SET_UUID].set)
+ {
+ grub_error (GRUB_ERR_INVALID_COMMAND, N_("-d and -u are required"));
+ goto done;
+ }
+
+ if (argc == 0)
+ root = grub_strdup (grub_env_get ("root"));
+ else if (argc == 1)
+ root = grub_strdup (args[0]);
+ else
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unexpected arguments"));
+ goto done;
+ }
+
+ if (!root)
+ goto done;
+
+ /* To make using $root practical strip off the partition name. */
+ p = grub_strchr (root, ',');
+ if (p)
+ *p = '\0';
+
+ if (grub_find_next (root, &part_type, &part_name, &part_guid))
+ goto done;
+
+ if (grub_env_set (state[NEXT_SET_DEVICE].arg, part_name))
+ goto done;
+
+ if (grub_env_set (state[NEXT_SET_UUID].arg, part_guid))
+ goto done;
+
+ grub_errno = GRUB_ERR_NONE;
+
+done:
+ grub_free (root);
+ grub_free (part_name);
+ grub_free (part_guid);
+
+ return grub_errno;
+}
+
+static grub_extcmd_t cmd_next;
+
+GRUB_MOD_INIT(gptprio)
+{
+ cmd_next = grub_register_extcmd ("gptprio.next", grub_cmd_next, 0,
+ N_("-d VARNAME -u VARNAME [DEVICE]"),
+ N_("Select next partition to boot."),
+ options_next);
+}
+
+GRUB_MOD_FINI(gptprio)
+{
+ grub_unregister_extcmd (cmd_next);
+}
diff --git a/include/grub/gpt_partition.h b/include/grub/gpt_partition.h
index faabaf05c..50592d6d0 100644
--- a/include/grub/gpt_partition.h
+++ b/include/grub/gpt_partition.h
@@ -53,6 +53,10 @@ typedef struct grub_gpt_guid grub_gpt_part_type_t;
GRUB_GPT_GUID_INIT (0x5808c8aa, 0x7e8f, 0x42e0, \
0x85, 0xd2, 0xe1, 0xe9, 0x04, 0x34, 0xcf, 0xb3)
+#define GRUB_GPT_PARTITION_TYPE_USR_X86_64 \
+ GRUB_GPT_GUID_INIT (0x5dfbf5f4, 0x2848, 0x4bac, \
+ 0xaa, 0x5e, 0x0d, 0x9a, 0x20, 0xb7, 0x45, 0xa6)
+
#define GRUB_GPT_HEADER_MAGIC \
{ 0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54 }
@@ -87,6 +91,51 @@ struct grub_gpt_partentry
char name[72];
} GRUB_PACKED;
+enum grub_gpt_part_attr_offset
+{
+ /* Standard partition attribute bits defined by UEFI. */
+ GRUB_GPT_PART_ATTR_OFFSET_REQUIRED = 0,
+ GRUB_GPT_PART_ATTR_OFFSET_NO_BLOCK_IO_PROTOCOL = 1,
+ GRUB_GPT_PART_ATTR_OFFSET_LEGACY_BIOS_BOOTABLE = 2,
+
+ /* De facto standard attribute bits defined by Microsoft and reused by
+ * http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec */
+ GRUB_GPT_PART_ATTR_OFFSET_READ_ONLY = 60,
+ GRUB_GPT_PART_ATTR_OFFSET_NO_AUTO = 63,
+
+ /* Partition attributes for priority based selection,
+ * Currently only valid for PARTITION_TYPE_USR_X86_64.
+ * TRIES_LEFT and PRIORITY are 4 bit wide fields. */
+ GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_PRIORITY = 48,
+ GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_TRIES_LEFT = 52,
+ GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_SUCCESSFUL = 56,
+};
+
+/* Helpers for reading/writing partition attributes. */
+static inline grub_uint64_t
+grub_gpt_entry_attribute (struct grub_gpt_partentry *entry,
+ enum grub_gpt_part_attr_offset offset,
+ unsigned int bits)
+{
+ grub_uint64_t attrib = grub_le_to_cpu64 (entry->attrib);
+
+ return (attrib >> offset) & ((1ULL << bits) - 1);
+}
+
+static inline void
+grub_gpt_entry_set_attribute (struct grub_gpt_partentry *entry,
+ grub_uint64_t value,
+ enum grub_gpt_part_attr_offset offset,
+ unsigned int bits)
+{
+ grub_uint64_t attrib, mask;
+
+ mask = (((1ULL << bits) - 1) << offset);
+ attrib = grub_le_to_cpu64 (entry->attrib) & ~mask;
+ attrib |= ((value << offset) & mask);
+ entry->attrib = grub_cpu_to_le64 (attrib);
+}
+
/* Basic GPT partmap module. */
grub_err_t
grub_gpt_partition_map_iterate (grub_disk_t disk,
diff --git a/tests/gptprio_test.in b/tests/gptprio_test.in
new file mode 100644
index 000000000..f4aea0dc9
--- /dev/null
+++ b/tests/gptprio_test.in
@@ -0,0 +1,150 @@
+#! /bin/bash
+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 .
+
+sgdisk=sgdisk
+grubshell=@builddir@/grub-shell
+
+if ! which "${sgdisk}" >/dev/null 2>&1; then
+ echo "sgdisk not installed; cannot test gptprio."
+ exit 77
+fi
+
+. "@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
+trap "rm -f '${img1}'" EXIT
+
+prio_type="5dfbf5f4-2848-4bac-aa5e-0d9a20b745a6"
+declare -a prio_uuid
+prio_uuid[2]="9b003904-d006-4ab3-97f1-73f547b7af1a"
+prio_uuid[3]="1aa5a658-5b02-414d-9b71-f7e6c151f0cd"
+prio_uuid[4]="8aa0240d-98af-42b0-b32a-ccbe0572d62b"
+
+create_disk_image () {
+ rm -f "${img1}"
+ dd if=/dev/zero of="${img1}" bs=512 count=1 seek=100 status=none
+ ${sgdisk} \
+ -n 1:0:+1 -c 1:ESP -t 1:ef00 \
+ -n 2:0:+1 -c 2:A -t 2:"${prio_type}" -u 2:"${prio_uuid[2]}" \
+ -n 3:0:+1 -c 3:B -t 3:"${prio_type}" -u 3:"${prio_uuid[3]}" \
+ -n 4:0:+1 -c 4:C -t 4:"${prio_type}" -u 4:"${prio_uuid[4]}" \
+ "${img1}" >/dev/null
+}
+
+
+fmt_prio () {
+ priority=$(( ( $1 & 15 ) << 48 ))
+ tries=$(( ( $2 & 15 ) << 52 ))
+ success=$(( ( $3 & 1 ) << 56 ))
+ printf %016x $(( priority | tries | success ))
+}
+
+set_prio () {
+ part="$1"
+ attr=$(fmt_prio $2 $3 $4)
+ ${sgdisk} -A "${part}:=:${attr}" "${img1}" >/dev/null
+}
+
+check_prio () {
+ part="$1"
+ expect=$(fmt_prio $2 $3 $4)
+ result=$(LANG=C ${sgdisk} -i "${part}" "${img1}" \
+ | awk '/^Attribute flags: / {print $3}')
+ if [[ "${expect}" != "${result}" ]]; then
+ echo "Partition ${part} has attributes ${result}, not ${expect}" >&2
+ exit 1
+ fi
+}
+
+run_next() {
+ "${grubshell}" --disk="${img1}" --modules=gptprio <