chiark / gitweb /
DEFAULT_PATH_SPLIT_USR macro
[elogind.git] / src / nspawn / nspawn.c
index 2778cd8411a047c1cc90c7df7713ed55eca29d4f..241b4b5393a1f81988a277583db61fd722cd50b4 100644 (file)
@@ -61,6 +61,8 @@
 #include "bus-util.h"
 #include "bus-error.h"
 #include "ptyfwd.h"
+#include "bus-kernel.h"
+#include "env-util.h"
 
 #ifndef TTY_GID
 #define TTY_GID 5
@@ -110,6 +112,7 @@ static uint64_t arg_retain =
         (1ULL << CAP_AUDIT_CONTROL);
 static char **arg_bind = NULL;
 static char **arg_bind_ro = NULL;
+static char **arg_setenv = NULL;
 
 static int help(void) {
 
@@ -127,11 +130,13 @@ static int help(void) {
                "     --read-only           Mount the root directory read-only\n"
                "     --capability=CAP      In addition to the default, retain specified\n"
                "                           capability\n"
+               "     --drop-capability=CAP Drop the specified capability from the default set\n"
                "     --link-journal=MODE   Link up guest journal, one of no, auto, guest, host\n"
                "  -j                       Equivalent to --link-journal=host\n"
                "     --bind=PATH[:PATH]    Bind mount a file or directory from the host into\n"
                "                           the container\n"
-               "     --bind-ro=PATH[:PATH] Similar, but creates a read-only bind mount\n",
+               "     --bind-ro=PATH[:PATH] Similar, but creates a read-only bind mount\n"
+               "     --setenv=NAME=VALUE   Pass an environment variable to PID 1\n",
                program_invocation_short_name);
 
         return 0;
@@ -145,9 +150,11 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_UUID,
                 ARG_READ_ONLY,
                 ARG_CAPABILITY,
+                ARG_DROP_CAPABILITY,
                 ARG_LINK_JOURNAL,
                 ARG_BIND,
-                ARG_BIND_RO
+                ARG_BIND_RO,
+                ARG_SETENV,
         };
 
         static const struct option options[] = {
@@ -160,11 +167,13 @@ static int parse_argv(int argc, char *argv[]) {
                 { "uuid",            required_argument, NULL, ARG_UUID            },
                 { "read-only",       no_argument,       NULL, ARG_READ_ONLY       },
                 { "capability",      required_argument, NULL, ARG_CAPABILITY      },
+                { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY },
                 { "link-journal",    required_argument, NULL, ARG_LINK_JOURNAL    },
                 { "bind",            required_argument, NULL, ARG_BIND            },
                 { "bind-ro",         required_argument, NULL, ARG_BIND_RO         },
                 { "machine",         required_argument, NULL, 'M'                 },
                 { "slice",           required_argument, NULL, 'S'                 },
+                { "setenv",          required_argument, NULL, ARG_SETENV          },
                 {}
         };
 
@@ -189,7 +198,7 @@ static int parse_argv(int argc, char *argv[]) {
                         free(arg_directory);
                         arg_directory = canonicalize_file_name(optarg);
                         if (!arg_directory) {
-                                log_error("Failed to canonicalize root directory.");
+                                log_error("Invalid root directory: %m");
                                 return -ENOMEM;
                         }
 
@@ -243,7 +252,8 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_read_only = true;
                         break;
 
-                case ARG_CAPABILITY: {
+                case ARG_CAPABILITY:
+                case ARG_DROP_CAPABILITY: {
                         char *state, *word;
                         size_t length;
 
@@ -262,7 +272,11 @@ static int parse_argv(int argc, char *argv[]) {
                                 }
 
                                 free(t);
-                                arg_retain |= 1ULL << (uint64_t) cap;
+
+                                if (c == ARG_CAPABILITY)
+                                        arg_retain |= 1ULL << (uint64_t) cap;
+                                else
+                                        arg_retain &= ~(1ULL << (uint64_t) cap);
                         }
 
                         break;
@@ -324,6 +338,23 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
+                case ARG_SETENV: {
+                        char **n;
+
+                        if (!env_assignment_is_valid(optarg)) {
+                                log_error("Environment variable assignment '%s' is not valid.", optarg);
+                                return -EINVAL;
+                        }
+
+                        n = strv_env_set(arg_setenv, optarg);
+                        if (!n)
+                                return log_oom();
+
+                        strv_free(arg_setenv);
+                        arg_setenv = n;
+                        break;
+                }
+
                 case '?':
                         return -EINVAL;
 
@@ -409,39 +440,46 @@ static int mount_binds(const char *dest, char **l, unsigned long flags) {
         char **x, **y;
 
         STRV_FOREACH_PAIR(x, y, l) {
-                _cleanup_free_ char *where = NULL;
+                char *where;
                 struct stat source_st, dest_st;
+                int r;
 
                 if (stat(*x, &source_st) < 0) {
                         log_error("failed to stat %s: %m", *x);
                         return -errno;
                 }
 
-                where = strjoin(dest, "/", *y, NULL);
-                if (!where)
-                        return log_oom();
-
-                if (stat(where, &dest_st) == 0) {
+                where = strappenda(dest, *y);
+                r = stat(where, &dest_st);
+                if (r == 0) {
                         if ((source_st.st_mode & S_IFMT) != (dest_st.st_mode & S_IFMT)) {
                                 log_error("The file types of %s and %s do not match. Refusing bind mount",
                                                 *x, where);
                                 return -EINVAL;
                         }
-                } else {
-                        /* Create the mount point, but be conservative -- refuse to create block
-                         * and char devices. */
-                        if (S_ISDIR(source_st.st_mode))
-                                mkdir_p_label(where, 0755);
-                        else if (S_ISFIFO(source_st.st_mode))
-                                mkfifo(where, 0644);
-                        else if (S_ISSOCK(source_st.st_mode))
-                                mknod(where, 0644 | S_IFSOCK, 0);
-                        else if (S_ISREG(source_st.st_mode))
-                                touch(where);
-                        else {
-                                log_error("Refusing to create mountpoint for file: %s", *x);
-                                return -ENOTSUP;
+                } else if (errno == ENOENT) {
+                        r = mkdir_parents_label(where, 0755);
+                        if (r < 0) {
+                                log_error("Failed to bind mount %s: %s", *x, strerror(-r));
+                                return r;
                         }
+                } else {
+                        log_error("Failed to bind mount %s: %s", *x, strerror(errno));
+                        return -errno;
+                }
+                /* Create the mount point, but be conservative -- refuse to create block
+                * and char devices. */
+                if (S_ISDIR(source_st.st_mode))
+                        mkdir_label(where, 0755);
+                else if (S_ISFIFO(source_st.st_mode))
+                        mkfifo(where, 0644);
+                else if (S_ISSOCK(source_st.st_mode))
+                        mknod(where, 0644 | S_IFSOCK, 0);
+                else if (S_ISREG(source_st.st_mode))
+                        touch(where);
+                else {
+                        log_error("Refusing to create mountpoint for file: %s", *x);
+                        return -ENOTSUP;
                 }
 
                 if (mount(*x, where, "bind", MS_BIND, NULL) < 0) {
@@ -788,14 +826,11 @@ static int setup_hostname(void) {
 }
 
 static int setup_journal(const char *directory) {
-        sd_id128_t machine_id;
+        sd_id128_t machine_id, this_id;
         _cleanup_free_ char *p = NULL, *b = NULL, *q = NULL, *d = NULL;
         char *id;
         int r;
 
-        if (arg_link_journal == LINK_NO)
-                return 0;
-
         p = strappend(directory, "/etc/machine-id");
         if (!p)
                 return log_oom();
@@ -819,6 +854,24 @@ static int setup_journal(const char *directory) {
                 return r;
         }
 
+        r = sd_id128_get_machine(&this_id);
+        if (r < 0) {
+                log_error("Failed to retrieve machine ID: %s", strerror(-r));
+                return r;
+        }
+
+        if (sd_id128_equal(machine_id, this_id)) {
+                log_full(arg_link_journal == LINK_AUTO ? LOG_WARNING : LOG_ERR,
+                         "Host and machine ids are equal (%s): refusing to link journals", id);
+                if (arg_link_journal == LINK_AUTO)
+                        return 0;
+                return
+                        -EEXIST;
+        }
+
+        if (arg_link_journal == LINK_NO)
+                return 0;
+
         free(p);
         p = strappend("/var/log/journal/", id);
         q = strjoin(directory, "/var/log/journal/", id, NULL);
@@ -919,6 +972,26 @@ static int setup_journal(const char *directory) {
         return 0;
 }
 
+static int setup_kdbus(const char *dest, const char *path) {
+        const char *p;
+
+        if (!path)
+                return 0;
+
+        p = strappenda(dest, "/dev/kdbus");
+        if (mkdir(p, 0755) < 0) {
+                log_error("Failed to create kdbus path: %m");
+                return  -errno;
+        }
+
+        if (mount(path, p, "bind", MS_BIND, NULL) < 0) {
+                log_error("Failed to mount kdbus namespace path: %m");
+                return -errno;
+        }
+
+        return 0;
+}
+
 static int drop_capabilities(void) {
         return capability_bounding_set_drop(~arg_retain, false);
 }
@@ -1024,12 +1097,14 @@ 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;
+        _cleanup_close_ int master = -1, kdbus_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;
+        const char *ns;
 
         log_parse_environment();
         log_open();
@@ -1059,7 +1134,7 @@ int main(int argc, char *argv[]) {
         path_kill_slashes(arg_directory);
 
         if (!arg_machine) {
-                arg_machine = strdup(path_get_file_name(arg_directory));
+                arg_machine = strdup(basename(arg_directory));
                 if (!arg_machine) {
                         log_oom();
                         goto finish;
@@ -1130,6 +1205,13 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
+        ns = strappenda("machine-", arg_machine);
+        kdbus_fd = bus_kernel_create_namespace(ns, &kdbus_namespace);
+        if (r < 0)
+                log_debug("Failed to create kdbus namespace: %s", strerror(-r));
+        else
+                log_debug("Successfully created kdbus namespace as %s", kdbus_namespace);
+
         if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) {
                 log_error("Failed to create kmsg socket pair.");
                 goto finish;
@@ -1161,7 +1243,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",
+                                "PATH=" DEFAULT_PATH_SPLIT_USR,
                                 "container=systemd-nspawn", /* LXC sets container=lxc, so follow the scheme here */
                                 NULL, /* TERM */
                                 NULL, /* HOME */
@@ -1172,6 +1254,7 @@ int main(int argc, char *argv[]) {
                                 NULL, /* LISTEN_PID */
                                 NULL
                         };
+                        char **env_use;
 
                         envp[n_env] = strv_find_prefix(environ, "TERM=");
                         if (envp[n_env])
@@ -1281,6 +1364,9 @@ int main(int argc, char *argv[]) {
                         if (mount_binds(arg_directory, arg_bind_ro, MS_RDONLY) < 0)
                                 goto child_fail;
 
+                        if (setup_kdbus(arg_directory, kdbus_namespace) < 0)
+                                goto child_fail;
+
                         if (chdir(arg_directory) < 0) {
                                 log_error("chdir(%s) failed: %m", arg_directory);
                                 goto child_fail;
@@ -1396,6 +1482,19 @@ int main(int argc, char *argv[]) {
 
                         setup_hostname();
 
+                        if (!strv_isempty(arg_setenv)) {
+                                char **n;
+
+                                n = strv_env_merge(2, envp, arg_setenv);
+                                if (!n) {
+                                        log_oom();
+                                        goto child_fail;
+                                }
+
+                                env_use = n;
+                        } else
+                                env_use = (char**) envp;
+
                         if (arg_boot) {
                                 char **a;
                                 size_t l;
@@ -1407,18 +1506,18 @@ int main(int argc, char *argv[]) {
                                 memcpy(a + 1, argv + optind, l * sizeof(char*));
 
                                 a[0] = (char*) "/usr/lib/systemd/systemd";
-                                execve(a[0], a, (char**) envp);
+                                execve(a[0], a, env_use);
 
                                 a[0] = (char*) "/lib/systemd/systemd";
-                                execve(a[0], a, (char**) envp);
+                                execve(a[0], a, env_use);
 
                                 a[0] = (char*) "/sbin/init";
-                                execve(a[0], a, (char**) envp);
+                                execve(a[0], a, env_use);
                         } else if (argc > optind)
-                                execvpe(argv[optind], argv + optind, (char**) envp);
+                                execvpe(argv[optind], argv + optind, env_use);
                         else {
                                 chdir(home ? home : "/root");
-                                execle("/bin/bash", "-bash", NULL, (char**) envp);
+                                execle("/bin/bash", "-bash", NULL, env_use);
                         }
 
                         log_error("execv() failed: %m");
@@ -1489,6 +1588,7 @@ finish:
 
         free(arg_directory);
         free(arg_machine);
+        free(arg_setenv);
 
         return r;
 }