static char *arg_settings_filename = NULL;
static Architecture arg_architecture = _ARCHITECTURE_INVALID;
static ImagePolicy *arg_image_policy = NULL;
+static char *arg_background = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_template, freep);
STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
static int handle_arg_console(const char *arg) {
if (streq(arg, "help")) {
" --version Print version string\n"
" -q --quiet Do not show status information\n"
" --no-pager Do not pipe output into a pager\n"
- " --settings=BOOLEAN Load additional settings from .nspawn file\n\n"
- "%3$sImage:%4$s\n"
+ " --settings=BOOLEAN Load additional settings from .nspawn file\n"
+ "\n%3$sImage:%4$s\n"
" -D --directory=PATH Root directory for the container\n"
" --template=PATH Initialize root directory from template directory,\n"
" if missing\n"
" 'base64:'\n"
" --verity-data=PATH Specify hash device for verity\n"
" --pivot-root=PATH[:PATH]\n"
- " Pivot root to given directory in the container\n\n"
- "%3$sExecution:%4$s\n"
+ " Pivot root to given directory in the container\n"
+ "\n%3$sExecution:%4$s\n"
" -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n"
" -b --boot Boot up full system (i.e. invoke init)\n"
" --chdir=PATH Set working directory in the container\n"
" --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n"
" --notify-ready=BOOLEAN Receive notifications from the child init process\n"
" --suppress-sync=BOOLEAN\n"
- " Suppress any form of disk data synchronization\n\n"
- "%3$sSystem Identity:%4$s\n"
+ " Suppress any form of disk data synchronization\n"
+ "\n%3$sSystem Identity:%4$s\n"
" -M --machine=NAME Set the machine name for the container\n"
" --hostname=NAME Override the hostname for the container\n"
- " --uuid=UUID Set a specific machine UUID for the container\n\n"
- "%3$sProperties:%4$s\n"
+ " --uuid=UUID Set a specific machine UUID for the container\n"
+ "\n%3$sProperties:%4$s\n"
" -S --slice=SLICE Place the container in the specified slice\n"
" --property=NAME=VALUE Set scope unit property\n"
" --register=BOOLEAN Register container as machine\n"
" --keep-unit Do not register a scope for the machine, reuse\n"
- " the service unit nspawn is running in\n\n"
- "%3$sUser Namespacing:%4$s\n"
+ " the service unit nspawn is running in\n"
+ "\n%3$sUser Namespacing:%4$s\n"
" --private-users=no Run without user namespacing\n"
" --private-users=yes|pick|identity\n"
" Run within user namespace, autoselect UID/GID range\n"
" Adjust ('chown') or map ('map') OS tree ownership\n"
" to private UID/GID range\n"
" -U Equivalent to --private-users=pick and\n"
- " --private-users-ownership=auto\n\n"
- "%3$sNetworking:%4$s\n"
+ " --private-users-ownership=auto\n"
+ "\n%3$sNetworking:%4$s\n"
" --private-network Disable network in container\n"
" --network-interface=HOSTIF[:CONTAINERIF]\n"
" Assign an existing network interface to the\n"
" Set network namespace to the one represented by\n"
" the specified kernel namespace file node\n"
" -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n"
- " Expose a container IP port on the host\n\n"
- "%3$sSecurity:%4$s\n"
+ " Expose a container IP port on the host\n"
+ "\n%3$sSecurity:%4$s\n"
" --capability=CAP In addition to the default, retain specified\n"
" capability\n"
" --drop-capability=CAP Drop the specified capability from the default set\n"
" processes in the container\n"
" -L --selinux-apifs-context=SECLABEL\n"
" Set the SELinux security context to be used by\n"
- " API/tmpfs file systems in the container\n\n"
- "%3$sResources:%4$s\n"
+ " API/tmpfs file systems in the container\n"
+ "\n%3$sResources:%4$s\n"
" --rlimit=NAME=LIMIT Set a resource limit for the payload\n"
" --oom-score-adjust=VALUE\n"
" Adjust the OOM score value for the payload\n"
" --cpu-affinity=CPUS Adjust the CPU affinity of the container\n"
- " --personality=ARCH Pick personality for this container\n\n"
- "%3$sIntegration:%4$s\n"
+ " --personality=ARCH Pick personality for this container\n"
+ "\n%3$sIntegration:%4$s\n"
" --resolv-conf=MODE Select mode of /etc/resolv.conf initialization\n"
" --timezone=MODE Select mode of /etc/localtime initialization\n"
" --link-journal=MODE Link up guest journal, one of no, auto, guest, \n"
" host, try-guest, try-host\n"
- " -j Equivalent to --link-journal=try-guest\n\n"
- "%3$sMounts:%4$s\n"
+ " -j Equivalent to --link-journal=try-guest\n"
+ "\n%3$sMounts:%4$s\n"
" --bind=PATH[:PATH[:OPTIONS]]\n"
" Bind mount a file or directory from the host into\n"
" the container\n"
" the container\n"
" --overlay-ro=PATH[:PATH...]:PATH\n"
" Similar, but creates a read-only overlay mount\n"
- " --bind-user=NAME Bind user from host to container\n\n"
- "%3$sInput/Output:%4$s\n"
+ " --bind-user=NAME Bind user from host to container\n"
+ "\n%3$sInput/Output:%4$s\n"
" --console=MODE Select how stdin/stdout/stderr and /dev/console are\n"
" set up for the container.\n"
- " -P --pipe Equivalent to --console=pipe\n\n"
- "%3$sCredentials:%4$s\n"
+ " -P --pipe Equivalent to --console=pipe\n"
+ " --background=COLOR Set ANSI color for background\n"
+ "\n%3$sCredentials:%4$s\n"
" --set-credential=ID:VALUE\n"
" Pass a credential with literal value to container.\n"
" --load-credential=ID:PATH\n"
ARG_BIND_USER,
ARG_SUPPRESS_SYNC,
ARG_IMAGE_POLICY,
+ ARG_BACKGROUND,
};
static const struct option options[] = {
{ "bind-user", required_argument, NULL, ARG_BIND_USER },
{ "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+ { "background", required_argument, NULL, ARG_BACKGROUND },
{}
};
arg_uid_shift = 0;
arg_uid_range = UINT32_C(0x10000);
} else {
- _cleanup_free_ char *buffer = NULL;
- const char *range, *shift;
-
/* anything else: User namespacing on, UID range is explicitly configured */
-
- range = strchr(optarg, ':');
- if (range) {
- buffer = strndup(optarg, range - optarg);
- if (!buffer)
- return log_oom();
- shift = buffer;
-
- range++;
- r = safe_atou32(range, &arg_uid_range);
- if (r < 0)
- return log_error_errno(r, "Failed to parse UID range \"%s\": %m", range);
- } else
- shift = optarg;
-
- r = parse_uid(shift, &arg_uid_shift);
+ r = parse_userns_uid_range(optarg, &arg_uid_shift, &arg_uid_range);
if (r < 0)
- return log_error_errno(r, "Failed to parse UID \"%s\": %m", optarg);
-
+ return r;
arg_userns_mode = USER_NAMESPACE_FIXED;
-
- if (!userns_shift_range_valid(arg_uid_shift, arg_uid_range))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID range cannot be empty or go beyond " UID_FMT ".", UID_INVALID);
}
arg_settings_mask |= SETTING_USERNS;
break;
- case ARG_CHDIR:
+ case ARG_CHDIR: {
+ _cleanup_free_ char *wd = NULL;
+
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);
+ r = path_simplify_alloc(optarg, &wd);
if (r < 0)
- return log_oom();
+ return log_error_errno(r, "Failed to simplify path %s: %m", optarg);
+ if (!path_is_normalized(wd))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory path is not normalized: %s", wd);
+
+ if (path_below_api_vfs(wd))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory is below API VFS, refusing: %s", wd);
+
+ free_and_replace(arg_chdir, wd);
arg_settings_mask |= SETTING_WORKING_DIRECTORY;
break;
+ }
case ARG_PIVOT_ROOT:
r = pivot_root_parse(&arg_pivot_root_new, &arg_pivot_root_old, optarg);
return r;
break;
+ case ARG_BACKGROUND:
+ r = free_and_strdup_warn(&arg_background, optarg);
+ if (r < 0)
+ return r;
+ break;
+
case '?':
return -EINVAL;
if (arg_ephemeral && arg_template)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--ephemeral and --template= may not be combined.");
- 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.");
+ /* Permit --ephemeral with --link-journal=try-* to satisfy principle of the least astonishment
+ * (by common sense, "try" means "do not fail if not possible") */
+ if (arg_ephemeral && !IN_SET(arg_link_journal, LINK_NO, LINK_AUTO) && !arg_link_journal_try)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--ephemeral and --link-journal={host,guest} may not be combined.");
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.");
p = strjoina("/var/log/journal/", SD_ID128_TO_STRING(arg_uuid));
q = prefix_roota(directory, p);
- if (path_is_mount_point(p, NULL, 0) > 0) {
+ if (path_is_mount_point(p) > 0) {
if (try)
return 0;
"%s: already a mount point, refusing to use for journal", p);
}
- if (path_is_mount_point(q, NULL, 0) > 0) {
+ if (path_is_mount_point(q) > 0) {
if (try)
return 0;
}
if (!arg_machine) {
- if (arg_directory && path_equal(arg_directory, "/"))
+ if (arg_directory && path_equal(arg_directory, "/")) {
arg_machine = gethostname_malloc();
- else if (arg_image) {
+ if (!arg_machine)
+ return log_oom();
+ } else if (arg_image) {
char *e;
r = path_extract_filename(arg_image, &arg_machine);
if (!barrier_place_and_sync(barrier)) /* #5 */
return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Parent died too early");
+ /* Note, this should be done this late (💣 and not moved earlier! 💣), so that all namespacing
+ * changes are already in effect by now, so that any resolved paths here definitely reference
+ * resources inside the container, and not outside of them. */
if (arg_chdir)
if (chdir(arg_chdir) < 0)
return log_error_errno(errno, "Failed to change to specified working directory %s: %m", arg_chdir);
if (!p)
return log_oom();
- r = path_is_mount_point(p, /* root= */ NULL, 0);
+ r = path_is_mount_point(p);
if (r > 0)
return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Mount point '%s' exists already, refusing.", p);
if (r < 0 && r != -ENOENT)
return 0;
}
+static DissectImageFlags determine_dissect_image_flags(void) {
+ return
+ DISSECT_IMAGE_USR_NO_ROOT |
+ DISSECT_IMAGE_DISCARD_ON_LOOP |
+ (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS) |
+ DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
+}
+
static int outer_child(
Barrier *barrier,
const char *directory,
arg_uid_shift,
arg_uid_range,
/* userns_fd= */ -EBADF,
+ determine_dissect_image_flags()|
DISSECT_IMAGE_MOUNT_ROOT_ONLY|
- DISSECT_IMAGE_DISCARD_ON_LOOP|
- DISSECT_IMAGE_USR_NO_ROOT|
- (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS)|
(arg_start_mode == START_BOOT ? DISSECT_IMAGE_VALIDATE_OS : 0));
if (r < 0)
return r;
dirs[i] = NULL;
- r = remount_idmap(dirs, arg_uid_shift, arg_uid_range, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT);
+ r = remount_idmap(dirs, arg_uid_shift, arg_uid_range, UID_INVALID, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT);
if (r == -EINVAL || ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
/* This might fail because the kernel or file system doesn't support idmapping. We
* can't really distinguish this nicely, nor do we have any guarantees about the
arg_uid_shift,
arg_uid_range,
/* userns_fd= */ -EBADF,
+ determine_dissect_image_flags()|
DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY|
- DISSECT_IMAGE_DISCARD_ON_LOOP|
- DISSECT_IMAGE_USR_NO_ROOT|
- (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS)|
(idmap ? DISSECT_IMAGE_MOUNT_IDMAPPED : 0));
if (r == -EUCLEAN)
return log_error_errno(r, "File system check for image failed: %m");
return 0;
}
+static void set_window_title(PTYForward *f) {
+ _cleanup_free_ char *hn = NULL, *dot = NULL;
+
+ assert(f);
+
+ (void) gethostname_strict(&hn);
+
+ if (emoji_enabled())
+ dot = strjoin(special_glyph(SPECIAL_GLYPH_BLUE_CIRCLE), " ");
+
+ if (hn)
+ (void) pty_forward_set_titlef(f, "%sContainer %s on %s", strempty(dot), arg_machine, hn);
+ else
+ (void) pty_forward_set_titlef(f, "%sContainer %s", strempty(dot), arg_machine);
+
+ if (dot)
+ (void) pty_forward_set_title_prefix(f, dot);
+}
+
static int merge_settings(Settings *settings, const char *path) {
int rl;
return log_error_errno(r, "Failed to create PTY forwarder: %m");
if (arg_console_width != UINT_MAX || arg_console_height != UINT_MAX)
- (void) pty_forward_set_width_height(forward,
- arg_console_width,
- arg_console_height);
+ (void) pty_forward_set_width_height(
+ forward,
+ arg_console_width,
+ arg_console_height);
+
+ if (!arg_background) {
+ _cleanup_free_ char *bg = NULL;
+
+ r = terminal_tint_color(220 /* blue */, &bg);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine terminal background color, not tinting.");
+ else
+ (void) pty_forward_set_background_color(forward, bg);
+ } else if (!isempty(arg_background))
+ (void) pty_forward_set_background_color(forward, arg_background);
+
+ set_window_title(forward);
break;
default:
if (r < 0)
goto finish;
+ r = resolve_network_interface_names(arg_network_interfaces);
+ if (r < 0)
+ goto finish;
+
r = verify_network_interfaces_initialized();
if (r < 0)
goto finish;
/* If the specified path is a mount point we generate the new snapshot immediately
* inside it under a random name. However if the specified is not a mount point we
* create the new snapshot in the parent directory, just next to it. */
- r = path_is_mount_point(arg_directory, NULL, 0);
+ r = path_is_mount_point(arg_directory);
if (r < 0) {
log_error_errno(r, "Failed to determine whether directory %s is mount point: %m", arg_directory);
goto finish;
_cleanup_free_ char *u = NULL;
(void) terminal_urlify_path(t, t, &u);
- log_info("%s %sSpawning container %s on %s.%s\n"
- "%s %sPress %sCtrl-]%s three times within 1s to kill container.%s",
- special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: t, ansi_normal(),
- special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
+ log_info("%s %sSpawning container %s on %s.%s",
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: t, ansi_normal());
+
+ if (arg_console_mode == CONSOLE_INTERACTIVE)
+ log_info("%s %sPress %sCtrl-]%s three times within 1s to kill container.%s",
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
}
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0);
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, SIGRTMIN+18) >= 0);
r = make_reaper_process(true);
if (r < 0) {