From 98bbb5a068d6d32ef0ba7db5647a59fc67dfe75b Mon Sep 17 00:00:00 2001 From: John Crispin Date: Tue, 21 Feb 2017 12:52:15 +0100 Subject: [PATCH] blockd: add automounting support Signed-off-by: John Crispin --- .gitignore | 1 + CMakeLists.txt | 8 +- block.c | 158 +++++++++++++++++-- blockd.c | 467 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 617 insertions(+), 17 deletions(-) create mode 100644 blockd.c diff --git a/.gitignore b/.gitignore index d00efbf..90f6381 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ mount_root snapshot_tool ubi block +blockd fs-state *.so *.a diff --git a/CMakeLists.txt b/CMakeLists.txt index e7a97db..08d277f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,14 +54,18 @@ ADD_EXECUTABLE(mount_root mount_root.c) TARGET_LINK_LIBRARIES(mount_root fstools) INSTALL(TARGETS mount_root RUNTIME DESTINATION sbin) +ADD_EXECUTABLE(blockd blockd.c) +TARGET_LINK_LIBRARIES(blockd fstools ubus blobmsg_json) +INSTALL(TARGETS blockd RUNTIME DESTINATION sbin) + find_library(json NAMES json-c json) ADD_EXECUTABLE(block block.c probe.c probe-libblkid.c) IF(DEFINED CMAKE_UBIFS_EXTROOT) ADD_DEFINITIONS(-DUBIFS_EXTROOT) - TARGET_LINK_LIBRARIES(block blkid-tiny dl uci ubox blobmsg_json ubi-utils ${json}) + TARGET_LINK_LIBRARIES(block blkid-tiny dl uci ubox ubus blobmsg_json ubi-utils ${json}) ELSE(DEFINED CMAKE_UBIFS_EXTROOT) - TARGET_LINK_LIBRARIES(block blkid-tiny dl uci ubox blobmsg_json ${json}) + TARGET_LINK_LIBRARIES(block blkid-tiny dl uci ubox ubus blobmsg_json ${json}) ENDIF(DEFINED CMAKE_UBIFS_EXTROOT) INSTALL(TARGETS block RUNTIME DESTINATION sbin) diff --git a/block.c b/block.c index 7db30ac..d5c3937 100644 --- a/block.c +++ b/block.c @@ -43,9 +43,12 @@ #include #include #include +#include #include "probe.h" +#define AUTOFS_MOUNT_PATH "/tmp/run/blockd/" + #ifdef UBIFS_EXTROOT #include "libubi/libubi.h" #endif @@ -55,6 +58,12 @@ enum { TYPE_SWAP, }; +enum { + TYPE_DEV, + TYPE_HOTPLUG, + TYPE_AUTOFS, +}; + struct mount { struct vlist_node node; int type; @@ -67,6 +76,7 @@ struct mount { char *label; char *device; int extroot; + int autofs; int overlay; int disabled_fsck; unsigned int prio; @@ -104,6 +114,7 @@ enum { MOUNT_TARGET, MOUNT_DEVICE, MOUNT_OPTIONS, + MOUNT_AUTOFS, __MOUNT_MAX }; @@ -119,6 +130,7 @@ static const struct blobmsg_policy mount_policy[__MOUNT_MAX] = { [MOUNT_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING }, [MOUNT_OPTIONS] = { .name = "options", .type = BLOBMSG_TYPE_STRING }, [MOUNT_ENABLE] = { .name = "enabled", .type = BLOBMSG_TYPE_INT32 }, + [MOUNT_AUTOFS] = { .name = "autofs", .type = BLOBMSG_TYPE_INT32 }, }; static const struct uci_blob_param_list mount_attr_list = { @@ -247,7 +259,7 @@ static int mount_add(struct uci_section *s) struct blob_attr *tb[__MOUNT_MAX] = { 0 }; struct mount *m; - blob_buf_init(&b, 0); + blob_buf_init(&b, 0); uci_to_blob(&b, s, &mount_attr_list); blobmsg_parse(mount_policy, __MOUNT_MAX, tb, blob_data(b.head), blob_len(b.head)); @@ -263,7 +275,10 @@ static int mount_add(struct uci_section *s) m->label = blobmsg_get_strdup(tb[MOUNT_LABEL]); m->target = blobmsg_get_strdup(tb[MOUNT_TARGET]); m->device = blobmsg_get_basename(tb[MOUNT_DEVICE]); - + if (tb[MOUNT_AUTOFS]) + m->autofs = blobmsg_get_u32(tb[MOUNT_AUTOFS]); + else + m->autofs = 0; parse_mount_options(m, blobmsg_get_strdup(tb[MOUNT_OPTIONS])); m->overlay = m->extroot = 0; @@ -900,7 +915,65 @@ static int handle_mount(const char *source, const char *target, return err; } -static int mount_device(struct probe_info *pr, int hotplug) +static void blockd_notify(char *device, struct mount *m, struct probe_info *pr) +{ + struct ubus_context *ctx = ubus_connect(NULL); + uint32_t id; + + if (!ctx) + return; + + if (!ubus_lookup_id(ctx, "block", &id)) { + struct blob_buf buf = { 0 }; + char *d = strrchr(device, '/'); + + if (d) + d++; + else + d = device; + + blob_buf_init(&buf, 0); + + if (m) { + + blobmsg_add_string(&buf, "device", d); + if (m->uuid) + blobmsg_add_string(&buf, "uuid", m->uuid); + if (m->label) + blobmsg_add_string(&buf, "label", m->label); + if (m->target) + blobmsg_add_string(&buf, "target", m->target); + if (m->options) + blobmsg_add_string(&buf, "options", m->options); + if (m->autofs) + blobmsg_add_u32(&buf, "autofs", m->autofs); + if (pr->type) + blobmsg_add_string(&buf, "type", pr->type); + if (pr->version) + blobmsg_add_string(&buf, "version", pr->version); + } else if (pr) { + blobmsg_add_string(&buf, "device", d); + if (pr->uuid) + blobmsg_add_string(&buf, "uuid", pr->uuid); + if (pr->label) + blobmsg_add_string(&buf, "label", pr->label); + if (pr->type) + blobmsg_add_string(&buf, "type", pr->type); + if (pr->version) + blobmsg_add_string(&buf, "version", pr->version); + blobmsg_add_u32(&buf, "anon", 1); + } else { + blobmsg_add_string(&buf, "device", d); + blobmsg_add_u32(&buf, "remove", 1); + } + + ubus_invoke(ctx, id, "hotplug", buf.head, NULL, NULL, 3000); + } + + ubus_free(ctx); +} + +static int mount_device(struct probe_info *pr, int type) { struct mount *m; char *device; @@ -912,7 +985,7 @@ static int mount_device(struct probe_info *pr, int hotplug) device = basename(pr->dev); if (!strcmp(pr->type, "swap")) { - if (hotplug && !auto_swap) + if ((type == TYPE_HOTPLUG) && !auto_swap) return -1; m = find_swap(pr->uuid, pr->label, device); if (m || anon_swap) @@ -921,11 +994,8 @@ static int mount_device(struct probe_info *pr, int hotplug) return 0; } - if (hotplug && !auto_mount) - return -1; - mp = find_mount_point(pr->dev); - if (mp) { + if (mp && (type != TYPE_HOTPLUG)) { ULOG_ERR("%s is already mounted on %s\n", pr->dev, mp); free(mp); return -1; @@ -935,11 +1005,35 @@ static int mount_device(struct probe_info *pr, int hotplug) if (m && m->extroot) return -1; + if (m) switch (type) { + case TYPE_HOTPLUG: + blockd_notify(device, m, pr); + if (m->autofs) + return 0; + if (!auto_mount) + return -1; + break; + case TYPE_AUTOFS: + if (!m->autofs) + return -1; + break; + case TYPE_DEV: + if (m->autofs) + return -1; + break; + } else if (type == TYPE_HOTPLUG) { + blockd_notify(device, NULL, pr); + } + if (m) { char *target = m->target; char _target[32]; int err = 0; + if (m->autofs) { + snprintf(_target, sizeof(_target), "/tmp/run/blockd/%s", device); + target = _target; + } if (!target) { snprintf(_target, sizeof(_target), "/mnt/%s", device); target = _target; @@ -1013,13 +1107,10 @@ static int umount_device(struct probe_info *pr) return err; } -static int main_hotplug(int argc, char **argv) +static int mount_action(char *action, char *device, int type) { char path[32]; - char *action, *device, *mount_point; - - action = getenv("ACTION"); - device = getenv("DEVNAME"); + char *mount_point; if (!action || !device) return -1; @@ -1027,6 +1118,10 @@ static int main_hotplug(int argc, char **argv) if (!strcmp(action, "remove")) { int err = 0; + + if (type == TYPE_HOTPLUG) + blockd_notify(device, NULL, NULL); + mount_point = find_mount_point(path); if (mount_point) err = umount2(mount_point, MNT_DETACH); @@ -1047,7 +1142,37 @@ static int main_hotplug(int argc, char **argv) return -1; cache_load(0); - return mount_device(find_block_info(NULL, NULL, path), 1); + return mount_device(find_block_info(NULL, NULL, path), type); +} + +static int main_hotplug(int argc, char **argv) +{ + return mount_action(getenv("ACTION"), getenv("DEVNAME"), TYPE_HOTPLUG); +} + +static int main_autofs(int argc, char **argv) +{ + if (argc < 3) + return -1; + + if (!strcmp(argv[2], "start")) { + struct probe_info *pr; + + if (config_load(NULL)) + return -1; + + cache_load(0); + list_for_each_entry(pr, &devices, list) { + struct mount *m = find_block(pr->uuid, pr->label, NULL, NULL); + + if (m && m->autofs) + mount_device(pr, TYPE_HOTPLUG); + else + blockd_notify(pr->dev, m, pr); + } + return 0; + } + return mount_action(argv[2], argv[3], TYPE_AUTOFS); } static int find_block_mtd(char *name, char *part, int plen) @@ -1425,7 +1550,7 @@ static int main_mount(int argc, char **argv) cache_load(1); list_for_each_entry(pr, &devices, list) - mount_device(pr, 0); + mount_device(pr, TYPE_DEV); handle_swapfiles(true); @@ -1645,6 +1770,9 @@ int main(int argc, char **argv) if (!strcmp(argv[1], "hotplug")) return main_hotplug(argc, argv); + if (!strcmp(argv[1], "autofs")) + return main_autofs(argc, argv); + if (!strcmp(argv[1], "extroot")) return main_extroot(argc, argv); diff --git a/blockd.c b/blockd.c new file mode 100644 index 0000000..3af5390 --- /dev/null +++ b/blockd.c @@ -0,0 +1,467 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "libfstools/libfstools.h" + +#define AUTOFS_MOUNT_PATH "/tmp/run/blockd/" +#define AUTOFS_TIMEOUT 30 +#define AUTOFS_EXPIRE_TIMER (5 * 1000) + +struct device { + struct vlist_node node; + struct blob_attr *msg; + char *name; + char *target; + int autofs; + int anon; +}; + +static struct uloop_fd fd_autofs_read; +static int fd_autofs_write = 0; +static struct ubus_auto_conn conn; +struct blob_buf bb = { 0 }; + +enum { + MOUNT_UUID, + MOUNT_LABEL, + MOUNT_ENABLE, + MOUNT_TARGET, + MOUNT_DEVICE, + MOUNT_OPTIONS, + MOUNT_AUTOFS, + MOUNT_ANON, + MOUNT_REMOVE, + __MOUNT_MAX +}; + +static const struct blobmsg_policy mount_policy[__MOUNT_MAX] = { + [MOUNT_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING }, + [MOUNT_LABEL] = { .name = "label", .type = BLOBMSG_TYPE_STRING }, + [MOUNT_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING }, + [MOUNT_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING }, + [MOUNT_OPTIONS] = { .name = "options", .type = BLOBMSG_TYPE_STRING }, + [MOUNT_ENABLE] = { .name = "enabled", .type = BLOBMSG_TYPE_INT32 }, + [MOUNT_AUTOFS] = { .name = "autofs", .type = BLOBMSG_TYPE_INT32 }, + [MOUNT_ANON] = { .name = "anon", .type = BLOBMSG_TYPE_INT32 }, + [MOUNT_REMOVE] = { .name = "remove", .type = BLOBMSG_TYPE_INT32 }, +}; + +static char* +_find_mount_point(char *device) +{ + char dev[32] = { 0 }; + + snprintf(dev, sizeof(dev), "/dev/%s", device); + + return find_mount_point(dev, 0); +} + +static int +block(char *cmd, char *action, char *device) +{ + pid_t pid = fork(); + int ret = -1; + int status; + char *argv[5] = { 0 }; + int a = 0; + + switch (pid) { + case -1: + ULOG_ERR("failed to fork block process\n"); + break; + + case 0: + argv[a++] = "/sbin/block"; + argv[a++] = cmd; + argv[a++] = action; + argv[a++] = device; + execvp(argv[0], argv); + ULOG_ERR("failed to spawn %s %s %s\n", *argv, action, device); + exit(-1); + + default: + waitpid(pid, &status, 0); + ret = WEXITSTATUS(status); + if (ret) + ULOG_ERR("failed to run block. %s/%s\n", action, device); + break; + } + + return ret; +} + +static void +device_free(struct device *device) +{ + struct blob_attr *data[__MOUNT_MAX]; + char *target = NULL; + char *path = NULL, _path[64], *mp; + + blobmsg_parse(mount_policy, __MOUNT_MAX, data, + blob_data(device->msg), blob_len(device->msg)); + + if (data[MOUNT_AUTOFS]) { + target = device->target; + snprintf(_path, sizeof(_path), "/tmp/run/blockd/%s", + blobmsg_get_string(data[MOUNT_DEVICE])); + path = _path; + } else { + path = target = device->target; + } + + mp = _find_mount_point(device->name); + if (path && mp) + if (umount2(path, MNT_DETACH)) + ULOG_ERR("failed to unmount %s\n", path); + free(mp); + + if (!target) + return; + + if (data[MOUNT_AUTOFS]) + unlink(target); + else + rmdir(target); +} + +static void +device_add(struct device *device) +{ + struct blob_attr *data[__MOUNT_MAX]; + char path[64]; + + blobmsg_parse(mount_policy, __MOUNT_MAX, data, + blob_data(device->msg), blob_len(device->msg)); + + if (!data[MOUNT_AUTOFS]) + return; + + snprintf(path, sizeof(path), "/tmp/run/blockd/%s", + blobmsg_get_string(data[MOUNT_DEVICE])); + if (symlink(path, device->target)) + ULOG_ERR("failed to symlink %s->%s\n", device->target, path); +} + +static int +device_move(struct device *device_o, struct device *device_n) +{ + char path[64]; + + if (device_o->autofs != device_n->autofs) + return -1; + + if (device_o->anon || device_n->anon) + return -1; + + if (device_o->autofs) { + unlink(device_o->target); + snprintf(path, sizeof(path), "/tmp/run/blockd/%s", device_n->name); + if (symlink(path, device_n->target)) + ULOG_ERR("failed to symlink %s->%s\n", device_n->target, path); + } else { + mkdir(device_n->target, 0755); + if (mount(device_o->target, device_n->target, NULL, MS_MOVE, NULL)) + rmdir(device_n->target); + else + rmdir(device_o->target); + } + + return 0; +} + +static void +devices_update_cb(struct vlist_tree *tree, struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct device *device_o = NULL, *device_n = NULL; + + if (node_old) + device_o = container_of(node_old, struct device, node); + + if (node_new) + device_n = container_of(node_new, struct device, node); + + if (device_o && device_n) { + if (device_move(device_o, device_n)) { + device_free(device_o); + device_add(device_n); + if (!device_n->autofs) + block("mount", NULL, NULL); + } + } else if (device_n) { + device_add(device_n); + } else { + device_free(device_o); + } + + if (device_o) + free(device_o); +} + +VLIST_TREE(devices, avl_strcmp, devices_update_cb, false, false); + +static int +block_hotplug(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *data[__MOUNT_MAX]; + struct device *device; + struct blob_attr *_msg; + char *devname, *_name; + char *target = NULL, *__target; + char _target[32]; + + blobmsg_parse(mount_policy, __MOUNT_MAX, data, blob_data(msg), blob_len(msg)); + + if (!data[MOUNT_DEVICE]) + return UBUS_STATUS_INVALID_ARGUMENT; + + devname = blobmsg_get_string(data[MOUNT_DEVICE]); + + if (data[MOUNT_TARGET]) { + target = blobmsg_get_string(data[MOUNT_TARGET]); + } else { + snprintf(_target, sizeof(_target), "/mnt/%s", + blobmsg_get_string(data[MOUNT_DEVICE])); + target = _target; + } + + if (data[MOUNT_REMOVE]) + device = vlist_find(&devices, devname, device, node); + else + device = calloc_a(sizeof(*device), &_msg, blob_raw_len(msg), + &_name, strlen(devname) + 1, &__target, strlen(target) + 1); + + if (!device) + return UBUS_STATUS_UNKNOWN_ERROR; + + vlist_update(&devices); + if (data[MOUNT_REMOVE]) { + vlist_delete(&devices, &device->node); + } else { + if (data[MOUNT_AUTOFS]) + device->autofs = blobmsg_get_u32(data[MOUNT_AUTOFS]); + else + device->autofs = 0; + if (data[MOUNT_ANON]) + device->anon = blobmsg_get_u32(data[MOUNT_ANON]); + else + device->anon = 0; + device->msg = _msg; + memcpy(_msg, msg, blob_raw_len(msg)); + device->name = _name; + strcpy(_name, devname); + device->target = __target; + strcpy(__target, target); + vlist_add(&devices, &device->node, blobmsg_get_string(data[MOUNT_DEVICE])); + } + vlist_flush(&devices); + + return 0; +} + +static int +block_info(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct device *device; + void *a; + + blob_buf_init(&bb, 0); + a = blobmsg_open_array(&bb, "devices"); + vlist_for_each_element(&devices, device, node) { + void *t = blobmsg_open_table(&bb, ""); + struct blob_attr *v; + char *mp; + int rem; + + blob_for_each_attr(v, device->msg, rem) + blobmsg_add_blob(&bb, v); + + mp = _find_mount_point(device->name); + if (mp) { + blobmsg_add_string(&bb, "mount", mp); + free(mp); + } + blobmsg_close_table(&bb, t); + } + blobmsg_close_array(&bb, a); + ubus_send_reply(ctx, req, bb.head); + + return 0; +} + +static const struct ubus_method block_methods[] = { + UBUS_METHOD("hotplug", block_hotplug, mount_policy), + UBUS_METHOD_NOARG("info", block_info), +}; + +static struct ubus_object_type block_object_type = + UBUS_OBJECT_TYPE("block", block_methods); + +static struct ubus_object block_object = { + .name = "block", + .type = &block_object_type, + .methods = block_methods, + .n_methods = ARRAY_SIZE(block_methods), +}; + +static void +ubus_connect_handler(struct ubus_context *ctx) +{ + int ret; + + ret = ubus_add_object(ctx, &block_object); + if (ret) + fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret)); +} + +static int autofs_umount(void) +{ + umount2(AUTOFS_MOUNT_PATH, MNT_DETACH); + return 0; +} + +static void autofs_read_handler(struct uloop_fd *u, unsigned int events) +{ + union autofs_v5_packet_union pktu; + const struct autofs_v5_packet *pkt; + int cmd = AUTOFS_IOC_READY; + struct stat st; + + while (read(u->fd, &pktu, sizeof(pktu)) == -1) { + if (errno != EINTR) + return; + continue; + } + + if (pktu.hdr.type != autofs_ptype_missing_indirect) { + ULOG_ERR("unknown packet type %d\n", pktu.hdr.type); + return; + } + + pkt = &pktu.missing_indirect; + ULOG_ERR("kernel is requesting a mount -> %s\n", pkt->name); + if (lstat(pkt->name, &st) == -1) + if (block("autofs", "add", (char *)pkt->name)) + cmd = AUTOFS_IOC_FAIL; + + if (ioctl(fd_autofs_write, cmd, pkt->wait_queue_token) < 0) + ULOG_ERR("failed to report back to kernel\n"); +} + +static void autofs_expire(struct uloop_timeout *t) +{ + struct autofs_packet_expire pkt; + + while (ioctl(fd_autofs_write, AUTOFS_IOC_EXPIRE, &pkt) == 0) + block("autofs", "remove", pkt.name); + + uloop_timeout_set(t, AUTOFS_EXPIRE_TIMER); +} + +struct uloop_timeout autofs_expire_timer = { + .cb = autofs_expire, +}; + +static int autofs_mount(void) +{ + int autofs_timeout = AUTOFS_TIMEOUT; + int kproto_version; + int pipefd[2]; + char source[64]; + char opts[64]; + + if (pipe(pipefd) < 0) { + ULOG_ERR("failed to get kernel pipe\n"); + return -1; + } + + snprintf(source, sizeof(source), "mountd(pid%u)", getpid()); + snprintf(opts, sizeof(opts), "fd=%d,pgrp=%u,minproto=5,maxproto=5", pipefd[1], (unsigned) getpgrp()); + mkdir(AUTOFS_MOUNT_PATH, 0555); + if (mount(source, AUTOFS_MOUNT_PATH, "autofs", 0, opts)) { + ULOG_ERR("unable to mount autofs on %s\n", AUTOFS_MOUNT_PATH); + close(pipefd[0]); + close(pipefd[1]); + return -1; + } + close(pipefd[1]); + fd_autofs_read.fd = pipefd[0]; + fd_autofs_read.cb = autofs_read_handler; + uloop_fd_add(&fd_autofs_read, ULOOP_READ); + + fd_autofs_write = open(AUTOFS_MOUNT_PATH, O_RDONLY); + if(fd_autofs_write < 0) { + autofs_umount(); + ULOG_ERR("failed to open direcory\n"); + return -1; + } + + ioctl(fd_autofs_write, AUTOFS_IOC_PROTOVER, &kproto_version); + if (kproto_version != 5) { + ULOG_ERR("only kernel protocol version 5 is tested. You have %d.\n", + kproto_version); + exit(1); + } + if (ioctl(fd_autofs_write, AUTOFS_IOC_SETTIMEOUT, &autofs_timeout)) + ULOG_ERR("failed to set autofs timeout\n"); + + uloop_timeout_set(&autofs_expire_timer, AUTOFS_EXPIRE_TIMER); + + fcntl(fd_autofs_write, F_SETFD, fcntl(fd_autofs_write, F_GETFD) | FD_CLOEXEC); + fcntl(fd_autofs_read.fd, F_SETFD, fcntl(fd_autofs_read.fd, F_GETFD) | FD_CLOEXEC); + + return 0; +} + +static void blockd_startup(struct uloop_timeout *t) +{ + block("autofs", "start", NULL); +} + +struct uloop_timeout startup = { + .cb = blockd_startup, +}; + +int main(int argc, char **argv) +{ + ulog_open(ULOG_SYSLOG | ULOG_STDIO, LOG_DAEMON, "blockd"); + uloop_init(); + + autofs_mount(); + + conn.cb = ubus_connect_handler; + ubus_auto_connect(&conn); + + uloop_timeout_set(&startup, 1000); + + uloop_run(); + uloop_done(); + + autofs_umount(); + + vlist_flush_all(&devices); + + return 0; +} -- 2.11.0