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>
36 #define UXC_CONFDIR "/etc/uxc"
37 #define UXC_RUNDIR "/var/run/uxc"
39 struct runtime_state
{
41 const char *container_name
;
42 const char *instance_name
;
43 const char *jail_name
;
49 AVL_TREE(runtime
, avl_strcmp
, false, NULL
);
50 static struct blob_buf conf
;
51 static struct blob_buf state
;
53 static struct ubus_context
*ctx
;
55 static int usage(void) {
56 printf("syntax: uxc {command} [parameters ...]\n");
57 printf("commands:\n");
58 printf("\tlist\t\t\t\tlist all configured containers\n");
59 printf("\tcreate {conf} {path} [enabled]\tcreate {conf} for OCI bundle at {path}\n");
60 printf("\tstart {conf}\t\t\tstart container {conf}\n");
61 printf("\tstop {conf}\t\t\tstop container {conf}\n");
62 printf("\tenable {conf}\t\t\tstart container {conf} on boot\n");
63 printf("\tdisable {conf}\t\t\tdon't start container {conf} on boot\n");
64 printf("\tdelete {conf}\t\t\tdelete {conf}\n");
76 static const struct blobmsg_policy conf_policy
[__CONF_MAX
] = {
77 [CONF_NAME
] = { .name
= "name", .type
= BLOBMSG_TYPE_STRING
},
78 [CONF_PATH
] = { .name
= "path", .type
= BLOBMSG_TYPE_STRING
},
79 [CONF_JAIL
] = { .name
= "jail", .type
= BLOBMSG_TYPE_STRING
},
80 [CONF_AUTOSTART
] = { .name
= "autostart", .type
= BLOBMSG_TYPE_BOOL
},
83 static int conf_load(bool load_state
)
85 int gl_flags
= GLOB_NOESCAPE
| GLOB_MARK
;
89 struct blob_buf
*target
= load_state
?&state
:&conf
;
92 if (asprintf(&globstr
, "%s/*.json", load_state
?UXC_RUNDIR
:UXC_CONFDIR
) == -1)
95 blob_buf_init(target
, 0);
96 c
= blobmsg_open_table(target
, NULL
);
98 res
= glob(globstr
, gl_flags
, NULL
, &gl
);
103 for (j
= 0; j
< gl
.gl_pathc
; j
++) {
104 o
= blobmsg_open_table(target
, strdup(gl
.gl_pathv
[j
]));
105 if (!blobmsg_add_json_from_file(target
, gl
.gl_pathv
[j
])) {
106 ERROR("uxc: failed to load %s\n", gl
.gl_pathv
[j
]);
109 blobmsg_close_table(target
, o
);
111 blobmsg_close_table(target
, c
);
122 static const struct blobmsg_policy list_policy
[__LIST_MAX
] = {
123 [LIST_INSTANCES
] = { .name
= "instances", .type
= BLOBMSG_TYPE_TABLE
},
134 static const struct blobmsg_policy instance_policy
[__INSTANCE_MAX
] = {
135 [INSTANCE_RUNNING
] = { .name
= "running", .type
= BLOBMSG_TYPE_BOOL
},
136 [INSTANCE_PID
] = { .name
= "pid", .type
= BLOBMSG_TYPE_INT32
},
137 [INSTANCE_EXITCODE
] = { .name
= "exit_code", .type
= BLOBMSG_TYPE_INT32
},
138 [INSTANCE_JAIL
] = { .name
= "jail", .type
= BLOBMSG_TYPE_TABLE
},
146 static const struct blobmsg_policy jail_policy
[__JAIL_MAX
] = {
147 [JAIL_NAME
] = { .name
= "name", .type
= BLOBMSG_TYPE_STRING
},
150 static struct runtime_state
*
151 runtime_alloc(const char *container_name
)
153 struct runtime_state
*s
;
155 s
= calloc_a(sizeof(*s
), &new_name
, strlen(container_name
) + 1);
156 strcpy(new_name
, container_name
);
157 s
->container_name
= new_name
;
158 s
->avl
.key
= s
->container_name
;
162 static void list_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
164 struct blob_attr
*cur
, *curi
, *tl
[__LIST_MAX
], *ti
[__INSTANCE_MAX
], *tj
[__JAIL_MAX
];
166 const char *container_name
, *instance_name
, *jail_name
;
169 struct runtime_state
*rs
;
171 blobmsg_for_each_attr(cur
, msg
, rem
) {
172 container_name
= blobmsg_name(cur
);
173 blobmsg_parse(list_policy
, __LIST_MAX
, tl
, blobmsg_data(cur
), blobmsg_len(cur
));
174 if (!tl
[LIST_INSTANCES
])
177 blobmsg_for_each_attr(curi
, tl
[LIST_INSTANCES
], remi
) {
178 instance_name
= blobmsg_name(curi
);
179 blobmsg_parse(instance_policy
, __INSTANCE_MAX
, ti
, blobmsg_data(curi
), blobmsg_len(curi
));
181 if (!ti
[INSTANCE_JAIL
])
184 blobmsg_parse(jail_policy
, __JAIL_MAX
, tj
, blobmsg_data(ti
[INSTANCE_JAIL
]), blobmsg_len(ti
[INSTANCE_JAIL
]));
188 jail_name
= blobmsg_get_string(tj
[JAIL_NAME
]);
190 running
= ti
[INSTANCE_RUNNING
] && blobmsg_get_bool(ti
[INSTANCE_RUNNING
]);
192 if (ti
[INSTANCE_PID
])
193 pid
= blobmsg_get_u32(ti
[INSTANCE_PID
]);
197 if (ti
[INSTANCE_EXITCODE
])
198 exitcode
= blobmsg_get_u32(ti
[INSTANCE_EXITCODE
]);
202 rs
= runtime_alloc(container_name
);
203 rs
->instance_name
= strdup(instance_name
);
204 rs
->jail_name
= strdup(jail_name
);
206 rs
->exitcode
= exitcode
;
207 rs
->running
= running
;
208 avl_insert(&runtime
, &rs
->avl
);
215 static int runtime_load(void)
219 avl_init(&runtime
, avl_strcmp
, false, NULL
);
220 if (ubus_lookup_id(ctx
, "container", &id
) ||
221 ubus_invoke(ctx
, id
, "list", NULL
, list_cb
, &runtime
, 3000))
227 static void runtime_free(void)
229 struct runtime_state
*item
, *tmp
;
231 avl_for_each_element_safe(&runtime
, item
, avl
, tmp
) {
232 avl_delete(&runtime
, &item
->avl
);
239 static int uxc_list(void)
241 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
243 struct runtime_state
*s
= NULL
;
247 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
248 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
249 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
252 autostart
= tb
[CONF_AUTOSTART
] && blobmsg_get_bool(tb
[CONF_AUTOSTART
]);
254 name
= blobmsg_get_string(tb
[CONF_NAME
]);
255 s
= avl_find_element(&runtime
, name
, s
, avl
);
257 printf("[%c] %s %s", autostart
?'*':' ', name
, (s
&& s
->running
)?"RUNNING":"STOPPED");
259 if (s
&& !s
->running
&& (s
->exitcode
>= 0))
260 printf(" exitcode: %d (%s)", s
->exitcode
, strerror(s
->exitcode
));
262 if (s
&& s
->running
&& (s
->pid
>= 0))
263 printf(" pid: %d", s
->pid
);
271 static int uxc_start(char *name
)
273 static struct blob_buf req
;
274 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
277 struct runtime_state
*s
= NULL
;
278 char *path
= NULL
, *jailname
= NULL
;
282 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
283 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
284 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
287 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
291 path
= strdup(blobmsg_get_string(tb
[CONF_PATH
]));
300 s
= avl_find_element(&runtime
, name
, s
, avl
);
302 if (s
&& (s
->running
))
306 jailname
= strdup(blobmsg_get_string(tb
[CONF_JAIL
]));
308 blob_buf_init(&req
, 0);
309 blobmsg_add_string(&req
, "name", name
);
310 ins
= blobmsg_open_table(&req
, "instances");
311 in
= blobmsg_open_table(&req
, name
);
312 blobmsg_add_string(&req
, "bundle", path
);
313 j
= blobmsg_open_table(&req
, "jail");
314 blobmsg_add_string(&req
, "name", jailname
?:name
);
315 blobmsg_close_table(&req
, j
);
316 blobmsg_close_table(&req
, in
);
317 blobmsg_close_table(&req
, ins
);
320 if (ubus_lookup_id(ctx
, "container", &id
) ||
321 ubus_invoke(ctx
, id
, "add", req
.head
, NULL
, NULL
, 3000)) {
332 static int uxc_stop(char *name
)
334 static struct blob_buf req
;
335 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
338 struct runtime_state
*s
= NULL
;
341 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
342 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
343 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
346 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
356 s
= avl_find_element(&runtime
, name
, s
, avl
);
358 if (!s
|| !(s
->running
))
361 blob_buf_init(&req
, 0);
362 blobmsg_add_string(&req
, "name", name
);
363 blobmsg_add_string(&req
, "instance", s
->instance_name
);
366 if (ubus_lookup_id(ctx
, "container", &id
) ||
367 ubus_invoke(ctx
, id
, "delete", req
.head
, NULL
, NULL
, 3000)) {
376 static int uxc_set(char *name
, char *path
, bool autostart
, bool add
)
378 static struct blob_buf req
;
379 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
383 char *keeppath
= NULL
;
387 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
388 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
389 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
392 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
409 if (stat(path
, &sb
) == -1)
412 if ((sb
.st_mode
& S_IFMT
) != S_IFDIR
)
416 ret
= mkdir(UXC_CONFDIR
, 0755);
418 if (ret
&& errno
!= EEXIST
)
421 if (asprintf(&fname
, "%s/%s.json", UXC_CONFDIR
, name
) < 1)
424 f
= open(fname
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0644);
429 keeppath
= strdup(blobmsg_get_string(tb
[CONF_PATH
]));
431 blob_buf_init(&req
, 0);
432 blobmsg_add_string(&req
, "name", name
);
433 blobmsg_add_string(&req
, "path", path
?:keeppath
);
434 blobmsg_add_u8(&req
, "autostart", autostart
);
436 dprintf(f
, "%s\n", blobmsg_format_json_indent(req
.head
, true, 0));
443 /* ToDo: tell ujail to run createRuntime and createContainer hooks */
447 static int uxc_boot(void)
449 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
453 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
454 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
455 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
] || !tb
[CONF_AUTOSTART
] || !blobmsg_get_bool(tb
[CONF_AUTOSTART
]))
458 name
= strdup(blobmsg_get_string(tb
[CONF_NAME
]));
459 ret
+= uxc_start(name
);
466 static int uxc_delete(char *name
)
468 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
474 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
475 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
476 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
479 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
482 fname
= strdup(blobmsg_name(cur
));
493 if (stat(fname
, &sb
) == -1) {
498 if (unlink(fname
) == -1)
506 int main(int argc
, char **argv
)
513 ctx
= ubus_connect(NULL
);
517 ret
= conf_load(false);
521 ret
= mkdir(UXC_RUNDIR
, 0755);
522 if (ret
&& errno
!= EEXIST
)
525 ret
= conf_load(true);
529 ret
= runtime_load();
533 if (!strcmp("list", argv
[1]))
535 else if (!strcmp("boot", argv
[1]))
537 else if(!strcmp("start", argv
[1])) {
541 ret
= uxc_start(argv
[2]);
542 } else if(!strcmp("stop", argv
[1])) {
546 ret
= uxc_stop(argv
[2]);
547 } else if(!strcmp("enable", argv
[1])) {
551 ret
= uxc_set(argv
[2], NULL
, true, false);
552 } else if(!strcmp("disable", argv
[1])) {
556 ret
= uxc_set(argv
[2], NULL
, false, false);
557 } else if(!strcmp("delete", argv
[1])) {
561 ret
= uxc_delete(argv
[2]);
562 } else if(!strcmp("create", argv
[1])) {
563 bool autostart
= false;
568 if (!strncmp("true", argv
[4], 5))
571 autostart
= atoi(argv
[4]);
573 ret
= uxc_set(argv
[2], argv
[3], autostart
, true);
584 blob_buf_free(&state
);
586 blob_buf_free(&conf
);
591 fprintf(stderr
, "uxc error: %s\n", strerror(ret
));