--- /dev/null
+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) {
--- /dev/null
+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