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