[libvirt] [PATCHv4 4/5] vbox_tmpl.c: Patch for redefining snapshots

Manuel VIVES manuel.vives at diateam.net
Mon Dec 23 15:49:05 UTC 2013


> On 11/25/2013 07:23 PM, Manuel VIVES wrote:
> > The snapshots are saved in xml files, and then can be redefined
> > ---
> > 
> >  src/vbox/vbox_tmpl.c |  852
> >  +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 844
> >  insertions(+), 8 deletions(-)
> > 
> > diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c
> > index e05ed97..23f8aab 100644
> > --- a/src/vbox/vbox_tmpl.c
> > +++ b/src/vbox/vbox_tmpl.c
> > @@ -61,6 +61,7 @@
> > 
> >  #include "virstring.h"
> >  #include "virtime.h"
> >  #include "virutil.h"
> > 
> > +#include "dirname.h"
> > 
> >  /* This one changes from version to version. */
> >  #if VBOX_API_VERSION == 2002
> > 
> > @@ -276,6 +277,12 @@ static vboxGlobalData *g_pVBoxGlobalData = NULL;
> > 
> >  #endif /* VBOX_API_VERSION >= 4000 */
> > 
> > +/*This error is a bit specific
> > + *In the VBOX API it is named E_ACCESSDENIED
> > + *It is returned when the called object is not ready. In
> > + *particular when we do any call on a disk which has been closed
> > +*/
> > +#define VBOX_E_ACCESSDENIED 0x80070005
> 
> Is this code not defined anywhere in vbox API include files?
> 

I did not find this code anywhere in the API, so I defined it here.

> >  #define reportInternalErrorIfNS_FAILED(message) \
> >  
> >      if (NS_FAILED(rc)) { \
> >      
> >          virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(message)); \
> > 
> > @@ -286,6 +293,8 @@ static vboxGlobalData *g_pVBoxGlobalData = NULL;
> > 
> >  static virDomainPtr vboxDomainDefineXML(virConnectPtr conn, const char
> >  *xml); static int vboxDomainCreate(virDomainPtr dom);
> >  static int vboxDomainUndefineFlags(virDomainPtr dom, unsigned int
> >  flags);
> > 
> > +static virStorageVolPtr vboxStorageVolLookupByPath(virConnectPtr conn,
> > const char *path); +static int vboxStorageDeleteOrClose(virStorageVolPtr
> > vol, unsigned int flags, unsigned int flagDeleteOrClose);
> 
> The above line looks to be > 80 columns.
> 
> >  static void vboxDriverLock(vboxGlobalData *data) {
> >  
> >      virMutexLock(&data->lock);
> >  
> >  }
> > 
> > @@ -5946,6 +5955,827 @@ cleanup:
> >      return snapshot;
> >  
> >  }
> > 
> > +#if VBOX_API_VERSION >=4002
> > +static void
> > +vboxSnapshotXmlRetrieveSnapshotNodeByName(xmlNodePtr a_node,
> > +                               const char *name,
> > +                               xmlNodePtr *snap_node)
> > +{
> > +    xmlNodePtr cur_node = NULL;
> > +
> > +    for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
> > +        if (cur_node->type == XML_ELEMENT_NODE) {
> > +            if (!xmlStrcmp(cur_node->name, (const xmlChar *) "Snapshot")
> > && +                STREQ(virXMLPropString(cur_node, "name"), name)) {
> 
> Two problems here:
> 
> 1) virXMLPropString() could return NULL in case it doesn't find the
> property you're looking for, and you haven't allowed for that.
> 
> 2) virXMLPropString returns a *copy* of the string, and you need to
> VIR_FREE() it after you're finished with it. But you haven't even saved
> a copy of the pointer, so you have no way of doing that.
> 
> So every time you call virXMLPropString(), you're either going to leak a
> string, or segv libvirtd.
> 
> > +                *snap_node = cur_node;
> > +                return;
> > +            }
> > +        }
> > +        if (cur_node->children)
> > +           
> > vboxSnapshotXmlRetrieveSnapshotNodeByName(cur_node->children, name,
> > snap_node); +    }
> > +}
> > +
> > +
> > +
> > +
> > +static int
> > +vboxDetachAndCloseDisks(virDomainPtr dom,
> > +                        IMedium *disk)
> > +{
> > +    VBOX_OBJECT_CHECK(dom->conn, int, -1);
> > +    nsresult rc;
> > +    PRUnichar *location = NULL;
> > +    vboxArray childrenDiskArray = VBOX_ARRAY_INITIALIZER;
> > +    virStorageVolPtr volPtr = NULL;
> > +    char *location_utf8 = NULL;
> > +    PRUint32 dummyState = 0;
> > +    size_t i = 0;
> > +    if (disk == NULL) {
> > +        VIR_DEBUG("Null pointer to disk");
> > +        return -1;
> 
> If something is an error, and will end up failing an operation, then you
> need to log an error message rather than just outputting a debug
> message. This is true even if the error can only be caused by an
> internal problem in libvirt's own code.
> 
> > +    }
> > +    rc = disk->vtbl->GetLocation(disk, &location);
> > +    if (rc == VBOX_E_ACCESSDENIED) {
> > +        VIR_DEBUG("Disk already closed");
> 
> Not sure if this is an actual error, or a valid occurrence (I'm guessing
> the former, since you skip the rest of the function on failure). If it
> shouldn't happen, then you should be logging an error.
>
 
This is a valid occurence, it just means that the disk has already
been closed (so it is not useful to continue in the function).

> > +        goto cleanup;
> > +    }
> > +    reportInternalErrorIfNS_FAILED("cannot get disk location");
> > +    rc = vboxArrayGet(&childrenDiskArray, disk,
> > disk->vtbl->GetChildren); +    reportInternalErrorIfNS_FAILED("cannot
> > get children disks"); +    for (i = 0; i < childrenDiskArray.count; ++i)
> > {
> > +        IMedium *childDisk = childrenDiskArray.items[i];
> > +        if (childDisk) {
> > +            vboxDetachAndCloseDisks(dom, childDisk);
> > +        }
> > +    }
> > +    rc = disk->vtbl->RefreshState(disk, &dummyState);
> > +    reportInternalErrorIfNS_FAILED("cannot refresh state");
> > +    VBOX_UTF16_TO_UTF8(location, &location_utf8);
> > +    volPtr = vboxStorageVolLookupByPath(dom->conn, location_utf8);
> > +
> > +    if (volPtr) {
> > +        VIR_DEBUG("Closing %s", location_utf8);
> > +        if (vboxStorageDeleteOrClose(volPtr, 0, VBOX_STORAGE_CLOSE_FLAG)
> > != 0) { +            VIR_DEBUG("Error while closing disk");
> 
> Same problem here.
> 
> > +        }
> > +    }
> > +    VBOX_UTF8_FREE(location_utf8);
> > +cleanup:
> > +    VBOX_UTF16_FREE(location);
> > +    vboxArrayRelease(&childrenDiskArray);
> > +    return ret;
> > +}
> > +
> > +static void
> > +vboxSnapshotXmlAddChild(xmlNodePtr parent,
> > +                xmlNodePtr child)
> > +{
> > +    /*Used in order to add child without writing the stuff concerning
> > xml namespaces*/ +    xmlBufferPtr tmpBuf = xmlBufferCreate();
> > +    char *tmpString = NULL;
> > +    xmlNodePtr tmpNode = NULL;
> > +    xmlNodeDump(tmpBuf, parent->doc, child, 0, 0);
> > +    ignore_value(VIR_STRDUP(tmpString, (char
> > *)xmlBufferContent(tmpBuf)));
> 
> I don't think it's safe to ignore the return of VIR_STRDUP here.
> 
> > +    xmlParseInNodeContext(parent, tmpString, (int)strlen(tmpString), 0,
> > &tmpNode); +    if (tmpNode) {
> > +        if (xmlAddChild(parent, xmlCopyNode(tmpNode, 1)) == NULL) {
> > +            VIR_DEBUG("Error while adding %s to %s", (char
> > *)tmpNode->name, (char *)parent->name); +        }
> > +    }
> > +    xmlFree(tmpNode);
> > +    xmlBufferFree(tmpBuf);
> > +}
> > +
> > +static void
> > +vboxSnapshotXmlRetrieveMachineNode(xmlNodePtr root,
> > +                        xmlNodePtr *machineNode)
> > +{
> > +    xmlNodePtr cur = root->xmlChildrenNode;
> > +    while (cur && xmlIsBlankNode(cur)) {
> > +        cur = cur -> next;
> > +    }
> > +    if (xmlStrcmp(cur->name, (const xmlChar *) "Machine")) {
> > +        VIR_DEBUG("Problem, found %s, Machine expected", (char
> > *)cur->name); +        return;
> > +    }
> > +    *machineNode = cur;
> > +}
> > +
> > +static void
> > +vboxSnapshotXmlRetrieveSnapshotsNode(xmlNodePtr snapshotNode,
> > +                            xmlNodePtr *snapshotsNode)
> > +{
> > +    xmlNodePtr cur_node = NULL;
> > +
> > +    for (cur_node = snapshotNode; cur_node; cur_node = cur_node->next) {
> > +        if (cur_node->type == XML_ELEMENT_NODE) {
> > +            if (!xmlStrcmp(cur_node->name, (const xmlChar *)
> > "Snapshots")) { +                *snapshotsNode = cur_node;
> > +                return;
> > +            }
> > +        }
> > +        if (cur_node->children)
> > +            vboxSnapshotXmlRetrieveSnapshotsNode(cur_node->children,
> > snapshotsNode); +    }
> > +}
> > +
> > +static void
> > +vboxSnapshotXmlRetrieveRootNodeByName(xmlNodePtr root,
> > +                           const char *name,
> > +                            xmlNodePtr *returnNode)
> > +{
> > +    xmlNodePtr cur = root->xmlChildrenNode;
> > +    while (cur && xmlIsBlankNode(cur)) {
> > +        cur = cur -> next;
> > +    }
> > +    if (xmlStrcmp(cur->name, (const xmlChar *) "Machine")) {
> > +        VIR_DEBUG("Problem, found %s, Machine expected", (char
> > *)cur->name); +    }
> > +    cur=cur->xmlChildrenNode;
> > +    while (cur != NULL) {
> > +        if (!xmlStrcmp(cur->name, (const xmlChar*) name)) {
> > +            *returnNode = cur;
> > +            return;
> > +        }
> > +        cur = cur -> next;
> > +    }
> > +}
> > +
> > +static void
> > +vboxSnapshotXmlAppendDiskToMediaRegistry(xmlNodePtr *inMediaRegistry,
> > +                              char *parentId,
> > +                              char *childId,
> > +                              char *childLocation,
> > +                              char *childFormat)
> > +{
> > +    /*This function will modify the inMediaregistry node to append all
> > the informations about the child disk +     */
> > +    xmlNodePtr cur_node = NULL;
> > +    for (cur_node = *inMediaRegistry; cur_node; cur_node =
> > cur_node->next) { +        if (cur_node) {
> > +            if (cur_node->type == XML_ELEMENT_NODE
> > +                && !xmlStrcmp(cur_node->name, (const xmlChar *)
> > "HardDisk") +                && xmlHasProp(cur_node, BAD_CAST "uuid") !=
> > NULL +                && STREQ((char *)xmlHasProp(cur_node, BAD_CAST
> > "uuid")->children->content, parentId)) { +
> > +                xmlNodePtr childDiskNode = xmlNewTextChild(cur_node,
> > NULL, BAD_CAST "HardDisk", NULL); +               
> > xmlNewProp(childDiskNode, BAD_CAST "uuid", BAD_CAST childId); +         
> >       xmlNewProp(childDiskNode, BAD_CAST "location", BAD_CAST
> > childLocation); +                xmlNewProp(childDiskNode, BAD_CAST
> > "format", BAD_CAST childFormat);
> 
> xmlNewProp can return an error on failure. But of course this function
> only returns void, so there's no way to report it to the caller anyway :-(
> 
> > +                return;
> > +            }
> > +        }
> > +        if (&(cur_node->children))
> > +           
> > vboxSnapshotXmlAppendDiskToMediaRegistry(&(cur_node->children),
> > parentId, childId, childLocation, childFormat); +
> > +    }
> > +}
> > +
> > +static int
> > +vboxSnapshotGenerateVboxXML(xmlNodePtr rootElementVboxXML,
> > +                            char *storageControllerString,
> > +                            char *parent,
> > +                            char *defName,
> > +                            char *timeStamp,
> > +                            char *uuid,
> > +                            xmlNodePtr *snapshotNodeReturned)
> > +{
> > +    xmlDocPtr doc;
> > +    xmlNodePtr snapshotNode;
> > +    xmlNodePtr snapshotsNode;
> > +    xmlNodePtr machineHardwareNode = NULL;
> > +    char *uuidstrWithBrackets = NULL;
> > +    char *timeVboxFormat = NULL;
> > +    int ret = -1;
> > +    /*We change the date format from "yyyy-MM-dd hh:mm:ss.msec+timeZone"
> > to "yyyy-MM-ddThh:mm:ssZ"*/ +    char *date =
> > virStringSplit(virStringJoin((const
> > char**)virStringSplit(virStringSplit(timeStamp, "+", 0)[0], " ", 0),
> > "T"), ".", 0)[0];
> 
> This is compact, but doesn't protect against OOM errors (I know some
> people argue that once there is an OOM error, you're dead anyway so
> there isn't any point, but that isn't the philosophy that libvirt tries
> to follow). This is a statement about the vbox driver in general, btw,
> not just this particular occurrence in this patch...
> 
> > +
> > +    /*Retrieve hardware*/
> > +    vboxSnapshotXmlRetrieveRootNodeByName(rootElementVboxXML,
> > "Hardware", &machineHardwareNode); +    /* Create a new XML DOM tree, to
> > which the XML document will be +         * written */
> > +    doc = xmlNewDoc(BAD_CAST XML_DEFAULT_VERSION);
> > +    if (doc == NULL) {
> > +        VIR_DEBUG("Error creating the xml document tree\n");
> > +        ret = -1;
> > +        goto cleanup;
> > +    }
> > +    /* Create a new XML node, to which the XML document will be
> > +         * appended */
> > +    snapshotNode = xmlNewDocNode(doc, NULL, BAD_CAST "Snapshot", NULL);
> > +    if (snapshotNode == NULL) {
> > +        VIR_DEBUG("Error creating the xml node Snapshot\n");
> > +        ret = -1;
> > +        goto cleanup;
> > +    }
> > +    if (virAsprintf(&uuidstrWithBrackets, "{%s}", uuid) != -1) {
> > +        xmlNewProp(snapshotNode, BAD_CAST "uuid", BAD_CAST
> > uuidstrWithBrackets); +        VIR_FREE(uuidstrWithBrackets);
> > +    }
> > +    xmlNewProp(snapshotNode, BAD_CAST "name", BAD_CAST defName);
> > +    if (virAsprintf(&timeVboxFormat, "%sZ", date) != -1) {
> > +        xmlNewProp(snapshotNode, BAD_CAST "timeStamp", BAD_CAST
> > timeVboxFormat); +        VIR_FREE(timeVboxFormat);
> > +    }
> > +    xmlDocSetRootElement(doc, snapshotNode);
> > +    if (machineHardwareNode) {
> > +        vboxSnapshotXmlAddChild(snapshotNode, machineHardwareNode);
> > +    }
> > +    xmlNodePtr listStorageControllerNodes;
> > +    xmlParseInNodeContext(snapshotNode, storageControllerString,
> > (int)strlen(storageControllerString), 0, &listStorageControllerNodes); +
> >    if (listStorageControllerNodes) {
> > +        if (xmlAddChild(snapshotNode,
> > xmlCopyNode(listStorageControllerNodes, 1)) == NULL) { +           
> > VIR_DEBUG("Could not add listStorageControllerNodes node to snapshot
> > node"); +            ret = -1;
> > +            goto cleanup;
> > +        }
> > +    }
> > +    snapshotsNode = xmlNewDocNode(doc, NULL, BAD_CAST "Snapshots",
> > NULL); +    if (snapshotsNode == NULL) {
> > +        VIR_DEBUG("Error creating the xml node Snapshots\n");
> > +        ret = -1;
> > +        goto cleanup;
> > +    }
> > +    if (snapshotsNode) {
> > +        if (xmlAddChild(snapshotNode, xmlCopyNode(snapshotsNode, 1)) ==
> > NULL) { +            VIR_DEBUG("Could not add snapshotsNode node to
> > snapshot node"); +            ret = -1;
> > +            goto cleanup;
> > +        }
> > +    }
> > +    if (parent) {
> > +        xmlNodePtr rootSnapshotNode = NULL;
> > +        xmlNodePtr parentNode = NULL;
> > +        xmlNodePtr parentSnapshotsNode = NULL;
> > +        vboxSnapshotXmlRetrieveRootNodeByName(rootElementVboxXML,
> > "Snapshot", &rootSnapshotNode); +        if (!rootSnapshotNode) {
> > +            VIR_DEBUG("Could not retrieve Snapshot node");
> > +            ret = -1;
> > +            goto cleanup;
> > +        }
> > +        vboxSnapshotXmlRetrieveSnapshotNodeByName(rootSnapshotNode,
> > parent, &parentNode); +        if (!parentNode) {
> > +            VIR_DEBUG("Could not retrieve Snapshot node of %s", parent);
> > +            ret = -1;
> > +            goto cleanup;
> > +        }
> > +        vboxSnapshotXmlRetrieveSnapshotsNode(parentNode->children,
> > &parentSnapshotsNode); +        if (!parentSnapshotsNode) {
> > +            VIR_DEBUG("Could not retrieve Snapshots node");
> > +            ret = -1;
> > +            goto cleanup;
> > +        }
> > +        if (xmlAddChild(parentSnapshotsNode, xmlCopyNode(snapshotNode,
> > 1)) == NULL) { +            VIR_DEBUG("Could not add snapshotsNode node
> > to its parent"); +            ret = -1;
> > +            goto cleanup;
> > +        } else {
> > +            *snapshotNodeReturned = xmlCopyNode(rootSnapshotNode, 1);
> > +        }
> > +    } else {
> > +        *snapshotNodeReturned = xmlCopyNode(snapshotNode, 1);
> > +    }
> > +    ret = 0;
> > +cleanup:
> > +    return ret;
> > +}
> > +
> > +static xmlBufferPtr
> > +vboxSnapshotGenerateXmlSkeleton(char *domainUuid,
> > +                        char *machineName,
> > +                        char *snapshotUuid,
> > +                        char *snapshotFolder,
> > +                        PRInt64 lastStateChange,
> > +                        bool isCurrent,
> > +                        char *savedCurrentSnapshotUuid)
> > +{
> > +    char *snapshotUuidWithBrackets = NULL;
> > +    char *lastStateChangeStr = NULL;
> > +    /*Let's write a new xml
> > +        */
> > +    xmlTextWriterPtr writer;
> > +    xmlBufferPtr buf = xmlBufferCreate();
> > +    xmlBufferPtr ret = NULL;
> > +    int resultXml;
> > +    char *uuidWithBracket = NULL;
> > +
> > +    /* Create a new XmlWriter with no compression. */
> > +    writer = xmlNewTextWriterMemory(buf, 0);
> > +    if (writer == NULL) {
> > +        VIR_DEBUG("Error creating the xml writer\n");
> > +        goto cleanup;
> > +    }
> > +
> > +    /* Start the document with the xml default for the version,
> > +     * encoding ISO 8859-1 and the default for the standalone
> > +     * declaration. */
> > +    resultXml = xmlTextWriterStartDocument(writer, NULL, "ISO-8859-1",
> > NULL); +    if (resultXml < 0) {
> > +        VIR_DEBUG("Error at xmlTextWriterStartDocument\n");
> > +        goto cleanup;
> > +    }
> > +    /* Write a comment as child of root.
> > +     * Please observe, that the input to the xmlTextWriter functions
> > +     * HAS to be in UTF-8, even if the output XML is encoded
> > +     * in iso-8859-1 */
> > +    resultXml = xmlTextWriterWriteFormatComment(writer, "WARNING: THIS
> > IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE \n" +         
> >                                       "OVERWRITTEN AND LOST.\n" +       
> >                                         "Changes to this xml
> > configuration should be made using Virtualbox \n" +                     
> >                           "or other application using the libvirt API");
> > +    if (resultXml < 0) {
> > +        VIR_DEBUG("Error at xmlTextWriterWriteComment\n");
> > +        goto cleanup;
> > +    }
> > +    xmlTextWriterWriteRaw(writer, BAD_CAST "\n"); /*Break line after
> > comment*/ +
> > +    /* Start an element named "VirtualBox". Since thist is the first
> > +     * element, this will be the root element of the document. */
> > +    resultXml = xmlTextWriterStartElement(writer, BAD_CAST
> > "VirtualBox"); +    if (resultXml < 0) {
> > +        VIR_DEBUG("Error creating the xml node\n");
> > +        goto cleanup;
> > +    }
> > +    resultXml = xmlTextWriterWriteAttribute(writer, BAD_CAST "version",
> > BAD_CAST "1.12-linux"); +    if (resultXml < 0) {
> > +        VIR_DEBUG("Error at xmlTextWriterWriteAttribute\n");
> > +        goto cleanup;
> > +    }
> > +    /* Start an element named "Machine" as child of VirtualBox. */
> > +    resultXml = xmlTextWriterStartElement(writer, BAD_CAST "Machine");
> > +    if (resultXml < 0) {
> > +        VIR_DEBUG("Error at xmlTextWriterStartElement\n");
> > +        goto cleanup;
> > +    }
> > +    /* Add an attribute with name "uuid" and value "{uuid}" to Machine.
> > */ +    if (virAsprintf(&uuidWithBracket, "{%s}", domainUuid) != -1) { +
> >        resultXml = xmlTextWriterWriteAttribute(writer, BAD_CAST "uuid",
> > BAD_CAST uuidWithBracket); +        if (resultXml < 0) {
> > +            VIR_DEBUG("Error at xmlTextWriterWriteAttribute\n");
> > +            goto cleanup;
> > +        }
> > +        VIR_FREE(uuidWithBracket);
> > +    }
> > +
> > +    if (!machineName) {
> > +        VIR_DEBUG("No machine name");
> > +        goto cleanup;
> > +    }
> > +    resultXml = xmlTextWriterWriteAttribute(writer, BAD_CAST "name",
> > BAD_CAST machineName); +    if (resultXml < 0) {
> > +        VIR_DEBUG("Error at xmlTextWriterWriteAttribute\n");
> > +        goto cleanup;
> > +    }
> > +    resultXml = xmlTextWriterWriteAttribute(writer, BAD_CAST "OSType",
> > BAD_CAST "Other"); +    if (resultXml < 0) {
> > +        VIR_DEBUG("Error at xmlTextWriterWriteAttribute\n");
> > +        goto cleanup;
> > +    }
> > +
> > +    if (!snapshotFolder) {
> > +        VIR_DEBUG("No machine snapshotFolder");
> > +        goto cleanup;
> > +    }
> > +    resultXml = xmlTextWriterWriteAttribute(writer, BAD_CAST
> > "snapshotFolder", BAD_CAST snapshotFolder); +    if (resultXml < 0) {
> > +        VIR_DEBUG("Error at xmlTextWriterWriteAttribute\n");
> > +        goto cleanup;
> > +    }
> > +    if (virAsprintf(&lastStateChangeStr, "%ld", lastStateChange) < 0) {
> > +        VIR_DEBUG("Could not convert lastStateChange from long to
> > string"); +        goto cleanup;
> > +    }
> > +    resultXml = xmlTextWriterWriteAttribute(writer, BAD_CAST
> > "lastStateChange", BAD_CAST lastStateChangeStr); +    if (resultXml < 0)
> > {
> > +        VIR_DEBUG("Error at xmlTextWriterWriteAttribute\n");
> > +        goto cleanup;
> > +    }
> > +    /* Current Snapshot
> > +     *
> > https://www.virtualbox.org/sdkref/interface_i_machine.html#ac785dbe04ecc
> > c0793d949d6940202767 +     * There is always a current snapshot, except
> > when there is no snapshot (obvious) or when they have all been deleted
> > (obvious) +     * or when the current snapshot is deleted and it does
> > not have a parent (not so obvious). +     * This can happen only when
> > the last snapshot is deleted because there can only be one snapshot with
> > no parent, the first one. +     * For the moment we always put the
> > snapshot we are defining as current snapshot. When all the snapshots are
> > redefined, you can always set +     * the current snapshot to one you
> > have previously saved.
> > +     */
> 
> Reformat to fit in 80 columns.
> 
> I'm going to have to stop here, as there is just too much vbox-centric
> code that I don't understand, and already a lot of re-occurring problems
> I've pointed out that should be fixed everywhere in all the patches
> before a rebase/resubmit.
> 
> If there is someone else who understands the vbox driver (and vbox
> itself) better than me, I would encourage them to jump in when revised
> patches are posted to do a lower level review - I can still look for
> problems related to the way that libvirt internal APIs (and do some
> small extent libxml APIs) are called, but can't even *run* the code to
> test for correctness.




More information about the libvir-list mailing list