+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
#include <talloc/talloc.h>
+#include <list/list.h>
+#include <log/log.h>
#include <pb-protocol/pb-protocol.h>
+#include <system/system.h>
#include "device-handler.h"
+#include "discover-server.h"
+#include "event.h"
+#include "parser.h"
+#include "udev.h"
+#include "paths.h"
+
+#define MOUNT_BIN "/bin/mount"
+
+#define UMOUNT_BIN "/bin/umount"
struct device_handler {
struct discover_server *server;
- struct device *devices;
- int n_devices;
+ struct device **devices;
+ unsigned int n_devices;
+
+ struct list contexts;
};
-struct device_handler *device_handler_init(struct discover_server *server)
+struct mount_map {
+ char *device_path;
+ char *mount_point;
+};
+
+/**
+ * device_handler_add - Add a device to the handler device array.
+ */
+
+static void device_handler_add(struct device_handler *handler,
+ struct device *device)
{
- struct device_handler *handler;
+ handler->n_devices++;
+ handler->devices = talloc_realloc(handler, handler->devices,
+ struct device *, handler->n_devices);
+ handler->devices[handler->n_devices - 1] = device;
+}
- handler = talloc(NULL, struct device_handler);
- handler->devices = NULL;
- handler->n_devices = 0;
+/**
+ * device_handler_remove - Remove a device from the handler device array.
+ */
- return handler;
+static void device_handler_remove(struct device_handler *handler,
+ struct device *device)
+{
+ unsigned int i;
+
+ for (i = 0; i < handler->n_devices; i++)
+ if (handler->devices[i] == device)
+ break;
+
+ if (i == handler->n_devices) {
+ assert(0 && "unknown 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 device *, handler->n_devices);
}
-void device_handler_destroy(struct device_handler *devices)
+/**
+ * device_handler_find - Find a handler device by id.
+ */
+
+static struct device *device_handler_find(struct device_handler *handler,
+ const char *id)
{
- talloc_free(devices);
+ unsigned int i;
+
+ assert(id);
+
+ for (i = 0; i < handler->n_devices; i++)
+ if (handler->devices[i]->id
+ && streq(handler->devices[i]->id, id))
+ return handler->devices[i];
+
+ pb_log("%s: unknown device: %s\n", __func__, id);
+ return NULL;
}
-static struct boot_option options[] = {
- {
- .id = "1.1",
- .name = "meep one",
- .description = "meep description one",
- .icon_file = "meep.one.png",
- .boot_args = "root=/dev/sda1",
- },
-};
+/**
+ * device_handler_get_device_count - Get the count of current handler devices.
+ */
-static struct device device = {
- .id = "1",
- .name = "meep",
- .description = "meep description",
- .icon_file = "meep.png",
- .n_options = 1,
- .options = options,
-};
+int device_handler_get_device_count(const struct device_handler *handler)
+{
+ return handler->n_devices;
+}
-int device_handler_get_current_devices(struct device_handler *handler,
- struct device **devices)
+/**
+ * device_handler_get_device - Get a handler device by index.
+ */
+const struct device *device_handler_get_device(
+ const struct device_handler *handler, unsigned int index)
{
- *devices = &device;
- return 1;
+ if (index >= handler->n_devices) {
+ assert(0 && "bad index");
+ return NULL;
+ }
+
+ return handler->devices[index];
}
+static void setup_device_links(struct discover_context *ctx)
+{
+ struct link {
+ char *env, *dir;
+ } *link, links[] = {
+ {
+ .env = "ID_FS_UUID",
+ .dir = "disk/by-uuid"
+ },
+ {
+ .env = "ID_FS_LABEL",
+ .dir = "disk/by-label"
+ },
+ {
+ .env = NULL
+ }
+ };
-int device_handler_event(struct device_handler *handler,
- struct udev_event *event)
+ for (link = links; link->env; link++) {
+ char *enc, *dir, *path;
+ const char *value;
+
+ value = event_get_param(ctx->event, link->env);
+ if (!value || !*value)
+ continue;
+
+ enc = encode_label(ctx, value);
+ dir = join_paths(ctx, mount_base(), link->dir);
+ path = join_paths(ctx, dir, value);
+
+ if (!pb_mkdir_recursive(dir)) {
+ unlink(path);
+ if (symlink(ctx->mount_path, path)) {
+ pb_log("symlink(%s,%s): %s\n",
+ ctx->mount_path, path,
+ strerror(errno));
+ talloc_free(path);
+ } else {
+ int i = ctx->n_links++;
+ ctx->links = talloc_realloc(ctx,
+ ctx->links, char *,
+ ctx->n_links);
+ ctx->links[i] = path;
+ }
+
+ }
+
+ talloc_free(dir);
+ talloc_free(enc);
+ }
+}
+
+static void remove_device_links(struct discover_context *ctx)
+{
+ int i;
+
+ for (i = 0; i < ctx->n_links; i++)
+ unlink(ctx->links[i]);
+}
+
+static int mount_device(struct discover_context *ctx)
+{
+ const char *mountpoint;
+ const char *argv[6];
+
+ if (!ctx->mount_path) {
+ mountpoint = mountpoint_for_device(ctx->device_path);
+ ctx->mount_path = talloc_strdup(ctx, mountpoint);
+ }
+
+ if (pb_mkdir_recursive(ctx->mount_path))
+ pb_log("couldn't create mount directory %s: %s\n",
+ ctx->mount_path, strerror(errno));
+
+ argv[0] = MOUNT_BIN;
+ argv[1] = ctx->device_path;
+ argv[2] = ctx->mount_path;
+ argv[3] = "-o";
+ argv[4] = "ro";
+ argv[5] = NULL;
+
+ if (pb_run_cmd(argv, 1))
+ argv[3] = NULL; /* try without ro */
+
+ if (pb_run_cmd(argv, 1))
+ goto out_rmdir;
+
+ setup_device_links(ctx);
+ return 0;
+
+out_rmdir:
+ pb_rmdir_recursive(mount_base(), ctx->mount_path);
+ return -1;
+}
+
+static int umount_device(struct discover_context *ctx)
+{
+ int status;
+ pid_t pid;
+
+ remove_device_links(ctx);
+
+ pid = fork();
+ if (pid == -1) {
+ pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ if (pid == 0) {
+ execl(UMOUNT_BIN, UMOUNT_BIN, ctx->mount_path, NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ if (waitpid(pid, &status, 0) == -1) {
+ pb_log("%s: waitpid failed: %s\n", __func__,
+ strerror(errno));
+ return -1;
+ }
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ return -1;
+
+ pb_rmdir_recursive(mount_base(), ctx->mount_path);
+
+ return 0;
+}
+
+static struct discover_context *find_context(struct device_handler *handler,
+ const char *id)
+{
+ struct discover_context *ctx;
+
+ list_for_each_entry(&handler->contexts, ctx, list) {
+ if (!strcmp(ctx->id, id))
+ return ctx;
+ }
+
+ return NULL;
+}
+
+static int destroy_context(void *arg)
+{
+ struct discover_context *ctx = arg;
+
+ list_remove(&ctx->list);
+ umount_device(ctx);
+
+ return 0;
+}
+
+static int handle_add_udev_event(struct device_handler *handler,
+ struct event *event)
{
+ struct discover_context *ctx;
+ const char *devname;
+ int rc;
+
+ /* create our context */
+ ctx = talloc(handler, struct discover_context);
+ ctx->event = event;
+ ctx->mount_path = NULL;
+ ctx->links = NULL;
+ ctx->n_links = 0;
+
+ ctx->id = talloc_strdup(ctx, event->device);
+
+ devname = event_get_param(ctx->event, "DEVNAME");
+ if (!devname) {
+ pb_log("no devname for %s?\n", event->device);
+ return 0;
+ }
+
+ ctx->device_path = talloc_strdup(ctx, devname);
+
+ rc = mount_device(ctx);
+ if (rc) {
+ talloc_free(ctx);
+ return 0;
+ }
+
+ list_add(&handler->contexts, &ctx->list);
+ talloc_set_destructor(ctx, destroy_context);
+
+ /* set up the top-level device */
+ ctx->device = talloc_zero(ctx, struct device);
+ ctx->device->id = talloc_strdup(ctx->device, ctx->id);
+ list_init(&ctx->device->boot_options);
+
+ /* run the parsers */
+ iterate_parsers(ctx);
+
+ /* add device to handler device array */
+ device_handler_add(handler, ctx->device);
+
+ discover_server_notify_add(handler->server, ctx->device);
+
return 0;
}
+
+static int handle_remove_udev_event(struct device_handler *handler,
+ struct event *event)
+{
+ struct discover_context *ctx;
+
+ ctx = find_context(handler, event->device);
+ if (!ctx)
+ return 0;
+
+ discover_server_notify_remove(handler->server, ctx->device);
+
+ /* remove device from handler device array */
+ device_handler_remove(handler, ctx->device);
+
+ talloc_free(ctx);
+
+ return 0;
+}
+
+static int handle_add_user_event(struct device_handler *handler,
+ struct event *event)
+{
+ struct device *device;
+
+ assert(event->device);
+
+ device = talloc_zero(handler, struct device);
+
+ if (!device)
+ goto fail;
+
+ device->id = talloc_strdup(device, event->device);
+ list_init(&device->boot_options);
+
+ parse_user_event(device, event);
+
+ discover_server_notify_add(handler->server, device);
+
+ /* add device to handler device array */
+ device_handler_add(handler, device);
+
+ return 0;
+
+fail:
+ talloc_free(device);
+ return 0;
+}
+
+static int handle_remove_user_event(struct device_handler *handler,
+ struct event *event)
+{
+ struct device *device = device_handler_find(handler, event->device);
+
+ if (!device)
+ return 0;
+
+ discover_server_notify_remove(handler->server, device);
+
+ /* remove device from handler device array */
+ device_handler_remove(handler, device);
+
+ talloc_free(device);
+ return 0;
+}
+
+int device_handler_event(struct device_handler *handler,
+ struct event *event)
+{
+ int rc = 0;
+
+ switch (event->type) {
+ case EVENT_TYPE_UDEV:
+ switch (event->action) {
+ case EVENT_ACTION_ADD:
+ rc = handle_add_udev_event(handler, event);
+ break;
+ case EVENT_ACTION_REMOVE:
+ rc = handle_remove_udev_event(handler, event);
+ break;
+ default:
+ pb_log("%s unknown action: %d\n", __func__,
+ event->action);
+ break;
+ }
+ break;
+ case EVENT_TYPE_USER:
+ switch (event->action) {
+ case EVENT_ACTION_ADD:
+ rc = handle_add_user_event(handler, event);
+ break;
+ case EVENT_ACTION_REMOVE:
+ rc = handle_remove_user_event(handler, event);
+ break;
+ default:
+ pb_log("%s unknown action: %d\n", __func__,
+ event->action);
+ break;
+ }
+ break;
+ default:
+ pb_log("%s unknown type: %d\n", __func__, event->type);
+ break;
+ }
+
+ return rc;
+}
+
+struct device_handler *device_handler_init(struct discover_server *server)
+{
+ struct device_handler *handler;
+
+ handler = talloc(NULL, struct device_handler);
+ handler->devices = NULL;
+ handler->n_devices = 0;
+ handler->server = server;
+
+ list_init(&handler->contexts);
+
+ /* set up our mount point base */
+ pb_mkdir_recursive(mount_base());
+
+ parser_init();
+
+ return handler;
+}
+
+void device_handler_destroy(struct device_handler *handler)
+{
+ talloc_free(handler);
+}