[libvirt] [PATCH 3/5] add XML parsing for vm status file

Functions to read and write vm status in $(statedir)/qemu/<domain>.xml. Keeps the necessary to reconnect to a running domain state within <domstate>...</domstate>. I kept most of the code in qemu_config.c for now. We can easily move it should another hypervisor need to do something similar. --- src/domain_conf.c | 36 ++++++--- src/domain_conf.h | 6 ++ src/libvirt_sym.version.in | 2 + src/qemu_conf.c | 191 ++++++++++++++++++++++++++++++++++++++++++++ src/qemu_conf.h | 17 ++++ 5 files changed, 241 insertions(+), 11 deletions(-) diff --git a/src/domain_conf.c b/src/domain_conf.c index 32ed59f..1b53c09 100644 --- a/src/domain_conf.c +++ b/src/domain_conf.c @@ -430,6 +430,7 @@ void virDomainObjFree(virDomainObjPtr dom) virDomainDefFree(dom->def); virDomainDefFree(dom->newDef); + VIR_FREE(dom->monitorpath); VIR_FREE(dom->vcpupids); VIR_FREE(dom); @@ -3224,11 +3225,11 @@ char *virDomainDefFormat(virConnectPtr conn, #ifndef PROXY -int virDomainSaveConfig(virConnectPtr conn, - const char *configDir, - virDomainDefPtr def) +int virDomainSaveXML(virConnectPtr conn, + const char *configDir, + virDomainDefPtr def, + const char *xml) { - char *xml; char *configFile = NULL; int fd = -1, ret = -1; size_t towrite; @@ -3237,11 +3238,6 @@ int virDomainSaveConfig(virConnectPtr conn, if ((configFile = virDomainConfigFile(conn, configDir, def->name)) == NULL) goto cleanup; - if (!(xml = virDomainDefFormat(conn, - def, - VIR_DOMAIN_XML_SECURE))) - goto cleanup; - if ((err = virFileMakePath(configDir))) { virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot create config directory %s: %s"), @@ -3274,12 +3270,30 @@ int virDomainSaveConfig(virConnectPtr conn, } ret = 0; - cleanup: - VIR_FREE(xml); if (fd != -1) close(fd); + return ret; +} + +int virDomainSaveConfig(virConnectPtr conn, + const char *configDir, + virDomainDefPtr def) +{ + int ret = -1; + char *xml; + if (!(xml = virDomainDefFormat(conn, + def, + VIR_DOMAIN_XML_SECURE))) + goto cleanup; + + if (virDomainSaveXML(conn, configDir, def, xml)) + goto cleanup; + + ret = 0; +cleanup: + VIR_FREE(xml); return ret; } diff --git a/src/domain_conf.h b/src/domain_conf.h index 51cf6d5..d44e2ad 100644 --- a/src/domain_conf.h +++ b/src/domain_conf.h @@ -462,6 +462,7 @@ struct _virDomainObj { int stderr_fd; int stderr_watch; int monitor; + char *monitorpath; int monitorWatch; int logfile; int pid; @@ -553,6 +554,11 @@ int virDomainDiskQSort(const void *a, const void *b); int virDomainDiskCompare(virDomainDiskDefPtr a, virDomainDiskDefPtr b); +int virDomainSaveXML(virConnectPtr conn, + const char *configDir, + virDomainDefPtr def, + const char *xml); + int virDomainSaveConfig(virConnectPtr conn, const char *configDir, virDomainDefPtr def); diff --git a/src/libvirt_sym.version.in b/src/libvirt_sym.version.in index 1eff790..e7b352d 100644 --- a/src/libvirt_sym.version.in +++ b/src/libvirt_sym.version.in @@ -368,6 +368,7 @@ LIBVIRT_PRIVATE_@VERSION@ { virDomainObjFree; virDomainObjListFree; virDomainRemoveInactive; + virDomainSaveXML; virDomainSaveConfig; virDomainSoundDefFree; virDomainSoundModelTypeFromString; @@ -614,6 +615,7 @@ LIBVIRT_PRIVATE_@VERSION@ { /* xml.h */ virXPathLong; + virXPathNode; virXPathNodeSet; virXPathString; virXMLPropString; diff --git a/src/qemu_conf.c b/src/qemu_conf.c index 218aefa..4cede63 100644 --- a/src/qemu_conf.c +++ b/src/qemu_conf.c @@ -49,6 +49,8 @@ #include "util.h" #include "memory.h" #include "verify.h" +#include "datatypes.h" +#include "xml.h" VIR_ENUM_DECL(virDomainDiskQEMUBus) VIR_ENUM_IMPL(virDomainDiskQEMUBus, VIR_DOMAIN_DISK_BUS_LAST, @@ -1335,3 +1337,192 @@ int qemudBuildCommandLine(virConnectPtr conn, #undef ADD_ENV_LIT #undef ADD_ENV_SPACE } + + +/* Called from SAX on parsing errors in the XML. */ +static void +catchXMLError (void *ctx, const char *msg ATTRIBUTE_UNUSED, ...) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) ctx; + + if (ctxt) { + virConnectPtr conn = ctxt->_private; + + if (ctxt->lastError.level == XML_ERR_FATAL && + ctxt->lastError.message != NULL) { + qemudReportError (conn, NULL, NULL, VIR_ERR_XML_DETAIL, + _("at line %d: %s"), + ctxt->lastError.line, + ctxt->lastError.message); + } + } +} + + +/** + * qemudDomainStatusParseFile + * + * read the last known status of a domain + * + * Returns 0 on success + */ +qemudDomainStatusPtr +qemudDomainStatusParseFile(virConnectPtr conn, + virCapsPtr caps, + const char *filename, int flags) +{ + xmlParserCtxtPtr pctxt = NULL; + xmlXPathContextPtr ctxt = NULL; + xmlDocPtr xml = NULL; + xmlNodePtr root, config_root; + virDomainDefPtr def = NULL; + char *tmp = NULL; + long val; + qemudDomainStatusPtr status = NULL; + + if (VIR_ALLOC(status) < 0) { + qemudReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, + "%s", _("failed to allocate space for vm status")); + goto error; + } + + /* Set up a parser context so we can catch the details of XML errors. */ + pctxt = xmlNewParserCtxt (); + if (!pctxt || !pctxt->sax) + goto error; + pctxt->sax->error = catchXMLError; + pctxt->_private = conn; + + if (conn) virResetError (&conn->err); + xml = xmlCtxtReadFile (pctxt, filename, NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (!xml) { + if (conn && conn->err.code == VIR_ERR_NONE) + qemudReportError(conn, NULL, NULL, VIR_ERR_XML_ERROR, + "%s", _("failed to parse xml document")); + goto error; + } + + if ((root = xmlDocGetRootElement(xml)) == NULL) { + qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("missing root element")); + goto error; + } + + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + qemudReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + goto error; + } + + if (!xmlStrEqual(root->name, BAD_CAST "domstatus")) { + qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("incorrect root element")); + goto error; + } + + ctxt->node = root; + if((virXPathLong(conn, "string(./@state)", ctxt, &val)) < 0) { + qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("invalid domain state")); + goto error; + } else + status->state = (int)val; + + if((virXPathLong(conn, "string(./@pid)", ctxt, &val)) < 0) { + qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("invalid pid")); + goto error; + } else + status->pid = (pid_t)val; + + if(!(tmp = virXPathString(conn, "string(./monitor[1]/@path)", ctxt))) { + qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("no monitor path")); + goto error; + } else + status->monitorpath = tmp; + + if(!(config_root = virXPathNode(conn, "./domain", ctxt))) { + qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("no domain config")); + goto error; + } + if(!(def = virDomainDefParseNode(conn, caps, xml, config_root, flags))) + goto error; + else + status->def = def; + +cleanup: + xmlFreeParserCtxt (pctxt); + xmlXPathFreeContext(ctxt); + xmlFreeDoc (xml); + return status; + +error: + VIR_FREE(tmp); + VIR_FREE(status); + goto cleanup; +} + + +/** + * qemudDomainStatusFormat + * + * Get the state of a running domain as XML + * + * Returns xml on success + */ +static char* +qemudDomainStatusFormat(virConnectPtr conn, + virDomainObjPtr vm) +{ + char *config_xml = NULL, *xml = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + + virBufferVSprintf(&buf, "<domstatus state='%d' pid='%d'>\n", vm->state, vm->pid); + virBufferVSprintf(&buf, " <monitor path='%s'/>\n", vm->monitorpath); + + if (!(config_xml = virDomainDefFormat(conn, + vm->def, + VIR_DOMAIN_XML_SECURE))) + goto cleanup; + + virBufferVSprintf(&buf, "%s", config_xml); + virBufferVSprintf(&buf, "</domstatus>\n"); + + xml = virBufferContentAndReset(&buf); +cleanup: + VIR_FREE(config_xml); + return xml; +} + + +/** + * qemudSaveDomainStatus + * + * Save the current status of a running domain + * + * Returns 0 on success + */ +int +qemudSaveDomainStatus(virConnectPtr conn, + struct qemud_driver *driver, + virDomainObjPtr vm) +{ + int ret = -1; + char *xml = NULL; + + if (!(xml = qemudDomainStatusFormat(conn, vm))) + goto cleanup; + + if ((ret = virDomainSaveXML(conn, driver->stateDir, vm->def, xml))) + goto cleanup; + + ret = 0; +cleanup: + VIR_FREE(xml); + return ret; +} + diff --git a/src/qemu_conf.h b/src/qemu_conf.h index ffbd0e7..70d9394 100644 --- a/src/qemu_conf.h +++ b/src/qemu_conf.h @@ -77,6 +77,16 @@ struct qemud_driver { int domainEventDispatching; }; +/* Status needed to reconenct to running VMs */ +typedef struct _qemudDomainStatus qemudDomainStatus; +typedef qemudDomainStatus *qemudDomainStatusPtr; +struct _qemudDomainStatus { + char *monitorpath; + pid_t pid; + int state; + virDomainDefPtr def; +}; + /* Port numbers used for KVM migration. */ #define QEMUD_MIGRATION_FIRST_PORT 49152 #define QEMUD_MIGRATION_NUM_PORTS 64 @@ -108,5 +118,12 @@ int qemudBuildCommandLine (virConnectPtr conn, const char *migrateFrom); const char *qemudVirtTypeToString (int type); +qemudDomainStatusPtr qemudDomainStatusParseFile(virConnectPtr conn, + virCapsPtr caps, + const char *filename, + int flags); +int qemudSaveDomainStatus(virConnectPtr conn, + struct qemud_driver *driver, + virDomainObjPtr vm); #endif /* __QEMUD_CONF_H */ -- 1.6.0.2

On Fri, Dec 12, 2008 at 07:26:51PM +0100, Guido Günther wrote:
Functions to read and write vm status in $(statedir)/qemu/<domain>.xml. Keeps the necessary to reconnect to a running domain state within <domstate>...</domstate>. I kept most of the code in qemu_config.c for now. We can easily move it should another hypervisor need to do something similar. [...] +static char* +qemudDomainStatusFormat(virConnectPtr conn, + virDomainObjPtr vm) +{ + char *config_xml = NULL, *xml = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + + virBufferVSprintf(&buf, "<domstatus state='%d' pid='%d'>\n", vm->state, vm->pid); + virBufferVSprintf(&buf, " <monitor path='%s'/>\n", vm->monitorpath);
I would use virBufferEscapeString here, even if by default the path should not contain characters needing escaping, I think it's safer.
+ if (!(config_xml = virDomainDefFormat(conn, + vm->def, + VIR_DOMAIN_XML_SECURE))) + goto cleanup; + + virBufferVSprintf(&buf, "%s", config_xml);
Same thing here.
+ virBufferVSprintf(&buf, "</domstatus>\n");
Overall the patch looks fine to me, +1 Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

On Mon, Dec 15, 2008 at 10:25:27AM +0100, Daniel Veillard wrote:
On Fri, Dec 12, 2008 at 07:26:51PM +0100, Guido Günther wrote:
Functions to read and write vm status in $(statedir)/qemu/<domain>.xml. Keeps the necessary to reconnect to a running domain state within <domstate>...</domstate>. I kept most of the code in qemu_config.c for now. We can easily move it should another hypervisor need to do something similar. [...] +static char* +qemudDomainStatusFormat(virConnectPtr conn, + virDomainObjPtr vm) +{ + char *config_xml = NULL, *xml = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + + virBufferVSprintf(&buf, "<domstatus state='%d' pid='%d'>\n", vm->state, vm->pid); + virBufferVSprintf(&buf, " <monitor path='%s'/>\n", vm->monitorpath);
I would use virBufferEscapeString here, even if by default the path should not contain characters needing escaping, I think it's safer. O.k. done.
+ if (!(config_xml = virDomainDefFormat(conn, + vm->def, + VIR_DOMAIN_XML_SECURE))) + goto cleanup; + + virBufferVSprintf(&buf, "%s", config_xml);
Same thing here. This one comes straight out of virDomainDefFormat so it should have everything escaped nicely so shouldn't virBufferVSprintf be safe here? O.k. to apply the attached version? Cheers, -- Guido

On Thu, Dec 18, 2008 at 10:34:03AM +0100, Guido G?nther wrote:
On Mon, Dec 15, 2008 at 10:25:27AM +0100, Daniel Veillard wrote:
On Fri, Dec 12, 2008 at 07:26:51PM +0100, Guido Günther wrote:
Functions to read and write vm status in $(statedir)/qemu/<domain>.xml. Keeps the necessary to reconnect to a running domain state within <domstate>...</domstate>. I kept most of the code in qemu_config.c for now. We can easily move it should another hypervisor need to do something similar. [...] +static char* +qemudDomainStatusFormat(virConnectPtr conn, + virDomainObjPtr vm) +{ + char *config_xml = NULL, *xml = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + + virBufferVSprintf(&buf, "<domstatus state='%d' pid='%d'>\n", vm->state, vm->pid); + virBufferVSprintf(&buf, " <monitor path='%s'/>\n", vm->monitorpath);
I would use virBufferEscapeString here, even if by default the path should not contain characters needing escaping, I think it's safer. O.k. done.
+ if (!(config_xml = virDomainDefFormat(conn, + vm->def, + VIR_DOMAIN_XML_SECURE))) + goto cleanup; + + virBufferVSprintf(&buf, "%s", config_xml);
Same thing here. This one comes straight out of virDomainDefFormat so it should have everything escaped nicely so shouldn't virBufferVSprintf be safe here?
No, not guarenteed to be safe because the 'config_xml' string could contain '%' sequences that'll be interpreted by sprintf. In any case why not just use virBufferAdd(&buf, config_xml) Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On Thu, Dec 18, 2008 at 10:23:39AM +0000, Daniel P. Berrange wrote:
No, not guarenteed to be safe because the 'config_xml' string could contain '%' sequences that'll be interpreted by sprintf. In any case why not just use
virBufferAdd(&buf, config_xml) Updated version using virBufferAdd attached. -- Guido

On Thu, Dec 18, 2008 at 03:53:17PM +0100, Guido Günther wrote:
On Thu, Dec 18, 2008 at 10:23:39AM +0000, Daniel P. Berrange wrote:
No, not guarenteed to be safe because the 'config_xml' string could contain '%' sequences that'll be interpreted by sprintf. In any case why not just use
virBufferAdd(&buf, config_xml) Updated version using virBufferAdd attached. Applied now after an Ack from Daniel P. on IRC. -- Guido

On Fri, Dec 12, 2008 at 07:26:51PM +0100, Guido G?nther wrote:
Functions to read and write vm status in $(statedir)/qemu/<domain>.xml. Keeps the necessary to reconnect to a running domain state within <domstate>...</domstate>. I kept most of the code in qemu_config.c for now. We can easily move it should another hypervisor need to do something similar.
ACK, This looks OK to me modulo DV's suggestion to escape the monitor path in XML Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|
participants (3)
-
Daniel P. Berrange
-
Daniel Veillard
-
Guido Günther