chiark / gitweb /
nspawn: don't make assumptions about the size of pid_t
[elogind.git] / src / nspawn / nspawn.c
index 2fec9023a1a8a5212483138db3da5f02ed5ff109..9268cbc246c6797d9030de4ee4d4f3f1694e5cf4 100644 (file)
 #include "build.h"
 #include "fileio.h"
 
+#ifndef TTY_GID
+#define TTY_GID 5
+#endif
+
 typedef enum LinkJournal {
         LINK_NO,
         LINK_AUTO,
@@ -101,23 +105,30 @@ static uint64_t arg_retain =
         (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"
-               "  --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 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;
@@ -131,7 +142,9 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_UUID,
                 ARG_READ_ONLY,
                 ARG_CAPABILITY,
-                ARG_LINK_JOURNAL
+                ARG_LINK_JOURNAL,
+                ARG_BIND,
+                ARG_BIND_RO
         };
 
         static const struct option options[] = {
@@ -146,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                   }
         };
 
@@ -258,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;
 
@@ -287,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
@@ -303,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) {
@@ -326,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,
@@ -345,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;
@@ -479,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;
@@ -531,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;
@@ -1207,7 +1294,7 @@ int main(int argc, char *argv[]) {
                 siginfo_t status;
                 int pipefd[2];
 
-                if(pipe2(pipefd, O_NONBLOCK|O_CLOEXEC) < 0) {
+                if (pipe2(pipefd, O_NONBLOCK|O_CLOEXEC) < 0) {
                         log_error("pipe2(): %m");
                         goto finish;
                 }
@@ -1227,7 +1314,7 @@ int main(int argc, char *argv[]) {
                         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 */
@@ -1241,12 +1328,13 @@ 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(pipefd[1]);
+                        close_nointr_nofail(pipefd[1]);
                         fd_wait_for_event(pipefd[0], POLLHUP, -1);
-                        close(pipefd[0]);
+                        close_nointr_nofail(pipefd[0]);
 
                         close_nointr_nofail(master);
                         master = -1;
@@ -1323,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)
@@ -1346,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;
@@ -1492,9 +1589,9 @@ int main(int argc, char *argv[]) {
                         _exit(EXIT_FAILURE);
                 }
 
-                log_info("Init process in the container running as PID %d", pid);
-                close(pipefd[0]);
-                close(pipefd[1]);
+                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;