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