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