2 * Copyright (C) 2020 Daniel Golle <daniel@makrotopia.org>
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
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.
22 #include <libubox/avl-cmp.h>
23 #include <libubox/blobmsg.h>
24 #include <libubox/blobmsg_json.h>
31 #include <sys/types.h>
37 #define OCI_VERSION_STRING "1.0.2"
38 #define UXC_CONFDIR "/etc/uxc"
39 #define UXC_RUNDIR "/var/run/uxc"
41 struct runtime_state
{
49 struct blob_attr
*ocistate
;
52 AVL_TREE(runtime
, avl_strcmp
, false, NULL
);
53 static struct blob_buf conf
;
54 static struct blob_buf state
;
56 static struct ubus_context
*ctx
;
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");
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
},
87 static int conf_load(bool load_state
)
89 int gl_flags
= GLOB_NOESCAPE
| GLOB_MARK
;
93 struct blob_buf
*target
= load_state
?&state
:&conf
;
96 if (asprintf(&globstr
, "%s/*.json", load_state
?UXC_RUNDIR
:UXC_CONFDIR
) == -1)
99 blob_buf_init(target
, 0);
100 c
= blobmsg_open_table(target
, NULL
);
102 res
= glob(globstr
, gl_flags
, NULL
, &gl
);
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
]);
113 blobmsg_close_table(target
, o
);
115 blobmsg_close_table(target
, c
);
126 static const struct blobmsg_policy list_policy
[__LIST_MAX
] = {
127 [LIST_INSTANCES
] = { .name
= "instances", .type
= BLOBMSG_TYPE_TABLE
},
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
},
150 static const struct blobmsg_policy jail_policy
[__JAIL_MAX
] = {
151 [JAIL_NAME
] = { .name
= "name", .type
= BLOBMSG_TYPE_STRING
},
154 static struct runtime_state
*
155 runtime_alloc(const char *container_name
)
157 struct runtime_state
*s
;
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
;
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
},
186 static void ocistate_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
188 struct blob_attr
**ocistate
= (struct blob_attr
**)req
->priv
;
189 struct blob_attr
*tb
[__STATE_MAX
];
191 blobmsg_parse(state_policy
, __STATE_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
193 if (!tb
[STATE_OCIVERSION
] ||
199 *ocistate
= blob_memdup(msg
);
202 static void get_ocistate(struct blob_attr
**ocistate
, const char *name
)
209 asprintf(&objname
, "container.%s", name
);
210 if (ubus_lookup_id(ctx
, objname
, &id
))
213 ubus_invoke(ctx
, id
, "state", NULL
, ocistate_cb
, ocistate
, 3000);
216 static void list_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
218 struct blob_attr
*cur
, *curi
, *tl
[__LIST_MAX
], *ti
[__INSTANCE_MAX
], *tj
[__JAIL_MAX
];
220 const char *container_name
, *instance_name
, *jail_name
;
223 struct runtime_state
*rs
;
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
])
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
));
235 if (!ti
[INSTANCE_JAIL
])
238 blobmsg_parse(jail_policy
, __JAIL_MAX
, tj
, blobmsg_data(ti
[INSTANCE_JAIL
]), blobmsg_len(ti
[INSTANCE_JAIL
]));
242 jail_name
= blobmsg_get_string(tj
[JAIL_NAME
]);
244 running
= ti
[INSTANCE_RUNNING
] && blobmsg_get_bool(ti
[INSTANCE_RUNNING
]);
246 if (ti
[INSTANCE_PID
])
247 pid
= blobmsg_get_u32(ti
[INSTANCE_PID
]);
251 if (ti
[INSTANCE_EXITCODE
])
252 exitcode
= blobmsg_get_u32(ti
[INSTANCE_EXITCODE
]);
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
);
269 static int runtime_load(void)
271 struct runtime_state
*item
, *tmp
;
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))
280 avl_for_each_element_safe(&runtime
, item
, avl
, tmp
)
281 get_ocistate(&item
->ocistate
, item
->jail_name
);
286 static void runtime_free(void)
288 struct runtime_state
*item
, *tmp
;
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
);
301 static int uxc_state(char *name
)
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
];
308 char *jail_name
= NULL
;
309 static struct blob_buf buf
;
312 ocistate
= s
->ocistate
;
315 printf("%s\n", blobmsg_format_json_indent(ocistate
, true, 0));
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
])
324 if (!strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
]))) {
326 jail_name
= blobmsg_get_string(tb
[CONF_JAIL
]);
330 bundle
= blobmsg_get_string(tb
[CONF_PATH
]);
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
);
344 printf("%s\n", blobmsg_format_json_indent(buf
.head
, true, 0));
350 static int uxc_list(void)
352 struct blob_attr
*cur
, *tb
[__CONF_MAX
], *ts
[__STATE_MAX
];
354 struct runtime_state
*s
= NULL
;
357 int container_pid
= -1;
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
])
365 autostart
= tb
[CONF_AUTOSTART
] && blobmsg_get_bool(tb
[CONF_AUTOSTART
]);
368 name
= blobmsg_get_string(tb
[CONF_NAME
]);
369 s
= avl_find_element(&runtime
, name
, s
, avl
);
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
]);
377 printf("[%c] %s %s", autostart
?'*':' ', name
, ocistatus
?:(s
&& s
->running
)?"creating":"stopped");
379 if (s
&& !s
->running
&& (s
->exitcode
>= 0))
380 printf(" exitcode: %d (%s)", s
->exitcode
, strerror(s
->exitcode
));
382 if (s
&& s
->running
&& (s
->runtime_pid
>= 0))
383 printf(" runtime pid: %d", s
->runtime_pid
);
385 if (s
&& s
->running
&& (container_pid
>= 0))
386 printf(" container pid: %d", container_pid
);
394 static int uxc_create(char *name
, bool immediately
)
396 static struct blob_buf req
;
397 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
400 struct runtime_state
*s
= NULL
;
401 char *path
= NULL
, *jailname
= NULL
;
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
])
410 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
414 path
= strdup(blobmsg_get_string(tb
[CONF_PATH
]));
422 s
= avl_find_element(&runtime
, name
, s
, avl
);
424 if (s
&& (s
->running
))
428 jailname
= strdup(blobmsg_get_string(tb
[CONF_JAIL
]));
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
);
443 if (ubus_lookup_id(ctx
, "container", &id
) ||
444 ubus_invoke(ctx
, id
, "add", req
.head
, NULL
, NULL
, 3000)) {
455 static int uxc_start(const char *name
)
460 asprintf(&objname
, "container.%s", name
);
461 if (ubus_lookup_id(ctx
, objname
, &id
))
464 return ubus_invoke(ctx
, id
, "start", NULL
, NULL
, NULL
, 3000);
467 static int uxc_kill(char *name
, int signal
)
469 static struct blob_buf req
;
470 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
473 struct runtime_state
*s
= NULL
;
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
])
481 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
491 s
= avl_find_element(&runtime
, name
, s
, avl
);
493 if (!s
|| !(s
->running
))
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
);
502 if (ubus_lookup_id(ctx
, "container", &id
) ||
503 ubus_invoke(ctx
, id
, "signal", req
.head
, NULL
, NULL
, 3000)) {
511 static int uxc_set(char *name
, char *path
, bool autostart
, bool add
)
513 static struct blob_buf req
;
514 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
518 char *keeppath
= NULL
;
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
])
527 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
544 if (stat(path
, &sb
) == -1)
547 if ((sb
.st_mode
& S_IFMT
) != S_IFDIR
)
551 ret
= mkdir(UXC_CONFDIR
, 0755);
553 if (ret
&& errno
!= EEXIST
)
556 if (asprintf(&fname
, "%s/%s.json", UXC_CONFDIR
, name
) < 1)
559 f
= open(fname
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0644);
564 keeppath
= strdup(blobmsg_get_string(tb
[CONF_PATH
]));
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
);
571 dprintf(f
, "%s\n", blobmsg_format_json_indent(req
.head
, true, 0));
581 static int uxc_boot(void)
583 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
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
]))
592 name
= strdup(blobmsg_get_string(tb
[CONF_NAME
]));
593 ret
+= uxc_create(name
, true);
600 static int uxc_delete(char *name
)
602 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
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
])
613 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
616 fname
= strdup(blobmsg_name(cur
));
627 if (stat(fname
, &sb
) == -1) {
632 if (unlink(fname
) == -1)
640 static void reload_conf(void)
642 blob_buf_free(&conf
);
646 int main(int argc
, char **argv
)
653 ctx
= ubus_connect(NULL
);
657 ret
= conf_load(false);
661 ret
= mkdir(UXC_RUNDIR
, 0755);
662 if (ret
&& errno
!= EEXIST
)
665 ret
= conf_load(true);
669 ret
= runtime_load();
673 if (!strcmp("list", argv
[1]))
675 else if (!strcmp("boot", argv
[1]))
677 else if(!strcmp("start", argv
[1])) {
681 ret
= uxc_start(argv
[2]);
682 } else if(!strcmp("state", argv
[1])) {
686 ret
= uxc_state(argv
[2]);
687 } else if(!strcmp("kill", argv
[1])) {
688 int signal
= SIGTERM
;
693 signal
= atoi(argv
[3]);
695 ret
= uxc_kill(argv
[2], signal
);
696 } else if(!strcmp("enable", argv
[1])) {
700 ret
= uxc_set(argv
[2], NULL
, true, false);
701 } else if(!strcmp("disable", argv
[1])) {
705 ret
= uxc_set(argv
[2], NULL
, false, false);
706 } else if(!strcmp("delete", argv
[1])) {
710 ret
= uxc_delete(argv
[2]);
711 } else if(!strcmp("create", argv
[1])) {
712 bool autostart
= false;
717 if (!strncmp("true", argv
[4], 5))
720 autostart
= atoi(argv
[4]);
724 ret
= uxc_set(argv
[2], argv
[3], autostart
, true);
730 ret
= uxc_create(argv
[2], false);
741 blob_buf_free(&state
);
743 blob_buf_free(&conf
);
748 fprintf(stderr
, "uxc error: %s\n", strerror(ret
));