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