X-Git-Url: http://git.openwrt.org/?a=blobdiff_plain;f=uxc.c;h=869db3320e08052d4cfb4d43ad8bdfba15f534d8;hb=0272c7c36e5185b3d7c7d6bd5b2ae5dfe6e905f6;hp=eb40eb2e6a5bc54345f80c649e33d8822a4fe040;hpb=d4d78dbe5ebe02d30e0cdf6f9ae85af4407b357d;p=project%2Fprocd.git diff --git a/uxc.c b/uxc.c index eb40eb2..869db33 100644 --- a/uxc.c +++ b/uxc.c @@ -15,30 +15,38 @@ #define _GNU_SOURCE #endif +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include #include -#include +#include +#include #include -#include -#include -#include #include #include -#include -#include + +#include +#include +#include +#include +#include #include "log.h" -#define UXC_VERSION "0.1" +#define UXC_VERSION "0.3" #define OCI_VERSION_STRING "1.0.2" -#define UXC_CONFDIR "/etc/uxc" -#define UXC_RUNDIR "/var/run/uxc" +#define UXC_ETC_CONFDIR "/etc/uxc" +#define UXC_VOL_CONFDIR "/tmp/run/uvol/.meta/uxc" + +static bool verbose = false; +static bool json_output = false; +static char *confdir = UXC_ETC_CONFDIR; +static struct ustream_fd cufd; +static struct ustream_fd lufd; + struct runtime_state { struct avl_node avl; @@ -51,7 +59,18 @@ struct runtime_state { struct blob_attr *ocistate; }; +struct settings { + struct avl_node avl; + char *container_name; + const char *fname; + char *tmprwsize; + char *writepath; + signed char autostart; + struct blob_attr *volumes; +}; + enum uxc_cmd { + CMD_ATTACH, CMD_LIST, CMD_BOOT, CMD_START, @@ -64,33 +83,47 @@ enum uxc_cmd { CMD_UNKNOWN }; -#define OPT_ARGS "ab:fp:vV" +#define OPT_ARGS "ab:fjm:p:t:vVw:" static struct option long_options[] = { - {"autostart", no_argument, 0, 'a' }, - {"bundle", required_argument, 0, 'b' }, - {"force", no_argument, 0, 'f' }, - {"pid-file", required_argument, 0, 'p' }, - {"verbose", no_argument, 0, 'v' }, - {"version", no_argument, 0, 'V' }, - {0, 0, 0, 0 } + {"autostart", no_argument, 0, 'a' }, + {"console", no_argument, 0, 'c' }, + {"bundle", required_argument, 0, 'b' }, + {"force", no_argument, 0, 'f' }, + {"json", no_argument, 0, 'j' }, + {"mounts", required_argument, 0, 'm' }, + {"pid-file", required_argument, 0, 'p' }, + {"temp-overlay-size", required_argument, 0, 't' }, + {"write-overlay-path", required_argument, 0, 'w' }, + {"verbose", no_argument, 0, 'v' }, + {"version", no_argument, 0, 'V' }, + {0, 0, 0, 0 } }; AVL_TREE(runtime, avl_strcmp, false, NULL); +AVL_TREE(settings, avl_strcmp, false, NULL); static struct blob_buf conf; -static struct blob_buf state; +static struct blob_buf settingsbuf; +static struct blob_attr *blockinfo; +static struct blob_attr *fstabinfo; static struct ubus_context *ctx; static int usage(void) { printf("syntax: uxc [parameters ...]\n"); printf("commands:\n"); - printf("\tlist\t\t\t\t\t\tlist all configured containers\n"); - printf("\tcreate [--bundle ] [--autostart]\tcreate for OCI bundle at \n"); - printf("\tstart \t\t\t\t\tstart container \n"); - printf("\tstate \t\t\t\t\tget state of container \n"); - printf("\tkill []\t\t\t\tsend signal to container \n"); - printf("\tenable \t\t\t\t\tstart container on boot\n"); - printf("\tdisable \t\t\t\t\tdon't start container on boot\n"); - printf("\tdelete [--force]\t\t\t\tdelete \n"); + printf("\tlist [--json]\t\t\t\tlist all configured containers\n"); + printf("\tattach \t\t\t\tattach to container console\n"); + printf("\tcreate \t\t\t\t(re-)create \n"); + printf("\t\t[--bundle ]\t\t\tOCI bundle at \n"); + printf("\t\t[--autostart]\t\t\t\tstart on boot\n"); + printf("\t\t[--temp-overlay-size ]\t\tuse tmpfs overlay with {size}\n"); + printf("\t\t[--write-overlay-path ]\t\tuse overlay on {path}\n"); + printf("\t\t[--mounts ,,...,]\t\trequire filesystems to be available\n"); + printf("\tstart [--console] \t\tstart container \n"); + printf("\tstate \t\t\t\tget state of container \n"); + printf("\tkill []\t\t\tsend signal to container \n"); + printf("\tenable \t\t\t\tstart container on boot\n"); + printf("\tdisable \t\t\t\tdon't start container on boot\n"); + printf("\tdelete [--force]\t\t\tdelete \n"); return EINVAL; } @@ -100,6 +133,9 @@ enum { CONF_JAIL, CONF_AUTOSTART, CONF_PIDFILE, + CONF_TEMP_OVERLAY_SIZE, + CONF_WRITE_OVERLAY_PATH, + CONF_VOLUMES, __CONF_MAX, }; @@ -109,25 +145,44 @@ static const struct blobmsg_policy conf_policy[__CONF_MAX] = { [CONF_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_STRING }, [CONF_AUTOSTART] = { .name = "autostart", .type = BLOBMSG_TYPE_BOOL }, [CONF_PIDFILE] = { .name = "pidfile", .type = BLOBMSG_TYPE_STRING }, + [CONF_TEMP_OVERLAY_SIZE] = { .name = "temp-overlay-size", .type = BLOBMSG_TYPE_STRING }, + [CONF_WRITE_OVERLAY_PATH] = { .name = "write-overlay-path", .type = BLOBMSG_TYPE_STRING }, + [CONF_VOLUMES] = { .name = "volumes", .type = BLOBMSG_TYPE_ARRAY }, }; -static int conf_load(bool load_state) +static int conf_load(bool load_settings) { 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; + struct stat sb; + struct blob_buf *target; - if (asprintf(&globstr, "%s/*.json", load_state?UXC_RUNDIR:UXC_CONFDIR) == -1) + if (asprintf(&globstr, "%s/%s*.json", UXC_ETC_CONFDIR, load_settings?"settings/":"") == -1) return ENOMEM; + res = glob(globstr, gl_flags, NULL, &gl); + if (res == 0) + gl_flags |= GLOB_APPEND; + + free(globstr); + + if (!stat(UXC_VOL_CONFDIR, &sb)) { + if (sb.st_mode & S_IFDIR) { + if (asprintf(&globstr, "%s/%s*.json", UXC_VOL_CONFDIR, load_settings?"settings/":"") == -1) + return ENOMEM; + + res = glob(globstr, gl_flags, NULL, &gl); + free(globstr); + } + } + + target = load_settings ? &settingsbuf : &conf; 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; @@ -145,6 +200,75 @@ static int conf_load(bool load_state) return 0; } +static struct settings * +settings_alloc(const char *container_name) +{ + struct settings *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; + s->autostart = -1; + s->tmprwsize = NULL; + s->writepath = NULL; + s->volumes = NULL; + return s; +} + +static int settings_add(void) +{ + struct blob_attr *cur, *tb[__CONF_MAX]; + struct settings *s; + int rem, err; + + avl_init(&settings, avl_strcmp, false, NULL); + + blobmsg_for_each_attr(cur, blob_data(settingsbuf.head), rem) { + blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur)); + if (!tb[CONF_NAME]) + continue; + + if (tb[CONF_TEMP_OVERLAY_SIZE] && tb[CONF_WRITE_OVERLAY_PATH]) + return -EINVAL; + + s = settings_alloc(blobmsg_get_string(tb[CONF_NAME])); + + if (tb[CONF_AUTOSTART]) + s->autostart = blobmsg_get_bool(tb[CONF_AUTOSTART]); + + if (tb[CONF_TEMP_OVERLAY_SIZE]) + s->tmprwsize = blobmsg_get_string(tb[CONF_TEMP_OVERLAY_SIZE]); + + if (tb[CONF_WRITE_OVERLAY_PATH]) + s->writepath = blobmsg_get_string(tb[CONF_WRITE_OVERLAY_PATH]); + + s->volumes = tb[CONF_VOLUMES]; + s->fname = blobmsg_name(cur); + + err = avl_insert(&settings, &s->avl); + if (err) { + fprintf(stderr, "error adding settings for %s\n", blobmsg_get_string(tb[CONF_NAME])); + free(s); + } + } + + return 0; +} + +static void settings_free(void) +{ + struct settings *item, *tmp; + + avl_for_each_element_safe(&settings, item, avl, tmp) { + avl_delete(&settings, &item->avl); + free(item); + } + + blob_buf_free(&settingsbuf); + return; +} + enum { LIST_INSTANCES, __LIST_MAX, @@ -233,7 +357,9 @@ static void get_ocistate(struct blob_attr **ocistate, const char *name) int ret; *ocistate = NULL; - asprintf(&objname, "container.%s", name); + if (asprintf(&objname, "container.%s", name) == -1) + exit(ENOMEM); + ret = ubus_lookup_id(ctx, objname, &id); free(objname); if (ret) @@ -305,7 +431,6 @@ static int runtime_load(void) ubus_invoke(ctx, id, "list", NULL, list_cb, &runtime, 3000)) return EIO; - avl_for_each_element_safe(&runtime, item, avl, tmp) get_ocistate(&item->ocistate, item->jail_name); @@ -327,21 +452,190 @@ static void runtime_free(void) return; } +static inline int setup_tios(int fd, struct termios *oldtios) +{ + struct termios newtios; + + if (!isatty(fd)) { + return -1; + } + + /* Get current termios */ + if (tcgetattr(fd, oldtios)) + return -1; + + newtios = *oldtios; + + /* We use the same settings that ssh does. */ + newtios.c_iflag |= IGNPAR; + newtios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); + newtios.c_lflag &= ~(TOSTOP | ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); + newtios.c_oflag &= ~ONLCR; + newtios.c_oflag |= OPOST; + newtios.c_cc[VMIN] = 1; + newtios.c_cc[VTIME] = 0; + + /* Set new attributes */ + if (tcsetattr(fd, TCSAFLUSH, &newtios)) + return -1; + + return 0; +} + + +static void client_cb(struct ustream *s, int bytes) +{ + char *buf; + int len, rv; + + do { + buf = ustream_get_read_buf(s, &len); + if (!buf) + break; + + rv = ustream_write(&lufd.stream, buf, len, false); + + if (rv > 0) + ustream_consume(s, rv); + + if (rv <= len) + break; + } while(1); +} + +static void local_cb(struct ustream *s, int bytes) +{ + char *buf; + int len, rv; + + do { + buf = ustream_get_read_buf(s, &len); + if (!buf) + break; + + if ((len > 0) && (buf[0] == 2)) + uloop_end(); + + rv = ustream_write(&cufd.stream, buf, len, false); + + if (rv > 0) + ustream_consume(s, rv); + + if (rv <= len) + break; + } while(1); +} + +static int uxc_attach(const char *container_name) +{ + struct ubus_context *ctx; + uint32_t id; + static struct blob_buf req; + int client_fd, server_fd, tty_fd; + struct termios oldtermios; + + ctx = ubus_connect(NULL); + if (!ctx) { + fprintf(stderr, "can't connect to ubus!\n"); + return -1; + } + + /* open pseudo-terminal pair */ + client_fd = posix_openpt(O_RDWR | O_NOCTTY); + if (client_fd < 0) { + fprintf(stderr, "can't create virtual console!\n"); + ubus_free(ctx); + return -1; + } + setup_tios(client_fd, &oldtermios); + grantpt(client_fd); + unlockpt(client_fd); + server_fd = open(ptsname(client_fd), O_RDWR | O_NOCTTY); + if (server_fd < 0) { + fprintf(stderr, "can't open virtual console!\n"); + close(client_fd); + ubus_free(ctx); + return -1; + } + setup_tios(server_fd, &oldtermios); + + tty_fd = open("/dev/tty", O_RDWR); + if (tty_fd < 0) { + fprintf(stderr, "can't open local console!\n"); + close(server_fd); + close(client_fd); + ubus_free(ctx); + return -1; + } + setup_tios(tty_fd, &oldtermios); + + /* register server-side with procd */ + blob_buf_init(&req, 0); + blobmsg_add_string(&req, "name", container_name); + blobmsg_add_string(&req, "instance", container_name); + + if (ubus_lookup_id(ctx, "container", &id) || + ubus_invoke_fd(ctx, id, "console_attach", req.head, NULL, NULL, 3000, server_fd)) { + fprintf(stderr, "ubus request failed\n"); + close(tty_fd); + close(server_fd); + close(client_fd); + blob_buf_free(&req); + ubus_free(ctx); + return -2; + } + + close(server_fd); + blob_buf_free(&req); + ubus_free(ctx); + + uloop_init(); + + /* forward between stdio and client_fd until detach is requested */ + lufd.stream.notify_read = local_cb; + ustream_fd_init(&lufd, tty_fd); + + cufd.stream.notify_read = client_cb; +/* ToDo: handle remote close and other events */ +// cufd.stream.notify_state = client_state_cb; + ustream_fd_init(&cufd, client_fd); + + fprintf(stderr, "attaching to jail console. press [CTRL]+[B] to exit.\n"); + close(0); + close(1); + close(2); + uloop_run(); + + tcsetattr(tty_fd, TCSAFLUSH, &oldtermios); + ustream_free(&lufd.stream); + ustream_free(&cufd.stream); + close(client_fd); + + return 0; +} + static int uxc_state(char *name) { - struct runtime_state *s = avl_find_element(&runtime, name, s, avl); + struct runtime_state *rsstate = avl_find_element(&runtime, name, rsstate, avl); struct blob_attr *ocistate = NULL; struct blob_attr *cur, *tb[__CONF_MAX]; int rem; char *bundle = NULL; char *jail_name = NULL; + char *state = NULL; + char *tmp; static struct blob_buf buf; - if (s) - ocistate = s->ocistate; + if (rsstate) + ocistate = rsstate->ocistate; if (ocistate) { - printf("%s\n", blobmsg_format_json_indent(ocistate, true, 0)); + state = blobmsg_format_json_indent(ocistate, true, 0); + if (!state) + return 1; + + printf("%s\n", state); + free(state); return 0; } @@ -367,10 +661,18 @@ static int uxc_state(char *name) blob_buf_init(&buf, 0); blobmsg_add_string(&buf, "ociVersion", OCI_VERSION_STRING); blobmsg_add_string(&buf, "id", jail_name); - blobmsg_add_string(&buf, "status", s?"stopped":"uninitialized"); + blobmsg_add_string(&buf, "status", rsstate?"stopped":"uninitialized"); blobmsg_add_string(&buf, "bundle", bundle); - printf("%s\n", blobmsg_format_json_indent(buf.head, true, 0)); + tmp = blobmsg_format_json_indent(buf.head, true, 0); + if (!tmp) { + blob_buf_free(&buf); + return ENOMEM; + } + + printf("%s\n", tmp); + free(tmp); + blob_buf_free(&buf); return 0; @@ -380,11 +682,18 @@ static int uxc_list(void) { struct blob_attr *cur, *tb[__CONF_MAX], *ts[__STATE_MAX]; int rem; - struct runtime_state *s = NULL; - char *name; - char *ocistatus; + struct runtime_state *rsstate = NULL; + struct settings *usettings = NULL; + char *name, *ocistatus, *status, *tmp; int container_pid = -1; bool autostart; + static struct blob_buf buf; + void *arr, *obj; + + if (json_output) { + blob_buf_init(&buf, 0); + arr = blobmsg_open_array(&buf, ""); + } blobmsg_for_each_attr(cur, blob_data(conf.head), rem) { blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur)); @@ -392,31 +701,84 @@ static int uxc_list(void) continue; autostart = tb[CONF_AUTOSTART] && blobmsg_get_bool(tb[CONF_AUTOSTART]); + ocistatus = NULL; container_pid = 0; name = blobmsg_get_string(tb[CONF_NAME]); - s = avl_find_element(&runtime, name, s, avl); + rsstate = avl_find_element(&runtime, name, rsstate, avl); - if (s && s->ocistate) { - blobmsg_parse(state_policy, __STATE_MAX, ts, blobmsg_data(s->ocistate), blobmsg_len(s->ocistate)); + if (rsstate && rsstate->ocistate) { + blobmsg_parse(state_policy, __STATE_MAX, ts, blobmsg_data(rsstate->ocistate), blobmsg_len(rsstate->ocistate)); ocistatus = blobmsg_get_string(ts[STATE_STATUS]); container_pid = blobmsg_get_u32(ts[STATE_PID]); } - printf("[%c] %s %s", autostart?'*':' ', name, ocistatus?:(s && s->running)?"creating":"stopped"); + status = ocistatus?:(rsstate && rsstate->running)?"creating":"stopped"; + + usettings = avl_find_element(&settings, name, usettings, avl); + + if (usettings && (usettings->autostart >= 0)) + autostart = !!(usettings->autostart); + + if (json_output) { + obj = blobmsg_open_table(&buf, ""); + blobmsg_add_string(&buf, "name", name); + blobmsg_add_string(&buf, "status", status); + blobmsg_add_u8(&buf, "autostart", autostart); + } else { + printf("[%c] %s %s", autostart?'*':' ', name, status); + } - if (s && !s->running && (s->exitcode >= 0)) - printf(" exitcode: %d (%s)", s->exitcode, strerror(s->exitcode)); + if (rsstate && !rsstate->running && (rsstate->exitcode >= 0)) { + if (json_output) + blobmsg_add_u32(&buf, "exitcode", rsstate->exitcode); + else + printf(" exitcode: %d (%s)", rsstate->exitcode, strerror(rsstate->exitcode)); + } - if (s && s->running && (s->runtime_pid >= 0)) - printf(" runtime pid: %d", s->runtime_pid); + if (rsstate && rsstate->running && (rsstate->runtime_pid >= 0)) { + if (json_output) + blobmsg_add_u32(&buf, "runtime_pid", rsstate->runtime_pid); + else + printf(" runtime pid: %d", rsstate->runtime_pid); + } - if (s && s->running && (container_pid >= 0)) - printf(" container pid: %d", container_pid); + if (rsstate && rsstate->running && (container_pid >= 0)) { + if (json_output) + blobmsg_add_u32(&buf, "container_pid", container_pid); + else + printf(" container pid: %d", container_pid); + } - printf("\n"); + if (!json_output) + printf("\n"); + else + blobmsg_close_table(&buf, obj); } + if (json_output) { + blobmsg_close_array(&buf, arr); + tmp = blobmsg_format_json_indent(buf.head, true, 0); + if (!tmp) { + blob_buf_free(&buf); + return ENOMEM; + } + printf("%s\n", tmp); + free(tmp); + blob_buf_free(&buf); + }; + + return 0; +} + +static int uxc_exists(char *name) +{ + struct runtime_state *rsstate = NULL; + rsstate = avl_find_element(&runtime, name, rsstate, avl); + + if (rsstate && (rsstate->running)) + return EEXIST; + return 0; } @@ -426,8 +788,9 @@ static int uxc_create(char *name, bool immediately) struct blob_attr *cur, *tb[__CONF_MAX]; int rem, ret; uint32_t id; - struct runtime_state *s = NULL; - char *path = NULL, *jailname = NULL, *pidfile = NULL; + struct settings *usettings = NULL; + char *path = NULL, *jailname = NULL, *pidfile = NULL, *tmprwsize = NULL, *writepath = NULL; + void *in, *ins, *j; bool found = false; @@ -440,23 +803,37 @@ static int uxc_create(char *name, bool immediately) continue; found = true; - path = strdup(blobmsg_get_string(tb[CONF_PATH])); - - if (tb[CONF_PIDFILE]) - pidfile = strdup(blobmsg_get_string(tb[CONF_PIDFILE])); break; } if (!found) return ENOENT; - s = avl_find_element(&runtime, name, s, avl); + path = blobmsg_get_string(tb[CONF_PATH]); - if (s && (s->running)) - return EEXIST; + if (tb[CONF_PIDFILE]) + pidfile = blobmsg_get_string(tb[CONF_PIDFILE]); + + if (tb[CONF_TEMP_OVERLAY_SIZE]) + tmprwsize = blobmsg_get_string(tb[CONF_TEMP_OVERLAY_SIZE]); + + if (tb[CONF_WRITE_OVERLAY_PATH]) + writepath = blobmsg_get_string(tb[CONF_WRITE_OVERLAY_PATH]); if (tb[CONF_JAIL]) - jailname = strdup(blobmsg_get_string(tb[CONF_JAIL])); + jailname = blobmsg_get_string(tb[CONF_JAIL]); + + usettings = avl_find_element(&settings, blobmsg_get_string(tb[CONF_NAME]), usettings, avl); + if (usettings) { + if (usettings->writepath) { + writepath = usettings->writepath; + tmprwsize = NULL; + } + if (usettings->tmprwsize) { + tmprwsize = usettings->tmprwsize; + writepath = NULL; + } + } blob_buf_init(&req, 0); blobmsg_add_string(&req, "name", name); @@ -466,35 +843,60 @@ static int uxc_create(char *name, bool immediately) j = blobmsg_open_table(&req, "jail"); blobmsg_add_string(&req, "name", jailname?:name); blobmsg_add_u8(&req, "immediately", immediately); + if (pidfile) blobmsg_add_string(&req, "pidfile", pidfile); blobmsg_close_table(&req, j); + + if (writepath) + blobmsg_add_string(&req, "overlaydir", writepath); + + if (tmprwsize) + blobmsg_add_string(&req, "tmpoverlaysize", tmprwsize); + blobmsg_close_table(&req, in); blobmsg_close_table(&req, ins); + if (verbose) { + char *tmp; + tmp = blobmsg_format_json_indent(req.head, true, 1); + if (!tmp) + return ENOMEM; + + fprintf(stderr, "adding container to procd:\n\t%s\n", tmp); + free(tmp); + } + ret = 0; if (ubus_lookup_id(ctx, "container", &id) || ubus_invoke(ctx, id, "add", req.head, NULL, NULL, 3000)) { + blob_buf_free(&req); ret = EIO; } - free(jailname); - free(path); - blob_buf_free(&req); - return ret; } -static int uxc_start(const char *name) +static int uxc_start(const char *name, bool console) { char *objname; unsigned int id; + pid_t pid; + + if (console) { + pid = fork(); + if (pid > 0) + exit(uxc_attach(name)); + } + + if (asprintf(&objname, "container.%s", name) == -1) + return ENOMEM; - asprintf(&objname, "container.%s", name); if (ubus_lookup_id(ctx, objname, &id)) return ENOENT; + free(objname); return ubus_invoke(ctx, id, "start", NULL, NULL, NULL, 3000); } @@ -505,7 +907,7 @@ static int uxc_kill(char *name, int signal) int rem, ret; char *objname; unsigned int id; - struct runtime_state *s = NULL; + struct runtime_state *rsstate = NULL; bool found = false; blobmsg_for_each_attr(cur, blob_data(conf.head), rem) { @@ -523,16 +925,18 @@ static int uxc_kill(char *name, int signal) if (!found) return ENOENT; - s = avl_find_element(&runtime, name, s, avl); + rsstate = avl_find_element(&runtime, name, rsstate, avl); - if (!s || !(s->running)) + if (!rsstate || !(rsstate->running)) return ENOENT; blob_buf_init(&req, 0); blobmsg_add_u32(&req, "signal", signal); blobmsg_add_string(&req, "name", name); - asprintf(&objname, "container.%s", name); + if (asprintf(&objname, "container.%s", name) == -1) + return ENOMEM; + ret = ubus_lookup_id(ctx, objname, &id); free(objname); if (ret) @@ -545,17 +949,24 @@ static int uxc_kill(char *name, int signal) } -static int uxc_set(char *name, char *path, bool autostart, bool add, char *pidfile) +static int uxc_set(char *name, char *path, signed char autostart, char *pidfile, char *tmprwsize, char *writepath, char *requiredmounts) { static struct blob_buf req; + struct settings *usettings = NULL; struct blob_attr *cur, *tb[__CONF_MAX]; int rem, ret; - bool found = false; + const char *cfname = NULL; + const char *sfname = NULL; char *fname = NULL; - char *keeppath = NULL; + char *curvol, *tmp, *mnttok; + void *mntarr; int f; struct stat sb; + /* nothing to do */ + if (!path && (autostart<0) && !pidfile && !tmprwsize && !writepath && !requiredmounts) + return 0; + 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]) @@ -564,19 +975,16 @@ static int uxc_set(char *name, char *path, bool autostart, bool add, char *pidfi if (strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) continue; - found = true; + cfname = blobmsg_name(cur); break; } - if (found && add) + if (cfname && path) return EEXIST; - if (!found && !add) + if (!cfname && !path) return ENOENT; - if (add && !path) - return EINVAL; - if (path) { if (stat(path, &sb) == -1) return ENOENT; @@ -585,50 +993,283 @@ static int uxc_set(char *name, char *path, bool autostart, bool add, char *pidfi return ENOTDIR; } - ret = mkdir(UXC_CONFDIR, 0755); + usettings = avl_find_element(&settings, blobmsg_get_string(tb[CONF_NAME]), usettings, avl); + if (path && usettings) + return EIO; - if (ret && errno != EEXIST) - return ret; + if (usettings) { + sfname = usettings->fname; + if (!tmprwsize && !writepath) { + if (usettings->tmprwsize) { + tmprwsize = usettings->tmprwsize; + writepath = NULL; + } + if (usettings->writepath) { + writepath = usettings->writepath; + tmprwsize = NULL; + } + } + if (usettings->autostart >= 0 && autostart < 0) + autostart = !!(usettings->autostart); + } - if (asprintf(&fname, "%s/%s.json", UXC_CONFDIR, name) < 1) - return ENOMEM; + if (path) { + ret = mkdir(confdir, 0755); - f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (f < 0) - return errno; + if (ret && errno != EEXIST) + return ret; - if (!add) - keeppath = strdup(blobmsg_get_string(tb[CONF_PATH])); + if (asprintf(&fname, "%s/%s.json", confdir, name) == -1) + return ENOMEM; + + f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (f < 0) + return errno; + + free(fname); + } else { + if (sfname) { + f = open(sfname, O_WRONLY | O_CREAT | O_TRUNC, 0644); + } else { + char *t1, *t2; + t1 = strdup(cfname); + t2 = strrchr(t1, '/'); + *t2 = '\0'; + + if (asprintf(&t2, "%s/settings", t1, name) == -1) + return ENOMEM; + + ret = mkdir(t2, 0755); + if (ret && ret != EEXIST) + return ret; + + free(t2); + if (asprintf(&t2, "%s/settings/%s.json", t1, name) == -1) + return ENOMEM; + + free(t1); + f = open(t2, O_WRONLY | O_CREAT | O_TRUNC, 0644); + free(t2); + } + if (f < 0) + return errno; + } blob_buf_init(&req, 0); blobmsg_add_string(&req, "name", name); - blobmsg_add_string(&req, "path", path?:keeppath); - blobmsg_add_u8(&req, "autostart", autostart); + if (path) + blobmsg_add_string(&req, "path", path); + + if (autostart >= 0) + blobmsg_add_u8(&req, "autostart", !!autostart); + if (pidfile) blobmsg_add_string(&req, "pidfile", pidfile); - dprintf(f, "%s\n", blobmsg_format_json_indent(req.head, true, 0)); + if (tmprwsize) + blobmsg_add_string(&req, "temp-overlay-size", tmprwsize); - if (!add) - free(keeppath); + if (writepath) + blobmsg_add_string(&req, "write-overlay-path", writepath); + + if (!requiredmounts && usettings && usettings->volumes) + blobmsg_add_blob(&req, usettings->volumes); + + if (requiredmounts) { + mntarr = blobmsg_open_array(&req, "volumes"); + for (mnttok = requiredmounts; ; mnttok = NULL) { + curvol = strtok_r(mnttok, ",;", &tmp); + if (!curvol) + break; + + blobmsg_add_string(&req, NULL, curvol); + } + blobmsg_close_array(&req, mntarr); + } + + tmp = blobmsg_format_json_indent(req.head, true, 0); + if (tmp) { + dprintf(f, "%s\n", tmp); + free(tmp); + } blob_buf_free(&req); + close(f); + + return 0; +} + +enum { + BLOCK_INFO_DEVICE, + BLOCK_INFO_UUID, + BLOCK_INFO_TARGET, + BLOCK_INFO_TYPE, + BLOCK_INFO_MOUNT, + __BLOCK_INFO_MAX, +}; + +static const struct blobmsg_policy block_info_policy[__BLOCK_INFO_MAX] = { + [BLOCK_INFO_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING }, + [BLOCK_INFO_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING }, + [BLOCK_INFO_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING }, + [BLOCK_INFO_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING }, + [BLOCK_INFO_MOUNT] = { .name = "mount", .type = BLOBMSG_TYPE_STRING }, +}; + + +/* check if device 'devname' is mounted according to blockd */ +static int checkblock(const char *uuid) +{ + struct blob_attr *tb[__BLOCK_INFO_MAX]; + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, blockinfo, rem) { + blobmsg_parse(block_info_policy, __BLOCK_INFO_MAX, tb, blobmsg_data(cur), blobmsg_len(cur)); + + if (!tb[BLOCK_INFO_UUID] || !tb[BLOCK_INFO_MOUNT]) + continue; + + if (!strcmp(uuid, blobmsg_get_string(tb[BLOCK_INFO_UUID]))) + return 0; + } + + return 1; +} + +enum { + UCI_FSTAB_UUID, + UCI_FSTAB_ANONYMOUS, + __UCI_FSTAB_MAX, +}; + +static const struct blobmsg_policy uci_fstab_policy[__UCI_FSTAB_MAX] = { + [UCI_FSTAB_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING }, + [UCI_FSTAB_ANONYMOUS] = { .name = ".anonymous", .type = BLOBMSG_TYPE_BOOL }, +}; + +static const char *resolveuuid(const char *volname) +{ + struct blob_attr *tb[__UCI_FSTAB_MAX]; + struct blob_attr *cur; + const char *mntname; + char *tmpvolname, *replc; + int rem, res; + + blobmsg_for_each_attr(cur, fstabinfo, rem) { + blobmsg_parse(uci_fstab_policy, __UCI_FSTAB_MAX, tb, blobmsg_data(cur), blobmsg_len(cur)); + + if (!tb[UCI_FSTAB_UUID]) + continue; + + if (tb[UCI_FSTAB_ANONYMOUS] && blobmsg_get_bool(tb[UCI_FSTAB_ANONYMOUS])) + continue; + + mntname = blobmsg_name(cur); + if (!mntname) + continue; + + tmpvolname = strdup(volname); + while ((replc = strchr(tmpvolname, '-'))) + *replc = '_'; + + res = strcmp(tmpvolname, mntname); + free(tmpvolname); + + if (!res) + return blobmsg_get_string(tb[UCI_FSTAB_UUID]); + }; + + return volname; +}; + +/* check status of each required volume */ +static int checkvolumes(struct blob_attr *volumes) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, volumes, rem) { + if (checkblock(resolveuuid(blobmsg_get_string(cur)))) + return 1; + } return 0; } +static void block_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + blockinfo = blob_memdup(blobmsg_data(msg)); +} + +static void fstab_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + fstabinfo = blob_memdup(blobmsg_data(msg)); +} + static int uxc_boot(void) { struct blob_attr *cur, *tb[__CONF_MAX]; + struct runtime_state *rsstate = NULL; + struct settings *usettings = NULL; + static struct blob_buf req; int rem, ret = 0; char *name; + unsigned int id; + bool autostart; + + ret = ubus_lookup_id(ctx, "block", &id); + if (ret) + return ENOENT; + + ret = ubus_invoke(ctx, id, "info", NULL, block_cb, NULL, 3000); + if (ret) + return ENXIO; + + ret = ubus_lookup_id(ctx, "uci", &id); + if (ret) + return ENOENT; + + blob_buf_init(&req, 0); + blobmsg_add_string(&req, "config", "fstab"); + blobmsg_add_string(&req, "type", "mount"); + + ret = ubus_invoke(ctx, id, "get", req.head, fstab_cb, NULL, 3000); + if (ret) + return ENXIO; 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])) + if (!tb[CONF_NAME] || !tb[CONF_PATH]) + continue; + + rsstate = avl_find_element(&runtime, blobmsg_get_string(tb[CONF_NAME]), rsstate, avl); + if (rsstate) + continue; + + if (tb[CONF_AUTOSTART]) + autostart = blobmsg_get_bool(tb[CONF_AUTOSTART]); + + usettings = avl_find_element(&settings, blobmsg_get_string(tb[CONF_NAME]), usettings, avl); + if (usettings && (usettings->autostart >= 0)) + autostart = !!(usettings->autostart); + + if (!autostart) continue; + /* make sure all volumes are ready before starting */ + if (tb[CONF_VOLUMES]) + if (checkvolumes(tb[CONF_VOLUMES])) + continue; + + if (usettings && usettings->volumes) + if (checkvolumes(usettings->volumes)) + continue; + name = strdup(blobmsg_get_string(tb[CONF_NAME])); + if (uxc_exists(name)) + continue; + ret += uxc_create(name, true); free(name); } @@ -639,12 +1280,13 @@ static int uxc_boot(void) static int uxc_delete(char *name, bool force) { struct blob_attr *cur, *tb[__CONF_MAX]; - struct runtime_state *s = NULL; + struct runtime_state *rsstate = NULL; + struct settings *usettings = NULL; static struct blob_buf req; uint32_t id; int rem, ret = 0; - bool found = false; - char *fname; + const char *cfname = NULL; + const char *sfname = NULL; struct stat sb; blobmsg_for_each_attr(cur, blob_data(conf.head), rem) { @@ -655,20 +1297,16 @@ static int uxc_delete(char *name, bool force) if (strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) continue; - fname = strdup(blobmsg_name(cur)); - if (!fname) - return errno; - - found = true; + cfname = blobmsg_name(cur); break; } - if (!found) + if (!cfname) return ENOENT; - s = avl_find_element(&runtime, name, s, avl); + rsstate = avl_find_element(&runtime, name, rsstate, avl); - if (s && s->running) { + if (rsstate && rsstate->running) { if (force) { ret = uxc_kill(name, SIGKILL); if (ret) @@ -680,32 +1318,47 @@ static int uxc_delete(char *name, bool force) } } - if (s) { + if (rsstate) { ret = ubus_lookup_id(ctx, "container", &id); if (ret) goto errout; blob_buf_init(&req, 0); - blobmsg_add_string(&req, "name", s->container_name); - blobmsg_add_string(&req, "instance", s->instance_name); + blobmsg_add_string(&req, "name", rsstate->container_name); + blobmsg_add_string(&req, "instance", rsstate->instance_name); if (ubus_invoke(ctx, id, "delete", req.head, NULL, NULL, 3000)) { blob_buf_free(&req); - ret=EIO; + ret = EIO; goto errout; } } - if (stat(fname, &sb) == -1) { - ret=ENOENT; + usettings = avl_find_element(&settings, name, usettings, avl); + if (usettings) + sfname = usettings->fname; + + if (sfname) { + if (stat(sfname, &sb) == -1) { + ret = ENOENT; + goto errout; + } + + if (unlink(sfname) == -1) { + ret = errno; + goto errout; + } + } + + if (stat(cfname, &sb) == -1) { + ret = ENOENT; goto errout; } - if (unlink(fname) == -1) - ret=errno; + if (unlink(cfname) == -1) + ret = errno; errout: - free(fname); return ret; } @@ -713,18 +1366,23 @@ static void reload_conf(void) { blob_buf_free(&conf); conf_load(false); + settings_free(); + conf_load(true); + settings_add(); } - int main(int argc, char **argv) { enum uxc_cmd cmd = CMD_UNKNOWN; int ret = EINVAL; char *bundle = NULL; char *pidfile = NULL; - bool autostart = false; + char *tmprwsize = NULL; + char *writepath = NULL; + char *requiredmounts = NULL; + signed char autostart = -1; bool force = false; - bool verbose = false; + bool console = false; int signal = SIGTERM; int c; @@ -739,17 +1397,12 @@ int main(int argc, char **argv) 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; + conf_load(true); + settings_add(); ret = runtime_load(); if (ret) - goto state_out; + goto conf_out; while (true) { int option_index = 0; @@ -759,21 +1412,33 @@ int main(int argc, char **argv) switch (c) { case 'a': - autostart = true; + autostart = 1; break; case 'b': bundle = optarg; break; + case 'c': + console = true; + break; + case 'f': force = true; break; + case 'j': + json_output = true; + break; + case 'p': pidfile = optarg; break; + case 't': + tmprwsize = optarg; + break; + case 'v': verbose = true; break; @@ -781,6 +1446,14 @@ int main(int argc, char **argv) case 'V': printf("uxc %s\n", UXC_VERSION); exit(0); + + case 'w': + writepath = optarg; + break; + + case 'm': + requiredmounts = optarg; + break; } } @@ -789,6 +1462,8 @@ int main(int argc, char **argv) if (!strcmp("list", argv[optind])) cmd = CMD_LIST; + else if (!strcmp("attach", argv[optind])) + cmd = CMD_ATTACH; else if (!strcmp("boot", argv[optind])) cmd = CMD_BOOT; else if(!strcmp("start", argv[optind])) @@ -807,6 +1482,13 @@ int main(int argc, char **argv) cmd = CMD_CREATE; switch (cmd) { + case CMD_ATTACH: + if (optind != argc - 2) + goto usage_out; + + ret = uxc_attach(argv[optind + 1]); + break; + case CMD_LIST: ret = uxc_list(); break; @@ -819,7 +1501,7 @@ int main(int argc, char **argv) if (optind != argc - 2) goto usage_out; - ret = uxc_start(argv[optind + 1]); + ret = uxc_start(argv[optind + 1], console); break; case CMD_STATE: @@ -842,14 +1524,14 @@ int main(int argc, char **argv) if (optind != argc - 2) goto usage_out; - ret = uxc_set(argv[optind + 1], NULL, true, false, NULL); + ret = uxc_set(argv[optind + 1], NULL, 1, NULL, NULL, NULL, NULL); break; case CMD_DISABLE: if (optind != argc - 2) goto usage_out; - ret = uxc_set(argv[optind + 1], NULL, false, false, NULL); + ret = uxc_set(argv[optind + 1], NULL, 0, NULL, NULL, NULL, NULL); break; case CMD_DELETE: @@ -863,13 +1545,15 @@ int main(int argc, char **argv) if (optind != argc - 2) goto usage_out; - if (bundle) { - ret = uxc_set(argv[optind + 1], bundle, autostart, true, pidfile); - if (ret) - goto runtime_out; + ret = uxc_exists(argv[optind + 1]); + if (ret) + goto runtime_out; - reload_conf(); - } + ret = uxc_set(argv[optind + 1], bundle, autostart, pidfile, tmprwsize, writepath, requiredmounts); + if (ret) + goto runtime_out; + + reload_conf(); ret = uxc_create(argv[optind + 1], false); break; @@ -884,8 +1568,6 @@ usage_out: usage(); runtime_out: runtime_free(); -state_out: - blob_buf_free(&state); conf_out: blob_buf_free(&conf); out: