procd: service_stop_all: also kill inittab actions
[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 break;
534 }
535
536 if (!found)
537 return ENOENT;
538
539 path = blobmsg_get_string(tb[CONF_PATH]);
540
541 if (tb[CONF_PIDFILE])
542 pidfile = blobmsg_get_string(tb[CONF_PIDFILE]);
543
544 if (tb[CONF_TEMP_OVERLAY_SIZE])
545 tmprwsize = blobmsg_get_string(tb[CONF_TEMP_OVERLAY_SIZE]);
546
547 if (tb[CONF_WRITE_OVERLAY_PATH])
548 writepath = blobmsg_get_string(tb[CONF_WRITE_OVERLAY_PATH]);
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 blob_buf_free(&req);
590 ret = EIO;
591 }
592
593 return ret;
594 }
595
596 static int uxc_start(const char *name)
597 {
598 char *objname;
599 unsigned int id;
600
601 if (asprintf(&objname, "container.%s", name) == -1)
602 return ENOMEM;
603
604 if (ubus_lookup_id(ctx, objname, &id))
605 return ENOENT;
606
607 return ubus_invoke(ctx, id, "start", NULL, NULL, NULL, 3000);
608 }
609
610 static int uxc_kill(char *name, int signal)
611 {
612 static struct blob_buf req;
613 struct blob_attr *cur, *tb[__CONF_MAX];
614 int rem, ret;
615 char *objname;
616 unsigned int id;
617 struct runtime_state *s = NULL;
618 bool found = false;
619
620 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
621 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
622 if (!tb[CONF_NAME] || !tb[CONF_PATH])
623 continue;
624
625 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
626 continue;
627
628 found = true;
629 break;
630 }
631
632 if (!found)
633 return ENOENT;
634
635 s = avl_find_element(&runtime, name, s, avl);
636
637 if (!s || !(s->running))
638 return ENOENT;
639
640 blob_buf_init(&req, 0);
641 blobmsg_add_u32(&req, "signal", signal);
642 blobmsg_add_string(&req, "name", name);
643
644 if (asprintf(&objname, "container.%s", name) == -1)
645 return ENOMEM;
646
647 ret = ubus_lookup_id(ctx, objname, &id);
648 free(objname);
649 if (ret)
650 return ENOENT;
651
652 if (ubus_invoke(ctx, id, "kill", req.head, NULL, NULL, 3000))
653 return EIO;
654
655 return 0;
656 }
657
658
659 static int uxc_set(char *name, char *path, bool autostart, bool add, char *pidfile, char *_tmprwsize, char *_writepath, char *requiredmounts)
660 {
661 static struct blob_buf req;
662 struct blob_attr *cur, *tb[__CONF_MAX];
663 int rem, ret;
664 bool found = false;
665 char *fname = NULL;
666 char *keeppath = NULL;
667 char *tmprwsize = _tmprwsize;
668 char *writepath = _writepath;
669 char *curvol, *tmp, *mnttok;
670 void *mntarr;
671 int f;
672 struct stat sb;
673
674 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
675 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
676 if (!tb[CONF_NAME] || !tb[CONF_PATH])
677 continue;
678
679 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
680 continue;
681
682 found = true;
683 break;
684 }
685
686 if (found && add)
687 return EEXIST;
688
689 if (!found && !add)
690 return ENOENT;
691
692 if (add && !path)
693 return EINVAL;
694
695 if (path) {
696 if (stat(path, &sb) == -1)
697 return ENOENT;
698
699 if ((sb.st_mode & S_IFMT) != S_IFDIR)
700 return ENOTDIR;
701 }
702
703 ret = mkdir(confdir, 0755);
704
705 if (ret && errno != EEXIST)
706 return ret;
707
708 if (asprintf(&fname, "%s/%s.json", confdir, name) == -1)
709 return ENOMEM;
710
711 f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
712 if (f < 0)
713 return errno;
714
715 if (!add) {
716 keeppath = blobmsg_get_string(tb[CONF_PATH]);
717 if (tb[CONF_WRITE_OVERLAY_PATH])
718 writepath = blobmsg_get_string(tb[CONF_WRITE_OVERLAY_PATH]);
719
720 if (tb[CONF_TEMP_OVERLAY_SIZE])
721 tmprwsize = blobmsg_get_string(tb[CONF_TEMP_OVERLAY_SIZE]);
722 }
723
724 blob_buf_init(&req, 0);
725 blobmsg_add_string(&req, "name", name);
726 blobmsg_add_string(&req, "path", path?:keeppath);
727 blobmsg_add_u8(&req, "autostart", autostart);
728 if (pidfile)
729 blobmsg_add_string(&req, "pidfile", pidfile);
730
731 if (tmprwsize)
732 blobmsg_add_string(&req, "temp-overlay-size", tmprwsize);
733
734 if (writepath)
735 blobmsg_add_string(&req, "write-overlay-path", writepath);
736
737 if (!add && tb[CONF_VOLUMES])
738 blobmsg_add_blob(&req, tb[CONF_VOLUMES]);
739
740 if (add && requiredmounts) {
741 mntarr = blobmsg_open_array(&req, "volumes");
742 for (mnttok = requiredmounts; ; mnttok = NULL) {
743 curvol = strtok_r(mnttok, ",;", &tmp);
744 if (!curvol)
745 break;
746
747 blobmsg_add_string(&req, NULL, curvol);
748 }
749 blobmsg_close_array(&req, mntarr);
750 }
751 tmp = blobmsg_format_json_indent(req.head, true, 0);
752 if (tmp) {
753 dprintf(f, "%s\n", tmp);
754 free(tmp);
755 }
756
757 blob_buf_free(&req);
758 close(f);
759
760 return 0;
761 }
762
763 enum {
764 BLOCK_INFO_DEVICE,
765 BLOCK_INFO_UUID,
766 BLOCK_INFO_TARGET,
767 BLOCK_INFO_TYPE,
768 BLOCK_INFO_MOUNT,
769 __BLOCK_INFO_MAX,
770 };
771
772 static const struct blobmsg_policy block_info_policy[__BLOCK_INFO_MAX] = {
773 [BLOCK_INFO_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
774 [BLOCK_INFO_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING },
775 [BLOCK_INFO_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
776 [BLOCK_INFO_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
777 [BLOCK_INFO_MOUNT] = { .name = "mount", .type = BLOBMSG_TYPE_STRING },
778 };
779
780
781 /* check if device 'devname' is mounted according to blockd */
782 static int checkblock(const char *uuid)
783 {
784 struct blob_attr *tb[__BLOCK_INFO_MAX];
785 struct blob_attr *cur;
786 int rem;
787
788 blobmsg_for_each_attr(cur, blockinfo, rem) {
789 blobmsg_parse(block_info_policy, __BLOCK_INFO_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
790
791 if (!tb[BLOCK_INFO_UUID] || !tb[BLOCK_INFO_MOUNT])
792 continue;
793
794 if (!strcmp(uuid, blobmsg_get_string(tb[BLOCK_INFO_UUID])))
795 return 0;
796 }
797
798 return 1;
799 }
800
801 enum {
802 UCI_FSTAB_UUID,
803 UCI_FSTAB_ANONYMOUS,
804 __UCI_FSTAB_MAX,
805 };
806
807 static const struct blobmsg_policy uci_fstab_policy[__UCI_FSTAB_MAX] = {
808 [UCI_FSTAB_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING },
809 [UCI_FSTAB_ANONYMOUS] = { .name = ".anonymous", .type = BLOBMSG_TYPE_BOOL },
810 };
811
812 static const char *resolveuuid(const char *volname)
813 {
814 struct blob_attr *tb[__UCI_FSTAB_MAX];
815 struct blob_attr *cur;
816 const char *mntname;
817 char *tmpvolname, *replc;
818 int rem, res;
819
820 blobmsg_for_each_attr(cur, fstabinfo, rem) {
821 blobmsg_parse(uci_fstab_policy, __UCI_FSTAB_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
822
823 if (!tb[UCI_FSTAB_UUID])
824 continue;
825
826 if (tb[UCI_FSTAB_ANONYMOUS] && blobmsg_get_bool(tb[UCI_FSTAB_ANONYMOUS]))
827 continue;
828
829 mntname = blobmsg_name(cur);
830 if (!mntname)
831 continue;
832
833 tmpvolname = strdup(volname);
834 while ((replc = strchr(tmpvolname, '-')))
835 *replc = '_';
836
837 res = strcmp(tmpvolname, mntname);
838 free(tmpvolname);
839
840 if (!res)
841 return blobmsg_get_string(tb[UCI_FSTAB_UUID]);
842 };
843
844 return volname;
845 };
846
847 /* check status of each required volume */
848 static int checkvolumes(struct blob_attr *volumes)
849 {
850 struct blob_attr *cur;
851 int rem;
852
853 blobmsg_for_each_attr(cur, volumes, rem) {
854 if (checkblock(resolveuuid(blobmsg_get_string(cur))))
855 return 1;
856 }
857
858 return 0;
859 }
860
861 static void block_cb(struct ubus_request *req, int type, struct blob_attr *msg)
862 {
863 blockinfo = blob_memdup(blobmsg_data(msg));
864 }
865
866 static void fstab_cb(struct ubus_request *req, int type, struct blob_attr *msg)
867 {
868 fstabinfo = blob_memdup(blobmsg_data(msg));
869 }
870
871 static int uxc_boot(void)
872 {
873 struct blob_attr *cur, *tb[__CONF_MAX];
874 struct runtime_state *s;
875 static struct blob_buf req;
876 int rem, ret = 0;
877 char *name;
878 unsigned int id;
879
880 ret = ubus_lookup_id(ctx, "block", &id);
881 if (ret)
882 return ENOENT;
883
884 ret = ubus_invoke(ctx, id, "info", NULL, block_cb, NULL, 3000);
885 if (ret)
886 return ENXIO;
887
888 ret = ubus_lookup_id(ctx, "uci", &id);
889 if (ret)
890 return ENOENT;
891
892 blob_buf_init(&req, 0);
893 blobmsg_add_string(&req, "config", "fstab");
894 blobmsg_add_string(&req, "type", "mount");
895
896 ret = ubus_invoke(ctx, id, "get", req.head, fstab_cb, NULL, 3000);
897 if (ret)
898 return ENXIO;
899
900 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
901 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
902 if (!tb[CONF_NAME] || !tb[CONF_PATH] || !tb[CONF_AUTOSTART] || !blobmsg_get_bool(tb[CONF_AUTOSTART]))
903 continue;
904
905 s = avl_find_element(&runtime, blobmsg_get_string(tb[CONF_NAME]), s, avl);
906 if (s)
907 continue;
908
909 /* make sure all volumes are ready before starting */
910 if (tb[CONF_VOLUMES])
911 if (checkvolumes(tb[CONF_VOLUMES]))
912 continue;
913
914 name = strdup(blobmsg_get_string(tb[CONF_NAME]));
915 ret += uxc_create(name, true);
916 free(name);
917 }
918
919 return ret;
920 }
921
922 static int uxc_delete(char *name, bool force)
923 {
924 struct blob_attr *cur, *tb[__CONF_MAX];
925 struct runtime_state *s = NULL;
926 static struct blob_buf req;
927 uint32_t id;
928 int rem, ret = 0;
929 bool found = false;
930 char *fname;
931 struct stat sb;
932
933 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
934 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
935 if (!tb[CONF_NAME] || !tb[CONF_PATH])
936 continue;
937
938 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
939 continue;
940
941 fname = strdup(blobmsg_name(cur));
942 if (!fname)
943 return errno;
944
945 found = true;
946 break;
947 }
948
949 if (!found)
950 return ENOENT;
951
952 s = avl_find_element(&runtime, name, s, avl);
953
954 if (s && s->running) {
955 if (force) {
956 ret = uxc_kill(name, SIGKILL);
957 if (ret)
958 goto errout;
959
960 } else {
961 ret = EWOULDBLOCK;
962 goto errout;
963 }
964 }
965
966 if (s) {
967 ret = ubus_lookup_id(ctx, "container", &id);
968 if (ret)
969 goto errout;
970
971 blob_buf_init(&req, 0);
972 blobmsg_add_string(&req, "name", s->container_name);
973 blobmsg_add_string(&req, "instance", s->instance_name);
974
975 if (ubus_invoke(ctx, id, "delete", req.head, NULL, NULL, 3000)) {
976 blob_buf_free(&req);
977 ret=EIO;
978 goto errout;
979 }
980 }
981
982 if (stat(fname, &sb) == -1) {
983 ret=ENOENT;
984 goto errout;
985 }
986
987 if (unlink(fname) == -1)
988 ret=errno;
989
990 errout:
991 free(fname);
992 return ret;
993 }
994
995 static void reload_conf(void)
996 {
997 blob_buf_free(&conf);
998 conf_load();
999 }
1000
1001 int main(int argc, char **argv)
1002 {
1003 enum uxc_cmd cmd = CMD_UNKNOWN;
1004 int ret = EINVAL;
1005 char *bundle = NULL;
1006 char *pidfile = NULL;
1007 char *tmprwsize = NULL;
1008 char *writepath = NULL;
1009 char *requiredmounts = NULL;
1010 bool autostart = false;
1011 bool force = false;
1012 int signal = SIGTERM;
1013 int c;
1014
1015 if (argc < 2)
1016 return usage();
1017
1018 ctx = ubus_connect(NULL);
1019 if (!ctx)
1020 return ENODEV;
1021
1022 ret = conf_load();
1023 if (ret)
1024 goto out;
1025
1026 ret = runtime_load();
1027 if (ret)
1028 goto conf_out;
1029
1030 while (true) {
1031 int option_index = 0;
1032 c = getopt_long(argc, argv, OPT_ARGS, long_options, &option_index);
1033 if (c == -1)
1034 break;
1035
1036 switch (c) {
1037 case 'a':
1038 autostart = true;
1039 break;
1040
1041 case 'b':
1042 bundle = optarg;
1043 break;
1044
1045 case 'f':
1046 force = true;
1047 break;
1048
1049 case 'j':
1050 json_output = true;
1051 break;
1052
1053 case 'p':
1054 pidfile = optarg;
1055 break;
1056
1057 case 't':
1058 tmprwsize = optarg;
1059 break;
1060
1061 case 'v':
1062 verbose = true;
1063 break;
1064
1065 case 'V':
1066 printf("uxc %s\n", UXC_VERSION);
1067 exit(0);
1068
1069 case 'w':
1070 writepath = optarg;
1071 break;
1072
1073 case 'm':
1074 requiredmounts = optarg;
1075 break;
1076 }
1077 }
1078
1079 if (optind == argc)
1080 goto usage_out;
1081
1082 if (!strcmp("list", argv[optind]))
1083 cmd = CMD_LIST;
1084 else if (!strcmp("boot", argv[optind]))
1085 cmd = CMD_BOOT;
1086 else if(!strcmp("start", argv[optind]))
1087 cmd = CMD_START;
1088 else if(!strcmp("state", argv[optind]))
1089 cmd = CMD_STATE;
1090 else if(!strcmp("kill", argv[optind]))
1091 cmd = CMD_KILL;
1092 else if(!strcmp("enable", argv[optind]))
1093 cmd = CMD_ENABLE;
1094 else if(!strcmp("disable", argv[optind]))
1095 cmd = CMD_DISABLE;
1096 else if(!strcmp("delete", argv[optind]))
1097 cmd = CMD_DELETE;
1098 else if(!strcmp("create", argv[optind]))
1099 cmd = CMD_CREATE;
1100
1101 switch (cmd) {
1102 case CMD_LIST:
1103 ret = uxc_list();
1104 break;
1105
1106 case CMD_BOOT:
1107 ret = uxc_boot();
1108 break;
1109
1110 case CMD_START:
1111 if (optind != argc - 2)
1112 goto usage_out;
1113
1114 ret = uxc_start(argv[optind + 1]);
1115 break;
1116
1117 case CMD_STATE:
1118 if (optind != argc - 2)
1119 goto usage_out;
1120
1121 ret = uxc_state(argv[optind + 1]);
1122 break;
1123
1124 case CMD_KILL:
1125 if (optind == (argc - 3))
1126 signal = atoi(argv[optind + 2]);
1127 else if (optind > argc - 2)
1128 goto usage_out;
1129
1130 ret = uxc_kill(argv[optind + 1], signal);
1131 break;
1132
1133 case CMD_ENABLE:
1134 if (optind != argc - 2)
1135 goto usage_out;
1136
1137 ret = uxc_set(argv[optind + 1], NULL, true, false, NULL, NULL, NULL, NULL);
1138 break;
1139
1140 case CMD_DISABLE:
1141 if (optind != argc - 2)
1142 goto usage_out;
1143
1144 ret = uxc_set(argv[optind + 1], NULL, false, false, NULL, NULL, NULL, NULL);
1145 break;
1146
1147 case CMD_DELETE:
1148 if (optind != argc - 2)
1149 goto usage_out;
1150
1151 ret = uxc_delete(argv[optind + 1], force);
1152 break;
1153
1154 case CMD_CREATE:
1155 if (optind != argc - 2)
1156 goto usage_out;
1157
1158 if (bundle) {
1159 ret = uxc_set(argv[optind + 1], bundle, autostart, true, pidfile, tmprwsize, writepath, requiredmounts);
1160 if (ret)
1161 goto runtime_out;
1162
1163 reload_conf();
1164 }
1165
1166 ret = uxc_create(argv[optind + 1], false);
1167 break;
1168
1169 default:
1170 goto usage_out;
1171 }
1172
1173 goto runtime_out;
1174
1175 usage_out:
1176 usage();
1177 runtime_out:
1178 runtime_free();
1179 conf_out:
1180 blob_buf_free(&conf);
1181 out:
1182 ubus_free(ctx);
1183
1184 if (ret != 0)
1185 fprintf(stderr, "uxc error: %s\n", strerror(ret));
1186
1187 return ret;
1188 }