jail: read and apply umask from OCI if defined
[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
34 #include "log.h"
35
36 #define UXC_CONFDIR "/etc/uxc"
37 #define UXC_RUNDIR "/var/run/uxc"
38
39 struct runtime_state {
40 struct avl_node avl;
41 const char *container_name;
42 const char *instance_name;
43 const char *jail_name;
44 bool running;
45 int pid;
46 int exitcode;
47 };
48
49 AVL_TREE(runtime, avl_strcmp, false, NULL);
50 static struct blob_buf conf;
51 static struct blob_buf state;
52
53 static struct ubus_context *ctx;
54
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");
65 return EINVAL;
66 }
67
68 enum {
69 CONF_NAME,
70 CONF_PATH,
71 CONF_JAIL,
72 CONF_AUTOSTART,
73 __CONF_MAX,
74 };
75
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 },
81 };
82
83 static int conf_load(bool load_state)
84 {
85 int gl_flags = GLOB_NOESCAPE | GLOB_MARK;
86 int j, res;
87 glob_t gl;
88 char *globstr;
89 struct blob_buf *target = load_state?&state:&conf;
90 void *c, *o;
91
92 if (asprintf(&globstr, "%s/*.json", load_state?UXC_RUNDIR:UXC_CONFDIR) == -1)
93 return ENOMEM;
94
95 blob_buf_init(target, 0);
96 c = blobmsg_open_table(target, NULL);
97
98 res = glob(globstr, gl_flags, NULL, &gl);
99 free(globstr);
100 if (res < 0)
101 return 0;
102
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]);
107 continue;
108 }
109 blobmsg_close_table(target, o);
110 }
111 blobmsg_close_table(target, c);
112 globfree(&gl);
113
114 return 0;
115 }
116
117 enum {
118 LIST_INSTANCES,
119 __LIST_MAX,
120 };
121
122 static const struct blobmsg_policy list_policy[__LIST_MAX] = {
123 [LIST_INSTANCES] = { .name = "instances", .type = BLOBMSG_TYPE_TABLE },
124 };
125
126 enum {
127 INSTANCE_RUNNING,
128 INSTANCE_PID,
129 INSTANCE_EXITCODE,
130 INSTANCE_JAIL,
131 __INSTANCE_MAX,
132 };
133
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 },
139 };
140
141 enum {
142 JAIL_NAME,
143 __JAIL_MAX,
144 };
145
146 static const struct blobmsg_policy jail_policy[__JAIL_MAX] = {
147 [JAIL_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
148 };
149
150 static struct runtime_state *
151 runtime_alloc(const char *container_name)
152 {
153 struct runtime_state *s;
154 char *new_name;
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;
159 return s;
160 }
161
162 static void list_cb(struct ubus_request *req, int type, struct blob_attr *msg)
163 {
164 struct blob_attr *cur, *curi, *tl[__LIST_MAX], *ti[__INSTANCE_MAX], *tj[__JAIL_MAX];
165 int rem, remi;
166 const char *container_name, *instance_name, *jail_name;
167 bool running;
168 int pid, exitcode;
169 struct runtime_state *rs;
170
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])
175 continue;
176
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));
180
181 if (!ti[INSTANCE_JAIL])
182 continue;
183
184 blobmsg_parse(jail_policy, __JAIL_MAX, tj, blobmsg_data(ti[INSTANCE_JAIL]), blobmsg_len(ti[INSTANCE_JAIL]));
185 if (!tj[JAIL_NAME])
186 continue;
187
188 jail_name = blobmsg_get_string(tj[JAIL_NAME]);
189
190 running = ti[INSTANCE_RUNNING] && blobmsg_get_bool(ti[INSTANCE_RUNNING]);
191
192 if (ti[INSTANCE_PID])
193 pid = blobmsg_get_u32(ti[INSTANCE_PID]);
194 else
195 pid = -1;
196
197 if (ti[INSTANCE_EXITCODE])
198 exitcode = blobmsg_get_u32(ti[INSTANCE_EXITCODE]);
199 else
200 exitcode = -1;
201
202 rs = runtime_alloc(container_name);
203 rs->instance_name = strdup(instance_name);
204 rs->jail_name = strdup(jail_name);
205 rs->pid = pid;
206 rs->exitcode = exitcode;
207 rs->running = running;
208 avl_insert(&runtime, &rs->avl);
209 }
210 }
211
212 return;
213 }
214
215 static int runtime_load(void)
216 {
217 uint32_t id;
218
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))
222 return EIO;
223
224 return 0;
225 }
226
227 static void runtime_free(void)
228 {
229 struct runtime_state *item, *tmp;
230
231 avl_for_each_element_safe(&runtime, item, avl, tmp) {
232 avl_delete(&runtime, &item->avl);
233 free(item);
234 }
235
236 return;
237 }
238
239 static int uxc_list(void)
240 {
241 struct blob_attr *cur, *tb[__CONF_MAX];
242 int rem;
243 struct runtime_state *s = NULL;
244 char *name;
245 bool autostart;
246
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])
250 continue;
251
252 autostart = tb[CONF_AUTOSTART] && blobmsg_get_bool(tb[CONF_AUTOSTART]);
253
254 name = blobmsg_get_string(tb[CONF_NAME]);
255 s = avl_find_element(&runtime, name, s, avl);
256
257 printf("[%c] %s %s", autostart?'*':' ', name, (s && s->running)?"RUNNING":"STOPPED");
258
259 if (s && !s->running && (s->exitcode >= 0))
260 printf(" exitcode: %d (%s)", s->exitcode, strerror(s->exitcode));
261
262 if (s && s->running && (s->pid >= 0))
263 printf(" pid: %d", s->pid);
264
265 printf("\n");
266 }
267
268 return 0;
269 }
270
271 static int uxc_start(char *name)
272 {
273 static struct blob_buf req;
274 struct blob_attr *cur, *tb[__CONF_MAX];
275 int rem, ret;
276 uint32_t id;
277 struct runtime_state *s = NULL;
278 char *path = NULL, *jailname = NULL;
279 void *in, *ins, *j;
280 bool found = false;
281
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])
285 continue;
286
287 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
288 continue;
289
290 found = true;
291 path = strdup(blobmsg_get_string(tb[CONF_PATH]));
292
293
294 break;
295 }
296
297 if (!found)
298 return ENOENT;
299
300 s = avl_find_element(&runtime, name, s, avl);
301
302 if (s && (s->running))
303 return EEXIST;
304
305 if (tb[CONF_JAIL])
306 jailname = strdup(blobmsg_get_string(tb[CONF_JAIL]));
307
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);
318
319 ret = 0;
320 if (ubus_lookup_id(ctx, "container", &id) ||
321 ubus_invoke(ctx, id, "add", req.head, NULL, NULL, 3000)) {
322 ret = EIO;
323 }
324
325 free(jailname);
326 free(path);
327 blob_buf_free(&req);
328
329 return ret;
330 }
331
332 static int uxc_stop(char *name)
333 {
334 static struct blob_buf req;
335 struct blob_attr *cur, *tb[__CONF_MAX];
336 int rem, ret;
337 uint32_t id;
338 struct runtime_state *s = NULL;
339 bool found = false;
340
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])
344 continue;
345
346 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
347 continue;
348
349 found = true;
350 break;
351 }
352
353 if (!found)
354 return ENOENT;
355
356 s = avl_find_element(&runtime, name, s, avl);
357
358 if (!s || !(s->running))
359 return ENOENT;
360
361 blob_buf_init(&req, 0);
362 blobmsg_add_string(&req, "name", name);
363 blobmsg_add_string(&req, "instance", s->instance_name);
364
365 ret = 0;
366 if (ubus_lookup_id(ctx, "container", &id) ||
367 ubus_invoke(ctx, id, "delete", req.head, NULL, NULL, 3000)) {
368 ret = EIO;
369 }
370
371 blob_buf_free(&req);
372
373 return ret;
374 }
375
376 static int uxc_set(char *name, char *path, bool autostart, bool add)
377 {
378 static struct blob_buf req;
379 struct blob_attr *cur, *tb[__CONF_MAX];
380 int rem, ret;
381 bool found = false;
382 char *fname = NULL;
383 char *keeppath = NULL;
384 int f;
385 struct stat sb;
386
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])
390 continue;
391
392 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
393 continue;
394
395 found = true;
396 break;
397 }
398
399 if (found && add)
400 return EEXIST;
401
402 if (!found && !add)
403 return ENOENT;
404
405 if (add && !path)
406 return EINVAL;
407
408 if (path) {
409 if (stat(path, &sb) == -1)
410 return ENOENT;
411
412 if ((sb.st_mode & S_IFMT) != S_IFDIR)
413 return ENOTDIR;
414 }
415
416 ret = mkdir(UXC_CONFDIR, 0755);
417
418 if (ret && errno != EEXIST)
419 return ret;
420
421 if (asprintf(&fname, "%s/%s.json", UXC_CONFDIR, name) < 1)
422 return ENOMEM;
423
424 f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
425 if (f < 0)
426 return errno;
427
428 if (!add)
429 keeppath = strdup(blobmsg_get_string(tb[CONF_PATH]));
430
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);
435
436 dprintf(f, "%s\n", blobmsg_format_json_indent(req.head, true, 0));
437
438 if (!add)
439 free(keeppath);
440
441 blob_buf_free(&req);
442
443 /* ToDo: tell ujail to run createRuntime and createContainer hooks */
444 return 0;
445 }
446
447 static int uxc_boot(void)
448 {
449 struct blob_attr *cur, *tb[__CONF_MAX];
450 int rem, ret = 0;
451 char *name;
452
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]))
456 continue;
457
458 name = strdup(blobmsg_get_string(tb[CONF_NAME]));
459 ret += uxc_start(name);
460 free(name);
461 }
462
463 return ret;
464 }
465
466 static int uxc_delete(char *name)
467 {
468 struct blob_attr *cur, *tb[__CONF_MAX];
469 int rem, ret = 0;
470 bool found = false;
471 char *fname;
472 struct stat sb;
473
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])
477 continue;
478
479 if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
480 continue;
481
482 fname = strdup(blobmsg_name(cur));
483 if (!fname)
484 return errno;
485
486 found = true;
487 break;
488 }
489
490 if (!found)
491 return ENOENT;
492
493 if (stat(fname, &sb) == -1) {
494 ret=ENOENT;
495 goto errout;
496 }
497
498 if (unlink(fname) == -1)
499 ret=errno;
500
501 errout:
502 free(fname);
503 return ret;
504 }
505
506 int main(int argc, char **argv)
507 {
508 int ret = EINVAL;
509
510 if (argc < 2)
511 return usage();
512
513 ctx = ubus_connect(NULL);
514 if (!ctx)
515 return ENODEV;
516
517 ret = conf_load(false);
518 if (ret)
519 goto out;
520
521 ret = mkdir(UXC_RUNDIR, 0755);
522 if (ret && errno != EEXIST)
523 goto conf_out;
524
525 ret = conf_load(true);
526 if (ret)
527 goto conf_out;
528
529 ret = runtime_load();
530 if (ret)
531 goto state_out;
532
533 if (!strcmp("list", argv[1]))
534 ret = uxc_list();
535 else if (!strcmp("boot", argv[1]))
536 ret = uxc_boot();
537 else if(!strcmp("start", argv[1])) {
538 if (argc < 3)
539 goto usage_out;
540
541 ret = uxc_start(argv[2]);
542 } else if(!strcmp("stop", argv[1])) {
543 if (argc < 3)
544 goto usage_out;
545
546 ret = uxc_stop(argv[2]);
547 } else if(!strcmp("enable", argv[1])) {
548 if (argc < 3)
549 goto usage_out;
550
551 ret = uxc_set(argv[2], NULL, true, false);
552 } else if(!strcmp("disable", argv[1])) {
553 if (argc < 3)
554 goto usage_out;
555
556 ret = uxc_set(argv[2], NULL, false, false);
557 } else if(!strcmp("delete", argv[1])) {
558 if (argc < 3)
559 goto usage_out;
560
561 ret = uxc_delete(argv[2]);
562 } else if(!strcmp("create", argv[1])) {
563 bool autostart = false;
564 if (argc < 4)
565 goto usage_out;
566
567 if (argc == 5) {
568 if (!strncmp("true", argv[4], 5))
569 autostart = true;
570 else
571 autostart = atoi(argv[4]);
572 }
573 ret = uxc_set(argv[2], argv[3], autostart, true);
574 } else
575 goto usage_out;
576
577 goto runtime_out;
578
579 usage_out:
580 usage();
581 runtime_out:
582 runtime_free();
583 state_out:
584 blob_buf_free(&state);
585 conf_out:
586 blob_buf_free(&conf);
587 out:
588 ubus_free(ctx);
589
590 if (ret != 0)
591 fprintf(stderr, "uxc error: %s\n", strerror(ret));
592
593 return ret;
594 }