uxc.c 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  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. #ifndef _GNU_SOURCE
  14. #define _GNU_SOURCE
  15. #endif
  16. #include <stdlib.h>
  17. #include <stdbool.h>
  18. #include <fcntl.h>
  19. #include <libubus.h>
  20. #include <libubox/avl-cmp.h>
  21. #include <libubox/blobmsg.h>
  22. #include <libubox/blobmsg_json.h>
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #include <unistd.h>
  26. #include <fcntl.h>
  27. #include <errno.h>
  28. #include <getopt.h>
  29. #include <sys/stat.h>
  30. #include <sys/types.h>
  31. #include <glob.h>
  32. #include <signal.h>
  33. #include "log.h"
  34. #define UXC_VERSION "0.2"
  35. #define OCI_VERSION_STRING "1.0.2"
  36. #define UXC_ETC_CONFDIR "/etc/uxc"
  37. #define UXC_VOL_CONFDIR "/var/run/uxc"
  38. static bool verbose = false;
  39. static bool json_output = false;
  40. static char *confdir = UXC_ETC_CONFDIR;
  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. enum uxc_cmd {
  52. CMD_LIST,
  53. CMD_BOOT,
  54. CMD_START,
  55. CMD_STATE,
  56. CMD_KILL,
  57. CMD_ENABLE,
  58. CMD_DISABLE,
  59. CMD_DELETE,
  60. CMD_CREATE,
  61. CMD_UNKNOWN
  62. };
  63. #define OPT_ARGS "ab:fjm:p:t:vVw:"
  64. static struct option long_options[] = {
  65. {"autostart", no_argument, 0, 'a' },
  66. {"bundle", required_argument, 0, 'b' },
  67. {"force", no_argument, 0, 'f' },
  68. {"json", no_argument, 0, 'j' },
  69. {"mounts", required_argument, 0, 'm' },
  70. {"pid-file", required_argument, 0, 'p' },
  71. {"temp-overlay-size", required_argument, 0, 't' },
  72. {"write-overlay-path", required_argument, 0, 'w' },
  73. {"verbose", no_argument, 0, 'v' },
  74. {"version", no_argument, 0, 'V' },
  75. {0, 0, 0, 0 }
  76. };
  77. AVL_TREE(runtime, avl_strcmp, false, NULL);
  78. static struct blob_buf conf;
  79. static struct blob_attr *blockinfo;
  80. static struct blob_attr *fstabinfo;
  81. static struct ubus_context *ctx;
  82. static int usage(void) {
  83. printf("syntax: uxc <command> [parameters ...]\n");
  84. printf("commands:\n");
  85. printf("\tlist [--json]\t\t\t\tlist all configured containers\n");
  86. printf("\tcreate <conf>\t\t\t\t\t(re-)create <conf>\n");
  87. printf(" [--bundle <path>]\t\t\tOCI bundle at <path>\n");
  88. printf(" [--autostart]\t\t\t\tstart on boot\n");
  89. printf(" [--temp-overlay-size size]\t\tuse tmpfs overlay with {size}\n");
  90. printf(" [--write-overlay-path path]\t\tuse overlay on {path}\n");
  91. printf(" [--mounts v1,v2,...,vN]\t\trequire filesystems to be available\n");
  92. printf("\tstart <conf>\t\t\t\t\tstart container <conf>\n");
  93. printf("\tstate <conf>\t\t\t\t\tget state of container <conf>\n");
  94. printf("\tkill <conf> [<signal>]\t\t\t\tsend signal to container <conf>\n");
  95. printf("\tenable <conf>\t\t\t\t\tstart container <conf> on boot\n");
  96. printf("\tdisable <conf>\t\t\t\t\tdon't start container <conf> on boot\n");
  97. printf("\tdelete <conf> [--force]\t\t\t\tdelete <conf>\n");
  98. return EINVAL;
  99. }
  100. enum {
  101. CONF_NAME,
  102. CONF_PATH,
  103. CONF_JAIL,
  104. CONF_AUTOSTART,
  105. CONF_PIDFILE,
  106. CONF_TEMP_OVERLAY_SIZE,
  107. CONF_WRITE_OVERLAY_PATH,
  108. CONF_VOLUMES,
  109. __CONF_MAX,
  110. };
  111. static const struct blobmsg_policy conf_policy[__CONF_MAX] = {
  112. [CONF_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
  113. [CONF_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
  114. [CONF_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_STRING },
  115. [CONF_AUTOSTART] = { .name = "autostart", .type = BLOBMSG_TYPE_BOOL },
  116. [CONF_PIDFILE] = { .name = "pidfile", .type = BLOBMSG_TYPE_STRING },
  117. [CONF_TEMP_OVERLAY_SIZE] = { .name = "temp-overlay-size", .type = BLOBMSG_TYPE_STRING },
  118. [CONF_WRITE_OVERLAY_PATH] = { .name = "write-overlay-path", .type = BLOBMSG_TYPE_STRING },
  119. [CONF_VOLUMES] = { .name = "volumes", .type = BLOBMSG_TYPE_ARRAY },
  120. };
  121. static int conf_load(void)
  122. {
  123. int gl_flags = GLOB_NOESCAPE | GLOB_MARK;
  124. int j, res;
  125. glob_t gl;
  126. char *globstr;
  127. void *c, *o;
  128. struct stat sb;
  129. if (!stat(UXC_VOL_CONFDIR, &sb)) {
  130. if (sb.st_mode & S_IFDIR)
  131. confdir = UXC_VOL_CONFDIR;
  132. }
  133. if (asprintf(&globstr, "%s/*.json", confdir) == -1)
  134. return ENOMEM;
  135. blob_buf_init(&conf, 0);
  136. c = blobmsg_open_table(&conf, NULL);
  137. res = glob(globstr, gl_flags, NULL, &gl);
  138. free(globstr);
  139. if (res < 0)
  140. return 0;
  141. for (j = 0; j < gl.gl_pathc; j++) {
  142. o = blobmsg_open_table(&conf, strdup(gl.gl_pathv[j]));
  143. if (!blobmsg_add_json_from_file(&conf, gl.gl_pathv[j])) {
  144. ERROR("uxc: failed to load %s\n", gl.gl_pathv[j]);
  145. continue;
  146. }
  147. blobmsg_close_table(&conf, o);
  148. }
  149. blobmsg_close_table(&conf, c);
  150. globfree(&gl);
  151. return 0;
  152. }
  153. enum {
  154. LIST_INSTANCES,
  155. __LIST_MAX,
  156. };
  157. static const struct blobmsg_policy list_policy[__LIST_MAX] = {
  158. [LIST_INSTANCES] = { .name = "instances", .type = BLOBMSG_TYPE_TABLE },
  159. };
  160. enum {
  161. INSTANCE_RUNNING,
  162. INSTANCE_PID,
  163. INSTANCE_EXITCODE,
  164. INSTANCE_JAIL,
  165. __INSTANCE_MAX,
  166. };
  167. static const struct blobmsg_policy instance_policy[__INSTANCE_MAX] = {
  168. [INSTANCE_RUNNING] = { .name = "running", .type = BLOBMSG_TYPE_BOOL },
  169. [INSTANCE_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 },
  170. [INSTANCE_EXITCODE] = { .name = "exit_code", .type = BLOBMSG_TYPE_INT32 },
  171. [INSTANCE_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_TABLE },
  172. };
  173. enum {
  174. JAIL_NAME,
  175. __JAIL_MAX,
  176. };
  177. static const struct blobmsg_policy jail_policy[__JAIL_MAX] = {
  178. [JAIL_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
  179. };
  180. static struct runtime_state *
  181. runtime_alloc(const char *container_name)
  182. {
  183. struct runtime_state *s;
  184. char *new_name;
  185. s = calloc_a(sizeof(*s), &new_name, strlen(container_name) + 1);
  186. strcpy(new_name, container_name);
  187. s->container_name = new_name;
  188. s->avl.key = s->container_name;
  189. return s;
  190. }
  191. enum {
  192. STATE_OCIVERSION,
  193. STATE_ID,
  194. STATE_STATUS,
  195. STATE_PID,
  196. STATE_BUNDLE,
  197. STATE_ANNOTATIONS,
  198. __STATE_MAX,
  199. };
  200. static const struct blobmsg_policy state_policy[__STATE_MAX] = {
  201. [STATE_OCIVERSION] = { .name = "ociVersion", .type = BLOBMSG_TYPE_STRING },
  202. [STATE_ID] = { .name = "id", .type = BLOBMSG_TYPE_STRING },
  203. [STATE_STATUS] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
  204. [STATE_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 },
  205. [STATE_BUNDLE] = { .name = "bundle", .type = BLOBMSG_TYPE_STRING },
  206. [STATE_ANNOTATIONS] = { .name = "annotations", .type = BLOBMSG_TYPE_TABLE },
  207. };
  208. static void ocistate_cb(struct ubus_request *req, int type, struct blob_attr *msg)
  209. {
  210. struct blob_attr **ocistate = (struct blob_attr **)req->priv;
  211. struct blob_attr *tb[__STATE_MAX];
  212. blobmsg_parse(state_policy, __STATE_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
  213. if (!tb[STATE_OCIVERSION] ||
  214. !tb[STATE_ID] ||
  215. !tb[STATE_STATUS] ||
  216. !tb[STATE_BUNDLE])
  217. return;
  218. *ocistate = blob_memdup(msg);
  219. }
  220. static void get_ocistate(struct blob_attr **ocistate, const char *name)
  221. {
  222. char *objname;
  223. unsigned int id;
  224. int ret;
  225. *ocistate = NULL;
  226. if (asprintf(&objname, "container.%s", name) == -1)
  227. exit(ENOMEM);
  228. ret = ubus_lookup_id(ctx, objname, &id);
  229. free(objname);
  230. if (ret)
  231. return;
  232. ubus_invoke(ctx, id, "state", NULL, ocistate_cb, ocistate, 3000);
  233. }
  234. static void list_cb(struct ubus_request *req, int type, struct blob_attr *msg)
  235. {
  236. struct blob_attr *cur, *curi, *tl[__LIST_MAX], *ti[__INSTANCE_MAX], *tj[__JAIL_MAX];
  237. int rem, remi;
  238. const char *container_name, *instance_name, *jail_name;
  239. bool running;
  240. int pid, exitcode;
  241. struct runtime_state *rs;
  242. blobmsg_for_each_attr(cur, msg, rem) {
  243. container_name = blobmsg_name(cur);
  244. blobmsg_parse(list_policy, __LIST_MAX, tl, blobmsg_data(cur), blobmsg_len(cur));
  245. if (!tl[LIST_INSTANCES])
  246. continue;
  247. blobmsg_for_each_attr(curi, tl[LIST_INSTANCES], remi) {
  248. instance_name = blobmsg_name(curi);
  249. blobmsg_parse(instance_policy, __INSTANCE_MAX, ti, blobmsg_data(curi), blobmsg_len(curi));
  250. if (!ti[INSTANCE_JAIL])
  251. continue;
  252. blobmsg_parse(jail_policy, __JAIL_MAX, tj, blobmsg_data(ti[INSTANCE_JAIL]), blobmsg_len(ti[INSTANCE_JAIL]));
  253. if (!tj[JAIL_NAME])
  254. continue;
  255. jail_name = blobmsg_get_string(tj[JAIL_NAME]);
  256. running = ti[INSTANCE_RUNNING] && blobmsg_get_bool(ti[INSTANCE_RUNNING]);
  257. if (ti[INSTANCE_PID])
  258. pid = blobmsg_get_u32(ti[INSTANCE_PID]);
  259. else
  260. pid = -1;
  261. if (ti[INSTANCE_EXITCODE])
  262. exitcode = blobmsg_get_u32(ti[INSTANCE_EXITCODE]);
  263. else
  264. exitcode = -1;
  265. rs = runtime_alloc(container_name);
  266. rs->instance_name = strdup(instance_name);
  267. rs->jail_name = strdup(jail_name);
  268. rs->runtime_pid = pid;
  269. rs->exitcode = exitcode;
  270. rs->running = running;
  271. avl_insert(&runtime, &rs->avl);
  272. }
  273. }
  274. return;
  275. }
  276. static int runtime_load(void)
  277. {
  278. struct runtime_state *item, *tmp;
  279. uint32_t id;
  280. avl_init(&runtime, avl_strcmp, false, NULL);
  281. if (ubus_lookup_id(ctx, "container", &id) ||
  282. ubus_invoke(ctx, id, "list", NULL, list_cb, &runtime, 3000))
  283. return EIO;
  284. avl_for_each_element_safe(&runtime, item, avl, tmp)
  285. get_ocistate(&item->ocistate, item->jail_name);
  286. return 0;
  287. }
  288. static void runtime_free(void)
  289. {
  290. struct runtime_state *item, *tmp;
  291. avl_for_each_element_safe(&runtime, item, avl, tmp) {
  292. avl_delete(&runtime, &item->avl);
  293. free(item->instance_name);
  294. free(item->jail_name);
  295. free(item->ocistate);
  296. free(item);
  297. }
  298. return;
  299. }
  300. static int uxc_state(char *name)
  301. {
  302. struct runtime_state *s = avl_find_element(&runtime, name, s, avl);
  303. struct blob_attr *ocistate = NULL;
  304. struct blob_attr *cur, *tb[__CONF_MAX];
  305. int rem;
  306. char *bundle = NULL;
  307. char *jail_name = NULL;
  308. char *state = NULL;
  309. char *tmp;
  310. static struct blob_buf buf;
  311. if (s)
  312. ocistate = s->ocistate;
  313. if (ocistate) {
  314. state = blobmsg_format_json_indent(ocistate, true, 0);
  315. if (!state)
  316. return 1;
  317. printf("%s\n", state);
  318. free(state);
  319. return 0;
  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. if (!strcmp(name, blobmsg_get_string(tb[CONF_NAME]))) {
  326. if (tb[CONF_JAIL])
  327. jail_name = blobmsg_get_string(tb[CONF_JAIL]);
  328. else
  329. jail_name = name;
  330. bundle = blobmsg_get_string(tb[CONF_PATH]);
  331. break;
  332. }
  333. }
  334. if (!bundle)
  335. return ENOENT;
  336. blob_buf_init(&buf, 0);
  337. blobmsg_add_string(&buf, "ociVersion", OCI_VERSION_STRING);
  338. blobmsg_add_string(&buf, "id", jail_name);
  339. blobmsg_add_string(&buf, "status", s?"stopped":"uninitialized");
  340. blobmsg_add_string(&buf, "bundle", bundle);
  341. tmp = blobmsg_format_json_indent(buf.head, true, 0);
  342. if (!tmp) {
  343. blob_buf_free(&buf);
  344. return ENOMEM;
  345. }
  346. printf("%s\n", tmp);
  347. free(tmp);
  348. blob_buf_free(&buf);
  349. return 0;
  350. }
  351. static int uxc_list(void)
  352. {
  353. struct blob_attr *cur, *tb[__CONF_MAX], *ts[__STATE_MAX];
  354. int rem;
  355. struct runtime_state *s = NULL;
  356. char *name, *ocistatus, *status, *tmp;
  357. int container_pid = -1;
  358. bool autostart;
  359. static struct blob_buf buf;
  360. void *arr, *obj;
  361. if (json_output) {
  362. blob_buf_init(&buf, 0);
  363. arr = blobmsg_open_array(&buf, "");
  364. }
  365. blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
  366. blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
  367. if (!tb[CONF_NAME] || !tb[CONF_PATH])
  368. continue;
  369. autostart = tb[CONF_AUTOSTART] && blobmsg_get_bool(tb[CONF_AUTOSTART]);
  370. ocistatus = NULL;
  371. container_pid = 0;
  372. name = blobmsg_get_string(tb[CONF_NAME]);
  373. s = avl_find_element(&runtime, name, s, avl);
  374. if (s && s->ocistate) {
  375. blobmsg_parse(state_policy, __STATE_MAX, ts, blobmsg_data(s->ocistate), blobmsg_len(s->ocistate));
  376. ocistatus = blobmsg_get_string(ts[STATE_STATUS]);
  377. container_pid = blobmsg_get_u32(ts[STATE_PID]);
  378. }
  379. status = ocistatus?:(s && s->running)?"creating":"stopped";
  380. if (json_output) {
  381. obj = blobmsg_open_table(&buf, "");
  382. blobmsg_add_string(&buf, "name", name);
  383. blobmsg_add_string(&buf, "status", status);
  384. blobmsg_add_u8(&buf, "autostart", autostart);
  385. } else {
  386. printf("[%c] %s %s", autostart?'*':' ', name, status);
  387. }
  388. if (s && !s->running && (s->exitcode >= 0)) {
  389. if (json_output)
  390. blobmsg_add_u32(&buf, "exitcode", s->exitcode);
  391. else
  392. printf(" exitcode: %d (%s)", s->exitcode, strerror(s->exitcode));
  393. }
  394. if (s && s->running && (s->runtime_pid >= 0)) {
  395. if (json_output)
  396. blobmsg_add_u32(&buf, "runtime_pid", s->runtime_pid);
  397. else
  398. printf(" runtime pid: %d", s->runtime_pid);
  399. }
  400. if (s && s->running && (container_pid >= 0)) {
  401. if (json_output)
  402. blobmsg_add_u32(&buf, "container_pid", container_pid);
  403. else
  404. printf(" container pid: %d", container_pid);
  405. }
  406. if (!json_output)
  407. printf("\n");
  408. else
  409. blobmsg_close_table(&buf, obj);
  410. }
  411. if (json_output) {
  412. blobmsg_close_array(&buf, arr);
  413. tmp = blobmsg_format_json_indent(buf.head, true, 0);
  414. if (!tmp) {
  415. blob_buf_free(&buf);
  416. return ENOMEM;
  417. }
  418. printf("%s\n", tmp);
  419. free(tmp);
  420. blob_buf_free(&buf);
  421. };
  422. return 0;
  423. }
  424. static int uxc_create(char *name, bool immediately)
  425. {
  426. static struct blob_buf req;
  427. struct blob_attr *cur, *tb[__CONF_MAX];
  428. int rem, ret;
  429. uint32_t id;
  430. struct runtime_state *s = NULL;
  431. char *path = NULL, *jailname = NULL, *pidfile = NULL, *tmprwsize = NULL, *writepath = NULL;
  432. void *in, *ins, *j;
  433. bool found = false;
  434. s = avl_find_element(&runtime, name, s, avl);
  435. if (s && (s->running))
  436. return EEXIST;
  437. blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
  438. blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
  439. if (!tb[CONF_NAME] || !tb[CONF_PATH])
  440. continue;
  441. if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
  442. continue;
  443. found = true;
  444. }
  445. if (!found)
  446. return ENOENT;
  447. path = blobmsg_get_string(tb[CONF_PATH]);
  448. if (tb[CONF_PIDFILE])
  449. pidfile = blobmsg_get_string(tb[CONF_PIDFILE]);
  450. if (tb[CONF_TEMP_OVERLAY_SIZE])
  451. tmprwsize = blobmsg_get_string(tb[CONF_TEMP_OVERLAY_SIZE]);
  452. if (tb[CONF_WRITE_OVERLAY_PATH])
  453. writepath = blobmsg_get_string(tb[CONF_WRITE_OVERLAY_PATH]);
  454. if (tb[CONF_JAIL])
  455. jailname = blobmsg_get_string(tb[CONF_JAIL]);
  456. blob_buf_init(&req, 0);
  457. blobmsg_add_string(&req, "name", name);
  458. ins = blobmsg_open_table(&req, "instances");
  459. in = blobmsg_open_table(&req, name);
  460. blobmsg_add_string(&req, "bundle", path);
  461. j = blobmsg_open_table(&req, "jail");
  462. blobmsg_add_string(&req, "name", jailname?:name);
  463. blobmsg_add_u8(&req, "immediately", immediately);
  464. if (pidfile)
  465. blobmsg_add_string(&req, "pidfile", pidfile);
  466. blobmsg_close_table(&req, j);
  467. if (writepath)
  468. blobmsg_add_string(&req, "overlaydir", writepath);
  469. if (tmprwsize)
  470. blobmsg_add_string(&req, "tmpoverlaysize", tmprwsize);
  471. blobmsg_close_table(&req, in);
  472. blobmsg_close_table(&req, ins);
  473. if (verbose) {
  474. char *tmp;
  475. tmp = blobmsg_format_json_indent(req.head, true, 1);
  476. if (!tmp)
  477. return ENOMEM;
  478. fprintf(stderr, "adding container to procd:\n\t%s\n", tmp);
  479. free(tmp);
  480. }
  481. ret = 0;
  482. if (ubus_lookup_id(ctx, "container", &id) ||
  483. ubus_invoke(ctx, id, "add", req.head, NULL, NULL, 3000)) {
  484. blob_buf_free(&req);
  485. ret = EIO;
  486. }
  487. return ret;
  488. }
  489. static int uxc_start(const char *name)
  490. {
  491. char *objname;
  492. unsigned int id;
  493. if (asprintf(&objname, "container.%s", name) == -1)
  494. return ENOMEM;
  495. if (ubus_lookup_id(ctx, objname, &id))
  496. return ENOENT;
  497. return ubus_invoke(ctx, id, "start", NULL, NULL, NULL, 3000);
  498. }
  499. static int uxc_kill(char *name, int signal)
  500. {
  501. static struct blob_buf req;
  502. struct blob_attr *cur, *tb[__CONF_MAX];
  503. int rem, ret;
  504. char *objname;
  505. unsigned int id;
  506. struct runtime_state *s = NULL;
  507. bool found = false;
  508. blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
  509. blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
  510. if (!tb[CONF_NAME] || !tb[CONF_PATH])
  511. continue;
  512. if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
  513. continue;
  514. found = true;
  515. break;
  516. }
  517. if (!found)
  518. return ENOENT;
  519. s = avl_find_element(&runtime, name, s, avl);
  520. if (!s || !(s->running))
  521. return ENOENT;
  522. blob_buf_init(&req, 0);
  523. blobmsg_add_u32(&req, "signal", signal);
  524. blobmsg_add_string(&req, "name", name);
  525. if (asprintf(&objname, "container.%s", name) == -1)
  526. return ENOMEM;
  527. ret = ubus_lookup_id(ctx, objname, &id);
  528. free(objname);
  529. if (ret)
  530. return ENOENT;
  531. if (ubus_invoke(ctx, id, "kill", req.head, NULL, NULL, 3000))
  532. return EIO;
  533. return 0;
  534. }
  535. static int uxc_set(char *name, char *path, bool autostart, bool add, char *pidfile, char *_tmprwsize, char *_writepath, char *requiredmounts)
  536. {
  537. static struct blob_buf req;
  538. struct blob_attr *cur, *tb[__CONF_MAX];
  539. int rem, ret;
  540. bool found = false;
  541. char *fname = NULL;
  542. char *keeppath = NULL;
  543. char *tmprwsize = _tmprwsize;
  544. char *writepath = _writepath;
  545. char *curvol, *tmp, *mnttok;
  546. void *mntarr;
  547. int f;
  548. struct stat sb;
  549. blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
  550. blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
  551. if (!tb[CONF_NAME] || !tb[CONF_PATH])
  552. continue;
  553. if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
  554. continue;
  555. found = true;
  556. break;
  557. }
  558. if (found && add)
  559. return EEXIST;
  560. if (!found && !add)
  561. return ENOENT;
  562. if (add && !path)
  563. return EINVAL;
  564. if (path) {
  565. if (stat(path, &sb) == -1)
  566. return ENOENT;
  567. if ((sb.st_mode & S_IFMT) != S_IFDIR)
  568. return ENOTDIR;
  569. }
  570. ret = mkdir(confdir, 0755);
  571. if (ret && errno != EEXIST)
  572. return ret;
  573. if (asprintf(&fname, "%s/%s.json", confdir, name) == -1)
  574. return ENOMEM;
  575. f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
  576. if (f < 0)
  577. return errno;
  578. if (!add) {
  579. keeppath = blobmsg_get_string(tb[CONF_PATH]);
  580. if (tb[CONF_WRITE_OVERLAY_PATH])
  581. writepath = blobmsg_get_string(tb[CONF_WRITE_OVERLAY_PATH]);
  582. if (tb[CONF_TEMP_OVERLAY_SIZE])
  583. tmprwsize = blobmsg_get_string(tb[CONF_TEMP_OVERLAY_SIZE]);
  584. }
  585. blob_buf_init(&req, 0);
  586. blobmsg_add_string(&req, "name", name);
  587. blobmsg_add_string(&req, "path", path?:keeppath);
  588. blobmsg_add_u8(&req, "autostart", autostart);
  589. if (pidfile)
  590. blobmsg_add_string(&req, "pidfile", pidfile);
  591. if (tmprwsize)
  592. blobmsg_add_string(&req, "temp-overlay-size", tmprwsize);
  593. if (writepath)
  594. blobmsg_add_string(&req, "write-overlay-path", writepath);
  595. if (!add && tb[CONF_VOLUMES])
  596. blobmsg_add_blob(&req, tb[CONF_VOLUMES]);
  597. if (add && requiredmounts) {
  598. mntarr = blobmsg_open_array(&req, "volumes");
  599. for (mnttok = requiredmounts; ; mnttok = NULL) {
  600. curvol = strtok_r(mnttok, ",;", &tmp);
  601. if (!curvol)
  602. break;
  603. blobmsg_add_string(&req, NULL, curvol);
  604. }
  605. blobmsg_close_array(&req, mntarr);
  606. }
  607. tmp = blobmsg_format_json_indent(req.head, true, 0);
  608. if (tmp) {
  609. dprintf(f, "%s\n", tmp);
  610. free(tmp);
  611. }
  612. blob_buf_free(&req);
  613. close(f);
  614. return 0;
  615. }
  616. enum {
  617. BLOCK_INFO_DEVICE,
  618. BLOCK_INFO_UUID,
  619. BLOCK_INFO_TARGET,
  620. BLOCK_INFO_TYPE,
  621. BLOCK_INFO_MOUNT,
  622. __BLOCK_INFO_MAX,
  623. };
  624. static const struct blobmsg_policy block_info_policy[__BLOCK_INFO_MAX] = {
  625. [BLOCK_INFO_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
  626. [BLOCK_INFO_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING },
  627. [BLOCK_INFO_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
  628. [BLOCK_INFO_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
  629. [BLOCK_INFO_MOUNT] = { .name = "mount", .type = BLOBMSG_TYPE_STRING },
  630. };
  631. /* check if device 'devname' is mounted according to blockd */
  632. static int checkblock(const char *uuid)
  633. {
  634. struct blob_attr *tb[__BLOCK_INFO_MAX];
  635. struct blob_attr *cur;
  636. int rem;
  637. blobmsg_for_each_attr(cur, blockinfo, rem) {
  638. blobmsg_parse(block_info_policy, __BLOCK_INFO_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
  639. if (!tb[BLOCK_INFO_UUID] || !tb[BLOCK_INFO_MOUNT])
  640. continue;
  641. if (!strcmp(uuid, blobmsg_get_string(tb[BLOCK_INFO_UUID])))
  642. return 0;
  643. }
  644. return 1;
  645. }
  646. enum {
  647. UCI_FSTAB_UUID,
  648. UCI_FSTAB_ANONYMOUS,
  649. __UCI_FSTAB_MAX,
  650. };
  651. static const struct blobmsg_policy uci_fstab_policy[__UCI_FSTAB_MAX] = {
  652. [UCI_FSTAB_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING },
  653. [UCI_FSTAB_ANONYMOUS] = { .name = ".anonymous", .type = BLOBMSG_TYPE_BOOL },
  654. };
  655. static const char *resolveuuid(const char *volname)
  656. {
  657. struct blob_attr *tb[__UCI_FSTAB_MAX];
  658. struct blob_attr *cur;
  659. const char *mntname;
  660. char *tmpvolname, *replc;
  661. int rem, res;
  662. blobmsg_for_each_attr(cur, fstabinfo, rem) {
  663. blobmsg_parse(uci_fstab_policy, __UCI_FSTAB_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
  664. if (!tb[UCI_FSTAB_UUID])
  665. continue;
  666. if (tb[UCI_FSTAB_ANONYMOUS] && blobmsg_get_bool(tb[UCI_FSTAB_ANONYMOUS]))
  667. continue;
  668. mntname = blobmsg_name(cur);
  669. if (!mntname)
  670. continue;
  671. tmpvolname = strdup(volname);
  672. while ((replc = strchr(tmpvolname, '-')))
  673. *replc = '_';
  674. res = strcmp(tmpvolname, mntname);
  675. free(tmpvolname);
  676. if (!res)
  677. return blobmsg_get_string(tb[UCI_FSTAB_UUID]);
  678. };
  679. return volname;
  680. };
  681. /* check status of each required volume */
  682. static int checkvolumes(struct blob_attr *volumes)
  683. {
  684. struct blob_attr *cur;
  685. int rem;
  686. blobmsg_for_each_attr(cur, volumes, rem) {
  687. if (checkblock(resolveuuid(blobmsg_get_string(cur))))
  688. return 1;
  689. }
  690. return 0;
  691. }
  692. static void block_cb(struct ubus_request *req, int type, struct blob_attr *msg)
  693. {
  694. blockinfo = blob_memdup(blobmsg_data(msg));
  695. }
  696. static void fstab_cb(struct ubus_request *req, int type, struct blob_attr *msg)
  697. {
  698. fstabinfo = blob_memdup(blobmsg_data(msg));
  699. }
  700. static int uxc_boot(void)
  701. {
  702. struct blob_attr *cur, *tb[__CONF_MAX];
  703. struct runtime_state *s;
  704. static struct blob_buf req;
  705. int rem, ret = 0;
  706. char *name;
  707. unsigned int id;
  708. ret = ubus_lookup_id(ctx, "block", &id);
  709. if (ret)
  710. return ENOENT;
  711. ret = ubus_invoke(ctx, id, "info", NULL, block_cb, NULL, 3000);
  712. if (ret)
  713. return ENXIO;
  714. ret = ubus_lookup_id(ctx, "uci", &id);
  715. if (ret)
  716. return ENOENT;
  717. blob_buf_init(&req, 0);
  718. blobmsg_add_string(&req, "config", "fstab");
  719. blobmsg_add_string(&req, "type", "mount");
  720. ret = ubus_invoke(ctx, id, "get", req.head, fstab_cb, NULL, 3000);
  721. if (ret)
  722. return ENXIO;
  723. blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
  724. blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
  725. if (!tb[CONF_NAME] || !tb[CONF_PATH] || !tb[CONF_AUTOSTART] || !blobmsg_get_bool(tb[CONF_AUTOSTART]))
  726. continue;
  727. s = avl_find_element(&runtime, blobmsg_get_string(tb[CONF_NAME]), s, avl);
  728. if (s)
  729. continue;
  730. /* make sure all volumes are ready before starting */
  731. if (tb[CONF_VOLUMES])
  732. if (checkvolumes(tb[CONF_VOLUMES]))
  733. continue;
  734. name = strdup(blobmsg_get_string(tb[CONF_NAME]));
  735. ret += uxc_create(name, true);
  736. free(name);
  737. }
  738. return ret;
  739. }
  740. static int uxc_delete(char *name, bool force)
  741. {
  742. struct blob_attr *cur, *tb[__CONF_MAX];
  743. struct runtime_state *s = NULL;
  744. static struct blob_buf req;
  745. uint32_t id;
  746. int rem, ret = 0;
  747. bool found = false;
  748. char *fname;
  749. struct stat sb;
  750. blobmsg_for_each_attr(cur, blob_data(conf.head), rem) {
  751. blobmsg_parse(conf_policy, __CONF_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
  752. if (!tb[CONF_NAME] || !tb[CONF_PATH])
  753. continue;
  754. if (strcmp(name, blobmsg_get_string(tb[CONF_NAME])))
  755. continue;
  756. fname = strdup(blobmsg_name(cur));
  757. if (!fname)
  758. return errno;
  759. found = true;
  760. break;
  761. }
  762. if (!found)
  763. return ENOENT;
  764. s = avl_find_element(&runtime, name, s, avl);
  765. if (s && s->running) {
  766. if (force) {
  767. ret = uxc_kill(name, SIGKILL);
  768. if (ret)
  769. goto errout;
  770. } else {
  771. ret = EWOULDBLOCK;
  772. goto errout;
  773. }
  774. }
  775. if (s) {
  776. ret = ubus_lookup_id(ctx, "container", &id);
  777. if (ret)
  778. goto errout;
  779. blob_buf_init(&req, 0);
  780. blobmsg_add_string(&req, "name", s->container_name);
  781. blobmsg_add_string(&req, "instance", s->instance_name);
  782. if (ubus_invoke(ctx, id, "delete", req.head, NULL, NULL, 3000)) {
  783. blob_buf_free(&req);
  784. ret=EIO;
  785. goto errout;
  786. }
  787. }
  788. if (stat(fname, &sb) == -1) {
  789. ret=ENOENT;
  790. goto errout;
  791. }
  792. if (unlink(fname) == -1)
  793. ret=errno;
  794. errout:
  795. free(fname);
  796. return ret;
  797. }
  798. static void reload_conf(void)
  799. {
  800. blob_buf_free(&conf);
  801. conf_load();
  802. }
  803. int main(int argc, char **argv)
  804. {
  805. enum uxc_cmd cmd = CMD_UNKNOWN;
  806. int ret = EINVAL;
  807. char *bundle = NULL;
  808. char *pidfile = NULL;
  809. char *tmprwsize = NULL;
  810. char *writepath = NULL;
  811. char *requiredmounts = NULL;
  812. bool autostart = false;
  813. bool force = false;
  814. int signal = SIGTERM;
  815. int c;
  816. if (argc < 2)
  817. return usage();
  818. ctx = ubus_connect(NULL);
  819. if (!ctx)
  820. return ENODEV;
  821. ret = conf_load();
  822. if (ret)
  823. goto out;
  824. ret = runtime_load();
  825. if (ret)
  826. goto conf_out;
  827. while (true) {
  828. int option_index = 0;
  829. c = getopt_long(argc, argv, OPT_ARGS, long_options, &option_index);
  830. if (c == -1)
  831. break;
  832. switch (c) {
  833. case 'a':
  834. autostart = true;
  835. break;
  836. case 'b':
  837. bundle = optarg;
  838. break;
  839. case 'f':
  840. force = true;
  841. break;
  842. case 'j':
  843. json_output = true;
  844. break;
  845. case 'p':
  846. pidfile = optarg;
  847. break;
  848. case 't':
  849. tmprwsize = optarg;
  850. break;
  851. case 'v':
  852. verbose = true;
  853. break;
  854. case 'V':
  855. printf("uxc %s\n", UXC_VERSION);
  856. exit(0);
  857. case 'w':
  858. writepath = optarg;
  859. break;
  860. case 'm':
  861. requiredmounts = optarg;
  862. break;
  863. }
  864. }
  865. if (optind == argc)
  866. goto usage_out;
  867. if (!strcmp("list", argv[optind]))
  868. cmd = CMD_LIST;
  869. else if (!strcmp("boot", argv[optind]))
  870. cmd = CMD_BOOT;
  871. else if(!strcmp("start", argv[optind]))
  872. cmd = CMD_START;
  873. else if(!strcmp("state", argv[optind]))
  874. cmd = CMD_STATE;
  875. else if(!strcmp("kill", argv[optind]))
  876. cmd = CMD_KILL;
  877. else if(!strcmp("enable", argv[optind]))
  878. cmd = CMD_ENABLE;
  879. else if(!strcmp("disable", argv[optind]))
  880. cmd = CMD_DISABLE;
  881. else if(!strcmp("delete", argv[optind]))
  882. cmd = CMD_DELETE;
  883. else if(!strcmp("create", argv[optind]))
  884. cmd = CMD_CREATE;
  885. switch (cmd) {
  886. case CMD_LIST:
  887. ret = uxc_list();
  888. break;
  889. case CMD_BOOT:
  890. ret = uxc_boot();
  891. break;
  892. case CMD_START:
  893. if (optind != argc - 2)
  894. goto usage_out;
  895. ret = uxc_start(argv[optind + 1]);
  896. break;
  897. case CMD_STATE:
  898. if (optind != argc - 2)
  899. goto usage_out;
  900. ret = uxc_state(argv[optind + 1]);
  901. break;
  902. case CMD_KILL:
  903. if (optind == (argc - 3))
  904. signal = atoi(argv[optind + 2]);
  905. else if (optind > argc - 2)
  906. goto usage_out;
  907. ret = uxc_kill(argv[optind + 1], signal);
  908. break;
  909. case CMD_ENABLE:
  910. if (optind != argc - 2)
  911. goto usage_out;
  912. ret = uxc_set(argv[optind + 1], NULL, true, false, NULL, NULL, NULL, NULL);
  913. break;
  914. case CMD_DISABLE:
  915. if (optind != argc - 2)
  916. goto usage_out;
  917. ret = uxc_set(argv[optind + 1], NULL, false, false, NULL, NULL, NULL, NULL);
  918. break;
  919. case CMD_DELETE:
  920. if (optind != argc - 2)
  921. goto usage_out;
  922. ret = uxc_delete(argv[optind + 1], force);
  923. break;
  924. case CMD_CREATE:
  925. if (optind != argc - 2)
  926. goto usage_out;
  927. if (bundle) {
  928. ret = uxc_set(argv[optind + 1], bundle, autostart, true, pidfile, tmprwsize, writepath, requiredmounts);
  929. if (ret)
  930. goto runtime_out;
  931. reload_conf();
  932. }
  933. ret = uxc_create(argv[optind + 1], false);
  934. break;
  935. default:
  936. goto usage_out;
  937. }
  938. goto runtime_out;
  939. usage_out:
  940. usage();
  941. runtime_out:
  942. runtime_free();
  943. conf_out:
  944. blob_buf_free(&conf);
  945. out:
  946. ubus_free(ctx);
  947. if (ret != 0)
  948. fprintf(stderr, "uxc error: %s\n", strerror(ret));
  949. return ret;
  950. }