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