ovfreader.cpp revision 5a90348de5b2dd39c2258d19dabd69369a0f9999
/* $Id$ */
/** @file
* OVF reader declarations.
*
* Depends only on IPRT, including the RTCString and IPRT XML classes.
*/
/*
* Copyright (C) 2008-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#include "ovfreader.h"
using namespace std;
using namespace ovf;
////////////////////////////////////////////////////////////////////////////////
//
// OVF reader implementation
//
////////////////////////////////////////////////////////////////////////////////
/**
* Constructor. This parses the given XML file out of the memory. Throws lots of exceptions
* on XML or OVF invalidity.
* @param pvBuf the memory buffer to parse
* @param cbSize the size of the memory buffer
* @param path path to a filename for error messages.
*/
{
m_doc);
/* Start the parsing */
parse();
}
/**
* Constructor. This opens the given XML file and parses it. Throws lots of exceptions
* on XML or OVF invalidity.
* @param path
*/
{
m_doc);
/* Start the parsing */
parse();
}
{
const char *pcszTypeAttr = "";
if ( !pRootElem
)
{
}
else
{
// throw OVFLogicError(N_("Error reading \"%s\": missing or invalid attribute '%s' in 'Envelope' element, line %d"),
// m_strPath.c_str(),
// "version",
// pRootElem->getLineNumber());
}
{
}
// OVF has the following rough layout:
/*
-- <References> .... files referenced from other parts of the file, such as VMDK images
-- Metadata, comprised of several section commands
-- virtual machines, either a single <VirtualSystem>, or a <VirtualSystemCollection>
-- optionally <Strings> for localization
*/
// get all "File" child elements of "References" section so we can look up files easily;
// first find the "References" sections so we can look up files
// now go though the sections
}
/**
* Private helper method that goes thru the elements of the given "current" element in the OVF XML
* and handles the contained child elements (which can be "Section" or "Content" elements).
*
* @param pcszPath Path spec of the XML file, for error messages.
* @param pReferencesElement "References" element from OVF, for looking up file specifications; can be NULL if no such element is present.
* @param pCurElem Element whose children are to be analyzed here.
* @return
*/
{
{
const char *pcszTypeAttr = "";
)
)
)
{
}
)
)
{
}
{
// TODO
}
{
// child of VirtualSystemCollection -- TODO
}
{
// child of VirtualSystemCollection -- TODO
}
{
// child of VirtualSystemCollection -- TODO
}
)
)
{
}
)
)
{
// TODO ResourceAllocationSection
// recurse for this, since it has VirtualSystem elements as children
}
}
}
/**
* Private helper method that handles disk sections in the OVF XML.
* Gets called indirectly from IAppliance::read().
*
* @param pcszPath Path spec of the XML file, for error messages.
* @param pReferencesElement "References" element from OVF, for looking up file specifications; can be NULL if no such element is present.
* @param pSectionElem Section element for which this helper is getting called.
* @return
*/
{
// contains "Disk" child elements
{
DiskImage d;
const char *pcszDiskId;
const char *pcszFormat;
pcszBad = "diskId";
pcszBad = "format";
pcszBad = "capacity";
else
{
d.strDiskId = pcszDiskId;
d.strFormat = pcszFormat;
// optional
d.iPopulatedSize = -1;
// optional vbox:uuid attribute (if OVF was exported by VirtualBox != 3.2)
const char *pcszFileRef;
{
// look up corresponding /References/File nodes (list built above)
if ( pReferencesElem
)
{
// copy remaining values from file node then
const char *pcszBadInFile = NULL;
const char *pcszHref;
pcszBadInFile = "href";
// if (!(pFileElem->getAttributeValue("size", d.iChunkSize))) TODO
const char *pcszCompression;
if (pcszBadInFile)
throw OVFLogicError(N_("Error reading \"%s\": missing or invalid attribute '%s' in 'File' element, line %d"),
pFileElem->getLineNumber());
}
else
throw OVFLogicError(N_("Error reading \"%s\": cannot find References/File element for ID '%s' referenced by 'Disk' element, line %d"),
pelmDisk->getLineNumber());
}
}
if (pcszBad)
throw OVFLogicError(N_("Error reading \"%s\": missing or invalid attribute '%s' in 'DiskSection' element, line %d"),
pelmDisk->getLineNumber());
// suggest a size in megabytes to help callers with progress reports
d.ulSuggestedSizeMB = 0;
if (d.iCapacity != -1)
else if (d.iPopulatedSize != -1)
else if (d.iSize != -1)
if (d.ulSuggestedSizeMB == 0)
m_mapDisks[d.strDiskId] = d;
}
}
/**
* Private helper method that handles network sections in the OVF XML.
* Gets called indirectly from IAppliance::read().
*
* @param pcszPath Path spec of the XML file, for error messages.
* @param pSectionElem Section element for which this helper is getting called.
* @return
*/
{
// we ignore network sections for now
// xml::NodesLoop loopNetworks(*pSectionElem, "Network");
// const xml::Node *pelmNetwork;
// while ((pelmNetwork = loopNetworks.forAllNodes()))
// {
// Network n;
// if (!(pelmNetwork->getAttributeValue("name", n.strNetworkName)))
// return setError(VBOX_E_FILE_ERROR,
// tr("Error reading \"%s\": missing 'name' attribute in 'Network', line %d"),
// pcszPath,
// pelmNetwork->getLineNumber());
//
// m->mapNetworks[n.strNetworkName] = n;
// }
}
/**
* Private helper method that handles a "VirtualSystem" element in the OVF XML.
* Gets called indirectly from IAppliance::read().
*
* @param pcszPath
* @param pContentElem
* @return
*/
{
// peek under the <VirtualSystem> node whether we have a <vbox:Machine> node;
// that case case, the caller can completely ignore the OVF but only load the VBox machine XML
// now look for real OVF
if (pIdAttr)
{
const char *pcszTypeAttr = "";
if (!strcmp(pcszElemName, "Section")) // OVF 0.9 used "Section" element always with a varying "type" attribute
{
)
else
throw OVFLogicError(N_("Error reading \"%s\": element \"Section\" has no \"type\" attribute, line %d"),
pelmThis->getLineNumber());
}
)
{
/* <EulaSection>
<Info ovf:msgid="6">License agreement for the Virtual System.</Info>
<License ovf:msgid="1">License terms can go in here.</License>
</EulaSection> */
}
)
{
/* <Section ovf:required="false" xsi:type="ovf:ProductSection_Type">
<Info>Meta-information about the installed software</Info>
<Product>VAtest</Product>
<Vendor>SUN Microsystems</Vendor>
<Version>10.0</Version>
<ProductUrl>http://blogs.sun.com/VirtualGuru</ProductUrl>
<VendorUrl>http://www.sun.com</VendorUrl>
</Section> */
}
)
{
{
/* <System>
<vssd:Description>Description of the virtual hardware section.</vssd:Description>
<vssd:ElementName>vmware</vssd:ElementName>
<vssd:InstanceID>1</vssd:InstanceID>
<vssd:VirtualSystemIdentifier>MyLampService</vssd:VirtualSystemIdentifier>
<vssd:VirtualSystemType>vmx-4</vssd:VirtualSystemType>
</System>*/
}
{
{
)
{
i.fResourceRequired = true;
const char *pcszAttValue;
{
i.fResourceRequired = false;
}
}
{
}
else if (!strcmp(pcszItemChildName, "BusNumber")) // seen in some old OVF, but it's not listed in the OVF specs
i.ulLineNumber);
}
// store!
}
// now go thru all hardware items and handle them according to their type;
// in this first loop we handle all items _except_ hard disk images,
// which we'll handle in a second loop below
++itH)
{
// do some analysis
switch (i.resourceType)
{
case ResourceType_Processor: // 3
/* <rasd:Caption>1 virtual CPU</rasd:Caption>
<rasd:Description>Number of virtual CPUs</rasd:Description>
<rasd:ElementName>virtual CPU</rasd:ElementName>
<rasd:InstanceID>1</rasd:InstanceID>
<rasd:ResourceType>3</rasd:ResourceType>
<rasd:VirtualQuantity>1</rasd:VirtualQuantity>*/
if (i.ullVirtualQuantity < UINT16_MAX)
else
i.ulLineNumber);
break;
case ResourceType_Memory: // 4
)
else
throw OVFLogicError(N_("Error reading \"%s\": Invalid allocation unit \"%s\" specified with memory size item, line %d"),
i.strAllocationUnits.c_str(),
i.ulLineNumber);
break;
case ResourceType_IDEController: // 5
{
/* <Item>
<rasd:Caption>ideController0</rasd:Caption>
<rasd:Description>IDE Controller</rasd:Description>
<rasd:InstanceId>5</rasd:InstanceId>
<rasd:ResourceType>5</rasd:ResourceType>
<rasd:Address>0</rasd:Address>
<rasd:BusNumber>0</rasd:BusNumber>
</Item> */
if (!pPrimaryIDEController)
// this is the first IDE controller found: then mark it as "primary"
else
{
// this is the second IDE controller found: If VMware exports two
// IDE controllers, it seems that they are given an "Address" of 0
// an 1, respectively, so assume address=0 means primary controller
if ( pPrimaryIDEController->lAddress == 0
)
{
pPrimaryIDEController->fPrimary = true;
}
)
{
pPrimaryIDEController->fPrimary = false;
}
else
// then we really can't tell, just hope for the best
}
if (!pPrimaryIDEController)
break;
}
case ResourceType_ParallelSCSIHBA: // 6 SCSI controller
{
/* <Item>
<rasd:Caption>SCSI Controller 0 - LSI Logic</rasd:Caption>
<rasd:Description>SCI Controller</rasd:Description>
<rasd:ElementName>SCSI controller</rasd:ElementName>
<rasd:InstanceID>4</rasd:InstanceID>
<rasd:ResourceSubType>LsiLogic</rasd:ResourceSubType>
<rasd:ResourceType>6</rasd:ResourceType>
</Item> */
break;
}
case ResourceType_EthernetAdapter: // 10
{
/* <Item>
<rasd:Caption>Ethernet adapter on 'Bridged'</rasd:Caption>
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
<rasd:Connection>Bridged</rasd:Connection>
<rasd:InstanceID>6</rasd:InstanceID>
<rasd:ResourceType>10</rasd:ResourceType>
<rasd:ResourceSubType>E1000</rasd:ResourceSubType>
</Item>
OVF spec DSP 0243 page 21:
"For an Ethernet adapter, this specifies the abstract network connection name
for the virtual machine. All Ethernet adapters that specify the same abstract
network connection name within an OVF package shall be deployed on the same
network. The abstract network connection name shall be listed in the NetworkSection
at the outermost envelope level." */
// only store the name
break;
}
case ResourceType_FloppyDrive: // 14
break;
case ResourceType_CDDrive: // 15
/* <Item ovf:required="false">
<rasd:Caption>cdrom1</rasd:Caption>
<rasd:InstanceId>7</rasd:InstanceId>
<rasd:ResourceType>15</rasd:ResourceType>
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
<rasd:Parent>5</rasd:Parent>
<rasd:AddressOnParent>0</rasd:AddressOnParent>
</Item> */
// I tried to see what happens if I set an ISO for the CD-ROM in VMware Workstation,
// but then the ovftool dies with "Device backing not supported". So I guess if
// VMware can't export ISOs, then we don't need to be able to import them right now.
break;
case ResourceType_HardDisk: // 17
// handled separately in second loop below
break;
case ResourceType_OtherStorageDevice: // 20 SATA controller
{
/* <Item>
<rasd:Description>SATA Controller</rasd:Description>
<rasd:Caption>sataController0</rasd:Caption>
<rasd:InstanceID>4</rasd:InstanceID>
<rasd:ResourceType>20</rasd:ResourceType>
<rasd:ResourceSubType>AHCI</rasd:ResourceSubType>
<rasd:Address>0</rasd:Address>
<rasd:BusNumber>0</rasd:BusNumber>
</Item> */
)
{
}
else
throw OVFLogicError(N_("Error reading \"%s\": Host resource of type \"Other Storage Device (%d)\" is supported with SATA AHCI controllers only, line %d"),
i.ulLineNumber);
break;
}
case ResourceType_USBController: // 23
/* <Item ovf:required="false">
<rasd:Caption>usb</rasd:Caption>
<rasd:Description>USB Controller</rasd:Description>
<rasd:InstanceId>3</rasd:InstanceId>
<rasd:ResourceType>23</rasd:ResourceType>
<rasd:Address>0</rasd:Address>
<rasd:BusNumber>0</rasd:BusNumber>
</Item> */
break;
case ResourceType_SoundCard: // 35
/* <Item ovf:required="false">
<rasd:Caption>sound</rasd:Caption>
<rasd:Description>Sound Card</rasd:Description>
<rasd:InstanceId>10</rasd:InstanceId>
<rasd:ResourceType>35</rasd:ResourceType>
<rasd:ResourceSubType>ensoniq1371</rasd:ResourceSubType>
<rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
<rasd:AddressOnParent>3</rasd:AddressOnParent>
</Item> */
break;
default:
{
/* If this unknown resource type isn't required, we simply skip it. */
if (i.fResourceRequired)
{
i.resourceType,
i.ulLineNumber);
}
}
} // end switch
}
// now run through the items for a second time, but handle only
// hard disk images; otherwise the code would fail if a hard
// disk image appears in the OVF before its hard disk controller
++itH)
{
// do some analysis
switch (i.resourceType)
{
case ResourceType_CDDrive: // 15
/* <Item ovf:required="false">
<rasd:Caption>cdrom1</rasd:Caption>
<rasd:InstanceId>7</rasd:InstanceId>
<rasd:ResourceType>15</rasd:ResourceType>
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
<rasd:Parent>5</rasd:Parent>
<rasd:AddressOnParent>0</rasd:AddressOnParent>
</Item> */
case ResourceType_HardDisk: // 17
{
/* <Item>
<rasd:Caption>Harddisk 1</rasd:Caption>
<rasd:Description>HD</rasd:Description>
<rasd:ElementName>Hard Disk</rasd:ElementName>
<rasd:InstanceID>5</rasd:InstanceID>
<rasd:Parent>4</rasd:Parent>
<rasd:ResourceType>17</rasd:ResourceType>
</Item> */
// look up the hard disk controller element whose InstanceID equals our Parent;
// this is how the connection is specified in OVF
throw OVFLogicError(N_("Error reading \"%s\": Disk item with instance ID %d specifies invalid parent %d, line %d"),
i.ulInstanceID,
i.ulParent,
i.ulLineNumber);
// 123456789012345
//the error may be missed for CD, because CD can be empty
&& i.resourceType == ResourceType_HardDisk)
throw OVFLogicError(N_("Error reading \"%s\": Disk item with instance ID %d specifies invalid host resource \"%s\", line %d"),
i.ulInstanceID,
i.strHostResource.c_str(),
i.ulLineNumber);
break;
}
default:
break;
}
}
}
)
{
throw OVFLogicError(N_("Error reading \"%s\": missing or invalid 'ovf:id' attribute in operating system section element, line %d"),
pelmThis->getLineNumber());
"OSType"))) // element name
}
)
{
}
}
// now create the virtual system
}
////////////////////////////////////////////////////////////////////////////////
//
// Errors
//
////////////////////////////////////////////////////////////////////////////////
{
char *pszNewMsg;
}