diff --git a/.gitignore b/.gitignore index a6a6171..ab55705 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ ccon +ccon-cli *.o diff --git a/Makefile b/Makefile index bac14df..33a0637 100644 --- a/Makefile +++ b/Makefile @@ -6,16 +6,16 @@ LDLIBS := $(shell pkg-config --libs-only-l jansson libcap-ng) .PHONY: all clean fmt .PRECIOUS: %.o -all: ccon +all: ccon ccon-cli %.o: %.c $(CC) $(CFLAGS) -c -o "$@" "$<" -ccon: %: %.o - $(CC) $(LDFLAGS) -o "$@" "$<" $(LDLIBS) +ccon ccon-cli: %: %.o libccon.o + $(CC) $(LDFLAGS) -o "$@" $^ $(LDLIBS) clean: - rm -f *.o ccon + rm -f *.o ccon ccon-cli fmt: - indent --ignore-profile --linux-style *.c + indent --ignore-profile --linux-style *.h *.c diff --git a/README.md b/README.md index 15a66b1..542e88f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ than [LXC][lxc.container.conf.5]). ## Table of contents * [Lifecycle](#lifecycle) +* [Socket communication](#socket-communication) + * [Getting the container process's + PID](#getting-the-container-processs-pid) + * [Start request](#start-request) * [Configuration](#configuration) * [Version](#version) * [Namespaces](#namespaces) @@ -58,8 +62,11 @@ synchronize the container setup. Here's an outline of the lifecycle: | | mounts filesystems | | | ← sends namespaces-complete | | runs pre-start hooks | blocks on exec-message | -| sends exec-message → | | -| | opens the local ptmx | +| binds to socket path | | +| sends connection socket → | | +| blocks on exec-process message | listens for process JSON | +| | ← sends exec-process message | +| removes socket path | opens the local ptmx | | | ← sends pseudoterminal master | | waits on child death | executes user process | | splicing standard streams | … | @@ -83,6 +90,77 @@ use [`nsenter`][nsenter.1] or a wrapping ccon invocation to join those namespaces before the main ccon invocation creates the new mount namespace. +## Socket communication + +With `--socket=PATH`, ccon will bind a [`SOCK_SEQPACKET` Unix +socket][unix.7] to `PATH`. This path is created after namespace-setup +completes, so users can use its presence as a trigger for further +configuration (e.g. network setup) before [starting](#start-request) +the [user-specified code](#process). The path is removed after a +[start request](#start-request) is received or after the container +process exits, whichever comes first. + +The [`ccon-cli`](ccon-cli.c) program distributed with this repository +is one client for the ccon socket. + +### Getting the container process's PID + +An [`SO_PEERCRED`][socket.7] request will return the container +process's PID [in the receiving process's PID +namespace][pid_namespaces.7]. The client can use this to look up the +container process in their local [`/proc`][proc.5]. This request may +be performed as many times as you like. + +### Start request + +The request is a single [`struct iovec`][recv.2] containing either a +leading null byte or process JSON. Sending a single null-byte message +will trigger the [**`process`**](#process) field present in the +original configuration, while non-empty strings will completely +override that field. + +The response is a single [`struct iovec`][recv.2] containing either a +single null-byte message (for success) or an error message encoded in +[ASCII][ascii.7]. In this context, “success” means “successfully +received the start request”, because the container process sends the +response before actually executing the +[user-specified code](#process). + +If you set [**`host`**](#host) in your process JSON, `ccon-cli` will +open the referenced path and pass the open file descriptor to the +container over the Unix socket. + +### Example + +In one shell, launch ccon and have it listen on a socket at +`/tmp/ccon-sock`: + +``` +$ ccon --socket /tmp/ccon-sock +``` + +In a second shell, get the container process's PID, but don't trigger +the user-specified code: + +``` +$ PID=$(ccon-cli --socket /tmp/ccon-sock --pid) +$ echo "${PID}" +2186 +``` + +You can then perform additional configuration using that PID: + +``` +$ ip link set ccon-ex-veth1 netns "${PID}" +``` + +And when you're finished setting up the environment, you can trigger +the [user-specified code](#process): + +``` +$ ccon-cli --socket /tmp/ccon-sock --config-string '{"args": ["busybox", "sh"]}' +``` + ## Configuration Ccon is similar to an [Open Container Specification][ocs] runtime in @@ -822,6 +900,7 @@ be distributed under the GPLv3+. [setgid.2]: http://man7.org/linux/man-pages/man2/setgid.2.html [setuid.2]: http://man7.org/linux/man-pages/man2/setuid.2.html [syscall.2]: http://man7.org/linux/man-pages/man2/syscall.2.html +[recv.2]: http://man7.org/linux/man-pages/man2/recv.2.html [environ.3p]: https://www.kernel.org/pub/linux/docs/man-pages/man-pages-posix/ [exec.3]: http://man7.org/linux/man-pages/man3/exec.3.html [getcwd.3]: http://man7.org/linux/man-pages/man3/getcwd.3.html @@ -830,11 +909,14 @@ be distributed under the GPLv3+. [pts.4]: http://man7.org/linux/man-pages/man4/pty.4.html [filesystems.5]: http://man7.org/linux/man-pages/man5/filesystems.5.html [lxc.container.conf.5]: https://linuxcontainers.org/lxc/manpages/man5/lxc.container.conf.5.html +[proc.5]: https://linuxcontainers.org/lxc/manpages/man5/proc.5.html +[ascii.7]: http://man7.org/linux/man-pages/man7/ascii.7.html [capabilities.7]: http://man7.org/linux/man-pages/man7/capabilities.7.html [namespaces.7]: http://man7.org/linux/man-pages/man7/namespaces.7.html [pid_namespaces.7]: http://man7.org/linux/man-pages/man7/pid_namespaces.7.html [pty.7]: http://man7.org/linux/man-pages/man7/pty.7.html [signal.7]: http://man7.org/linux/man-pages/man7/signal.7.html +[socket.7]: http://man7.org/linux/man-pages/man7/socket.7.html [unix.7]: http://man7.org/linux/man-pages/man7/unix.7.html [user_namespaces.7]: http://man7.org/linux/man-pages/man7/user_namespaces.7.html [mount.8]: http://man7.org/linux/man-pages/man8/pty.8.html @@ -843,4 +925,5 @@ be distributed under the GPLv3+. [cgroups]: https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt [cgroups-unified]: https://www.kernel.org/doc/Documentation/cgroup-v2.txt [devpts]: https://www.kernel.org/doc/Documentation/filesystems/devpts.txt +[rfc1345.s5]: https://tools.ietf.org/html/rfc1345#section-5 [sd_listen_fds]: http://www.freedesktop.org/software/systemd/man/sd_listen_fds.html diff --git a/ccon-cli.c b/ccon-cli.c new file mode 100644 index 0000000..9561d3e --- /dev/null +++ b/ccon-cli.c @@ -0,0 +1,275 @@ +/* + * ccon-cli(1) - Client for the ccon --socket + * Copyright (C) 2016 W. Trevor King + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "libccon.h" + +static int parse_args(int argc, char **argv, int *get_pid, + const char **config_path, const char **config_string, + const char **socket_path); +static void usage(FILE * stream, char *path); +static void version(); +static char *read_file(const char *path); + +int main(int argc, char **argv) +{ + const char *config_path = NULL; + const char *config_string = NULL; + const char *socket_path = NULL; + struct sockaddr_un name; + struct ucred ucred; + socklen_t len; + char buf[CLIENT_MESSAGE_SIZE]; + struct iovec iov = { buf, CLIENT_MESSAGE_SIZE }; + struct msghdr msg = { NULL, 0, &iov, 1, NULL, 0, 0 }; + json_t *process; + json_error_t error; + ssize_t n; + int sock = -1, get_pid = 0, exec_fd = -1; + + if (parse_args + (argc, argv, &get_pid, &config_path, &config_string, + &socket_path)) { + return 1; + } + + sock = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (sock == -1) { + PERROR("socket"); + return 1; + } + + memset(&name, 0, sizeof(struct sockaddr_un)); + name.sun_family = AF_UNIX; + strncpy(name.sun_path, socket_path, sizeof(name.sun_path) - 1); + LOG("connecting to %s\n", socket_path); + if (connect + (sock, (const struct sockaddr *)&name, + sizeof(struct sockaddr_un)) == -1) { + PERROR("connect"); + return 1; + } + + if (get_pid) { + LOG("get peer PID\n"); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == + -1) { + PERROR("getsockopt"); + return 1; + } + printf("%ld\n", (long)ucred.pid); + } + + if (config_path) { + LOG("read configuration from %s\n", config_path); + config_string = read_file(config_path); + if (!config_string) { + return 1; + } + } + + if (config_string) { + if (strlen(config_string) == 0) { + config_string = "\0"; + } else { + process = + json_loads(config_string, JSON_REJECT_DUPLICATES, + &error); + if (!process) { + LOG("error on %s:%d:%d: %s\n", config_path, + error.line, error.column, error.text); + return 1; + } + + if (get_host_exec_fd(process, &exec_fd) == -1) { + return 1; + } + } + + iov.iov_base = (void *)config_string; + if (config_string[0] == '\0') { + iov.iov_len = 1; + } else { + iov.iov_len = strlen(config_string) + 1; + } + if (iov.iov_len > CLIENT_MESSAGE_SIZE) { + LOG("configuration string is too long for a ccon socket message (%d > %d)\n", (int)iov.iov_len, CLIENT_MESSAGE_SIZE); + return 1; + } + LOG("send start message\n"); + if (sendmsg(sock, &msg, 0) == -1) { + PERROR("sendmsg"); + return 1; + } + if (exec_fd >= 0) { + if (sendfd(sock, &exec_fd)) { + return 1; + } + } + + iov.iov_base = (void *)buf; + iov.iov_len = CLIENT_MESSAGE_SIZE; + LOG("wait for response\n"); + n = recvmsg(sock, &msg, 0); + if (n == -1) { + PERROR("recvmsg"); + return 1; + } + if (n != 1 || ((char *)iov.iov_base)[0] != '\0') { + LOG("unexpected message from container (%d): %.*s\n", + (int)n, (int)n, (char *)iov.iov_base); + return 1; + } + LOG("received response\n"); + } + + if (config_path) { + free((void *)config_string); + } + + return 0; +} + +static int parse_args(int argc, char **argv, int *get_pid, + const char **config_path, const char **config_string, + const char **socket_path) +{ + int c, option_index; + static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"verbose", no_argument, &verbose, 1}, + {"version", no_argument, NULL, 'v'}, + {"config", required_argument, NULL, 'c'}, + {"config-string", required_argument, NULL, 's'}, + {"socket", required_argument, NULL, 'S'}, + {"pid", no_argument, NULL, 'p'}, + {}, + }; + + while (1) { + option_index = 0; + c = getopt_long(argc, argv, "hVvc:s:S:p", long_options, + &option_index); + if (c == -1) { + break; + } + switch (c) { + case 0: + break; /* long-option flag was set */ + case 'h': + usage(stdout, argv[0]); + exit(0); + case 'V': + verbose = 1; /* set short-option flag */ + break; + case 'v': + version(); + exit(0); + case 'c': + *config_path = optarg; + *config_string = NULL; + break; + case 's': + *config_path = NULL; + *config_string = optarg; + break; + case 'S': + *socket_path = optarg; + break; + case 'p': + *get_pid = 1; + break; + default: /* '?' */ + usage(stderr, argv[0]); + exit(1); + } + } + + if (!*socket_path) { + LOG("missing --socket PATH\n"); + exit(1); + } + + return 0; +} + +static void usage(FILE * stream, char *path) +{ + fprintf(stream, "usage: %s [OPTION]...\n\n", path); + fprintf(stream, "Options:\n"); + fprintf(stream, " -h, --help\tShow this usage information and exit\n"); + fprintf(stream, + " -v, --version\tPrint version information and exit\n"); + fprintf(stream, " -c, --config=PATH\tFile containing process JSON\n"); + fprintf(stream, + " -s, --config-string=JSON\tProcess JSON from the argument\n"); + fprintf(stream, " -S, --socket=PATH\tCcon socket path\n"); + fprintf(stream, + " -p, --pid\tPrint the container process's PID to stdout\n"); +} + +static void version() +{ + printf("ccon-cli 0.3.0\n"); +} + +static char *read_file(const char *path) +{ + char *buf = NULL; + ssize_t rc; + size_t pos = 0, len = 0; + int fd; + + fd = open(path, O_RDONLY); + if (fd == -1) { + PERROR("open"); + return NULL; + } + + while (1) { + if (pos >= len - 1) { + len += 1024; + buf = realloc(buf, sizeof(char) * len); + if (!buf) { + PERROR("realloc"); + return NULL; + } + } + rc = read(fd, buf + pos, len - pos - 1); + if (rc == -1) { + PERROR("read"); + return NULL; + } + if (rc == 0) { + return buf; + } + pos += rc; + buf[pos] = '\0'; + } +} diff --git a/ccon.c b/ccon.c index 3934f30..13e1be3 100644 --- a/ccon.c +++ b/ccon.c @@ -40,15 +40,16 @@ #include #include +#include "libccon.h" #define STACK_SIZE (1024 * 1024) -#define MAX_PATH 1024 /* messages passed between the host and container */ #define MESSAGE_SIZE 80 #define USER_NAMESPACE_MAPPING_COMPLETE "user-namespace-mapping-complete" #define CONTAINER_SETUP_COMPLETE "container-setup-complete" #define EXEC_PROCESS "exec-process" +#define CONNECTION_SOCKET "connection-socket" #ifndef execveat static int execveat(int fd, const char *path, char **argv, char **envp, @@ -76,21 +77,17 @@ extern char **environ; static pid_t child_pid; static pid_t hook_pid; -/* logging */ -static int verbose; -#define LOG(...) do {if (verbose) {fprintf(stderr, __VA_ARGS__);}} while(0) -#define PERROR(...) do {if (verbose) {perror(__VA_ARGS__);}} while(0) - static int parse_args(int argc, char **argv, const char **config_path, - const char **config_string); + const char **config_string, const char **socket_path); static void usage(FILE * stream, char *path); static void version(); static void kill_child(int signum, siginfo_t * siginfo, void *unused); static void reap_child(int signum, siginfo_t * siginfo, void *unused); static int validate_config(json_t * config); static int validate_version(const char *version); -static int run_container(json_t * config); -static int handle_parent(json_t * config, pid_t cpid, int *socket); +static int run_container(json_t * config, const char *socket_path); +static int handle_parent(json_t * config, const char *socket_path, pid_t cpid, + int *socket); static int child_func(void *arg); static int handle_child(json_t * config, int *socket, int *exec_fd, namespace_fd_t ** namespace_fds); @@ -99,12 +96,12 @@ static int set_working_directory(json_t * process); static int set_user_group(json_t * process); static int _capng_name_to_capability(const char *name); static int set_capabilities(json_t * process); -static void exec_container_process(json_t * config, int *socket, int *exec_fd); static void exec_process(json_t * process, int dup_stdin, int *socket, int *exec_fd); -static int get_host_exec_fd(json_t * config, int *exec_fd); static int get_namespace_fds(json_t * config, namespace_fd_t ** namespace_fds); static int run_hooks(json_t * config, const char *name, pid_t cpid); +static int setup_socket(const char *path, int *container_socket); +static int serve_socket(json_t * process, int *socket); static int get_namespace_type(const char *name, int *nstype); static int get_clone_flags(json_t * config, int *flags); static int join_namespaces(json_t * config, namespace_fd_t ** namespace_fds); @@ -117,23 +114,21 @@ static int set_user_setgroups(json_t * user, pid_t cpid); static int get_mount_flag(const char *name, unsigned long *flag); static int handle_mounts(json_t * config); static int pivot_root_remove_old(const char *new_root); -static int open_in_path(const char *name, int flags); static int _wait(pid_t pid, const char *name); static char **json_array_of_strings_value(json_t * array); static int close_pipe(int pipe_fd[]); -static int sendfd(int socket, int *fd); -static int recvfd(int socket, int *fd); static int splice_pseudoterminal_master(int *master); int main(int argc, char **argv) { const char *config_path = "config.json"; const char *config_string = NULL; + const char *socket_path = NULL; int err; json_t *config; json_error_t error; - if (parse_args(argc, argv, &config_path, &config_string)) { + if (parse_args(argc, argv, &config_path, &config_string, &socket_path)) { return 1; } @@ -156,7 +151,7 @@ int main(int argc, char **argv) goto cleanup; } - err = run_container(config); + err = run_container(config, socket_path); cleanup: if (config) { @@ -167,7 +162,7 @@ int main(int argc, char **argv) } static int parse_args(int argc, char **argv, const char **config_path, - const char **config_string) + const char **config_string, const char **socket_path) { int c, option_index; static struct option long_options[] = { @@ -176,12 +171,13 @@ static int parse_args(int argc, char **argv, const char **config_path, {"version", no_argument, NULL, 'v'}, {"config", required_argument, NULL, 'c'}, {"config-string", required_argument, NULL, 's'}, + {"socket", required_argument, NULL, 'S'}, {}, }; while (1) { option_index = 0; - c = getopt_long(argc, argv, "hVvc:s:", long_options, + c = getopt_long(argc, argv, "hVvc:s:S:", long_options, &option_index); if (c == -1) { break; @@ -204,6 +200,9 @@ static int parse_args(int argc, char **argv, const char **config_path, case 's': *config_string = optarg; break; + case 'S': + *socket_path = optarg; + break; default: /* '?' */ usage(stderr, argv[0]); exit(1); @@ -225,6 +224,8 @@ static void usage(FILE * stream, char *path) " -c, --config=PATH\tOverride config.json with an alternate path\n"); fprintf(stream, " -s, --config-string=JSON\tSpecify config JSON on the command line, overriding --config and its PATH\n"); + fprintf(stream, + " -S, --socket=PATH\tSpecify a socket path for container PID and start requests\n"); } static void version() @@ -410,8 +411,9 @@ static int validate_version(const char *version) return 1; } -static int run_container(json_t * config) +static int run_container(json_t * config, const char *socket_path) { + json_t *process; struct sigaction act; child_func_args_t child_args; char *stack = NULL, *stack_top; @@ -437,9 +439,12 @@ static int run_container(json_t * config) child_args.config = config; child_args.socket = sockets[1]; - if (get_host_exec_fd(config, &child_args.exec_fd)) { - err = 1; - goto cleanup; + process = json_object_get(config, "process"); + if (process) { + if (get_host_exec_fd(process, &child_args.exec_fd) == -1) { + err = 1; + goto cleanup; + } } if (get_namespace_fds(config, &child_args.namespace_fds)) { @@ -515,7 +520,7 @@ static int run_container(json_t * config) child_args.namespace_fds = NULL; } - err = handle_parent(config, cpid, &sockets[0]); + err = handle_parent(config, socket_path, cpid, &sockets[0]); cleanup: cpid = child_pid; @@ -552,7 +557,8 @@ static int run_container(json_t * config) return err; } -static int handle_parent(json_t * config, pid_t cpid, int *socket) +static int handle_parent(json_t * config, const char *socket_path, pid_t cpid, + int *socket) { json_t *process, *terminal = NULL; char buf[MESSAGE_SIZE]; @@ -603,18 +609,24 @@ static int handle_parent(json_t * config, pid_t cpid, int *socket) goto wait; } - iov.iov_base = (void *)EXEC_PROCESS; - iov.iov_len = strlen(iov.iov_base); - n = sendmsg(*socket, &msg, 0); - if (n == -1) { - PERROR("sendmsg"); - err = 1; - goto wait; - } else if (n != iov.iov_len) { - LOG("did not send the expected number of bytes: %d != %d\n", - (int)n, (int)iov.iov_len); - err = 1; - goto wait; + if (socket_path) { + if (setup_socket(socket_path, socket)) { + err = 1; + goto wait; + } + } else { + iov.iov_base = (void *)EXEC_PROCESS; + iov.iov_len = strlen(iov.iov_base); + n = sendmsg(*socket, &msg, 0); + if (n == -1) { + PERROR("sendmsg"); + err = 1; + goto wait; + } else if (n != iov.iov_len) { + LOG("did not send the expected number of bytes: %d != %d\n", (int)n, (int)iov.iov_len); + err = 1; + goto wait; + } } process = json_object_get(config, "process"); @@ -716,10 +728,11 @@ static int child_func(void *arg) static int handle_child(json_t * config, int *socket, int *exec_fd, namespace_fd_t ** namespace_fds) { + json_t *process; char buf[MESSAGE_SIZE]; struct iovec iov = { buf, MESSAGE_SIZE }; struct msghdr msg = { NULL, 0, &iov, 1, NULL, 0, 0 }; - size_t len; + size_t len, len2; ssize_t n; n = recvmsg(*socket, &msg, 0); @@ -755,6 +768,8 @@ static int handle_child(json_t * config, int *socket, int *exec_fd, return 1; } + process = json_object_get(config, "process"); + /* block while parent runs pre-start hooks */ iov.iov_base = (char *)buf; @@ -765,14 +780,21 @@ static int handle_child(json_t * config, int *socket, int *exec_fd, return 1; } len = strlen(EXEC_PROCESS); - if (len != n || strncmp(EXEC_PROCESS, iov.iov_base, len) != 0) { + len2 = strlen(CONNECTION_SOCKET); + if (len == n && strncmp(EXEC_PROCESS, iov.iov_base, len) == 0) { + if (!process) { + LOG("process not defined, exiting\n"); + return 0; + } + exec_process(process, 1, socket, exec_fd); + } else if (len2 == n + && strncmp(CONNECTION_SOCKET, iov.iov_base, len2) == 0) { + serve_socket(process, socket); + } else { LOG("unexpected message from host (%d): %.*s\n", (int)n, (int)n, (char *)iov.iov_base); - return 1; } - exec_container_process(config, socket, exec_fd); - return 1; } @@ -1031,20 +1053,6 @@ static int set_capabilities(json_t * process) return 0; } -static void exec_container_process(json_t * config, int *socket, int *exec_fd) -{ - json_t *process; - - process = json_object_get(config, "process"); - if (!process) { - LOG("process not defined, exiting\n"); - exit(0); - } - - exec_process(process, 1, socket, exec_fd); - return; -} - static void exec_process(json_t * process, int dup_stdin, int *socket, int *exec_fd) { @@ -1157,55 +1165,6 @@ static void exec_process(json_t * process, int dup_stdin, int *socket, return; } -static int get_host_exec_fd(json_t * config, int *exec_fd) -{ - json_t *process, *v1, *v2; - const char *arg0; - - *exec_fd = -1; - - process = json_object_get(config, "process"); - if (!process) { - return 0; - } - - v1 = json_object_get(process, "host"); - if (!v1 || !json_boolean_value(v1)) { - return 0; - } - - v1 = json_object_get(process, "path"); - if (v1) { - arg0 = json_string_value(v1); - if (!arg0) { - LOG("failed to extract process.path\n"); - return 1; - } - } else { - v1 = json_object_get(process, "args"); - if (!v1) { - return 0; - } - v2 = json_array_get(v1, 0); - if (!v2) { - LOG("failed to extract process.args[0]\n"); - return 1; - } - arg0 = json_string_value(v2); - if (!arg0) { - LOG("failed to extract process.args[0]\n"); - return 1; - } - } - - *exec_fd = open_in_path(arg0, O_PATH | O_CLOEXEC); - if (*exec_fd == -1) { - return 1; - } - - return 0; -} - static int get_namespace_fds(json_t * config, namespace_fd_t ** namespace_fds) { json_t *namespaces, *value, *path; @@ -1415,6 +1374,259 @@ static int run_hooks(json_t * config, const char *name, pid_t cpid) return err; } +static int setup_socket(const char *path, int *container_socket) +{ + char buf[MESSAGE_SIZE]; + struct iovec iov = { buf, MESSAGE_SIZE }; + struct msghdr msg = { NULL, 0, &iov, 1, NULL, 0, 0 }; + struct sockaddr_un name; + size_t len; + ssize_t n; + int connection_socket = -1, err = 0; + + connection_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (connection_socket == -1) { + PERROR("socket"); + err = 1; + goto cleanup; + } + + memset(&name, 0, sizeof(struct sockaddr_un)); + name.sun_family = AF_UNIX; + strncpy(name.sun_path, path, sizeof(name.sun_path) - 1); + LOG("bind connection socket to %s\n", path); + if (bind + (connection_socket, (const struct sockaddr *)&name, + sizeof(struct sockaddr_un)) == -1) { + PERROR("bind"); + err = 1; + goto cleanup; + } + + iov.iov_base = (void *)CONNECTION_SOCKET; + iov.iov_len = strlen(iov.iov_base); + if (sendmsg(*container_socket, &msg, 0) == -1) { + PERROR("sendmsg"); + err = 1; + goto cleanup; + } + + if (sendfd(*container_socket, &connection_socket)) { + err = 1; + goto cleanup; + } + + iov.iov_base = (char *)buf; + iov.iov_len = MESSAGE_SIZE; + n = recvmsg(*container_socket, &msg, 0); + if (n == -1) { + PERROR("recvmsg"); + err = 1; + goto cleanup; + } + len = strlen(EXEC_PROCESS); + if (len != n || strncmp(EXEC_PROCESS, iov.iov_base, len) != 0) { + LOG("unexpected message from container (%d): %.*s\n", (int)n, + (int)n, (char *)iov.iov_base); + err = 1; + } + + cleanup: + if (connection_socket >= 0) { + if (close(connection_socket) == -1) { + PERROR("close connection socket"); + err = 1; + } + } + LOG("unlink connection socket at %s\n", path); + if (unlink(path) == -1) { + PERROR("unlink"); + return 1; + } + return err; +} + +static int serve_socket(json_t * process, int *socket) +{ + char buf[CLIENT_MESSAGE_SIZE]; + struct iovec iov = { buf, CLIENT_MESSAGE_SIZE }; + struct msghdr msg = { NULL, 0, &iov, 1, NULL, 0, 0 }; + json_t *host; + json_error_t error; + ssize_t n; + int connection_socket = -1, data_socket = -1, exec_fd = -1, + err = 0, size; + + if (recvfd(*socket, &connection_socket) == -1) { + return 1; + } + LOG("listening on %d\n", connection_socket); + if (listen(connection_socket, 5) == -1) { + PERROR("listen"); + err = 1; + goto cleanup; + } + + while (1) { + data_socket = accept(connection_socket, NULL, NULL); + if (data_socket == -1) { + PERROR("accept"); + err = 1; + goto cleanup; + } + LOG("accepted connection on %d\n", data_socket); + + while (1) { + n = recvmsg(data_socket, &msg, 0); + if (n == -1) { + perror("recvmsg"); + err = 1; + goto cleanup; + } else if (n == 0) { + LOG("lost connection on %d\n", data_socket); + if (close(data_socket) == -1) { + PERROR("close data socket"); + err = 1; + data_socket = -1; + goto cleanup; + } + data_socket = -1; + break; + } + + LOG("received start request (%d): %.*s\n", (int)n, + (int)n, (char *)iov.iov_base); + if (n == 1 && ((char *)iov.iov_base)[0] != '\0') { + LOG("unexpected message from client (%d): %.*s\n", (int)n, (int)n, (char *)iov.iov_base); + err = 1; + iov.iov_base = + (void *)"unexpected length-one message"; + iov.iov_len = strlen((char *)iov.iov_base); + n = sendmsg(data_socket, &msg, 0); + if (n == -1) { + PERROR("sendmsg"); + } else if (n != iov.iov_len) { + LOG("did not send the expected number of bytes: %d != %d\n", (int)n, (int)iov.iov_len); + } + goto cleanup; + } else if (n > 1) { + process = + json_loads(iov.iov_base, + JSON_REJECT_DUPLICATES, &error); + if (!process) { + err = 1; + size = + snprintf((char *)buf, + CLIENT_MESSAGE_SIZE, + "error on process message %d:%d: %s\n", + error.line, error.column, + error.text); + if (size < 0) { + LOG("failed to format process JSON error\n"); + } else { + LOG(buf); + iov.iov_base = (void *)buf; + iov.iov_len = strlen(buf) - 1; /* -1 to remove trailing \n */ + n = sendmsg(data_socket, + &msg, 0); + if (n == -1) { + PERROR("sendmsg"); + } else if (n != iov.iov_len) { + LOG("did not send the expected number of bytes: %d != %d\n", (int)n, (int)iov.iov_len); + } + } + goto cleanup; + } + host = json_object_get(process, "host"); + if (host && json_boolean_value(host)) { + if (recvfd(data_socket, &exec_fd) == -1) { + err = 1; + LOG("failed to receive executable file descriptor\n"); + iov.iov_base = (void *) + "failed to receive executable file descriptor"; + iov.iov_len = + strlen(iov.iov_base); + n = sendmsg(data_socket, + &msg, 0); + if (n == -1) { + PERROR("sendmsg"); + } else if (n != iov.iov_len) { + LOG("did not send the expected number of bytes: %d != %d\n", (int)n, (int)iov.iov_len); + } + goto cleanup; + } + } + } + + iov.iov_base = "\0"; + iov.iov_len = 1; + n = sendmsg(data_socket, &msg, 0); + if (n == -1) { + PERROR("sendmsg"); + err = 1; + goto cleanup; + } else if (n != iov.iov_len) { + LOG("did not send the expected number of bytes: %d != %d\n", (int)n, (int)iov.iov_len); + err = 1; + goto cleanup; + } + + if (close(data_socket) == -1) { + PERROR("close data socket"); + err = 1; + data_socket = -1; + goto cleanup; + } + data_socket = -1; + if (close(connection_socket) == -1) { + PERROR("close connection socket"); + err = 1; + connection_socket = -1; + goto cleanup; + } + connection_socket = -1; + + iov.iov_base = (void *)EXEC_PROCESS; + iov.iov_len = strlen(iov.iov_base); + n = sendmsg(*socket, &msg, 0); + if (n == -1) { + PERROR("sendmsg"); + err = 1; + goto cleanup; + } else if (n != iov.iov_len) { + LOG("did not send the expected number of bytes: %d != %d\n", (int)n, (int)iov.iov_len); + err = 1; + goto cleanup; + } + + exec_process(process, 1, socket, &exec_fd); + err = 1; + goto cleanup; + } + } + + cleanup: + if (exec_fd >= 0) { + if (close(exec_fd) == -1) { + PERROR("close container-process executable"); + err = 1; + } + } + if (connection_socket >= 0) { + if (close(connection_socket) == -1) { + PERROR("close connection socket"); + err = 1; + } + } + if (data_socket >= 0) { + if (close(data_socket) == -1) { + PERROR("close data socket"); + err = 1; + } + } + return err; +} + static int get_namespace_type(const char *name, int *nstype) { if (strncmp("mount", name, strlen("mount") + 1) == 0) { @@ -1900,94 +2112,6 @@ static int handle_mounts(json_t * config) return 0; } -static int open_in_path(const char *name, int flags) -{ - const char *p; - char *paths = NULL, *paths2, *path; - size_t i; - int fd; - - if (name[0] == '/') { - LOG("open container-process executable from host %s\n", name); - fd = open(name, flags); - if (fd == -1) { - PERROR("open"); - return -1; - } - return fd; - } - - path = malloc(sizeof(char) * MAX_PATH); - if (!path) { - PERROR("malloc"); - return -1; - } - memset(path, 0, sizeof(char) * MAX_PATH); - - p = strchr(name, '/'); - if (p) { - if (!getcwd(path, MAX_PATH)) { - PERROR("getcwd"); - goto cleanup; - } - i = strlen(path); - if (i + strlen(name) + 2 > MAX_PATH) { - LOG("failed to format relative path (needed a buffer with %d byes)\n", (int)(i + strlen(name) + 2)); - goto cleanup; - } - path[i++] = '/'; - strcpy(path + i, name); - LOG("open container-process executable from host %s\n", path); - fd = open(path, flags); - if (fd == -1) { - PERROR("open"); - return -1; - } - free(path); - return fd; - } - - paths = getenv("PATH"); - if (!paths) { - LOG("failed to get host PATH\n"); - goto cleanup; - } - paths = strdup(paths); - if (!paths) { - PERROR("strdup"); - goto cleanup; - } - - paths2 = paths; - while ((p = strtok(paths2, ":"))) { - paths2 = NULL; - i = strlen(p); - if (i + strlen(name) + 2 > MAX_PATH) { - LOG("failed to format relative path (needed a buffer with %d byes)\n", (int)(i + strlen(name) + 2)); - goto cleanup; - } - strcpy(path, p); - path[i++] = '/'; - strcpy(path + i, name); - fd = open(path, flags); - if (fd >= 0) { - LOG("open container-process executable from host %s\n", - path); - free(path); - return fd; - } - } - - LOG("failed to find %s in the host PATH\n", name); - - cleanup: - if (paths) { - free(paths); - } - free(path); - return -1; -} - static int _wait(pid_t pid, const char *name) { siginfo_t siginfo; @@ -2144,65 +2268,6 @@ static int close_pipe(int pipe_fd[]) return err; } -static int sendfd(int socket, int *fd) -{ - struct cmsghdr *cmsg; - union { - char buf[CMSG_SPACE(sizeof(int))]; - struct cmsghdr align; - } u; - struct msghdr msg = { 0 }; - int *fdptr; - - msg.msg_control = u.buf; - msg.msg_controllen = sizeof(u.buf), cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - fdptr = (int *)CMSG_DATA(cmsg); - *fdptr = *fd; - if (sendmsg(socket, &msg, 0) == -1) { - PERROR("sendmsg"); - return 1; - } - - if (close(*fd)) { - PERROR("close"); - *fd = -1; - return 1; - } - *fd = -1; - - return 0; -} - -int recvfd(int socket, int *fd) -{ - struct cmsghdr *cmsg; - union { - char buf[CMSG_SPACE(sizeof(int))]; - struct cmsghdr align; - } u; - struct msghdr msg = { 0 }; - int *fdptr; - - msg.msg_control = u.buf; - msg.msg_controllen = sizeof(u.buf); - if (recvmsg(socket, &msg, 0) == -1) { - PERROR("recvmsg"); - return 1; - } - cmsg = CMSG_FIRSTHDR(&msg); - if (cmsg == NULL || cmsg->cmsg_type != SCM_RIGHTS) { - LOG("unexpected message from container (no file descriptor)\n"); - return 1; - } - fdptr = (int *)CMSG_DATA(cmsg); - *fd = *fdptr; - - return 0; -} - static int splice_pseudoterminal_master(int *master) { fd_set rfds, wfds, efds; diff --git a/libccon.c b/libccon.c new file mode 100644 index 0000000..a1d834d --- /dev/null +++ b/libccon.c @@ -0,0 +1,222 @@ +/* + * libccon - Utilities for ccon (shared between ccon and ccon-cli) + * Copyright (C) 2016 W. Trevor King + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include "libccon.h" + +int verbose; + +int get_host_exec_fd(json_t * process, int *exec_fd) +{ + json_t *v1, *v2; + const char *arg0; + + *exec_fd = -1; + + v1 = json_object_get(process, "host"); + if (!v1 || !json_boolean_value(v1)) { + return 0; + } + + v1 = json_object_get(process, "path"); + if (v1) { + arg0 = json_string_value(v1); + if (!arg0) { + LOG("failed to extract process.path\n"); + return -1; + } + } else { + v1 = json_object_get(process, "args"); + if (!v1) { + return 0; + } + v2 = json_array_get(v1, 0); + if (!v2) { + LOG("failed to extract process.args[0]\n"); + return -1; + } + arg0 = json_string_value(v2); + if (!arg0) { + LOG("failed to extract process.args[0]\n"); + return -1; + } + } + + *exec_fd = open_in_path(arg0, O_PATH | O_CLOEXEC); + if (*exec_fd == -1) { + return -1; + } + + return 0; +} + +int open_in_path(const char *name, int flags) +{ + const char *p; + char *paths = NULL, *paths2, *path; + size_t i; + int fd; + + if (name[0] == '/') { + LOG("open container-process executable from host %s\n", name); + fd = open(name, flags); + if (fd == -1) { + PERROR("open"); + return -1; + } + return fd; + } + + path = malloc(sizeof(char) * MAX_PATH); + if (!path) { + PERROR("malloc"); + return -1; + } + memset(path, 0, sizeof(char) * MAX_PATH); + + p = strchr(name, '/'); + if (p) { + if (!getcwd(path, MAX_PATH)) { + PERROR("getcwd"); + goto cleanup; + } + i = strlen(path); + if (i + strlen(name) + 2 > MAX_PATH) { + LOG("failed to format relative path (needed a buffer with %d byes)\n", (int)(i + strlen(name) + 2)); + goto cleanup; + } + path[i++] = '/'; + strcpy(path + i, name); + LOG("open container-process executable from host %s\n", path); + fd = open(path, flags); + if (fd == -1) { + PERROR("open"); + return -1; + } + free(path); + return fd; + } + + paths = getenv("PATH"); + if (!paths) { + LOG("failed to get host PATH\n"); + goto cleanup; + } + paths = strdup(paths); + if (!paths) { + PERROR("strdup"); + goto cleanup; + } + + paths2 = paths; + while ((p = strtok(paths2, ":"))) { + paths2 = NULL; + i = strlen(p); + if (i + strlen(name) + 2 > MAX_PATH) { + LOG("failed to format relative path (needed a buffer with %d byes)\n", (int)(i + strlen(name) + 2)); + goto cleanup; + } + strcpy(path, p); + path[i++] = '/'; + strcpy(path + i, name); + fd = open(path, flags); + if (fd >= 0) { + LOG("open container-process executable from host %s\n", + path); + free(path); + return fd; + } + } + + LOG("failed to find %s in the host PATH\n", name); + + cleanup: + if (paths) { + free(paths); + } + free(path); + return -1; +} + +int sendfd(int socket, int *fd) +{ + struct cmsghdr *cmsg; + union { + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + struct msghdr msg = { 0 }; + int *fdptr; + + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf), cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + fdptr = (int *)CMSG_DATA(cmsg); + *fdptr = *fd; + if (sendmsg(socket, &msg, 0) == -1) { + PERROR("sendmsg"); + return -1; + } + + if (close(*fd)) { + PERROR("close"); + *fd = -1; + return -1; + } + *fd = -1; + + return 0; +} + +int recvfd(int socket, int *fd) +{ + struct cmsghdr *cmsg; + union { + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + struct msghdr msg = { 0 }; + int *fdptr; + + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + if (recvmsg(socket, &msg, 0) == -1) { + PERROR("recvmsg"); + return -1; + } + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg == NULL || cmsg->cmsg_type != SCM_RIGHTS) { + LOG("unexpected message (no file descriptor)\n"); + return -1; + } + fdptr = (int *)CMSG_DATA(cmsg); + *fd = *fdptr; + + return 0; +} diff --git a/libccon.h b/libccon.h new file mode 100644 index 0000000..4f8a62a --- /dev/null +++ b/libccon.h @@ -0,0 +1,40 @@ +/* + * libccon - Utilities for ccon (shared between ccon and ccon-cli) + * Copyright (C) 2016 W. Trevor King + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _libccon_h +#define _libccon_h + +#include +#include + +#define MAX_PATH 1024 + +/* client messages passed through the --socket */ +#define CLIENT_MESSAGE_SIZE 1024 + +/* logging */ +extern int verbose; +#define LOG(...) do {if (verbose) {fprintf(stderr, __VA_ARGS__);}} while(0) +#define PERROR(...) do {if (verbose) {perror(__VA_ARGS__);}} while(0) + +extern int get_host_exec_fd(json_t * process, int *exec_fd); +extern int open_in_path(const char *name, int flags); +extern int sendfd(int socket, int *fd); +extern int recvfd(int socket, int *fd); + +#endif /* _libccon_h */ diff --git a/test/README.md b/test/README.md index ea7c8ae..b6dae16 100644 --- a/test/README.md +++ b/test/README.md @@ -28,24 +28,29 @@ digit. The first digit classifies the test: 2 - Namespace-configuration compliance. +3 - Interactions with related tools (like + [`ccon-cli`](../README.md#socket-communication)). + ## Dependencies -* A [POSIX shell][sh.1] for `sh`. +* A [POSIX shell][sh.1] for `sh` and [`wait`][wait.1]. * [GNU Core Utilities][coreutils] for [`cat`][cat.1], [`echo`][echo.1], [`env`][env.1], [`head`][head.1], [`id`][id.1], [`pwd`][pwd.1], - [`readlink`][readlink.1], [`touch`][touch.1], [`test`][test.1], and - [`tty`][tty.1]. + [`readlink`][readlink.1], [`sleep`][sleep.1], [`touch`][touch.1], + [`test`][test.1], [`timeout`][timeout.1], and [`tty`][tty.1]. * [Grep][] for [`grep`][grep.1]. * [net-tools][] for [`hostname`][hostname.1]. * [Sed][] for [`sed`][sed.1]. * [BusyBox][] for `busybox`. * [iproute2][] for [`ip`][ip.8]. +* [inotify-tools][] for [`inotifywait.1`][inotifywait.1]. * [libcap-ng][] for [`captest`][captest.8]. [BusyBox]: http://www.busybox.net/ [coreutils]: http://www.gnu.org/software/coreutils/coreutils.html [Grep]: https://www.gnu.org/software/grep/ [iproute2]: http://www.linuxfoundation.org/collaborate/workgroups/networking/iproute2 +[inotify-tools]: https://github.com/rvoicilas/inotify-tools/wiki [libcap-ng]: http://people.redhat.com/sgrubb/libcap-ng/ [net-tools]: http://net-tools.sourceforge.net/ [prove]: http://perldoc.perl.org/prove.html @@ -60,12 +65,16 @@ digit. The first digit classifies the test: [head.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/head.html [hostname.1]: http://man7.org/linux/man-pages/man1/hostname.1.html [id.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/id.html +[inotifywait.1]: http://man7.org/linux/man-pages/man1/inotifywait.1.html [pwd.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pwd.html [sed.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html [sh.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html +[sleep.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sleep.html [readlink.1]: http://man7.org/linux/man-pages/man1/readlink.1.html [test.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html +[timeout.1]: http://man7.org/linux/man-pages/man1/timeout.1.html [touch.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/touch.html [tty.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/tty.html +[wait.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/wait.html [captest.8]: https://fedorahosted.org/libcap-ng/browser/trunk/utils/captest.8 [ip.8]: https://git.kernel.org/cgit/linux/kernel/git/shemminger/iproute2.git/tree/man/man8/ip.8?id=v4.2.0 diff --git a/test/sharness.d/coreutils.sh b/test/sharness.d/coreutils.sh index 22d7856..b315fb7 100644 --- a/test/sharness.d/coreutils.sh +++ b/test/sharness.d/coreutils.sh @@ -14,5 +14,6 @@ # along with this program. If not, see . command -v readlink >/dev/null 2>/dev/null && test_set_prereq READLINK +command -v timeout >/dev/null 2>/dev/null && test_set_prereq TIMEOUT true diff --git a/test/sharness.d/inotify.sh b/test/sharness.d/inotify.sh new file mode 100644 index 0000000..c966757 --- /dev/null +++ b/test/sharness.d/inotify.sh @@ -0,0 +1,18 @@ +# Copyright (C) 2016 W. Trevor King +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +command -v inotifywait >/dev/null 2>/dev/null && test_set_prereq INOTIFYWAIT + +true diff --git a/test/sharness.d/posix-utilities.sh b/test/sharness.d/posix-utilities.sh index 9ec2c06..8aa6ff7 100644 --- a/test/sharness.d/posix-utilities.sh +++ b/test/sharness.d/posix-utilities.sh @@ -22,8 +22,10 @@ command -v id >/dev/null 2>/dev/null && test_set_prereq ID command -v pwd >/dev/null 2>/dev/null && test_set_prereq PWD command -v sed >/dev/null 2>/dev/null && test_set_prereq SED command -v sh >/dev/null 2>/dev/null && test_set_prereq SHELL +command -v sleep >/dev/null 2>/dev/null && test_set_prereq SLEEP command -v test >/dev/null 2>/dev/null && test_set_prereq TEST command -v touch >/dev/null 2>/dev/null && test_set_prereq TOUCH command -v tty >/dev/null 2>/dev/null && test_set_prereq TTY +command -v wait >/dev/null 2>/dev/null && test_set_prereq WAIT true diff --git a/test/t3001-start-socket.t b/test/t3001-start-socket.t new file mode 100755 index 0000000..b0324a8 --- /dev/null +++ b/test/t3001-start-socket.t @@ -0,0 +1,149 @@ +#!/bin/sh +# +# Copyright (C) 2016 W. Trevor King +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +test_description='Test start socket' + +. ./sharness.sh + +test_expect_success BUSYBOX,TIMEOUT 'Test socket blocks without start' " + test_expect_code 124 timeout 1 ccon --socket sock --config-string '{ + \"version\": \"0.1.0\", + \"process\": { + \"args\": [\"busybox\", \"echo\", \"hello\"] + } + }' +" + +test_expect_success BUSYBOX,ECHO,GREP,INOTIFYWAIT,SLEEP,WAIT 'Test start with empty config' " + mkdir -p sock && + > wait && + ( + inotifywait -e create sock 2>>wait && + ccon-cli --socket sock/sock --config-string '' + ) & + while ! grep '^Watches established.$' wait + do + sleep 0 + done && + ccon --socket sock/sock --config-string '{ + \"version\": \"0.1.0\", + \"process\": { + \"args\": [\"busybox\", \"echo\", \"hello\"] + } + }' >actual && + wait && + echo 'hello' >expected && + test_cmp expected actual +" + +test_expect_success BUSYBOX,ECHO,GREP,INOTIFYWAIT,SLEEP,WAIT 'Test start with process config' " + mkdir -p sock && + > wait && + ( + inotifywait -e create sock 2>>wait && + ccon-cli --socket sock/sock --config-string '{ + \"args\": [\"busybox\", \"echo\", \"goodbye\"] + }' + ) & + while ! grep '^Watches established.$' wait + do + sleep 0 + done && + ccon --socket sock/sock --config-string '{ + \"version\": \"0.1.0\" + }' >actual && + wait && + echo 'goodbye' >expected && + test_cmp expected actual +" + +test_expect_success BUSYBOX,ECHO,GREP,ID,INOTIFYWAIT,SLEEP,WAIT 'Test start with process.host' " + mkdir -p sock && + > wait && + ( + inotifywait -e create sock 2>>wait && + ccon-cli --socket sock/sock --config-string '{ + \"host\": true, + \"args\": [\"busybox\", \"echo\", \"goodbye\"] + }' + ) & + while ! grep '^Watches established.$' wait + do + sleep 0 + done && + ccon --socket sock/sock --config-string '{ + \"version\": \"0.2.0\", + \"namespaces\": { + \"user\": { + \"setgroups\": false, + \"uidMappings\": [ + { + \"containerID\": 0, + \"hostID\": $(id -u), + \"size\": 1 + } + ], + \"gidMappings\": [ + { + \"containerID\": 0, + \"hostID\": $(id -u), + \"size\": 1 + } + ] + }, + \"mount\": { + \"mounts\": [ + { + \"source\": \".\", + \"target\": \".\", + \"flags\": [ + \"MS_BIND\" + ] + }, + { + \"source\": \".\", + \"type\": \"pivot-root\" + } + ] + } + } + }' >actual && + wait && + echo 'goodbye' >expected && + test_cmp expected actual +" + +test_expect_success BUSYBOX,ECHO,GREP,INOTIFYWAIT,SLEEP,WAIT 'Test recover container process exit code' " + mkdir -p sock && + > wait && + ( + inotifywait -e create sock 2>>wait && + ccon-cli --socket sock/sock --config-string '{ + \"args\": [\"busybox\", \"sh\", \"-c\", \"exit 124\"] + }' + ) & + while ! grep '^Watches established.$' wait + do + sleep 0 + done && + test_expect_code 124 ccon --socket sock/sock --config-string '{ + \"version\": \"0.1.0\" + }' && + wait +" + +test_done