]> git.ozlabs.org Git - petitboot/blobdiff - discover/device-handler.c
discover/device-handler: Ignore options without kernel
[petitboot] / discover / device-handler.c
index 4f7a7b7500c4fee446747df10861095d30b2ce33..e230e7669ac31e3c4e04a35b35cf71be8155eb81 100644 (file)
@@ -396,7 +396,7 @@ void device_handler_remove(struct device_handler *handler,
        talloc_free(device);
 }
 
-static void boot_status(void *arg, struct boot_status *status)
+void device_handler_boot_status(void *arg, struct boot_status *status)
 {
        struct device_handler *handler = arg;
 
@@ -445,8 +445,11 @@ static int default_timeout(void *arg)
 
        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, boot_status, handler);
+                       NULL, handler->dry_run, device_handler_boot_status,
+                       handler);
        handler->pending_boot_is_default = true;
        return 0;
 }
@@ -457,6 +460,7 @@ struct {
 } 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 },
 };
 
@@ -704,50 +708,6 @@ struct discover_context *device_handler_discover_context_create(
        return ctx;
 }
 
-/**
- * context_commit - Commit a temporary discovery context to the handler,
- * and notify the clients about any new options / devices
- */
-void device_handler_discover_context_commit(struct device_handler *handler,
-               struct discover_context *ctx)
-{
-       struct discover_device *dev = ctx->device;
-       struct discover_boot_option *opt, *tmp;
-
-       if (!device_lookup_by_id(handler, dev->device->id))
-               device_handler_add_device(handler, dev);
-
-       /* move boot options from the context to the device */
-       list_for_each_entry_safe(&ctx->boot_options, opt, tmp, list) {
-               list_remove(&opt->list);
-
-               if (boot_option_resolve(opt, handler)) {
-                       pb_log("boot option %s is resolved, "
-                                       "sending to clients\n",
-                                       opt->option->id);
-                       list_add_tail(&dev->boot_options, &opt->list);
-                       talloc_steal(dev, opt);
-                       boot_option_finalise(handler, opt);
-                       notify_boot_option(handler, opt);
-               } else {
-                       if (!opt->source->resolve_resource) {
-                               pb_log("parser %s gave us an unresolved "
-                                       "resource (%s), but no way to "
-                                       "resolve it\n",
-                                       opt->source->name, opt->option->id);
-                               talloc_free(opt);
-                       } else {
-                               pb_log("boot option %s is unresolved, "
-                                               "adding to queue\n",
-                                               opt->option->id);
-                               list_add(&handler->unresolved_boot_options,
-                                               &opt->list);
-                               talloc_steal(handler, opt);
-                       }
-               }
-       }
-}
-
 void device_handler_add_device(struct device_handler *handler,
                struct discover_device *device)
 {
@@ -861,10 +821,16 @@ int device_handler_discover(struct device_handler *handler,
 
        status = talloc_zero(handler, struct boot_status);
        status->type = BOOT_STATUS_INFO;
-       status->message = talloc_asprintf(status, "Processing %s device %s",
+       /*
+        * TRANSLATORS: this string will be passed the type and identifier
+        * of the device. For example, the first parameter could be "Disk",
+        * (which will be translated accordingly) and the second a Linux device
+        * identifier like 'sda1' (which will not be translated)
+        */
+       status->message = talloc_asprintf(status, _("Processing %s device %s"),
                                device_type_display_name(dev->device->type),
                                dev->device->id);
-       boot_status(handler, status);
+       device_handler_boot_status(handler, status);
 
        process_boot_option_queue(handler);
 
@@ -886,12 +852,16 @@ int device_handler_discover(struct device_handler *handler,
        device_handler_discover_context_commit(handler, ctx);
 
 out:
-       status->message = talloc_asprintf(status,"Processing %s complete\n",
+       /*
+        * TRANSLATORS: the format specifier in this string is a Linux
+        * device identifier, like 'sda1'
+        */
+       status->message = talloc_asprintf(status,_("Processing %s complete"),
                                dev->device->id);
-       boot_status(handler, status);
+       device_handler_boot_status(handler, status);
 
        talloc_free(status);
-       talloc_free(ctx);
+       talloc_unlink(handler, ctx);
 
        return 0;
 }
@@ -905,9 +875,13 @@ int device_handler_dhcp(struct device_handler *handler,
 
        status = talloc_zero(handler, struct boot_status);
        status->type = BOOT_STATUS_INFO;
-       status->message = talloc_asprintf(status, "Processing dhcp event on %s",
+       /*
+        * TRANSLATORS: this format specifier will be the name of a network
+        * device, like 'eth0'.
+        */
+       status->message = talloc_asprintf(status, _("Processing dhcp event on %s"),
                                dev->device->id);
-       boot_status(handler, status);
+       device_handler_boot_status(handler, status);
 
        /* create our context */
        ctx = device_handler_discover_context_create(handler, dev);
@@ -917,12 +891,16 @@ int device_handler_dhcp(struct device_handler *handler,
 
        device_handler_discover_context_commit(handler, ctx);
 
-       status->message = talloc_asprintf(status,"Processing %s complete\n",
+       /*
+        * TRANSLATORS: this format specifier will be the name of a network
+        * device, like 'eth0'.
+        */
+       status->message = talloc_asprintf(status,_("Processing %s complete"),
                                dev->device->id);
-       boot_status(handler, status);
+       device_handler_boot_status(handler, status);
 
        talloc_free(status);
-       talloc_free(ctx);
+       talloc_unlink(handler, ctx);
 
        return 0;
 }
@@ -936,8 +914,8 @@ int device_handler_conf(struct device_handler *handler,
 
        status = talloc_zero(handler, struct boot_status);
        status->type = BOOT_STATUS_INFO;
-       status->message = talloc_asprintf(status, "Processing user config");
-       boot_status(handler, status);
+       status->message = talloc_asprintf(status, _("Processing user config"));
+       device_handler_boot_status(handler, status);
 
        /* create our context */
        ctx = device_handler_discover_context_create(handler, dev);
@@ -948,11 +926,11 @@ int device_handler_conf(struct device_handler *handler,
        device_handler_discover_context_commit(handler, ctx);
 
        status->message = talloc_asprintf(status,
-                               "Processing user config complete");
-       boot_status(handler, status);
+                               _("Processing user config complete"));
+       device_handler_boot_status(handler, status);
 
        talloc_free(status);
-       talloc_free(ctx);
+       talloc_unlink(handler, ctx);
 
        return 0;
 }
@@ -988,7 +966,7 @@ void device_handler_boot(struct device_handler *handler,
        platform_pre_boot();
 
        handler->pending_boot = boot(handler, opt, cmd, handler->dry_run,
-                       boot_status, handler);
+                       device_handler_boot_status, handler);
        handler->pending_boot_is_default = false;
 }
 
@@ -1080,10 +1058,12 @@ static char *device_from_addr(void *ctx, struct pb_url *url)
 
        rc = process_run_sync(p);
 
-       if (rc) {
+       if (rc || p->exit_status) {
                /* ip has complained for some reason; most likely
                 * there is no route to the host - bail out */
-               pb_debug("%s: No route to %s\n",__func__,url->host);
+               pb_debug("%s: `ip` returns non-zero exit status\n", __func__);
+               pb_debug("ip buf: %s\n", p->stdout_buf);
+               process_release(p);
                return NULL;
        }
 
@@ -1106,9 +1086,8 @@ static char *device_from_addr(void *ctx, struct pb_url *url)
        return dev;
 }
 
-
 void device_handler_process_url(struct device_handler *handler,
-               const char *url)
+               const char *url, const char *mac, const char *ip)
 {
        struct discover_context *ctx;
        struct discover_device *dev;
@@ -1134,20 +1113,38 @@ void device_handler_process_url(struct device_handler *handler,
        event->type = EVENT_TYPE_USER;
        event->action = EVENT_ACTION_CONF;
 
-       event->params = talloc_array(event, struct param, 1);
-       param = &event->params[0];
-       param->name = talloc_strdup(event, "pxeconffile");
-       param->value = talloc_strdup(event, url);
-       event->n_params = 1;
+       if (url[strlen(url) - 1] == '/') {
+               event->params = talloc_array(event, struct param, 3);
+               param = &event->params[0];
+               param->name = talloc_strdup(event, "pxepathprefix");
+               param->value = talloc_strdup(event, url);
+               param = &event->params[1];
+               param->name = talloc_strdup(event, "mac");
+               param->value = talloc_strdup(event, mac);
+               param = &event->params[2];
+               param->name = talloc_strdup(event, "ip");
+               param->value = talloc_strdup(event, ip);
+               event->n_params = 3;
+       } else {
+               event->params = talloc_array(event, struct param, 1);
+               param = &event->params[0];
+               param->name = talloc_strdup(event, "pxeconffile");
+               param->value = talloc_strdup(event, url);
+               event->n_params = 1;
+       }
 
        pb_url = pb_url_parse(event, event->params->value);
-       if (!pb_url || !pb_url->host) {
+       if (!pb_url || (pb_url->scheme != pb_url_file && !pb_url->host)) {
                status->message = talloc_asprintf(handler,
                                        _("Invalid config URL!"));
                goto msg;
        }
 
-       event->device = device_from_addr(event, pb_url);
+       if (pb_url->scheme == pb_url_file)
+               event->device = talloc_asprintf(event, "local");
+       else
+               event->device = device_from_addr(event, pb_url);
+
        if (!event->device) {
                status->message = talloc_asprintf(status,
                                        _("Unable to route to host %s"),
@@ -1156,6 +1153,8 @@ void device_handler_process_url(struct device_handler *handler,
        }
 
        dev = discover_device_create(handler, event->device);
+       if (pb_url->scheme == pb_url_file)
+               dev->device->type = DEVICE_TYPE_ANY;
        ctx = device_handler_discover_context_create(handler, dev);
        ctx->event = event;
 
@@ -1163,18 +1162,70 @@ void device_handler_process_url(struct device_handler *handler,
 
        device_handler_discover_context_commit(handler, ctx);
 
-       talloc_free(ctx);
+       talloc_unlink(handler, ctx);
 
        status->type = BOOT_STATUS_INFO;
        status->message = talloc_asprintf(status, _("Config file %s parsed"),
                                        pb_url->file);
 msg:
-       boot_status(handler, status);
+       device_handler_boot_status(handler, status);
        talloc_free(status);
 }
 
 #ifndef PETITBOOT_TEST
 
+/**
+ * context_commit - Commit a temporary discovery context to the handler,
+ * and notify the clients about any new options / devices
+ */
+void device_handler_discover_context_commit(struct device_handler *handler,
+               struct discover_context *ctx)
+{
+       struct discover_device *dev = ctx->device;
+       struct discover_boot_option *opt, *tmp;
+
+       if (!device_lookup_by_id(handler, dev->device->id))
+               device_handler_add_device(handler, dev);
+
+       /* move boot options from the context to the device */
+       list_for_each_entry_safe(&ctx->boot_options, opt, tmp, list) {
+               list_remove(&opt->list);
+
+               /* All boot options need at least a kernel image */
+               if (!opt->boot_image || !opt->boot_image->url) {
+                       pb_log("boot option %s is missing boot image, ignoring\n",
+                               opt->option->id);
+                       talloc_free(opt);
+                       continue;
+               }
+
+               if (boot_option_resolve(opt, handler)) {
+                       pb_log("boot option %s is resolved, "
+                                       "sending to clients\n",
+                                       opt->option->id);
+                       list_add_tail(&dev->boot_options, &opt->list);
+                       talloc_steal(dev, opt);
+                       boot_option_finalise(handler, opt);
+                       notify_boot_option(handler, opt);
+               } else {
+                       if (!opt->source->resolve_resource) {
+                               pb_log("parser %s gave us an unresolved "
+                                       "resource (%s), but no way to "
+                                       "resolve it\n",
+                                       opt->source->name, opt->option->id);
+                               talloc_free(opt);
+                       } else {
+                               pb_log("boot option %s is unresolved, "
+                                               "adding to queue\n",
+                                               opt->option->id);
+                               list_add(&handler->unresolved_boot_options,
+                                               &opt->list);
+                               talloc_steal(handler, opt);
+                       }
+               }
+       }
+}
+
 static void device_handler_update_lang(const char *lang)
 {
        const char *cur_lang;
@@ -1224,23 +1275,31 @@ static void device_handler_reinit_sources(struct device_handler *handler)
                        handler->dry_run);
 }
 
-static const char *fs_parameters(unsigned int rw_flags, const char *fstype)
+static inline const char *get_device_path(struct discover_device *dev)
 {
-       if ((rw_flags | MS_RDONLY) != MS_RDONLY)
-               return "";
-
-       /* Avoid writing back to the disk on journaled filesystems */
-       if (!strncmp(fstype, "ext4", strlen("ext4")))
-               return "norecovery";
-       if (!strncmp(fstype, "xfs", strlen("xfs")))
-               return "norecovery";
-
-       return "";
+       return dev->ramdisk ? dev->ramdisk->snapshot : dev->device_path;
 }
 
-static inline const char *get_device_path(struct discover_device *dev)
+static char *check_subvols(struct discover_device *dev)
 {
-       return dev->ramdisk ? dev->ramdisk->snapshot : dev->device_path;
+       const char *fstype = discover_device_get_param(dev, "ID_FS_TYPE");
+       struct stat sb;
+       char *path;
+       int rc;
+
+       if (strncmp(fstype, "btrfs", strlen("btrfs")))
+               return dev->mount_path;
+
+       /* On btrfs a device's root may be under a subvolume path */
+       path = join_paths(dev, dev->mount_path, "@");
+       rc = stat(path, &sb);
+       if (!rc && S_ISDIR(sb.st_mode)) {
+               pb_debug("Using '%s' for btrfs root path\n", path);
+               return path;
+       }
+
+       talloc_free(path);
+       return dev->mount_path;
 }
 
 static bool check_existing_mount(struct discover_device *dev)
@@ -1284,6 +1343,7 @@ static bool check_existing_mount(struct discover_device *dev)
 
                if (mntstat.st_rdev == devstat.st_rdev) {
                        dev->mount_path = talloc_strdup(dev, mnt->mnt_dir);
+                       dev->root_path = check_subvols(dev);
                        dev->mounted_rw = !!hasmntopt(mnt, "rw");
                        dev->mounted = true;
                        dev->unmount = false;
@@ -1301,6 +1361,52 @@ static bool check_existing_mount(struct discover_device *dev)
        return mnt != NULL;
 }
 
+/*
+ * Attempt to mount a filesystem safely, while handling certain filesytem-
+ * specific options
+ */
+static int try_mount(const char *device_path, const char *mount_path,
+                            const char *fstype, unsigned long flags,
+                            bool have_snapshot)
+{
+       const char *fs, *safe_opts;
+       int rc;
+
+       /* Mount ext3 as ext4 instead so 'norecovery' can be used */
+       if (strncmp(fstype, "ext3", strlen("ext3")) == 0) {
+               pb_debug("Mounting ext3 filesystem as ext4\n");
+               fs = "ext4";
+       } else
+               fs = fstype;
+
+       if (strncmp(fs, "xfs", strlen("xfs")) == 0 ||
+           strncmp(fs, "ext4", strlen("ext4")) == 0)
+               safe_opts = "norecovery";
+       else
+               safe_opts = NULL;
+
+       errno = 0;
+       /* If no snapshot is available don't attempt recovery */
+       if (!have_snapshot)
+               return mount(device_path, mount_path, fs, flags, safe_opts);
+
+       rc = mount(device_path, mount_path, fs, flags, NULL);
+
+       if (!rc)
+               return rc;
+
+       /* Mounting failed; some filesystems will fail to mount if a recovery
+        * journal exists (eg. cross-endian XFS), so try again with norecovery
+        * where that option is available.
+        * If mounting read-write just return the error as norecovery is not a
+        * valid option */
+       if ((flags & MS_RDONLY) != MS_RDONLY || !safe_opts)
+               return rc;
+
+       errno = 0;
+       return mount(device_path, mount_path, fs, flags, safe_opts);
+}
+
 static int mount_device(struct discover_device *dev)
 {
        const char *fstype, *device_path;
@@ -1319,13 +1425,6 @@ static int mount_device(struct discover_device *dev)
        if (!fstype)
                return 0;
 
-       /* ext3 treats the norecovery option as an error, so mount the device
-        * as an ext4 filesystem instead */
-       if (!strncmp(fstype, "ext3", strlen("ext3"))) {
-               pb_debug("Mounting ext3 filesystem as ext4\n");
-               fstype = talloc_asprintf(dev, "ext4");
-       }
-
        dev->mount_path = join_paths(dev, mount_base(),
                                        dev->device_path);
 
@@ -1338,14 +1437,14 @@ static int mount_device(struct discover_device *dev)
        device_path = get_device_path(dev);
 
        pb_log("mounting device %s read-only\n", dev->device_path);
-       errno = 0;
-       rc = mount(device_path, dev->mount_path, fstype,
-                       MS_RDONLY | MS_SILENT,
-                       fs_parameters(MS_RDONLY, fstype));
+       rc = try_mount(device_path, dev->mount_path, fstype,
+                      MS_RDONLY | MS_SILENT, dev->ramdisk);
+
        if (!rc) {
                dev->mounted = true;
                dev->mounted_rw = false;
                dev->unmount = true;
+               dev->root_path = check_subvols(dev);
                return 0;
        }
 
@@ -1384,6 +1483,7 @@ static int umount_device(struct discover_device *dev)
 
        talloc_free(dev->mount_path);
        dev->mount_path = NULL;
+       dev->root_path = NULL;
 
        return 0;
 }
@@ -1419,9 +1519,8 @@ int device_request_write(struct discover_device *dev, bool *release)
                return -1;
        }
 
-       rc = mount(device_path, dev->mount_path, fstype,
-                       MS_SILENT,
-                       fs_parameters(MS_REMOUNT, fstype));
+       rc = try_mount(device_path, dev->mount_path, fstype,
+                      MS_SILENT, dev->ramdisk);
        if (rc)
                goto mount_ro;
 
@@ -1432,9 +1531,9 @@ int device_request_write(struct discover_device *dev, bool *release)
 mount_ro:
        pb_log("Unable to remount device %s read-write: %s\n",
               device_path, strerror(errno));
-       if (mount(device_path, dev->mount_path, fstype,
-                       MS_RDONLY | MS_SILENT,
-                       fs_parameters(MS_RDONLY, fstype)))
+       rc = try_mount(device_path, dev->mount_path, fstype,
+                      MS_RDONLY | MS_SILENT, dev->ramdisk);
+       if (rc)
                pb_log("Unable to recover mount for %s: %s\n",
                       device_path, strerror(errno));
        return -1;
@@ -1443,7 +1542,6 @@ mount_ro:
 void device_release_write(struct discover_device *dev, bool release)
 {
        const char *fstype, *device_path;
-       int rc = 0;
 
        if (!release)
                return;
@@ -1466,10 +1564,8 @@ void device_release_write(struct discover_device *dev, bool release)
                device_path = get_device_path(dev);
        }
 
-       mount(device_path, dev->mount_path, fstype,
-                       MS_RDONLY | MS_SILENT,
-                       fs_parameters(MS_RDONLY, fstype));
-       if (rc)
+       if (try_mount(device_path, dev->mount_path, fstype,
+                      MS_RDONLY | MS_SILENT, dev->ramdisk))
                pb_log("Failed to remount %s read-only: %s\n",
                       device_path, strerror(errno));
        else
@@ -1478,6 +1574,13 @@ void device_release_write(struct discover_device *dev, bool release)
 
 #else
 
+void device_handler_discover_context_commit(
+               struct device_handler *handler __attribute__((unused)),
+               struct discover_context *ctx __attribute__((unused)))
+{
+       pb_log("%s stubbed out for test cases\n", __func__);
+}
+
 static void device_handler_update_lang(const char *lang __attribute__((unused)))
 {
 }
@@ -1517,4 +1620,3 @@ void device_release_write(struct discover_device *dev __attribute__((unused)),
 }
 
 #endif
-