nspawn has been called "chroot on steroids". Continue that tradition by supporting target directories that are not root directories.
This patch handles the simple case: a static binary. --- Makefile.am | 2 + man/systemd-nspawn.xml | 11 +++++ src/nspawn/elf.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++ src/nspawn/elf.h | 30 +++++++++++ src/nspawn/nspawn.c | 47 +++++++++++++++--- src/shared/path-util.c | 57 +++++++++++++++------ src/shared/path-util.h | 5 +- 7 files changed, 260 insertions(+), 23 deletions(-) create mode 100644 src/nspawn/elf.c create mode 100644 src/nspawn/elf.h diff --git a/Makefile.am b/Makefile.am index 7a45029..67c26f4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1832,6 +1832,8 @@ systemd_cgtop_LDADD = \ # ------------------------------------------------------------------------------ systemd_nspawn_SOURCES = \ src/nspawn/nspawn.c \ + src/nspawn/elf.c \ + src/nspawn/elf.h \ src/core/mount-setup.c \ src/core/mount-setup.h \ src/core/loopback-setup.c \ diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 75d2e6d..24bc0d7 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -211,6 +211,17 @@ </varlistentry> <varlistentry> + <term><option>-p</option></term> + <term><option>--populate</option></term> + + <listitem><para>If COMMAND does not exist in + target root directory, launch host COMMAND.</para> + + <para>Can be used on empty target directories + (if COMMAND a static executable).</para></listitem> + </varlistentry> + + <varlistentry> <term><option>-u</option></term> <term><option>--user=</option></term> diff --git a/src/nspawn/elf.c b/src/nspawn/elf.c new file mode 100644 index 0000000..f91b374 --- /dev/null +++ b/src/nspawn/elf.c @@ -0,0 +1,131 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Shawn Landden + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + + +#include <elf.h> +#include <fcntl.h> + +#include "elf.h" +#include "util.h" +#include "log.h" + +int analyze_executable(const char *path, + int *_fd, + bool *_elf64, + char **_linker, + char **shebang) { + + char e_ident[sizeof(Elf64_Ehdr)]; + uint16_t e_type; + off_t e_phoff; + uint16_t e_phentsize, e_phnum; + bool elf64; + int fd = -1; + bool have_interp = false; + int r; + + assert(path); + assert(_elf64); + assert(_linker); + assert(shebang); + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + log_error("open(\"%s\") failed: %m", path); + return -errno; + } + + r = read(fd, e_ident, sizeof(Elf64_Ehdr)); + if (r < 0) { + log_error("read() on %s failed: %m", path); + return -errno; + } + + if (memcmp(e_ident, ELFMAG, SELFMAG) != 0) { + log_error("%s is not an ELF executable.", path); + return -ENOSYS; + } else + *shebang = NULL; + + switch (e_ident[EI_CLASS]) { + case ELFCLASS32: + elf64 = false; + break; + case ELFCLASS64: + elf64 = true; + break; + default: + log_error("Unknown ELF class."); + return -EINVAL; + } + + if (elf64) { + Elf64_Ehdr *ehdr = (Elf64_Ehdr *)&e_ident; + + e_type = ehdr->e_type; + e_phoff = ehdr->e_phoff; + e_phentsize = ehdr->e_phentsize; + e_phnum = ehdr->e_phnum; + } else { + Elf32_Ehdr *ehdr = (Elf32_Ehdr *)&e_ident; + + e_type = ehdr->e_type; + e_phoff = ehdr->e_phoff; + e_phentsize = ehdr->e_phentsize; + e_phnum = ehdr->e_phnum; + } + + /* Not checking e_ident[E_DATA], file is assumed to be of host endianness. */ + if (e_type != ET_EXEC) { + log_error("%s is not an ELF executable, or is of alien endianness.", path); + return -EINVAL; + } + + char phdrs[e_phentsize * e_phnum]; + char *phdr; + + lseek(fd, e_phoff, SEEK_SET); + r = read(fd, &phdrs, e_phentsize * e_phnum); + if (r < 0) { + log_error("read() on %s failed: %m", path); + return -errno; + } + + for (int i = 0; i < e_phnum; i++) { + phdr = ((char *)&phdrs + i * e_phentsize); + + /* p_type is the same offset and size regardless + * of 32 or 64-bit, so this works for 64-bit too */ + if (((Elf32_Phdr *)phdr)->p_type == PT_INTERP) { + have_interp = true; + break; + } + } + + if (!have_interp) { + *_elf64 = elf64; + *_linker = NULL; + return 0; + } + + /* is dynamic executable */ + return -ENOSYS; +} diff --git a/src/nspawn/elf.h b/src/nspawn/elf.h new file mode 100644 index 0000000..55f8566 --- /dev/null +++ b/src/nspawn/elf.h @@ -0,0 +1,30 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Shawn Landden + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +int analyze_executable(const char *path, + int *_fd, + bool *_elf64, + char **linker, + char **shebang); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 0151cf3..24a48e8 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -62,6 +62,7 @@ #include "bus-error.h" #include "ptyfwd.h" #include "bus-kernel.h" +#include "elf.h" #ifndef TTY_GID #define TTY_GID 5 @@ -74,6 +75,7 @@ typedef enum LinkJournal { LINK_GUEST } LinkJournal; +static bool arg_populate = false; static char *arg_directory = NULL; static char *arg_user = NULL; static sd_id128_t arg_uuid = {}; @@ -114,12 +116,13 @@ static char **arg_bind_ro = NULL; static int help(void) { - printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" + printf("%s [OPTIONS...] [COMMAND [ARGS...]]\n\n" "Spawn a minimal namespace container for debugging, testing and building.\n\n" " -h --help Show this help\n" " --version Print version string\n" " -D --directory=NAME Root directory for the container\n" " -b --boot Boot up full system (i.e. invoke init)\n" + " -p --populate Runs COMMAND from the host\n" " -u --user=USER Run the command under specified user or uid\n" " --uuid=UUID Set a specific machine UUID for the container\n" " -M --machine=NAME Set the machine name for the container\n" @@ -169,6 +172,7 @@ static int parse_argv(int argc, char *argv[]) { { "bind-ro", required_argument, NULL, ARG_BIND_RO }, { "machine", required_argument, NULL, 'M' }, { "slice", required_argument, NULL, 'S' }, + { "populate", no_argument, NULL, 'p' }, {} }; @@ -177,7 +181,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "+hD:u:bM:jS:", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "+hD:u:bM:jS:p", options, NULL)) >= 0) { switch (c) { @@ -333,6 +337,10 @@ static int parse_argv(int argc, char *argv[]) { break; } + case 'p': + arg_populate = true; + break; + case '?': return -EINVAL; @@ -341,6 +349,11 @@ static int parse_argv(int argc, char *argv[]) { } } + if (arg_boot && arg_populate) { + log_error("--boot is incompatible with --populate"); + return -EINVAL; + } + return 1; } @@ -1048,13 +1061,13 @@ static bool audit_enabled(void) { int main(int argc, char *argv[]) { pid_t pid = 0; int r = EXIT_FAILURE, k; - _cleanup_close_ int master = -1, kdbus_fd = -1; + _cleanup_close_ int master = -1, kdbus_fd = -1, exec_fd = -1; int n_fd_passed; const char *console = NULL; sigset_t mask; _cleanup_close_pipe_ int kmsg_socket_pair[2] = { -1, -1 }; _cleanup_fdset_free_ FDSet *fds = NULL; - _cleanup_free_ char *kdbus_namespace = NULL; + _cleanup_free_ char *kdbus_namespace = NULL, *proc_exec = NULL; log_parse_environment(); log_open(); @@ -1119,7 +1132,7 @@ int main(int argc, char *argv[]) { goto finish; } - if (path_is_os_tree(arg_directory) <= 0) { + if (path_is_os_tree(arg_directory) <= 0 && !arg_populate) { log_error("Directory %s doesn't look like an OS root directory (/etc/os-release is missing). Refusing.", arg_directory); goto finish; } @@ -1166,6 +1179,23 @@ int main(int argc, char *argv[]) { goto finish; } + if (arg_populate && argc > optind && find_binary_path_root(argv[optind], DEFAULT_PATH_SPLIT_USR, arg_directory, NULL) == -ENOENT) { + _cleanup_free_ char *t = NULL, *linker = NULL, *shebang = NULL; + bool elf64; + + k = find_binary(argv[optind], &t); + if (k < 0) { + log_error("COMMAND %s not available in host or target: %s", argv[optind], strerror(-r)); + goto finish; + } + + k = analyze_executable(t, &exec_fd, &elf64, &linker, &shebang); + if (k < 0) { + printf("analyze failed: %m");/* nothing */ + } else + asprintf(&proc_exec, "/proc/self/fd/%d", exec_fd); + } + sd_notify(0, "READY=1"); assert_se(sigemptyset(&mask) == 0); @@ -1192,7 +1222,7 @@ int main(int argc, char *argv[]) { gid_t gid = (gid_t) -1; unsigned n_env = 2; const char *envp[] = { - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + DEFAULT_PATH_SPLIT_USR, "container=systemd-nspawn", /* LXC sets container=lxc, so follow the scheme here */ NULL, /* TERM */ NULL, /* HOME */ @@ -1449,7 +1479,10 @@ int main(int argc, char *argv[]) { a[0] = (char*) "/sbin/init"; execve(a[0], a, (char**) envp); } else if (argc > optind) - execvpe(argv[optind], argv + optind, (char**) envp); + if (proc_exec) + execve(proc_exec, argv + optind, (char**) envp); + else + execvpe(argv[optind], argv + optind, (char**) envp); else { chdir(home ? home : "/root"); execle("/bin/bash", "-bash", NULL, (char**) envp); diff --git a/src/shared/path-util.c b/src/shared/path-util.c index 6c4efbf..882ddde 100644 --- a/src/shared/path-util.c +++ b/src/shared/path-util.c @@ -426,9 +426,10 @@ int path_is_os_tree(const char *path) { return r < 0 ? 0 : 1; } -int find_binary(const char *name, char **filename) { +int find_binary_path_root(const char *name, const char *path, const char *root, char **filename) { assert(name); - assert(filename); + assert(path); + assert(root); if (strchr(name, '/')) { char *p; @@ -440,25 +441,34 @@ int find_binary(const char *name, char **filename) { if (!p) return -ENOMEM; - *filename = p; + if (*root != '\0') { + p = realloc(p, strlen(p) + strlen(root) + 1); + if (!p) + return -ENOMEM; + + memmove(p + strlen(root), p, strlen(p) + 1); + memcpy(p, root, strlen(root)); + } + + if (access(p, X_OK) < 0) { + free(p); + return -ENOENT; + } + + if (filename) + *filename = p; + else + free(p); + return 0; } else { - const char *path; char *state, *w; size_t l; - /** - * Plain getenv, not secure_getenv, because we want - * to actually allow the user to pick the binary. - */ - path = getenv("PATH"); - if (!path) - path = DEFAULT_PATH; - FOREACH_WORD_SEPARATOR(w, l, path, ":", state) { char *p; - if (asprintf(&p, "%.*s/%s", (int) l, w, name) < 0) + if (asprintf(&p, "%s/%.*s/%s", root, (int) l, w, name) < 0) return -ENOMEM; if (access(p, X_OK) < 0) { @@ -466,8 +476,11 @@ int find_binary(const char *name, char **filename) { continue; } - path_kill_slashes(p); - *filename = p; + if (filename) { + path_kill_slashes(p); + *filename = p; + } else + free(p); return 0; } @@ -476,6 +489,20 @@ int find_binary(const char *name, char **filename) { } } +int find_binary(const char *name, char **filename) { + const char *path; + + /** + * Plain getenv, not secure_getenv, because we want + * to actually allow the user to pick the binary. + */ + path = getenv("PATH"); + if (!path) + path = DEFAULT_PATH; + + return find_binary_path_root(name, path, "", filename); +} + bool paths_check_timestamp(char **paths, usec_t *timestamp, bool update) { bool changed = false; char **i; diff --git a/src/shared/path-util.h b/src/shared/path-util.h index 42b4189..103c245 100644 --- a/src/shared/path-util.h +++ b/src/shared/path-util.h @@ -26,8 +26,10 @@ #include "macro.h" #include "time-util.h" +#define DEFAULT_PATH_SPLIT_USR "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + #ifdef HAVE_SPLIT_USR -# define DEFAULT_PATH "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +# define DEFAULT_PATH DEFAULT_PATH_SPLIT_USR #else # define DEFAULT_PATH "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" #endif @@ -51,6 +53,7 @@ int path_is_mount_point(const char *path, bool allow_symlink); int path_is_read_only_fs(const char *path); int path_is_os_tree(const char *path); +int find_binary_path_root(const char *name, const char *path, const char *root, char **filename); int find_binary(const char *name, char **filename); bool paths_check_timestamp(char **paths, usec_t *paths_ts_usec, bool update); -- 1.8.4.4 _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel