[libvirt] PATCH: Generic internal API for network XML parser/formatter

Jim Meyering jim at meyering.net
Tue Jul 1 12:04:39 UTC 2008


"Daniel P. Berrange" <berrange at redhat.com> wrote:
> We currently have two drivers which handle the networking XML containing
> duplicated parsers and formatters for the XML, and very similar structs.
> This patch introduces a new general purpose internal API for parsing and
> formatting network XML, and representing it as a series of structs.
>
> This code is derived from the current equivalent code in the QEMU driver
> for networks.
>
> The naming conventions I'm adopting in this patch follow those  in the
> storage driver:
>
>  - virNetworkPtr - the public opaque object in libvirt.h
>  - virNetworkObjPtr - the primary internal object for network state
>  - virNetworkDefPtr - the configuration data for a network
>
> A virNetworkObjPtr contains a reference to one or two virNetworkDefPtr
> objects - the current live config, and potentially a secondary inactive
> config which will become live at the next restart.
>
> The structs are defined in network_conf.h, along with a bunch of APIs for
> dealing with them. These APIs are the same as similarly named ones from
> the current qemu driver, but I'll go over them again for a reminder:
>
>   virNetworkObjPtr virNetworkFindByUUID(const virNetworkObjPtr nets,
>                                         const unsigned char *uuid);
>   virNetworkObjPtr virNetworkFindByName(const virNetworkObjPtr nets,
>                                         const char *name);
>
> Allow lookup of a virNetworkObjPtr object based on its name or UUID, as
> typically obtained from the public virNetworkPtr object.
> The 'nets' parameter to both of thse is a linked list of networks which
> are currently known to the driver using this API.
...

Thanks for writing those descriptions.  When you write so much code,
I'm reluctant to say anything about comment density, but I was surprised
to see the same functions defined below with no comment saying what each
does.  It'd be nice to copy each of the above into a function-describing
comment.

> diff -r a6e5acdd23df src/Makefile.am
> --- a/src/Makefile.am	Tue Jun 24 15:07:21 2008 +0100
> +++ b/src/Makefile.am	Tue Jun 24 15:07:23 2008 +0100
> @@ -52,6 +52,7 @@
>  		driver.h					\
>  		proxy_internal.c proxy_internal.h		\
>  		conf.c conf.h                                   \
> +                network_conf.c network_conf.h                   \

leading spaces in Makefile.am is technically ok in this context,
but it's inconsistent with the neighboring lines.

>  		xm_internal.c xm_internal.h                     \
>  		remote_internal.c remote_internal.h		\
>  		bridge.c bridge.h				\
> diff -r a6e5acdd23df src/network_conf.c
...
> +++ b/src/network_conf.c	Tue Jun 24 15:07:23 2008 +0100
> @@ -0,0 +1,467 @@
> +/*
> + * network_conf.h: network XML handling

s/\.h/.c/  This is a good reason not to put the name
of the file in a comment inside the file itself.

...
> +static int
> +virNetworkDHCPRangeDefParseXML(virConnectPtr conn,
> +                               virNetworkDefPtr def,
> +                               xmlNodePtr node) {
> +
> +    xmlNodePtr cur;
> +
> +    cur = node->children;
> +    while (cur != NULL) {
> +        xmlChar *start, *end;
> +
> +        if (cur->type != XML_ELEMENT_NODE ||
> +            !xmlStrEqual(cur->name, BAD_CAST "range")) {
> +            cur = cur->next;
> +            continue;
> +        }
> +
> +        if (!(start = xmlGetProp(cur, BAD_CAST "start"))) {
> +            cur = cur->next;
> +            continue;
> +        }
> +        if (!(end = xmlGetProp(cur, BAD_CAST "end"))) {
> +            cur = cur->next;
> +            xmlFree(start);
> +            continue;
> +        }
> +
> +        if (VIR_REALLOC_N(def->ranges, def->nranges + 1) < 0) {
> +            xmlFree(start);
> +            xmlFree(end);
> +            virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL);
> +            return -1;
> +        }
> +        def->ranges[def->nranges].start = (char *)start;
> +        def->ranges[def->nranges].end = (char *)end;
> +        def->nranges++;
> +
> +        cur = cur->next;
> +    }
> +
> +    return 0;
> +}

With four "cur = cur->next;" statements, I prefer a for-loop.
And rather than all the if-negated-condition-then-continue, I find the
positive-cond tests to be more readable.  Here's an untested alternative
that should be equivalent:

static int
virNetworkDHCPRangeDefParseXML(virConnectPtr conn,
                               virNetworkDefPtr def,
                               xmlNodePtr node)
{
    xmlNodePtr cur;
    for (cur = node->children; cur != NULL; cur = cur->next) {
        xmlChar *start, *end = NULL;

        if (!(cur->type == XML_ELEMENT_NODE &&
              xmlStrEqual(cur->name, BAD_CAST "range"))
            continue;

        if ((start = xmlGetProp(cur, BAD_CAST "start"))
            && (end = xmlGetProp(cur, BAD_CAST "end"))) {
            if (VIR_REALLOC_N(def->ranges, def->nranges + 1) < 0) {
                xmlFree(start);
                xmlFree(end);
                virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL);
                return -1;
            }
            def->ranges[def->nranges].start = (char *)start;
            def->ranges[def->nranges].end = (char *)end;
            def->nranges++;
        } else {
            xmlFree(start);
            xmlFree(end);
        }
    }

    return 0;
}

> +static virNetworkDefPtr
> +virNetworkDefParseXML(virConnectPtr conn,
> +                      xmlDocPtr xml)
> +{
> +    xmlNodePtr root = NULL;

Initialization not required.

> +    xmlXPathContextPtr ctxt = NULL;
> +    virNetworkDefPtr def;
> +    char *tmp;
> +
> +    if (VIR_ALLOC(def) < 0) {
> +        virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL);
> +        return NULL;
> +    }
> +
> +    /* Prepare parser / xpath context */
> +    root = xmlDocGetRootElement(xml);
> +    if ((root == NULL) || (!xmlStrEqual(root->name, BAD_CAST "network"))) {
> +        virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
> +                              "%s", _("incorrect root element"));
> +        goto error;
> +    }
> +
> +    ctxt = xmlXPathNewContext(xml);
> +    if (ctxt == NULL) {
> +        virNetworkReportError(conn, VIR_ERR_NO_MEMORY,
> +                              "%s", _("failed to allocate space for xmlXPathContext string"));
> +        goto error;
> +    }
> +
> +
> +    /* Extract network name */
> +    def->name = virXPathString("string(/network/name[1])", ctxt);
> +    if (!def->name) {
> +        virNetworkReportError(conn, VIR_ERR_NO_NAME, NULL);
> +        goto error;
> +    }
> +
> +    /* Extract network uuid */
> +    tmp = virXPathString("string(/network/uuid[1])", ctxt);
> +    if (!tmp) {
> +        int err;
> +        if ((err = virUUIDGenerate(def->uuid))) {
> +            virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
> +                             _("Failed to generate UUID: %s"), strerror(err));
> +            goto error;
> +        }
> +    } else {
> +        if (virUUIDParse(tmp, def->uuid) < 0) {
> +            VIR_FREE(tmp);
> +            virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
> +                                  "%s", _("malformed uuid element"));
> +            goto error;
> +        }
> +        VIR_FREE(tmp);
> +    }
> +
> +    /* Parse bridge information */
> +    def->bridge = virXPathString("string(/network/bridge[1]/@name)", ctxt);
> +    tmp = virXPathString("string(/network/bridge[1]/@stp)", ctxt);
> +    if (tmp && STREQ(tmp, "off"))
> +        def->stp = 0;
> +    else
> +        def->stp = 1;

I have a slight preference for the one-liner,
but if you prefer to avoid C's ternary operator, ...

    /* Default is "on".  */
    def->stp = (tmp && STREQ(tmp, "off") ? 0 : 1);

> +    VIR_FREE(tmp);
> +
> +    if (virXPathLong("string(/network/bridge[1]/@delay)", ctxt, &def->delay) < 0)
> +        def->delay = 0;
> +
> +    def->ipAddress = virXPathString("string(/network/ip[1]/@address)", ctxt);
> +    def->netmask = virXPathString("string(/network/ip[1]/@netmask)", ctxt);
> +    if (def->ipAddress &&
> +        def->netmask) {
> +        /* XXX someday we want IPv6 too, so inet_aton won't work there */
> +        struct in_addr inaddress, innetmask;
> +        char *netaddr;
> +        int netlen;
> +        xmlNodePtr dhcp;
> +
> +        if (!inet_aton(def->ipAddress, &inaddress)) {
> +            virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
> +                                  _("cannot parse IP address '%s'"),
> +                                  def->ipAddress);
> +            goto error;
> +        }
> +        if (!inet_aton(def->netmask, &innetmask)) {
> +            virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
> +                                  _("cannot parse netmask '%s'"),
> +                                  def->netmask);
> +            goto error;
> +        }
> +
> +        inaddress.s_addr &= innetmask.s_addr;
> +        netaddr = inet_ntoa(inaddress);
> +
> +        netlen = strlen(netaddr) + 1 + strlen(def->netmask) + 1;
> +        if (VIR_ALLOC_N(def->network, netlen) < 0) {
> +            virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL);
> +            goto error;
> +        }
> +        strcpy(def->network, netaddr);
> +        strcat(def->network, "/");
> +        strcat(def->network, def->netmask);

I prefer to use asprintf, and then remove 4 of the 8 lines above,
as well as the netlen declaration.

> +        if ((dhcp = virXPathNode("/network/ip[1]/dhcp[1]", ctxt)) &&
> +            virNetworkDHCPRangeDefParseXML(conn, def, dhcp) < 0)
> +            goto error;
> +    }
> +
> +
> +    /* IPv4 forwarding setup */
> +    if (virXPathBoolean("count(/network/forward) > 0", ctxt)) {
> +        if (!def->ipAddress ||
> +            !def->netmask) {
> +            virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
> +                                  "%s", _("Forwarding requested, but no IPv4 address/netmask provided"));
> +            goto error;
> +        }
> +
> +        tmp = virXPathString("string(/network/forward[1]/@mode)", ctxt);
> +        if (tmp) {
> +            if ((def->forwardType = virNetworkForwardTypeFromString(tmp)) < 0) {
> +                virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
> +                                      _("unknown forwarding type '%s'"), tmp);
> +                VIR_FREE(tmp);

missing goto

> +            }
> +            VIR_FREE(tmp);

Recently, I've begun using a slightly different paradigm, in order to remove
the need for a separate free call in the error path, since it's so easy
to omit.  i.e, here, you can do this:

        if (tmp) {
            def->forwardType = virNetworkForwardTypeFromString(tmp);
            VIR_FREE(tmp);
            if (def->forwardType < 0) {
                virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                      _("unknown forwarding type '%s'"), tmp);
                goto error;
            }
        } ...

Here, it's not ideal, since this separation duplicates the relatively
long LHS, "def->forwardType", but if you don't like that, use a temporary:

        if (tmp) {
            int fail = ((def->forwardType
                         = virNetworkForwardTypeFromString(tmp)) < 0);
            VIR_FREE(tmp);
            if (fail) {
                virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                      _("unknown forwarding type '%s'"), tmp);
                goto error;
            }
        } ...

> +        } else {
> +            def->forwardType = VIR_NETWORK_FORWARD_NAT;
> +        }
> +
> +
> +        def->forwardDev = virXPathString("string(/network/forward[1]/@dev)", ctxt);
> +    } else {
> +        def->forwardType = VIR_NETWORK_FORWARD_NONE;
> +    }
> +
> +    xmlXPathFreeContext(ctxt);
> +
> +    return def;
> +
> + error:
> +    xmlXPathFreeContext(ctxt);
> +    virNetworkDefFree(def);
> +    return NULL;
> +}

Here's a patch with the suggested changes to that
single function:

--- parse.c	2008-07-01 11:27:58.000000000 +0200
+++ parse.c	2008-07-01 13:41:18.000000000 +0200
@@ -45,22 +45,20 @@
             goto error;
         }
     } else {
-        if (virUUIDParse(tmp, def->uuid) < 0) {
-            VIR_FREE(tmp);
+        int fail = (virUUIDParse(tmp, def->uuid) < 0);
+        VIR_FREE(tmp);
+        if (fail) {
             virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                   "%s", _("malformed uuid element"));
             goto error;
         }
-        VIR_FREE(tmp);
     }
 
     /* Parse bridge information */
     def->bridge = virXPathString("string(/network/bridge[1]/@name)", ctxt);
     tmp = virXPathString("string(/network/bridge[1]/@stp)", ctxt);
-    if (tmp && STREQ(tmp, "off"))
-        def->stp = 0;
-    else
-        def->stp = 1;
+    /* Default is "on".  */
+    def->stp = (tmp && STREQ(tmp, "off") ? 0 : 1);
     VIR_FREE(tmp);
 
     if (virXPathLong("string(/network/bridge[1]/@delay)", ctxt, &def->delay) < 0)
@@ -73,7 +71,6 @@
         /* XXX someday we want IPv6 too, so inet_aton won't work there */
         struct in_addr inaddress, innetmask;
         char *netaddr;
-        int netlen;
         xmlNodePtr dhcp;
 
         if (!inet_aton(def->ipAddress, &inaddress)) {
@@ -92,15 +89,11 @@
         inaddress.s_addr &= innetmask.s_addr;
         netaddr = inet_ntoa(inaddress);
 
-        netlen = strlen(netaddr) + 1 + strlen(def->netmask) + 1;
-        if (VIR_ALLOC_N(def->network, netlen) < 0) {
+        if (asprintf(&def->network, "%s/%s", netaddr, def->netmask) < 0) {
+            def->network = NULL;
             virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL);
             goto error;
         }
-        strcpy(def->network, netaddr);
-        strcat(def->network, "/");
-        strcat(def->network, def->netmask);
-
 
         if ((dhcp = virXPathNode("/network/ip[1]/dhcp[1]", ctxt)) &&
             virNetworkDHCPRangeDefParseXML(conn, def, dhcp) < 0)
@@ -119,12 +112,13 @@
 
         tmp = virXPathString("string(/network/forward[1]/@mode)", ctxt);
         if (tmp) {
-            if ((def->forwardType = virNetworkForwardTypeFromString(tmp)) < 0) {
+            def->forwardType = virNetworkForwardTypeFromString(tmp);
+            VIR_FREE(tmp);
+            if (def->forwardType < 0) {
                 virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR,
                                       _("unknown forwarding type '%s'"), tmp);
-                VIR_FREE(tmp);
+                goto error;
             }
-            VIR_FREE(tmp);
         } else {
             def->forwardType = VIR_NETWORK_FORWARD_NAT;
         }

> +virNetworkDefPtr virNetworkDefParse(virConnectPtr conn,

I'll look at the rest later.




More information about the libvir-list mailing list