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 @@ -858,6 +858,54 @@ . + + + + Configures how /etc/resolv.conf inside of the container (i.e. DNS + configuration synchronization from host to container) shall be handled. Takes one of off, + copy-host, copy-static, bind-host, + bind-static, delete or auto. If set to + off the /etc/resolv.conf file in the container is left as it is + included in the image, and neither modified nor bind mounted over. If set to copy-host, the + /etc/resolv.conf file from the host is copied into the container. Similar, if + bind-host is used, the file is bind mounted from the host into the container. If set to + copy-static the static resolv.conf file supplied with + systemd-resolved.service8 is + copied into the container, and correspondingly bind-static bind mounts it there. If set to + delete the /etc/resolv.conf file in the container is deleted if it + exists. Finally, if set to auto the file is left as it is if private networking is turned on + (see ). Otherwise, if systemd-resolved.service is + connectible its static resolv.conf file is used, and if not the host's + /etc/resolv.conf 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 copy if the container shall be + able to make changes to the DNS configuration on its own, deviating from the host's settings. Otherwise + bind is preferable, as it means direct changes to /etc/resolv.conf 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 + auto. + + + + + + Configures how /etc/localtime inside of the container (i.e. local timezone + synchronization from host to container) shall be handled. Takes one of off, + copy, bind, symlink, delete or + auto. If set to off the /etc/localtime file in the + container is left as it is included in the image, and neither modified nor bind mounted over. If set to + copy the /etc/localtime file of the host is copied into the + container. Similar, if bind is used, it is bind mounted from the host into the container. If + set to symlink a symlink from /etc/localtime 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 delete the file in the container is deleted, should it exist. If set to + auto and the /etc/localtime file of the host is a symlink, then + symlink mode is used, and copy otherwise, except if the image is + read-only in which case bind is used instead. Defaults to + auto. + + 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. + + ResolvConf= + + Configures how /etc/resolv.conf in the container shall be handled. This is + equivalent to the command line switch, and takes the same argument. See + systemd-nspawn1 for + details. + + + + Timezone= + + Configures how /etc/localtime in the container shall be handled. This is + equivalent to the command line switch, and takes the same argument. See + systemd-nspawn1 for + details. + + + + LinkJournal= + + Configures how to link host and container journal setups. This is equivalent to the + command line switch, and takes the same parameter. See + systemd-nspawn1 for + details. + + 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 (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; }