VirtualBox

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

Last change on this file since 39252 was 39248, checked in by vboxsync, 13 years ago

Runtime: new guest OS type for Solaris 11
Frontends/VirtualBox: add new patterns for Solaris 11 guest OS type, reuse the icon
Frontends/VBoxManage: more details for "list ostypes"
Main/xml: make guest OS type in config file an arbitrary string (still validated/mapped in the old way in the settings code), remove hardcoded limit of 8 network adapters
Main/Global: move list of valid guest OS types into a single place, add function to get the network adapter limit for each chipset type
Main/Console+Machine+Snapshot+NetworkAdapter+Appliance+VirtualBox+Guest+SystemProperties: consistently use the appropriate network adapter limit so that ICH9 chipset can use 36 network adapters, adapt to cleaned up guest OS type handling, remove leftover rendundant guest OS mapping, whitespace
Network/NAT: release log message cosmetics, allow unlimited number of instances, fix maxconn clamping
Network/PCNet+VirtioNet+E1000: allow unlimited number of instances

  • 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 39248 2011-11-09 12:29:53Z vboxsync $ */
2/** @file
3 *
4 * IAppliance and IVirtualSystem COM class implementations.
5 */
6
7/*
8 * Copyright (C) 2008-2011 Oracle Corporation
9 *
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.
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 "Global.h"
37#include "SystemPropertiesImpl.h"
38
39#include "AutoCaller.h"
40#include "Logging.h"
41
42#include "ApplianceImplPrivate.h"
43
44using namespace std;
45
46////////////////////////////////////////////////////////////////////////////////
47//
48// IMachine public methods
49//
50////////////////////////////////////////////////////////////////////////////////
51
52// This code is here so we won't have to include the appliance headers in the
53// IMachine implementation, and we also need to access private appliance data.
54
55/**
56* Public method implementation.
57* @param appliance
58* @return
59*/
60STDMETHODIMP Machine::Export(IAppliance *aAppliance, IN_BSTR location, IVirtualSystemDescription **aDescription)
61{
62 HRESULT rc = S_OK;
63
64 if (!aAppliance)
65 return E_POINTER;
66
67 AutoCaller autoCaller(this);
68 if (FAILED(autoCaller.rc())) return autoCaller.rc();
69
70 ComObjPtr<VirtualSystemDescription> pNewDesc;
71
72 try
73 {
74 Appliance *pAppliance = static_cast<Appliance*>(aAppliance);
75 AutoCaller autoCaller1(pAppliance);
76 if (FAILED(autoCaller1.rc())) return autoCaller1.rc();
77
78 LocationInfo locInfo;
79 parseURI(location, locInfo);
80 // create a new virtual system to store in the appliance
81 rc = pNewDesc.createObject();
82 if (FAILED(rc)) throw rc;
83 rc = pNewDesc->init();
84 if (FAILED(rc)) throw rc;
85
86 // store the machine object so we can dump the XML in Appliance::Write()
87 pNewDesc->m->pMachine = this;
88
89 // now fill it with description items
90 Bstr bstrName1;
91 Bstr bstrDescription;
92 Bstr bstrGuestOSType;
93 uint32_t cCPUs;
94 uint32_t ulMemSizeMB;
95 BOOL fUSBEnabled;
96 BOOL fAudioEnabled;
97 AudioControllerType_T audioController;
98
99 ComPtr<IUSBController> pUsbController;
100 ComPtr<IAudioAdapter> pAudioAdapter;
101
102 // first, call the COM methods, as they request locks
103 rc = COMGETTER(USBController)(pUsbController.asOutParam());
104 if (FAILED(rc))
105 fUSBEnabled = false;
106 else
107 rc = pUsbController->COMGETTER(Enabled)(&fUSBEnabled);
108
109 // request the machine lock while accessing internal members
110 AutoReadLock alock1(this COMMA_LOCKVAL_SRC_POS);
111
112 pAudioAdapter = mAudioAdapter;
113 rc = pAudioAdapter->COMGETTER(Enabled)(&fAudioEnabled);
114 if (FAILED(rc)) throw rc;
115 rc = pAudioAdapter->COMGETTER(AudioController)(&audioController);
116 if (FAILED(rc)) throw rc;
117
118 // get name
119 Utf8Str strVMName = mUserData->s.strName;
120 // get description
121 Utf8Str strDescription = mUserData->s.strDescription;
122 // get guest OS
123 Utf8Str strOsTypeVBox = mUserData->s.strOsType;
124 // CPU count
125 cCPUs = mHWData->mCPUCount;
126 // memory size in MB
127 ulMemSizeMB = mHWData->mMemorySize;
128 // VRAM size?
129 // BIOS settings?
130 // 3D acceleration enabled?
131 // hardware virtualization enabled?
132 // nested paging enabled?
133 // HWVirtExVPIDEnabled?
134 // PAEEnabled?
135 // snapshotFolder?
136 // VRDPServer?
137
138 /* Guest OS type */
139 ovf::CIMOSType_T cim = convertVBoxOSType2CIMOSType(strOsTypeVBox.c_str());
140 pNewDesc->addEntry(VirtualSystemDescriptionType_OS,
141 "",
142 Utf8StrFmt("%RI32", cim),
143 strOsTypeVBox);
144
145 /* VM name */
146 pNewDesc->addEntry(VirtualSystemDescriptionType_Name,
147 "",
148 strVMName,
149 strVMName);
150
151 // description
152 pNewDesc->addEntry(VirtualSystemDescriptionType_Description,
153 "",
154 strDescription,
155 strDescription);
156
157 /* CPU count*/
158 Utf8Str strCpuCount = Utf8StrFmt("%RI32", cCPUs);
159 pNewDesc->addEntry(VirtualSystemDescriptionType_CPU,
160 "",
161 strCpuCount,
162 strCpuCount);
163
164 /* Memory */
165 Utf8Str strMemory = Utf8StrFmt("%RI64", (uint64_t)ulMemSizeMB * _1M);
166 pNewDesc->addEntry(VirtualSystemDescriptionType_Memory,
167 "",
168 strMemory,
169 strMemory);
170
171 // the one VirtualBox IDE controller has two channels with two ports each, which is
172 // considered two IDE controllers with two ports each by OVF, so export it as two
173 int32_t lIDEControllerPrimaryIndex = 0;
174 int32_t lIDEControllerSecondaryIndex = 0;
175 int32_t lSATAControllerIndex = 0;
176 int32_t lSCSIControllerIndex = 0;
177
178 /* Fetch all available storage controllers */
179 com::SafeIfaceArray<IStorageController> nwControllers;
180 rc = COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(nwControllers));
181 if (FAILED(rc)) throw rc;
182
183 ComPtr<IStorageController> pIDEController;
184 ComPtr<IStorageController> pSATAController;
185 ComPtr<IStorageController> pSCSIController;
186 ComPtr<IStorageController> pSASController;
187 for (size_t j = 0; j < nwControllers.size(); ++j)
188 {
189 StorageBus_T eType;
190 rc = nwControllers[j]->COMGETTER(Bus)(&eType);
191 if (FAILED(rc)) throw rc;
192 if ( eType == StorageBus_IDE
193 && pIDEController.isNull())
194 pIDEController = nwControllers[j];
195 else if ( eType == StorageBus_SATA
196 && pSATAController.isNull())
197 pSATAController = nwControllers[j];
198 else if ( eType == StorageBus_SCSI
199 && pSATAController.isNull())
200 pSCSIController = nwControllers[j];
201 else if ( eType == StorageBus_SAS
202 && pSASController.isNull())
203 pSASController = nwControllers[j];
204 }
205
206// <const name="HardDiskControllerIDE" value="6" />
207 if (!pIDEController.isNull())
208 {
209 Utf8Str strVbox;
210 StorageControllerType_T ctlr;
211 rc = pIDEController->COMGETTER(ControllerType)(&ctlr);
212 if (FAILED(rc)) throw rc;
213 switch(ctlr)
214 {
215 case StorageControllerType_PIIX3: strVbox = "PIIX3"; break;
216 case StorageControllerType_PIIX4: strVbox = "PIIX4"; break;
217 case StorageControllerType_ICH6: strVbox = "ICH6"; break;
218 }
219
220 if (strVbox.length())
221 {
222 lIDEControllerPrimaryIndex = (int32_t)pNewDesc->m->llDescriptions.size();
223 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
224 Utf8StrFmt("%d", lIDEControllerPrimaryIndex), // strRef
225 strVbox, // aOvfValue
226 strVbox); // aVboxValue
227 lIDEControllerSecondaryIndex = lIDEControllerPrimaryIndex + 1;
228 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
229 Utf8StrFmt("%d", lIDEControllerSecondaryIndex),
230 strVbox,
231 strVbox);
232 }
233 }
234
235// <const name="HardDiskControllerSATA" value="7" />
236 if (!pSATAController.isNull())
237 {
238 Utf8Str strVbox = "AHCI";
239 lSATAControllerIndex = (int32_t)pNewDesc->m->llDescriptions.size();
240 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA,
241 Utf8StrFmt("%d", lSATAControllerIndex),
242 strVbox,
243 strVbox);
244 }
245
246// <const name="HardDiskControllerSCSI" value="8" />
247 if (!pSCSIController.isNull())
248 {
249 StorageControllerType_T ctlr;
250 rc = pSCSIController->COMGETTER(ControllerType)(&ctlr);
251 if (SUCCEEDED(rc))
252 {
253 Utf8Str strVbox = "LsiLogic"; // the default in VBox
254 switch(ctlr)
255 {
256 case StorageControllerType_LsiLogic: strVbox = "LsiLogic"; break;
257 case StorageControllerType_BusLogic: strVbox = "BusLogic"; break;
258 }
259 lSCSIControllerIndex = (int32_t)pNewDesc->m->llDescriptions.size();
260 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSCSI,
261 Utf8StrFmt("%d", lSCSIControllerIndex),
262 strVbox,
263 strVbox);
264 }
265 else
266 throw rc;
267 }
268
269 if (!pSASController.isNull())
270 {
271 // VirtualBox considers the SAS controller a class of its own but in OVF
272 // it should be a SCSI controller
273 Utf8Str strVbox = "LsiLogicSas";
274 lSCSIControllerIndex = (int32_t)pNewDesc->m->llDescriptions.size();
275 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSAS,
276 Utf8StrFmt("%d", lSCSIControllerIndex),
277 strVbox,
278 strVbox);
279 }
280
281// <const name="HardDiskImage" value="9" />
282// <const name="Floppy" value="18" />
283// <const name="CDROM" value="19" />
284
285 MediaData::AttachmentList::iterator itA;
286 for (itA = mMediaData->mAttachments.begin();
287 itA != mMediaData->mAttachments.end();
288 ++itA)
289 {
290 ComObjPtr<MediumAttachment> pHDA = *itA;
291
292 // the attachment's data
293 ComPtr<IMedium> pMedium;
294 ComPtr<IStorageController> ctl;
295 Bstr controllerName;
296
297 rc = pHDA->COMGETTER(Controller)(controllerName.asOutParam());
298 if (FAILED(rc)) throw rc;
299
300 rc = GetStorageControllerByName(controllerName.raw(), ctl.asOutParam());
301 if (FAILED(rc)) throw rc;
302
303 StorageBus_T storageBus;
304 DeviceType_T deviceType;
305 LONG lChannel;
306 LONG lDevice;
307
308 rc = ctl->COMGETTER(Bus)(&storageBus);
309 if (FAILED(rc)) throw rc;
310
311 rc = pHDA->COMGETTER(Type)(&deviceType);
312 if (FAILED(rc)) throw rc;
313
314 rc = pHDA->COMGETTER(Medium)(pMedium.asOutParam());
315 if (FAILED(rc)) throw rc;
316
317 rc = pHDA->COMGETTER(Port)(&lChannel);
318 if (FAILED(rc)) throw rc;
319
320 rc = pHDA->COMGETTER(Device)(&lDevice);
321 if (FAILED(rc)) throw rc;
322
323 Utf8Str strTargetVmdkName;
324 Utf8Str strLocation;
325 LONG64 llSize = 0;
326
327 if ( deviceType == DeviceType_HardDisk
328 && pMedium
329 )
330 {
331 Bstr bstrLocation;
332 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
333 if (FAILED(rc)) throw rc;
334 strLocation = bstrLocation;
335
336 // find the source's base medium for two things:
337 // 1) we'll use its name to determine the name of the target disk, which is readable,
338 // as opposed to the UUID filename of a differencing image, if pMedium is one
339 // 2) we need the size of the base image so we can give it to addEntry(), and later
340 // on export, the progress will be based on that (and not the diff image)
341 ComPtr<IMedium> pBaseMedium;
342 rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
343 // returns pMedium if there are no diff images
344 if (FAILED(rc)) throw rc;
345
346 Bstr bstrBaseName;
347 rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
348 if (FAILED(rc)) throw rc;
349
350 Utf8Str strTargetName = Utf8Str(locInfo.strPath).stripPath().stripExt();
351 strTargetVmdkName = Utf8StrFmt("%s-disk%d.vmdk", strTargetName.c_str(), ++pAppliance->m->cDisks);
352
353 // force reading state, or else size will be returned as 0
354 MediumState_T ms;
355 rc = pBaseMedium->RefreshState(&ms);
356 if (FAILED(rc)) throw rc;
357
358 rc = pBaseMedium->COMGETTER(Size)(&llSize);
359 if (FAILED(rc)) throw rc;
360 }
361
362 // and how this translates to the virtual system
363 int32_t lControllerVsys = 0;
364 LONG lChannelVsys;
365
366 switch (storageBus)
367 {
368 case StorageBus_IDE:
369 // this is the exact reverse to what we're doing in Appliance::taskThreadImportMachines,
370 // and it must be updated when that is changed!
371 // Before 3.2 we exported one IDE controller with channel 0-3, but we now maintain
372 // compatibility with what VMware does and export two IDE controllers with two channels each
373
374 if (lChannel == 0 && lDevice == 0) // primary master
375 {
376 lControllerVsys = lIDEControllerPrimaryIndex;
377 lChannelVsys = 0;
378 }
379 else if (lChannel == 0 && lDevice == 1) // primary slave
380 {
381 lControllerVsys = lIDEControllerPrimaryIndex;
382 lChannelVsys = 1;
383 }
384 else if (lChannel == 1 && lDevice == 0) // secondary master; by default this is the CD-ROM but as of VirtualBox 3.1 that can change
385 {
386 lControllerVsys = lIDEControllerSecondaryIndex;
387 lChannelVsys = 0;
388 }
389 else if (lChannel == 1 && lDevice == 1) // secondary slave
390 {
391 lControllerVsys = lIDEControllerSecondaryIndex;
392 lChannelVsys = 1;
393 }
394 else
395 throw setError(VBOX_E_NOT_SUPPORTED,
396 tr("Cannot handle medium attachment: channel is %d, device is %d"), lChannel, lDevice);
397 break;
398
399 case StorageBus_SATA:
400 lChannelVsys = lChannel; // should be between 0 and 29
401 lControllerVsys = lSATAControllerIndex;
402 break;
403
404 case StorageBus_SCSI:
405 case StorageBus_SAS:
406 lChannelVsys = lChannel; // should be between 0 and 15
407 lControllerVsys = lSCSIControllerIndex;
408 break;
409
410 case StorageBus_Floppy:
411 lChannelVsys = 0;
412 lControllerVsys = 0;
413 break;
414
415 default:
416 throw setError(VBOX_E_NOT_SUPPORTED,
417 tr("Cannot handle medium attachment: storageBus is %d, channel is %d, device is %d"), storageBus, lChannel, lDevice);
418 break;
419 }
420
421 Utf8StrFmt strExtra("controller=%RI32;channel=%RI32", lControllerVsys, lChannelVsys);
422 Utf8Str strEmpty;
423
424 switch (deviceType)
425 {
426 case DeviceType_HardDisk:
427 Log(("Adding VirtualSystemDescriptionType_HardDiskImage, disk size: %RI64\n", llSize));
428 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskImage,
429 strTargetVmdkName, // disk ID: let's use the name
430 strTargetVmdkName, // OVF value:
431 strLocation, // vbox value: media path
432 (uint32_t)(llSize / _1M),
433 strExtra);
434 break;
435
436 case DeviceType_DVD:
437 pNewDesc->addEntry(VirtualSystemDescriptionType_CDROM,
438 strEmpty, // disk ID
439 strEmpty, // OVF value
440 strEmpty, // vbox value
441 1, // ulSize
442 strExtra);
443 break;
444
445 case DeviceType_Floppy:
446 pNewDesc->addEntry(VirtualSystemDescriptionType_Floppy,
447 strEmpty, // disk ID
448 strEmpty, // OVF value
449 strEmpty, // vbox value
450 1, // ulSize
451 strExtra);
452 break;
453 }
454 }
455
456// <const name="NetworkAdapter" />
457 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(getChipsetType());
458 size_t a;
459 for (a = 0; a < maxNetworkAdapters; ++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.virtualbox.org/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 pSha1Io = 0;
1624 PVDINTERFACEIO pFileIo = 0;
1625 do
1626 {
1627 pSha1Io = Sha1CreateInterface();
1628 if (!pSha1Io)
1629 {
1630 rc = E_OUTOFMEMORY;
1631 break;
1632 }
1633 pFileIo = FileCreateInterface();
1634 if (!pFileIo)
1635 {
1636 rc = E_OUTOFMEMORY;
1637 break;
1638 }
1639
1640 SHA1STORAGE storage;
1641 RT_ZERO(storage);
1642 storage.fCreateDigest = m->fManifest;
1643 int vrc = VDInterfaceAdd(&pFileIo->Core, "Appliance::IOFile",
1644 VDINTERFACETYPE_IO, 0, sizeof(VDINTERFACEIO),
1645 &storage.pVDImageIfaces);
1646 if (RT_FAILURE(vrc))
1647 {
1648 rc = E_FAIL;
1649 break;
1650 }
1651 rc = writeFSImpl(pTask, writeLock, pSha1Io, &storage);
1652 }while(0);
1653
1654 /* Cleanup */
1655 if (pSha1Io)
1656 RTMemFree(pSha1Io);
1657 if (pFileIo)
1658 RTMemFree(pFileIo);
1659
1660 LogFlowFuncLeave();
1661 return rc;
1662}
1663
1664HRESULT Appliance::writeFSOVA(TaskOVF *pTask, AutoWriteLockBase& writeLock)
1665{
1666 LogFlowFuncEnter();
1667
1668 RTTAR tar;
1669 int vrc = RTTarOpen(&tar, pTask->locInfo.strPath.c_str(), RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL, false);
1670 if (RT_FAILURE(vrc))
1671 return setError(VBOX_E_FILE_ERROR,
1672 tr("Could not create OVA file '%s' (%Rrc)"),
1673 pTask->locInfo.strPath.c_str(), vrc);
1674
1675 HRESULT rc = S_OK;
1676
1677 PVDINTERFACEIO pSha1Io = 0;
1678 PVDINTERFACEIO pTarIo = 0;
1679 do
1680 {
1681 pSha1Io = Sha1CreateInterface();
1682 if (!pSha1Io)
1683 {
1684 rc = E_OUTOFMEMORY;
1685 break;
1686 }
1687 pTarIo = TarCreateInterface();
1688 if (!pTarIo)
1689 {
1690 rc = E_OUTOFMEMORY;
1691 break;
1692 }
1693 SHA1STORAGE storage;
1694 RT_ZERO(storage);
1695 storage.fCreateDigest = m->fManifest;
1696 vrc = VDInterfaceAdd(&pTarIo->Core, "Appliance::IOTar",
1697 VDINTERFACETYPE_IO, tar, sizeof(VDINTERFACEIO),
1698 &storage.pVDImageIfaces);
1699 if (RT_FAILURE(vrc))
1700 {
1701 rc = E_FAIL;
1702 break;
1703 }
1704 rc = writeFSImpl(pTask, writeLock, pSha1Io, &storage);
1705 }while(0);
1706
1707 RTTarClose(tar);
1708
1709 /* Cleanup */
1710 if (pSha1Io)
1711 RTMemFree(pSha1Io);
1712 if (pTarIo)
1713 RTMemFree(pTarIo);
1714
1715 /* Delete ova file on error */
1716 if(FAILED(rc))
1717 RTFileDelete(pTask->locInfo.strPath.c_str());
1718
1719 LogFlowFuncLeave();
1720 return rc;
1721}
1722
1723HRESULT Appliance::writeFSImpl(TaskOVF *pTask, AutoWriteLockBase& writeLock, PVDINTERFACEIO pIfIo, PSHA1STORAGE pStorage)
1724{
1725 LogFlowFuncEnter();
1726
1727 HRESULT rc = S_OK;
1728
1729 list<STRPAIR> fileList;
1730 try
1731 {
1732 int vrc;
1733 // the XML stack contains two maps for disks and networks, which allows us to
1734 // a) have a list of unique disk names (to make sure the same disk name is only added once)
1735 // and b) keep a list of all networks
1736 XMLStack stack;
1737 // Scope this to free the memory as soon as this is finished
1738 {
1739 // Create a xml document
1740 xml::Document doc;
1741 // Now fully build a valid ovf document in memory
1742 buildXML(writeLock, doc, stack, pTask->locInfo.strPath, pTask->enFormat);
1743 /* Extract the path */
1744 Utf8Str strOvfFile = Utf8Str(pTask->locInfo.strPath).stripExt().append(".ovf");
1745 // Create a memory buffer containing the XML. */
1746 void *pvBuf = 0;
1747 size_t cbSize;
1748 xml::XmlMemWriter writer;
1749 writer.write(doc, &pvBuf, &cbSize);
1750 if (RT_UNLIKELY(!pvBuf))
1751 throw setError(VBOX_E_FILE_ERROR,
1752 tr("Could not create OVF file '%s'"),
1753 strOvfFile.c_str());
1754 /* Write the ovf file to disk. */
1755 vrc = Sha1WriteBuf(strOvfFile.c_str(), pvBuf, cbSize, pIfIo, pStorage);
1756 if (RT_FAILURE(vrc))
1757 throw setError(VBOX_E_FILE_ERROR,
1758 tr("Could not create OVF file '%s' (%Rrc)"),
1759 strOvfFile.c_str(), vrc);
1760 fileList.push_back(STRPAIR(strOvfFile, pStorage->strDigest));
1761 }
1762
1763 // We need a proper format description
1764 ComObjPtr<MediumFormat> format;
1765 // Scope for the AutoReadLock
1766 {
1767 SystemProperties *pSysProps = mVirtualBox->getSystemProperties();
1768 AutoReadLock propsLock(pSysProps COMMA_LOCKVAL_SRC_POS);
1769 // We are always exporting to VMDK stream optimized for now
1770 format = pSysProps->mediumFormat("VMDK");
1771 if (format.isNull())
1772 throw setError(VBOX_E_NOT_SUPPORTED,
1773 tr("Invalid medium storage format"));
1774 }
1775
1776 // Finally, write out the disks!
1777 map<Utf8Str, const VirtualSystemDescriptionEntry*>::const_iterator itS;
1778 for (itS = stack.mapDisks.begin();
1779 itS != stack.mapDisks.end();
1780 ++itS)
1781 {
1782 const VirtualSystemDescriptionEntry *pDiskEntry = itS->second;
1783
1784 // source path: where the VBox image is
1785 const Utf8Str &strSrcFilePath = pDiskEntry->strVboxCurrent;
1786
1787 // Do NOT check here whether the file exists. findHardDisk will
1788 // figure that out, and filesystem-based tests are simply wrong
1789 // in the general case (think of iSCSI).
1790
1791 // clone the disk:
1792 ComObjPtr<Medium> pSourceDisk;
1793
1794 Log(("Finding source disk \"%s\"\n", strSrcFilePath.c_str()));
1795 rc = mVirtualBox->findHardDiskByLocation(strSrcFilePath, true, &pSourceDisk);
1796 if (FAILED(rc)) throw rc;
1797
1798 Bstr uuidSource;
1799 rc = pSourceDisk->COMGETTER(Id)(uuidSource.asOutParam());
1800 if (FAILED(rc)) throw rc;
1801 Guid guidSource(uuidSource);
1802
1803 // output filename
1804 const Utf8Str &strTargetFileNameOnly = pDiskEntry->strOvf;
1805 // target path needs to be composed from where the output OVF is
1806 Utf8Str strTargetFilePath(pTask->locInfo.strPath);
1807 strTargetFilePath.stripFilename()
1808 .append("/")
1809 .append(strTargetFileNameOnly);
1810
1811 // The exporting requests a lock on the media tree. So leave our lock temporary.
1812 writeLock.release();
1813 try
1814 {
1815 ComObjPtr<Progress> pProgress2;
1816 pProgress2.createObject();
1817 rc = pProgress2->init(mVirtualBox, static_cast<IAppliance*>(this), BstrFmt(tr("Creating medium '%s'"), strTargetFilePath.c_str()).raw(), TRUE);
1818 if (FAILED(rc)) throw rc;
1819
1820 // advance to the next operation
1821 pTask->pProgress->SetNextOperation(BstrFmt(tr("Exporting to disk image '%s'"), RTPathFilename(strTargetFilePath.c_str())).raw(),
1822 pDiskEntry->ulSizeMB); // operation's weight, as set up with the IProgress originally
1823
1824 // create a flat copy of the source disk image
1825 rc = pSourceDisk->exportFile(strTargetFilePath.c_str(), format, MediumVariant_VmdkStreamOptimized, pIfIo, pStorage, pProgress2);
1826 if (FAILED(rc)) throw rc;
1827
1828 ComPtr<IProgress> pProgress3(pProgress2);
1829 // now wait for the background disk operation to complete; this throws HRESULTs on error
1830 waitForAsyncProgress(pTask->pProgress, pProgress3);
1831 }
1832 catch (HRESULT rc3)
1833 {
1834 writeLock.acquire();
1835 // Todo: file deletion on error? If not, we can remove that whole try/catch block.
1836 throw rc3;
1837 }
1838 // Finished, lock again (so nobody mess around with the medium tree
1839 // in the meantime)
1840 writeLock.acquire();
1841 fileList.push_back(STRPAIR(strTargetFilePath, pStorage->strDigest));
1842 }
1843
1844 if (m->fManifest)
1845 {
1846 // Create & write the manifest file
1847 Utf8Str strMfFilePath = Utf8Str(pTask->locInfo.strPath).stripExt().append(".mf");
1848 Utf8Str strMfFileName = Utf8Str(strMfFilePath).stripPath();
1849 pTask->pProgress->SetNextOperation(BstrFmt(tr("Creating manifest file '%s'"), strMfFileName.c_str()).raw(),
1850 m->ulWeightForManifestOperation); // operation's weight, as set up with the IProgress originally);
1851 PRTMANIFESTTEST paManifestFiles = (PRTMANIFESTTEST)RTMemAlloc(sizeof(RTMANIFESTTEST) * fileList.size());
1852 size_t i = 0;
1853 list<STRPAIR>::const_iterator it1;
1854 for (it1 = fileList.begin();
1855 it1 != fileList.end();
1856 ++it1, ++i)
1857 {
1858 paManifestFiles[i].pszTestFile = (*it1).first.c_str();
1859 paManifestFiles[i].pszTestDigest = (*it1).second.c_str();
1860 }
1861 void *pvBuf;
1862 size_t cbSize;
1863 vrc = RTManifestWriteFilesBuf(&pvBuf, &cbSize, paManifestFiles, fileList.size());
1864 RTMemFree(paManifestFiles);
1865 if (RT_FAILURE(vrc))
1866 throw setError(VBOX_E_FILE_ERROR,
1867 tr("Could not create manifest file '%s' (%Rrc)"),
1868 strMfFileName.c_str(), vrc);
1869 /* Disable digest creation for the manifest file. */
1870 pStorage->fCreateDigest = false;
1871 /* Write the manifest file to disk. */
1872 vrc = Sha1WriteBuf(strMfFilePath.c_str(), pvBuf, cbSize, pIfIo, pStorage);
1873 RTMemFree(pvBuf);
1874 if (RT_FAILURE(vrc))
1875 throw setError(VBOX_E_FILE_ERROR,
1876 tr("Could not create manifest file '%s' (%Rrc)"),
1877 strMfFilePath.c_str(), vrc);
1878 }
1879 }
1880 catch (RTCError &x) // includes all XML exceptions
1881 {
1882 rc = setError(VBOX_E_FILE_ERROR,
1883 x.what());
1884 }
1885 catch (HRESULT aRC)
1886 {
1887 rc = aRC;
1888 }
1889
1890 /* Cleanup on error */
1891 if (FAILED(rc))
1892 {
1893 list<STRPAIR>::const_iterator it1;
1894 for (it1 = fileList.begin();
1895 it1 != fileList.end();
1896 ++it1)
1897 pIfIo->pfnDelete(pStorage, (*it1).first.c_str());
1898 }
1899
1900 LogFlowFunc(("rc=%Rhrc\n", rc));
1901 LogFlowFuncLeave();
1902
1903 return rc;
1904}
1905
1906#ifdef VBOX_WITH_S3
1907/**
1908 * Worker code for writing out OVF to the cloud. This is called from Appliance::taskThreadWriteOVF()
1909 * in S3 mode and therefore runs on the OVF write worker thread. This then starts a second worker
1910 * thread to create temporary files (see Appliance::writeFS()).
1911 *
1912 * @param pTask
1913 * @return
1914 */
1915HRESULT Appliance::writeS3(TaskOVF *pTask)
1916{
1917 LogFlowFuncEnter();
1918 LogFlowFunc(("Appliance %p\n", this));
1919
1920 AutoCaller autoCaller(this);
1921 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1922
1923 HRESULT rc = S_OK;
1924
1925 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
1926
1927 int vrc = VINF_SUCCESS;
1928 RTS3 hS3 = NIL_RTS3;
1929 char szOSTmpDir[RTPATH_MAX];
1930 RTPathTemp(szOSTmpDir, sizeof(szOSTmpDir));
1931 /* The template for the temporary directory created below */
1932 char *pszTmpDir = RTPathJoinA(szOSTmpDir, "vbox-ovf-XXXXXX");
1933 list< pair<Utf8Str, ULONG> > filesList;
1934
1935 // todo:
1936 // - usable error codes
1937 // - seems snapshot filenames are problematic {uuid}.vdi
1938 try
1939 {
1940 /* Extract the bucket */
1941 Utf8Str tmpPath = pTask->locInfo.strPath;
1942 Utf8Str bucket;
1943 parseBucket(tmpPath, bucket);
1944
1945 /* We need a temporary directory which we can put the OVF file & all
1946 * disk images in */
1947 vrc = RTDirCreateTemp(pszTmpDir);
1948 if (RT_FAILURE(vrc))
1949 throw setError(VBOX_E_FILE_ERROR,
1950 tr("Cannot create temporary directory '%s' (%Rrc)"), pszTmpDir, vrc);
1951
1952 /* The temporary name of the target OVF file */
1953 Utf8StrFmt strTmpOvf("%s/%s", pszTmpDir, RTPathFilename(tmpPath.c_str()));
1954
1955 /* Prepare the temporary writing of the OVF */
1956 ComObjPtr<Progress> progress;
1957 /* Create a temporary file based location info for the sub task */
1958 LocationInfo li;
1959 li.strPath = strTmpOvf;
1960 rc = writeImpl(pTask->enFormat, li, progress);
1961 if (FAILED(rc)) throw rc;
1962
1963 /* Unlock the appliance for the writing thread */
1964 appLock.release();
1965 /* Wait until the writing is done, but report the progress back to the
1966 caller */
1967 ComPtr<IProgress> progressInt(progress);
1968 waitForAsyncProgress(pTask->pProgress, progressInt); /* Any errors will be thrown */
1969
1970 /* Again lock the appliance for the next steps */
1971 appLock.acquire();
1972
1973 vrc = RTPathExists(strTmpOvf.c_str()); /* Paranoid check */
1974 if (RT_FAILURE(vrc))
1975 throw setError(VBOX_E_FILE_ERROR,
1976 tr("Cannot find source file '%s' (%Rrc)"), strTmpOvf.c_str(), vrc);
1977 /* Add the OVF file */
1978 filesList.push_back(pair<Utf8Str, ULONG>(strTmpOvf, m->ulWeightForXmlOperation)); /* Use 1% of the total for the OVF file upload */
1979 /* Add the manifest file */
1980 if (m->fManifest)
1981 {
1982 Utf8Str strMfFile = Utf8Str(strTmpOvf).stripExt().append(".mf");
1983 filesList.push_back(pair<Utf8Str, ULONG>(strMfFile , m->ulWeightForXmlOperation)); /* Use 1% of the total for the manifest file upload */
1984 }
1985
1986 /* Now add every disks of every virtual system */
1987 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it;
1988 for (it = m->virtualSystemDescriptions.begin();
1989 it != m->virtualSystemDescriptions.end();
1990 ++it)
1991 {
1992 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it);
1993 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskImage);
1994 std::list<VirtualSystemDescriptionEntry*>::const_iterator itH;
1995 for (itH = avsdeHDs.begin();
1996 itH != avsdeHDs.end();
1997 ++itH)
1998 {
1999 const Utf8Str &strTargetFileNameOnly = (*itH)->strOvf;
2000 /* Target path needs to be composed from where the output OVF is */
2001 Utf8Str strTargetFilePath(strTmpOvf);
2002 strTargetFilePath.stripFilename();
2003 strTargetFilePath.append("/");
2004 strTargetFilePath.append(strTargetFileNameOnly);
2005 vrc = RTPathExists(strTargetFilePath.c_str()); /* Paranoid check */
2006 if (RT_FAILURE(vrc))
2007 throw setError(VBOX_E_FILE_ERROR,
2008 tr("Cannot find source file '%s' (%Rrc)"), strTargetFilePath.c_str(), vrc);
2009 filesList.push_back(pair<Utf8Str, ULONG>(strTargetFilePath, (*itH)->ulSizeMB));
2010 }
2011 }
2012 /* Next we have to upload the OVF & all disk images */
2013 vrc = RTS3Create(&hS3, pTask->locInfo.strUsername.c_str(), pTask->locInfo.strPassword.c_str(), pTask->locInfo.strHostname.c_str(), "virtualbox-agent/"VBOX_VERSION_STRING);
2014 if (RT_FAILURE(vrc))
2015 throw setError(VBOX_E_IPRT_ERROR,
2016 tr("Cannot create S3 service handler"));
2017 RTS3SetProgressCallback(hS3, pTask->updateProgress, &pTask);
2018
2019 /* Upload all files */
2020 for (list< pair<Utf8Str, ULONG> >::const_iterator it1 = filesList.begin(); it1 != filesList.end(); ++it1)
2021 {
2022 const pair<Utf8Str, ULONG> &s = (*it1);
2023 char *pszFilename = RTPathFilename(s.first.c_str());
2024 /* Advance to the next operation */
2025 pTask->pProgress->SetNextOperation(BstrFmt(tr("Uploading file '%s'"), pszFilename).raw(), s.second);
2026 vrc = RTS3PutKey(hS3, bucket.c_str(), pszFilename, s.first.c_str());
2027 if (RT_FAILURE(vrc))
2028 {
2029 if (vrc == VERR_S3_CANCELED)
2030 break;
2031 else if (vrc == VERR_S3_ACCESS_DENIED)
2032 throw setError(E_ACCESSDENIED,
2033 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);
2034 else if (vrc == VERR_S3_NOT_FOUND)
2035 throw setError(VBOX_E_FILE_ERROR,
2036 tr("Cannot upload file '%s' to S3 storage server (File not found)"), pszFilename);
2037 else
2038 throw setError(VBOX_E_IPRT_ERROR,
2039 tr("Cannot upload file '%s' to S3 storage server (%Rrc)"), pszFilename, vrc);
2040 }
2041 }
2042 }
2043 catch(HRESULT aRC)
2044 {
2045 rc = aRC;
2046 }
2047 /* Cleanup */
2048 RTS3Destroy(hS3);
2049 /* Delete all files which where temporary created */
2050 for (list< pair<Utf8Str, ULONG> >::const_iterator it1 = filesList.begin(); it1 != filesList.end(); ++it1)
2051 {
2052 const char *pszFilePath = (*it1).first.c_str();
2053 if (RTPathExists(pszFilePath))
2054 {
2055 vrc = RTFileDelete(pszFilePath);
2056 if (RT_FAILURE(vrc))
2057 rc = setError(VBOX_E_FILE_ERROR,
2058 tr("Cannot delete file '%s' (%Rrc)"), pszFilePath, vrc);
2059 }
2060 }
2061 /* Delete the temporary directory */
2062 if (RTPathExists(pszTmpDir))
2063 {
2064 vrc = RTDirRemove(pszTmpDir);
2065 if (RT_FAILURE(vrc))
2066 rc = setError(VBOX_E_FILE_ERROR,
2067 tr("Cannot delete temporary directory '%s' (%Rrc)"), pszTmpDir, vrc);
2068 }
2069 if (pszTmpDir)
2070 RTStrFree(pszTmpDir);
2071
2072 LogFlowFunc(("rc=%Rhrc\n", rc));
2073 LogFlowFuncLeave();
2074
2075 return rc;
2076}
2077#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