VirtualBox

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

Last change on this file since 55182 was 55182, checked in by vboxsync, 10 years ago

Main,FE/VBoxManage: Support exporting machines as appliances which have encrypted disks. Because the OVF standard doesn't support encrypted disks so far we always decrypt exported images which requires the password before starting the export proess

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

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