+
+/**
+ * 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_uuid(handler, dev->uuid))
+ 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);
+ }
+ }
+ }
+}
+
+void device_handler_add_plugin_option(struct device_handler *handler,
+ struct plugin_option *opt)
+{
+ struct plugin_option *tmp;
+ unsigned int i;
+
+ for (i = 0; i < handler->n_plugins; i++) {
+ tmp = handler->plugins[i];
+ /* If both id and version match, ignore */
+ if (strncmp(opt->id, tmp->id, strlen(opt->id)) == 0 &&
+ strncmp(opt->version, tmp->version,
+ strlen(opt->version) == 0)) {
+ pb_log("discover: Plugin '%s' already exists, ignoring\n",
+ opt->id);
+ return;
+ }
+ }
+
+ handler->plugins = talloc_realloc(handler, handler->plugins,
+ struct plugin_option *, handler->n_plugins + 1);
+ if (!handler->plugins) {
+ pb_log("Failed to allocate memory for new plugin\n");
+ handler->n_plugins = 0;
+ return;
+ }
+
+ handler->plugins[handler->n_plugins++] = opt;
+ discover_server_notify_plugin_option_add(handler->server, opt);
+}
+
+static void device_handler_update_lang(const char *lang)
+{
+ const char *cur_lang;
+
+ if (!lang)
+ return;
+
+ cur_lang = setlocale(LC_ALL, NULL);
+ if (cur_lang && !strcmp(cur_lang, lang))
+ return;
+
+ setlocale(LC_ALL, lang);
+}
+
+static int device_handler_init_sources(struct device_handler *handler)
+{
+ /* init our device sources: udev, network and user events */
+ handler->user_event = user_event_init(handler, handler->waitset);
+ if (!handler->user_event)
+ return -1;
+
+ handler->network = network_init(handler, handler->waitset,
+ handler->dry_run);
+ if (!handler->network)
+ return -1;
+
+ handler->udev = udev_init(handler, handler->waitset);
+ if (!handler->udev)
+ return -1;
+
+ return 0;
+}
+
+static void device_handler_reinit_sources(struct device_handler *handler)
+{
+ /* if we haven't initialised sources previously (becuase we started in
+ * safe mode), then init once here. */
+ if (!(handler->udev || handler->network || handler->user_event)) {
+ device_handler_init_sources(handler);
+ return;
+ }
+
+ system_info_reinit();
+
+ network_shutdown(handler->network);
+ handler->network = network_init(handler, handler->waitset,
+ handler->dry_run);
+
+ udev_reinit(handler->udev);
+}
+
+static inline const char *get_device_path(struct discover_device *dev)
+{
+ return dev->ramdisk ? dev->ramdisk->snapshot : dev->device_path;
+}
+
+static char *check_subvols(struct discover_device *dev)
+{
+ 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)
+{
+ struct stat devstat, mntstat;
+ const char *device_path;
+ struct mntent *mnt;
+ FILE *fp;
+ int rc;
+
+ device_path = get_device_path(dev);
+
+ rc = stat(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->root_path = check_subvols(dev);
+ 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;
+}
+
+/*
+ * 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);
+}
+