uxc: use new container.%s kill ubus API
[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 printf("%s\n", blobmsg_format_json_indent(req.head, true, 0));
503
504 asprintf(&objname, "container.%s", name);
505 ret = ubus_lookup_id(ctx, objname, &id);
506 free(objname);
507 if (ret)
508 return ENOENT;
509
510 if (ubus_invoke(ctx, id, "kill", req.head, NULL, NULL, 3000))
511 return EIO;
512
513 return 0;
514 }
515
516
517 static int uxc_set(char *name, char *path, bool autostart, bool add)
518 {
519 static struct blob_buf req;
520 struct blob_attr *cur, *tb[__CONF_MAX];
521 int rem, ret;
522 bool found = false;
523 char *fname = NULL;
524 char *keeppath = NULL;
525 int f;
526 struct stat sb;
527
528 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
529 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
530 if (!tb[CONF_NAME] || !tb[CONF_PATH])
531 continue;
532
533 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
534 continue;
535
536 found = true;
537 break;
538 }
539
540 if (found && add)
541 return EEXIST;
542
543 if (!found && !add)
544 return ENOENT;
545
546 if (add && !path)
547 return EINVAL;
548
549 if (path) {
550 if (stat(path, &sb) == -1)
551 return ENOENT;
552
553 if ((sb.st_mode & S_IFMT) != S_IFDIR)
554 return ENOTDIR;
555 }
556
557 ret = mkdir(UXC_CONFDIR, 0755);
558
559 if (ret && errno != EEXIST)
560 return ret;
561
562 if (asprintf(&fname, "%s/%s.json", UXC_CONFDIR, name) < 1)
563 return ENOMEM;
564
565 f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
566 if (f < 0)
567 return errno;
568
569 if (!add)
570 keeppath = strdup(blobmsg_get_string(tb[CONF_PATH]));
571
572 blob_buf_init(&req, 0);
573 blobmsg_add_string(&req, "name", name);
574 blobmsg_add_string(&req, "path", path?:keeppath);
575 blobmsg_add_u8(&req, "autostart", autostart);
576
577 dprintf(f, "%s\n", blobmsg_format_json_indent(req.head, true, 0));
578
579 if (!add)
580 free(keeppath);
581
582 blob_buf_free(&req);
583
584 return 0;
585 }
586
587 static int uxc_boot(void)
588 {
589 struct blob_attr *cur, *tb[__CONF_MAX];
590 int rem, ret = 0;
591 char *name;
592
593 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
594 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
595 if (!tb[CONF_NAME] || !tb[CONF_PATH] || !tb[CONF_AUTOSTART] || !blobmsg_get_bool(tb[CONF_AUTOSTART]))
596 continue;
597
598 name = strdup(blobmsg_get_string(tb[CONF_NAME]));
599 ret += uxc_create(name, true);
600 free(name);
601 }
602
603 return ret;
604 }
605
606 static int uxc_delete(char *name)
607 {
608 struct blob_attr *cur, *tb[__CONF_MAX];
609 int rem, ret = 0;
610 bool found = false;
611 char *fname;
612 struct stat sb;
613
614 blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
615 blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
616 if (!tb[CONF_NAME] || !tb[CONF_PATH])
617 continue;
618
619 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
620 continue;
621
622 fname = strdup(blobmsg_name(cur));
623 if (!fname)
624 return errno;
625
626 found = true;
627 break;
628 }
629
630 if (!found)
631 return ENOENT;
632
633 if (stat(fname, &sb) == -1) {
634 ret=ENOENT;
635 goto errout;
636 }
637
638 if (unlink(fname) == -1)
639 ret=errno;
640
641 errout:
642 free(fname);
643 return ret;
644 }
645
646 static void reload_conf(void)
647 {
648 blob_buf_free(&conf);
649 conf_load(false);
650 }
651
652 int main(int argc, char **argv)
653 {
654 int ret = EINVAL;
655
656 if (argc < 2)
657 return usage();
658
659 ctx = ubus_connect(NULL);
660 if (!ctx)
661 return ENODEV;
662
663 ret = conf_load(false);
664 if (ret)
665 goto out;
666
667 ret = mkdir(UXC_RUNDIR, 0755);
668 if (ret && errno != EEXIST)
669 goto conf_out;
670
671 ret = conf_load(true);
672 if (ret)
673 goto conf_out;
674
675 ret = runtime_load();
676 if (ret)
677 goto state_out;
678
679 if (!strcmp("list", argv[1]))
680 ret = uxc_list();
681 else if (!strcmp("boot", argv[1]))
682 ret = uxc_boot();
683 else if(!strcmp("start", argv[1])) {
684 if (argc < 3)
685 goto usage_out;
686
687 ret = uxc_start(argv[2]);
688 } else if(!strcmp("state", argv[1])) {
689 if (argc < 3)
690 goto usage_out;
691
692 ret = uxc_state(argv[2]);
693 } else if(!strcmp("kill", argv[1])) {
694 int signal = SIGTERM;
695 if (argc < 3)
696 goto usage_out;
697
698 if (argc == 4)
699 signal = atoi(argv[3]);
700
701 ret = uxc_kill(argv[2], signal);
702 } else if(!strcmp("enable", argv[1])) {
703 if (argc < 3)
704 goto usage_out;
705
706 ret = uxc_set(argv[2], NULL, true, false);
707 } else if(!strcmp("disable", argv[1])) {
708 if (argc < 3)
709 goto usage_out;
710
711 ret = uxc_set(argv[2], NULL, false, false);
712 } else if(!strcmp("delete", argv[1])) {
713 if (argc < 3)
714 goto usage_out;
715
716 ret = uxc_delete(argv[2]);
717 } else if(!strcmp("create", argv[1])) {
718 bool autostart = false;
719 if (argc < 3)
720 goto usage_out;
721
722 if (argc == 5) {
723 if (!strncmp("true", argv[4], 5))
724 autostart = true;
725 else
726 autostart = atoi(argv[4]);
727 }
728
729 if (argc >= 4) {
730 ret = uxc_set(argv[2], argv[3], autostart, true);
731 if (ret)
732 goto runtime_out;
733
734 reload_conf();
735 }
736 ret = uxc_create(argv[2], false);
737 } else
738 goto usage_out;
739
740 goto runtime_out;
741
742 usage_out:
743 usage();
744 runtime_out:
745 runtime_free();
746 state_out:
747 blob_buf_free(&state);
748 conf_out:
749 blob_buf_free(&conf);
750 out:
751 ubus_free(ctx);
752
753 if (ret != 0)
754 fprintf(stderr, "uxc error: %s\n", strerror(ret));
755
756 return ret;
757 }