]> git.ozlabs.org Git - petitboot/blobdiff - discover/device-handler.c
discover/handler: Implement temporary autoboot messages
[petitboot] / discover / device-handler.c
index 569e65290bd7e98cef518b078a16f76547ecb564..b8e933088b28f5c7eb3aed788287af48ab0df365 100644 (file)
 #include "ipmi.h"
 
 enum default_priority {
-       DEFAULT_PRIORITY_REMOTE         = 1,
-       DEFAULT_PRIORITY_LOCAL_FIRST    = 2,
+       DEFAULT_PRIORITY_TEMP_USER      = 1,
+       DEFAULT_PRIORITY_REMOTE         = 2,
+       DEFAULT_PRIORITY_LOCAL_FIRST    = 3,
        DEFAULT_PRIORITY_LOCAL_LAST     = 0xfe,
        DEFAULT_PRIORITY_DISABLED       = 0xff,
 };
 
+static int default_rescan_timeout = 5 * 60; /* seconds */
+
 struct progress_info {
        unsigned int                    percentage;
        unsigned long                   size;           /* size in bytes */
@@ -75,6 +78,7 @@ struct device_handler {
        struct waiter           *timeout_waiter;
        bool                    autoboot_enabled;
        unsigned int            sec_to_boot;
+       struct autoboot_option  *temp_autoboot;
 
        struct discover_boot_option *default_boot_option;
        int                     default_boot_option_priority;
@@ -418,10 +422,13 @@ void device_handler_reinit(struct device_handler *handler)
 
        /* drop all devices */
        for (i = 0; i < handler->n_devices; i++) {
+               struct discover_device *device = handler->devices[i];
                discover_server_notify_device_remove(handler->server,
-                               handler->devices[i]->device);
-               ramdisk = handler->devices[i]->ramdisk;
-               talloc_free(handler->devices[i]);
+                               device->device);
+               ramdisk = device->ramdisk;
+               if (device->requery_waiter)
+                       waiter_remove(device->requery_waiter);
+               talloc_free(device);
                talloc_free(ramdisk);
        }
 
@@ -451,6 +458,9 @@ void device_handler_reinit(struct device_handler *handler)
                discover_server_notify_config(handler->server, config);
        }
 
+       /* Force rediscovery on SCSI devices */
+       process_run_simple(handler, pb_system_apps.scsi_rescan, NULL);
+
        device_handler_reinit_sources(handler);
 }
 
@@ -460,6 +470,9 @@ void device_handler_remove(struct device_handler *handler,
        struct discover_boot_option *opt, *tmp;
        unsigned int i;
 
+       if (device->requery_waiter)
+               waiter_remove(device->requery_waiter);
+
        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",
@@ -699,7 +712,17 @@ void device_handler_status_download_remove(struct device_handler *handler,
 
 static void device_handler_boot_status_cb(void *arg, struct status *status)
 {
-       device_handler_status(arg, status);
+       struct device_handler *handler = arg;
+
+       /* boot had failed; update handler state to allow a new default if one
+        * is found later
+        */
+       if (status->type == STATUS_ERROR) {
+               handler->pending_boot = NULL;
+               handler->default_boot_option = NULL;
+       }
+
+       device_handler_status(handler, status);
 }
 
 static void countdown_status(struct device_handler *handler,
@@ -776,24 +799,31 @@ static bool ipmi_device_type_matches(enum ipmi_bootdev ipmi_type,
        return false;
 }
 
+static bool autoboot_option_matches(struct autoboot_option *opt,
+               struct discover_device *dev)
+{
+       if (opt->boot_type == BOOT_DEVICE_UUID)
+               if (!strcmp(opt->uuid, dev->uuid))
+                       return true;
+
+       if (opt->boot_type == BOOT_DEVICE_TYPE)
+               if (opt->type == dev->device->type ||
+                   opt->type == DEVICE_TYPE_ANY)
+                       return true;
+
+       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;
+               if (autoboot_option_matches(auto_opt, opt->device))
+                       return DEFAULT_PRIORITY_LOCAL_FIRST + i;
        }
 
        return -1;
@@ -805,15 +835,27 @@ static int autoboot_option_priority(const struct config *config,
  * for these options.
  */
 static enum default_priority default_option_priority(
+               struct device_handler *handler,
                struct discover_boot_option *opt)
 {
        const struct config *config;
 
+       /* Temporary user-provided autoboot options have highest priority */
+       if (handler->temp_autoboot) {
+               if (autoboot_option_matches(handler->temp_autoboot,
+                                       opt->device))
+                       return DEFAULT_PRIORITY_TEMP_USER;
+
+               pb_debug("handler: disabled default priority due to "
+                               "temporary user override\n");
+               return DEFAULT_PRIORITY_DISABLED;
+       }
+
        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 */
+       /* Next 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);
@@ -853,7 +895,7 @@ static void set_default(struct device_handler *handler,
 
        pb_debug("handler: new default option: %s\n", opt->option->id);
 
-       new_prio = default_option_priority(opt);
+       new_prio = default_option_priority(handler, opt);
 
        /* Anything outside our range prevents a default boot */
        if (new_prio >= DEFAULT_PRIORITY_DISABLED)
@@ -893,6 +935,43 @@ static void set_default(struct device_handler *handler,
        default_timeout(handler);
 }
 
+void device_handler_apply_temp_autoboot(struct device_handler *handler,
+               struct autoboot_option *opt)
+{
+       unsigned int i;
+
+       handler->temp_autoboot = talloc_steal(handler, opt);
+
+       if (!handler->autoboot_enabled)
+               return;
+
+       if (!handler->default_boot_option)
+               return;
+
+       if (autoboot_option_matches(opt, handler->default_boot_option->device))
+               return;
+
+       /* cancel the default, and rescan available options */
+       device_handler_cancel_default(handler);
+
+       handler->autoboot_enabled = true;
+
+       for (i = 0; i < handler->n_devices; i++) {
+               struct discover_device *dev = handler->devices[i];
+               struct discover_boot_option *boot_opt;
+
+               if (!autoboot_option_matches(opt, dev))
+                       continue;
+
+               list_for_each_entry(&dev->boot_options, boot_opt, list) {
+                       if (boot_opt->option->is_default) {
+                               set_default(handler, boot_opt);
+                               break;
+                       }
+               }
+       }
+}
+
 static bool resource_is_resolved(struct resource *res)
 {
        return !res || res->resolved;
@@ -1162,6 +1241,109 @@ out:
        return 0;
 }
 
+struct requery_data {
+       struct device_handler   *handler;
+       struct discover_device  *device;
+};
+
+static int device_handler_requery_timeout_fn(void *data)
+{
+       struct discover_boot_option *opt, *tmp;
+       struct requery_data *rqd = data;
+       struct device_handler *handler;
+       struct discover_device *device;
+
+       handler = rqd->handler;
+       device = rqd->device;
+
+       talloc_free(rqd);
+
+       /* network_requery_device may re-add a timeout, so clear the device
+        * waiter here, so we can potentially start a new one. */
+       device->requery_waiter = NULL;
+
+       /* We keep the device around, but get rid of the parsed boot
+        * options on that device. That involves delaring out the lists,
+        * and potentially cancelling a default.
+        */
+       list_for_each_entry_safe(&handler->unresolved_boot_options,
+                       opt, tmp, list) {
+               if (opt->device != device)
+                       continue;
+               list_remove(&opt->list);
+               talloc_free(opt);
+       }
+
+       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 is being requeried",
+                                       opt->option->name);
+                       device_handler_cancel_default(handler);
+               }
+               list_remove(&opt->list);
+               talloc_free(opt);
+       }
+
+       discover_server_notify_device_remove(handler->server, device->device);
+       device->notified = false;
+
+       network_requery_device(handler->network, device);
+
+       return 0;
+}
+
+/* Schedule a requery in timeout (seconds).
+ *
+ * Special values of timeout:
+ *   0: no requery
+ *  -1: use default
+ */
+void device_handler_start_requery_timeout( struct device_handler *handler,
+               struct discover_device *dev, int timeout)
+{
+       struct requery_data *rqd;
+
+       if (dev->requery_waiter)
+               return;
+
+       if (timeout == -1)
+               timeout = default_rescan_timeout;
+       else if (timeout == 0)
+               return;
+
+       rqd = talloc(dev, struct requery_data);
+       rqd->handler = handler;
+       rqd->device = dev;
+
+       pb_debug("starting requery timeout for device %s, in %d sec\n",
+                       dev->device->id, timeout);
+
+       dev->requery_waiter = waiter_register_timeout(handler->waitset,
+                       timeout * 1000, device_handler_requery_timeout_fn, rqd);
+}
+
+static int event_requery_timeout(struct event *event)
+{
+       int timeout = -1;
+       unsigned long x;
+       const char *str;
+       char *endp;
+
+       if (!event)
+               return timeout;
+
+       str = event_get_param(event, "reboottime");
+       if (!str)
+               return timeout;
+
+       x = strtoul(str, &endp, 0);
+       if (endp != str)
+               timeout = x;
+
+       return timeout;
+}
+
+
 /* Incoming dhcp event */
 int device_handler_dhcp(struct device_handler *handler,
                struct discover_device *dev, struct event *event)
@@ -1179,6 +1361,9 @@ int device_handler_dhcp(struct device_handler *handler,
        talloc_steal(ctx, event);
        ctx->event = event;
 
+       device_handler_start_requery_timeout(handler, dev,
+                       event_requery_timeout(event));
+
        iterate_parsers(ctx);
 
        device_handler_discover_context_commit(handler, ctx);