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