uxc: support config in uvol
[project/procd.git] / uxc.c
1 /*
2 * Copyright (C) 2020 Daniel Golle <daniel@makrotopia.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License version 2.1
6 * as published by the Free Software Foundation
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14 #ifndef _GNU_SOURCE
15 #define _GNU_SOURCE
16 #endif
17
18 #include <stdlib.h>
19 #include <stdbool.h>
20 #include <fcntl.h>
21 #include <libubus.h>
22 #include <libubox/avl-cmp.h>
23 #include <libubox/blobmsg.h>
24 #include <libubox/blobmsg_json.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <errno.h>
30 #include <getopt.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <glob.h>
34 #include <signal.h>
35
36 #include "log.h"
37
38 #define UXC_VERSION "0.2"
39 #define OCI_VERSION_STRING "1.0.2"
40 #define UXC_ETC_CONFDIR "/etc/uxc"
41 #define UXC_VOL_CONFDIR "/var/state/uxc"
42
43 static bool verbose = false;
44 static char *confdir = UXC_ETC_CONFDIR;
45
46 struct runtime_state {
47 struct avl_node avl;
48 char *container_name;
49 char *instance_name;
50 char *jail_name;
51 bool running;
52 int runtime_pid;
53 int exitcode;
54 struct blob_attr *ocistate;
55 };
56
57 enum uxc_cmd {
58 CMD_LIST,
59 CMD_BOOT,
60 CMD_START,
61 CMD_STATE,
62 CMD_KILL,
63 CMD_ENABLE,
64 CMD_DISABLE,
65 CMD_DELETE,
66 CMD_CREATE,
67 CMD_UNKNOWN
68 };
69
70 #define OPT_ARGS "ab:fm:p:t:vVw:"
71 static struct option long_options[] = {
72 {"autostart", no_argument, 0, 'a' },
73 {"bundle", required_argument, 0, 'b' },
74 {"force", no_argument, 0, 'f' },
75 {"mounts", required_argument, 0, 'm' },
76 {"pid-file", required_argument, 0, 'p' },
77 {"temp-overlay-size", required_argument, 0, 't' },
78 {"write-overlay-path", required_argument, 0, 'w' },
79 {"verbose", no_argument, 0, 'v' },
80 {"version", no_argument, 0, 'V' },
81 {0, 0, 0, 0 }
82 };
83
84 AVL_TREE(runtime, avl_strcmp, false, NULL);
85 static struct blob_buf conf;
86 static struct blob_attr *blockinfo;
87 static struct blob_attr *fstabinfo;
88 static struct ubus_context *ctx;
89
90 static int usage(void) {
91 printf("syntax: uxc <command> [parameters ...]\n");
92 printf("commands:\n");
93 printf("\tlist\t\t\t\t\t\tlist all configured containers\n");
94 printf("\tcreate <conf>\t\t\t\t\t(re-)create <conf>\n");
95 printf(" [--bundle <path>]\t\t\tOCI bundle at <path>\n");
96 printf(" [--autostart]\t\t\t\tstart on boot\n");
97 printf(" [--temp-overlay-size size]\t\tuse tmpfs overlay with {size}\n");
98 printf(" [--write-overlay-path path]\t\tuse overlay on {path}\n");
99 printf(" [--mounts v1,v2,...,vN]\t\trequire filesystems to be available\n");
100 printf("\tstart <conf>\t\t\t\t\tstart container <conf>\n");
101 printf("\tstate <conf>\t\t\t\t\tget state of container <conf>\n");
102 printf("\tkill <conf> [<signal>]\t\t\t\tsend signal to container <conf>\n");
103 printf("\tenable <conf>\t\t\t\t\tstart container <conf> on boot\n");
104 printf("\tdisable <conf>\t\t\t\t\tdon't start container <conf> on boot\n");
105 printf("\tdelete <conf> [--force]\t\t\t\tdelete <conf>\n");
106 return EINVAL;
107 }
108
109 enum {
110 CONF_NAME,
111 CONF_PATH,
112 CONF_JAIL,
113 CONF_AUTOSTART,
114 CONF_PIDFILE,
115 CONF_TEMP_OVERLAY_SIZE,
116 CONF_WRITE_OVERLAY_PATH,
117 CONF_VOLUMES,
118 __CONF_MAX,
119 };
120
121 static const struct blobmsg_policy conf_policy[__CONF_MAX] = {
122 [CONF_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
123 [CONF_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
124 [CONF_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_STRING },
125 [CONF_AUTOSTART] = { .name = "autostart", .type = BLOBMSG_TYPE_BOOL },
126 [CONF_PIDFILE] = { .name = "pidfile", .type = BLOBMSG_TYPE_STRING },
127 [CONF_TEMP_OVERLAY_SIZE] = { .name = "temp-overlay-size", .type = BLOBMSG_TYPE_STRING },
128 [CONF_WRITE_OVERLAY_PATH] = { .name = "write-overlay-path", .type = BLOBMSG_TYPE_STRING },
129 [CONF_VOLUMES] = { .name = "volumes", .type = BLOBMSG_TYPE_ARRAY },
130 };
131
132 static int conf_load(void)
133 {
134 int gl_flags = GLOB_NOESCAPE | GLOB_MARK;
135 int j, res;
136 glob_t gl;
137 char *globstr;
138 void *c, *o;
139 struct stat sb;
140
141 if (!stat(UXC_VOL_CONFDIR, &sb)) {
142 if (sb.st_mode & S_IFDIR)
143 confdir = UXC_VOL_CONFDIR;
144 }
145
146 if (asprintf(&globstr, "%s/*.json", confdir) == -1)
147 return ENOMEM;
148
149 blob_buf_init(&conf, 0);
150 c = blobmsg_open_table(&conf, NULL);
151
152 res = glob(globstr, gl_flags, NULL, &gl);
153 free(globstr);
154 if (res < 0)
155 return 0;
156
157 for (j = 0; j < gl.gl_pathc; j++) {
158 o = blobmsg_open_table(&conf, strdup(gl.gl_pathv[j]));
159 if (!blobmsg_add_json_from_file(&conf, gl.gl_pathv[j])) {
160 ERROR("uxc: failed to load %s\n", gl.gl_pathv[j]);
161 continue;
162 }
163 blobmsg_close_table(&conf, o);
164 }
165 blobmsg_close_table(&conf, c);
166 globfree(&gl);
167
168 return 0;
169 }
170
171 enum {
172 LIST_INSTANCES,
173 __LIST_MAX,
174 };
175
176 static const struct blobmsg_policy list_policy[__LIST_MAX] = {
177 [LIST_INSTANCES] = { .name = "instances", .type = BLOBMSG_TYPE_TABLE },
178 };
179
180 enum {
181 INSTANCE_RUNNING,
182 INSTANCE_PID,
183 INSTANCE_EXITCODE,
184 INSTANCE_JAIL,
185 __INSTANCE_MAX,
186 };
187
188 static const struct blobmsg_policy instance_policy[__INSTANCE_MAX] = {
189 [INSTANCE_RUNNING] = { .name = "running", .type = BLOBMSG_TYPE_BOOL },
190 [INSTANCE_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 },
191 [INSTANCE_EXITCODE] = { .name = "exit_code", .type = BLOBMSG_TYPE_INT32 },
192 [INSTANCE_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_TABLE },
193 };
194
195 enum {
196 JAIL_NAME,
197 __JAIL_MAX,
198 };
199
200 static const struct blobmsg_policy jail_policy[__JAIL_MAX] = {
201 [JAIL_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
202 };
203
204 static struct runtime_state *
205 runtime_alloc(const char *container_name)
206 {
207 struct runtime_state *s;
208 char *new_name;
209 s = calloc_a(sizeof(*s), &new_name, strlen(container_name) + 1);
210 strcpy(new_name, container_name);
211 s->container_name = new_name;
212 s->avl.key = s->container_name;
213 return s;
214 }
215
216 enum {
217 STATE_OCIVERSION,
218 STATE_ID,
219 STATE_STATUS,
220 STATE_PID,
221 STATE_BUNDLE,
222 STATE_ANNOTATIONS,
223 __STATE_MAX,
224 };
225
226 static const struct blobmsg_policy state_policy[__STATE_MAX] = {
227 [STATE_OCIVERSION] = { .name = "ociVersion", .type = BLOBMSG_TYPE_STRING },
228 [STATE_ID] = { .name = "id", .type = BLOBMSG_TYPE_STRING },
229 [STATE_STATUS] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
230 [STATE_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 },
231 [STATE_BUNDLE] = { .name = "bundle", .type = BLOBMSG_TYPE_STRING },
232 [STATE_ANNOTATIONS] = { .name = "annotations", .type = BLOBMSG_TYPE_TABLE },
233 };
234
235
236 static void ocistate_cb(struct ubus_request *req, int type, struct blob_attr *msg)
237 {
238 struct blob_attr **ocistate = (struct blob_attr **)req->priv;
239 struct blob_attr *tb[__STATE_MAX];
240
241 blobmsg_parse(state_policy, __STATE_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
242
243 if (!tb[STATE_OCIVERSION] ||
244 !tb[STATE_ID] ||
245 !tb[STATE_STATUS] ||
246 !tb[STATE_BUNDLE])
247 return;
248
249 *ocistate = blob_memdup(msg);
250 }
251
252 static void get_ocistate(struct blob_attr **ocistate, const char *name)
253 {
254 char *objname;
255 unsigned int id;
256 int ret;
257 *ocistate = NULL;
258
259 if (asprintf(&objname, "container.%s", name) == -1)
260 exit(ENOMEM);
261
262 ret = ubus_lookup_id(ctx, objname, &id);
263 free(objname);
264 if (ret)
265 return;
266
267 ubus_invoke(ctx, id, "state", NULL, ocistate_cb, ocistate, 3000);
268 }
269
270 static void list_cb(struct ubus_request *req, int type, struct blob_attr *msg)
271 {
272 struct blob_attr *cur, *curi, *tl[__LIST_MAX], *ti[__INSTANCE_MAX], *tj[__JAIL_MAX];
273 int rem, remi;
274 const char *container_name, *instance_name, *jail_name;
275 bool running;
276 int pid, exitcode;
277 struct runtime_state *rs;
278
279 blobmsg_for_each_attr(cur, msg, rem) {
280 container_name = blobmsg_name(cur);
281 blobmsg_parse(list_policy, __LIST_MAX, tl, blobmsg_data(cur), blobmsg_len(cur));
282 if (!tl[LIST_INSTANCES])
283 continue;
284
285 blobmsg_for_each_attr(curi, tl[LIST_INSTANCES], remi) {
286 instance_name = blobmsg_name(curi);
287 blobmsg_parse(instance_policy, __INSTANCE_MAX, ti, blobmsg_data(curi), blobmsg_len(curi));
288
289 if (!ti[INSTANCE_JAIL])
290 continue;
291
292 blobmsg_parse(jail_policy, __JAIL_MAX, tj, blobmsg_data(ti[INSTANCE_JAIL]), blobmsg_len(ti[INSTANCE_JAIL]));
293 if (!tj[JAIL_NAME])
294 continue;
295
296 jail_name = blobmsg_get_string(tj[JAIL_NAME]);
297
298 running = ti[INSTANCE_RUNNING] && blobmsg_get_bool(ti[INSTANCE_RUNNING]);
299
300 if (ti[INSTANCE_PID])
301 pid = blobmsg_get_u32(ti[INSTANCE_PID]);
302 else
303 pid = -1;
304
305 if (ti[INSTANCE_EXITCODE])
306 exitcode = blobmsg_get_u32(ti[INSTANCE_EXITCODE]);
307 else
308 exitcode = -1;
309
310 rs = runtime_alloc(container_name);
311 rs->instance_name = strdup(instance_name);
312 rs->jail_name = strdup(jail_name);
313 rs->runtime_pid = pid;
314 rs->exitcode = exitcode;
315 rs->running = running;
316 avl_insert(&runtime, &rs->avl);
317 }
318 }
319
320 return;
321 }
322
323 static int runtime_load(void)
324 {
325 struct runtime_state *item, *tmp;
326 uint32_t id;
327
328 avl_init(&runtime, avl_strcmp, false, NULL);
329 if (ubus_lookup_id(ctx, "container", &id) ||
330 ubus_invoke(ctx, id, "list", NULL, list_cb, &runtime, 3000))
331 return EIO;
332
333
334 avl_for_each_element_safe(&runtime, item, avl, tmp)
335 get_ocistate(&item->ocistate, item->jail_name);
336
337 return 0;
338 }
339
340 static void runtime_free(void)
341 {
342 struct runtime_state *item, *tmp;
343
344 avl_for_each_element_safe(&runtime, item, avl, tmp) {
345 avl_delete(&runtime, &item->avl);
346 free(item->instance_name);
347 free(item->jail_name);
348 free(item->ocistate);
349 free(item);
350 }
351
352 return;
353 }
354
355 static int uxc_state(char *name)
356 {
357 struct runtime_state *s = avl_find_element(&runtime, name, s, avl);
358 struct blob_attr *ocistate = NULL;
359 struct blob_attr *cur, *tb[__CONF_MAX];
360 int rem;
361 char *bundle = NULL;
362 char *jail_name = NULL;
363 char *state = NULL;
364 static struct blob_buf buf;
365
366 if (s)
367 ocistate = s->ocistate;
368
369 if (ocistate) {
370 state = blobmsg_format_json_indent(ocistate, true, 0);
371 if (!state)
372 return 1;
373
374 printf("%s\n", state);
375 free(state);
376 return 0;
377 }
378
379 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
380 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
381 if (!tb[CONF_NAME] || !tb[CONF_PATH])
382 continue;
383
384 if (!strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) {
385 if (tb[CONF_JAIL])
386 jail_name = blobmsg_get_string(tb[CONF_JAIL]);
387 else
388 jail_name = name;
389
390 bundle = blobmsg_get_string(tb[CONF_PATH]);
391 break;
392 }
393 }
394
395 if (!bundle)
396 return ENOENT;
397
398 blob_buf_init(&buf, 0);
399 blobmsg_add_string(&buf, "ociVersion", OCI_VERSION_STRING);
400 blobmsg_add_string(&buf, "id", jail_name);
401 blobmsg_add_string(&buf, "status", s?"stopped":"uninitialized");
402 blobmsg_add_string(&buf, "bundle", bundle);
403
404 printf("%s\n", blobmsg_format_json_indent(buf.head, true, 0));
405 blob_buf_free(&buf);
406
407 return 0;
408 }
409
410 static int uxc_list(void)
411 {
412 struct blob_attr *cur, *tb[__CONF_MAX], *ts[__STATE_MAX];
413 int rem;
414 struct runtime_state *s = NULL;
415 char *name;
416 char *ocistatus;
417 int container_pid = -1;
418 bool autostart;
419
420 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
421 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
422 if (!tb[CONF_NAME] || !tb[CONF_PATH])
423 continue;
424
425 autostart = tb[CONF_AUTOSTART] && blobmsg_get_bool(tb[CONF_AUTOSTART]);
426 ocistatus = NULL;
427 container_pid = 0;
428 name = blobmsg_get_string(tb[CONF_NAME]);
429 s = avl_find_element(&runtime, name, s, avl);
430
431 if (s && s->ocistate) {
432 blobmsg_parse(state_policy, __STATE_MAX, ts, blobmsg_data(s->ocistate), blobmsg_len(s->ocistate));
433 ocistatus = blobmsg_get_string(ts[STATE_STATUS]);
434 container_pid = blobmsg_get_u32(ts[STATE_PID]);
435 }
436
437 printf("[%c] %s %s", autostart?'*':' ', name, ocistatus?:(s && s->running)?"creating":"stopped");
438
439 if (s && !s->running && (s->exitcode >= 0))
440 printf(" exitcode: %d (%s)", s->exitcode, strerror(s->exitcode));
441
442 if (s && s->running && (s->runtime_pid >= 0))
443 printf(" runtime pid: %d", s->runtime_pid);
444
445 if (s && s->running && (container_pid >= 0))
446 printf(" container pid: %d", container_pid);
447
448 printf("\n");
449 }
450
451 return 0;
452 }
453
454 static int uxc_create(char *name, bool immediately)
455 {
456 static struct blob_buf req;
457 struct blob_attr *cur, *tb[__CONF_MAX];
458 int rem, ret;
459 uint32_t id;
460 struct runtime_state *s = NULL;
461 char *path = NULL, *jailname = NULL, *pidfile = NULL, *tmprwsize = NULL, *writepath = NULL;
462
463 void *in, *ins, *j;
464 bool found = false;
465
466 s = avl_find_element(&runtime, name, s, avl);
467
468 if (s && (s->running))
469 return EEXIST;
470
471 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
472 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
473 if (!tb[CONF_NAME] || !tb[CONF_PATH])
474 continue;
475
476 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
477 continue;
478
479 found = true;
480 }
481
482 if (!found)
483 return ENOENT;
484
485 path = blobmsg_get_string(tb[CONF_PATH]);
486
487 if (tb[CONF_PIDFILE])
488 pidfile = blobmsg_get_string(tb[CONF_PIDFILE]);
489
490 if (tb[CONF_TEMP_OVERLAY_SIZE])
491 tmprwsize = blobmsg_get_string(tb[CONF_TEMP_OVERLAY_SIZE]);
492
493 if (tb[CONF_WRITE_OVERLAY_PATH])
494 writepath = blobmsg_get_string(tb[CONF_WRITE_OVERLAY_PATH]);
495
496
497 if (tb[CONF_JAIL])
498 jailname = blobmsg_get_string(tb[CONF_JAIL]);
499
500 blob_buf_init(&req, 0);
501 blobmsg_add_string(&req, "name", name);
502 ins = blobmsg_open_table(&req, "instances");
503 in = blobmsg_open_table(&req, name);
504 blobmsg_add_string(&req, "bundle", path);
505 j = blobmsg_open_table(&req, "jail");
506 blobmsg_add_string(&req, "name", jailname?:name);
507 blobmsg_add_u8(&req, "immediately", immediately);
508
509 if (pidfile)
510 blobmsg_add_string(&req, "pidfile", pidfile);
511
512 blobmsg_close_table(&req, j);
513
514 if (writepath)
515 blobmsg_add_string(&req, "overlaydir", writepath);
516
517 if (tmprwsize)
518 blobmsg_add_string(&req, "tmpoverlaysize", tmprwsize);
519
520 blobmsg_close_table(&req, in);
521 blobmsg_close_table(&req, ins);
522
523 if (verbose) {
524 char *tmp;
525 tmp = blobmsg_format_json_indent(req.head, true, 1);
526 if (!tmp)
527 return ENOMEM;
528
529 fprintf(stderr, "adding container to procd:\n\t%s\n", tmp);
530 free(tmp);
531 }
532
533 ret = 0;
534 if (ubus_lookup_id(ctx, "container", &id) ||
535 ubus_invoke(ctx, id, "add", req.head, NULL, NULL, 3000)) {
536 ret = EIO;
537 }
538
539 free(jailname);
540 free(path);
541 blob_buf_free(&req);
542
543 return ret;
544 }
545
546 static int uxc_start(const char *name)
547 {
548 char *objname;
549 unsigned int id;
550
551 if (asprintf(&objname, "container.%s", name) == -1)
552 return ENOMEM;
553
554 if (ubus_lookup_id(ctx, objname, &id))
555 return ENOENT;
556
557 return ubus_invoke(ctx, id, "start", NULL, NULL, NULL, 3000);
558 }
559
560 static int uxc_kill(char *name, int signal)
561 {
562 static struct blob_buf req;
563 struct blob_attr *cur, *tb[__CONF_MAX];
564 int rem, ret;
565 char *objname;
566 unsigned int id;
567 struct runtime_state *s = NULL;
568 bool found = false;
569
570 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
571 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
572 if (!tb[CONF_NAME] || !tb[CONF_PATH])
573 continue;
574
575 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
576 continue;
577
578 found = true;
579 break;
580 }
581
582 if (!found)
583 return ENOENT;
584
585 s = avl_find_element(&runtime, name, s, avl);
586
587 if (!s || !(s->running))
588 return ENOENT;
589
590 blob_buf_init(&req, 0);
591 blobmsg_add_u32(&req, "signal", signal);
592 blobmsg_add_string(&req, "name", name);
593
594 if (asprintf(&objname, "container.%s", name) == -1)
595 return ENOMEM;
596
597 ret = ubus_lookup_id(ctx, objname, &id);
598 free(objname);
599 if (ret)
600 return ENOENT;
601
602 if (ubus_invoke(ctx, id, "kill", req.head, NULL, NULL, 3000))
603 return EIO;
604
605 return 0;
606 }
607
608
609 static int uxc_set(char *name, char *path, bool autostart, bool add, char *pidfile, char *_tmprwsize, char *_writepath, char *requiredmounts)
610 {
611 static struct blob_buf req;
612 struct blob_attr *cur, *tb[__CONF_MAX];
613 int rem, ret;
614 bool found = false;
615 char *fname = NULL;
616 char *keeppath = NULL;
617 char *tmprwsize = _tmprwsize;
618 char *writepath = _writepath;
619 char *curvol, *tmp, *mnttok;
620 void *mntarr;
621 int f;
622 struct stat sb;
623
624 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
625 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
626 if (!tb[CONF_NAME] || !tb[CONF_PATH])
627 continue;
628
629 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
630 continue;
631
632 found = true;
633 break;
634 }
635
636 if (found && add)
637 return EEXIST;
638
639 if (!found && !add)
640 return ENOENT;
641
642 if (add && !path)
643 return EINVAL;
644
645 if (path) {
646 if (stat(path, &sb) == -1)
647 return ENOENT;
648
649 if ((sb.st_mode & S_IFMT) != S_IFDIR)
650 return ENOTDIR;
651 }
652
653 ret = mkdir(confdir, 0755);
654
655 if (ret && errno != EEXIST)
656 return ret;
657
658 if (asprintf(&fname, "%s/%s.json", confdir, name) == -1)
659 return ENOMEM;
660
661 f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
662 if (f < 0)
663 return errno;
664
665 if (!add) {
666 keeppath = blobmsg_get_string(tb[CONF_PATH]);
667 if (tb[CONF_WRITE_OVERLAY_PATH])
668 writepath = blobmsg_get_string(tb[CONF_WRITE_OVERLAY_PATH]);
669
670 if (tb[CONF_TEMP_OVERLAY_SIZE])
671 tmprwsize = blobmsg_get_string(tb[CONF_TEMP_OVERLAY_SIZE]);
672 }
673
674 blob_buf_init(&req, 0);
675 blobmsg_add_string(&req, "name", name);
676 blobmsg_add_string(&req, "path", path?:keeppath);
677 blobmsg_add_u8(&req, "autostart", autostart);
678 if (pidfile)
679 blobmsg_add_string(&req, "pidfile", pidfile);
680
681 if (tmprwsize)
682 blobmsg_add_string(&req, "temp-overlay-size", tmprwsize);
683
684 if (writepath)
685 blobmsg_add_string(&req, "write-overlay-path", writepath);
686
687 if (!add && tb[CONF_VOLUMES])
688 blobmsg_add_blob(&req, tb[CONF_VOLUMES]);
689
690 if (add && requiredmounts) {
691 mntarr = blobmsg_open_array(&req, "volumes");
692 for (mnttok = requiredmounts; ; mnttok = NULL) {
693 curvol = strtok_r(mnttok, ",;", &tmp);
694 if (!curvol)
695 break;
696
697 blobmsg_add_string(&req, NULL, curvol);
698 }
699 blobmsg_close_array(&req, mntarr);
700 }
701 tmp = blobmsg_format_json_indent(req.head, true, 0);
702 if (tmp) {
703 dprintf(f, "%s\n", tmp);
704 free(tmp);
705 }
706
707 blob_buf_free(&req);
708
709 return 0;
710 }
711
712 enum {
713 BLOCK_INFO_DEVICE,
714 BLOCK_INFO_UUID,
715 BLOCK_INFO_TARGET,
716 BLOCK_INFO_TYPE,
717 BLOCK_INFO_MOUNT,
718 __BLOCK_INFO_MAX,
719 };
720
721 static const struct blobmsg_policy block_info_policy[__BLOCK_INFO_MAX] = {
722 [BLOCK_INFO_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
723 [BLOCK_INFO_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING },
724 [BLOCK_INFO_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
725 [BLOCK_INFO_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
726 [BLOCK_INFO_MOUNT] = { .name = "mount", .type = BLOBMSG_TYPE_STRING },
727 };
728
729
730 /* check if device 'devname' is mounted according to blockd */
731 static int checkblock(const char *uuid)
732 {
733 struct blob_attr *tb[__BLOCK_INFO_MAX];
734 struct blob_attr *cur;
735 int rem;
736
737 blobmsg_for_each_attr(cur, blockinfo, rem) {
738 blobmsg_parse(block_info_policy, __BLOCK_INFO_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
739
740 if (!tb[BLOCK_INFO_UUID] || !tb[BLOCK_INFO_MOUNT])
741 continue;
742
743 if (!strcmp(uuid, blobmsg_get_string(tb[BLOCK_INFO_UUID])))
744 return 0;
745 }
746
747 return 1;
748 }
749
750 enum {
751 UCI_FSTAB_UUID,
752 UCI_FSTAB_ANONYMOUS,
753 __UCI_FSTAB_MAX,
754 };
755
756 static const struct blobmsg_policy uci_fstab_policy[__UCI_FSTAB_MAX] = {
757 [UCI_FSTAB_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING },
758 [UCI_FSTAB_ANONYMOUS] = { .name = ".anonymous", .type = BLOBMSG_TYPE_BOOL },
759 };
760
761 static const char *resolveuuid(const char *volname)
762 {
763 struct blob_attr *tb[__UCI_FSTAB_MAX];
764 struct blob_attr *cur;
765 const char *mntname;
766 char *tmpvolname, *replc;
767 int rem, res;
768
769 blobmsg_for_each_attr(cur, fstabinfo, rem) {
770 blobmsg_parse(uci_fstab_policy, __UCI_FSTAB_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
771
772 if (!tb[UCI_FSTAB_UUID])
773 continue;
774
775 if (tb[UCI_FSTAB_ANONYMOUS] && blobmsg_get_bool(tb[UCI_FSTAB_ANONYMOUS]))
776 continue;
777
778 mntname = blobmsg_name(cur);
779 if (!mntname)
780 continue;
781
782 tmpvolname = strdup(volname);
783 while ((replc = strchr(tmpvolname, '-')))
784 *replc = '_';
785
786 res = strcmp(tmpvolname, mntname);
787 free(tmpvolname);
788
789 if (!res)
790 return blobmsg_get_string(tb[UCI_FSTAB_UUID]);
791 };
792
793 return volname;
794 };
795
796 /* check status of each required volume */
797 static int checkvolumes(struct blob_attr *volumes)
798 {
799 struct blob_attr *cur;
800 int rem;
801
802 blobmsg_for_each_attr(cur, volumes, rem) {
803 if (checkblock(resolveuuid(blobmsg_get_string(cur))))
804 return 1;
805 }
806
807 return 0;
808 }
809
810 static void block_cb(struct ubus_request *req, int type, struct blob_attr *msg)
811 {
812 blockinfo = blob_memdup(blobmsg_data(msg));
813 }
814
815 static void fstab_cb(struct ubus_request *req, int type, struct blob_attr *msg)
816 {
817 fstabinfo = blob_memdup(blobmsg_data(msg));
818 }
819
820 static int uxc_boot(void)
821 {
822 struct blob_attr *cur, *tb[__CONF_MAX];
823 struct runtime_state *s;
824 static struct blob_buf req;
825 int rem, ret = 0;
826 char *name;
827 unsigned int id;
828
829 ret = ubus_lookup_id(ctx, "block", &id);
830 if (ret)
831 return ENOENT;
832
833 ret = ubus_invoke(ctx, id, "info", NULL, block_cb, NULL, 3000);
834 if (ret)
835 return ENXIO;
836
837 ret = ubus_lookup_id(ctx, "uci", &id);
838 if (ret)
839 return ENOENT;
840
841 blob_buf_init(&req, 0);
842 blobmsg_add_string(&req, "config", "fstab");
843 blobmsg_add_string(&req, "type", "mount");
844
845 ret = ubus_invoke(ctx, id, "get", req.head, fstab_cb, NULL, 3000);
846 if (ret)
847 return ENXIO;
848
849 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
850 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
851 if (!tb[CONF_NAME] || !tb[CONF_PATH] || !tb[CONF_AUTOSTART] || !blobmsg_get_bool(tb[CONF_AUTOSTART]))
852 continue;
853
854 s = avl_find_element(&runtime, blobmsg_get_string(tb[CONF_NAME]), s, avl);
855 if (s)
856 continue;
857
858 /* make sure all volumes are ready before starting */
859 if (tb[CONF_VOLUMES])
860 if (checkvolumes(tb[CONF_VOLUMES]))
861 continue;
862
863 name = strdup(blobmsg_get_string(tb[CONF_NAME]));
864 ret += uxc_create(name, true);
865 free(name);
866 }
867
868 return ret;
869 }
870
871 static int uxc_delete(char *name, bool force)
872 {
873 struct blob_attr *cur, *tb[__CONF_MAX];
874 struct runtime_state *s = NULL;
875 static struct blob_buf req;
876 uint32_t id;
877 int rem, ret = 0;
878 bool found = false;
879 char *fname;
880 struct stat sb;
881
882 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
883 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
884 if (!tb[CONF_NAME] || !tb[CONF_PATH])
885 continue;
886
887 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
888 continue;
889
890 fname = strdup(blobmsg_name(cur));
891 if (!fname)
892 return errno;
893
894 found = true;
895 break;
896 }
897
898 if (!found)
899 return ENOENT;
900
901 s = avl_find_element(&runtime, name, s, avl);
902
903 if (s && s->running) {
904 if (force) {
905 ret = uxc_kill(name, SIGKILL);
906 if (ret)
907 goto errout;
908
909 } else {
910 ret = EWOULDBLOCK;
911 goto errout;
912 }
913 }
914
915 if (s) {
916 ret = ubus_lookup_id(ctx, "container", &id);
917 if (ret)
918 goto errout;
919
920 blob_buf_init(&req, 0);
921 blobmsg_add_string(&req, "name", s->container_name);
922 blobmsg_add_string(&req, "instance", s->instance_name);
923
924 if (ubus_invoke(ctx, id, "delete", req.head, NULL, NULL, 3000)) {
925 blob_buf_free(&req);
926 ret=EIO;
927 goto errout;
928 }
929 }
930
931 if (stat(fname, &sb) == -1) {
932 ret=ENOENT;
933 goto errout;
934 }
935
936 if (unlink(fname) == -1)
937 ret=errno;
938
939 errout:
940 free(fname);
941 return ret;
942 }
943
944 static void reload_conf(void)
945 {
946 blob_buf_free(&conf);
947 conf_load();
948 }
949
950 int main(int argc, char **argv)
951 {
952 enum uxc_cmd cmd = CMD_UNKNOWN;
953 int ret = EINVAL;
954 char *bundle = NULL;
955 char *pidfile = NULL;
956 char *tmprwsize = NULL;
957 char *writepath = NULL;
958 char *requiredmounts = NULL;
959 bool autostart = false;
960 bool force = false;
961 int signal = SIGTERM;
962 int c;
963
964 if (argc < 2)
965 return usage();
966
967 ctx = ubus_connect(NULL);
968 if (!ctx)
969 return ENODEV;
970
971 ret = conf_load();
972 if (ret)
973 goto out;
974
975 ret = runtime_load();
976 if (ret)
977 goto conf_out;
978
979 while (true) {
980 int option_index = 0;
981 c = getopt_long(argc, argv, OPT_ARGS, long_options, &option_index);
982 if (c == -1)
983 break;
984
985 switch (c) {
986 case 'a':
987 autostart = true;
988 break;
989
990 case 'b':
991 bundle = optarg;
992 break;
993
994 case 'f':
995 force = true;
996 break;
997
998 case 'p':
999 pidfile = optarg;
1000 break;
1001
1002 case 't':
1003 tmprwsize = optarg;
1004 break;
1005
1006 case 'v':
1007 verbose = true;
1008 break;
1009
1010 case 'V':
1011 printf("uxc %s\n", UXC_VERSION);
1012 exit(0);
1013
1014 case 'w':
1015 writepath = optarg;
1016 break;
1017
1018 case 'm':
1019 requiredmounts = optarg;
1020 break;
1021 }
1022 }
1023
1024 if (optind == argc)
1025 goto usage_out;
1026
1027 if (!strcmp("list", argv[optind]))
1028 cmd = CMD_LIST;
1029 else if (!strcmp("boot", argv[optind]))
1030 cmd = CMD_BOOT;
1031 else if(!strcmp("start", argv[optind]))
1032 cmd = CMD_START;
1033 else if(!strcmp("state", argv[optind]))
1034 cmd = CMD_STATE;
1035 else if(!strcmp("kill", argv[optind]))
1036 cmd = CMD_KILL;
1037 else if(!strcmp("enable", argv[optind]))
1038 cmd = CMD_ENABLE;
1039 else if(!strcmp("disable", argv[optind]))
1040 cmd = CMD_DISABLE;
1041 else if(!strcmp("delete", argv[optind]))
1042 cmd = CMD_DELETE;
1043 else if(!strcmp("create", argv[optind]))
1044 cmd = CMD_CREATE;
1045
1046 switch (cmd) {
1047 case CMD_LIST:
1048 ret = uxc_list();
1049 break;
1050
1051 case CMD_BOOT:
1052 ret = uxc_boot();
1053 break;
1054
1055 case CMD_START:
1056 if (optind != argc - 2)
1057 goto usage_out;
1058
1059 ret = uxc_start(argv[optind + 1]);
1060 break;
1061
1062 case CMD_STATE:
1063 if (optind != argc - 2)
1064 goto usage_out;
1065
1066 ret = uxc_state(argv[optind + 1]);
1067 break;
1068
1069 case CMD_KILL:
1070 if (optind == (argc - 3))
1071 signal = atoi(argv[optind + 2]);
1072 else if (optind > argc - 2)
1073 goto usage_out;
1074
1075 ret = uxc_kill(argv[optind + 1], signal);
1076 break;
1077
1078 case CMD_ENABLE:
1079 if (optind != argc - 2)
1080 goto usage_out;
1081
1082 ret = uxc_set(argv[optind + 1], NULL, true, false, NULL, NULL, NULL, NULL);
1083 break;
1084
1085 case CMD_DISABLE:
1086 if (optind != argc - 2)
1087 goto usage_out;
1088
1089 ret = uxc_set(argv[optind + 1], NULL, false, false, NULL, NULL, NULL, NULL);
1090 break;
1091
1092 case CMD_DELETE:
1093 if (optind != argc - 2)
1094 goto usage_out;
1095
1096 ret = uxc_delete(argv[optind + 1], force);
1097 break;
1098
1099 case CMD_CREATE:
1100 if (optind != argc - 2)
1101 goto usage_out;
1102
1103 if (bundle) {
1104 ret = uxc_set(argv[optind + 1], bundle, autostart, true, pidfile, tmprwsize, writepath, requiredmounts);
1105 if (ret)
1106 goto runtime_out;
1107
1108 reload_conf();
1109 }
1110
1111 ret = uxc_create(argv[optind + 1], false);
1112 break;
1113
1114 default:
1115 goto usage_out;
1116 }
1117
1118 goto runtime_out;
1119
1120 usage_out:
1121 usage();
1122 runtime_out:
1123 runtime_free();
1124 conf_out:
1125 blob_buf_free(&conf);
1126 out:
1127 ubus_free(ctx);
1128
1129 if (ret != 0)
1130 fprintf(stderr, "uxc error: %s\n", strerror(ret));
1131
1132 return ret;
1133 }