1
/* $Id: ApplianceImplExport.cpp $ */
4
* IAppliance and IVirtualSystem COM class implementations.
8
* Copyright (C) 2008-2010 Oracle Corporation
10
* This file is part of VirtualBox Open Source Edition (OSE), as
11
* available from http://www.virtualbox.org. This file is free software;
12
* you can redistribute it and/or modify it under the terms of the GNU
13
* General Public License (GPL) as published by the Free Software
14
* Foundation, in version 2 as it comes in the "COPYING" file of the
15
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
#include <iprt/path.h>
21
#include <iprt/param.h>
23
#include <iprt/manifest.h>
25
#include <VBox/version.h>
27
#include "ApplianceImpl.h"
28
#include "VirtualBoxImpl.h"
30
#include "ProgressImpl.h"
31
#include "MachineImpl.h"
33
#include "AutoCaller.h"
36
#include "ApplianceImplPrivate.h"
40
////////////////////////////////////////////////////////////////////////////////
42
// IMachine public methods
44
////////////////////////////////////////////////////////////////////////////////
46
// This code is here so we won't have to include the appliance headers in the
47
// IMachine implementation, and we also need to access private appliance data.
50
* Public method implementation.
55
STDMETHODIMP Machine::Export(IAppliance *aAppliance, IVirtualSystemDescription **aDescription)
62
AutoCaller autoCaller(this);
63
if (FAILED(autoCaller.rc())) return autoCaller.rc();
65
ComObjPtr<VirtualSystemDescription> pNewDesc;
69
// create a new virtual system to store in the appliance
70
rc = pNewDesc.createObject();
71
if (FAILED(rc)) throw rc;
72
rc = pNewDesc->init();
73
if (FAILED(rc)) throw rc;
75
// store the machine object so we can dump the XML in Appliance::Write()
76
pNewDesc->m->pMachine = this;
78
// now fill it with description items
86
AudioControllerType_T audioController;
88
ComPtr<IUSBController> pUsbController;
89
ComPtr<IAudioAdapter> pAudioAdapter;
91
// first, call the COM methods, as they request locks
92
rc = COMGETTER(USBController)(pUsbController.asOutParam());
96
rc = pUsbController->COMGETTER(Enabled)(&fUSBEnabled);
98
// request the machine lock while acessing internal members
99
AutoReadLock alock1(this COMMA_LOCKVAL_SRC_POS);
101
pAudioAdapter = mAudioAdapter;
102
rc = pAudioAdapter->COMGETTER(Enabled)(&fAudioEnabled);
103
if (FAILED(rc)) throw rc;
104
rc = pAudioAdapter->COMGETTER(AudioController)(&audioController);
105
if (FAILED(rc)) throw rc;
108
bstrName1 = mUserData->mName;
110
bstrDescription = mUserData->mDescription;
112
bstrGuestOSType = mUserData->mOSTypeId;
114
cCPUs = mHWData->mCPUCount;
116
ulMemSizeMB = mHWData->mMemorySize;
119
// 3D acceleration enabled?
120
// hardware virtualization enabled?
121
// nested paging enabled?
122
// HWVirtExVPIDEnabled?
128
Utf8Str strOsTypeVBox(bstrGuestOSType);
129
ovf::CIMOSType_T cim = convertVBoxOSType2CIMOSType(strOsTypeVBox.c_str());
130
pNewDesc->addEntry(VirtualSystemDescriptionType_OS,
132
Utf8StrFmt("%RI32", cim),
136
Utf8Str strVMName(bstrName1);
137
pNewDesc->addEntry(VirtualSystemDescriptionType_Name,
143
Utf8Str strDescription(bstrDescription);
144
pNewDesc->addEntry(VirtualSystemDescriptionType_Description,
150
Utf8Str strCpuCount = Utf8StrFmt("%RI32", cCPUs);
151
pNewDesc->addEntry(VirtualSystemDescriptionType_CPU,
157
Utf8Str strMemory = Utf8StrFmt("%RI64", (uint64_t)ulMemSizeMB * _1M);
158
pNewDesc->addEntry(VirtualSystemDescriptionType_Memory,
163
// the one VirtualBox IDE controller has two channels with two ports each, which is
164
// considered two IDE controllers with two ports each by OVF, so export it as two
165
int32_t lIDEControllerPrimaryIndex = 0;
166
int32_t lIDEControllerSecondaryIndex = 0;
167
int32_t lSATAControllerIndex = 0;
168
int32_t lSCSIControllerIndex = 0;
170
/* Fetch all available storage controllers */
171
com::SafeIfaceArray<IStorageController> nwControllers;
172
rc = COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(nwControllers));
173
if (FAILED(rc)) throw rc;
175
ComPtr<IStorageController> pIDEController;
176
ComPtr<IStorageController> pSATAController;
177
ComPtr<IStorageController> pSCSIController;
178
ComPtr<IStorageController> pSASController;
179
for (size_t j = 0; j < nwControllers.size(); ++j)
182
rc = nwControllers[j]->COMGETTER(Bus)(&eType);
183
if (FAILED(rc)) throw rc;
184
if ( eType == StorageBus_IDE
185
&& pIDEController.isNull())
186
pIDEController = nwControllers[j];
187
else if ( eType == StorageBus_SATA
188
&& pSATAController.isNull())
189
pSATAController = nwControllers[j];
190
else if ( eType == StorageBus_SCSI
191
&& pSATAController.isNull())
192
pSCSIController = nwControllers[j];
193
else if ( eType == StorageBus_SAS
194
&& pSASController.isNull())
195
pSASController = nwControllers[j];
198
// <const name="HardDiskControllerIDE" value="6" />
199
if (!pIDEController.isNull())
202
StorageControllerType_T ctlr;
203
rc = pIDEController->COMGETTER(ControllerType)(&ctlr);
204
if (FAILED(rc)) throw rc;
207
case StorageControllerType_PIIX3: strVbox = "PIIX3"; break;
208
case StorageControllerType_PIIX4: strVbox = "PIIX4"; break;
209
case StorageControllerType_ICH6: strVbox = "ICH6"; break;
212
if (strVbox.length())
214
lIDEControllerPrimaryIndex = (int32_t)pNewDesc->m->llDescriptions.size();
215
pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
216
Utf8StrFmt("%d", lIDEControllerPrimaryIndex), // strRef
217
strVbox, // aOvfValue
218
strVbox); // aVboxValue
219
lIDEControllerSecondaryIndex = lIDEControllerPrimaryIndex + 1;
220
pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
221
Utf8StrFmt("%d", lIDEControllerSecondaryIndex),
227
// <const name="HardDiskControllerSATA" value="7" />
228
if (!pSATAController.isNull())
230
Utf8Str strVbox = "AHCI";
231
lSATAControllerIndex = (int32_t)pNewDesc->m->llDescriptions.size();
232
pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA,
233
Utf8StrFmt("%d", lSATAControllerIndex),
238
// <const name="HardDiskControllerSCSI" value="8" />
239
if (!pSCSIController.isNull())
241
StorageControllerType_T ctlr;
242
rc = pSCSIController->COMGETTER(ControllerType)(&ctlr);
245
Utf8Str strVbox = "LsiLogic"; // the default in VBox
248
case StorageControllerType_LsiLogic: strVbox = "LsiLogic"; break;
249
case StorageControllerType_BusLogic: strVbox = "BusLogic"; break;
251
lSCSIControllerIndex = (int32_t)pNewDesc->m->llDescriptions.size();
252
pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSCSI,
253
Utf8StrFmt("%d", lSCSIControllerIndex),
261
if (!pSASController.isNull())
263
// VirtualBox considers the SAS controller a class of its own but in OVF
264
// it should be a SCSI controller
265
Utf8Str strVbox = "LsiLogicSas";
266
lSCSIControllerIndex = (int32_t)pNewDesc->m->llDescriptions.size();
267
pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSAS,
268
Utf8StrFmt("%d", lSCSIControllerIndex),
273
// <const name="HardDiskImage" value="9" />
274
// <const name="Floppy" value="18" />
275
// <const name="CDROM" value="19" />
277
MediaData::AttachmentList::iterator itA;
278
for (itA = mMediaData->mAttachments.begin();
279
itA != mMediaData->mAttachments.end();
282
ComObjPtr<MediumAttachment> pHDA = *itA;
284
// the attachment's data
285
ComPtr<IMedium> pMedium;
286
ComPtr<IStorageController> ctl;
289
rc = pHDA->COMGETTER(Controller)(controllerName.asOutParam());
290
if (FAILED(rc)) throw rc;
292
rc = GetStorageControllerByName(controllerName, ctl.asOutParam());
293
if (FAILED(rc)) throw rc;
295
StorageBus_T storageBus;
296
DeviceType_T deviceType;
300
rc = ctl->COMGETTER(Bus)(&storageBus);
301
if (FAILED(rc)) throw rc;
303
rc = pHDA->COMGETTER(Type)(&deviceType);
304
if (FAILED(rc)) throw rc;
306
rc = pHDA->COMGETTER(Medium)(pMedium.asOutParam());
307
if (FAILED(rc)) throw rc;
309
rc = pHDA->COMGETTER(Port)(&lChannel);
310
if (FAILED(rc)) throw rc;
312
rc = pHDA->COMGETTER(Device)(&lDevice);
313
if (FAILED(rc)) throw rc;
315
Utf8Str strTargetVmdkName;
319
if ( deviceType == DeviceType_HardDisk
324
rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
325
if (FAILED(rc)) throw rc;
326
strLocation = bstrLocation;
328
// find the source's base medium for two things:
329
// 1) we'll use its name to determine the name of the target disk, which is readable,
330
// as opposed to the UUID filename of a differencing image, if pMedium is one
331
// 2) we need the size of the base image so we can give it to addEntry(), and later
332
// on export, the progress will be based on that (and not the diff image)
333
ComPtr<IMedium> pBaseMedium;
334
rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
335
// returns pMedium if there are no diff images
336
if (FAILED(rc)) throw rc;
339
rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
340
if (FAILED(rc)) throw rc;
342
strTargetVmdkName = bstrBaseName;
343
strTargetVmdkName.stripExt();
344
strTargetVmdkName.append(".vmdk");
346
// force reading state, or else size will be returned as 0
348
rc = pBaseMedium->RefreshState(&ms);
349
if (FAILED(rc)) throw rc;
351
rc = pBaseMedium->COMGETTER(Size)(&ullSize);
352
if (FAILED(rc)) throw rc;
355
// and how this translates to the virtual system
356
int32_t lControllerVsys = 0;
362
// this is the exact reverse to what we're doing in Appliance::taskThreadImportMachines,
363
// and it must be updated when that is changed!
364
// Before 3.2 we exported one IDE controller with channel 0-3, but we now maintain
365
// compatibility with what VMware does and export two IDE controllers with two channels each
367
if (lChannel == 0 && lDevice == 0) // primary master
369
lControllerVsys = lIDEControllerPrimaryIndex;
372
else if (lChannel == 0 && lDevice == 1) // primary slave
374
lControllerVsys = lIDEControllerPrimaryIndex;
377
else if (lChannel == 1 && lDevice == 0) // secondary master; by default this is the CD-ROM but as of VirtualBox 3.1 that can change
379
lControllerVsys = lIDEControllerSecondaryIndex;
382
else if (lChannel == 1 && lDevice == 1) // secondary slave
384
lControllerVsys = lIDEControllerSecondaryIndex;
388
throw setError(VBOX_E_NOT_SUPPORTED,
389
tr("Cannot handle medium attachment: channel is %d, device is %d"), lChannel, lDevice);
392
case StorageBus_SATA:
393
lChannelVsys = lChannel; // should be between 0 and 29
394
lControllerVsys = lSATAControllerIndex;
397
case StorageBus_SCSI:
399
lChannelVsys = lChannel; // should be between 0 and 15
400
lControllerVsys = lSCSIControllerIndex;
403
case StorageBus_Floppy:
409
throw setError(VBOX_E_NOT_SUPPORTED,
410
tr("Cannot handle medium attachment: storageBus is %d, channel is %d, device is %d"), storageBus, lChannel, lDevice);
414
Utf8StrFmt strExtra("controller=%RI32;channel=%RI32", lControllerVsys, lChannelVsys);
419
case DeviceType_HardDisk:
420
Log(("Adding VirtualSystemDescriptionType_HardDiskImage, disk size: %RI64\n", ullSize));
421
pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskImage,
422
strTargetVmdkName, // disk ID: let's use the name
423
strTargetVmdkName, // OVF value:
424
strLocation, // vbox value: media path
425
(uint32_t)(ullSize / _1M),
430
pNewDesc->addEntry(VirtualSystemDescriptionType_CDROM,
432
strEmpty, // OVF value
433
strEmpty, // vbox value
438
case DeviceType_Floppy:
439
pNewDesc->addEntry(VirtualSystemDescriptionType_Floppy,
441
strEmpty, // OVF value
442
strEmpty, // vbox value
449
// <const name="NetworkAdapter" />
452
a < SchemaDefs::NetworkAdapterCount;
455
ComPtr<INetworkAdapter> pNetworkAdapter;
457
NetworkAdapterType_T adapterType;
458
NetworkAttachmentType_T attachmentType;
460
rc = GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
461
if (FAILED(rc)) throw rc;
462
/* Enable the network card & set the adapter type */
463
rc = pNetworkAdapter->COMGETTER(Enabled)(&fEnabled);
464
if (FAILED(rc)) throw rc;
468
Utf8Str strAttachmentType;
470
rc = pNetworkAdapter->COMGETTER(AdapterType)(&adapterType);
471
if (FAILED(rc)) throw rc;
473
rc = pNetworkAdapter->COMGETTER(AttachmentType)(&attachmentType);
474
if (FAILED(rc)) throw rc;
476
switch (attachmentType)
478
case NetworkAttachmentType_Null:
479
strAttachmentType = "Null";
482
case NetworkAttachmentType_NAT:
483
strAttachmentType = "NAT";
486
case NetworkAttachmentType_Bridged:
487
strAttachmentType = "Bridged";
490
case NetworkAttachmentType_Internal:
491
strAttachmentType = "Internal";
494
case NetworkAttachmentType_HostOnly:
495
strAttachmentType = "HostOnly";
499
pNewDesc->addEntry(VirtualSystemDescriptionType_NetworkAdapter,
501
strAttachmentType, // orig
502
Utf8StrFmt("%RI32", (uint32_t)adapterType), // conf
504
Utf8StrFmt("type=%s", strAttachmentType.c_str())); // extra conf
508
// <const name="USBController" />
511
pNewDesc->addEntry(VirtualSystemDescriptionType_USBController, "", "", "");
512
#endif /* VBOX_WITH_USB */
514
// <const name="SoundCard" />
516
pNewDesc->addEntry(VirtualSystemDescriptionType_SoundCard,
518
"ensoniq1371", // this is what OVFTool writes and VMware supports
519
Utf8StrFmt("%RI32", audioController));
521
// finally, add the virtual system to the appliance
522
Appliance *pAppliance = static_cast<Appliance*>(aAppliance);
523
AutoCaller autoCaller1(pAppliance);
524
if (FAILED(autoCaller1.rc())) return autoCaller1.rc();
526
/* We return the new description to the caller */
527
ComPtr<IVirtualSystemDescription> copy(pNewDesc);
528
copy.queryInterfaceTo(aDescription);
530
AutoWriteLock alock(pAppliance COMMA_LOCKVAL_SRC_POS);
532
pAppliance->m->virtualSystemDescriptions.push_back(pNewDesc);
542
////////////////////////////////////////////////////////////////////////////////
544
// IAppliance public methods
546
////////////////////////////////////////////////////////////////////////////////
549
* Public method implementation.
555
STDMETHODIMP Appliance::Write(IN_BSTR format, IN_BSTR path, IProgress **aProgress)
557
if (!path) return E_POINTER;
558
CheckComArgOutPointerValid(aProgress);
560
AutoCaller autoCaller(this);
561
if (FAILED(autoCaller.rc())) return autoCaller.rc();
563
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
565
// do not allow entering this method if the appliance is busy reading or writing
566
if (!isApplianceIdle())
567
return E_ACCESSDENIED;
569
// see if we can handle this file; for now we insist it has an ".ovf" extension
570
Utf8Str strPath = path;
571
if (!strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
572
return setError(VBOX_E_FILE_ERROR,
573
tr("Appliance file must have .ovf extension"));
575
Utf8Str strFormat(format);
577
if (strFormat == "ovf-0.9")
579
else if (strFormat == "ovf-1.0")
582
return setError(VBOX_E_FILE_ERROR,
583
tr("Invalid format \"%s\" specified"), strFormat.c_str());
585
ComObjPtr<Progress> progress;
589
/* Parse all necessary info out of the URI */
590
parseURI(strPath, m->locInfo);
591
rc = writeImpl(ovfF, m->locInfo, progress);
599
/* Return progress to the caller */
600
progress.queryInterfaceTo(aProgress);
605
////////////////////////////////////////////////////////////////////////////////
607
// Appliance private methods
609
////////////////////////////////////////////////////////////////////////////////
612
* Implementation for writing out the OVF to disk. This starts a new thread which will call
613
* Appliance::taskThreadWriteOVF().
615
* This is in a separate private method because it is used from two locations:
617
* 1) from the public Appliance::Write().
618
* 2) from Appliance::writeS3(), which got called from a previous instance of Appliance::taskThreadWriteOVF().
625
HRESULT Appliance::writeImpl(OVFFormat aFormat, const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
630
rc = setUpProgress(aProgress,
631
BstrFmt(tr("Export appliance '%s'"), aLocInfo.strPath.c_str()),
632
(aLocInfo.storageType == VFSType_File) ? WriteFile : WriteS3);
634
/* Initialize our worker task */
635
std::auto_ptr<TaskOVF> task(new TaskOVF(this, TaskOVF::Write, aLocInfo, aProgress));
636
/* The OVF version to write */
637
task->enFormat = aFormat;
639
rc = task->startThread();
640
if (FAILED(rc)) throw rc;
642
/* Don't destruct on success */
654
* Called from Appliance::writeFS() for each virtual system (machine) that needs XML written out.
656
* @param elmToAddVirtualSystemsTo XML element to append elements to.
657
* @param pllElementsWithUuidAttributes out: list of XML elements produced here with UUID attributes for quick fixing by caller later
658
* @param vsdescThis The IVirtualSystemDescription instance for which to write XML.
659
* @param enFormat OVF format (0.9 or 1.0).
660
* @param stack Structure for temporary private data shared with caller.
662
void Appliance::buildXMLForOneVirtualSystem(xml::ElementNode &elmToAddVirtualSystemsTo,
663
std::list<xml::ElementNode*> *pllElementsWithUuidAttributes,
664
ComObjPtr<VirtualSystemDescription> &vsdescThis,
668
LogFlowFunc(("ENTER appliance %p\n", this));
670
xml::ElementNode *pelmVirtualSystem;
671
if (enFormat == OVF_0_9)
673
// <Section xsi:type="ovf:NetworkSection_Type">
674
pelmVirtualSystem = elmToAddVirtualSystemsTo.createChild("Content");
675
pelmVirtualSystem->setAttribute("xsi:type", "ovf:VirtualSystem_Type");
678
pelmVirtualSystem = elmToAddVirtualSystemsTo.createChild("VirtualSystem");
680
/*xml::ElementNode *pelmVirtualSystemInfo =*/ pelmVirtualSystem->createChild("Info")->addContent("A virtual machine");
682
std::list<VirtualSystemDescriptionEntry*> llName = vsdescThis->findByType(VirtualSystemDescriptionType_Name);
683
if (llName.size() != 1)
684
throw setError(VBOX_E_NOT_SUPPORTED,
685
tr("Missing VM name"));
686
Utf8Str &strVMName = llName.front()->strVboxCurrent;
687
pelmVirtualSystem->setAttribute("ovf:id", strVMName);
690
std::list<VirtualSystemDescriptionEntry*> llProduct = vsdescThis->findByType(VirtualSystemDescriptionType_Product);
691
std::list<VirtualSystemDescriptionEntry*> llProductUrl = vsdescThis->findByType(VirtualSystemDescriptionType_ProductUrl);
692
std::list<VirtualSystemDescriptionEntry*> llVendor = vsdescThis->findByType(VirtualSystemDescriptionType_Vendor);
693
std::list<VirtualSystemDescriptionEntry*> llVendorUrl = vsdescThis->findByType(VirtualSystemDescriptionType_VendorUrl);
694
std::list<VirtualSystemDescriptionEntry*> llVersion = vsdescThis->findByType(VirtualSystemDescriptionType_Version);
695
bool fProduct = llProduct.size() && !llProduct.front()->strVboxCurrent.isEmpty();
696
bool fProductUrl = llProductUrl.size() && !llProductUrl.front()->strVboxCurrent.isEmpty();
697
bool fVendor = llVendor.size() && !llVendor.front()->strVboxCurrent.isEmpty();
698
bool fVendorUrl = llVendorUrl.size() && !llVendorUrl.front()->strVboxCurrent.isEmpty();
699
bool fVersion = llVersion.size() && !llVersion.front()->strVboxCurrent.isEmpty();
706
/* <Section ovf:required="false" xsi:type="ovf:ProductSection_Type">
707
<Info>Meta-information about the installed software</Info>
708
<Product>VAtest</Product>
709
<Vendor>SUN Microsystems</Vendor>
710
<Version>10.0</Version>
711
<ProductUrl>http://blogs.sun.com/VirtualGuru</ProductUrl>
712
<VendorUrl>http://www.sun.com</VendorUrl>
714
xml::ElementNode *pelmAnnotationSection;
715
if (enFormat == OVF_0_9)
717
// <Section ovf:required="false" xsi:type="ovf:ProductSection_Type">
718
pelmAnnotationSection = pelmVirtualSystem->createChild("Section");
719
pelmAnnotationSection->setAttribute("xsi:type", "ovf:ProductSection_Type");
722
pelmAnnotationSection = pelmVirtualSystem->createChild("ProductSection");
724
pelmAnnotationSection->createChild("Info")->addContent("Meta-information about the installed software");
726
pelmAnnotationSection->createChild("Product")->addContent(llProduct.front()->strVboxCurrent);
728
pelmAnnotationSection->createChild("Vendor")->addContent(llVendor.front()->strVboxCurrent);
730
pelmAnnotationSection->createChild("Version")->addContent(llVersion.front()->strVboxCurrent);
732
pelmAnnotationSection->createChild("ProductUrl")->addContent(llProductUrl.front()->strVboxCurrent);
734
pelmAnnotationSection->createChild("VendorUrl")->addContent(llVendorUrl.front()->strVboxCurrent);
738
std::list<VirtualSystemDescriptionEntry*> llDescription = vsdescThis->findByType(VirtualSystemDescriptionType_Description);
739
if (llDescription.size() &&
740
!llDescription.front()->strVboxCurrent.isEmpty())
742
/* <Section ovf:required="false" xsi:type="ovf:AnnotationSection_Type">
743
<Info>A human-readable annotation</Info>
744
<Annotation>Plan 9</Annotation>
746
xml::ElementNode *pelmAnnotationSection;
747
if (enFormat == OVF_0_9)
749
// <Section ovf:required="false" xsi:type="ovf:AnnotationSection_Type">
750
pelmAnnotationSection = pelmVirtualSystem->createChild("Section");
751
pelmAnnotationSection->setAttribute("xsi:type", "ovf:AnnotationSection_Type");
754
pelmAnnotationSection = pelmVirtualSystem->createChild("AnnotationSection");
756
pelmAnnotationSection->createChild("Info")->addContent("A human-readable annotation");
757
pelmAnnotationSection->createChild("Annotation")->addContent(llDescription.front()->strVboxCurrent);
761
std::list<VirtualSystemDescriptionEntry*> llLicense = vsdescThis->findByType(VirtualSystemDescriptionType_License);
762
if (llLicense.size() &&
763
!llLicense.front()->strVboxCurrent.isEmpty())
766
<Info ovf:msgid="6">License agreement for the Virtual System.</Info>
767
<License ovf:msgid="1">License terms can go in here.</License>
769
xml::ElementNode *pelmEulaSection;
770
if (enFormat == OVF_0_9)
772
pelmEulaSection = pelmVirtualSystem->createChild("Section");
773
pelmEulaSection->setAttribute("xsi:type", "ovf:EulaSection_Type");
776
pelmEulaSection = pelmVirtualSystem->createChild("EulaSection");
778
pelmEulaSection->createChild("Info")->addContent("License agreement for the virtual system");
779
pelmEulaSection->createChild("License")->addContent(llLicense.front()->strVboxCurrent);
783
std::list<VirtualSystemDescriptionEntry*> llOS = vsdescThis->findByType(VirtualSystemDescriptionType_OS);
784
if (llOS.size() != 1)
785
throw setError(VBOX_E_NOT_SUPPORTED,
786
tr("Missing OS type"));
787
/* <OperatingSystemSection ovf:id="82">
788
<Info>Guest Operating System</Info>
789
<Description>Linux 2.6.x</Description>
790
</OperatingSystemSection> */
791
xml::ElementNode *pelmOperatingSystemSection;
792
if (enFormat == OVF_0_9)
794
pelmOperatingSystemSection = pelmVirtualSystem->createChild("Section");
795
pelmOperatingSystemSection->setAttribute("xsi:type", "ovf:OperatingSystemSection_Type");
798
pelmOperatingSystemSection = pelmVirtualSystem->createChild("OperatingSystemSection");
800
pelmOperatingSystemSection->setAttribute("ovf:id", llOS.front()->strOvf);
801
pelmOperatingSystemSection->createChild("Info")->addContent("The kind of installed guest operating system");
803
convertCIMOSType2VBoxOSType(strOSDesc, (ovf::CIMOSType_T)llOS.front()->strOvf.toInt32(), "");
804
pelmOperatingSystemSection->createChild("Description")->addContent(strOSDesc);
806
// <VirtualHardwareSection ovf:id="hw1" ovf:transport="iso">
807
xml::ElementNode *pelmVirtualHardwareSection;
808
if (enFormat == OVF_0_9)
810
// <Section xsi:type="ovf:VirtualHardwareSection_Type">
811
pelmVirtualHardwareSection = pelmVirtualSystem->createChild("Section");
812
pelmVirtualHardwareSection->setAttribute("xsi:type", "ovf:VirtualHardwareSection_Type");
815
pelmVirtualHardwareSection = pelmVirtualSystem->createChild("VirtualHardwareSection");
817
pelmVirtualHardwareSection->createChild("Info")->addContent("Virtual hardware requirements for a virtual machine");
820
<vssd:Description>Description of the virtual hardware section.</vssd:Description>
821
<vssd:ElementName>vmware</vssd:ElementName>
822
<vssd:InstanceID>1</vssd:InstanceID>
823
<vssd:VirtualSystemIdentifier>MyLampService</vssd:VirtualSystemIdentifier>
824
<vssd:VirtualSystemType>vmx-4</vssd:VirtualSystemType>
826
xml::ElementNode *pelmSystem = pelmVirtualHardwareSection->createChild("System");
828
pelmSystem->createChild("vssd:ElementName")->addContent("Virtual Hardware Family"); // required OVF 1.0
830
// <vssd:InstanceId>0</vssd:InstanceId>
831
if (enFormat == OVF_0_9)
832
pelmSystem->createChild("vssd:InstanceId")->addContent("0");
833
else // capitalization changed...
834
pelmSystem->createChild("vssd:InstanceID")->addContent("0");
836
// <vssd:VirtualSystemIdentifier>VAtest</vssd:VirtualSystemIdentifier>
837
pelmSystem->createChild("vssd:VirtualSystemIdentifier")->addContent(strVMName);
838
// <vssd:VirtualSystemType>vmx-4</vssd:VirtualSystemType>
839
const char *pcszHardware = "virtualbox-2.2";
840
if (enFormat == OVF_0_9)
841
// pretend to be vmware compatible then
842
pcszHardware = "vmx-6";
843
pelmSystem->createChild("vssd:VirtualSystemType")->addContent(pcszHardware);
845
// loop thru all description entries twice; once to write out all
846
// devices _except_ disk images, and a second time to assign the
847
// disk images; this is because disk images need to reference
848
// IDE controllers, and we can't know their instance IDs without
849
// assigning them first
851
uint32_t idIDEPrimaryController = 0;
852
int32_t lIDEPrimaryControllerIndex = 0;
853
uint32_t idIDESecondaryController = 0;
854
int32_t lIDESecondaryControllerIndex = 0;
855
uint32_t idSATAController = 0;
856
int32_t lSATAControllerIndex = 0;
857
uint32_t idSCSIController = 0;
858
int32_t lSCSIControllerIndex = 0;
860
uint32_t ulInstanceID = 1;
862
for (size_t uLoop = 1; uLoop <= 2; ++uLoop)
864
int32_t lIndexThis = 0;
865
list<VirtualSystemDescriptionEntry>::const_iterator itD;
866
for (itD = vsdescThis->m->llDescriptions.begin();
867
itD != vsdescThis->m->llDescriptions.end();
870
const VirtualSystemDescriptionEntry &desc = *itD;
872
LogFlowFunc(("Loop %u: handling description entry ulIndex=%u, type=%s, strRef=%s, strOvf=%s, strVbox=%s, strExtraConfig=%s\n",
875
( desc.type == VirtualSystemDescriptionType_HardDiskControllerIDE ? "HardDiskControllerIDE"
876
: desc.type == VirtualSystemDescriptionType_HardDiskControllerSATA ? "HardDiskControllerSATA"
877
: desc.type == VirtualSystemDescriptionType_HardDiskControllerSCSI ? "HardDiskControllerSCSI"
878
: desc.type == VirtualSystemDescriptionType_HardDiskControllerSAS ? "HardDiskControllerSAS"
879
: desc.type == VirtualSystemDescriptionType_HardDiskImage ? "HardDiskImage"
880
: Utf8StrFmt("%d", desc.type).c_str()),
883
desc.strVboxCurrent.c_str(),
884
desc.strExtraConfigCurrent.c_str()));
886
ovf::ResourceType_T type = (ovf::ResourceType_T)0; // if this becomes != 0 then we do stuff
887
Utf8Str strResourceSubType;
889
Utf8Str strDescription; // results in <rasd:Description>...</rasd:Description> block
890
Utf8Str strCaption; // results in <rasd:Caption>...</rasd:Caption> block
892
uint32_t ulParent = 0;
894
int32_t lVirtualQuantity = -1;
895
Utf8Str strAllocationUnits;
897
int32_t lAddress = -1;
898
int32_t lBusNumber = -1;
899
int32_t lAddressOnParent = -1;
901
int32_t lAutomaticAllocation = -1; // 0 means "false", 1 means "true"
902
Utf8Str strConnection; // results in <rasd:Connection>...</rasd:Connection> block
903
Utf8Str strHostResource;
909
case VirtualSystemDescriptionType_CPU:
911
<rasd:Caption>1 virtual CPU</rasd:Caption>
912
<rasd:Description>Number of virtual CPUs</rasd:Description>
913
<rasd:ElementName>virtual CPU</rasd:ElementName>
914
<rasd:InstanceID>1</rasd:InstanceID>
915
<rasd:ResourceType>3</rasd:ResourceType>
916
<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
920
strDescription = "Number of virtual CPUs";
921
type = ovf::ResourceType_Processor; // 3
922
desc.strVboxCurrent.toInt(uTemp);
923
lVirtualQuantity = (int32_t)uTemp;
924
strCaption = Utf8StrFmt("%d virtual CPU", lVirtualQuantity); // without this ovftool won't eat the item
928
case VirtualSystemDescriptionType_Memory:
930
<rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
931
<rasd:Caption>256 MB of memory</rasd:Caption>
932
<rasd:Description>Memory Size</rasd:Description>
933
<rasd:ElementName>Memory</rasd:ElementName>
934
<rasd:InstanceID>2</rasd:InstanceID>
935
<rasd:ResourceType>4</rasd:ResourceType>
936
<rasd:VirtualQuantity>256</rasd:VirtualQuantity>
940
strDescription = "Memory Size";
941
type = ovf::ResourceType_Memory; // 4
942
desc.strVboxCurrent.toInt(uTemp);
943
lVirtualQuantity = (int32_t)(uTemp / _1M);
944
strAllocationUnits = "MegaBytes";
945
strCaption = Utf8StrFmt("%d MB of memory", lVirtualQuantity); // without this ovftool won't eat the item
949
case VirtualSystemDescriptionType_HardDiskControllerIDE:
951
<rasd:Caption>ideController1</rasd:Caption>
952
<rasd:Description>IDE Controller</rasd:Description>
953
<rasd:InstanceId>5</rasd:InstanceId>
954
<rasd:ResourceType>5</rasd:ResourceType>
955
<rasd:Address>1</rasd:Address>
956
<rasd:BusNumber>1</rasd:BusNumber>
960
strDescription = "IDE Controller";
961
type = ovf::ResourceType_IDEController; // 5
962
strResourceSubType = desc.strVboxCurrent;
964
if (!lIDEPrimaryControllerIndex)
966
// first IDE controller:
967
strCaption = "ideController0";
971
idIDEPrimaryController = ulInstanceID;
972
lIDEPrimaryControllerIndex = lIndexThis;
976
// second IDE controller:
977
strCaption = "ideController1";
981
idIDESecondaryController = ulInstanceID;
982
lIDESecondaryControllerIndex = lIndexThis;
987
case VirtualSystemDescriptionType_HardDiskControllerSATA:
989
<rasd:Caption>sataController0</rasd:Caption>
990
<rasd:Description>SATA Controller</rasd:Description>
991
<rasd:InstanceId>4</rasd:InstanceId>
992
<rasd:ResourceType>20</rasd:ResourceType>
993
<rasd:ResourceSubType>ahci</rasd:ResourceSubType>
994
<rasd:Address>0</rasd:Address>
995
<rasd:BusNumber>0</rasd:BusNumber>
1000
strDescription = "SATA Controller";
1001
strCaption = "sataController0";
1002
type = ovf::ResourceType_OtherStorageDevice; // 20
1003
// it seems that OVFTool always writes these two, and since we can only
1004
// have one SATA controller, we'll use this as well
1008
if ( desc.strVboxCurrent.isEmpty() // AHCI is the default in VirtualBox
1009
|| (!desc.strVboxCurrent.compare("ahci", Utf8Str::CaseInsensitive))
1011
strResourceSubType = "AHCI";
1013
throw setError(VBOX_E_NOT_SUPPORTED,
1014
tr("Invalid config string \"%s\" in SATA controller"), desc.strVboxCurrent.c_str());
1017
idSATAController = ulInstanceID;
1018
lSATAControllerIndex = lIndexThis;
1022
case VirtualSystemDescriptionType_HardDiskControllerSCSI:
1023
case VirtualSystemDescriptionType_HardDiskControllerSAS:
1025
<rasd:Caption>scsiController0</rasd:Caption>
1026
<rasd:Description>SCSI Controller</rasd:Description>
1027
<rasd:InstanceId>4</rasd:InstanceId>
1028
<rasd:ResourceType>6</rasd:ResourceType>
1029
<rasd:ResourceSubType>buslogic</rasd:ResourceSubType>
1030
<rasd:Address>0</rasd:Address>
1031
<rasd:BusNumber>0</rasd:BusNumber>
1036
strDescription = "SCSI Controller";
1037
strCaption = "scsiController0";
1038
type = ovf::ResourceType_ParallelSCSIHBA; // 6
1039
// it seems that OVFTool always writes these two, and since we can only
1040
// have one SATA controller, we'll use this as well
1044
if ( desc.strVboxCurrent.isEmpty() // LsiLogic is the default in VirtualBox
1045
|| (!desc.strVboxCurrent.compare("lsilogic", Utf8Str::CaseInsensitive))
1047
strResourceSubType = "lsilogic";
1048
else if (!desc.strVboxCurrent.compare("buslogic", Utf8Str::CaseInsensitive))
1049
strResourceSubType = "buslogic";
1050
else if (!desc.strVboxCurrent.compare("lsilogicsas", Utf8Str::CaseInsensitive))
1051
strResourceSubType = "lsilogicsas";
1053
throw setError(VBOX_E_NOT_SUPPORTED,
1054
tr("Invalid config string \"%s\" in SCSI/SAS controller"), desc.strVboxCurrent.c_str());
1057
idSCSIController = ulInstanceID;
1058
lSCSIControllerIndex = lIndexThis;
1062
case VirtualSystemDescriptionType_HardDiskImage:
1064
<rasd:Caption>disk1</rasd:Caption>
1065
<rasd:InstanceId>8</rasd:InstanceId>
1066
<rasd:ResourceType>17</rasd:ResourceType>
1067
<rasd:HostResource>/disk/vmdisk1</rasd:HostResource>
1068
<rasd:Parent>4</rasd:Parent>
1069
<rasd:AddressOnParent>0</rasd:AddressOnParent>
1073
uint32_t cDisks = stack.mapDisks.size();
1074
Utf8Str strDiskID = Utf8StrFmt("vmdisk%RI32", ++cDisks);
1076
strDescription = "Disk Image";
1077
strCaption = Utf8StrFmt("disk%RI32", cDisks); // this is not used for anything else
1078
type = ovf::ResourceType_HardDisk; // 17
1080
// the following references the "<Disks>" XML block
1081
strHostResource = Utf8StrFmt("/disk/%s", strDiskID.c_str());
1083
// controller=<index>;channel=<c>
1084
size_t pos1 = desc.strExtraConfigCurrent.find("controller=");
1085
size_t pos2 = desc.strExtraConfigCurrent.find("channel=");
1086
int32_t lControllerIndex = -1;
1087
if (pos1 != Utf8Str::npos)
1089
RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos1 + 11, NULL, 0, &lControllerIndex);
1090
if (lControllerIndex == lIDEPrimaryControllerIndex)
1091
ulParent = idIDEPrimaryController;
1092
else if (lControllerIndex == lIDESecondaryControllerIndex)
1093
ulParent = idIDESecondaryController;
1094
else if (lControllerIndex == lSCSIControllerIndex)
1095
ulParent = idSCSIController;
1096
else if (lControllerIndex == lSATAControllerIndex)
1097
ulParent = idSATAController;
1099
if (pos2 != Utf8Str::npos)
1100
RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos2 + 8, NULL, 0, &lAddressOnParent);
1102
LogFlowFunc(("HardDiskImage details: pos1=%d, pos2=%d, lControllerIndex=%d, lIDEPrimaryControllerIndex=%d, lIDESecondaryControllerIndex=%d, ulParent=%d, lAddressOnParent=%d\n",
1103
pos1, pos2, lControllerIndex, lIDEPrimaryControllerIndex, lIDESecondaryControllerIndex, ulParent, lAddressOnParent));
1106
|| lAddressOnParent == -1
1108
throw setError(VBOX_E_NOT_SUPPORTED,
1109
tr("Missing or bad extra config string in hard disk image: \"%s\""), desc.strExtraConfigCurrent.c_str());
1111
stack.mapDisks[strDiskID] = &desc;
1115
case VirtualSystemDescriptionType_Floppy:
1118
strDescription = "Floppy Drive";
1119
strCaption = "floppy0"; // this is what OVFTool writes
1120
type = ovf::ResourceType_FloppyDrive; // 14
1121
lAutomaticAllocation = 0;
1122
lAddressOnParent = 0; // this is what OVFTool writes
1126
case VirtualSystemDescriptionType_CDROM:
1129
// we can't have a CD without an IDE controller
1130
if (!idIDESecondaryController)
1131
throw setError(VBOX_E_NOT_SUPPORTED,
1132
tr("Can't have CD-ROM without secondary IDE controller"));
1134
strDescription = "CD-ROM Drive";
1135
strCaption = "cdrom1"; // this is what OVFTool writes
1136
type = ovf::ResourceType_CDDrive; // 15
1137
lAutomaticAllocation = 1;
1138
ulParent = idIDESecondaryController;
1139
lAddressOnParent = 0; // this is what OVFTool writes
1143
case VirtualSystemDescriptionType_NetworkAdapter:
1145
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
1146
<rasd:Caption>Ethernet adapter on 'VM Network'</rasd:Caption>
1147
<rasd:Connection>VM Network</rasd:Connection>
1148
<rasd:ElementName>VM network</rasd:ElementName>
1149
<rasd:InstanceID>3</rasd:InstanceID>
1150
<rasd:ResourceType>10</rasd:ResourceType>
1154
lAutomaticAllocation = 1;
1155
strCaption = Utf8StrFmt("Ethernet adapter on '%s'", desc.strOvf.c_str());
1156
type = ovf::ResourceType_EthernetAdapter; // 10
1157
/* Set the hardware type to something useful.
1158
* To be compatible with vmware & others we set
1159
* PCNet32 for our PCNet types & E1000 for the
1161
switch (desc.strVboxCurrent.toInt32())
1163
case NetworkAdapterType_Am79C970A:
1164
case NetworkAdapterType_Am79C973: strResourceSubType = "PCNet32"; break;
1165
#ifdef VBOX_WITH_E1000
1166
case NetworkAdapterType_I82540EM:
1167
case NetworkAdapterType_I82545EM:
1168
case NetworkAdapterType_I82543GC: strResourceSubType = "E1000"; break;
1169
#endif /* VBOX_WITH_E1000 */
1171
strConnection = desc.strOvf;
1173
stack.mapNetworks[desc.strOvf] = true;
1177
case VirtualSystemDescriptionType_USBController:
1178
/* <Item ovf:required="false">
1179
<rasd:Caption>usb</rasd:Caption>
1180
<rasd:Description>USB Controller</rasd:Description>
1181
<rasd:InstanceId>3</rasd:InstanceId>
1182
<rasd:ResourceType>23</rasd:ResourceType>
1183
<rasd:Address>0</rasd:Address>
1184
<rasd:BusNumber>0</rasd:BusNumber>
1188
strDescription = "USB Controller";
1190
type = ovf::ResourceType_USBController; // 23
1191
lAddress = 0; // this is what OVFTool writes
1192
lBusNumber = 0; // this is what OVFTool writes
1196
case VirtualSystemDescriptionType_SoundCard:
1197
/* <Item ovf:required="false">
1198
<rasd:Caption>sound</rasd:Caption>
1199
<rasd:Description>Sound Card</rasd:Description>
1200
<rasd:InstanceId>10</rasd:InstanceId>
1201
<rasd:ResourceType>35</rasd:ResourceType>
1202
<rasd:ResourceSubType>ensoniq1371</rasd:ResourceSubType>
1203
<rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
1204
<rasd:AddressOnParent>3</rasd:AddressOnParent>
1208
strDescription = "Sound Card";
1209
strCaption = "sound";
1210
type = ovf::ResourceType_SoundCard; // 35
1211
strResourceSubType = desc.strOvf; // e.g. ensoniq1371
1212
lAutomaticAllocation = 0;
1213
lAddressOnParent = 3; // what gives? this is what OVFTool writes
1220
xml::ElementNode *pItem;
1222
pItem = pelmVirtualHardwareSection->createChild("Item");
1224
// NOTE: DO NOT CHANGE THE ORDER of these items! The OVF standards prescribes that
1225
// the elements from the rasd: namespace must be sorted by letter, and VMware
1226
// actually requires this as well (see public bug #6612)
1229
pItem->createChild("rasd:Address")->addContent(Utf8StrFmt("%d", lAddress));
1231
if (lAddressOnParent != -1)
1232
pItem->createChild("rasd:AddressOnParent")->addContent(Utf8StrFmt("%d", lAddressOnParent));
1234
if (!strAllocationUnits.isEmpty())
1235
pItem->createChild("rasd:AllocationUnits")->addContent(strAllocationUnits);
1237
if (lAutomaticAllocation != -1)
1238
pItem->createChild("rasd:AutomaticAllocation")->addContent( (lAutomaticAllocation) ? "true" : "false" );
1240
if (lBusNumber != -1)
1241
if (enFormat == OVF_0_9) // BusNumber is invalid OVF 1.0 so only write it in 0.9 mode for OVFTool compatibility
1242
pItem->createChild("rasd:BusNumber")->addContent(Utf8StrFmt("%d", lBusNumber));
1244
if (!strCaption.isEmpty())
1245
pItem->createChild("rasd:Caption")->addContent(strCaption);
1247
if (!strConnection.isEmpty())
1248
pItem->createChild("rasd:Connection")->addContent(strConnection);
1250
if (!strDescription.isEmpty())
1251
pItem->createChild("rasd:Description")->addContent(strDescription);
1253
if (!strCaption.isEmpty())
1254
if (enFormat == OVF_1_0)
1255
pItem->createChild("rasd:ElementName")->addContent(strCaption);
1257
if (!strHostResource.isEmpty())
1258
pItem->createChild("rasd:HostResource")->addContent(strHostResource);
1260
// <rasd:InstanceID>1</rasd:InstanceID>
1261
xml::ElementNode *pelmInstanceID;
1262
if (enFormat == OVF_0_9)
1263
pelmInstanceID = pItem->createChild("rasd:InstanceId");
1265
pelmInstanceID = pItem->createChild("rasd:InstanceID"); // capitalization changed...
1266
pelmInstanceID->addContent(Utf8StrFmt("%d", ulInstanceID++));
1269
pItem->createChild("rasd:Parent")->addContent(Utf8StrFmt("%d", ulParent));
1271
if (!strResourceSubType.isEmpty())
1272
pItem->createChild("rasd:ResourceSubType")->addContent(strResourceSubType);
1274
// <rasd:ResourceType>3</rasd:ResourceType>
1275
pItem->createChild("rasd:ResourceType")->addContent(Utf8StrFmt("%d", type));
1277
// <rasd:VirtualQuantity>1</rasd:VirtualQuantity>
1278
if (lVirtualQuantity != -1)
1279
pItem->createChild("rasd:VirtualQuantity")->addContent(Utf8StrFmt("%d", lVirtualQuantity));
1282
} // for (size_t uLoop = 1; uLoop <= 2; ++uLoop)
1284
// now that we're done with the official OVF <Item> tags under <VirtualSystem>, write out VirtualBox XML
1285
// under the vbox: namespace
1286
xml::ElementNode *pelmVBoxMachine = pelmVirtualSystem->createChild("vbox:Machine");
1287
// ovf:required="false" tells other OVF parsers that they can ignore this thing
1288
pelmVBoxMachine->setAttribute("ovf:required", "false");
1289
// ovf:Info element is required or VMware will bail out on the vbox:Machine element
1290
pelmVBoxMachine->createChild("ovf:Info")->addContent("Complete VirtualBox machine configuration in VirtualBox format");
1292
// create an empty machine config
1293
settings::MachineConfigFile *pConfig = new settings::MachineConfigFile(NULL);
1297
AutoWriteLock machineLock(vsdescThis->m->pMachine COMMA_LOCKVAL_SRC_POS);
1298
// fill the machine config
1299
vsdescThis->m->pMachine->copyMachineDataToSettings(*pConfig);
1300
// write the machine config to the vbox:Machine element
1301
pConfig->buildMachineXML(*pelmVBoxMachine,
1302
settings::MachineConfigFile::BuildMachineXML_WriteVboxVersionAttribute
1303
| settings::MachineConfigFile::BuildMachineXML_SkipRemovableMedia,
1304
// but not BuildMachineXML_IncludeSnapshots
1305
pllElementsWithUuidAttributes);
1316
* Actual worker code for writing out OVF to disk. This is called from Appliance::taskThreadWriteOVF()
1317
* and therefore runs on the OVF write worker thread. This runs in two contexts:
1319
* 1) in a first worker thread; in that case, Appliance::Write() called Appliance::writeImpl();
1321
* 2) in a second worker thread; in that case, Appliance::Write() called Appliance::writeImpl(), which
1322
* called Appliance::writeS3(), which called Appliance::writeImpl(), which then called this. In other
1323
* words, to write to the cloud, the first worker thread first starts a second worker thread to create
1324
* temporary files and then uploads them to the S3 cloud server.
1329
HRESULT Appliance::writeFS(const LocationInfo &locInfo, const OVFFormat enFormat, ComObjPtr<Progress> &pProgress)
1331
LogFlowFunc(("ENTER appliance %p\n", this));
1333
AutoCaller autoCaller(this);
1334
if (FAILED(autoCaller.rc())) return autoCaller.rc();
1340
AutoMultiWriteLock2 multiLock(&mVirtualBox->getMediaTreeLockHandle(), this->lockHandle() COMMA_LOCKVAL_SRC_POS);
1343
xml::ElementNode *pelmRoot = doc.createRootElement("Envelope");
1345
pelmRoot->setAttribute("ovf:version", (enFormat == OVF_1_0) ? "1.0" : "0.9");
1346
pelmRoot->setAttribute("xml:lang", "en-US");
1348
Utf8Str strNamespace = (enFormat == OVF_0_9)
1349
? "http://www.vmware.com/schema/ovf/1/envelope" // 0.9
1350
: "http://schemas.dmtf.org/ovf/envelope/1"; // 1.0
1351
pelmRoot->setAttribute("xmlns", strNamespace);
1352
pelmRoot->setAttribute("xmlns:ovf", strNamespace);
1354
// pelmRoot->setAttribute("xmlns:ovfstr", "http://schema.dmtf.org/ovf/strings/1");
1355
pelmRoot->setAttribute("xmlns:rasd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData");
1356
pelmRoot->setAttribute("xmlns:vssd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData");
1357
pelmRoot->setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
1358
pelmRoot->setAttribute("xmlns:vbox", "http://www.virtualbox.org/ovf/machine");
1359
// pelmRoot->setAttribute("xsi:schemaLocation", "http://schemas.dmtf.org/ovf/envelope/1 ../ovf-envelope.xsd");
1361
// <Envelope>/<References>
1362
xml::ElementNode *pelmReferences = pelmRoot->createChild("References"); // 0.9 and 1.0
1364
/* <Envelope>/<DiskSection>:
1366
<Info>List of the virtual disks used in the package</Info>
1367
<Disk ovf:capacity="4294967296" ovf:diskId="lamp" ovf:format="..." ovf:populatedSize="1924967692"/>
1369
xml::ElementNode *pelmDiskSection;
1370
if (enFormat == OVF_0_9)
1372
// <Section xsi:type="ovf:DiskSection_Type">
1373
pelmDiskSection = pelmRoot->createChild("Section");
1374
pelmDiskSection->setAttribute("xsi:type", "ovf:DiskSection_Type");
1377
pelmDiskSection = pelmRoot->createChild("DiskSection");
1379
xml::ElementNode *pelmDiskSectionInfo = pelmDiskSection->createChild("Info");
1380
pelmDiskSectionInfo->addContent("List of the virtual disks used in the package");
1382
// the XML stack contains two maps for disks and networks, which allows us to
1383
// a) have a list of unique disk names (to make sure the same disk name is only added once)
1384
// and b) keep a list of all networks
1387
/* <Envelope>/<NetworkSection>:
1389
<Info>Logical networks used in the package</Info>
1390
<Network ovf:name="VM Network">
1391
<Description>The network that the LAMP Service will be available on</Description>
1393
</NetworkSection> */
1394
xml::ElementNode *pelmNetworkSection;
1395
if (enFormat == OVF_0_9)
1397
// <Section xsi:type="ovf:NetworkSection_Type">
1398
pelmNetworkSection = pelmRoot->createChild("Section");
1399
pelmNetworkSection->setAttribute("xsi:type", "ovf:NetworkSection_Type");
1402
pelmNetworkSection = pelmRoot->createChild("NetworkSection");
1404
xml::ElementNode *pelmNetworkSectionInfo = pelmNetworkSection->createChild("Info");
1405
pelmNetworkSectionInfo->addContent("Logical networks used in the package");
1407
// and here come the virtual systems:
1409
// This can take a very long time so leave the locks; in particular, we have the media tree
1410
// lock which Medium::CloneTo() will request, and that would deadlock. Instead, protect
1411
// the appliance by resetting its state so we can safely leave the lock
1412
m->state = Data::ApplianceExporting;
1413
multiLock.release();
1415
// write a collection if we have more than one virtual system _and_ we're
1416
// writing OVF 1.0; otherwise fail since ovftool can't import more than
1417
// one machine, it seems
1418
xml::ElementNode *pelmToAddVirtualSystemsTo;
1419
if (m->virtualSystemDescriptions.size() > 1)
1421
if (enFormat == OVF_0_9)
1422
throw setError(VBOX_E_FILE_ERROR,
1423
tr("Cannot export more than one virtual system with OVF 0.9, use OVF 1.0"));
1425
pelmToAddVirtualSystemsTo = pelmRoot->createChild("VirtualSystemCollection");
1426
pelmToAddVirtualSystemsTo->setAttribute("ovf:name", "ExportedVirtualBoxMachines"); // whatever
1429
pelmToAddVirtualSystemsTo = pelmRoot; // add virtual system directly under root element
1431
// this list receives pointers to the XML elements in the machine XML which
1432
// might have UUIDs that need fixing after we know the UUIDs of the exported images
1433
std::list<xml::ElementNode*> llElementsWithUuidAttributes;
1435
list< ComObjPtr<VirtualSystemDescription> >::const_iterator it;
1436
/* Iterate throughs all virtual systems of that appliance */
1437
for (it = m->virtualSystemDescriptions.begin();
1438
it != m->virtualSystemDescriptions.end();
1441
ComObjPtr<VirtualSystemDescription> vsdescThis = *it;
1442
buildXMLForOneVirtualSystem(*pelmToAddVirtualSystemsTo,
1443
&llElementsWithUuidAttributes,
1446
stack); // disks and networks stack
1449
// now, fill in the network section we set up empty above according
1450
// to the networks we found with the hardware items
1451
map<Utf8Str, bool>::const_iterator itN;
1452
for (itN = stack.mapNetworks.begin();
1453
itN != stack.mapNetworks.end();
1456
const Utf8Str &strNetwork = itN->first;
1457
xml::ElementNode *pelmNetwork = pelmNetworkSection->createChild("Network");
1458
pelmNetwork->setAttribute("ovf:name", strNetwork.c_str());
1459
pelmNetwork->createChild("Description")->addContent("Logical network used by this appliance.");
1462
// Finally, write out the disks!
1464
list<Utf8Str> diskList;
1465
map<Utf8Str, const VirtualSystemDescriptionEntry*>::const_iterator itS;
1466
uint32_t ulFile = 1;
1467
for (itS = stack.mapDisks.begin();
1468
itS != stack.mapDisks.end();
1471
const Utf8Str &strDiskID = itS->first;
1472
const VirtualSystemDescriptionEntry *pDiskEntry = itS->second;
1474
// source path: where the VBox image is
1475
const Utf8Str &strSrcFilePath = pDiskEntry->strVboxCurrent;
1476
Bstr bstrSrcFilePath(strSrcFilePath);
1477
if (!RTPathExists(strSrcFilePath.c_str()))
1478
/* This isn't allowed */
1479
throw setError(VBOX_E_FILE_ERROR,
1480
tr("Source virtual disk image file '%s' doesn't exist"),
1481
strSrcFilePath.c_str());
1484
ComPtr<IMedium> pSourceDisk;
1485
ComPtr<IMedium> pTargetDisk;
1486
ComPtr<IProgress> pProgress2;
1488
Log(("Finding source disk \"%ls\"\n", bstrSrcFilePath.raw()));
1489
rc = mVirtualBox->FindHardDisk(bstrSrcFilePath, pSourceDisk.asOutParam());
1490
if (FAILED(rc)) throw rc;
1493
rc = pSourceDisk->COMGETTER(Id)(uuidSource.asOutParam());
1494
if (FAILED(rc)) throw rc;
1495
Guid guidSource(uuidSource);
1498
const Utf8Str &strTargetFileNameOnly = pDiskEntry->strOvf;
1499
// target path needs to be composed from where the output OVF is
1500
Utf8Str strTargetFilePath(locInfo.strPath);
1501
strTargetFilePath.stripFilename();
1502
strTargetFilePath.append("/");
1503
strTargetFilePath.append(strTargetFileNameOnly);
1505
// We are always exporting to VMDK stream optimized for now
1506
Bstr bstrSrcFormat = L"VMDK";
1508
// create a new hard disk interface for the destination disk image
1509
Log(("Creating target disk \"%s\"\n", strTargetFilePath.raw()));
1510
rc = mVirtualBox->CreateHardDisk(bstrSrcFormat, Bstr(strTargetFilePath), pTargetDisk.asOutParam());
1511
if (FAILED(rc)) throw rc;
1513
// the target disk is now registered and needs to be removed again,
1514
// both after successful cloning or if anything goes bad!
1517
// create a flat copy of the source disk image
1518
rc = pSourceDisk->CloneTo(pTargetDisk, MediumVariant_VmdkStreamOptimized, NULL, pProgress2.asOutParam());
1519
if (FAILED(rc)) throw rc;
1521
// advance to the next operation
1522
pProgress->SetNextOperation(BstrFmt(tr("Exporting to disk image '%s'"), strTargetFilePath.c_str()),
1523
pDiskEntry->ulSizeMB); // operation's weight, as set up with the IProgress originally);
1525
// now wait for the background disk operation to complete; this throws HRESULTs on error
1526
waitForAsyncProgress(pProgress, pProgress2);
1530
// upon error after registering, close the disk or
1531
// it'll stick in the registry forever
1532
pTargetDisk->Close();
1535
diskList.push_back(strTargetFilePath);
1537
// we need the following for the XML
1538
ULONG64 cbFile = 0; // actual file size
1539
rc = pTargetDisk->COMGETTER(Size)(&cbFile);
1540
if (FAILED(rc)) throw rc;
1542
ULONG64 cbCapacity = 0; // size reported to guest
1543
rc = pTargetDisk->COMGETTER(LogicalSize)(&cbCapacity);
1544
if (FAILED(rc)) throw rc;
1545
// capacity is reported in megabytes, so...
1549
rc = pTargetDisk->COMGETTER(Id)(uuidTarget.asOutParam());
1550
if (FAILED(rc)) throw rc;
1551
Guid guidTarget(uuidTarget);
1553
// upon success, close the disk as well
1554
rc = pTargetDisk->Close();
1555
if (FAILED(rc)) throw rc;
1557
// now handle the XML for the disk:
1558
Utf8StrFmt strFileRef("file%RI32", ulFile++);
1559
// <File ovf:href="WindowsXpProfessional-disk1.vmdk" ovf:id="file1" ovf:size="1710381056"/>
1560
xml::ElementNode *pelmFile = pelmReferences->createChild("File");
1561
pelmFile->setAttribute("ovf:href", strTargetFileNameOnly);
1562
pelmFile->setAttribute("ovf:id", strFileRef);
1563
pelmFile->setAttribute("ovf:size", Utf8StrFmt("%RI64", cbFile).c_str());
1565
// add disk to XML Disks section
1566
// <Disk ovf:capacity="8589934592" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="..."/>
1567
xml::ElementNode *pelmDisk = pelmDiskSection->createChild("Disk");
1568
pelmDisk->setAttribute("ovf:capacity", Utf8StrFmt("%RI64", cbCapacity).c_str());
1569
pelmDisk->setAttribute("ovf:diskId", strDiskID);
1570
pelmDisk->setAttribute("ovf:fileRef", strFileRef);
1571
pelmDisk->setAttribute("ovf:format",
1572
(enFormat == OVF_0_9)
1573
? "http://www.vmware.com/specifications/vmdk.html#sparse" // must be sparse or ovftool chokes
1574
: "http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"
1575
// correct string as communicated to us by VMware (public bug #6612)
1578
// add the UUID of the newly created target image to the OVF disk element, but in the
1579
// vbox: namespace since it's not part of the standard
1580
pelmDisk->setAttribute("vbox:uuid", Utf8StrFmt("%RTuuid", guidTarget.raw()).c_str());
1582
// now, we might have other XML elements from vbox:Machine pointing to this image,
1583
// but those would refer to the UUID of the _source_ image (which we created the
1584
// export image from); those UUIDs need to be fixed to the export image
1585
Utf8Str strGuidSourceCurly = guidSource.toStringCurly();
1586
for (std::list<xml::ElementNode*>::iterator eit = llElementsWithUuidAttributes.begin();
1587
eit != llElementsWithUuidAttributes.end();
1590
xml::ElementNode *pelmImage = *eit;
1592
pelmImage->getAttributeValue("uuid", strUUID);
1593
if (strUUID == strGuidSourceCurly)
1594
// overwrite existing uuid attribute
1595
pelmImage->setAttribute("uuid", guidTarget.toStringCurly());
1599
// now go write the XML
1600
xml::XmlFileWriter writer(doc);
1601
writer.write(locInfo.strPath.c_str(), false /*fSafe*/);
1603
#if 0 // VBox 3.2.10: disable manifest writing until it's actually usable
1604
// Create & write the manifest file
1605
Utf8Str strMfFile = manifestFileName(locInfo.strPath.c_str());
1606
const char *pcszManifestFileOnly = RTPathFilename(strMfFile.c_str());
1607
pProgress->SetNextOperation(BstrFmt(tr("Creating manifest file '%s'"), pcszManifestFileOnly),
1608
m->ulWeightForManifestOperation); // operation's weight, as set up with the IProgress originally);
1610
const char** ppManifestFiles = (const char**)RTMemAlloc(sizeof(char*)*diskList.size() + 1);
1611
ppManifestFiles[0] = locInfo.strPath.c_str();
1612
list<Utf8Str>::const_iterator it1;
1614
for (it1 = diskList.begin();
1615
it1 != diskList.end();
1617
ppManifestFiles[i] = (*it1).c_str();
1618
int vrc = RTManifestWriteFiles(strMfFile.c_str(), ppManifestFiles, diskList.size()+1, NULL, NULL);
1619
RTMemFree(ppManifestFiles);
1620
if (RT_FAILURE(vrc))
1621
throw setError(VBOX_E_FILE_ERROR,
1622
tr("Could not create manifest file '%s' (%Rrc)"),
1623
pcszManifestFileOnly, vrc);
1626
catch(xml::Error &x)
1628
rc = setError(VBOX_E_FILE_ERROR,
1636
AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
1637
// reset the state so others can call methods again
1638
m->state = Data::ApplianceIdle;
1640
LogFlowFunc(("rc=%Rhrc\n", rc));
1647
* Worker code for writing out OVF to the cloud. This is called from Appliance::taskThreadWriteOVF()
1648
* in S3 mode and therefore runs on the OVF write worker thread. This then starts a second worker
1649
* thread to create temporary files (see Appliance::writeFS()).
1654
HRESULT Appliance::writeS3(TaskOVF *pTask)
1657
LogFlowFunc(("Appliance %p\n", this));
1659
AutoCaller autoCaller(this);
1660
if (FAILED(autoCaller.rc())) return autoCaller.rc();
1664
AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
1666
int vrc = VINF_SUCCESS;
1667
RTS3 hS3 = NIL_RTS3;
1668
char szOSTmpDir[RTPATH_MAX];
1669
RTPathTemp(szOSTmpDir, sizeof(szOSTmpDir));
1670
/* The template for the temporary directory created below */
1672
RTStrAPrintf(&pszTmpDir, "%s"RTPATH_SLASH_STR"vbox-ovf-XXXXXX", szOSTmpDir);
1673
list< pair<Utf8Str, ULONG> > filesList;
1676
// - usable error codes
1677
// - seems snapshot filenames are problematic {uuid}.vdi
1680
/* Extract the bucket */
1681
Utf8Str tmpPath = pTask->locInfo.strPath;
1683
parseBucket(tmpPath, bucket);
1685
/* We need a temporary directory which we can put the OVF file & all
1687
vrc = RTDirCreateTemp(pszTmpDir);
1688
if (RT_FAILURE(vrc))
1689
throw setError(VBOX_E_FILE_ERROR,
1690
tr("Cannot create temporary directory '%s'"), pszTmpDir);
1692
/* The temporary name of the target OVF file */
1693
Utf8StrFmt strTmpOvf("%s/%s", pszTmpDir, RTPathFilename(tmpPath.c_str()));
1695
/* Prepare the temporary writing of the OVF */
1696
ComObjPtr<Progress> progress;
1697
/* Create a temporary file based location info for the sub task */
1699
li.strPath = strTmpOvf;
1700
rc = writeImpl(pTask->enFormat, li, progress);
1701
if (FAILED(rc)) throw rc;
1703
/* Unlock the appliance for the writing thread */
1705
/* Wait until the writing is done, but report the progress back to the
1707
ComPtr<IProgress> progressInt(progress);
1708
waitForAsyncProgress(pTask->pProgress, progressInt); /* Any errors will be thrown */
1710
/* Again lock the appliance for the next steps */
1713
vrc = RTPathExists(strTmpOvf.c_str()); /* Paranoid check */
1714
if (RT_FAILURE(vrc))
1715
throw setError(VBOX_E_FILE_ERROR,
1716
tr("Cannot find source file '%s'"), strTmpOvf.c_str());
1717
/* Add the OVF file */
1718
filesList.push_back(pair<Utf8Str, ULONG>(strTmpOvf, m->ulWeightForXmlOperation)); /* Use 1% of the total for the OVF file upload */
1719
Utf8Str strMfFile = manifestFileName(strTmpOvf);
1720
filesList.push_back(pair<Utf8Str, ULONG>(strMfFile , m->ulWeightForXmlOperation)); /* Use 1% of the total for the manifest file upload */
1722
/* Now add every disks of every virtual system */
1723
list< ComObjPtr<VirtualSystemDescription> >::const_iterator it;
1724
for (it = m->virtualSystemDescriptions.begin();
1725
it != m->virtualSystemDescriptions.end();
1728
ComObjPtr<VirtualSystemDescription> vsdescThis = (*it);
1729
std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskImage);
1730
std::list<VirtualSystemDescriptionEntry*>::const_iterator itH;
1731
for (itH = avsdeHDs.begin();
1732
itH != avsdeHDs.end();
1735
const Utf8Str &strTargetFileNameOnly = (*itH)->strOvf;
1736
/* Target path needs to be composed from where the output OVF is */
1737
Utf8Str strTargetFilePath(strTmpOvf);
1738
strTargetFilePath.stripFilename();
1739
strTargetFilePath.append("/");
1740
strTargetFilePath.append(strTargetFileNameOnly);
1741
vrc = RTPathExists(strTargetFilePath.c_str()); /* Paranoid check */
1742
if (RT_FAILURE(vrc))
1743
throw setError(VBOX_E_FILE_ERROR,
1744
tr("Cannot find source file '%s'"), strTargetFilePath.c_str());
1745
filesList.push_back(pair<Utf8Str, ULONG>(strTargetFilePath, (*itH)->ulSizeMB));
1748
/* Next we have to upload the OVF & all disk images */
1749
vrc = RTS3Create(&hS3, pTask->locInfo.strUsername.c_str(), pTask->locInfo.strPassword.c_str(), pTask->locInfo.strHostname.c_str(), "virtualbox-agent/"VBOX_VERSION_STRING);
1750
if (RT_FAILURE(vrc))
1751
throw setError(VBOX_E_IPRT_ERROR,
1752
tr("Cannot create S3 service handler"));
1753
RTS3SetProgressCallback(hS3, pTask->updateProgress, &pTask);
1755
/* Upload all files */
1756
for (list< pair<Utf8Str, ULONG> >::const_iterator it1 = filesList.begin(); it1 != filesList.end(); ++it1)
1758
const pair<Utf8Str, ULONG> &s = (*it1);
1759
char *pszFilename = RTPathFilename(s.first.c_str());
1760
/* Advance to the next operation */
1761
pTask->pProgress->SetNextOperation(BstrFmt(tr("Uploading file '%s'"), pszFilename), s.second);
1762
vrc = RTS3PutKey(hS3, bucket.c_str(), pszFilename, s.first.c_str());
1763
if (RT_FAILURE(vrc))
1765
if (vrc == VERR_S3_CANCELED)
1767
else if (vrc == VERR_S3_ACCESS_DENIED)
1768
throw setError(E_ACCESSDENIED,
1769
tr("Cannot upload file '%s' to S3 storage server (Access denied). Make sure that your credentials are right. Also check that your host clock is properly synced"), pszFilename);
1770
else if (vrc == VERR_S3_NOT_FOUND)
1771
throw setError(VBOX_E_FILE_ERROR,
1772
tr("Cannot upload file '%s' to S3 storage server (File not found)"), pszFilename);
1774
throw setError(VBOX_E_IPRT_ERROR,
1775
tr("Cannot upload file '%s' to S3 storage server (%Rrc)"), pszFilename, vrc);
1785
/* Delete all files which where temporary created */
1786
for (list< pair<Utf8Str, ULONG> >::const_iterator it1 = filesList.begin(); it1 != filesList.end(); ++it1)
1788
const char *pszFilePath = (*it1).first.c_str();
1789
if (RTPathExists(pszFilePath))
1791
vrc = RTFileDelete(pszFilePath);
1792
if (RT_FAILURE(vrc))
1793
rc = setError(VBOX_E_FILE_ERROR,
1794
tr("Cannot delete file '%s' (%Rrc)"), pszFilePath, vrc);
1797
/* Delete the temporary directory */
1798
if (RTPathExists(pszTmpDir))
1800
vrc = RTDirRemove(pszTmpDir);
1801
if (RT_FAILURE(vrc))
1802
rc = setError(VBOX_E_FILE_ERROR,
1803
tr("Cannot delete temporary directory '%s' (%Rrc)"), pszTmpDir, vrc);
1806
RTStrFree(pszTmpDir);
1808
LogFlowFunc(("rc=%Rhrc\n", rc));