From a8c3ac66721de23cceff359d946ecd9695bbacb8 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Tue, 4 Nov 2025 18:19:07 +0800 Subject: [PATCH] systemctl: Fix shutdown time parsing across DST changes When parsing an absolute time specification like `hh:mm` for the `shutdown` command, the code interprets a time in the past as "tomorrow at this time". It currently implements this by adding a fixed 24-hour duration (`USEC_PER_DAY`) to the timestamp. This assumption breaks across DST transitions, as the day might not be 24 hours long. This can cause the shutdown to be scheduled at the wrong time (typically off by one hour in either direction). Change the logic to perform calendar arithmetic instead of timestamp arithmetic. If the calculated time is in the past, we increment `tm.tm_mday` and call `mktime_or_timegm_usec()` a second time. This delegates all date normalization logic to `mktime()`, which correctly handles all edge cases, including DST transitions, month-end rollovers, and leap years. Fixes: https://github.com/systemd/systemd/issues/39232 --- src/systemctl/systemctl-compat-shutdown.c | 24 +++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/systemctl/systemctl-compat-shutdown.c b/src/systemctl/systemctl-compat-shutdown.c index d50dec372a7..877ea8378b3 100644 --- a/src/systemctl/systemctl-compat-shutdown.c +++ b/src/systemctl/systemctl-compat-shutdown.c @@ -95,8 +95,28 @@ static int parse_shutdown_time_spec(const char *t, usec_t *ret) { if (r < 0) return r; - while (s <= n) - s += USEC_PER_DAY; + if (s <= n) { + /* The specified time is today, but in the past. We need to schedule it for tomorrow + * at the same time. Adding USEC_PER_DAY would be wrong across DST changes, so just + * let mktime() normalise it. */ + int requested_hour = tm.tm_hour; + int requested_min = tm.tm_min; + + tm.tm_mday++; + tm.tm_isdst = -1; + r = mktime_or_timegm_usec(&tm, /* utc= */ false, &s); + if (r < 0) + return r; + + if (tm.tm_hour != requested_hour || tm.tm_min != requested_min) { + log_warning("Requested shutdown time %02d:%02d does not exist. " + "Rescheduling to %02d:%02d.", + requested_hour, + requested_min, + tm.tm_hour, + tm.tm_min); + } + } *ret = s; } -- 2.47.3