VirtualBox

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

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

Main/Appliance: Teach importing new tricks: importing to specific location (by settings file name or base folder) and also importing straight into a group. Lots of cleanup and minor fixing (bad code quality due to lots of copy/paste, and what's worse is that the original code was broken already, using the variables inconsistently), plus some smallish coding style cleaup. Much more needed. Also fixed the incomplete use of the VM name on expert (the one in the VBox XML was not changed, and it's the preferred name on import).
VBoxManage: small updates to reflect the new features (and actually offer setting the VM name on export, which is something the GUI could do for a long time).

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