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

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/3 explains what qemu does, and how this patch fixes it. (NB: the flaw 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 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/3 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/3 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 (3): util: new function virTimeLocalOffsetFromUTC Revert "qemu: Report the offset from host UTC for RTC_CHANGE event" qemu: fix broken RTC_CHANGE event when clock offset='variable' src/conf/domain_conf.c | 38 +++++++++++++++++++------------------- src/conf/domain_conf.h | 9 ++++++--- src/libvirt_private.syms | 1 + src/qemu/qemu_command.c | 16 +++++++++++++--- src/qemu/qemu_process.c | 47 ++++++++++++++++++++++++++++++++--------------- src/util/virtime.c | 41 ++++++++++++++++++++++++++++++++++++++++- src/util/virtime.h | 5 +++-- 7 files changed, 114 insertions(+), 43 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. --- src/libvirt_private.syms | 1 + src/util/virtime.c | 41 ++++++++++++++++++++++++++++++++++++++++- src/util/virtime.h | 5 +++-- 3 files changed, 44 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 -- 1.9.0

On Wed, May 21, 2014 at 04:16:29PM +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. --- src/libvirt_private.syms | 1 + src/util/virtime.c | 41 ++++++++++++++++++++++++++++++++++++++++- src/util/virtime.h | 5 +++-- 3 files changed, 44 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; +}
I think we ought to be able to unit test this code to make sure it is doing what we want. ie setenv("TZ", "EDT") in the start of the test suite to force a predictable timezone, then check the delta is correct. 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/21/2014 07:26 AM, Daniel P. Berrange wrote:
On Wed, May 21, 2014 at 04:16:29PM +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).
I think we ought to be able to unit test this code to make sure it is doing what we want. ie setenv("TZ", "EDT") in the start of the test suite to force a predictable timezone, then check the delta is correct.
Even better than "EDT" (which is not POSIX-compliant), reuse what we have done in other situations where we want to guarantee a known non-zero timezone that uses just POSIX functionality: tests/qemuxml2argvtest.c: if (setenv("TZ", "VIR00:30", 1) < 0) { -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

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. --- 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

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. In the case of a clock that has offset='variable' basis='localtime', the original adjustment in the XML will not have accounted for UTC (i.e. it will be the offset of the domain's RTC from *local* time). So although the value we want to save back into adjustment is still the addition of "latest offset from qemu" + adjustment0, we still need to adjust that value before sending to an event handler; in order to convert the offset from localtime into offset from UTC, we need to add the difference between UTC and localtime. (NB: to eliminate confusion caused by a change in daylight savings status between when the domain was started and when the RTC is changed, the offset from UTC to localtime is retrieved only once when the domain is started, and saved in the domain's status as "localtimeAdjustment0"). This patch (*not* e31b5cf393857, which should be reverted prior to applying this patch) fixes: https://bugzilla.redhat.com/show_bug.cgi?id=964177 --- src/conf/domain_conf.c | 27 +++++++++++++++++++++++---- src/conf/domain_conf.h | 8 ++++++++ src/qemu/qemu_command.c | 13 +++++++++++++ src/qemu/qemu_process.c | 40 +++++++++++++++++++++++++++++++++++----- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index a7863c3..9c6c80c 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,12 @@ 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; + if (virXPathInt("number(./clock/@localtimeAdjustment0)", ctxt, + &def->clock.data.variable.localtimeAdjustment0) < 0) + def->clock.data.variable.localtimeAdjustment0 = 0; tmp = virXPathString("string(./clock/@basis)", ctxt); if (tmp) { if ((def->clock.data.variable.basis = virDomainClockBasisTypeFromString(tmp)) < 0) { @@ -17086,7 +17093,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 +17116,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 +17651,14 @@ 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); + if (def->clock.data.variable.localtimeAdjustment0) + virBufferAsprintf(buf, " localtimeAdjustment0='%d'", + def->clock.data.variable.localtimeAdjustment0); + } break; case VIR_DOMAIN_CLOCK_OFFSET_TIMEZONE: virBufferEscapeString(buf, " timezone='%s'", def->clock.data.timezone); @@ -18072,7 +18089,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 +18178,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..6e45915 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1727,6 +1727,14 @@ 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; + int localtimeAdjustment0; } variable; /* Timezone name, when diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 95f747f..ffec3ad 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" @@ -5974,6 +5975,15 @@ qemuBuildClockArgStr(virDomainClockDefPtr def) case VIR_DOMAIN_CLOCK_OFFSET_VARIABLE: { time_t now = time(NULL); struct tm nowbits; + time_t localOffset; + + /* 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; switch ((enum virDomainClockBasis) def->data.variable.basis) { case VIR_DOMAIN_CLOCK_BASIS_UTC: @@ -5983,6 +5993,9 @@ qemuBuildClockArgStr(virDomainClockDefPtr def) case VIR_DOMAIN_CLOCK_BASIS_LOCALTIME: now += def->data.variable.adjustment; localtime_r(&now, &nowbits); + if (virTimeLocalOffsetFromUTC(&localOffset) < 0) + goto error; + def->data.variable.localtimeAdjustment0 = localOffset; break; case VIR_DOMAIN_CLOCK_BASIS_LAST: break; diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 6dcb737..f618462 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,44 @@ 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 (vm->def->clock.data.variable.basis == VIR_DOMAIN_CLOCK_BASIS_LOCALTIME) { + /* in the case of basis='localtime', the adjustment stored + * in the domain status contains the adjustment to move + * the RTC to localtime, *not* UTC. But the offset sent in + * the RTCChange event is defined to always send the new + * offset from UTC, regardless of libvirt domain clock + * configuration. So we must add the additional offset + * between localtime and UTC to the offset sent in the + * event. + */ + offset += vm->def->clock.data.variable.localtimeAdjustment0; + } + + 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
participants (3)
-
Daniel P. Berrange
-
Eric Blake
-
Laine Stump