X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Fnspawn%2Fnspawn.c;h=2a1f37bffcd50dca1a40bc3763e4f0827dc230e7;hb=2c21044f05e32ec483b6ab13e175278779e9ebe3;hp=9fc256e51d073f73d34664e4139b5a4cda36f41a;hpb=e58a12770c0c7b9571cc80f487d666151811c1ee;p=elogind.git diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 9fc256e51..2a1f37bff 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include @@ -53,7 +55,10 @@ static char *arg_directory = NULL; static char *arg_user = NULL; static char **arg_controllers = NULL; +static char *arg_uuid = NULL; static bool arg_private_network = false; +static bool arg_read_only = false; +static bool arg_boot = false; static int help(void) { @@ -61,9 +66,12 @@ static int help(void) { "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" - " --private-network Disable network in container\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", program_invocation_short_name); return 0; @@ -72,7 +80,9 @@ static int help(void) { static int parse_argv(int argc, char *argv[]) { enum { - ARG_PRIVATE_NETWORK = 0x100 + ARG_PRIVATE_NETWORK = 0x100, + ARG_UUID, + ARG_READ_ONLY }; static const struct option options[] = { @@ -81,6 +91,9 @@ static int parse_argv(int argc, char *argv[]) { { "user", required_argument, NULL, 'u' }, { "controllers", required_argument, NULL, 'C' }, { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, + { "boot", no_argument, NULL, 'b' }, + { "uuid", required_argument, NULL, ARG_UUID }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, { NULL, 0, NULL, 0 } }; @@ -89,7 +102,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "+hD:u:C:", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "+hD:u:C:b", options, NULL)) >= 0) { switch (c) { @@ -99,8 +112,9 @@ static int parse_argv(int argc, char *argv[]) { case 'D': free(arg_directory); - if (!(arg_directory = strdup(optarg))) { - log_error("Failed to duplicate root directory."); + arg_directory = canonicalize_file_name(optarg); + if (!arg_directory) { + log_error("Failed to canonicalize root directory."); return -ENOMEM; } @@ -130,6 +144,18 @@ static int parse_argv(int argc, char *argv[]) { arg_private_network = true; break; + case 'b': + arg_boot = true; + break; + + case ARG_UUID: + arg_uuid = optarg; + break; + + case ARG_READ_ONLY: + arg_read_only = true; + break; + case '?': return -EINVAL; @@ -245,6 +271,28 @@ static int setup_timezone(const char *dest) { return 0; } +static int setup_resolv_conf(const char *dest) { + char *where; + + assert(dest); + + if (arg_private_network) + return 0; + + /* Fix resolv.conf, if possible */ + if (asprintf(&where, "%s/etc/resolv.conf", dest) < 0) { + log_error("Out of memory"); + return -ENOMEM; + } + + if (mount("/etc/resolv.conf", where, "bind", MS_BIND, NULL) >= 0) + mount("/etc/resolv.conf", where, "bind", MS_BIND|MS_REMOUNT|MS_RDONLY, NULL); + + free(where); + + return 0; +} + static int copy_devnodes(const char *dest) { static const char devnodes[] = @@ -392,6 +440,13 @@ static int setup_kmsg(const char *dest, int kmsg_socket) { u = umask(0000); + /* We create the kmsg FIFO as /dev/kmsg, but immediately + * delete it after bind mounting it to /proc/kmsg. While FIFOs + * on the reading side behave very similar to /proc/kmsg, + * their writing side behaves differently from /dev/kmsg in + * that writing blocks when nothing is reading. In order to + * avoid any problems with containers deadlocking due to this + * we simply make /dev/kmsg unavailable to the container. */ if (asprintf(&from, "%s/dev/kmsg", dest) < 0) { log_error("Out of memory"); r = -ENOMEM; @@ -454,6 +509,9 @@ static int setup_kmsg(const char *dest, int kmsg_socket) { goto finish; } + /* And now make the FIFO unavailable as /dev/kmsg... */ + unlink(from); + finish: free(from); free(to); @@ -462,6 +520,28 @@ finish: return r; } +static int setup_hostname(void) { + char *hn; + int r = 0; + + hn = file_name_from_path(arg_directory); + if (hn) { + hn = strdup(hn); + if (!hn) + return -ENOMEM; + + hostname_cleanup(hn); + + if (!isempty(hn)) + if (sethostname(hn, strlen(hn)) < 0) + r = -errno; + + free(hn); + } + + return r; +} + static int drop_capabilities(void) { static const unsigned long retain[] = { CAP_CHOWN, @@ -860,7 +940,6 @@ int main(int argc, char *argv[]) { if (pid == 0) { /* child */ - const char *hn; const char *home = NULL; uid_t uid = (uid_t) -1; gid_t gid = (gid_t) -1; @@ -871,6 +950,7 @@ int main(int argc, char *argv[]) { NULL, /* HOME */ NULL, /* USER */ NULL, /* LOGNAME */ + NULL, /* container_uuid */ NULL }; @@ -899,6 +979,18 @@ int main(int argc, char *argv[]) { if (mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL) < 0) goto child_fail; + /* Turn directory into bind mount */ + if (mount(arg_directory, arg_directory, "bind", MS_BIND, NULL) < 0) { + log_error("Failed to make bind mount."); + goto child_fail; + } + + if (arg_read_only) + if (mount(arg_directory, arg_directory, "bind", MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) { + log_error("Failed to make read-only."); + goto child_fail; + } + if (mount_all(arg_directory) < 0) goto child_fail; @@ -916,6 +1008,9 @@ int main(int argc, char *argv[]) { if (setup_timezone(arg_directory) < 0) goto child_fail; + if (setup_resolv_conf(arg_directory) < 0) + goto child_fail; + if (chdir(arg_directory) < 0) { log_error("chdir(%s) failed: %m", arg_directory); goto child_fail; @@ -926,8 +1021,8 @@ int main(int argc, char *argv[]) { dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO) goto child_fail; - if (mount(arg_directory, "/", "bind", MS_BIND|MS_MOVE, NULL) < 0) { - log_error("mount(MS_MOVE) failed: %m"); + if (mount(arg_directory, "/", "bind", MS_MOVE, NULL) < 0) { + log_error("mount(MS_BIND) failed: %m"); goto child_fail; } @@ -981,17 +1076,41 @@ int main(int argc, char *argv[]) { } } - if ((asprintf((char**)(envp + 3), "HOME=%s", home? home: "/root") < 0) || - (asprintf((char**)(envp + 4), "USER=%s", arg_user? arg_user : "root") < 0) || - (asprintf((char**)(envp + 5), "LOGNAME=%s", arg_user? arg_user : "root") < 0)) { + if ((asprintf((char**)(envp + 3), "HOME=%s", home ? home: "/root") < 0) || + (asprintf((char**)(envp + 4), "USER=%s", arg_user ? arg_user : "root") < 0) || + (asprintf((char**)(envp + 5), "LOGNAME=%s", arg_user ? arg_user : "root") < 0)) { log_error("Out of memory"); goto child_fail; } - if ((hn = file_name_from_path(arg_directory))) - sethostname(hn, strlen(hn)); + if (arg_uuid) { + if (asprintf((char**)(envp + 6), "container_uuid=%s", arg_uuid) < 0) { + log_error("Out of memory"); + goto child_fail; + } + } + + setup_hostname(); + + if (arg_boot) { + char **a; + size_t l; + + /* Automatically search for the init system */ + + l = 1 + argc - optind; + a = newa(char*, l + 1); + memcpy(a + 1, argv + optind, l * sizeof(char*)); + + a[0] = (char*) "/usr/lib/systemd/systemd"; + execve(a[0], a, (char**) envp); + + a[0] = (char*) "/lib/systemd/systemd"; + execve(a[0], a, (char**) envp); - if (argc > optind) + a[0] = (char*) "/sbin/init"; + execve(a[0], a, (char**) envp); + } else if (argc > optind) execvpe(argv[optind], argv + optind, (char**) envp); else { chdir(home ? home : "/root");