nspawn.c revision 9875fd7875d433eea5c6e3319916e1be18722086
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering This file is part of systemd.
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering Copyright 2010 Lennart Poettering
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering systemd is free software; you can redistribute it and/or modify it
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering under the terms of the GNU Lesser General Public License as published by
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering (at your option) any later version.
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering systemd is distributed in the hope that it will be useful, but
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering Lesser General Public License for more details.
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering You should have received a copy of the GNU Lesser General Public License
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic const char *arg_selinux_context = NULL;
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic const char *arg_selinux_apifs_context = NULL;
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic bool arg_private_network = false;
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic bool arg_read_only = false;
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic bool arg_boot = false;
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic LinkJournal arg_link_journal = LINK_AUTO;
90b2de37b80603168f4e9c9c81cff7eea4efa21aZbigniew Jędrzejewski-Szmek (1ULL << CAP_DAC_READ_SEARCH) |
88231eb62cafc8bb51406919c8cf6019dc1ac916Thomas Hindoe Paaboel Andersenstatic char **arg_bind_ro = NULL;
88231eb62cafc8bb51406919c8cf6019dc1ac916Thomas Hindoe Paaboel Andersenstatic char **arg_setenv = NULL;
88231eb62cafc8bb51406919c8cf6019dc1ac916Thomas Hindoe Paaboel Andersenstatic bool arg_quiet = false;
88231eb62cafc8bb51406919c8cf6019dc1ac916Thomas Hindoe Paaboel Andersenstatic bool arg_share_system = false;
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic bool arg_register = true;
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic bool arg_keep_unit = false;
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic char **arg_network_interfaces = NULL;
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic bool arg_network_veth = false;
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poetteringstatic const char *arg_network_bridge = NULL;
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poetteringstatic unsigned long arg_personality = 0xffffffffLU;
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic int help(void) {
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n"
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poettering "Spawn a minimal namespace container for debugging, testing and building.\n\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " -h --help Show this help\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --version Print version string\n"
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poettering " -q --quiet Do not show status information\n"
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poettering " -D --directory=NAME Root directory for the container\n"
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poettering " -b --boot Boot up full system (i.e. invoke init)\n"
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poettering " -u --user=USER Run the command under specified user or uid\n"
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poettering " -M --machine=NAME Set the machine name for the container\n"
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poettering " --uuid=UUID Set a specific machine UUID for the container\n"
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poettering " -S --slice=SLICE Place the container in the specified slice\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --private-network Disable network in container\n"
dacd6cee76a08331b8c8616c5f30f70ee49aa2f9Lennart Poettering " --network-interface=INTERFACE\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " Assign an existing network interface to the\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " container\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --network-macvlan=INTERFACE\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " Create a macvlan network interface based on an\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " existing network interface to the container\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --network-veth Add a virtual ethernet connection between host\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " and container\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --network-bridge=INTERFACE\n"
c7b5eb98e8eeafe63a079ee3c51e9670872437aeLennart Poettering " Add a virtual ethernet connection between host\n"
c7b5eb98e8eeafe63a079ee3c51e9670872437aeLennart Poettering " and container and add it to an existing bridge on\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " -Z --selinux-context=SECLABEL\n"
de0671ee7fe465e108f62dcbbbe9366f81dd9e9aZbigniew Jędrzejewski-Szmek " Set the SELinux security context to be used by\n"
eecd1362f7f4de432483b5d77c56726c3621a83aLennart Poettering " processes in the container\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " -L --selinux-apifs-context=SECLABEL\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " Set the SELinux security context to be used by\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " API/tmpfs file systems in the container\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --capability=CAP In addition to the default, retain specified\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " capability\n"
cc3773810855956bad92337cee8fa193584ab62eLennart Poettering " --drop-capability=CAP Drop the specified capability from the default set\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --link-journal=MODE Link up guest journal, one of no, auto, guest, host\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " -j Equivalent to --link-journal=host\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --read-only Mount the root directory read-only\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --bind=PATH[:PATH] Bind mount a file or directory from the host into\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " the container\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --bind-ro=PATH[:PATH] Similar, but creates a read-only bind mount\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --setenv=NAME=VALUE Pass an environment variable to PID 1\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --share-system Share system namespaces with host\n"
de0671ee7fe465e108f62dcbbbe9366f81dd9e9aZbigniew Jędrzejewski-Szmek " --register=BOOLEAN Register container as machine\n"
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering " --keep-unit Do not register a scope for the machine, reuse\n"
de0671ee7fe465e108f62dcbbbe9366f81dd9e9aZbigniew Jędrzejewski-Szmek " the service unit nspawn is running in\n",
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poetteringstatic int parse_argv(int argc, char *argv[]) {
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering { "version", no_argument, NULL, ARG_VERSION },
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering { "directory", required_argument, NULL, 'D' },
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering { "user", required_argument, NULL, 'u' },
eecd1362f7f4de432483b5d77c56726c3621a83aLennart Poettering { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK },
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering { "uuid", required_argument, NULL, ARG_UUID },
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering { "read-only", no_argument, NULL, ARG_READ_ONLY },
cc3773810855956bad92337cee8fa193584ab62eLennart Poettering { "capability", required_argument, NULL, ARG_CAPABILITY },
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY },
eecd1362f7f4de432483b5d77c56726c3621a83aLennart Poettering { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL },
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering { "bind", required_argument, NULL, ARG_BIND },
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering { "bind-ro", required_argument, NULL, ARG_BIND_RO },
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering { "machine", required_argument, NULL, 'M' },
eecd1362f7f4de432483b5d77c56726c3621a83aLennart Poettering { "slice", required_argument, NULL, 'S' },
eecd1362f7f4de432483b5d77c56726c3621a83aLennart Poettering { "setenv", required_argument, NULL, ARG_SETENV },
eecd1362f7f4de432483b5d77c56726c3621a83aLennart Poettering { "selinux-context", required_argument, NULL, 'Z' },
eecd1362f7f4de432483b5d77c56726c3621a83aLennart Poettering { "selinux-apifs-context", required_argument, NULL, 'L' },
a34faf579d2be139b0b9e8cd0c73ad4d918ef736Lukas Nykryn { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM },
a34faf579d2be139b0b9e8cd0c73ad4d918ef736Lukas Nykryn { "register", required_argument, NULL, ARG_REGISTER },
cc3773810855956bad92337cee8fa193584ab62eLennart Poettering { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT },
a34faf579d2be139b0b9e8cd0c73ad4d918ef736Lukas Nykryn { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE },
eecd1362f7f4de432483b5d77c56726c3621a83aLennart Poettering { "network-macvlan", required_argument, NULL, ARG_NETWORK_MACVLAN },
a34faf579d2be139b0b9e8cd0c73ad4d918ef736Lukas Nykryn { "network-veth", no_argument, NULL, ARG_NETWORK_VETH },
a34faf579d2be139b0b9e8cd0c73ad4d918ef736Lukas Nykryn { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE },
a34faf579d2be139b0b9e8cd0c73ad4d918ef736Lukas Nykryn { "personality", required_argument, NULL, ARG_PERSONALITY },
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering while ((c = getopt_long(argc, argv, "+hD:u:bL:M:jS:Z:q", options, NULL)) >= 0) {
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering arg_directory = canonicalize_file_name(optarg);
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering /* fall through */
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering if (strv_extend(&arg_network_interfaces, optarg) < 0)
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering if (strv_extend(&arg_network_macvlan, optarg) < 0)
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering /* fall through */
151b9b9662a90455262ce575a8a8ae74bf4ff336Lennart Poettering r = sd_id128_from_string(optarg, &arg_uuid);
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering log_error("Invalid machine name: %s", optarg);
beaafb2ea6be591882aef21fe19b88e3b2461087Lennart Poettering FOREACH_WORD_SEPARATOR(word, length, optarg, ",", state) {
beaafb2ea6be591882aef21fe19b88e3b2461087Lennart Poettering log_error("Failed to parse capability %s.", t);
c7b5eb98e8eeafe63a079ee3c51e9670872437aeLennart Poettering log_error("Failed to parse link journal mode %s", optarg);
c7b5eb98e8eeafe63a079ee3c51e9670872437aeLennart Poettering _cleanup_free_ char *a = NULL, *b = NULL;
c7b5eb98e8eeafe63a079ee3c51e9670872437aeLennart Poettering x = c == ARG_BIND ? &arg_bind : &arg_bind_ro;
beaafb2ea6be591882aef21fe19b88e3b2461087Lennart Poettering if (!path_is_absolute(a) || !path_is_absolute(b)) {
beaafb2ea6be591882aef21fe19b88e3b2461087Lennart Poettering log_error("Invalid bind mount specification: %s", optarg);
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering log_error("Environment variable assignment '%s' is not valid.", optarg);
f8e2fb7b14e53f5a4bcfd66d26910af1dee185c6Lennart Poettering log_error("Failed to parse --register= argument: %s", optarg);
case ARG_PERSONALITY:
return -EINVAL;
return -EINVAL;
if (arg_share_system)
arg_register = false;
return -EINVAL;
return -EINVAL;
typedef struct MountPoint {
const char *what;
const char *where;
const char *type;
const char *options;
unsigned long flags;
bool fatal;
} MountPoint;
{ "devpts", "/dev/pts", "devpts","newinstance,ptmxmode=0666,mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, true },
#ifdef HAVE_SELINUX
{ NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, false }, /* Then, make it r/o */
#ifdef HAVE_SELINUX
if (!where)
return log_oom();
#ifdef HAVE_SELINUX
if (arg_selinux_apifs_context &&
if (!options)
return log_oom();
o = options;
r = -errno;
STRV_FOREACH_PAIR(x, y, l) {
char *where;
return -errno;
*x, where);
return -EINVAL;
return -errno;
return -ENOTSUP;
return -errno;
return -errno;
log_warning("/etc/localtime does not point into /usr/share/zoneinfo/, not updating container timezone.");
if (!where)
return log_oom();
if (y && streq(y, z))
if (!check)
return log_oom();
if (!what)
return log_oom();
if (arg_private_network)
/* Fix resolv.conf, if possible */
if (!where)
return log_oom();
if (arg_share_system)
return log_oom();
r = -errno;
static const char devnodes[] =
u = umask(0000);
return log_oom();
return -errno;
return -EIO;
return -errno;
return log_oom();
return -errno;
u = umask(0000);
return -errno;
return -EIO;
return log_oom();
return -errno;
return -errno;
int r, fd, k;
} control = {};
u = umask(0000);
return log_oom();
return -errno;
return -errno;
if (fd < 0) {
return -errno;
return -errno;
static int setup_hostname(void) {
if (arg_share_system)
return -errno;
char *id;
return log_oom();
r = read_one_line_file(p, &b);
-EEXIST;
free(p);
return log_oom();
if (path_is_mount_point(p, false) > 0) {
return -EEXIST;
if (path_is_mount_point(q, false) > 0) {
return -EEXIST;
r = readlink_and_make_absolute(p, &d);
path_equal(d, q)) {
if (unlink(p) < 0) {
return -errno;
} else if (r == -EINVAL) {
rmdir(p) < 0) {
return -errno;
} else if (r != -ENOENT) {
if (symlink(q, p) < 0) {
return -errno;
if (dir_is_empty(q) == 0) {
return -ENOTEMPTY;
return -errno;
if (!path)
return -errno;
return -errno;
static int drop_capabilities(void) {
if (!arg_register)
if (arg_keep_unit) {
r = sd_bus_call_method(
bus,
"/org/freedesktop/machine1",
&error,
NULL,
bus,
"/org/freedesktop/machine1",
r = sd_bus_message_close_container(m);
const char *path;
if (!arg_register)
r = sd_bus_call_method(
bus,
"/org/freedesktop/machine1",
&error,
&reply,
return bus_log_parse_error(r);
r = sd_bus_call_method(
bus,
path,
&error,
NULL,
NULL);
static int reset_audit_loginuid(void) {
if (arg_share_system)
if (r == -EEXIST)
if (!arg_private_network)
if (!arg_network_veth)
if (arg_network_bridge)
r = sd_rtnl_message_close_container(m);
r = sd_rtnl_message_close_container(m);
r = sd_rtnl_message_close_container(m);
int r, bridge;
if (!arg_private_network)
if (!arg_network_veth)
if (!arg_network_bridge)
if (bridge <= 0) {
return -errno;
int ifi;
if (ifi <= 0) {
return -errno;
return -errno;
if (udev_device_get_is_initialized(d) <= 0) {
return -EBUSY;
return ifi;
if (!arg_private_network)
if (!udev) {
return -ENOMEM;
int ifi;
if (ifi < 0)
return ifi;
if (!arg_private_network)
if (!udev) {
return -ENOMEM;
int ifi;
if (ifi < 0)
return ifi;
return log_oom();
r = sd_rtnl_message_close_container(m);
r = sd_rtnl_message_close_container(m);
static int audit_still_doesnt_work_in_containers(void) {
#ifdef HAVE_SECCOMP
if (!seccomp)
return log_oom();
goto finish;
r = seccomp_rule_add(
goto finish;
goto finish;
int r = EXIT_FAILURE, k;
int n_fd_passed;
log_open();
goto finish;
r = EXIT_SUCCESS;
goto finish;
if (arg_directory) {
arg_directory = p;
if (!arg_directory) {
goto finish;
if (!arg_machine) {
if (!arg_machine) {
log_oom();
goto finish;
goto finish;
if (geteuid() != 0) {
goto finish;
if (sd_booted() <= 0) {
goto finish;
goto finish;
if (arg_boot) {
log_error("Directory %s doesn't look like an OS root directory (/etc/os-release is missing). Refusing.", arg_directory);
goto finish;
log_error("Directory %s lacks the binary to execute or doesn't look like a binary tree. Refusing.", arg_directory);
goto finish;
log_close();
if (n_fd_passed > 0) {
goto finish;
log_open();
if (master < 0) {
goto finish;
if (!console) {
goto finish;
if (!arg_quiet)
log_info("Spawning container %s on %s. Press ^] three times within 1s to abort execution.", arg_machine, arg_directory);
goto finish;
if (arg_share_system) {
if (!kdbus_domain) {
log_oom();
goto finish;
const char *ns;
goto finish;
if (sync_fd < 0) {
goto finish;
if (pid < 0) {
log_error("clone() failed, do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in): %m");
goto finish;
if (pid == 0) {
const char *envp[] = {
char **env_use;
eventfd_t x;
n_env ++;
if (k != STDIN_FILENO) {
k = -EINVAL;
goto child_fail;
goto child_fail;
if (setsid() < 0) {
goto child_fail;
if (reset_audit_loginuid() < 0)
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
if (arg_read_only)
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
if (audit_still_doesnt_work_in_containers() < 0)
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
if (arg_private_network)
if (drop_capabilities() < 0) {
goto child_fail;
if (arg_user) {
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
goto child_fail;
if (setresgid(0, 0, 0) < 0) {
goto child_fail;
if (setresuid(0, 0, 0) < 0) {
goto child_fail;
log_oom();
goto child_fail;
if (asprintf((char**)(envp + n_env++), "container_uuid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(arg_uuid)) < 0) {
log_oom();
goto child_fail;
goto child_fail;
log_oom();
goto child_fail;
goto child_fail;
log_oom();
goto child_fail;
env_use = n;
#ifdef HAVE_SELINUX
if (arg_selinux_context)
if (arg_boot) {
size_t l;
goto finish;
goto finish;
goto finish;
goto finish;
goto finish;
r = EXIT_FAILURE;
if (!arg_quiet)
pid = 0;
r = EXIT_FAILURE;
if (!arg_quiet)
if (!arg_quiet)
if (!arg_quiet)
log_error("Container %s terminated by signal %s.", arg_machine, signal_to_string(status.si_status));
r = EXIT_FAILURE;
r = EXIT_FAILURE;
if (pid > 0)