From 129cb6e249bef30dc33e08f98f0b27a6de976f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= 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. --- 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 5c66641294..bf24d8d5bb 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -1195,15 +1195,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 c62e6860cf..4f1d0f64d5 100644 --- a/src/test/test-calendarspec.c +++ b/src/test/test-calendarspec.c @@ -214,6 +214,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 d7f7967e2f..6b94058fd3 100644 --- a/test/test-functions +++ b/test/test-functions @@ -1340,6 +1340,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