summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2018-05-24 16:27:27 +0200
committerGitHub <noreply@github.com>2018-05-24 16:27:27 +0200
commit17c1b9a93fd89df4f070797cd5bcd2bf0049d6ae (patch)
tree14b684a749b6125b2a755e24e3df7b811b548467
parent7cd92e2e9dcaed160558e7f955bebf8824a8c768 (diff)
parent72d711efa3376f26727d099524651b70c31860b8 (diff)
downloadsystemd-17c1b9a93fd89df4f070797cd5bcd2bf0049d6ae.tar.gz
systemd-17c1b9a93fd89df4f070797cd5bcd2bf0049d6ae.tar.bz2
systemd-17c1b9a93fd89df4f070797cd5bcd2bf0049d6ae.zip
Merge pull request #9024 from poettering/nspawn-attrs-more
make even more nspawn concepts configurable
-rw-r--r--TODO6
-rw-r--r--man/systemd-nspawn.xml48
-rw-r--r--man/systemd.nspawn.xml27
-rw-r--r--src/nspawn/nspawn-gperf.gperf3
-rw-r--r--src/nspawn/nspawn-settings.c87
-rw-r--r--src/nspawn/nspawn-settings.h58
-rw-r--r--src/nspawn/nspawn.c340
7 files changed, 469 insertions, 100 deletions
diff --git a/TODO b/TODO
index 32838a4f25..bdb8a18de9 100644
--- a/TODO
+++ b/TODO
@@ -24,9 +24,9 @@ Janitorial Clean-ups:
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
@@ -559,7 +559,7 @@ Features:
- document chaining of signal handler for SIGCHLD and child handlers
- 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
- - 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
diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml
index 9a0e02187f..1c8c6c8e60 100644
--- a/man/systemd-nspawn.xml
+++ b/man/systemd-nspawn.xml
@@ -859,6 +859,54 @@
</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>
<term><option>--read-only</option></term>
<listitem><para>Mount the root file system read-only for the
diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml
index 1780bfd79a..275f96ca13 100644
--- a/man/systemd.nspawn.xml
+++ b/man/systemd.nspawn.xml
@@ -340,6 +340,33 @@
details.</para></listitem>
</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>
</refsect1>
diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf
index f8234e75d4..6029686ee9 100644
--- a/src/nspawn/nspawn-gperf.gperf
+++ b/src/nspawn/nspawn-gperf.gperf
@@ -53,6 +53,9 @@ Exec.Hostname, config_parse_hostname, 0, of
Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges)
Exec.OOMScoreAdjust, config_parse_oom_score_adjust, 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.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode)
Files.Bind, config_parse_bind, 0, 0
diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c
index 0acf718456..126335da58 100644
--- a/src/nspawn/nspawn-settings.c
+++ b/src/nspawn/nspawn-settings.c
@@ -16,6 +16,7 @@
#include "process-util.h"
#include "rlimit-util.h"
#include "socket-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.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->personality = PERSONALITY_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_range = UID_INVALID;
s->no_new_privileges = -1;
@@ -724,3 +728,86 @@ int config_parse_cpu_affinity(
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);
diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h
index 8dc310d569..28a0d8b8e1 100644
--- a/src/nspawn/nspawn-settings.h
+++ b/src/nspawn/nspawn-settings.h
@@ -33,6 +33,38 @@ typedef enum UserNamespaceMode {
_USER_NAMESPACE_MODE_INVALID = -1,
} 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 {
SETTING_START_MODE = UINT64_C(1) << 0,
SETTING_ENVIRONMENT = UINT64_C(1) << 1,
@@ -55,10 +87,13 @@ typedef enum SettingsMask {
SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18,
SETTING_OOM_SCORE_ADJUST = UINT64_C(1) << 19,
SETTING_CPU_AFFINITY = UINT64_C(1) << 20,
- SETTING_RLIMIT_FIRST = UINT64_C(1) << 21, /* we define one bit per resource limit here */
- SETTING_RLIMIT_LAST = UINT64_C(1) << (21 + _RLIMIT_MAX - 1),
- _SETTINGS_MASK_ALL = (UINT64_C(1) << (21 + _RLIMIT_MAX)) - 1,
- _FORCE_ENUM_WIDTH = UINT64_MAX
+ SETTING_RESOLV_CONF = UINT64_C(1) << 21,
+ SETTING_LINK_JOURNAL = UINT64_C(1) << 22,
+ SETTING_TIMEZONE = UINT64_C(1) << 23,
+ 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;
/* 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;
cpu_set_t *cpuset;
unsigned cpuset_ncpus;
+ ResolvConfMode resolv_conf;
+ LinkJournal link_journal;
+ bool link_journal_try;
+ TimezoneMode timezone;
/* [Image] */
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_oom_score_adjust);
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);
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 638935d551..009ecf4e4a 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -38,6 +38,7 @@
#include "base-filesystem.h"
#include "blkid-util.h"
#include "btrfs-util.h"
+#include "bus-error.h"
#include "bus-util.h"
#include "cap-list.h"
#include "capability-util.h"
@@ -117,13 +118,6 @@ typedef enum ContainerStatus {
CONTAINER_REBOOTED
} ContainerStatus;
-typedef enum LinkJournal {
- LINK_NO,
- LINK_AUTO,
- LINK_HOST,
- LINK_GUEST
-} LinkJournal;
-
static char *arg_directory = NULL;
static char *arg_template = 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 cpu_set_t *arg_cpuset = NULL;
static unsigned arg_cpuset_ncpus = 0;
+static ResolvConfMode arg_resolv_conf = RESOLV_CONF_AUTO;
+static TimezoneMode arg_timezone = TIMEZONE_AUTO;
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"
" host, try-guest, try-host\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"
" --bind=PATH[:PATH[:OPTIONS]]\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_OOM_SCORE_ADJUST,
ARG_CPU_AFFINITY,
+ ARG_RESOLV_CONF,
+ ARG_TIMEZONE,
};
static const struct option options[] = {
@@ -521,6 +521,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "rlimit", required_argument, NULL, ARG_RLIMIT },
{ "oom-score-adjust", required_argument, NULL, ARG_OOM_SCORE_ADJUST },
{ "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':
arg_link_journal = LINK_GUEST;
arg_link_journal_try = true;
+ arg_settings_mask |= SETTING_LINK_JOURNAL;
break;
case ARG_LINK_JOURNAL:
- if (streq(optarg, "auto")) {
- arg_link_journal = LINK_AUTO;
- arg_link_journal_try = false;
- } 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);
+ r = parse_link_journal(optarg, &arg_link_journal, &arg_link_journal_try);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse link journal mode %s", optarg);
return -EINVAL;
}
+ arg_settings_mask |= SETTING_LINK_JOURNAL;
break;
case ARG_BIND:
@@ -885,6 +872,7 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_SHARE_SYSTEM:
/* We don't officially support this anymore, except for compat reasons. People should use the
* $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;
break;
@@ -1222,6 +1210,36 @@ static int parse_argv(int argc, char *argv[]) {
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 '?':
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);
}
+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) {
- _cleanup_free_ char *p = NULL, *q = NULL;
- const char *where, *check, *what;
- char *z, *y;
+ _cleanup_free_ char *p = NULL, *etc = NULL;
+ const char *where, *check;
+ TimezoneMode m;
int r;
assert(dest);
- /* Fix the timezone, if possible */
- r = readlink_malloc("/etc/localtime", &p);
+ if (IN_SET(arg_timezone, TIMEZONE_AUTO, TIMEZONE_SYMLINK)) {
+
+ r = readlink_malloc("/etc/localtime", &p);
+ if (r == -ENOENT && arg_timezone == TIMEZONE_AUTO)
+ m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? TIMEZONE_OFF : TIMEZONE_DELETE;
+ else if (r == -EINVAL && arg_timezone == TIMEZONE_AUTO) /* regular file? */
+ m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? TIMEZONE_BIND : TIMEZONE_COPY;
+ 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
+ * file.
+ *
+ * Example:
+ * ln -s /usr/share/zoneinfo/UTC /etc/localtime
+ */
+ 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;
+
+ if (m == TIMEZONE_OFF)
+ return 0;
+
+ r = chase_symlinks("/etc", dest, CHASE_PREFIX_ROOT, &etc);
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
- */
+ log_warning_errno(r, "Failed to resolve /etc path in container, ignoring: %m");
return 0;
}
- z = path_startswith(p, "../usr/share/zoneinfo/");
- if (!z)
- z = path_startswith(p, "/usr/share/zoneinfo/");
- if (!z) {
- log_warning("/etc/localtime does not point into /usr/share/zoneinfo/, not updating container timezone.");
+ where = strjoina(etc, "/localtime");
+
+ switch (m) {
+
+ 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;
- }
- where = prefix_roota(dest, "/etc/localtime");
- r = readlink_malloc(where, &q);
- if (r >= 0) {
- y = path_startswith(q, "../usr/share/zoneinfo/");
- if (!y)
- y = path_startswith(q, "/usr/share/zoneinfo/");
+ case TIMEZONE_SYMLINK: {
+ _cleanup_free_ char *q = NULL;
+ const char *z, *what;
- /* Already pointing to the right place? Then do nothing .. */
- if (y && streq(y, z))
+ z = timezone_from_path(p);
+ if (!z) {
+ log_warning("/etc/localtime does not point into /usr/share/zoneinfo/, not updating container timezone.");
return 0;
- }
+ }
- check = strjoina("/usr/share/zoneinfo/", z);
- check = prefix_roota(dest, check);
- if (laccess(check, F_OK) < 0) {
- log_warning("Timezone %s does not exist in container, not updating container timezone.", z);
- 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_;
}
- 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;
+ case TIMEZONE_BIND: {
+ _cleanup_free_ char *resolved = NULL;
+ int found;
+
+ found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved);
+ 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_;
}
- 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;
+ 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);
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;
}
+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) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *dns_stub_listener_mode = NULL;
int r;
@@ -1516,33 +1623,53 @@ static int resolved_listening(void) {
r = sd_bus_open_system(&bus);
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);
- if (r <= 0)
- return r;
+ if (r < 0)
+ 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,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"DNSStubListener",
- NULL,
+ &error,
&dns_stub_listener_mode);
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");
}
static int setup_resolv_conf(const char *dest) {
- _cleanup_free_ char *resolved = NULL, *etc = NULL;
- const char *where;
- int r, found;
+ _cleanup_free_ char *etc = NULL;
+ const char *where, *what;
+ ResolvConfMode m;
+ int r;
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;
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");
- 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");
+
+ if (m == RESOLV_CONF_DELETE) {
+ if (unlink(where) < 0)
+ log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, "Failed to remove '%s', ignoring: %m", where);
+
return 0;
}
- if (access(STATIC_RESOLV_CONF, F_OK) >= 0 &&
- resolved_listening() > 0) {
+ if (IN_SET(m, RESOLV_CONF_BIND_STATIC, RESOLV_CONF_COPY_STATIC))
+ 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
- * 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. */
+ if (IN_SET(m, RESOLV_CONF_BIND_HOST, RESOLV_CONF_BIND_STATIC)) {
+ _cleanup_free_ char *resolved = NULL;
+ int found;
+
+ 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? */
(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)
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 */
- 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 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.
*
* 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);
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;
}