summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2021-03-22 12:51:47 +0100
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2021-03-23 10:01:29 +0100
commite5cf86ff98a21b427e1439a001d8e6b81c07b19c (patch)
treea4f31c684e2898382166f6de180ddcf0a7107c3e
parentf14b80e09e225ccf7cfd8a85578b7e64c3fdebb9 (diff)
downloadsystemd-e5cf86ff98a21b427e1439a001d8e6b81c07b19c.tar.gz
shared/calendarspec: when mktime() moves us backwards, jump forward
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)
-rw-r--r--src/shared/calendarspec.c19
-rw-r--r--src/test/test-calendarspec.c3
-rw-r--r--test/test-functions1
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