/* * Copyright (C) 2020 Daniel Golle * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #define UXC_CONFDIR "/etc/uxc" #define UXC_RUNDIR "/var/run/uxc" struct runtime_state { struct avl_node avl; const char *container_name; const char *instance_name; const char *jail_name; bool running; int pid; int exitcode; }; AVL_TREE(runtime, avl_strcmp, false, NULL); static struct blob_buf conf; static struct blob_buf state; static struct ubus_context *ctx; static int usage(void) { printf("syntax: uxc {command} [parameters ...]\n"); printf("commands:\n"); printf("\tlist\t\t\t\tlist all configured containers\n"); printf("\tcreate {conf} {path} [enabled]\tcreate {conf} for OCI bundle at {path}\n"); printf("\tstart {conf}\t\t\tstart container {conf}\n"); printf("\tstop {conf}\t\t\tstop container {conf}\n"); printf("\tenable {conf}\t\t\tstart container {conf} on boot\n"); printf("\tdisable {conf}\t\t\tdon't start container {conf} on boot\n"); printf("\tdelete {conf}\t\t\tdelete {conf}\n"); return EINVAL; } enum { CONF_NAME, CONF_PATH, CONF_JAIL, CONF_AUTOSTART, __CONF_MAX, }; static const struct blobmsg_policy conf_policy[__CONF_MAX] = { [CONF_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, [CONF_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, [CONF_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_STRING }, [CONF_AUTOSTART] = { .name = "autostart", .type = BLOBMSG_TYPE_BOOL }, }; static int conf_load(bool load_state) { int gl_flags = GLOB_NOESCAPE | GLOB_MARK; int j, res; glob_t gl; char *globstr; struct blob_buf *target = load_state?&state:&conf; void *c, *o; if (asprintf(&globstr, "%s/*.json", load_state?UXC_RUNDIR:UXC_CONFDIR) == -1) return ENOMEM; blob_buf_init(target, 0); c = blobmsg_open_table(target, NULL); res = glob(globstr, gl_flags, NULL, &gl); free(globstr); if (res < 0) return 0; for (j = 0; j < gl.gl_pathc; j++) { o = blobmsg_open_table(target, strdup(gl.gl_pathv[j])); if (!blobmsg_add_json_from_file(target, gl.gl_pathv[j])) { ERROR("uxc: failed to load %s\n", gl.gl_pathv[j]); continue; } blobmsg_close_table(target, o); } blobmsg_close_table(target, c); globfree(&gl); return 0; } enum { LIST_INSTANCES, __LIST_MAX, }; static const struct blobmsg_policy list_policy[__LIST_MAX] = { [LIST_INSTANCES] = { .name = "instances", .type = BLOBMSG_TYPE_TABLE }, }; enum { INSTANCE_RUNNING, INSTANCE_PID, INSTANCE_EXITCODE, INSTANCE_JAIL, __INSTANCE_MAX, }; static const struct blobmsg_policy instance_policy[__INSTANCE_MAX] = { [INSTANCE_RUNNING] = { .name = "running", .type = BLOBMSG_TYPE_BOOL }, [INSTANCE_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 }, [INSTANCE_EXITCODE] = { .name = "exit_code", .type = BLOBMSG_TYPE_INT32 }, [INSTANCE_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_TABLE }, }; enum { JAIL_NAME, __JAIL_MAX, }; static const struct blobmsg_policy jail_policy[__JAIL_MAX] = { [JAIL_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, }; static struct runtime_state * runtime_alloc(const char *container_name) { struct runtime_state *s; char *new_name; s = calloc_a(sizeof(*s), &new_name, strlen(container_name) + 1); strcpy(new_name, container_name); s->container_name = new_name; s->avl.key = s->container_name; return s; } static void list_cb(struct ubus_request *req, int type, struct blob_attr *msg) { struct blob_attr *cur, *curi, *tl[__LIST_MAX], *ti[__INSTANCE_MAX], *tj[__JAIL_MAX]; int rem, remi; const char *container_name, *instance_name, *jail_name; bool running; int pid, exitcode; struct runtime_state *rs; blobmsg_for_each_attr(cur, msg, rem) { container_name = blobmsg_name(cur); blobmsg_parse(list_policy, __LIST_MAX, tl, blobmsg_data(cur), blobmsg_len(cur)); if (!tl[LIST_INSTANCES]) continue; blobmsg_for_each_attr(curi, tl[LIST_INSTANCES], remi) { instance_name = blobmsg_name(curi); blobmsg_parse(instance_policy, __INSTANCE_MAX, ti, blobmsg_data(curi), blobmsg_len(curi)); if (!ti[INSTANCE_JAIL]) continue; blobmsg_parse(jail_policy, __JAIL_MAX, tj, blobmsg_data(ti[INSTANCE_JAIL]), blobmsg_len(ti[INSTANCE_JAIL])); if (!tj[JAIL_NAME]) continue; jail_name = blobmsg_get_string(tj[JAIL_NAME]); running = ti[INSTANCE_RUNNING] && blobmsg_get_bool(ti[INSTANCE_RUNNING]); if (ti[INSTANCE_PID]) pid = blobmsg_get_u32(ti[INSTANCE_PID]); else pid = -1; if (ti[INSTANCE_EXITCODE]) exitcode = blobmsg_get_u32(ti[INSTANCE_EXITCODE]); else exitcode = -1; rs = runtime_alloc(container_name); rs->instance_name = strdup(instance_name); rs->jail_name = strdup(jail_name); rs->pid = pid; rs->exitcode = exitcode; rs->running = running; avl_insert(&runtime, &rs->avl); } } return; } static int runtime_load(void) { uint32_t id; avl_init(&runtime, avl_strcmp, false, NULL); if (ubus_lookup_id(ctx, "container", &id) || ubus_invoke(ctx, id, "list", NULL, list_cb, &runtime, 3000)) return EIO; return 0; } static void runtime_free(void) { struct runtime_state *item, *tmp; avl_for_each_element_safe(&runtime, item, avl, tmp) { avl_delete(&runtime, &item->avl); free(item); } return; } static int uxc_list(void) { struct blob_attr *cur, *tb[__CONF_MAX]; int rem; struct runtime_state *s = NULL; char *name; bool autostart; blobmsg_for_each_attr(cur, blob_data(conf.head), rem) { blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur)); if (!tb[CONF_NAME] || !tb[CONF_PATH]) continue; autostart = tb[CONF_AUTOSTART] && blobmsg_get_bool(tb[CONF_AUTOSTART]); name = blobmsg_get_string(tb[CONF_NAME]); s = avl_find_element(&runtime, name, s, avl); printf("[%c] %s %s", autostart?'*':' ', name, (s && s->running)?"RUNNING":"STOPPED"); if (s && !s->running && (s->exitcode >= 0)) printf(" exitcode: %d (%s)", s->exitcode, strerror(s->exitcode)); if (s && s->running && (s->pid >= 0)) printf(" pid: %d", s->pid); printf("\n"); } return 0; } static int uxc_start(char *name) { static struct blob_buf req; struct blob_attr *cur, *tb[__CONF_MAX]; int rem, ret; uint32_t id; struct runtime_state *s = NULL; char *path = NULL, *jailname = NULL; void *in, *ins, *j; bool found = false; blobmsg_for_each_attr(cur, blob_data(conf.head), rem) { blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur)); if (!tb[CONF_NAME] || !tb[CONF_PATH]) continue; if (strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) continue; found = true; path = strdup(blobmsg_get_string(tb[CONF_PATH])); break; } if (!found) return ENOENT; s = avl_find_element(&runtime, name, s, avl); if (s && (s->running)) return EEXIST; if (tb[CONF_JAIL]) jailname = strdup(blobmsg_get_string(tb[CONF_JAIL])); blob_buf_init(&req, 0); blobmsg_add_string(&req, "name", name); ins = blobmsg_open_table(&req, "instances"); in = blobmsg_open_table(&req, name); blobmsg_add_string(&req, "bundle", path); j = blobmsg_open_table(&req, "jail"); blobmsg_add_string(&req, "name", jailname?:name); blobmsg_close_table(&req, j); blobmsg_close_table(&req, in); blobmsg_close_table(&req, ins); ret = 0; if (ubus_lookup_id(ctx, "container", &id) || ubus_invoke(ctx, id, "add", req.head, NULL, NULL, 3000)) { ret = EIO; } free(jailname); free(path); blob_buf_free(&req); return ret; } static int uxc_stop(char *name) { static struct blob_buf req; struct blob_attr *cur, *tb[__CONF_MAX]; int rem, ret; uint32_t id; struct runtime_state *s = NULL; bool found = false; blobmsg_for_each_attr(cur, blob_data(conf.head), rem) { blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur)); if (!tb[CONF_NAME] || !tb[CONF_PATH]) continue; if (strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) continue; found = true; break; } if (!found) return ENOENT; s = avl_find_element(&runtime, name, s, avl); if (!s || !(s->running)) return ENOENT; blob_buf_init(&req, 0); blobmsg_add_string(&req, "name", name); blobmsg_add_string(&req, "instance", s->instance_name); ret = 0; if (ubus_lookup_id(ctx, "container", &id) || ubus_invoke(ctx, id, "del", req.head, NULL, NULL, 3000)) { ret = EIO; } blob_buf_free(&req); return ret; } static int uxc_set(char *name, char *path, bool autostart, bool add) { static struct blob_buf req; struct blob_attr *cur, *tb[__CONF_MAX]; int rem, ret; bool found = false; char *fname = NULL; char *keeppath = NULL; int f; struct stat sb; blobmsg_for_each_attr(cur, blob_data(conf.head), rem) { blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur)); if (!tb[CONF_NAME] || !tb[CONF_PATH]) continue; if (strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) continue; found = true; break; } if (found && add) return EEXIST; if (!found && !add) return ENOENT; if (add && !path) return EINVAL; if (path) { if (stat(path, &sb) == -1) return ENOENT; if ((sb.st_mode & S_IFMT) != S_IFDIR) return ENOTDIR; } ret = mkdir(UXC_CONFDIR, 0755); if (ret && errno != EEXIST) return ret; if (asprintf(&fname, "%s/%s.json", UXC_CONFDIR, name) < 1) return ENOMEM; f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (f < 0) return errno; if (!add) keeppath = strdup(blobmsg_get_string(tb[CONF_PATH])); blob_buf_init(&req, 0); blobmsg_add_string(&req, "name", name); blobmsg_add_string(&req, "path", path?:keeppath); blobmsg_add_u8(&req, "autostart", autostart); dprintf(f, "%s\n", blobmsg_format_json_indent(req.head, true, 0)); if (!add) free(keeppath); blob_buf_free(&req); /* ToDo: tell ujail to run createRuntime and createContainer hooks */ return 0; } static int uxc_boot(void) { struct blob_attr *cur, *tb[__CONF_MAX]; int rem, ret = 0; char *name; blobmsg_for_each_attr(cur, blob_data(conf.head), rem) { blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur)); if (!tb[CONF_NAME] || !tb[CONF_PATH] || !tb[CONF_AUTOSTART] || !blobmsg_get_bool(tb[CONF_AUTOSTART])) continue; name = strdup(blobmsg_get_string(tb[CONF_NAME])); ret += uxc_start(name); free(name); } return ret; } static int uxc_delete(char *name) { struct blob_attr *cur, *tb[__CONF_MAX]; int rem, ret = 0; bool found = false; char *fname; struct stat sb; blobmsg_for_each_attr(cur, blob_data(conf.head), rem) { blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur)); if (!tb[CONF_NAME] || !tb[CONF_PATH]) continue; if (strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) continue; fname = strdup(blobmsg_name(cur)); if (!fname) return errno; found = true; break; } if (!found) return ENOENT; if (stat(fname, &sb) == -1) { ret=ENOENT; goto errout; } if (unlink(fname) == -1) ret=errno; errout: free(fname); return ret; } int main(int argc, char **argv) { int ret = EINVAL; if (argc < 2) return usage(); ctx = ubus_connect(NULL); if (!ctx) return ENODEV; ret = conf_load(false); if (ret) goto out; ret = mkdir(UXC_RUNDIR, 0755); if (ret && errno != EEXIST) goto conf_out; ret = conf_load(true); if (ret) goto conf_out; ret = runtime_load(); if (ret) goto state_out; if (!strcmp("list", argv[1])) ret = uxc_list(); else if (!strcmp("boot", argv[1])) ret = uxc_boot(); else if(!strcmp("start", argv[1])) { if (argc < 3) goto usage_out; ret = uxc_start(argv[2]); } else if(!strcmp("stop", argv[1])) { if (argc < 3) goto usage_out; ret = uxc_stop(argv[2]); } else if(!strcmp("enable", argv[1])) { if (argc < 3) goto usage_out; ret = uxc_set(argv[2], NULL, true, false); } else if(!strcmp("disable", argv[1])) { if (argc < 3) goto usage_out; ret = uxc_set(argv[2], NULL, false, false); } else if(!strcmp("delete", argv[1])) { if (argc < 3) goto usage_out; ret = uxc_delete(argv[2]); } else if(!strcmp("create", argv[1])) { bool autostart = false; if (argc < 4) goto usage_out; if (argc == 5) { if (!strncmp("true", argv[4], 5)) autostart = true; else autostart = atoi(argv[4]); } ret = uxc_set(argv[2], argv[3], autostart, true); } else goto usage_out; goto runtime_out; usage_out: usage(); runtime_out: runtime_free(); state_out: blob_buf_free(&state); conf_out: blob_buf_free(&conf); out: ubus_free(ctx); if (ret != 0) fprintf(stderr, "uxc error: %s\n", strerror(ret)); return ret; }