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