+void discover_device_set_param(struct discover_device *device,
+ const char *name, const char *value)
+{
+ struct discover_device_param *param;
+ bool found = false;
+
+ list_for_each_entry(&device->params, param, list) {
+ if (!strcmp(param->name, name)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (!value)
+ return;
+ param = talloc(device, struct discover_device_param);
+ param->name = talloc_strdup(param, name);
+ list_add(&device->params, ¶m->list);
+ } else {
+ if (!value) {
+ list_remove(¶m->list);
+ talloc_free(param);
+ return;
+ }
+ talloc_free(param->value);
+ }
+
+ param->value = talloc_strdup(param, value);
+}
+
+const char *discover_device_get_param(struct discover_device *device,
+ const char *name)
+{
+ struct discover_device_param *param;
+
+ list_for_each_entry(&device->params, param, list) {
+ if (!strcmp(param->name, name))
+ return param->value;
+ }
+ return NULL;
+}
+
+struct device_handler *device_handler_init(struct discover_server *server,
+ struct waitset *waitset, int dry_run)
+{
+ struct device_handler *handler;
+ int rc;
+
+ handler = talloc_zero(NULL, struct device_handler);
+ handler->server = server;
+ handler->waitset = waitset;
+ handler->dry_run = dry_run;
+ handler->autoboot_enabled = config_get()->autoboot_enabled;
+
+ list_init(&handler->unresolved_boot_options);
+
+ /* set up our mount point base */
+ pb_mkdir_recursive(mount_base());
+
+ parser_init();
+
+ if (config_get()->safe_mode)
+ return handler;
+
+ rc = device_handler_init_sources(handler);
+ if (rc) {
+ talloc_free(handler);
+ return NULL;
+ }
+
+ return handler;
+}
+
+void device_handler_reinit(struct device_handler *handler)
+{
+ struct discover_boot_option *opt, *tmp;
+ unsigned int i;
+
+ device_handler_cancel_default(handler);
+
+ /* free unresolved boot options */
+ list_for_each_entry_safe(&handler->unresolved_boot_options,
+ opt, tmp, list)
+ talloc_free(opt);
+ list_init(&handler->unresolved_boot_options);
+
+ /* drop all devices */
+ for (i = 0; i < handler->n_devices; i++)
+ discover_server_notify_device_remove(handler->server,
+ handler->devices[i]->device);
+
+ talloc_free(handler->devices);
+ handler->devices = NULL;
+ handler->n_devices = 0;
+
+ device_handler_reinit_sources(handler);
+}
+
+void device_handler_remove(struct device_handler *handler,
+ struct discover_device *device)
+{
+ struct discover_boot_option *opt, *tmp;
+ unsigned int i;
+
+ for (i = 0; i < handler->n_devices; i++)
+ if (handler->devices[i] == device)
+ break;
+
+ if (i == handler->n_devices) {
+ talloc_free(device);
+ return;
+ }
+
+ /* Free any unresolved options, as they're currently allocated
+ * against the handler */
+ list_for_each_entry_safe(&handler->unresolved_boot_options,
+ opt, tmp, list) {
+ if (opt->device != device)
+ continue;
+ list_remove(&opt->list);
+ talloc_free(opt);
+ }
+
+ /* if this is a network device, we have to unregister it from the
+ * network code */
+ if (device->device->type == DEVICE_TYPE_NETWORK)
+ network_unregister_device(handler->network, device);
+
+ handler->n_devices--;
+ memmove(&handler->devices[i], &handler->devices[i + 1],
+ (handler->n_devices - i) * sizeof(handler->devices[0]));
+ handler->devices = talloc_realloc(handler, handler->devices,
+ struct discover_device *, handler->n_devices);
+
+ if (device->notified)
+ discover_server_notify_device_remove(handler->server,
+ device->device);
+
+ talloc_free(device);
+}
+
+static void boot_status(void *arg, struct boot_status *status)
+{
+ struct device_handler *handler = arg;
+
+ discover_server_notify_boot_status(handler->server, status);
+}
+
+static void countdown_status(struct device_handler *handler,
+ struct discover_boot_option *opt, unsigned int sec)
+{
+ struct boot_status status;
+
+ status.type = BOOT_STATUS_INFO;
+ status.progress = -1;
+ status.detail = NULL;
+ status.message = talloc_asprintf(handler,
+ _("Booting in %d sec: %s"), sec, opt->option->name);
+
+ discover_server_notify_boot_status(handler->server, &status);
+
+ talloc_free(status.message);
+}
+
+static int default_timeout(void *arg)
+{
+ struct device_handler *handler = arg;
+ struct discover_boot_option *opt;
+
+ if (!handler->default_boot_option)
+ return 0;
+
+ if (handler->pending_boot)
+ return 0;
+
+ opt = handler->default_boot_option;
+
+ if (handler->sec_to_boot) {
+ countdown_status(handler, opt, handler->sec_to_boot);
+ handler->sec_to_boot--;
+ handler->timeout_waiter = waiter_register_timeout(
+ handler->waitset, 1000,
+ default_timeout, handler);
+ return 0;
+ }
+
+ handler->timeout_waiter = NULL;
+
+ pb_log("Timeout expired, booting default option %s\n", opt->option->id);
+
+ handler->pending_boot = boot(handler, handler->default_boot_option,
+ NULL, handler->dry_run, boot_status, handler);
+ handler->pending_boot_is_default = true;
+ return 0;
+}
+
+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_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 bool priority_matches(struct boot_priority *prio,
+ struct discover_boot_option *opt)
+{
+ return prio->type == opt->device->device->type ||
+ prio->type == DEVICE_TYPE_ANY;
+}
+
+/*
+ * 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;
+ const char *dev_str;
+ unsigned int i;
+
+ 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, allow matching by device UUID. If we have one set but it
+ * doesn't match, disallow the default entirely */
+ dev_str = config->boot_device;
+ if (dev_str && dev_str[0]) {
+ if (!strcmp(opt->device->uuid, dev_str))
+ return DEFAULT_PRIORITY_LOCAL_UUID;
+
+ pb_debug("handler: disabled default priority due to "
+ "non-matching UUID\n");
+ return DEFAULT_PRIORITY_DISABLED;
+ }
+
+ /* Lastly, use the local priorities */
+ for (i = 0; i < config->n_boot_priorities; i++) {
+ struct boot_priority *prio = &config->boot_priorities[i];
+ if (priority_matches(prio, opt))
+ return DEFAULT_PRIORITY_LOCAL_FIRST + prio->priority;
+ }
+
+ 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;
+ }
+
+ return;
+ }
+
+ handler->sec_to_boot = config_get()->autoboot_timeout_sec;
+ handler->default_boot_option = opt;
+ handler->default_boot_option_priority = new_prio;
+
+ pb_log("handler: boot option %s set as default, timeout %u sec.\n",
+ opt->option->id, handler->sec_to_boot);
+
+ default_timeout(handler);
+}
+