VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/ApplianceImplExport.cpp@ 38469

Last change on this file since 38469 was 38469, checked in by vboxsync, 14 years ago

VD: Interface cleanup. Merge the two involved structures (generic interface descriptor and callback table) into one, remove the duplicated interface wrappers in the backends and move the interface definitions into separate headers separating public and private interfaces.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 89.7 KB
Line 
1/* $Id: ApplianceImplExport.cpp 38469 2011-08-16 10:34:32Z vboxsync $ */
2/** @file
3 *
4 * IAppliance and IVirtualSystem COM class implementations.
5 */
6
7/*
8 * Copyright (C) 2008-2010 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.215389.xyz. 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.
17 */
18
19#include <iprt/path.h>
20#include <iprt/dir.h>
21#include <iprt/param.h>
22#include <iprt/s3.h>
23#include <iprt/manifest.h>
24#include <iprt/tar.h>
25#include <iprt/stream.h>
26
27#include <VBox/version.h>
28
29#include "ApplianceImpl.h"
30#include "VirtualBoxImpl.h"
31
32#include "ProgressImpl.h"
33#include "MachineImpl.h"
34#include "MediumImpl.h"
35#include "MediumFormatImpl.h"
36#include "SystemPropertiesImpl.h"
37
38#include "AutoCaller.h"
39#include "Logging.h"
40
41#include "ApplianceImplPrivate.h"
42
43using namespace std;
44
45////////////////////////////////////////////////////////////////////////////////
46//
47// IMachine public methods
48//
49////////////////////////////////////////////////////////////////////////////////
50
51// This code is here so we won't have to include the appliance headers in the
52// IMachine implementation, and we also need to access private appliance data.
53
54/**
55* Public method implementation.
56* @param appliance
57* @return
58*/
59STDMETHODIMP Machine::Export(IAppliance *aAppliance, IN_BSTR location, IVirtualSystemDescription **aDescription)
60{
61 HRESULT rc = S_OK;
62
63 if (!aAppliance)
64 return E_POINTER;
65
66 AutoCaller autoCaller(this);
67 if (FAILED(autoCaller.rc())) return autoCaller.rc();
68
69 ComObjPtr<VirtualSystemDescription> pNewDesc;
70
71 try
72 {
73 Appliance *pAppliance = static_cast<Appliance*>(aAppliance);
74 AutoCaller autoCaller1(pAppliance);
75 if (FAILED(autoCaller1.rc())) return autoCaller1.rc();
76
77 LocationInfo locInfo;
78 parseURI(location, locInfo);
79 // create a new virtual system to store in the appliance
80 rc = pNewDesc.createObject();
81 if (FAILED(rc)) throw rc;
82 rc = pNewDesc->init();
83 if (FAILED(rc)) throw rc;
84
85 // store the machine object so we can dump the XML in Appliance::Write()
86 pNewDesc->m->pMachine = this;
87
88 // now fill it with description items
89 Bstr bstrName1;
90 Bstr bstrDescription;
91 Bstr bstrGuestOSType;
92 uint32_t cCPUs;
93 uint32_t ulMemSizeMB;
94 BOOL fUSBEnabled;
95 BOOL fAudioEnabled;
96 AudioControllerType_T audioController;
97
98 ComPtr<IUSBController> pUsbController;
99 ComPtr<IAudioAdapter> pAudioAdapter;
100
101 // first, call the COM methods, as they request locks
102 rc = COMGETTER(USBController)(pUsbController.asOutParam());
103 if (FAILED(rc))
104 fUSBEnabled = false;
105 else
106 rc = pUsbController->COMGETTER(Enabled)(&fUSBEnabled);
107
108 // request the machine lock while accessing internal members
109 AutoReadLock alock1(this COMMA_LOCKVAL_SRC_POS);
110
111 pAudioAdapter = mAudioAdapter;
112 rc = pAudioAdapter->COMGETTER(Enabled)(&fAudioEnabled);
113 if (FAILED(rc)) throw rc;
114 rc = pAudioAdapter->COMGETTER(AudioController)(&audioController);
115 if (FAILED(rc)) throw rc;
116
117 // get name
118 Utf8Str strVMName = mUserData->s.strName;
119 // get description
120 Utf8Str strDescription = mUserData->s.strDescription;
121 // get guest OS
122 Utf8Str strOsTypeVBox = mUserData->s.strOsType;
123 // CPU count
124 cCPUs = mHWData->mCPUCount;
125 // memory size in MB
126 ulMemSizeMB = mHWData->mMemorySize;
127 // VRAM size?
128 // BIOS settings?
129 // 3D acceleration enabled?
130 // hardware virtualization enabled?
131 // nested paging enabled?
132 // HWVirtExVPIDEnabled?
133 // PAEEnabled?
134 // snapshotFolder?
135 // VRDPServer?
136
137 /* Guest OS type */
138 ovf::CIMOSType_T cim = convertVBoxOSType2CIMOSType(strOsTypeVBox.c_str());
139 pNewDesc->addEntry(VirtualSystemDescriptionType_OS,
140 "",
141 Utf8StrFmt("%RI32", cim),
142 strOsTypeVBox);
143
144 /* VM name */
145 pNewDesc->addEntry(VirtualSystemDescriptionType_Name,
146 "",
147 strVMName,
148 strVMName);
149
150 // description
151 pNewDesc->addEntry(VirtualSystemDescriptionType_Description,
152 "",
153 strDescription,
154 strDescription);
155
156 /* CPU count*/
157 Utf8Str strCpuCount = Utf8StrFmt("%RI32", cCPUs);
158 pNewDesc->addEntry(VirtualSystemDescriptionType_CPU,
159 "",
160 strCpuCount,
161 strCpuCount);
162
163 /* Memory */
164 Utf8Str strMemory = Utf8StrFmt("%RI64", (uint64_t)ulMemSizeMB * _1M);
165 pNewDesc->addEntry(VirtualSystemDescriptionType_Memory,
166 "",
167 strMemory,
168 strMemory);
169
170 // the one VirtualBox IDE controller has two channels with two ports each, which is
171 // considered two IDE controllers with two ports each by OVF, so export it as two
172 int32_t lIDEControllerPrimaryIndex = 0;
173 int32_t lIDEControllerSecondaryIndex = 0;
174 int32_t lSATAControllerIndex = 0;
175 int32_t lSCSIControllerIndex = 0;
176
177 /* Fetch all available storage controllers */
178 com::SafeIfaceArray<IStorageController> nwControllers;
179 rc = COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(nwControllers));
180 if (FAILED(rc)) throw rc;
181
182 ComPtr<IStorageController> pIDEController;
183 ComPtr<IStorageController> pSATAController;
184 ComPtr<IStorageController> pSCSIController;
185 ComPtr<IStorageController> pSASController;
186 for (size_t j = 0; j < nwControllers.size(); ++j)
187 {
188 StorageBus_T eType;
189 rc = nwControllers[j]->COMGETTER(Bus)(&eType);
190 if (FAILED(rc)) throw rc;
191 if ( eType == StorageBus_IDE
192 && pIDEController.isNull())
193 pIDEController = nwControllers[j];
194 else if ( eType == StorageBus_SATA
195 && pSATAController.isNull())
196 pSATAController = nwControllers[j];
197 else if ( eType == StorageBus_SCSI
198 && pSATAController.isNull())
199 pSCSIController = nwControllers[j];
200 else if ( eType == StorageBus_SAS
201 && pSASController.isNull())
202 pSASController = nwControllers[j];
203 }
204
205// <const name="HardDiskControllerIDE" value="6" />
206 if (!pIDEController.isNull())
207 {
208 Utf8Str strVbox;
209 StorageControllerType_T ctlr;
210 rc = pIDEController->COMGETTER(ControllerType)(&ctlr);
211 if (FAILED(rc)) throw rc;
212 switch(ctlr)
213 {
214 case StorageControllerType_PIIX3: strVbox = "PIIX3"; break;
215 case StorageControllerType_PIIX4: strVbox = "PIIX4"; break;
216 case StorageControllerType_ICH6: strVbox = "ICH6"; break;
217 }
218
219 if (strVbox.length())
220 {
221 lIDEControllerPrimaryIndex = (int32_t)pNewDesc->m->llDescriptions.size();
222 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
223 Utf8StrFmt("%d", lIDEControllerPrimaryIndex), // strRef
224 strVbox, // aOvfValue
225 strVbox); // aVboxValue
226 lIDEControllerSecondaryIndex = lIDEControllerPrimaryIndex + 1;
227 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
228 Utf8StrFmt("%d", lIDEControllerSecondaryIndex),
229 strVbox,
230 strVbox);
231 }
232 }
233
234// <const name="HardDiskControllerSATA" value="7" />
235 if (!pSATAController.isNull())
236 {
237 Utf8Str strVbox = "AHCI";
238 lSATAControllerIndex = (int32_t)pNewDesc->m->llDescriptions.size();
239 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA,
240 Utf8StrFmt("%d", lSATAControllerIndex),
241 strVbox,
242 strVbox);
243 }
244
245// <const name="HardDiskControllerSCSI" value="8" />
246 if (!pSCSIController.isNull())
247 {
248 StorageControllerType_T ctlr;
249 rc = pSCSIController->COMGETTER(ControllerType)(&ctlr);
250 if (SUCCEEDED(rc))
251 {
252 Utf8Str strVbox = "LsiLogic"; // the default in VBox
253 switch(ctlr)
254 {
255 case StorageControllerType_LsiLogic: strVbox = "LsiLogic"; break;
256 case StorageControllerType_BusLogic: strVbox = "BusLogic"; break;
257 }
258 lSCSIControllerIndex = (int32_t)pNewDesc->m->llDescriptions.size();
259 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSCSI,
260 Utf8StrFmt("%d", lSCSIControllerIndex),
261 strVbox,
262 strVbox);
263 }
264 else
265 throw rc;
266 }
267
268 if (!pSASController.isNull())
269 {
270 // VirtualBox considers the SAS controller a class of its own but in OVF
271 // it should be a SCSI controller
272 Utf8Str strVbox = "LsiLogicSas";
273 lSCSIControllerIndex = (int32_t)pNewDesc->m->llDescriptions.size();
274 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSAS,
275 Utf8StrFmt("%d", lSCSIControllerIndex),
276 strVbox,
277 strVbox);
278 }
279
280// <const name="HardDiskImage" value="9" />
281// <const name="Floppy" value="18" />
282// <const name="CDROM" value="19" />
283
284 MediaData::AttachmentList::iterator itA;
285 for (itA = mMediaData->mAttachments.begin();
286 itA != mMediaData->mAttachments.end();
287 ++itA)
288 {
289 ComObjPtr<MediumAttachment> pHDA = *itA;
290
291 // the attachment's data
292 ComPtr<IMedium> pMedium;
293 ComPtr<IStorageController> ctl;
294 Bstr controllerName;
295
296 rc = pHDA->COMGETTER(Controller)(controllerName.asOutParam());
297 if (FAILED(rc)) throw rc;
298
299 rc = GetStorageControllerByName(controllerName.raw(), ctl.asOutParam());
300 if (FAILED(rc)) throw rc;
301
302 StorageBus_T storageBus;
303 DeviceType_T deviceType;
304 LONG lChannel;
305 LONG lDevice;
306
307 rc = ctl->COMGETTER(Bus)(&storageBus);
308 if (FAILED(rc)) throw rc;
309
310 rc = pHDA->COMGETTER(Type)(&deviceType);
311 if (FAILED(rc)) throw rc;
312
313 rc = pHDA->COMGETTER(Medium)(pMedium.asOutParam());
314 if (FAILED(rc)) throw rc;
315
316 rc = pHDA->COMGETTER(Port)(&lChannel);
317 if (FAILED(rc)) throw rc;
318
319 rc = pHDA->COMGETTER(Device)(&lDevice);
320 if (FAILED(rc)) throw rc;
321
322 Utf8Str strTargetVmdkName;
323 Utf8Str strLocation;
324 LONG64 llSize = 0;
325
326 if ( deviceType == DeviceType_HardDisk
327 && pMedium
328 )
329 {
330 Bstr bstrLocation;
331 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
332 if (FAILED(rc)) throw rc;
333 strLocation = bstrLocation;
334
335 // find the source's base medium for two things:
336 // 1) we'll use its name to determine the name of the target disk, which is readable,
337 // as opposed to the UUID filename of a differencing image, if pMedium is one
338 // 2) we need the size of the base image so we can give it to addEntry(), and later
339 // on export, the progress will be based on that (and not the diff image)
340 ComPtr<IMedium> pBaseMedium;
341 rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
342 // returns pMedium if there are no diff images
343 if (FAILED(rc)) throw rc;
344
345 Bstr bstrBaseName;
346 rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
347 if (FAILED(rc)) throw rc;
348
349 Utf8Str strTargetName = Utf8Str(locInfo.strPath).stripPath().stripExt();
350 strTargetVmdkName = Utf8StrFmt("%s-disk%d.vmdk", strTargetName.c_str(), ++pAppliance->m->cDisks);
351
352 // force reading state, or else size will be returned as 0
353 MediumState_T ms;
354 rc = pBaseMedium->RefreshState(&ms);
355 if (FAILED(rc)) throw rc;
356
357 rc = pBaseMedium->COMGETTER(Size)(&llSize);
358 if (FAILED(rc)) throw rc;
359 }
360
361 // and how this translates to the virtual system
362 int32_t lControllerVsys = 0;
363 LONG lChannelVsys;
364
365 switch (storageBus)
366 {
367 case StorageBus_IDE:
368 // this is the exact reverse to what we're doing in Appliance::taskThreadImportMachines,
369 // and it must be updated when that is changed!
370 // Before 3.2 we exported one IDE controller with channel 0-3, but we now maintain
371 // compatibility with what VMware does and export two IDE controllers with two channels each
372
373 if (lChannel == 0 && lDevice == 0) // primary master
374 {
375 lControllerVsys = lIDEControllerPrimaryIndex;
376 lChannelVsys = 0;
377 }
378 else if (lChannel == 0 && lDevice == 1) // primary slave
379 {
380 lControllerVsys = lIDEControllerPrimaryIndex;
381 lChannelVsys = 1;
382 }
383 else if (lChannel == 1 && lDevice == 0) // secondary master; by default this is the CD-ROM but as of VirtualBox 3.1 that can change
384 {
385 lControllerVsys = lIDEControllerSecondaryIndex;
386 lChannelVsys = 0;
387 }
388 else if (lChannel == 1 && lDevice == 1) // secondary slave
389 {
390 lControllerVsys = lIDEControllerSecondaryIndex;
391 lChannelVsys = 1;
392 }
393 else
394 throw setError(VBOX_E_NOT_SUPPORTED,
395 tr("Cannot handle medium attachment: channel is %d, device is %d"), lChannel, lDevice);
396 break;
397
398 case StorageBus_SATA:
399 lChannelVsys = lChannel; // should be between 0 and 29
400 lControllerVsys = lSATAControllerIndex;
401 break;
402
403 case StorageBus_SCSI:
404 case StorageBus_SAS:
405 lChannelVsys = lChannel; // should be between 0 and 15
406 lControllerVsys = lSCSIControllerIndex;
407 break;
408
409 case StorageBus_Floppy:
410 lChannelVsys = 0;
411 lControllerVsys = 0;
412 break;
413
414 default:
415 throw setError(VBOX_E_NOT_SUPPORTED,
416 tr("Cannot handle medium attachment: storageBus is %d, channel is %d, device is %d"), storageBus, lChannel, lDevice);
417 break;
418 }
419
420 Utf8StrFmt strExtra("controller=%RI32;channel=%RI32", lControllerVsys, lChannelVsys);
421 Utf8Str strEmpty;
422
423 switch (deviceType)
424 {
425 case DeviceType_HardDisk:
426 Log(("Adding VirtualSystemDescriptionType_HardDiskImage, disk size: %RI64\n", llSize));
427 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskImage,
428 strTargetVmdkName, // disk ID: let's use the name
429 strTargetVmdkName, // OVF value:
430 strLocation, // vbox value: media path
431 (uint32_t)(llSize / _1M),
432 strExtra);
433 break;
434
435 case DeviceType_DVD:
436 pNewDesc->addEntry(VirtualSystemDescriptionType_CDROM,
437 strEmpty, // disk ID
438 strEmpty, // OVF value
439 strEmpty, // vbox value
440 1, // ulSize
441 strExtra);
442 break;
443
444 case DeviceType_Floppy:
445 pNewDesc->addEntry(VirtualSystemDescriptionType_Floppy,
446 strEmpty, // disk ID
447 strEmpty, // OVF value
448 strEmpty, // vbox value
449 1, // ulSize
450 strExtra);
451 break;
452 }
453 }
454
455// <const name="NetworkAdapter" />
456 size_t a;
457 for (a = 0;
458 a < SchemaDefs::NetworkAdapterCount;
459 ++a)
460 {
461 ComPtr<INetworkAdapter> pNetworkAdapter;
462 BOOL fEnabled;
463 NetworkAdapterType_T adapterType;
464 NetworkAttachmentType_T attachmentType;
465
466 rc = GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
467 if (FAILED(rc)) throw rc;
468 /* Enable the network card & set the adapter type */
469 rc = pNetworkAdapter->COMGETTER(Enabled)(&fEnabled);
470 if (FAILED(rc)) throw rc;
471
472 if (fEnabled)
473 {
474 rc = pNetworkAdapter->COMGETTER(AdapterType)(&adapterType);
475 if (FAILED(rc)) throw rc;
476
477 rc = pNetworkAdapter->COMGETTER(AttachmentType)(&attachmentType);
478 if (FAILED(rc)) throw rc;
479
480 Utf8Str strAttachmentType = convertNetworkAttachmentTypeToString(attachmentType);
481 pNewDesc->addEntry(VirtualSystemDescriptionType_NetworkAdapter,
482 "", // ref
483 strAttachmentType, // orig
484 Utf8StrFmt("%RI32", (uint32_t)adapterType), // conf
485 0,
486 Utf8StrFmt("type=%s", strAttachmentType.c_str())); // extra conf
487 }
488 }
489
490// <const name="USBController" />
491#ifdef VBOX_WITH_USB
492 if (fUSBEnabled)
493 pNewDesc->addEntry(VirtualSystemDescriptionType_USBController, "", "", "");
494#endif /* VBOX_WITH_USB */
495
496// <const name="SoundCard" />
497 if (fAudioEnabled)
498 pNewDesc->addEntry(VirtualSystemDescriptionType_SoundCard,
499 "",
500 "ensoniq1371", // this is what OVFTool writes and VMware supports
501 Utf8StrFmt("%RI32", audioController));
502
503 /* We return the new description to the caller */
504 ComPtr<IVirtualSystemDescription> copy(pNewDesc);
505 copy.queryInterfaceTo(aDescription);
506
507 AutoWriteLock alock(pAppliance COMMA_LOCKVAL_SRC_POS);
508 // finally, add the virtual system to the appliance
509 pAppliance->m->virtualSystemDescriptions.push_back(pNewDesc);
510 }
511 catch(HRESULT arc)
512 {
513 rc = arc;
514 }
515
516 return rc;
517}
518
519////////////////////////////////////////////////////////////////////////////////
520//
521// IAppliance public methods
522//
523////////////////////////////////////////////////////////////////////////////////
524
525/**
526 * Public method implementation.
527 * @param format
528 * @param path
529 * @param aProgress
530 * @return
531 */
532STDMETHODIMP Appliance::Write(IN_BSTR format, BOOL fManifest, IN_BSTR path, IProgress **aProgress)
533{
534 if (!path) return E_POINTER;
535 CheckComArgOutPointerValid(aProgress);
536
537 AutoCaller autoCaller(this);
538 if (FAILED(autoCaller.rc())) return autoCaller.rc();
539
540 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
541
542 // do not allow entering this method if the appliance is busy reading or writing
543 if (!isApplianceIdle())
544 return E_ACCESSDENIED;
545
546 // see if we can handle this file; for now we insist it has an ".ovf" extension
547 Utf8Str strPath = path;
548 if (!( strPath.endsWith(".ovf", Utf8Str::CaseInsensitive)
549 || strPath.endsWith(".ova", Utf8Str::CaseInsensitive)))
550 return setError(VBOX_E_FILE_ERROR,
551 tr("Appliance file must have .ovf or .ova extension"));
552
553 m->fManifest = !!fManifest;
554 Utf8Str strFormat(format);
555 OVFFormat ovfF;
556 if (strFormat == "ovf-0.9")
557 ovfF = OVF_0_9;
558 else if (strFormat == "ovf-1.0")
559 ovfF = OVF_1_0;
560 else
561 return setError(VBOX_E_FILE_ERROR,
562 tr("Invalid format \"%s\" specified"), strFormat.c_str());
563
564 ComObjPtr<Progress> progress;
565 HRESULT rc = S_OK;
566 try
567 {
568 /* Parse all necessary info out of the URI */
569 parseURI(strPath, m->locInfo);
570 rc = writeImpl(ovfF, m->locInfo, progress);
571 }
572 catch (HRESULT aRC)
573 {
574 rc = aRC;
575 }
576
577 if (SUCCEEDED(rc))
578 /* Return progress to the caller */
579 progress.queryInterfaceTo(aProgress);
580
581 return rc;
582}
583
584////////////////////////////////////////////////////////////////////////////////
585//
586// Appliance private methods
587//
588////////////////////////////////////////////////////////////////////////////////
589
590/*******************************************************************************
591 * Export stuff
592 ******************************************************************************/
593
594/**
595 * Implementation for writing out the OVF to disk. This starts a new thread which will call
596 * Appliance::taskThreadWriteOVF().
597 *
598 * This is in a separate private method because it is used from two locations:
599 *
600 * 1) from the public Appliance::Write().
601 *
602 * 2) in a second worker thread; in that case, Appliance::Write() called Appliance::writeImpl(), which
603 * called Appliance::writeFSOVA(), which called Appliance::writeImpl(), which then called this again.
604 *
605 * 3) from Appliance::writeS3(), which got called from a previous instance of Appliance::taskThreadWriteOVF().
606 *
607 * @param aFormat
608 * @param aLocInfo
609 * @param aProgress
610 * @return
611 */
612HRESULT Appliance::writeImpl(OVFFormat aFormat, const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
613{
614 HRESULT rc = S_OK;
615 try
616 {
617 rc = setUpProgress(aProgress,
618 BstrFmt(tr("Export appliance '%s'"), aLocInfo.strPath.c_str()),
619 (aLocInfo.storageType == VFSType_File) ? WriteFile : WriteS3);
620
621 /* Initialize our worker task */
622 std::auto_ptr<TaskOVF> task(new TaskOVF(this, TaskOVF::Write, aLocInfo, aProgress));
623 /* The OVF version to write */
624 task->enFormat = aFormat;
625
626 rc = task->startThread();
627 if (FAILED(rc)) throw rc;
628
629 /* Don't destruct on success */
630 task.release();
631 }
632 catch (HRESULT aRC)
633 {
634 rc = aRC;
635 }
636
637 return rc;
638}
639
640/**
641 * Called from Appliance::writeFS() for creating a XML document for this
642 * Appliance.
643 *
644 * @param writeLock The current write lock.
645 * @param doc The xml document to fill.
646 * @param stack Structure for temporary private
647 * data shared with caller.
648 * @param strPath Path to the target OVF.
649 * instance for which to write XML.
650 * @param enFormat OVF format (0.9 or 1.0).
651 */
652void Appliance::buildXML(AutoWriteLockBase& writeLock,
653 xml::Document &doc,
654 XMLStack &stack,
655 const Utf8Str &strPath,
656 OVFFormat enFormat)
657{
658 xml::ElementNode *pelmRoot = doc.createRootElement("Envelope");
659
660 pelmRoot->setAttribute("ovf:version", (enFormat == OVF_1_0) ? "1.0" : "0.9");
661 pelmRoot->setAttribute("xml:lang", "en-US");
662
663 Utf8Str strNamespace = (enFormat == OVF_0_9)
664 ? "http://www.vmware.com/schema/ovf/1/envelope" // 0.9
665 : "http://schemas.dmtf.org/ovf/envelope/1"; // 1.0
666 pelmRoot->setAttribute("xmlns", strNamespace);
667 pelmRoot->setAttribute("xmlns:ovf", strNamespace);
668
669 // pelmRoot->setAttribute("xmlns:ovfstr", "http://schema.dmtf.org/ovf/strings/1");
670 pelmRoot->setAttribute("xmlns:rasd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData");
671 pelmRoot->setAttribute("xmlns:vssd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData");
672 pelmRoot->setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
673 pelmRoot->setAttribute("xmlns:vbox", "http://www.215389.xyz/ovf/machine");
674 // pelmRoot->setAttribute("xsi:schemaLocation", "http://schemas.dmtf.org/ovf/envelope/1 ../ovf-envelope.xsd");
675
676 // <Envelope>/<References>
677 xml::ElementNode *pelmReferences = pelmRoot->createChild("References"); // 0.9 and 1.0
678
679 /* <Envelope>/<DiskSection>:
680 <DiskSection>
681 <Info>List of the virtual disks used in the package</Info>
682 <Disk ovf:capacity="4294967296" ovf:diskId="lamp" ovf:format="..." ovf:populatedSize="1924967692"/>
683 </DiskSection> */
684 xml::ElementNode *pelmDiskSection;
685 if (enFormat == OVF_0_9)
686 {
687 // <Section xsi:type="ovf:DiskSection_Type">
688 pelmDiskSection = pelmRoot->createChild("Section");
689 pelmDiskSection->setAttribute("xsi:type", "ovf:DiskSection_Type");
690 }
691 else
692 pelmDiskSection = pelmRoot->createChild("DiskSection");
693
694 xml::ElementNode *pelmDiskSectionInfo = pelmDiskSection->createChild("Info");
695 pelmDiskSectionInfo->addContent("List of the virtual disks used in the package");
696
697 /* <Envelope>/<NetworkSection>:
698 <NetworkSection>
699 <Info>Logical networks used in the package</Info>
700 <Network ovf:name="VM Network">
701 <Description>The network that the LAMP Service will be available on</Description>
702 </Network>
703 </NetworkSection> */
704 xml::ElementNode *pelmNetworkSection;
705 if (enFormat == OVF_0_9)
706 {
707 // <Section xsi:type="ovf:NetworkSection_Type">
708 pelmNetworkSection = pelmRoot->createChild("Section");
709 pelmNetworkSection->setAttribute("xsi:type", "ovf:NetworkSection_Type");
710 }
711 else
712 pelmNetworkSection = pelmRoot->createChild("NetworkSection");
713
714 xml::ElementNode *pelmNetworkSectionInfo = pelmNetworkSection->createChild("Info");
715 pelmNetworkSectionInfo->addContent("Logical networks used in the package");
716
717 // and here come the virtual systems:
718
719 // write a collection if we have more than one virtual system _and_ we're
720 // writing OVF 1.0; otherwise fail since ovftool can't import more than
721 // one machine, it seems
722 xml::ElementNode *pelmToAddVirtualSystemsTo;
723 if (m->virtualSystemDescriptions.size() > 1)
724 {
725 if (enFormat == OVF_0_9)
726 throw setError(VBOX_E_FILE_ERROR,
727 tr("Cannot export more than one virtual system with OVF 0.9, use OVF 1.0"));
728
729 pelmToAddVirtualSystemsTo = pelmRoot->createChild("VirtualSystemCollection");
730 pelmToAddVirtualSystemsTo->setAttribute("ovf:name", "ExportedVirtualBoxMachines"); // whatever
731 }
732 else
733 pelmToAddVirtualSystemsTo = pelmRoot; // add virtual system directly under root element
734
735 // this list receives pointers to the XML elements in the machine XML which
736 // might have UUIDs that need fixing after we know the UUIDs of the exported images
737 std::list<xml::ElementNode*> llElementsWithUuidAttributes;
738
739 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it;
740 /* Iterate through all virtual systems of that appliance */
741 for (it = m->virtualSystemDescriptions.begin();
742 it != m->virtualSystemDescriptions.end();
743 ++it)
744 {
745 ComObjPtr<VirtualSystemDescription> vsdescThis = *it;
746 buildXMLForOneVirtualSystem(writeLock,
747 *pelmToAddVirtualSystemsTo,
748 &llElementsWithUuidAttributes,
749 vsdescThis,
750 enFormat,
751 stack); // disks and networks stack
752 }
753
754 // now, fill in the network section we set up empty above according
755 // to the networks we found with the hardware items
756 map<Utf8Str, bool>::const_iterator itN;
757 for (itN = stack.mapNetworks.begin();
758 itN != stack.mapNetworks.end();
759 ++itN)
760 {
761 const Utf8Str &strNetwork = itN->first;
762 xml::ElementNode *pelmNetwork = pelmNetworkSection->createChild("Network");
763 pelmNetwork->setAttribute("ovf:name", strNetwork.c_str());
764 pelmNetwork->createChild("Description")->addContent("Logical network used by this appliance.");
765 }
766
767 // Finally, write out the disk info
768 list<Utf8Str> diskList;
769 map<Utf8Str, const VirtualSystemDescriptionEntry*>::const_iterator itS;
770 uint32_t ulFile = 1;
771 for (itS = stack.mapDisks.begin();
772 itS != stack.mapDisks.end();
773 ++itS)
774 {
775 const Utf8Str &strDiskID = itS->first;
776 const VirtualSystemDescriptionEntry *pDiskEntry = itS->second;
777
778 // source path: where the VBox image is
779 const Utf8Str &strSrcFilePath = pDiskEntry->strVboxCurrent;
780 Bstr bstrSrcFilePath(strSrcFilePath);
781
782 // Do NOT check here whether the file exists. FindMedium will figure
783 // that out, and filesystem-based tests are simply wrong in the
784 // general case (think of iSCSI).
785
786 // We need some info from the source disks
787 ComPtr<IMedium> pSourceDisk;
788
789 Log(("Finding source disk \"%ls\"\n", bstrSrcFilePath.raw()));
790 HRESULT rc = mVirtualBox->FindMedium(bstrSrcFilePath.raw(), DeviceType_HardDisk, pSourceDisk.asOutParam());
791 if (FAILED(rc)) throw rc;
792
793 Bstr uuidSource;
794 rc = pSourceDisk->COMGETTER(Id)(uuidSource.asOutParam());
795 if (FAILED(rc)) throw rc;
796 Guid guidSource(uuidSource);
797
798 // output filename
799 const Utf8Str &strTargetFileNameOnly = pDiskEntry->strOvf;
800 // target path needs to be composed from where the output OVF is
801 Utf8Str strTargetFilePath(strPath);
802 strTargetFilePath.stripFilename();
803 strTargetFilePath.append("/");
804 strTargetFilePath.append(strTargetFileNameOnly);
805
806 // We are always exporting to VMDK stream optimized for now
807 Bstr bstrSrcFormat = L"VMDK";
808
809 diskList.push_back(strTargetFilePath);
810
811 LONG64 cbCapacity = 0; // size reported to guest
812 rc = pSourceDisk->COMGETTER(LogicalSize)(&cbCapacity);
813 if (FAILED(rc)) throw rc;
814 // Todo r=poetzsch: wrong it is reported in bytes ...
815 // capacity is reported in megabytes, so...
816 //cbCapacity *= _1M;
817
818 Guid guidTarget; /* Creates a new uniq number for the target disk. */
819 guidTarget.create();
820
821 // now handle the XML for the disk:
822 Utf8StrFmt strFileRef("file%RI32", ulFile++);
823 // <File ovf:href="WindowsXpProfessional-disk1.vmdk" ovf:id="file1" ovf:size="1710381056"/>
824 xml::ElementNode *pelmFile = pelmReferences->createChild("File");
825 pelmFile->setAttribute("ovf:href", strTargetFileNameOnly);
826 pelmFile->setAttribute("ovf:id", strFileRef);
827 // Todo: the actual size is not available at this point of time,
828 // cause the disk will be compressed. The 1.0 standard says this is
829 // optional! 1.1 isn't fully clear if the "gzip" format is used.
830 // Need to be checked. */
831 // pelmFile->setAttribute("ovf:size", Utf8StrFmt("%RI64", cbFile).c_str());
832
833 // add disk to XML Disks section
834 // <Disk ovf:capacity="8589934592" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="..."/>
835 xml::ElementNode *pelmDisk = pelmDiskSection->createChild("Disk");
836 pelmDisk->setAttribute("ovf:capacity", Utf8StrFmt("%RI64", cbCapacity).c_str());
837 pelmDisk->setAttribute("ovf:diskId", strDiskID);
838 pelmDisk->setAttribute("ovf:fileRef", strFileRef);
839 pelmDisk->setAttribute("ovf:format",
840 (enFormat == OVF_0_9)
841 ? "http://www.vmware.com/specifications/vmdk.html#sparse" // must be sparse or ovftool chokes
842 : "http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"
843 // correct string as communicated to us by VMware (public bug #6612)
844 );
845
846 // add the UUID of the newly target image to the OVF disk element, but in the
847 // vbox: namespace since it's not part of the standard
848 pelmDisk->setAttribute("vbox:uuid", Utf8StrFmt("%RTuuid", guidTarget.raw()).c_str());
849
850 // now, we might have other XML elements from vbox:Machine pointing to this image,
851 // but those would refer to the UUID of the _source_ image (which we created the
852 // export image from); those UUIDs need to be fixed to the export image
853 Utf8Str strGuidSourceCurly = guidSource.toStringCurly();
854 for (std::list<xml::ElementNode*>::iterator eit = llElementsWithUuidAttributes.begin();
855 eit != llElementsWithUuidAttributes.end();
856 ++eit)
857 {
858 xml::ElementNode *pelmImage = *eit;
859 Utf8Str strUUID;
860 pelmImage->getAttributeValue("uuid", strUUID);
861 if (strUUID == strGuidSourceCurly)
862 // overwrite existing uuid attribute
863 pelmImage->setAttribute("uuid", guidTarget.toStringCurly());
864 }
865 }
866}
867
868/**
869 * Called from Appliance::buildXML() for each virtual system (machine) that
870 * needs XML written out.
871 *
872 * @param writeLock The current write lock.
873 * @param elmToAddVirtualSystemsTo XML element to append elements to.
874 * @param pllElementsWithUuidAttributes out: list of XML elements produced here
875 * with UUID attributes for quick
876 * fixing by caller later
877 * @param vsdescThis The IVirtualSystemDescription
878 * instance for which to write XML.
879 * @param enFormat OVF format (0.9 or 1.0).
880 * @param stack Structure for temporary private
881 * data shared with caller.
882 */
883void Appliance::buildXMLForOneVirtualSystem(AutoWriteLockBase& writeLock,
884 xml::ElementNode &elmToAddVirtualSystemsTo,
885 std::list<xml::ElementNode*> *pllElementsWithUuidAttributes,
886 ComObjPtr<VirtualSystemDescription> &vsdescThis,
887 OVFFormat enFormat,
888 XMLStack &stack)
889{
890 LogFlowFunc(("ENTER appliance %p\n", this));
891
892 xml::ElementNode *pelmVirtualSystem;
893 if (enFormat == OVF_0_9)
894 {
895 // <Section xsi:type="ovf:NetworkSection_Type">
896 pelmVirtualSystem = elmToAddVirtualSystemsTo.createChild("Content");
897 pelmVirtualSystem->setAttribute("xsi:type", "ovf:VirtualSystem_Type");
898 }
899 else
900 pelmVirtualSystem = elmToAddVirtualSystemsTo.createChild("VirtualSystem");
901
902 /*xml::ElementNode *pelmVirtualSystemInfo =*/ pelmVirtualSystem->createChild("Info")->addContent("A virtual machine");
903
904 std::list<VirtualSystemDescriptionEntry*> llName = vsdescThis->findByType(VirtualSystemDescriptionType_Name);
905 if (llName.size() != 1)
906 throw setError(VBOX_E_NOT_SUPPORTED,
907 tr("Missing VM name"));
908 Utf8Str &strVMName = llName.front()->strVboxCurrent;
909 pelmVirtualSystem->setAttribute("ovf:id", strVMName);
910
911 // product info
912 std::list<VirtualSystemDescriptionEntry*> llProduct = vsdescThis->findByType(VirtualSystemDescriptionType_Product);
913 std::list<VirtualSystemDescriptionEntry*> llProductUrl = vsdescThis->findByType(VirtualSystemDescriptionType_ProductUrl);
914 std::list<VirtualSystemDescriptionEntry*> llVendor = vsdescThis->findByType(VirtualSystemDescriptionType_Vendor);
915 std::list<VirtualSystemDescriptionEntry*> llVendorUrl = vsdescThis->findByType(VirtualSystemDescriptionType_VendorUrl);
916 std::list<VirtualSystemDescriptionEntry*> llVersion = vsdescThis->findByType(VirtualSystemDescriptionType_Version);
917 bool fProduct = llProduct.size() && !llProduct.front()->strVboxCurrent.isEmpty();
918 bool fProductUrl = llProductUrl.size() && !llProductUrl.front()->strVboxCurrent.isEmpty();
919 bool fVendor = llVendor.size() && !llVendor.front()->strVboxCurrent.isEmpty();
920 bool fVendorUrl = llVendorUrl.size() && !llVendorUrl.front()->strVboxCurrent.isEmpty();
921 bool fVersion = llVersion.size() && !llVersion.front()->strVboxCurrent.isEmpty();
922 if (fProduct ||
923 fProductUrl ||
924 fVersion ||
925 fVendorUrl ||
926 fVersion)
927 {
928 /* <Section ovf:required="false" xsi:type="ovf:ProductSection_Type">
929 <Info>Meta-information about the installed software</Info>
930 <Product>VAtest</Product>
931 <Vendor>SUN Microsystems</Vendor>
932 <Version>10.0</Version>
933 <ProductUrl>http://blogs.sun.com/VirtualGuru</ProductUrl>
934 <VendorUrl>http://www.sun.com</VendorUrl>
935 </Section> */
936 xml::ElementNode *pelmAnnotationSection;
937 if (enFormat == OVF_0_9)
938 {
939 // <Section ovf:required="false" xsi:type="ovf:ProductSection_Type">
940 pelmAnnotationSection = pelmVirtualSystem->createChild("Section");
941 pelmAnnotationSection->setAttribute("xsi:type", "ovf:ProductSection_Type");
942 }
943 else
944 pelmAnnotationSection = pelmVirtualSystem->createChild("ProductSection");
945
946 pelmAnnotationSection->createChild("Info")->addContent("Meta-information about the installed software");
947 if (fProduct)
948 pelmAnnotationSection->createChild("Product")->addContent(llProduct.front()->strVboxCurrent);
949 if (fVendor)
950 pelmAnnotationSection->createChild("Vendor")->addContent(llVendor.front()->strVboxCurrent);
951 if (fVersion)
952 pelmAnnotationSection->createChild("Version")->addContent(llVersion.front()->strVboxCurrent);
953 if (fProductUrl)
954 pelmAnnotationSection->createChild("ProductUrl")->addContent(llProductUrl.front()->strVboxCurrent);
955 if (fVendorUrl)
956 pelmAnnotationSection->createChild("VendorUrl")->addContent(llVendorUrl.front()->strVboxCurrent);
957 }
958
959 // description
960 std::list<VirtualSystemDescriptionEntry*> llDescription = vsdescThis->findByType(VirtualSystemDescriptionType_Description);
961 if (llDescription.size() &&
962 !llDescription.front()->strVboxCurrent.isEmpty())
963 {
964 /* <Section ovf:required="false" xsi:type="ovf:AnnotationSection_Type">
965 <Info>A human-readable annotation</Info>
966 <Annotation>Plan 9</Annotation>
967 </Section> */
968 xml::ElementNode *pelmAnnotationSection;
969 if (enFormat == OVF_0_9)
970 {
971 // <Section ovf:required="false" xsi:type="ovf:AnnotationSection_Type">
972 pelmAnnotationSection = pelmVirtualSystem->createChild("Section");
973 pelmAnnotationSection->setAttribute("xsi:type", "ovf:AnnotationSection_Type");
974 }
975 else
976 pelmAnnotationSection = pelmVirtualSystem->createChild("AnnotationSection");
977
978 pelmAnnotationSection->createChild("Info")->addContent("A human-readable annotation");
979 pelmAnnotationSection->createChild("Annotation")->addContent(llDescription.front()->strVboxCurrent);
980 }
981
982 // license
983 std::list<VirtualSystemDescriptionEntry*> llLicense = vsdescThis->findByType(VirtualSystemDescriptionType_License);
984 if (llLicense.size() &&
985 !llLicense.front()->strVboxCurrent.isEmpty())
986 {
987 /* <EulaSection>
988 <Info ovf:msgid="6">License agreement for the Virtual System.</Info>
989 <License ovf:msgid="1">License terms can go in here.</License>
990 </EulaSection> */
991 xml::ElementNode *pelmEulaSection;
992 if (enFormat == OVF_0_9)
993 {
994 pelmEulaSection = pelmVirtualSystem->createChild("Section");
995 pelmEulaSection->setAttribute("xsi:type", "ovf:EulaSection_Type");
996 }
997 else
998 pelmEulaSection = pelmVirtualSystem->createChild("EulaSection");
999
1000 pelmEulaSection->createChild("Info")->addContent("License agreement for the virtual system");
1001 pelmEulaSection->createChild("License")->addContent(llLicense.front()->strVboxCurrent);
1002 }
1003
1004 // operating system
1005 std::list<VirtualSystemDescriptionEntry*> llOS = vsdescThis->findByType(VirtualSystemDescriptionType_OS);
1006 if (llOS.size() != 1)
1007 throw setError(VBOX_E_NOT_SUPPORTED,
1008 tr("Missing OS type"));
1009 /* <OperatingSystemSection ovf:id="82">
1010 <Info>Guest Operating System</Info>
1011 <Description>Linux 2.6.x</Description>
1012 </OperatingSystemSection> */
1013 VirtualSystemDescriptionEntry *pvsdeOS = llOS.front();
1014 xml::ElementNode *pelmOperatingSystemSection;
1015 if (enFormat == OVF_0_9)
1016 {
1017 pelmOperatingSystemSection = pelmVirtualSystem->createChild("Section");
1018 pelmOperatingSystemSection->setAttribute("xsi:type", "ovf:OperatingSystemSection_Type");
1019 }
1020 else
1021 pelmOperatingSystemSection = pelmVirtualSystem->createChild("OperatingSystemSection");
1022
1023 pelmOperatingSystemSection->setAttribute("ovf:id", pvsdeOS->strOvf);
1024 pelmOperatingSystemSection->createChild("Info")->addContent("The kind of installed guest operating system");
1025 Utf8Str strOSDesc;
1026 convertCIMOSType2VBoxOSType(strOSDesc, (ovf::CIMOSType_T)pvsdeOS->strOvf.toInt32(), "");
1027 pelmOperatingSystemSection->createChild("Description")->addContent(strOSDesc);
1028 // add the VirtualBox ostype in a custom tag in a different namespace
1029 xml::ElementNode *pelmVBoxOSType = pelmOperatingSystemSection->createChild("vbox:OSType");
1030 pelmVBoxOSType->setAttribute("ovf:required", "false");
1031 pelmVBoxOSType->addContent(pvsdeOS->strVboxCurrent);
1032
1033 // <VirtualHardwareSection ovf:id="hw1" ovf:transport="iso">
1034 xml::ElementNode *pelmVirtualHardwareSection;
1035 if (enFormat == OVF_0_9)
1036 {
1037 // <Section xsi:type="ovf:VirtualHardwareSection_Type">
1038 pelmVirtualHardwareSection = pelmVirtualSystem->createChild("Section");
1039 pelmVirtualHardwareSection->setAttribute("xsi:type", "ovf:VirtualHardwareSection_Type");
1040 }
1041 else
1042 pelmVirtualHardwareSection = pelmVirtualSystem->createChild("VirtualHardwareSection");
1043
1044 pelmVirtualHardwareSection->createChild("Info")->addContent("Virtual hardware requirements for a virtual machine");
1045
1046 /* <System>
1047 <vssd:Description>Description of the virtual hardware section.</vssd:Description>
1048 <vssd:ElementName>vmware</vssd:ElementName>
1049 <vssd:InstanceID>1</vssd:InstanceID>
1050 <vssd:VirtualSystemIdentifier>MyLampService</vssd:VirtualSystemIdentifier>
1051 <vssd:VirtualSystemType>vmx-4</vssd:VirtualSystemType>
1052 </System> */
1053 xml::ElementNode *pelmSystem = pelmVirtualHardwareSection->createChild("System");
1054
1055 pelmSystem->createChild("vssd:ElementName")->addContent("Virtual Hardware Family"); // required OVF 1.0
1056
1057 // <vssd:InstanceId>0</vssd:InstanceId>
1058 if (enFormat == OVF_0_9)
1059 pelmSystem->createChild("vssd:InstanceId")->addContent("0");
1060 else // capitalization changed...
1061 pelmSystem->createChild("vssd:InstanceID")->addContent("0");
1062
1063 // <vssd:VirtualSystemIdentifier>VAtest</vssd:VirtualSystemIdentifier>
1064 pelmSystem->createChild("vssd:VirtualSystemIdentifier")->addContent(strVMName);
1065 // <vssd:VirtualSystemType>vmx-4</vssd:VirtualSystemType>
1066 const char *pcszHardware = "virtualbox-2.2";
1067 if (enFormat == OVF_0_9)
1068 // pretend to be vmware compatible then
1069 pcszHardware = "vmx-6";
1070 pelmSystem->createChild("vssd:VirtualSystemType")->addContent(pcszHardware);
1071
1072 // loop thru all description entries twice; once to write out all
1073 // devices _except_ disk images, and a second time to assign the
1074 // disk images; this is because disk images need to reference
1075 // IDE controllers, and we can't know their instance IDs without
1076 // assigning them first
1077
1078 uint32_t idIDEPrimaryController = 0;
1079 int32_t lIDEPrimaryControllerIndex = 0;
1080 uint32_t idIDESecondaryController = 0;
1081 int32_t lIDESecondaryControllerIndex = 0;
1082 uint32_t idSATAController = 0;
1083 int32_t lSATAControllerIndex = 0;
1084 uint32_t idSCSIController = 0;
1085 int32_t lSCSIControllerIndex = 0;
1086
1087 uint32_t ulInstanceID = 1;
1088
1089 uint32_t cDVDs = 0;
1090
1091 for (size_t uLoop = 1; uLoop <= 2; ++uLoop)
1092 {
1093 int32_t lIndexThis = 0;
1094 list<VirtualSystemDescriptionEntry>::const_iterator itD;
1095 for (itD = vsdescThis->m->llDescriptions.begin();
1096 itD != vsdescThis->m->llDescriptions.end();
1097 ++itD, ++lIndexThis)
1098 {
1099 const VirtualSystemDescriptionEntry &desc = *itD;
1100
1101 LogFlowFunc(("Loop %u: handling description entry ulIndex=%u, type=%s, strRef=%s, strOvf=%s, strVbox=%s, strExtraConfig=%s\n",
1102 uLoop,
1103 desc.ulIndex,
1104 ( desc.type == VirtualSystemDescriptionType_HardDiskControllerIDE ? "HardDiskControllerIDE"
1105 : desc.type == VirtualSystemDescriptionType_HardDiskControllerSATA ? "HardDiskControllerSATA"
1106 : desc.type == VirtualSystemDescriptionType_HardDiskControllerSCSI ? "HardDiskControllerSCSI"
1107 : desc.type == VirtualSystemDescriptionType_HardDiskControllerSAS ? "HardDiskControllerSAS"
1108 : desc.type == VirtualSystemDescriptionType_HardDiskImage ? "HardDiskImage"
1109 : Utf8StrFmt("%d", desc.type).c_str()),
1110 desc.strRef.c_str(),
1111 desc.strOvf.c_str(),
1112 desc.strVboxCurrent.c_str(),
1113 desc.strExtraConfigCurrent.c_str()));
1114
1115 ovf::ResourceType_T type = (ovf::ResourceType_T)0; // if this becomes != 0 then we do stuff
1116 Utf8Str strResourceSubType;
1117
1118 Utf8Str strDescription; // results in <rasd:Description>...</rasd:Description> block
1119 Utf8Str strCaption; // results in <rasd:Caption>...</rasd:Caption> block
1120
1121 uint32_t ulParent = 0;
1122
1123 int32_t lVirtualQuantity = -1;
1124 Utf8Str strAllocationUnits;
1125
1126 int32_t lAddress = -1;
1127 int32_t lBusNumber = -1;
1128 int32_t lAddressOnParent = -1;
1129
1130 int32_t lAutomaticAllocation = -1; // 0 means "false", 1 means "true"
1131 Utf8Str strConnection; // results in <rasd:Connection>...</rasd:Connection> block
1132 Utf8Str strHostResource;
1133
1134 uint64_t uTemp;
1135
1136 switch (desc.type)
1137 {
1138 case VirtualSystemDescriptionType_CPU:
1139 /* <Item>
1140 <rasd:Caption>1 virtual CPU</rasd:Caption>
1141 <rasd:Description>Number of virtual CPUs</rasd:Description>
1142 <rasd:ElementName>virtual CPU</rasd:ElementName>
1143 <rasd:InstanceID>1</rasd:InstanceID>
1144 <rasd:ResourceType>3</rasd:ResourceType>
1145 <rasd:VirtualQuantity>1</rasd:VirtualQuantity>
1146 </Item> */
1147 if (uLoop == 1)
1148 {
1149 strDescription = "Number of virtual CPUs";
1150 type = ovf::ResourceType_Processor; // 3
1151 desc.strVboxCurrent.toInt(uTemp);
1152 lVirtualQuantity = (int32_t)uTemp;
1153 strCaption = Utf8StrFmt("%d virtual CPU", lVirtualQuantity); // without this ovftool won't eat the item
1154 }
1155 break;
1156
1157 case VirtualSystemDescriptionType_Memory:
1158 /* <Item>
1159 <rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
1160 <rasd:Caption>256 MB of memory</rasd:Caption>
1161 <rasd:Description>Memory Size</rasd:Description>
1162 <rasd:ElementName>Memory</rasd:ElementName>
1163 <rasd:InstanceID>2</rasd:InstanceID>
1164 <rasd:ResourceType>4</rasd:ResourceType>
1165 <rasd:VirtualQuantity>256</rasd:VirtualQuantity>
1166 </Item> */
1167 if (uLoop == 1)
1168 {
1169 strDescription = "Memory Size";
1170 type = ovf::ResourceType_Memory; // 4
1171 desc.strVboxCurrent.toInt(uTemp);
1172 lVirtualQuantity = (int32_t)(uTemp / _1M);
1173 strAllocationUnits = "MegaBytes";
1174 strCaption = Utf8StrFmt("%d MB of memory", lVirtualQuantity); // without this ovftool won't eat the item
1175 }
1176 break;
1177
1178 case VirtualSystemDescriptionType_HardDiskControllerIDE:
1179 /* <Item>
1180 <rasd:Caption>ideController1</rasd:Caption>
1181 <rasd:Description>IDE Controller</rasd:Description>
1182 <rasd:InstanceId>5</rasd:InstanceId>
1183 <rasd:ResourceType>5</rasd:ResourceType>
1184 <rasd:Address>1</rasd:Address>
1185 <rasd:BusNumber>1</rasd:BusNumber>
1186 </Item> */
1187 if (uLoop == 1)
1188 {
1189 strDescription = "IDE Controller";
1190 type = ovf::ResourceType_IDEController; // 5
1191 strResourceSubType = desc.strVboxCurrent;
1192
1193 if (!lIDEPrimaryControllerIndex)
1194 {
1195 // first IDE controller:
1196 strCaption = "ideController0";
1197 lAddress = 0;
1198 lBusNumber = 0;
1199 // remember this ID
1200 idIDEPrimaryController = ulInstanceID;
1201 lIDEPrimaryControllerIndex = lIndexThis;
1202 }
1203 else
1204 {
1205 // second IDE controller:
1206 strCaption = "ideController1";
1207 lAddress = 1;
1208 lBusNumber = 1;
1209 // remember this ID
1210 idIDESecondaryController = ulInstanceID;
1211 lIDESecondaryControllerIndex = lIndexThis;
1212 }
1213 }
1214 break;
1215
1216 case VirtualSystemDescriptionType_HardDiskControllerSATA:
1217 /* <Item>
1218 <rasd:Caption>sataController0</rasd:Caption>
1219 <rasd:Description>SATA Controller</rasd:Description>
1220 <rasd:InstanceId>4</rasd:InstanceId>
1221 <rasd:ResourceType>20</rasd:ResourceType>
1222 <rasd:ResourceSubType>ahci</rasd:ResourceSubType>
1223 <rasd:Address>0</rasd:Address>
1224 <rasd:BusNumber>0</rasd:BusNumber>
1225 </Item>
1226 */
1227 if (uLoop == 1)
1228 {
1229 strDescription = "SATA Controller";
1230 strCaption = "sataController0";
1231 type = ovf::ResourceType_OtherStorageDevice; // 20
1232 // it seems that OVFTool always writes these two, and since we can only
1233 // have one SATA controller, we'll use this as well
1234 lAddress = 0;
1235 lBusNumber = 0;
1236
1237 if ( desc.strVboxCurrent.isEmpty() // AHCI is the default in VirtualBox
1238 || (!desc.strVboxCurrent.compare("ahci", Utf8Str::CaseInsensitive))
1239 )
1240 strResourceSubType = "AHCI";
1241 else
1242 throw setError(VBOX_E_NOT_SUPPORTED,
1243 tr("Invalid config string \"%s\" in SATA controller"), desc.strVboxCurrent.c_str());
1244
1245 // remember this ID
1246 idSATAController = ulInstanceID;
1247 lSATAControllerIndex = lIndexThis;
1248 }
1249 break;
1250
1251 case VirtualSystemDescriptionType_HardDiskControllerSCSI:
1252 case VirtualSystemDescriptionType_HardDiskControllerSAS:
1253 /* <Item>
1254 <rasd:Caption>scsiController0</rasd:Caption>
1255 <rasd:Description>SCSI Controller</rasd:Description>
1256 <rasd:InstanceId>4</rasd:InstanceId>
1257 <rasd:ResourceType>6</rasd:ResourceType>
1258 <rasd:ResourceSubType>buslogic</rasd:ResourceSubType>
1259 <rasd:Address>0</rasd:Address>
1260 <rasd:BusNumber>0</rasd:BusNumber>
1261 </Item>
1262 */
1263 if (uLoop == 1)
1264 {
1265 strDescription = "SCSI Controller";
1266 strCaption = "scsiController0";
1267 type = ovf::ResourceType_ParallelSCSIHBA; // 6
1268 // it seems that OVFTool always writes these two, and since we can only
1269 // have one SATA controller, we'll use this as well
1270 lAddress = 0;
1271 lBusNumber = 0;
1272
1273 if ( desc.strVboxCurrent.isEmpty() // LsiLogic is the default in VirtualBox
1274 || (!desc.strVboxCurrent.compare("lsilogic", Utf8Str::CaseInsensitive))
1275 )
1276 strResourceSubType = "lsilogic";
1277 else if (!desc.strVboxCurrent.compare("buslogic", Utf8Str::CaseInsensitive))
1278 strResourceSubType = "buslogic";
1279 else if (!desc.strVboxCurrent.compare("lsilogicsas", Utf8Str::CaseInsensitive))
1280 strResourceSubType = "lsilogicsas";
1281 else
1282 throw setError(VBOX_E_NOT_SUPPORTED,
1283 tr("Invalid config string \"%s\" in SCSI/SAS controller"), desc.strVboxCurrent.c_str());
1284
1285 // remember this ID
1286 idSCSIController = ulInstanceID;
1287 lSCSIControllerIndex = lIndexThis;
1288 }
1289 break;
1290
1291 case VirtualSystemDescriptionType_HardDiskImage:
1292 /* <Item>
1293 <rasd:Caption>disk1</rasd:Caption>
1294 <rasd:InstanceId>8</rasd:InstanceId>
1295 <rasd:ResourceType>17</rasd:ResourceType>
1296 <rasd:HostResource>/disk/vmdisk1</rasd:HostResource>
1297 <rasd:Parent>4</rasd:Parent>
1298 <rasd:AddressOnParent>0</rasd:AddressOnParent>
1299 </Item> */
1300 if (uLoop == 2)
1301 {
1302 uint32_t cDisks = stack.mapDisks.size();
1303 Utf8Str strDiskID = Utf8StrFmt("vmdisk%RI32", ++cDisks);
1304
1305 strDescription = "Disk Image";
1306 strCaption = Utf8StrFmt("disk%RI32", cDisks); // this is not used for anything else
1307 type = ovf::ResourceType_HardDisk; // 17
1308
1309 // the following references the "<Disks>" XML block
1310 strHostResource = Utf8StrFmt("/disk/%s", strDiskID.c_str());
1311
1312 // controller=<index>;channel=<c>
1313 size_t pos1 = desc.strExtraConfigCurrent.find("controller=");
1314 size_t pos2 = desc.strExtraConfigCurrent.find("channel=");
1315 int32_t lControllerIndex = -1;
1316 if (pos1 != Utf8Str::npos)
1317 {
1318 RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos1 + 11, NULL, 0, &lControllerIndex);
1319 if (lControllerIndex == lIDEPrimaryControllerIndex)
1320 ulParent = idIDEPrimaryController;
1321 else if (lControllerIndex == lIDESecondaryControllerIndex)
1322 ulParent = idIDESecondaryController;
1323 else if (lControllerIndex == lSCSIControllerIndex)
1324 ulParent = idSCSIController;
1325 else if (lControllerIndex == lSATAControllerIndex)
1326 ulParent = idSATAController;
1327 }
1328 if (pos2 != Utf8Str::npos)
1329 RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos2 + 8, NULL, 0, &lAddressOnParent);
1330
1331 LogFlowFunc(("HardDiskImage details: pos1=%d, pos2=%d, lControllerIndex=%d, lIDEPrimaryControllerIndex=%d, lIDESecondaryControllerIndex=%d, ulParent=%d, lAddressOnParent=%d\n",
1332 pos1, pos2, lControllerIndex, lIDEPrimaryControllerIndex, lIDESecondaryControllerIndex, ulParent, lAddressOnParent));
1333
1334 if ( !ulParent
1335 || lAddressOnParent == -1
1336 )
1337 throw setError(VBOX_E_NOT_SUPPORTED,
1338 tr("Missing or bad extra config string in hard disk image: \"%s\""), desc.strExtraConfigCurrent.c_str());
1339
1340 stack.mapDisks[strDiskID] = &desc;
1341 }
1342 break;
1343
1344 case VirtualSystemDescriptionType_Floppy:
1345 if (uLoop == 1)
1346 {
1347 strDescription = "Floppy Drive";
1348 strCaption = "floppy0"; // this is what OVFTool writes
1349 type = ovf::ResourceType_FloppyDrive; // 14
1350 lAutomaticAllocation = 0;
1351 lAddressOnParent = 0; // this is what OVFTool writes
1352 }
1353 break;
1354
1355 case VirtualSystemDescriptionType_CDROM:
1356 if (uLoop == 2)
1357 {
1358 strDescription = "CD-ROM Drive";
1359 strCaption = Utf8StrFmt("cdrom%RI32", ++cDVDs); // OVFTool starts with 1
1360 type = ovf::ResourceType_CDDrive; // 15
1361 lAutomaticAllocation = 1;
1362
1363 // controller=<index>;channel=<c>
1364 size_t pos1 = desc.strExtraConfigCurrent.find("controller=");
1365 size_t pos2 = desc.strExtraConfigCurrent.find("channel=");
1366 int32_t lControllerIndex = -1;
1367 if (pos1 != Utf8Str::npos)
1368 {
1369 RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos1 + 11, NULL, 0, &lControllerIndex);
1370 if (lControllerIndex == lIDEPrimaryControllerIndex)
1371 ulParent = idIDEPrimaryController;
1372 else if (lControllerIndex == lIDESecondaryControllerIndex)
1373 ulParent = idIDESecondaryController;
1374 else if (lControllerIndex == lSCSIControllerIndex)
1375 ulParent = idSCSIController;
1376 else if (lControllerIndex == lSATAControllerIndex)
1377 ulParent = idSATAController;
1378 }
1379 if (pos2 != Utf8Str::npos)
1380 RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos2 + 8, NULL, 0, &lAddressOnParent);
1381
1382 LogFlowFunc(("DVD drive details: pos1=%d, pos2=%d, lControllerIndex=%d, lIDEPrimaryControllerIndex=%d, lIDESecondaryControllerIndex=%d, ulParent=%d, lAddressOnParent=%d\n",
1383 pos1, pos2, lControllerIndex, lIDEPrimaryControllerIndex, lIDESecondaryControllerIndex, ulParent, lAddressOnParent));
1384
1385 if ( !ulParent
1386 || lAddressOnParent == -1
1387 )
1388 throw setError(VBOX_E_NOT_SUPPORTED,
1389 tr("Missing or bad extra config string in DVD drive medium: \"%s\""), desc.strExtraConfigCurrent.c_str());
1390
1391 // there is no DVD drive map to update because it is
1392 // handled completely with this entry.
1393 }
1394 break;
1395
1396 case VirtualSystemDescriptionType_NetworkAdapter:
1397 /* <Item>
1398 <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
1399 <rasd:Caption>Ethernet adapter on 'VM Network'</rasd:Caption>
1400 <rasd:Connection>VM Network</rasd:Connection>
1401 <rasd:ElementName>VM network</rasd:ElementName>
1402 <rasd:InstanceID>3</rasd:InstanceID>
1403 <rasd:ResourceType>10</rasd:ResourceType>
1404 </Item> */
1405 if (uLoop == 1)
1406 {
1407 lAutomaticAllocation = 1;
1408 strCaption = Utf8StrFmt("Ethernet adapter on '%s'", desc.strOvf.c_str());
1409 type = ovf::ResourceType_EthernetAdapter; // 10
1410 /* Set the hardware type to something useful.
1411 * To be compatible with vmware & others we set
1412 * PCNet32 for our PCNet types & E1000 for the
1413 * E1000 cards. */
1414 switch (desc.strVboxCurrent.toInt32())
1415 {
1416 case NetworkAdapterType_Am79C970A:
1417 case NetworkAdapterType_Am79C973: strResourceSubType = "PCNet32"; break;
1418#ifdef VBOX_WITH_E1000
1419 case NetworkAdapterType_I82540EM:
1420 case NetworkAdapterType_I82545EM:
1421 case NetworkAdapterType_I82543GC: strResourceSubType = "E1000"; break;
1422#endif /* VBOX_WITH_E1000 */
1423 }
1424 strConnection = desc.strOvf;
1425
1426 stack.mapNetworks[desc.strOvf] = true;
1427 }
1428 break;
1429
1430 case VirtualSystemDescriptionType_USBController:
1431 /* <Item ovf:required="false">
1432 <rasd:Caption>usb</rasd:Caption>
1433 <rasd:Description>USB Controller</rasd:Description>
1434 <rasd:InstanceId>3</rasd:InstanceId>
1435 <rasd:ResourceType>23</rasd:ResourceType>
1436 <rasd:Address>0</rasd:Address>
1437 <rasd:BusNumber>0</rasd:BusNumber>
1438 </Item> */
1439 if (uLoop == 1)
1440 {
1441 strDescription = "USB Controller";
1442 strCaption = "usb";
1443 type = ovf::ResourceType_USBController; // 23
1444 lAddress = 0; // this is what OVFTool writes
1445 lBusNumber = 0; // this is what OVFTool writes
1446 }
1447 break;
1448
1449 case VirtualSystemDescriptionType_SoundCard:
1450 /* <Item ovf:required="false">
1451 <rasd:Caption>sound</rasd:Caption>
1452 <rasd:Description>Sound Card</rasd:Description>
1453 <rasd:InstanceId>10</rasd:InstanceId>
1454 <rasd:ResourceType>35</rasd:ResourceType>
1455 <rasd:ResourceSubType>ensoniq1371</rasd:ResourceSubType>
1456 <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
1457 <rasd:AddressOnParent>3</rasd:AddressOnParent>
1458 </Item> */
1459 if (uLoop == 1)
1460 {
1461 strDescription = "Sound Card";
1462 strCaption = "sound";
1463 type = ovf::ResourceType_SoundCard; // 35
1464 strResourceSubType = desc.strOvf; // e.g. ensoniq1371
1465 lAutomaticAllocation = 0;
1466 lAddressOnParent = 3; // what gives? this is what OVFTool writes
1467 }
1468 break;
1469 }
1470
1471 if (type)
1472 {
1473 xml::ElementNode *pItem;
1474
1475 pItem = pelmVirtualHardwareSection->createChild("Item");
1476
1477 // NOTE: DO NOT CHANGE THE ORDER of these items! The OVF standards prescribes that
1478 // the elements from the rasd: namespace must be sorted by letter, and VMware
1479 // actually requires this as well (see public bug #6612)
1480
1481 if (lAddress != -1)
1482 pItem->createChild("rasd:Address")->addContent(Utf8StrFmt("%d", lAddress));
1483
1484 if (lAddressOnParent != -1)
1485 pItem->createChild("rasd:AddressOnParent")->addContent(Utf8StrFmt("%d", lAddressOnParent));
1486
1487 if (!strAllocationUnits.isEmpty())
1488 pItem->createChild("rasd:AllocationUnits")->addContent(strAllocationUnits);
1489
1490 if (lAutomaticAllocation != -1)
1491 pItem->createChild("rasd:AutomaticAllocation")->addContent( (lAutomaticAllocation) ? "true" : "false" );
1492
1493 if (lBusNumber != -1)
1494 if (enFormat == OVF_0_9) // BusNumber is invalid OVF 1.0 so only write it in 0.9 mode for OVFTool compatibility
1495 pItem->createChild("rasd:BusNumber")->addContent(Utf8StrFmt("%d", lBusNumber));
1496
1497 if (!strCaption.isEmpty())
1498 pItem->createChild("rasd:Caption")->addContent(strCaption);
1499
1500 if (!strConnection.isEmpty())
1501 pItem->createChild("rasd:Connection")->addContent(strConnection);
1502
1503 if (!strDescription.isEmpty())
1504 pItem->createChild("rasd:Description")->addContent(strDescription);
1505
1506 if (!strCaption.isEmpty())
1507 if (enFormat == OVF_1_0)
1508 pItem->createChild("rasd:ElementName")->addContent(strCaption);
1509
1510 if (!strHostResource.isEmpty())
1511 pItem->createChild("rasd:HostResource")->addContent(strHostResource);
1512
1513 // <rasd:InstanceID>1</rasd:InstanceID>
1514 xml::ElementNode *pelmInstanceID;
1515 if (enFormat == OVF_0_9)
1516 pelmInstanceID = pItem->createChild("rasd:InstanceId");
1517 else
1518 pelmInstanceID = pItem->createChild("rasd:InstanceID"); // capitalization changed...
1519 pelmInstanceID->addContent(Utf8StrFmt("%d", ulInstanceID++));
1520
1521 if (ulParent)
1522 pItem->createChild("rasd:Parent")->addContent(Utf8StrFmt("%d", ulParent));
1523
1524 if (!strResourceSubType.isEmpty())
1525 pItem->createChild("rasd:ResourceSubType")->addContent(strResourceSubType);
1526
1527 // <rasd:ResourceType>3</rasd:ResourceType>
1528 pItem->createChild("rasd:ResourceType")->addContent(Utf8StrFmt("%d", type));
1529
1530 // <rasd:VirtualQuantity>1</rasd:VirtualQuantity>
1531 if (lVirtualQuantity != -1)
1532 pItem->createChild("rasd:VirtualQuantity")->addContent(Utf8StrFmt("%d", lVirtualQuantity));
1533 }
1534 }
1535 } // for (size_t uLoop = 1; uLoop <= 2; ++uLoop)
1536
1537 // now that we're done with the official OVF <Item> tags under <VirtualSystem>, write out VirtualBox XML
1538 // under the vbox: namespace
1539 xml::ElementNode *pelmVBoxMachine = pelmVirtualSystem->createChild("vbox:Machine");
1540 // ovf:required="false" tells other OVF parsers that they can ignore this thing
1541 pelmVBoxMachine->setAttribute("ovf:required", "false");
1542 // ovf:Info element is required or VMware will bail out on the vbox:Machine element
1543 pelmVBoxMachine->createChild("ovf:Info")->addContent("Complete VirtualBox machine configuration in VirtualBox format");
1544
1545 // create an empty machine config
1546 settings::MachineConfigFile *pConfig = new settings::MachineConfigFile(NULL);
1547
1548 writeLock.release();
1549 try
1550 {
1551 AutoWriteLock machineLock(vsdescThis->m->pMachine COMMA_LOCKVAL_SRC_POS);
1552 // fill the machine config
1553 vsdescThis->m->pMachine->copyMachineDataToSettings(*pConfig);
1554 // write the machine config to the vbox:Machine element
1555 pConfig->buildMachineXML(*pelmVBoxMachine,
1556 settings::MachineConfigFile::BuildMachineXML_WriteVboxVersionAttribute
1557 | settings::MachineConfigFile::BuildMachineXML_SkipRemovableMedia
1558 | settings::MachineConfigFile::BuildMachineXML_SuppressSavedState,
1559 // but not BuildMachineXML_IncludeSnapshots nor BuildMachineXML_MediaRegistry
1560 pllElementsWithUuidAttributes);
1561 delete pConfig;
1562 }
1563 catch (...)
1564 {
1565 writeLock.acquire();
1566 delete pConfig;
1567 throw;
1568 }
1569 writeLock.acquire();
1570}
1571
1572/**
1573 * Actual worker code for writing out OVF/OVA to disk. This is called from Appliance::taskThreadWriteOVF()
1574 * and therefore runs on the OVF/OVA write worker thread. This runs in two contexts:
1575 *
1576 * 1) in a first worker thread; in that case, Appliance::Write() called Appliance::writeImpl();
1577 *
1578 * 2) in a second worker thread; in that case, Appliance::Write() called Appliance::writeImpl(), which
1579 * called Appliance::writeS3(), which called Appliance::writeImpl(), which then called this. In other
1580 * words, to write to the cloud, the first worker thread first starts a second worker thread to create
1581 * temporary files and then uploads them to the S3 cloud server.
1582 *
1583 * @param pTask
1584 * @return
1585 */
1586HRESULT Appliance::writeFS(TaskOVF *pTask)
1587{
1588 LogFlowFuncEnter();
1589 LogFlowFunc(("ENTER appliance %p\n", this));
1590
1591 AutoCaller autoCaller(this);
1592 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1593
1594 HRESULT rc = S_OK;
1595
1596 // Lock the media tree early to make sure nobody else tries to make changes
1597 // to the tree. Also lock the IAppliance object for writing.
1598 AutoMultiWriteLock2 multiLock(&mVirtualBox->getMediaTreeLockHandle(), this->lockHandle() COMMA_LOCKVAL_SRC_POS);
1599 // Additional protect the IAppliance object, cause we leave the lock
1600 // when starting the disk export and we don't won't block other
1601 // callers on this lengthy operations.
1602 m->state = Data::ApplianceExporting;
1603
1604 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
1605 rc = writeFSOVF(pTask, multiLock);
1606 else
1607 rc = writeFSOVA(pTask, multiLock);
1608
1609 // reset the state so others can call methods again
1610 m->state = Data::ApplianceIdle;
1611
1612 LogFlowFunc(("rc=%Rhrc\n", rc));
1613 LogFlowFuncLeave();
1614 return rc;
1615}
1616
1617HRESULT Appliance::writeFSOVF(TaskOVF *pTask, AutoWriteLockBase& writeLock)
1618{
1619 LogFlowFuncEnter();
1620
1621 HRESULT rc = S_OK;
1622
1623 PVDINTERFACEIO pSha1Callbacks = 0;
1624 PVDINTERFACEIO pFileCallbacks = 0;
1625 do
1626 {
1627 pSha1Callbacks = Sha1CreateInterface();
1628 if (!pSha1Callbacks)
1629 {
1630 rc = E_OUTOFMEMORY;
1631 break;
1632 }
1633 pFileCallbacks = FileCreateInterface();
1634 if (!pFileCallbacks)
1635 {
1636 rc = E_OUTOFMEMORY;
1637 break;
1638 }
1639
1640 SHA1STORAGE storage;
1641 RT_ZERO(storage);
1642 storage.fCreateDigest = m->fManifest;
1643 VDINTERFACE VDInterfaceIO;
1644 int vrc = VDInterfaceAdd(&VDInterfaceIO, "Appliance::IOFile",
1645 VDINTERFACETYPE_IO, pFileCallbacks,
1646 0, &storage.pVDImageIfaces);
1647 if (RT_FAILURE(vrc))
1648 {
1649 rc = E_FAIL;
1650 break;
1651 }
1652 rc = writeFSImpl(pTask, writeLock, pSha1Callbacks, &storage);
1653 }while(0);
1654
1655 /* Cleanup */
1656 if (pSha1Callbacks)
1657 RTMemFree(pSha1Callbacks);
1658 if (pFileCallbacks)
1659 RTMemFree(pFileCallbacks);
1660
1661 LogFlowFuncLeave();
1662 return rc;
1663}
1664
1665HRESULT Appliance::writeFSOVA(TaskOVF *pTask, AutoWriteLockBase& writeLock)
1666{
1667 LogFlowFuncEnter();
1668
1669 RTTAR tar;
1670 int vrc = RTTarOpen(&tar, pTask->locInfo.strPath.c_str(), RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL, false);
1671 if (RT_FAILURE(vrc))
1672 return setError(VBOX_E_FILE_ERROR,
1673 tr("Could not create OVA file '%s' (%Rrc)"),
1674 pTask->locInfo.strPath.c_str(), vrc);
1675
1676 HRESULT rc = S_OK;
1677
1678 PVDINTERFACEIO pSha1Io = 0;
1679 PVDINTERFACEIO pTarIo = 0;
1680 do
1681 {
1682 pSha1Io = Sha1CreateInterface();
1683 if (!pSha1Io)
1684 {
1685 rc = E_OUTOFMEMORY;
1686 break;
1687 }
1688 pTarIo = TarCreateInterface();
1689 if (!pTarIo)
1690 {
1691 rc = E_OUTOFMEMORY;
1692 break;
1693 }
1694 SHA1STORAGE storage;
1695 RT_ZERO(storage);
1696 storage.fCreateDigest = m->fManifest;
1697 vrc = VDInterfaceAdd(&pTarIo->Core, "Appliance::IOTar",
1698 VDINTERFACETYPE_IO, tar, sizeof(VDINTERFACEIO),
1699 &storage.pVDImageIfaces);
1700 if (RT_FAILURE(vrc))
1701 {
1702 rc = E_FAIL;
1703 break;
1704 }
1705 rc = writeFSImpl(pTask, writeLock, pSha1Io, &storage);
1706 }while(0);
1707
1708 RTTarClose(tar);
1709
1710 /* Cleanup */
1711 if (pSha1Io)
1712 RTMemFree(pSha1Io);
1713 if (pTarIo)
1714 RTMemFree(pTarIo);
1715
1716 /* Delete ova file on error */
1717 if(FAILED(rc))
1718 RTFileDelete(pTask->locInfo.strPath.c_str());
1719
1720 LogFlowFuncLeave();
1721 return rc;
1722}
1723
1724HRESULT Appliance::writeFSImpl(TaskOVF *pTask, AutoWriteLockBase& writeLock, PVDINTERFACEIO pIfIo, PSHA1STORAGE pStorage)
1725{
1726 LogFlowFuncEnter();
1727
1728 HRESULT rc = S_OK;
1729
1730 list<STRPAIR> fileList;
1731 try
1732 {
1733 int vrc;
1734 // the XML stack contains two maps for disks and networks, which allows us to
1735 // a) have a list of unique disk names (to make sure the same disk name is only added once)
1736 // and b) keep a list of all networks
1737 XMLStack stack;
1738 // Scope this to free the memory as soon as this is finished
1739 {
1740 // Create a xml document
1741 xml::Document doc;
1742 // Now fully build a valid ovf document in memory
1743 buildXML(writeLock, doc, stack, pTask->locInfo.strPath, pTask->enFormat);
1744 /* Extract the path */
1745 Utf8Str strOvfFile = Utf8Str(pTask->locInfo.strPath).stripExt().append(".ovf");
1746 // Create a memory buffer containing the XML. */
1747 void *pvBuf = 0;
1748 size_t cbSize;
1749 xml::XmlMemWriter writer;
1750 writer.write(doc, &pvBuf, &cbSize);
1751 if (RT_UNLIKELY(!pvBuf))
1752 throw setError(VBOX_E_FILE_ERROR,
1753 tr("Could not create OVF file '%s'"),
1754 strOvfFile.c_str());
1755 /* Write the ovf file to disk. */
1756 vrc = Sha1WriteBuf(strOvfFile.c_str(), pvBuf, cbSize, pIfIo, pStorage);
1757 if (RT_FAILURE(vrc))
1758 throw setError(VBOX_E_FILE_ERROR,
1759 tr("Could not create OVF file '%s' (%Rrc)"),
1760 strOvfFile.c_str(), vrc);
1761 fileList.push_back(STRPAIR(strOvfFile, pStorage->strDigest));
1762 }
1763
1764 // We need a proper format description
1765 ComObjPtr<MediumFormat> format;
1766 // Scope for the AutoReadLock
1767 {
1768 SystemProperties *pSysProps = mVirtualBox->getSystemProperties();
1769 AutoReadLock propsLock(pSysProps COMMA_LOCKVAL_SRC_POS);
1770 // We are always exporting to VMDK stream optimized for now
1771 format = pSysProps->mediumFormat("VMDK");
1772 if (format.isNull())
1773 throw setError(VBOX_E_NOT_SUPPORTED,
1774 tr("Invalid medium storage format"));
1775 }
1776
1777 // Finally, write out the disks!
1778 map<Utf8Str, const VirtualSystemDescriptionEntry*>::const_iterator itS;
1779 for (itS = stack.mapDisks.begin();
1780 itS != stack.mapDisks.end();
1781 ++itS)
1782 {
1783 const VirtualSystemDescriptionEntry *pDiskEntry = itS->second;
1784
1785 // source path: where the VBox image is
1786 const Utf8Str &strSrcFilePath = pDiskEntry->strVboxCurrent;
1787
1788 // Do NOT check here whether the file exists. findHardDisk will
1789 // figure that out, and filesystem-based tests are simply wrong
1790 // in the general case (think of iSCSI).
1791
1792 // clone the disk:
1793 ComObjPtr<Medium> pSourceDisk;
1794
1795 Log(("Finding source disk \"%s\"\n", strSrcFilePath.c_str()));
1796 rc = mVirtualBox->findHardDiskByLocation(strSrcFilePath, true, &pSourceDisk);
1797 if (FAILED(rc)) throw rc;
1798
1799 Bstr uuidSource;
1800 rc = pSourceDisk->COMGETTER(Id)(uuidSource.asOutParam());
1801 if (FAILED(rc)) throw rc;
1802 Guid guidSource(uuidSource);
1803
1804 // output filename
1805 const Utf8Str &strTargetFileNameOnly = pDiskEntry->strOvf;
1806 // target path needs to be composed from where the output OVF is
1807 Utf8Str strTargetFilePath(pTask->locInfo.strPath);
1808 strTargetFilePath.stripFilename()
1809 .append("/")
1810 .append(strTargetFileNameOnly);
1811
1812 // The exporting requests a lock on the media tree. So leave our lock temporary.
1813 writeLock.release();
1814 try
1815 {
1816 ComObjPtr<Progress> pProgress2;
1817 pProgress2.createObject();
1818 rc = pProgress2->init(mVirtualBox, static_cast<IAppliance*>(this), BstrFmt(tr("Creating medium '%s'"), strTargetFilePath.c_str()).raw(), TRUE);
1819 if (FAILED(rc)) throw rc;
1820
1821 // advance to the next operation
1822 pTask->pProgress->SetNextOperation(BstrFmt(tr("Exporting to disk image '%s'"), RTPathFilename(strTargetFilePath.c_str())).raw(),
1823 pDiskEntry->ulSizeMB); // operation's weight, as set up with the IProgress originally
1824
1825 // create a flat copy of the source disk image
1826 rc = pSourceDisk->exportFile(strTargetFilePath.c_str(), format, MediumVariant_VmdkStreamOptimized, pIfIo, pStorage, pProgress2);
1827 if (FAILED(rc)) throw rc;
1828
1829 ComPtr<IProgress> pProgress3(pProgress2);
1830 // now wait for the background disk operation to complete; this throws HRESULTs on error
1831 waitForAsyncProgress(pTask->pProgress, pProgress3);
1832 }
1833 catch (HRESULT rc3)
1834 {
1835 writeLock.acquire();
1836 // Todo: file deletion on error? If not, we can remove that whole try/catch block.
1837 throw rc3;
1838 }
1839 // Finished, lock again (so nobody mess around with the medium tree
1840 // in the meantime)
1841 writeLock.acquire();
1842 fileList.push_back(STRPAIR(strTargetFilePath, pStorage->strDigest));
1843 }
1844
1845 if (m->fManifest)
1846 {
1847 // Create & write the manifest file
1848 Utf8Str strMfFilePath = Utf8Str(pTask->locInfo.strPath).stripExt().append(".mf");
1849 Utf8Str strMfFileName = Utf8Str(strMfFilePath).stripPath();
1850 pTask->pProgress->SetNextOperation(BstrFmt(tr("Creating manifest file '%s'"), strMfFileName.c_str()).raw(),
1851 m->ulWeightForManifestOperation); // operation's weight, as set up with the IProgress originally);
1852 PRTMANIFESTTEST paManifestFiles = (PRTMANIFESTTEST)RTMemAlloc(sizeof(RTMANIFESTTEST) * fileList.size());
1853 size_t i = 0;
1854 list<STRPAIR>::const_iterator it1;
1855 for (it1 = fileList.begin();
1856 it1 != fileList.end();
1857 ++it1, ++i)
1858 {
1859 paManifestFiles[i].pszTestFile = (*it1).first.c_str();
1860 paManifestFiles[i].pszTestDigest = (*it1).second.c_str();
1861 }
1862 void *pvBuf;
1863 size_t cbSize;
1864 vrc = RTManifestWriteFilesBuf(&pvBuf, &cbSize, paManifestFiles, fileList.size());
1865 RTMemFree(paManifestFiles);
1866 if (RT_FAILURE(vrc))
1867 throw setError(VBOX_E_FILE_ERROR,
1868 tr("Could not create manifest file '%s' (%Rrc)"),
1869 strMfFileName.c_str(), vrc);
1870 /* Disable digest creation for the manifest file. */
1871 pStorage->fCreateDigest = false;
1872 /* Write the manifest file to disk. */
1873 vrc = Sha1WriteBuf(strMfFilePath.c_str(), pvBuf, cbSize, pIfIo, pStorage);
1874 RTMemFree(pvBuf);
1875 if (RT_FAILURE(vrc))
1876 throw setError(VBOX_E_FILE_ERROR,
1877 tr("Could not create manifest file '%s' (%Rrc)"),
1878 strMfFilePath.c_str(), vrc);
1879 }
1880 }
1881 catch (RTCError &x) // includes all XML exceptions
1882 {
1883 rc = setError(VBOX_E_FILE_ERROR,
1884 x.what());
1885 }
1886 catch (HRESULT aRC)
1887 {
1888 rc = aRC;
1889 }
1890
1891 /* Cleanup on error */
1892 if (FAILED(rc))
1893 {
1894 list<STRPAIR>::const_iterator it1;
1895 for (it1 = fileList.begin();
1896 it1 != fileList.end();
1897 ++it1)
1898 pIfIo->pfnDelete(pStorage, (*it1).first.c_str());
1899 }
1900
1901 LogFlowFunc(("rc=%Rhrc\n", rc));
1902 LogFlowFuncLeave();
1903
1904 return rc;
1905}
1906
1907#ifdef VBOX_WITH_S3
1908/**
1909 * Worker code for writing out OVF to the cloud. This is called from Appliance::taskThreadWriteOVF()
1910 * in S3 mode and therefore runs on the OVF write worker thread. This then starts a second worker
1911 * thread to create temporary files (see Appliance::writeFS()).
1912 *
1913 * @param pTask
1914 * @return
1915 */
1916HRESULT Appliance::writeS3(TaskOVF *pTask)
1917{
1918 LogFlowFuncEnter();
1919 LogFlowFunc(("Appliance %p\n", this));
1920
1921 AutoCaller autoCaller(this);
1922 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1923
1924 HRESULT rc = S_OK;
1925
1926 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
1927
1928 int vrc = VINF_SUCCESS;
1929 RTS3 hS3 = NIL_RTS3;
1930 char szOSTmpDir[RTPATH_MAX];
1931 RTPathTemp(szOSTmpDir, sizeof(szOSTmpDir));
1932 /* The template for the temporary directory created below */
1933 char *pszTmpDir = RTPathJoinA(szOSTmpDir, "vbox-ovf-XXXXXX");
1934 list< pair<Utf8Str, ULONG> > filesList;
1935
1936 // todo:
1937 // - usable error codes
1938 // - seems snapshot filenames are problematic {uuid}.vdi
1939 try
1940 {
1941 /* Extract the bucket */
1942 Utf8Str tmpPath = pTask->locInfo.strPath;
1943 Utf8Str bucket;
1944 parseBucket(tmpPath, bucket);
1945
1946 /* We need a temporary directory which we can put the OVF file & all
1947 * disk images in */
1948 vrc = RTDirCreateTemp(pszTmpDir);
1949 if (RT_FAILURE(vrc))
1950 throw setError(VBOX_E_FILE_ERROR,
1951 tr("Cannot create temporary directory '%s' (%Rrc)"), pszTmpDir, vrc);
1952
1953 /* The temporary name of the target OVF file */
1954 Utf8StrFmt strTmpOvf("%s/%s", pszTmpDir, RTPathFilename(tmpPath.c_str()));
1955
1956 /* Prepare the temporary writing of the OVF */
1957 ComObjPtr<Progress> progress;
1958 /* Create a temporary file based location info for the sub task */
1959 LocationInfo li;
1960 li.strPath = strTmpOvf;
1961 rc = writeImpl(pTask->enFormat, li, progress);
1962 if (FAILED(rc)) throw rc;
1963
1964 /* Unlock the appliance for the writing thread */
1965 appLock.release();
1966 /* Wait until the writing is done, but report the progress back to the
1967 caller */
1968 ComPtr<IProgress> progressInt(progress);
1969 waitForAsyncProgress(pTask->pProgress, progressInt); /* Any errors will be thrown */
1970
1971 /* Again lock the appliance for the next steps */
1972 appLock.acquire();
1973
1974 vrc = RTPathExists(strTmpOvf.c_str()); /* Paranoid check */
1975 if (RT_FAILURE(vrc))
1976 throw setError(VBOX_E_FILE_ERROR,
1977 tr("Cannot find source file '%s' (%Rrc)"), strTmpOvf.c_str(), vrc);
1978 /* Add the OVF file */
1979 filesList.push_back(pair<Utf8Str, ULONG>(strTmpOvf, m->ulWeightForXmlOperation)); /* Use 1% of the total for the OVF file upload */
1980 /* Add the manifest file */
1981 if (m->fManifest)
1982 {
1983 Utf8Str strMfFile = Utf8Str(strTmpOvf).stripExt().append(".mf");
1984 filesList.push_back(pair<Utf8Str, ULONG>(strMfFile , m->ulWeightForXmlOperation)); /* Use 1% of the total for the manifest file upload */
1985 }
1986
1987 /* Now add every disks of every virtual system */
1988 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it;
1989 for (it = m->virtualSystemDescriptions.begin();
1990 it != m->virtualSystemDescriptions.end();
1991 ++it)
1992 {
1993 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it);
1994 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskImage);
1995 std::list<VirtualSystemDescriptionEntry*>::const_iterator itH;
1996 for (itH = avsdeHDs.begin();
1997 itH != avsdeHDs.end();
1998 ++itH)
1999 {
2000 const Utf8Str &strTargetFileNameOnly = (*itH)->strOvf;
2001 /* Target path needs to be composed from where the output OVF is */
2002 Utf8Str strTargetFilePath(strTmpOvf);
2003 strTargetFilePath.stripFilename();
2004 strTargetFilePath.append("/");
2005 strTargetFilePath.append(strTargetFileNameOnly);
2006 vrc = RTPathExists(strTargetFilePath.c_str()); /* Paranoid check */
2007 if (RT_FAILURE(vrc))
2008 throw setError(VBOX_E_FILE_ERROR,
2009 tr("Cannot find source file '%s' (%Rrc)"), strTargetFilePath.c_str(), vrc);
2010 filesList.push_back(pair<Utf8Str, ULONG>(strTargetFilePath, (*itH)->ulSizeMB));
2011 }
2012 }
2013 /* Next we have to upload the OVF & all disk images */
2014 vrc = RTS3Create(&hS3, pTask->locInfo.strUsername.c_str(), pTask->locInfo.strPassword.c_str(), pTask->locInfo.strHostname.c_str(), "virtualbox-agent/"VBOX_VERSION_STRING);
2015 if (RT_FAILURE(vrc))
2016 throw setError(VBOX_E_IPRT_ERROR,
2017 tr("Cannot create S3 service handler"));
2018 RTS3SetProgressCallback(hS3, pTask->updateProgress, &pTask);
2019
2020 /* Upload all files */
2021 for (list< pair<Utf8Str, ULONG> >::const_iterator it1 = filesList.begin(); it1 != filesList.end(); ++it1)
2022 {
2023 const pair<Utf8Str, ULONG> &s = (*it1);
2024 char *pszFilename = RTPathFilename(s.first.c_str());
2025 /* Advance to the next operation */
2026 pTask->pProgress->SetNextOperation(BstrFmt(tr("Uploading file '%s'"), pszFilename).raw(), s.second);
2027 vrc = RTS3PutKey(hS3, bucket.c_str(), pszFilename, s.first.c_str());
2028 if (RT_FAILURE(vrc))
2029 {
2030 if (vrc == VERR_S3_CANCELED)
2031 break;
2032 else if (vrc == VERR_S3_ACCESS_DENIED)
2033 throw setError(E_ACCESSDENIED,
2034 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);
2035 else if (vrc == VERR_S3_NOT_FOUND)
2036 throw setError(VBOX_E_FILE_ERROR,
2037 tr("Cannot upload file '%s' to S3 storage server (File not found)"), pszFilename);
2038 else
2039 throw setError(VBOX_E_IPRT_ERROR,
2040 tr("Cannot upload file '%s' to S3 storage server (%Rrc)"), pszFilename, vrc);
2041 }
2042 }
2043 }
2044 catch(HRESULT aRC)
2045 {
2046 rc = aRC;
2047 }
2048 /* Cleanup */
2049 RTS3Destroy(hS3);
2050 /* Delete all files which where temporary created */
2051 for (list< pair<Utf8Str, ULONG> >::const_iterator it1 = filesList.begin(); it1 != filesList.end(); ++it1)
2052 {
2053 const char *pszFilePath = (*it1).first.c_str();
2054 if (RTPathExists(pszFilePath))
2055 {
2056 vrc = RTFileDelete(pszFilePath);
2057 if (RT_FAILURE(vrc))
2058 rc = setError(VBOX_E_FILE_ERROR,
2059 tr("Cannot delete file '%s' (%Rrc)"), pszFilePath, vrc);
2060 }
2061 }
2062 /* Delete the temporary directory */
2063 if (RTPathExists(pszTmpDir))
2064 {
2065 vrc = RTDirRemove(pszTmpDir);
2066 if (RT_FAILURE(vrc))
2067 rc = setError(VBOX_E_FILE_ERROR,
2068 tr("Cannot delete temporary directory '%s' (%Rrc)"), pszTmpDir, vrc);
2069 }
2070 if (pszTmpDir)
2071 RTStrFree(pszTmpDir);
2072
2073 LogFlowFunc(("rc=%Rhrc\n", rc));
2074 LogFlowFuncLeave();
2075
2076 return rc;
2077}
2078#endif /* VBOX_WITH_S3 */
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette