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>
32 #include <sys/types.h>
38 #define UXC_VERSION "0.2"
39 #define OCI_VERSION_STRING "1.0.2"
40 #define UXC_CONFDIR "/etc/uxc"
42 static bool verbose
= false;
44 struct runtime_state
{
52 struct blob_attr
*ocistate
;
68 #define OPT_ARGS "ab:fp:t:vVw:"
69 static struct option long_options
[] = {
70 {"autostart", no_argument
, 0, 'a' },
71 {"bundle", required_argument
, 0, 'b' },
72 {"force", no_argument
, 0, 'f' },
73 {"pid-file", required_argument
, 0, 'p' },
74 {"temp-overlay-size", required_argument
, 0, 't' },
75 {"write-overlay-path", required_argument
, 0, 'w' },
76 {"verbose", no_argument
, 0, 'v' },
77 {"version", no_argument
, 0, 'V' },
81 AVL_TREE(runtime
, avl_strcmp
, false, NULL
);
82 static struct blob_buf conf
;
83 static struct ubus_context
*ctx
;
85 static int usage(void) {
86 printf("syntax: uxc <command> [parameters ...]\n");
87 printf("commands:\n");
88 printf("\tlist\t\t\t\t\t\tlist all configured containers\n");
89 printf("\tcreate <conf> [--bundle <path>] [--autostart]\tcreate <conf> for OCI bundle at <path>\n");
90 printf("\tstart <conf>\t\t\t\t\tstart container <conf>\n");
91 printf("\tstate <conf>\t\t\t\t\tget state of container <conf>\n");
92 printf("\tkill <conf> [<signal>]\t\t\t\tsend signal to container <conf>\n");
93 printf("\tenable <conf>\t\t\t\t\tstart container <conf> on boot\n");
94 printf("\tdisable <conf>\t\t\t\t\tdon't start container <conf> on boot\n");
95 printf("\tdelete <conf> [--force]\t\t\t\tdelete <conf>\n");
105 CONF_TEMP_OVERLAY_SIZE
,
106 CONF_WRITE_OVERLAY_PATH
,
110 static const struct blobmsg_policy conf_policy
[__CONF_MAX
] = {
111 [CONF_NAME
] = { .name
= "name", .type
= BLOBMSG_TYPE_STRING
},
112 [CONF_PATH
] = { .name
= "path", .type
= BLOBMSG_TYPE_STRING
},
113 [CONF_JAIL
] = { .name
= "jail", .type
= BLOBMSG_TYPE_STRING
},
114 [CONF_AUTOSTART
] = { .name
= "autostart", .type
= BLOBMSG_TYPE_BOOL
},
115 [CONF_PIDFILE
] = { .name
= "pidfile", .type
= BLOBMSG_TYPE_STRING
},
116 [CONF_TEMP_OVERLAY_SIZE
] = { .name
= "temp-overlay-size", .type
= BLOBMSG_TYPE_STRING
},
117 [CONF_WRITE_OVERLAY_PATH
] = { .name
= "write-overlay-path", .type
= BLOBMSG_TYPE_STRING
},
120 static int conf_load(void)
122 int gl_flags
= GLOB_NOESCAPE
| GLOB_MARK
;
128 if (asprintf(&globstr
, "%s/*.json", UXC_CONFDIR
) == -1)
131 blob_buf_init(&conf
, 0);
132 c
= blobmsg_open_table(&conf
, NULL
);
134 res
= glob(globstr
, gl_flags
, NULL
, &gl
);
139 for (j
= 0; j
< gl
.gl_pathc
; j
++) {
140 o
= blobmsg_open_table(&conf
, strdup(gl
.gl_pathv
[j
]));
141 if (!blobmsg_add_json_from_file(&conf
, gl
.gl_pathv
[j
])) {
142 ERROR("uxc: failed to load %s\n", gl
.gl_pathv
[j
]);
145 blobmsg_close_table(&conf
, o
);
147 blobmsg_close_table(&conf
, c
);
158 static const struct blobmsg_policy list_policy
[__LIST_MAX
] = {
159 [LIST_INSTANCES
] = { .name
= "instances", .type
= BLOBMSG_TYPE_TABLE
},
170 static const struct blobmsg_policy instance_policy
[__INSTANCE_MAX
] = {
171 [INSTANCE_RUNNING
] = { .name
= "running", .type
= BLOBMSG_TYPE_BOOL
},
172 [INSTANCE_PID
] = { .name
= "pid", .type
= BLOBMSG_TYPE_INT32
},
173 [INSTANCE_EXITCODE
] = { .name
= "exit_code", .type
= BLOBMSG_TYPE_INT32
},
174 [INSTANCE_JAIL
] = { .name
= "jail", .type
= BLOBMSG_TYPE_TABLE
},
182 static const struct blobmsg_policy jail_policy
[__JAIL_MAX
] = {
183 [JAIL_NAME
] = { .name
= "name", .type
= BLOBMSG_TYPE_STRING
},
186 static struct runtime_state
*
187 runtime_alloc(const char *container_name
)
189 struct runtime_state
*s
;
191 s
= calloc_a(sizeof(*s
), &new_name
, strlen(container_name
) + 1);
192 strcpy(new_name
, container_name
);
193 s
->container_name
= new_name
;
194 s
->avl
.key
= s
->container_name
;
208 static const struct blobmsg_policy state_policy
[__STATE_MAX
] = {
209 [STATE_OCIVERSION
] = { .name
= "ociVersion", .type
= BLOBMSG_TYPE_STRING
},
210 [STATE_ID
] = { .name
= "id", .type
= BLOBMSG_TYPE_STRING
},
211 [STATE_STATUS
] = { .name
= "status", .type
= BLOBMSG_TYPE_STRING
},
212 [STATE_PID
] = { .name
= "pid", .type
= BLOBMSG_TYPE_INT32
},
213 [STATE_BUNDLE
] = { .name
= "bundle", .type
= BLOBMSG_TYPE_STRING
},
214 [STATE_ANNOTATIONS
] = { .name
= "annotations", .type
= BLOBMSG_TYPE_TABLE
},
218 static void ocistate_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
220 struct blob_attr
**ocistate
= (struct blob_attr
**)req
->priv
;
221 struct blob_attr
*tb
[__STATE_MAX
];
223 blobmsg_parse(state_policy
, __STATE_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
225 if (!tb
[STATE_OCIVERSION
] ||
231 *ocistate
= blob_memdup(msg
);
234 static void get_ocistate(struct blob_attr
**ocistate
, const char *name
)
241 asprintf(&objname
, "container.%s", name
);
242 ret
= ubus_lookup_id(ctx
, objname
, &id
);
247 ubus_invoke(ctx
, id
, "state", NULL
, ocistate_cb
, ocistate
, 3000);
250 static void list_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
252 struct blob_attr
*cur
, *curi
, *tl
[__LIST_MAX
], *ti
[__INSTANCE_MAX
], *tj
[__JAIL_MAX
];
254 const char *container_name
, *instance_name
, *jail_name
;
257 struct runtime_state
*rs
;
259 blobmsg_for_each_attr(cur
, msg
, rem
) {
260 container_name
= blobmsg_name(cur
);
261 blobmsg_parse(list_policy
, __LIST_MAX
, tl
, blobmsg_data(cur
), blobmsg_len(cur
));
262 if (!tl
[LIST_INSTANCES
])
265 blobmsg_for_each_attr(curi
, tl
[LIST_INSTANCES
], remi
) {
266 instance_name
= blobmsg_name(curi
);
267 blobmsg_parse(instance_policy
, __INSTANCE_MAX
, ti
, blobmsg_data(curi
), blobmsg_len(curi
));
269 if (!ti
[INSTANCE_JAIL
])
272 blobmsg_parse(jail_policy
, __JAIL_MAX
, tj
, blobmsg_data(ti
[INSTANCE_JAIL
]), blobmsg_len(ti
[INSTANCE_JAIL
]));
276 jail_name
= blobmsg_get_string(tj
[JAIL_NAME
]);
278 running
= ti
[INSTANCE_RUNNING
] && blobmsg_get_bool(ti
[INSTANCE_RUNNING
]);
280 if (ti
[INSTANCE_PID
])
281 pid
= blobmsg_get_u32(ti
[INSTANCE_PID
]);
285 if (ti
[INSTANCE_EXITCODE
])
286 exitcode
= blobmsg_get_u32(ti
[INSTANCE_EXITCODE
]);
290 rs
= runtime_alloc(container_name
);
291 rs
->instance_name
= strdup(instance_name
);
292 rs
->jail_name
= strdup(jail_name
);
293 rs
->runtime_pid
= pid
;
294 rs
->exitcode
= exitcode
;
295 rs
->running
= running
;
296 avl_insert(&runtime
, &rs
->avl
);
303 static int runtime_load(void)
305 struct runtime_state
*item
, *tmp
;
308 avl_init(&runtime
, avl_strcmp
, false, NULL
);
309 if (ubus_lookup_id(ctx
, "container", &id
) ||
310 ubus_invoke(ctx
, id
, "list", NULL
, list_cb
, &runtime
, 3000))
314 avl_for_each_element_safe(&runtime
, item
, avl
, tmp
)
315 get_ocistate(&item
->ocistate
, item
->jail_name
);
320 static void runtime_free(void)
322 struct runtime_state
*item
, *tmp
;
324 avl_for_each_element_safe(&runtime
, item
, avl
, tmp
) {
325 avl_delete(&runtime
, &item
->avl
);
326 free(item
->instance_name
);
327 free(item
->jail_name
);
328 free(item
->ocistate
);
335 static int uxc_state(char *name
)
337 struct runtime_state
*s
= avl_find_element(&runtime
, name
, s
, avl
);
338 struct blob_attr
*ocistate
= NULL
;
339 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
342 char *jail_name
= NULL
;
343 static struct blob_buf buf
;
346 ocistate
= s
->ocistate
;
349 printf("%s\n", blobmsg_format_json_indent(ocistate
, true, 0));
353 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
354 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
355 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
358 if (!strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
]))) {
360 jail_name
= blobmsg_get_string(tb
[CONF_JAIL
]);
364 bundle
= blobmsg_get_string(tb
[CONF_PATH
]);
372 blob_buf_init(&buf
, 0);
373 blobmsg_add_string(&buf
, "ociVersion", OCI_VERSION_STRING
);
374 blobmsg_add_string(&buf
, "id", jail_name
);
375 blobmsg_add_string(&buf
, "status", s
?"stopped":"uninitialized");
376 blobmsg_add_string(&buf
, "bundle", bundle
);
378 printf("%s\n", blobmsg_format_json_indent(buf
.head
, true, 0));
384 static int uxc_list(void)
386 struct blob_attr
*cur
, *tb
[__CONF_MAX
], *ts
[__STATE_MAX
];
388 struct runtime_state
*s
= NULL
;
391 int container_pid
= -1;
394 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
395 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
396 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
399 autostart
= tb
[CONF_AUTOSTART
] && blobmsg_get_bool(tb
[CONF_AUTOSTART
]);
402 name
= blobmsg_get_string(tb
[CONF_NAME
]);
403 s
= avl_find_element(&runtime
, name
, s
, avl
);
405 if (s
&& s
->ocistate
) {
406 blobmsg_parse(state_policy
, __STATE_MAX
, ts
, blobmsg_data(s
->ocistate
), blobmsg_len(s
->ocistate
));
407 ocistatus
= blobmsg_get_string(ts
[STATE_STATUS
]);
408 container_pid
= blobmsg_get_u32(ts
[STATE_PID
]);
411 printf("[%c] %s %s", autostart
?'*':' ', name
, ocistatus
?:(s
&& s
->running
)?"creating":"stopped");
413 if (s
&& !s
->running
&& (s
->exitcode
>= 0))
414 printf(" exitcode: %d (%s)", s
->exitcode
, strerror(s
->exitcode
));
416 if (s
&& s
->running
&& (s
->runtime_pid
>= 0))
417 printf(" runtime pid: %d", s
->runtime_pid
);
419 if (s
&& s
->running
&& (container_pid
>= 0))
420 printf(" container pid: %d", container_pid
);
428 static int uxc_create(char *name
, bool immediately
)
430 static struct blob_buf req
;
431 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
434 struct runtime_state
*s
= NULL
;
435 char *path
= NULL
, *jailname
= NULL
, *pidfile
= NULL
, *tmprwsize
= NULL
, *writepath
= NULL
;
440 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
441 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
442 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
445 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
449 path
= strdup(blobmsg_get_string(tb
[CONF_PATH
]));
451 if (tb
[CONF_PIDFILE
])
452 pidfile
= strdup(blobmsg_get_string(tb
[CONF_PIDFILE
]));
454 if (tb
[CONF_TEMP_OVERLAY_SIZE
])
455 tmprwsize
= strdup(blobmsg_get_string(tb
[CONF_TEMP_OVERLAY_SIZE
]));
457 if (tb
[CONF_WRITE_OVERLAY_PATH
])
458 writepath
= strdup(blobmsg_get_string(tb
[CONF_WRITE_OVERLAY_PATH
]));
466 s
= avl_find_element(&runtime
, name
, s
, avl
);
468 if (s
&& (s
->running
))
472 jailname
= strdup(blobmsg_get_string(tb
[CONF_JAIL
]));
474 blob_buf_init(&req
, 0);
475 blobmsg_add_string(&req
, "name", name
);
476 ins
= blobmsg_open_table(&req
, "instances");
477 in
= blobmsg_open_table(&req
, name
);
478 blobmsg_add_string(&req
, "bundle", path
);
479 j
= blobmsg_open_table(&req
, "jail");
480 blobmsg_add_string(&req
, "name", jailname
?:name
);
481 blobmsg_add_u8(&req
, "immediately", immediately
);
484 blobmsg_add_string(&req
, "pidfile", pidfile
);
486 blobmsg_close_table(&req
, j
);
489 blobmsg_add_string(&req
, "overlaydir", writepath
);
492 blobmsg_add_string(&req
, "tmpoverlaysize", tmprwsize
);
494 blobmsg_close_table(&req
, in
);
495 blobmsg_close_table(&req
, ins
);
498 fprintf(stderr
, "adding container to procd:\n\t%s\n",
499 blobmsg_format_json_indent(req
.head
, true, 1));
502 if (ubus_lookup_id(ctx
, "container", &id
) ||
503 ubus_invoke(ctx
, id
, "add", req
.head
, NULL
, NULL
, 3000)) {
514 static int uxc_start(const char *name
)
519 asprintf(&objname
, "container.%s", name
);
520 if (ubus_lookup_id(ctx
, objname
, &id
))
523 return ubus_invoke(ctx
, id
, "start", NULL
, NULL
, NULL
, 3000);
526 static int uxc_kill(char *name
, int signal
)
528 static struct blob_buf req
;
529 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
533 struct runtime_state
*s
= NULL
;
536 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
537 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
538 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
541 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
551 s
= avl_find_element(&runtime
, name
, s
, avl
);
553 if (!s
|| !(s
->running
))
556 blob_buf_init(&req
, 0);
557 blobmsg_add_u32(&req
, "signal", signal
);
558 blobmsg_add_string(&req
, "name", name
);
560 asprintf(&objname
, "container.%s", name
);
561 ret
= ubus_lookup_id(ctx
, objname
, &id
);
566 if (ubus_invoke(ctx
, id
, "kill", req
.head
, NULL
, NULL
, 3000))
573 static int uxc_set(char *name
, char *path
, bool autostart
, bool add
, char *pidfile
, char *_tmprwsize
, char *_writepath
)
575 static struct blob_buf req
;
576 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
580 char *keeppath
= NULL
;
581 char *tmprwsize
= _tmprwsize
;
582 char *writepath
= _writepath
;
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
])
592 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
609 if (stat(path
, &sb
) == -1)
612 if ((sb
.st_mode
& S_IFMT
) != S_IFDIR
)
616 ret
= mkdir(UXC_CONFDIR
, 0755);
618 if (ret
&& errno
!= EEXIST
)
621 if (asprintf(&fname
, "%s/%s.json", UXC_CONFDIR
, name
) < 1)
624 f
= open(fname
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0644);
629 keeppath
= strdup(blobmsg_get_string(tb
[CONF_PATH
]));
630 if (tb
[CONF_WRITE_OVERLAY_PATH
])
631 writepath
= strdup(blobmsg_get_string(tb
[CONF_WRITE_OVERLAY_PATH
]));
633 if (tb
[CONF_TEMP_OVERLAY_SIZE
])
634 tmprwsize
= strdup(blobmsg_get_string(tb
[CONF_TEMP_OVERLAY_SIZE
]));
637 blob_buf_init(&req
, 0);
638 blobmsg_add_string(&req
, "name", name
);
639 blobmsg_add_string(&req
, "path", path
?:keeppath
);
640 blobmsg_add_u8(&req
, "autostart", autostart
);
642 blobmsg_add_string(&req
, "pidfile", pidfile
);
645 blobmsg_add_string(&req
, "temp-overlay-size", tmprwsize
);
648 blobmsg_add_string(&req
, "write-overlay-path", writepath
);
650 dprintf(f
, "%s\n", blobmsg_format_json_indent(req
.head
, true, 0));
660 static int uxc_boot(void)
662 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
666 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
667 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
668 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
] || !tb
[CONF_AUTOSTART
] || !blobmsg_get_bool(tb
[CONF_AUTOSTART
]))
671 name
= strdup(blobmsg_get_string(tb
[CONF_NAME
]));
672 ret
+= uxc_create(name
, true);
679 static int uxc_delete(char *name
, bool force
)
681 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
682 struct runtime_state
*s
= NULL
;
683 static struct blob_buf req
;
690 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
691 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
692 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
695 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
698 fname
= strdup(blobmsg_name(cur
));
709 s
= avl_find_element(&runtime
, name
, s
, avl
);
711 if (s
&& s
->running
) {
713 ret
= uxc_kill(name
, SIGKILL
);
724 ret
= ubus_lookup_id(ctx
, "container", &id
);
728 blob_buf_init(&req
, 0);
729 blobmsg_add_string(&req
, "name", s
->container_name
);
730 blobmsg_add_string(&req
, "instance", s
->instance_name
);
732 if (ubus_invoke(ctx
, id
, "delete", req
.head
, NULL
, NULL
, 3000)) {
739 if (stat(fname
, &sb
) == -1) {
744 if (unlink(fname
) == -1)
752 static void reload_conf(void)
754 blob_buf_free(&conf
);
758 int main(int argc
, char **argv
)
760 enum uxc_cmd cmd
= CMD_UNKNOWN
;
763 char *pidfile
= NULL
;
764 char *tmprwsize
= NULL
;
765 char *writepath
= NULL
;
766 bool autostart
= false;
768 int signal
= SIGTERM
;
774 ctx
= ubus_connect(NULL
);
782 ret
= runtime_load();
787 int option_index
= 0;
788 c
= getopt_long(argc
, argv
, OPT_ARGS
, long_options
, &option_index
);
818 printf("uxc %s\n", UXC_VERSION
);
830 if (!strcmp("list", argv
[optind
]))
832 else if (!strcmp("boot", argv
[optind
]))
834 else if(!strcmp("start", argv
[optind
]))
836 else if(!strcmp("state", argv
[optind
]))
838 else if(!strcmp("kill", argv
[optind
]))
840 else if(!strcmp("enable", argv
[optind
]))
842 else if(!strcmp("disable", argv
[optind
]))
844 else if(!strcmp("delete", argv
[optind
]))
846 else if(!strcmp("create", argv
[optind
]))
859 if (optind
!= argc
- 2)
862 ret
= uxc_start(argv
[optind
+ 1]);
866 if (optind
!= argc
- 2)
869 ret
= uxc_state(argv
[optind
+ 1]);
873 if (optind
== (argc
- 3))
874 signal
= atoi(argv
[optind
+ 2]);
875 else if (optind
> argc
- 2)
878 ret
= uxc_kill(argv
[optind
+ 1], signal
);
882 if (optind
!= argc
- 2)
885 ret
= uxc_set(argv
[optind
+ 1], NULL
, true, false, NULL
, NULL
, NULL
);
889 if (optind
!= argc
- 2)
892 ret
= uxc_set(argv
[optind
+ 1], NULL
, false, false, NULL
, NULL
, NULL
);
896 if (optind
!= argc
- 2)
899 ret
= uxc_delete(argv
[optind
+ 1], force
);
903 if (optind
!= argc
- 2)
907 ret
= uxc_set(argv
[optind
+ 1], bundle
, autostart
, true, pidfile
, tmprwsize
, writepath
);
914 ret
= uxc_create(argv
[optind
+ 1], false);
928 blob_buf_free(&conf
);
933 fprintf(stderr
, "uxc error: %s\n", strerror(ret
));