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:
* 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

View File

@ -858,6 +858,54 @@
<option>--link-journal=try-guest</option>.</para></listitem>
</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>

View File

@ -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>

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.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

View File

@ -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);

View File

@ -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);

View File

@ -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 (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;
}
if (IN_SET(arg_timezone, TIMEZONE_AUTO, TIMEZONE_SYMLINK)) {
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.");
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/");
/* Already pointing to the right place? Then do nothing .. */
if (y && streq(y, z))
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;
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);
if (m == TIMEZONE_OFF)
return 0;
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;
}
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);
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;
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);
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_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_;
}
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;
}