blockd: include missing libubox/utils.h
[project/fstools.git] / blockd.c
1 #define _GNU_SOURCE
2 #include <sys/stat.h>
3 #include <sys/mount.h>
4 #include <sys/wait.h>
5
6 #include <stdlib.h>
7 #include <stdio.h>
8 #include <unistd.h>
9 #include <fcntl.h>
10
11 #include <errno.h>
12
13 #include <linux/limits.h>
14 #include <linux/auto_fs4.h>
15
16 #include <libubox/uloop.h>
17 #include <libubox/utils.h>
18 #include <libubox/vlist.h>
19 #include <libubox/ulog.h>
20 #include <libubox/avl-cmp.h>
21 #include <libubus.h>
22
23 #include "libfstools/libfstools.h"
24
25 #define AUTOFS_MOUNT_PATH "/tmp/run/blockd/"
26 #define AUTOFS_TIMEOUT 30
27 #define AUTOFS_EXPIRE_TIMER (5 * 1000)
28
29 struct hotplug_context {
30 struct uloop_process process;
31 void *priv;
32 };
33
34 struct device {
35 struct vlist_node node;
36 struct blob_attr *msg;
37 char *name;
38 char *target;
39 int autofs;
40 int anon;
41 };
42
43 static struct uloop_fd fd_autofs_read;
44 static int fd_autofs_write = 0;
45 static struct ubus_auto_conn conn;
46 struct blob_buf bb = { 0 };
47
48 enum {
49 MOUNT_UUID,
50 MOUNT_LABEL,
51 MOUNT_ENABLE,
52 MOUNT_TARGET,
53 MOUNT_DEVICE,
54 MOUNT_OPTIONS,
55 MOUNT_AUTOFS,
56 MOUNT_ANON,
57 MOUNT_REMOVE,
58 __MOUNT_MAX
59 };
60
61 static const struct blobmsg_policy mount_policy[__MOUNT_MAX] = {
62 [MOUNT_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING },
63 [MOUNT_LABEL] = { .name = "label", .type = BLOBMSG_TYPE_STRING },
64 [MOUNT_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
65 [MOUNT_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
66 [MOUNT_OPTIONS] = { .name = "options", .type = BLOBMSG_TYPE_STRING },
67 [MOUNT_ENABLE] = { .name = "enabled", .type = BLOBMSG_TYPE_INT32 },
68 [MOUNT_AUTOFS] = { .name = "autofs", .type = BLOBMSG_TYPE_INT32 },
69 [MOUNT_ANON] = { .name = "anon", .type = BLOBMSG_TYPE_INT32 },
70 [MOUNT_REMOVE] = { .name = "remove", .type = BLOBMSG_TYPE_INT32 },
71 };
72
73 enum {
74 INFO_DEVICE,
75 __INFO_MAX
76 };
77
78 static const struct blobmsg_policy info_policy[__INFO_MAX] = {
79 [INFO_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
80 };
81
82 static char*
83 _find_mount_point(char *device)
84 {
85 char *dev, *mp;
86
87 if (asprintf(&dev, "/dev/%s", device) == -1)
88 exit(ENOMEM);
89
90 mp = find_mount_point(dev, 0);
91 free(dev);
92
93 return mp;
94 }
95
96 static int
97 block(char *cmd, char *action, char *device, int sync, struct uloop_process *process)
98 {
99 pid_t pid = fork();
100 int ret = sync;
101 int status;
102 char *argv[5] = { 0 };
103 int a = 0;
104
105 switch (pid) {
106 case -1:
107 ULOG_ERR("failed to fork block process\n");
108 break;
109
110 case 0:
111 uloop_end();
112
113 argv[a++] = "/sbin/block";
114 argv[a++] = cmd;
115 argv[a++] = action;
116 argv[a++] = device;
117 execvp(argv[0], argv);
118 ULOG_ERR("failed to spawn %s %s %s\n", *argv, action, device);
119 exit(EXIT_FAILURE);
120
121 default:
122 if (!sync && process) {
123 process->pid = pid;
124 uloop_process_add(process);
125 } else if (sync) {
126 waitpid(pid, &status, 0);
127 ret = WEXITSTATUS(status);
128 if (ret)
129 ULOG_ERR("failed to run block. %s/%s\n", action, device);
130 }
131 break;
132 }
133
134 return ret;
135 }
136
137 static int send_block_notification(struct ubus_context *ctx, const char *action,
138 const char *devname, const char *target);
139 static int hotplug_call_mount(struct ubus_context *ctx, const char *action,
140 const char *devname, uloop_process_handler cb, void *priv)
141 {
142 char * const argv[] = { "hotplug-call", "mount", NULL };
143 struct hotplug_context *c = NULL;
144 pid_t pid;
145 int err;
146
147 if (cb) {
148 c = calloc(1, sizeof(*c));
149 if (!c)
150 return -ENOMEM;
151 }
152
153 pid = fork();
154 switch (pid) {
155 case -1:
156 if (c)
157 free(c);
158
159 err = -errno;
160 ULOG_ERR("fork() failed\n");
161 return err;
162 case 0:
163 uloop_end();
164
165 setenv("ACTION", action, 1);
166 setenv("DEVICE", devname, 1);
167
168 execv("/sbin/hotplug-call", argv);
169 exit(-1);
170 break;
171 default:
172 if (c) {
173 c->process.pid = pid;
174 c->process.cb = cb;
175 c->priv = priv;
176 uloop_process_add(&c->process);
177 }
178 break;
179 }
180
181 return 0;
182 }
183
184 static void device_mount_remove_hotplug_cb(struct uloop_process *p, int stat)
185 {
186 struct hotplug_context *hctx = container_of(p, struct hotplug_context, process);
187 struct device *device = hctx->priv;
188 char *mp;
189
190 if (device->target)
191 unlink(device->target);
192
193 mp = _find_mount_point(device->name);
194 if (mp) {
195 block("autofs", "remove", device->name, 0, NULL);
196 free(mp);
197 }
198
199 free(device);
200 free(hctx);
201 }
202
203 static void device_mount_remove(struct ubus_context *ctx, struct device *device)
204 {
205 static const char *action = "remove";
206
207 hotplug_call_mount(ctx, action, device->name,
208 device_mount_remove_hotplug_cb, device);
209
210 send_block_notification(ctx, action, device->name, device->target);
211 }
212
213 static void device_mount_add(struct ubus_context *ctx, struct device *device)
214 {
215 struct stat st;
216 char *path, *tmp;
217
218 if (asprintf(&path, "/tmp/run/blockd/%s", device->name) == -1)
219 exit(ENOMEM);
220
221 if (!lstat(device->target, &st)) {
222 if (S_ISLNK(st.st_mode))
223 unlink(device->target);
224 else if (S_ISDIR(st.st_mode))
225 rmdir(device->target);
226 }
227
228 tmp = strrchr(device->target, '/');
229 if (tmp && tmp != device->target && tmp != &device->target[strlen(path)-1]) {
230 *tmp = '\0';
231 mkdir_p(device->target, 0755);
232 *tmp = '/';
233 }
234
235 if (symlink(path, device->target)) {
236 ULOG_ERR("failed to symlink %s->%s (%d) - %m\n", device->target, path, errno);
237 } else {
238 static const char *action = "add";
239 hotplug_call_mount(ctx, action, device->name, NULL, NULL);
240 send_block_notification(ctx, action, device->name, device->target);
241 }
242 free(path);
243 }
244
245 static int
246 device_move(struct device *device_o, struct device *device_n)
247 {
248 char *path;
249
250 if (device_o->autofs != device_n->autofs)
251 return -1;
252
253 if (device_o->anon || device_n->anon)
254 return -1;
255
256 if (device_o->autofs) {
257 unlink(device_o->target);
258 if (asprintf(&path, "/tmp/run/blockd/%s", device_n->name) == -1)
259 exit(ENOMEM);
260
261 if (symlink(path, device_n->target))
262 ULOG_ERR("failed to symlink %s->%s (%d) - %m\n", device_n->target, path, errno);
263
264 free(path);
265 } else {
266 mkdir(device_n->target, 0755);
267 if (mount(device_o->target, device_n->target, NULL, MS_MOVE, NULL))
268 rmdir(device_n->target);
269 else
270 rmdir(device_o->target);
271 }
272
273 return 0;
274 }
275
276 static void vlist_nop_update(struct vlist_tree *tree,
277 struct vlist_node *node_new,
278 struct vlist_node *node_old)
279 {
280 }
281
282 VLIST_TREE(devices, avl_strcmp, vlist_nop_update, false, false);
283
284 static int
285 block_hotplug(struct ubus_context *ctx, struct ubus_object *obj,
286 struct ubus_request_data *req, const char *method,
287 struct blob_attr *msg)
288 {
289 struct blob_attr *data[__MOUNT_MAX];
290 struct device *device;
291 struct blob_attr *_msg;
292 char *devname, *_name;
293 char *target = NULL, *__target;
294 char *_target = NULL;
295
296 blobmsg_parse(mount_policy, __MOUNT_MAX, data, blob_data(msg), blob_len(msg));
297
298 if (!data[MOUNT_DEVICE])
299 return UBUS_STATUS_INVALID_ARGUMENT;
300
301 devname = blobmsg_get_string(data[MOUNT_DEVICE]);
302
303 if (data[MOUNT_TARGET]) {
304 target = blobmsg_get_string(data[MOUNT_TARGET]);
305 } else {
306 if (asprintf(&_target, "/mnt/%s",
307 blobmsg_get_string(data[MOUNT_DEVICE])) == -1)
308 exit(ENOMEM);
309
310 target = _target;
311 }
312
313 if (data[MOUNT_REMOVE])
314 device = vlist_find(&devices, devname, device, node);
315 else
316 device = calloc_a(sizeof(*device), &_msg, blob_raw_len(msg),
317 &_name, strlen(devname) + 1, &__target, strlen(target) + 1);
318
319 if (!device) {
320 if (_target)
321 free(_target);
322
323 return UBUS_STATUS_UNKNOWN_ERROR;
324 }
325
326 if (data[MOUNT_REMOVE]) {
327 vlist_delete(&devices, &device->node);
328
329 if (device->autofs)
330 device_mount_remove(ctx, device);
331 else
332 free(device);
333
334 if (_target)
335 free(_target);
336 } else {
337 struct device *old = vlist_find(&devices, devname, device, node);
338
339 device->autofs = data[MOUNT_AUTOFS] ? blobmsg_get_u32(data[MOUNT_AUTOFS]) : 0;
340 device->anon = data[MOUNT_ANON] ? blobmsg_get_u32(data[MOUNT_ANON]) : 0;
341 device->msg = _msg;
342 memcpy(_msg, msg, blob_raw_len(msg));
343 device->name = _name;
344 strcpy(_name, devname);
345 device->target = __target;
346 strcpy(__target, target);
347 if (_target)
348 free(_target);
349
350 vlist_add(&devices, &device->node, device->name);
351
352 if (old && !device_move(old, device)) {
353 if (device->autofs) {
354 device_mount_remove(ctx, old);
355 device_mount_add(ctx, device);
356 } else {
357 block("mount", NULL, NULL, 0, NULL);
358 }
359 } else if (device->autofs) {
360 device_mount_add(ctx, device);
361 }
362 }
363
364 return 0;
365 }
366
367 static int blockd_mount(struct ubus_context *ctx, struct ubus_object *obj,
368 struct ubus_request_data *req, const char *method,
369 struct blob_attr *msg)
370 {
371 static const char *action = "add";
372 struct blob_attr *data[__MOUNT_MAX];
373 struct device *device;
374 char *devname;
375
376 blobmsg_parse(mount_policy, __MOUNT_MAX, data, blob_data(msg), blob_len(msg));
377
378 if (!data[MOUNT_DEVICE])
379 return UBUS_STATUS_INVALID_ARGUMENT;
380
381 devname = blobmsg_get_string(data[MOUNT_DEVICE]);
382
383 device = vlist_find(&devices, devname, device, node);
384 if (!device)
385 return UBUS_STATUS_UNKNOWN_ERROR;
386
387 hotplug_call_mount(ctx, action, device->name, NULL, NULL);
388 send_block_notification(ctx, action, device->name, device->target);
389
390 return 0;
391 }
392
393 struct blockd_umount_context {
394 struct ubus_context *ctx;
395 struct ubus_request_data req;
396 };
397
398 static void blockd_umount_hotplug_cb(struct uloop_process *p, int stat)
399 {
400 struct hotplug_context *hctx = container_of(p, struct hotplug_context, process);
401 struct blockd_umount_context *c = hctx->priv;
402
403 ubus_complete_deferred_request(c->ctx, &c->req, 0);
404
405 free(c);
406 free(hctx);
407 }
408
409 static int blockd_umount(struct ubus_context *ctx, struct ubus_object *obj,
410 struct ubus_request_data *req, const char *method,
411 struct blob_attr *msg)
412 {
413 struct blob_attr *data[__MOUNT_MAX];
414 struct blockd_umount_context *c;
415 static const char *action = "remove";
416 char *devname;
417 static char oldtarget[PATH_MAX];
418 struct device *device;
419 int err;
420
421 blobmsg_parse(mount_policy, __MOUNT_MAX, data, blob_data(msg), blob_len(msg));
422
423 if (!data[MOUNT_DEVICE])
424 return UBUS_STATUS_INVALID_ARGUMENT;
425
426 devname = blobmsg_get_string(data[MOUNT_DEVICE]);
427 device = vlist_find(&devices, devname, device, node);
428 if (device) {
429 strncpy(oldtarget, device->target, sizeof(oldtarget)-1);
430 oldtarget[PATH_MAX - 1] = '\0';
431 }
432
433 c = calloc(1, sizeof(*c));
434 if (!c)
435 return UBUS_STATUS_UNKNOWN_ERROR;
436
437 c->ctx = ctx;
438 ubus_defer_request(ctx, req, &c->req);
439
440 err = hotplug_call_mount(ctx, action, devname, blockd_umount_hotplug_cb, c);
441 if (err) {
442 free(c);
443 return UBUS_STATUS_UNKNOWN_ERROR;
444 }
445
446 send_block_notification(ctx, action, devname, oldtarget);
447
448 return 0;
449 }
450
451 static void block_info_dump(struct blob_buf *b, struct device *device)
452 {
453 struct blob_attr *v;
454 char *mp;
455 int rem;
456
457 blob_for_each_attr(v, device->msg, rem)
458 blobmsg_add_blob(b, v);
459
460 mp = _find_mount_point(device->name);
461 if (mp) {
462 blobmsg_add_string(b, "mount", mp);
463 free(mp);
464 } else if (device->autofs && device->target) {
465 blobmsg_add_string(b, "mount", device->target);
466 }
467 }
468
469 static int
470 block_info(struct ubus_context *ctx, struct ubus_object *obj,
471 struct ubus_request_data *req, const char *method,
472 struct blob_attr *msg)
473 {
474 struct blob_attr *data[__INFO_MAX];
475 struct device *device = NULL;
476
477 blobmsg_parse(info_policy, __INFO_MAX, data, blob_data(msg), blob_len(msg));
478
479 if (data[INFO_DEVICE]) {
480 device = vlist_find(&devices, blobmsg_get_string(data[INFO_DEVICE]), device, node);
481 if (!device)
482 return UBUS_STATUS_INVALID_ARGUMENT;
483 }
484
485 blob_buf_init(&bb, 0);
486 if (device) {
487 block_info_dump(&bb, device);
488 } else {
489 void *a;
490
491 a = blobmsg_open_array(&bb, "devices");
492 vlist_for_each_element(&devices, device, node) {
493 void *t;
494
495 t = blobmsg_open_table(&bb, "");
496 block_info_dump(&bb, device);
497 blobmsg_close_table(&bb, t);
498 }
499 blobmsg_close_array(&bb, a);
500 }
501 ubus_send_reply(ctx, req, bb.head);
502
503 return 0;
504 }
505
506 static const struct ubus_method block_methods[] = {
507 UBUS_METHOD("hotplug", block_hotplug, mount_policy),
508 UBUS_METHOD("mount", blockd_mount, mount_policy),
509 UBUS_METHOD("umount", blockd_umount, mount_policy),
510 UBUS_METHOD("info", block_info, info_policy),
511 };
512
513 static struct ubus_object_type block_object_type =
514 UBUS_OBJECT_TYPE("block", block_methods);
515
516 static struct ubus_object block_object = {
517 .name = "block",
518 .type = &block_object_type,
519 .methods = block_methods,
520 .n_methods = ARRAY_SIZE(block_methods),
521 };
522
523 /* send ubus event for successful mounts, useful for procd triggers */
524 static int send_block_notification(struct ubus_context *ctx, const char *action,
525 const char *devname, const char *target)
526 {
527 struct blob_buf buf = { 0 };
528 char evname[16] = "mount.";
529 int err;
530
531 if (!ctx)
532 return -ENXIO;
533
534 strncat(evname, action, sizeof(evname) - 1);
535
536 blob_buf_init(&buf, 0);
537
538 if (devname)
539 blobmsg_add_string(&buf, "device", devname);
540
541 if (target)
542 blobmsg_add_string(&buf, "target", target);
543
544 err = ubus_notify(ctx, &block_object, evname, buf.head, -1);
545
546 return err;
547 }
548
549 static void
550 ubus_connect_handler(struct ubus_context *ctx)
551 {
552 int ret;
553
554 ret = ubus_add_object(ctx, &block_object);
555 if (ret)
556 fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));
557 }
558
559 static int autofs_umount(void)
560 {
561 umount2(AUTOFS_MOUNT_PATH, MNT_DETACH);
562 return 0;
563 }
564
565 static void autofs_read_handler(struct uloop_fd *u, unsigned int events)
566 {
567 union autofs_v5_packet_union pktu;
568 const struct autofs_v5_packet *pkt;
569 int cmd = AUTOFS_IOC_READY;
570 struct stat st;
571
572 while (read(u->fd, &pktu, sizeof(pktu)) == -1) {
573 if (errno != EINTR)
574 return;
575 continue;
576 }
577
578 if (pktu.hdr.type != autofs_ptype_missing_indirect) {
579 ULOG_ERR("unknown packet type %d\n", pktu.hdr.type);
580 return;
581 }
582
583 pkt = &pktu.missing_indirect;
584 ULOG_ERR("kernel is requesting a mount -> %s\n", pkt->name);
585 if (lstat(pkt->name, &st) == -1)
586 if (block("autofs", "add", (char *)pkt->name, 1, NULL))
587 cmd = AUTOFS_IOC_FAIL;
588
589 if (ioctl(fd_autofs_write, cmd, pkt->wait_queue_token) < 0)
590 ULOG_ERR("failed to report back to kernel\n");
591 }
592
593 static void autofs_expire(struct uloop_timeout *t)
594 {
595 struct autofs_packet_expire pkt;
596
597 while (ioctl(fd_autofs_write, AUTOFS_IOC_EXPIRE, &pkt) == 0)
598 block("autofs", "remove", pkt.name, 1, NULL);
599
600 uloop_timeout_set(t, AUTOFS_EXPIRE_TIMER);
601 }
602
603 struct uloop_timeout autofs_expire_timer = {
604 .cb = autofs_expire,
605 };
606
607 static int autofs_mount(void)
608 {
609 unsigned long autofs_timeout = AUTOFS_TIMEOUT;
610 int kproto_version;
611 int pipefd[2];
612 char source[64];
613 char opts[64];
614
615 if (pipe(pipefd) < 0) {
616 ULOG_ERR("failed to get kernel pipe\n");
617 return -1;
618 }
619
620 snprintf(source, sizeof(source), "mountd(pid%u)", getpid());
621 snprintf(opts, sizeof(opts), "fd=%d,pgrp=%u,minproto=5,maxproto=5", pipefd[1], (unsigned) getpgrp());
622 mkdir(AUTOFS_MOUNT_PATH, 0555);
623 if (mount(source, AUTOFS_MOUNT_PATH, "autofs", 0, opts)) {
624 ULOG_ERR("unable to mount autofs on %s\n", AUTOFS_MOUNT_PATH);
625 close(pipefd[0]);
626 close(pipefd[1]);
627 return -1;
628 }
629 close(pipefd[1]);
630 fd_autofs_read.fd = pipefd[0];
631 fd_autofs_read.cb = autofs_read_handler;
632 uloop_fd_add(&fd_autofs_read, ULOOP_READ);
633
634 fd_autofs_write = open(AUTOFS_MOUNT_PATH, O_RDONLY);
635 if(fd_autofs_write < 0) {
636 autofs_umount();
637 ULOG_ERR("failed to open direcory\n");
638 return -1;
639 }
640
641 ioctl(fd_autofs_write, AUTOFS_IOC_PROTOVER, &kproto_version);
642 if (kproto_version != 5) {
643 ULOG_ERR("only kernel protocol version 5 is tested. You have %d.\n",
644 kproto_version);
645 exit(EXIT_FAILURE);
646 }
647 if (ioctl(fd_autofs_write, AUTOFS_IOC_SETTIMEOUT, &autofs_timeout))
648 ULOG_ERR("failed to set autofs timeout\n");
649
650 uloop_timeout_set(&autofs_expire_timer, AUTOFS_EXPIRE_TIMER);
651
652 fcntl(fd_autofs_write, F_SETFD, fcntl(fd_autofs_write, F_GETFD) | FD_CLOEXEC);
653 fcntl(fd_autofs_read.fd, F_SETFD, fcntl(fd_autofs_read.fd, F_GETFD) | FD_CLOEXEC);
654
655 return 0;
656 }
657
658 static void blockd_startup_cb(struct uloop_process *p, int stat)
659 {
660 send_block_notification(&conn.ctx, "ready", NULL, NULL);
661 }
662
663 static struct uloop_process startup_process = {
664 .cb = blockd_startup_cb,
665 };
666
667 static void blockd_startup(struct uloop_timeout *t)
668 {
669 block("autofs", "start", NULL, 0, &startup_process);
670 }
671
672 struct uloop_timeout startup = {
673 .cb = blockd_startup,
674 };
675
676 int main(int argc, char **argv)
677 {
678 /* make sure blockd is in it's own POSIX process group */
679 setpgrp();
680
681 ulog_open(ULOG_SYSLOG | ULOG_STDIO, LOG_DAEMON, "blockd");
682 uloop_init();
683
684 autofs_mount();
685
686 conn.cb = ubus_connect_handler;
687 ubus_auto_connect(&conn);
688
689 uloop_timeout_set(&startup, 1000);
690
691 uloop_run();
692 uloop_done();
693
694 autofs_umount();
695
696 vlist_flush_all(&devices);
697
698 return 0;
699 }