+static int mount_device(struct discover_device *dev);
+static int umount_device(struct discover_device *dev);
+
+void discover_context_add_boot_option(struct discover_context *ctx,
+ struct discover_boot_option *boot_option)
+{
+ boot_option->source = ctx->parser;
+ list_add_tail(&ctx->boot_options, &boot_option->list);
+ talloc_steal(ctx, boot_option);
+}
+
+/**
+ * device_handler_get_device_count - Get the count of current handler devices.
+ */
+
+int device_handler_get_device_count(const struct device_handler *handler)
+{
+ return handler->n_devices;
+}
+
+/**
+ * device_handler_get_device - Get a handler device by index.
+ */
+
+const struct discover_device *device_handler_get_device(
+ const struct device_handler *handler, unsigned int index)
+{
+ if (index >= handler->n_devices) {
+ assert(0 && "bad index");
+ return NULL;
+ }
+
+ return handler->devices[index];
+}
+
+struct discover_boot_option *discover_boot_option_create(
+ struct discover_context *ctx,
+ struct discover_device *device)
+{
+ struct discover_boot_option *opt;
+
+ opt = talloc_zero(ctx, struct discover_boot_option);
+ opt->option = talloc_zero(opt, struct boot_option);
+ opt->device = device;
+
+ return opt;
+}
+
+static int device_match_uuid(struct discover_device *dev, const char *uuid)
+{
+ return dev->uuid && !strcmp(dev->uuid, uuid);
+}
+
+static int device_match_label(struct discover_device *dev, const char *label)
+{
+ return dev->label && !strcmp(dev->label, label);
+}
+
+static int device_match_id(struct discover_device *dev, const char *id)
+{
+ return !strcmp(dev->device->id, id);
+}
+
+static int device_match_serial(struct discover_device *dev, const char *serial)
+{
+ const char *val = discover_device_get_param(dev, "ID_SERIAL");
+ return val && !strcmp(val, serial);
+}
+
+static struct discover_device *device_lookup(
+ struct device_handler *device_handler,
+ int (match_fn)(struct discover_device *, const char *),
+ const char *str)
+{
+ struct discover_device *dev;
+ unsigned int i;
+
+ if (!str)
+ return NULL;
+
+ for (i = 0; i < device_handler->n_devices; i++) {
+ dev = device_handler->devices[i];
+
+ if (match_fn(dev, str))
+ return dev;
+ }
+
+ return NULL;
+}
+
+struct discover_device *device_lookup_by_name(struct device_handler *handler,
+ const char *name)
+{
+ if (!strncmp(name, "/dev/", strlen("/dev/")))
+ name += strlen("/dev/");
+
+ return device_lookup_by_id(handler, name);
+}
+
+struct discover_device *device_lookup_by_uuid(
+ struct device_handler *device_handler,
+ const char *uuid)
+{
+ return device_lookup(device_handler, device_match_uuid, uuid);
+}
+
+struct discover_device *device_lookup_by_label(
+ struct device_handler *device_handler,
+ const char *label)
+{
+ return device_lookup(device_handler, device_match_label, label);
+}
+
+struct discover_device *device_lookup_by_id(
+ struct device_handler *device_handler,
+ const char *id)
+{
+ return device_lookup(device_handler, device_match_id, id);
+}
+
+struct discover_device *device_lookup_by_serial(
+ struct device_handler *device_handler,
+ const char *serial)
+{
+ return device_lookup(device_handler, device_match_serial, serial);
+}
+
+void device_handler_destroy(struct device_handler *handler)
+{
+ talloc_free(handler);
+}
+
+static int destroy_device(void *arg)
+{
+ struct discover_device *dev = arg;
+
+ umount_device(dev);
+
+ return 0;
+}
+
+struct discover_device *discover_device_create(struct device_handler *handler,
+ const char *id)
+{
+ struct discover_device *dev;
+
+ dev = device_lookup_by_id(handler, id);
+ if (dev)
+ return dev;
+
+ dev = talloc_zero(handler, struct discover_device);
+ dev->device = talloc_zero(dev, struct device);
+ dev->device->id = talloc_strdup(dev->device, id);
+ list_init(&dev->params);
+ list_init(&dev->boot_options);
+
+ talloc_set_destructor(dev, destroy_device);
+
+ return dev;
+}
+
+struct discover_device_param {
+ char *name;
+ char *value;
+ struct list_item list;
+};
+
+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->name;
+ }
+ return NULL;
+}
+
+struct device_handler *device_handler_init(struct discover_server *server,
+ struct waitset *waitset, int dry_run)
+{
+ struct device_handler *handler;
+
+ 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();
+
+ return 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);
+ }
+
+ 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 %s in %u sec", opt->option->name, sec);
+
+ 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;
+}
+
+static bool priority_match(struct boot_priority *prio,
+ struct discover_boot_option *opt)
+{
+ return prio->type == opt->device->device->type;
+}
+
+static int default_option_priority(struct discover_boot_option *opt)
+{
+ const struct config *config;
+ struct boot_priority *prio;
+ unsigned int i;
+
+ config = config_get();
+
+ for (i = 0; i < config->n_boot_priorities; i++) {
+ prio = &config->boot_priorities[i];
+ if (priority_match(prio, opt))
+ break;
+ }
+
+ return i;
+}
+
+static void set_default(struct device_handler *handler,
+ struct discover_boot_option *opt)
+{
+ if (!handler->autoboot_enabled)
+ return;
+
+ /* 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) {
+ int new_prio, cur_prio;
+
+ new_prio = default_option_priority(opt);
+ cur_prio = default_option_priority(
+ handler->default_boot_option);
+
+ if (new_prio < cur_prio) {
+ handler->default_boot_option = opt;
+ /* 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;
+
+ pb_log("Boot option %s set as default, timeout %u sec.\n",
+ opt->option->id, handler->sec_to_boot);
+
+ default_timeout(handler);
+}
+