Files
procd/uxc.c
Daniel Golle dd93c2ac6b uxc: kill command expects --signal parameter
OCI Runtime Command Line Interface specification states that the kill
command should take a --signal parameter which can be either a numeric
signal or a signal name. Implement that to comply with the OCI spec.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
2025-01-06 23:46:55 +00:00

1748 lines
38 KiB
C

/*
* Copyright (C) 2020 Daniel Golle <daniel@makrotopia.org>
*
* 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.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <glob.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <signal.h>
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <libubus.h>
#include <libubox/avl-cmp.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/ustream.h>
#ifndef ARRAY_SIZE
# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#endif
#include "log.h"
#define UXC_VERSION "0.3"
#define OCI_VERSION_STRING "1.0.2"
#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;
char *container_name;
char *instance_name;
char *jail_name;
bool running;
int runtime_pid;
int exitcode;
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,
CMD_STATE,
CMD_KILL,
CMD_ENABLE,
CMD_DISABLE,
CMD_DELETE,
CMD_CREATE,
CMD_UNKNOWN
};
#define OPT_ARGS "ab:fjm:p:t:vVw:"
static struct option long_options[] = {
{"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' },
{"signal", required_argument, 0, 's' },
{"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 }
};
struct signame {
int signal;
char name[7];
};
static const struct signame signames[] = {
#ifdef SIGABRT
{ .signal = SIGABRT, .name = "ABRT" },
#endif
#ifdef SIGALRM
{ .signal = SIGALRM, .name = "ALRM" },
#endif
#ifdef SIGBUS
{ .signal = SIGBUS, .name = "BUS" },
#endif
#ifdef SIGCHLD
{ .signal = SIGCHLD, .name = "CHLD" },
#endif
#ifdef SIGCLD
{ .signal = SIGCLD, .name = "CLD" },
#endif
#ifdef SIGCONT
{ .signal = SIGCONT, .name = "CONT" },
#endif
#ifdef SIGEMT
{ .signal = SIGEMT, .name = "EMT" },
#endif
#ifdef SIGFPE
{ .signal = SIGFPE, .name = "FPE" },
#endif
#ifdef SIGHUP
{ .signal = SIGHUP, .name = "HUP" },
#endif
#ifdef SIGILL
{ .signal = SIGILL, .name = "ILL" },
#endif
#ifdef SIGINFO
{ .signal = SIGINFO, .name = "INFO" },
#endif
#ifdef SIGINT
{ .signal = SIGINT, .name = "INT" },
#endif
#ifdef SIGIO
{ .signal = SIGIO, .name = "IO" },
#endif
#ifdef SIGIOT
{ .signal = SIGIOT, .name = "IOT" },
#endif
#ifdef SIGKILL
{ .signal = SIGKILL, .name = "KILL" },
#endif
#ifdef SIGLOST
{ .signal = SIGLOST, .name = "LOST" },
#endif
#ifdef SIGPIPE
{ .signal = SIGPIPE, .name = "PIPE" },
#endif
#ifdef SIGPOLL
{ .signal = SIGPOLL, .name = "POLL" },
#endif
#ifdef SIGPROF
{ .signal = SIGPROF, .name = "PROF" },
#endif
#ifdef SIGPWR
{ .signal = SIGPWR, .name = "PWR" },
#endif
#ifdef SIGQUIT
{ .signal = SIGQUIT, .name = "QUIT" },
#endif
#ifdef SIGSEGV
{ .signal = SIGSEGV, .name = "SEGV" },
#endif
#ifdef SIGSTKFLT
{ .signal = SIGSTKFLT, .name = "STKFLT" },
#endif
#ifdef SIGSTOP
{ .signal = SIGSTOP, .name = "STOP" },
#endif
#ifdef SIGSYS
{ .signal = SIGSYS, .name = "SYS" },
#endif
#ifdef SIGTERM
{ .signal = SIGTERM, .name = "TERM" },
#endif
#ifdef SIGTRAP
{ .signal = SIGTRAP, .name = "TRAP" },
#endif
#ifdef SIGTSTP
{ .signal = SIGTSTP, .name = "TSTP" },
#endif
#ifdef SIGTTIN
{ .signal = SIGTTIN, .name = "TTIN" },
#endif
#ifdef SIGTTOU
{ .signal = SIGTTOU, .name = "TTOU" },
#endif
#ifdef SIGUNUSED
{ .signal = SIGUNUSED, .name = "UNUSED" },
#endif
#ifdef SIGURG
{ .signal = SIGURG, .name = "URG" },
#endif
#ifdef SIGUSR1
{ .signal = SIGUSR1, .name = "USR1" },
#endif
#ifdef SIGUSR2
{ .signal = SIGUSR2, .name = "USR2" },
#endif
#ifdef SIGVTALRM
{ .signal = SIGVTALRM, .name = "VTALRM" },
#endif
#ifdef SIGWINCH
{ .signal = SIGWINCH, .name = "WINCH" },
#endif
#ifdef SIGXCPU
{ .signal = SIGXCPU, .name = "XCPU" },
#endif
#ifdef SIGXFSZ
{ .signal = SIGXFSZ, .name = "XFSZ" },
#endif
};
AVL_TREE(runtime, avl_strcmp, false, NULL);
AVL_TREE(settings, avl_strcmp, false, NULL);
static struct blob_buf conf;
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 <command> [parameters ...]\n");
printf("commands:\n");
printf("\tlist [--json]\t\t\t\tlist all configured containers\n");
printf("\tattach <conf>\t\t\t\tattach to container console\n");
printf("\tcreate <conf>\t\t\t\t(re-)create <conf>\n");
printf("\t\t[--bundle <path>]\t\t\tOCI bundle at <path>\n");
printf("\t\t[--autostart]\t\t\t\tstart on boot\n");
printf("\t\t[--temp-overlay-size <size>]\t\tuse tmpfs overlay with {size}\n");
printf("\t\t[--write-overlay-path <path>]\t\tuse overlay on {path}\n");
printf("\t\t[--mounts <v1>,<v2>,...,<vN>]\t\trequire filesystems to be available\n");
printf("\tstart [--console] <conf>\t\tstart container <conf>\n");
printf("\tstate <conf>\t\t\t\tget state of container <conf>\n");
printf("\tkill <conf> [--signal <signal>]\t\tsend signal to container <conf>\n");
printf("\tenable <conf>\t\t\t\tstart container <conf> on boot\n");
printf("\tdisable <conf>\t\t\t\tdon't start container <conf> on boot\n");
printf("\tdelete <conf> [--force]\t\t\tdelete <conf>\n");
return -EINVAL;
}
enum {
CONF_NAME,
CONF_PATH,
CONF_JAIL,
CONF_AUTOSTART,
CONF_PIDFILE,
CONF_TEMP_OVERLAY_SIZE,
CONF_WRITE_OVERLAY_PATH,
CONF_VOLUMES,
__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 },
[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_settings)
{
int gl_flags = GLOB_NOESCAPE | GLOB_MARK;
int j, res;
glob_t gl;
char *globstr;
void *c, *o;
struct stat sb;
struct blob_buf *target;
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);
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;
}
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);
}
return;
}
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;
}
enum {
STATE_OCIVERSION,
STATE_ID,
STATE_STATUS,
STATE_PID,
STATE_BUNDLE,
STATE_ANNOTATIONS,
__STATE_MAX,
};
static const struct blobmsg_policy state_policy[__STATE_MAX] = {
[STATE_OCIVERSION] = { .name = "ociVersion", .type = BLOBMSG_TYPE_STRING },
[STATE_ID] = { .name = "id", .type = BLOBMSG_TYPE_STRING },
[STATE_STATUS] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
[STATE_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 },
[STATE_BUNDLE] = { .name = "bundle", .type = BLOBMSG_TYPE_STRING },
[STATE_ANNOTATIONS] = { .name = "annotations", .type = BLOBMSG_TYPE_TABLE },
};
static void ocistate_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
struct blob_attr **ocistate = (struct blob_attr **)req->priv;
struct blob_attr *tb[__STATE_MAX];
blobmsg_parse(state_policy, __STATE_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
if (!tb[STATE_OCIVERSION] ||
!tb[STATE_ID] ||
!tb[STATE_STATUS] ||
!tb[STATE_BUNDLE])
return;
*ocistate = blob_memdup(msg);
}
static void get_ocistate(struct blob_attr **ocistate, const char *name)
{
char *objname;
unsigned int id;
int ret;
*ocistate = NULL;
if (asprintf(&objname, "container.%s", name) == -1)
exit(ENOMEM);
ret = ubus_lookup_id(ctx, objname, &id);
free(objname);
if (ret)
return;
ubus_invoke(ctx, id, "state", NULL, ocistate_cb, ocistate, 3000);
}
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->runtime_pid = pid;
rs->exitcode = exitcode;
rs->running = running;
avl_insert(&runtime, &rs->avl);
}
}
return;
}
static int runtime_load(void)
{
struct runtime_state *item, *tmp;
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;
avl_for_each_element_safe(&runtime, item, avl, tmp)
get_ocistate(&item->ocistate, item->jail_name);
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->instance_name);
free(item->jail_name);
free(item->ocistate);
free(item);
}
return;
}
static inline int setup_tios(int fd, struct termios *oldtios)
{
struct termios newtios;
if (!isatty(fd)) {
return -EIO;
}
/* Get current termios */
if (tcgetattr(fd, oldtios) < 0)
return -errno;
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) < 0)
return -errno;
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 -ECONNREFUSED;
}
/* 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 -EIO;
}
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 -EIO;
}
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 -EIO;
}
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 -ENXIO;
}
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 *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 (rsstate)
ocistate = rsstate->ocistate;
if (ocistate) {
state = blobmsg_format_json_indent(ocistate, true, 0);
if (!state)
return -ENOMEM;
printf("%s\n", state);
free(state);
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])
continue;
if (!strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) {
if (tb[CONF_JAIL])
jail_name = blobmsg_get_string(tb[CONF_JAIL]);
else
jail_name = name;
bundle = blobmsg_get_string(tb[CONF_PATH]);
break;
}
}
if (!bundle)
return -ENOENT;
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", rsstate?"stopped":"uninitialized");
blobmsg_add_string(&buf, "bundle", bundle);
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_list(void)
{
struct blob_attr *cur, *tb[__CONF_MAX], *ts[__STATE_MAX];
int rem;
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));
if (!tb[CONF_NAME] || !tb[CONF_PATH])
continue;
autostart = tb[CONF_AUTOSTART] && blobmsg_get_bool(tb[CONF_AUTOSTART]);
ocistatus = NULL;
container_pid = 0;
name = blobmsg_get_string(tb[CONF_NAME]);
rsstate = avl_find_element(&runtime, name, rsstate, avl);
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]);
}
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 (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 (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 (rsstate && rsstate->running && (container_pid >= 0)) {
if (json_output)
blobmsg_add_u32(&buf, "container_pid", container_pid);
else
printf(" container pid: %d", container_pid);
}
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;
}
static int uxc_create(char *name, bool immediately)
{
static struct blob_buf req;
struct blob_attr *cur, *tb[__CONF_MAX];
int rem, ret = 0;
uint32_t id;
struct settings *usettings = NULL;
char *path = NULL, *jailname = NULL, *pidfile = NULL, *tmprwsize = NULL, *writepath = 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;
break;
}
if (!found)
return -ENOENT;
path = blobmsg_get_string(tb[CONF_PATH]);
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 = 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);
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_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);
}
if (ubus_lookup_id(ctx, "container", &id) ||
ubus_invoke(ctx, id, "add", req.head, NULL, NULL, 3000)) {
blob_buf_free(&req);
ret = -EIO;
}
return ret;
}
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;
if (ubus_lookup_id(ctx, objname, &id))
return -ENOENT;
free(objname);
return ubus_invoke(ctx, id, "start", NULL, NULL, NULL, 3000);
}
static int uxc_kill(char *name, int signal)
{
static struct blob_buf req;
struct blob_attr *cur, *tb[__CONF_MAX];
int rem, ret;
char *objname;
unsigned int id;
struct runtime_state *rsstate = 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;
rsstate = avl_find_element(&runtime, name, rsstate, avl);
if (!rsstate || !(rsstate->running))
return -ENOENT;
blob_buf_init(&req, 0);
blobmsg_add_u32(&req, "signal", signal);
blobmsg_add_string(&req, "name", name);
if (asprintf(&objname, "container.%s", name) == -1)
return -ENOMEM;
ret = ubus_lookup_id(ctx, objname, &id);
free(objname);
if (ret)
return -ENOENT;
if (ubus_invoke(ctx, id, "kill", req.head, NULL, NULL, 3000))
return -EIO;
return 0;
}
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;
const char *cfname = NULL;
const char *sfname = NULL;
char *fname = 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])
continue;
if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
continue;
cfname = blobmsg_name(cur);
break;
}
if (cfname && path)
return -EEXIST;
if (!cfname && !path)
return -ENOENT;
if (path) {
if (stat(path, &sb) == -1)
return -ENOENT;
if ((sb.st_mode & S_IFMT) != S_IFDIR)
return -ENOTDIR;
}
usettings = avl_find_element(&settings, blobmsg_get_string(tb[CONF_NAME]), usettings, avl);
if (path && usettings)
return -EIO;
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 (path) {
ret = mkdir(confdir, 0755);
if (ret && errno != EEXIST)
return -errno;
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, '/');
if (!t2)
return -EINVAL;
*t2 = '\0';
if (asprintf(&t2, "%s/settings", t1) == -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);
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);
if (tmprwsize)
blobmsg_add_string(&req, "temp-overlay-size", tmprwsize);
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 1;
}
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 bool 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 false;
}
return true;
}
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 bool 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 true;
}
return false;
}
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 ret;
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;
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;
if (uxc_create(name, true))
++ret;
free(name);
}
return ret;
}
static int uxc_delete(char *name, bool force)
{
struct blob_attr *cur, *tb[__CONF_MAX];
struct runtime_state *rsstate = NULL;
struct settings *usettings = NULL;
static struct blob_buf req;
uint32_t id;
int rem, ret = 0;
const char *cfname = NULL;
const char *sfname = NULL;
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;
cfname = blobmsg_name(cur);
break;
}
if (!cfname)
return -ENOENT;
rsstate = avl_find_element(&runtime, name, rsstate, avl);
if (rsstate && rsstate->running) {
if (force) {
ret = uxc_kill(name, SIGKILL);
if (ret)
goto errout;
} else {
ret = -EWOULDBLOCK;
goto errout;
}
}
if (rsstate) {
ret = ubus_lookup_id(ctx, "container", &id);
if (ret)
goto errout;
blob_buf_init(&req, 0);
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;
goto errout;
}
}
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(cfname) == -1)
ret = -errno;
errout:
return ret;
}
static void reload_conf(void)
{
blob_buf_free(&conf);
conf_load(false);
settings_free();
blob_buf_free(&settingsbuf);
conf_load(true);
settings_add();
}
static int get_signum(const char *name)
{
char *endptr;
long sig;
int i;
sig = strtol(name, &endptr, 10);
if (endptr == name + strlen(name) && sig < NSIG)
/* string is a valid signal number */
return sig;
if (strncasecmp(name, "SIG", 3) == 0)
name += 3;
for (i = 0; i < ARRAY_SIZE(signames); ++i) {
if (!strcmp(name, signames[i].name))
return signames[i].signal;
}
return -1;
}
int main(int argc, char **argv)
{
enum uxc_cmd cmd = CMD_UNKNOWN;
int ret = -EINVAL;
char *bundle = NULL;
char *pidfile = NULL;
char *tmprwsize = NULL;
char *writepath = NULL;
char *requiredmounts = NULL;
signed char autostart = -1;
bool force = false;
bool console = false;
int signal = SIGTERM;
int c;
if (argc < 2)
return usage();
ctx = ubus_connect(NULL);
if (!ctx)
return -ENODEV;
ret = conf_load(false);
if (ret < 0)
goto out;
ret = conf_load(true);
if (ret < 0)
goto conf_out;
ret = settings_add();
if (ret < 0)
goto settings_out;
ret = runtime_load();
if (ret)
goto settings_avl_out;
while (true) {
int option_index = 0;
c = getopt_long(argc, argv, OPT_ARGS, long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'a':
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 's':
signal = get_signum(optarg);
if (signal < 0)
goto usage_out;
break;
case 't':
tmprwsize = optarg;
break;
case 'v':
verbose = true;
break;
case 'V':
printf("uxc %s\n", UXC_VERSION);
exit(0);
case 'w':
writepath = optarg;
break;
case 'm':
requiredmounts = optarg;
break;
}
}
if (optind == argc)
goto usage_out;
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]))
cmd = CMD_START;
else if(!strcmp("state", argv[optind]))
cmd = CMD_STATE;
else if(!strcmp("kill", argv[optind]))
cmd = CMD_KILL;
else if(!strcmp("enable", argv[optind]))
cmd = CMD_ENABLE;
else if(!strcmp("disable", argv[optind]))
cmd = CMD_DISABLE;
else if(!strcmp("delete", argv[optind]))
cmd = CMD_DELETE;
else if(!strcmp("create", argv[optind]))
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;
case CMD_BOOT:
ret = uxc_boot();
break;
case CMD_START:
if (optind != argc - 2)
goto usage_out;
ret = uxc_start(argv[optind + 1], console);
break;
case CMD_STATE:
if (optind != argc - 2)
goto usage_out;
ret = uxc_state(argv[optind + 1]);
break;
case CMD_KILL:
if (optind > argc - 2)
goto usage_out;
ret = uxc_kill(argv[optind + 1], signal);
break;
case CMD_ENABLE:
if (optind != argc - 2)
goto usage_out;
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, 0, NULL, NULL, NULL, NULL);
break;
case CMD_DELETE:
if (optind != argc - 2)
goto usage_out;
ret = uxc_delete(argv[optind + 1], force);
break;
case CMD_CREATE:
if (optind != argc - 2)
goto usage_out;
ret = uxc_exists(argv[optind + 1]);
if (ret)
goto runtime_out;
ret = uxc_set(argv[optind + 1], bundle, autostart, pidfile, tmprwsize, writepath, requiredmounts);
if (ret < 0)
goto runtime_out;
if (ret > 0)
reload_conf();
ret = uxc_create(argv[optind + 1], false);
break;
default:
goto usage_out;
}
goto runtime_out;
usage_out:
ret = usage();
runtime_out:
runtime_free();
settings_avl_out:
settings_free();
settings_out:
blob_buf_free(&settingsbuf);
conf_out:
blob_buf_free(&conf);
out:
ubus_free(ctx);
if (ret < 0)
fprintf(stderr, "uxc error: %s\n", strerror(-ret));
return ret;
}