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