/* $Id: ApplianceImpl.cpp 17418 2009-03-05 16:48:30Z vboxsync $ */ /** @file * * IAppliance and IVirtualSystem COM class implementations. */ /* * Copyright (C) 2008-2009 Sun Microsystems, Inc. * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * 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. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 USA or visit http://www.sun.com if you need * additional information or have any questions. */ #include #include #include #include #include "ApplianceImpl.h" #include "VirtualBoxImpl.h" #include "GuestOSTypeImpl.h" #include "ProgressImpl.h" #include "MachineImpl.h" #include "Logging.h" #include "VBox/xml.h" using namespace std; //////////////////////////////////////////////////////////////////////////////// // // hardware definitions // //////////////////////////////////////////////////////////////////////////////// struct DiskImage { Utf8Str strDiskId; // value from DiskSection/Disk/@diskId int64_t iCapacity; // value from DiskSection/Disk/@capacity; // (maximum size for dynamic images, I guess; we always translate this to bytes) int64_t iPopulatedSize; // value from DiskSection/Disk/@populatedSize // (actual used size of disk, always in bytes; can be an estimate of used disk // space, but cannot be larger than iCapacity) Utf8Str strFormat; // value from DiskSection/Disk/@format // typically http://www.vmware.com/specifications/vmdk.html#sparse // fields from /References/File; the spec says the file reference from disk can be empty, // so in that case, strFilename will be empty, then a new disk should be created Utf8Str strHref; // value from /References/File/@href (filename); if empty, then the remaining fields are ignored int64_t iSize; // value from /References/File/@size (optional according to spec; then we set -1 here) int64_t iChunkSize; // value from /References/File/@chunkSize (optional, unsupported) Utf8Str strCompression; // value from /References/File/@compression (optional, can be "gzip" according to spec) }; struct Network { Utf8Str strNetworkName; // value from NetworkSection/Network/@name // unfortunately the OVF spec is unspecific about how networks should be specified further }; struct VirtualHardwareItem { Utf8Str strDescription; Utf8Str strCaption; Utf8Str strElementName; uint32_t ulInstanceID; uint32_t ulParent; OVFResourceType_T resourceType; Utf8Str strOtherResourceType; Utf8Str strResourceSubType; Utf8Str strHostResource; // "Abstractly specifies how a device shall connect to a resource on the deployment platform. // Not all devices need a backing." Used with disk items, for which this references a virtual // disk from the Disks section. bool fAutomaticAllocation; bool fAutomaticDeallocation; Utf8Str strConnection; // "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." Utf8Str strAddress; // "Device-specific. For an Ethernet adapter, this specifies the MAC address." Utf8Str strAddressOnParent; // "For a device, this specifies its location on the controller." Utf8Str strAllocationUnits; // "Specifies the units of allocation used. For example, “byte * 2^20”." uint64_t ullVirtualQuantity; // "Specifies the quantity of resources presented. For example, “256”." uint64_t ullReservation; // "Specifies the minimum quantity of resources guaranteed to be available." uint64_t ullLimit; // "Specifies the maximum quantity of resources that will be granted." uint64_t ullWeight; // "Specifies a relative priority for this allocation in relation to other allocations." Utf8Str strConsumerVisibility; Utf8Str strMappingBehavior; Utf8Str strPoolID; uint32_t ulBusNumber; // seen with IDE controllers, but not listed in OVF spec uint32_t ulLineNumber; // line number of element in XML source; cached for error messages VirtualHardwareItem() : ulInstanceID(0), fAutomaticAllocation(false), fAutomaticDeallocation(false), ullVirtualQuantity(0), ullReservation(0), ullLimit(0), ullWeight(0), ulBusNumber(0), ulLineNumber(0) {}; }; typedef map DiskImagesMap; typedef map NetworksMap; struct VirtualSystem; typedef map HardwareItemsMap; struct HardDiskController { uint32_t idController; // instance ID (Item/InstanceId); this gets referenced from HardDisk enum ControllerSystemType { IDE, SATA, SCSI }; ControllerSystemType system; // one of IDE, SATA, SCSI Utf8Str strControllerType; // controller subtype (Item/ResourceSubType); e.g. "LsiLogic"; can be empty (esp. for IDE) Utf8Str strAddress; // for IDE uint32_t ulBusNumber; // for IDE HardDiskController() : idController(0), ulBusNumber(0) { } }; typedef map ControllersMap; struct VirtualDisk { uint32_t idController; // SCSI (or IDE) controller this disk is connected to; // points into VirtualSystem.mapControllers uint32_t ulAddressOnParent; // parsed strAddressOnParent of hardware item; will be 0 or 1 for IDE // and possibly higher for disks attached to SCSI controllers (untested) Utf8Str strDiskId; // if the hard disk has an ovf:/disk/ reference, // this receives the component; points to one of the // references in Appliance::Data.mapDisks }; typedef map VirtualDisksMap; struct VirtualSystem { Utf8Str strName; // copy of VirtualSystem/@id CIMOSType_T cimos; Utf8Str strVirtualSystemType; // generic hardware description; OVF says this can be something like "vmx-4" or "xen"; // VMware Workstation 6.5 is "vmx-07" HardwareItemsMap mapHardwareItems; // map of virtual hardware items, sorted by unique instance ID uint64_t ullMemorySize; // always in bytes, copied from llHardwareItems; default = 0 (unspecified) uint16_t cCPUs; // no. of CPUs, copied from llHardwareItems; default = 1 list llNetworkNames; // list of strings referring to network names // (one for each VirtualSystem/Item[@ResourceType=10]/Connection element) ControllersMap mapControllers; // list of hard disk controllers // (one for each VirtualSystem/Item[@ResourceType=6] element with accumulated data from children) VirtualDisksMap mapVirtualDisks; // (one for each VirtualSystem/Item[@ResourceType=17] element with accumulated data from children) bool fHasFloppyDrive; // true if there's a floppy item in mapHardwareItems bool fHasCdromDrive; // true if there's a CD-ROM item in mapHardwareItems; ISO images are not yet supported by OVFtool bool fHasUsbController; // true if there's a USB controller item in mapHardwareItems Utf8Str strSoundCardType; // if not empty, then the system wants a soundcard; this then specifies the hardware; // VMware Workstation 6.5 uses "ensoniq1371" for example Utf8Str strLicenceInfo; // license info if any; receives contents of VirtualSystem/EulaSection/Info Utf8Str strLicenceText; // license info if any; receives contents of VirtualSystem/EulaSection/License VirtualSystem() : ullMemorySize(0), cCPUs(1), fHasFloppyDrive(false), fHasCdromDrive(false), fHasUsbController(false) { } }; //////////////////////////////////////////////////////////////////////////////// // // Appliance data definition // //////////////////////////////////////////////////////////////////////////////// // opaque private instance data of Appliance class struct Appliance::Data { Utf8Str strPath; // file name last given to either read() or write() DiskImagesMap mapDisks; // map of DiskImage structs, sorted by DiskImage.strDiskId NetworksMap mapNetworks; // map of Network structs, sorted by Network.strNetworkName list llVirtualSystems; // list of virtual systems, created by and valid after read() list< ComObjPtr > virtualSystemDescriptions; // }; struct VirtualSystemDescription::Data { list llDescriptions; list llWarnings; }; //////////////////////////////////////////////////////////////////////////////// // // Threads // //////////////////////////////////////////////////////////////////////////////// struct Appliance::TaskImportMachines { TaskImportMachines(Appliance *aThat, Progress *aProgress) : pAppliance(aThat) , progress(aProgress) , rc(S_OK) {} ~TaskImportMachines() {} HRESULT startThread(); Appliance *pAppliance; ComObjPtr progress; HRESULT rc; }; struct Appliance::TaskExportOVF { TaskExportOVF(Appliance *aThat, Progress *aProgress) : pAppliance(aThat) , progress(aProgress) , rc(S_OK) {} ~TaskExportOVF() {} HRESULT startThread(); Appliance *pAppliance; ComObjPtr progress; HRESULT rc; }; //////////////////////////////////////////////////////////////////////////////// // // globals // //////////////////////////////////////////////////////////////////////////////// static Utf8Str stripFilename(const Utf8Str &strFile) { Utf8Str str2(strFile); RTPathStripFilename(str2.mutableRaw()); return str2; } //////////////////////////////////////////////////////////////////////////////// // // IVirtualBox public methods // //////////////////////////////////////////////////////////////////////////////// // This code is here so we won't have to include the appliance headers in the // IVirtualBox implementation. /** * Implementation for IVirtualBox::createAppliance. * * @param anAppliance IAppliance object created if S_OK is returned. * @return S_OK or error. */ STDMETHODIMP VirtualBox::CreateAppliance(IAppliance** anAppliance) { HRESULT rc; ComObjPtr appliance; appliance.createObject(); rc = appliance->init(this); if (SUCCEEDED(rc)) appliance.queryInterfaceTo(anAppliance); return rc; } //////////////////////////////////////////////////////////////////////////////// // // Appliance::task methods // //////////////////////////////////////////////////////////////////////////////// HRESULT Appliance::TaskImportMachines::startThread() { int vrc = RTThreadCreate(NULL, Appliance::taskThreadImportMachines, this, 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0, "Appliance::Task"); ComAssertMsgRCRet(vrc, ("Could not create taskThreadImportMachines (%Rrc)\n", vrc), E_FAIL); return S_OK; } HRESULT Appliance::TaskExportOVF::startThread() { int vrc = RTThreadCreate(NULL, Appliance::taskThreadExportOVF, this, 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0, "Appliance::Task"); ComAssertMsgRCRet(vrc, ("Could not create taskThreadExportOVF (%Rrc)\n", vrc), E_FAIL); return S_OK; } //////////////////////////////////////////////////////////////////////////////// // // Appliance constructor / destructor // //////////////////////////////////////////////////////////////////////////////// DEFINE_EMPTY_CTOR_DTOR(Appliance) struct shutup {}; /** * Appliance COM initializer. * @param * @return */ HRESULT Appliance::init(VirtualBox *aVirtualBox) { /* Enclose the state transition NotReady->InInit->Ready */ AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); /* Weak reference to a VirtualBox object */ unconst(mVirtualBox) = aVirtualBox; // initialize data m = new Data; /* Confirm a successful initialization */ autoInitSpan.setSucceeded(); return S_OK; } /** * Appliance COM uninitializer. * @return */ void Appliance::uninit() { delete m; m = NULL; } //////////////////////////////////////////////////////////////////////////////// // // Appliance private methods // //////////////////////////////////////////////////////////////////////////////// /** * 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 */ HRESULT Appliance::LoopThruSections(const char *pcszPath, const xml::Node *pReferencesElem, const xml::Node *pCurElem) { HRESULT rc; xml::NodesLoop loopChildren(*pCurElem); const xml::Node *pElem; while ((pElem = loopChildren.forAllNodes())) { const char *pcszElemName = pElem->getName(); const char *pcszTypeAttr = ""; const xml::Node *pTypeAttr; if ((pTypeAttr = pElem->findAttribute("type"))) pcszTypeAttr = pTypeAttr->getValue(); if ( (!strcmp(pcszElemName, "DiskSection")) || ( (!strcmp(pcszElemName, "Section")) && (!strcmp(pcszTypeAttr, "ovf:DiskSection_Type")) ) ) { if (!(SUCCEEDED((rc = HandleDiskSection(pcszPath, pReferencesElem, pElem))))) return rc; } else if ( (!strcmp(pcszElemName, "NetworkSection")) || ( (!strcmp(pcszElemName, "Section")) && (!strcmp(pcszTypeAttr, "ovf:NetworkSection_Type")) ) ) { if (!(SUCCEEDED((rc = HandleNetworkSection(pcszPath, pElem))))) return rc; } else if ( (!strcmp(pcszElemName, "DeploymentOptionSection>"))) { // TODO } else if ( (!strcmp(pcszElemName, "Info"))) { // child of VirtualSystemCollection -- TODO } else if ( (!strcmp(pcszElemName, "ResourceAllocationSection"))) { // child of VirtualSystemCollection -- TODO } else if ( (!strcmp(pcszElemName, "StartupSection"))) { // child of VirtualSystemCollection -- TODO } else if ( (!strcmp(pcszElemName, "VirtualSystem")) || ( (!strcmp(pcszElemName, "Content")) && (!strcmp(pcszTypeAttr, "ovf:VirtualSystem_Type")) ) ) { if (!(SUCCEEDED((rc = HandleVirtualSystemContent(pcszPath, pElem))))) return rc; } else if ( (!strcmp(pcszElemName, "VirtualSystemCollection")) || ( (!strcmp(pcszElemName, "Content")) && (!strcmp(pcszTypeAttr, "ovf:VirtualSystemCollection_Type")) ) ) { // TODO ResourceAllocationSection // recurse for this, since it has VirtualSystem elements as children if (!(SUCCEEDED((rc = LoopThruSections(pcszPath, pReferencesElem, pElem))))) return rc; } } return S_OK; } /** * 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 */ HRESULT Appliance::HandleDiskSection(const char *pcszPath, const xml::Node *pReferencesElem, const xml::Node *pSectionElem) { // contains "Disk" child elements xml::NodesLoop loopDisks(*pSectionElem, "Disk"); const xml::Node *pelmDisk; while ((pelmDisk = loopDisks.forAllNodes())) { DiskImage d; const char *pcszBad = NULL; if (!(pelmDisk->getAttributeValue("diskId", d.strDiskId))) pcszBad = "diskId"; else if (!(pelmDisk->getAttributeValue("format", d.strFormat))) pcszBad = "format"; else if (!(pelmDisk->getAttributeValue("capacity", d.iCapacity))) pcszBad = "capacity"; else { if (!(pelmDisk->getAttributeValue("populatedSize", d.iPopulatedSize))) // optional d.iPopulatedSize = -1; Utf8Str strFileRef; if (pelmDisk->getAttributeValue("fileRef", strFileRef)) // optional { // look up corresponding /References/File nodes (list built above) const xml::Node *pFileElem; if ( pReferencesElem && ((pFileElem = pReferencesElem->findChildElementFromId(strFileRef.c_str()))) ) { // copy remaining values from file node then const char *pcszBadInFile = NULL; if (!(pFileElem->getAttributeValue("href", d.strHref))) pcszBadInFile = "href"; else if (!(pFileElem->getAttributeValue("size", d.iSize))) d.iSize = -1; // optional // if (!(pFileElem->getAttributeValue("size", d.iChunkSize))) TODO d.iChunkSize = -1; // optional pFileElem->getAttributeValue("compression", d.strCompression); if (pcszBadInFile) return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": missing or invalid attribute '%s' in 'File' element, line %d"), pcszPath, pcszBadInFile, pFileElem->getLineNumber()); } else return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": cannot find References/File element for ID '%s' referenced by 'Disk' element, line %d"), pcszPath, strFileRef.c_str(), pelmDisk->getLineNumber()); } } if (pcszBad) return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": missing or invalid attribute '%s' in 'DiskSection' element, line %d"), pcszPath, pcszBad, pelmDisk->getLineNumber()); m->mapDisks[d.strDiskId] = d; } return S_OK; } /** * 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 */ HRESULT Appliance::HandleNetworkSection(const char *pcszPath, const xml::Node *pSectionElem) { // contains "Disk" child elements 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; } return S_OK; } /** * Private helper method that handles a "VirtualSystem" element in the OVF XML. * Gets called indirectly from IAppliance::read(). * * @param pcszPath * @param pContentElem * @return */ HRESULT Appliance::HandleVirtualSystemContent(const char *pcszPath, const xml::Node *pelmVirtualSystem) { VirtualSystem vsys; const xml::Node *pIdAttr = pelmVirtualSystem->findAttribute("id"); if (pIdAttr) vsys.strName = pIdAttr->getValue(); xml::NodesLoop loop(*pelmVirtualSystem); // all child elements const xml::Node *pelmThis; while ((pelmThis = loop.forAllNodes())) { const char *pcszElemName = pelmThis->getName(); const xml::Node *pTypeAttr = pelmThis->findAttribute("type"); const char *pcszTypeAttr = (pTypeAttr) ? pTypeAttr->getValue() : ""; if (!strcmp(pcszElemName, "EulaSection")) { /* License agreement for the Virtual System. License terms can go in here. */ const xml::Node *pelmInfo, *pelmLicense; if ( ((pelmInfo = pelmThis->findChildElement("Info"))) && ((pelmLicense = pelmThis->findChildElement("License"))) ) { vsys.strLicenceInfo = pelmInfo->getValue(); vsys.strLicenceText = pelmLicense->getValue(); } } else if ( (!strcmp(pcszElemName, "VirtualHardwareSection")) || (!strcmp(pcszTypeAttr, "ovf:VirtualHardwareSection_Type")) ) { const xml::Node *pelmSystem, *pelmVirtualSystemType; if ((pelmSystem = pelmThis->findChildElement("System"))) { /* Description of the virtual hardware section. vmware 1 MyLampService vmx-4 */ if ((pelmVirtualSystemType = pelmSystem->findChildElement("VirtualSystemType"))) vsys.strVirtualSystemType = pelmVirtualSystemType->getValue(); } xml::NodesLoop loopVirtualHardwareItems(*pelmThis, "Item"); // all "Item" child elements const xml::Node *pelmItem; while ((pelmItem = loopVirtualHardwareItems.forAllNodes())) { VirtualHardwareItem i; i.ulLineNumber = pelmItem->getLineNumber(); xml::NodesLoop loopItemChildren(*pelmItem); // all child elements const xml::Node *pelmItemChild; while ((pelmItemChild = loopItemChildren.forAllNodes())) { const char *pcszItemChildName = pelmItemChild->getName(); if (!strcmp(pcszItemChildName, "Description")) i.strDescription = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "Caption")) i.strCaption = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "ElementName")) i.strElementName = pelmItemChild->getValue(); else if ( (!strcmp(pcszItemChildName, "InstanceID")) || (!strcmp(pcszItemChildName, "InstanceId")) ) pelmItemChild->copyValue(i.ulInstanceID); else if (!strcmp(pcszItemChildName, "HostResource")) i.strHostResource = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "ResourceType")) { int32_t iType; /** @todo how to fix correctly? (enum fun.) */ pelmItemChild->copyValue(iType); i.resourceType = (OVFResourceType_T)iType; } else if (!strcmp(pcszItemChildName, "OtherResourceType")) i.strOtherResourceType = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "ResourceSubType")) i.strResourceSubType = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "AutomaticAllocation")) i.fAutomaticAllocation = (!strcmp(pelmItemChild->getValue(), "true")) ? true : false; else if (!strcmp(pcszItemChildName, "AutomaticDeallocation")) i.fAutomaticDeallocation = (!strcmp(pelmItemChild->getValue(), "true")) ? true : false; else if (!strcmp(pcszItemChildName, "Parent")) pelmItemChild->copyValue(i.ulParent); else if (!strcmp(pcszItemChildName, "Connection")) i.strConnection = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "Address")) i.strAddress = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "AddressOnParent")) i.strAddressOnParent = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "AllocationUnits")) i.strAllocationUnits = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "VirtualQuantity")) pelmItemChild->copyValue(i.ullVirtualQuantity); else if (!strcmp(pcszItemChildName, "Reservation")) pelmItemChild->copyValue(i.ullReservation); else if (!strcmp(pcszItemChildName, "Limit")) pelmItemChild->copyValue(i.ullLimit); else if (!strcmp(pcszItemChildName, "Weight")) pelmItemChild->copyValue(i.ullWeight); else if (!strcmp(pcszItemChildName, "ConsumerVisibility")) i.strConsumerVisibility = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "MappingBehavior")) i.strMappingBehavior = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "PoolID")) i.strPoolID = pelmItemChild->getValue(); else if (!strcmp(pcszItemChildName, "BusNumber")) pelmItemChild->copyValue(i.ulBusNumber); else return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": unknown element \"%s\" under Item element, line %d"), pcszPath, pcszItemChildName, i.ulLineNumber); } // store! vsys.mapHardwareItems[i.ulInstanceID] = i; } HardwareItemsMap::const_iterator itH; for (itH = vsys.mapHardwareItems.begin(); itH != vsys.mapHardwareItems.end(); ++itH) { const VirtualHardwareItem &i = itH->second; // do some analysis switch (i.resourceType) { case OVFResourceType_Processor: // 3 /* 1 virtual CPU Number of virtual CPUs virtual CPU 1 3 1*/ if (i.ullVirtualQuantity < UINT16_MAX) vsys.cCPUs = (uint16_t)i.ullVirtualQuantity; else return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": CPU count %RI64 is larger than %d, line %d"), pcszPath, i.ullVirtualQuantity, UINT16_MAX, i.ulLineNumber); break; case OVFResourceType_Memory: // 4 if ( (i.strAllocationUnits == "MegaBytes") // found in OVF created by OVF toolkit || (i.strAllocationUnits == "MB") // found in MS docs || (i.strAllocationUnits == "byte * 2^20") // suggested by OVF spec DSP0243 page 21 ) vsys.ullMemorySize = i.ullVirtualQuantity * 1024 * 1024; else return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": Invalid allocation unit \"%s\" specified with memory size item, line %d"), pcszPath, i.strAllocationUnits.c_str(), i.ulLineNumber); break; case OVFResourceType_IdeController: // 5 IdeController { /* ideController0 IDE Controller 5 5 0 0 */ HardDiskController hdc; hdc.system = HardDiskController::IDE; hdc.idController = i.ulInstanceID; hdc.strAddress = i.strAddress; hdc.ulBusNumber = i.ulBusNumber; vsys.mapControllers[i.ulInstanceID] = hdc; } break; case OVFResourceType_ParallelScsiHba: // 6 SCSI controller { /* SCSI Controller 0 - LSI Logic SCI Controller SCSI controller 4 LsiLogic 6 */ HardDiskController hdc; hdc.system = HardDiskController::SCSI; hdc.idController = i.ulInstanceID; hdc.strControllerType = i.strResourceSubType; vsys.mapControllers[i.ulInstanceID] = hdc; } break; case OVFResourceType_EthernetAdapter: // 10 { /* true Ethernet adapter on 'VM Network' VM Network VM Network? Ethernet adapter 3 10 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." */ // make sure we have a matching NetworkSection/Network NetworksMap::iterator it = m->mapNetworks.find(i.strConnection); if (it == m->mapNetworks.end()) return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": Invalid connection \"%s\"; cannot find matching NetworkSection/Network element, line %d"), pcszPath, i.strConnection.c_str(), i.ulLineNumber); vsys.llNetworkNames.push_back(i.strConnection); } break; case OVFResourceType_FloppyDrive: // 14 vsys.fHasFloppyDrive = true; // we have no additional information break; case OVFResourceType_CdDrive: // 15 /* cdrom1 7 15 true 5 0 */ // 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. vsys.fHasCdromDrive = true; // we have no additional information break; case OVFResourceType_HardDisk: // 17 { /* Harddisk 1 HD Hard Disk ovf://disk/lamp 5 4 17 */ // look up the hard disk controller element whose InstanceID equals our Parent; // this is how the connection is specified in OVF ControllersMap::const_iterator it = vsys.mapControllers.find(i.ulParent); if (it == vsys.mapControllers.end()) return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": Hard disk item with instance ID %d specifies invalid parent %d, line %d"), pcszPath, i.ulInstanceID, i.ulParent, i.ulLineNumber); const HardDiskController &hdc = it->second; VirtualDisk vd; vd.idController = i.ulParent; i.strAddressOnParent.toInt(vd.ulAddressOnParent); bool fFound = false; // ovf://disk/lamp // 12345678901234 if (i.strHostResource.substr(0, 11) == "ovf://disk/") vd.strDiskId = i.strHostResource.substr(11); else if (i.strHostResource.substr(0, 6) == "/disk/") vd.strDiskId = i.strHostResource.substr(6); if ( !(vd.strDiskId.length()) || (m->mapDisks.find(vd.strDiskId) == m->mapDisks.end()) ) return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": Hard disk item with instance ID %d specifies invalid host resource \"%s\", line %d"), pcszPath, i.ulInstanceID, i.strHostResource.c_str(), i.ulLineNumber); vsys.mapVirtualDisks[vd.strDiskId] = vd; } break; case OVFResourceType_UsbController: // 23 /* usb USB Controller 3 23 0 0 */ vsys.fHasUsbController = true; // we have no additional information break; case OVFResourceType_SoundCard: // 35 /* sound Sound Card 10 35 ensoniq1371 false 3 */ vsys.strSoundCardType = i.strResourceSubType; break; default: return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": Unknown resource type %d in hardware item, line %d"), pcszPath, i.resourceType, i.ulLineNumber); } } } else if ( (!strcmp(pcszElemName, "OperatingSystemSection")) || (!strcmp(pcszTypeAttr, "ovf:OperatingSystemSection_Type")) ) { uint64_t cimos64; if (!(pelmThis->getAttributeValue("id", cimos64))) return setError(VBOX_E_FILE_ERROR, tr("Error reading \"%s\": missing or invalid 'ovf:id' attribute in operating system section element, line %d"), pcszPath, pelmThis->getLineNumber()); vsys.cimos = (CIMOSType_T)cimos64; } } // now create the virtual system m->llVirtualSystems.push_back(vsys); return S_OK; } //////////////////////////////////////////////////////////////////////////////// // // IAppliance public methods // //////////////////////////////////////////////////////////////////////////////// /** * Public method implementation. * @param * @return */ STDMETHODIMP Appliance::COMGETTER(Path)(BSTR *aPath) { if (!aPath) return E_POINTER; AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoReadLock alock(this); Bstr bstrPath(m->strPath); bstrPath.cloneTo(aPath); return S_OK; } /** * Public method implementation. * @param * @return */ STDMETHODIMP Appliance::COMGETTER(Disks)(ComSafeArrayOut(BSTR, aDisks)) { CheckComArgOutSafeArrayPointerValid(aDisks); AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoReadLock alock(this); size_t c = m->mapDisks.size(); com::SafeArray sfaDisks(c); DiskImagesMap::const_iterator it; size_t i = 0; for (it = m->mapDisks.begin(); it != m->mapDisks.end(); ++it, ++i) { // create a string representing this disk const DiskImage &d = it->second; char *psz = NULL; RTStrAPrintf(&psz, "%s\t" "%RI64\t" "%RI64\t" "%s\t" "%s\t" "%RI64\t" "%RI64\t" "%s", d.strDiskId.c_str(), d.iCapacity, d.iPopulatedSize, d.strFormat.c_str(), d.strHref.c_str(), d.iSize, d.iChunkSize, d.strCompression.c_str()); Utf8Str utf(psz); Bstr bstr(utf); // push to safearray bstr.cloneTo(&sfaDisks[i]); RTStrFree(psz); } sfaDisks.detachTo(ComSafeArrayOutArg(aDisks)); return S_OK; } /** * Public method implementation. * @param * @return */ STDMETHODIMP Appliance::COMGETTER(VirtualSystemDescriptions)(ComSafeArrayOut(IVirtualSystemDescription*, aVirtualSystemDescriptions)) { CheckComArgOutSafeArrayPointerValid(aVirtualSystemDescriptions); AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoReadLock alock(this); SafeIfaceArray sfaVSD(m->virtualSystemDescriptions); sfaVSD.detachTo(ComSafeArrayOutArg(aVirtualSystemDescriptions)); return S_OK; } /** * Private helper func that suggests a VirtualBox guest OS type * for the given OVF operating system type. * @param osTypeVBox * @param c */ static void convertCIMOSType2VBoxOSType(Utf8Str &osTypeVBox, CIMOSType_T c) { switch (c) { case CIMOSType_CIMOS_Unknown: // 0 - Unknown osTypeVBox = SchemaDefs_OSTypeId_Other; break; case CIMOSType_CIMOS_OS2: // 12 - OS/2 osTypeVBox = SchemaDefs_OSTypeId_OS2; break; case CIMOSType_CIMOS_MSDOS: // 14 - MSDOS osTypeVBox = SchemaDefs_OSTypeId_DOS; break; case CIMOSType_CIMOS_WIN3x: // 15 - WIN3x osTypeVBox = SchemaDefs_OSTypeId_Windows31; break; case CIMOSType_CIMOS_WIN95: // 16 - WIN95 osTypeVBox = SchemaDefs_OSTypeId_Windows95; break; case CIMOSType_CIMOS_WIN98: // 17 - WIN98 osTypeVBox = SchemaDefs_OSTypeId_Windows98; break; case CIMOSType_CIMOS_WINNT: // 18 - WINNT osTypeVBox = SchemaDefs_OSTypeId_WindowsNT4; break; case CIMOSType_CIMOS_NetWare: // 21 - NetWare case CIMOSType_CIMOS_NovellOES: // 86 - Novell OES osTypeVBox = SchemaDefs_OSTypeId_Netware; break; case CIMOSType_CIMOS_Solaris: // 29 - Solaris case CIMOSType_CIMOS_SunOS: // 30 - SunOS osTypeVBox = SchemaDefs_OSTypeId_Solaris; break; case CIMOSType_CIMOS_FreeBSD: // 42 - FreeBSD osTypeVBox = SchemaDefs_OSTypeId_FreeBSD; break; case CIMOSType_CIMOS_NetBSD: // 43 - NetBSD osTypeVBox = SchemaDefs_OSTypeId_NetBSD; break; case CIMOSType_CIMOS_QNX: // 48 - QNX osTypeVBox = SchemaDefs_OSTypeId_QNX; break; case CIMOSType_CIMOS_Windows2000: // 58 - Windows 2000 osTypeVBox = SchemaDefs_OSTypeId_Windows2000; break; case CIMOSType_CIMOS_WindowsMe: // 63 - Windows (R) Me osTypeVBox = SchemaDefs_OSTypeId_WindowsMe; break; case CIMOSType_CIMOS_OpenBSD: // 65 - OpenBSD osTypeVBox = SchemaDefs_OSTypeId_OpenBSD; break; case CIMOSType_CIMOS_WindowsXP: // 67 - Windows XP case CIMOSType_CIMOS_WindowsXPEmbedded: // 72 - Windows XP Embedded case CIMOSType_CIMOS_WindowsEmbeddedforPointofService: // 75 - Windows Embedded for Point of Service osTypeVBox = SchemaDefs_OSTypeId_WindowsXP; break; case CIMOSType_CIMOS_MicrosoftWindowsServer2003: // 69 - Microsoft Windows Server 2003 osTypeVBox = SchemaDefs_OSTypeId_Windows2003; break; case CIMOSType_CIMOS_MicrosoftWindowsServer2003_64: // 70 - Microsoft Windows Server 2003 64-Bit osTypeVBox = SchemaDefs_OSTypeId_Windows2003_64; break; case CIMOSType_CIMOS_WindowsXP_64: // 71 - Windows XP 64-Bit osTypeVBox = SchemaDefs_OSTypeId_WindowsXP_64; break; case CIMOSType_CIMOS_WindowsVista: // 73 - Windows Vista osTypeVBox = SchemaDefs_OSTypeId_WindowsVista; break; case CIMOSType_CIMOS_WindowsVista_64: // 74 - Windows Vista 64-Bit osTypeVBox = SchemaDefs_OSTypeId_WindowsVista_64; break; case CIMOSType_CIMOS_MicrosoftWindowsServer2008: // 76 - Microsoft Windows Server 2008 osTypeVBox = SchemaDefs_OSTypeId_Windows2008; break; case CIMOSType_CIMOS_MicrosoftWindowsServer2008_64: // 77 - Microsoft Windows Server 2008 64-Bit osTypeVBox = SchemaDefs_OSTypeId_Windows2008_64; break; case CIMOSType_CIMOS_FreeBSD_64: // 78 - FreeBSD 64-Bit osTypeVBox = SchemaDefs_OSTypeId_FreeBSD_64; break; case CIMOSType_CIMOS_RedHatEnterpriseLinux: // 79 - RedHat Enterprise Linux osTypeVBox = SchemaDefs_OSTypeId_RedHat; break; case CIMOSType_CIMOS_RedHatEnterpriseLinux_64: // 80 - RedHat Enterprise Linux 64-Bit osTypeVBox = SchemaDefs_OSTypeId_RedHat_64; break; case CIMOSType_CIMOS_Solaris_64: // 81 - Solaris 64-Bit osTypeVBox = SchemaDefs_OSTypeId_Solaris_64; break; case CIMOSType_CIMOS_SUSE: // 82 - SUSE case CIMOSType_CIMOS_SLES: // 84 - SLES case CIMOSType_CIMOS_NovellLinuxDesktop: // 87 - Novell Linux Desktop osTypeVBox = SchemaDefs_OSTypeId_OpenSUSE; break; case CIMOSType_CIMOS_SUSE_64: // 83 - SUSE 64-Bit case CIMOSType_CIMOS_SLES_64: // 85 - SLES 64-Bit osTypeVBox = SchemaDefs_OSTypeId_OpenSUSE_64; break; case CIMOSType_CIMOS_LINUX: // 36 - LINUX case CIMOSType_CIMOS_SunJavaDesktopSystem: // 88 - Sun Java Desktop System case CIMOSType_CIMOS_TurboLinux: // 91 - TurboLinux osTypeVBox = SchemaDefs_OSTypeId_Linux; break; // case CIMOSType_CIMOS_TurboLinux_64: // 92 - TurboLinux 64-Bit // case CIMOSType_CIMOS_Linux_64: // 101 - Linux 64-Bit // osTypeVBox = VBOXOSTYPE_Linux_x64; // break; case CIMOSType_CIMOS_Mandriva: // 89 - Mandriva osTypeVBox = SchemaDefs_OSTypeId_Mandriva; break; case CIMOSType_CIMOS_Mandriva_64: // 90 - Mandriva 64-Bit osTypeVBox = SchemaDefs_OSTypeId_Mandriva_64; break; case CIMOSType_CIMOS_Ubuntu: // 93 - Ubuntu osTypeVBox = SchemaDefs_OSTypeId_Ubuntu; break; case CIMOSType_CIMOS_Ubuntu_64: // 94 - Ubuntu 64-Bit osTypeVBox = SchemaDefs_OSTypeId_Ubuntu_64; break; case CIMOSType_CIMOS_Debian: // 95 - Debian osTypeVBox = SchemaDefs_OSTypeId_Debian; break; case CIMOSType_CIMOS_Debian_64: // 96 - Debian 64-Bit osTypeVBox = SchemaDefs_OSTypeId_Debian_64; break; case CIMOSType_CIMOS_Linux_2_4_x: // 97 - Linux 2.4.x osTypeVBox = SchemaDefs_OSTypeId_Linux24; break; case CIMOSType_CIMOS_Linux_2_4_x_64: // 98 - Linux 2.4.x 64-Bit osTypeVBox = SchemaDefs_OSTypeId_Linux24_64; break; case CIMOSType_CIMOS_Linux_2_6_x: // 99 - Linux 2.6.x osTypeVBox = SchemaDefs_OSTypeId_Linux26; break; case CIMOSType_CIMOS_Linux_2_6_x_64: // 100 - Linux 2.6.x 64-Bit osTypeVBox = SchemaDefs_OSTypeId_Linux26_64; break; default: { /* If we are here we have no clue what OS this should be. Set to other type as default. */ osTypeVBox = SchemaDefs_OSTypeId_Other; } } } /** * Public method implementation. * @param path * @return */ STDMETHODIMP Appliance::Read(IN_BSTR path) { HRESULT rc = S_OK; if (!path) return E_POINTER; AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoWriteLock alock(this); // see if we can handle this file; for now we insist it has an ".ovf" extension m->strPath = path; const char *pcszLastDot = strrchr(m->strPath, '.'); if ( (!pcszLastDot) || ( strcmp(pcszLastDot, ".ovf") && strcmp(pcszLastDot, ".OVF") ) ) return setError(VBOX_E_FILE_ERROR, tr("Appliance file must have .ovf extension")); try { xml::XmlFileParser parser; xml::Document doc; parser.read(m->strPath.raw(), doc); const xml::Node *pRootElem = doc.getRootElement(); if (strcmp(pRootElem->getName(), "Envelope")) return setError(VBOX_E_FILE_ERROR, tr("Root element in OVF file must be \"Envelope\".")); // OVF has the following rough layout: /* -- .... files referenced from other parts of the file, such as VMDK images -- Metadata, comprised of several section commands -- virtual machines, either a single , or a -- optionally 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 xml::NodesList listFileElements; // receives all /Envelope/References/File nodes const xml::Node *pReferencesElem; if ((pReferencesElem = pRootElem->findChildElement("References"))) pReferencesElem->getChildElements(listFileElements, "File"); // now go though the sections if (!(SUCCEEDED(rc = LoopThruSections(m->strPath.raw(), pReferencesElem, pRootElem)))) return rc; } catch(xml::Error &x) { return setError(VBOX_E_FILE_ERROR, x.what()); } return S_OK; } /** * Public method implementation. * @return */ STDMETHODIMP Appliance::Interpret() { // @todo: // - don't use COM methods but the methods directly (faster, but needs appropriate locking of that objects itself (s. HardDisk)) // - Appropriate handle errors like not supported file formats AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoWriteLock(this); HRESULT rc = S_OK; /* Clear any previous virtual system descriptions */ // @todo: have the entries deleted also? m->virtualSystemDescriptions.clear(); /* We need the default path for storing disk images */ ComPtr systemProps; rc = mVirtualBox->COMGETTER(SystemProperties)(systemProps.asOutParam()); CheckComRCReturnRC(rc); Bstr bstrDefaultHardDiskLocation; rc = systemProps->COMGETTER(DefaultHardDiskFolder)(bstrDefaultHardDiskLocation.asOutParam()); CheckComRCReturnRC(rc); /* Try/catch so we can clean up on error */ try { list::const_iterator it; /* Iterate through all virtual systems */ for (it = m->llVirtualSystems.begin(); it != m->llVirtualSystems.end(); ++it) { const VirtualSystem &vsysThis = *it; ComObjPtr pNewDesc; rc = pNewDesc.createObject(); CheckComRCThrowRC(rc); rc = pNewDesc->init(); CheckComRCThrowRC(rc); /* Guest OS type */ Utf8Str strOsTypeVBox, strCIMOSType = Utf8StrFmt("%RI32", (uint32_t)vsysThis.cimos); convertCIMOSType2VBoxOSType(strOsTypeVBox, vsysThis.cimos); pNewDesc->addEntry(VirtualSystemDescriptionType_OS, "", strCIMOSType, strOsTypeVBox); /* VM name */ /* If the there isn't any name specified create a default one out of * the OS type */ Utf8Str nameVBox = vsysThis.strName; if (nameVBox == "") nameVBox = strOsTypeVBox; searchUniqueVMName(nameVBox); pNewDesc->addEntry(VirtualSystemDescriptionType_Name, "", vsysThis.strName, nameVBox); /* Now that we know the OS type, get our internal defaults based on that. */ ComPtr pGuestOSType; rc = mVirtualBox->GetGuestOSType(Bstr(strOsTypeVBox), pGuestOSType.asOutParam()); CheckComRCThrowRC(rc); /* CPU count */ ULONG cpuCountVBox = vsysThis.cCPUs; /* Check for the constrains */ if (cpuCountVBox > 1) //SchemaDefs::MaxCPUCount) { pNewDesc->addWarning(tr("The virtual system claims support for %u CPU's, but VirtualBox has support for max %u CPU's only."), cpuCountVBox, 1); //SchemaDefs::MaxCPUCount); cpuCountVBox = 1; //SchemaDefs::MaxCPUCount; } if (vsysThis.cCPUs == 0) cpuCountVBox = 1; pNewDesc->addEntry(VirtualSystemDescriptionType_CPU, "", Utf8StrFmt("%RI32", (uint32_t)vsysThis.cCPUs), Utf8StrFmt("%RI32", (uint32_t)cpuCountVBox)); /* RAM */ uint64_t ullMemSizeVBox = vsysThis.ullMemorySize / _1M; /* Check for the constrains */ if (ullMemSizeVBox != 0 && (ullMemSizeVBox < static_cast(SchemaDefs::MinGuestRAM) || ullMemSizeVBox > static_cast(SchemaDefs::MaxGuestRAM))) { pNewDesc->addWarning(tr("The virtual system claims support for %llu MB RAM size, but VirtualBox has support for min %u & max %u MB RAM size only."), ullMemSizeVBox, SchemaDefs::MinGuestRAM, SchemaDefs::MaxGuestRAM); ullMemSizeVBox = RT_MIN(RT_MAX(ullMemSizeVBox, static_cast(SchemaDefs::MinGuestRAM)), static_cast(SchemaDefs::MaxGuestRAM)); } if (vsysThis.ullMemorySize == 0) { /* If the RAM of the OVF is zero, use our predefined values */ ULONG memSizeVBox2; rc = pGuestOSType->COMGETTER(RecommendedRAM)(&memSizeVBox2); CheckComRCThrowRC(rc); /* VBox stores that in MByte */ ullMemSizeVBox = (uint64_t)memSizeVBox2; } pNewDesc->addEntry(VirtualSystemDescriptionType_Memory, "", Utf8StrFmt("%RI64", (uint64_t)vsysThis.ullMemorySize), Utf8StrFmt("%RI64", (uint64_t)ullMemSizeVBox)); /* Audio */ if (!vsysThis.strSoundCardType.isNull()) /* Currently we set the AC97 always. @todo: figure out the hardware which could be possible */ pNewDesc->addEntry(VirtualSystemDescriptionType_SoundCard, "", vsysThis.strSoundCardType, Utf8StrFmt("%RI32", (uint32_t)AudioControllerType_AC97)); #ifdef VBOX_WITH_USB /* USB Controller */ if (vsysThis.fHasUsbController) pNewDesc->addEntry(VirtualSystemDescriptionType_USBController, "", "", ""); #endif /* VBOX_WITH_USB */ NetworksMap::const_iterator itN; for (itN = m->mapNetworks.begin(); itN != m->mapNetworks.end(); ++itN) { const Network &nw = itN->second; pNewDesc->addEntry(VirtualSystemDescriptionType_LogicalNetwork, "", nw.strNetworkName, nw.strNetworkName); } /* Network Controller */ // @todo: there is no hardware specification in the OVF file; supposedly the // hardware will then be determined by the VirtualSystemType element (e.g. "vmx-07") if (vsysThis.llNetworkNames.size() > 0) { /* Check for the constrains */ if (vsysThis.llNetworkNames.size() > SchemaDefs::NetworkAdapterCount) { pNewDesc->addWarning(tr("The virtual system claims support for %u network adapters, but VirtualBox has support for max %u network adapter only."), vsysThis.llNetworkNames.size(), SchemaDefs::NetworkAdapterCount); } /* Get the default network adapter type for the selected guest OS */ NetworkAdapterType_T nwAdapterVBox = NetworkAdapterType_Am79C970A; rc = pGuestOSType->COMGETTER(AdapterType)(&nwAdapterVBox); CheckComRCThrowRC(rc); list::const_iterator nwIt; /* Iterate through all abstract networks. We support 8 network * adapters at the maximum, so the first 8 will be added only. */ size_t a = 0; for (nwIt = vsysThis.llNetworkNames.begin(); nwIt != vsysThis.llNetworkNames.end() && a < SchemaDefs::NetworkAdapterCount; ++nwIt, ++a) { Utf8Str strNetwork = *nwIt; // logical network to connect to pNewDesc->addEntry(VirtualSystemDescriptionType_NetworkAdapter, "", // ref strNetwork, // orig Utf8StrFmt("%RI32", (uint32_t)nwAdapterVBox), // conf Utf8StrFmt("network=%s", strNetwork.c_str())); // extra conf } } /* Floppy Drive */ if (vsysThis.fHasFloppyDrive) pNewDesc->addEntry(VirtualSystemDescriptionType_Floppy, "", "", ""); /* CD Drive */ /* @todo: I can't disable the CDROM. So nothing to do for now */ /* if (vsysThis.fHasCdromDrive) pNewDesc->addEntry(VirtualSystemDescriptionType_CDROM, "", "", "");*/ /* Hard disk Controller */ uint16_t cIDEused = 0; uint16_t cSATAused = 0; uint16_t cSCSIused = 0; ControllersMap::const_iterator hdcIt; /* Iterate through all hard disk controllers */ for (hdcIt = vsysThis.mapControllers.begin(); hdcIt != vsysThis.mapControllers.end(); ++hdcIt) { const HardDiskController &hdc = hdcIt->second; Utf8Str strControllerID = Utf8StrFmt("%RI32", (uint32_t)hdc.idController); switch (hdc.system) { case HardDiskController::IDE: { /* Check for the constrains */ /* @todo: I'm very confused! Are these bits *one* controller or is every port/bus declared as an extra controller. */ if (cIDEused < 4) { // @todo: figure out the IDE types /* Use PIIX4 as default */ Utf8Str strType = "PIIX4"; if (!RTStrICmp(hdc.strControllerType.c_str(), "PIIX3")) strType = "PIIX3"; pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE, strControllerID, hdc.strControllerType, strType); } else { /* Warn only once */ if (cIDEused == 1) pNewDesc->addWarning(tr("The virtual system claims support for more than one IDE controller, but VirtualBox has support for only one.")); } ++cIDEused; break; } #ifdef VBOX_WITH_AHCI case HardDiskController::SATA: { /* Check for the constrains */ if (cSATAused < 1) { // @todo: figure out the SATA types /* We only support a plain AHCI controller, so use them always */ pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA, strControllerID, hdc.strControllerType, "AHCI"); } else { /* Warn only once */ if (cSATAused == 1) pNewDesc->addWarning(tr("The virtual system claims support for more than one SATA controller, but VirtualBox has support for only one.")); } ++cSATAused; break; } #endif /* VBOX_WITH_AHCI */ case HardDiskController::SCSI: { /* Check for the constrains */ if (cSCSIused < 1) { // @todo: figure out the SCSI types Utf8Str hdcController = "LsiLogic"; /* if (!RTStrICmp(hdc.strControllerType.c_str(), "LsiLogic")) hdcController = "LsiLogic"; else*/ if (!RTStrICmp(hdc.strControllerType.c_str(), "BusLogic")) hdcController = "BusLogic"; pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSCSI, strControllerID, hdc.strControllerType, hdcController); } else { /* Warn only once */ if (cSCSIused == 1) pNewDesc->addWarning(tr("The virtual system claims support for more than one SCSI controller, but VirtualBox has support for only one.")); } ++cSCSIused; break; } default: { /* @todo: should we stop? */ } } } /* Hard disks */ if (vsysThis.mapVirtualDisks.size() > 0) { VirtualDisksMap::const_iterator itVD; /* Iterate through all hard disks ()*/ for (itVD = vsysThis.mapVirtualDisks.begin(); itVD != vsysThis.mapVirtualDisks.end(); ++itVD) { const VirtualDisk &hd = itVD->second; /* Get the associated disk image */ const DiskImage &di = m->mapDisks[hd.strDiskId]; // @todo: // - figure out all possible vmdk formats we also support // - figure out if there is a url specifier for vhd already // - we need a url specifier for the vdi format if ( (!RTStrICmp(di.strFormat.c_str(), "http://www.vmware.com/specifications/vmdk.html#sparse")) || (!RTStrICmp(di.strFormat.c_str(), "http://www.vmware.com/specifications/vmdk.html#compressed")) ) { /* If the href is empty use the VM name as filename */ Utf8Str strFilename = di.strHref; if (!strFilename.length()) strFilename = Utf8StrFmt("%s.vmdk", nameVBox.c_str()); /* Construct a unique target path */ Utf8StrFmt strPath("%ls%c%s", bstrDefaultHardDiskLocation.raw(), RTPATH_DELIMITER, strFilename.c_str()); searchUniqueDiskImageFilePath(strPath); /* find the description for the hard disk controller * that has the same ID as hd.idController */ const VirtualSystemDescriptionEntry *pController; if (!(pController = pNewDesc->findControllerFromID(hd.idController))) throw setError(E_FAIL, tr("Internal inconsistency looking up hard disk controller.")); /* controller to attach to, and the bus within that controller */ Utf8StrFmt strExtraConfig("controller=%RI16;channel=%RI16", pController->ulIndex, hd.ulAddressOnParent); pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskImage, hd.strDiskId, di.strHref, strPath, strExtraConfig); } else { /* @todo: should we stop here? */ pNewDesc->addWarning(tr("The virtual system claims support for the following virtual disk image format which VirtualBox not support: %s"), di.strFormat.c_str()); } } } m->virtualSystemDescriptions.push_back(pNewDesc); } } catch (HRESULT aRC) { /* On error we clear the list & return */ m->virtualSystemDescriptions.clear(); rc = aRC; } return rc; } /** * Public method implementation. * @param aProgress * @return */ STDMETHODIMP Appliance::ImportMachines(IProgress **aProgress) { CheckComArgOutPointerValid(aProgress); AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoReadLock(this); HRESULT rc = S_OK; ComObjPtr progress; try { uint32_t opCount = calcMaxProgress(); Bstr progressDesc = BstrFmt(tr("Import appliance '%s'"), m->strPath.raw()); /* Create the progress object */ progress.createObject(); rc = progress->init(mVirtualBox, static_cast(this), progressDesc, FALSE /* aCancelable */, opCount, progressDesc); if (FAILED(rc)) throw rc; /* Initialize our worker task */ std::auto_ptr task(new TaskImportMachines(this, progress)); //AssertComRCThrowRC (task->autoCaller.rc()); rc = task->startThread(); if (FAILED(rc)) throw rc; task.release(); } catch (HRESULT aRC) { rc = aRC; } if (SUCCEEDED(rc)) /* Return progress to the caller */ progress.queryInterfaceTo(aProgress); return rc; } STDMETHODIMP Appliance::Write(IN_BSTR path, IProgress **aProgress) { HRESULT rc = S_OK; CheckComArgOutPointerValid(aProgress); AutoCaller autoCaller(this); if (FAILED(rc = autoCaller.rc())) return rc; AutoWriteLock(this); // see if we can handle this file; for now we insist it has an ".ovf" extension m->strPath = path; const char *pcszLastDot = strrchr(m->strPath, '.'); if ( (!pcszLastDot) || ( strcmp(pcszLastDot, ".ovf") && strcmp(pcszLastDot, ".OVF") ) ) return setError(VBOX_E_FILE_ERROR, tr("Appliance file must have .ovf extension")); ComObjPtr progress; try { uint32_t opCount = calcMaxProgress(); Bstr progressDesc = BstrFmt(tr("Write appliance '%s'"), m->strPath.raw()); /* Create the progress object */ progress.createObject(); rc = progress->init(mVirtualBox, static_cast(this), progressDesc, FALSE /* aCancelable */, opCount, progressDesc); CheckComRCThrowRC(rc); /* Initialize our worker task */ std::auto_ptr task(new TaskExportOVF(this, progress)); //AssertComRCThrowRC (task->autoCaller.rc()); rc = task->startThread(); CheckComRCThrowRC(rc); task.release(); } catch (HRESULT aRC) { rc = aRC; } if (SUCCEEDED(rc)) /* Return progress to the caller */ progress.queryInterfaceTo(aProgress); return rc; } HRESULT Appliance::searchUniqueVMName(Utf8Str& aName) const { IMachine *machine = NULL; char *tmpName = RTStrDup(aName.c_str()); int i = 1; /* @todo: Maybe too cost-intensive; try to find a lighter way */ while (mVirtualBox->FindMachine(Bstr(tmpName), &machine) != VBOX_E_OBJECT_NOT_FOUND) { RTStrFree(tmpName); RTStrAPrintf(&tmpName, "%s_%d", aName.c_str(), i); ++i; } aName = tmpName; RTStrFree(tmpName); return S_OK; } HRESULT Appliance::searchUniqueDiskImageFilePath(Utf8Str& aName) const { IHardDisk *harddisk = NULL; char *tmpName = RTStrDup(aName.c_str()); int i = 1; /* Check if the file exists or if a file with this path is registered * already */ /* @todo: Maybe too cost-intensive; try to find a lighter way */ while (RTPathExists(tmpName) || mVirtualBox->FindHardDisk(Bstr(tmpName), &harddisk) != VBOX_E_OBJECT_NOT_FOUND) { RTStrFree(tmpName); char *tmpDir = RTStrDup(aName.c_str()); RTPathStripFilename(tmpDir);; char *tmpFile = RTStrDup(RTPathFilename(aName.c_str())); RTPathStripExt(tmpFile); const char *tmpExt = RTPathExt(aName.c_str()); RTStrAPrintf(&tmpName, "%s%c%s_%d%s", tmpDir, RTPATH_DELIMITER, tmpFile, i, tmpExt); RTStrFree(tmpFile); RTStrFree(tmpDir); ++i; } aName = tmpName; RTStrFree(tmpName); return S_OK; } /** * Calculates the maximum progress value for importMachines() and write(). * @return */ uint32_t Appliance::calcMaxProgress() { /* Figure out how many sub operation the import will need */ /* One for the appliance */ uint32_t opCount = 1; list< ComObjPtr >::const_iterator it; for (it = m->virtualSystemDescriptions.begin(); it != m->virtualSystemDescriptions.end(); ++it) { /* One for every Virtual System */ ++opCount; ComObjPtr vsdescThis = (*it); /* One for every hard disk of the Virtual System */ std::list avsdeHDs = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskImage); opCount += (uint32_t)avsdeHDs.size(); } return opCount; } struct MyHardDiskAttachment { Guid uuid; ComPtr pMachine; StorageBus_T busType; int32_t lChannel; int32_t lDevice; }; /** * Worker thread implementation for ImportMachines(). * @param aThread * @param pvUser */ /* static */ DECLCALLBACK(int) Appliance::taskThreadImportMachines(RTTHREAD aThread, void *pvUser) { std::auto_ptr task(static_cast(pvUser)); AssertReturn(task.get(), VERR_GENERAL_FAILURE); Appliance *pAppliance = task->pAppliance; LogFlowFuncEnter(); LogFlowFunc(("Appliance %p\n", pAppliance)); AutoCaller autoCaller(pAppliance); CheckComRCReturnRC(autoCaller.rc()); AutoWriteLock appLock(pAppliance); HRESULT rc = S_OK; ComPtr pVirtualBox(pAppliance->mVirtualBox); // rollback for errors: // 1) a list of images that we created/imported list llHardDiskAttachments; list< ComPtr > llHardDisksCreated; list llMachinesRegistered; ComPtr session; bool fSessionOpen = false; rc = session.createInprocObject(CLSID_Session); CheckComRCReturnRC(rc); list::const_iterator it; list< ComObjPtr >::const_iterator it1; /* Iterate through all virtual systems of that appliance */ size_t i = 0; for (it = pAppliance->m->llVirtualSystems.begin(), it1 = pAppliance->m->virtualSystemDescriptions.begin(); it != pAppliance->m->llVirtualSystems.end(); ++it, ++it1, ++i) { const VirtualSystem &vsysThis = *it; ComObjPtr vsdescThis = (*it1); ComPtr pNewMachine; /* Catch possible errors */ try { if (!task->progress.isNull()) task->progress->advanceOperation(BstrFmt(tr("Importing Virtual System %d"), i + 1)); /* How many sub notifications are necessary? */ const float opCountMax = 100.0/5; uint32_t opCount = 0; /* Guest OS type */ std::list vsdeOS; vsdeOS = vsdescThis->findByType(VirtualSystemDescriptionType_OS); if (vsdeOS.size() < 1) throw setError(VBOX_E_FILE_ERROR, tr("Missing guest OS type")); const Utf8Str &strOsTypeVBox = vsdeOS.front()->strVbox; /* Now that we know the base system get our internal defaults based on that. */ ComPtr osType; rc = pVirtualBox->GetGuestOSType(Bstr(strOsTypeVBox), osType.asOutParam()); if (FAILED(rc)) throw rc; /* Create the machine */ /* First get the name */ std::list vsdeName = vsdescThis->findByType(VirtualSystemDescriptionType_Name); if (vsdeName.size() < 1) throw setError(VBOX_E_FILE_ERROR, tr("Missing VM name")); const Utf8Str &strNameVBox = vsdeName.front()->strVbox; rc = pVirtualBox->CreateMachine(Bstr(strNameVBox), Bstr(strOsTypeVBox), Bstr(), Guid(), pNewMachine.asOutParam()); if (FAILED(rc)) throw rc; if (!task->progress.isNull()) rc = task->progress->notifyProgress((uint32_t)(opCountMax * opCount++)); /* CPU count (ignored for now) */ // EntriesList vsdeCPU = vsd->findByType (VirtualSystemDescriptionType_CPU); /* RAM */ std::list vsdeRAM = vsdescThis->findByType(VirtualSystemDescriptionType_Memory); ComAssertMsgThrow(vsdeRAM.size() == 1, ("RAM size missing"), E_FAIL); const Utf8Str &memoryVBox = vsdeRAM.front()->strVbox; ULONG tt = (ULONG)RTStrToUInt64(memoryVBox.c_str()); rc = pNewMachine->COMSETTER(MemorySize)(tt); if (FAILED(rc)) throw rc; /* VRAM */ /* Get the recommended VRAM for this guest OS type */ ULONG vramVBox; rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox); if (FAILED(rc)) throw rc; /* Set the VRAM */ rc = pNewMachine->COMSETTER(VRAMSize)(vramVBox); if (FAILED(rc)) throw rc; if (!task->progress.isNull()) task->progress->notifyProgress((uint32_t)(opCountMax * opCount++)); /* Audio Adapter */ std::list vsdeAudioAdapter = vsdescThis->findByType(VirtualSystemDescriptionType_SoundCard); /* @todo: we support one audio adapter only */ if (vsdeAudioAdapter.size() > 0) { const Utf8Str& audioAdapterVBox = vsdeAudioAdapter.front()->strVbox; if (RTStrICmp(audioAdapterVBox, "null") != 0) { uint32_t audio = RTStrToUInt32(audioAdapterVBox.c_str()); ComPtr audioAdapter; rc = pNewMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam()); if (FAILED(rc)) throw rc; rc = audioAdapter->COMSETTER(Enabled)(true); if (FAILED(rc)) throw rc; rc = audioAdapter->COMSETTER(AudioController)(static_cast(audio)); if (FAILED(rc)) throw rc; } } #ifdef VBOX_WITH_USB /* USB Controller */ std::list vsdeUSBController = vsdescThis->findByType(VirtualSystemDescriptionType_USBController); // USB support is enabled if there's at least one such entry; to disable USB support, // the type of the USB item would have been changed to "ignore" bool fUSBEnabled = vsdeUSBController.size() > 0; ComPtr usbController; rc = pNewMachine->COMGETTER(USBController)(usbController.asOutParam()); if (FAILED(rc)) throw rc; rc = usbController->COMSETTER(Enabled)(fUSBEnabled); if (FAILED(rc)) throw rc; #endif /* VBOX_WITH_USB */ if (!task->progress.isNull()) task->progress->notifyProgress((uint32_t)(opCountMax * opCount++)); /* Change the network adapters */ std::list vsdeNW = vsdescThis->findByType(VirtualSystemDescriptionType_NetworkAdapter); if (vsdeNW.size() == 0) { /* No network adapters, so we have to disable our default one */ ComPtr nwVBox; rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam()); if (FAILED(rc)) throw rc; rc = nwVBox->COMSETTER(Enabled)(false); if (FAILED(rc)) throw rc; } else { list::const_iterator nwIt; /* Iterate through all network cards. We support 8 network adapters * at the maximum. (@todo: warn if there are more!) */ size_t a = 0; for (nwIt = vsdeNW.begin(); (nwIt != vsdeNW.end() && a < SchemaDefs::NetworkAdapterCount); ++nwIt, ++a) { const Utf8Str &nwTypeVBox = (*nwIt)->strVbox; uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str()); ComPtr nwVBox; rc = pNewMachine->GetNetworkAdapter((ULONG)a, nwVBox.asOutParam()); if (FAILED(rc)) throw rc; /* Enable the network card & set the adapter type */ /* NAT is set as default */ rc = nwVBox->COMSETTER(Enabled)(true); if (FAILED(rc)) throw rc; rc = nwVBox->COMSETTER(AdapterType)(static_cast(tt1)); if (FAILED(rc)) throw rc; } } /* Floppy drive */ std::list vsdeFloppy = vsdescThis->findByType(VirtualSystemDescriptionType_Floppy); // Floppy support is enabled if there's at least one such entry; to disable floppy support, // the type of the floppy item would have been changed to "ignore" bool fFloppyEnabled = vsdeFloppy.size() > 0; ComPtr floppyDrive; rc = pNewMachine->COMGETTER(FloppyDrive)(floppyDrive.asOutParam()); if (FAILED(rc)) throw rc; rc = floppyDrive->COMSETTER(Enabled)(fFloppyEnabled); if (FAILED(rc)) throw rc; if (!task->progress.isNull()) task->progress->notifyProgress((uint32_t)(opCountMax * opCount++)); /* CDROM drive */ /* @todo: I can't disable the CDROM. So nothing to do for now */ // std::list vsdeFloppy = vsd->findByType(VirtualSystemDescriptionType_CDROM); /* Hard disk controller IDE */ std::list vsdeHDCIDE = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskControllerIDE); /* @todo: we support one IDE controller only */ if (vsdeHDCIDE.size() > 0) { /* Set the appropriate IDE controller in the virtual BIOS of the VM */ ComPtr biosSettings; rc = pNewMachine->COMGETTER(BIOSSettings)(biosSettings.asOutParam()); if (FAILED(rc)) throw rc; const char *pcszIDEType = vsdeHDCIDE.front()->strVbox.c_str(); if (!strcmp(pcszIDEType, "PIIX3")) rc = biosSettings->COMSETTER(IDEControllerType)(IDEControllerType_PIIX3); else if (!strcmp(pcszIDEType, "PIIX4")) rc = biosSettings->COMSETTER(IDEControllerType)(IDEControllerType_PIIX4); else if (!strcmp(pcszIDEType, "ICH6")) rc = biosSettings->COMSETTER(IDEControllerType)(IDEControllerType_ICH6); else throw setError(VBOX_E_FILE_ERROR, tr("Invalid IDE controller type \"%s\""), pcszIDEType); if (FAILED(rc)) throw rc; } #ifdef VBOX_WITH_AHCI /* Hard disk controller SATA */ std::list vsdeHDCSATA = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskControllerSATA); /* @todo: we support one SATA controller only */ if (vsdeHDCSATA.size() > 0) { const Utf8Str &hdcVBox = vsdeHDCIDE.front()->strVbox; if (hdcVBox == "AHCI") { /* For now we have just to enable the AHCI controller. */ ComPtr hdcSATAVBox; rc = pNewMachine->COMGETTER(SATAController)(hdcSATAVBox.asOutParam()); if (FAILED(rc)) throw rc; rc = hdcSATAVBox->COMSETTER(Enabled)(true); if (FAILED(rc)) throw rc; } else { throw setError(VBOX_E_FILE_ERROR, tr("Invalid SATA controller type \"%s\""), hdcVBox.c_str()); } } #endif /* VBOX_WITH_AHCI */ /* Hard disk controller SCSI */ std::list vsdeHDCSCSI = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI); /* @todo: do we support more than one SCSI controller? */ if (vsdeHDCSCSI.size() > 0) { /* @todo: revisit when Main support for SCSI is ready */ } /* Now its time to register the machine before we add any hard disks */ rc = pVirtualBox->RegisterMachine(pNewMachine); if (FAILED(rc)) throw rc; Guid newMachineId; rc = pNewMachine->COMGETTER(Id)(newMachineId.asOutParam()); if (FAILED(rc)) throw rc; if (!task->progress.isNull()) task->progress->notifyProgress((uint32_t)(opCountMax * opCount++)); // store new machine for roll-back in case of errors llMachinesRegistered.push_back(newMachineId); /* Create the hard disks & connect them to the appropriate controllers. */ std::list avsdeHDs = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskImage); if (avsdeHDs.size() > 0) { /* If in the next block an error occur we have to deregister the machine, so make an extra try/catch block. */ ComPtr srcHdVBox; bool fSourceHdNeedsClosing = false; try { /* In order to attach hard disks we need to open a session * for the new machine */ rc = pVirtualBox->OpenSession(session, newMachineId); if (FAILED(rc)) throw rc; fSessionOpen = true; /* The disk image has to be on the same place as the OVF file. So * strip the filename out of the full file path. */ Utf8Str strSrcDir = stripFilename(pAppliance->m->strPath); /* Iterate over all given disk images */ list::const_iterator itHD; for (itHD = avsdeHDs.begin(); itHD != avsdeHDs.end(); ++itHD) { VirtualSystemDescriptionEntry *vsdeHD = *itHD; const char *pcszDstFilePath = vsdeHD->strVbox.c_str(); /* Check if the destination file exists already or the * destination path is empty. */ if ( !(*pcszDstFilePath) || RTPathExists(pcszDstFilePath) ) /* This isn't allowed */ throw setError(VBOX_E_FILE_ERROR, tr("Destination file '%s' exists", pcszDstFilePath)); /* Find the disk from the OVF's disk list */ DiskImagesMap::const_iterator itDiskImage = pAppliance->m->mapDisks.find(vsdeHD->strRef); /* vsdeHD->strRef contains the disk identifier (e.g. "vmdisk1"), which should exist in the virtual system's disks map under that ID and also in the global images map. */ VirtualDisksMap::const_iterator itVirtualDisk = vsysThis.mapVirtualDisks.find(vsdeHD->strRef); if ( itDiskImage == pAppliance->m->mapDisks.end() || itVirtualDisk == vsysThis.mapVirtualDisks.end() ) throw setError(E_FAIL, tr("Internal inconsistency looking up disk images.")); const DiskImage &di = itDiskImage->second; const VirtualDisk &vd = itVirtualDisk->second; /* Make sure all target directories exists */ rc = VirtualBox::ensureFilePathExists(pcszDstFilePath); if (FAILED(rc)) throw rc; ComPtr progress; ComPtr dstHdVBox; /* If strHref is empty we have to create a new file */ if (di.strHref.c_str()[0] == 0) { /* Which format to use? */ Bstr srcFormat = L"VDI"; if ( (!RTStrICmp(di.strFormat.c_str(), "http://www.vmware.com/specifications/vmdk.html#sparse")) || (!RTStrICmp(di.strFormat.c_str(), "http://www.vmware.com/specifications/vmdk.html#compressed"))) srcFormat = L"VMDK"; /* Create an empty hard disk */ rc = pVirtualBox->CreateHardDisk(srcFormat, Bstr(pcszDstFilePath), dstHdVBox.asOutParam()); if (FAILED(rc)) throw rc; /* Create a dynamic growing disk image with the given capacity */ ComPtr progress; rc = dstHdVBox->CreateDynamicStorage(di.iCapacity / _1M, progress.asOutParam()); if (FAILED(rc)) throw rc; /* Advance to the next operation */ if (!task->progress.isNull()) task->progress->advanceOperation (BstrFmt(tr("Creating virtual disk image '%s'"), pcszDstFilePath)); } else { /* Construct the source file path */ Utf8StrFmt strSrcFilePath("%s%c%s", strSrcDir.c_str(), RTPATH_DELIMITER, di.strHref.c_str()); /* Check if the source file exists */ if (!RTPathExists(strSrcFilePath.c_str())) /* This isn't allowed */ throw setError(VBOX_E_FILE_ERROR, tr("Source virtual disk image file '%s' doesn't exist"), strSrcFilePath.c_str()); /* Clone the disk image (this is necessary cause the id has * to be recreated for the case the same hard disk is * attached already from a previous import) */ /* First open the existing disk image */ rc = pVirtualBox->OpenHardDisk(Bstr(strSrcFilePath), srcHdVBox.asOutParam()); if (FAILED(rc)) throw rc; fSourceHdNeedsClosing = true; /* We need the format description of the source disk image */ Bstr srcFormat; rc = srcHdVBox->COMGETTER(Format)(srcFormat.asOutParam()); if (FAILED(rc)) throw rc; /* Create a new hard disk interface for the destination disk image */ rc = pVirtualBox->CreateHardDisk(srcFormat, Bstr(pcszDstFilePath), dstHdVBox.asOutParam()); if (FAILED(rc)) throw rc; /* Clone the source disk image */ rc = srcHdVBox->CloneTo(dstHdVBox, progress.asOutParam()); if (FAILED(rc)) throw rc; /* Advance to the next operation */ if (!task->progress.isNull()) task->progress->advanceOperation (BstrFmt(tr("Importing virtual disk image '%s'"), strSrcFilePath.c_str())); } // now loop until the asynchronous operation completes and then // report its result BOOL fCompleted; LONG currentPercent; while (SUCCEEDED(progress->COMGETTER(Completed(&fCompleted)))) { rc = progress->COMGETTER(Percent(¤tPercent)); if (FAILED(rc)) throw rc; if (!task->progress.isNull()) task->progress->notifyProgress(currentPercent); if (fCompleted) break; /* Make sure the loop is not too tight */ rc = progress->WaitForCompletion(100); if (FAILED(rc)) throw rc; } // report result of asynchronous operation HRESULT vrc; rc = progress->COMGETTER(ResultCode)(&vrc); if (FAILED(rc)) throw rc; // if the thread of the progress object has an error, then // retrieve the error info from there, or it'll be lost if (FAILED(vrc)) { com::ErrorInfo info(progress); const char *pcsz = Utf8Str(info.getText()).c_str(); HRESULT rc2 = setError(vrc, pcsz); throw rc2; } if (fSourceHdNeedsClosing) { rc = srcHdVBox->Close(); if (FAILED(rc)) throw rc; fSourceHdNeedsClosing = false; } llHardDisksCreated.push_back(dstHdVBox); /* Now use the new uuid to attach the disk image to our new machine */ ComPtr sMachine; rc = session->COMGETTER(Machine)(sMachine.asOutParam()); if (FAILED(rc)) throw rc; Guid hdId; rc = dstHdVBox->COMGETTER(Id)(hdId.asOutParam()); if (FAILED(rc)) throw rc; /* For now we assume we have one controller of every type only */ HardDiskController hdc = (*vsysThis.mapControllers.find(vd.idController)).second; // this is for rollback later MyHardDiskAttachment mhda; mhda.uuid = newMachineId; mhda.pMachine = pNewMachine; mhda.busType = StorageBus_IDE; switch (hdc.system) { case HardDiskController::IDE: // For the IDE bus, the channel parameter can be either 0 or 1, to specify the primary // or secondary IDE controller, respectively. For the primary controller of the IDE bus, // the device number can be either 0 or 1, to specify the master or the slave device, // respectively. For the secondary IDE controller, the device number is always 1 because // the master device is reserved for the CD-ROM drive. switch (vd.ulAddressOnParent) { case 0: // interpret this as primary master mhda.lChannel = (long)0; mhda.lDevice = (long)0; break; case 1: // interpret this as primary slave mhda.lChannel = (long)0; mhda.lDevice = (long)1; break; case 2: // interpret this as secondary slave mhda.lChannel = (long)1; mhda.lDevice = (long)1; break; default: throw setError(VBOX_E_NOT_SUPPORTED, tr("Invalid channel %RI16 specified; IDE controllers support only 0, 1 or 2"), vd.ulAddressOnParent); break; } break; case HardDiskController::SATA: mhda.busType = StorageBus_SATA; mhda.lChannel = (long)vd.ulAddressOnParent; mhda.lDevice = (long)0; break; case HardDiskController::SCSI: // mhda.busType = StorageBus_SCSI; throw setError(VBOX_E_NOT_SUPPORTED, tr("SCSI controller support is not available yet in VirtualBox")); // @todo break; default: break; } Log(("Attaching disk %s to channel %d on device %d\n", pcszDstFilePath, mhda.lChannel, mhda.lDevice)); rc = sMachine->AttachHardDisk(hdId, mhda.busType, mhda.lChannel, mhda.lDevice); if (FAILED(rc)) throw rc; llHardDiskAttachments.push_back(mhda); rc = sMachine->SaveSettings(); if (FAILED(rc)) throw rc; } // end for (itHD = avsdeHDs.begin(); // only now that we're done with all disks, close the session rc = session->Close(); if (FAILED(rc)) throw rc; fSessionOpen = false; } catch(HRESULT /* aRC */) { if (fSourceHdNeedsClosing) srcHdVBox->Close(); if (fSessionOpen) session->Close(); throw; } } } catch(HRESULT aRC) { rc = aRC; } if (FAILED(rc)) break; } // for (it = pAppliance->m->llVirtualSystems.begin(), if (FAILED(rc)) { // with _whatever_ error we've had, do a complete roll-back of // machines and disks we've created; unfortunately this is // not so trivially done... HRESULT rc2; // detach all hard disks from all machines we created list::iterator itM; for (itM = llHardDiskAttachments.begin(); itM != llHardDiskAttachments.end(); ++itM) { const MyHardDiskAttachment &mhda = *itM; rc2 = pVirtualBox->OpenSession(session, mhda.uuid); if (SUCCEEDED(rc2)) { ComPtr sMachine; rc2 = session->COMGETTER(Machine)(sMachine.asOutParam()); if (SUCCEEDED(rc2)) { rc2 = sMachine->DetachHardDisk(mhda.busType, mhda.lChannel, mhda.lDevice); rc2 = sMachine->SaveSettings(); } session->Close(); } } // now clean up all hard disks we created list< ComPtr >::iterator itHD; for (itHD = llHardDisksCreated.begin(); itHD != llHardDisksCreated.end(); ++itHD) { ComPtr pDisk = *itHD; ComPtr pProgress; rc2 = pDisk->DeleteStorage(pProgress.asOutParam()); rc2 = pProgress->WaitForCompletion(-1); } // finally, deregister and remove all machines list::iterator itID; for (itID = llMachinesRegistered.begin(); itID != llMachinesRegistered.end(); ++itID) { const Guid &guid = *itID; ComPtr failedMachine; rc2 = pVirtualBox->UnregisterMachine(guid, failedMachine.asOutParam()); if (SUCCEEDED(rc2)) rc2 = failedMachine->DeleteSettings(); } } task->rc = rc; if (!task->progress.isNull()) task->progress->notifyComplete(rc); LogFlowFunc(("rc=%Rhrc\n", rc)); LogFlowFuncLeave(); return VINF_SUCCESS; } /** * Worker thread implementation for ImportMachines(). * @param aThread * @param pvUser */ /* static */ DECLCALLBACK(int) Appliance::taskThreadExportOVF(RTTHREAD aThread, void *pvUser) { std::auto_ptr task(static_cast(pvUser)); AssertReturn(task.get(), VERR_GENERAL_FAILURE); Appliance *pAppliance = task->pAppliance; LogFlowFuncEnter(); LogFlowFunc(("Appliance %p\n", pAppliance)); AutoCaller autoCaller(pAppliance); CheckComRCReturnRC(autoCaller.rc()); AutoWriteLock appLock(pAppliance); HRESULT rc = S_OK; ComPtr pVirtualBox(pAppliance->mVirtualBox); try { xml::Document doc; xml::Node *pelmRoot = doc.createRootElement("Envelope"); pelmRoot->setAttribute("ovf:version", "1.0"); pelmRoot->setAttribute("xml:lang", "en-US"); pelmRoot->setAttribute("xmlns", "http://schemas.dmtf.org/ovf/envelope/1"); pelmRoot->setAttribute("xmlns:ovf", "http://schemas.dmtf.org/ovf/envelope/1"); pelmRoot->setAttribute("xmlns:ovfstr", "http://schema.dmtf.org/ovf/strings/1"); pelmRoot->setAttribute("xmlns:rasd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"); pelmRoot->setAttribute("xmlns:vssd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"); pelmRoot->setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); pelmRoot->setAttribute("xsi:schemaLocation", "http://schemas.dmtf.org/ovf/envelope/1 ../ovf-envelope.xsd"); // / xml::Node *pelmReferences = pelmRoot->createChild("References"); // @ŧodo /* /: List of the virtual disks used in the package */ xml::Node *pelmDiskSection = pelmRoot->createChild("DiskSection"); xml::Node *pelmDiskSectionInfo = pelmDiskSection->createChild("Info"); pelmDiskSectionInfo->addContent("List of the virtual disks used in the package"); // @todo for each disk: // xml::Node *pelmDisk = pelmDiskSection->createChild("Disk"); /* /: Logical networks used in the package The network that the LAMP Service will be available on */ xml::Node *pelmNetworkSection = pelmRoot->createChild("NetworkSection"); xml::Node *pelmNetworkSectionInfo = pelmNetworkSection->createChild("Info"); pelmNetworkSectionInfo->addContent("Logical networks used in the package"); // @todo for each network: // xml::Node *pelmNetwork = pelmNetworkSection->createChild("Network"); // and here come the virtual systems: xml::Node *pelmVirtualSystemCollection = pelmRoot->createChild("VirtualSystemCollection"); xml::Node *pattrVirtualSystemCollectionId = pelmVirtualSystemCollection->setAttribute("ovf:id", "ExportedVirtualBoxMachines"); // whatever list< ComObjPtr >::const_iterator it; /* Iterate through all virtual systems of that appliance */ for (it = pAppliance->m->virtualSystemDescriptions.begin(); it != pAppliance->m->virtualSystemDescriptions.end(); ++it) { ComObjPtr vsdescThis = (*it); xml::Node *pelmVirtualSystem = pelmVirtualSystemCollection->createChild("VirtualSystem"); xml::Node *pelmVirtualSystemInfo = pelmVirtualSystem->createChild("Info"); // @todo put in description here after implementing an entry for it std::list llName = vsdescThis->findByType(VirtualSystemDescriptionType_Name); std::list llOS = vsdescThis->findByType(VirtualSystemDescriptionType_OS); /* Guest Operating System Linux 2.6.x */ xml::Node *pelmOperatingSystemSection = pelmVirtualSystem->createChild("OperatingSystemSection"); pelmOperatingSystemSection->setAttribute("ovf:id", "82"); // @todo convert vbox OS type into OVF ID pelmOperatingSystemSection->createChild("Info")->addContent("blah"); // @ŧodo pelmOperatingSystemSection->createChild("Description")->addContent("blah"); // @ŧodo // xml::Node *pelmVirtualHardwareSection = pelmVirtualSystem->createChild("VirtualHardwareSection"); /* Description of the virtual hardware section. vmware 1 MyLampService vmx-4 */ xml::Node *pelmSystem = pelmVirtualHardwareSection->createChild("System"); // vmx-4 xml::Node *pelmVirtualSystemType = pelmSystem->createChild("VirtualSystemType"); pelmVirtualSystemType->addContent("virtualbox-2.2"); // instead of vmx-7? uint32_t ulInstanceID = 1; list::const_iterator itD; for (itD = vsdescThis->m->llDescriptions.begin(); itD != vsdescThis->m->llDescriptions.end(); ++itD) { const VirtualSystemDescriptionEntry &desc = *itD; OVFResourceType_T type = (OVFResourceType_T)0; // if this becomes != 0 then we do stuff Utf8Str strDescription; // must also be set then int32_t lVirtualQuantity = -1; uint64_t uTemp; switch (desc.type) { case VirtualSystemDescriptionType_CPU: /* 1 virtual CPU Number of virtual CPUs virtual CPU 1 3 1 */ strDescription = "Number of virtual CPUs"; type = OVFResourceType_Processor; // 3 lVirtualQuantity = 1; break; case VirtualSystemDescriptionType_Memory: /* MegaBytes 256 MB of memory Memory Size Memory 2 4 256 */ strDescription = "Memory Size"; type = OVFResourceType_Memory; // 4 desc.strVbox.toInt(uTemp); lVirtualQuantity = (int32_t)(uTemp / _1M); break; case VirtualSystemDescriptionType_HardDiskControllerIDE: break; case VirtualSystemDescriptionType_HardDiskControllerSATA: break; case VirtualSystemDescriptionType_HardDiskControllerSCSI: break; case VirtualSystemDescriptionType_HardDiskImage: break; case VirtualSystemDescriptionType_Floppy: break; case VirtualSystemDescriptionType_CDROM: break; case VirtualSystemDescriptionType_LogicalNetwork: break; case VirtualSystemDescriptionType_NetworkAdapter: break; case VirtualSystemDescriptionType_USBController: break; case VirtualSystemDescriptionType_SoundCard: break; } if (type) { xml::Node *pItem; pItem = pelmVirtualHardwareSection->createChild("Item"); pItem->createChild("rasd:Description")->addContent(strDescription.c_str()); // 1 pItem->createChild("rasd:InstanceID")->addContent(Utf8StrFmt("%d", ulInstanceID).c_str()); // 3 pItem->createChild("rasd:ResourceType")->addContent(Utf8StrFmt("%d", type).c_str()); // 1 if (lVirtualQuantity != -1) pItem->createChild("rasd:VirtualQuantity")->addContent(Utf8StrFmt("%d", lVirtualQuantity).c_str()); } } } // now go write the XML xml::XmlFileWriter writer(doc); writer.write(pAppliance->m->strPath.c_str()); } catch(xml::Error &x) { rc = setError(VBOX_E_FILE_ERROR, x.what()); } catch(HRESULT aRC) { rc = aRC; } task->rc = rc; if (!task->progress.isNull()) task->progress->notifyComplete(rc); LogFlowFunc(("rc=%Rhrc\n", rc)); LogFlowFuncLeave(); return VINF_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// // // IVirtualSystemDescription constructor / destructor // //////////////////////////////////////////////////////////////////////////////// DEFINE_EMPTY_CTOR_DTOR(VirtualSystemDescription) struct shutup3 {}; /** * COM initializer. * @return */ HRESULT VirtualSystemDescription::init() { /* Enclose the state transition NotReady->InInit->Ready */ AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); /* Initialize data */ m = new Data(); /* Confirm a successful initialization */ autoInitSpan.setSucceeded(); return S_OK; } /** * COM uninitializer. */ void VirtualSystemDescription::uninit() { delete m; m = NULL; } //////////////////////////////////////////////////////////////////////////////// // // IVirtualSystemDescription public methods // //////////////////////////////////////////////////////////////////////////////// /** * Public method implementation. * @param * @return */ STDMETHODIMP VirtualSystemDescription::COMGETTER(Count)(ULONG *aCount) { if (!aCount) return E_POINTER; AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoReadLock alock(this); *aCount = (ULONG)m->llDescriptions.size(); return S_OK; } /** * Public method implementation. * @return */ STDMETHODIMP VirtualSystemDescription::GetDescription(ComSafeArrayOut(VirtualSystemDescriptionType_T, aTypes), ComSafeArrayOut(BSTR, aRefs), ComSafeArrayOut(BSTR, aOrigValues), ComSafeArrayOut(BSTR, aVboxValues), ComSafeArrayOut(BSTR, aExtraConfigValues)) { if (ComSafeArrayOutIsNull(aTypes) || ComSafeArrayOutIsNull(aRefs) || ComSafeArrayOutIsNull(aOrigValues) || ComSafeArrayOutIsNull(aVboxValues) || ComSafeArrayOutIsNull(aExtraConfigValues)) return E_POINTER; AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoReadLock alock(this); ULONG c = (ULONG)m->llDescriptions.size(); com::SafeArray sfaTypes(c); com::SafeArray sfaRefs(c); com::SafeArray sfaOrigValues(c); com::SafeArray sfaVboxValues(c); com::SafeArray sfaExtraConfigValues(c); list::const_iterator it; size_t i = 0; for (it = m->llDescriptions.begin(); it != m->llDescriptions.end(); ++it, ++i) { const VirtualSystemDescriptionEntry &vsde = (*it); sfaTypes[i] = vsde.type; Bstr bstr = vsde.strRef; bstr.cloneTo(&sfaRefs[i]); bstr = vsde.strOvf; bstr.cloneTo(&sfaOrigValues[i]); bstr = vsde.strVbox; bstr.cloneTo(&sfaVboxValues[i]); bstr = vsde.strExtraConfig; bstr.cloneTo(&sfaExtraConfigValues[i]); } sfaTypes.detachTo(ComSafeArrayOutArg(aTypes)); sfaRefs.detachTo(ComSafeArrayOutArg(aRefs)); sfaOrigValues.detachTo(ComSafeArrayOutArg(aOrigValues)); sfaVboxValues.detachTo(ComSafeArrayOutArg(aVboxValues)); sfaExtraConfigValues.detachTo(ComSafeArrayOutArg(aExtraConfigValues)); return S_OK; } /** * Public method implementation. * @return */ STDMETHODIMP VirtualSystemDescription::SetFinalValues(ComSafeArrayIn(BOOL, aEnabled), ComSafeArrayIn(IN_BSTR, argVboxValues), ComSafeArrayIn(IN_BSTR, argExtraConfigValues)) { CheckComArgSafeArrayNotNull(argVboxValues); CheckComArgSafeArrayNotNull(argExtraConfigValues); AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoWriteLock alock(this); com::SafeArray aVboxValues(ComSafeArrayInArg(argVboxValues)); com::SafeArray aExtraConfigValues(ComSafeArrayInArg(argExtraConfigValues)); if ( (aVboxValues.size() != m->llDescriptions.size()) || (aExtraConfigValues.size() != m->llDescriptions.size()) ) return E_INVALIDARG; list::iterator it; size_t i = 0; for (it = m->llDescriptions.begin(); it != m->llDescriptions.end(); ++it, ++i) { VirtualSystemDescriptionEntry& vsde = *it; if (aEnabled[i]) { vsde.strVbox = aVboxValues[i]; vsde.strExtraConfig = aExtraConfigValues[i]; } else vsde.type = VirtualSystemDescriptionType_Ignore; } return S_OK; } /** * Public method implementation. * @return */ STDMETHODIMP VirtualSystemDescription::GetWarnings(ComSafeArrayOut(BSTR, aWarnings)) { if (ComSafeArrayOutIsNull(aWarnings)) return E_POINTER; AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoReadLock alock(this); com::SafeArray sfaWarnings(m->llWarnings.size()); list::const_iterator it; size_t i = 0; for (it = m->llWarnings.begin(); it != m->llWarnings.end(); ++it, ++i) { Bstr bstr = *it; bstr.cloneTo(&sfaWarnings[i]); } sfaWarnings.detachTo(ComSafeArrayOutArg(aWarnings)); return S_OK; } /** * Internal method; adds a new description item to the member list. * @param aType Type of description for the new item. * @param strRef Reference item; only used with hard disk controllers. * @param aOrigValue Corresponding original value from OVF. * @param aAutoValue Initial configuration value (can be overridden by caller with setFinalValues). * @param strExtraConfig Extra configuration; meaning dependent on type. */ void VirtualSystemDescription::addEntry(VirtualSystemDescriptionType_T aType, const Utf8Str &strRef, const Utf8Str &aOrigValue, const Utf8Str &aAutoValue, const Utf8Str &strExtraConfig /*= ""*/) { VirtualSystemDescriptionEntry vsde; vsde.ulIndex = (uint32_t)m->llDescriptions.size(); // each entry gets an index so the client side can reference them vsde.type = aType; vsde.strRef = strRef; vsde.strOvf = aOrigValue; vsde.strVbox = aAutoValue; vsde.strExtraConfig = strExtraConfig; m->llDescriptions.push_back(vsde); } void VirtualSystemDescription::addWarning(const char* aWarning, ...) { va_list args; va_start(args, aWarning); Utf8StrFmtVA str(aWarning, args); va_end(args); m->llWarnings.push_back(str); } /** * Private method; returns a list of description items containing all the items from the member * description items of this virtual system that match the given type. * @param aType * @return */ std::list VirtualSystemDescription::findByType(VirtualSystemDescriptionType_T aType) { std::list vsd; list::iterator it; for (it = m->llDescriptions.begin(); it != m->llDescriptions.end(); ++it) { if (it->type == aType) vsd.push_back(&(*it)); } return vsd; } /** * Private method; looks thru the member hardware items for the IDE, SATA, or SCSI controller with * the given reference ID. Useful when needing the controller for a particular * virtual disk. * @param id * @return */ const VirtualSystemDescriptionEntry* VirtualSystemDescription::findControllerFromID(uint32_t id) { Utf8Str strRef = Utf8StrFmt("%RI32", id); list::const_iterator it; for (it = m->llDescriptions.begin(); it != m->llDescriptions.end(); ++it) { switch (it->type) { case VirtualSystemDescriptionType_HardDiskControllerIDE: case VirtualSystemDescriptionType_HardDiskControllerSATA: case VirtualSystemDescriptionType_HardDiskControllerSCSI: if (it->strRef == strRef) return &(*it); break; } } return NULL; } //////////////////////////////////////////////////////////////////////////////// // // IMachine public methods // //////////////////////////////////////////////////////////////////////////////// // This code is here so we won't have to include the appliance headers in the // IMachine implementation, and we also need to access private appliance data. /** * Public method implementation. * @param appliance * @return */ STDMETHODIMP Machine::Export(IAppliance *appliance) { HRESULT rc = S_OK; if (!appliance) return E_POINTER; AutoCaller autoCaller(this); CheckComRCReturnRC(autoCaller.rc()); AutoReadLock alock(this); ComObjPtr pNewDesc; try { Bstr bstrName; Bstr bstrDescription; Bstr bstrGuestOSType; uint32_t cCPUs; uint32_t ulMemSizeMB; BOOL fDVDEnabled; BOOL fFloppyEnabled; ComPtr pUsbController; ComPtr pAudioAdapter; // get name bstrName = mUserData->mName; // get description bstrName = mUserData->mDescription; // get guest OS bstrGuestOSType = mUserData->mOSTypeId; // CPU count cCPUs = mHWData->mCPUCount; // memory size in MB ulMemSizeMB = mHWData->mMemorySize; // VRAM size? // BIOS settings? // 3D acceleration enabled? // hardware virtualization enabled? // nested paging enabled? // HWVirtExVPIDEnabled? // PAEEnabled? // snapshotFolder? // VRDPServer? // floppy rc = mFloppyDrive->COMGETTER(Enabled)(&fFloppyEnabled); if (FAILED(rc)) throw rc; // CD-ROM ?!? // ComPtr pDVDDrive; fDVDEnabled = 1; // this is more tricky so use the COM method rc = COMGETTER(USBController)(pUsbController.asOutParam()); if (FAILED(rc)) throw rc; pAudioAdapter = mAudioAdapter; // create a new virtual system rc = pNewDesc.createObject(); CheckComRCThrowRC(rc); rc = pNewDesc->init(); CheckComRCThrowRC(rc); /* Guest OS type */ Utf8Str strOsTypeVBox(bstrGuestOSType), strCIMOSType = "Linux"; // @todo convert back pNewDesc->addEntry(VirtualSystemDescriptionType_OS, "", strCIMOSType, strOsTypeVBox); /* VM name */ Utf8Str strVMName(bstrName); pNewDesc->addEntry(VirtualSystemDescriptionType_Name, "", strVMName, Utf8Str(bstrName)); /* CPU count*/ Utf8Str strCpuCount = Utf8StrFmt("%RI32", cCPUs); pNewDesc->addEntry(VirtualSystemDescriptionType_CPU, "", strCpuCount, strCpuCount); /* Memory */ Utf8Str strMemory = Utf8StrFmt("%RI32", (uint64_t)ulMemSizeMB * _1M); pNewDesc->addEntry(VirtualSystemDescriptionType_Memory, "", strMemory, strMemory); uint32_t uControllerId = 1; uint32_t uidIdeController; uint32_t uidSataController; // ComPtr pBiosSettings; pBiosSettings = mBIOSSettings; Utf8Str strVbox; IDEControllerType_T ctlr; rc = pBiosSettings->COMGETTER(IDEControllerType)(&ctlr); if (FAILED(rc)) throw rc; switch(ctlr) { case IDEControllerType_PIIX3: strVbox = "PIIX3"; break; case IDEControllerType_PIIX4: strVbox = "PIIX4"; break; case IDEControllerType_ICH6: strVbox = "ICH6"; break; } if (strVbox.length()) { uidIdeController = uControllerId++; pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE, Utf8StrFmt("%d", uidIdeController), strVbox, ""); } #ifdef VBOX_WITH_AHCI // ComPtr pSataController; pSataController = mSATAController; BOOL fSataEnabled; rc = pSataController->COMGETTER(Enabled)(&fSataEnabled); if (FAILED(rc)) throw rc; if (fSataEnabled) { uidSataController = uControllerId++; pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA, Utf8StrFmt("%d", uidSataController), strVbox, ""); } #endif // VBOX_WITH_AHCI // // @todo // // hardDiskAttachments // mHDData->mAttachments @todo HDData::AttachmentList::iterator itA; for (itA = mHDData->mAttachments.begin(); itA != mHDData->mAttachments.end(); ++itA) { ComObjPtr pHDA = *itA; // the attachment's data ComPtr pHardDisk; StorageBus_T storageBus; LONG lChannel; LONG lDevice; // and how this translates to the virtual system LONG lChannelVsys; rc = pHDA->COMGETTER(HardDisk)(pHardDisk.asOutParam()); if (FAILED(rc)) throw rc; rc = pHDA->COMGETTER(Bus)(&storageBus); if (FAILED(rc)) throw rc; rc = pHDA->COMGETTER(Channel)(&lChannel); if (FAILED(rc)) throw rc; rc = pHDA->COMGETTER(Device)(&lDevice); if (FAILED(rc)) throw rc; Bstr bstrLocation; rc = pHardDisk->COMGETTER(Location)(bstrLocation.asOutParam()); Bstr bstrName; rc = pHardDisk->COMGETTER(Name)(bstrName.asOutParam()); uint32_t uidControllerVsys; switch (storageBus) { case HardDiskController::IDE: // this is the exact reverse to what we're doing in Appliance::taskThreadImportMachines, // and it must be updated when that is changed! if (lChannel == 0 && lDevice == 0) // primary master lChannelVsys = 0; else if (lChannel == 0 && lDevice == 1) // primary slave lChannelVsys = 1; else if (lChannel == 1 && lDevice == 1) // secondary slave; secondary master is always CDROM lChannelVsys = 2; else throw setError(VBOX_E_NOT_SUPPORTED, tr("Cannot handle hard disk attachment: channel is %d, device is %d"), lChannel, lDevice); break; uidControllerVsys = uidIdeController; break; case HardDiskController::SATA: lChannelVsys = lChannel; // should be between 0 and 29 uidControllerVsys = uidSataController; break; case HardDiskController::SCSI: // mhda.busType = StorageBus_SCSI; throw setError(VBOX_E_NOT_SUPPORTED, tr("SCSI controller support is not available yet in VirtualBox")); // @todo break; default: break; } pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskImage, Utf8Str(bstrName), // disk ID: let's use the name "", // OVF value: unknown as of now Utf8Str(bstrLocation), // vbox value: media path Utf8StrFmt("controller=%d;channel=%d", uidControllerVsys, lChannelVsys)); } /* Floppy Drive */ if (fFloppyEnabled) pNewDesc->addEntry(VirtualSystemDescriptionType_Floppy, "", "", ""); /* CD Drive */ if (fDVDEnabled) pNewDesc->addEntry(VirtualSystemDescriptionType_CDROM, "", "", ""); // // // // // finally, add the virtual system to the appliance Appliance *pAppliance = static_cast(appliance); AutoCaller autoCaller(pAppliance); if (FAILED(rc)) throw rc; AutoWriteLock alock(pAppliance); pAppliance->m->virtualSystemDescriptions.push_back(pNewDesc); } catch(HRESULT arc) { rc = arc; } return rc; }