blockd: don't reparse blob msg in the vlist callbacks
[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 device {
29 struct vlist_node node;
30 struct blob_attr *msg;
31 char *name;
32 char *target;
33 int autofs;
34 int anon;
35 };
36
37 static struct uloop_fd fd_autofs_read;
38 static int fd_autofs_write = 0;
39 static struct ubus_auto_conn conn;
40 struct blob_buf bb = { 0 };
41
42 enum {
43 MOUNT_UUID,
44 MOUNT_LABEL,
45 MOUNT_ENABLE,
46 MOUNT_TARGET,
47 MOUNT_DEVICE,
48 MOUNT_OPTIONS,
49 MOUNT_AUTOFS,
50 MOUNT_ANON,
51 MOUNT_REMOVE,
52 __MOUNT_MAX
53 };
54
55 static const struct blobmsg_policy mount_policy[__MOUNT_MAX] = {
56 [MOUNT_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING },
57 [MOUNT_LABEL] = { .name = "label", .type = BLOBMSG_TYPE_STRING },
58 [MOUNT_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
59 [MOUNT_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
60 [MOUNT_OPTIONS] = { .name = "options", .type = BLOBMSG_TYPE_STRING },
61 [MOUNT_ENABLE] = { .name = "enabled", .type = BLOBMSG_TYPE_INT32 },
62 [MOUNT_AUTOFS] = { .name = "autofs", .type = BLOBMSG_TYPE_INT32 },
63 [MOUNT_ANON] = { .name = "anon", .type = BLOBMSG_TYPE_INT32 },
64 [MOUNT_REMOVE] = { .name = "remove", .type = BLOBMSG_TYPE_INT32 },
65 };
66
67 static char*
68 _find_mount_point(char *device)
69 {
70 char dev[32] = { 0 };
71
72 snprintf(dev, sizeof(dev), "/dev/%s", device);
73
74 return find_mount_point(dev, 0);
75 }
76
77 static int
78 block(char *cmd, char *action, char *device)
79 {
80 pid_t pid = fork();
81 int ret = -1;
82 int status;
83 char *argv[5] = { 0 };
84 int a = 0;
85
86 switch (pid) {
87 case -1:
88 ULOG_ERR("failed to fork block process\n");
89 break;
90
91 case 0:
92 argv[a++] = "/sbin/block";
93 argv[a++] = cmd;
94 argv[a++] = action;
95 argv[a++] = device;
96 execvp(argv[0], argv);
97 ULOG_ERR("failed to spawn %s %s %s\n", *argv, action, device);
98 exit(EXIT_FAILURE);
99
100 default:
101 waitpid(pid, &status, 0);
102 ret = WEXITSTATUS(status);
103 if (ret)
104 ULOG_ERR("failed to run block. %s/%s\n", action, device);
105 break;
106 }
107
108 return ret;
109 }
110
111 static void
112 device_free(struct device *device)
113 {
114 if (device->autofs && device->target)
115 unlink(device->target);
116 }
117
118 static void
119 device_add(struct device *device)
120 {
121 char path[64];
122
123 if (!device->autofs)
124 return;
125
126 snprintf(path, sizeof(path), "/tmp/run/blockd/%s", device->name);
127 if (symlink(path, device->target))
128 ULOG_ERR("failed to symlink %s->%s\n", device->target, path);
129 }
130
131 static int
132 device_move(struct device *device_o, struct device *device_n)
133 {
134 char path[64];
135
136 if (device_o->autofs != device_n->autofs)
137 return -1;
138
139 if (device_o->anon || device_n->anon)
140 return -1;
141
142 if (device_o->autofs) {
143 unlink(device_o->target);
144 snprintf(path, sizeof(path), "/tmp/run/blockd/%s", device_n->name);
145 if (symlink(path, device_n->target))
146 ULOG_ERR("failed to symlink %s->%s\n", device_n->target, path);
147 } else {
148 mkdir(device_n->target, 0755);
149 if (mount(device_o->target, device_n->target, NULL, MS_MOVE, NULL))
150 rmdir(device_n->target);
151 else
152 rmdir(device_o->target);
153 }
154
155 return 0;
156 }
157
158 static void
159 devices_update_cb(struct vlist_tree *tree, struct vlist_node *node_new,
160 struct vlist_node *node_old)
161 {
162 struct device *device_o = NULL, *device_n = NULL;
163
164 if (node_old)
165 device_o = container_of(node_old, struct device, node);
166
167 if (node_new)
168 device_n = container_of(node_new, struct device, node);
169
170 if (device_o && device_n) {
171 if (device_move(device_o, device_n)) {
172 device_free(device_o);
173 device_add(device_n);
174 if (!device_n->autofs)
175 block("mount", NULL, NULL);
176 }
177 } else if (device_n) {
178 device_add(device_n);
179 } else {
180 device_free(device_o);
181 }
182
183 if (device_o)
184 free(device_o);
185 }
186
187 VLIST_TREE(devices, avl_strcmp, devices_update_cb, false, false);
188
189 static int
190 block_hotplug(struct ubus_context *ctx, struct ubus_object *obj,
191 struct ubus_request_data *req, const char *method,
192 struct blob_attr *msg)
193 {
194 struct blob_attr *data[__MOUNT_MAX];
195 struct device *device;
196 struct blob_attr *_msg;
197 char *devname, *_name;
198 char *target = NULL, *__target;
199 char _target[32];
200
201 blobmsg_parse(mount_policy, __MOUNT_MAX, data, blob_data(msg), blob_len(msg));
202
203 if (!data[MOUNT_DEVICE])
204 return UBUS_STATUS_INVALID_ARGUMENT;
205
206 devname = blobmsg_get_string(data[MOUNT_DEVICE]);
207
208 if (data[MOUNT_TARGET]) {
209 target = blobmsg_get_string(data[MOUNT_TARGET]);
210 } else {
211 snprintf(_target, sizeof(_target), "/mnt/%s",
212 blobmsg_get_string(data[MOUNT_DEVICE]));
213 target = _target;
214 }
215
216 if (data[MOUNT_REMOVE])
217 device = vlist_find(&devices, devname, device, node);
218 else
219 device = calloc_a(sizeof(*device), &_msg, blob_raw_len(msg),
220 &_name, strlen(devname) + 1, &__target, strlen(target) + 1);
221
222 if (!device)
223 return UBUS_STATUS_UNKNOWN_ERROR;
224
225 vlist_update(&devices);
226 if (data[MOUNT_REMOVE]) {
227 vlist_delete(&devices, &device->node);
228 } else {
229 if (data[MOUNT_AUTOFS])
230 device->autofs = blobmsg_get_u32(data[MOUNT_AUTOFS]);
231 else
232 device->autofs = 0;
233 if (data[MOUNT_ANON])
234 device->anon = blobmsg_get_u32(data[MOUNT_ANON]);
235 else
236 device->anon = 0;
237 device->msg = _msg;
238 memcpy(_msg, msg, blob_raw_len(msg));
239 device->name = _name;
240 strcpy(_name, devname);
241 device->target = __target;
242 strcpy(__target, target);
243 vlist_add(&devices, &device->node, blobmsg_get_string(data[MOUNT_DEVICE]));
244 }
245 vlist_flush(&devices);
246
247 return 0;
248 }
249
250 static int
251 block_info(struct ubus_context *ctx, struct ubus_object *obj,
252 struct ubus_request_data *req, const char *method,
253 struct blob_attr *msg)
254 {
255 struct device *device;
256 void *a;
257
258 blob_buf_init(&bb, 0);
259 a = blobmsg_open_array(&bb, "devices");
260 vlist_for_each_element(&devices, device, node) {
261 void *t = blobmsg_open_table(&bb, "");
262 struct blob_attr *v;
263 char *mp;
264 int rem;
265
266 blob_for_each_attr(v, device->msg, rem)
267 blobmsg_add_blob(&bb, v);
268
269 mp = _find_mount_point(device->name);
270 if (mp) {
271 blobmsg_add_string(&bb, "mount", mp);
272 free(mp);
273 }
274 blobmsg_close_table(&bb, t);
275 }
276 blobmsg_close_array(&bb, a);
277 ubus_send_reply(ctx, req, bb.head);
278
279 return 0;
280 }
281
282 static const struct ubus_method block_methods[] = {
283 UBUS_METHOD("hotplug", block_hotplug, mount_policy),
284 UBUS_METHOD_NOARG("info", block_info),
285 };
286
287 static struct ubus_object_type block_object_type =
288 UBUS_OBJECT_TYPE("block", block_methods);
289
290 static struct ubus_object block_object = {
291 .name = "block",
292 .type = &block_object_type,
293 .methods = block_methods,
294 .n_methods = ARRAY_SIZE(block_methods),
295 };
296
297 static void
298 ubus_connect_handler(struct ubus_context *ctx)
299 {
300 int ret;
301
302 ret = ubus_add_object(ctx, &block_object);
303 if (ret)
304 fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));
305 }
306
307 static int autofs_umount(void)
308 {
309 umount2(AUTOFS_MOUNT_PATH, MNT_DETACH);
310 return 0;
311 }
312
313 static void autofs_read_handler(struct uloop_fd *u, unsigned int events)
314 {
315 union autofs_v5_packet_union pktu;
316 const struct autofs_v5_packet *pkt;
317 int cmd = AUTOFS_IOC_READY;
318 struct stat st;
319
320 while (read(u->fd, &pktu, sizeof(pktu)) == -1) {
321 if (errno != EINTR)
322 return;
323 continue;
324 }
325
326 if (pktu.hdr.type != autofs_ptype_missing_indirect) {
327 ULOG_ERR("unknown packet type %d\n", pktu.hdr.type);
328 return;
329 }
330
331 pkt = &pktu.missing_indirect;
332 ULOG_ERR("kernel is requesting a mount -> %s\n", pkt->name);
333 if (lstat(pkt->name, &st) == -1)
334 if (block("autofs", "add", (char *)pkt->name))
335 cmd = AUTOFS_IOC_FAIL;
336
337 if (ioctl(fd_autofs_write, cmd, pkt->wait_queue_token) < 0)
338 ULOG_ERR("failed to report back to kernel\n");
339 }
340
341 static void autofs_expire(struct uloop_timeout *t)
342 {
343 struct autofs_packet_expire pkt;
344
345 while (ioctl(fd_autofs_write, AUTOFS_IOC_EXPIRE, &pkt) == 0)
346 block("autofs", "remove", pkt.name);
347
348 uloop_timeout_set(t, AUTOFS_EXPIRE_TIMER);
349 }
350
351 struct uloop_timeout autofs_expire_timer = {
352 .cb = autofs_expire,
353 };
354
355 static int autofs_mount(void)
356 {
357 int autofs_timeout = AUTOFS_TIMEOUT;
358 int kproto_version;
359 int pipefd[2];
360 char source[64];
361 char opts[64];
362
363 if (pipe(pipefd) < 0) {
364 ULOG_ERR("failed to get kernel pipe\n");
365 return -1;
366 }
367
368 snprintf(source, sizeof(source), "mountd(pid%u)", getpid());
369 snprintf(opts, sizeof(opts), "fd=%d,pgrp=%u,minproto=5,maxproto=5", pipefd[1], (unsigned) getpgrp());
370 mkdir(AUTOFS_MOUNT_PATH, 0555);
371 if (mount(source, AUTOFS_MOUNT_PATH, "autofs", 0, opts)) {
372 ULOG_ERR("unable to mount autofs on %s\n", AUTOFS_MOUNT_PATH);
373 close(pipefd[0]);
374 close(pipefd[1]);
375 return -1;
376 }
377 close(pipefd[1]);
378 fd_autofs_read.fd = pipefd[0];
379 fd_autofs_read.cb = autofs_read_handler;
380 uloop_fd_add(&fd_autofs_read, ULOOP_READ);
381
382 fd_autofs_write = open(AUTOFS_MOUNT_PATH, O_RDONLY);
383 if(fd_autofs_write < 0) {
384 autofs_umount();
385 ULOG_ERR("failed to open direcory\n");
386 return -1;
387 }
388
389 ioctl(fd_autofs_write, AUTOFS_IOC_PROTOVER, &kproto_version);
390 if (kproto_version != 5) {
391 ULOG_ERR("only kernel protocol version 5 is tested. You have %d.\n",
392 kproto_version);
393 exit(EXIT_FAILURE);
394 }
395 if (ioctl(fd_autofs_write, AUTOFS_IOC_SETTIMEOUT, &autofs_timeout))
396 ULOG_ERR("failed to set autofs timeout\n");
397
398 uloop_timeout_set(&autofs_expire_timer, AUTOFS_EXPIRE_TIMER);
399
400 fcntl(fd_autofs_write, F_SETFD, fcntl(fd_autofs_write, F_GETFD) | FD_CLOEXEC);
401 fcntl(fd_autofs_read.fd, F_SETFD, fcntl(fd_autofs_read.fd, F_GETFD) | FD_CLOEXEC);
402
403 return 0;
404 }
405
406 static void blockd_startup(struct uloop_timeout *t)
407 {
408 block("autofs", "start", NULL);
409 }
410
411 struct uloop_timeout startup = {
412 .cb = blockd_startup,
413 };
414
415 int main(int argc, char **argv)
416 {
417 ulog_open(ULOG_SYSLOG | ULOG_STDIO, LOG_DAEMON, "blockd");
418 uloop_init();
419
420 autofs_mount();
421
422 conn.cb = ubus_connect_handler;
423 ubus_auto_connect(&conn);
424
425 uloop_timeout_set(&startup, 1000);
426
427 uloop_run();
428 uloop_done();
429
430 autofs_umount();
431
432 vlist_flush_all(&devices);
433
434 return 0;
435 }