Merge pull request #9024 from poettering/nspawn-attrs-more

make even more nspawn concepts configurable
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2018-05-24 16:27:27 +02:00 committed by GitHub
commit 17c1b9a93f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 473 additions and 104 deletions

6
TODO
View File

@ -24,9 +24,9 @@ Janitorial Clean-ups:
Features: Features:
* nspawn: greater control over hostname, resolv.conf, timezone, rlim * add O_TMPFILE support to copy_file_atomic()
* nspawn: when operating in a scope, also create /payload subcrgoup * nspawn: greater control over selinux label?
* the error paths in usbffs_dispatch_ep() leak memory * the error paths in usbffs_dispatch_ep() leak memory
@ -559,7 +559,7 @@ Features:
- document chaining of signal handler for SIGCHLD and child handlers - document chaining of signal handler for SIGCHLD and child handlers
- define more intervals where we will shift wakeup intervals around in, 1h, 6h, 24h, ... - define more intervals where we will shift wakeup intervals around in, 1h, 6h, 24h, ...
- generate a failure of a default event loop is executed out-of-thread - generate a failure of a default event loop is executed out-of-thread
- maybe add support for inotify events - maybe add support for inotify events (which we can do safely now, with O_PATH)
* investigate endianness issues of UUID vs. GUID * investigate endianness issues of UUID vs. GUID

View File

@ -858,6 +858,54 @@
<option>--link-journal=try-guest</option>.</para></listitem> <option>--link-journal=try-guest</option>.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>--resolv-conf=</option></term>
<listitem><para>Configures how <filename>/etc/resolv.conf</filename> inside of the container (i.e. DNS
configuration synchronization from host to container) shall be handled. Takes one of <literal>off</literal>,
<literal>copy-host</literal>, <literal>copy-static</literal>, <literal>bind-host</literal>,
<literal>bind-static</literal>, <literal>delete</literal> or <literal>auto</literal>. If set to
<literal>off</literal> the <filename>/etc/resolv.conf</filename> file in the container is left as it is
included in the image, and neither modified nor bind mounted over. If set to <literal>copy-host</literal>, the
<filename>/etc/resolv.conf</filename> file from the host is copied into the container. Similar, if
<literal>bind-host</literal> is used, the file is bind mounted from the host into the container. If set to
<literal>copy-static</literal> the static <filename>resolv.conf</filename> file supplied with
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> is
copied into the container, and correspondingly <literal>bind-static</literal> bind mounts it there. If set to
<literal>delete</literal> the <filename>/etc/resolv.conf</filename> file in the container is deleted if it
exists. Finally, if set to <literal>auto</literal> the file is left as it is if private networking is turned on
(see <option>--private-network</option>). Otherwise, if <filename>systemd-resolved.service</filename> is
connectible its static <filename>resolv.conf</filename> file is used, and if not the host's
<filename>/etc/resolv.conf</filename> file is used. In the latter cases the file is copied if the image is
writable, and bind mounted otherwise. It's recommended to use <literal>copy</literal> if the container shall be
able to make changes to the DNS configuration on its own, deviating from the host's settings. Otherwise
<literal>bind</literal> is preferable, as it means direct changes to <filename>/etc/resolv.conf</filename> in
the container are not allowed, as it is a read-only bind mount (but note that if the container has enough
privileges, it might simply go ahead and unmount the bind mount anyway). Note that both if the file is bind
mounted and if it is copied no further propagation of configuration is generally done after the one-time early
initialization (this is because the file is usually updated through copying and renaming). Defaults to
<literal>auto</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--timezone=</option></term>
<listitem><para>Configures how <filename>/etc/localtime</filename> inside of the container (i.e. local timezone
synchronization from host to container) shall be handled. Takes one of <literal>off</literal>,
<literal>copy</literal>, <literal>bind</literal>, <literal>symlink</literal>, <literal>delete</literal> or
<literal>auto</literal>. If set to <literal>off</literal> the <filename>/etc/localtime</filename> file in the
container is left as it is included in the image, and neither modified nor bind mounted over. If set to
<literal>copy</literal> the <filename>/etc/localtime</filename> file of the host is copied into the
container. Similar, if <literal>bind</literal> is used, it is bind mounted from the host into the container. If
set to <literal>symlink</literal> a symlink from <filename>/etc/localtime</filename> in the container is
created pointing to the matching the timezone file of the container that matches the timezone setting on the
host. If set to <literal>delete</literal> the file in the container is deleted, should it exist. If set to
<literal>auto</literal> and the <filename>/etc/localtime</filename> file of the host is a symlink, then
<literal>symlink</literal> mode is used, and <literal>copy</literal> otherwise, except if the image is
read-only in which case <literal>bind</literal> is used instead. Defaults to
<literal>auto</literal>.</para></listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><option>--read-only</option></term> <term><option>--read-only</option></term>

View File

@ -340,6 +340,33 @@
details.</para></listitem> details.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>ResolvConf=</varname></term>
<listitem><para>Configures how <filename>/etc/resolv.conf</filename> in the container shall be handled. This is
equivalent to the <option>--resolv-conf=</option> command line switch, and takes the same argument. See
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>Timezone=</varname></term>
<listitem><para>Configures how <filename>/etc/localtime</filename> in the container shall be handled. This is
equivalent to the <option>--localtime=</option> command line switch, and takes the same argument. See
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LinkJournal=</varname></term>
<listitem><para>Configures how to link host and container journal setups. This is equivalent to the
<option>--link-journal=</option> command line switch, and takes the same parameter. See
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details.</para></listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View File

@ -53,6 +53,9 @@ Exec.Hostname, config_parse_hostname, 0, of
Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges) Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges)
Exec.OOMScoreAdjust, config_parse_oom_score_adjust, 0, 0 Exec.OOMScoreAdjust, config_parse_oom_score_adjust, 0, 0
Exec.CPUAffinity, config_parse_cpu_affinity, 0, 0 Exec.CPUAffinity, config_parse_cpu_affinity, 0, 0
Exec.ResolvConf, config_parse_resolv_conf, 0, offsetof(Settings, resolv_conf)
Exec.LinkJournal, config_parse_link_journal, 0, 0
Exec.Timezone, config_parse_timezone, 0, offsetof(Settings, timezone)
Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only)
Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode)
Files.Bind, config_parse_bind, 0, 0 Files.Bind, config_parse_bind, 0, 0

View File

@ -16,6 +16,7 @@
#include "process-util.h" #include "process-util.h"
#include "rlimit-util.h" #include "rlimit-util.h"
#include "socket-util.h" #include "socket-util.h"
#include "string-table.h"
#include "string-util.h" #include "string-util.h"
#include "strv.h" #include "strv.h"
#include "user-util.h" #include "user-util.h"
@ -35,6 +36,9 @@ int settings_load(FILE *f, const char *path, Settings **ret) {
s->start_mode = _START_MODE_INVALID; s->start_mode = _START_MODE_INVALID;
s->personality = PERSONALITY_INVALID; s->personality = PERSONALITY_INVALID;
s->userns_mode = _USER_NAMESPACE_MODE_INVALID; s->userns_mode = _USER_NAMESPACE_MODE_INVALID;
s->resolv_conf = _RESOLV_CONF_MODE_INVALID;
s->link_journal = _LINK_JOURNAL_INVALID;
s->timezone = _TIMEZONE_MODE_INVALID;
s->uid_shift = UID_INVALID; s->uid_shift = UID_INVALID;
s->uid_range = UID_INVALID; s->uid_range = UID_INVALID;
s->no_new_privileges = -1; s->no_new_privileges = -1;
@ -724,3 +728,86 @@ int config_parse_cpu_affinity(
return 0; return 0;
} }
DEFINE_CONFIG_PARSE_ENUM(config_parse_resolv_conf, resolv_conf_mode, ResolvConfMode, "Failed to parse resolv.conf mode");
static const char *const resolv_conf_mode_table[_RESOLV_CONF_MODE_MAX] = {
[RESOLV_CONF_OFF] = "off",
[RESOLV_CONF_COPY_HOST] = "copy-host",
[RESOLV_CONF_COPY_STATIC] = "copy-static",
[RESOLV_CONF_BIND_HOST] = "bind-host",
[RESOLV_CONF_BIND_STATIC] = "bind-static",
[RESOLV_CONF_DELETE] = "delete",
[RESOLV_CONF_AUTO] = "auto",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolv_conf_mode, ResolvConfMode, RESOLV_CONF_AUTO);
int parse_link_journal(const char *s, LinkJournal *ret_mode, bool *ret_try) {
assert(s);
assert(ret_mode);
assert(ret_try);
if (streq(s, "auto")) {
*ret_mode = LINK_AUTO;
*ret_try = false;
} else if (streq(s, "no")) {
*ret_mode = LINK_NO;
*ret_try = false;
} else if (streq(s, "guest")) {
*ret_mode = LINK_GUEST;
*ret_try = false;
} else if (streq(s, "host")) {
*ret_mode = LINK_HOST;
*ret_try = false;
} else if (streq(s, "try-guest")) {
*ret_mode = LINK_GUEST;
*ret_try = true;
} else if (streq(s, "try-host")) {
*ret_mode = LINK_HOST;
*ret_try = true;
} else
return -EINVAL;
return 0;
}
int config_parse_link_journal(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Settings *settings = data;
int r;
assert(rvalue);
assert(settings);
r = parse_link_journal(rvalue, &settings->link_journal, &settings->link_journal_try);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse link journal mode, ignoring: %s", rvalue);
return 0;
}
return 0;
}
DEFINE_CONFIG_PARSE_ENUM(config_parse_timezone, timezone_mode, TimezoneMode, "Failed to parse timezone mode");
static const char *const timezone_mode_table[_TIMEZONE_MODE_MAX] = {
[TIMEZONE_OFF] = "off",
[TIMEZONE_COPY] = "copy",
[TIMEZONE_BIND] = "bind",
[TIMEZONE_SYMLINK] = "symlink",
[TIMEZONE_DELETE] = "delete",
[TIMEZONE_AUTO] = "auto",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(timezone_mode, TimezoneMode, TIMEZONE_AUTO);

View File

@ -33,6 +33,38 @@ typedef enum UserNamespaceMode {
_USER_NAMESPACE_MODE_INVALID = -1, _USER_NAMESPACE_MODE_INVALID = -1,
} UserNamespaceMode; } UserNamespaceMode;
typedef enum ResolvConfMode {
RESOLV_CONF_OFF,
RESOLV_CONF_COPY_HOST,
RESOLV_CONF_COPY_STATIC,
RESOLV_CONF_BIND_HOST,
RESOLV_CONF_BIND_STATIC,
RESOLV_CONF_DELETE,
RESOLV_CONF_AUTO,
_RESOLV_CONF_MODE_MAX,
_RESOLV_CONF_MODE_INVALID = -1
} ResolvConfMode;
typedef enum LinkJournal {
LINK_NO,
LINK_AUTO,
LINK_HOST,
LINK_GUEST,
_LINK_JOURNAL_MAX,
_LINK_JOURNAL_INVALID = -1
} LinkJournal;
typedef enum TimezoneMode {
TIMEZONE_OFF,
TIMEZONE_COPY,
TIMEZONE_BIND,
TIMEZONE_SYMLINK,
TIMEZONE_DELETE,
TIMEZONE_AUTO,
_TIMEZONE_MODE_MAX,
_TIMEZONE_MODE_INVALID = -1
} TimezoneMode;
typedef enum SettingsMask { typedef enum SettingsMask {
SETTING_START_MODE = UINT64_C(1) << 0, SETTING_START_MODE = UINT64_C(1) << 0,
SETTING_ENVIRONMENT = UINT64_C(1) << 1, SETTING_ENVIRONMENT = UINT64_C(1) << 1,
@ -55,10 +87,13 @@ typedef enum SettingsMask {
SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18, SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18,
SETTING_OOM_SCORE_ADJUST = UINT64_C(1) << 19, SETTING_OOM_SCORE_ADJUST = UINT64_C(1) << 19,
SETTING_CPU_AFFINITY = UINT64_C(1) << 20, SETTING_CPU_AFFINITY = UINT64_C(1) << 20,
SETTING_RLIMIT_FIRST = UINT64_C(1) << 21, /* we define one bit per resource limit here */ SETTING_RESOLV_CONF = UINT64_C(1) << 21,
SETTING_RLIMIT_LAST = UINT64_C(1) << (21 + _RLIMIT_MAX - 1), SETTING_LINK_JOURNAL = UINT64_C(1) << 22,
_SETTINGS_MASK_ALL = (UINT64_C(1) << (21 + _RLIMIT_MAX)) - 1, SETTING_TIMEZONE = UINT64_C(1) << 23,
_FORCE_ENUM_WIDTH = UINT64_MAX SETTING_RLIMIT_FIRST = UINT64_C(1) << 24, /* we define one bit per resource limit here */
SETTING_RLIMIT_LAST = UINT64_C(1) << (24 + _RLIMIT_MAX - 1),
_SETTINGS_MASK_ALL = (UINT64_C(1) << (24 + _RLIMIT_MAX)) -1,
_SETTING_FORCE_ENUM_WIDTH = UINT64_MAX
} SettingsMask; } SettingsMask;
/* We want to use SETTING_RLIMIT_FIRST in shifts, so make sure it is really 64 bits /* We want to use SETTING_RLIMIT_FIRST in shifts, so make sure it is really 64 bits
@ -96,6 +131,10 @@ typedef struct Settings {
bool oom_score_adjust_set; bool oom_score_adjust_set;
cpu_set_t *cpuset; cpu_set_t *cpuset;
unsigned cpuset_ncpus; unsigned cpuset_ncpus;
ResolvConfMode resolv_conf;
LinkJournal link_journal;
bool link_journal_try;
TimezoneMode timezone;
/* [Image] */ /* [Image] */
int read_only; int read_only;
@ -143,3 +182,14 @@ CONFIG_PARSER_PROTOTYPE(config_parse_syscall_filter);
CONFIG_PARSER_PROTOTYPE(config_parse_hostname); CONFIG_PARSER_PROTOTYPE(config_parse_hostname);
CONFIG_PARSER_PROTOTYPE(config_parse_oom_score_adjust); CONFIG_PARSER_PROTOTYPE(config_parse_oom_score_adjust);
CONFIG_PARSER_PROTOTYPE(config_parse_cpu_affinity); CONFIG_PARSER_PROTOTYPE(config_parse_cpu_affinity);
CONFIG_PARSER_PROTOTYPE(config_parse_resolv_conf);
CONFIG_PARSER_PROTOTYPE(config_parse_link_journal);
CONFIG_PARSER_PROTOTYPE(config_parse_timezone);
const char *resolv_conf_mode_to_string(ResolvConfMode a) _const_;
ResolvConfMode resolv_conf_mode_from_string(const char *s) _pure_;
const char *timezone_mode_to_string(TimezoneMode a) _const_;
TimezoneMode timezone_mode_from_string(const char *s) _pure_;
int parse_link_journal(const char *s, LinkJournal *ret_mode, bool *ret_try);

View File

@ -38,6 +38,7 @@
#include "base-filesystem.h" #include "base-filesystem.h"
#include "blkid-util.h" #include "blkid-util.h"
#include "btrfs-util.h" #include "btrfs-util.h"
#include "bus-error.h"
#include "bus-util.h" #include "bus-util.h"
#include "cap-list.h" #include "cap-list.h"
#include "capability-util.h" #include "capability-util.h"
@ -117,13 +118,6 @@ typedef enum ContainerStatus {
CONTAINER_REBOOTED CONTAINER_REBOOTED
} ContainerStatus; } ContainerStatus;
typedef enum LinkJournal {
LINK_NO,
LINK_AUTO,
LINK_HOST,
LINK_GUEST
} LinkJournal;
static char *arg_directory = NULL; static char *arg_directory = NULL;
static char *arg_template = NULL; static char *arg_template = NULL;
static char *arg_chdir = NULL; static char *arg_chdir = NULL;
@ -211,6 +205,8 @@ static int arg_oom_score_adjust = 0;
static bool arg_oom_score_adjust_set = false; static bool arg_oom_score_adjust_set = false;
static cpu_set_t *arg_cpuset = NULL; static cpu_set_t *arg_cpuset = NULL;
static unsigned arg_cpuset_ncpus = 0; static unsigned arg_cpuset_ncpus = 0;
static ResolvConfMode arg_resolv_conf = RESOLV_CONF_AUTO;
static TimezoneMode arg_timezone = TIMEZONE_AUTO;
static void help(void) { static void help(void) {
@ -287,6 +283,8 @@ static void help(void) {
" --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n"
" host, try-guest, try-host\n" " host, try-guest, try-host\n"
" -j Equivalent to --link-journal=try-guest\n" " -j Equivalent to --link-journal=try-guest\n"
" --resolv-conf=MODE Select mode of /etc/resolv.conf initialization\n"
" --timezone=MODE Select mode of /etc/localtime initialization\n"
" --read-only Mount the root directory read-only\n" " --read-only Mount the root directory read-only\n"
" --bind=PATH[:PATH[:OPTIONS]]\n" " --bind=PATH[:PATH[:OPTIONS]]\n"
" Bind mount a file or directory from the host into\n" " Bind mount a file or directory from the host into\n"
@ -463,6 +461,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_NO_NEW_PRIVILEGES, ARG_NO_NEW_PRIVILEGES,
ARG_OOM_SCORE_ADJUST, ARG_OOM_SCORE_ADJUST,
ARG_CPU_AFFINITY, ARG_CPU_AFFINITY,
ARG_RESOLV_CONF,
ARG_TIMEZONE,
}; };
static const struct option options[] = { static const struct option options[] = {
@ -521,6 +521,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "rlimit", required_argument, NULL, ARG_RLIMIT }, { "rlimit", required_argument, NULL, ARG_RLIMIT },
{ "oom-score-adjust", required_argument, NULL, ARG_OOM_SCORE_ADJUST }, { "oom-score-adjust", required_argument, NULL, ARG_OOM_SCORE_ADJUST },
{ "cpu-affinity", required_argument, NULL, ARG_CPU_AFFINITY }, { "cpu-affinity", required_argument, NULL, ARG_CPU_AFFINITY },
{ "resolv-conf", required_argument, NULL, ARG_RESOLV_CONF },
{ "timezone", required_argument, NULL, ARG_TIMEZONE },
{} {}
}; };
@ -805,32 +807,17 @@ static int parse_argv(int argc, char *argv[]) {
case 'j': case 'j':
arg_link_journal = LINK_GUEST; arg_link_journal = LINK_GUEST;
arg_link_journal_try = true; arg_link_journal_try = true;
arg_settings_mask |= SETTING_LINK_JOURNAL;
break; break;
case ARG_LINK_JOURNAL: case ARG_LINK_JOURNAL:
if (streq(optarg, "auto")) { r = parse_link_journal(optarg, &arg_link_journal, &arg_link_journal_try);
arg_link_journal = LINK_AUTO; if (r < 0) {
arg_link_journal_try = false; log_error_errno(r, "Failed to parse link journal mode %s", optarg);
} else if (streq(optarg, "no")) {
arg_link_journal = LINK_NO;
arg_link_journal_try = false;
} else if (streq(optarg, "guest")) {
arg_link_journal = LINK_GUEST;
arg_link_journal_try = false;
} else if (streq(optarg, "host")) {
arg_link_journal = LINK_HOST;
arg_link_journal_try = false;
} else if (streq(optarg, "try-guest")) {
arg_link_journal = LINK_GUEST;
arg_link_journal_try = true;
} else if (streq(optarg, "try-host")) {
arg_link_journal = LINK_HOST;
arg_link_journal_try = true;
} else {
log_error("Failed to parse link journal mode %s", optarg);
return -EINVAL; return -EINVAL;
} }
arg_settings_mask |= SETTING_LINK_JOURNAL;
break; break;
case ARG_BIND: case ARG_BIND:
@ -885,6 +872,7 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_SHARE_SYSTEM: case ARG_SHARE_SYSTEM:
/* We don't officially support this anymore, except for compat reasons. People should use the /* We don't officially support this anymore, except for compat reasons. People should use the
* $SYSTEMD_NSPAWN_SHARE_* environment variables instead. */ * $SYSTEMD_NSPAWN_SHARE_* environment variables instead. */
log_warning("Please do not use --share-system anymore, use $SYSTEMD_NSPAWN_SHARE_* instead.");
arg_clone_ns_flags = 0; arg_clone_ns_flags = 0;
break; break;
@ -1222,6 +1210,36 @@ static int parse_argv(int argc, char *argv[]) {
break; break;
} }
case ARG_RESOLV_CONF:
if (streq(optarg, "help")) {
DUMP_STRING_TABLE(resolv_conf_mode, ResolvConfMode, _RESOLV_CONF_MODE_MAX);
return 0;
}
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;
}
arg_settings_mask |= SETTING_RESOLV_CONF;
break;
case ARG_TIMEZONE:
if (streq(optarg, "help")) {
DUMP_STRING_TABLE(timezone_mode, TimezoneMode, _TIMEZONE_MODE_MAX);
return 0;
}
arg_timezone = timezone_mode_from_string(optarg);
if (arg_timezone < 0) {
log_error("Failed to parse /etc/localtime mode: %s", optarg);
return -EINVAL;
}
arg_settings_mask |= SETTING_TIMEZONE;
break;
case '?': case '?':
return -EINVAL; return -EINVAL;
@ -1437,77 +1455,166 @@ static int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t u
return userns_lchown(q, uid, gid); return userns_lchown(q, uid, gid);
} }
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;
}
static int setup_timezone(const char *dest) { static int setup_timezone(const char *dest) {
_cleanup_free_ char *p = NULL, *q = NULL; _cleanup_free_ char *p = NULL, *etc = NULL;
const char *where, *check, *what; const char *where, *check;
char *z, *y; TimezoneMode m;
int r; int r;
assert(dest); assert(dest);
/* Fix the timezone, if possible */ if (IN_SET(arg_timezone, TIMEZONE_AUTO, TIMEZONE_SYMLINK)) {
r = readlink_malloc("/etc/localtime", &p);
if (r < 0) {
log_warning("host's /etc/localtime is not a symlink, not updating container timezone.");
/* to handle warning, delete /etc/localtime and replace it
* with a symbolic link to a time zone data file.
*
* Example:
* ln -s /usr/share/zoneinfo/UTC /etc/localtime
*/
return 0;
}
z = path_startswith(p, "../usr/share/zoneinfo/"); r = readlink_malloc("/etc/localtime", &p);
if (!z) if (r == -ENOENT && arg_timezone == TIMEZONE_AUTO)
z = path_startswith(p, "/usr/share/zoneinfo/"); m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? TIMEZONE_OFF : TIMEZONE_DELETE;
if (!z) { else if (r == -EINVAL && arg_timezone == TIMEZONE_AUTO) /* regular file? */
log_warning("/etc/localtime does not point into /usr/share/zoneinfo/, not updating container timezone."); m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? TIMEZONE_BIND : TIMEZONE_COPY;
return 0; else if (r < 0) {
} log_warning_errno(r, "Failed to read host's /etc/localtime symlink, not updating container timezone: %m");
/* To handle warning, delete /etc/localtime and replace it with a symbolic link to a time zone data
where = prefix_roota(dest, "/etc/localtime"); * file.
r = readlink_malloc(where, &q); *
if (r >= 0) { * Example:
y = path_startswith(q, "../usr/share/zoneinfo/"); * ln -s /usr/share/zoneinfo/UTC /etc/localtime
if (!y) */
y = path_startswith(q, "/usr/share/zoneinfo/");
/* Already pointing to the right place? Then do nothing .. */
if (y && streq(y, z))
return 0; return 0;
} } else if (arg_timezone == TIMEZONE_AUTO)
m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? TIMEZONE_BIND : TIMEZONE_SYMLINK;
else
m = arg_timezone;
} else
m = arg_timezone;
check = strjoina("/usr/share/zoneinfo/", z); if (m == TIMEZONE_OFF)
check = prefix_roota(dest, check); return 0;
if (laccess(check, F_OK) < 0) {
log_warning("Timezone %s does not exist in container, not updating container timezone.", z); r = chase_symlinks("/etc", dest, CHASE_PREFIX_ROOT, &etc);
if (r < 0) {
log_warning_errno(r, "Failed to resolve /etc path in container, ignoring: %m");
return 0; return 0;
} }
if (unlink(where) < 0 && errno != ENOENT) { where = strjoina(etc, "/localtime");
log_full_errno(IN_SET(errno, EROFS, EACCES, EPERM) ? LOG_DEBUG : LOG_WARNING, /* Don't complain on read-only images */
errno, switch (m) {
"Failed to remove existing timezone info %s in container, ignoring: %m", where);
case TIMEZONE_DELETE:
if (unlink(where) < 0)
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, "Failed to remove '%s', ignoring: %m", where);
return 0; return 0;
case TIMEZONE_SYMLINK: {
_cleanup_free_ char *q = NULL;
const char *z, *what;
z = timezone_from_path(p);
if (!z) {
log_warning("/etc/localtime does not point into /usr/share/zoneinfo/, not updating container timezone.");
return 0;
}
r = readlink_malloc(where, &q);
if (r >= 0 && streq_ptr(timezone_from_path(q), z))
return 0; /* Already pointing to the right place? Then do nothing .. */
check = strjoina(dest, "/usr/share/zoneinfo/", z);
r = chase_symlinks(check, dest, 0, NULL);
if (r < 0)
log_debug_errno(r, "Timezone %s does not exist (or is not accessible) in container, not creating symlink: %m", z);
else {
if (unlink(where) < 0 && errno != ENOENT) {
log_full_errno(IN_SET(errno, EROFS, EACCES, EPERM) ? LOG_DEBUG : LOG_WARNING, /* Don't complain on read-only images */
errno, "Failed to remove existing timezone info %s in container, ignoring: %m", where);
return 0;
}
what = strjoina("../usr/share/zoneinfo/", z);
if (symlink(what, where) < 0) {
log_full_errno(IN_SET(errno, EROFS, EACCES, EPERM) ? LOG_DEBUG : LOG_WARNING,
errno, "Failed to correct timezone of container, ignoring: %m");
return 0;
}
break;
}
_fallthrough_;
} }
what = strjoina("../usr/share/zoneinfo/", z); case TIMEZONE_BIND: {
if (symlink(what, where) < 0) { _cleanup_free_ char *resolved = NULL;
log_full_errno(IN_SET(errno, EROFS, EACCES, EPERM) ? LOG_DEBUG : LOG_WARNING, int found;
errno,
"Failed to correct timezone of container, ignoring: %m"); found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved);
return 0; if (found < 0) {
log_warning_errno(found, "Failed to resolve /etc/localtime path in container, ignoring: %m");
return 0;
}
if (found == 0) /* missing? */
(void) touch(resolved);
r = mount_verbose(LOG_WARNING, "/etc/localtime", resolved, NULL, MS_BIND, NULL);
if (r >= 0)
return mount_verbose(LOG_ERR, NULL, resolved, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL);
_fallthrough_;
} }
case TIMEZONE_COPY:
/* If mounting failed, try to copy */
r = copy_file_atomic("/etc/localtime", where, 0644, 0, COPY_REFLINK|COPY_REPLACE);
if (r < 0) {
log_full_errno(IN_SET(r, -EROFS, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING, r,
"Failed to copy /etc/localtime to %s, ignoring: %m", where);
return 0;
}
break;
default:
assert_not_reached("unexpected mode");
}
/* Fix permissions of the symlink or file copy we just created */
r = userns_lchown(where, 0, 0); r = userns_lchown(where, 0, 0);
if (r < 0) if (r < 0)
return log_warning_errno(r, "Failed to chown /etc/localtime: %m"); log_warning_errno(r, "Failed to chown /etc/localtime, ignoring: %m");
return 0; return 0;
} }
static int have_resolv_conf(const char *path) {
assert(path);
if (access(path, F_OK) < 0) {
if (errno == ENOENT)
return 0;
return log_debug_errno(errno, "Failed to determine whether '%s' is available: %m", path);
}
return 1;
}
static int resolved_listening(void) { static int resolved_listening(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *dns_stub_listener_mode = NULL; _cleanup_free_ char *dns_stub_listener_mode = NULL;
int r; int r;
@ -1516,33 +1623,53 @@ static int resolved_listening(void) {
r = sd_bus_open_system(&bus); r = sd_bus_open_system(&bus);
if (r < 0) if (r < 0)
return r; return log_debug_errno(r, "Failed to open system bus: %m");
r = bus_name_has_owner(bus, "org.freedesktop.resolve1", NULL); r = bus_name_has_owner(bus, "org.freedesktop.resolve1", NULL);
if (r <= 0) if (r < 0)
return r; return log_debug_errno(r, "Failed to check whether the 'org.freedesktop.resolve1' bus name is taken: %m");
if (r == 0)
return 0;
r = sd_bus_get_property_string(bus, r = sd_bus_get_property_string(bus,
"org.freedesktop.resolve1", "org.freedesktop.resolve1",
"/org/freedesktop/resolve1", "/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager", "org.freedesktop.resolve1.Manager",
"DNSStubListener", "DNSStubListener",
NULL, &error,
&dns_stub_listener_mode); &dns_stub_listener_mode);
if (r < 0) if (r < 0)
return r; return log_debug_errno(r, "Failed to query DNSStubListener property: %s", bus_error_message(&error, r));
return STR_IN_SET(dns_stub_listener_mode, "udp", "yes"); return STR_IN_SET(dns_stub_listener_mode, "udp", "yes");
} }
static int setup_resolv_conf(const char *dest) { static int setup_resolv_conf(const char *dest) {
_cleanup_free_ char *resolved = NULL, *etc = NULL; _cleanup_free_ char *etc = NULL;
const char *where; const char *where, *what;
int r, found; ResolvConfMode m;
int r;
assert(dest); assert(dest);
if (arg_private_network) if (arg_resolv_conf == RESOLV_CONF_AUTO) {
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;
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
m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? RESOLV_CONF_OFF : RESOLV_CONF_DELETE;
} else
m = arg_resolv_conf;
if (m == RESOLV_CONF_OFF)
return 0; return 0;
r = chase_symlinks("/etc", dest, CHASE_PREFIX_ROOT, &etc); r = chase_symlinks("/etc", dest, CHASE_PREFIX_ROOT, &etc);
@ -1552,38 +1679,46 @@ static int setup_resolv_conf(const char *dest) {
} }
where = strjoina(etc, "/resolv.conf"); where = strjoina(etc, "/resolv.conf");
found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved);
if (found < 0) { if (m == RESOLV_CONF_DELETE) {
log_warning_errno(found, "Failed to resolve /etc/resolv.conf path in container, ignoring: %m"); if (unlink(where) < 0)
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, "Failed to remove '%s', ignoring: %m", where);
return 0; return 0;
} }
if (access(STATIC_RESOLV_CONF, F_OK) >= 0 && if (IN_SET(m, RESOLV_CONF_BIND_STATIC, RESOLV_CONF_COPY_STATIC))
resolved_listening() > 0) { what = STATIC_RESOLV_CONF;
else
what = "/etc/resolv.conf";
/* resolved is enabled on the host. In this, case bind mount its static resolv.conf file into the if (IN_SET(m, RESOLV_CONF_BIND_HOST, RESOLV_CONF_BIND_STATIC)) {
* container, so that the container can use the host's resolver. Given that network namespacing is _cleanup_free_ char *resolved = NULL;
* disabled it's only natural of the container also uses the host's resolver. It also has the big int found;
* advantage that the container will be able to follow the host's DNS server configuration changes
* transparently. */ found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved);
if (found < 0) {
log_warning_errno(found, "Failed to resolve /etc/resolv.conf path in container, ignoring: %m");
return 0;
}
if (found == 0) /* missing? */ if (found == 0) /* missing? */
(void) touch(resolved); (void) touch(resolved);
r = mount_verbose(LOG_DEBUG, STATIC_RESOLV_CONF, resolved, NULL, MS_BIND, NULL); r = mount_verbose(LOG_WARNING, what, resolved, NULL, MS_BIND, NULL);
if (r >= 0) if (r >= 0)
return mount_verbose(LOG_ERR, NULL, resolved, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL); return mount_verbose(LOG_ERR, NULL, resolved, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL);
} }
/* If that didn't work, let's copy the file */ /* If that didn't work, let's copy the file */
r = copy_file("/etc/resolv.conf", where, O_TRUNC|O_NOFOLLOW, 0644, 0, COPY_REFLINK); r = copy_file(what, where, O_TRUNC|O_NOFOLLOW, 0644, 0, COPY_REFLINK);
if (r < 0) { if (r < 0) {
/* If the file already exists as symlink, let's suppress the warning, under the assumption that /* If the file already exists as symlink, let's suppress the warning, under the assumption that
* resolved or something similar runs inside and the symlink points there. * resolved or something similar runs inside and the symlink points there.
* *
* If the disk image is read-only, there's also no point in complaining. * If the disk image is read-only, there's also no point in complaining.
*/ */
log_full_errno(IN_SET(r, -ELOOP, -EROFS, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING, r, log_full_errno(!IN_SET(RESOLV_CONF_COPY_HOST, RESOLV_CONF_COPY_STATIC) && IN_SET(r, -ELOOP, -EROFS, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING, r,
"Failed to copy /etc/resolv.conf to %s, ignoring: %m", where); "Failed to copy /etc/resolv.conf to %s, ignoring: %m", where);
return 0; return 0;
} }
@ -3385,6 +3520,25 @@ static int merge_settings(Settings *settings, const char *path) {
} }
} }
if ((arg_settings_mask & SETTING_RESOLV_CONF) == 0 &&
settings->resolv_conf != _RESOLV_CONF_MODE_INVALID)
arg_resolv_conf = settings->resolv_conf;
if ((arg_settings_mask & SETTING_LINK_JOURNAL) == 0 &&
settings->link_journal != _LINK_JOURNAL_INVALID) {
if (!arg_settings_trusted)
log_warning("Ignoring journal link setting, file '%s' is not trusted.", path);
else {
arg_link_journal = settings->link_journal;
arg_link_journal_try = settings->link_journal_try;
}
}
if ((arg_settings_mask & SETTING_TIMEZONE) == 0 &&
settings->timezone != _TIMEZONE_MODE_INVALID)
arg_timezone = settings->timezone;
return 0; return 0;
} }