This patch changes the implementations lots of functions which parse XML documents, so that if the XML document is not well-formed then we get detailed error messages. The general form of the change is: static void catchXMLError (void *ctx, const char *msg ATTRIBUTE_UNUSED, ...) { // a callback which calls virDomainReportError } virDomainDefPtr virDomainDefParseString(virConnectPtr conn, virCapsPtr caps, const char *xmlStr) { xmlParserCtxtPtr pctxt; pctxt = xmlNewParserCtxt (); pctxt->sax->error = catchXMLError; xml = xmlCtxtReadDoc (pctxt, //...) etc. There are some unavoidable shortcomings: (1) There is no place to stash user pointers during the callback (the suggestively named pctxt->userData field is already used for something else), so we cannot pass the virConnectPtr to the error function. As a result, virterror will store the error in a global variable, and callers will probably not be able to access it. (2) The XML parser routinely produces multiple error messages, and virterror throws away all but the last one. The errors do, however, get printed to stderr. You can test this by using 'virsh define', 'virsh net-define', 'virsh pool-define', etc. on not-well-formed XML files. Rich. -- Richard Jones, Emerging Technologies, Red Hat http://et.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v
Index: src/domain_conf.c =================================================================== RCS file: /data/cvs/libvirt/src/domain_conf.c,v retrieving revision 1.10 diff -u -r1.10 domain_conf.c --- src/domain_conf.c 28 Jul 2008 14:06:54 -0000 1.10 +++ src/domain_conf.c 30 Jul 2008 14:38:51 -0000 @@ -1838,32 +1838,61 @@ return NULL; } +/* 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 && + ctxt->lastError.level == XML_ERR_FATAL && + ctxt->lastError.message != NULL) { + virDomainReportError (NULL, VIR_ERR_XML_DETAIL, + ctxt->lastError.message, ctxt->lastError.line); + } +} virDomainDefPtr virDomainDefParseString(virConnectPtr conn, virCapsPtr caps, const char *xmlStr) { - xmlDocPtr xml; + xmlParserCtxtPtr pctxt; + xmlDocPtr xml = NULL; xmlNodePtr root; virDomainDefPtr def = NULL; - if (!(xml = xmlReadDoc(BAD_CAST xmlStr, "domain.xml", NULL, - XML_PARSE_NOENT | XML_PARSE_NONET | - XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { - virDomainReportError(conn, VIR_ERR_XML_ERROR, NULL); - return NULL; + /* Set up a parser context so we can catch the details of XML errors. */ + pctxt = xmlNewParserCtxt (); + if (!pctxt || !pctxt->sax) + goto cleanup; + pctxt->sax->error = catchXMLError; + + /* It'd be nice to do this, but seems ->userData field is used + * by something else. + */ + /*pctxt->userData = conn;*/ + + xml = xmlCtxtReadDoc (pctxt, BAD_CAST xmlStr, "domain.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (!xml) { + /* virDomainReportError has already been called by the + * callback function (hopefully). + */ + goto cleanup; } if ((root = xmlDocGetRootElement(xml)) == NULL) { virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing root element")); - xmlFreeDoc(xml); - return NULL; + goto cleanup; } def = virDomainDefParseNode(conn, caps, xml, root); - xmlFreeDoc(xml); +cleanup: + xmlFreeParserCtxt (pctxt); + xmlFreeDoc (xml); return def; } @@ -1871,27 +1900,43 @@ virCapsPtr caps, const char *filename) { - xmlDocPtr xml; + xmlParserCtxtPtr pctxt; + xmlDocPtr xml = NULL; xmlNodePtr root; virDomainDefPtr def = NULL; - if (!(xml = xmlReadFile(filename, NULL, - XML_PARSE_NOENT | XML_PARSE_NONET | - XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { - virDomainReportError(conn, VIR_ERR_XML_ERROR, NULL); - return NULL; + /* Set up a parser context so we can catch the details of XML errors. */ + pctxt = xmlNewParserCtxt (); + if (!pctxt || !pctxt->sax) + goto cleanup; + pctxt->sax->error = catchXMLError; + + /* It'd be nice to do this, but seems ->userData field is used + * by something else. + */ + /*pctxt->userData = conn;*/ + + xml = xmlCtxtReadFile (pctxt, filename, NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (!xml) { + /* virDomainReportError has already been called by the + * callback function (hopefully). + */ + goto cleanup; } if ((root = xmlDocGetRootElement(xml)) == NULL) { virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing root element")); - xmlFreeDoc(xml); - return NULL; + goto cleanup; } def = virDomainDefParseNode(conn, caps, xml, root); - xmlFreeDoc(xml); +cleanup: + xmlFreeParserCtxt (pctxt); + xmlFreeDoc (xml); return def; } Index: src/network_conf.c =================================================================== RCS file: /data/cvs/libvirt/src/network_conf.c,v retrieving revision 1.3 diff -u -r1.3 network_conf.c --- src/network_conf.c 25 Jul 2008 14:27:25 -0000 1.3 +++ src/network_conf.c 30 Jul 2008 14:38:51 -0000 @@ -348,57 +348,103 @@ return NULL; } +/* 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 && + ctxt->lastError.level == XML_ERR_FATAL && + ctxt->lastError.message != NULL) { + virNetworkReportError (NULL, VIR_ERR_XML_DETAIL, + ctxt->lastError.message, ctxt->lastError.line); + } +} + virNetworkDefPtr virNetworkDefParseString(virConnectPtr conn, const char *xmlStr) { - xmlDocPtr xml; + xmlParserCtxtPtr pctxt; + xmlDocPtr xml = NULL; xmlNodePtr root; - virNetworkDefPtr def; + virNetworkDefPtr def = NULL; - if (!(xml = xmlReadDoc(BAD_CAST xmlStr, "network.xml", NULL, - XML_PARSE_NOENT | XML_PARSE_NONET | - XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { - virNetworkReportError(conn, VIR_ERR_XML_ERROR, NULL); - return NULL; + /* Set up a parser context so we can catch the details of XML errors. */ + pctxt = xmlNewParserCtxt (); + if (!pctxt || !pctxt->sax) + goto cleanup; + pctxt->sax->error = catchXMLError; + + /* It'd be nice to do this, but seems ->userData field is used + * by something else. + */ + /*pctxt->userData = conn;*/ + + xml = xmlCtxtReadDoc (pctxt, BAD_CAST xmlStr, "network.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (!xml) { + /* virNetworkReportError has already been called by the + * callback function (hopefully). + */ + goto cleanup; } if ((root = xmlDocGetRootElement(xml)) == NULL) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing root element")); - xmlFreeDoc(xml); - return NULL; + goto cleanup; } def = virNetworkDefParseNode(conn, xml, root); - xmlFreeDoc(xml); +cleanup: + xmlFreeParserCtxt (pctxt); + xmlFreeDoc (xml); return def; } virNetworkDefPtr virNetworkDefParseFile(virConnectPtr conn, const char *filename) { - xmlDocPtr xml; + xmlParserCtxtPtr pctxt; + xmlDocPtr xml = NULL; xmlNodePtr root; - virNetworkDefPtr def; + virNetworkDefPtr def = NULL; - if (!(xml = xmlReadFile(filename, NULL, - XML_PARSE_NOENT | XML_PARSE_NONET | - XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { - virNetworkReportError(conn, VIR_ERR_XML_ERROR, NULL); - return NULL; + /* Set up a parser context so we can catch the details of XML errors. */ + pctxt = xmlNewParserCtxt (); + if (!pctxt || !pctxt->sax) + goto cleanup; + pctxt->sax->error = catchXMLError; + + /* It'd be nice to do this, but seems ->userData field is used + * by something else. + */ + /*pctxt->userData = conn;*/ + + xml = xmlCtxtReadFile (pctxt, filename, NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (!xml) { + /* virNetworkReportError has already been called by the + * callback function (hopefully). + */ + goto cleanup; } if ((root = xmlDocGetRootElement(xml)) == NULL) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing root element")); - xmlFreeDoc(xml); - return NULL; + goto cleanup; } def = virNetworkDefParseNode(conn, xml, root); - xmlFreeDoc(xml); +cleanup: + xmlFreeParserCtxt (pctxt); + xmlFreeDoc (xml); return def; } Index: src/storage_conf.c =================================================================== RCS file: /data/cvs/libvirt/src/storage_conf.c,v retrieving revision 1.10 diff -u -r1.10 storage_conf.c --- src/storage_conf.c 25 Jul 2008 14:27:25 -0000 1.10 +++ src/storage_conf.c 30 Jul 2008 14:38:52 -0000 @@ -361,22 +361,50 @@ return NULL; } +/* 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 && + ctxt->lastError.level == XML_ERR_FATAL && + ctxt->lastError.message != NULL) { + virStorageReportError (NULL, VIR_ERR_XML_DETAIL, + ctxt->lastError.message, ctxt->lastError.line); + } +} + virStoragePoolDefPtr virStoragePoolDefParse(virConnectPtr conn, const char *xmlStr, const char *filename) { virStoragePoolDefPtr ret = NULL; + xmlParserCtxtPtr pctxt; xmlDocPtr xml = NULL; xmlNodePtr node = NULL; xmlXPathContextPtr ctxt = NULL; - if (!(xml = xmlReadDoc(BAD_CAST xmlStr, - filename ? filename : "storage.xml", - NULL, - XML_PARSE_NOENT | XML_PARSE_NONET | - XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { - virStorageReportError(conn, VIR_ERR_XML_ERROR, - "%s", _("malformed xml document")); + /* Set up a parser context so we can catch the details of XML errors. */ + pctxt = xmlNewParserCtxt (); + if (!pctxt || !pctxt->sax) + goto cleanup; + pctxt->sax->error = catchXMLError; + + /* It'd be nice to do this, but seems ->userData field is used + * by something else. + */ + /*pctxt->userData = conn;*/ + + xml = xmlCtxtReadDoc (pctxt, BAD_CAST xmlStr, + filename ? filename : "storage.xml", + NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (!xml) { + /* virStorageReportError has already been called by the + * callback function (hopefully). + */ goto cleanup; } @@ -396,12 +424,14 @@ ret = virStoragePoolDefParseDoc(conn, ctxt, node); + xmlFreeParserCtxt (pctxt); xmlXPathFreeContext(ctxt); xmlFreeDoc(xml); return ret; cleanup: + xmlFreeParserCtxt (pctxt); xmlXPathFreeContext(ctxt); xmlFreeDoc(xml); return NULL; @@ -725,15 +755,30 @@ const char *xmlStr, const char *filename) { virStorageVolDefPtr ret = NULL; + xmlParserCtxtPtr pctxt; xmlDocPtr xml = NULL; xmlNodePtr node = NULL; xmlXPathContextPtr ctxt = NULL; - if (!(xml = xmlReadDoc(BAD_CAST xmlStr, filename ? filename : "storage.xml", NULL, - XML_PARSE_NOENT | XML_PARSE_NONET | - XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { - virStorageReportError(conn, VIR_ERR_XML_ERROR, - "%s", _("malformed xml document")); + /* Set up a parser context so we can catch the details of XML errors. */ + pctxt = xmlNewParserCtxt (); + if (!pctxt || !pctxt->sax) + goto cleanup; + pctxt->sax->error = catchXMLError; + + /* It'd be nice to do this, but seems ->userData field is used + * by something else. + */ + /*pctxt->userData = conn;*/ + + xml = xmlCtxtReadDoc (pctxt, BAD_CAST xmlStr, + filename ? filename : "storage.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (!xml) { + /* virStorageReportError has already been called by the + * callback function (hopefully). + */ goto cleanup; } @@ -753,12 +798,14 @@ ret = virStorageVolDefParseDoc(conn, pool, ctxt, node); + xmlFreeParserCtxt (pctxt); xmlXPathFreeContext(ctxt); xmlFreeDoc(xml); return ret; cleanup: + xmlFreeParserCtxt (pctxt); xmlXPathFreeContext(ctxt); xmlFreeDoc(xml); return NULL;
-- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list