+struct {
+ enum ipmi_bootdev ipmi_type;
+ enum device_type device_type;
+} device_type_map[] = {
+ { IPMI_BOOTDEV_NETWORK, DEVICE_TYPE_NETWORK },
+ { IPMI_BOOTDEV_DISK, DEVICE_TYPE_DISK },
+ { IPMI_BOOTDEV_DISK, DEVICE_TYPE_USB },
+ { IPMI_BOOTDEV_CDROM, DEVICE_TYPE_OPTICAL },
+};
+
+static bool ipmi_device_type_matches(enum ipmi_bootdev ipmi_type,
+ enum device_type device_type)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(device_type_map); i++) {
+ if (device_type_map[i].device_type == device_type)
+ return device_type_map[i].ipmi_type == ipmi_type;
+ }
+
+ return false;
+}
+
+static int autoboot_option_priority(const struct config *config,
+ struct discover_boot_option *opt)
+{
+ enum device_type type = opt->device->device->type;
+ const char *uuid = opt->device->uuid;
+ struct autoboot_option *auto_opt;
+ unsigned int i;
+
+ for (i = 0; i < config->n_autoboot_opts; i++) {
+ auto_opt = &config->autoboot_opts[i];
+ if (auto_opt->boot_type == BOOT_DEVICE_UUID)
+ if (!strcmp(auto_opt->uuid, uuid))
+ return DEFAULT_PRIORITY_LOCAL_FIRST + i;
+
+ if (auto_opt->boot_type == BOOT_DEVICE_TYPE)
+ if (auto_opt->type == type ||
+ auto_opt->type == DEVICE_TYPE_ANY)
+ return DEFAULT_PRIORITY_LOCAL_FIRST + i;
+ }
+
+ return -1;
+}
+
+/*
+ * We have different priorities to resolve conflicts between boot options that
+ * report to be the default for their device. This function assigns a priority
+ * for these options.
+ */
+static enum default_priority default_option_priority(
+ struct discover_boot_option *opt)
+{
+ const struct config *config;
+
+ config = config_get();
+
+ /* We give highest priority to IPMI-configured boot options. If
+ * we have an IPMI bootdev configuration set, then we don't allow
+ * any other defaults */
+ if (config->ipmi_bootdev) {
+ bool ipmi_match = ipmi_device_type_matches(config->ipmi_bootdev,
+ opt->device->device->type);
+ if (ipmi_match)
+ return DEFAULT_PRIORITY_REMOTE;
+
+ pb_debug("handler: disabled default priority due to "
+ "non-matching IPMI type %x\n",
+ config->ipmi_bootdev);
+ return DEFAULT_PRIORITY_DISABLED;
+ }
+
+ /* Next, try to match the option against the user-defined autoboot
+ * options, either by device UUID or type. */
+ if (config->n_autoboot_opts) {
+ int boot_match = autoboot_option_priority(config, opt);
+ if (boot_match > 0)
+ return boot_match;
+ }
+
+ /* If the option didn't match any entry in the array, it is disabled */
+ pb_debug("handler: disabled default priority due to "
+ "non-matching UUID or type\n");
+ return DEFAULT_PRIORITY_DISABLED;
+}
+
+static void set_default(struct device_handler *handler,
+ struct discover_boot_option *opt)
+{
+ enum default_priority cur_prio, new_prio;
+
+ if (!handler->autoboot_enabled)
+ return;
+
+ pb_debug("handler: new default option: %s\n", opt->option->id);
+
+ new_prio = default_option_priority(opt);
+
+ /* Anything outside our range prevents a default boot */
+ if (new_prio >= DEFAULT_PRIORITY_DISABLED)
+ return;
+
+ pb_debug("handler: calculated priority %d\n", new_prio);
+
+ /* Resolve any conflicts: if we have a new default option, it only
+ * replaces the current if it has a higher priority. */
+ if (handler->default_boot_option) {
+
+ cur_prio = handler->default_boot_option_priority;
+
+ if (new_prio < cur_prio) {
+ pb_log("handler: new prio %d beats "
+ "old prio %d for %s\n",
+ new_prio, cur_prio,
+ handler->default_boot_option
+ ->option->id);
+ handler->default_boot_option = opt;
+ handler->default_boot_option_priority = new_prio;
+ /* extend the timeout a little, so the user sees some
+ * indication of the change */
+ handler->sec_to_boot += 2;