VirtualBox

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

Last change on this file since 73003 was 73003, checked in by vboxsync, 6 years ago

Main: Use setErrorBoth when we've got a VBox status code handy. (The COM status codes aren't too specfic and this may help us decode error messages and provide an alternative to strstr for API clients. setErrorBoth isn't new, btw.)

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