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