[systemd-devel] [PATCH 3/4] nspawn: --populate to run static binaries on empty target directory

Shawn Landden shawn at churchofgit.com
Sun Dec 1 14:50:16 PST 2013


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



More information about the systemd-devel mailing list