#define _GNU_SOURCE #include #include #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) #define STARTUP_TIMEOUT (10 * 1000) struct hotplug_context { struct uloop_process process; void *priv; }; 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 }; static int blockd_ready; enum { MOUNT_UUID, MOUNT_LABEL, MOUNT_ENABLE, MOUNT_TARGET, MOUNT_DEVICE, MOUNT_OPTIONS, MOUNT_AUTOFS, MOUNT_ANON, MOUNT_REMOVE, MOUNT_CHECK_FS, __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 }, [MOUNT_CHECK_FS] = { .name = "check_fs", .type = BLOBMSG_TYPE_INT32 }, }; enum { INFO_DEVICE, __INFO_MAX }; static const struct blobmsg_policy info_policy[__INFO_MAX] = { [INFO_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING }, }; static char* _find_mount_point(char *device) { char *dev, *mp; if (asprintf(&dev, "/dev/%s", device) == -1) exit(ENOMEM); mp = find_mount_point(dev, 0); free(dev); return mp; } static int block(char *cmd, char *action, char *device, char *options, int sync, struct uloop_process *process) { pid_t pid = fork(); int ret = sync; int status = 0; char *argv[6] = { 0 }; int a = 0; switch (pid) { case -1: ULOG_ERR("failed to fork block process\n"); break; case 0: uloop_end(); argv[a++] = "/sbin/block"; argv[a++] = cmd; argv[a++] = action; argv[a++] = device; if (options) argv[a++] = options; execvp(argv[0], argv); ULOG_ERR("failed to spawn %s %s %s\n", *argv, action, device); exit(EXIT_FAILURE); default: if (!sync && process) { process->pid = pid; uloop_process_add(process); } else if (sync) { while (waitpid(pid, &status, 0) < 0 && errno == EINTR) ; ret = WEXITSTATUS(status); if (ret) ULOG_ERR("failed to run block. %s/%s\n", action, device); } break; } return ret; } static int send_block_notification(struct ubus_context *ctx, const char *action, const char *devname, const char *target); static int hotplug_call_mount(struct ubus_context *ctx, const char *action, const char *devname, uloop_process_handler cb, void *priv) { char * const argv[] = { "hotplug-call", "mount", NULL }; struct hotplug_context *c = NULL; pid_t pid; int err; if (cb) { c = calloc(1, sizeof(*c)); if (!c) return -ENOMEM; } pid = fork(); switch (pid) { case -1: if (c) free(c); err = -errno; ULOG_ERR("fork() failed\n"); return err; case 0: uloop_end(); setenv("ACTION", action, 1); setenv("DEVICE", devname, 1); execv("/sbin/hotplug-call", argv); exit(-1); break; default: if (c) { c->process.pid = pid; c->process.cb = cb; c->priv = priv; uloop_process_add(&c->process); } break; } return 0; } static void device_mount_remove_hotplug_cb(struct uloop_process *p, int stat) { struct hotplug_context *hctx = container_of(p, struct hotplug_context, process); struct device *device = hctx->priv; char *mp; if (device->target) unlink(device->target); mp = _find_mount_point(device->name); if (mp) { block("autofs", "remove", device->name, NULL, 0, NULL); free(mp); } free(device); free(hctx); } static void device_mount_remove(struct ubus_context *ctx, struct device *device) { static const char *action = "remove"; hotplug_call_mount(ctx, action, device->name, device_mount_remove_hotplug_cb, device); send_block_notification(ctx, action, device->name, device->target); } static void device_mount_add(struct ubus_context *ctx, struct device *device) { struct stat st; char *path, *tmp; if (asprintf(&path, "/tmp/run/blockd/%s", device->name) == -1) exit(ENOMEM); if (!lstat(device->target, &st)) { if (S_ISLNK(st.st_mode)) unlink(device->target); else if (S_ISDIR(st.st_mode)) rmdir(device->target); } tmp = strrchr(device->target, '/'); if (tmp && tmp != device->target && tmp != &device->target[strlen(path)-1]) { *tmp = '\0'; mkdir_p(device->target, 0755); *tmp = '/'; } if (symlink(path, device->target)) { ULOG_ERR("failed to symlink %s->%s (%d) - %m\n", device->target, path, errno); } else { static const char *action = "add"; hotplug_call_mount(ctx, action, device->name, NULL, NULL); send_block_notification(ctx, action, device->name, device->target); } free(path); } static int device_move(struct device *device_o, struct device *device_n) { char *path; 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); if (asprintf(&path, "/tmp/run/blockd/%s", device_n->name) == -1) exit(ENOMEM); if (symlink(path, device_n->target)) ULOG_ERR("failed to symlink %s->%s (%d) - %m\n", device_n->target, path, errno); free(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 vlist_nop_update(struct vlist_tree *tree, struct vlist_node *node_new, struct vlist_node *node_old) { } VLIST_TREE(devices, avl_strcmp, vlist_nop_update, 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 = NULL; 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 { if (asprintf(&_target, "/mnt/%s", blobmsg_get_string(data[MOUNT_DEVICE])) == -1) exit(ENOMEM); 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) { if (_target) free(_target); return UBUS_STATUS_UNKNOWN_ERROR; } if (data[MOUNT_REMOVE]) { vlist_delete(&devices, &device->node); if (device->autofs) device_mount_remove(ctx, device); else free(device); if (_target) free(_target); } else { struct device *old = vlist_find(&devices, devname, device, node); device->autofs = data[MOUNT_AUTOFS] ? blobmsg_get_u32(data[MOUNT_AUTOFS]) : 0; device->anon = data[MOUNT_ANON] ? blobmsg_get_u32(data[MOUNT_ANON]) : 0; device->msg = _msg; memcpy(_msg, msg, blob_raw_len(msg)); device->name = _name; strcpy(_name, devname); device->target = __target; strcpy(__target, target); if (_target) free(_target); vlist_add(&devices, &device->node, device->name); if (old && device_move(old, device)) { device_mount_remove(ctx, old); device_mount_add(ctx, device); if (!device->autofs) block("mount", NULL, NULL, NULL, 0, NULL); } else if (device->autofs) { device_mount_add(ctx, device); } } return 0; } static int blockd_mount(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { static const char *action = "add"; struct blob_attr *data[__MOUNT_MAX]; struct device *device; char *devname; 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]); device = vlist_find(&devices, devname, device, node); if (!device) return UBUS_STATUS_UNKNOWN_ERROR; hotplug_call_mount(ctx, action, device->name, NULL, NULL); send_block_notification(ctx, action, device->name, device->target); return 0; } struct blockd_umount_context { struct ubus_context *ctx; struct ubus_request_data req; }; static void blockd_umount_hotplug_cb(struct uloop_process *p, int stat) { struct hotplug_context *hctx = container_of(p, struct hotplug_context, process); struct blockd_umount_context *c = hctx->priv; ubus_complete_deferred_request(c->ctx, &c->req, 0); free(c); free(hctx); } static int blockd_umount(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 blockd_umount_context *c; static const char *action = "remove"; char *devname; static char oldtarget[PATH_MAX]; struct device *device; int err; 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]); device = vlist_find(&devices, devname, device, node); if (device) { strncpy(oldtarget, device->target, sizeof(oldtarget)-1); oldtarget[PATH_MAX - 1] = '\0'; } c = calloc(1, sizeof(*c)); if (!c) return UBUS_STATUS_UNKNOWN_ERROR; c->ctx = ctx; ubus_defer_request(ctx, req, &c->req); err = hotplug_call_mount(ctx, action, devname, blockd_umount_hotplug_cb, c); if (err) { free(c); return UBUS_STATUS_UNKNOWN_ERROR; } send_block_notification(ctx, action, devname, oldtarget); return 0; } static void block_info_dump(struct blob_buf *b, struct device *device) { struct blob_attr *v; char *mp; int rem; blob_for_each_attr(v, device->msg, rem) blobmsg_add_blob(b, v); mp = _find_mount_point(device->name); if (mp) { blobmsg_add_string(b, "mount", mp); free(mp); } else if (device->autofs && device->target) { blobmsg_add_string(b, "mount", device->target); } } 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 blob_attr *data[__INFO_MAX]; struct device *device = NULL; blobmsg_parse(info_policy, __INFO_MAX, data, blob_data(msg), blob_len(msg)); if (data[INFO_DEVICE]) { device = vlist_find(&devices, blobmsg_get_string(data[INFO_DEVICE]), device, node); if (!device) return UBUS_STATUS_INVALID_ARGUMENT; } blob_buf_init(&bb, 0); if (device) { block_info_dump(&bb, device); } else { void *a; blobmsg_add_u8(&bb, "ready", blockd_ready); a = blobmsg_open_array(&bb, "devices"); vlist_for_each_element(&devices, device, node) { void *t; t = blobmsg_open_table(&bb, ""); block_info_dump(&bb, device); blobmsg_close_table(&bb, t); } blobmsg_close_array(&bb, a); } ubus_send_reply(ctx, req, bb.head); return 0; } static int block_status(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { blob_buf_init(&bb, 0); blobmsg_add_u8(&bb, "ready", blockd_ready); 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("mount", blockd_mount, mount_policy), UBUS_METHOD("umount", blockd_umount, mount_policy), UBUS_METHOD("info", block_info, info_policy), UBUS_METHOD_NOARG("status", block_status), }; 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), }; /* send ubus event for successful mounts, useful for procd triggers */ static int send_block_notification(struct ubus_context *ctx, const char *action, const char *devname, const char *target) { struct blob_buf buf = { 0 }; char evname[16]; int err; if (!ctx) return -ENXIO; snprintf(evname, sizeof(evname), "mount.%s", action); blob_buf_init(&buf, 0); if (devname) blobmsg_add_string(&buf, "device", devname); if (target) blobmsg_add_string(&buf, "target", target); err = ubus_notify(ctx, &block_object, evname, buf.head, -1); return err; } 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; struct device *device; struct blob_attr *data[__MOUNT_MAX]; char *options = NULL; char optbuf[256]; 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); /* pass the registrant's mount options (e.g. ro) down to block */ device = vlist_find(&devices, pkt->name, device, node); if (device && device->msg) { blobmsg_parse(mount_policy, __MOUNT_MAX, data, blob_data(device->msg), blob_len(device->msg)); if (data[MOUNT_OPTIONS]) options = blobmsg_get_string(data[MOUNT_OPTIONS]); if (data[MOUNT_CHECK_FS] && blobmsg_get_u32(data[MOUNT_CHECK_FS])) { snprintf(optbuf, sizeof(optbuf), "%s%scheck_fs", options ? options : "", options ? "," : ""); options = optbuf; } } if (lstat(pkt->name, &st) == -1) if (block("autofs", "add", (char *)pkt->name, options, 1, NULL)) 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, NULL, 1, NULL); /* the idle mount has been torn down, so its backing block device * now has no holder; signal subscribers (e.g. uvol's deferred reap) * that the device became free */ send_block_notification(&conn.ctx, "umount", pkt.name, NULL); } uloop_timeout_set(t, AUTOFS_EXPIRE_TIMER); } struct uloop_timeout autofs_expire_timer = { .cb = autofs_expire, }; static int autofs_mount(void) { unsigned long 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(EXIT_FAILURE); } 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 glob_t coldplug_gl; static size_t coldplug_next_idx; static void startup_expire(struct uloop_timeout *t); static struct uloop_timeout startup_timer = { .cb = startup_expire, }; static void startup_ready(void) { if (blockd_ready) return; uloop_timeout_cancel(&startup_timer); blockd_ready = 1; send_block_notification(&conn.ctx, "ready", NULL, NULL); } /* * A coldplug helper is allowed to take as long as it needs, e.g. while fsck * repairs a large volume, and interrupting it would leave that volume in a worse * state than not mounting it at all. Announce readiness anyway once the deadline * passes and let the remaining helpers run to completion: the devices they do * register still emit their own notifications, and a consumer which has to wait * for a specific volume can wait for that instead of for a level which may never * be reached. */ static void startup_expire(struct uloop_timeout *t) { ULOG_WARN("startup still busy after %ds, announcing readiness anyway\n", STARTUP_TIMEOUT / 1000); startup_ready(); } /* * block's autofs and hotplug actions call back into this daemon over ubus, so * waiting for a helper synchronously would starve our own ubus loop and make * the call time out. Reap the child via uloop instead and run the next one from * its callback, keeping the loop alive while at most one helper is in flight. */ static bool startup_spawn(char *const argv[], char *const envp[], uloop_process_handler cb) { struct uloop_process *p; pid_t pid; p = calloc(1, sizeof(*p)); if (!p) return false; pid = fork(); if (pid < 0) { ULOG_ERR("failed to fork block %s\n", argv[1]); free(p); return false; } if (!pid) { uloop_end(); if (envp) execve(argv[0], argv, envp); else execvp(argv[0], argv); _exit(EXIT_FAILURE); } p->pid = pid; p->cb = cb; uloop_process_add(p); return true; } static void coldplug_next(void); static void coldplug_cb(struct uloop_process *p, int stat) { free(p); coldplug_next(); } static void coldplug_next(void) { char *argv[] = { "/sbin/block", "hotplug", NULL }; char *env[] = { "ACTION=add", NULL, NULL }; while (coldplug_next_idx < coldplug_gl.gl_pathc) { char *dev = coldplug_gl.gl_pathv[coldplug_next_idx++]; bool spawned; if (asprintf(&env[1], "DEVNAME=%s", basename(dev)) == -1) continue; spawned = startup_spawn(argv, env, coldplug_cb); free(env[1]); if (spawned) return; } globfree(&coldplug_gl); startup_ready(); } static void coldplug(void) { if (glob("/sys/class/block/*", GLOB_NOESCAPE | GLOB_MARK, NULL, &coldplug_gl)) { startup_ready(); return; } coldplug_next(); } static void autofs_start_cb(struct uloop_process *p, int stat) { free(p); coldplug(); } static void blockd_startup(struct uloop_timeout *t) { char *argv[] = { "/sbin/block", "autofs", "start", NULL }; uloop_timeout_set(&startup_timer, STARTUP_TIMEOUT); if (!startup_spawn(argv, NULL, autofs_start_cb)) coldplug(); } struct uloop_timeout startup = { .cb = blockd_startup, }; int main(int argc, char **argv) { /* make sure blockd is in it's own POSIX process group */ setpgrp(); 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; }