From: Lennart Poettering Date: Thu, 17 May 2018 03:43:03 +0000 (-0400) Subject: nspawn: similar to the previous patches, also make /etc/localtime handling more confi... X-Git-Tag: v239~213^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1688841f4628f4e55ad31e769da3a712474bf995;p=thirdparty%2Fsystemd.git nspawn: similar to the previous patches, also make /etc/localtime handling more configurable Fixes: #9009 --- diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 03e79683bcd..1c8c6c8e60b 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -887,6 +887,25 @@ 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 3484d5cac6b..275f96ca138 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -349,6 +349,15 @@ 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= diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index 485ae201b8d..6029686ee9a 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -55,6 +55,7 @@ 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 e63a14cbac5..126335da582 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -38,6 +38,7 @@ int settings_load(FILE *f, const char *path, Settings **ret) { 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; @@ -797,3 +798,16 @@ int config_parse_link_journal( 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 69fce584a9f..9be7679027f 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -54,6 +54,17 @@ typedef enum LinkJournal { _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, @@ -78,9 +89,10 @@ typedef enum SettingsMask { SETTING_CPU_AFFINITY = UINT64_C(1) << 20, SETTING_RESOLV_CONF = UINT64_C(1) << 21, SETTING_LINK_JOURNAL = UINT64_C(1) << 22, - SETTING_RLIMIT_FIRST = UINT64_C(1) << 23, /* we define one bit per resource limit here */ - SETTING_RLIMIT_LAST = UINT64_C(1) << (23 + _RLIMIT_MAX - 1), - _SETTINGS_MASK_ALL = (UINT64_C(1) << (23 + _RLIMIT_MAX)) - 1, + 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, _FORCE_ENUM_WIDTH = UINT64_MAX } SettingsMask; @@ -122,6 +134,7 @@ typedef struct Settings { ResolvConfMode resolv_conf; LinkJournal link_journal; bool link_journal_try; + TimezoneMode timezone; /* [Image] */ int read_only; @@ -171,8 +184,12 @@ 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 8a234b8f880..5a0a3891750 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -206,6 +206,7 @@ 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) { @@ -283,6 +284,7 @@ static void help(void) { " 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" @@ -460,6 +462,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_OOM_SCORE_ADJUST, ARG_CPU_AFFINITY, ARG_RESOLV_CONF, + ARG_TIMEZONE, }; static const struct option options[] = { @@ -519,6 +522,7 @@ static int parse_argv(int argc, char *argv[]) { { "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 }, {} }; @@ -1221,6 +1225,21 @@ static int parse_argv(int argc, char *argv[]) { 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; @@ -1436,72 +1455,147 @@ 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; } @@ -3441,6 +3535,10 @@ static int merge_settings(Settings *settings, const char *path) { } } + if ((arg_settings_mask & SETTING_TIMEZONE) == 0 && + settings->timezone != _TIMEZONE_MODE_INVALID) + arg_timezone = settings->timezone; + return 0; }