[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