+ return handler;
+}
+
+void device_handler_reinit(struct device_handler *handler)
+{
+ struct discover_boot_option *opt, *tmp;
+ struct ramdisk_device *ramdisk;
+ unsigned int i;
+
+ device_handler_cancel_default(handler);
+ /* Cancel any pending non-default boot */
+ if (handler->pending_boot) {
+ boot_cancel(handler->pending_boot);
+ handler->pending_boot = NULL;
+ handler->pending_boot_is_default = false;
+ }
+
+ /* Cancel any remaining async jobs */
+ process_stop_async_all();
+ pending_network_jobs_cancel();
+
+ /* 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);
+ ramdisk = handler->devices[i]->ramdisk;
+ talloc_free(handler->devices[i]);
+ talloc_free(ramdisk);
+ }
+
+ talloc_free(handler->devices);
+ handler->devices = NULL;
+ handler->n_devices = 0;
+ talloc_free(handler->ramdisks);
+ handler->ramdisks = NULL;
+ handler->n_ramdisks = 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;
+
+ list_for_each_entry_safe(&device->boot_options, opt, tmp, list) {
+ if (opt == handler->default_boot_option) {
+ pb_log("Default option %s cancelled since device removed",
+ opt->option->name);
+ device_handler_cancel_default(handler);
+ break;
+ }
+ }
+
+ 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);
+}
+
+void device_handler_status(struct device_handler *handler,
+ struct status *status)
+{
+ discover_server_notify_boot_status(handler->server, status);
+}
+
+static void _device_handler_vstatus(struct device_handler *handler,
+ enum status_type type, const char *fmt, va_list ap)
+{
+ struct status status;
+
+ status.type = type;
+ status.message = talloc_vasprintf(handler, fmt, ap);
+ status.backlog = false;
+
+ device_handler_status(handler, &status);
+
+ talloc_free(status.message);
+}
+
+static void _device_handler_vdevstatus(struct device_handler *handler,
+ struct discover_device *device, enum status_type type,
+ const char *fmt, va_list ap)
+{
+ char *msg;
+
+ msg = talloc_asprintf(handler, "[%s] %s",
+ device ? device->device->id : "unknown", fmt);
+ _device_handler_vstatus(handler, type, msg, ap);
+ talloc_free(msg);
+}
+
+void device_handler_status_dev_info(struct device_handler *handler,
+ struct discover_device *dev, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ _device_handler_vdevstatus(handler, dev, STATUS_INFO, fmt, ap);
+ va_end(ap);
+}
+
+void device_handler_status_dev_err(struct device_handler *handler,
+ struct discover_device *dev, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ _device_handler_vdevstatus(handler, dev, STATUS_ERROR, fmt, ap);
+ va_end(ap);
+}
+
+void device_handler_status_info(struct device_handler *handler,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ _device_handler_vstatus(handler, STATUS_INFO, fmt, ap);
+ va_end(ap);
+}
+
+void device_handler_status_err(struct device_handler *handler,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ _device_handler_vstatus(handler, STATUS_ERROR, fmt, ap);
+ va_end(ap);
+}
+
+void device_handler_status_download(struct device_handler *handler,
+ const struct process_info *procinfo,
+ unsigned int percentage, unsigned int size, char suffix)
+{
+ struct progress_info *p, *progress = NULL;
+ uint64_t current_converted, current = 0;
+ const char *units = " kMGTP";
+ unsigned long size_bytes;
+ char *update = NULL;
+ double total = 0;
+ unsigned int i;
+ int unit = 0;
+
+ list_for_each_entry(&handler->progress, p, list)
+ if (p->procinfo == procinfo)
+ progress = p;
+
+ if (!progress) {
+ pb_log("Registering new progress struct\n");
+ progress = talloc_zero(handler, struct progress_info);
+ if (!progress) {
+ pb_log("Failed to allocate room for progress struct\n");
+ return;
+ }
+ progress->procinfo = procinfo;
+ list_add(&handler->progress, &progress->list);
+ handler->n_progress++;
+ }
+
+ size_bytes = size;
+ for (i = 0; i < strlen(units); i++) {
+ if (units[i] == suffix)
+ break;
+ }
+
+ if (i >= strlen(units)) {
+ pb_log("Couldn't recognise suffix '%c'\n", suffix);
+ size_bytes = 0;
+ } else {
+ while (i--)
+ size_bytes <<= 10;
+ }
+
+ progress->percentage = percentage;
+ progress->size = size_bytes;
+
+ /*
+ * Aggregate the info we have and update status. If a progress struct
+ * has zero for both percentage and size we assume progress information
+ * is unavailable and fall back to a generic progress message.
+ */
+ list_for_each_entry(&handler->progress, p, list) {
+ uint64_t c;
+ double t;
+ if (!p->percentage || !p->size) {
+ update = talloc_asprintf(handler,
+ _("%u downloads in progress..."),
+ handler->n_progress);
+ current = total = 0;
+ break;
+ }
+
+ c = p->size;
+ t = (100 * c) / p->percentage;
+
+ current += c;
+ total += t;
+ }
+
+ if (total) {
+ current_converted = current;
+ while (current_converted >= 1000) {
+ current_converted >>= 10;
+ unit++;
+ }
+ update = talloc_asprintf(handler,
+ _("%u %s downloading: %.0f%% - %lu%cB"),
+ handler->n_progress,
+ ngettext("item", "items", handler->n_progress),
+ (current / total) * 100, current_converted,
+ units[unit]);
+ }
+
+ if (!update) {
+ pb_log("%s: failed to allocate new status\n", __func__);
+ } else {
+ device_handler_status_info(handler, "%s\n", update);
+ talloc_free(update);
+ }
+}
+
+void device_handler_status_download_remove(struct device_handler *handler,
+ struct process_info *procinfo)
+{
+ struct progress_info *p, *tmp;
+
+ list_for_each_entry_safe(&handler->progress, p, tmp, list)
+ if (p->procinfo == procinfo) {
+ list_remove(&p->list);
+ talloc_free(p);
+ handler->n_progress--;
+ }
+}
+
+static void device_handler_boot_status_cb(void *arg, struct status *status)
+{
+ device_handler_status(arg, status);
+}
+
+static void countdown_status(struct device_handler *handler,
+ struct discover_boot_option *opt, unsigned int sec)
+{
+ struct status status;
+
+ status.type = STATUS_INFO;
+ status.message = talloc_asprintf(handler,
+ _("Booting in %d sec: %s"), sec, opt->option->name);
+ status.backlog = false;
+
+ device_handler_status(handler, &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);
+
+ platform_pre_boot();
+
+ handler->pending_boot = boot(handler, handler->default_boot_option,
+ NULL, handler->dry_run, device_handler_boot_status_cb,
+ 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_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;
+ }
+
+ 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);
+}
+
+static bool resource_is_resolved(struct resource *res)
+{
+ return !res || res->resolved;
+}
+
+/* We only use this in an assert, which will disappear if we're compiling
+ * with NDEBUG, so we need the 'used' attribute for these builds */
+static bool __attribute__((used)) boot_option_is_resolved(
+ struct discover_boot_option *opt)
+{
+ return resource_is_resolved(opt->boot_image) &&
+ resource_is_resolved(opt->initrd) &&
+ resource_is_resolved(opt->dtb) &&
+ resource_is_resolved(opt->args_sig_file) &&
+ resource_is_resolved(opt->icon);
+}
+
+static bool resource_resolve(struct resource *res, const char *name,
+ struct discover_boot_option *opt,
+ struct device_handler *handler)
+{
+ struct parser *parser = opt->source;
+
+ if (resource_is_resolved(res))
+ return true;
+
+ pb_debug("Attempting to resolve resource %s->%s with parser %s\n",
+ opt->option->id, name, parser->name);
+ parser->resolve_resource(handler, res);
+
+ return res->resolved;
+}
+
+static bool boot_option_resolve(struct discover_boot_option *opt,
+ struct device_handler *handler)
+{
+ return resource_resolve(opt->boot_image, "boot_image", opt, handler) &&
+ resource_resolve(opt->initrd, "initrd", opt, handler) &&
+ resource_resolve(opt->dtb, "dtb", opt, handler) &&
+ resource_resolve(opt->args_sig_file, "args_sig_file", opt,
+ handler) &&
+ resource_resolve(opt->icon, "icon", opt, handler);
+}
+
+static void boot_option_finalise(struct device_handler *handler,
+ struct discover_boot_option *opt)
+{
+ assert(boot_option_is_resolved(opt));
+
+ /* check that the parsers haven't set any of the final data */
+ assert(!opt->option->boot_image_file);
+ assert(!opt->option->initrd_file);
+ assert(!opt->option->dtb_file);
+ assert(!opt->option->icon_file);
+ assert(!opt->option->device_id);
+ assert(!opt->option->args_sig_file);
+
+ if (opt->boot_image)
+ opt->option->boot_image_file = opt->boot_image->url->full;
+ if (opt->initrd)
+ opt->option->initrd_file = opt->initrd->url->full;
+ if (opt->dtb)
+ opt->option->dtb_file = opt->dtb->url->full;
+ if (opt->icon)
+ opt->option->icon_file = opt->icon->url->full;
+ if (opt->args_sig_file)
+ opt->option->args_sig_file = opt->args_sig_file->url->full;
+
+ opt->option->device_id = opt->device->device->id;
+
+ if (opt->option->is_default)
+ set_default(handler, opt);
+}
+
+static void notify_boot_option(struct device_handler *handler,
+ struct discover_boot_option *opt)
+{
+ struct discover_device *dev = opt->device;
+
+ if (!dev->notified)
+ discover_server_notify_device_add(handler->server,
+ opt->device->device);
+ dev->notified = true;
+ discover_server_notify_boot_option_add(handler->server, opt->option);
+}
+
+static void process_boot_option_queue(struct device_handler *handler)
+{
+ struct discover_boot_option *opt, *tmp;
+
+ list_for_each_entry_safe(&handler->unresolved_boot_options,
+ opt, tmp, list) {
+
+ pb_debug("queue: attempting resolution for %s\n",
+ opt->option->id);
+
+ if (!boot_option_resolve(opt, handler))
+ continue;
+
+ pb_debug("\tresolved!\n");
+
+ list_remove(&opt->list);
+ list_add_tail(&opt->device->boot_options, &opt->list);
+ talloc_steal(opt->device, opt);
+ boot_option_finalise(handler, opt);
+ notify_boot_option(handler, opt);
+ }
+}
+
+struct discover_context *device_handler_discover_context_create(
+ struct device_handler *handler,
+ struct discover_device *device)
+{
+ struct discover_context *ctx;
+
+ ctx = talloc_zero(handler, struct discover_context);
+ ctx->handler = handler;
+ ctx->device = device;
+ list_init(&ctx->boot_options);
+
+ return ctx;
+}
+
+void device_handler_add_device(struct device_handler *handler,
+ struct discover_device *device)
+{
+ handler->n_devices++;
+ handler->devices = talloc_realloc(handler, handler->devices,
+ struct discover_device *, handler->n_devices);
+ handler->devices[handler->n_devices - 1] = device;
+
+ if (device->device->type == DEVICE_TYPE_NETWORK)
+ network_register_device(handler->network, device);
+}
+
+void device_handler_add_ramdisk(struct device_handler *handler,
+ const char *path)
+{
+ struct ramdisk_device *dev;
+ unsigned int i;
+
+ if (!path)
+ return;
+
+ for (i = 0; i < handler->n_ramdisks; i++)
+ if (!strcmp(handler->ramdisks[i]->path, path))
+ return;
+
+ dev = talloc_zero(handler, struct ramdisk_device);
+ if (!dev) {
+ pb_log("Failed to allocate memory to track %s\n", path);
+ return;
+ }
+
+ dev->path = talloc_strdup(handler, path);
+
+ handler->ramdisks = talloc_realloc(handler, handler->ramdisks,
+ struct ramdisk_device *,
+ handler->n_ramdisks + 1);
+ if (!handler->ramdisks) {
+ pb_log("Failed to reallocate memory"
+ "- ramdisk tracking inconsistent!\n");
+ return;
+ }
+
+ handler->ramdisks[i] = dev;
+ i = handler->n_ramdisks++;
+}
+
+struct ramdisk_device *device_handler_get_ramdisk(
+ struct device_handler *handler)
+{
+ unsigned int i;
+ char *name;
+ dev_t id;
+
+ /* Check if free ramdisk exists */
+ for (i = 0; i < handler->n_ramdisks; i++)
+ if (!handler->ramdisks[i]->snapshot &&
+ !handler->ramdisks[i]->origin &&
+ !handler->ramdisks[i]->base)
+ return handler->ramdisks[i];
+
+ /* Otherwise create a new one */
+ name = talloc_asprintf(handler, "/dev/ram%d",
+ handler->n_ramdisks);
+ if (!name) {
+ pb_debug("Failed to allocate memory to name /dev/ram%d",
+ handler->n_ramdisks);
+ return NULL;
+ }
+
+ id = makedev(1, handler->n_ramdisks);
+ if (mknod(name, S_IFBLK, id)) {
+ if (errno == EEXIST) {
+ /* We haven't yet received updates for existing
+ * ramdisks - add and use this one */
+ pb_debug("Using untracked ramdisk %s\n", name);
+ } else {
+ pb_log("Failed to create new ramdisk %s: %s\n",
+ name, strerror(errno));
+ return NULL;
+ }
+ }
+ device_handler_add_ramdisk(handler, name);
+ talloc_free(name);
+
+ return handler->ramdisks[i];
+}
+
+void device_handler_release_ramdisk(struct discover_device *device)
+{
+ struct ramdisk_device *ramdisk = device->ramdisk;
+
+ talloc_free(ramdisk->snapshot);
+ talloc_free(ramdisk->origin);
+ talloc_free(ramdisk->base);
+
+ ramdisk->snapshot = ramdisk->origin = ramdisk->base = NULL;
+ ramdisk->sectors = 0;
+
+ device->ramdisk = NULL;
+}
+
+/* Start discovery on a hotplugged device. The device will be in our devices
+ * array, but has only just been initialised by the hotplug source.
+ */
+int device_handler_discover(struct device_handler *handler,
+ struct discover_device *dev)
+{
+ struct discover_context *ctx;
+ int rc;
+
+ device_handler_status_dev_info(handler, dev,
+ /*
+ * TRANSLATORS: this string will be passed the type of the
+ * device (eg "disk" or "network"), which will be translated
+ * accordingly.
+ */
+ _("Processing new %s device"),
+ device_type_display_name(dev->device->type));
+
+ /* create our context */
+ ctx = device_handler_discover_context_create(handler, dev);
+
+ rc = mount_device(dev);
+ if (rc)
+ goto out;
+
+ /* add this device to our system info */
+ system_info_register_blockdev(dev->device->id, dev->uuid,
+ dev->mount_path);
+
+ /* run the parsers. This will populate the ctx's boot_option list. */
+ iterate_parsers(ctx);
+