/etc/localtime - /usr/share/zoneinfo/...
or
/etc/localtime - ../usr/share/zoneinfo/...
(note, ../usr is not the same if /etc is a symlink, as this isn't
using canonicalize_file_name())
keep other method for now, consider dropping later.
Supporting relative links here are problematic as timezones in
/usr/share/zoneinfo are often themselves symlinks (and symlinks to
symlinks), so this implamentation only supports absolute symlinks
/usr/share/zoneinfo/ and relative symlinks starting with
../usr/share/zoneinfo/
From TODO (kay sievers):
* kill /etc/timezone handling entirely? What does it provide?
- /etc/localtime carries the same information already:
$ ls -l /etc/localtime; cat /etc/timezone
lrwxrwxrwx 1 root root 33 Jul 27 09:55 /etc/localtime -
/usr/share/zoneinfo/Europe/Berlin
Europe/Berlin
- systemd enforces /usr to be available at bootup, so we can
enforce the use of the symlink
---
src/timedate/timedated.c | 52 +-
1 file changed, 42 insertions(+), 10 deletions(-)
diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c
index 09fd808..b11acae 100644
--- a/src/timedate/timedated.c
+++ b/src/timedate/timedated.c
@@ -74,6 +74,9 @@
BUS_GENERIC_INTERFACES_LIST \
org.freedesktop.timedate1\0
+/* Must start and end with '/' */
+#define ZONEINFO_PATH /usr/share/zoneinfo/
+
const char timedate_interface[] _introspect_(timedate1) = INTERFACE;
typedef struct TZ {
@@ -127,7 +130,7 @@ static bool valid_timezone(const char *name) {
if (slash)
return false;
-t = strappend(/usr/share/zoneinfo/, name);
+t = strappend(ZONEINFO_PATH, name);
if (!t)
return false;
@@ -151,17 +154,17 @@ static void verify_timezone(void) {
if (!tz.zone)
return;
-p = strappend(/usr/share/zoneinfo/, tz.zone);
+p = strappend(ZONEINFO_PATH, tz.zone);
if (!p) {
log_oom();
return;
}
-j = read_full_file(/etc/localtime, a, l);
k = read_full_file(p, b, q);
-
free(p);
+j = read_full_file(/etc/localtime, a, l);
+
if (j 0 || k 0 || l != q || memcmp(a, b, l)) {
log_warning(/etc/localtime and /etc/timezone out of sync.);
free(tz.zone);
@@ -174,9 +177,34 @@ static void verify_timezone(void) {
static int read_data(void) {
int r;
+char *t = NULL;
free_data();
+r = readlink_malloc(/etc/localtime, t);
+if (r 0) {
+if (r == -EINVAL)
+log_warning(/etc/localtime should be a symbolic link
to a timezone data file in ZONEINFO_PATH);
+else
+log_warning(Failed to get target of %s: %s,
/etc/localtime, strerror(-r));
+} else {
+/* we only support the trivial relative link of
(/etc/)..$ABSOLUTE */
+int rel_link_offset = startswith(t, ..) ? strlen(..) : 0;
+
+if (!startswith(t + rel_link_offset, ZONEINFO_PATH))
+log_warning(/etc/localtime should be a symbolic link
to a timezone data file in ZONEINFO_PATH);
+else {
+tz.zone = strdup(t + rel_link_offset +
strlen(ZONEINFO_PATH));
+free(t);
+if (!tz.zone)
+return log_oom();
+
+goto have_timezone;
+}
+}
+
+free(t);
+
r = read_one_line_file(/etc/timezone, tz.zone);
if (r 0) {
if (r != -ENOENT)
@@ -192,6 +220,7 @@ static int read_data(void) {
#endif
}
+have_timezone:
if (isempty(tz.zone)) {
free(tz.zone);
tz.zone = NULL;
@@ -207,6 +236,7 @@ static int read_data(void) {
static int write_data_timezone(void) {
int r = 0;
char *p;
+struct stat st;
if (!tz.zone) {
if (unlink(/etc/timezone) 0 errno != ENOENT)
@@ -218,19 +248,21 @@ static int write_data_timezone(void) {
return r;
}
-p = strappend(/usr/share/zoneinfo/, tz.zone);
+p = strappend(ZONEINFO_PATH, tz.zone);
if (!p)
return log_oom();
-r = symlink_or_copy_atomic(p, /etc/localtime);
+r = symlink(p, /etc/localtime);
free(p);
if (r 0)
-return r;
+return -errno;
-r = write_one_line_file_atomic(/etc/timezone, tz.zone);
-if (r 0)
-return r;
+if (stat(/etc/timezone, st) == 0 S_ISREG(st.st_mode)) {
+r = write_one_line_file_atomic(/etc/timezone, tz.zone);
+if (r 0)
+return r;
+}
return 0;
}
--
1.7.9.5