uxc: behave more like a compliant OCI run-time
[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 <stdlib.h>
19 #include <stdbool.h>
20 #include <fcntl.h>
21 #include <libubus.h>
22 #include <libubox/avl-cmp.h>
23 #include <libubox/blobmsg.h>
24 #include <libubox/blobmsg_json.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <errno.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <glob.h>
33 #include <signal.h>
34
35 #include "log.h"
36
37 #define OCI_VERSION_STRING "1.0.2"
38 #define UXC_CONFDIR "/etc/uxc"
39 #define UXC_RUNDIR "/var/run/uxc"
40
41 struct runtime_state {
42 struct avl_node avl;
43 char *container_name;
44 char *instance_name;
45 char *jail_name;
46 bool running;
47 int runtime_pid;
48 int exitcode;
49 struct blob_attr *ocistate;
50 };
51
52 AVL_TREE(runtime, avl_strcmp, false, NULL);
53 static struct blob_buf conf;
54 static struct blob_buf state;
55
56 static struct ubus_context *ctx;
57
58 static int usage(void) {
59 printf("syntax: uxc {command} [parameters ...]\n");
60 printf("commands:\n");
61 printf("\tlist\t\t\t\tlist all configured containers\n");
62 printf("\tcreate {conf} [path] [enabled]\tcreate {conf} for OCI bundle at {path}\n");
63 printf("\tstart {conf}\t\t\tstart container {conf}\n");
64 printf("\tstate {conf}\t\t\tget state of container {conf}\n");
65 printf("\tkill {conf} {signal}\t\tsend signal to container {conf}\n");
66 printf("\tenable {conf}\t\t\tstart container {conf} on boot\n");
67 printf("\tdisable {conf}\t\t\tdon't start container {conf} on boot\n");
68 printf("\tdelete {conf}\t\t\tdelete {conf}\n");
69 return EINVAL;
70 }
71
72 enum {
73 CONF_NAME,
74 CONF_PATH,
75 CONF_JAIL,
76 CONF_AUTOSTART,
77 __CONF_MAX,
78 };
79
80 static const struct blobmsg_policy conf_policy[__CONF_MAX] = {
81 [CONF_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
82 [CONF_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
83 [CONF_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_STRING },
84 [CONF_AUTOSTART] = { .name = "autostart", .type = BLOBMSG_TYPE_BOOL },
85 };
86
87 static int conf_load(bool load_state)
88 {
89 int gl_flags = GLOB_NOESCAPE | GLOB_MARK;
90 int j, res;
91 glob_t gl;
92 char *globstr;
93 struct blob_buf *target = load_state?&state:&conf;
94 void *c, *o;
95
96 if (asprintf(&globstr, "%s/*.json", load_state?UXC_RUNDIR:UXC_CONFDIR) == -1)
97 return ENOMEM;
98
99 blob_buf_init(target, 0);
100 c = blobmsg_open_table(target, NULL);
101
102 res = glob(globstr, gl_flags, NULL, &gl);
103 free(globstr);
104 if (res < 0)
105 return 0;
106
107 for (j = 0; j < gl.gl_pathc; j++) {
108 o = blobmsg_open_table(target, strdup(gl.gl_pathv[j]));
109 if (!blobmsg_add_json_from_file(target, gl.gl_pathv[j])) {
110 ERROR("uxc: failed to load %s\n", gl.gl_pathv[j]);
111 continue;
112 }
113 blobmsg_close_table(target, o);
114 }
115 blobmsg_close_table(target, c);
116 globfree(&gl);
117
118 return 0;
119 }
120
121 enum {
122 LIST_INSTANCES,
123 __LIST_MAX,
124 };
125
126 static const struct blobmsg_policy list_policy[__LIST_MAX] = {
127 [LIST_INSTANCES] = { .name = "instances", .type = BLOBMSG_TYPE_TABLE },
128 };
129
130 enum {
131 INSTANCE_RUNNING,
132 INSTANCE_PID,
133 INSTANCE_EXITCODE,
134 INSTANCE_JAIL,
135 __INSTANCE_MAX,
136 };
137
138 static const struct blobmsg_policy instance_policy[__INSTANCE_MAX] = {
139 [INSTANCE_RUNNING] = { .name = "running", .type = BLOBMSG_TYPE_BOOL },
140 [INSTANCE_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 },
141 [INSTANCE_EXITCODE] = { .name = "exit_code", .type = BLOBMSG_TYPE_INT32 },
142 [INSTANCE_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_TABLE },
143 };
144
145 enum {
146 JAIL_NAME,
147 __JAIL_MAX,
148 };
149
150 static const struct blobmsg_policy jail_policy[__JAIL_MAX] = {
151 [JAIL_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
152 };
153
154 static struct runtime_state *
155 runtime_alloc(const char *container_name)
156 {
157 struct runtime_state *s;
158 char *new_name;
159 s = calloc_a(sizeof(*s), &new_name, strlen(container_name) + 1);
160 strcpy(new_name, container_name);
161 s->container_name = new_name;
162 s->avl.key = s->container_name;
163 return s;
164 }
165
166 enum {
167 STATE_OCIVERSION,
168 STATE_ID,
169 STATE_STATUS,
170 STATE_PID,
171 STATE_BUNDLE,
172 STATE_ANNOTATIONS,
173 __STATE_MAX,
174 };
175
176 static const struct blobmsg_policy state_policy[__STATE_MAX] = {
177 [STATE_OCIVERSION] = { .name = "ociVersion", .type = BLOBMSG_TYPE_STRING },
178 [STATE_ID] = { .name = "id", .type = BLOBMSG_TYPE_STRING },
179 [STATE_STATUS] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
180 [STATE_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 },
181 [STATE_BUNDLE] = { .name = "bundle", .type = BLOBMSG_TYPE_STRING },
182 [STATE_ANNOTATIONS] = { .name = "annotations", .type = BLOBMSG_TYPE_TABLE },
183 };
184
185
186 static void ocistate_cb(struct ubus_request *req, int type, struct blob_attr *msg)
187 {
188 struct blob_attr **ocistate = (struct blob_attr **)req->priv;
189 struct blob_attr *tb[__STATE_MAX];
190
191 blobmsg_parse(state_policy, __STATE_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
192
193 if (!tb[STATE_OCIVERSION] ||
194 !tb[STATE_ID] ||
195 !tb[STATE_STATUS] ||
196 !tb[STATE_BUNDLE])
197 return;
198
199 *ocistate = blob_memdup(msg);
200 }
201
202 static void get_ocistate(struct blob_attr **ocistate, const char *name)
203 {
204 char *objname;
205 unsigned int id;
206
207 *ocistate = NULL;
208
209 asprintf(&objname, "container.%s", name);
210 if (ubus_lookup_id(ctx, objname, &id))
211 return;
212
213 ubus_invoke(ctx, id, "state", NULL, ocistate_cb, ocistate, 3000);
214 }
215
216 static void list_cb(struct ubus_request *req, int type, struct blob_attr *msg)
217 {
218 struct blob_attr *cur, *curi, *tl[__LIST_MAX], *ti[__INSTANCE_MAX], *tj[__JAIL_MAX];
219 int rem, remi;
220 const char *container_name, *instance_name, *jail_name;
221 bool running;
222 int pid, exitcode;
223 struct runtime_state *rs;
224
225 blobmsg_for_each_attr(cur, msg, rem) {
226 container_name = blobmsg_name(cur);
227 blobmsg_parse(list_policy, __LIST_MAX, tl, blobmsg_data(cur), blobmsg_len(cur));
228 if (!tl[LIST_INSTANCES])
229 continue;
230
231 blobmsg_for_each_attr(curi, tl[LIST_INSTANCES], remi) {
232 instance_name = blobmsg_name(curi);
233 blobmsg_parse(instance_policy, __INSTANCE_MAX, ti, blobmsg_data(curi), blobmsg_len(curi));
234
235 if (!ti[INSTANCE_JAIL])
236 continue;
237
238 blobmsg_parse(jail_policy, __JAIL_MAX, tj, blobmsg_data(ti[INSTANCE_JAIL]), blobmsg_len(ti[INSTANCE_JAIL]));
239 if (!tj[JAIL_NAME])
240 continue;
241
242 jail_name = blobmsg_get_string(tj[JAIL_NAME]);
243
244 running = ti[INSTANCE_RUNNING] && blobmsg_get_bool(ti[INSTANCE_RUNNING]);
245
246 if (ti[INSTANCE_PID])
247 pid = blobmsg_get_u32(ti[INSTANCE_PID]);
248 else
249 pid = -1;
250
251 if (ti[INSTANCE_EXITCODE])
252 exitcode = blobmsg_get_u32(ti[INSTANCE_EXITCODE]);
253 else
254 exitcode = -1;
255
256 rs = runtime_alloc(container_name);
257 rs->instance_name = strdup(instance_name);
258 rs->jail_name = strdup(jail_name);
259 rs->runtime_pid = pid;
260 rs->exitcode = exitcode;
261 rs->running = running;
262 avl_insert(&runtime, &rs->avl);
263 }
264 }
265
266 return;
267 }
268
269 static int runtime_load(void)
270 {
271 struct runtime_state *item, *tmp;
272 uint32_t id;
273
274 avl_init(&runtime, avl_strcmp, false, NULL);
275 if (ubus_lookup_id(ctx, "container", &id) ||
276 ubus_invoke(ctx, id, "list", NULL, list_cb, &runtime, 3000))
277 return EIO;
278
279
280 avl_for_each_element_safe(&runtime, item, avl, tmp)
281 get_ocistate(&item->ocistate, item->jail_name);
282
283 return 0;
284 }
285
286 static void runtime_free(void)
287 {
288 struct runtime_state *item, *tmp;
289
290 avl_for_each_element_safe(&runtime, item, avl, tmp) {
291 avl_delete(&runtime, &item->avl);
292 free(item->instance_name);
293 free(item->jail_name);
294 free(item->ocistate);
295 free(item);
296 }
297
298 return;
299 }
300
301 static int uxc_state(char *name)
302 {
303 struct runtime_state *s = avl_find_element(&runtime, name, s, avl);
304 struct blob_attr *ocistate = NULL;
305 struct blob_attr *cur, *tb[__CONF_MAX];
306 int rem;
307 char *bundle = NULL;
308 char *jail_name = NULL;
309 static struct blob_buf buf;
310
311 if (s)
312 ocistate = s->ocistate;
313
314 if (ocistate) {
315 printf("%s\n", blobmsg_format_json_indent(ocistate, true, 0));
316 return 0;
317 }
318
319 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
320 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
321 if (!tb[CONF_NAME] || !tb[CONF_PATH])
322 continue;
323
324 if (!strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) {
325 if (tb[CONF_JAIL])
326 jail_name = blobmsg_get_string(tb[CONF_JAIL]);
327 else
328 jail_name = name;
329
330 bundle = blobmsg_get_string(tb[CONF_PATH]);
331 break;
332 }
333 }
334
335 if (!bundle)
336 return ENOENT;
337
338 blob_buf_init(&buf, 0);
339 blobmsg_add_string(&buf, "ociVersion", OCI_VERSION_STRING);
340 blobmsg_add_string(&buf, "id", jail_name);
341 blobmsg_add_string(&buf, "status", s?"stopped":"uninitialized");
342 blobmsg_add_string(&buf, "bundle", bundle);
343
344 printf("%s\n", blobmsg_format_json_indent(buf.head, true, 0));
345 blob_buf_free(&buf);
346
347 return 0;
348 }
349
350 static int uxc_list(void)
351 {
352 struct blob_attr *cur, *tb[__CONF_MAX], *ts[__STATE_MAX];
353 int rem;
354 struct runtime_state *s = NULL;
355 char *name;
356 char *ocistatus;
357 int container_pid = -1;
358 bool autostart;
359
360 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
361 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
362 if (!tb[CONF_NAME] || !tb[CONF_PATH])
363 continue;
364
365 autostart = tb[CONF_AUTOSTART] && blobmsg_get_bool(tb[CONF_AUTOSTART]);
366 ocistatus = NULL;
367 container_pid = 0;
368 name = blobmsg_get_string(tb[CONF_NAME]);
369 s = avl_find_element(&runtime, name, s, avl);
370
371 if (s && s->ocistate) {
372 blobmsg_parse(state_policy, __STATE_MAX, ts, blobmsg_data(s->ocistate), blobmsg_len(s->ocistate));
373 ocistatus = blobmsg_get_string(ts[STATE_STATUS]);
374 container_pid = blobmsg_get_u32(ts[STATE_PID]);
375 }
376
377 printf("[%c] %s %s", autostart?'*':' ', name, ocistatus?:(s && s->running)?"creating":"stopped");
378
379 if (s && !s->running && (s->exitcode >= 0))
380 printf(" exitcode: %d (%s)", s->exitcode, strerror(s->exitcode));
381
382 if (s && s->running && (s->runtime_pid >= 0))
383 printf(" runtime pid: %d", s->runtime_pid);
384
385 if (s && s->running && (container_pid >= 0))
386 printf(" container pid: %d", container_pid);
387
388 printf("\n");
389 }
390
391 return 0;
392 }
393
394 static int uxc_create(char *name, bool immediately)
395 {
396 static struct blob_buf req;
397 struct blob_attr *cur, *tb[__CONF_MAX];
398 int rem, ret;
399 uint32_t id;
400 struct runtime_state *s = NULL;
401 char *path = NULL, *jailname = NULL;
402 void *in, *ins, *j;
403 bool found = false;
404
405 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
406 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
407 if (!tb[CONF_NAME] || !tb[CONF_PATH])
408 continue;
409
410 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
411 continue;
412
413 found = true;
414 path = strdup(blobmsg_get_string(tb[CONF_PATH]));
415
416 break;
417 }
418
419 if (!found)
420 return ENOENT;
421
422 s = avl_find_element(&runtime, name, s, avl);
423
424 if (s && (s->running))
425 return EEXIST;
426
427 if (tb[CONF_JAIL])
428 jailname = strdup(blobmsg_get_string(tb[CONF_JAIL]));
429
430 blob_buf_init(&req, 0);
431 blobmsg_add_string(&req, "name", name);
432 ins = blobmsg_open_table(&req, "instances");
433 in = blobmsg_open_table(&req, name);
434 blobmsg_add_string(&req, "bundle", path);
435 j = blobmsg_open_table(&req, "jail");
436 blobmsg_add_string(&req, "name", jailname?:name);
437 blobmsg_add_u8(&req, "immediately", immediately);
438 blobmsg_close_table(&req, j);
439 blobmsg_close_table(&req, in);
440 blobmsg_close_table(&req, ins);
441
442 ret = 0;
443 if (ubus_lookup_id(ctx, "container", &id) ||
444 ubus_invoke(ctx, id, "add", req.head, NULL, NULL, 3000)) {
445 ret = EIO;
446 }
447
448 free(jailname);
449 free(path);
450 blob_buf_free(&req);
451
452 return ret;
453 }
454
455 static int uxc_start(const char *name)
456 {
457 char *objname;
458 unsigned int id;
459
460 asprintf(&objname, "container.%s", name);
461 if (ubus_lookup_id(ctx, objname, &id))
462 return ENOENT;
463
464 return ubus_invoke(ctx, id, "start", NULL, NULL, NULL, 3000);
465 }
466
467 static int uxc_kill(char *name, int signal)
468 {
469 static struct blob_buf req;
470 struct blob_attr *cur, *tb[__CONF_MAX];
471 int rem, ret;
472 uint32_t id;
473 struct runtime_state *s = NULL;
474 bool found = false;
475
476 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
477 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
478 if (!tb[CONF_NAME] || !tb[CONF_PATH])
479 continue;
480
481 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
482 continue;
483
484 found = true;
485 break;
486 }
487
488 if (!found)
489 return ENOENT;
490
491 s = avl_find_element(&runtime, name, s, avl);
492
493 if (!s || !(s->running))
494 return ENOENT;
495
496 blob_buf_init(&req, 0);
497 blobmsg_add_string(&req, "name", name);
498 blobmsg_add_string(&req, "instance", s->instance_name);
499 blobmsg_add_u32(&req, "signal", signal);
500
501 ret = 0;
502 if (ubus_lookup_id(ctx, "container", &id) ||
503 ubus_invoke(ctx, id, "signal", req.head, NULL, NULL, 3000)) {
504 ret = EIO;
505 }
506
507 return ret;
508 }
509
510
511 static int uxc_set(char *name, char *path, bool autostart, bool add)
512 {
513 static struct blob_buf req;
514 struct blob_attr *cur, *tb[__CONF_MAX];
515 int rem, ret;
516 bool found = false;
517 char *fname = NULL;
518 char *keeppath = NULL;
519 int f;
520 struct stat sb;
521
522 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
523 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
524 if (!tb[CONF_NAME] || !tb[CONF_PATH])
525 continue;
526
527 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
528 continue;
529
530 found = true;
531 break;
532 }
533
534 if (found && add)
535 return EEXIST;
536
537 if (!found && !add)
538 return ENOENT;
539
540 if (add && !path)
541 return EINVAL;
542
543 if (path) {
544 if (stat(path, &sb) == -1)
545 return ENOENT;
546
547 if ((sb.st_mode & S_IFMT) != S_IFDIR)
548 return ENOTDIR;
549 }
550
551 ret = mkdir(UXC_CONFDIR, 0755);
552
553 if (ret && errno != EEXIST)
554 return ret;
555
556 if (asprintf(&fname, "%s/%s.json", UXC_CONFDIR, name) < 1)
557 return ENOMEM;
558
559 f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
560 if (f < 0)
561 return errno;
562
563 if (!add)
564 keeppath = strdup(blobmsg_get_string(tb[CONF_PATH]));
565
566 blob_buf_init(&req, 0);
567 blobmsg_add_string(&req, "name", name);
568 blobmsg_add_string(&req, "path", path?:keeppath);
569 blobmsg_add_u8(&req, "autostart", autostart);
570
571 dprintf(f, "%s\n", blobmsg_format_json_indent(req.head, true, 0));
572
573 if (!add)
574 free(keeppath);
575
576 blob_buf_free(&req);
577
578 return 0;
579 }
580
581 static int uxc_boot(void)
582 {
583 struct blob_attr *cur, *tb[__CONF_MAX];
584 int rem, ret = 0;
585 char *name;
586
587 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
588 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
589 if (!tb[CONF_NAME] || !tb[CONF_PATH] || !tb[CONF_AUTOSTART] || !blobmsg_get_bool(tb[CONF_AUTOSTART]))
590 continue;
591
592 name = strdup(blobmsg_get_string(tb[CONF_NAME]));
593 ret += uxc_create(name, true);
594 free(name);
595 }
596
597 return ret;
598 }
599
600 static int uxc_delete(char *name)
601 {
602 struct blob_attr *cur, *tb[__CONF_MAX];
603 int rem, ret = 0;
604 bool found = false;
605 char *fname;
606 struct stat sb;
607
608 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
609 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
610 if (!tb[CONF_NAME] || !tb[CONF_PATH])
611 continue;
612
613 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
614 continue;
615
616 fname = strdup(blobmsg_name(cur));
617 if (!fname)
618 return errno;
619
620 found = true;
621 break;
622 }
623
624 if (!found)
625 return ENOENT;
626
627 if (stat(fname, &sb) == -1) {
628 ret=ENOENT;
629 goto errout;
630 }
631
632 if (unlink(fname) == -1)
633 ret=errno;
634
635 errout:
636 free(fname);
637 return ret;
638 }
639
640 int main(int argc, char **argv)
641 {
642 int ret = EINVAL;
643
644 if (argc < 2)
645 return usage();
646
647 ctx = ubus_connect(NULL);
648 if (!ctx)
649 return ENODEV;
650
651 ret = conf_load(false);
652 if (ret)
653 goto out;
654
655 ret = mkdir(UXC_RUNDIR, 0755);
656 if (ret && errno != EEXIST)
657 goto conf_out;
658
659 ret = conf_load(true);
660 if (ret)
661 goto conf_out;
662
663 ret = runtime_load();
664 if (ret)
665 goto state_out;
666
667 if (!strcmp("list", argv[1]))
668 ret = uxc_list();
669 else if (!strcmp("boot", argv[1]))
670 ret = uxc_boot();
671 else if(!strcmp("start", argv[1])) {
672 if (argc < 3)
673 goto usage_out;
674
675 ret = uxc_start(argv[2]);
676 } else if(!strcmp("state", argv[1])) {
677 if (argc < 3)
678 goto usage_out;
679
680 ret = uxc_state(argv[2]);
681 } else if(!strcmp("kill", argv[1])) {
682 int signal = SIGTERM;
683 if (argc < 3)
684 goto usage_out;
685
686 if (argc == 4)
687 signal = atoi(argv[3]);
688
689 ret = uxc_kill(argv[2], signal);
690 } else if(!strcmp("enable", argv[1])) {
691 if (argc < 3)
692 goto usage_out;
693
694 ret = uxc_set(argv[2], NULL, true, false);
695 } else if(!strcmp("disable", argv[1])) {
696 if (argc < 3)
697 goto usage_out;
698
699 ret = uxc_set(argv[2], NULL, false, false);
700 } else if(!strcmp("delete", argv[1])) {
701 if (argc < 3)
702 goto usage_out;
703
704 ret = uxc_delete(argv[2]);
705 } else if(!strcmp("create", argv[1])) {
706 bool autostart = false;
707 if (argc < 3)
708 goto usage_out;
709
710 if (argc == 5) {
711 if (!strncmp("true", argv[4], 5))
712 autostart = true;
713 else
714 autostart = atoi(argv[4]);
715 }
716
717 if (argc >= 4) {
718 ret = uxc_set(argv[2], argv[3], autostart, true);
719 if (ret)
720 goto runtime_out;
721 }
722
723 ret = uxc_create(argv[2], false);
724 } else
725 goto usage_out;
726
727 goto runtime_out;
728
729 usage_out:
730 usage();
731 runtime_out:
732 runtime_free();
733 state_out:
734 blob_buf_free(&state);
735 conf_out:
736 blob_buf_free(&conf);
737 out:
738 ubus_free(ctx);
739
740 if (ret != 0)
741 fprintf(stderr, "uxc error: %s\n", strerror(ret));
742
743 return ret;
744 }