]> git.proxmox.com Git - systemd.git/commitdiff
backport calendarspec fix for reverse DST change and safety net
authorThomas Lamprecht <t.lamprecht@proxmox.com>
Sun, 26 Mar 2023 09:43:15 +0000 (11:43 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Sun, 26 Mar 2023 09:53:18 +0000 (11:53 +0200)
backport a patch that copes with mktime moving backward for change to
"summer time" like the Ireland/Dubline TZ (uniquely) does, as for
them the summer time is the standard time. And such, IIUC the
algorithm was caught in an endless loop as basically it continued to
jump back before the time change and redid that time change; this is
not a problem for other TZ as they got the standard time switched and
such time only "moves back" on switch from DST to standard time,
where the switch then doesn't happen anymore.

Additionally add a safety net that aborts calendar spec calculation
after 1000 iterations.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
debian/patches/proxmox/0001-shared-calendarspec-abort-calculation-after-1000-ite.patch [new file with mode: 0644]
debian/patches/proxmox/0002-shared-calendarspec-when-mktime-moves-us-backwards-j.patch [new file with mode: 0644]
debian/patches/series

diff --git a/debian/patches/proxmox/0001-shared-calendarspec-abort-calculation-after-1000-ite.patch b/debian/patches/proxmox/0001-shared-calendarspec-abort-calculation-after-1000-ite.patch
new file mode 100644 (file)
index 0000000..efbeb5e
--- /dev/null
@@ -0,0 +1,58 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Sun, 21 Mar 2021 20:59:32 +0100
+Subject: [PATCH] shared/calendarspec: abort calculation after 1000 iterations
+
+We have a bug where we seem to enter an infinite loop when running in the
+Europe/Dublin timezone. The timezone is "special" because it has negative SAVE
+values. The handling of this should obviously be fixed, but let's use a
+belt-and-suspenders approach, and gracefully fail if we fail to find an answer
+within a specific number of attempts. The code in this function is rather
+complex, and it's hard to rule out another bug in the future.
+
+(cherry picked from commit 169615c9a8cdc54d748d4dfc8279be9b3c2bec44)
+(cherry picked from commit f14b80e09e225ccf7cfd8a85578b7e64c3fdebb9)
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ src/shared/calendarspec.c | 14 +++++++++++++-
+ 1 file changed, 13 insertions(+), 1 deletion(-)
+
+diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c
+index 7162592fbf..80acc57800 100644
+--- a/src/shared/calendarspec.c
++++ b/src/shared/calendarspec.c
+@@ -1211,6 +1211,10 @@ static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
+         return (weekdays_bits & (1 << k));
+ }
++/* A safety valve: if we get stuck in the calculation, return an error.
++ * C.f. https://bugzilla.redhat.com/show_bug.cgi?id=1941335. */
++#define MAX_CALENDAR_ITERATIONS 1000
++
+ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
+         struct tm c;
+         int tm_usec;
+@@ -1224,7 +1228,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
+         c = *tm;
+         tm_usec = *usec;
+-        for (;;) {
++        for (unsigned iteration = 0; iteration < MAX_CALENDAR_ITERATIONS; iteration++) {
+                 /* Normalize the current date */
+                 (void) mktime_or_timegm(&c, spec->utc);
+                 c.tm_isdst = spec->dst;
+@@ -1321,6 +1325,14 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
+                 *usec = tm_usec;
+                 return 0;
+         }
++
++        /* It seems we entered an infinite loop. Let's gracefully return an error instead of hanging or
++         * aborting. This code is also exercised when timers.target is brought up during early boot, so
++         * aborting here is problematic and hard to diagnose for users. */
++        _cleanup_free_ char *s = NULL;
++        (void) calendar_spec_to_string(spec, &s);
++        return log_warning_errno(SYNTHETIC_ERRNO(EDEADLK),
++                                 "Infinite loop in calendar calculation: %s", strna(s));
+ }
+ static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *ret_next) {
diff --git a/debian/patches/proxmox/0002-shared-calendarspec-when-mktime-moves-us-backwards-j.patch b/debian/patches/proxmox/0002-shared-calendarspec-when-mktime-moves-us-backwards-j.patch
new file mode 100644 (file)
index 0000000..87f6322
--- /dev/null
@@ -0,0 +1,109 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Mon, 22 Mar 2021 12:51:47 +0100
+Subject: [PATCH] shared/calendarspec: when mktime() moves us backwards, jump
+ forward
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+When trying to calculate the next firing of 'Sun *-*-* 01:00:00', we'd fall
+into an infinite loop, because mktime() moves us "backwards":
+
+Before this patch:
+tm_within_bounds: good=0 2021-03-29 01:00:00 → 2021-03-29 00:00:00
+tm_within_bounds: good=0 2021-03-29 01:00:00 → 2021-03-29 00:00:00
+tm_within_bounds: good=0 2021-03-29 01:00:00 → 2021-03-29 00:00:00
+...
+
+We rely on mktime() normalizing the time. The man page does not say that it'll
+move the time forward, but our algorithm relies on this. So let's catch this
+case explicitly.
+
+With this patch:
+$ TZ=Europe/Dublin faketime 2021-03-21 build/systemd-analyze calendar --iterations=5 'Sun *-*-* 01:00:00'
+Normalized form: Sun *-*-* 01:00:00
+    Next elapse: Sun 2021-03-21 01:00:00 GMT
+       (in UTC): Sun 2021-03-21 01:00:00 UTC
+       From now: 59min left
+       Iter. #2: Sun 2021-04-04 01:00:00 IST
+       (in UTC): Sun 2021-04-04 00:00:00 UTC
+       From now: 1 weeks 6 days left           <---- note the 2 week jump here
+       Iter. #3: Sun 2021-04-11 01:00:00 IST
+       (in UTC): Sun 2021-04-11 00:00:00 UTC
+       From now: 2 weeks 6 days left
+       Iter. #4: Sun 2021-04-18 01:00:00 IST
+       (in UTC): Sun 2021-04-18 00:00:00 UTC
+       From now: 3 weeks 6 days left
+       Iter. #5: Sun 2021-04-25 01:00:00 IST
+       (in UTC): Sun 2021-04-25 00:00:00 UTC
+       From now: 1 months 4 days left
+
+Fixes https://bugzilla.redhat.com/show_bug.cgi?id=1941335.
+
+(cherry picked from commit 129cb6e249bef30dc33e08f98f0b27a6de976f6f)
+(cherry picked from commit e5cf86ff98a21b427e1439a001d8e6b81c07b19c)
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ src/shared/calendarspec.c    | 19 +++++++++++--------
+ src/test/test-calendarspec.c |  3 +++
+ test/test-functions          |  1 +
+ 3 files changed, 15 insertions(+), 8 deletions(-)
+
+diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c
+index 80acc57800..c8d97c3da1 100644
+--- a/src/shared/calendarspec.c
++++ b/src/shared/calendarspec.c
+@@ -1185,15 +1185,18 @@ static int tm_within_bounds(struct tm *tm, bool utc) {
+                 return negative_errno();
+         /* Did any normalization take place? If so, it was out of bounds before */
+-        bool good = t.tm_year == tm->tm_year &&
+-                    t.tm_mon  == tm->tm_mon  &&
+-                    t.tm_mday == tm->tm_mday &&
+-                    t.tm_hour == tm->tm_hour &&
+-                    t.tm_min  == tm->tm_min  &&
+-                    t.tm_sec  == tm->tm_sec;
+-        if (!good)
++        int cmp = CMP(t.tm_year, tm->tm_year) ?:
++                  CMP(t.tm_mon, tm->tm_mon) ?:
++                  CMP(t.tm_mday, tm->tm_mday) ?:
++                  CMP(t.tm_hour, tm->tm_hour) ?:
++                  CMP(t.tm_min, tm->tm_min) ?:
++                  CMP(t.tm_sec, tm->tm_sec);
++
++        if (cmp < 0)
++                return -EDEADLK; /* Refuse to go backward */
++        if (cmp > 0)
+                 *tm = t;
+-        return good;
++        return cmp == 0;
+ }
+ static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
+diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c
+index e0b7f22808..1b04186a23 100644
+--- a/src/test/test-calendarspec.c
++++ b/src/test/test-calendarspec.c
+@@ -218,6 +218,9 @@ int main(int argc, char* argv[]) {
+         // Confirm that timezones in the Spec work regardless of current timezone
+         test_next("2017-09-09 20:42:00 Pacific/Auckland", "", 12345, 1504946520000000);
+         test_next("2017-09-09 20:42:00 Pacific/Auckland", "EET", 12345, 1504946520000000);
++        /* Check that we don't start looping if mktime() moves us backwards */
++        test_next("Sun *-*-* 01:00:00 Europe/Dublin", "", 1616412478000000, 1617494400000000);
++        test_next("Sun *-*-* 01:00:00 Europe/Dublin", "IST", 1616412478000000, 1617494400000000);
+         assert_se(calendar_spec_from_string("test", &c) < 0);
+         assert_se(calendar_spec_from_string(" utc", &c) < 0);
+diff --git a/test/test-functions b/test/test-functions
+index 52b52bf29e..beaf4fae2f 100644
+--- a/test/test-functions
++++ b/test/test-functions
+@@ -1120,6 +1120,7 @@ install_zoneinfo() {
+     inst_any /usr/share/zoneinfo/Asia/Vladivostok
+     inst_any /usr/share/zoneinfo/Australia/Sydney
+     inst_any /usr/share/zoneinfo/Europe/Berlin
++    inst_any /usr/share/zoneinfo/Europe/Dublin
+     inst_any /usr/share/zoneinfo/Europe/Kiev
+     inst_any /usr/share/zoneinfo/Pacific/Auckland
+     inst_any /usr/share/zoneinfo/Pacific/Honolulu
index d0ebfa1ba6e22a049aa631a1e138f39d201a324a..fa40ea278a8df877513010d418808d4e1279e680 100644 (file)
@@ -53,3 +53,5 @@ debian/Downgrade-a-couple-of-warnings-to-debug.patch
 debian/Revert-udev-fix-memleak.patch
 debian/Revert-udev-link_update-should-fail-if-the-entry-in-symli.patch
 debian/Revert-udev-make-algorithm-that-selects-highest-priority-.patch
+proxmox/0001-shared-calendarspec-abort-calculation-after-1000-ite.patch
+proxmox/0002-shared-calendarspec-when-mktime-moves-us-backwards-j.patch