#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "device-handler.h" #include "discover-server.h" #include "event.h" #include "parser.h" #include "resource.h" #include "paths.h" #include "boot.h" struct device_handler { struct discover_server *server; int dry_run; struct discover_device **devices; unsigned int n_devices; struct waitset *waitset; struct waiter *timeout_waiter; bool autoboot_enabled; unsigned int sec_to_boot; struct discover_boot_option *default_boot_option; struct list unresolved_boot_options; }; 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) { 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; } 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; 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); boot(handler, handler->default_boot_option, NULL, handler->dry_run, boot_status, handler); 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->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); } 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->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_log("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->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); 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; 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_log("queue: attempting resolution for %s\n", opt->option->id); if (!boot_option_resolve(opt, handler)) continue; pb_log("\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(handler, struct discover_context); ctx->device = device; ctx->conf_url = NULL; ctx->test_data = NULL; list_init(&ctx->boot_options); 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) { handler->n_devices++; handler->devices = talloc_realloc(handler, handler->devices, struct discover_device *, handler->n_devices); handler->devices[handler->n_devices - 1] = device; } /* 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, 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); rc = mount_device(dev); if (rc) goto out; /* run the parsers. This will populate the ctx's boot_option list. */ iterate_parsers(ctx, method); /* add discovered stuff to the handler */ device_handler_discover_context_commit(handler, ctx); out: talloc_free(ctx); return 0; } /* incoming conf event */ int device_handler_conf(struct device_handler *handler, struct discover_device *dev, struct pb_url *url, enum conf_method method) { struct discover_context *ctx; /* create our context */ ctx = device_handler_discover_context_create(handler, dev); ctx->conf_url = url; iterate_parsers(ctx, method); device_handler_discover_context_commit(handler, ctx); talloc_free(ctx); return 0; } static struct discover_boot_option *find_boot_option_by_id( struct device_handler *handler, const char *id) { unsigned int i; for (i = 0; i < handler->n_devices; i++) { struct discover_device *dev = handler->devices[i]; struct discover_boot_option *opt; list_for_each_entry(&dev->boot_options, opt, list) if (!strcmp(opt->option->id, id)) return opt; } return NULL; } void device_handler_boot(struct device_handler *handler, struct boot_command *cmd) { struct discover_boot_option *opt = NULL; if (cmd->option_id && strlen(cmd->option_id)) opt = find_boot_option_by_id(handler, cmd->option_id); boot(handler, opt, cmd, handler->dry_run, boot_status, handler); } void device_handler_cancel_default(struct device_handler *handler) { struct boot_status status; if (handler->timeout_waiter) waiter_remove(handler->timeout_waiter); handler->timeout_waiter = NULL; handler->autoboot_enabled = false; /* we only send status if we had a default boot option queued */ if (!handler->default_boot_option) return; pb_log("Cancelling default boot option\n"); handler->default_boot_option = NULL; status.type = BOOT_STATUS_INFO; status.progress = -1; status.detail = NULL; status.message = "Default boot cancelled"; discover_server_notify_boot_status(handler->server, &status); } #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; if (!dev->device_path) return -1; if (dev->mounted) return 0; 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) { 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) { 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; } static int umount_device(struct discover_device *dev) { int status; if (!dev->mounted || !dev->unmount) return 0; status = process_run_simple(dev, pb_system_apps.umount, dev->mount_path, NULL); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) return -1; dev->mounted = false; pb_rmdir_recursive(mount_base(), dev->mount_path); talloc_free(dev->mount_path); dev->mount_path = NULL; 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))) { return 0; } static int __attribute__((unused)) mount_device( struct discover_device *dev __attribute__((unused))) { 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