chiark / gitweb /
nspawn: don't make assumptions about the size of pid_t
[elogind.git] / src / nspawn / nspawn.c
index f5fb59d2d4affb7f07713aa6ff1ce5a9c4a57193..9268cbc246c6797d9030de4ee4d4f3f1694e5cf4 100644 (file)
@@ -33,6 +33,7 @@
 #include <sys/prctl.h>
 #include <sys/capability.h>
 #include <getopt.h>
+#include <sys/poll.h>
 #include <sys/epoll.h>
 #include <termios.h>
 #include <sys/signalfd.h>
 #include "sd-id128.h"
 #include "dev-setup.h"
 #include "fdset.h"
+#include "build.h"
+#include "fileio.h"
+
+#ifndef TTY_GID
+#define TTY_GID 5
+#endif
 
 typedef enum LinkJournal {
         LINK_NO,
@@ -95,23 +102,33 @@ static uint64_t arg_retain =
         (1ULL << CAP_SYS_PTRACE) |
         (1ULL << CAP_SYS_TTY_CONFIG) |
         (1ULL << CAP_SYS_RESOURCE) |
-        (1ULL << CAP_SYS_BOOT);
+        (1ULL << CAP_SYS_BOOT) |
+        (1ULL << CAP_AUDIT_WRITE) |
+        (1ULL << CAP_AUDIT_CONTROL);
+static char **arg_bind = NULL;
+static char **arg_bind_ro = NULL;
 
 static int help(void) {
 
         printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n"
                "Spawn a minimal namespace container for debugging, testing and building.\n\n"
-               "  -h --help               Show this help\n"
-               "  -D --directory=NAME     Root directory for the container\n"
-               "  -b --boot               Boot up full system (i.e. invoke init)\n"
-               "  -u --user=USER          Run the command under specified user or uid\n"
-               "  -C --controllers=LIST   Put the container in specified comma-separated cgroup hierarchies\n"
-               "     --uuid=UUID          Set a specific machine UUID for the container\n"
-               "     --private-network    Disable network in container\n"
-               "     --read-only          Mount the root directory read-only\n"
-               "     --capability=CAP     In addition to the default, retain specified capability\n"
-               "     --link-journal=MODE  Link up guest journal, one of no, auto, guest, host\n"
-               "  -j                      Equivalent to --link-journal=host\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"
+               "  -u --user=USER           Run the command under specified user or uid\n"
+               "  -C --controllers=LIST    Put the container in specified comma-separated\n"
+               "                           cgroup hierarchies\n"
+               "     --uuid=UUID           Set a specific machine UUID for the container\n"
+               "     --private-network     Disable network in container\n"
+               "     --read-only           Mount the root directory read-only\n"
+               "     --capability=CAP      In addition to the default, retain specified\n"
+               "                           capability\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",
                program_invocation_short_name);
 
         return 0;
@@ -120,15 +137,19 @@ static int help(void) {
 static int parse_argv(int argc, char *argv[]) {
 
         enum {
-                ARG_PRIVATE_NETWORK = 0x100,
+                ARG_VERSION = 0x100,
+                ARG_PRIVATE_NETWORK,
                 ARG_UUID,
                 ARG_READ_ONLY,
                 ARG_CAPABILITY,
-                ARG_LINK_JOURNAL
+                ARG_LINK_JOURNAL,
+                ARG_BIND,
+                ARG_BIND_RO
         };
 
         static const struct option options[] = {
                 { "help",            no_argument,       NULL, 'h'                 },
+                { "version",         no_argument,       NULL, ARG_VERSION         },
                 { "directory",       required_argument, NULL, 'D'                 },
                 { "user",            required_argument, NULL, 'u'                 },
                 { "controllers",     required_argument, NULL, 'C'                 },
@@ -138,6 +159,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "read-only",       no_argument,       NULL, ARG_READ_ONLY       },
                 { "capability",      required_argument, NULL, ARG_CAPABILITY      },
                 { "link-journal",    required_argument, NULL, ARG_LINK_JOURNAL    },
+                { "bind",            required_argument, NULL, ARG_BIND            },
+                { "bind-ro",         required_argument, NULL, ARG_BIND_RO         },
                 { NULL,              0,                 NULL, 0                   }
         };
 
@@ -154,6 +177,11 @@ static int parse_argv(int argc, char *argv[]) {
                         help();
                         return 0;
 
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
                 case 'D':
                         free(arg_directory);
                         arg_directory = canonicalize_file_name(optarg);
@@ -245,6 +273,43 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case ARG_BIND:
+                case ARG_BIND_RO: {
+                        _cleanup_free_ char *a = NULL, *b = NULL;
+                        char *e;
+                        char ***x;
+                        int r;
+
+                        x = c == ARG_BIND ? &arg_bind : &arg_bind_ro;
+
+                        e = strchr(optarg, ':');
+                        if (e) {
+                                a = strndup(optarg, e - optarg);
+                                b = strdup(e + 1);
+                        } else {
+                                a = strdup(optarg);
+                                b = strdup(optarg);
+                        }
+
+                        if (!a || !b)
+                                return log_oom();
+
+                        if (!path_is_absolute(a) || !path_is_absolute(b)) {
+                                log_error("Invalid bind mount specification: %s", optarg);
+                                return -EINVAL;
+                        }
+
+                        r = strv_extend(x, a);
+                        if (r < 0)
+                                return r;
+
+                        r = strv_extend(x, b);
+                        if (r < 0)
+                                return r;
+
+                        break;
+                }
+
                 case '?':
                         return -EINVAL;
 
@@ -274,7 +339,7 @@ static int mount_all(const char *dest) {
                 { NULL,        "/proc/sys", NULL,    NULL,       MS_BIND|MS_RDONLY|MS_REMOUNT, true  },   /* Then, make it r/o */
                 { "sysfs",     "/sys",      "sysfs", NULL,       MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, true  },
                 { "tmpfs",     "/dev",      "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME,     true  },
-                { "/dev/pts",  "/dev/pts",  NULL,    NULL,       MS_BIND,                      true  },
+                { "devpts",    "/dev/pts",  "devpts","newinstance,ptmxmode=0666,mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, true },
                 { "tmpfs",     "/dev/shm",  "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true  },
                 { "tmpfs",     "/run",      "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true  },
 #ifdef HAVE_SELINUX
@@ -290,14 +355,9 @@ static int mount_all(const char *dest) {
                 char _cleanup_free_ *where = NULL;
                 int t;
 
-                if (asprintf(&where, "%s/%s", dest, mount_table[k].where) < 0) {
-                        log_oom();
-
-                        if (r == 0)
-                                r = -ENOMEM;
-
-                        break;
-                }
+                where = strjoin(dest, "/", mount_table[k].where, NULL);
+                if (!where)
+                        return log_oom();
 
                 t = path_is_mount_point(where, true);
                 if (t < 0) {
@@ -313,7 +373,7 @@ static int mount_all(const char *dest) {
                 if (mount_table[k].what && t > 0)
                         continue;
 
-                mkdir_p_label(where, 0755);
+                mkdir_p(where, 0755);
 
                 if (mount(mount_table[k].what,
                           where,
@@ -332,6 +392,32 @@ static int mount_all(const char *dest) {
         return r;
 }
 
+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;
+
+                where = strjoin(dest, "/", *y, NULL);
+                if (!where)
+                        return log_oom();
+
+                mkdir_p_label(where, 0755);
+
+                if (mount(*x, where, "bind", MS_BIND, NULL) < 0) {
+                        log_error("mount(%s) failed: %m", where);
+                        return -errno;
+                }
+
+                if (flags && mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|flags, NULL) < 0) {
+                        log_error("mount(%s) failed: %m", where);
+                        return -errno;
+                }
+        }
+
+        return 0;
+}
+
 static int setup_timezone(const char *dest) {
         _cleanup_free_ char *where = NULL, *p = NULL, *q = NULL, *check = NULL, *what = NULL;
         char *z, *y;
@@ -466,8 +552,7 @@ static int copy_devnodes(const char *dest) {
                 "full\0"
                 "random\0"
                 "urandom\0"
-                "tty\0"
-                "ptmx\0";
+                "tty\0";
 
         const char *d;
         int r = 0;
@@ -518,6 +603,21 @@ static int copy_devnodes(const char *dest) {
         return r;
 }
 
+static int setup_ptmx(const char *dest) {
+        _cleanup_free_ char *p = NULL;
+
+        p = strappend(dest, "/dev/ptmx");
+        if (!p)
+                return log_oom();
+
+        if (symlink("pts/ptmx", p) < 0) {
+                log_error("Failed to create /dev/ptmx symlink: %m");
+                return -errno;
+        }
+
+        return 0;
+}
+
 static int setup_dev_console(const char *dest, const char *console) {
         struct stat st;
         char _cleanup_free_ *to = NULL;
@@ -817,13 +917,18 @@ static int is_os_tree(const char *path) {
         return r < 0 ? 0 : 1;
 }
 
-static int process_pty(int master, sigset_t *mask) {
+static int process_pty(int master, pid_t pid, sigset_t *mask) {
 
         char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
         size_t in_buffer_full = 0, out_buffer_full = 0;
         struct epoll_event stdin_ev, stdout_ev, master_ev, signal_ev;
         bool stdin_readable = false, stdout_writable = false, master_readable = false, master_writable = false;
         int ep = -1, signal_fd = -1, r;
+        bool tried_orderly_shutdown = false;
+
+        assert(master >= 0);
+        assert(pid > 0);
+        assert(mask);
 
         fd_nonblock(STDIN_FILENO, 1);
         fd_nonblock(STDOUT_FILENO, 1);
@@ -869,8 +974,17 @@ static int process_pty(int master, sigset_t *mask) {
         signal_ev.events = EPOLLIN;
         signal_ev.data.fd = signal_fd;
 
-        if (epoll_ctl(ep, EPOLL_CTL_ADD, STDOUT_FILENO, &stdout_ev) < 0 ||
-            epoll_ctl(ep, EPOLL_CTL_ADD, master, &master_ev) < 0 ||
+        if (epoll_ctl(ep, EPOLL_CTL_ADD, STDOUT_FILENO, &stdout_ev) < 0) {
+                if (errno != EPERM) {
+                        log_error("Failed to register stdout in epoll: %m");
+                        r = -errno;
+                        goto finish;
+                }
+                /* stdout without epoll support. Likely redirected to regular file. */
+                stdout_writable = true;
+        }
+
+        if (epoll_ctl(ep, EPOLL_CTL_ADD, master, &master_ev) < 0 ||
             epoll_ctl(ep, EPOLL_CTL_ADD, signal_fd, &signal_ev) < 0) {
                 log_error("Failed to register fds in epoll: %m");
                 r = -errno;
@@ -940,6 +1054,14 @@ static int process_pty(int master, sigset_t *mask) {
                                                 /* The window size changed, let's forward that. */
                                                 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
                                                         ioctl(master, TIOCSWINSZ, &ws);
+                                        } else if (sfsi.ssi_signo == SIGTERM && arg_boot && !tried_orderly_shutdown) {
+
+                                                log_info("Trying to halt container. Send SIGTERM again to trigger immediate termination.");
+
+                                                /* This only works for systemd... */
+                                                tried_orderly_shutdown = true;
+                                                kill(pid, SIGRTMIN+3);
+
                                         } else {
                                                 r = 0;
                                                 goto finish;
@@ -1170,12 +1292,11 @@ int main(int argc, char *argv[]) {
 
         for (;;) {
                 siginfo_t status;
+                int pipefd[2];
 
-                if (saved_attr_valid) {
-                        if (tcsetattr(STDIN_FILENO, TCSANOW, &raw_attr) < 0) {
-                                log_error("Failed to set terminal attributes: %m");
-                                goto finish;
-                        }
+                if (pipe2(pipefd, O_NONBLOCK|O_CLOEXEC) < 0) {
+                        log_error("pipe2(): %m");
+                        goto finish;
                 }
 
                 pid = syscall(__NR_clone, SIGCHLD|CLONE_NEWIPC|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUTS|(arg_private_network ? CLONE_NEWNET : 0), NULL);
@@ -1190,11 +1311,10 @@ int main(int argc, char *argv[]) {
 
                 if (pid == 0) {
                         /* child */
-
                         const char *home = NULL;
                         uid_t uid = (uid_t) -1;
                         gid_t gid = (gid_t) -1;
-                        unsigned n_env = 0;
+                        unsigned n_env = 2;
                         const char *envp[] = {
                                 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                                 "container=systemd-nspawn", /* LXC sets container=lxc, so follow the scheme here */
@@ -1208,12 +1328,24 @@ int main(int argc, char *argv[]) {
                                 NULL
                         };
 
-                        envp[2] = strv_find_prefix(environ, "TERM=");
-                        n_env = 3;
+                        envp[n_env] = strv_find_prefix(environ, "TERM=");
+                        if (envp[n_env])
+                                n_env ++;
+
+                        close_nointr_nofail(pipefd[1]);
+                        fd_wait_for_event(pipefd[0], POLLHUP, -1);
+                        close_nointr_nofail(pipefd[0]);
 
                         close_nointr_nofail(master);
                         master = -1;
 
+                        if (saved_attr_valid) {
+                                if (tcsetattr(STDIN_FILENO, TCSANOW, &raw_attr) < 0) {
+                                        log_error("Failed to set terminal attributes: %m");
+                                        goto child_fail;
+                                }
+                        }
+
                         close_nointr(STDIN_FILENO);
                         close_nointr(STDOUT_FILENO);
                         close_nointr(STDERR_FILENO);
@@ -1279,6 +1411,9 @@ int main(int argc, char *argv[]) {
                         if (copy_devnodes(arg_directory) < 0)
                                 goto child_fail;
 
+                        if (setup_ptmx(arg_directory) < 0)
+                                goto child_fail;
+
                         dev_setup(arg_directory);
 
                         if (setup_dev_console(arg_directory, console) < 0)
@@ -1302,6 +1437,12 @@ int main(int argc, char *argv[]) {
                         if (setup_journal(arg_directory) < 0)
                                 goto child_fail;
 
+                        if (mount_binds(arg_directory, arg_bind, 0) < 0)
+                                goto child_fail;
+
+                        if (mount_binds(arg_directory, arg_bind_ro, MS_RDONLY) < 0)
+                                goto child_fail;
+
                         if (chdir(arg_directory) < 0) {
                                 log_error("chdir(%s) failed: %m", arg_directory);
                                 goto child_fail;
@@ -1448,10 +1589,14 @@ int main(int argc, char *argv[]) {
                         _exit(EXIT_FAILURE);
                 }
 
+                log_info("Init process in the container running as PID %lu.", (unsigned long) pid);
+                close_nointr_nofail(pipefd[0]);
+                close_nointr_nofail(pipefd[1]);
+
                 fdset_free(fds);
                 fds = NULL;
 
-                if (process_pty(master, &mask) < 0)
+                if (process_pty(master, pid, &mask) < 0)
                         goto finish;
 
                 if (saved_attr_valid)