diff options
Diffstat (limited to 'src/nspawn/nspawn.c')
-rw-r--r-- | src/nspawn/nspawn.c | 730 |
1 files changed, 369 insertions, 361 deletions
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 56877bd932..91c97b60a7 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -6,6 +6,7 @@ #include <errno.h> #include <getopt.h> #include <grp.h> +#include <linux/fs.h> #include <linux/loop.h> #include <pwd.h> #include <sched.h> @@ -17,7 +18,6 @@ #include <stdlib.h> #include <string.h> #include <sys/file.h> -#include <sys/mount.h> #include <sys/personality.h> #include <sys/prctl.h> #include <sys/types.h> @@ -60,6 +60,7 @@ #include "missing.h" #include "mkdir.h" #include "mount-util.h" +#include "mountpoint-util.h" #include "netlink-util.h" #include "nspawn-cgroup.h" #include "nspawn-def.h" @@ -76,6 +77,7 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "ptyfwd.h" #include "random-util.h" @@ -91,7 +93,7 @@ #include "string-util.h" #include "strv.h" #include "terminal-util.h" -#include "udev-util.h" +#include "tmpfile-util.h" #include "umask-util.h" #include "user-util.h" #include "util.h" @@ -190,7 +192,7 @@ static const char *arg_container_service_name = "systemd-nspawn"; static bool arg_notify_ready = false; static bool arg_use_cgns = true; static unsigned long arg_clone_ns_flags = CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS; -static MountSettingsMask arg_mount_settings = MOUNT_APPLY_APIVFS_RO; +static MountSettingsMask arg_mount_settings = MOUNT_APPLY_APIVFS_RO|MOUNT_APPLY_TMPFS_TMP; static void *arg_root_hash = NULL; static size_t arg_root_hash_size = 0; static char **arg_syscall_whitelist = NULL; @@ -204,11 +206,18 @@ static unsigned arg_cpuset_ncpus = 0; static ResolvConfMode arg_resolv_conf = RESOLV_CONF_AUTO; static TimezoneMode arg_timezone = TIMEZONE_AUTO; -static void help(void) { - (void) pager_open(false, false); +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + (void) pager_open(false); + + r = terminal_urlify_man("systemd-nspawn", "1", &link); + if (r < 0) + return log_oom(); printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" - "Spawn a minimal namespace container for debugging, testing and building.\n\n" + "Spawn a command or OS in a light-weight container.\n\n" " -h --help Show this help\n" " --version Print version string\n" " -q --quiet Do not show status information\n" @@ -299,7 +308,12 @@ static void help(void) { " --volatile[=MODE] Run the system in volatile mode\n" " --settings=BOOLEAN Load additional settings from .nspawn file\n" " --notify-ready=BOOLEAN Receive notifications from the child init process\n" - , program_invocation_short_name); + "\nSee the %s for details.\n" + , program_invocation_short_name + , link + ); + + return 0; } static int custom_mount_check_all(void) { @@ -309,14 +323,12 @@ static int custom_mount_check_all(void) { CustomMount *m = &arg_custom_mounts[i]; if (path_equal(m->destination, "/") && arg_userns_mode != USER_NAMESPACE_NO) { - - if (arg_userns_chown) { - log_error("--private-users-chown may not be combined with custom root mounts."); - return -EINVAL; - } else if (arg_uid_shift == UID_INVALID) { - log_error("--private-users with automatic UID shift may not be combined with custom root mounts."); - return -EINVAL; - } + if (arg_userns_chown) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--private-users-chown may not be combined with custom root mounts."); + else if (arg_uid_shift == UID_INVALID) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--private-users with automatic UID shift may not be combined with custom root mounts."); } } @@ -391,8 +403,14 @@ static void parse_share_ns_env(const char *name, unsigned long ns_flag) { } static void parse_mount_settings_env(void) { - int r; const char *e; + int r; + + r = getenv_bool("SYSTEMD_NSPAWN_TMPFS_TMP"); + if (r >= 0) + SET_FLAG(arg_mount_settings, MOUNT_APPLY_TMPFS_TMP, r > 0); + else if (r != -ENXIO) + log_warning_errno(r, "Failed to parse $SYSTEMD_NSPAWN_TMPFS_TMP, ignoring: %m"); e = getenv("SYSTEMD_NSPAWN_API_VFS_WRITABLE"); if (!e) @@ -413,6 +431,30 @@ static void parse_mount_settings_env(void) { SET_FLAG(arg_mount_settings, MOUNT_APPLY_APIVFS_NETNS, false); } +static void parse_environment(void) { + const char *e; + int r; + + parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_NS_IPC", CLONE_NEWIPC); + parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_NS_PID", CLONE_NEWPID); + parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_NS_UTS", CLONE_NEWUTS); + parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_SYSTEM", CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS); + + parse_mount_settings_env(); + + /* SYSTEMD_NSPAWN_USE_CGNS=0 can be used to disable CLONE_NEWCGROUP use, + * even if it is supported. If not supported, it has no effect. */ + r = getenv_bool("SYSTEMD_NSPAWN_USE_CGNS"); + if (r == 0 || !cg_ns_supported()) + arg_use_cgns = false; + + e = getenv("SYSTEMD_NSPAWN_CONTAINER_SERVICE"); + if (e) + arg_container_service_name = e; + + detect_unified_cgroup_hierarchy_from_environment(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -521,7 +563,7 @@ static int parse_argv(int argc, char *argv[]) { }; int c, r; - const char *p, *e; + const char *p; uint64_t plus = 0, minus = 0; bool mask_all_settings = false, mask_no_settings = false; @@ -532,8 +574,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - help(); - return 0; + return help(); case ARG_VERSION: return version(); @@ -558,6 +599,7 @@ static int parse_argv(int argc, char *argv[]) { case 'x': arg_ephemeral = true; + arg_settings_mask |= SETTING_EPHEMERAL; break; case 'u': @@ -591,10 +633,9 @@ static int parse_argv(int argc, char *argv[]) { case ARG_NETWORK_BRIDGE: - if (!ifname_valid(optarg)) { - log_error("Bridge interface name not valid: %s", optarg); - return -EINVAL; - } + if (!ifname_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Bridge interface name not valid: %s", optarg); r = free_and_strdup(&arg_network_bridge, optarg); if (r < 0) @@ -617,10 +658,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_NETWORK_INTERFACE: - if (!ifname_valid(optarg)) { - log_error("Network interface name not valid: %s", optarg); - return -EINVAL; - } + if (!ifname_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Network interface name not valid: %s", optarg); if (strv_extend(&arg_network_interfaces, optarg) < 0) return log_oom(); @@ -631,10 +671,9 @@ static int parse_argv(int argc, char *argv[]) { case ARG_NETWORK_MACVLAN: - if (!ifname_valid(optarg)) { - log_error("MACVLAN network interface name not valid: %s", optarg); - return -EINVAL; - } + if (!ifname_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "MACVLAN network interface name not valid: %s", optarg); if (strv_extend(&arg_network_macvlan, optarg) < 0) return log_oom(); @@ -645,10 +684,9 @@ static int parse_argv(int argc, char *argv[]) { case ARG_NETWORK_IPVLAN: - if (!ifname_valid(optarg)) { - log_error("IPVLAN network interface name not valid: %s", optarg); - return -EINVAL; - } + if (!ifname_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "IPVLAN network interface name not valid: %s", optarg); if (strv_extend(&arg_network_ipvlan, optarg) < 0) return log_oom(); @@ -667,20 +705,18 @@ static int parse_argv(int argc, char *argv[]) { break; case 'b': - if (arg_start_mode == START_PID2) { - log_error("--boot and --as-pid2 may not be combined."); - return -EINVAL; - } + if (arg_start_mode == START_PID2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--boot and --as-pid2 may not be combined."); arg_start_mode = START_BOOT; arg_settings_mask |= SETTING_START_MODE; break; case 'a': - if (arg_start_mode == START_BOOT) { - log_error("--boot and --as-pid2 may not be combined."); - return -EINVAL; - } + if (arg_start_mode == START_BOOT) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--boot and --as-pid2 may not be combined."); arg_start_mode = START_PID2; arg_settings_mask |= SETTING_START_MODE; @@ -691,10 +727,9 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Invalid UUID: %s", optarg); - if (sd_id128_is_null(arg_uuid)) { - log_error("Machine UUID may not be all zeroes."); - return -EINVAL; - } + if (sd_id128_is_null(arg_uuid)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Machine UUID may not be all zeroes."); arg_settings_mask |= SETTING_MACHINE_ID; break; @@ -707,10 +742,9 @@ static int parse_argv(int argc, char *argv[]) { if (isempty(optarg)) arg_machine = mfree(arg_machine); else { - if (!machine_name_is_valid(optarg)) { - log_error("Invalid machine name: %s", optarg); - return -EINVAL; - } + if (!machine_name_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid machine name: %s", optarg); r = free_and_strdup(&arg_machine, optarg); if (r < 0) @@ -722,10 +756,9 @@ static int parse_argv(int argc, char *argv[]) { if (isempty(optarg)) arg_hostname = mfree(arg_hostname); else { - if (!hostname_is_valid(optarg, false)) { - log_error("Invalid hostname: %s", optarg); - return -EINVAL; - } + if (!hostname_is_valid(optarg, false)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid hostname: %s", optarg); r = free_and_strdup(&arg_hostname, optarg); if (r < 0) @@ -767,18 +800,14 @@ static int parse_argv(int argc, char *argv[]) { else minus = (uint64_t) -1; } else { - int cap; - - cap = capability_from_name(t); - if (cap < 0) { - log_error("Failed to parse capability %s.", t); - return -EINVAL; - } + r = capability_from_name(t); + if (r < 0) + return log_error_errno(r, "Failed to parse capability %s.", t); if (c == ARG_CAPABILITY) - plus |= 1ULL << (uint64_t) cap; + plus |= 1ULL << r; else - minus |= 1ULL << (uint64_t) cap; + minus |= 1ULL << r; } } @@ -842,10 +871,9 @@ static int parse_argv(int argc, char *argv[]) { case 'E': { char **n; - if (!env_assignment_is_valid(optarg)) { - log_error("Environment variable assignment '%s' is not valid.", optarg); - return -EINVAL; - } + if (!env_assignment_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Environment variable assignment '%s' is not valid.", optarg); n = strv_env_set(arg_setenv, optarg); if (!n) @@ -884,10 +912,9 @@ static int parse_argv(int argc, char *argv[]) { case ARG_PERSONALITY: arg_personality = personality_from_string(optarg); - if (arg_personality == PERSONALITY_INVALID) { - log_error("Unknown or unsupported personality '%s'.", optarg); - return -EINVAL; - } + if (arg_personality == PERSONALITY_INVALID) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown or unsupported personality '%s'.", optarg); arg_settings_mask |= SETTING_PERSONALITY; break; @@ -903,10 +930,10 @@ static int parse_argv(int argc, char *argv[]) { VolatileMode m; m = volatile_mode_from_string(optarg); - if (m < 0) { - log_error("Failed to parse --volatile= argument: %s", optarg); - return -EINVAL; - } else + if (m < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse --volatile= argument: %s", optarg); + else arg_volatile_mode = m; } @@ -980,10 +1007,9 @@ static int parse_argv(int argc, char *argv[]) { arg_userns_mode = USER_NAMESPACE_FIXED; } - if (arg_uid_range <= 0) { - log_error("UID range cannot be 0."); - return -EINVAL; - } + if (arg_uid_range <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "UID range cannot be 0."); arg_settings_mask |= SETTING_USERNS; break; @@ -1013,10 +1039,9 @@ static int parse_argv(int argc, char *argv[]) { } arg_kill_signal = signal_from_string(optarg); - if (arg_kill_signal < 0) { - log_error("Cannot parse signal: %s", optarg); - return -EINVAL; - } + if (arg_kill_signal < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Cannot parse signal: %s", optarg); arg_settings_mask |= SETTING_KILL_SIGNAL; break; @@ -1057,10 +1082,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_CHDIR: - if (!path_is_absolute(optarg)) { - log_error("Working directory %s is not an absolute path.", optarg); - return -EINVAL; - } + if (!path_is_absolute(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Working directory %s is not an absolute path.", optarg); r = free_and_strdup(&arg_chdir, optarg); if (r < 0) @@ -1079,10 +1103,9 @@ static int parse_argv(int argc, char *argv[]) { case ARG_NOTIFY_READY: r = parse_boolean(optarg); - if (r < 0) { - log_error("%s is not a valid notify mode. Valid modes are: yes, no, and ready.", optarg); - return -EINVAL; - } + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s is not a valid notify mode. Valid modes are: yes, no, and ready.", optarg); arg_notify_ready = r; arg_settings_mask |= SETTING_NOTIFY_READY; break; @@ -1147,20 +1170,18 @@ static int parse_argv(int argc, char *argv[]) { } eq = strchr(optarg, '='); - if (!eq) { - log_error("--rlimit= expects an '=' assignment."); - return -EINVAL; - } + if (!eq) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--rlimit= expects an '=' assignment."); name = strndup(optarg, eq - optarg); if (!name) return log_oom(); rl = rlimit_from_string_harder(name); - if (rl < 0) { - log_error("Unknown resource limit: %s", name); - return -EINVAL; - } + if (rl < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown resource limit: %s", name); if (!arg_rlimit[rl]) { arg_rlimit[rl] = new0(struct rlimit, 1); @@ -1208,10 +1229,9 @@ static int parse_argv(int argc, char *argv[]) { } arg_resolv_conf = resolv_conf_mode_from_string(optarg); - if (arg_resolv_conf < 0) { - log_error("Failed to parse /etc/resolv.conf mode: %s", optarg); - return -EINVAL; - } + if (arg_resolv_conf < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse /etc/resolv.conf mode: %s", optarg); arg_settings_mask |= SETTING_RESOLV_CONF; break; @@ -1223,10 +1243,9 @@ static int parse_argv(int argc, char *argv[]) { } arg_timezone = timezone_mode_from_string(optarg); - if (arg_timezone < 0) { - log_error("Failed to parse /etc/localtime mode: %s", optarg); - return -EINVAL; - } + if (arg_timezone < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse /etc/localtime mode: %s", optarg); arg_settings_mask |= SETTING_TIMEZONE; break; @@ -1238,21 +1257,37 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached("Unhandled option"); } - /* If --network-namespace-path is given with any other network-related option, - * we need to error out, to avoid conflicts between different network options. */ - if (arg_network_namespace_path && - (arg_network_interfaces || arg_network_macvlan || - arg_network_ipvlan || arg_network_veth_extra || - arg_network_bridge || arg_network_zone || - arg_network_veth || arg_private_network)) { - log_error("--network-namespace-path cannot be combined with other network options."); - return -EINVAL; + if (argc > optind) { + strv_free(arg_parameters); + arg_parameters = strv_copy(argv + optind); + if (!arg_parameters) + return log_oom(); + + arg_settings_mask |= SETTING_START_MODE; } - parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_NS_IPC", CLONE_NEWIPC); - parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_NS_PID", CLONE_NEWPID); - parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_NS_UTS", CLONE_NEWUTS); - parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_SYSTEM", CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS); + if (arg_ephemeral && arg_template && !arg_directory) + /* User asked for ephemeral execution but specified --template= instead of --directory=. Semantically + * such an invocation makes some sense, see https://github.com/systemd/systemd/issues/3667. Let's + * accept this here, and silently make "--ephemeral --template=" equivalent to "--ephemeral + * --directory=". */ + arg_directory = TAKE_PTR(arg_template); + + arg_caps_retain = (arg_caps_retain | plus | (arg_private_network ? 1ULL << CAP_NET_ADMIN : 0)) & ~minus; + + /* Load all settings from .nspawn files */ + if (mask_no_settings) + arg_settings_mask = 0; + + /* Don't load any settings from .nspawn files */ + if (mask_all_settings) + arg_settings_mask = _SETTINGS_MASK_ALL; + + return 1; +} + +static int verify_arguments(void) { + int r; if (arg_userns_mode != USER_NAMESPACE_NO) arg_mount_settings |= MOUNT_USE_USERNS; @@ -1260,146 +1295,78 @@ static int parse_argv(int argc, char *argv[]) { if (arg_private_network) arg_mount_settings |= MOUNT_APPLY_APIVFS_NETNS; - parse_mount_settings_env(); - if (!(arg_clone_ns_flags & CLONE_NEWPID) || !(arg_clone_ns_flags & CLONE_NEWUTS)) { arg_register = false; - if (arg_start_mode != START_PID1) { - log_error("--boot cannot be used without namespacing."); - return -EINVAL; - } + if (arg_start_mode != START_PID1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--boot cannot be used without namespacing."); } if (arg_userns_mode == USER_NAMESPACE_PICK) arg_userns_chown = true; - if (arg_keep_unit && arg_register && cg_pid_get_owner_uid(0, NULL) >= 0) { + if (arg_start_mode == START_BOOT && arg_kill_signal <= 0) + arg_kill_signal = SIGRTMIN+3; + + if (arg_keep_unit && arg_register && cg_pid_get_owner_uid(0, NULL) >= 0) /* Save the user from accidentally registering either user-$SESSION.scope or user@.service. * The latter is not technically a user session, but we don't need to labour the point. */ - log_error("--keep-unit --register=yes may not be used when invoked from a user session."); - return -EINVAL; - } + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--keep-unit --register=yes may not be used when invoked from a user session."); - if (arg_directory && arg_image) { - log_error("--directory= and --image= may not be combined."); - return -EINVAL; - } + if (arg_directory && arg_image) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--directory= and --image= may not be combined."); - if (arg_template && arg_image) { - log_error("--template= and --image= may not be combined."); - return -EINVAL; - } + if (arg_template && arg_image) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--template= and --image= may not be combined."); - if (arg_ephemeral && arg_template && !arg_directory) { - /* User asked for ephemeral execution but specified --template= instead of --directory=. Semantically - * such an invocation makes some sense, see https://github.com/systemd/systemd/issues/3667. Let's - * accept this here, and silently make "--ephemeral --template=" equivalent to "--ephemeral - * --directory=". */ + if (arg_template && !(arg_directory || arg_machine)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--template= needs --directory= or --machine=."); - arg_directory = TAKE_PTR(arg_template); - } + if (arg_ephemeral && arg_template) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--ephemeral and --template= may not be combined."); - if (arg_template && !(arg_directory || arg_machine)) { - log_error("--template= needs --directory= or --machine=."); - return -EINVAL; - } + if (arg_ephemeral && !IN_SET(arg_link_journal, LINK_NO, LINK_AUTO)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--ephemeral and --link-journal= may not be combined."); - if (arg_ephemeral && arg_template) { - log_error("--ephemeral and --template= may not be combined."); - return -EINVAL; - } - - if (arg_ephemeral && !IN_SET(arg_link_journal, LINK_NO, LINK_AUTO)) { - log_error("--ephemeral and --link-journal= may not be combined."); - return -EINVAL; - } - - if (arg_userns_mode != USER_NAMESPACE_NO && !userns_supported()) { - log_error("--private-users= is not supported, kernel compiled without user namespace support."); - return -EOPNOTSUPP; - } - - if (arg_userns_chown && arg_read_only) { - log_error("--read-only and --private-users-chown may not be combined."); - return -EINVAL; - } + if (arg_userns_mode != USER_NAMESPACE_NO && !userns_supported()) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--private-users= is not supported, kernel compiled without user namespace support."); - if (arg_network_bridge && arg_network_zone) { - log_error("--network-bridge= and --network-zone= may not be combined."); - return -EINVAL; - } + if (arg_userns_chown && arg_read_only) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--read-only and --private-users-chown may not be combined."); - if (argc > optind) { - arg_parameters = strv_copy(argv + optind); - if (!arg_parameters) - return log_oom(); + /* If --network-namespace-path is given with any other network-related option, + * we need to error out, to avoid conflicts between different network options. */ + if (arg_network_namespace_path && + (arg_network_interfaces || arg_network_macvlan || + arg_network_ipvlan || arg_network_veth_extra || + arg_network_bridge || arg_network_zone || + arg_network_veth || arg_private_network)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--network-namespace-path cannot be combined with other network options."); - arg_settings_mask |= SETTING_START_MODE; - } + if (arg_network_bridge && arg_network_zone) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--network-bridge= and --network-zone= may not be combined."); - /* Load all settings from .nspawn files */ - if (mask_no_settings) - arg_settings_mask = 0; + if (arg_userns_mode != USER_NAMESPACE_NO && (arg_mount_settings & MOUNT_APPLY_APIVFS_NETNS) && !arg_private_network) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid namespacing settings. Mounting sysfs with --private-users requires --private-network."); - /* Don't load any settings from .nspawn files */ - if (mask_all_settings) - arg_settings_mask = _SETTINGS_MASK_ALL; + if (arg_userns_mode != USER_NAMESPACE_NO && !(arg_mount_settings & MOUNT_APPLY_APIVFS_RO)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --private-users with read-write mounts."); - arg_caps_retain = (arg_caps_retain | plus | (arg_private_network ? 1ULL << CAP_NET_ADMIN : 0)) & ~minus; + if (arg_volatile_mode != VOLATILE_NO && arg_read_only) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --read-only with --volatile. Note that --volatile already implies a read-only base hierarchy."); - r = cg_unified_flush(); - if (r < 0) - return log_error_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m"); + if (arg_expose_ports && !arg_private_network) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --port= without private networking."); - e = getenv("SYSTEMD_NSPAWN_CONTAINER_SERVICE"); - if (e) - arg_container_service_name = e; - - r = getenv_bool("SYSTEMD_NSPAWN_USE_CGNS"); - if (r < 0) - arg_use_cgns = cg_ns_supported(); - else - arg_use_cgns = r; +#if ! HAVE_LIBIPTC + if (arg_expose_ports) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--port= is not supported, compiled without libiptc support."); +#endif r = custom_mount_check_all(); if (r < 0) return r; - return 1; -} - -static int verify_arguments(void) { - if (arg_userns_mode != USER_NAMESPACE_NO && (arg_mount_settings & MOUNT_APPLY_APIVFS_NETNS) && !arg_private_network) { - log_error("Invalid namespacing settings. Mounting sysfs with --private-users requires --private-network."); - return -EINVAL; - } - - if (arg_userns_mode != USER_NAMESPACE_NO && !(arg_mount_settings & MOUNT_APPLY_APIVFS_RO)) { - log_error("Cannot combine --private-users with read-write mounts."); - return -EINVAL; - } - - if (arg_volatile_mode != VOLATILE_NO && arg_read_only) { - log_error("Cannot combine --read-only with --volatile. Note that --volatile already implies a read-only base hierarchy."); - return -EINVAL; - } - - if (arg_expose_ports && !arg_private_network) { - log_error("Cannot use --port= without private networking."); - return -EINVAL; - } - -#if ! HAVE_LIBIPTC - if (arg_expose_ports) { - log_error("--port= is not supported, compiled without libiptc support."); - return -EOPNOTSUPP; - } -#endif - - if (arg_start_mode == START_BOOT && arg_kill_signal <= 0) - arg_kill_signal = SIGRTMIN+3; - return 0; } @@ -1447,17 +1414,10 @@ static int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t u } static const char *timezone_from_path(const char *path) { - const char *z; - - z = path_startswith(path, "../usr/share/zoneinfo/"); - if (z) - return z; - - z = path_startswith(path, "/usr/share/zoneinfo/"); - if (z) - return z; - - return NULL; + return PATH_STARTSWITH_SET( + path, + "../usr/share/zoneinfo/", + "/usr/share/zoneinfo/"); } static int setup_timezone(const char *dest) { @@ -1646,12 +1606,7 @@ static int setup_resolv_conf(const char *dest) { if (arg_private_network) m = RESOLV_CONF_OFF; else if (have_resolv_conf(STATIC_RESOLV_CONF) > 0 && resolved_listening() > 0) - /* resolved is enabled on the host. In this, case bind mount its static resolv.conf file into the - * container, so that the container can use the host's resolver. Given that network namespacing is - * disabled it's only natural of the container also uses the host's resolver. It also has the big - * advantage that the container will be able to follow the host's DNS server configuration changes - * transparently. */ - m = RESOLV_CONF_BIND_STATIC; + m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? RESOLV_CONF_BIND_STATIC : RESOLV_CONF_COPY_STATIC; else if (have_resolv_conf("/etc/resolv.conf") > 0) m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? RESOLV_CONF_BIND_HOST : RESOLV_CONF_COPY_HOST; else @@ -1779,19 +1734,24 @@ static int copy_devnodes(const char *dest) { struct stat st; from = strappend("/dev/", d); + if (!from) + return log_oom(); + to = prefix_root(dest, from); + if (!to) + return log_oom(); if (stat(from, &st) < 0) { if (errno != ENOENT) return log_error_errno(errno, "Failed to stat %s: %m", from); - } else if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { - - log_error("%s is not a char or block device, cannot copy.", from); - return -EIO; + } else if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "%s is not a char or block device, cannot copy.", from); + else { + _cleanup_free_ char *sl = NULL, *prefixed = NULL, *dn = NULL, *t = NULL; - } else { if (mknod(to, st.st_mode, st.st_rdev) < 0) { /* Explicitly warn the user when /dev is already populated. */ if (errno == EEXIST) @@ -1799,8 +1759,7 @@ static int copy_devnodes(const char *dest) { if (errno != EPERM) return log_error_errno(errno, "mknod(%s) failed: %m", to); - /* Some systems abusively restrict mknod but - * allow bind mounts. */ + /* Some systems abusively restrict mknod but allow bind mounts. */ r = touch(to); if (r < 0) return log_error_errno(r, "touch (%s) failed: %m", to); @@ -1812,6 +1771,28 @@ static int copy_devnodes(const char *dest) { r = userns_lchown(to, 0, 0); if (r < 0) return log_error_errno(r, "chown() of device node %s failed: %m", to); + + dn = strjoin("/dev/", S_ISCHR(st.st_mode) ? "char" : "block"); + if (!dn) + return log_oom(); + + r = userns_mkdir(dest, dn, 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create '%s': %m", dn); + + if (asprintf(&sl, "%s/%u:%u", dn, major(st.st_rdev), minor(st.st_rdev)) < 0) + return log_oom(); + + prefixed = prefix_root(dest, sl); + if (!prefixed) + return log_oom(); + + t = strjoin("../", d); + if (!t) + return log_oom(); + + if (symlink(t, prefixed) < 0) + log_debug_errno(errno, "Failed to symlink '%s' to '%s': %m", t, prefixed); } } @@ -1987,7 +1968,7 @@ static int setup_journal(const char *directory) { _cleanup_free_ char *d = NULL; const char *p, *q; bool try; - char id[33]; + char id[33], *dirname; int r; /* Don't link journals in ephemeral mode */ @@ -2011,17 +1992,15 @@ static int setup_journal(const char *directory) { return -EEXIST; } - r = userns_mkdir(directory, "/var", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /var: %m"); - - r = userns_mkdir(directory, "/var/log", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /var/log: %m"); - - r = userns_mkdir(directory, "/var/log/journal", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /var/log/journal: %m"); + FOREACH_STRING(dirname, "/var", "/var/log", "/var/log/journal") { + r = userns_mkdir(directory, dirname, 0755, 0, 0); + if (r < 0) { + bool ignore = r == -EROFS && try; + log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, r, + "Failed to create %s%s: %m", dirname, ignore ? ", ignoring" : ""); + return ignore ? 0 : r; + } + } (void) sd_id128_to_string(arg_uuid, id); @@ -2032,16 +2011,16 @@ static int setup_journal(const char *directory) { if (try) return 0; - log_error("%s: already a mount point, refusing to use for journal", p); - return -EEXIST; + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "%s: already a mount point, refusing to use for journal", p); } if (path_is_mount_point(q, NULL, 0) > 0) { if (try) return 0; - log_error("%s: already a mount point, refusing to use for journal", q); - return -EEXIST; + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "%s: already a mount point, refusing to use for journal", q); } r = readlink_and_make_absolute(p, &d); @@ -2138,7 +2117,7 @@ static int reset_audit_loginuid(void) { if (streq(p, "4294967295")) return 0; - r = write_string_file("/proc/self/loginuid", "4294967295", 0); + r = write_string_file("/proc/self/loginuid", "4294967295", WRITE_STRING_FILE_DISABLE_BUFFER); if (r < 0) { log_error_errno(r, "Failed to reset audit login UID. This probably means that your kernel is too\n" @@ -2213,10 +2192,9 @@ static int setup_machine_id(const char *directory) { return log_error_errno(r, "Failed to acquire randomized machine UUID: %m"); } } else { - if (sd_id128_is_null(id)) { - log_error("Machine ID in container image is zero, refusing."); - return -EINVAL; - } + if (sd_id128_is_null(id)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Machine ID in container image is zero, refusing."); arg_uuid = id; } @@ -2297,12 +2275,12 @@ static int wait_for_container(pid_t pid, ContainerStatus *container) { _fallthrough_; case CLD_DUMPED: - log_error("Container %s terminated by signal %s.", arg_machine, signal_to_string(status.si_status)); - return -EIO; + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Container %s terminated by signal %s.", arg_machine, signal_to_string(status.si_status)); default: - log_error("Container %s failed due to unknown reason.", arg_machine); - return -EIO; + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Container %s failed due to unknown reason.", arg_machine); } } @@ -2492,18 +2470,16 @@ static int determine_uid_shift(const char *directory) { arg_uid_shift = st.st_uid & UINT32_C(0xffff0000); - if (arg_uid_shift != (st.st_gid & UINT32_C(0xffff0000))) { - log_error("UID and GID base of %s don't match.", directory); - return -EINVAL; - } + if (arg_uid_shift != (st.st_gid & UINT32_C(0xffff0000))) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "UID and GID base of %s don't match.", directory); arg_uid_range = UINT32_C(0x10000); } - if (arg_uid_shift > (uid_t) -1 - arg_uid_range) { - log_error("UID base too high for UID range."); - return -EINVAL; - } + if (arg_uid_shift > (uid_t) -1 - arg_uid_range) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "UID base too high for UID range."); return 0; } @@ -2536,6 +2512,17 @@ static int inner_child( _cleanup_strv_free_ char **env_use = NULL; int r; + /* This is the "inner" child process, i.e. the one forked off by the "outer" child process, which is the one + * the container manager itself forked off. At the time of clone() it gained its own CLONE_NEWNS, CLONE_NEWPID, + * CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWUSER namespaces. Note that it has its own CLONE_NEWNS namespace, + * separate from the CLONE_NEWNS created for the "outer" child, and also separate from the host's CLONE_NEWNS + * namespace. The reason for having two levels of CLONE_NEWNS namespaces is that the "inner" one is owned by + * the CLONE_NEWUSER namespace of the container, while the "outer" one is owned by the host's CLONE_NEWUSER + * namespace. + * + * Note at this point we have no CLONE_NEWNET namespace yet. We'll acquire that one later through + * unshare(). See below. */ + assert(barrier); assert(directory); assert(kmsg_socket >= 0); @@ -2545,10 +2532,9 @@ static int inner_child( (void) barrier_place(barrier); /* #1 */ /* Wait until the parent wrote the UID map */ - if (!barrier_place_and_sync(barrier)) { /* #2 */ - log_error("Parent died too early"); - return -ESRCH; - } + if (!barrier_place_and_sync(barrier)) /* #2 */ + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), + "Parent died too early"); } r = reset_uid_gid(); @@ -2558,7 +2544,6 @@ static int inner_child( r = mount_all(NULL, arg_mount_settings | MOUNT_IN_USERNS, arg_uid_shift, - arg_uid_range, arg_selinux_apifs_context); if (r < 0) return r; @@ -2578,12 +2563,11 @@ static int inner_child( /* Wait until we are cgroup-ified, so that we * can mount the right cgroup path writable */ - if (!barrier_place_and_sync(barrier)) { /* #4 */ - log_error("Parent died too early"); - return -ESRCH; - } + if (!barrier_place_and_sync(barrier)) /* #4 */ + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), + "Parent died too early"); - if (arg_use_cgns && cg_ns_supported()) { + if (arg_use_cgns) { r = unshare(CLONE_NEWCGROUP); if (r < 0) return log_error_errno(errno, "Failed to unshare cgroup namespace: %m"); @@ -2701,10 +2685,9 @@ static int inner_child( /* Let the parent know that we are ready and * wait until the parent is ready with the * setup, too... */ - if (!barrier_place_and_sync(barrier)) { /* #5 */ - log_error("Parent died too early"); - return -ESRCH; - } + if (!barrier_place_and_sync(barrier)) /* #5 */ + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), + "Parent died too early"); if (arg_chdir) if (chdir(arg_chdir) < 0) @@ -2746,7 +2729,18 @@ static int inner_child( exec_target = "/usr/lib/systemd/systemd, /lib/systemd/systemd, /sbin/init"; } else if (!strv_isempty(arg_parameters)) { + const char *dollar_path; + exec_target = arg_parameters[0]; + + /* Use the user supplied search $PATH if there is one, or DEFAULT_PATH_COMPAT if not to search the + * binary. */ + dollar_path = strv_env_get(env_use, "PATH"); + if (dollar_path) { + if (putenv((char*) dollar_path) != 0) + return log_error_errno(errno, "Failed to update $PATH: %m"); + } + execvpe(arg_parameters[0], arg_parameters, env_use); } else { if (!arg_chdir) @@ -2763,10 +2757,10 @@ static int inner_child( } static int setup_sd_notify_child(void) { - static const int one = 1; - int fd = -1; + _cleanup_close_ int fd = -1; union sockaddr_union sa = { - .sa.sa_family = AF_UNIX, + .un.sun_family = AF_UNIX, + .un.sun_path = NSPAWN_NOTIFY_SOCKET_PATH, }; int r; @@ -2775,28 +2769,21 @@ static int setup_sd_notify_child(void) { return log_error_errno(errno, "Failed to allocate notification socket: %m"); (void) mkdir_parents(NSPAWN_NOTIFY_SOCKET_PATH, 0755); - (void) unlink(NSPAWN_NOTIFY_SOCKET_PATH); + (void) sockaddr_un_unlink(&sa.un); - strncpy(sa.un.sun_path, NSPAWN_NOTIFY_SOCKET_PATH, sizeof(sa.un.sun_path)-1); r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) { - safe_close(fd); - return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); - } + if (r < 0) + return log_error_errno(errno, "bind(" NSPAWN_NOTIFY_SOCKET_PATH ") failed: %m"); r = userns_lchown(NSPAWN_NOTIFY_SOCKET_PATH, 0, 0); - if (r < 0) { - safe_close(fd); + if (r < 0) return log_error_errno(r, "Failed to chown " NSPAWN_NOTIFY_SOCKET_PATH ": %m"); - } - r = setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); - if (r < 0) { - safe_close(fd); - return log_error_errno(errno, "SO_PASSCRED failed: %m"); - } + r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true); + if (r < 0) + return log_error_errno(r, "SO_PASSCRED failed: %m"); - return fd; + return TAKE_FD(fd); } static int outer_child( @@ -2821,6 +2808,11 @@ static int outer_child( pid_t pid; ssize_t l; + /* This is the "outer" child process, i.e the one forked off by the container manager itself. It already has + * its own CLONE_NEWNS namespace (which was created by the clone()). It still lives in the host's CLONE_NEWPID, + * CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWUSER and CLONE_NEWNET namespaces. After it completed a number of + * initializations a second child (the "inner" one) is forked off it, and it exits. */ + assert(barrier); assert(directory); assert(console); @@ -2883,10 +2875,9 @@ static int outer_child( l = send(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); if (l < 0) return log_error_errno(errno, "Failed to send UID shift: %m"); - if (l != sizeof(arg_uid_shift)) { - log_error("Short write while sending UID shift."); - return -EIO; - } + if (l != sizeof(arg_uid_shift)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Short write while sending UID shift."); if (arg_userns_mode == USER_NAMESPACE_PICK) { /* When we are supposed to pick the UID shift, the parent will check now whether the UID shift @@ -2896,13 +2887,13 @@ static int outer_child( l = recv(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), 0); if (l < 0) return log_error_errno(errno, "Failed to recv UID shift: %m"); - if (l != sizeof(arg_uid_shift)) { - log_error("Short read while receiving UID shift."); - return -EIO; - } + if (l != sizeof(arg_uid_shift)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Short read while receiving UID shift."); } - log_info("Selected user namespace base " UID_FMT " and range " UID_FMT ".", arg_uid_shift, arg_uid_range); + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "Selected user namespace base " UID_FMT " and range " UID_FMT ".", arg_uid_shift, arg_uid_range); } if (dissected_image) { @@ -2923,10 +2914,9 @@ static int outer_child( l = send(unified_cgroup_hierarchy_socket, &arg_unified_cgroup_hierarchy, sizeof(arg_unified_cgroup_hierarchy), MSG_NOSIGNAL); if (l < 0) return log_error_errno(errno, "Failed to send cgroup mode: %m"); - if (l != sizeof(arg_unified_cgroup_hierarchy)) { - log_error("Short write while sending cgroup mode."); - return -EIO; - } + if (l != sizeof(arg_unified_cgroup_hierarchy)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Short write while sending cgroup mode."); unified_cgroup_hierarchy_socket = safe_close(unified_cgroup_hierarchy_socket); } @@ -2990,7 +2980,6 @@ static int outer_child( r = mount_all(directory, arg_mount_settings, arg_uid_shift, - arg_uid_range, arg_selinux_apifs_context); if (r < 0) return r; @@ -3048,7 +3037,7 @@ static int outer_child( if (r < 0) return r; - if (!arg_use_cgns || !cg_ns_supported()) { + if (!arg_use_cgns) { r = mount_cgroups( directory, arg_unified_cgroup_hierarchy, @@ -3091,7 +3080,7 @@ static int outer_child( if (arg_network_namespace_path) { r = namespace_enter(-1, -1, netns_fd, -1, -1); if (r < 0) - return r; + return log_error_errno(r, "Failed to join network namespace: %m"); } r = inner_child(barrier, directory, secondary, kmsg_socket, rtnl_socket, fds); @@ -3104,18 +3093,16 @@ static int outer_child( l = send(pid_socket, &pid, sizeof(pid), MSG_NOSIGNAL); if (l < 0) return log_error_errno(errno, "Failed to send PID: %m"); - if (l != sizeof(pid)) { - log_error("Short write while sending PID."); - return -EIO; - } + if (l != sizeof(pid)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Short write while sending PID."); l = send(uuid_socket, &arg_uuid, sizeof(arg_uuid), MSG_NOSIGNAL); if (l < 0) return log_error_errno(errno, "Failed to send machine ID: %m"); - if (l != sizeof(arg_uuid)) { - log_error("Short write while sending machine ID."); - return -EIO; - } + if (l != sizeof(arg_uuid)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Short write while sending machine ID."); l = send_one_fd(notify_socket, fd, 0); if (l < 0) @@ -3208,13 +3195,13 @@ static int setup_uid_map(pid_t pid) { xsprintf(uid_map, "/proc/" PID_FMT "/uid_map", pid); xsprintf(line, UID_FMT " " UID_FMT " " UID_FMT "\n", 0, arg_uid_shift, arg_uid_range); - r = write_string_file(uid_map, line, 0); + r = write_string_file(uid_map, line, WRITE_STRING_FILE_DISABLE_BUFFER); if (r < 0) return log_error_errno(r, "Failed to write UID map: %m"); /* We always assign the same UID and GID ranges */ xsprintf(uid_map, "/proc/" PID_FMT "/gid_map", pid); - r = write_string_file(uid_map, line, 0); + r = write_string_file(uid_map, line, WRITE_STRING_FILE_DISABLE_BUFFER); if (r < 0) return log_error_errno(r, "Failed to write GID map: %m"); @@ -3324,6 +3311,9 @@ static int merge_settings(Settings *settings, const char *path) { strv_free_and_replace(arg_parameters, settings->parameters); } + if ((arg_settings_mask & SETTING_EPHEMERAL) == 0) + arg_ephemeral = settings->ephemeral; + if ((arg_settings_mask & SETTING_PIVOT_ROOT) == 0 && settings->pivot_root_new) { free_and_replace(arg_pivot_root_new, settings->pivot_root_new); @@ -3701,10 +3691,12 @@ static int run(int master, return log_error_errno(errno, "Cannot open file %s: %m", arg_network_namespace_path); r = fd_is_network_ns(netns_fd); - if (r < 0 && r != -ENOTTY) + if (r == -EUCLEAN) + log_debug_errno(r, "Cannot determine if passed network namespace path '%s' really refers to a network namespace, assuming it does.", arg_network_namespace_path); + else if (r < 0) return log_error_errno(r, "Failed to check %s fs type: %m", arg_network_namespace_path); - if (r == 0) { - log_error("Path %s doesn't refer to a network namespace", arg_network_namespace_path); + else if (r == 0) { + log_error("Path %s doesn't refer to a network namespace, refusing.", arg_network_namespace_path); return -EINVAL; } } @@ -3800,7 +3792,8 @@ static int run(int master, if (l < 0) return log_error_errno(errno, "Failed to read cgroup mode: %m"); if (l != sizeof(arg_unified_cgroup_hierarchy)) { - log_error("Short read while reading cgroup mode."); + log_error("Short read while reading cgroup mode (%zu bytes).%s", + l, l == 0 ? " The child is most likely dead." : ""); return -EIO; } } @@ -3912,6 +3905,10 @@ static int run(int master, r = sd_bus_default_system(&bus); if (r < 0) return log_error_errno(r, "Failed to open system bus: %m"); + + r = sd_bus_set_close_on_exit(bus, false); + if (r < 0) + return log_error_errno(r, "Failed to disable close-on-exit behaviour: %m"); } if (!arg_keep_unit) { @@ -4063,8 +4060,12 @@ static int run(int master, putc('\n', stdout); /* Kill if it is not dead yet anyway */ - if (arg_register && !arg_keep_unit && bus) - terminate_machine(bus, *pid); + if (bus) { + if (arg_register) + terminate_machine(bus, arg_machine); + else if (!arg_keep_unit) + terminate_scope(bus, arg_machine); + } /* Normally redundant, but better safe than sorry */ (void) kill(*pid, SIGKILL); @@ -4213,6 +4214,14 @@ int main(int argc, char *argv[]) { if (r < 0) goto finish; + parse_environment(); + + r = cg_unified_flush(); + if (r < 0) { + log_error_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m"); + goto finish; + } + r = verify_arguments(); if (r < 0) goto finish; @@ -4319,16 +4328,15 @@ int main(int argc, char *argv[]) { BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); - if (r == -EEXIST) { - if (!arg_quiet) - log_info("Directory %s already exists, not populating from template %s.", arg_directory, arg_template); - } else if (r < 0) { + if (r == -EEXIST) + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "Directory %s already exists, not populating from template %s.", arg_directory, arg_template); + else if (r < 0) { log_error_errno(r, "Couldn't create snapshot %s from %s: %m", arg_directory, arg_template); goto finish; - } else { - if (!arg_quiet) - log_info("Populated %s from template %s.", arg_directory, arg_template); - } + } else + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "Populated %s from template %s.", arg_directory, arg_template); } } |