]> git.ozlabs.org Git - petitboot/blobdiff - discover/device-handler.c
discover: Add device_{request,release}_write
[petitboot] / discover / device-handler.c
index cdfee483767e449c885d0ebf3db3671040a4c78f..04a44848d4c452db00e7c9e80cda0b5c6274ac9e 100644 (file)
@@ -1,9 +1,11 @@
 
 #include <assert.h>
 #include <stdlib.h>
+#include <stdbool.h>
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
+#include <mntent.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 
@@ -103,6 +105,12 @@ 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 *),
@@ -154,6 +162,13 @@ struct discover_device *device_lookup_by_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);
@@ -327,6 +342,8 @@ static int default_timeout(void *arg)
                return 0;
        }
 
+       handler->timeout_waiter = NULL;
+
        pb_log("Timeout expired, booting default option %s\n", opt->option->id);
 
        boot(handler, handler->default_boot_option, NULL,
@@ -334,17 +351,56 @@ static int default_timeout(void *arg)
        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;
+       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->default_boot_option)
+       if (!handler->autoboot_enabled)
                return;
 
-       if (!handler->autoboot_enabled)
+       /* 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->default_boot_option = opt;
        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);
@@ -464,6 +520,7 @@ struct discover_context *device_handler_discover_context_create(
        ctx = talloc(handler, struct discover_context);
        ctx->device = device;
        ctx->conf_url = NULL;
+       ctx->test_data = NULL;
        list_init(&ctx->boot_options);
 
        return ctx;
@@ -530,13 +587,16 @@ int device_handler_discover(struct device_handler *handler,
                struct discover_device *dev, enum conf_method method)
 {
        struct discover_context *ctx;
+       int rc;
 
        process_boot_option_queue(handler);
 
        /* create our context */
        ctx = device_handler_discover_context_create(handler, dev);
 
-       mount_device(dev);
+       rc = mount_device(dev);
+       if (rc)
+               goto out;
 
        /* run the parsers. This will populate the ctx's boot_option list. */
        iterate_parsers(ctx, method);
@@ -544,6 +604,7 @@ int device_handler_discover(struct device_handler *handler,
        /* add discovered stuff to the handler */
        device_handler_discover_context_commit(handler, ctx);
 
+out:
        talloc_free(ctx);
 
        return 0;
@@ -623,6 +684,61 @@ void device_handler_cancel_default(struct device_handler *handler)
 }
 
 #ifndef PETITBOOT_TEST
+static bool check_existing_mount(struct discover_device *dev)
+{
+       struct stat devstat, mntstat;
+       struct mntent *mnt;
+       FILE *fp;
+       int rc;
+
+       rc = stat(dev->device_path, &devstat);
+       if (rc) {
+               pb_debug("%s: stat failed: %s\n", __func__, strerror(errno));
+               return false;
+       }
+
+       if (!S_ISBLK(devstat.st_mode)) {
+               pb_debug("%s: %s isn't a block device?\n", __func__,
+                               dev->device_path);
+               return false;
+       }
+
+       fp = fopen("/proc/self/mounts", "r");
+
+       for (;;) {
+               mnt = getmntent(fp);
+               if (!mnt)
+                       break;
+
+               if (!mnt->mnt_fsname || mnt->mnt_fsname[0] != '/')
+                       continue;
+
+               rc = stat(mnt->mnt_fsname, &mntstat);
+               if (rc)
+                       continue;
+
+               if (!S_ISBLK(mntstat.st_mode))
+                       continue;
+
+               if (mntstat.st_rdev == devstat.st_rdev) {
+                       dev->mount_path = talloc_strdup(dev, mnt->mnt_dir);
+                       dev->mounted_rw = !!hasmntopt(mnt, "rw");
+                       dev->mounted = true;
+                       dev->unmount = false;
+
+                       pb_debug("%s: %s is already mounted (r%c) at %s\n",
+                                       __func__, dev->device_path,
+                                       dev->mounted_rw ? 'w' : 'o',
+                                       mnt->mnt_dir);
+                       break;
+               }
+       }
+
+       fclose(fp);
+
+       return mnt != NULL;
+}
+
 static int mount_device(struct discover_device *dev)
 {
        int rc;
@@ -630,29 +746,46 @@ static int mount_device(struct discover_device *dev)
        if (!dev->device_path)
                return -1;
 
-       if (!dev->mount_path)
-               dev->mount_path = join_paths(dev, mount_base(),
-                                               dev->device_path);
+       if (dev->mounted)
+               return 0;
 
-       if (pb_mkdir_recursive(dev->mount_path))
+       if (check_existing_mount(dev))
+               return 0;
+
+       dev->mount_path = join_paths(dev, mount_base(),
+                                       dev->device_path);
+
+       if (pb_mkdir_recursive(dev->mount_path)) {
                pb_log("couldn't create mount directory %s: %s\n",
                                dev->mount_path, strerror(errno));
+               goto err_free;
+       }
 
        rc = process_run_simple(dev, pb_system_apps.mount,
                        dev->device_path, dev->mount_path,
                        "-o", "ro", NULL);
-
-       if (!rc)
+       if (!rc) {
+               dev->mounted = true;
+               dev->mounted_rw = false;
+               dev->unmount = true;
                return 0;
+       }
 
        /* Retry mount without ro option. */
        rc = process_run_simple(dev, pb_system_apps.mount,
                        dev->device_path, dev->mount_path, NULL);
 
-       if (!rc)
+       if (!rc) {
+               dev->mounted = true;
+               dev->mounted_rw = true;
+               dev->unmount = true;
                return 0;
+       }
 
        pb_rmdir_recursive(mount_base(), dev->mount_path);
+err_free:
+       talloc_free(dev->mount_path);
+       dev->mount_path = NULL;
        return -1;
 }
 
@@ -660,7 +793,7 @@ static int umount_device(struct discover_device *dev)
 {
        int status;
 
-       if (!dev->mount_path)
+       if (!dev->mounted || !dev->unmount)
                return 0;
 
        status = process_run_simple(dev, pb_system_apps.umount,
@@ -669,10 +802,47 @@ static int umount_device(struct discover_device *dev)
        if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
                return -1;
 
+       dev->mounted = false;
+       talloc_free(dev->mount_path);
+       dev->mount_path = NULL;
+
        pb_rmdir_recursive(mount_base(), dev->mount_path);
 
        return 0;
 }
+
+int device_request_write(struct discover_device *dev, bool *release)
+{
+       int rc;
+
+       *release = false;
+
+       if (!dev->mounted)
+               return -1;
+
+       if (dev->mounted_rw)
+               return 0;
+
+       rc = process_run_simple(dev, pb_system_apps.mount, dev->mount_path,
+                       "-o", "remount,rw", NULL);
+       if (rc)
+               return -1;
+
+       dev->mounted_rw = true;
+       *release = true;
+       return 0;
+}
+
+void device_release_write(struct discover_device *dev, bool release)
+{
+       if (!release)
+               return;
+
+       process_run_simple(dev, pb_system_apps.mount, dev->mount_path,
+                       "-o", "remount,ro", NULL);
+       dev->mounted_rw = false;
+}
+
 #else
 
 static int umount_device(struct discover_device *dev __attribute__((unused)))
@@ -686,5 +856,17 @@ static int __attribute__((unused)) mount_device(
        return 0;
 }
 
+int device_request_write(struct discover_device *dev __attribute__((unused)),
+               bool *release)
+{
+       *release = true;
+       return 0;
+}
+
+void device_release_write(struct discover_device *dev __attribute__((unused)),
+       bool release __attribute__((unused)))
+{
+}
+
 #endif