uxc: remove debugging left-over
[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 int ret;
207 *ocistate = NULL;
208
209 asprintf(&objname, "container.%s", name);
210 ret = ubus_lookup_id(ctx, objname, &id);
211 free(objname);
212 if (ret)
213 return;
214
215 ubus_invoke(ctx, id, "state", NULL, ocistate_cb, ocistate, 3000);
216 }
217
218 static void list_cb(struct ubus_request *req, int type, struct blob_attr *msg)
219 {
220 struct blob_attr *cur, *curi, *tl[__LIST_MAX], *ti[__INSTANCE_MAX], *tj[__JAIL_MAX];
221 int rem, remi;
222 const char *container_name, *instance_name, *jail_name;
223 bool running;
224 int pid, exitcode;
225 struct runtime_state *rs;
226
227 blobmsg_for_each_attr(cur, msg, rem) {
228 container_name = blobmsg_name(cur);
229 blobmsg_parse(list_policy, __LIST_MAX, tl, blobmsg_data(cur), blobmsg_len(cur));
230 if (!tl[LIST_INSTANCES])
231 continue;
232
233 blobmsg_for_each_attr(curi, tl[LIST_INSTANCES], remi) {
234 instance_name = blobmsg_name(curi);
235 blobmsg_parse(instance_policy, __INSTANCE_MAX, ti, blobmsg_data(curi), blobmsg_len(curi));
236
237 if (!ti[INSTANCE_JAIL])
238 continue;
239
240 blobmsg_parse(jail_policy, __JAIL_MAX, tj, blobmsg_data(ti[INSTANCE_JAIL]), blobmsg_len(ti[INSTANCE_JAIL]));
241 if (!tj[JAIL_NAME])
242 continue;
243
244 jail_name = blobmsg_get_string(tj[JAIL_NAME]);
245
246 running = ti[INSTANCE_RUNNING] && blobmsg_get_bool(ti[INSTANCE_RUNNING]);
247
248 if (ti[INSTANCE_PID])
249 pid = blobmsg_get_u32(ti[INSTANCE_PID]);
250 else
251 pid = -1;
252
253 if (ti[INSTANCE_EXITCODE])
254 exitcode = blobmsg_get_u32(ti[INSTANCE_EXITCODE]);
255 else
256 exitcode = -1;
257
258 rs = runtime_alloc(container_name);
259 rs->instance_name = strdup(instance_name);
260 rs->jail_name = strdup(jail_name);
261 rs->runtime_pid = pid;
262 rs->exitcode = exitcode;
263 rs->running = running;
264 avl_insert(&runtime, &rs->avl);
265 }
266 }
267
268 return;
269 }
270
271 static int runtime_load(void)
272 {
273 struct runtime_state *item, *tmp;
274 uint32_t id;
275
276 avl_init(&runtime, avl_strcmp, false, NULL);
277 if (ubus_lookup_id(ctx, "container", &id) ||
278 ubus_invoke(ctx, id, "list", NULL, list_cb, &runtime, 3000))
279 return EIO;
280
281
282 avl_for_each_element_safe(&runtime, item, avl, tmp)
283 get_ocistate(&item->ocistate, item->jail_name);
284
285 return 0;
286 }
287
288 static void runtime_free(void)
289 {
290 struct runtime_state *item, *tmp;
291
292 avl_for_each_element_safe(&runtime, item, avl, tmp) {
293 avl_delete(&runtime, &item->avl);
294 free(item->instance_name);
295 free(item->jail_name);
296 free(item->ocistate);
297 free(item);
298 }
299
300 return;
301 }
302
303 static int uxc_state(char *name)
304 {
305 struct runtime_state *s = avl_find_element(&runtime, name, s, avl);
306 struct blob_attr *ocistate = NULL;
307 struct blob_attr *cur, *tb[__CONF_MAX];
308 int rem;
309 char *bundle = NULL;
310 char *jail_name = NULL;
311 static struct blob_buf buf;
312
313 if (s)
314 ocistate = s->ocistate;
315
316 if (ocistate) {
317 printf("%s\n", blobmsg_format_json_indent(ocistate, true, 0));
318 return 0;
319 }
320
321 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
322 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
323 if (!tb[CONF_NAME] || !tb[CONF_PATH])
324 continue;
325
326 if (!strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) {
327 if (tb[CONF_JAIL])
328 jail_name = blobmsg_get_string(tb[CONF_JAIL]);
329 else
330 jail_name = name;
331
332 bundle = blobmsg_get_string(tb[CONF_PATH]);
333 break;
334 }
335 }
336
337 if (!bundle)
338 return ENOENT;
339
340 blob_buf_init(&buf, 0);
341 blobmsg_add_string(&buf, "ociVersion", OCI_VERSION_STRING);
342 blobmsg_add_string(&buf, "id", jail_name);
343 blobmsg_add_string(&buf, "status", s?"stopped":"uninitialized");
344 blobmsg_add_string(&buf, "bundle", bundle);
345
346 printf("%s\n", blobmsg_format_json_indent(buf.head, true, 0));
347 blob_buf_free(&buf);
348
349 return 0;
350 }
351
352 static int uxc_list(void)
353 {
354 struct blob_attr *cur, *tb[__CONF_MAX], *ts[__STATE_MAX];
355 int rem;
356 struct runtime_state *s = NULL;
357 char *name;
358 char *ocistatus;
359 int container_pid = -1;
360 bool autostart;
361
362 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
363 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
364 if (!tb[CONF_NAME] || !tb[CONF_PATH])
365 continue;
366
367 autostart = tb[CONF_AUTOSTART] && blobmsg_get_bool(tb[CONF_AUTOSTART]);
368 ocistatus = NULL;
369 container_pid = 0;
370 name = blobmsg_get_string(tb[CONF_NAME]);
371 s = avl_find_element(&runtime, name, s, avl);
372
373 if (s && s->ocistate) {
374 blobmsg_parse(state_policy, __STATE_MAX, ts, blobmsg_data(s->ocistate), blobmsg_len(s->ocistate));
375 ocistatus = blobmsg_get_string(ts[STATE_STATUS]);
376 container_pid = blobmsg_get_u32(ts[STATE_PID]);
377 }
378
379 printf("[%c] %s %s", autostart?'*':' ', name, ocistatus?:(s && s->running)?"creating":"stopped");
380
381 if (s && !s->running && (s->exitcode >= 0))
382 printf(" exitcode: %d (%s)", s->exitcode, strerror(s->exitcode));
383
384 if (s && s->running && (s->runtime_pid >= 0))
385 printf(" runtime pid: %d", s->runtime_pid);
386
387 if (s && s->running && (container_pid >= 0))
388 printf(" container pid: %d", container_pid);
389
390 printf("\n");
391 }
392
393 return 0;
394 }
395
396 static int uxc_create(char *name, bool immediately)
397 {
398 static struct blob_buf req;
399 struct blob_attr *cur, *tb[__CONF_MAX];
400 int rem, ret;
401 uint32_t id;
402 struct runtime_state *s = NULL;
403 char *path = NULL, *jailname = NULL;
404 void *in, *ins, *j;
405 bool found = false;
406
407 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
408 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
409 if (!tb[CONF_NAME] || !tb[CONF_PATH])
410 continue;
411
412 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
413 continue;
414
415 found = true;
416 path = strdup(blobmsg_get_string(tb[CONF_PATH]));
417
418 break;
419 }
420
421 if (!found)
422 return ENOENT;
423
424 s = avl_find_element(&runtime, name, s, avl);
425
426 if (s && (s->running))
427 return EEXIST;
428
429 if (tb[CONF_JAIL])
430 jailname = strdup(blobmsg_get_string(tb[CONF_JAIL]));
431
432 blob_buf_init(&req, 0);
433 blobmsg_add_string(&req, "name", name);
434 ins = blobmsg_open_table(&req, "instances");
435 in = blobmsg_open_table(&req, name);
436 blobmsg_add_string(&req, "bundle", path);
437 j = blobmsg_open_table(&req, "jail");
438 blobmsg_add_string(&req, "name", jailname?:name);
439 blobmsg_add_u8(&req, "immediately", immediately);
440 blobmsg_close_table(&req, j);
441 blobmsg_close_table(&req, in);
442 blobmsg_close_table(&req, ins);
443
444 ret = 0;
445 if (ubus_lookup_id(ctx, "container", &id) ||
446 ubus_invoke(ctx, id, "add", req.head, NULL, NULL, 3000)) {
447 ret = EIO;
448 }
449
450 free(jailname);
451 free(path);
452 blob_buf_free(&req);
453
454 return ret;
455 }
456
457 static int uxc_start(const char *name)
458 {
459 char *objname;
460 unsigned int id;
461
462 asprintf(&objname, "container.%s", name);
463 if (ubus_lookup_id(ctx, objname, &id))
464 return ENOENT;
465
466 return ubus_invoke(ctx, id, "start", NULL, NULL, NULL, 3000);
467 }
468
469 static int uxc_kill(char *name, int signal)
470 {
471 static struct blob_buf req;
472 struct blob_attr *cur, *tb[__CONF_MAX];
473 int rem, ret;
474 char *objname;
475 unsigned int id;
476 struct runtime_state *s = NULL;
477 bool found = false;
478
479 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
480 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
481 if (!tb[CONF_NAME] || !tb[CONF_PATH])
482 continue;
483
484 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
485 continue;
486
487 found = true;
488 break;
489 }
490
491 if (!found)
492 return ENOENT;
493
494 s = avl_find_element(&runtime, name, s, avl);
495
496 if (!s || !(s->running))
497 return ENOENT;
498
499 blob_buf_init(&req, 0);
500 blobmsg_add_u32(&req, "signal", signal);
501 blobmsg_add_string(&req, "name", name);
502
503 asprintf(&objname, "container.%s", name);
504 ret = ubus_lookup_id(ctx, objname, &id);
505 free(objname);
506 if (ret)
507 return ENOENT;
508
509 if (ubus_invoke(ctx, id, "kill", req.head, NULL, NULL, 3000))
510 return EIO;
511
512 return 0;
513 }
514
515
516 static int uxc_set(char *name, char *path, bool autostart, bool add)
517 {
518 static struct blob_buf req;
519 struct blob_attr *cur, *tb[__CONF_MAX];
520 int rem, ret;
521 bool found = false;
522 char *fname = NULL;
523 char *keeppath = NULL;
524 int f;
525 struct stat sb;
526
527 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
528 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
529 if (!tb[CONF_NAME] || !tb[CONF_PATH])
530 continue;
531
532 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
533 continue;
534
535 found = true;
536 break;
537 }
538
539 if (found && add)
540 return EEXIST;
541
542 if (!found && !add)
543 return ENOENT;
544
545 if (add && !path)
546 return EINVAL;
547
548 if (path) {
549 if (stat(path, &sb) == -1)
550 return ENOENT;
551
552 if ((sb.st_mode & S_IFMT) != S_IFDIR)
553 return ENOTDIR;
554 }
555
556 ret = mkdir(UXC_CONFDIR, 0755);
557
558 if (ret && errno != EEXIST)
559 return ret;
560
561 if (asprintf(&fname, "%s/%s.json", UXC_CONFDIR, name) < 1)
562 return ENOMEM;
563
564 f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
565 if (f < 0)
566 return errno;
567
568 if (!add)
569 keeppath = strdup(blobmsg_get_string(tb[CONF_PATH]));
570
571 blob_buf_init(&req, 0);
572 blobmsg_add_string(&req, "name", name);
573 blobmsg_add_string(&req, "path", path?:keeppath);
574 blobmsg_add_u8(&req, "autostart", autostart);
575
576 dprintf(f, "%s\n", blobmsg_format_json_indent(req.head, true, 0));
577
578 if (!add)
579 free(keeppath);
580
581 blob_buf_free(&req);
582
583 return 0;
584 }
585
586 static int uxc_boot(void)
587 {
588 struct blob_attr *cur, *tb[__CONF_MAX];
589 int rem, ret = 0;
590 char *name;
591
592 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
593 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
594 if (!tb[CONF_NAME] || !tb[CONF_PATH] || !tb[CONF_AUTOSTART] || !blobmsg_get_bool(tb[CONF_AUTOSTART]))
595 continue;
596
597 name = strdup(blobmsg_get_string(tb[CONF_NAME]));
598 ret += uxc_create(name, true);
599 free(name);
600 }
601
602 return ret;
603 }
604
605 static int uxc_delete(char *name)
606 {
607 struct blob_attr *cur, *tb[__CONF_MAX];
608 int rem, ret = 0;
609 bool found = false;
610 char *fname;
611 struct stat sb;
612
613 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
614 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
615 if (!tb[CONF_NAME] || !tb[CONF_PATH])
616 continue;
617
618 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
619 continue;
620
621 fname = strdup(blobmsg_name(cur));
622 if (!fname)
623 return errno;
624
625 found = true;
626 break;
627 }
628
629 if (!found)
630 return ENOENT;
631
632 if (stat(fname, &sb) == -1) {
633 ret=ENOENT;
634 goto errout;
635 }
636
637 if (unlink(fname) == -1)
638 ret=errno;
639
640 errout:
641 free(fname);
642 return ret;
643 }
644
645 static void reload_conf(void)
646 {
647 blob_buf_free(&conf);
648 conf_load(false);
649 }
650
651 int main(int argc, char **argv)
652 {
653 int ret = EINVAL;
654
655 if (argc < 2)
656 return usage();
657
658 ctx = ubus_connect(NULL);
659 if (!ctx)
660 return ENODEV;
661
662 ret = conf_load(false);
663 if (ret)
664 goto out;
665
666 ret = mkdir(UXC_RUNDIR, 0755);
667 if (ret && errno != EEXIST)
668 goto conf_out;
669
670 ret = conf_load(true);
671 if (ret)
672 goto conf_out;
673
674 ret = runtime_load();
675 if (ret)
676 goto state_out;
677
678 if (!strcmp("list", argv[1]))
679 ret = uxc_list();
680 else if (!strcmp("boot", argv[1]))
681 ret = uxc_boot();
682 else if(!strcmp("start", argv[1])) {
683 if (argc < 3)
684 goto usage_out;
685
686 ret = uxc_start(argv[2]);
687 } else if(!strcmp("state", argv[1])) {
688 if (argc < 3)
689 goto usage_out;
690
691 ret = uxc_state(argv[2]);
692 } else if(!strcmp("kill", argv[1])) {
693 int signal = SIGTERM;
694 if (argc < 3)
695 goto usage_out;
696
697 if (argc == 4)
698 signal = atoi(argv[3]);
699
700 ret = uxc_kill(argv[2], signal);
701 } else if(!strcmp("enable", argv[1])) {
702 if (argc < 3)
703 goto usage_out;
704
705 ret = uxc_set(argv[2], NULL, true, false);
706 } else if(!strcmp("disable", argv[1])) {
707 if (argc < 3)
708 goto usage_out;
709
710 ret = uxc_set(argv[2], NULL, false, false);
711 } else if(!strcmp("delete", argv[1])) {
712 if (argc < 3)
713 goto usage_out;
714
715 ret = uxc_delete(argv[2]);
716 } else if(!strcmp("create", argv[1])) {
717 bool autostart = false;
718 if (argc < 3)
719 goto usage_out;
720
721 if (argc == 5) {
722 if (!strncmp("true", argv[4], 5))
723 autostart = true;
724 else
725 autostart = atoi(argv[4]);
726 }
727
728 if (argc >= 4) {
729 ret = uxc_set(argv[2], argv[3], autostart, true);
730 if (ret)
731 goto runtime_out;
732
733 reload_conf();
734 }
735 ret = uxc_create(argv[2], false);
736 } else
737 goto usage_out;
738
739 goto runtime_out;
740
741 usage_out:
742 usage();
743 runtime_out:
744 runtime_free();
745 state_out:
746 blob_buf_free(&state);
747 conf_out:
748 blob_buf_free(&conf);
749 out:
750 ubus_free(ctx);
751
752 if (ret != 0)
753 fprintf(stderr, "uxc error: %s\n", strerror(ret));
754
755 return ret;
756 }