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.
29 #include <sys/types.h>
32 #include <libubox/avl-cmp.h>
33 #include <libubox/blobmsg.h>
34 #include <libubox/blobmsg_json.h>
35 #include <libubox/ustream.h>
39 #define UXC_VERSION "0.3"
40 #define OCI_VERSION_STRING "1.0.2"
41 #define UXC_ETC_CONFDIR "/etc/uxc"
42 #define UXC_VOL_CONFDIR "/tmp/run/uvol/.meta/uxc"
44 static bool verbose
= false;
45 static bool json_output
= false;
46 static char *confdir
= UXC_ETC_CONFDIR
;
47 static struct ustream_fd cufd
;
48 static struct ustream_fd lufd
;
51 struct runtime_state
{
59 struct blob_attr
*ocistate
;
68 signed char autostart
;
69 struct blob_attr
*volumes
;
86 #define OPT_ARGS "ab:fjm:p:t:vVw:"
87 static struct option long_options
[] = {
88 {"autostart", no_argument
, 0, 'a' },
89 {"console", no_argument
, 0, 'c' },
90 {"bundle", required_argument
, 0, 'b' },
91 {"force", no_argument
, 0, 'f' },
92 {"json", no_argument
, 0, 'j' },
93 {"mounts", required_argument
, 0, 'm' },
94 {"pid-file", required_argument
, 0, 'p' },
95 {"temp-overlay-size", required_argument
, 0, 't' },
96 {"write-overlay-path", required_argument
, 0, 'w' },
97 {"verbose", no_argument
, 0, 'v' },
98 {"version", no_argument
, 0, 'V' },
102 AVL_TREE(runtime
, avl_strcmp
, false, NULL
);
103 AVL_TREE(settings
, avl_strcmp
, false, NULL
);
104 static struct blob_buf conf
;
105 static struct blob_buf settingsbuf
;
106 static struct blob_attr
*blockinfo
;
107 static struct blob_attr
*fstabinfo
;
108 static struct ubus_context
*ctx
;
110 static int usage(void) {
111 printf("syntax: uxc <command> [parameters ...]\n");
112 printf("commands:\n");
113 printf("\tlist [--json]\t\t\t\tlist all configured containers\n");
114 printf("\tattach <conf>\t\t\t\tattach to container console\n");
115 printf("\tcreate <conf>\t\t\t\t(re-)create <conf>\n");
116 printf("\t\t[--bundle <path>]\t\t\tOCI bundle at <path>\n");
117 printf("\t\t[--autostart]\t\t\t\tstart on boot\n");
118 printf("\t\t[--temp-overlay-size <size>]\t\tuse tmpfs overlay with {size}\n");
119 printf("\t\t[--write-overlay-path <path>]\t\tuse overlay on {path}\n");
120 printf("\t\t[--mounts <v1>,<v2>,...,<vN>]\t\trequire filesystems to be available\n");
121 printf("\tstart [--console] <conf>\t\tstart container <conf>\n");
122 printf("\tstate <conf>\t\t\t\tget state of container <conf>\n");
123 printf("\tkill <conf> [<signal>]\t\t\tsend signal to container <conf>\n");
124 printf("\tenable <conf>\t\t\t\tstart container <conf> on boot\n");
125 printf("\tdisable <conf>\t\t\t\tdon't start container <conf> on boot\n");
126 printf("\tdelete <conf> [--force]\t\t\tdelete <conf>\n");
136 CONF_TEMP_OVERLAY_SIZE
,
137 CONF_WRITE_OVERLAY_PATH
,
142 static const struct blobmsg_policy conf_policy
[__CONF_MAX
] = {
143 [CONF_NAME
] = { .name
= "name", .type
= BLOBMSG_TYPE_STRING
},
144 [CONF_PATH
] = { .name
= "path", .type
= BLOBMSG_TYPE_STRING
},
145 [CONF_JAIL
] = { .name
= "jail", .type
= BLOBMSG_TYPE_STRING
},
146 [CONF_AUTOSTART
] = { .name
= "autostart", .type
= BLOBMSG_TYPE_BOOL
},
147 [CONF_PIDFILE
] = { .name
= "pidfile", .type
= BLOBMSG_TYPE_STRING
},
148 [CONF_TEMP_OVERLAY_SIZE
] = { .name
= "temp-overlay-size", .type
= BLOBMSG_TYPE_STRING
},
149 [CONF_WRITE_OVERLAY_PATH
] = { .name
= "write-overlay-path", .type
= BLOBMSG_TYPE_STRING
},
150 [CONF_VOLUMES
] = { .name
= "volumes", .type
= BLOBMSG_TYPE_ARRAY
},
153 static int conf_load(bool load_settings
)
155 int gl_flags
= GLOB_NOESCAPE
| GLOB_MARK
;
161 struct blob_buf
*target
;
163 if (asprintf(&globstr
, "%s/%s*.json", UXC_ETC_CONFDIR
, load_settings
?"settings/":"") == -1)
166 res
= glob(globstr
, gl_flags
, NULL
, &gl
);
168 gl_flags
|= GLOB_APPEND
;
172 if (!stat(UXC_VOL_CONFDIR
, &sb
)) {
173 if (sb
.st_mode
& S_IFDIR
) {
174 if (asprintf(&globstr
, "%s/%s*.json", UXC_VOL_CONFDIR
, load_settings
?"settings/":"") == -1)
177 res
= glob(globstr
, gl_flags
, NULL
, &gl
);
182 target
= load_settings
? &settingsbuf
: &conf
;
183 blob_buf_init(target
, 0);
184 c
= blobmsg_open_table(target
, NULL
);
189 for (j
= 0; j
< gl
.gl_pathc
; j
++) {
190 o
= blobmsg_open_table(target
, strdup(gl
.gl_pathv
[j
]));
191 if (!blobmsg_add_json_from_file(target
, gl
.gl_pathv
[j
])) {
192 ERROR("uxc: failed to load %s\n", gl
.gl_pathv
[j
]);
195 blobmsg_close_table(target
, o
);
197 blobmsg_close_table(target
, c
);
203 static struct settings
*
204 settings_alloc(const char *container_name
)
208 s
= calloc_a(sizeof(*s
), &new_name
, strlen(container_name
) + 1);
209 strcpy(new_name
, container_name
);
210 s
->container_name
= new_name
;
211 s
->avl
.key
= s
->container_name
;
219 static int settings_add(void)
221 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
225 avl_init(&settings
, avl_strcmp
, false, NULL
);
227 blobmsg_for_each_attr(cur
, blob_data(settingsbuf
.head
), rem
) {
228 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
232 if (tb
[CONF_TEMP_OVERLAY_SIZE
] && tb
[CONF_WRITE_OVERLAY_PATH
])
235 s
= settings_alloc(blobmsg_get_string(tb
[CONF_NAME
]));
237 if (tb
[CONF_AUTOSTART
])
238 s
->autostart
= blobmsg_get_bool(tb
[CONF_AUTOSTART
]);
240 if (tb
[CONF_TEMP_OVERLAY_SIZE
])
241 s
->tmprwsize
= blobmsg_get_string(tb
[CONF_TEMP_OVERLAY_SIZE
]);
243 if (tb
[CONF_WRITE_OVERLAY_PATH
])
244 s
->writepath
= blobmsg_get_string(tb
[CONF_WRITE_OVERLAY_PATH
]);
246 s
->volumes
= tb
[CONF_VOLUMES
];
247 s
->fname
= blobmsg_name(cur
);
249 err
= avl_insert(&settings
, &s
->avl
);
251 fprintf(stderr
, "error adding settings for %s\n", blobmsg_get_string(tb
[CONF_NAME
]));
259 static void settings_free(void)
261 struct settings
*item
, *tmp
;
263 avl_for_each_element_safe(&settings
, item
, avl
, tmp
) {
264 avl_delete(&settings
, &item
->avl
);
276 static const struct blobmsg_policy list_policy
[__LIST_MAX
] = {
277 [LIST_INSTANCES
] = { .name
= "instances", .type
= BLOBMSG_TYPE_TABLE
},
288 static const struct blobmsg_policy instance_policy
[__INSTANCE_MAX
] = {
289 [INSTANCE_RUNNING
] = { .name
= "running", .type
= BLOBMSG_TYPE_BOOL
},
290 [INSTANCE_PID
] = { .name
= "pid", .type
= BLOBMSG_TYPE_INT32
},
291 [INSTANCE_EXITCODE
] = { .name
= "exit_code", .type
= BLOBMSG_TYPE_INT32
},
292 [INSTANCE_JAIL
] = { .name
= "jail", .type
= BLOBMSG_TYPE_TABLE
},
300 static const struct blobmsg_policy jail_policy
[__JAIL_MAX
] = {
301 [JAIL_NAME
] = { .name
= "name", .type
= BLOBMSG_TYPE_STRING
},
304 static struct runtime_state
*
305 runtime_alloc(const char *container_name
)
307 struct runtime_state
*s
;
309 s
= calloc_a(sizeof(*s
), &new_name
, strlen(container_name
) + 1);
310 strcpy(new_name
, container_name
);
311 s
->container_name
= new_name
;
312 s
->avl
.key
= s
->container_name
;
326 static const struct blobmsg_policy state_policy
[__STATE_MAX
] = {
327 [STATE_OCIVERSION
] = { .name
= "ociVersion", .type
= BLOBMSG_TYPE_STRING
},
328 [STATE_ID
] = { .name
= "id", .type
= BLOBMSG_TYPE_STRING
},
329 [STATE_STATUS
] = { .name
= "status", .type
= BLOBMSG_TYPE_STRING
},
330 [STATE_PID
] = { .name
= "pid", .type
= BLOBMSG_TYPE_INT32
},
331 [STATE_BUNDLE
] = { .name
= "bundle", .type
= BLOBMSG_TYPE_STRING
},
332 [STATE_ANNOTATIONS
] = { .name
= "annotations", .type
= BLOBMSG_TYPE_TABLE
},
336 static void ocistate_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
338 struct blob_attr
**ocistate
= (struct blob_attr
**)req
->priv
;
339 struct blob_attr
*tb
[__STATE_MAX
];
341 blobmsg_parse(state_policy
, __STATE_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
343 if (!tb
[STATE_OCIVERSION
] ||
349 *ocistate
= blob_memdup(msg
);
352 static void get_ocistate(struct blob_attr
**ocistate
, const char *name
)
359 if (asprintf(&objname
, "container.%s", name
) == -1)
362 ret
= ubus_lookup_id(ctx
, objname
, &id
);
367 ubus_invoke(ctx
, id
, "state", NULL
, ocistate_cb
, ocistate
, 3000);
370 static void list_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
372 struct blob_attr
*cur
, *curi
, *tl
[__LIST_MAX
], *ti
[__INSTANCE_MAX
], *tj
[__JAIL_MAX
];
374 const char *container_name
, *instance_name
, *jail_name
;
377 struct runtime_state
*rs
;
379 blobmsg_for_each_attr(cur
, msg
, rem
) {
380 container_name
= blobmsg_name(cur
);
381 blobmsg_parse(list_policy
, __LIST_MAX
, tl
, blobmsg_data(cur
), blobmsg_len(cur
));
382 if (!tl
[LIST_INSTANCES
])
385 blobmsg_for_each_attr(curi
, tl
[LIST_INSTANCES
], remi
) {
386 instance_name
= blobmsg_name(curi
);
387 blobmsg_parse(instance_policy
, __INSTANCE_MAX
, ti
, blobmsg_data(curi
), blobmsg_len(curi
));
389 if (!ti
[INSTANCE_JAIL
])
392 blobmsg_parse(jail_policy
, __JAIL_MAX
, tj
, blobmsg_data(ti
[INSTANCE_JAIL
]), blobmsg_len(ti
[INSTANCE_JAIL
]));
396 jail_name
= blobmsg_get_string(tj
[JAIL_NAME
]);
398 running
= ti
[INSTANCE_RUNNING
] && blobmsg_get_bool(ti
[INSTANCE_RUNNING
]);
400 if (ti
[INSTANCE_PID
])
401 pid
= blobmsg_get_u32(ti
[INSTANCE_PID
]);
405 if (ti
[INSTANCE_EXITCODE
])
406 exitcode
= blobmsg_get_u32(ti
[INSTANCE_EXITCODE
]);
410 rs
= runtime_alloc(container_name
);
411 rs
->instance_name
= strdup(instance_name
);
412 rs
->jail_name
= strdup(jail_name
);
413 rs
->runtime_pid
= pid
;
414 rs
->exitcode
= exitcode
;
415 rs
->running
= running
;
416 avl_insert(&runtime
, &rs
->avl
);
423 static int runtime_load(void)
425 struct runtime_state
*item
, *tmp
;
428 avl_init(&runtime
, avl_strcmp
, false, NULL
);
429 if (ubus_lookup_id(ctx
, "container", &id
) ||
430 ubus_invoke(ctx
, id
, "list", NULL
, list_cb
, &runtime
, 3000))
433 avl_for_each_element_safe(&runtime
, item
, avl
, tmp
)
434 get_ocistate(&item
->ocistate
, item
->jail_name
);
439 static void runtime_free(void)
441 struct runtime_state
*item
, *tmp
;
443 avl_for_each_element_safe(&runtime
, item
, avl
, tmp
) {
444 avl_delete(&runtime
, &item
->avl
);
445 free(item
->instance_name
);
446 free(item
->jail_name
);
447 free(item
->ocistate
);
454 static inline int setup_tios(int fd
, struct termios
*oldtios
)
456 struct termios newtios
;
462 /* Get current termios */
463 if (tcgetattr(fd
, oldtios
) < 0)
468 /* We use the same settings that ssh does. */
469 newtios
.c_iflag
|= IGNPAR
;
470 newtios
.c_iflag
&= ~(ISTRIP
| INLCR
| IGNCR
| ICRNL
| IXON
| IXANY
| IXOFF
);
471 newtios
.c_lflag
&= ~(TOSTOP
| ISIG
| ICANON
| ECHO
| ECHOE
| ECHOK
| ECHONL
);
472 newtios
.c_oflag
&= ~ONLCR
;
473 newtios
.c_oflag
|= OPOST
;
474 newtios
.c_cc
[VMIN
] = 1;
475 newtios
.c_cc
[VTIME
] = 0;
477 /* Set new attributes */
478 if (tcsetattr(fd
, TCSAFLUSH
, &newtios
) < 0)
485 static void client_cb(struct ustream
*s
, int bytes
)
491 buf
= ustream_get_read_buf(s
, &len
);
495 rv
= ustream_write(&lufd
.stream
, buf
, len
, false);
498 ustream_consume(s
, rv
);
505 static void local_cb(struct ustream
*s
, int bytes
)
511 buf
= ustream_get_read_buf(s
, &len
);
515 if ((len
> 0) && (buf
[0] == 2))
518 rv
= ustream_write(&cufd
.stream
, buf
, len
, false);
521 ustream_consume(s
, rv
);
528 static int uxc_attach(const char *container_name
)
530 struct ubus_context
*ctx
;
532 static struct blob_buf req
;
533 int client_fd
, server_fd
, tty_fd
;
534 struct termios oldtermios
;
536 ctx
= ubus_connect(NULL
);
538 fprintf(stderr
, "can't connect to ubus!\n");
539 return -ECONNREFUSED
;
542 /* open pseudo-terminal pair */
543 client_fd
= posix_openpt(O_RDWR
| O_NOCTTY
);
545 fprintf(stderr
, "can't create virtual console!\n");
549 setup_tios(client_fd
, &oldtermios
);
552 server_fd
= open(ptsname(client_fd
), O_RDWR
| O_NOCTTY
);
554 fprintf(stderr
, "can't open virtual console!\n");
559 setup_tios(server_fd
, &oldtermios
);
561 tty_fd
= open("/dev/tty", O_RDWR
);
563 fprintf(stderr
, "can't open local console!\n");
569 setup_tios(tty_fd
, &oldtermios
);
571 /* register server-side with procd */
572 blob_buf_init(&req
, 0);
573 blobmsg_add_string(&req
, "name", container_name
);
574 blobmsg_add_string(&req
, "instance", container_name
);
576 if (ubus_lookup_id(ctx
, "container", &id
) ||
577 ubus_invoke_fd(ctx
, id
, "console_attach", req
.head
, NULL
, NULL
, 3000, server_fd
)) {
578 fprintf(stderr
, "ubus request failed\n");
593 /* forward between stdio and client_fd until detach is requested */
594 lufd
.stream
.notify_read
= local_cb
;
595 ustream_fd_init(&lufd
, tty_fd
);
597 cufd
.stream
.notify_read
= client_cb
;
598 /* ToDo: handle remote close and other events */
599 // cufd.stream.notify_state = client_state_cb;
600 ustream_fd_init(&cufd
, client_fd
);
602 fprintf(stderr
, "attaching to jail console. press [CTRL]+[B] to exit.\n");
608 tcsetattr(tty_fd
, TCSAFLUSH
, &oldtermios
);
609 ustream_free(&lufd
.stream
);
610 ustream_free(&cufd
.stream
);
616 static int uxc_state(char *name
)
618 struct runtime_state
*rsstate
= avl_find_element(&runtime
, name
, rsstate
, avl
);
619 struct blob_attr
*ocistate
= NULL
;
620 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
623 char *jail_name
= NULL
;
626 static struct blob_buf buf
;
629 ocistate
= rsstate
->ocistate
;
632 state
= blobmsg_format_json_indent(ocistate
, true, 0);
636 printf("%s\n", state
);
641 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
642 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
643 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
646 if (!strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
]))) {
648 jail_name
= blobmsg_get_string(tb
[CONF_JAIL
]);
652 bundle
= blobmsg_get_string(tb
[CONF_PATH
]);
660 blob_buf_init(&buf
, 0);
661 blobmsg_add_string(&buf
, "ociVersion", OCI_VERSION_STRING
);
662 blobmsg_add_string(&buf
, "id", jail_name
);
663 blobmsg_add_string(&buf
, "status", rsstate
?"stopped":"uninitialized");
664 blobmsg_add_string(&buf
, "bundle", bundle
);
666 tmp
= blobmsg_format_json_indent(buf
.head
, true, 0);
680 static int uxc_list(void)
682 struct blob_attr
*cur
, *tb
[__CONF_MAX
], *ts
[__STATE_MAX
];
684 struct runtime_state
*rsstate
= NULL
;
685 struct settings
*usettings
= NULL
;
686 char *name
, *ocistatus
, *status
, *tmp
;
687 int container_pid
= -1;
689 static struct blob_buf buf
;
693 blob_buf_init(&buf
, 0);
694 arr
= blobmsg_open_array(&buf
, "");
697 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
698 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
699 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
702 autostart
= tb
[CONF_AUTOSTART
] && blobmsg_get_bool(tb
[CONF_AUTOSTART
]);
706 name
= blobmsg_get_string(tb
[CONF_NAME
]);
707 rsstate
= avl_find_element(&runtime
, name
, rsstate
, avl
);
709 if (rsstate
&& rsstate
->ocistate
) {
710 blobmsg_parse(state_policy
, __STATE_MAX
, ts
, blobmsg_data(rsstate
->ocistate
), blobmsg_len(rsstate
->ocistate
));
711 ocistatus
= blobmsg_get_string(ts
[STATE_STATUS
]);
712 container_pid
= blobmsg_get_u32(ts
[STATE_PID
]);
715 status
= ocistatus
?:(rsstate
&& rsstate
->running
)?"creating":"stopped";
717 usettings
= avl_find_element(&settings
, name
, usettings
, avl
);
719 if (usettings
&& (usettings
->autostart
>= 0))
720 autostart
= !!(usettings
->autostart
);
723 obj
= blobmsg_open_table(&buf
, "");
724 blobmsg_add_string(&buf
, "name", name
);
725 blobmsg_add_string(&buf
, "status", status
);
726 blobmsg_add_u8(&buf
, "autostart", autostart
);
728 printf("[%c] %s %s", autostart
?'*':' ', name
, status
);
731 if (rsstate
&& !rsstate
->running
&& (rsstate
->exitcode
>= 0)) {
733 blobmsg_add_u32(&buf
, "exitcode", rsstate
->exitcode
);
735 printf(" exitcode: %d (%s)", rsstate
->exitcode
, strerror(rsstate
->exitcode
));
738 if (rsstate
&& rsstate
->running
&& (rsstate
->runtime_pid
>= 0)) {
740 blobmsg_add_u32(&buf
, "runtime_pid", rsstate
->runtime_pid
);
742 printf(" runtime pid: %d", rsstate
->runtime_pid
);
745 if (rsstate
&& rsstate
->running
&& (container_pid
>= 0)) {
747 blobmsg_add_u32(&buf
, "container_pid", container_pid
);
749 printf(" container pid: %d", container_pid
);
755 blobmsg_close_table(&buf
, obj
);
759 blobmsg_close_array(&buf
, arr
);
760 tmp
= blobmsg_format_json_indent(buf
.head
, true, 0);
773 static int uxc_exists(char *name
)
775 struct runtime_state
*rsstate
= NULL
;
776 rsstate
= avl_find_element(&runtime
, name
, rsstate
, avl
);
778 if (rsstate
&& (rsstate
->running
))
784 static int uxc_create(char *name
, bool immediately
)
786 static struct blob_buf req
;
787 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
790 struct settings
*usettings
= NULL
;
791 char *path
= NULL
, *jailname
= NULL
, *pidfile
= NULL
, *tmprwsize
= NULL
, *writepath
= NULL
;
796 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
797 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
798 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
801 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
811 path
= blobmsg_get_string(tb
[CONF_PATH
]);
813 if (tb
[CONF_PIDFILE
])
814 pidfile
= blobmsg_get_string(tb
[CONF_PIDFILE
]);
816 if (tb
[CONF_TEMP_OVERLAY_SIZE
])
817 tmprwsize
= blobmsg_get_string(tb
[CONF_TEMP_OVERLAY_SIZE
]);
819 if (tb
[CONF_WRITE_OVERLAY_PATH
])
820 writepath
= blobmsg_get_string(tb
[CONF_WRITE_OVERLAY_PATH
]);
823 jailname
= blobmsg_get_string(tb
[CONF_JAIL
]);
825 usettings
= avl_find_element(&settings
, blobmsg_get_string(tb
[CONF_NAME
]), usettings
, avl
);
827 if (usettings
->writepath
) {
828 writepath
= usettings
->writepath
;
831 if (usettings
->tmprwsize
) {
832 tmprwsize
= usettings
->tmprwsize
;
837 blob_buf_init(&req
, 0);
838 blobmsg_add_string(&req
, "name", name
);
839 ins
= blobmsg_open_table(&req
, "instances");
840 in
= blobmsg_open_table(&req
, name
);
841 blobmsg_add_string(&req
, "bundle", path
);
842 j
= blobmsg_open_table(&req
, "jail");
843 blobmsg_add_string(&req
, "name", jailname
?:name
);
844 blobmsg_add_u8(&req
, "immediately", immediately
);
847 blobmsg_add_string(&req
, "pidfile", pidfile
);
849 blobmsg_close_table(&req
, j
);
852 blobmsg_add_string(&req
, "overlaydir", writepath
);
855 blobmsg_add_string(&req
, "tmpoverlaysize", tmprwsize
);
857 blobmsg_close_table(&req
, in
);
858 blobmsg_close_table(&req
, ins
);
862 tmp
= blobmsg_format_json_indent(req
.head
, true, 1);
866 fprintf(stderr
, "adding container to procd:\n\t%s\n", tmp
);
870 if (ubus_lookup_id(ctx
, "container", &id
) ||
871 ubus_invoke(ctx
, id
, "add", req
.head
, NULL
, NULL
, 3000)) {
879 static int uxc_start(const char *name
, bool console
)
888 exit(uxc_attach(name
));
891 if (asprintf(&objname
, "container.%s", name
) == -1)
894 if (ubus_lookup_id(ctx
, objname
, &id
))
898 return ubus_invoke(ctx
, id
, "start", NULL
, NULL
, NULL
, 3000);
901 static int uxc_kill(char *name
, int signal
)
903 static struct blob_buf req
;
904 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
908 struct runtime_state
*rsstate
= NULL
;
911 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
912 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
913 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
916 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
926 rsstate
= avl_find_element(&runtime
, name
, rsstate
, avl
);
928 if (!rsstate
|| !(rsstate
->running
))
931 blob_buf_init(&req
, 0);
932 blobmsg_add_u32(&req
, "signal", signal
);
933 blobmsg_add_string(&req
, "name", name
);
935 if (asprintf(&objname
, "container.%s", name
) == -1)
938 ret
= ubus_lookup_id(ctx
, objname
, &id
);
943 if (ubus_invoke(ctx
, id
, "kill", req
.head
, NULL
, NULL
, 3000))
950 static int uxc_set(char *name
, char *path
, signed char autostart
, char *pidfile
, char *tmprwsize
, char *writepath
, char *requiredmounts
)
952 static struct blob_buf req
;
953 struct settings
*usettings
= NULL
;
954 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
956 const char *cfname
= NULL
;
957 const char *sfname
= NULL
;
959 char *curvol
, *tmp
, *mnttok
;
965 if (!path
&& (autostart
<0) && !pidfile
&& !tmprwsize
&& !writepath
&& !requiredmounts
)
968 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
969 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
970 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
973 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
976 cfname
= blobmsg_name(cur
);
983 if (!cfname
&& !path
)
987 if (stat(path
, &sb
) == -1)
990 if ((sb
.st_mode
& S_IFMT
) != S_IFDIR
)
994 usettings
= avl_find_element(&settings
, blobmsg_get_string(tb
[CONF_NAME
]), usettings
, avl
);
995 if (path
&& usettings
)
999 sfname
= usettings
->fname
;
1000 if (!tmprwsize
&& !writepath
) {
1001 if (usettings
->tmprwsize
) {
1002 tmprwsize
= usettings
->tmprwsize
;
1005 if (usettings
->writepath
) {
1006 writepath
= usettings
->writepath
;
1010 if (usettings
->autostart
>= 0 && autostart
< 0)
1011 autostart
= !!(usettings
->autostart
);
1015 ret
= mkdir(confdir
, 0755);
1017 if (ret
&& errno
!= EEXIST
)
1020 if (asprintf(&fname
, "%s/%s.json", confdir
, name
) == -1)
1023 f
= open(fname
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0644);
1030 f
= open(sfname
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0644);
1033 t1
= strdup(cfname
);
1034 t2
= strrchr(t1
, '/');
1037 if (asprintf(&t2
, "%s/settings", t1
) == -1)
1040 ret
= mkdir(t2
, 0755);
1041 if (ret
&& ret
!= EEXIST
)
1045 if (asprintf(&t2
, "%s/settings/%s.json", t1
, name
) == -1)
1049 f
= open(t2
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0644);
1056 blob_buf_init(&req
, 0);
1057 blobmsg_add_string(&req
, "name", name
);
1059 blobmsg_add_string(&req
, "path", path
);
1062 blobmsg_add_u8(&req
, "autostart", !!autostart
);
1065 blobmsg_add_string(&req
, "pidfile", pidfile
);
1068 blobmsg_add_string(&req
, "temp-overlay-size", tmprwsize
);
1071 blobmsg_add_string(&req
, "write-overlay-path", writepath
);
1073 if (!requiredmounts
&& usettings
&& usettings
->volumes
)
1074 blobmsg_add_blob(&req
, usettings
->volumes
);
1076 if (requiredmounts
) {
1077 mntarr
= blobmsg_open_array(&req
, "volumes");
1078 for (mnttok
= requiredmounts
; ; mnttok
= NULL
) {
1079 curvol
= strtok_r(mnttok
, ",;", &tmp
);
1083 blobmsg_add_string(&req
, NULL
, curvol
);
1085 blobmsg_close_array(&req
, mntarr
);
1088 tmp
= blobmsg_format_json_indent(req
.head
, true, 0);
1090 dprintf(f
, "%s\n", tmp
);
1094 blob_buf_free(&req
);
1109 static const struct blobmsg_policy block_info_policy
[__BLOCK_INFO_MAX
] = {
1110 [BLOCK_INFO_DEVICE
] = { .name
= "device", .type
= BLOBMSG_TYPE_STRING
},
1111 [BLOCK_INFO_UUID
] = { .name
= "uuid", .type
= BLOBMSG_TYPE_STRING
},
1112 [BLOCK_INFO_TARGET
] = { .name
= "target", .type
= BLOBMSG_TYPE_STRING
},
1113 [BLOCK_INFO_TYPE
] = { .name
= "type", .type
= BLOBMSG_TYPE_STRING
},
1114 [BLOCK_INFO_MOUNT
] = { .name
= "mount", .type
= BLOBMSG_TYPE_STRING
},
1118 /* check if device 'devname' is mounted according to blockd */
1119 static bool checkblock(const char *uuid
)
1121 struct blob_attr
*tb
[__BLOCK_INFO_MAX
];
1122 struct blob_attr
*cur
;
1125 blobmsg_for_each_attr(cur
, blockinfo
, rem
) {
1126 blobmsg_parse(block_info_policy
, __BLOCK_INFO_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
1128 if (!tb
[BLOCK_INFO_UUID
] || !tb
[BLOCK_INFO_MOUNT
])
1131 if (!strcmp(uuid
, blobmsg_get_string(tb
[BLOCK_INFO_UUID
])))
1140 UCI_FSTAB_ANONYMOUS
,
1144 static const struct blobmsg_policy uci_fstab_policy
[__UCI_FSTAB_MAX
] = {
1145 [UCI_FSTAB_UUID
] = { .name
= "uuid", .type
= BLOBMSG_TYPE_STRING
},
1146 [UCI_FSTAB_ANONYMOUS
] = { .name
= ".anonymous", .type
= BLOBMSG_TYPE_BOOL
},
1149 static const char *resolveuuid(const char *volname
)
1151 struct blob_attr
*tb
[__UCI_FSTAB_MAX
];
1152 struct blob_attr
*cur
;
1153 const char *mntname
;
1154 char *tmpvolname
, *replc
;
1157 blobmsg_for_each_attr(cur
, fstabinfo
, rem
) {
1158 blobmsg_parse(uci_fstab_policy
, __UCI_FSTAB_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
1160 if (!tb
[UCI_FSTAB_UUID
])
1163 if (tb
[UCI_FSTAB_ANONYMOUS
] && blobmsg_get_bool(tb
[UCI_FSTAB_ANONYMOUS
]))
1166 mntname
= blobmsg_name(cur
);
1170 tmpvolname
= strdup(volname
);
1171 while ((replc
= strchr(tmpvolname
, '-')))
1174 res
= strcmp(tmpvolname
, mntname
);
1178 return blobmsg_get_string(tb
[UCI_FSTAB_UUID
]);
1184 /* check status of each required volume */
1185 static bool checkvolumes(struct blob_attr
*volumes
)
1187 struct blob_attr
*cur
;
1190 blobmsg_for_each_attr(cur
, volumes
, rem
) {
1191 if (checkblock(resolveuuid(blobmsg_get_string(cur
))))
1198 static void block_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
1200 blockinfo
= blob_memdup(blobmsg_data(msg
));
1203 static void fstab_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
1205 fstabinfo
= blob_memdup(blobmsg_data(msg
));
1208 static int uxc_boot(void)
1210 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
1211 struct runtime_state
*rsstate
= NULL
;
1212 struct settings
*usettings
= NULL
;
1213 static struct blob_buf req
;
1219 ret
= ubus_lookup_id(ctx
, "block", &id
);
1223 ret
= ubus_invoke(ctx
, id
, "info", NULL
, block_cb
, NULL
, 3000);
1227 ret
= ubus_lookup_id(ctx
, "uci", &id
);
1231 blob_buf_init(&req
, 0);
1232 blobmsg_add_string(&req
, "config", "fstab");
1233 blobmsg_add_string(&req
, "type", "mount");
1235 ret
= ubus_invoke(ctx
, id
, "get", req
.head
, fstab_cb
, NULL
, 3000);
1239 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
1240 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
1241 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
1244 rsstate
= avl_find_element(&runtime
, blobmsg_get_string(tb
[CONF_NAME
]), rsstate
, avl
);
1248 if (tb
[CONF_AUTOSTART
])
1249 autostart
= blobmsg_get_bool(tb
[CONF_AUTOSTART
]);
1251 usettings
= avl_find_element(&settings
, blobmsg_get_string(tb
[CONF_NAME
]), usettings
, avl
);
1252 if (usettings
&& (usettings
->autostart
>= 0))
1253 autostart
= !!(usettings
->autostart
);
1258 /* make sure all volumes are ready before starting */
1259 if (tb
[CONF_VOLUMES
])
1260 if (checkvolumes(tb
[CONF_VOLUMES
]))
1263 if (usettings
&& usettings
->volumes
)
1264 if (checkvolumes(usettings
->volumes
))
1267 name
= strdup(blobmsg_get_string(tb
[CONF_NAME
]));
1268 if (uxc_exists(name
))
1271 if (uxc_create(name
, true))
1280 static int uxc_delete(char *name
, bool force
)
1282 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
1283 struct runtime_state
*rsstate
= NULL
;
1284 struct settings
*usettings
= NULL
;
1285 static struct blob_buf req
;
1288 const char *cfname
= NULL
;
1289 const char *sfname
= NULL
;
1292 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
1293 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
1294 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
1297 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
1300 cfname
= blobmsg_name(cur
);
1307 rsstate
= avl_find_element(&runtime
, name
, rsstate
, avl
);
1309 if (rsstate
&& rsstate
->running
) {
1311 ret
= uxc_kill(name
, SIGKILL
);
1322 ret
= ubus_lookup_id(ctx
, "container", &id
);
1326 blob_buf_init(&req
, 0);
1327 blobmsg_add_string(&req
, "name", rsstate
->container_name
);
1328 blobmsg_add_string(&req
, "instance", rsstate
->instance_name
);
1330 if (ubus_invoke(ctx
, id
, "delete", req
.head
, NULL
, NULL
, 3000)) {
1331 blob_buf_free(&req
);
1337 usettings
= avl_find_element(&settings
, name
, usettings
, avl
);
1339 sfname
= usettings
->fname
;
1342 if (stat(sfname
, &sb
) == -1) {
1347 if (unlink(sfname
) == -1) {
1353 if (stat(cfname
, &sb
) == -1) {
1358 if (unlink(cfname
) == -1)
1365 static void reload_conf(void)
1367 blob_buf_free(&conf
);
1370 blob_buf_free(&settingsbuf
);
1375 int main(int argc
, char **argv
)
1377 enum uxc_cmd cmd
= CMD_UNKNOWN
;
1379 char *bundle
= NULL
;
1380 char *pidfile
= NULL
;
1381 char *tmprwsize
= NULL
;
1382 char *writepath
= NULL
;
1383 char *requiredmounts
= NULL
;
1384 signed char autostart
= -1;
1386 bool console
= false;
1387 int signal
= SIGTERM
;
1393 ctx
= ubus_connect(NULL
);
1397 ret
= conf_load(false);
1401 ret
= conf_load(true);
1405 ret
= settings_add();
1409 ret
= runtime_load();
1411 goto settings_avl_out
;
1414 int option_index
= 0;
1415 c
= getopt_long(argc
, argv
, OPT_ARGS
, long_options
, &option_index
);
1453 printf("uxc %s\n", UXC_VERSION
);
1461 requiredmounts
= optarg
;
1469 if (!strcmp("list", argv
[optind
]))
1471 else if (!strcmp("attach", argv
[optind
]))
1473 else if (!strcmp("boot", argv
[optind
]))
1475 else if(!strcmp("start", argv
[optind
]))
1477 else if(!strcmp("state", argv
[optind
]))
1479 else if(!strcmp("kill", argv
[optind
]))
1481 else if(!strcmp("enable", argv
[optind
]))
1483 else if(!strcmp("disable", argv
[optind
]))
1485 else if(!strcmp("delete", argv
[optind
]))
1487 else if(!strcmp("create", argv
[optind
]))
1492 if (optind
!= argc
- 2)
1495 ret
= uxc_attach(argv
[optind
+ 1]);
1507 if (optind
!= argc
- 2)
1510 ret
= uxc_start(argv
[optind
+ 1], console
);
1514 if (optind
!= argc
- 2)
1517 ret
= uxc_state(argv
[optind
+ 1]);
1521 if (optind
== (argc
- 3))
1522 signal
= atoi(argv
[optind
+ 2]);
1523 else if (optind
> argc
- 2)
1526 ret
= uxc_kill(argv
[optind
+ 1], signal
);
1530 if (optind
!= argc
- 2)
1533 ret
= uxc_set(argv
[optind
+ 1], NULL
, 1, NULL
, NULL
, NULL
, NULL
);
1537 if (optind
!= argc
- 2)
1540 ret
= uxc_set(argv
[optind
+ 1], NULL
, 0, NULL
, NULL
, NULL
, NULL
);
1544 if (optind
!= argc
- 2)
1547 ret
= uxc_delete(argv
[optind
+ 1], force
);
1551 if (optind
!= argc
- 2)
1554 ret
= uxc_exists(argv
[optind
+ 1]);
1558 ret
= uxc_set(argv
[optind
+ 1], bundle
, autostart
, pidfile
, tmprwsize
, writepath
, requiredmounts
);
1565 ret
= uxc_create(argv
[optind
+ 1], false);
1581 blob_buf_free(&settingsbuf
);
1583 blob_buf_free(&conf
);
1588 fprintf(stderr
, "uxc error: %s\n", strerror(-ret
));