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.2"
40 #define OCI_VERSION_STRING "1.0.2"
41 #define UXC_ETC_CONFDIR "/etc/uxc"
42 #define UXC_VOL_CONFDIR "/var/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
;
76 #define OPT_ARGS "ab:fjm:p:t:vVw:"
77 static struct option long_options
[] = {
78 {"autostart", no_argument
, 0, 'a' },
79 {"console", no_argument
, 0, 'c' },
80 {"bundle", required_argument
, 0, 'b' },
81 {"force", no_argument
, 0, 'f' },
82 {"json", no_argument
, 0, 'j' },
83 {"mounts", required_argument
, 0, 'm' },
84 {"pid-file", required_argument
, 0, 'p' },
85 {"temp-overlay-size", required_argument
, 0, 't' },
86 {"write-overlay-path", required_argument
, 0, 'w' },
87 {"verbose", no_argument
, 0, 'v' },
88 {"version", no_argument
, 0, 'V' },
92 AVL_TREE(runtime
, avl_strcmp
, false, NULL
);
93 static struct blob_buf conf
;
94 static struct blob_attr
*blockinfo
;
95 static struct blob_attr
*fstabinfo
;
96 static struct ubus_context
*ctx
;
98 static int usage(void) {
99 printf("syntax: uxc <command> [parameters ...]\n");
100 printf("commands:\n");
101 printf("\tlist [--json]\t\t\t\tlist all configured containers\n");
102 printf("\tattach <conf>\t\t\t\tattach to container console\n");
103 printf("\tcreate <conf>\t\t\t\t(re-)create <conf>\n");
104 printf("\t\t[--bundle <path>]\t\t\tOCI bundle at <path>\n");
105 printf("\t\t[--autostart]\t\t\t\tstart on boot\n");
106 printf("\t\t[--temp-overlay-size <size>]\t\tuse tmpfs overlay with {size}\n");
107 printf("\t\t[--write-overlay-path <path>]\t\tuse overlay on {path}\n");
108 printf("\t\t[--mounts <v1>,<v2>,...,<vN>]\t\trequire filesystems to be available\n");
109 printf("\tstart [--console] <conf>\t\tstart container <conf>\n");
110 printf("\tstate <conf>\t\t\t\tget state of container <conf>\n");
111 printf("\tkill <conf> [<signal>]\t\t\tsend signal to container <conf>\n");
112 printf("\tenable <conf>\t\t\t\tstart container <conf> on boot\n");
113 printf("\tdisable <conf>\t\t\t\tdon't start container <conf> on boot\n");
114 printf("\tdelete <conf> [--force]\t\t\tdelete <conf>\n");
124 CONF_TEMP_OVERLAY_SIZE
,
125 CONF_WRITE_OVERLAY_PATH
,
130 static const struct blobmsg_policy conf_policy
[__CONF_MAX
] = {
131 [CONF_NAME
] = { .name
= "name", .type
= BLOBMSG_TYPE_STRING
},
132 [CONF_PATH
] = { .name
= "path", .type
= BLOBMSG_TYPE_STRING
},
133 [CONF_JAIL
] = { .name
= "jail", .type
= BLOBMSG_TYPE_STRING
},
134 [CONF_AUTOSTART
] = { .name
= "autostart", .type
= BLOBMSG_TYPE_BOOL
},
135 [CONF_PIDFILE
] = { .name
= "pidfile", .type
= BLOBMSG_TYPE_STRING
},
136 [CONF_TEMP_OVERLAY_SIZE
] = { .name
= "temp-overlay-size", .type
= BLOBMSG_TYPE_STRING
},
137 [CONF_WRITE_OVERLAY_PATH
] = { .name
= "write-overlay-path", .type
= BLOBMSG_TYPE_STRING
},
138 [CONF_VOLUMES
] = { .name
= "volumes", .type
= BLOBMSG_TYPE_ARRAY
},
141 static int conf_load(void)
143 int gl_flags
= GLOB_NOESCAPE
| GLOB_MARK
;
151 if (asprintf(&globstr
, "%s/*.json", UXC_ETC_CONFDIR
) == -1)
154 if (glob(globstr
, gl_flags
, NULL
, &gl
) == 0)
155 gl_flags
|= GLOB_APPEND
;
159 if (!stat(UXC_VOL_CONFDIR
, &sb
)) {
160 if (sb
.st_mode
& S_IFDIR
) {
161 if (asprintf(&globstr
, "%s/*.json", UXC_VOL_CONFDIR
) == -1)
164 res
= glob(globstr
, gl_flags
, NULL
, &gl
);
169 blob_buf_init(&conf
, 0);
170 c
= blobmsg_open_table(&conf
, NULL
);
175 for (j
= 0; j
< gl
.gl_pathc
; j
++) {
176 o
= blobmsg_open_table(&conf
, strdup(gl
.gl_pathv
[j
]));
177 if (!blobmsg_add_json_from_file(&conf
, gl
.gl_pathv
[j
])) {
178 ERROR("uxc: failed to load %s\n", gl
.gl_pathv
[j
]);
181 blobmsg_close_table(&conf
, o
);
183 blobmsg_close_table(&conf
, c
);
194 static const struct blobmsg_policy list_policy
[__LIST_MAX
] = {
195 [LIST_INSTANCES
] = { .name
= "instances", .type
= BLOBMSG_TYPE_TABLE
},
206 static const struct blobmsg_policy instance_policy
[__INSTANCE_MAX
] = {
207 [INSTANCE_RUNNING
] = { .name
= "running", .type
= BLOBMSG_TYPE_BOOL
},
208 [INSTANCE_PID
] = { .name
= "pid", .type
= BLOBMSG_TYPE_INT32
},
209 [INSTANCE_EXITCODE
] = { .name
= "exit_code", .type
= BLOBMSG_TYPE_INT32
},
210 [INSTANCE_JAIL
] = { .name
= "jail", .type
= BLOBMSG_TYPE_TABLE
},
218 static const struct blobmsg_policy jail_policy
[__JAIL_MAX
] = {
219 [JAIL_NAME
] = { .name
= "name", .type
= BLOBMSG_TYPE_STRING
},
222 static struct runtime_state
*
223 runtime_alloc(const char *container_name
)
225 struct runtime_state
*s
;
227 s
= calloc_a(sizeof(*s
), &new_name
, strlen(container_name
) + 1);
228 strcpy(new_name
, container_name
);
229 s
->container_name
= new_name
;
230 s
->avl
.key
= s
->container_name
;
244 static const struct blobmsg_policy state_policy
[__STATE_MAX
] = {
245 [STATE_OCIVERSION
] = { .name
= "ociVersion", .type
= BLOBMSG_TYPE_STRING
},
246 [STATE_ID
] = { .name
= "id", .type
= BLOBMSG_TYPE_STRING
},
247 [STATE_STATUS
] = { .name
= "status", .type
= BLOBMSG_TYPE_STRING
},
248 [STATE_PID
] = { .name
= "pid", .type
= BLOBMSG_TYPE_INT32
},
249 [STATE_BUNDLE
] = { .name
= "bundle", .type
= BLOBMSG_TYPE_STRING
},
250 [STATE_ANNOTATIONS
] = { .name
= "annotations", .type
= BLOBMSG_TYPE_TABLE
},
254 static void ocistate_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
256 struct blob_attr
**ocistate
= (struct blob_attr
**)req
->priv
;
257 struct blob_attr
*tb
[__STATE_MAX
];
259 blobmsg_parse(state_policy
, __STATE_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
261 if (!tb
[STATE_OCIVERSION
] ||
267 *ocistate
= blob_memdup(msg
);
270 static void get_ocistate(struct blob_attr
**ocistate
, const char *name
)
277 if (asprintf(&objname
, "container.%s", name
) == -1)
280 ret
= ubus_lookup_id(ctx
, objname
, &id
);
285 ubus_invoke(ctx
, id
, "state", NULL
, ocistate_cb
, ocistate
, 3000);
288 static void list_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
290 struct blob_attr
*cur
, *curi
, *tl
[__LIST_MAX
], *ti
[__INSTANCE_MAX
], *tj
[__JAIL_MAX
];
292 const char *container_name
, *instance_name
, *jail_name
;
295 struct runtime_state
*rs
;
297 blobmsg_for_each_attr(cur
, msg
, rem
) {
298 container_name
= blobmsg_name(cur
);
299 blobmsg_parse(list_policy
, __LIST_MAX
, tl
, blobmsg_data(cur
), blobmsg_len(cur
));
300 if (!tl
[LIST_INSTANCES
])
303 blobmsg_for_each_attr(curi
, tl
[LIST_INSTANCES
], remi
) {
304 instance_name
= blobmsg_name(curi
);
305 blobmsg_parse(instance_policy
, __INSTANCE_MAX
, ti
, blobmsg_data(curi
), blobmsg_len(curi
));
307 if (!ti
[INSTANCE_JAIL
])
310 blobmsg_parse(jail_policy
, __JAIL_MAX
, tj
, blobmsg_data(ti
[INSTANCE_JAIL
]), blobmsg_len(ti
[INSTANCE_JAIL
]));
314 jail_name
= blobmsg_get_string(tj
[JAIL_NAME
]);
316 running
= ti
[INSTANCE_RUNNING
] && blobmsg_get_bool(ti
[INSTANCE_RUNNING
]);
318 if (ti
[INSTANCE_PID
])
319 pid
= blobmsg_get_u32(ti
[INSTANCE_PID
]);
323 if (ti
[INSTANCE_EXITCODE
])
324 exitcode
= blobmsg_get_u32(ti
[INSTANCE_EXITCODE
]);
328 rs
= runtime_alloc(container_name
);
329 rs
->instance_name
= strdup(instance_name
);
330 rs
->jail_name
= strdup(jail_name
);
331 rs
->runtime_pid
= pid
;
332 rs
->exitcode
= exitcode
;
333 rs
->running
= running
;
334 avl_insert(&runtime
, &rs
->avl
);
341 static int runtime_load(void)
343 struct runtime_state
*item
, *tmp
;
346 avl_init(&runtime
, avl_strcmp
, false, NULL
);
347 if (ubus_lookup_id(ctx
, "container", &id
) ||
348 ubus_invoke(ctx
, id
, "list", NULL
, list_cb
, &runtime
, 3000))
352 avl_for_each_element_safe(&runtime
, item
, avl
, tmp
)
353 get_ocistate(&item
->ocistate
, item
->jail_name
);
358 static void runtime_free(void)
360 struct runtime_state
*item
, *tmp
;
362 avl_for_each_element_safe(&runtime
, item
, avl
, tmp
) {
363 avl_delete(&runtime
, &item
->avl
);
364 free(item
->instance_name
);
365 free(item
->jail_name
);
366 free(item
->ocistate
);
373 static inline int setup_tios(int fd
, struct termios
*oldtios
)
375 struct termios newtios
;
381 /* Get current termios */
382 if (tcgetattr(fd
, oldtios
))
387 /* We use the same settings that ssh does. */
388 newtios
.c_iflag
|= IGNPAR
;
389 newtios
.c_iflag
&= ~(ISTRIP
| INLCR
| IGNCR
| ICRNL
| IXON
| IXANY
| IXOFF
);
390 newtios
.c_lflag
&= ~(TOSTOP
| ISIG
| ICANON
| ECHO
| ECHOE
| ECHOK
| ECHONL
);
391 newtios
.c_oflag
&= ~ONLCR
;
392 newtios
.c_oflag
|= OPOST
;
393 newtios
.c_cc
[VMIN
] = 1;
394 newtios
.c_cc
[VTIME
] = 0;
396 /* Set new attributes */
397 if (tcsetattr(fd
, TCSAFLUSH
, &newtios
))
404 static void client_cb(struct ustream
*s
, int bytes
)
410 buf
= ustream_get_read_buf(s
, &len
);
414 rv
= ustream_write(&lufd
.stream
, buf
, len
, false);
417 ustream_consume(s
, rv
);
424 static void local_cb(struct ustream
*s
, int bytes
)
430 buf
= ustream_get_read_buf(s
, &len
);
434 if ((len
> 0) && (buf
[0] == 2))
437 rv
= ustream_write(&cufd
.stream
, buf
, len
, false);
440 ustream_consume(s
, rv
);
447 static int uxc_attach(const char *container_name
)
449 struct ubus_context
*ctx
;
451 static struct blob_buf req
;
452 int client_fd
, server_fd
, tty_fd
;
453 struct termios oldtermios
;
455 ctx
= ubus_connect(NULL
);
457 fprintf(stderr
, "can't connect to ubus!\n");
461 /* open pseudo-terminal pair */
462 client_fd
= posix_openpt(O_RDWR
| O_NOCTTY
);
464 fprintf(stderr
, "can't create virtual console!\n");
468 setup_tios(client_fd
, &oldtermios
);
471 server_fd
= open(ptsname(client_fd
), O_RDWR
| O_NOCTTY
);
473 fprintf(stderr
, "can't open virtual console!\n");
478 setup_tios(server_fd
, &oldtermios
);
480 tty_fd
= open("/dev/tty", O_RDWR
);
482 fprintf(stderr
, "can't open local console!\n");
488 setup_tios(tty_fd
, &oldtermios
);
490 /* register server-side with procd */
491 blob_buf_init(&req
, 0);
492 blobmsg_add_string(&req
, "name", container_name
);
493 blobmsg_add_string(&req
, "instance", container_name
);
495 if (ubus_lookup_id(ctx
, "container", &id
) ||
496 ubus_invoke_fd(ctx
, id
, "console_attach", req
.head
, NULL
, NULL
, 3000, server_fd
)) {
497 fprintf(stderr
, "ubus request failed\n");
511 /* forward between stdio and client_fd until detach is requested */
512 lufd
.stream
.notify_read
= local_cb
;
513 ustream_fd_init(&lufd
, tty_fd
);
515 cufd
.stream
.notify_read
= client_cb
;
516 /* ToDo: handle remote close and other events */
517 // cufd.stream.notify_state = client_state_cb;
518 ustream_fd_init(&cufd
, client_fd
);
520 fprintf(stderr
, "attaching to jail console. press [CTRL]+[B] to exit.\n");
526 tcsetattr(tty_fd
, TCSAFLUSH
, &oldtermios
);
527 ustream_free(&lufd
.stream
);
528 ustream_free(&cufd
.stream
);
534 static int uxc_state(char *name
)
536 struct runtime_state
*s
= avl_find_element(&runtime
, name
, s
, avl
);
537 struct blob_attr
*ocistate
= NULL
;
538 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
541 char *jail_name
= NULL
;
544 static struct blob_buf buf
;
547 ocistate
= s
->ocistate
;
550 state
= blobmsg_format_json_indent(ocistate
, true, 0);
554 printf("%s\n", state
);
559 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
560 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
561 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
564 if (!strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
]))) {
566 jail_name
= blobmsg_get_string(tb
[CONF_JAIL
]);
570 bundle
= blobmsg_get_string(tb
[CONF_PATH
]);
578 blob_buf_init(&buf
, 0);
579 blobmsg_add_string(&buf
, "ociVersion", OCI_VERSION_STRING
);
580 blobmsg_add_string(&buf
, "id", jail_name
);
581 blobmsg_add_string(&buf
, "status", s
?"stopped":"uninitialized");
582 blobmsg_add_string(&buf
, "bundle", bundle
);
584 tmp
= blobmsg_format_json_indent(buf
.head
, true, 0);
598 static int uxc_list(void)
600 struct blob_attr
*cur
, *tb
[__CONF_MAX
], *ts
[__STATE_MAX
];
602 struct runtime_state
*s
= NULL
;
603 char *name
, *ocistatus
, *status
, *tmp
;
604 int container_pid
= -1;
606 static struct blob_buf buf
;
610 blob_buf_init(&buf
, 0);
611 arr
= blobmsg_open_array(&buf
, "");
614 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
615 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
616 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
619 autostart
= tb
[CONF_AUTOSTART
] && blobmsg_get_bool(tb
[CONF_AUTOSTART
]);
622 name
= blobmsg_get_string(tb
[CONF_NAME
]);
623 s
= avl_find_element(&runtime
, name
, s
, avl
);
625 if (s
&& s
->ocistate
) {
626 blobmsg_parse(state_policy
, __STATE_MAX
, ts
, blobmsg_data(s
->ocistate
), blobmsg_len(s
->ocistate
));
627 ocistatus
= blobmsg_get_string(ts
[STATE_STATUS
]);
628 container_pid
= blobmsg_get_u32(ts
[STATE_PID
]);
631 status
= ocistatus
?:(s
&& s
->running
)?"creating":"stopped";
634 obj
= blobmsg_open_table(&buf
, "");
635 blobmsg_add_string(&buf
, "name", name
);
636 blobmsg_add_string(&buf
, "status", status
);
637 blobmsg_add_u8(&buf
, "autostart", autostart
);
639 printf("[%c] %s %s", autostart
?'*':' ', name
, status
);
642 if (s
&& !s
->running
&& (s
->exitcode
>= 0)) {
644 blobmsg_add_u32(&buf
, "exitcode", s
->exitcode
);
646 printf(" exitcode: %d (%s)", s
->exitcode
, strerror(s
->exitcode
));
649 if (s
&& s
->running
&& (s
->runtime_pid
>= 0)) {
651 blobmsg_add_u32(&buf
, "runtime_pid", s
->runtime_pid
);
653 printf(" runtime pid: %d", s
->runtime_pid
);
656 if (s
&& s
->running
&& (container_pid
>= 0)) {
658 blobmsg_add_u32(&buf
, "container_pid", container_pid
);
660 printf(" container pid: %d", container_pid
);
666 blobmsg_close_table(&buf
, obj
);
670 blobmsg_close_array(&buf
, arr
);
671 tmp
= blobmsg_format_json_indent(buf
.head
, true, 0);
684 static int uxc_create(char *name
, bool immediately
)
686 static struct blob_buf req
;
687 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
690 struct runtime_state
*s
= NULL
;
691 char *path
= NULL
, *jailname
= NULL
, *pidfile
= NULL
, *tmprwsize
= NULL
, *writepath
= NULL
;
696 s
= avl_find_element(&runtime
, name
, s
, avl
);
698 if (s
&& (s
->running
))
701 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
702 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
703 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
706 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
716 path
= blobmsg_get_string(tb
[CONF_PATH
]);
718 if (tb
[CONF_PIDFILE
])
719 pidfile
= blobmsg_get_string(tb
[CONF_PIDFILE
]);
721 if (tb
[CONF_TEMP_OVERLAY_SIZE
])
722 tmprwsize
= blobmsg_get_string(tb
[CONF_TEMP_OVERLAY_SIZE
]);
724 if (tb
[CONF_WRITE_OVERLAY_PATH
])
725 writepath
= blobmsg_get_string(tb
[CONF_WRITE_OVERLAY_PATH
]);
728 jailname
= blobmsg_get_string(tb
[CONF_JAIL
]);
730 blob_buf_init(&req
, 0);
731 blobmsg_add_string(&req
, "name", name
);
732 ins
= blobmsg_open_table(&req
, "instances");
733 in
= blobmsg_open_table(&req
, name
);
734 blobmsg_add_string(&req
, "bundle", path
);
735 j
= blobmsg_open_table(&req
, "jail");
736 blobmsg_add_string(&req
, "name", jailname
?:name
);
737 blobmsg_add_u8(&req
, "immediately", immediately
);
740 blobmsg_add_string(&req
, "pidfile", pidfile
);
742 blobmsg_close_table(&req
, j
);
745 blobmsg_add_string(&req
, "overlaydir", writepath
);
748 blobmsg_add_string(&req
, "tmpoverlaysize", tmprwsize
);
750 blobmsg_close_table(&req
, in
);
751 blobmsg_close_table(&req
, ins
);
755 tmp
= blobmsg_format_json_indent(req
.head
, true, 1);
759 fprintf(stderr
, "adding container to procd:\n\t%s\n", tmp
);
764 if (ubus_lookup_id(ctx
, "container", &id
) ||
765 ubus_invoke(ctx
, id
, "add", req
.head
, NULL
, NULL
, 3000)) {
773 static int uxc_start(const char *name
, bool console
)
782 exit(uxc_attach(name
));
785 if (asprintf(&objname
, "container.%s", name
) == -1)
788 if (ubus_lookup_id(ctx
, objname
, &id
))
791 return ubus_invoke(ctx
, id
, "start", NULL
, NULL
, NULL
, 3000);
794 static int uxc_kill(char *name
, int signal
)
796 static struct blob_buf req
;
797 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
801 struct runtime_state
*s
= NULL
;
804 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
805 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
806 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
809 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
819 s
= avl_find_element(&runtime
, name
, s
, avl
);
821 if (!s
|| !(s
->running
))
824 blob_buf_init(&req
, 0);
825 blobmsg_add_u32(&req
, "signal", signal
);
826 blobmsg_add_string(&req
, "name", name
);
828 if (asprintf(&objname
, "container.%s", name
) == -1)
831 ret
= ubus_lookup_id(ctx
, objname
, &id
);
836 if (ubus_invoke(ctx
, id
, "kill", req
.head
, NULL
, NULL
, 3000))
843 static int uxc_set(char *name
, char *path
, bool autostart
, bool add
, char *pidfile
, char *_tmprwsize
, char *_writepath
, char *requiredmounts
)
845 static struct blob_buf req
;
846 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
848 const char *cfname
= NULL
;
850 char *keeppath
= NULL
;
851 char *tmprwsize
= _tmprwsize
;
852 char *writepath
= _writepath
;
853 char *curvol
, *tmp
, *mnttok
;
858 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
859 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
860 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
863 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
866 cfname
= blobmsg_name(cur
);
880 if (stat(path
, &sb
) == -1)
883 if ((sb
.st_mode
& S_IFMT
) != S_IFDIR
)
888 ret
= mkdir(confdir
, 0755);
890 if (ret
&& errno
!= EEXIST
)
893 if (asprintf(&fname
, "%s/%s.json", confdir
, name
) == -1)
896 f
= open(fname
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0644);
900 f
= open(cfname
, O_WRONLY
| O_TRUNC
, 0644);
906 keeppath
= blobmsg_get_string(tb
[CONF_PATH
]);
907 if (tb
[CONF_WRITE_OVERLAY_PATH
])
908 writepath
= blobmsg_get_string(tb
[CONF_WRITE_OVERLAY_PATH
]);
910 if (tb
[CONF_TEMP_OVERLAY_SIZE
])
911 tmprwsize
= blobmsg_get_string(tb
[CONF_TEMP_OVERLAY_SIZE
]);
914 blob_buf_init(&req
, 0);
915 blobmsg_add_string(&req
, "name", name
);
916 blobmsg_add_string(&req
, "path", path
?:keeppath
);
917 blobmsg_add_u8(&req
, "autostart", autostart
);
919 blobmsg_add_string(&req
, "pidfile", pidfile
);
922 blobmsg_add_string(&req
, "temp-overlay-size", tmprwsize
);
925 blobmsg_add_string(&req
, "write-overlay-path", writepath
);
927 if (!add
&& tb
[CONF_VOLUMES
])
928 blobmsg_add_blob(&req
, tb
[CONF_VOLUMES
]);
930 if (add
&& requiredmounts
) {
931 mntarr
= blobmsg_open_array(&req
, "volumes");
932 for (mnttok
= requiredmounts
; ; mnttok
= NULL
) {
933 curvol
= strtok_r(mnttok
, ",;", &tmp
);
937 blobmsg_add_string(&req
, NULL
, curvol
);
939 blobmsg_close_array(&req
, mntarr
);
941 tmp
= blobmsg_format_json_indent(req
.head
, true, 0);
943 dprintf(f
, "%s\n", tmp
);
962 static const struct blobmsg_policy block_info_policy
[__BLOCK_INFO_MAX
] = {
963 [BLOCK_INFO_DEVICE
] = { .name
= "device", .type
= BLOBMSG_TYPE_STRING
},
964 [BLOCK_INFO_UUID
] = { .name
= "uuid", .type
= BLOBMSG_TYPE_STRING
},
965 [BLOCK_INFO_TARGET
] = { .name
= "target", .type
= BLOBMSG_TYPE_STRING
},
966 [BLOCK_INFO_TYPE
] = { .name
= "type", .type
= BLOBMSG_TYPE_STRING
},
967 [BLOCK_INFO_MOUNT
] = { .name
= "mount", .type
= BLOBMSG_TYPE_STRING
},
971 /* check if device 'devname' is mounted according to blockd */
972 static int checkblock(const char *uuid
)
974 struct blob_attr
*tb
[__BLOCK_INFO_MAX
];
975 struct blob_attr
*cur
;
978 blobmsg_for_each_attr(cur
, blockinfo
, rem
) {
979 blobmsg_parse(block_info_policy
, __BLOCK_INFO_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
981 if (!tb
[BLOCK_INFO_UUID
] || !tb
[BLOCK_INFO_MOUNT
])
984 if (!strcmp(uuid
, blobmsg_get_string(tb
[BLOCK_INFO_UUID
])))
997 static const struct blobmsg_policy uci_fstab_policy
[__UCI_FSTAB_MAX
] = {
998 [UCI_FSTAB_UUID
] = { .name
= "uuid", .type
= BLOBMSG_TYPE_STRING
},
999 [UCI_FSTAB_ANONYMOUS
] = { .name
= ".anonymous", .type
= BLOBMSG_TYPE_BOOL
},
1002 static const char *resolveuuid(const char *volname
)
1004 struct blob_attr
*tb
[__UCI_FSTAB_MAX
];
1005 struct blob_attr
*cur
;
1006 const char *mntname
;
1007 char *tmpvolname
, *replc
;
1010 blobmsg_for_each_attr(cur
, fstabinfo
, rem
) {
1011 blobmsg_parse(uci_fstab_policy
, __UCI_FSTAB_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
1013 if (!tb
[UCI_FSTAB_UUID
])
1016 if (tb
[UCI_FSTAB_ANONYMOUS
] && blobmsg_get_bool(tb
[UCI_FSTAB_ANONYMOUS
]))
1019 mntname
= blobmsg_name(cur
);
1023 tmpvolname
= strdup(volname
);
1024 while ((replc
= strchr(tmpvolname
, '-')))
1027 res
= strcmp(tmpvolname
, mntname
);
1031 return blobmsg_get_string(tb
[UCI_FSTAB_UUID
]);
1037 /* check status of each required volume */
1038 static int checkvolumes(struct blob_attr
*volumes
)
1040 struct blob_attr
*cur
;
1043 blobmsg_for_each_attr(cur
, volumes
, rem
) {
1044 if (checkblock(resolveuuid(blobmsg_get_string(cur
))))
1051 static void block_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
1053 blockinfo
= blob_memdup(blobmsg_data(msg
));
1056 static void fstab_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
1058 fstabinfo
= blob_memdup(blobmsg_data(msg
));
1061 static int uxc_boot(void)
1063 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
1064 struct runtime_state
*s
;
1065 static struct blob_buf req
;
1070 ret
= ubus_lookup_id(ctx
, "block", &id
);
1074 ret
= ubus_invoke(ctx
, id
, "info", NULL
, block_cb
, NULL
, 3000);
1078 ret
= ubus_lookup_id(ctx
, "uci", &id
);
1082 blob_buf_init(&req
, 0);
1083 blobmsg_add_string(&req
, "config", "fstab");
1084 blobmsg_add_string(&req
, "type", "mount");
1086 ret
= ubus_invoke(ctx
, id
, "get", req
.head
, fstab_cb
, NULL
, 3000);
1090 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
1091 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
1092 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
] || !tb
[CONF_AUTOSTART
] || !blobmsg_get_bool(tb
[CONF_AUTOSTART
]))
1095 s
= avl_find_element(&runtime
, blobmsg_get_string(tb
[CONF_NAME
]), s
, avl
);
1099 /* make sure all volumes are ready before starting */
1100 if (tb
[CONF_VOLUMES
])
1101 if (checkvolumes(tb
[CONF_VOLUMES
]))
1104 name
= strdup(blobmsg_get_string(tb
[CONF_NAME
]));
1105 ret
+= uxc_create(name
, true);
1112 static int uxc_delete(char *name
, bool force
)
1114 struct blob_attr
*cur
, *tb
[__CONF_MAX
];
1115 struct runtime_state
*s
= NULL
;
1116 static struct blob_buf req
;
1119 const char *fname
= NULL
;
1122 blobmsg_for_each_attr(cur
, blob_data(conf
.head
), rem
) {
1123 blobmsg_parse(conf_policy
, __CONF_MAX
, tb
, blobmsg_data(cur
), blobmsg_len(cur
));
1124 if (!tb
[CONF_NAME
] || !tb
[CONF_PATH
])
1127 if (strcmp(name
, blobmsg_get_string(tb
[CONF_NAME
])))
1130 fname
= blobmsg_name(cur
);
1137 s
= avl_find_element(&runtime
, name
, s
, avl
);
1139 if (s
&& s
->running
) {
1141 ret
= uxc_kill(name
, SIGKILL
);
1152 ret
= ubus_lookup_id(ctx
, "container", &id
);
1156 blob_buf_init(&req
, 0);
1157 blobmsg_add_string(&req
, "name", s
->container_name
);
1158 blobmsg_add_string(&req
, "instance", s
->instance_name
);
1160 if (ubus_invoke(ctx
, id
, "delete", req
.head
, NULL
, NULL
, 3000)) {
1161 blob_buf_free(&req
);
1167 if (stat(fname
, &sb
) == -1) {
1172 if (unlink(fname
) == -1)
1179 static void reload_conf(void)
1181 blob_buf_free(&conf
);
1185 int main(int argc
, char **argv
)
1187 enum uxc_cmd cmd
= CMD_UNKNOWN
;
1189 char *bundle
= NULL
;
1190 char *pidfile
= NULL
;
1191 char *tmprwsize
= NULL
;
1192 char *writepath
= NULL
;
1193 char *requiredmounts
= NULL
;
1194 bool autostart
= false;
1196 bool console
= false;
1197 int signal
= SIGTERM
;
1203 ctx
= ubus_connect(NULL
);
1211 ret
= runtime_load();
1216 int option_index
= 0;
1217 c
= getopt_long(argc
, argv
, OPT_ARGS
, long_options
, &option_index
);
1255 printf("uxc %s\n", UXC_VERSION
);
1263 requiredmounts
= optarg
;
1271 if (!strcmp("list", argv
[optind
]))
1273 else if (!strcmp("attach", argv
[optind
]))
1275 else if (!strcmp("boot", argv
[optind
]))
1277 else if(!strcmp("start", argv
[optind
]))
1279 else if(!strcmp("state", argv
[optind
]))
1281 else if(!strcmp("kill", argv
[optind
]))
1283 else if(!strcmp("enable", argv
[optind
]))
1285 else if(!strcmp("disable", argv
[optind
]))
1287 else if(!strcmp("delete", argv
[optind
]))
1289 else if(!strcmp("create", argv
[optind
]))
1294 if (optind
!= argc
- 2)
1297 ret
= uxc_attach(argv
[optind
+ 1]);
1309 if (optind
!= argc
- 2)
1312 ret
= uxc_start(argv
[optind
+ 1], console
);
1316 if (optind
!= argc
- 2)
1319 ret
= uxc_state(argv
[optind
+ 1]);
1323 if (optind
== (argc
- 3))
1324 signal
= atoi(argv
[optind
+ 2]);
1325 else if (optind
> argc
- 2)
1328 ret
= uxc_kill(argv
[optind
+ 1], signal
);
1332 if (optind
!= argc
- 2)
1335 ret
= uxc_set(argv
[optind
+ 1], NULL
, true, false, NULL
, NULL
, NULL
, NULL
);
1339 if (optind
!= argc
- 2)
1342 ret
= uxc_set(argv
[optind
+ 1], NULL
, false, false, NULL
, NULL
, NULL
, NULL
);
1346 if (optind
!= argc
- 2)
1349 ret
= uxc_delete(argv
[optind
+ 1], force
);
1353 if (optind
!= argc
- 2)
1357 ret
= uxc_set(argv
[optind
+ 1], bundle
, autostart
, true, pidfile
, tmprwsize
, writepath
, requiredmounts
);
1364 ret
= uxc_create(argv
[optind
+ 1], false);
1378 blob_buf_free(&conf
);
1383 fprintf(stderr
, "uxc error: %s\n", strerror(ret
));