From 844c44a0d44bc4db053d9a46c6f0477aad009b8d Mon Sep 17 00:00:00 2001 From: dongshengyuan <545258830@qq.com> Date: Mon, 17 Aug 2026 11:03:55 +0800 Subject: [PATCH] timedated: roll back state on config write failure SetTimezone() and SetLocalRTC() updated the Context before writing /etc/localtime or /etc/adjtime. If the write failed, D-Bus properties showed the new value and a repeated request with the same value returned success without retrying the file update. Keep the write helpers Context-based, but save the old value before installing the requested state. Restore the old Context value when the backing file update fails. Add TEST-45 coverage for failed alternate localtime and adjtime paths. Follow-up for: f401e48c2db22ff9d1a05885b5599bebf19c2707 --- src/timedate/timedated.c | 14 +++++++++++--- test/units/TEST-45-TIMEDATE.sh | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c index efc255d5e5..25e6ddccdf 100644 --- a/src/timedate/timedated.c +++ b/src/timedate/timedated.c @@ -672,6 +672,7 @@ static int property_get_ntp( static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) { Context *c = ASSERT_PTR(userdata); + _cleanup_free_ char *old_zone = NULL, *zone = NULL; int interactive, r; const char *z; @@ -701,13 +702,17 @@ static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error * if (r == 0) return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - r = free_and_strdup(&c->zone, z); - if (r < 0) - return r; + zone = strdup(z); + if (!zone) + return -ENOMEM; + + old_zone = TAKE_PTR(c->zone); + c->zone = TAKE_PTR(zone); /* 1. Write new configuration file */ r = context_write_data_timezone(c); if (r < 0) { + free_and_replace(c->zone, old_zone); log_error_errno(r, "Failed to set time zone: %m"); return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m"); } @@ -778,11 +783,14 @@ static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error return 1; if (lrtc != c->local_rtc) { + bool old_local_rtc = c->local_rtc; + c->local_rtc = lrtc; /* 1. Write new configuration file */ r = context_write_data_local_rtc(c); if (r < 0) { + c->local_rtc = old_local_rtc; log_error_errno(r, "Failed to set RTC to %s: %m", lrtc ? "local" : "UTC"); return sd_bus_error_set_errnof(error, r, "Failed to set RTC to %s: %m", lrtc ? "local" : "UTC"); } diff --git a/test/units/TEST-45-TIMEDATE.sh b/test/units/TEST-45-TIMEDATE.sh index 0af99fc96c..8b2d027982 100755 --- a/test/units/TEST-45-TIMEDATE.sh +++ b/test/units/TEST-45-TIMEDATE.sh @@ -484,6 +484,7 @@ teardown_timedated_alternate_paths() { set +eu rm -rf /run/systemd/system/systemd-timedated.service.d + rm -rf /run/alternate-path systemctl daemon-reload systemctl restart systemd-timedated } @@ -507,11 +508,25 @@ EOF assert_eq "$(readlink /run/alternate-path/mylocaltime | sed 's#^.*zoneinfo/##')" "Europe/Kyiv" assert_in "Time zone: Europe/Kyiv \(EES*T, \+0[0-9]00\)" "$(timedatectl)" + rm -f /run/alternate-path/mylocaltime + mkdir /run/alternate-path/mylocaltime + assert_rc 1 timedatectl --no-pager set-timezone UTC + assert_eq "$(timedatectl show -p Timezone --value)" "Europe/Kyiv" + assert_rc 1 timedatectl --no-pager set-timezone UTC + rmdir /run/alternate-path/mylocaltime + assert_rc 0 timedatectl --no-pager set-timezone UTC + assert_rc 0 timedatectl --no-pager set-timezone Europe/Kyiv + # Restart to force using get_timezine systemctl restart systemd-timedated assert_in "Time zone: Europe/Kyiv \(EES*T, \+0[0-9]00\)" "$(timedatectl)" assert_in "RTC in local TZ: no" "$(timedatectl --no-pager)" + mkdir /run/alternate-path/myadjtime + assert_rc 1 timedatectl set-local-rtc 1 + assert_in "RTC in local TZ: no" "$(timedatectl --no-pager)" + assert_rc 1 timedatectl set-local-rtc 1 + rmdir /run/alternate-path/myadjtime assert_rc 0 timedatectl set-local-rtc 1 assert_in "RTC in local TZ: yes" "$(timedatectl --no-pager)" assert_eq "$(cat /run/alternate-path/myadjtime)" "0.0 0 0