[libvirt] [PATCHv2 0/4] qemu: fix broken RTC_CHANGE event when clock offset='variable'

(the cover letter from V1 has been preserved here with small updates) This patch series is all about: https://bugzilla.redhat.com/show_bug.cgi?id=964177 which points out a flaw in qemu's RTC_CHANGE event when the base for the guest's real time clock was given as an explicit date (rather than as 'utc' or 'localtime'). Patch 3/4 explains what qemu does, and how this patch fixes it (for the case of basis='utc' - an additional fix to properly support basis='localtime', both for RTC_CHANGE and when migrating across timezone/DST boundaries, is in Patch 4/4). (NB: the flawed RTC_CHANGE offset has been in QEMU for so long now that it has been documented and cannot be changed, so libvirt must work around it) In the meantime, the fix for basis='localtime' required that we learn the offset of localtime from utc, and that seems like something we might want to do for other reasons, so Patch 1/4 adds a new utility function do get that value that is (I hope!) POSIX compliant. Since the original patch to fix this problem was incorrect, and the new patch doesn't use any of its code, Patch 2/4 reverts that patch completely. Note that I have tested this patch by starting a domain with several variations of <clock> parameters (in a locale that is currently at UTC+3) and using the following short script to add and remove seconds from the guest's RTC while watching both the <clock> line in /var/run/libvirt/qemu/$domain.xml and the offset value sent in libvirt's VIR_DOMAIN_EVENT_ID_RTC_CHANGE event (using examples/domain-events/events-c/event-test from a libvirt source tree that has been built). All cases appear to maintain the adjustment in the status properly, as well as sending the correct value to any event handler. Here is the script I used to add/remove time from the RTC: #!/bin/sh old=$(hwclock --show | cut -f1-7 -d' ') oldabs=$(date +%s --date="$old") newabs=$(expr $oldabs + $1) new=$(date --date="@$newabs") echo Old: $oldabs $old echo New: $newabs $new hwclock --set --date="@$newabs" Laine Stump (4): util: new function virTimeLocalOffsetFromUTC Revert "qemu: Report the offset from host UTC for RTC_CHANGE event" qemu: fix RTC_CHANGE event for <clock offset='variable' basis='utc'/> qemu: fix <clock offset='variable' basis='localtime'/> src/conf/domain_conf.c | 32 +++++++++++++------------------- src/conf/domain_conf.h | 8 +++++--- src/libvirt_private.syms | 1 + src/qemu/qemu_command.c | 43 ++++++++++++++++++++++++++++++------------- src/qemu/qemu_process.c | 34 +++++++++++++++++++--------------- src/util/virtime.c | 41 ++++++++++++++++++++++++++++++++++++++++- src/util/virtime.h | 5 +++-- tests/virtimetest.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 155 insertions(+), 53 deletions(-) -- 1.9.0

Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if dst is set in the localtime timeinfo (because for some reason mktime doesn't take that into account). This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage. --- Change from V1: add test cases with TZ set to different values (if someone knows how to force DST on/off, I would gladly add some test cases for this as well). src/libvirt_private.syms | 1 + src/util/virtime.c | 41 ++++++++++++++++++++++++++++++++++++++++- src/util/virtime.h | 5 +++-- tests/virtimetest.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c3332c9..cb635cd 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1984,6 +1984,7 @@ virTimeFieldsNow; virTimeFieldsNowRaw; virTimeFieldsThen; virTimeFieldsThenRaw; +virTimeLocalOffsetFromUTC; virTimeMillisNow; virTimeMillisNowRaw; virTimeStringNow; diff --git a/src/util/virtime.c b/src/util/virtime.c index caa4e24..c487eba 100644 --- a/src/util/virtime.c +++ b/src/util/virtime.c @@ -1,7 +1,7 @@ /* * virtime.c: Time handling functions * - * Copyright (C) 2006-2012 Red Hat, Inc. + * Copyright (C) 2006-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -344,3 +344,42 @@ char *virTimeStringThen(unsigned long long when) return ret; } + +/** + * virTimeLocalOffsetFromUTC: + * + * This function is threadsafe, but is *not* async signal safe (due to + * localtime_r()). + * + * @offset: pointer to time_t that will difference between localtime + * and UTC in seconds. + * + * Returns 0 on success, -1 on error with error reported + */ +int +virTimeLocalOffsetFromUTC(time_t *offset) +{ + struct tm gmtimeinfo, localtimeinfo; + time_t current, utc, local; + + if ((current = time(NULL)) < 0) { + virReportSystemError(errno, "%s", + _("failed to get current system time")); + return -1; + } + + localtime_r(¤t, &localtimeinfo); + gmtime_r(¤t, &gmtimeinfo); + + if ((local = mktime(&localtimeinfo)) < 0 || + (utc = mktime(&gmtimeinfo)) < 0) { + virReportSystemError(errno, "%s", + _("mktime failed")); + return -1; + } + + *offset = local - utc; + if (localtimeinfo.tm_isdst) + *offset += 3600; + return 0; +} diff --git a/src/util/virtime.h b/src/util/virtime.h index a5bd652..cda9707 100644 --- a/src/util/virtime.h +++ b/src/util/virtime.h @@ -1,7 +1,7 @@ /* * virtime.h: Time handling functions * - * Copyright (C) 2006-2011 Red Hat, Inc. + * Copyright (C) 2006-2011, 2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -50,7 +50,6 @@ int virTimeStringNowRaw(char *buf) int virTimeStringThenRaw(unsigned long long when, char *buf) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; - /* These APIs are *not* async signal safe and return -1, * raising a libvirt error on failure */ @@ -63,5 +62,7 @@ int virTimeFieldsThen(unsigned long long when, struct tm *fields) char *virTimeStringNow(void); char *virTimeStringThen(unsigned long long when); +int virTimeLocalOffsetFromUTC(time_t *offset) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; #endif diff --git a/tests/virtimetest.c b/tests/virtimetest.c index 73a3c3e..d1a409f 100644 --- a/tests/virtimetest.c +++ b/tests/virtimetest.c @@ -72,6 +72,35 @@ static int testTimeFields(const void *args) } +typedef struct { + const char *zone; + time_t offset; +} testTimeLocalOffsetData; + +static int +testTimeLocalOffset(const void *args) +{ + const testTimeLocalOffsetData *data = args; + time_t actual; + + if (setenv("TZ", data->zone, 1) < 0) { + perror("setenv"); + return -1; + } + tzset(); + + if (virTimeLocalOffsetFromUTC(&actual) < 0) { + return -1; + } + + if (data->offset != actual) { + VIR_DEBUG("Expect Offset %d got %d\n", + (int) data->offset, (int) actual); + return -1; + } + return 0; +} + static int mymain(void) { @@ -119,6 +148,21 @@ mymain(void) TEST_FIELDS(2147483648000ull, 2038, 1, 19, 3, 14, 8); +#define TEST_LOCALOFFSET(tz, off) \ + do { \ + testTimeLocalOffsetData data = { \ + .zone = tz, \ + .offset = off, \ + }; \ + if (virtTestRun("Test localtime offset for " #tz, \ + testTimeLocalOffset, &data) < 0) \ + ret = -1; \ + } while (0) + + TEST_LOCALOFFSET("VIR00:30", -30 * 60); + TEST_LOCALOFFSET("UTC", 0); + TEST_LOCALOFFSET("VIR-00:30", 30 * 60); + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- 1.9.0

On 05/22/2014 05:07 AM, Laine Stump wrote:
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if dst is set in the localtime timeinfo (because for some reason mktime doesn't take that into account).
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage. ---
Change from V1: add test cases with TZ set to different values (if someone knows how to force DST on/off, I would gladly add some test cases for this as well).
Re-reading POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html I think you can do something like: VIR00:30VID where "VIR" is the normal time, and "VID" is the daylight-savings time 1 hour ahead. It was the absence of a second name that made POSIX treat the TZ value as not having daylight savings. I'll review the actual patch in another mail. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Thu, May 22, 2014 at 02:07:27PM +0300, Laine Stump wrote:
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if dst is set in the localtime timeinfo (because for some reason mktime doesn't take that into account).
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage. ---
Change from V1: add test cases with TZ set to different values (if someone knows how to force DST on/off, I would gladly add some test cases for this as well).
man tzset: The second format is used when there is daylight saving time: std offset dst [offset],start[/time],end[/time]

On 05/22/2014 09:49 PM, Marcelo Tosatti wrote:
On Thu, May 22, 2014 at 02:07:27PM +0300, Laine Stump wrote:
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if dst is set in the localtime timeinfo (because for some reason mktime doesn't take that into account).
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage. ---
Change from V1: add test cases with TZ set to different values (if someone knows how to force DST on/off, I would gladly add some test cases for this as well).
man tzset:
The second format is used when there is daylight saving time:
std offset dst [offset],start[/time],end[/time]
Aha! And combining that with the "VIR" timezone idea that we're already using, I've found that the following string *does* set DST: TZ="VIR02:30VID,0,365" while the following *doesn't* (at least not today :-): TZ="VIR02:30VID,300,365" So I can add the former as a test case. Thanks!

On 05/23/2014 05:43 AM, Laine Stump wrote:
man tzset:
The second format is used when there is daylight saving time:
std offset dst [offset],start[/time],end[/time]
Aha! And combining that with the "VIR" timezone idea that we're already using, I've found that the following string *does* set DST:
TZ="VIR02:30VID,0,365"
That probably still has a window where running 'make check' on Dec 31 may fail; maybe using the time argument as in /23:59 will minimize the window to one minute?
while the following *doesn't* (at least not today :-):
TZ="VIR02:30VID,300,365"
So I can add the former as a test case.
It's awkward testing for daylight savings cases if the test is dependent on today's date; hopefully whatever you come up with is sufficiently isolated that it doesn't introduce spurious failures on certain days of the year. Also, if I understand correctly, once you have a TZ with daylight savings rules, the tm_isdst of struct tm can be set to negative to use the TZ rules for determining if it is in effect, to 0 to force the standard time (even if the current time is during dst) and to 1 to force the daylight time (even if the current time is during std). (And that this is true for tm_isdst, but NOT for the global 'daylight', which is merely specified as positive year-round if TZ includes daylight rules). -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 05/23/2014 03:47 PM, Eric Blake wrote:
On 05/23/2014 05:43 AM, Laine Stump wrote:
man tzset:
The second format is used when there is daylight saving time:
std offset dst [offset],start[/time],end[/time] Aha! And combining that with the "VIR" timezone idea that we're already using, I've found that the following string *does* set DST:
TZ="VIR02:30VID,0,365"
That probably still has a window where running 'make check' on Dec 31 may fail; maybe using the time argument as in /23:59 will minimize the window to one minute?
Ah, right - because that is the *end* day. I just tried this in a virtual machine: declare -x TZ="VIR00:30VID,0,366 date --set "Dec 31 2012 23:59:57" (2012 is a leap year) The output of date showed "VID" (i.e. DST is in effect), and it continued to show VID right through midnight.
while the following *doesn't* (at least not today :-):
TZ="VIR02:30VID,300,365"
So I can add the former as a test case. It's awkward testing for daylight savings cases if the test is dependent on today's date; hopefully whatever you come up with is sufficiently isolated that it doesn't introduce spurious failures on certain days of the year.
I think the above does that.
Also, if I understand correctly, once you have a TZ with daylight savings rules, the tm_isdst of struct tm can be set to negative to use the TZ rules for determining if it is in effect, to 0 to force the standard time (even if the current time is during dst) and to 1 to force the daylight time (even if the current time is during std). (And that this is true for tm_isdst, but NOT for the global 'daylight', which is merely specified as positive year-round if TZ includes daylight rules).
Since the existence of tm_isdst is transparent to the caller of virTimeLocallOffsetFromUTC, we can't use that for testing purposes. And from the point of view of the function itself, I'd rather not manipulate that value if I don't have to - I'd rather leave as much to the system as possible, and just detect it with the test on systems that might behave differently - then we can fix it if necessary.

On Fri, May 23, 2014 at 02:43:08PM +0300, Laine Stump wrote:
On 05/22/2014 09:49 PM, Marcelo Tosatti wrote:
On Thu, May 22, 2014 at 02:07:27PM +0300, Laine Stump wrote:
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if dst is set in the localtime timeinfo (because for some reason mktime doesn't take that into account).
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage. ---
Change from V1: add test cases with TZ set to different values (if someone knows how to force DST on/off, I would gladly add some test cases for this as well).
man tzset:
The second format is used when there is daylight saving time:
std offset dst [offset],start[/time],end[/time]
Aha! And combining that with the "VIR" timezone idea that we're already using, I've found that the following string *does* set DST:
TZ="VIR02:30VID,0,365"
while the following *doesn't* (at least not today :-):
TZ="VIR02:30VID,300,365"
So I can add the former as a test case.
Thanks!
Btw TZ also contains != 1hour daylight offset information (see that section of man tzset). I'll send a QEMU patch to add a command to query the RTC offset.

On 05/22/2014 05:07 AM, Laine Stump wrote:
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if dst is set in the localtime timeinfo (because for some reason mktime doesn't take that into account).
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage. ---
+ +/** + * virTimeLocalOffsetFromUTC: + * + * This function is threadsafe, but is *not* async signal safe (due to + * localtime_r()). + * + * @offset: pointer to time_t that will difference between localtime
s/will/will be set to the/
+ * and UTC in seconds.
Should you also mention whether positive is east of UTC vs. west of UTC? time_t is not necessarily a signed type, but even if it is unsigned, you can treat it as a 2s-complement negative value. Maybe this function should take 'long *offset' instead of 'time_t *offset', to match the POSIX 'timezone' variable?
+ * + * Returns 0 on success, -1 on error with error reported + */ +int +virTimeLocalOffsetFromUTC(time_t *offset) +{ + struct tm gmtimeinfo, localtimeinfo; + time_t current, utc, local; + + if ((current = time(NULL)) < 0) {
time_t is allowed to be an unsigned type. The only portable way to check for failure is: if ((current = time(NULL)) == (time_t)-1) {
+ virReportSystemError(errno, "%s", + _("failed to get current system time")); + return -1; + } + + localtime_r(¤t, &localtimeinfo); + gmtime_r(¤t, &gmtimeinfo);
localtime_r() and gmtime_r() can fail (returning NULL, and leaving unspecified contents in the 2nd argument) - although arguably the failure can only occur due to EOVERFLOW at the year 2038 boundary. So ignoring the error is not necessarily fatal, although it might be worth a command and/or ignore_value() to state why we are ignoring failure.
+ + if ((local = mktime(&localtimeinfo)) < 0 || + (utc = mktime(&gmtimeinfo)) < 0) {
Two more comparisons that must be made against ==(time_t)-1, rather than <0, due to time_t possibly being unsigned. What's more, mktime() will fail only for EOVERFLOW; but if that's the case, then the earlier localtime_r() or even time() calls would have failed. It seems inconsistent to check here but not earlier.
+ virReportSystemError(errno, "%s", + _("mktime failed")); + return -1; + } + + *offset = local - utc; + if (localtimeinfo.tm_isdst) + *offset += 3600;
Technically, POSIX allows for a daylight savings that is different than a mere 3600-second gap. But I'm not sure how you would be able to figure that out from struct tm. It would be a LOT simpler to just do: #include <time.h> tzset(); *offset = timezone; except that some older builds of mingw lack the extern variable timezone. Or maybe even do a configure check, for AC_CHECK_DECLS([timezone]) (untested, just throwing out the idea), and having #if HAVE_DECL_TIMEZONE with the short code and the #else clause using this dance as the fallback for mingw? Or even just ditch older mingw? I see this in Fedora 20's cross-packages for mingw: /usr/i686-w64-mingw32/sys-root/mingw/include/time.h: __MINGW_IMPORT long _timezone; /usr/i686-w64-mingw32/sys-root/mingw/include/time.h: _CRTIMP extern long timezone; but have no idea how accurate they are.
@@ -119,6 +148,21 @@ mymain(void)
TEST_FIELDS(2147483648000ull, 2038, 1, 19, 3, 14, 8);
+#define TEST_LOCALOFFSET(tz, off) \ + do { \ + testTimeLocalOffsetData data = { \ + .zone = tz, \ + .offset = off, \ + }; \ + if (virtTestRun("Test localtime offset for " #tz, \ + testTimeLocalOffset, &data) < 0) \ + ret = -1; \ + } while (0) + + TEST_LOCALOFFSET("VIR00:30", -30 * 60); + TEST_LOCALOFFSET("UTC", 0); + TEST_LOCALOFFSET("VIR-00:30", 30 * 60);
This looks accurate, but as you say, it doesn't do any testing of daylight savings to know if we're getting that part right. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 05/22/2014 10:03 PM, Eric Blake wrote:
On 05/22/2014 05:07 AM, Laine Stump wrote:
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if dst is set in the localtime timeinfo (because for some reason mktime doesn't take that into account).
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage. --- + +/** + * virTimeLocalOffsetFromUTC: + * + * This function is threadsafe, but is *not* async signal safe (due to + * localtime_r()). + * + * @offset: pointer to time_t that will difference between localtime s/will/will be set to the/
fixed
+ * and UTC in seconds. Should you also mention whether positive is east of UTC vs. west of UTC?
Done.
time_t is not necessarily a signed type, but even if it is unsigned, you can treat it as a 2s-complement negative value. Maybe this function should take 'long *offset' instead of 'time_t *offset', to match the POSIX 'timezone' variable?
+ * + * Returns 0 on success, -1 on error with error reported + */ +int +virTimeLocalOffsetFromUTC(time_t *offset) +{ + struct tm gmtimeinfo, localtimeinfo; + time_t current, utc, local; + + if ((current = time(NULL)) < 0) { time_t is allowed to be an unsigned type. The only portable way to check for failure is:
if ((current = time(NULL)) == (time_t)-1) {
Done.
+ virReportSystemError(errno, "%s", + _("failed to get current system time")); + return -1; + } + + localtime_r(¤t, &localtimeinfo); + gmtime_r(¤t, &gmtimeinfo); localtime_r() and gmtime_r() can fail (returning NULL, and leaving unspecified contents in the 2nd argument) - although arguably the failure can only occur due to EOVERFLOW at the year 2038 boundary. So ignoring the error is not necessarily fatal, although it might be worth a command and/or ignore_value() to state why we are ignoring failure.
Nah, I just changed it to check both of those for error.
+ + if ((local = mktime(&localtimeinfo)) < 0 || + (utc = mktime(&gmtimeinfo)) < 0) { Two more comparisons that must be made against ==(time_t)-1, rather than <0, due to time_t possibly being unsigned. What's more, mktime() will fail only for EOVERFLOW; but if that's the case, then the earlier localtime_r() or even time() calls would have failed. It seems inconsistent to check here but not earlier.
Okay, I've fixed the comparisons.
+ virReportSystemError(errno, "%s", + _("mktime failed")); + return -1; + } + + *offset = local - utc; + if (localtimeinfo.tm_isdst) + *offset += 3600; Technically, POSIX allows for a daylight savings that is different than a mere 3600-second gap. But I'm not sure how you would be able to figure that out from struct tm.
It would be a LOT simpler to just do:
#include <time.h>
tzset(); *offset = timezone;
except that some older builds of mingw lack the extern variable timezone. Or maybe even do a configure check, for AC_CHECK_DECLS([timezone]) (untested, just throwing out the idea), and having #if HAVE_DECL_TIMEZONE with the short code and the #else clause using this dance as the fallback for mingw? Or even just ditch older mingw? I see this in Fedora 20's cross-packages for mingw:
I'm so sick of this topic right now that I'd prefer leaving as is, although that is tempting. Let me think about it for the next couple hours and get back to you.
/usr/i686-w64-mingw32/sys-root/mingw/include/time.h: __MINGW_IMPORT long _timezone; /usr/i686-w64-mingw32/sys-root/mingw/include/time.h: _CRTIMP extern long timezone;
but have no idea how accurate they are.
@@ -119,6 +148,21 @@ mymain(void)
TEST_FIELDS(2147483648000ull, 2038, 1, 19, 3, 14, 8);
+#define TEST_LOCALOFFSET(tz, off) \ + do { \ + testTimeLocalOffsetData data = { \ + .zone = tz, \ + .offset = off, \ + }; \ + if (virtTestRun("Test localtime offset for " #tz, \ + testTimeLocalOffset, &data) < 0) \ + ret = -1; \ + } while (0) + + TEST_LOCALOFFSET("VIR00:30", -30 * 60); + TEST_LOCALOFFSET("UTC", 0); + TEST_LOCALOFFSET("VIR-00:30", 30 * 60); This looks accurate, but as you say, it doesn't do any testing of daylight savings to know if we're getting that part right.
I've added in tests as discussed in other mails.

On 05/23/2014 05:45 PM, Laine Stump wrote:
On 05/22/2014 10:03 PM, Eric Blake wrote:
It would be a LOT simpler to just do:
#include <time.h>
tzset(); *offset = timezone;
except that some older builds of mingw lack the extern variable timezone. Or maybe even do a configure check, for AC_CHECK_DECLS([timezone]) (untested, just throwing out the idea), and having #if HAVE_DECL_TIMEZONE with the short code and the #else clause using this dance as the fallback for mingw? Or even just ditch older mingw? I see this in Fedora 20's cross-packages for mingw:
I'm so sick of this topic right now that I'd prefer leaving as is, although that is tempting.
Let me think about it for the next couple hours and get back to you.
Okay, I implemented this and the two tests I just added that checked DST started to fail. It appears that timezone doesn't account for DST. So unless there is also a standard way to get that information from a global, I think it would be cleaner just to forget about the global timezone and have the single implementation

Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if daylight savings time (DST) is set in the localtime timeinfo (because for some reason mktime doesn't take that into account). The returned value is the difference between UTC and localtime in seconds. If localtime is ahead of UTC (east) the offset will be a positive number, and if localtime is behind UTC (west) the offset will be negative. This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage. Note that it is theoretically possible to set a DST offset that is not exactly one hour. This function doesn't handle that (is that actually in use anywhere?) (If it weren't for DST, we could simply replace this entire function with "-timezone"; timezone contains the offset of the current timezone (negated from what we want) but doesn't account for DST. And in spite of being guaranteed by POSIX, it isn't available on older versions of mingw.) --- Change from V2: * added test cases that force DST to be active so that the "tm_isdst" logic can be properly tested. * addressed Eric's review points. Note that I tried out using the global "timezone", but it doesn't adjust for DST, so I decided to keep a single implementation of the function. src/libvirt_private.syms | 1 + src/util/virtime.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++- src/util/virtime.h | 5 +++-- tests/virtimetest.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 3 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c3332c9..cb635cd 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1984,6 +1984,7 @@ virTimeFieldsNow; virTimeFieldsNowRaw; virTimeFieldsThen; virTimeFieldsThenRaw; +virTimeLocalOffsetFromUTC; virTimeMillisNow; virTimeMillisNowRaw; virTimeStringNow; diff --git a/src/util/virtime.c b/src/util/virtime.c index caa4e24..79b7bd5 100644 --- a/src/util/virtime.c +++ b/src/util/virtime.c @@ -1,7 +1,7 @@ /* * virtime.c: Time handling functions * - * Copyright (C) 2006-2012 Red Hat, Inc. + * Copyright (C) 2006-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -344,3 +344,51 @@ char *virTimeStringThen(unsigned long long when) return ret; } + +/** + * virTimeLocalOffsetFromUTC: + * + * This function is threadsafe, but is *not* async signal safe (due to + * localtime_r()). + * + * @offset: pointer to time_t that will be set to the difference + * between localtime and UTC in seconds (east of UTC is a + * positive number, and west of UTC is a negative number. + * + * Returns 0 on success, -1 on error with error reported + */ +int +virTimeLocalOffsetFromUTC(long *offset) +{ + struct tm gmtimeinfo, localtimeinfo; + time_t current, utc, local; + + if ((current = time(NULL)) == (time_t)-1) { + virReportSystemError(errno, "%s", + _("failed to get current system time")); + return -1; + } + + if (!localtime_r(¤t, &localtimeinfo)) { + virReportSystemError(errno, "%s", + _("localtime_r failed")); + return -1; + } + if (!gmtime_r(¤t, &gmtimeinfo)) { + virReportSystemError(errno, "%s", + _("gmtime_r failed")); + return -1; + } + + if ((local = mktime(&localtimeinfo)) == (time_t)-1 || + (utc = mktime(&gmtimeinfo)) == (time_t)-1) { + virReportSystemError(errno, "%s", + _("mktime failed")); + return -1; + } + + *offset = local - utc; + if (localtimeinfo.tm_isdst) + *offset += 3600; + return 0; +} diff --git a/src/util/virtime.h b/src/util/virtime.h index a5bd652..25332db 100644 --- a/src/util/virtime.h +++ b/src/util/virtime.h @@ -1,7 +1,7 @@ /* * virtime.h: Time handling functions * - * Copyright (C) 2006-2011 Red Hat, Inc. + * Copyright (C) 2006-2011, 2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -50,7 +50,6 @@ int virTimeStringNowRaw(char *buf) int virTimeStringThenRaw(unsigned long long when, char *buf) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; - /* These APIs are *not* async signal safe and return -1, * raising a libvirt error on failure */ @@ -63,5 +62,7 @@ int virTimeFieldsThen(unsigned long long when, struct tm *fields) char *virTimeStringNow(void); char *virTimeStringThen(unsigned long long when); +int virTimeLocalOffsetFromUTC(long *offset) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; #endif diff --git a/tests/virtimetest.c b/tests/virtimetest.c index 73a3c3e..a14dcb9 100644 --- a/tests/virtimetest.c +++ b/tests/virtimetest.c @@ -72,6 +72,35 @@ static int testTimeFields(const void *args) } +typedef struct { + const char *zone; + long offset; +} testTimeLocalOffsetData; + +static int +testTimeLocalOffset(const void *args) +{ + const testTimeLocalOffsetData *data = args; + long actual; + + if (setenv("TZ", data->zone, 1) < 0) { + perror("setenv"); + return -1; + } + tzset(); + + if (virTimeLocalOffsetFromUTC(&actual) < 0) { + return -1; + } + + if (data->offset != actual) { + VIR_DEBUG("Expect Offset %ld got %ld\n", + data->offset, actual); + return -1; + } + return 0; +} + static int mymain(void) { @@ -119,6 +148,28 @@ mymain(void) TEST_FIELDS(2147483648000ull, 2038, 1, 19, 3, 14, 8); +#define TEST_LOCALOFFSET(tz, off) \ + do { \ + testTimeLocalOffsetData data = { \ + .zone = tz, \ + .offset = off, \ + }; \ + if (virtTestRun("Test localtime offset for " #tz, \ + testTimeLocalOffset, &data) < 0) \ + ret = -1; \ + } while (0) + + TEST_LOCALOFFSET("VIR00:30", -30 * 60); + TEST_LOCALOFFSET("VIR01:30", -90 * 60); + TEST_LOCALOFFSET("UTC", 0); + TEST_LOCALOFFSET("VIR-00:30", 30 * 60); + TEST_LOCALOFFSET("VIR-01:30", 90 * 60); + /* test DST processing with timezones that always + * have DST in effect + */ + TEST_LOCALOFFSET("VIR-00:30VID,0,365", 90 * 60); + TEST_LOCALOFFSET("VIR-02:30VID,0,365", 210 * 60); + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- 1.9.0

From: Laine Stump <laine@laine.org> Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current time, then converting that into a struct tm with gmtime_r(), then back to a time_t using mktime. The returned value is the difference between UTC and localtime in seconds. If localtime is ahead of UTC (east) the offset will be a positive number, and if localtime is behind UTC (west) the offset will be negative. This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage; that would also have the problem that it might not be accurate after a local daylight savings boundary. (If it weren't for DST, we could simply replace this entire function with "-timezone"; timezone contains the offset of the current timezone (negated from what we want) but doesn't account for DST. And in spite of being guaranteed by POSIX, it isn't available on older versions of mingw.) Signed-off-by: Eric Blake <eblake@redhat.com> --- After some more playing around, I learned we don't need localtime_r at all: time() and mktime() both honor the current timezone, so the original and resulting value were always the same. I also figured out how to force a timezone with a daylight savings different than an hour; the code still works without having to hardcode any guess based on tm_isdst. src/libvirt_private.syms | 1 + src/util/virtime.c | 45 +++++++++++++++++++++++++++++++++++++++- src/util/virtime.h | 5 +++-- tests/virtimetest.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 25dab2d..91f13a4 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1984,6 +1984,7 @@ virTimeFieldsNow; virTimeFieldsNowRaw; virTimeFieldsThen; virTimeFieldsThenRaw; +virTimeLocalOffsetFromUTC; virTimeMillisNow; virTimeMillisNowRaw; virTimeStringNow; diff --git a/src/util/virtime.c b/src/util/virtime.c index caa4e24..3a56400 100644 --- a/src/util/virtime.c +++ b/src/util/virtime.c @@ -1,7 +1,7 @@ /* * virtime.c: Time handling functions * - * Copyright (C) 2006-2012 Red Hat, Inc. + * Copyright (C) 2006-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -344,3 +344,46 @@ char *virTimeStringThen(unsigned long long when) return ret; } + +/** + * virTimeLocalOffsetFromUTC: + * + * This function is threadsafe, but is *not* async signal safe (due to + * localtime_r()). + * + * @offset: pointer to time_t that will be set to the difference + * between localtime and UTC in seconds (east of UTC is a + * positive number, and west of UTC is a negative number. + * + * Returns 0 on success, -1 on error with error reported + */ +int +virTimeLocalOffsetFromUTC(long *offset) +{ + struct tm gmtimeinfo; + time_t current, utc; + + /* time() gives seconds since Epoch in current timezone */ + if ((current = time(NULL)) == (time_t)-1) { + virReportSystemError(errno, "%s", + _("failed to get current system time")); + return -1; + } + + /* treat current as if it were in UTC */ + if (!gmtime_r(¤t, &gmtimeinfo)) { + virReportSystemError(errno, "%s", + _("gmtime_r failed")); + return -1; + } + + /* mktime() also obeys current timezone rules */ + if ((utc = mktime(&gmtimeinfo)) == (time_t)-1) { + virReportSystemError(errno, "%s", + _("mktime failed")); + return -1; + } + + *offset = current - utc; + return 0; +} diff --git a/src/util/virtime.h b/src/util/virtime.h index a5bd652..25332db 100644 --- a/src/util/virtime.h +++ b/src/util/virtime.h @@ -1,7 +1,7 @@ /* * virtime.h: Time handling functions * - * Copyright (C) 2006-2011 Red Hat, Inc. + * Copyright (C) 2006-2011, 2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -50,7 +50,6 @@ int virTimeStringNowRaw(char *buf) int virTimeStringThenRaw(unsigned long long when, char *buf) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; - /* These APIs are *not* async signal safe and return -1, * raising a libvirt error on failure */ @@ -63,5 +62,7 @@ int virTimeFieldsThen(unsigned long long when, struct tm *fields) char *virTimeStringNow(void); char *virTimeStringThen(unsigned long long when); +int virTimeLocalOffsetFromUTC(long *offset) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; #endif diff --git a/tests/virtimetest.c b/tests/virtimetest.c index 73a3c3e..5202d4b 100644 --- a/tests/virtimetest.c +++ b/tests/virtimetest.c @@ -72,6 +72,35 @@ static int testTimeFields(const void *args) } +typedef struct { + const char *zone; + long offset; +} testTimeLocalOffsetData; + +static int +testTimeLocalOffset(const void *args) +{ + const testTimeLocalOffsetData *data = args; + long actual; + + if (setenv("TZ", data->zone, 1) < 0) { + perror("setenv"); + return -1; + } + tzset(); + + if (virTimeLocalOffsetFromUTC(&actual) < 0) { + return -1; + } + + if (data->offset != actual) { + VIR_DEBUG("Expect Offset %ld got %ld\n", + data->offset, actual); + return -1; + } + return 0; +} + static int mymain(void) { @@ -119,6 +148,30 @@ mymain(void) TEST_FIELDS(2147483648000ull, 2038, 1, 19, 3, 14, 8); +#define TEST_LOCALOFFSET(tz, off) \ + do { \ + testTimeLocalOffsetData data = { \ + .zone = tz, \ + .offset = off, \ + }; \ + if (virtTestRun("Test localtime offset for " #tz, \ + testTimeLocalOffset, &data) < 0) \ + ret = -1; \ + } while (0) + + TEST_LOCALOFFSET("VIR00:30", -30 * 60); + TEST_LOCALOFFSET("VIR01:30", -90 * 60); + TEST_LOCALOFFSET("UTC", 0); + TEST_LOCALOFFSET("VIR-00:30", 30 * 60); + TEST_LOCALOFFSET("VIR-01:30", 90 * 60); + /* test DST processing with timezones that always + * have DST in effect; what's more, cover a zone with + * with an unusual DST different than a usual one hour + */ + TEST_LOCALOFFSET("VIR-00:30VID,0,365", 90 * 60); + TEST_LOCALOFFSET("VIR-02:30VID,0,365", 210 * 60); + TEST_LOCALOFFSET("VIR-02:30VID-04:30,0,365", 270 * 60); + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- 1.9.3

On 05/24/2014 05:21 PM, Eric Blake wrote:
From: Laine Stump <laine@laine.org>
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current time, then converting that into a struct tm with gmtime_r(), then back to a time_t using mktime.
The returned value is the difference between UTC and localtime in seconds. If localtime is ahead of UTC (east) the offset will be a positive number, and if localtime is behind UTC (west) the offset will be negative.
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage; that would also have the problem that it might not be accurate after a local daylight savings boundary.
(If it weren't for DST, we could simply replace this entire function with "-timezone"; timezone contains the offset of the current timezone (negated from what we want) but doesn't account for DST. And in spite of being guaranteed by POSIX, it isn't available on older versions of mingw.)
Signed-off-by: Eric Blake <eblake@redhat.com> ---
After some more playing around, I learned we don't need localtime_r at all:
I could have sworn I tried exactly the same thing back in the very beginning and ended up with the offset always being 0. But since all the tests pass, I'm now guessing that I must have mistakenly done only localtime_r() then did a mktime on it (which would give back the original time_t). Your version is simpler and works in more cases, so I would ACK if I could, but as I'm the original author I don't think my ACK can count, can it? Or does your modification of my original patch count as an ACK on the parts it didn't change? In short, do we need ACK from a 3rd person to push this?
time() and mktime() both honor the current timezone, so the original and resulting value were always the same. I also figured out how to force a timezone with a daylight savings different than an hour; the code still works without having to hardcode any guess based on tm_isdst.
src/libvirt_private.syms | 1 + src/util/virtime.c | 45 +++++++++++++++++++++++++++++++++++++++- src/util/virtime.h | 5 +++-- tests/virtimetest.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 25dab2d..91f13a4 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1984,6 +1984,7 @@ virTimeFieldsNow; virTimeFieldsNowRaw; virTimeFieldsThen; virTimeFieldsThenRaw; +virTimeLocalOffsetFromUTC; virTimeMillisNow; virTimeMillisNowRaw; virTimeStringNow; diff --git a/src/util/virtime.c b/src/util/virtime.c index caa4e24..3a56400 100644 --- a/src/util/virtime.c +++ b/src/util/virtime.c @@ -1,7 +1,7 @@ /* * virtime.c: Time handling functions * - * Copyright (C) 2006-2012 Red Hat, Inc. + * Copyright (C) 2006-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -344,3 +344,46 @@ char *virTimeStringThen(unsigned long long when)
return ret; } + +/** + * virTimeLocalOffsetFromUTC: + * + * This function is threadsafe, but is *not* async signal safe (due to + * localtime_r()). + * + * @offset: pointer to time_t that will be set to the difference + * between localtime and UTC in seconds (east of UTC is a + * positive number, and west of UTC is a negative number. + * + * Returns 0 on success, -1 on error with error reported + */ +int +virTimeLocalOffsetFromUTC(long *offset) +{ + struct tm gmtimeinfo; + time_t current, utc; + + /* time() gives seconds since Epoch in current timezone */ + if ((current = time(NULL)) == (time_t)-1) { + virReportSystemError(errno, "%s", + _("failed to get current system time")); + return -1; + } + + /* treat current as if it were in UTC */ + if (!gmtime_r(¤t, &gmtimeinfo)) { + virReportSystemError(errno, "%s", + _("gmtime_r failed")); + return -1; + } + + /* mktime() also obeys current timezone rules */ + if ((utc = mktime(&gmtimeinfo)) == (time_t)-1) { + virReportSystemError(errno, "%s", + _("mktime failed")); + return -1; + } + + *offset = current - utc; + return 0; +} diff --git a/src/util/virtime.h b/src/util/virtime.h index a5bd652..25332db 100644 --- a/src/util/virtime.h +++ b/src/util/virtime.h @@ -1,7 +1,7 @@ /* * virtime.h: Time handling functions * - * Copyright (C) 2006-2011 Red Hat, Inc. + * Copyright (C) 2006-2011, 2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -50,7 +50,6 @@ int virTimeStringNowRaw(char *buf) int virTimeStringThenRaw(unsigned long long when, char *buf) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK;
- /* These APIs are *not* async signal safe and return -1, * raising a libvirt error on failure */ @@ -63,5 +62,7 @@ int virTimeFieldsThen(unsigned long long when, struct tm *fields) char *virTimeStringNow(void); char *virTimeStringThen(unsigned long long when);
+int virTimeLocalOffsetFromUTC(long *offset) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK;
#endif diff --git a/tests/virtimetest.c b/tests/virtimetest.c index 73a3c3e..5202d4b 100644 --- a/tests/virtimetest.c +++ b/tests/virtimetest.c @@ -72,6 +72,35 @@ static int testTimeFields(const void *args) }
+typedef struct { + const char *zone; + long offset; +} testTimeLocalOffsetData; + +static int +testTimeLocalOffset(const void *args) +{ + const testTimeLocalOffsetData *data = args; + long actual; + + if (setenv("TZ", data->zone, 1) < 0) { + perror("setenv"); + return -1; + } + tzset(); + + if (virTimeLocalOffsetFromUTC(&actual) < 0) { + return -1; + } + + if (data->offset != actual) { + VIR_DEBUG("Expect Offset %ld got %ld\n", + data->offset, actual); + return -1; + } + return 0; +} + static int mymain(void) { @@ -119,6 +148,30 @@ mymain(void)
TEST_FIELDS(2147483648000ull, 2038, 1, 19, 3, 14, 8);
+#define TEST_LOCALOFFSET(tz, off) \ + do { \ + testTimeLocalOffsetData data = { \ + .zone = tz, \ + .offset = off, \ + }; \ + if (virtTestRun("Test localtime offset for " #tz, \ + testTimeLocalOffset, &data) < 0) \ + ret = -1; \ + } while (0) + + TEST_LOCALOFFSET("VIR00:30", -30 * 60); + TEST_LOCALOFFSET("VIR01:30", -90 * 60); + TEST_LOCALOFFSET("UTC", 0); + TEST_LOCALOFFSET("VIR-00:30", 30 * 60); + TEST_LOCALOFFSET("VIR-01:30", 90 * 60); + /* test DST processing with timezones that always + * have DST in effect; what's more, cover a zone with + * with an unusual DST different than a usual one hour + */ + TEST_LOCALOFFSET("VIR-00:30VID,0,365", 90 * 60); + TEST_LOCALOFFSET("VIR-02:30VID,0,365", 210 * 60); + TEST_LOCALOFFSET("VIR-02:30VID-04:30,0,365", 270 * 60); + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; }

On 05/24/2014 05:21 PM, Eric Blake wrote:
From: Laine Stump <laine@laine.org>
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current time, then converting that into a struct tm with gmtime_r(), then back to a time_t using mktime.
The returned value is the difference between UTC and localtime in seconds. If localtime is ahead of UTC (east) the offset will be a positive number, and if localtime is behind UTC (west) the offset will be negative.
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage; that would also have the problem that it might not be accurate after a local daylight savings boundary.
(If it weren't for DST, we could simply replace this entire function with "-timezone"; timezone contains the offset of the current timezone (negated from what we want) but doesn't account for DST. And in spite of being guaranteed by POSIX, it isn't available on older versions of mingw.)
Signed-off-by: Eric Blake <eblake@redhat.com> ---
After some more playing around, I learned we don't need localtime_r at all: time() and mktime() both honor the current timezone, so the original and resulting value were always the same. I also figured out how to force a timezone with a daylight savings different than an hour; the code still works without having to hardcode any guess based on tm_isdst.
Actually this patch doesn't work as well as an initial test might indicate. When I first reviewed it on Saturday afternoon (UTC+3), it passed the tests, but when I went back and ran it again in the morning on Monday, it failed the tests that had DST set. In the interest of getting as much testing time as possible on the code (and with DV's agreement), I disabled the failing tests and pushed the patches, then began investigating in more detail. I ran some custom-written tests based on this patch on a Fedora guest while modifying the system date and found that at different times of the day the tests that involved DST failed, yet at some times they failed (the failure times of different tests were different, and as the clock ticked forward, TZs with larger offsets would begin to succeed). I spent a significant amount of time experimenting and found that the function as written results in a *lot* of failures over the course of a year (although there are certain times of certain days when it is successful for some or all of the tests). So I tried setting gmtimeinfo.tm_isdst = -1 prior to calling mktime and Yay! It seemed to succeed for all tests on all dates within a year. Then I added a few more tests, and changed my test script to change the system to a different timezone prior to calling the test - after seeing a pattern during a couple tests, to save time I changed the test to only run through the clock on Jan 1, May 31, and Dec 31, but to do that both with the shell environment set to TZ="MST07:00MDT06:00,0/00:00:00,366/23:59:59" and to TZ="EET-02:00EEDT-03:00,0/00:00:00,366/23:59:59". (in recognition of my timezone and Eric's timezone :-) I've found that in the both cases only three of the 4 new tests I'd added will fail - these two new tests: TEST_LOCALOFFSET("VIR05:00VID04:00,0/00:00:00,366/23:59:59", ((-4 * 60) + 0) * 60); TEST_LOCALOFFSET("VIR11:00VID10:00,0/00:00:00,366/23:59:59", ((-10 * 60) + 0) * 60); will fail for: Dec 31 18:00-18:59 for MST Jan 1 from 03:00 - 03:59 for EET and this test: TEST_LOCALOFFSET("VIR02:45VID00:45,0/00:00:00,366/23:59:59", -45 * 60); fails for Dec 31 18:00-19:59 for MST Jan 1 from 03:00 - 04:59 for EET (Note that the output of the date command *always* showed "MDT" or "EEDT", indicating that the trick of forcing DST for every time of every day was working properly, at least for the time in the shell). The failure in all cases is that the offset returned is the offset that would be correct if DST hadn't been in effect. Since the method of setting permanent DST appears to work correctly, and it's happening for an entire 1-2 hour stretch, I'm guessing that the problem isn't that time() is called while DST is off and then mktime called while it is on, but something more insidious. My conclusions from all this are: 1) *definitely* we need to set gmtimeinfo.tm_isdst = -1 in virTimeLocalOffsetFromUTC(), 2) I guess we can add all the net test cases I've added that contain DST, but we should comment out those that fail on Jan 1 / Dec 31. I'll send a patch in about an hour to do that. (P.S. if you're interested in the ugly shell script I'm using to flip through the dates, let me know :-)
src/libvirt_private.syms | 1 + src/util/virtime.c | 45 +++++++++++++++++++++++++++++++++++++++- src/util/virtime.h | 5 +++-- tests/virtimetest.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 25dab2d..91f13a4 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1984,6 +1984,7 @@ virTimeFieldsNow; virTimeFieldsNowRaw; virTimeFieldsThen; virTimeFieldsThenRaw; +virTimeLocalOffsetFromUTC; virTimeMillisNow; virTimeMillisNowRaw; virTimeStringNow; diff --git a/src/util/virtime.c b/src/util/virtime.c index caa4e24..3a56400 100644 --- a/src/util/virtime.c +++ b/src/util/virtime.c @@ -1,7 +1,7 @@ /* * virtime.c: Time handling functions * - * Copyright (C) 2006-2012 Red Hat, Inc. + * Copyright (C) 2006-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -344,3 +344,46 @@ char *virTimeStringThen(unsigned long long when)
return ret; } + +/** + * virTimeLocalOffsetFromUTC: + * + * This function is threadsafe, but is *not* async signal safe (due to + * localtime_r()). + * + * @offset: pointer to time_t that will be set to the difference + * between localtime and UTC in seconds (east of UTC is a + * positive number, and west of UTC is a negative number. + * + * Returns 0 on success, -1 on error with error reported + */ +int +virTimeLocalOffsetFromUTC(long *offset) +{ + struct tm gmtimeinfo; + time_t current, utc; + + /* time() gives seconds since Epoch in current timezone */ + if ((current = time(NULL)) == (time_t)-1) { + virReportSystemError(errno, "%s", + _("failed to get current system time")); + return -1; + } + + /* treat current as if it were in UTC */ + if (!gmtime_r(¤t, &gmtimeinfo)) { + virReportSystemError(errno, "%s", + _("gmtime_r failed")); + return -1; + } +
right here is where I added: gmtimeinfo.tm_isdst = -1; 1, 0, and leaving it alone all caused more failures.
+ /* mktime() also obeys current timezone rules */ + if ((utc = mktime(&gmtimeinfo)) == (time_t)-1) { + virReportSystemError(errno, "%s", + _("mktime failed")); + return -1; + } + + *offset = current - utc; + return 0; +} +#define TEST_LOCALOFFSET(tz, off) \ + do { \ + testTimeLocalOffsetData data = { \ + .zone = tz, \ + .offset = off, \ + }; \ + if (virtTestRun("Test localtime offset for " #tz, \ + testTimeLocalOffset, &data) < 0) \ + ret = -1; \ + } while (0) + + TEST_LOCALOFFSET("VIR00:30", -30 * 60); + TEST_LOCALOFFSET("VIR01:30", -90 * 60); + TEST_LOCALOFFSET("UTC", 0); + TEST_LOCALOFFSET("VIR-00:30", 30 * 60); + TEST_LOCALOFFSET("VIR-01:30", 90 * 60); + /* test DST processing with timezones that always + * have DST in effect; what's more, cover a zone with + * with an unusual DST different than a usual one hour + */ + TEST_LOCALOFFSET("VIR-00:30VID,0,365", 90 * 60); + TEST_LOCALOFFSET("VIR-02:30VID,0,365", 210 * 60); + TEST_LOCALOFFSET("VIR-02:30VID-04:30,0,365", 270 * 60);
Here are my new tests: TEST_LOCALOFFSET("VIR-00:30VID,0/00:00:00,366/23:59:59",((1 * 60) + 30) * 60); TEST_LOCALOFFSET("VIR-02:30VID,0/00:00:00,366/23:59:59", ((3 * 60) + 30) * 60); TEST_LOCALOFFSET("VIR-02:30VID-04:30,0/00:00:00,366/23:59:59", ((4 * 60) + 30) * 60); TEST_LOCALOFFSET("VIR-02:30VID-04:30,0/00:00:00,366/23:59:59", ((4 * 60) + 30) * 60); * TEST_LOCALOFFSET("VIR02:45VID00:45,0/00:00:00,366/23:59:59", -45 * 60); TEST_LOCALOFFSET("VIR-12:00VID-13:00,0/00:00:00,366/23:59:59",((13 * 60) + 0) * 60); * TEST_LOCALOFFSET("VIR05:00VID04:00,0/00:00:00,366/23:59:59", ((-4 * 60) + 0) * 60); * TEST_LOCALOFFSET("VIR11:00VID10:00,0/00:00:00,366/23:59:59", ((-10 * 60) + 0) * 60); (those marked with "*" fail on Dec 31 or Jan 1, depending on TZ setting of the shell that is running virtimetest)

And don't forget to update comments if localtime_r is removed :)
+ * This function is threadsafe, but is *not* async signal safe (due to + * localtime_r()).
-----Original Message----- From: libvir-list-bounces@redhat.com [mailto:libvir-list-bounces@redhat.com] On Behalf Of Laine Stump Sent: Tuesday, May 27, 2014 7:46 PM To: libvir-list@redhat.com Subject: Re: [libvirt] [PATCH v4] util: new function virTimeLocalOffsetFromUTC
On 05/24/2014 05:21 PM, Eric Blake wrote:
From: Laine Stump <laine@laine.org>
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current time, then converting that into a struct tm with gmtime_r(), then back to a time_t using mktime.
The returned value is the difference between UTC and localtime in seconds. If localtime is ahead of UTC (east) the offset will be a positive number, and if localtime is behind UTC (west) the offset will be negative.
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage; that would also have the problem that it might not be accurate after a local daylight savings boundary.
(If it weren't for DST, we could simply replace this entire function with "-timezone"; timezone contains the offset of the current timezone (negated from what we want) but doesn't account for DST. And in spite of being guaranteed by POSIX, it isn't available on older versions of mingw.)
Signed-off-by: Eric Blake <eblake@redhat.com> ---
After some more playing around, I learned we don't need localtime_r at all: time() and mktime() both honor the current timezone, so the original and resulting value were always the same. I also figured out how to force a timezone with a daylight savings different than an hour; the code still works without having to hardcode any guess based on tm_isdst.
Actually this patch doesn't work as well as an initial test might indicate. When I first reviewed it on Saturday afternoon (UTC+3), it passed the tests, but when I went back and ran it again in the morning on Monday, it failed the tests that had DST set.
In the interest of getting as much testing time as possible on the code (and with DV's agreement), I disabled the failing tests and pushed the patches, then began investigating in more detail.
I ran some custom-written tests based on this patch on a Fedora guest while modifying the system date and found that at different times of the day the tests that involved DST failed, yet at some times they failed (the failure times of different tests were different, and as the clock ticked forward, TZs with larger offsets would begin to succeed).
I spent a significant amount of time experimenting and found that the function as written results in a *lot* of failures over the course of a year (although there are certain times of certain days when it is successful for some or all of the tests).
So I tried setting gmtimeinfo.tm_isdst = -1 prior to calling mktime and Yay! It seemed to succeed for all tests on all dates within a year. Then I added a few more tests, and changed my test script to change the system to a different timezone prior to calling the test - after seeing a pattern during a couple tests, to save time I changed the test to only run through the clock on Jan 1, May 31, and Dec 31, but to do that both with the shell environment set to
TZ="MST07:00MDT06:00,0/00:00:00,366/23:59:59"
and to
TZ="EET-02:00EEDT-03:00,0/00:00:00,366/23:59:59".
(in recognition of my timezone and Eric's timezone :-)
I've found that in the both cases only three of the 4 new tests I'd added will fail - these two new tests:
TEST_LOCALOFFSET("VIR05:00VID04:00,0/00:00:00,366/23:59:59", ((-4 * 60) + 0) * 60); TEST_LOCALOFFSET("VIR11:00VID10:00,0/00:00:00,366/23:59:59", ((-10 * 60) + 0) * 60);
will fail for:
Dec 31 18:00-18:59 for MST Jan 1 from 03:00 - 03:59 for EET
and this test:
TEST_LOCALOFFSET("VIR02:45VID00:45,0/00:00:00,366/23:59:59", -45 * 60);
fails for
Dec 31 18:00-19:59 for MST Jan 1 from 03:00 - 04:59 for EET
(Note that the output of the date command *always* showed "MDT" or "EEDT", indicating that the trick of forcing DST for every time of every day was working properly, at least for the time in the shell).
The failure in all cases is that the offset returned is the offset that would be correct if DST hadn't been in effect. Since the method of setting permanent DST appears to work correctly, and it's happening for an entire 1-2 hour stretch, I'm guessing that the problem isn't that time() is called while DST is off and then mktime called while it is on, but something more insidious.
My conclusions from all this are:
1) *definitely* we need to set gmtimeinfo.tm_isdst = -1 in virTimeLocalOffsetFromUTC(), 2) I guess we can add all the net test cases I've added that contain DST, but we should comment out those that fail on Jan 1 / Dec 31.
I'll send a patch in about an hour to do that.
(P.S. if you're interested in the ugly shell script I'm using to flip through the dates, let me know :-)
src/libvirt_private.syms | 1 + src/util/virtime.c | 45
+++++++++++++++++++++++++++++++++++++++-
src/util/virtime.h | 5 +++-- tests/virtimetest.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 25dab2d..91f13a4 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1984,6 +1984,7 @@ virTimeFieldsNow; virTimeFieldsNowRaw; virTimeFieldsThen; virTimeFieldsThenRaw; +virTimeLocalOffsetFromUTC; virTimeMillisNow; virTimeMillisNowRaw; virTimeStringNow; diff --git a/src/util/virtime.c b/src/util/virtime.c index caa4e24..3a56400 100644 --- a/src/util/virtime.c +++ b/src/util/virtime.c @@ -1,7 +1,7 @@ /* * virtime.c: Time handling functions * - * Copyright (C) 2006-2012 Red Hat, Inc. + * Copyright (C) 2006-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -344,3 +344,46 @@ char *virTimeStringThen(unsigned long long when)
return ret; } + +/** + * virTimeLocalOffsetFromUTC: + * + * This function is threadsafe, but is *not* async signal safe (due to + * localtime_r()). + * + * @offset: pointer to time_t that will be set to the difference + * between localtime and UTC in seconds (east of UTC is a + * positive number, and west of UTC is a negative number. + * + * Returns 0 on success, -1 on error with error reported + */ +int +virTimeLocalOffsetFromUTC(long *offset) +{ + struct tm gmtimeinfo; + time_t current, utc; + + /* time() gives seconds since Epoch in current timezone */ + if ((current = time(NULL)) == (time_t)-1) { + virReportSystemError(errno, "%s", + _("failed to get current system time")); + return -1; + } + + /* treat current as if it were in UTC */ + if (!gmtime_r(¤t, &gmtimeinfo)) { + virReportSystemError(errno, "%s", + _("gmtime_r failed")); + return -1; + } + right here is where I added:
gmtimeinfo.tm_isdst = -1;
1, 0, and leaving it alone all caused more failures.
+ /* mktime() also obeys current timezone rules */ + if ((utc = mktime(&gmtimeinfo)) == (time_t)-1) { + virReportSystemError(errno, "%s", + _("mktime failed")); + return -1; + } + + *offset = current - utc; + return 0; +} +#define TEST_LOCALOFFSET(tz, off) \ + do { \ + testTimeLocalOffsetData data = { \ + .zone = tz, \ + .offset = off, \ + }; \ + if (virtTestRun("Test localtime offset for " #tz, \ + testTimeLocalOffset, &data) < 0) \ + ret = -1; \ + } while (0) + + TEST_LOCALOFFSET("VIR00:30", -30 * 60); + TEST_LOCALOFFSET("VIR01:30", -90 * 60); + TEST_LOCALOFFSET("UTC", 0); + TEST_LOCALOFFSET("VIR-00:30", 30 * 60); + TEST_LOCALOFFSET("VIR-01:30", 90 * 60); + /* test DST processing with timezones that always + * have DST in effect; what's more, cover a zone with + * with an unusual DST different than a usual one hour + */ + TEST_LOCALOFFSET("VIR-00:30VID,0,365", 90 * 60); + TEST_LOCALOFFSET("VIR-02:30VID,0,365", 210 * 60); + TEST_LOCALOFFSET("VIR-02:30VID-04:30,0,365", 270 * 60);
Here are my new tests:
TEST_LOCALOFFSET("VIR-00:30VID,0/00:00:00,366/23:59:59",((1 * 60) + 30) * 60); TEST_LOCALOFFSET("VIR-02:30VID,0/00:00:00,366/23:59:59", ((3 * 60) + 30) * 60); TEST_LOCALOFFSET("VIR-02:30VID-04:30,0/00:00:00,366/23:59:59", ((4 * 60) + 30) * 60); TEST_LOCALOFFSET("VIR-02:30VID-04:30,0/00:00:00,366/23:59:59", ((4 * 60) + 30) * 60); * TEST_LOCALOFFSET("VIR02:45VID00:45,0/00:00:00,366/23:59:59", -45 * 60); TEST_LOCALOFFSET("VIR-12:00VID-13:00,0/00:00:00,366/23:59:59",((13 * 60) + 0) * 60); * TEST_LOCALOFFSET("VIR05:00VID04:00,0/00:00:00,366/23:59:59", ((-4 * 60) + 0) * 60); * TEST_LOCALOFFSET("VIR11:00VID10:00,0/00:00:00,366/23:59:59", ((-10 * 60) + 0) * 60);
(those marked with "*" fail on Dec 31 or Jan 1, depending on TZ setting of the shell that is running virtimetest)
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

Laine Stump wrote:
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if dst is set in the localtime timeinfo (because for some reason mktime doesn't take that into account).
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage.
Appears it breaks virtimetest on FreeBSD: TEST: virtimetest 20) Test localtime offset for "VIR-00:30" ... OK 21) Test localtime offset for "VIR-01:30" ... OK 22) Test localtime offset for "VIR-00:30VID,0/00:00:00,366/23:59:59" ... FAILED 23) Test localtime offset for "VIR-02:30VID,0/00:00:00,366/23:59:59" ... FAILED 24) Test localtime offset for "VIR-02:30VID-04:30,0/00:00:00,366/23:59:59" ... FAILED 25) Test localtime offset for "VIR-12:00VID-13:00,0/00:00:00,366/23:59:59" ... FAILED 26) Test localtime offset for "VIR02:45VID00:45,0/00:00:00,366/23:59:59" ... FAILED 27) Test localtime offset for "VIR05:00VID04:00,0/00:00:00,366/23:59:59" ... FAILED 28) Test localtime offset for "VIR11:00VID10:00,0/00:00:00,366/23:59:59" ... FAILED My initial impression is that it's something wrong on the FreeBSD side, because date(1) doesn't respect TZs from failing tests, for example: $ date Sat May 31 23:15:45 MSK 2014 $ TZ=VIR-00:30 date Sat May 31 19:45:53 VIR 2014 $ TZ=VIR-00:30VID,0/00:00:00,366/23:59:59 date Sat May 31 19:15:58 UTC 2014 So it looks like it's failing back to the UTC time. Roman Bogorodskiy

Roman Bogorodskiy wrote:
Laine Stump wrote:
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if dst is set in the localtime timeinfo (because for some reason mktime doesn't take that into account).
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage.
Appears it breaks virtimetest on FreeBSD:
TEST: virtimetest 20) Test localtime offset for "VIR-00:30" ... OK 21) Test localtime offset for "VIR-01:30" ... OK 22) Test localtime offset for "VIR-00:30VID,0/00:00:00,366/23:59:59" ... FAILED 23) Test localtime offset for "VIR-02:30VID,0/00:00:00,366/23:59:59" ... FAILED 24) Test localtime offset for "VIR-02:30VID-04:30,0/00:00:00,366/23:59:59" ... FAILED 25) Test localtime offset for "VIR-12:00VID-13:00,0/00:00:00,366/23:59:59" ... FAILED 26) Test localtime offset for "VIR02:45VID00:45,0/00:00:00,366/23:59:59" ... FAILED 27) Test localtime offset for "VIR05:00VID04:00,0/00:00:00,366/23:59:59" ... FAILED 28) Test localtime offset for "VIR11:00VID10:00,0/00:00:00,366/23:59:59" ... FAILED
My initial impression is that it's something wrong on the FreeBSD side, because date(1) doesn't respect TZs from failing tests, for example:
$ date Sat May 31 23:15:45 MSK 2014 $ TZ=VIR-00:30 date Sat May 31 19:45:53 VIR 2014 $ TZ=VIR-00:30VID,0/00:00:00,366/23:59:59 date Sat May 31 19:15:58 UTC 2014
So it looks like it's failing back to the UTC time.
After some more experiments I noticed that changing 366 to 365 makes it work: $ TZ=VIR-00:30VID,0/00:00:00,365/23:59:59 date Sat May 31 21:34:25 VID 2014 $ And also changing that in the test makes it pass. Looking at this document: http://pubs.opengroup.org/onlinepubs/9699919799/ It describes TZ environment variable format and says about the days that it should be: 0 <= n <= 365 I haven't yet found what should happen when the format given in TZ environment variable doesn't follow the format. Roman Bogorodskiy

On 05/31/2014 11:11 PM, Roman Bogorodskiy wrote:
Roman Bogorodskiy wrote:
Laine Stump wrote:
Since there isn't a single libc API to get this value, this patch supplies one which gets the value by grabbing current UTC, then converting that into a struct tm with localtime_r(), then back to a time_t using mktime; it again does the same operation, but using gmtime_r() instead (for UTC). It then subtracts utc time from the localtime, and finally adjusts if dst is set in the localtime timeinfo (because for some reason mktime doesn't take that into account).
This function should be POSIX-compliant, and is threadsafe, but not async signal safe. If it was ever necessary to know this value in a child process, we could cache it with a one-time init function when libvirtd starts, then just supply the cached value, but that complexity isn't needed for current usage. Appears it breaks virtimetest on FreeBSD:
TEST: virtimetest 20) Test localtime offset for "VIR-00:30" ... OK 21) Test localtime offset for "VIR-01:30" ... OK 22) Test localtime offset for "VIR-00:30VID,0/00:00:00,366/23:59:59" ... FAILED 23) Test localtime offset for "VIR-02:30VID,0/00:00:00,366/23:59:59" ... FAILED 24) Test localtime offset for "VIR-02:30VID-04:30,0/00:00:00,366/23:59:59" ... FAILED 25) Test localtime offset for "VIR-12:00VID-13:00,0/00:00:00,366/23:59:59" ... FAILED 26) Test localtime offset for "VIR02:45VID00:45,0/00:00:00,366/23:59:59" ... FAILED 27) Test localtime offset for "VIR05:00VID04:00,0/00:00:00,366/23:59:59" ... FAILED 28) Test localtime offset for "VIR11:00VID10:00,0/00:00:00,366/23:59:59" ... FAILED
My initial impression is that it's something wrong on the FreeBSD side, because date(1) doesn't respect TZs from failing tests, for example:
$ date Sat May 31 23:15:45 MSK 2014 $ TZ=VIR-00:30 date Sat May 31 19:45:53 VIR 2014 $ TZ=VIR-00:30VID,0/00:00:00,366/23:59:59 date Sat May 31 19:15:58 UTC 2014
So it looks like it's failing back to the UTC time. After some more experiments I noticed that changing 366 to 365 makes it work:
$ TZ=VIR-00:30VID,0/00:00:00,365/23:59:59 date Sat May 31 21:34:25 VID 2014 $
And also changing that in the test makes it pass.
Sigh. Yeah, I made it 366 in an attempt to force DST to be always active, and it didn't seem to have any adverse side effects. In the meantime I ended up making most of the DST tests not run on Dec 31 / Jan 1 anyway, but never changed the end day because it wasn't causing harm. I just made that change locally and did a short test that ran successfully. If anything, it *could* cause a failure of the few DST tests that are left enabled all year, but only at the end of the year. Since the release of 1.2.5 is imminent, I've pushed the patch as a buil breaker.
Looking at this document:
http://pubs.opengroup.org/onlinepubs/9699919799/
It describes TZ environment variable format and says about the days that it should be:
0 <= n <= 365
I haven't yet found what should happen when the format given in TZ environment variable doesn't follow the format.
Roman Bogorodskiy

This reverts commit e31b5cf393857a6ca78d148c19393e28dfb39de1. This commit attempted to work around a bug in the offset value reported by qemu's RTC_CHANGE event in the case that a variable base date was given on the qemu commandline. The patch mixed up the math involved in arriving at the corrected offset to report, and in the process added an unnecessary private attribute to the clock element. Since that element is private/internal and not used by anyone else, it makes sense to simplify things by removing it. --- Unchanged from V2. This is the direct result of: git revert e31b5cf393857a6ca78d148c19393e28dfb39de1 src/conf/domain_conf.c | 27 ++++----------------------- src/conf/domain_conf.h | 5 ----- src/qemu/qemu_command.c | 3 --- src/qemu/qemu_process.c | 13 ------------- 4 files changed, 4 insertions(+), 44 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 055b979..a7863c3 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -99,7 +99,6 @@ typedef enum { VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES = (1<<18), VIR_DOMAIN_XML_INTERNAL_ALLOW_ROM = (1<<19), VIR_DOMAIN_XML_INTERNAL_ALLOW_BOOT = (1<<20), - VIR_DOMAIN_XML_INTERNAL_BASEDATE = (1 << 21), } virDomainXMLInternalFlags; VIR_ENUM_IMPL(virDomainTaint, VIR_DOMAIN_TAINT_LAST, @@ -12026,16 +12025,6 @@ virDomainDefParseXML(xmlDocPtr xml, break; } - if (def->clock.offset == VIR_DOMAIN_CLOCK_OFFSET_VARIABLE && - flags & VIR_DOMAIN_XML_INTERNAL_BASEDATE) { - if (virXPathULongLong("number(./clock/@basedate)", ctxt, - &def->clock.data.variable.basedate) < 0) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("invalid basedate")); - goto error; - } - } - if ((n = virXPathNodeSet("./clock/timer", ctxt, &nodes)) < 0) goto error; @@ -17097,8 +17086,7 @@ virDomainResourceDefFormat(virBufferPtr buf, verify(((VIR_DOMAIN_XML_INTERNAL_STATUS | VIR_DOMAIN_XML_INTERNAL_ACTUAL_NET | - VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES | - VIR_DOMAIN_XML_INTERNAL_BASEDATE) + VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES) & DUMPXML_FLAGS) == 0); /* This internal version can accept VIR_DOMAIN_XML_INTERNAL_*, @@ -17120,8 +17108,7 @@ virDomainDefFormatInternal(virDomainDefPtr def, virCheckFlags(DUMPXML_FLAGS | VIR_DOMAIN_XML_INTERNAL_STATUS | VIR_DOMAIN_XML_INTERNAL_ACTUAL_NET | - VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES | - VIR_DOMAIN_XML_INTERNAL_BASEDATE, + VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES, -1); if (!(type = virDomainVirtTypeToString(def->virtType))) { @@ -17655,10 +17642,6 @@ virDomainDefFormatInternal(virDomainDefPtr def, virBufferAsprintf(buf, " adjustment='%lld' basis='%s'", def->clock.data.variable.adjustment, virDomainClockBasisTypeToString(def->clock.data.variable.basis)); - - if (flags & VIR_DOMAIN_XML_INTERNAL_BASEDATE) - virBufferAsprintf(buf, " basedate='%llu'", - def->clock.data.variable.basedate); break; case VIR_DOMAIN_CLOCK_OFFSET_TIMEZONE: virBufferEscapeString(buf, " timezone='%s'", def->clock.data.timezone); @@ -18089,8 +18072,7 @@ virDomainSaveStatus(virDomainXMLOptionPtr xmlopt, unsigned int flags = (VIR_DOMAIN_XML_SECURE | VIR_DOMAIN_XML_INTERNAL_STATUS | VIR_DOMAIN_XML_INTERNAL_ACTUAL_NET | - VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES | - VIR_DOMAIN_XML_INTERNAL_BASEDATE); + VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES); int ret = -1; char *xml; @@ -18178,8 +18160,7 @@ virDomainObjListLoadStatus(virDomainObjListPtr doms, if (!(obj = virDomainObjParseFile(statusFile, caps, xmlopt, expectedVirtTypes, VIR_DOMAIN_XML_INTERNAL_STATUS | VIR_DOMAIN_XML_INTERNAL_ACTUAL_NET | - VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES | - VIR_DOMAIN_XML_INTERNAL_BASEDATE))) + VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES))) goto error; virUUIDFormat(obj->def->uuid, uuidstr); diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index bde303c..94285e3 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1727,11 +1727,6 @@ struct _virDomainClockDef { struct { long long adjustment; int basis; - - /* Store the base date (-rtc base=$date, in seconds - * since the Epoch) of guest process, internal only - */ - unsigned long long basedate; } variable; /* Timezone name, when diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 193959f..95f747f 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -5988,9 +5988,6 @@ qemuBuildClockArgStr(virDomainClockDefPtr def) break; } - /* Store the guest's basedate */ - def->data.variable.basedate = now; - virBufferAsprintf(&buf, "base=%d-%02d-%02dT%02d:%02d:%02d", nowbits.tm_year + 1900, nowbits.tm_mon + 1, diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index a83780f..6dcb737 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -843,19 +843,6 @@ qemuProcessHandleRTCChange(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); - - /* QEMU's RTC_CHANGE event returns the offset from the specified - * date instead of the host UTC if a specific date is provided - * (-rtc base=$date). We need to convert it to be offset from - * host UTC. - */ - if (vm->def->clock.offset == VIR_DOMAIN_CLOCK_OFFSET_VARIABLE) { - time_t now = time(NULL); - - offset += vm->def->clock.data.variable.basedate - - (unsigned long long)now; - } - event = virDomainEventRTCChangeNewFromObj(vm, offset); if (vm->def->clock.offset == VIR_DOMAIN_CLOCK_OFFSET_VARIABLE) -- 1.9.0

On 05/22/2014 05:07 AM, Laine Stump wrote:
This reverts commit e31b5cf393857a6ca78d148c19393e28dfb39de1.
This commit attempted to work around a bug in the offset value reported by qemu's RTC_CHANGE event in the case that a variable base date was given on the qemu commandline. The patch mixed up the math involved in arriving at the corrected offset to report, and in the process added an unnecessary private attribute to the clock element. Since that element is private/internal and not used by anyone else, it makes sense to simplify things by removing it. --- Unchanged from V2. This is the direct result of:
git revert e31b5cf393857a6ca78d148c19393e28dfb39de1
src/conf/domain_conf.c | 27 ++++----------------------- src/conf/domain_conf.h | 5 ----- src/qemu/qemu_command.c | 3 --- src/qemu/qemu_process.c | 13 ------------- 4 files changed, 4 insertions(+), 44 deletions(-)
ACK; but don't push until we have the rest of the series in good shape. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

commit e31b5cf393857 attempted to fix libvirt's VIR_DOMAIN_EVENT_ID_RTC_CHANGE, which is documentated to always provide the new offset of the domain's real time clock from UTC. The problem was that, in the case that qemu is provided with an "-rtc base=x" where x is an absolute time (rather than "utc" or "localtime"), the offset sent by qemu's RTC_CHANGE event is *not* the new offset from UTC, but rather is the sum of all changes to the domain's RTC since it was started with base=x. So, despite what was said in commit e31b5cf393857, if we assume that the original value stored in "adjustment" was the offset from UTC at the time the domain was started, we can always determine the current offset from UTC by simply adding the most recent (i.e. current) offset from qemu to that original adjustment. This patch accomplishes that by storing the initial adjustment in the domain's status as "adjustment0". Each time a new RTC_CHANGE event is received from qemu, we simply add adjustment0 to the value sent by qemu, store that as the new adjustment, and forward that value on to any event handler. This patch (*not* e31b5cf393857, which should be reverted prior to applying this patch) fixes: https://bugzilla.redhat.com/show_bug.cgi?id=964177 (for the case where basis='utc'. It does not fix basis='localtime') --- Changes from V1: remove all attempts to fix basis='localtime' in favor of fixing it in a simpler and better manner in a separate patch. src/conf/domain_conf.c | 21 +++++++++++++++++---- src/conf/domain_conf.h | 7 +++++++ src/qemu/qemu_command.c | 9 +++++++++ src/qemu/qemu_process.c | 27 ++++++++++++++++++++++----- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index a7863c3..cc33b86 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -99,6 +99,7 @@ typedef enum { VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES = (1<<18), VIR_DOMAIN_XML_INTERNAL_ALLOW_ROM = (1<<19), VIR_DOMAIN_XML_INTERNAL_ALLOW_BOOT = (1<<20), + VIR_DOMAIN_XML_INTERNAL_CLOCK_ADJUST = (1<<21), } virDomainXMLInternalFlags; VIR_ENUM_IMPL(virDomainTaint, VIR_DOMAIN_TAINT_LAST, @@ -12002,6 +12003,9 @@ virDomainDefParseXML(xmlDocPtr xml, if (virXPathLongLong("number(./clock/@adjustment)", ctxt, &def->clock.data.variable.adjustment) < 0) def->clock.data.variable.adjustment = 0; + if (virXPathLongLong("number(./clock/@adjustment0)", ctxt, + &def->clock.data.variable.adjustment0) < 0) + def->clock.data.variable.adjustment0 = 0; tmp = virXPathString("string(./clock/@basis)", ctxt); if (tmp) { if ((def->clock.data.variable.basis = virDomainClockBasisTypeFromString(tmp)) < 0) { @@ -17086,7 +17090,8 @@ virDomainResourceDefFormat(virBufferPtr buf, verify(((VIR_DOMAIN_XML_INTERNAL_STATUS | VIR_DOMAIN_XML_INTERNAL_ACTUAL_NET | - VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES) + VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES | + VIR_DOMAIN_XML_INTERNAL_CLOCK_ADJUST) & DUMPXML_FLAGS) == 0); /* This internal version can accept VIR_DOMAIN_XML_INTERNAL_*, @@ -17108,7 +17113,8 @@ virDomainDefFormatInternal(virDomainDefPtr def, virCheckFlags(DUMPXML_FLAGS | VIR_DOMAIN_XML_INTERNAL_STATUS | VIR_DOMAIN_XML_INTERNAL_ACTUAL_NET | - VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES, + VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES | + VIR_DOMAIN_XML_INTERNAL_CLOCK_ADJUST, -1); if (!(type = virDomainVirtTypeToString(def->virtType))) { @@ -17642,6 +17648,11 @@ virDomainDefFormatInternal(virDomainDefPtr def, virBufferAsprintf(buf, " adjustment='%lld' basis='%s'", def->clock.data.variable.adjustment, virDomainClockBasisTypeToString(def->clock.data.variable.basis)); + if (flags & VIR_DOMAIN_XML_INTERNAL_CLOCK_ADJUST) { + if (def->clock.data.variable.adjustment0) + virBufferAsprintf(buf, " adjustment0='%lld'", + def->clock.data.variable.adjustment0); + } break; case VIR_DOMAIN_CLOCK_OFFSET_TIMEZONE: virBufferEscapeString(buf, " timezone='%s'", def->clock.data.timezone); @@ -18072,7 +18083,8 @@ virDomainSaveStatus(virDomainXMLOptionPtr xmlopt, unsigned int flags = (VIR_DOMAIN_XML_SECURE | VIR_DOMAIN_XML_INTERNAL_STATUS | VIR_DOMAIN_XML_INTERNAL_ACTUAL_NET | - VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES); + VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES | + VIR_DOMAIN_XML_INTERNAL_CLOCK_ADJUST); int ret = -1; char *xml; @@ -18160,7 +18172,8 @@ virDomainObjListLoadStatus(virDomainObjListPtr doms, if (!(obj = virDomainObjParseFile(statusFile, caps, xmlopt, expectedVirtTypes, VIR_DOMAIN_XML_INTERNAL_STATUS | VIR_DOMAIN_XML_INTERNAL_ACTUAL_NET | - VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES))) + VIR_DOMAIN_XML_INTERNAL_PCI_ORIG_STATES | + VIR_DOMAIN_XML_INTERNAL_CLOCK_ADJUST))) goto error; virUUIDFormat(obj->def->uuid, uuidstr); diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 94285e3..8f17c74 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1727,6 +1727,13 @@ struct _virDomainClockDef { struct { long long adjustment; int basis; + + /* domain start-time adjustment. This is a + * private/internal read-only value that only exists when + * a domain is running, and only if the clock + * offset='variable' + */ + long long adjustment0; } variable; /* Timezone name, when diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 95f747f..4998bca 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -36,6 +36,7 @@ #include "virfile.h" #include "virnetdev.h" #include "virstring.h" +#include "virtime.h" #include "viruuid.h" #include "c-ctype.h" #include "domain_nwfilter.h" @@ -5988,6 +5989,14 @@ qemuBuildClockArgStr(virDomainClockDefPtr def) break; } + /* when an RTC_CHANGE event is received from qemu, we need to + * have the adjustment used at domain start time available to + * compute the new offset from UTC. As this new value is + * itself stored in def->data.variable.adjustment, we need to + * save a copy of it now. + */ + def->data.variable.adjustment0 = def->data.variable.adjustment; + virBufferAsprintf(&buf, "base=%d-%02d-%02dT%02d:%02d:%02d", nowbits.tm_year + 1900, nowbits.tm_mon + 1, diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 6dcb737..fb90ac9 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -831,7 +831,6 @@ qemuProcessHandleResume(qemuMonitorPtr mon ATTRIBUTE_UNUSED, return 0; } - static int qemuProcessHandleRTCChange(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, @@ -843,13 +842,31 @@ qemuProcessHandleRTCChange(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); - event = virDomainEventRTCChangeNewFromObj(vm, offset); - if (vm->def->clock.offset == VIR_DOMAIN_CLOCK_OFFSET_VARIABLE) + if (vm->def->clock.offset == VIR_DOMAIN_CLOCK_OFFSET_VARIABLE) { + /* when a basedate is manually given on the qemu commandline + * rather than simply "-rtc base=utc", the offset sent by qemu + * in this event is *not* the new offset from UTC, but is + * instead the new offset from the *original basedate* + + * uptime. For example, if the original offset was 3600 and + * the guest clock has been advanced by 10 seconds, qemu will + * send "10" in the event - this means that the new offset + * from UTC is 3610, *not* 10. If the guest clock is advanced + * by another 10 seconds, qemu will now send "20" - i.e. each + * event is the sum of the most recent change and all previous + * changes since the domain was started. Fortunately, we have + * saved the initial offset in "adjustment0", so to arrive at + * the proper new "adjustment", we just add the most recent + * offset to adjustment0. + */ + offset += vm->def->clock.data.variable.adjustment0; vm->def->clock.data.variable.adjustment = offset; - if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm) < 0) - VIR_WARN("unable to save domain status with RTC change"); + if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm) < 0) + VIR_WARN("unable to save domain status with RTC change"); + } + + event = virDomainEventRTCChangeNewFromObj(vm, offset); virObjectUnlock(vm); -- 1.9.0

[Adding qemu] On 05/22/2014 05:07 AM, Laine Stump wrote:
commit e31b5cf393857 attempted to fix libvirt's VIR_DOMAIN_EVENT_ID_RTC_CHANGE, which is documentated to always
s/documentated/documented/
provide the new offset of the domain's real time clock from UTC. The problem was that, in the case that qemu is provided with an "-rtc base=x" where x is an absolute time (rather than "utc" or "localtime"), the offset sent by qemu's RTC_CHANGE event is *not* the new offset from UTC, but rather is the sum of all changes to the domain's RTC since it was started with base=x.
So, despite what was said in commit e31b5cf393857, if we assume that the original value stored in "adjustment" was the offset from UTC at the time the domain was started, we can always determine the current offset from UTC by simply adding the most recent (i.e. current) offset from qemu to that original adjustment.
Is this true even if we miss an RTC update event from qemu? I'm worried about the following situation: user prepares to do a libvirtd upgrade, so libvirtd is shut down. Then the guest triggers an RTC update, so qemu sends an event, but the event is lost. Then libvirtd starts again, and doesn't realize the event is lost. Do we need more help from qemu, such as a new field to an existing QMP command (or a new QMP command) that lists the cumulative offset that qemu is using, where we call that query command any time after an RTC update event or after a libvirtd restart? I'm wondering if this is more a bug in qemu for not providing the right information rather than libvirt's responsibility to work around it. If the only way to keep accurate information is to sum the values we get from events, we are at risk of a lost event getting us messed up.
This patch accomplishes that by storing the initial adjustment in the domain's status as "adjustment0". Each time a new RTC_CHANGE event is received from qemu, we simply add adjustment0 to the value sent by qemu, store that as the new adjustment, and forward that value on to any event handler.
This patch (*not* e31b5cf393857, which should be reverted prior to applying this patch) fixes:
https://bugzilla.redhat.com/show_bug.cgi?id=964177
(for the case where basis='utc'. It does not fix basis='localtime') ---
Changes from V1: remove all attempts to fix basis='localtime' in favor of fixing it in a simpler and better manner in a separate patch.
I'd also appreciate it if the qemu developers can chime in on what is supposed to happen for localtime guests. There are at least four combinations: host running on UTC bios time, guest running on UTC time (in my opinion, the only sane setting, but we're talking about reality not sanity) host running on UTC, guest running on localtime (perhaps the guest is windows, and we know that windows prefers to run on localtime) host running on localtime bios (perhaps because it is dual-boot with windows, and windows prefers bios in localtime), guest running on UTC time host running on localtime, guest running on localtime But it gets even more complicated. The host localtime need not be consistent with the guest localtime. That is, I could be a cloud provider with servers on the east coast, and renting out processor time to a client on the west coast that wants their guest tied to west coast localtime. And that's assuming that both host and guest switch in and out of daylight savings at the same time, which falls apart when you cross political boundaries. Then there's the fun of migration (what if my server farm is spread across multiple timezones - does migration take into account the difference in localtime between source and destination servers). I can _totally_ understand the desire to run a GUEST in such a way that the guest thinks it has a bios stored in localtime (and when the guest updates the RTC twice a year to account for daylight savings, it changes what offset we track about the guest). But I think it is INSANITY to ever try and run a host on a localtime system (daylight savings changes in the host are just asking for problems to the guests) - so even if the host is tied to localtime bios, it is still probably wiser for qemu to base its offsets to UTC no matter what. If the commandline allows a specification of a localtime offset, I think it should be used ONLY for a one-time up-front conversion into a corresponding UTC offset, and then execute qemu in relation to utc thereafter (therefore, migration is always done in terms of utc, without regards for whether source and destination have a different localtime).
src/conf/domain_conf.c | 21 +++++++++++++++++---- src/conf/domain_conf.h | 7 +++++++ src/qemu/qemu_command.c | 9 +++++++++ src/qemu/qemu_process.c | 27 ++++++++++++++++++++++----- 4 files changed, 55 insertions(+), 9 deletions(-)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
- if (vm->def->clock.offset == VIR_DOMAIN_CLOCK_OFFSET_VARIABLE) + if (vm->def->clock.offset == VIR_DOMAIN_CLOCK_OFFSET_VARIABLE) { + /* when a basedate is manually given on the qemu commandline + * rather than simply "-rtc base=utc", the offset sent by qemu + * in this event is *not* the new offset from UTC, but is + * instead the new offset from the *original basedate* + + * uptime. For example, if the original offset was 3600 and + * the guest clock has been advanced by 10 seconds, qemu will + * send "10" in the event - this means that the new offset + * from UTC is 3610, *not* 10. If the guest clock is advanced + * by another 10 seconds, qemu will now send "20" - i.e. each + * event is the sum of the most recent change and all previous + * changes since the domain was started. Fortunately, we have + * saved the initial offset in "adjustment0", so to arrive at + * the proper new "adjustment", we just add the most recent + * offset to adjustment0. + */ + offset += vm->def->clock.data.variable.adjustment0; vm->def->clock.data.variable.adjustment = offset;
- if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm) < 0) - VIR_WARN("unable to save domain status with RTC change"); + if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm) < 0) + VIR_WARN("unable to save domain status with RTC change"); + } + + event = virDomainEventRTCChangeNewFromObj(vm, offset);
virObjectUnlock(vm);
-- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Thu, May 22, 2014 at 01:33:14PM -0600, Eric Blake wrote:
[Adding qemu]
On 05/22/2014 05:07 AM, Laine Stump wrote:
commit e31b5cf393857 attempted to fix libvirt's VIR_DOMAIN_EVENT_ID_RTC_CHANGE, which is documentated to always
s/documentated/documented/
provide the new offset of the domain's real time clock from UTC. The problem was that, in the case that qemu is provided with an "-rtc base=x" where x is an absolute time (rather than "utc" or "localtime"), the offset sent by qemu's RTC_CHANGE event is *not* the new offset from UTC, but rather is the sum of all changes to the domain's RTC since it was started with base=x.
So, despite what was said in commit e31b5cf393857, if we assume that the original value stored in "adjustment" was the offset from UTC at the time the domain was started, we can always determine the current offset from UTC by simply adding the most recent (i.e. current) offset from qemu to that original adjustment.
Is this true even if we miss an RTC update event from qemu? I'm worried about the following situation:
user prepares to do a libvirtd upgrade, so libvirtd is shut down.
If adjustment0 field is updated from adjustment, via a libvirtd shutdown, the current patch will also break, i believe. Not sure if thats possible, though.
Then the guest triggers an RTC update, so qemu sends an event, but the event is lost. Then libvirtd starts again, and doesn't realize the event is lost.
Yes, but that case is also true for any other QMP asynchronous event, and therefore should be handled generically i suppose (QMP channel data should be maintained across libvirtd shutdown). Luiz?
Do we need more help from qemu, such as a new field to an existing QMP command (or a new QMP command) that lists the cumulative offset that qemu is using, where we call that query command any time after an RTC update event or after a libvirtd restart? I'm wondering if this is more a bug in qemu for not providing the right information rather than libvirt's responsibility to work around it. If the only way to keep accurate information is to sum the values we get from events, we are at risk of a lost event getting us messed up.
Good point, unsure whether its specific to this command, though. Could add a new QMP command to query the RTC offset, yes.
This patch accomplishes that by storing the initial adjustment in the domain's status as "adjustment0". Each time a new RTC_CHANGE event is received from qemu, we simply add adjustment0 to the value sent by qemu, store that as the new adjustment, and forward that value on to any event handler.
This patch (*not* e31b5cf393857, which should be reverted prior to applying this patch) fixes:
https://bugzilla.redhat.com/show_bug.cgi?id=964177
(for the case where basis='utc'. It does not fix basis='localtime') ---
Changes from V1: remove all attempts to fix basis='localtime' in favor of fixing it in a simpler and better manner in a separate patch.
I'd also appreciate it if the qemu developers can chime in on what is supposed to happen for localtime guests.
There are at least four combinations:
host running on UTC bios time, guest running on UTC time (in my opinion, the only sane setting, but we're talking about reality not sanity)
1) Why does Windows keep your BIOS clock on local time? http://blogs.msdn.com/b/oldnewthing/archive/2004/09/02/224672.aspx 2) Windows with RTC UTC https://wiki.archlinux.org/index.php/Time#UTC_in_Windows
host running on UTC, guest running on localtime (perhaps the guest is windows, and we know that windows prefers to run on localtime)
Its the default, thats all. See 1) above.
host running on localtime bios (perhaps because it is dual-boot with windows, and windows prefers bios in localtime), guest running on UTC time
Can't see why hosts using localtime or UTC is relevant. Assume host is synchronized to UTC via NTP (so you can use UTC or convert to localtime if desired).
host running on localtime, guest running on localtime
But it gets even more complicated. The host localtime need not be consistent with the guest localtime. That is, I could be a cloud provider with servers on the east coast, and renting out processor time to a client on the west coast that wants their guest tied to west coast localtime. And that's assuming that both host and guest switch in and out of daylight savings at the same time, which falls apart when you cross political boundaries. Then there's the fun of migration (what if my server farm is spread across multiple timezones - does migration take into account the difference in localtime between source and destination servers).
I can _totally_ understand the desire to run a GUEST in such a way that the guest thinks it has a bios stored in localtime (and when the guest updates the RTC twice a year to account for daylight savings, it changes what offset we track about the guest). But I think it is INSANITY to ever try and run a host on a localtime system (daylight savings changes in the host are just asking for problems to the guests) - so even if the host is tied to localtime bios, it is still probably wiser for qemu to base its offsets to UTC no matter what. If the commandline allows a specification of a localtime offset, I think it should be used ONLY for a one-time up-front conversion into a corresponding UTC offset, and then execute qemu in relation to utc thereafter (therefore, migration is always done in terms of utc, without regards for whether source and destination have a different localtime).
Guest side: * Emulated RTC CMOS clock is initialized to either UTC or localtime when the guest initializes. * Guest reads RTC CMOS clock on boot, or if explicitly requested to during runtime, and transfers that value to its system time. * Migration maintains the emulated RTC CMOS clock value, but a subsequent VM restart will use the destination hosts localtime, in case -rtc base=localtime. So using -rtc base= "localtime_r(time())" (this is a date) on VM creation and then maintaining that base, along with guest RTC writes, across VM restarts, seems much saner than ever using -rtc base=localtime on a cluster with different timezones.

On 05/23/2014 06:50 AM, Marcelo Tosatti wrote: > On Thu, May 22, 2014 at 01:33:14PM -0600, Eric Blake wrote: >> [Adding qemu] >> >> On 05/22/2014 05:07 AM, Laine Stump wrote: >>> commit e31b5cf393857 attempted to fix libvirt's >>> VIR_DOMAIN_EVENT_ID_RTC_CHANGE, which is documentated to always >> s/documentated/documented/ >> >>> provide the new offset of the domain's real time clock from UTC. The >>> problem was that, in the case that qemu is provided with an "-rtc >>> base=x" where x is an absolute time (rather than "utc" or >>> "localtime"), the offset sent by qemu's RTC_CHANGE event is *not* the >>> new offset from UTC, but rather is the sum of all changes to the >>> domain's RTC since it was started with base=x. >>> >>> So, despite what was said in commit e31b5cf393857, if we assume that >>> the original value stored in "adjustment" was the offset from UTC at >>> the time the domain was started, we can always determine the current >>> offset from UTC by simply adding the most recent (i.e. current) offset >>> from qemu to that original adjustment. >> Is this true even if we miss an RTC update event from qemu? I'm worried >> about the following situation: >> >> user prepares to do a libvirtd upgrade, so libvirtd is shut down. > If adjustment0 field is updated from adjustment, via a libvirtd shutdown, the current > patch will also break, i believe. Not sure if thats possible, though. No, adjustment0 is only set at the time a new qemu process is started. > >> Then the guest triggers an RTC update, so qemu sends an event, but the >> event is lost. Then libvirtd starts again, and doesn't realize the >> event is lost. That case would only be a problem until the *next* time an RTC update is sent; at that time the adjustment would be readjusted to adjustment0 + new offset (and that new offset is the cumulative sum of all adjustments since the domain was started). > Yes, but that case is also true for any other QMP asynchronous event, > and therefore should be handled generically i suppose (QMP channel data > should be maintained across libvirtd shutdown). Luiz? > >> Do we need more help from qemu, such as a new field to an existing QMP >> command (or a new QMP command) that lists the cumulative offset that >> qemu is using, where we call that query command any time after an RTC >> update event or after a libvirtd restart? I'm wondering if this is more >> a bug in qemu for not providing the right information rather than >> libvirt's responsibility to work around it. If the only way to keep >> accurate information is to sum the values we get from events, we are at >> risk of a lost event getting us messed up. > Good point, unsure whether its specific to this command, though. Could > add a new QMP command to query the RTC offset, yes. > >>> This patch accomplishes that by storing the initial adjustment in the >>> domain's status as "adjustment0". Each time a new RTC_CHANGE event is >>> received from qemu, we simply add adjustment0 to the value sent by >>> qemu, store that as the new adjustment, and forward that value on to >>> any event handler. >>> >>> This patch (*not* e31b5cf393857, which should be reverted prior to >>> applying this patch) fixes: >>> >>> https://bugzilla.redhat.com/show_bug.cgi?id=964177 >>> >>> (for the case where basis='utc'. It does not fix basis='localtime') >>> --- >>> >>> Changes from V1: remove all attempts to fix basis='localtime' in favor >>> of fixing it in a simpler and better manner in a separate patch. >> I'd also appreciate it if the qemu developers can chime in on what is >> supposed to happen for localtime guests. >> >> There are at least four combinations: >> >> host running on UTC bios time, guest running on UTC time (in my opinion, >> the only sane setting, but we're talking about reality not sanity) > 1) Why does Windows keep your BIOS clock on local time? > http://blogs.msdn.com/b/oldnewthing/archive/2004/09/02/224672.aspx > > 2) Windows with RTC UTC > https://wiki.archlinux.org/index.php/Time#UTC_in_Windows > >> host running on UTC, guest running on localtime (perhaps the guest is >> windows, and we know that windows prefers to run on localtime) > Its the default, thats all. See 1) above. > >> host running on localtime bios (perhaps because it is dual-boot with >> windows, and windows prefers bios in localtime), guest running on UTC time > Can't see why hosts using localtime or UTC is relevant. Assume host is > synchronized to UTC via NTP (so you can use UTC or convert to localtime > if desired). Correct - the host's use of its own RTC is irrelevant. The only potential difference is basis='utc' vs. basis='localtime', and anyone using basis='localtime' more or less deserves the confusion it causes :-P (That really is a "tongue in cheek" emoticon - I'm only half serious). *However*, this discussion forced me to investigate some of the basic assumptions that I'd been making when coming in to fix this bug. In particular, my assumption was that the value of "adjustment" that was set in the status would be preserved across a domain save/restore operation, or a migration, but after talking to jdenemar and looking at the code, I believe that this is *not* the case. As I understand it, one of the driving factors behind having adjustment='variable' was that changes to the RTC would be properly maintained across save/resoter and migrations, and thus I ASSumed that the setting *was* being maintained, but that the math was wrong. As far as I can tell, though, only the *inactive* XML is saved in a save image or sent in a migration, while the modified adjustment is only in the transient/status XML; the result is that the modified adjustment (and modified basis per patch 4/4) are lost any time there is a save/restore or migration. So while these patches are correcting the math, I guess any claims that I make in the commit logs about them fixing problems with migration or save/restore are ill-informed, and should be removed (and we should file/track a separate bug about that issue). >> host running on localtime, guest running on localtime >> >> But it gets even more complicated. The host localtime need not be >> consistent with the guest localtime. That is, I could be a cloud >> provider with servers on the east coast, and renting out processor time >> to a client on the west coast that wants their guest tied to west coast >> localtime. And that's assuming that both host and guest switch in and >> out of daylight savings at the same time, which falls apart when you >> cross political boundaries. Then there's the fun of migration (what if >> my server farm is spread across multiple timezones - does migration take >> into account the difference in localtime between source and destination >> servers). I'm pretty sure that Daniel's suggestion (which I've implemented in patch 4) removes all of the problems *that don't involve migration or save/restore* to the extent that they can be removed (and they *do* set the adjustment/basis in the status such that if they *were* preserved across migrate or save/restore, behavior would then be correct) although there is still a problem if you don't know which timezone the initial host will be in, but want your guest to have RTC set to the localtime of a particular timezone - I suppose for that we would need to expand "basis='utc|localtime'" to allow specifying a particular timezone, then learn the time of that timezone when qemu is started. That's beyond the scope of this "bugfix" patch series though. >> >> I can _totally_ understand the desire to run a GUEST in such a way that >> the guest thinks it has a bios stored in localtime (and when the guest >> updates the RTC twice a year to account for daylight savings, it changes >> what offset we track about the guest). But I think it is INSANITY to >> ever try and run a host on a localtime system (daylight savings changes >> in the host are just asking for problems to the guests) - so even if the >> host is tied to localtime bios, it is still probably wiser for qemu to >> base its offsets to UTC no matter what. If the commandline allows a >> specification of a localtime offset, I think it should be used ONLY for >> a one-time up-front conversion into a corresponding UTC offset, and then >> execute qemu in relation to utc thereafter (therefore, migration is >> always done in terms of utc, without regards for whether source and >> destination have a different localtime). Yep :-) > Guest side: > * Emulated RTC CMOS clock is initialized to either UTC or localtime when the > guest initializes. > * Guest reads RTC CMOS clock on boot, or if explicitly requested to > during runtime, and transfers that value to its system time. > > * Migration maintains the emulated RTC CMOS clock value, but a > subsequent VM restart will use the destination hosts localtime, > in case -rtc base=localtime. > > So using > > -rtc base= "localtime_r(time())" (this is a date) on VM creation > > and then maintaining that base, along with guest RTC writes, across VM > restarts, seems much saner than ever using -rtc base=localtime on a cluster > with different timezones. > > Again, yep :-)

On 05/23/2014 12:17 PM, Laine Stump wrote:
*However*, this discussion forced me to investigate some of the basic assumptions that I'd been making when coming in to fix this bug. In particular, my assumption was that the value of "adjustment" that was set in the status would be preserved across a domain save/restore operation, or a migration, but after talking to jdenemar and looking at the code, I believe that this is *not* the case.
Okay, disregard this "sky is falling" outburst. I was misreading the code and misinterpreting what jdenemar told me. The updated value of adjustment and basis *are* properly preserved across save/restore and migrate. The problem Eric pointed out is real though (if the domain RTC is modified while libvirtd isn't available to catch the RTC_CHANGE event, libvirt will have an incorrect idea of adjustment. This will be temporary until another RTC_CHANGE event happens *unless* the domain is migrated or saved/restored before another RTC_CHANGE happens, in which case the incorrectness will be semi-permanent (until the domain is completely stopped and restarted).) Also, as I understand it, this means that if a domain is migrated with persistence, the new domain on the destination will have made the change to adjustment and basis permanent, but if it stays on the same host that change will only be valid until the domain is destroyed - next time it is started it will go back to the original settings.

On 05/23/2014 04:19 AM, Laine Stump wrote:
On 05/23/2014 12:17 PM, Laine Stump wrote:
*However*, this discussion forced me to investigate some of the basic assumptions that I'd been making when coming in to fix this bug. In particular, my assumption was that the value of "adjustment" that was set in the status would be preserved across a domain save/restore operation, or a migration, but after talking to jdenemar and looking at the code, I believe that this is *not* the case.
Okay, disregard this "sky is falling" outburst. I was misreading the code and misinterpreting what jdenemar told me. The updated value of adjustment and basis *are* properly preserved across save/restore and migrate.
Okay, now to rephrase things to see if I understand correctly, or maybe add more confusion to the discussion. If I understand it, the qemu event is always outputting the 'current adjustment to the command-line offset', and not 'the delta applied to the most recent RTC change'; while libvirt is trying to report 'the current delta that the guest is using in relation to UTC'. If the command line was specified with UTC as the basis (that is, 0 command-line offset), then the qemu offset happens to also be the libvirt desired offset from UTC. If the command line was specified with any other offset, then the offset from UTC is always the sum of the command line initial offset + the current offset reported by the event, and libvirt is not altering the initial offset while the guest runs. And I don't think libvirt has a way for management to query the current offset (libvirt was only tracking the current offset of running domains in its private xml). For a guest that uses localtime bios, you would start the guest initially with a non-zero command-line offset (representing the offset of the timezone the guest thinks it is running in). Twice a year, the guest will change its RTC by an hour, and the GOAL is that if we stop the guest and then restart it later, the restarted guest will resume with bios at the same offset from UTC as what it last had (that is, the freshly-booted guest will behave as if bios has silently advanced by the amount of time that the guest was not running, similar to how bare-metal hardware has a battery powering the RTC even when the box is completely off and unplugged). If, during the downtime, the guest has crossed a daylight savings boundary, we don't care - the guest OS upon boot will recognize that it is using localtime bios, and that it missed a dst boundary while the machine was powered off, and so it will presumably do an RTC update by an hour fairly early in its boot process - which will be caught as an RTC update event so we once again know the new offset to be applied the next time we boot the guest. For this to work, a persistent guest definition needs to record the offset that was in use at the time the guest powered off, and the next time qemu is started, pass _that_ offset as the command-line offset against UTC. In libvirt's case, we intentionally run qemu to stay alive even after the guest quits, and send an event that the guest is no longer running; this event could be our trigger to read the qemu offset and adjust the persistent state to track the new offset to use on the next boot of the guest. More interesting is migration (whether by saving to a file or by migration between hosts). Libvirt is able to query the current qemu offset prior to starting the migration, but what happens if the guest changes the RTC on the source after the time that libvirt did the query but before the guest is paused in preparation for the destination to start running the guest? Is the RTC offset transmitted as part of the migration stream, even if it is a different offset at the time the migration finally converges than what it was when the migration started? Furthermore, what command line offset should libvirt tell the destination machine to use, since qemu is only tracking the RTC offset relative to the command line, and not the offset relative to UTC? Is there a race here? More concretely, suppose I start a guest with a command-line offset of 3 hours. Then the guest does an RTC change to 4 hours (an offset of one hour from the command line). Then I want to do a migration - do I tell the destination qemu to start with a 3 hour offset (identical to what the source had) and the migration stream corrects it so that the destination picks up with RTC already at 4 hours (and the guest sees no discontinuity)? Or does libvirt have to query the current offset at the time it gets ready to start the migration, and start the destination with a command-line offset of 4 hours, because the offset is not migrated? If the latter, what happens if the guest changes RTC in between when libvirt queries the source for the value to give to the destination, vs. actually starting the migration? It sounds like the only sane way for migration to work is for the RTC offset to be part of the stream, and that the destination must be started with the SAME command line as the source; and therefore, the only time the command line should be altered is when the guest is shut down (not for live migration) - where libvirt must update the domain XML to track the offset in use at the time the guest shuts down. Meanwhile, I know we also recently added the qemu-guest-agent command to set time, with two modes: 1. tell the guest what UTC time it should be using, and update RTC to match (which will trigger an RTC change event; and perhaps can be used to snoop whether the guest is setting RTC to a localtime rather than UTC time); 2. tell the guest to re-read the RTC and adjust its local time to match (which pre-supposes that RTC is accurate). The idea is that after management does something where the guest has a long downtime (either migration to file, or a long suspend), the guest's internal notion of time did not advance (because the CPU was not running) while the RTC did advance (because qemu is still exposing RTC relative to the host's UTC), so the management will tell the guest to resync time as part of resuming. Now, what good does the RTC change event do, if libvirt really only needs to query the current RTC offset at the time shuts down? Given that the guest-agent command deals in UTC (when setting time), does the management app need to know when the guest changes RTC due to daylight savings? Or is the case where management tells guest to reset its own time by re-reading RTC matter for management to know whether that RTC is not tracking UTC? Also, it seems like most people want their guests to run with a proper notion of current time, even over gaps of time where the guest is not running (true if the guest is connected to a network and expects to do anything where being in sync with other computers is important). But is there ever someone that wants a guest to run as though it were seeing all possible time values, and thus where every time the guest is paused and then resumed, its offset from UTC is increased by the amount of downtime that elapsed (most likely, for a standalone guest with no emulated network connections)? Do we need to do anything different for a management app trying to run a guest in such a mode? -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Fri, May 23, 2014 at 08:54:54AM -0600, Eric Blake wrote:
On 05/23/2014 04:19 AM, Laine Stump wrote:
On 05/23/2014 12:17 PM, Laine Stump wrote:
*However*, this discussion forced me to investigate some of the basic assumptions that I'd been making when coming in to fix this bug. In particular, my assumption was that the value of "adjustment" that was set in the status would be preserved across a domain save/restore operation, or a migration, but after talking to jdenemar and looking at the code, I believe that this is *not* the case.
Okay, disregard this "sky is falling" outburst. I was misreading the code and misinterpreting what jdenemar told me. The updated value of adjustment and basis *are* properly preserved across save/restore and migrate.
Okay, now to rephrase things to see if I understand correctly, or maybe add more confusion to the discussion.
If I understand it, the qemu event is always outputting the 'current adjustment to the command-line offset', and not 'the delta applied to the most recent RTC change'; while libvirt is trying to report 'the current delta that the guest is using in relation to UTC'. If the command line was specified with UTC as the basis (that is, 0 command-line offset), then the qemu offset happens to also be the libvirt desired offset from UTC. If the command line was specified with any other offset, then the offset from UTC is always the sum of the command line initial offset + the current offset reported by the event, and libvirt is not altering the initial offset while the guest runs.
It reports the offset to a running clock. That running clock is either UTC or UTC+basedate, where basedate has been specified in -rtc base=date.
And I don't think libvirt has a way for management to query the current offset (libvirt was only tracking the current offset of running domains in its private xml).
No, have to fix, good point.
For a guest that uses localtime bios, you would start the guest initially with a non-zero command-line offset (representing the offset of the timezone the guest thinks it is running in). Twice a year, the guest will change its RTC by an hour, and the GOAL is that if we stop the guest and then restart it later, the restarted guest will resume with bios at the same offset from UTC as what it last had (that is, the freshly-booted guest will behave as if bios has silently advanced by the amount of time that the guest was not running, similar to how bare-metal hardware has a battery powering the RTC even when the box is completely off and unplugged). If, during the downtime, the guest has crossed a daylight savings boundary, we don't care - the guest OS upon boot will recognize that it is using localtime bios, and that it missed a dst boundary while the machine was powered off, and so it will presumably do an RTC update by an hour fairly early in its boot process - which will be caught as an RTC update event so we once again know the new offset to be applied the next time we boot the guest.
Should start the RTC in VM creation with a given localtime, because Windows expects RTC time in localtime format. After that, maintain whatever offset the guest wrote to RTC afterwards, and emulate RTC CMOS semantics by advancing time when guest is powered off.
For this to work, a persistent guest definition needs to record the offset that was in use at the time the guest powered off, and the next time qemu is started, pass _that_ offset as the command-line offset against UTC. In libvirt's case, we intentionally run qemu to stay alive even after the guest quits, and send an event that the guest is no longer running; this event could be our trigger to read the qemu offset and adjust the persistent state to track the new offset to use on the next boot of the guest.
Only have to query offset if libvirtd is shutdown, otherwise value from RTC_EVENT notification is sufficient.
More interesting is migration (whether by saving to a file or by migration between hosts). Libvirt is able to query the current qemu offset prior to starting the migration, but what happens if the guest changes the RTC on the source after the time that libvirt did the query but before the guest is paused in preparation for the destination to start running the guest? Is the RTC offset transmitted as part of the migration stream,
Yes.
even if it is a different offset at the time the migration finally converges than what it was when the migration started? Furthermore, what command line offset should libvirt tell the destination machine to use,
Always -rtc base="offset of guest RTC to UTC".
since qemu is only tracking the RTC offset relative to the command line, and not the offset relative to UTC? Is there a race here?
But the command line date should be "UTC + offset". So if you track against that, you can track against UTC.
More concretely, suppose I start a guest with a command-line offset of 3 hours. Then the guest does an RTC change to 4 hours (an offset of one hour from the command line). Then I want to do a migration - do I tell the destination qemu to start with a 3 hour offset (identical to what the source had) and the migration stream corrects it so that the destination picks up with RTC already at 4 hours (and the guest sees no discontinuity)? Or does libvirt have to query the current offset at the time it gets ready to start the migration, and start the destination with a command-line offset of 4 hours, because the offset is not migrated? If the latter, what happens if the guest changes RTC in between when libvirt queries the source for the value to give to the destination, vs. actually starting the migration? It sounds like the only sane way for migration to work is for the RTC offset to be part of the stream, and that the destination must be started with the SAME command line as the source; and therefore, the only time the command line should be altered is when the guest is shut down (not for live migration) - where libvirt must update the domain XML to track the offset in use at the time the guest shuts down.
Libvirt has to query the RTC offset only on libvirt reinitialization. Otherwise, use value from RTC_EVENT.
Meanwhile, I know we also recently added the qemu-guest-agent command to set time, with two modes: 1. tell the guest what UTC time it should be using, and update RTC to match (which will trigger an RTC change event; and perhaps can be used to snoop whether the guest is setting RTC to a localtime rather than UTC time); 2. tell the guest to re-read the RTC and adjust its local time to match (which pre-supposes that RTC is accurate). The idea is that after management does something where the guest has a long downtime (either migration to file, or a long suspend), the guest's internal notion of time did not advance (because the CPU was not running) while the RTC did advance (because qemu is still exposing RTC relative to the host's UTC), so the management will tell the guest to resync time as part of resuming.
Yes.
Now, what good does the RTC change event do, if libvirt really only needs to query the current RTC offset at the time shuts down? Given that the guest-agent command deals in UTC (when setting time), does the management app need to know when the guest changes RTC due to daylight savings? Or is the case where management tells guest to reset its own time by re-reading RTC matter for management to know whether that RTC is not tracking UTC?
Management does not need to know anything about guest RTC. What it needs to do is: 1) Initialize it to a given timezone's localtime value instead of UTC value, because Windows assumes localtime value by default, on VM creation. Or UTC, if desired (user choice). 2) Maintain whatever value the guest writes into its RTC. 3) When the guest is off, the RTC counts (just as real RTC).
Also, it seems like most people want their guests to run with a proper notion of current time, even over gaps of time where the guest is not running (true if the guest is connected to a network and expects to do anything where being in sync with other computers is important). But is there ever someone that wants a guest to run as though it were seeing all possible time values, and thus where every time the guest is paused and then resumed, its offset from UTC is increased by the amount of downtime that elapsed (most likely, for a standalone guest with no emulated network connections)? Do we need to do anything different for a management app trying to run a guest in such a mode?
Guest RTC clock should count when guest is paused, or shutdown. That covers all cases? (because it mimicks real RTC).

On 05/23/2014 08:54 AM, Eric Blake wrote:
For this to work, a persistent guest definition needs to record the offset that was in use at the time the guest powered off, and the next time qemu is started, pass _that_ offset as the command-line offset against UTC. In libvirt's case, we intentionally run qemu to stay alive even after the guest quits, and send an event that the guest is no longer running; this event could be our trigger to read the qemu offset and adjust the persistent state to track the new offset to use on the next boot of the guest.
Hmm, I wonder if we have a libvirt design dilemma on our hands. Remember, ever since qemu exposed the -no-shutdown flag, we have been using it in libvirt. It says that even though the guest has quit executing, the qemu process must stick around until we explicitly let go of it. This is because the guest can shut down during a libvirtd restart, so the new libvirtd can still connect to the qemu process, learn that the guest has shut down, and take appropriate actions (query qemu for other details like the final state of the RTC offset, tidy up any transient XML, generate the libvirt event to management app, etc). As long as the libvirt guest is persistent, the management app can still query about the guest even after it shuts down. But VDSM uses only transient guests. Similar to how libvirt uses -no-shutdown to keep qemu hanging around until libvirt acknowledges that the guest is done, what recourse does VDSM have to keep a transient guest's virDomainPtr around (even across libvirtd restarts) until VDSM has had time to query the RTC offset from libvirt? That is, since the only libvirt use of RTC offset is to adjust the persistent definition for the next boot, but VDSM is using only transient guests, VDSM needs a way to track the RTC offset to tell libvirt to use the next time VDSM creates a new transient guest for the same disk image. Or put another way, the moment that libvirt offloads persistence to a higher level application, that higher level application needs a way to grab all information that libvirt would have grabbed for a persistent guest, which means a transient guest needs to exist in the shutoff state for at least as long as VDSM hasn't closed it. Do we have a design flaw in that currently libvirt gets rid of transient guests as soon as they shutdown, rather than waiting for management to close their last reference to the shutdown domain? -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 05/23/2014 05:54 PM, Eric Blake wrote:
On 05/23/2014 04:19 AM, Laine Stump wrote:
On 05/23/2014 12:17 PM, Laine Stump wrote:
*However*, this discussion forced me to investigate some of the basic assumptions that I'd been making when coming in to fix this bug. In particular, my assumption was that the value of "adjustment" that was set in the status would be preserved across a domain save/restore operation, or a migration, but after talking to jdenemar and looking at the code, I believe that this is *not* the case. Okay, disregard this "sky is falling" outburst. I was misreading the code and misinterpreting what jdenemar told me. The updated value of adjustment and basis *are* properly preserved across save/restore and migrate. Okay, now to rephrase things to see if I understand correctly, or maybe add more confusion to the discussion.
If I understand it, the qemu event is always outputting the 'current adjustment to the command-line offset', and not 'the delta applied to the most recent RTC change';
Correct. It reports the new offset of the RTC relative to a hypothetical clock that was started when the domain started, and hasn't been adjusted at all.
while libvirt is trying to report 'the current delta that the guest is using in relation to UTC'. If the command line was specified with UTC as the basis (that is, 0 command-line offset), then the qemu offset happens to also be the libvirt desired offset from UTC.
Well, if it was "<clock offset='variable' basis='utc' adjustment='0'/>", then that is true.
If the command line was specified with any other offset, then the offset from UTC is always the sum of the command line initial offset + the current offset reported by the event,
Correct.
and libvirt is not altering the initial offset while the guest runs.
Altering where? libvirt does change "adjustment" in the domain status to reflect the most recent RTC_CHANGE event. It does this by preserving the initial adjustment, and adding that to the offset in the RTC_CHANGE event; this is set as the new adjustment.
And I don't think libvirt has a way for management to query the current offset (libvirt was only tracking the current offset of running domains in its private xml).
For a guest that uses localtime bios, you would start the guest initially with a non-zero command-line offset (representing the offset of the timezone the guest thinks it is running in). Twice a year, the guest will change its RTC by an hour, and the GOAL is that if we stop the guest and then restart it later, the restarted guest will resume with bios at the same offset from UTC as what it last had (that is, the freshly-booted guest will behave as if bios has silently advanced by the amount of time that the guest was not running, similar to how bare-metal hardware has a battery powering the RTC even when the box is completely off and unplugged). If, during the downtime, the guest has crossed a daylight savings boundary, we don't care - the guest OS upon boot will recognize that it is using localtime bios, and that it missed a dst boundary while the machine was powered off, and so it will presumably do an RTC update by an hour fairly early in its boot process - which will be caught as an RTC update event so we once again know the new offset to be applied the next time we boot the guest.
All good up to here.
For this to work, a persistent guest definition needs to record the offset that was in use at the time the guest powered off, and the next time qemu is started, pass _that_ offset as the command-line offset against UTC.
but this part has never been there. We only modify the adjustment in the live XML, never in the persistent XML.
In libvirt's case, we intentionally run qemu to stay alive even after the guest quits, and send an event that the guest is no longer running; this event could be our trigger to read the qemu offset and adjust the persistent state to track the new offset to use on the next boot of the guest.
More interesting is migration (whether by saving to a file or by migration between hosts). Libvirt is able to query the current qemu offset prior to starting the migration, but what happens if the guest changes the RTC on the source after the time that libvirt did the query but before the guest is paused in preparation for the destination to start running the guest?
Yes - that is one of my questions.
Is the RTC offset transmitted as part of the migration stream, even if it is a different offset at the time the migration finally converges than what it was when the migration started?
My uninformed guess has been that even if the RTC is part of the migration stream, that it will be overridden by what is put on the qemu commandline on the destination host.
Furthermore, what command line offset should libvirt tell the destination machine to use, since qemu is only tracking the RTC offset relative to the command line, and not the offset relative to UTC? Is there a race here?
More concretely, suppose I start a guest with a command-line offset of 3 hours. Then the guest does an RTC change to 4 hours (an offset of one hour from the command line). Then I want to do a migration - do I tell the destination qemu to start with a 3 hour offset (identical to what the source had) and the migration stream corrects it so that the destination picks up with RTC already at 4 hours (and the guest sees no discontinuity)? Or does libvirt have to query the current offset at the time it gets ready to start the migration, and start the destination with a command-line offset of 4 hours, because the offset is not migrated? If the latter, what happens if the guest changes RTC in between when libvirt queries the source for the value to give to the destination, vs. actually starting the migration? It sounds like the only sane way for migration to work is for the RTC offset to be part of the stream, and that the destination must be started with the SAME command line as the source; and therefore, the only time the command line should be altered is when the guest is shut down (not for live migration) - where libvirt must update the domain XML to track the offset in use at the time the guest shuts down.
But of course this isn't how it works now. So whatever we do is either wrong now, or wrong in the future.
Meanwhile, I know we also recently added the qemu-guest-agent command to set time, with two modes: 1. tell the guest what UTC time it should be using, and update RTC to match (which will trigger an RTC change event; and perhaps can be used to snoop whether the guest is setting RTC to a localtime rather than UTC time); 2. tell the guest to re-read the RTC and adjust its local time to match (which pre-supposes that RTC is accurate). The idea is that after management does something where the guest has a long downtime (either migration to file, or a long suspend), the guest's internal notion of time did not advance (because the CPU was not running) while the RTC did advance (because qemu is still exposing RTC relative to the host's UTC), so the management will tell the guest to resync time as part of resuming.
Now, what good does the RTC change event do, if libvirt really only needs to query the current RTC offset at the time shuts down? Given that the guest-agent command deals in UTC (when setting time), does the management app need to know when the guest changes RTC due to daylight savings? Or is the case where management tells guest to reset its own time by re-reading RTC matter for management to know whether that RTC is not tracking UTC?
Also, it seems like most people want their guests to run with a proper notion of current time, even over gaps of time where the guest is not running (true if the guest is connected to a network and expects to do anything where being in sync with other computers is important). But is there ever someone that wants a guest to run as though it were seeing all possible time values, and thus where every time the guest is paused and then resumed, its offset from UTC is increased by the amount of downtime that elapsed (most likely, for a standalone guest with no emulated network connections)? Do we need to do anything different for a management app trying to run a guest in such a mode?

On Fri, 23 May 2014 00:50:38 -0300 Marcelo Tosatti <mtosatti@redhat.com> wrote:
Then the guest triggers an RTC update, so qemu sends an event, but the event is lost. Then libvirtd starts again, and doesn't realize the event is lost.
Yes, but that case is also true for any other QMP asynchronous event, and therefore should be handled generically i suppose (QMP channel data should be maintained across libvirtd shutdown). Luiz?
Maintaining QMP channel data doesn't solve this problem, because all sorts of race conditions are still possible. For example, libvirt could crash after having received the event but before handling it. The most reliable way we found to solve this problem, and that's what we do for other events, is to allow libvirt to query the information the event is reporting. An event is nothing more than a state change in QEMU, and QEMU state is persistent during the life time of the VM, so we allow libvirt to query the state of anything that may send an event.

Luiz Capitulino <lcapitulino@redhat.com> writes:
On Fri, 23 May 2014 00:50:38 -0300 Marcelo Tosatti <mtosatti@redhat.com> wrote:
Then the guest triggers an RTC update, so qemu sends an event, but the event is lost. Then libvirtd starts again, and doesn't realize the event is lost.
Yes, but that case is also true for any other QMP asynchronous event, and therefore should be handled generically i suppose (QMP channel data should be maintained across libvirtd shutdown). Luiz?
Maintaining QMP channel data doesn't solve this problem, because all sorts of race conditions are still possible. For example, libvirt could crash after having received the event but before handling it.
The most reliable way we found to solve this problem, and that's what we do for other events, is to allow libvirt to query the information the event is reporting. An event is nothing more than a state change in QEMU, and QEMU state is persistent during the life time of the VM, so we allow libvirt to query the state of anything that may send an event.
In fact, this is a general rule: when libvirt tracks an event, it also needs a way to poll for the information in the event.

On Fri, May 23, 2014 at 03:35:19PM +0200, Markus Armbruster wrote:
Luiz Capitulino <lcapitulino@redhat.com> writes:
On Fri, 23 May 2014 00:50:38 -0300 Marcelo Tosatti <mtosatti@redhat.com> wrote:
Then the guest triggers an RTC update, so qemu sends an event, but the event is lost. Then libvirtd starts again, and doesn't realize the event is lost.
Yes, but that case is also true for any other QMP asynchronous event, and therefore should be handled generically i suppose (QMP channel data should be maintained across libvirtd shutdown). Luiz?
Maintaining QMP channel data doesn't solve this problem, because all sorts of race conditions are still possible. For example, libvirt could crash after having received the event but before handling it.
The most reliable way we found to solve this problem, and that's what we do for other events, is to allow libvirt to query the information the event is reporting. An event is nothing more than a state change in QEMU, and QEMU state is persistent during the life time of the VM, so we allow libvirt to query the state of anything that may send an event.
In fact, this is a general rule: when libvirt tracks an event, it also needs a way to poll for the information in the event.
I see. This also seems pretty harmful wrt losing events: /* Global, one-time initializer to configure the rate limiting * and initialize state */ static void monitor_protocol_event_init(void) { /* Limit RTC & BALLOON events to 1 per second */ monitor_protocol_event_throttle(QEVENT_RTC_CHANGE, 1000); Better remove it.

On Fri, May 23, 2014 at 10:48:18AM -0300, Marcelo Tosatti wrote:
On Fri, May 23, 2014 at 03:35:19PM +0200, Markus Armbruster wrote:
Luiz Capitulino <lcapitulino@redhat.com> writes:
On Fri, 23 May 2014 00:50:38 -0300 Marcelo Tosatti <mtosatti@redhat.com> wrote:
Then the guest triggers an RTC update, so qemu sends an event, but the event is lost. Then libvirtd starts again, and doesn't realize the event is lost.
Yes, but that case is also true for any other QMP asynchronous event, and therefore should be handled generically i suppose (QMP channel data should be maintained across libvirtd shutdown). Luiz?
Maintaining QMP channel data doesn't solve this problem, because all sorts of race conditions are still possible. For example, libvirt could crash after having received the event but before handling it.
The most reliable way we found to solve this problem, and that's what we do for other events, is to allow libvirt to query the information the event is reporting. An event is nothing more than a state change in QEMU, and QEMU state is persistent during the life time of the VM, so we allow libvirt to query the state of anything that may send an event.
In fact, this is a general rule: when libvirt tracks an event, it also needs a way to poll for the information in the event.
I see.
This also seems pretty harmful wrt losing events:
/* Global, one-time initializer to configure the rate limiting * and initialize state */ static void monitor_protocol_event_init(void) { /* Limit RTC & BALLOON events to 1 per second */ monitor_protocol_event_throttle(QEVENT_RTC_CHANGE, 1000);
Better remove it.
Well, libvirt should disable throttling for events it cares about notifications not being lost. Is it doing so?

On Fri, 23 May 2014 10:48:18 -0300 Marcelo Tosatti <mtosatti@redhat.com> wrote:
On Fri, May 23, 2014 at 03:35:19PM +0200, Markus Armbruster wrote:
Luiz Capitulino <lcapitulino@redhat.com> writes:
On Fri, 23 May 2014 00:50:38 -0300 Marcelo Tosatti <mtosatti@redhat.com> wrote:
Then the guest triggers an RTC update, so qemu sends an event, but the event is lost. Then libvirtd starts again, and doesn't realize the event is lost.
Yes, but that case is also true for any other QMP asynchronous event, and therefore should be handled generically i suppose (QMP channel data should be maintained across libvirtd shutdown). Luiz?
Maintaining QMP channel data doesn't solve this problem, because all sorts of race conditions are still possible. For example, libvirt could crash after having received the event but before handling it.
The most reliable way we found to solve this problem, and that's what we do for other events, is to allow libvirt to query the information the event is reporting. An event is nothing more than a state change in QEMU, and QEMU state is persistent during the life time of the VM, so we allow libvirt to query the state of anything that may send an event.
In fact, this is a general rule: when libvirt tracks an event, it also needs a way to poll for the information in the event.
I see.
This also seems pretty harmful wrt losing events:
/* Global, one-time initializer to configure the rate limiting * and initialize state */ static void monitor_protocol_event_init(void) { /* Limit RTC & BALLOON events to 1 per second */ monitor_protocol_event_throttle(QEVENT_RTC_CHANGE, 1000);
Better remove it.
You mean, this is causing problems to the RTC_CHANGE event or you found a general problem? Can you give more details?

On Fri, May 23, 2014 at 10:48:18AM -0300, Marcelo Tosatti wrote:
On Fri, May 23, 2014 at 03:35:19PM +0200, Markus Armbruster wrote:
Luiz Capitulino <lcapitulino@redhat.com> writes:
On Fri, 23 May 2014 00:50:38 -0300 Marcelo Tosatti <mtosatti@redhat.com> wrote:
Then the guest triggers an RTC update, so qemu sends an event, but the event is lost. Then libvirtd starts again, and doesn't realize the event is lost.
Yes, but that case is also true for any other QMP asynchronous event, and therefore should be handled generically i suppose (QMP channel data should be maintained across libvirtd shutdown). Luiz?
Maintaining QMP channel data doesn't solve this problem, because all sorts of race conditions are still possible. For example, libvirt could crash after having received the event but before handling it.
The most reliable way we found to solve this problem, and that's what we do for other events, is to allow libvirt to query the information the event is reporting. An event is nothing more than a state change in QEMU, and QEMU state is persistent during the life time of the VM, so we allow libvirt to query the state of anything that may send an event.
In fact, this is a general rule: when libvirt tracks an event, it also needs a way to poll for the information in the event.
I see.
This also seems pretty harmful wrt losing events:
/* Global, one-time initializer to configure the rate limiting * and initialize state */ static void monitor_protocol_event_init(void) { /* Limit RTC & BALLOON events to 1 per second */ monitor_protocol_event_throttle(QEVENT_RTC_CHANGE, 1000);
Better remove it.
That is intentionally designed such that it doesn't cause any real problems actually - the monitor rate limiting code will only drop intermediate events - it is guaranteed you'll get the most recent event after the rate limiting period elapse. eg if the guest OS emits 6 events in the space on 1 second: RTC_CHANGE 353 RTC_CHANGE 1338 RTC_CHANGE 3542 RTC_CHANGE 255 RTC_CHANGE 522 RTC_CHANGE 320 then, the monitor rate limiting may discard all except the very last event. ie libvirt will see RTC_CHANGE == 320. The fact that it didn't see the previous events is no problem, because they're obsoleted by the new event. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 05/23/2014 07:48 AM, Marcelo Tosatti wrote:
This also seems pretty harmful wrt losing events:
/* Global, one-time initializer to configure the rate limiting * and initialize state */ static void monitor_protocol_event_init(void) { /* Limit RTC & BALLOON events to 1 per second */ monitor_protocol_event_throttle(QEVENT_RTC_CHANGE, 1000);
Better remove it.
No, this throttling MUST be present to avoid a guest-triggered denial-of-service attack (otherwise the guest could trigger RTC change events fast enough to starve the host in dealing with them). Again, lost events are expected. What is important is that when throttling, and event is guaranteed to be sent at the end of the throttling period so that libvirt will always eventually get an event with the latest state (even if intermediate events were lost) with no more latency than the throttling period; and either the event caries the current state (and not something that needs accumulation), or the event is a witness that libvirt needs to do an additional query- command to learn the current state. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

Il 23/05/2014 15:35, Markus Armbruster ha scritto:
Luiz Capitulino <lcapitulino@redhat.com> writes:
On Fri, 23 May 2014 00:50:38 -0300 Marcelo Tosatti <mtosatti@redhat.com> wrote:
Then the guest triggers an RTC update, so qemu sends an event, but the event is lost. Then libvirtd starts again, and doesn't realize the event is lost.
Yes, but that case is also true for any other QMP asynchronous event, and therefore should be handled generically i suppose (QMP channel data should be maintained across libvirtd shutdown). Luiz?
Maintaining QMP channel data doesn't solve this problem, because all sorts of race conditions are still possible. For example, libvirt could crash after having received the event but before handling it.
The most reliable way we found to solve this problem, and that's what we do for other events, is to allow libvirt to query the information the event is reporting. An event is nothing more than a state change in QEMU, and QEMU state is persistent during the life time of the VM, so we allow libvirt to query the state of anything that may send an event.
In fact, this is a general rule: when libvirt tracks an event, it also needs a way to poll for the information in the event.
It can be polled even right now. It's not pretty, but it's doable. You can get the current time via the qom-get command, and then follow the same algorithm as QEMU: time_t seconds; if (rtc_date_offset == -1) { if (rtc_utc) { seconds = mktimegm(tm); } else { struct tm tmp = *tm; tmp.tm_isdst = -1; /* use timezone to figure it out */ seconds = mktime(&tmp); } } else { seconds = mktimegm(tm) + rtc_date_offset; } return seconds - time(NULL); Unfortunately the QOM path to the RTC device is not stable. We can add a /machine/rtc link, and if the PPC guys implement the link and current-time property as well, the same mechanism can work for any board. Paolo

On Fri, May 23, 2014 at 11:36:56PM +0200, Paolo Bonzini wrote:
You can get the current time via the qom-get command, and then follow the same algorithm as QEMU:
time_t seconds;
if (rtc_date_offset == -1) { if (rtc_utc) { seconds = mktimegm(tm); } else { struct tm tmp = *tm; tmp.tm_isdst = -1; /* use timezone to figure it out */ seconds = mktime(&tmp); } } else { seconds = mktimegm(tm) + rtc_date_offset; } return seconds - time(NULL);
Unfortunately the QOM path to the RTC device is not stable. We can add a /machine/rtc link, and if the PPC guys implement the link and current-time property as well, the same mechanism can work for any board.
Paolo
I like that idea. Questions: - What guarantees are there that the interface is stable? (you can argue that "now it is"). - Perhaps add /machine/stable/ path to contain stable links ? - How do i go about adding a link to a device again?

On 05/22/2014 05:07 AM, Laine Stump wrote: [I've re-read this thread, trying to determine whether this patch is good enough to apply as-is]
commit e31b5cf393857 attempted to fix libvirt's VIR_DOMAIN_EVENT_ID_RTC_CHANGE, which is documentated to always provide the new offset of the domain's real time clock from UTC. The problem was that, in the case that qemu is provided with an "-rtc base=x" where x is an absolute time (rather than "utc" or "localtime"), the offset sent by qemu's RTC_CHANGE event is *not* the new offset from UTC, but rather is the sum of all changes to the domain's RTC since it was started with base=x.
So, despite what was said in commit e31b5cf393857, if we assume that the original value stored in "adjustment" was the offset from UTC at the time the domain was started, we can always determine the current offset from UTC by simply adding the most recent (i.e. current) offset from qemu to that original adjustment.
I agree that 'adjustment' is a runtime-only value - it only exists as long as a qemu guest is running; I also agree that the offset from UTC requires knowing both 'adjustment0' (the command-line offset) and 'adjustment' (the current qemu state, whether learned by RTC Change event, or by a new [yet-to-be-written] QMP query command).
This patch accomplishes that by storing the initial adjustment in the domain's status as "adjustment0". Each time a new RTC_CHANGE event is received from qemu, we simply add adjustment0 to the value sent by qemu, store that as the new adjustment, and forward that value on to any event handler.
Okay, I'm convinced that this guarantees that the right value is exposed in the event handler, so this patch is a strict improvement. I still think we have one (or more) libvirt bugs (basically, 'adjustment0' is the persistent domain's definition of what the next boot of the guest will start with, and unless the user manually changes that value, I think libvirt should automatically be adjusting the persistent 'adjustment0' to the final value of the run-time when the guest shuts down; I also think VDSM needs a way to query the UTC offset that was last used by a transient domain even after the domain has shut down) - but those can (and should) be separate patches.
This patch (*not* e31b5cf393857, which should be reverted prior to applying this patch) fixes:
https://bugzilla.redhat.com/show_bug.cgi?id=964177
(for the case where basis='utc'. It does not fix basis='localtime') ---
Changes from V1: remove all attempts to fix basis='localtime' in favor of fixing it in a simpler and better manner in a separate patch.
src/conf/domain_conf.c | 21 +++++++++++++++++---- src/conf/domain_conf.h | 7 +++++++ src/qemu/qemu_command.c | 9 +++++++++ src/qemu/qemu_process.c | 27 ++++++++++++++++++++++----- 4 files changed, 55 insertions(+), 9 deletions(-)
In addition to the commit typo found earlier,
+ /* domain start-time adjustment. This is a + * private/internal read-only value that only exists when + * a domain is running, and only if the clock + * offset='variable'
s/clock/clock uses/
+++ b/src/qemu/qemu_process.c @@ -831,7 +831,6 @@ qemuProcessHandleResume(qemuMonitorPtr mon ATTRIBUTE_UNUSED, return 0; }
- static int
Spurious line deletion. ACK. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 05/23/2014 01:36 PM, Eric Blake wrote:
This patch accomplishes that by storing the initial adjustment in the domain's status as "adjustment0". Each time a new RTC_CHANGE event is received from qemu, we simply add adjustment0 to the value sent by qemu, store that as the new adjustment, and forward that value on to any event handler.
Okay, I'm convinced that this guarantees that the right value is exposed in the event handler, so this patch is a strict improvement.
ACK.
ACK still holds on the grounds of strict improvement, even if we end up changing things further; although given my naming suggestions below you may still want to do a v3. More thoughts on the matter: We NEED to store adjustment0 in XML: both persistent xml (to control what the next boot uses) and in the runtime xml (so that when converting RTC change events from qemu into libvirt events, we have the right conversion). But if you look, we ALREADY store adjustment0 in the xml: <clock offset='variable' adjustment='10962' basis='utc'/> The adjustment attribute IS adjustment0, when basis is utc (and patch 4/4 makes it so that any other basis is immediately converted to utc at the first time we start qemu). Meanwhile, I argued that 'adjustment' is a live-only attribute. You could argue that if qemu gave us a way to learn the current adjustment via a query command, rather than having to listen for the most recent RTC change event, we would not even need to store 'adjustment' in the live XML (the only two times we would use 'adjustment' are when converting an RTC event [if we got the event, we don't need anything from storage; if we missed the event, no one will ever know] and when writing the final state of adjustment into the persistent adjustment0 when the live domain shuts down [but we'd use the new qemu query command for that]). On the other hand, if qemu doesn't have the new query command, then storing 'adjustment' in the live xml proves to be a nice fallback so that guest shutdown still lets us write the correct value to the persistent xml in all cases where we didn't miss an event. Furthermore, I think it should be user-visible rather than hidden, so that VDSM can learn the current adjustment in use by the guest (whether or not we also solve the problem of letting VDSM do post-mortem queries after a transient domain shuts down). So I'm wondering if: <clock offset='variable' adjustment='109062' delta='3600' basis='utc'/> makes sense for live xml - that is, the XML 'adjustment' attribute matches what you called 'adjustment0' and appears in both live and persistent, and the XML 'delta' attribute matches what you called 'adjustment' and which exists only for a running domain (the above domain was started with a command-line offset of 109062, and the current RTC offset in the guest is 3600 whether it was done via one or a series of RTC change events; the next time VDSM creates a new transient guest for the same resources, it should do so with adjustment='112662'). -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

For a clock element as above, libvirt simply converts current system time with localtime_r(), then starts qemu with a time string that doesn't contain any timezone information. So, from qemu's point of view, the -rtc string it gets for: <clock offset='variable' basis='utc' adjustment='10800'/> is identical to the -rtc string it gets for: <clock offset='variable' basis='localtime' adjustment='0'/> (assuming the host is in a timezone that is 10800 seconds ahead of UTC, as is the case on the machine where this message is being written). Since the commandlines are identical, qemu will behave identically after this point in either case. There are two problems in the case of basis='localtime' though: Problem 1) If the guest modifies its RTC, for example to add 20 seconds, the RTC_CHANGE event from qemu will then contain offset:20 in both cases. But libvirt will have saved the original adjustment into adjustment0, and will add that value onto the offset in the event. This means that in the case of basis=;utc', it will properly emit an event with offset:10820, but in the case of basis='localtime' the event will contain offset:20, which is *not* the new offset of the RTC from UTC (as the event it documented to provide). Problem 2) If the guest is migrated to another host that is in a different timezone, or if it is migrated or saved/restored after the DST status has changed from what it was when the guest was originally started, the newly restarted guest will have a different RTC (since it will be based on the new localtime, which could have shifted by several hours). The solution to both of these problems is simple - rather than maintaining the original adjustment value along with "basis='localtime'" in the domain status, when the domain is started we convert the adjustment offset to one relative to UTC, and set the status to "basis='utc'". Thus, whatever the RTC offset was from UTC when it was initially started, that offset will be maintained when migrating across timezones and DST settings, and the RTC_CHANGE events will automatically contain the proper offset (which should by definition always be relative to UTC). This fixes a problem that was implied but not openly stated in: https://bugzilla.redhat.com/show_bug.cgi?id=964177 --- New in V2 src/qemu/qemu_command.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 4998bca..28885f2 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -5976,19 +5976,30 @@ qemuBuildClockArgStr(virDomainClockDefPtr def) time_t now = time(NULL); struct tm nowbits; - switch ((enum virDomainClockBasis) def->data.variable.basis) { - case VIR_DOMAIN_CLOCK_BASIS_UTC: - now += def->data.variable.adjustment; - gmtime_r(&now, &nowbits); - break; - case VIR_DOMAIN_CLOCK_BASIS_LOCALTIME: - now += def->data.variable.adjustment; - localtime_r(&now, &nowbits); - break; - case VIR_DOMAIN_CLOCK_BASIS_LAST: - break; + if (def->data.variable.basis == VIR_DOMAIN_CLOCK_BASIS_LOCALTIME) { + time_t localOffset; + + /* in the case of basis='localtime', rather than trying to + * keep that basis (and associated offset from UTC) in the + * status and deal with adding in the difference each time + * there is an RTC_CHANGE event, it is simpler and less + * error prone to just convert the adjustment an offset + * from UTC right now (and change the status to + * "basis='utc' to reflect this). This eliminates + * potential errors in both RTC_CHANGE events and in + * migration (in the case that the status of DST, or the + * timezone of the destination host, changed relative to + * startup). + */ + if (virTimeLocalOffsetFromUTC(&localOffset) < 0) + goto error; + def->data.variable.adjustment += localOffset; + def->data.variable.basis = VIR_DOMAIN_CLOCK_BASIS_UTC; } + now += def->data.variable.adjustment; + gmtime_r(&now, &nowbits); + /* when an RTC_CHANGE event is received from qemu, we need to * have the adjustment used at domain start time available to * compute the new offset from UTC. As this new value is -- 1.9.0

On 05/22/2014 05:07 AM, Laine Stump wrote:
For a clock element as above, libvirt simply converts current system time with localtime_r(), then starts qemu with a time string that doesn't contain any timezone information. So, from qemu's point of view, the -rtc string it gets for:
<clock offset='variable' basis='utc' adjustment='10800'/>
is identical to the -rtc string it gets for:
<clock offset='variable' basis='localtime' adjustment='0'/>
(assuming the host is in a timezone that is 10800 seconds ahead of UTC, as is the case on the machine where this message is being written).
Since the commandlines are identical, qemu will behave identically after this point in either case.
So basically, we are arguing that basis='localtime' should be treated as syntactic sugar which we rewrite to a more convenient form at the time we start the guest, and not as a permanent basis that we attempt to migrate. I can live with that.
There are two problems in the case of basis='localtime' though:
Problem 1) If the guest modifies its RTC, for example to add 20 seconds, the RTC_CHANGE event from qemu will then contain offset:20 in both cases. But libvirt will have saved the original adjustment into adjustment0, and will add that value onto the offset in the event. This means that in the case of basis=;utc', it will properly emit an event with offset:10820, but in the case of basis='localtime' the event will contain offset:20, which is *not* the new offset of the RTC from UTC (as the event it documented to provide).
Problem 2) If the guest is migrated to another host that is in a different timezone, or if it is migrated or saved/restored after the DST status has changed from what it was when the guest was originally started, the newly restarted guest will have a different RTC (since it will be based on the new localtime, which could have shifted by several hours).
The solution to both of these problems is simple - rather than maintaining the original adjustment value along with "basis='localtime'" in the domain status, when the domain is started we convert the adjustment offset to one relative to UTC, and set the status to "basis='utc'". Thus, whatever the RTC offset was from UTC when it was initially started, that offset will be maintained when migrating across timezones and DST settings, and the RTC_CHANGE events will automatically contain the proper offset (which should by definition always be relative to UTC).
Okay, so this patch is cementing some of the arguments I made in response to 3/4 (before I had read this patch).
This fixes a problem that was implied but not openly stated in:
https://bugzilla.redhat.com/show_bug.cgi?id=964177 --- New in V2
src/qemu/qemu_command.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-)
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 4998bca..28885f2 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -5976,19 +5976,30 @@ qemuBuildClockArgStr(virDomainClockDefPtr def) time_t now = time(NULL); struct tm nowbits;
- switch ((enum virDomainClockBasis) def->data.variable.basis) { - case VIR_DOMAIN_CLOCK_BASIS_UTC: - now += def->data.variable.adjustment; - gmtime_r(&now, &nowbits); - break; - case VIR_DOMAIN_CLOCK_BASIS_LOCALTIME: - now += def->data.variable.adjustment; - localtime_r(&now, &nowbits); - break; - case VIR_DOMAIN_CLOCK_BASIS_LAST: - break; + if (def->data.variable.basis == VIR_DOMAIN_CLOCK_BASIS_LOCALTIME) { + time_t localOffset; + + /* in the case of basis='localtime', rather than trying to + * keep that basis (and associated offset from UTC) in the + * status and deal with adding in the difference each time + * there is an RTC_CHANGE event, it is simpler and less + * error prone to just convert the adjustment an offset
s/an/to an/
+ * from UTC right now (and change the status to + * "basis='utc' to reflect this). This eliminates + * potential errors in both RTC_CHANGE events and in + * migration (in the case that the status of DST, or the + * timezone of the destination host, changed relative to + * startup).
including migration to file, across a single host that changes localtime RTC across daylight savings between when the file was saved and when it gets loaded :)
+ */ + if (virTimeLocalOffsetFromUTC(&localOffset) < 0) + goto error; + def->data.variable.adjustment += localOffset; + def->data.variable.basis = VIR_DOMAIN_CLOCK_BASIS_UTC; }
+ now += def->data.variable.adjustment; + gmtime_r(&now, &nowbits);
Seems to make sense. But I'd like feedback from qemu on 3/4 before giving outright ack to this patch.
+ /* when an RTC_CHANGE event is received from qemu, we need to * have the adjustment used at domain start time available to * compute the new offset from UTC. As this new value is
-- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
participants (9)
-
Daniel P. Berrange
-
Eric Blake
-
Laine Stump
-
Luiz Capitulino
-
Marcelo Tosatti
-
Markus Armbruster
-
Paolo Bonzini
-
Roman Bogorodskiy
-
Wangrui (K)