VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/ApplianceImplImport.cpp@ 79093

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

Main/Appliance: When importing an OVF with the vbox config xml the media ended up in the global registry. This prevented them from being added to the right registry when the VM is finally created and registered.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 220.4 KB
Line 
1/* $Id: ApplianceImplImport.cpp 79093 2019-06-11 17:09:16Z vboxsync $ */
2/** @file
3 * IAppliance and IVirtualSystem COM class implementations.
4 */
5
6/*
7 * Copyright (C) 2008-2019 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#define LOG_GROUP LOG_GROUP_MAIN_APPLIANCE
19#include <iprt/alloca.h>
20#include <iprt/path.h>
21#include <iprt/cpp/path.h>
22#include <iprt/dir.h>
23#include <iprt/file.h>
24#include <iprt/s3.h>
25#include <iprt/sha.h>
26#include <iprt/manifest.h>
27#include <iprt/tar.h>
28#include <iprt/zip.h>
29#include <iprt/stream.h>
30#include <iprt/crypto/digest.h>
31#include <iprt/crypto/pkix.h>
32#include <iprt/crypto/store.h>
33#include <iprt/crypto/x509.h>
34
35#include <VBox/vd.h>
36#include <VBox/com/array.h>
37
38#include "ApplianceImpl.h"
39#include "VirtualBoxImpl.h"
40#include "GuestOSTypeImpl.h"
41#include "ProgressImpl.h"
42#include "MachineImpl.h"
43#include "MediumImpl.h"
44#include "MediumFormatImpl.h"
45#include "SystemPropertiesImpl.h"
46#include "HostImpl.h"
47
48#include "AutoCaller.h"
49#include "LoggingNew.h"
50
51#include "ApplianceImplPrivate.h"
52#include "CertificateImpl.h"
53#include "ovfreader.h"
54
55#include <VBox/param.h>
56#include <VBox/version.h>
57#include <VBox/settings.h>
58
59#include <set>
60
61using namespace std;
62
63////////////////////////////////////////////////////////////////////////////////
64//
65// IAppliance public methods
66//
67////////////////////////////////////////////////////////////////////////////////
68
69/**
70 * Public method implementation. This opens the OVF with ovfreader.cpp.
71 * Thread implementation is in Appliance::readImpl().
72 *
73 * @param aFile File to read the appliance from.
74 * @param aProgress Progress object.
75 * @return
76 */
77HRESULT Appliance::read(const com::Utf8Str &aFile,
78 ComPtr<IProgress> &aProgress)
79{
80 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
81
82 if (!i_isApplianceIdle())
83 return E_ACCESSDENIED;
84
85 if (m->pReader)
86 {
87 delete m->pReader;
88 m->pReader = NULL;
89 }
90
91 /* Parse all necessary info out of the URI (please not how stupid utterly wasteful
92 this status & allocation error throwing is): */
93 try
94 {
95 i_parseURI(aFile, m->locInfo); /* may trhow rc. */
96 }
97 catch (HRESULT aRC)
98 {
99 return aRC;
100 }
101 catch (std::bad_alloc &)
102 {
103 return E_OUTOFMEMORY;
104 }
105
106 // see if we can handle this file; for now we insist it has an ovf/ova extension
107 if ( m->locInfo.storageType == VFSType_File
108 && !aFile.endsWith(".ovf", Utf8Str::CaseInsensitive)
109 && !aFile.endsWith(".ova", Utf8Str::CaseInsensitive))
110 return setError(VBOX_E_FILE_ERROR, tr("Appliance file must have .ovf or .ova extension"));
111
112 ComObjPtr<Progress> progress;
113 HRESULT hrc = i_readImpl(m->locInfo, progress);
114 if (SUCCEEDED(hrc))
115 progress.queryInterfaceTo(aProgress.asOutParam());
116 return hrc;
117}
118
119/**
120 * Public method implementation. This looks at the output of ovfreader.cpp and creates
121 * VirtualSystemDescription instances.
122 * @return
123 */
124HRESULT Appliance::interpret()
125{
126 /// @todo
127 // - don't use COM methods but the methods directly (faster, but needs appropriate
128 // locking of that objects itself (s. HardDisk))
129 // - Appropriate handle errors like not supported file formats
130 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
131
132 if (!i_isApplianceIdle())
133 return E_ACCESSDENIED;
134
135 HRESULT rc = S_OK;
136
137 /* Clear any previous virtual system descriptions */
138 m->virtualSystemDescriptions.clear();
139
140 if (m->locInfo.storageType == VFSType_File && !m->pReader)
141 return setError(E_FAIL,
142 tr("Cannot interpret appliance without reading it first (call read() before interpret())"));
143
144 // Change the appliance state so we can safely leave the lock while doing time-consuming
145 // medium imports; also the below method calls do all kinds of locking which conflicts with
146 // the appliance object lock
147 m->state = ApplianceImporting;
148 alock.release();
149
150 /* Try/catch so we can clean up on error */
151 try
152 {
153 list<ovf::VirtualSystem>::const_iterator it;
154 /* Iterate through all virtual systems */
155 for (it = m->pReader->m_llVirtualSystems.begin();
156 it != m->pReader->m_llVirtualSystems.end();
157 ++it)
158 {
159 const ovf::VirtualSystem &vsysThis = *it;
160
161 ComObjPtr<VirtualSystemDescription> pNewDesc;
162 rc = pNewDesc.createObject();
163 if (FAILED(rc)) throw rc;
164 rc = pNewDesc->init();
165 if (FAILED(rc)) throw rc;
166
167 // if the virtual system in OVF had a <vbox:Machine> element, have the
168 // VirtualBox settings code parse that XML now
169 if (vsysThis.pelmVBoxMachine)
170 pNewDesc->i_importVBoxMachineXML(*vsysThis.pelmVBoxMachine);
171
172 // Guest OS type
173 // This is taken from one of three places, in this order:
174 Utf8Str strOsTypeVBox;
175 Utf8StrFmt strCIMOSType("%RU32", (uint32_t)vsysThis.cimos);
176 // 1) If there is a <vbox:Machine>, then use the type from there.
177 if ( vsysThis.pelmVBoxMachine
178 && pNewDesc->m->pConfig->machineUserData.strOsType.isNotEmpty()
179 )
180 strOsTypeVBox = pNewDesc->m->pConfig->machineUserData.strOsType;
181 // 2) Otherwise, if there is OperatingSystemSection/vbox:OSType, use that one.
182 else if (vsysThis.strTypeVBox.isNotEmpty()) // OVFReader has found vbox:OSType
183 strOsTypeVBox = vsysThis.strTypeVBox;
184 // 3) Otherwise, make a best guess what the vbox type is from the OVF (CIM) OS type.
185 else
186 convertCIMOSType2VBoxOSType(strOsTypeVBox, vsysThis.cimos, vsysThis.strCimosDesc);
187 pNewDesc->i_addEntry(VirtualSystemDescriptionType_OS,
188 "",
189 strCIMOSType,
190 strOsTypeVBox);
191
192 /* VM name */
193 Utf8Str nameVBox;
194 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
195 if ( vsysThis.pelmVBoxMachine
196 && pNewDesc->m->pConfig->machineUserData.strName.isNotEmpty())
197 nameVBox = pNewDesc->m->pConfig->machineUserData.strName;
198 else
199 nameVBox = vsysThis.strName;
200 /* If there isn't any name specified create a default one out
201 * of the OS type */
202 if (nameVBox.isEmpty())
203 nameVBox = strOsTypeVBox;
204 i_searchUniqueVMName(nameVBox);
205 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Name,
206 "",
207 vsysThis.strName,
208 nameVBox);
209
210 /* VM Primary Group */
211 Utf8Str strPrimaryGroup;
212 if ( vsysThis.pelmVBoxMachine
213 && pNewDesc->m->pConfig->machineUserData.llGroups.size())
214 strPrimaryGroup = pNewDesc->m->pConfig->machineUserData.llGroups.front();
215 if (strPrimaryGroup.isEmpty())
216 strPrimaryGroup = "/";
217 pNewDesc->i_addEntry(VirtualSystemDescriptionType_PrimaryGroup,
218 "",
219 "" /* no direct OVF correspondence */,
220 strPrimaryGroup);
221
222 /* Based on the VM name, create a target machine path. */
223 Bstr bstrSettingsFilename;
224 rc = mVirtualBox->ComposeMachineFilename(Bstr(nameVBox).raw(),
225 Bstr(strPrimaryGroup).raw(),
226 NULL /* aCreateFlags */,
227 NULL /* aBaseFolder */,
228 bstrSettingsFilename.asOutParam());
229 if (FAILED(rc)) throw rc;
230 Utf8Str strMachineFolder(bstrSettingsFilename);
231 strMachineFolder.stripFilename();
232
233#if 1
234 /* The import logic should work exactly the same whether the
235 * following 2 items are present or not, but of course it may have
236 * an influence on the exact presentation of the import settings
237 * of an API client. */
238 Utf8Str strSettingsFilename(bstrSettingsFilename);
239 pNewDesc->i_addEntry(VirtualSystemDescriptionType_SettingsFile,
240 "",
241 "" /* no direct OVF correspondence */,
242 strSettingsFilename);
243 Utf8Str strBaseFolder;
244 mVirtualBox->i_getDefaultMachineFolder(strBaseFolder);
245 pNewDesc->i_addEntry(VirtualSystemDescriptionType_BaseFolder,
246 "",
247 "" /* no direct OVF correspondence */,
248 strBaseFolder);
249#endif
250
251 /* VM Product */
252 if (!vsysThis.strProduct.isEmpty())
253 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Product,
254 "",
255 vsysThis.strProduct,
256 vsysThis.strProduct);
257
258 /* VM Vendor */
259 if (!vsysThis.strVendor.isEmpty())
260 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Vendor,
261 "",
262 vsysThis.strVendor,
263 vsysThis.strVendor);
264
265 /* VM Version */
266 if (!vsysThis.strVersion.isEmpty())
267 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Version,
268 "",
269 vsysThis.strVersion,
270 vsysThis.strVersion);
271
272 /* VM ProductUrl */
273 if (!vsysThis.strProductUrl.isEmpty())
274 pNewDesc->i_addEntry(VirtualSystemDescriptionType_ProductUrl,
275 "",
276 vsysThis.strProductUrl,
277 vsysThis.strProductUrl);
278
279 /* VM VendorUrl */
280 if (!vsysThis.strVendorUrl.isEmpty())
281 pNewDesc->i_addEntry(VirtualSystemDescriptionType_VendorUrl,
282 "",
283 vsysThis.strVendorUrl,
284 vsysThis.strVendorUrl);
285
286 /* VM description */
287 if (!vsysThis.strDescription.isEmpty())
288 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Description,
289 "",
290 vsysThis.strDescription,
291 vsysThis.strDescription);
292
293 /* VM license */
294 if (!vsysThis.strLicenseText.isEmpty())
295 pNewDesc->i_addEntry(VirtualSystemDescriptionType_License,
296 "",
297 vsysThis.strLicenseText,
298 vsysThis.strLicenseText);
299
300 /* Now that we know the OS type, get our internal defaults based on
301 * that, if it is known (otherwise pGuestOSType will be NULL). */
302 ComPtr<IGuestOSType> pGuestOSType;
303 mVirtualBox->GetGuestOSType(Bstr(strOsTypeVBox).raw(), pGuestOSType.asOutParam());
304
305 /* CPU count */
306 ULONG cpuCountVBox;
307 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
308 if ( vsysThis.pelmVBoxMachine
309 && pNewDesc->m->pConfig->hardwareMachine.cCPUs)
310 cpuCountVBox = pNewDesc->m->pConfig->hardwareMachine.cCPUs;
311 else
312 cpuCountVBox = vsysThis.cCPUs;
313 /* Check for the constraints */
314 if (cpuCountVBox > SchemaDefs::MaxCPUCount)
315 {
316 i_addWarning(tr("The virtual system \"%s\" claims support for %u CPU's, but VirtualBox has support for "
317 "max %u CPU's only."),
318 vsysThis.strName.c_str(), cpuCountVBox, SchemaDefs::MaxCPUCount);
319 cpuCountVBox = SchemaDefs::MaxCPUCount;
320 }
321 if (vsysThis.cCPUs == 0)
322 cpuCountVBox = 1;
323 pNewDesc->i_addEntry(VirtualSystemDescriptionType_CPU,
324 "",
325 Utf8StrFmt("%RU32", (uint32_t)vsysThis.cCPUs),
326 Utf8StrFmt("%RU32", (uint32_t)cpuCountVBox));
327
328 /* RAM */
329 uint64_t ullMemSizeVBox;
330 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
331 if ( vsysThis.pelmVBoxMachine
332 && pNewDesc->m->pConfig->hardwareMachine.ulMemorySizeMB)
333 ullMemSizeVBox = pNewDesc->m->pConfig->hardwareMachine.ulMemorySizeMB;
334 else
335 ullMemSizeVBox = vsysThis.ullMemorySize / _1M;
336 /* Check for the constraints */
337 if ( ullMemSizeVBox != 0
338 && ( ullMemSizeVBox < MM_RAM_MIN_IN_MB
339 || ullMemSizeVBox > MM_RAM_MAX_IN_MB
340 )
341 )
342 {
343 i_addWarning(tr("The virtual system \"%s\" claims support for %llu MB RAM size, but VirtualBox has "
344 "support for min %u & max %u MB RAM size only."),
345 vsysThis.strName.c_str(), ullMemSizeVBox, MM_RAM_MIN_IN_MB, MM_RAM_MAX_IN_MB);
346 ullMemSizeVBox = RT_MIN(RT_MAX(ullMemSizeVBox, MM_RAM_MIN_IN_MB), MM_RAM_MAX_IN_MB);
347 }
348 if (vsysThis.ullMemorySize == 0)
349 {
350 /* If the RAM of the OVF is zero, use our predefined values */
351 ULONG memSizeVBox2;
352 if (!pGuestOSType.isNull())
353 {
354 rc = pGuestOSType->COMGETTER(RecommendedRAM)(&memSizeVBox2);
355 if (FAILED(rc)) throw rc;
356 }
357 else
358 memSizeVBox2 = 1024;
359 /* VBox stores that in MByte */
360 ullMemSizeVBox = (uint64_t)memSizeVBox2;
361 }
362 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Memory,
363 "",
364 Utf8StrFmt("%RU64", (uint64_t)vsysThis.ullMemorySize),
365 Utf8StrFmt("%RU64", (uint64_t)ullMemSizeVBox));
366
367 /* Audio */
368 Utf8Str strSoundCard;
369 Utf8Str strSoundCardOrig;
370 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
371 if ( vsysThis.pelmVBoxMachine
372 && pNewDesc->m->pConfig->hardwareMachine.audioAdapter.fEnabled)
373 {
374 strSoundCard = Utf8StrFmt("%RU32",
375 (uint32_t)pNewDesc->m->pConfig->hardwareMachine.audioAdapter.controllerType);
376 }
377 else if (vsysThis.strSoundCardType.isNotEmpty())
378 {
379 /* Set the AC97 always for the simple OVF case.
380 * @todo: figure out the hardware which could be possible */
381 strSoundCard = Utf8StrFmt("%RU32", (uint32_t)AudioControllerType_AC97);
382 strSoundCardOrig = vsysThis.strSoundCardType;
383 }
384 if (strSoundCard.isNotEmpty())
385 pNewDesc->i_addEntry(VirtualSystemDescriptionType_SoundCard,
386 "",
387 strSoundCardOrig,
388 strSoundCard);
389
390#ifdef VBOX_WITH_USB
391 /* USB Controller */
392 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
393 if ( ( vsysThis.pelmVBoxMachine
394 && pNewDesc->m->pConfig->hardwareMachine.usbSettings.llUSBControllers.size() > 0)
395 || vsysThis.fHasUsbController)
396 pNewDesc->i_addEntry(VirtualSystemDescriptionType_USBController, "", "", "");
397#endif /* VBOX_WITH_USB */
398
399 /* Network Controller */
400 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
401 if (vsysThis.pelmVBoxMachine)
402 {
403 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(pNewDesc->m->pConfig->hardwareMachine.chipsetType);
404
405 const settings::NetworkAdaptersList &llNetworkAdapters = pNewDesc->m->pConfig->hardwareMachine.llNetworkAdapters;
406 /* Check for the constrains */
407 if (llNetworkAdapters.size() > maxNetworkAdapters)
408 i_addWarning(tr("The virtual system \"%s\" claims support for %zu network adapters, but VirtualBox "
409 "has support for max %u network adapter only."),
410 vsysThis.strName.c_str(), llNetworkAdapters.size(), maxNetworkAdapters);
411 /* Iterate through all network adapters. */
412 settings::NetworkAdaptersList::const_iterator it1;
413 size_t a = 0;
414 for (it1 = llNetworkAdapters.begin();
415 it1 != llNetworkAdapters.end() && a < maxNetworkAdapters;
416 ++it1, ++a)
417 {
418 if (it1->fEnabled)
419 {
420 Utf8Str strMode = convertNetworkAttachmentTypeToString(it1->mode);
421 pNewDesc->i_addEntry(VirtualSystemDescriptionType_NetworkAdapter,
422 "", // ref
423 strMode, // orig
424 Utf8StrFmt("%RU32", (uint32_t)it1->type), // conf
425 0,
426 Utf8StrFmt("slot=%RU32;type=%s", it1->ulSlot, strMode.c_str())); // extra conf
427 }
428 }
429 }
430 /* else we use the ovf configuration. */
431 else if (vsysThis.llEthernetAdapters.size() > 0)
432 {
433 size_t cEthernetAdapters = vsysThis.llEthernetAdapters.size();
434 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
435
436 /* Check for the constrains */
437 if (cEthernetAdapters > maxNetworkAdapters)
438 i_addWarning(tr("The virtual system \"%s\" claims support for %zu network adapters, but VirtualBox "
439 "has support for max %u network adapter only."),
440 vsysThis.strName.c_str(), cEthernetAdapters, maxNetworkAdapters);
441
442 /* Get the default network adapter type for the selected guest OS */
443 NetworkAdapterType_T defaultAdapterVBox = NetworkAdapterType_Am79C970A;
444 if (!pGuestOSType.isNull())
445 {
446 rc = pGuestOSType->COMGETTER(AdapterType)(&defaultAdapterVBox);
447 if (FAILED(rc)) throw rc;
448 }
449 else
450 {
451#ifdef VBOX_WITH_E1000
452 defaultAdapterVBox = NetworkAdapterType_I82540EM;
453#else
454 defaultAdapterVBox = NetworkAdapterType_Am79C973A;
455#endif
456 }
457
458 ovf::EthernetAdaptersList::const_iterator itEA;
459 /* Iterate through all abstract networks. Ignore network cards
460 * which exceed the limit of VirtualBox. */
461 size_t a = 0;
462 for (itEA = vsysThis.llEthernetAdapters.begin();
463 itEA != vsysThis.llEthernetAdapters.end() && a < maxNetworkAdapters;
464 ++itEA, ++a)
465 {
466 const ovf::EthernetAdapter &ea = *itEA; // logical network to connect to
467 Utf8Str strNetwork = ea.strNetworkName;
468 // make sure it's one of these two
469 if ( (strNetwork.compare("Null", Utf8Str::CaseInsensitive))
470 && (strNetwork.compare("NAT", Utf8Str::CaseInsensitive))
471 && (strNetwork.compare("Bridged", Utf8Str::CaseInsensitive))
472 && (strNetwork.compare("Internal", Utf8Str::CaseInsensitive))
473 && (strNetwork.compare("HostOnly", Utf8Str::CaseInsensitive))
474 && (strNetwork.compare("Generic", Utf8Str::CaseInsensitive))
475 )
476 strNetwork = "Bridged"; // VMware assumes this is the default apparently
477
478 /* Figure out the hardware type */
479 NetworkAdapterType_T nwAdapterVBox = defaultAdapterVBox;
480 if (!ea.strAdapterType.compare("PCNet32", Utf8Str::CaseInsensitive))
481 {
482 /* If the default adapter is already one of the two
483 * PCNet adapters use the default one. If not use the
484 * Am79C970A as fallback. */
485 if (!(defaultAdapterVBox == NetworkAdapterType_Am79C970A ||
486 defaultAdapterVBox == NetworkAdapterType_Am79C973))
487 nwAdapterVBox = NetworkAdapterType_Am79C970A;
488 }
489#ifdef VBOX_WITH_E1000
490 /* VMWare accidentally write this with VirtualCenter 3.5,
491 so make sure in this case always to use the VMWare one */
492 else if (!ea.strAdapterType.compare("E10000", Utf8Str::CaseInsensitive))
493 nwAdapterVBox = NetworkAdapterType_I82545EM;
494 else if (!ea.strAdapterType.compare("E1000", Utf8Str::CaseInsensitive))
495 {
496 /* Check if this OVF was written by VirtualBox */
497 if (Utf8Str(vsysThis.strVirtualSystemType).contains("virtualbox", Utf8Str::CaseInsensitive))
498 {
499 /* If the default adapter is already one of the three
500 * E1000 adapters use the default one. If not use the
501 * I82545EM as fallback. */
502 if (!(defaultAdapterVBox == NetworkAdapterType_I82540EM ||
503 defaultAdapterVBox == NetworkAdapterType_I82543GC ||
504 defaultAdapterVBox == NetworkAdapterType_I82545EM))
505 nwAdapterVBox = NetworkAdapterType_I82540EM;
506 }
507 else
508 /* Always use this one since it's what VMware uses */
509 nwAdapterVBox = NetworkAdapterType_I82545EM;
510 }
511#endif /* VBOX_WITH_E1000 */
512
513 pNewDesc->i_addEntry(VirtualSystemDescriptionType_NetworkAdapter,
514 "", // ref
515 ea.strNetworkName, // orig
516 Utf8StrFmt("%RU32", (uint32_t)nwAdapterVBox), // conf
517 0,
518 Utf8StrFmt("type=%s", strNetwork.c_str())); // extra conf
519 }
520 }
521
522 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
523 bool fFloppy = false;
524 bool fDVD = false;
525 if (vsysThis.pelmVBoxMachine)
526 {
527 settings::StorageControllersList &llControllers = pNewDesc->m->pConfig->hardwareMachine.storage.llStorageControllers;
528 settings::StorageControllersList::iterator it3;
529 for (it3 = llControllers.begin();
530 it3 != llControllers.end();
531 ++it3)
532 {
533 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
534 settings::AttachedDevicesList::iterator it4;
535 for (it4 = llAttachments.begin();
536 it4 != llAttachments.end();
537 ++it4)
538 {
539 fDVD |= it4->deviceType == DeviceType_DVD;
540 fFloppy |= it4->deviceType == DeviceType_Floppy;
541 if (fFloppy && fDVD)
542 break;
543 }
544 if (fFloppy && fDVD)
545 break;
546 }
547 }
548 else
549 {
550 fFloppy = vsysThis.fHasFloppyDrive;
551 fDVD = vsysThis.fHasCdromDrive;
552 }
553 /* Floppy Drive */
554 if (fFloppy)
555 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Floppy, "", "", "");
556 /* CD Drive */
557 if (fDVD)
558 pNewDesc->i_addEntry(VirtualSystemDescriptionType_CDROM, "", "", "");
559
560 /* Storage Controller */
561 uint16_t cIDEused = 0;
562 uint16_t cSATAused = 0; NOREF(cSATAused);
563 uint16_t cSCSIused = 0; NOREF(cSCSIused);
564 ovf::ControllersMap::const_iterator hdcIt;
565 /* Iterate through all storage controllers */
566 for (hdcIt = vsysThis.mapControllers.begin();
567 hdcIt != vsysThis.mapControllers.end();
568 ++hdcIt)
569 {
570 const ovf::HardDiskController &hdc = hdcIt->second;
571 Utf8Str strControllerID = Utf8StrFmt("%RI32", (uint32_t)hdc.idController);
572
573 switch (hdc.system)
574 {
575 case ovf::HardDiskController::IDE:
576 /* Check for the constrains */
577 if (cIDEused < 4)
578 {
579 /// @todo figure out the IDE types
580 /* Use PIIX4 as default */
581 Utf8Str strType = "PIIX4";
582 if (!hdc.strControllerType.compare("PIIX3", Utf8Str::CaseInsensitive))
583 strType = "PIIX3";
584 else if (!hdc.strControllerType.compare("ICH6", Utf8Str::CaseInsensitive))
585 strType = "ICH6";
586 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
587 strControllerID, // strRef
588 hdc.strControllerType, // aOvfValue
589 strType); // aVBoxValue
590 }
591 else
592 /* Warn only once */
593 if (cIDEused == 2)
594 i_addWarning(tr("The virtual \"%s\" system requests support for more than two "
595 "IDE controller channels, but VirtualBox supports only two."),
596 vsysThis.strName.c_str());
597
598 ++cIDEused;
599 break;
600
601 case ovf::HardDiskController::SATA:
602 /* Check for the constrains */
603 if (cSATAused < 1)
604 {
605 /// @todo figure out the SATA types
606 /* We only support a plain AHCI controller, so use them always */
607 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA,
608 strControllerID,
609 hdc.strControllerType,
610 "AHCI");
611 }
612 else
613 {
614 /* Warn only once */
615 if (cSATAused == 1)
616 i_addWarning(tr("The virtual system \"%s\" requests support for more than one "
617 "SATA controller, but VirtualBox has support for only one"),
618 vsysThis.strName.c_str());
619
620 }
621 ++cSATAused;
622 break;
623
624 case ovf::HardDiskController::SCSI:
625 /* Check for the constrains */
626 if (cSCSIused < 1)
627 {
628 VirtualSystemDescriptionType_T vsdet = VirtualSystemDescriptionType_HardDiskControllerSCSI;
629 Utf8Str hdcController = "LsiLogic";
630 if (!hdc.strControllerType.compare("lsilogicsas", Utf8Str::CaseInsensitive))
631 {
632 // OVF considers SAS a variant of SCSI but VirtualBox considers it a class of its own
633 vsdet = VirtualSystemDescriptionType_HardDiskControllerSAS;
634 hdcController = "LsiLogicSas";
635 }
636 else if (!hdc.strControllerType.compare("BusLogic", Utf8Str::CaseInsensitive))
637 hdcController = "BusLogic";
638 pNewDesc->i_addEntry(vsdet,
639 strControllerID,
640 hdc.strControllerType,
641 hdcController);
642 }
643 else
644 i_addWarning(tr("The virtual system \"%s\" requests support for an additional "
645 "SCSI controller of type \"%s\" with ID %s, but VirtualBox presently "
646 "supports only one SCSI controller."),
647 vsysThis.strName.c_str(),
648 hdc.strControllerType.c_str(),
649 strControllerID.c_str());
650 ++cSCSIused;
651 break;
652 }
653 }
654
655 /* Storage devices (hard disks/DVDs/...) */
656 if (vsysThis.mapVirtualDisks.size() > 0)
657 {
658 ovf::VirtualDisksMap::const_iterator itVD;
659 /* Iterate through all storage devices */
660 for (itVD = vsysThis.mapVirtualDisks.begin();
661 itVD != vsysThis.mapVirtualDisks.end();
662 ++itVD)
663 {
664 const ovf::VirtualDisk &hd = itVD->second;
665 /* Get the associated image */
666 ovf::DiskImage di;
667 std::map<RTCString, ovf::DiskImage>::iterator foundDisk;
668
669 foundDisk = m->pReader->m_mapDisks.find(hd.strDiskId);
670 if (foundDisk == m->pReader->m_mapDisks.end())
671 continue;
672 else
673 {
674 di = foundDisk->second;
675 }
676
677 /*
678 * Figure out from URI which format the image has.
679 * There is no strict mapping of image URI to image format.
680 * It's possible we aren't able to recognize some URIs.
681 */
682
683 ComObjPtr<MediumFormat> mediumFormat;
684 rc = i_findMediumFormatFromDiskImage(di, mediumFormat);
685 if (FAILED(rc))
686 throw rc;
687
688 Bstr bstrFormatName;
689 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
690 if (FAILED(rc))
691 throw rc;
692 Utf8Str vdf = Utf8Str(bstrFormatName);
693
694 /// @todo
695 // - figure out all possible vmdk formats we also support
696 // - figure out if there is a url specifier for vhd already
697 // - we need a url specifier for the vdi format
698
699 Utf8Str strFilename = di.strHref;
700 DeviceType_T devType = DeviceType_Null;
701 if (vdf.compare("VMDK", Utf8Str::CaseInsensitive) == 0)
702 {
703 /* If the href is empty use the VM name as filename */
704 if (!strFilename.length())
705 strFilename = Utf8StrFmt("%s.vmdk", hd.strDiskId.c_str());
706 devType = DeviceType_HardDisk;
707 }
708 else if (vdf.compare("RAW", Utf8Str::CaseInsensitive) == 0)
709 {
710 /* If the href is empty use the VM name as filename */
711 if (!strFilename.length())
712 strFilename = Utf8StrFmt("%s.iso", hd.strDiskId.c_str());
713 devType = DeviceType_DVD;
714 }
715 else
716 throw setError(VBOX_E_FILE_ERROR,
717 tr("Unsupported format for virtual disk image %s in OVF: \"%s\""),
718 di.strHref.c_str(),
719 di.strFormat.c_str());
720
721 /*
722 * Remove last extension from the file name if the file is compressed
723 */
724 if (di.strCompression.compare("gzip", Utf8Str::CaseInsensitive)==0)
725 strFilename.stripSuffix();
726
727 i_searchUniqueImageFilePath(strMachineFolder, devType, strFilename); /** @todo check the return code! */
728
729 /* find the description for the storage controller
730 * that has the same ID as hd.idController */
731 const VirtualSystemDescriptionEntry *pController;
732 if (!(pController = pNewDesc->i_findControllerFromID(hd.idController)))
733 throw setError(E_FAIL,
734 tr("Cannot find storage controller with OVF instance ID %RI32 "
735 "to which medium \"%s\" should be attached"),
736 hd.idController,
737 di.strHref.c_str());
738
739 /* controller to attach to, and the bus within that controller */
740 Utf8StrFmt strExtraConfig("controller=%RI16;channel=%RI16",
741 pController->ulIndex,
742 hd.ulAddressOnParent);
743 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
744 hd.strDiskId,
745 di.strHref,
746 strFilename,
747 di.ulSuggestedSizeMB,
748 strExtraConfig);
749 }
750 }
751
752 m->virtualSystemDescriptions.push_back(pNewDesc);
753 }
754 }
755 catch (HRESULT aRC)
756 {
757 /* On error we clear the list & return */
758 m->virtualSystemDescriptions.clear();
759 rc = aRC;
760 }
761
762 // reset the appliance state
763 alock.acquire();
764 m->state = ApplianceIdle;
765
766 return rc;
767}
768
769/**
770 * Public method implementation. This creates one or more new machines according to the
771 * VirtualSystemScription instances created by Appliance::Interpret().
772 * Thread implementation is in Appliance::i_importImpl().
773 * @param aOptions Import options.
774 * @param aProgress Progress object.
775 * @return
776 */
777HRESULT Appliance::importMachines(const std::vector<ImportOptions_T> &aOptions,
778 ComPtr<IProgress> &aProgress)
779{
780 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
781
782 if (aOptions.size())
783 {
784 try
785 {
786 m->optListImport.setCapacity(aOptions.size());
787 for (size_t i = 0; i < aOptions.size(); ++i)
788 m->optListImport.insert(i, aOptions[i]);
789 }
790 catch (std::bad_alloc &)
791 {
792 return E_OUTOFMEMORY;
793 }
794 }
795
796 AssertReturn(!( m->optListImport.contains(ImportOptions_KeepAllMACs)
797 && m->optListImport.contains(ImportOptions_KeepNATMACs) )
798 , E_INVALIDARG);
799
800 // do not allow entering this method if the appliance is busy reading or writing
801 if (!i_isApplianceIdle())
802 return E_ACCESSDENIED;
803
804 //check for the local import only. For import from the Cloud m->pReader is always NULL.
805 if (m->locInfo.storageType == VFSType_File && !m->pReader)
806 return setError(E_FAIL,
807 tr("Cannot import machines without reading it first (call read() before i_importMachines())"));
808
809 ComObjPtr<Progress> progress;
810 HRESULT hrc = i_importImpl(m->locInfo, progress);
811 if (SUCCEEDED(hrc))
812 progress.queryInterfaceTo(aProgress.asOutParam());
813
814 return hrc;
815}
816
817////////////////////////////////////////////////////////////////////////////////
818//
819// Appliance private methods
820//
821////////////////////////////////////////////////////////////////////////////////
822
823/**
824 * Ensures that there is a look-ahead object ready.
825 *
826 * @returns true if there's an object handy, false if end-of-stream.
827 * @throws HRESULT if the next object isn't a regular file. Sets error info
828 * (which is why it's a method on Appliance and not the
829 * ImportStack).
830 */
831bool Appliance::i_importEnsureOvaLookAhead(ImportStack &stack)
832{
833 Assert(stack.hVfsFssOva != NULL);
834 if (stack.hVfsIosOvaLookAhead == NIL_RTVFSIOSTREAM)
835 {
836 RTStrFree(stack.pszOvaLookAheadName);
837 stack.pszOvaLookAheadName = NULL;
838
839 RTVFSOBJTYPE enmType;
840 RTVFSOBJ hVfsObj;
841 int vrc = RTVfsFsStrmNext(stack.hVfsFssOva, &stack.pszOvaLookAheadName, &enmType, &hVfsObj);
842 if (RT_SUCCESS(vrc))
843 {
844 stack.hVfsIosOvaLookAhead = RTVfsObjToIoStream(hVfsObj);
845 RTVfsObjRelease(hVfsObj);
846 if ( ( enmType != RTVFSOBJTYPE_FILE
847 && enmType != RTVFSOBJTYPE_IO_STREAM)
848 || stack.hVfsIosOvaLookAhead == NIL_RTVFSIOSTREAM)
849 throw setError(VBOX_E_FILE_ERROR,
850 tr("Malformed OVA. '%s' is not a regular file (%d)."), stack.pszOvaLookAheadName, enmType);
851 }
852 else if (vrc == VERR_EOF)
853 return false;
854 else
855 throw setErrorVrc(vrc, tr("RTVfsFsStrmNext failed (%Rrc)"), vrc);
856 }
857 return true;
858}
859
860HRESULT Appliance::i_preCheckImageAvailability(ImportStack &stack)
861{
862 if (i_importEnsureOvaLookAhead(stack))
863 return S_OK;
864 throw setError(VBOX_E_FILE_ERROR, tr("Unexpected end of OVA package"));
865 /** @todo r=bird: dunno why this bother returning a value and the caller
866 * having a special 'continue' case for it. It always threw all non-OK
867 * status codes. It's possibly to handle out of order stuff, so that
868 * needs adding to the testcase! */
869}
870
871/**
872 * Opens a source file (for reading obviously).
873 *
874 * @param stack
875 * @param rstrSrcPath The source file to open.
876 * @param pszManifestEntry The manifest entry of the source file. This is
877 * used when constructing our manifest using a pass
878 * thru.
879 * @returns I/O stream handle to the source file.
880 * @throws HRESULT error status, error info set.
881 */
882RTVFSIOSTREAM Appliance::i_importOpenSourceFile(ImportStack &stack, Utf8Str const &rstrSrcPath, const char *pszManifestEntry)
883{
884 /*
885 * Open the source file. Special considerations for OVAs.
886 */
887 RTVFSIOSTREAM hVfsIosSrc;
888 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
889 {
890 for (uint32_t i = 0;; i++)
891 {
892 if (!i_importEnsureOvaLookAhead(stack))
893 throw setErrorBoth(VBOX_E_FILE_ERROR, VERR_EOF,
894 tr("Unexpected end of OVA / internal error - missing '%s' (skipped %u)"),
895 rstrSrcPath.c_str(), i);
896 if (RTStrICmp(stack.pszOvaLookAheadName, rstrSrcPath.c_str()) == 0)
897 break;
898
899 /* release the current object, loop to get the next. */
900 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
901 }
902 hVfsIosSrc = stack.claimOvaLookAHead();
903 }
904 else
905 {
906 int vrc = RTVfsIoStrmOpenNormal(rstrSrcPath.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosSrc);
907 if (RT_FAILURE(vrc))
908 throw setErrorVrc(vrc, tr("Error opening '%s' for reading (%Rrc)"), rstrSrcPath.c_str(), vrc);
909 }
910
911 /*
912 * Digest calculation filtering.
913 */
914 hVfsIosSrc = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosSrc, pszManifestEntry);
915 if (hVfsIosSrc == NIL_RTVFSIOSTREAM)
916 throw E_FAIL;
917
918 return hVfsIosSrc;
919}
920
921/**
922 * Creates the destination file and fills it with bytes from the source stream.
923 *
924 * This assumes that we digest the source when fDigestTypes is non-zero, and
925 * thus calls RTManifestPtIosAddEntryNow when done.
926 *
927 * @param rstrDstPath The path to the destination file. Missing path
928 * components will be created.
929 * @param hVfsIosSrc The source I/O stream.
930 * @param rstrSrcLogNm The name of the source for logging and error
931 * messages.
932 * @returns COM status code.
933 * @throws Nothing (as the caller has VFS handles to release).
934 */
935HRESULT Appliance::i_importCreateAndWriteDestinationFile(Utf8Str const &rstrDstPath, RTVFSIOSTREAM hVfsIosSrc,
936 Utf8Str const &rstrSrcLogNm)
937{
938 int vrc;
939
940 /*
941 * Create the output file, including necessary paths.
942 * Any existing file will be overwritten.
943 */
944 HRESULT hrc = VirtualBox::i_ensureFilePathExists(rstrDstPath, true /*fCreate*/);
945 if (SUCCEEDED(hrc))
946 {
947 RTVFSIOSTREAM hVfsIosDst;
948 vrc = RTVfsIoStrmOpenNormal(rstrDstPath.c_str(),
949 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL,
950 &hVfsIosDst);
951 if (RT_SUCCESS(vrc))
952 {
953 /*
954 * Pump the bytes thru. If we fail, delete the output file.
955 */
956 vrc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
957 if (RT_SUCCESS(vrc))
958 hrc = S_OK;
959 else
960 hrc = setErrorVrc(vrc, tr("Error occured decompressing '%s' to '%s' (%Rrc)"),
961 rstrSrcLogNm.c_str(), rstrDstPath.c_str(), vrc);
962 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosDst);
963 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
964 if (RT_FAILURE(vrc))
965 RTFileDelete(rstrDstPath.c_str());
966 }
967 else
968 hrc = setErrorVrc(vrc, tr("Error opening destionation image '%s' for writing (%Rrc)"), rstrDstPath.c_str(), vrc);
969 }
970 return hrc;
971}
972
973
974/**
975 *
976 * @param stack Import stack.
977 * @param rstrSrcPath Source path.
978 * @param rstrDstPath Destination path.
979 * @param pszManifestEntry The manifest entry of the source file. This is
980 * used when constructing our manifest using a pass
981 * thru.
982 * @throws HRESULT error status, error info set.
983 */
984void Appliance::i_importCopyFile(ImportStack &stack, Utf8Str const &rstrSrcPath, Utf8Str const &rstrDstPath,
985 const char *pszManifestEntry)
986{
987 /*
988 * Open the file (throws error) and add a read ahead thread so we can do
989 * concurrent reads (+digest) and writes.
990 */
991 RTVFSIOSTREAM hVfsIosSrc = i_importOpenSourceFile(stack, rstrSrcPath, pszManifestEntry);
992 RTVFSIOSTREAM hVfsIosReadAhead;
993 int vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/, 0 /*cbBuffers=default*/,
994 &hVfsIosReadAhead);
995 if (RT_FAILURE(vrc))
996 {
997 RTVfsIoStrmRelease(hVfsIosSrc);
998 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
999 }
1000
1001 /*
1002 * Write the destination file (nothrow).
1003 */
1004 HRESULT hrc = i_importCreateAndWriteDestinationFile(rstrDstPath, hVfsIosReadAhead, rstrSrcPath);
1005 RTVfsIoStrmRelease(hVfsIosReadAhead);
1006
1007 /*
1008 * Before releasing the source stream, make sure we've successfully added
1009 * the digest to our manifest.
1010 */
1011 if (SUCCEEDED(hrc) && m->fDigestTypes)
1012 {
1013 vrc = RTManifestPtIosAddEntryNow(hVfsIosSrc);
1014 if (RT_FAILURE(vrc))
1015 hrc = setErrorVrc(vrc, tr("RTManifestPtIosAddEntryNow failed with %Rrc"), vrc);
1016 }
1017
1018 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosSrc);
1019 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
1020 if (SUCCEEDED(hrc))
1021 return;
1022 throw hrc;
1023}
1024
1025/**
1026 *
1027 * @param stack
1028 * @param rstrSrcPath
1029 * @param rstrDstPath
1030 * @param pszManifestEntry The manifest entry of the source file. This is
1031 * used when constructing our manifest using a pass
1032 * thru.
1033 * @throws HRESULT error status, error info set.
1034 */
1035void Appliance::i_importDecompressFile(ImportStack &stack, Utf8Str const &rstrSrcPath, Utf8Str const &rstrDstPath,
1036 const char *pszManifestEntry)
1037{
1038 RTVFSIOSTREAM hVfsIosSrcCompressed = i_importOpenSourceFile(stack, rstrSrcPath, pszManifestEntry);
1039
1040 /*
1041 * Add a read ahead thread here. This means reading and digest calculation
1042 * is done on one thread, while unpacking and writing is one on this thread.
1043 */
1044 RTVFSIOSTREAM hVfsIosReadAhead;
1045 int vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrcCompressed, 0 /*fFlags*/, 0 /*cBuffers=default*/,
1046 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
1047 if (RT_FAILURE(vrc))
1048 {
1049 RTVfsIoStrmRelease(hVfsIosSrcCompressed);
1050 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
1051 }
1052
1053 /*
1054 * Add decompression step.
1055 */
1056 RTVFSIOSTREAM hVfsIosSrc;
1057 vrc = RTZipGzipDecompressIoStream(hVfsIosReadAhead, 0, &hVfsIosSrc);
1058 RTVfsIoStrmRelease(hVfsIosReadAhead);
1059 if (RT_FAILURE(vrc))
1060 {
1061 RTVfsIoStrmRelease(hVfsIosSrcCompressed);
1062 throw setErrorVrc(vrc, tr("Error initializing gzip decompression for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
1063 }
1064
1065 /*
1066 * Write the stream to the destination file (nothrow).
1067 */
1068 HRESULT hrc = i_importCreateAndWriteDestinationFile(rstrDstPath, hVfsIosSrc, rstrSrcPath);
1069
1070 /*
1071 * Before releasing the source stream, make sure we've successfully added
1072 * the digest to our manifest.
1073 */
1074 if (SUCCEEDED(hrc) && m->fDigestTypes)
1075 {
1076 vrc = RTManifestPtIosAddEntryNow(hVfsIosSrcCompressed);
1077 if (RT_FAILURE(vrc))
1078 hrc = setErrorVrc(vrc, tr("RTManifestPtIosAddEntryNow failed with %Rrc"), vrc);
1079 }
1080
1081 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosSrc);
1082 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
1083
1084 cRefs = RTVfsIoStrmRelease(hVfsIosSrcCompressed);
1085 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
1086
1087 if (SUCCEEDED(hrc))
1088 return;
1089 throw hrc;
1090}
1091
1092/*******************************************************************************
1093 * Read stuff
1094 ******************************************************************************/
1095
1096/**
1097 * Implementation for reading an OVF (via task).
1098 *
1099 * This starts a new thread which will call
1100 * Appliance::taskThreadImportOrExport() which will then call readFS(). This
1101 * will then open the OVF with ovfreader.cpp.
1102 *
1103 * This is in a separate private method because it is used from two locations:
1104 *
1105 * 1) from the public Appliance::Read().
1106 *
1107 * 2) in a second worker thread; in that case, Appliance::ImportMachines() called Appliance::i_importImpl(), which
1108 * called Appliance::readFSOVA(), which called Appliance::i_importImpl(), which then called this again.
1109 *
1110 * @returns COM status with error info set.
1111 * @param aLocInfo The OVF location.
1112 * @param aProgress Where to return the progress object.
1113 */
1114HRESULT Appliance::i_readImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
1115{
1116 /*
1117 * Create the progress object.
1118 */
1119 HRESULT hrc;
1120 aProgress.createObject();
1121 try
1122 {
1123 if (aLocInfo.storageType == VFSType_Cloud)
1124 {
1125 /* 1 operation only */
1126 hrc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this),
1127 Utf8Str(tr("Getting cloud instance information")), TRUE /* aCancelable */);
1128
1129 /* Create an empty ovf::OVFReader for manual filling it.
1130 * It's not a normal usage case, but we try to re-use some OVF stuff to friend
1131 * the cloud import with OVF import.
1132 * In the standard case the ovf::OVFReader is created in the Appliance::i_readOVFFile().
1133 * We need the existing m->pReader for Appliance::i_importCloudImpl() where we re-use OVF logic. */
1134 m->pReader = new ovf::OVFReader();
1135 }
1136 else
1137 {
1138 Utf8StrFmt strDesc(tr("Reading appliance '%s'"), aLocInfo.strPath.c_str());
1139 if (aLocInfo.storageType == VFSType_File)
1140 /* 1 operation only */
1141 hrc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this), strDesc, TRUE /* aCancelable */);
1142 else
1143 /* 4/5 is downloading, 1/5 is reading */
1144 hrc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this), strDesc, TRUE /* aCancelable */,
1145 2, // ULONG cOperations,
1146 5, // ULONG ulTotalOperationsWeight,
1147 Utf8StrFmt(tr("Download appliance '%s'"),
1148 aLocInfo.strPath.c_str()), // CBSTR bstrFirstOperationDescription,
1149 4); // ULONG ulFirstOperationWeight,
1150 }
1151 }
1152 catch (std::bad_alloc &) /* Utf8Str/Utf8StrFmt */
1153 {
1154 return E_OUTOFMEMORY;
1155 }
1156 if (FAILED(hrc))
1157 return hrc;
1158
1159 /*
1160 * Initialize the worker task.
1161 */
1162 ThreadTask *pTask;
1163 try
1164 {
1165 if (aLocInfo.storageType == VFSType_Cloud)
1166 pTask = new TaskCloud(this, TaskCloud::ReadData, aLocInfo, aProgress);
1167 else
1168 pTask = new TaskOVF(this, TaskOVF::Read, aLocInfo, aProgress);
1169 }
1170 catch (std::bad_alloc &)
1171 {
1172 return E_OUTOFMEMORY;
1173 }
1174
1175 /*
1176 * Kick off the worker thread.
1177 */
1178 hrc = pTask->createThread();
1179 pTask = NULL; /* Note! createThread has consumed the task.*/
1180 if (SUCCEEDED(hrc))
1181 return hrc;
1182 return setError(hrc, tr("Failed to create thread for reading appliance data"));
1183}
1184
1185HRESULT Appliance::i_gettingCloudData(TaskCloud *pTask)
1186{
1187 LogFlowFuncEnter();
1188 LogFlowFunc(("Appliance %p\n", this));
1189
1190 AutoCaller autoCaller(this);
1191 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1192
1193 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
1194
1195 HRESULT hrc = S_OK;
1196
1197 try
1198 {
1199 Utf8Str strBasename(pTask->locInfo.strPath);
1200 RTCList<RTCString, RTCString *> parts = strBasename.split("/" );
1201 if (parts.size() != 2)//profile + instance id
1202 {
1203 return setErrorVrc(VERR_MISMATCH, tr("%s: The profile name or instance id are absent or"
1204 "contain unsupported characters.", __FUNCTION__));
1205 }
1206
1207 //Get information about the passed cloud instance
1208 ComPtr<ICloudProviderManager> cpm;
1209 hrc = mVirtualBox->COMGETTER(CloudProviderManager)(cpm.asOutParam());
1210 if (FAILED(hrc))
1211 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud provider manager object wasn't found", __FUNCTION__));
1212
1213 Utf8Str strProviderName = pTask->locInfo.strProvider;
1214 ComPtr<ICloudProvider> cloudProvider;
1215 ComPtr<ICloudProfile> cloudProfile;
1216 hrc = cpm->GetProviderByShortName(Bstr(strProviderName.c_str()).raw(), cloudProvider.asOutParam());
1217
1218 if (FAILED(hrc))
1219 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud provider object wasn't found", __FUNCTION__));
1220
1221 Utf8Str profileName(parts.at(0));//profile
1222 if (profileName.isEmpty())
1223 return setErrorVrc(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud user profile name wasn't found", __FUNCTION__));
1224
1225 hrc = cloudProvider->GetProfileByName(Bstr(parts.at(0)).raw(), cloudProfile.asOutParam());
1226 if (FAILED(hrc))
1227 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud profile object wasn't found", __FUNCTION__));
1228
1229 ComObjPtr<ICloudClient> cloudClient;
1230 hrc = cloudProfile->CreateCloudClient(cloudClient.asOutParam());
1231 if (FAILED(hrc))
1232 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud client object wasn't found", __FUNCTION__));
1233
1234 m->virtualSystemDescriptions.clear();//clear all for assurance before creating new
1235 std::vector<ComPtr<IVirtualSystemDescription> > vsdArray;
1236 ULONG requestedVSDnums = 1;
1237 ULONG newVSDnums = 0;
1238 hrc = createVirtualSystemDescriptions(requestedVSDnums, &newVSDnums);
1239 if (FAILED(hrc)) throw hrc;
1240 if (requestedVSDnums != newVSDnums)
1241 throw setErrorVrc(VERR_MISMATCH, tr("%s: Requested and created numbers of VSD are differ.", __FUNCTION__));
1242
1243 hrc = getVirtualSystemDescriptions(vsdArray);
1244 if (FAILED(hrc)) throw hrc;
1245 ComPtr<IVirtualSystemDescription> instanceDescription = vsdArray[0];
1246
1247 LogRel(("%s: calling CloudClient::GetInstanceInfo()\n", __FUNCTION__));
1248
1249 ComPtr<IProgress> pProgress;
1250 hrc = cloudClient->GetInstanceInfo(Bstr(parts.at(1)).raw(), instanceDescription, pProgress.asOutParam());
1251 if (FAILED(hrc)) throw hrc;
1252 hrc = pTask->pProgress->WaitForOtherProgressCompletion(pProgress, 60000);//timeout 1 min = 60000 millisec
1253 if (FAILED(hrc)) throw hrc;
1254
1255 // set cloud profile
1256 instanceDescription->AddDescription(VirtualSystemDescriptionType_CloudProfileName,
1257 Bstr(profileName).raw(),
1258 Bstr(profileName).raw());
1259
1260 Utf8StrFmt strSetting("VM with id %s imported from the cloud provider %s",
1261 parts.at(1).c_str(), strProviderName.c_str());
1262 // set description
1263 instanceDescription->AddDescription(VirtualSystemDescriptionType_Description,
1264 Bstr(strSetting).raw(),
1265 Bstr(strSetting).raw());
1266 }
1267 catch (HRESULT arc)
1268 {
1269 LogFlowFunc(("arc=%Rhrc\n", arc));
1270 hrc = arc;
1271 }
1272
1273 LogFlowFunc(("rc=%Rhrc\n", hrc));
1274 LogFlowFuncLeave();
1275
1276 return hrc;
1277}
1278
1279void Appliance::i_setApplianceState(const ApplianceState &state)
1280{
1281 AutoWriteLock writeLock(this COMMA_LOCKVAL_SRC_POS);
1282 m->state = state;
1283 writeLock.release();
1284}
1285
1286/**
1287 * Actual worker code for import from the Cloud
1288 *
1289 * @param pTask
1290 * @return
1291 */
1292HRESULT Appliance::i_importCloudImpl(TaskCloud *pTask)
1293{
1294 LogFlowFuncEnter();
1295 LogFlowFunc(("Appliance %p\n", this));
1296
1297 int vrc = VINF_SUCCESS;
1298 HRESULT hrc = S_OK;
1299 Utf8Str strLastActualErrorDesc("No errors");
1300
1301 /* Clear the list of imported machines, if any */
1302 m->llGuidsMachinesCreated.clear();
1303
1304 ComPtr<ICloudProviderManager> cpm;
1305 hrc = mVirtualBox->COMGETTER(CloudProviderManager)(cpm.asOutParam());
1306 if (FAILED(hrc))
1307 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud provider manager object wasn't found", __FUNCTION__));
1308
1309 Utf8Str strProviderName = pTask->locInfo.strProvider;
1310 ComPtr<ICloudProvider> cloudProvider;
1311 ComPtr<ICloudProfile> cloudProfile;
1312 hrc = cpm->GetProviderByShortName(Bstr(strProviderName.c_str()).raw(), cloudProvider.asOutParam());
1313
1314 if (FAILED(hrc))
1315 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud provider object wasn't found", __FUNCTION__));
1316
1317 /* Get the actual VSD, only one VSD object can be there for now so just call the function front() */
1318 ComPtr<IVirtualSystemDescription> vsd = m->virtualSystemDescriptions.front();
1319
1320 Utf8Str vsdData;
1321 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
1322 com::SafeArray<BSTR> aRefs;
1323 com::SafeArray<BSTR> aOvfValues;
1324 com::SafeArray<BSTR> aVBoxValues;
1325 com::SafeArray<BSTR> aExtraConfigValues;
1326
1327/*
1328 * local #define for better reading the code
1329 * uses only the previously locally declared variable names
1330 * set hrc as the result of operation
1331 */
1332#define GET_VSD_DESCRIPTION_BY_TYPE(aParamType) \
1333 retTypes.setNull(); \
1334 aRefs.setNull(); \
1335 aOvfValues.setNull(); \
1336 aVBoxValues.setNull(); \
1337 aExtraConfigValues.setNull(); \
1338 vsd->GetDescriptionByType(aParamType, \
1339 ComSafeArrayAsOutParam(retTypes), \
1340 ComSafeArrayAsOutParam(aRefs), \
1341 ComSafeArrayAsOutParam(aOvfValues), \
1342 ComSafeArrayAsOutParam(aVBoxValues), \
1343 ComSafeArrayAsOutParam(aExtraConfigValues)); \
1344
1345
1346 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudProfileName)
1347 if (aVBoxValues.size() == 0)
1348 return setErrorVrc(VERR_NOT_FOUND, tr("%s: Cloud user profile name wasn't found", __FUNCTION__));
1349
1350 Utf8Str profileName(aVBoxValues[0]);
1351 if (profileName.isEmpty())
1352 return setErrorVrc(VERR_INVALID_STATE, tr("%s: Cloud user profile name is empty", __FUNCTION__));
1353
1354 hrc = cloudProvider->GetProfileByName(aVBoxValues[0], cloudProfile.asOutParam());
1355 if (FAILED(hrc))
1356 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud profile object wasn't found", __FUNCTION__));
1357
1358 ComObjPtr<ICloudClient> cloudClient;
1359 hrc = cloudProfile->CreateCloudClient(cloudClient.asOutParam());
1360 if (FAILED(hrc))
1361 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud client object wasn't found", __FUNCTION__));
1362
1363 ComPtr<IProgress> pProgress;
1364 hrc = pTask->pProgress.queryInterfaceTo(pProgress.asOutParam());
1365 if (FAILED(hrc))
1366 return hrc;
1367
1368 Utf8Str strOsType;
1369 ComPtr<IGuestOSType> pGuestOSType;
1370 {
1371 VBOXOSTYPE guestOsType = VBOXOSTYPE_Unknown;
1372 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_OS)//aVBoxValues is set in this #define
1373 if (aVBoxValues.size() != 0)
1374 {
1375 strOsType = aVBoxValues[0];
1376 /* Check the OS type */
1377 uint32_t const idxOSType = Global::getOSTypeIndexFromId(strOsType.c_str());
1378 guestOsType = idxOSType < Global::cOSTypes ? Global::sOSTypes[idxOSType].osType : VBOXOSTYPE_Unknown;
1379
1380 /* Case when some invalid OS type or garbage was passed. Set to VBOXOSTYPE_Unknown. */
1381 if (idxOSType > Global::cOSTypes)
1382 {
1383 strOsType = Global::OSTypeId(guestOsType);
1384 vsd->RemoveDescriptionByType(VirtualSystemDescriptionType_OS);
1385 vsd->AddDescription(VirtualSystemDescriptionType_OS,
1386 Bstr(strOsType).raw(),
1387 Bstr(strOsType).raw());
1388 }
1389 }
1390 /* Case when no OS type was passed. Set to VBOXOSTYPE_Unknown. */
1391 else
1392 {
1393 strOsType = Global::OSTypeId(guestOsType);
1394 vsd->AddDescription(VirtualSystemDescriptionType_OS,
1395 Bstr(strOsType).raw(),
1396 Bstr(strOsType).raw());
1397 }
1398
1399 LogRel(("%s: OS type is %s\n", __FUNCTION__, strOsType.c_str()));
1400
1401 /* We can get some default settings from GuestOSType when it's needed */
1402 hrc = mVirtualBox->GetGuestOSType(Bstr(strOsType).raw(), pGuestOSType.asOutParam());
1403 if (FAILED(hrc))
1404 return hrc;
1405 }
1406
1407 /* Should be defined here because it's used later, at least when ComposeMachineFilename() is called */
1408 Utf8Str strVMName("VM_exported_from_cloud");
1409
1410 if (m->virtualSystemDescriptions.size() == 1)
1411 {
1412 do
1413 {
1414 ComPtr<IVirtualBox> VBox(mVirtualBox);
1415
1416 {
1417 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Name)//aVBoxValues is set in this #define
1418 if (aVBoxValues.size() != 0)//paranoia but anyway...
1419 strVMName = aVBoxValues[0];
1420 LogRel(("%s: VM name is %s\n", __FUNCTION__, strVMName.c_str()));
1421 }
1422
1423// i_searchUniqueVMName(strVMName);//internally calls setError() in the case of absent the registered VM with such name
1424
1425 ComPtr<IMachine> machine;
1426 hrc = mVirtualBox->FindMachine(Bstr(strVMName.c_str()).raw(), machine.asOutParam());
1427 if (SUCCEEDED(hrc))
1428 {
1429 /* what to do? create a new name from the old one with some suffix? */
1430 com::Guid newId;
1431 newId.create();
1432 strVMName.append("__").append(newId.toString());
1433 vsd->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
1434 vsd->AddDescription(VirtualSystemDescriptionType_Name,
1435 Bstr(strVMName).raw(),
1436 Bstr(strVMName).raw());
1437 /* No check again because it would be weird if a VM with such unique name exists */
1438 }
1439
1440 Utf8Str strInsId;
1441 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId)//aVBoxValues is set in this #define
1442 if (aVBoxValues.size() == 0)
1443 {
1444 hrc = setErrorVrc(VERR_NOT_FOUND, "%s: Cloud Instance Id wasn't found", __FUNCTION__);
1445 break;
1446 }
1447 strInsId = aVBoxValues[0];
1448
1449 LogRel(("%s: calling CloudClient::ImportInstance\n", __FUNCTION__));
1450
1451 /* Here it's strongly supposed that cloud import produces ONE object on the disk.
1452 * Because it much easier to manage one object in any case.
1453 * In the case when cloud import creates several object on the disk all of them
1454 * must be combined together into one object by cloud client.
1455 * The most simple way is to create a TAR archive. */
1456 hrc = cloudClient->ImportInstance(m->virtualSystemDescriptions.front(),
1457 aVBoxValues[0],
1458 VBox,
1459 pProgress);
1460
1461 if (FAILED(hrc))
1462 {
1463 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (cloud phase) failed. "
1464 "Used cloud instance is \'%s\'\n", __FUNCTION__, strInsId.c_str());
1465
1466 LogRel((strLastActualErrorDesc.c_str()));
1467 hrc = setError(hrc, strLastActualErrorDesc.c_str());
1468 break;
1469 }
1470
1471 } while (0);
1472 }
1473 else
1474 {
1475 hrc = setErrorVrc(VERR_NOT_SUPPORTED, tr("Import from Cloud isn't supported for more than one VM instance."));
1476
1477 /* Reset the state so others can call methods again */
1478// m->state = ApplianceIdle;
1479 return hrc;
1480 }
1481
1482 if (FAILED(hrc))
1483 {
1484 HRESULT temp_hrc = hrc;//save the original result
1485 Utf8Str generalRollBackErrorMessage("Rollback action for Import Cloud operation failed."
1486 "Some leavings may exist on the local disk or in the Cloud.");
1487 /*
1488 * Roll-back actions.
1489 * we finish here if:
1490 * 1. Getting the object from the Cloud has been failed.
1491 * 2. Something is wrong with getting data from ComPtr<IVirtualSystemDescription> vsd.
1492 * 3. More than 1 VirtualSystemDescription is presented in the list m->virtualSystemDescriptions.
1493 * Maximum what we have there are:
1494 * 1. The downloaded object, so just check the presence and delete it if one exists
1495 * 2. Some leftovers in the Cloud. Also delete them too if it's possible.
1496 * Should they be deleted in the OCICloudClient::importInstance()?
1497 * Because deleting them here is not easy as it in the importInstance().
1498 */
1499
1500 {
1501 /* small explanation here, the image here points out to the whole downloaded object (not to the image only)
1502 * filled during the first cloud import stage (in the ICloudClient::importInstance()) */
1503 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage)//aVBoxValues is set in this #define
1504 if (aVBoxValues.size() == 0)
1505 hrc = setErrorVrc(VERR_NOT_FOUND, generalRollBackErrorMessage.c_str());
1506 else
1507 {
1508 vsdData = aVBoxValues[0];
1509 //try to delete the downloaded object
1510 bool fExist = RTPathExists(vsdData.c_str());
1511 if (fExist)
1512 {
1513 vrc = RTFileDelete(vsdData.c_str());
1514 if (RT_FAILURE(vrc))
1515 {
1516 hrc = setErrorVrc(vrc, generalRollBackErrorMessage.c_str());
1517 LogRel(("%s: Rollback action - the object %s hasn't been deleted\n", __FUNCTION__, vsdData.c_str()));
1518 }
1519 else
1520 LogRel(("%s: Rollback action - the object %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
1521 }
1522 }
1523 }
1524
1525 {
1526 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId)//aVBoxValues is set in this #define
1527 if (aVBoxValues.size() == 0)
1528 hrc = setErrorVrc(VERR_NOT_FOUND, generalRollBackErrorMessage.c_str());
1529 else
1530 {
1531 vsdData = aVBoxValues[0];
1532
1533 /** @todo
1534 * future function which will eliminate the temporary objects created during the first phase.
1535 * hrc = cloud.EliminateImportLeavings(aVBoxValues[0], pProgress); */
1536 if (FAILED(hrc))
1537 {
1538 hrc = setErrorVrc(VERR_INVALID_STATE, generalRollBackErrorMessage.c_str());
1539 LogRel(("%s: Rollback action - the leavings in the %s after import the "
1540 "instance %s may not have been deleted\n",
1541 __FUNCTION__, strProviderName.c_str(), vsdData.c_str()));
1542 }
1543 else
1544 LogRel(("%s: Rollback action - the leavings in the %s after import the "
1545 "instance %s have been deleted\n",
1546 __FUNCTION__, strProviderName.c_str(), vsdData.c_str()));
1547 }
1548 }
1549
1550 /* Because during the rollback phase the hrc may have the good result
1551 * Thus we restore the original error in the case when the rollback phase was successful
1552 * Otherwise we return not the original error but the last error in the rollback phase */
1553 if (SUCCEEDED(hrc))
1554 hrc = setError(temp_hrc, strLastActualErrorDesc.c_str());//restore the original result
1555 }
1556 else
1557 {
1558 /* Small explanation here, the image here points out to the whole downloaded object (not to the image only)
1559 * filled during the first cloud import stage (in the ICloudClient::importInstance()) */
1560 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage)//aVBoxValues is set in this #define
1561 if (aVBoxValues.size() == 0)
1562 throw setErrorVrc(VERR_NOT_FOUND, "%s: The description of the downloaded object wasn't found", __FUNCTION__);
1563
1564 Utf8Str strAbsSrcPath(aVBoxValues[0]);
1565
1566 /* Put all VFS* declaration here because they are needed to be release by the corresponding
1567 RTVfs***Release functions in the case of exception */
1568 RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
1569 RTVFSFSSTREAM hVfsFssObject = NIL_RTVFSFSSTREAM;
1570 RTVFSIOSTREAM hVfsIosCurr = NIL_RTVFSIOSTREAM;
1571
1572 /* Continue and create new VM using data from VSD and downloaded object.
1573 * The downloaded images should be converted to VDI/VMDK if they have another format */
1574 try
1575 {
1576 Utf8Str strInstId("default cloud instance id");
1577 {
1578 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId)//aVBoxValues is set in this #define
1579 if (aVBoxValues.size() != 0)//paranoia but anyway...
1580 strInstId = aVBoxValues[0];
1581 LogRel(("%s: Importing cloud instance %s\n", __FUNCTION__, strInstId.c_str()));
1582 }
1583
1584 Utf8Str strGroup("/");//default VM group
1585 Utf8Str strTargetFormat("VMDK");//default image format
1586
1587 /* Based on the VM name, create a target machine path. */
1588 Bstr bstrSettingsFilename;
1589 hrc = mVirtualBox->ComposeMachineFilename(Bstr(strVMName).raw(),
1590 Bstr(strGroup).raw(),
1591 NULL /* aCreateFlags */,
1592 NULL /* aBaseFolder */,
1593 bstrSettingsFilename.asOutParam());
1594 if (FAILED(hrc)) throw hrc;
1595
1596 Utf8Str strMachineFolder(bstrSettingsFilename);
1597 strMachineFolder.stripFilename();
1598
1599 /* Get the system properties. */
1600 SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
1601
1602 ComObjPtr<MediumFormat> trgFormat;
1603 trgFormat = pSysProps->i_mediumFormatFromExtension(strTargetFormat);
1604
1605 /* Processing the downloaded object (prepare for the local import) */
1606 RTVFSIOSTREAM hVfsIosSrc;
1607 vrc = RTVfsIoStrmOpenNormal(strAbsSrcPath.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosSrc);
1608 if (RT_FAILURE(vrc))
1609 {
1610 strLastActualErrorDesc = Utf8StrFmt("Error opening '%s' for reading (%Rrc)\n", strAbsSrcPath.c_str(), vrc);
1611 throw setErrorVrc(vrc, strLastActualErrorDesc.c_str());
1612 }
1613
1614 vrc = RTZipTarFsStreamFromIoStream(hVfsIosSrc, 0 /*fFlags*/, &hVfsFssObject);
1615 RTVfsIoStrmRelease(hVfsIosSrc);
1616 if (RT_FAILURE(vrc))
1617 {
1618 strLastActualErrorDesc = Utf8StrFmt("Error reading the downloaded file '%s' (%Rrc)", strAbsSrcPath.c_str(), vrc);
1619 throw setErrorVrc(vrc, strLastActualErrorDesc.c_str());
1620 }
1621
1622 /* Create a new virtual system and work directly on the list copy. */
1623 m->pReader->m_llVirtualSystems.push_back(ovf::VirtualSystem());
1624 ovf::VirtualSystem &vsys = m->pReader->m_llVirtualSystems.back();
1625
1626 /* Try to re-use some OVF stuff here */
1627 {
1628 vsys.strName = strVMName;
1629 uint32_t cpus = 1;
1630 {
1631 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CPU)//aVBoxValues is set in this #define
1632 if (aVBoxValues.size() != 0)
1633 {
1634 vsdData = aVBoxValues[0];
1635 cpus = vsdData.toUInt32();
1636 }
1637 vsys.cCPUs = (uint16_t)cpus;
1638 LogRel(("%s: Number of CPUs is %s\n", __FUNCTION__, vsdData.c_str()));
1639 }
1640
1641 ULONG memory;//Mb
1642 pGuestOSType->COMGETTER(RecommendedRAM)(&memory);
1643 {
1644 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Memory)//aVBoxValues is set in this #define
1645 if (aVBoxValues.size() != 0)
1646 {
1647 vsdData = aVBoxValues[0];
1648 if (memory > vsdData.toUInt32())
1649 memory = vsdData.toUInt32();
1650 }
1651 vsys.ullMemorySize = memory;
1652 LogRel(("%s: Size of RAM is %dMB\n", __FUNCTION__, vsys.ullMemorySize));
1653 }
1654
1655 {
1656 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Description)//aVBoxValues is set in this #define
1657 if (aVBoxValues.size() != 0)
1658 {
1659 vsdData = aVBoxValues[0];
1660 vsys.strDescription = vsdData;
1661 }
1662 LogRel(("%s: VM description \'%s\'\n", __FUNCTION__, vsdData.c_str()));
1663 }
1664
1665 {
1666 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_OS)//aVBoxValues is set in this #define
1667 if (aVBoxValues.size() != 0)
1668 strOsType = aVBoxValues[0];
1669 vsys.strTypeVBox = strOsType;
1670 LogRel(("%s: OS type is %s\n", __FUNCTION__, strOsType.c_str()));
1671 }
1672
1673 ovf::EthernetAdapter ea;
1674 {
1675 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_NetworkAdapter)//aVBoxValues is set in this #define
1676 if (aVBoxValues.size() != 0)
1677 {
1678 ea.strAdapterType = (Utf8Str)(aVBoxValues[0]);
1679 ea.strNetworkName = "NAT";//default
1680 vsys.llEthernetAdapters.push_back(ea);
1681 LogRel(("%s: Network adapter type is %s\n", __FUNCTION__, ea.strAdapterType.c_str()));
1682 }
1683 else
1684 {
1685 NetworkAdapterType_T defaultAdapterType = NetworkAdapterType_Am79C970A;
1686 pGuestOSType->COMGETTER(AdapterType)(&defaultAdapterType);
1687 Utf8StrFmt dat("%RU32", (uint32_t)defaultAdapterType);
1688 vsd->AddDescription(VirtualSystemDescriptionType_NetworkAdapter,
1689 Bstr(dat).raw(),
1690 Bstr(Utf8Str("NAT")).raw());
1691 }
1692 }
1693
1694 ovf::HardDiskController hdc;
1695 {
1696 //It's thought that SATA is supported by any OS types
1697 hdc.system = ovf::HardDiskController::SATA;
1698 hdc.idController = 0;
1699
1700 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskControllerSATA)//aVBoxValues is set in this #define
1701 if (aVBoxValues.size() != 0)
1702 hdc.strControllerType = (Utf8Str)(aVBoxValues[0]);
1703 else
1704 hdc.strControllerType = "AHCI";
1705
1706 LogRel(("%s: Hard disk controller type is %s\n", __FUNCTION__, hdc.strControllerType.c_str()));
1707 vsys.mapControllers[hdc.idController] = hdc;
1708
1709 if (aVBoxValues.size() == 0)
1710 {
1711 /* we should do it here because it'll be used later in the OVF logic (inside i_importMachines()) */
1712 vsd->AddDescription(VirtualSystemDescriptionType_HardDiskControllerSATA,
1713 Bstr(hdc.strControllerType).raw(),
1714 Bstr(hdc.strControllerType).raw());
1715 }
1716 }
1717
1718 {
1719 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_SoundCard)//aVBoxValues is set in this #define
1720 if (aVBoxValues.size() != 0)
1721 vsys.strSoundCardType = (Utf8Str)(aVBoxValues[0]);
1722 else
1723 {
1724 AudioControllerType_T defaultAudioController;
1725 pGuestOSType->COMGETTER(RecommendedAudioController)(&defaultAudioController);
1726 vsys.strSoundCardType = Utf8StrFmt("%RU32", (uint32_t)defaultAudioController);//"ensoniq1371";//"AC97";
1727 vsd->AddDescription(VirtualSystemDescriptionType_SoundCard,
1728 Bstr(vsys.strSoundCardType).raw(),
1729 Bstr(vsys.strSoundCardType).raw());
1730 }
1731
1732 LogRel(("%s: Sound card is %s\n", __FUNCTION__, vsys.strSoundCardType.c_str()));
1733 }
1734
1735 vsys.fHasFloppyDrive = false;
1736 vsys.fHasCdromDrive = false;
1737 vsys.fHasUsbController = true;
1738 }
1739
1740 unsigned currImageObjectNum = 0;
1741 hrc = S_OK;
1742 do
1743 {
1744 char *pszName = NULL;
1745 RTVFSOBJTYPE enmType;
1746 vrc = RTVfsFsStrmNext(hVfsFssObject, &pszName, &enmType, &hVfsObj);
1747 if (RT_FAILURE(vrc))
1748 {
1749 if (vrc != VERR_EOF)
1750 {
1751 hrc = setErrorVrc(vrc, tr("%s: Error reading '%s' (%Rrc)"), __FUNCTION__, strAbsSrcPath.c_str(), vrc);
1752 throw hrc;
1753 }
1754 break;
1755 }
1756
1757 /* We only care about entries that are files. Get the I/O stream handle for them. */
1758 if ( enmType == RTVFSOBJTYPE_IO_STREAM
1759 || enmType == RTVFSOBJTYPE_FILE)
1760 {
1761 /* Find the suffix and check if this is a possibly interesting file. */
1762 char *pszSuffix = RTStrToLower(strrchr(pszName, '.'));
1763
1764 /* Get the I/O stream. */
1765 hVfsIosCurr = RTVfsObjToIoStream(hVfsObj);
1766 Assert(hVfsIosCurr != NIL_RTVFSIOSTREAM);
1767
1768 /* Get the source medium format */
1769 ComObjPtr<MediumFormat> srcFormat;
1770 srcFormat = pSysProps->i_mediumFormatFromExtension(pszSuffix + 1);
1771
1772 /* unknown image format so just extract a file without any processing */
1773 if (srcFormat == NULL)
1774 {
1775 /* Read the file into a memory buffer */
1776 void *pvBuffered;
1777 size_t cbBuffered;
1778 RTVFSFILE hVfsDstFile = NIL_RTVFSFILE;
1779 try
1780 {
1781 vrc = RTVfsIoStrmReadAll(hVfsIosCurr, &pvBuffered, &cbBuffered);
1782 RTVfsIoStrmRelease(hVfsIosCurr);
1783 hVfsIosCurr = NIL_RTVFSIOSTREAM;
1784 if (RT_FAILURE(vrc))
1785 throw setErrorVrc(vrc, tr("Could not read the file '%s' (%Rrc)"), strAbsSrcPath.c_str(), vrc);
1786
1787 Utf8StrFmt strAbsDstPath("%s%s%s", strMachineFolder.c_str(), RTPATH_SLASH_STR, pszName);
1788
1789 /* Simple logic - just try to get dir info, in case of absent try to create one.
1790 No deep errors analysis */
1791 RTFSOBJINFO dirInfo;
1792 vrc = RTPathQueryInfo(strMachineFolder.c_str(), &dirInfo, RTFSOBJATTRADD_NOTHING);
1793 if (RT_FAILURE(vrc))
1794 {
1795 if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
1796 {
1797 vrc = RTDirCreate(strMachineFolder.c_str(), 0755, 0);
1798 if (RT_FAILURE(vrc))
1799 throw setErrorVrc(vrc, tr("Could not create the directory '%s' (%Rrc)"),
1800 strMachineFolder.c_str(), vrc);
1801 }
1802 else
1803 throw setErrorVrc(vrc, tr("Error during getting info about the directory '%s' (%Rrc)"),
1804 strMachineFolder.c_str(), vrc);
1805 }
1806
1807 /* Write the file on the disk */
1808 vrc = RTVfsFileOpenNormal(strAbsDstPath.c_str(),
1809 RTFILE_O_WRITE | RTFILE_O_DENY_ALL | RTFILE_O_CREATE,
1810 &hVfsDstFile);
1811 if (RT_FAILURE(vrc))
1812 throw setErrorVrc(vrc, tr("Could not create the file '%s' (%Rrc)"), strAbsDstPath.c_str(), vrc);
1813
1814 size_t cbWritten;
1815 vrc = RTVfsFileWrite(hVfsDstFile, pvBuffered, cbBuffered, &cbWritten);
1816 if (RT_FAILURE(vrc))
1817 throw setErrorVrc(vrc, tr("Could not write into the file '%s' (%Rrc)"), strAbsDstPath.c_str(), vrc);
1818 }
1819 catch (HRESULT aRc)
1820 {
1821 hrc = aRc;
1822 strLastActualErrorDesc = Utf8StrFmt("%s: Processing the downloaded object was failed. "
1823 "The exception (%Rrc)\n", __FUNCTION__, hrc);
1824 LogRel((strLastActualErrorDesc.c_str()));
1825 }
1826 catch (int aRc)
1827 {
1828 hrc = setErrorVrc(aRc);
1829 strLastActualErrorDesc = Utf8StrFmt("%s: Processing the downloaded object was failed. "
1830 "The exception (%Rrc)\n", __FUNCTION__, aRc);
1831 LogRel((strLastActualErrorDesc.c_str()));
1832 }
1833 catch (...)
1834 {
1835 hrc = VERR_UNEXPECTED_EXCEPTION;
1836 strLastActualErrorDesc = Utf8StrFmt("%s: Processing the downloaded object was failed. "
1837 "The exception (%Rrc)\n", __FUNCTION__, hrc);
1838 LogRel((strLastActualErrorDesc.c_str()));
1839 }
1840
1841// /* Don't forget to release all resources */
1842// RTVfsFileRelease(hVfsDstFile);
1843// RTVfsIoStrmReadAllFree(pvBuffered, cbBuffered);
1844 }
1845 else
1846 {
1847 /* Image format is supported by VBox so extract the file and try to convert
1848 * one to the default format (which is VMDK for now) */
1849 Utf8Str z(bstrSettingsFilename);
1850 Utf8StrFmt strAbsDstPath("%s_%d.%s",
1851 z.stripSuffix().c_str(),
1852 currImageObjectNum,
1853 strTargetFormat.c_str());
1854
1855 hrc = mVirtualBox->i_findHardDiskByLocation(strAbsDstPath, false, NULL);
1856 if (SUCCEEDED(hrc))
1857 throw setError(VERR_ALREADY_EXISTS, tr("The hard disk '%s' already exists."), strAbsDstPath.c_str());
1858
1859 /* Create an IMedium object. */
1860 ComObjPtr<Medium> pTargetMedium;
1861 pTargetMedium.createObject();
1862 hrc = pTargetMedium->init(mVirtualBox,
1863 strTargetFormat,
1864 strAbsDstPath,
1865 Guid::Empty /* media registry: none yet */,
1866 DeviceType_HardDisk);
1867 if (FAILED(hrc))
1868 throw hrc;
1869
1870 pTask->pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"), pszName).raw(),
1871 200);
1872 ComObjPtr<Medium> nullParent;
1873 ComPtr<IProgress> pProgressImport;
1874 ComObjPtr<Progress> pProgressImportTmp;
1875 hrc = pProgressImportTmp.createObject();
1876 if (FAILED(hrc))
1877 throw hrc;
1878
1879 hrc = pProgressImportTmp->init(mVirtualBox,
1880 static_cast<IAppliance*>(this),
1881 Utf8StrFmt(tr("Importing medium '%s'"),
1882 pszName),
1883 TRUE);
1884 if (FAILED(hrc))
1885 throw hrc;
1886
1887 pProgressImportTmp.queryInterfaceTo(pProgressImport.asOutParam());
1888
1889 hrc = pTargetMedium->i_importFile(pszName,
1890 srcFormat,
1891 MediumVariant_Standard,
1892 hVfsIosCurr,
1893 nullParent,
1894 pProgressImportTmp,
1895 true /* aNotify */);
1896 RTVfsIoStrmRelease(hVfsIosCurr);
1897 hVfsIosCurr = NIL_RTVFSIOSTREAM;
1898 /* Now wait for the background import operation to complete;
1899 * this throws HRESULTs on error. */
1900 pTask->pProgress->WaitForOtherProgressCompletion(pProgressImport, 0 /* indefinite wait */);
1901
1902 /* Try to re-use some OVF stuff here */
1903 {
1904 /* Small trick here.
1905 * We add new item into the actual VSD after successful conversion.
1906 * There is no need to delete any previous records describing the images in the VSD
1907 * because later in the code the search of the images in the VSD will use such records
1908 * with the actual image id (d.strDiskId = pTargetMedium->i_getId().toString()) which is
1909 * used as a key for m->pReader->m_mapDisks, vsys.mapVirtualDisks.
1910 * So all 3 objects are tied via the image id.
1911 * In the OVF case we already have all such records in the VSD after reading OVF
1912 * description file (read() and interpret() functions).*/
1913 ovf::DiskImage d;
1914 d.strDiskId = pTargetMedium->i_getId().toString();
1915 d.strHref = pTargetMedium->i_getLocationFull();
1916 d.strFormat = pTargetMedium->i_getFormat();
1917 d.iSize = pTargetMedium->i_getSize();
1918 d.ulSuggestedSizeMB = (uint32_t)(d.iSize/_1M);
1919
1920 m->pReader->m_mapDisks[d.strDiskId] = d;
1921
1922 ComObjPtr<VirtualSystemDescription> vsdescThis = m->virtualSystemDescriptions.front();
1923
1924 /* It's needed here to use the internal function i_addEntry() instead of the API function
1925 * addDescription() because we should pass the d.strDiskId for the proper handling this
1926 * disk later in the i_importMachineGeneric():
1927 * - find the line like this "if (vsdeHD->strRef == diCurrent.strDiskId)".
1928 * if those code can be eliminated then addDescription() will be used. */
1929 vsdescThis->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
1930 d.strDiskId,
1931 d.strHref,
1932 d.strHref,
1933 d.ulSuggestedSizeMB);
1934
1935 ovf::VirtualDisk vd;
1936 vd.idController = vsys.mapControllers[0].idController;
1937 vd.ulAddressOnParent = 0;
1938 vd.strDiskId = d.strDiskId;
1939 vsys.mapVirtualDisks[vd.strDiskId] = vd;
1940
1941 }
1942
1943 ++currImageObjectNum;
1944 }
1945
1946 RTVfsIoStrmRelease(hVfsIosCurr);
1947 hVfsIosCurr = NIL_RTVFSIOSTREAM;
1948 }
1949
1950 RTVfsObjRelease(hVfsObj);
1951 hVfsObj = NIL_RTVFSOBJ;
1952
1953 RTStrFree(pszName);
1954
1955 } while (SUCCEEDED(hrc));
1956
1957 RTVfsFsStrmRelease(hVfsFssObject);
1958 hVfsFssObject = NIL_RTVFSFSSTREAM;
1959
1960 if (SUCCEEDED(hrc))
1961 {
1962 pTask->pProgress->SetNextOperation(BstrFmt(tr("Creating new VM '%s'", strVMName.c_str())).raw(), 50);
1963 /* Create the import stack to comply OVF logic.
1964 * Before we filled some other data structures which are needed by OVF logic too.*/
1965 ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, NIL_RTVFSFSSTREAM);
1966 i_importMachines(stack);
1967 }
1968
1969 }
1970 catch (HRESULT aRc)
1971 {
1972 hrc = aRc;
1973 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
1974 "The exception (%Rrc)\n", __FUNCTION__, hrc);
1975 LogRel((strLastActualErrorDesc.c_str()));
1976 }
1977 catch (int aRc)
1978 {
1979 hrc = setErrorVrc(aRc);
1980 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
1981 "The exception (%Rrc)\n", __FUNCTION__, aRc);
1982 LogRel((strLastActualErrorDesc.c_str()));
1983 }
1984 catch (...)
1985 {
1986 hrc = VERR_UNRESOLVED_ERROR;
1987 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
1988 "The exception (%Rrc)\n", __FUNCTION__, hrc);
1989 LogRel((strLastActualErrorDesc.c_str()));
1990 }
1991
1992 LogRel(("%s: Cloud import (local phase) final result (%Rrc).\n", __FUNCTION__, hrc));
1993
1994 if (FAILED(hrc))
1995 {
1996 /* Try to free VFS stuff because some of them might not be released due to the exception */
1997 if (hVfsIosCurr != NIL_RTVFSIOSTREAM)
1998 RTVfsIoStrmRelease(hVfsIosCurr);
1999 if (hVfsObj != NIL_RTVFSOBJ)
2000 RTVfsObjRelease(hVfsObj);
2001 if (hVfsFssObject != NIL_RTVFSFSSTREAM)
2002 RTVfsFsStrmRelease(hVfsFssObject);
2003
2004 /* What to do here?
2005 * Delete or not the downloaded object?
2006 * For now:
2007 * - check the list of imported images, detach them and next delete.
2008 * - check the registration of created VM and delete one.
2009 * - check some other leavings if they may exist and delete them too.
2010 */
2011
2012 /* Small explanation here.
2013 * After adding extracted files into the actual VSD the returned list will contain not only the
2014 * record about the downloaded object but also the records about the extracted files from this object.
2015 * It's needed to go through this list to find the record about the downloaded object.
2016 * But it was the first record added into the list, so aVBoxValues[0] should be correct here.
2017 */
2018 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage)//aVBoxValues is set in this #define
2019 if (aVBoxValues.size() != 0)
2020 hrc = setError(hrc, "%s: Cloud import (local phase) failed. Find the root cause in the log file. "
2021 "But the first phase was successful and the downloaded object was stored as \'%s\'\n",
2022 __FUNCTION__, Utf8Str(aVBoxValues[0]).c_str());
2023 }
2024 }
2025
2026 /* Reset the state so others can call methods again */
2027// m->state = ApplianceIdle;
2028
2029 LogFlowFunc(("rc=%Rhrc\n", hrc));
2030 LogFlowFuncLeave();
2031 return hrc;
2032}
2033
2034/**
2035 * Actual worker code for reading an OVF from disk. This is called from Appliance::taskThreadImportOrExport()
2036 * and therefore runs on the OVF read worker thread. This opens the OVF with ovfreader.cpp.
2037 *
2038 * This runs in one context:
2039 *
2040 * 1) in a first worker thread; in that case, Appliance::Read() called Appliance::readImpl();
2041 *
2042 * @param pTask
2043 * @return
2044 */
2045HRESULT Appliance::i_readFS(TaskOVF *pTask)
2046{
2047 LogFlowFuncEnter();
2048 LogFlowFunc(("Appliance %p\n", this));
2049
2050 AutoCaller autoCaller(this);
2051 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2052
2053 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
2054
2055 HRESULT rc;
2056 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
2057 rc = i_readFSOVF(pTask);
2058 else
2059 rc = i_readFSOVA(pTask);
2060
2061 LogFlowFunc(("rc=%Rhrc\n", rc));
2062 LogFlowFuncLeave();
2063
2064 return rc;
2065}
2066
2067HRESULT Appliance::i_readFSOVF(TaskOVF *pTask)
2068{
2069 LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
2070
2071 /*
2072 * Allocate a buffer for filenames and prep it for suffix appending.
2073 */
2074 char *pszNameBuf = (char *)alloca(pTask->locInfo.strPath.length() + 16);
2075 AssertReturn(pszNameBuf, VERR_NO_TMP_MEMORY);
2076 memcpy(pszNameBuf, pTask->locInfo.strPath.c_str(), pTask->locInfo.strPath.length() + 1);
2077 RTPathStripSuffix(pszNameBuf);
2078 size_t const cchBaseName = strlen(pszNameBuf);
2079
2080 /*
2081 * Open the OVF file first since that is what this is all about.
2082 */
2083 RTVFSIOSTREAM hIosOvf;
2084 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
2085 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosOvf);
2086 if (RT_FAILURE(vrc))
2087 return setErrorVrc(vrc, tr("Failed to open OVF file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2088
2089 HRESULT hrc = i_readOVFFile(pTask, hIosOvf, RTPathFilename(pTask->locInfo.strPath.c_str())); /* consumes hIosOvf */
2090 if (FAILED(hrc))
2091 return hrc;
2092
2093 /*
2094 * Try open the manifest file (for signature purposes and to determine digest type(s)).
2095 */
2096 RTVFSIOSTREAM hIosMf;
2097 strcpy(&pszNameBuf[cchBaseName], ".mf");
2098 vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosMf);
2099 if (RT_SUCCESS(vrc))
2100 {
2101 const char * const pszFilenamePart = RTPathFilename(pszNameBuf);
2102 hrc = i_readManifestFile(pTask, hIosMf /*consumed*/, pszFilenamePart);
2103 if (FAILED(hrc))
2104 return hrc;
2105
2106 /*
2107 * Check for the signature file.
2108 */
2109 RTVFSIOSTREAM hIosCert;
2110 strcpy(&pszNameBuf[cchBaseName], ".cert");
2111 vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosCert);
2112 if (RT_SUCCESS(vrc))
2113 {
2114 hrc = i_readSignatureFile(pTask, hIosCert /*consumed*/, pszFilenamePart);
2115 if (FAILED(hrc))
2116 return hrc;
2117 }
2118 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
2119 return setErrorVrc(vrc, tr("Failed to open the signature file '%s' (%Rrc)"), pszNameBuf, vrc);
2120
2121 }
2122 else if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
2123 {
2124 m->fDeterminedDigestTypes = true;
2125 m->fDigestTypes = 0;
2126 }
2127 else
2128 return setErrorVrc(vrc, tr("Failed to open the manifest file '%s' (%Rrc)"), pszNameBuf, vrc);
2129
2130 /*
2131 * Do tail processing (check the signature).
2132 */
2133 hrc = i_readTailProcessing(pTask);
2134
2135 LogFlowFunc(("returns %Rhrc\n", hrc));
2136 return hrc;
2137}
2138
2139HRESULT Appliance::i_readFSOVA(TaskOVF *pTask)
2140{
2141 LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
2142
2143 /*
2144 * Open the tar file as file stream.
2145 */
2146 RTVFSIOSTREAM hVfsIosOva;
2147 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
2148 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
2149 if (RT_FAILURE(vrc))
2150 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2151
2152 RTVFSFSSTREAM hVfsFssOva;
2153 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
2154 RTVfsIoStrmRelease(hVfsIosOva);
2155 if (RT_FAILURE(vrc))
2156 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2157
2158 /*
2159 * Since jumping thru an OVA file with seekable disk backing is rather
2160 * efficient, we can process .ovf, .mf and .cert files here without any
2161 * strict ordering restrictions.
2162 *
2163 * (Technically, the .ovf-file comes first, while the manifest and its
2164 * optional signature file either follows immediately or at the very end of
2165 * the OVA. The manifest is optional.)
2166 */
2167 char *pszOvfNameBase = NULL;
2168 size_t cchOvfNameBase = 0; NOREF(cchOvfNameBase);
2169 unsigned cLeftToFind = 3;
2170 HRESULT hrc = S_OK;
2171 do
2172 {
2173 char *pszName = NULL;
2174 RTVFSOBJTYPE enmType;
2175 RTVFSOBJ hVfsObj;
2176 vrc = RTVfsFsStrmNext(hVfsFssOva, &pszName, &enmType, &hVfsObj);
2177 if (RT_FAILURE(vrc))
2178 {
2179 if (vrc != VERR_EOF)
2180 hrc = setErrorVrc(vrc, tr("Error reading OVA '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2181 break;
2182 }
2183
2184 /* We only care about entries that are files. Get the I/O stream handle for them. */
2185 if ( enmType == RTVFSOBJTYPE_IO_STREAM
2186 || enmType == RTVFSOBJTYPE_FILE)
2187 {
2188 /* Find the suffix and check if this is a possibly interesting file. */
2189 char *pszSuffix = strrchr(pszName, '.');
2190 if ( pszSuffix
2191 && ( RTStrICmp(pszSuffix + 1, "ovf") == 0
2192 || RTStrICmp(pszSuffix + 1, "mf") == 0
2193 || RTStrICmp(pszSuffix + 1, "cert") == 0) )
2194 {
2195 /* Match the OVF base name. */
2196 *pszSuffix = '\0';
2197 if ( pszOvfNameBase == NULL
2198 || RTStrICmp(pszName, pszOvfNameBase) == 0)
2199 {
2200 *pszSuffix = '.';
2201
2202 /* Since we're pretty sure we'll be processing this file, get the I/O stream. */
2203 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
2204 Assert(hVfsIos != NIL_RTVFSIOSTREAM);
2205
2206 /* Check for the OVF (should come first). */
2207 if (RTStrICmp(pszSuffix + 1, "ovf") == 0)
2208 {
2209 if (pszOvfNameBase == NULL)
2210 {
2211 hrc = i_readOVFFile(pTask, hVfsIos, pszName);
2212 hVfsIos = NIL_RTVFSIOSTREAM;
2213
2214 /* Set the base name. */
2215 *pszSuffix = '\0';
2216 pszOvfNameBase = pszName;
2217 cchOvfNameBase = strlen(pszName);
2218 pszName = NULL;
2219 cLeftToFind--;
2220 }
2221 else
2222 LogRel(("i_readFSOVA: '%s' contains more than one OVF file ('%s'), picking the first one\n",
2223 pTask->locInfo.strPath.c_str(), pszName));
2224 }
2225 /* Check for manifest. */
2226 else if (RTStrICmp(pszSuffix + 1, "mf") == 0)
2227 {
2228 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
2229 {
2230 hrc = i_readManifestFile(pTask, hVfsIos, pszName);
2231 hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
2232 cLeftToFind--;
2233 }
2234 else
2235 LogRel(("i_readFSOVA: '%s' contains more than one manifest file ('%s'), picking the first one\n",
2236 pTask->locInfo.strPath.c_str(), pszName));
2237 }
2238 /* Check for signature. */
2239 else if (RTStrICmp(pszSuffix + 1, "cert") == 0)
2240 {
2241 if (!m->fSignerCertLoaded)
2242 {
2243 hrc = i_readSignatureFile(pTask, hVfsIos, pszName);
2244 hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
2245 cLeftToFind--;
2246 }
2247 else
2248 LogRel(("i_readFSOVA: '%s' contains more than one signature file ('%s'), picking the first one\n",
2249 pTask->locInfo.strPath.c_str(), pszName));
2250 }
2251 else
2252 AssertFailed();
2253 if (hVfsIos != NIL_RTVFSIOSTREAM)
2254 RTVfsIoStrmRelease(hVfsIos);
2255 }
2256 }
2257 }
2258 RTVfsObjRelease(hVfsObj);
2259 RTStrFree(pszName);
2260 } while (cLeftToFind > 0 && SUCCEEDED(hrc));
2261
2262 RTVfsFsStrmRelease(hVfsFssOva);
2263 RTStrFree(pszOvfNameBase);
2264
2265 /*
2266 * Check that we found and OVF file.
2267 */
2268 if (SUCCEEDED(hrc) && !pszOvfNameBase)
2269 hrc = setError(VBOX_E_FILE_ERROR, tr("OVA '%s' does not contain an .ovf-file"), pTask->locInfo.strPath.c_str());
2270 if (SUCCEEDED(hrc))
2271 {
2272 /*
2273 * Do tail processing (check the signature).
2274 */
2275 hrc = i_readTailProcessing(pTask);
2276 }
2277 LogFlowFunc(("returns %Rhrc\n", hrc));
2278 return hrc;
2279}
2280
2281/**
2282 * Reads & parses the OVF file.
2283 *
2284 * @param pTask The read task.
2285 * @param hVfsIosOvf The I/O stream for the OVF. The reference is
2286 * always consumed.
2287 * @param pszManifestEntry The manifest entry name.
2288 * @returns COM status code, error info set.
2289 * @throws Nothing
2290 */
2291HRESULT Appliance::i_readOVFFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosOvf, const char *pszManifestEntry)
2292{
2293 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszManifestEntry));
2294
2295 /*
2296 * Set the OVF manifest entry name (needed for tweaking the manifest
2297 * validation during import).
2298 */
2299 try { m->strOvfManifestEntry = pszManifestEntry; }
2300 catch (...) { return E_OUTOFMEMORY; }
2301
2302 /*
2303 * Set up digest calculation.
2304 */
2305 hVfsIosOvf = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosOvf, pszManifestEntry);
2306 if (hVfsIosOvf == NIL_RTVFSIOSTREAM)
2307 return VBOX_E_FILE_ERROR;
2308
2309 /*
2310 * Read the OVF into a memory buffer and parse it.
2311 */
2312 void *pvBufferedOvf;
2313 size_t cbBufferedOvf;
2314 int vrc = RTVfsIoStrmReadAll(hVfsIosOvf, &pvBufferedOvf, &cbBufferedOvf);
2315 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosOvf); /* consumes stream handle. */
2316 NOREF(cRefs);
2317 Assert(cRefs == 0);
2318 if (RT_FAILURE(vrc))
2319 return setErrorVrc(vrc, tr("Could not read the OVF file for '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2320
2321 HRESULT hrc;
2322 try
2323 {
2324 m->pReader = new ovf::OVFReader(pvBufferedOvf, cbBufferedOvf, pTask->locInfo.strPath);
2325 hrc = S_OK;
2326 }
2327 catch (RTCError &rXcpt) // includes all XML exceptions
2328 {
2329 hrc = setError(VBOX_E_FILE_ERROR, rXcpt.what());
2330 }
2331 catch (HRESULT aRC)
2332 {
2333 hrc = aRC;
2334 }
2335 catch (...)
2336 {
2337 hrc = E_FAIL;
2338 }
2339 LogFlowFunc(("OVFReader(%s) -> rc=%Rhrc\n", pTask->locInfo.strPath.c_str(), hrc));
2340
2341 RTVfsIoStrmReadAllFree(pvBufferedOvf, cbBufferedOvf);
2342 if (SUCCEEDED(hrc))
2343 {
2344 /*
2345 * If we see an OVF v2.0 envelope, select only the SHA-256 digest.
2346 */
2347 if ( !m->fDeterminedDigestTypes
2348 && m->pReader->m_envelopeData.getOVFVersion() == ovf::OVFVersion_2_0)
2349 m->fDigestTypes &= ~RTMANIFEST_ATTR_SHA256;
2350 }
2351
2352 return hrc;
2353}
2354
2355/**
2356 * Reads & parses the manifest file.
2357 *
2358 * @param pTask The read task.
2359 * @param hVfsIosMf The I/O stream for the manifest file. The
2360 * reference is always consumed.
2361 * @param pszSubFileNm The manifest filename (no path) for error
2362 * messages and logging.
2363 * @returns COM status code, error info set.
2364 * @throws Nothing
2365 */
2366HRESULT Appliance::i_readManifestFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosMf, const char *pszSubFileNm)
2367{
2368 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
2369
2370 /*
2371 * Copy the manifest into a memory backed file so we can later do signature
2372 * validation indepentend of the algorithms used by the signature.
2373 */
2374 int vrc = RTVfsMemorizeIoStreamAsFile(hVfsIosMf, RTFILE_O_READ, &m->hMemFileTheirManifest);
2375 RTVfsIoStrmRelease(hVfsIosMf); /* consumes stream handle. */
2376 if (RT_FAILURE(vrc))
2377 return setErrorVrc(vrc, tr("Error reading the manifest file '%s' for '%s' (%Rrc)"),
2378 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
2379
2380 /*
2381 * Parse the manifest.
2382 */
2383 Assert(m->hTheirManifest == NIL_RTMANIFEST);
2384 vrc = RTManifestCreate(0 /*fFlags*/, &m->hTheirManifest);
2385 AssertStmt(RT_SUCCESS(vrc), Global::vboxStatusCodeToCOM(vrc));
2386
2387 char szErr[256];
2388 RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(m->hMemFileTheirManifest);
2389 vrc = RTManifestReadStandardEx(m->hTheirManifest, hVfsIos, szErr, sizeof(szErr));
2390 RTVfsIoStrmRelease(hVfsIos);
2391 if (RT_FAILURE(vrc))
2392 throw setErrorVrc(vrc, tr("Failed to parse manifest file '%s' for '%s' (%Rrc): %s"),
2393 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, szErr);
2394
2395 /*
2396 * Check which digest files are used.
2397 * Note! the file could be empty, in which case fDigestTypes is set to 0.
2398 */
2399 vrc = RTManifestQueryAllAttrTypes(m->hTheirManifest, true /*fEntriesOnly*/, &m->fDigestTypes);
2400 AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc));
2401 m->fDeterminedDigestTypes = true;
2402
2403 return S_OK;
2404}
2405
2406/**
2407 * Reads the signature & certificate file.
2408 *
2409 * @param pTask The read task.
2410 * @param hVfsIosCert The I/O stream for the signature file. The
2411 * reference is always consumed.
2412 * @param pszSubFileNm The signature filename (no path) for error
2413 * messages and logging. Used to construct
2414 * .mf-file name.
2415 * @returns COM status code, error info set.
2416 * @throws Nothing
2417 */
2418HRESULT Appliance::i_readSignatureFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosCert, const char *pszSubFileNm)
2419{
2420 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
2421
2422 /*
2423 * Construct the manifest filename from pszSubFileNm.
2424 */
2425 Utf8Str strManifestName;
2426 try
2427 {
2428 const char *pszSuffix = strrchr(pszSubFileNm, '.');
2429 AssertReturn(pszSuffix, E_FAIL);
2430 strManifestName = Utf8Str(pszSubFileNm, pszSuffix - pszSubFileNm);
2431 strManifestName.append(".mf");
2432 }
2433 catch (...)
2434 {
2435 return E_OUTOFMEMORY;
2436 }
2437
2438 /*
2439 * Copy the manifest into a memory buffer. We'll do the signature processing
2440 * later to not force any specific order in the OVAs or any other archive we
2441 * may be accessing later.
2442 */
2443 void *pvSignature;
2444 size_t cbSignature;
2445 int vrc = RTVfsIoStrmReadAll(hVfsIosCert, &pvSignature, &cbSignature);
2446 RTVfsIoStrmRelease(hVfsIosCert); /* consumes stream handle. */
2447 if (RT_FAILURE(vrc))
2448 return setErrorVrc(vrc, tr("Error reading the signature file '%s' for '%s' (%Rrc)"),
2449 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
2450
2451 /*
2452 * Parse the signing certificate. Unlike the manifest parser we use below,
2453 * this API ignores parse of the file that aren't relevant.
2454 */
2455 RTERRINFOSTATIC StaticErrInfo;
2456 vrc = RTCrX509Certificate_ReadFromBuffer(&m->SignerCert, pvSignature, cbSignature,
2457 RTCRX509CERT_READ_F_PEM_ONLY,
2458 &g_RTAsn1DefaultAllocator, RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
2459 HRESULT hrc;
2460 if (RT_SUCCESS(vrc))
2461 {
2462 m->fSignerCertLoaded = true;
2463 m->fCertificateIsSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->SignerCert);
2464
2465 /*
2466 * Find the start of the certificate part of the file, so we can avoid
2467 * upsetting the manifest parser with it.
2468 */
2469 char *pszSplit = (char *)RTCrPemFindFirstSectionInContent(pvSignature, cbSignature,
2470 g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers);
2471 if (pszSplit)
2472 while ( pszSplit != (char *)pvSignature
2473 && pszSplit[-1] != '\n'
2474 && pszSplit[-1] != '\r')
2475 pszSplit--;
2476 else
2477 {
2478 AssertLogRelMsgFailed(("Failed to find BEGIN CERTIFICATE markers in '%s'::'%s' - impossible unless it's a DER encoded certificate!",
2479 pTask->locInfo.strPath.c_str(), pszSubFileNm));
2480 pszSplit = (char *)pvSignature + cbSignature;
2481 }
2482 *pszSplit = '\0';
2483
2484 /*
2485 * Now, read the manifest part. We use the IPRT manifest reader here
2486 * to avoid duplicating code and be somewhat flexible wrt the digest
2487 * type choosen by the signer.
2488 */
2489 RTMANIFEST hSignedDigestManifest;
2490 vrc = RTManifestCreate(0 /*fFlags*/, &hSignedDigestManifest);
2491 if (RT_SUCCESS(vrc))
2492 {
2493 RTVFSIOSTREAM hVfsIosTmp;
2494 vrc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pvSignature, pszSplit - (char *)pvSignature, &hVfsIosTmp);
2495 if (RT_SUCCESS(vrc))
2496 {
2497 vrc = RTManifestReadStandardEx(hSignedDigestManifest, hVfsIosTmp, StaticErrInfo.szMsg, sizeof(StaticErrInfo.szMsg));
2498 RTVfsIoStrmRelease(hVfsIosTmp);
2499 if (RT_SUCCESS(vrc))
2500 {
2501 /*
2502 * Get signed digest, we prefer SHA-2, so explicitly query those first.
2503 */
2504 uint32_t fDigestType;
2505 char szSignedDigest[_8K + 1];
2506 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
2507 RTMANIFEST_ATTR_SHA512 | RTMANIFEST_ATTR_SHA256,
2508 szSignedDigest, sizeof(szSignedDigest), &fDigestType);
2509 if (vrc == VERR_MANIFEST_ATTR_TYPE_NOT_FOUND)
2510 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
2511 RTMANIFEST_ATTR_ANY, szSignedDigest, sizeof(szSignedDigest), &fDigestType);
2512 if (RT_SUCCESS(vrc))
2513 {
2514 const char *pszSignedDigest = RTStrStrip(szSignedDigest);
2515 size_t cbSignedDigest = strlen(pszSignedDigest) / 2;
2516 uint8_t abSignedDigest[sizeof(szSignedDigest) / 2];
2517 vrc = RTStrConvertHexBytes(szSignedDigest, abSignedDigest, cbSignedDigest, 0 /*fFlags*/);
2518 if (RT_SUCCESS(vrc))
2519 {
2520 /*
2521 * Convert it to RTDIGESTTYPE_XXX and save the binary value for later use.
2522 */
2523 switch (fDigestType)
2524 {
2525 case RTMANIFEST_ATTR_SHA1: m->enmSignedDigestType = RTDIGESTTYPE_SHA1; break;
2526 case RTMANIFEST_ATTR_SHA256: m->enmSignedDigestType = RTDIGESTTYPE_SHA256; break;
2527 case RTMANIFEST_ATTR_SHA512: m->enmSignedDigestType = RTDIGESTTYPE_SHA512; break;
2528 case RTMANIFEST_ATTR_MD5: m->enmSignedDigestType = RTDIGESTTYPE_MD5; break;
2529 default: AssertFailed(); m->enmSignedDigestType = RTDIGESTTYPE_INVALID; break;
2530 }
2531 if (m->enmSignedDigestType != RTDIGESTTYPE_INVALID)
2532 {
2533 m->pbSignedDigest = (uint8_t *)RTMemDup(abSignedDigest, cbSignedDigest);
2534 m->cbSignedDigest = cbSignedDigest;
2535 hrc = S_OK;
2536 }
2537 else
2538 hrc = setError(E_FAIL, tr("Unsupported signed digest type (%#x)"), fDigestType);
2539 }
2540 else
2541 hrc = setErrorVrc(vrc, tr("Error reading signed manifest digest: %Rrc"), vrc);
2542 }
2543 else if (vrc == VERR_NOT_FOUND)
2544 hrc = setErrorVrc(vrc, tr("Could not locate signed digest for '%s' in the cert-file for '%s'"),
2545 strManifestName.c_str(), pTask->locInfo.strPath.c_str());
2546 else
2547 hrc = setErrorVrc(vrc, tr("RTManifestEntryQueryAttr failed unexpectedly: %Rrc"), vrc);
2548 }
2549 else
2550 hrc = setErrorVrc(vrc, tr("Error parsing the .cert-file for '%s': %s"),
2551 pTask->locInfo.strPath.c_str(), StaticErrInfo.szMsg);
2552 }
2553 else
2554 hrc = E_OUTOFMEMORY;
2555 RTManifestRelease(hSignedDigestManifest);
2556 }
2557 else
2558 hrc = E_OUTOFMEMORY;
2559 }
2560 else if (vrc == VERR_NOT_FOUND || vrc == VERR_EOF)
2561 hrc = setErrorBoth(E_FAIL, vrc, tr("Malformed .cert-file for '%s': Signer's certificate not found (%Rrc)"),
2562 pTask->locInfo.strPath.c_str(), vrc);
2563 else
2564 hrc = setErrorVrc(vrc, tr("Error reading the signer's certificate from '%s' for '%s' (%Rrc): %s"),
2565 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
2566
2567 RTVfsIoStrmReadAllFree(pvSignature, cbSignature);
2568 LogFlowFunc(("returns %Rhrc (%Rrc)\n", hrc, vrc));
2569 return hrc;
2570}
2571
2572
2573/**
2574 * Does tail processing after the files have been read in.
2575 *
2576 * @param pTask The read task.
2577 * @returns COM status.
2578 * @throws Nothing!
2579 */
2580HRESULT Appliance::i_readTailProcessing(TaskOVF *pTask)
2581{
2582 /*
2583 * Parse and validate the signature file.
2584 *
2585 * The signature file has two parts, manifest part and a PEM encoded
2586 * certificate. The former contains an entry for the manifest file with a
2587 * digest that is encrypted with the certificate in the latter part.
2588 */
2589 if (m->pbSignedDigest)
2590 {
2591 /* Since we're validating the digest of the manifest, there have to be
2592 a manifest. We cannot allow a the manifest to be missing. */
2593 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
2594 return setError(VBOX_E_FILE_ERROR, tr("Found .cert-file but no .mf-file for '%s'"), pTask->locInfo.strPath.c_str());
2595
2596 /*
2597 * Validate the signed digest.
2598 *
2599 * It's possible we should allow the user to ignore signature
2600 * mismatches, but for now it is a solid show stopper.
2601 */
2602 HRESULT hrc;
2603 RTERRINFOSTATIC StaticErrInfo;
2604
2605 /* Calc the digest of the manifest using the algorithm found above. */
2606 RTCRDIGEST hDigest;
2607 int vrc = RTCrDigestCreateByType(&hDigest, m->enmSignedDigestType);
2608 if (RT_SUCCESS(vrc))
2609 {
2610 vrc = RTCrDigestUpdateFromVfsFile(hDigest, m->hMemFileTheirManifest, true /*fRewindFile*/);
2611 if (RT_SUCCESS(vrc))
2612 {
2613 /* Compare the signed digest with the one we just calculated. (This
2614 API will do the verification twice, once using IPRT's own crypto
2615 and once using OpenSSL. Both must OK it for success.) */
2616 vrc = RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(&m->SignerCert.TbsCertificate.SubjectPublicKeyInfo,
2617 m->pbSignedDigest, m->cbSignedDigest, hDigest,
2618 RTErrInfoInitStatic(&StaticErrInfo));
2619 if (RT_SUCCESS(vrc))
2620 {
2621 m->fSignatureValid = true;
2622 hrc = S_OK;
2623 }
2624 else if (vrc == VERR_CR_PKIX_SIGNATURE_MISMATCH)
2625 hrc = setErrorVrc(vrc, tr("The manifest signature does not match"));
2626 else
2627 hrc = setErrorVrc(vrc,
2628 tr("Error validating the manifest signature (%Rrc, %s)"), vrc, StaticErrInfo.Core.pszMsg);
2629 }
2630 else
2631 hrc = setErrorVrc(vrc, tr("RTCrDigestUpdateFromVfsFile failed: %Rrc"), vrc);
2632 RTCrDigestRelease(hDigest);
2633 }
2634 else
2635 hrc = setErrorVrc(vrc, tr("RTCrDigestCreateByType failed: %Rrc"), vrc);
2636
2637 /*
2638 * Validate the certificate.
2639 *
2640 * We don't fail here on if we cannot validate the certificate, we postpone
2641 * that till the import stage, so that we can allow the user to ignore it.
2642 *
2643 * The certificate validity time is deliberately left as warnings as the
2644 * OVF specification does not provision for any timestamping of the
2645 * signature. This is course a security concern, but the whole signing
2646 * of OVFs is currently weirdly trusting (self signed * certs), so this
2647 * is the least of our current problems.
2648 *
2649 * While we try build and verify certificate paths properly, the
2650 * "neighbours" quietly ignores this and seems only to check the signature
2651 * and not whether the certificate is trusted. Also, we don't currently
2652 * complain about self-signed certificates either (ditto "neighbours").
2653 * The OVF creator is also a bit restricted wrt to helping us build the
2654 * path as he cannot supply intermediate certificates. Anyway, we issue
2655 * warnings (goes to /dev/null, am I right?) for self-signed certificates
2656 * and certificates we cannot build and verify a root path for.
2657 *
2658 * (The OVF sillibuggers should've used PKCS#7, CMS or something else
2659 * that's already been standardized instead of combining manifests with
2660 * certificate PEM files in some very restrictive manner! I wonder if
2661 * we could add a PKCS#7 section to the .cert file in addition to the CERT
2662 * and manifest stuff dictated by the standard. Would depend on how others
2663 * deal with it.)
2664 */
2665 Assert(!m->fCertificateValid);
2666 Assert(m->fCertificateMissingPath);
2667 Assert(!m->fCertificateValidTime);
2668 Assert(m->strCertError.isEmpty());
2669 Assert(m->fCertificateIsSelfSigned == RTCrX509Certificate_IsSelfSigned(&m->SignerCert));
2670
2671 HRESULT hrc2 = S_OK;
2672 if (m->fCertificateIsSelfSigned)
2673 {
2674 /*
2675 * It's a self signed certificate. We assume the frontend will
2676 * present this fact to the user and give a choice whether this
2677 * is acceptible. But, first make sure it makes internal sense.
2678 */
2679 m->fCertificateMissingPath = true; /** @todo need to check if the certificate is trusted by the system! */
2680 vrc = RTCrX509Certificate_VerifySignatureSelfSigned(&m->SignerCert, RTErrInfoInitStatic(&StaticErrInfo));
2681 if (RT_SUCCESS(vrc))
2682 {
2683 m->fCertificateValid = true;
2684
2685 /* Check whether the certificate is currently valid, just warn if not. */
2686 RTTIMESPEC Now;
2687 if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
2688 {
2689 m->fCertificateValidTime = true;
2690 i_addWarning(tr("A self signed certificate was used to sign '%s'"), pTask->locInfo.strPath.c_str());
2691 }
2692 else
2693 i_addWarning(tr("Self signed certificate used to sign '%s' is not currently valid"),
2694 pTask->locInfo.strPath.c_str());
2695
2696 /* Just warn if it's not a CA. Self-signed certificates are
2697 hardly trustworthy to start with without the user's consent. */
2698 if ( !m->SignerCert.TbsCertificate.T3.pBasicConstraints
2699 || !m->SignerCert.TbsCertificate.T3.pBasicConstraints->CA.fValue)
2700 i_addWarning(tr("Self signed certificate used to sign '%s' is not marked as certificate authority (CA)"),
2701 pTask->locInfo.strPath.c_str());
2702 }
2703 else
2704 {
2705 try { m->strCertError = Utf8StrFmt(tr("Verification of the self signed certificate failed (%Rrc, %s)"),
2706 vrc, StaticErrInfo.Core.pszMsg); }
2707 catch (...) { AssertFailed(); }
2708 i_addWarning(tr("Verification of the self signed certificate used to sign '%s' failed (%Rrc): %s"),
2709 pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
2710 }
2711 }
2712 else
2713 {
2714 /*
2715 * The certificate is not self-signed. Use the system certificate
2716 * stores to try build a path that validates successfully.
2717 */
2718 RTCRX509CERTPATHS hCertPaths;
2719 vrc = RTCrX509CertPathsCreate(&hCertPaths, &m->SignerCert);
2720 if (RT_SUCCESS(vrc))
2721 {
2722 /* Get trusted certificates from the system and add them to the path finding mission. */
2723 RTCRSTORE hTrustedCerts;
2724 vrc = RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(&hTrustedCerts,
2725 RTErrInfoInitStatic(&StaticErrInfo));
2726 if (RT_SUCCESS(vrc))
2727 {
2728 vrc = RTCrX509CertPathsSetTrustedStore(hCertPaths, hTrustedCerts);
2729 if (RT_FAILURE(vrc))
2730 hrc2 = setErrorBoth(E_FAIL, vrc, tr("RTCrX509CertPathsSetTrustedStore failed (%Rrc)"), vrc);
2731 RTCrStoreRelease(hTrustedCerts);
2732 }
2733 else
2734 hrc2 = setErrorBoth(E_FAIL, vrc,
2735 tr("Failed to query trusted CAs and Certificates from the system and for the current user (%Rrc, %s)"),
2736 vrc, StaticErrInfo.Core.pszMsg);
2737
2738 /* Add untrusted intermediate certificates. */
2739 if (RT_SUCCESS(vrc))
2740 {
2741 /// @todo RTCrX509CertPathsSetUntrustedStore(hCertPaths, hAdditionalCerts);
2742 /// By scanning for additional certificates in the .cert file? It would be
2743 /// convenient to be able to supply intermediate certificates for the user,
2744 /// right? Or would that be unacceptable as it may weaken security?
2745 ///
2746 /// Anyway, we should look for intermediate certificates on the system, at
2747 /// least.
2748 }
2749 if (RT_SUCCESS(vrc))
2750 {
2751 /*
2752 * Do the building and verification of certificate paths.
2753 */
2754 vrc = RTCrX509CertPathsBuild(hCertPaths, RTErrInfoInitStatic(&StaticErrInfo));
2755 if (RT_SUCCESS(vrc))
2756 {
2757 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(&StaticErrInfo));
2758 if (RT_SUCCESS(vrc))
2759 {
2760 /*
2761 * Mark the certificate as good.
2762 */
2763 /** @todo check the certificate purpose? If so, share with self-signed. */
2764 m->fCertificateValid = true;
2765 m->fCertificateMissingPath = false;
2766
2767 /*
2768 * We add a warning if the certificate path isn't valid at the current
2769 * time. Since the time is only considered during path validation and we
2770 * can repeat the validation process (but not building), it's easy to check.
2771 */
2772 RTTIMESPEC Now;
2773 vrc = RTCrX509CertPathsSetValidTimeSpec(hCertPaths, RTTimeNow(&Now));
2774 if (RT_SUCCESS(vrc))
2775 {
2776 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(&StaticErrInfo));
2777 if (RT_SUCCESS(vrc))
2778 m->fCertificateValidTime = true;
2779 else
2780 i_addWarning(tr("The certificate used to sign '%s' (or a certificate in the path) is not currently valid (%Rrc)"),
2781 pTask->locInfo.strPath.c_str(), vrc);
2782 }
2783 else
2784 hrc2 = setErrorVrc(vrc, "RTCrX509CertPathsSetValidTimeSpec failed: %Rrc", vrc);
2785 }
2786 else if (vrc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS)
2787 {
2788 m->fCertificateValid = true;
2789 i_addWarning(tr("No trusted certificate paths"));
2790
2791 /* Add another warning if the pathless certificate is not valid at present. */
2792 RTTIMESPEC Now;
2793 if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
2794 m->fCertificateValidTime = true;
2795 else
2796 i_addWarning(tr("The certificate used to sign '%s' is not currently valid"),
2797 pTask->locInfo.strPath.c_str());
2798 }
2799 else
2800 hrc2 = setErrorBoth(E_FAIL, vrc, tr("Certificate path validation failed (%Rrc, %s)"),
2801 vrc, StaticErrInfo.Core.pszMsg);
2802 }
2803 else
2804 hrc2 = setErrorBoth(E_FAIL, vrc, tr("Certificate path building failed (%Rrc, %s)"),
2805 vrc, StaticErrInfo.Core.pszMsg);
2806 }
2807 RTCrX509CertPathsRelease(hCertPaths);
2808 }
2809 else
2810 hrc2 = setErrorVrc(vrc, tr("RTCrX509CertPathsCreate failed: %Rrc"), vrc);
2811 }
2812
2813 /* Merge statuses from signature and certificate validation, prefering the signature one. */
2814 if (SUCCEEDED(hrc) && FAILED(hrc2))
2815 hrc = hrc2;
2816 if (FAILED(hrc))
2817 return hrc;
2818 }
2819
2820 /** @todo provide details about the signatory, signature, etc. */
2821 if (m->fSignerCertLoaded)
2822 {
2823 m->ptrCertificateInfo.createObject();
2824 m->ptrCertificateInfo->initCertificate(&m->SignerCert,
2825 m->fCertificateValid && !m->fCertificateMissingPath,
2826 !m->fCertificateValidTime);
2827 }
2828
2829 /*
2830 * If there is a manifest, check that the OVF digest matches up (if present).
2831 */
2832
2833 NOREF(pTask);
2834 return S_OK;
2835}
2836
2837
2838
2839/*******************************************************************************
2840 * Import stuff
2841 ******************************************************************************/
2842
2843/**
2844 * Implementation for importing OVF data into VirtualBox. This starts a new thread which will call
2845 * Appliance::taskThreadImportOrExport().
2846 *
2847 * This creates one or more new machines according to the VirtualSystemScription instances created by
2848 * Appliance::Interpret().
2849 *
2850 * This is in a separate private method because it is used from one location:
2851 *
2852 * 1) from the public Appliance::ImportMachines().
2853 *
2854 * @param locInfo
2855 * @param progress
2856 * @return
2857 */
2858HRESULT Appliance::i_importImpl(const LocationInfo &locInfo,
2859 ComObjPtr<Progress> &progress)
2860{
2861 HRESULT rc;
2862
2863 /* Initialize our worker task */
2864 ThreadTask *pTask;
2865 if (locInfo.storageType != VFSType_Cloud)
2866 {
2867 rc = i_setUpProgress(progress, Utf8StrFmt(tr("Importing appliance '%s'"), locInfo.strPath.c_str()),
2868 locInfo.storageType == VFSType_File ? ImportFile : ImportS3);
2869 if (FAILED(rc))
2870 return setError(rc, tr("Failed to create task for importing appliance into VirtualBox"));
2871 try
2872 {
2873 pTask = new TaskOVF(this, TaskOVF::Import, locInfo, progress);
2874 }
2875 catch (std::bad_alloc &)
2876 {
2877 return E_OUTOFMEMORY;
2878 }
2879 }
2880 else
2881 {
2882 if (locInfo.strProvider.equals("OCI"))
2883 {
2884 /*
2885 * 1. Create a custom image from the instance:
2886 * - 2 operations (starting and waiting)
2887 * 2. Import the custom image into the Object Storage (OCI format - TAR file with QCOW2 image and JSON file):
2888 * - 2 operations (starting and waiting)
2889 * 3. Download the object from the Object Storage:
2890 * - 2 operations (starting and waiting)
2891 * 4. Open the object, extract QCOW2 image and convert one QCOW2->VDI:
2892 * - 1 operation (extracting and conversion are piped)
2893 * 5. Create VM with user settings and attach the converted image to VM:
2894 * - 1 operation.
2895 *
2896 * Total: 2+2+2+1+1 = 8 operations
2897 *
2898 * See src/VBox/ExtPacks/Puel/OCI/OCICloudClient.h.
2899 * Weight of cloud import operations (1-3 items from above):
2900 * Total = 750 = 10+40+50+50+200x2+200.
2901 *
2902 * Weight of local import operations (4-5 items from above):
2903 * Total = 250 = 200 (extract and convert) + 50 (create VM, attach disks)
2904 */
2905 try
2906 {
2907 rc = progress.createObject();
2908 if (SUCCEEDED(rc))
2909 rc = progress->init(mVirtualBox, static_cast<IAppliance *>(this),
2910 Utf8Str(tr("Importing VM from Cloud...")),
2911 TRUE /* aCancelable */,
2912 8, // ULONG cOperations,
2913 1000, // ULONG ulTotalOperationsWeight,
2914 Utf8Str(tr("Importing VM from Cloud...")), // aFirstOperationDescription
2915 10); // ULONG ulFirstOperationWeight
2916 if (SUCCEEDED(rc))
2917 pTask = new TaskCloud(this, TaskCloud::Import, locInfo, progress);
2918 else
2919 pTask = NULL; /* shut up vcc */
2920 }
2921 catch (std::bad_alloc &)
2922 {
2923 return E_OUTOFMEMORY;
2924 }
2925 if (FAILED(rc))
2926 return setError(rc, tr("Failed to create task for importing appliance into VirtualBox"));
2927 }
2928 else
2929 return setError(E_NOTIMPL, tr("Only \"OCI\" cloud provider is supported for now. \"%s\" isn't supported."),
2930 locInfo.strProvider.c_str());
2931 }
2932
2933 /*
2934 * Start the task thread.
2935 */
2936 rc = pTask->createThread();
2937 pTask = NULL;
2938 if (SUCCEEDED(rc))
2939 return rc;
2940 return setError(rc, tr("Failed to start thread for importing appliance into VirtualBox"));
2941}
2942
2943/**
2944 * Actual worker code for importing OVF data into VirtualBox.
2945 *
2946 * This is called from Appliance::taskThreadImportOrExport() and therefore runs
2947 * on the OVF import worker thread. This creates one or more new machines
2948 * according to the VirtualSystemScription instances created by
2949 * Appliance::Interpret().
2950 *
2951 * This runs in two contexts:
2952 *
2953 * 1) in a first worker thread; in that case, Appliance::ImportMachines() called
2954 * Appliance::i_importImpl();
2955 *
2956 * 2) in a second worker thread; in that case, Appliance::ImportMachines()
2957 * called Appliance::i_importImpl(), which called Appliance::i_importFSOVA(),
2958 * which called Appliance::i_importImpl(), which then called this again.
2959 *
2960 * @param pTask The OVF task data.
2961 * @return COM status code.
2962 */
2963HRESULT Appliance::i_importFS(TaskOVF *pTask)
2964{
2965 LogFlowFuncEnter();
2966 LogFlowFunc(("Appliance %p\n", this));
2967
2968 /* Change the appliance state so we can safely leave the lock while doing
2969 * time-consuming image imports; also the below method calls do all kinds of
2970 * locking which conflicts with the appliance object lock. */
2971 AutoWriteLock writeLock(this COMMA_LOCKVAL_SRC_POS);
2972 /* Check if the appliance is currently busy. */
2973 if (!i_isApplianceIdle())
2974 return E_ACCESSDENIED;
2975 /* Set the internal state to importing. */
2976 m->state = ApplianceImporting;
2977
2978 HRESULT rc = S_OK;
2979
2980 /* Clear the list of imported machines, if any */
2981 m->llGuidsMachinesCreated.clear();
2982
2983 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
2984 rc = i_importFSOVF(pTask, writeLock);
2985 else
2986 rc = i_importFSOVA(pTask, writeLock);
2987 if (FAILED(rc))
2988 {
2989 /* With _whatever_ error we've had, do a complete roll-back of
2990 * machines and images we've created */
2991 writeLock.release();
2992 ErrorInfoKeeper eik;
2993 for (list<Guid>::iterator itID = m->llGuidsMachinesCreated.begin();
2994 itID != m->llGuidsMachinesCreated.end();
2995 ++itID)
2996 {
2997 Guid guid = *itID;
2998 Bstr bstrGuid = guid.toUtf16();
2999 ComPtr<IMachine> failedMachine;
3000 HRESULT rc2 = mVirtualBox->FindMachine(bstrGuid.raw(), failedMachine.asOutParam());
3001 if (SUCCEEDED(rc2))
3002 {
3003 SafeIfaceArray<IMedium> aMedia;
3004 rc2 = failedMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
3005 ComPtr<IProgress> pProgress2;
3006 rc2 = failedMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress2.asOutParam());
3007 pProgress2->WaitForCompletion(-1);
3008 }
3009 }
3010 writeLock.acquire();
3011 }
3012
3013 /* Reset the state so others can call methods again */
3014 m->state = ApplianceIdle;
3015
3016 LogFlowFunc(("rc=%Rhrc\n", rc));
3017 LogFlowFuncLeave();
3018 return rc;
3019}
3020
3021HRESULT Appliance::i_importFSOVF(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
3022{
3023 return i_importDoIt(pTask, rWriteLock);
3024}
3025
3026HRESULT Appliance::i_importFSOVA(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
3027{
3028 LogFlowFuncEnter();
3029
3030 /*
3031 * Open the tar file as file stream.
3032 */
3033 RTVFSIOSTREAM hVfsIosOva;
3034 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
3035 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
3036 if (RT_FAILURE(vrc))
3037 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
3038
3039 RTVFSFSSTREAM hVfsFssOva;
3040 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
3041 RTVfsIoStrmRelease(hVfsIosOva);
3042 if (RT_FAILURE(vrc))
3043 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
3044
3045 /*
3046 * Join paths with the i_importFSOVF code.
3047 *
3048 * Note! We don't need to skip the OVF, manifest or signature files, as the
3049 * i_importMachineGeneric, i_importVBoxMachine and i_importOpenSourceFile
3050 * code will deal with this (as there could be other files in the OVA
3051 * that we don't process, like 'de-DE-resources.xml' in EXAMPLE 1,
3052 * Appendix D.1, OVF v2.1.0).
3053 */
3054 HRESULT hrc = i_importDoIt(pTask, rWriteLock, hVfsFssOva);
3055
3056 RTVfsFsStrmRelease(hVfsFssOva);
3057
3058 LogFlowFunc(("returns %Rhrc\n", hrc));
3059 return hrc;
3060}
3061
3062/**
3063 * Does the actual importing after the caller has made the source accessible.
3064 *
3065 * @param pTask The import task.
3066 * @param rWriteLock The write lock the caller's caller is holding,
3067 * will be released for some reason.
3068 * @param hVfsFssOva The file system stream if OVA, NIL if not.
3069 * @returns COM status code.
3070 * @throws Nothing.
3071 */
3072HRESULT Appliance::i_importDoIt(TaskOVF *pTask, AutoWriteLockBase &rWriteLock, RTVFSFSSTREAM hVfsFssOva /*= NIL_RTVFSFSSTREAM*/)
3073{
3074 rWriteLock.release();
3075
3076 HRESULT hrc = E_FAIL;
3077 try
3078 {
3079 /*
3080 * Create the import stack for the rollback on errors.
3081 */
3082 ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, hVfsFssOva);
3083
3084 try
3085 {
3086 /* Do the importing. */
3087 i_importMachines(stack);
3088
3089 /* We should've processed all the files now, so compare. */
3090 hrc = i_verifyManifestFile(stack);
3091
3092 /* If everything was successful so far check if some extension
3093 * pack wants to do file sanity checking. */
3094 if (SUCCEEDED(hrc))
3095 {
3096 /** @todo */;
3097 }
3098 }
3099 catch (HRESULT hrcXcpt)
3100 {
3101 hrc = hrcXcpt;
3102 }
3103 catch (...)
3104 {
3105 AssertFailed();
3106 hrc = E_FAIL;
3107 }
3108 if (FAILED(hrc))
3109 {
3110 /*
3111 * Restoring original UUID from OVF description file.
3112 * During import VBox creates new UUIDs for imported images and
3113 * assigns them to the images. In case of failure we have to restore
3114 * the original UUIDs because those new UUIDs are obsolete now and
3115 * won't be used anymore.
3116 */
3117 ErrorInfoKeeper eik; /* paranoia */
3118 list< ComObjPtr<VirtualSystemDescription> >::const_iterator itvsd;
3119 /* Iterate through all virtual systems of that appliance */
3120 for (itvsd = m->virtualSystemDescriptions.begin();
3121 itvsd != m->virtualSystemDescriptions.end();
3122 ++itvsd)
3123 {
3124 ComObjPtr<VirtualSystemDescription> vsdescThis = (*itvsd);
3125 settings::MachineConfigFile *pConfig = vsdescThis->m->pConfig;
3126 if(vsdescThis->m->pConfig!=NULL)
3127 stack.restoreOriginalUUIDOfAttachedDevice(pConfig);
3128 }
3129 }
3130 }
3131 catch (...)
3132 {
3133 hrc = E_FAIL;
3134 AssertFailed();
3135 }
3136
3137 rWriteLock.acquire();
3138 return hrc;
3139}
3140
3141/**
3142 * Undocumented, you figure it from the name.
3143 *
3144 * @returns Undocumented
3145 * @param stack Undocumented.
3146 */
3147HRESULT Appliance::i_verifyManifestFile(ImportStack &stack)
3148{
3149 LogFlowThisFuncEnter();
3150 HRESULT hrc;
3151 int vrc;
3152
3153 /*
3154 * No manifest is fine, it always matches.
3155 */
3156 if (m->hTheirManifest == NIL_RTMANIFEST)
3157 hrc = S_OK;
3158 else
3159 {
3160 /*
3161 * Hack: If the manifest we just read doesn't have a digest for the OVF, copy
3162 * it from the manifest we got from the caller.
3163 * @bugref{6022#c119}
3164 */
3165 if ( !RTManifestEntryExists(m->hTheirManifest, m->strOvfManifestEntry.c_str())
3166 && RTManifestEntryExists(m->hOurManifest, m->strOvfManifestEntry.c_str()) )
3167 {
3168 uint32_t fType = 0;
3169 char szDigest[512 + 1];
3170 vrc = RTManifestEntryQueryAttr(m->hOurManifest, m->strOvfManifestEntry.c_str(), NULL, RTMANIFEST_ATTR_ANY,
3171 szDigest, sizeof(szDigest), &fType);
3172 if (RT_SUCCESS(vrc))
3173 vrc = RTManifestEntrySetAttr(m->hTheirManifest, m->strOvfManifestEntry.c_str(),
3174 NULL /*pszAttr*/, szDigest, fType);
3175 if (RT_FAILURE(vrc))
3176 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Error fudging missing OVF digest in manifest: %Rrc"), vrc);
3177 }
3178
3179 /*
3180 * Compare with the digests we've created while read/processing the import.
3181 *
3182 * We specify the RTMANIFEST_EQUALS_IGN_MISSING_ATTRS to ignore attributes
3183 * (SHA1, SHA256, etc) that are only present in one of the manifests, as long
3184 * as each entry has at least one common attribute that we can check. This
3185 * is important for the OVF in OVAs, for which we generates several digests
3186 * since we don't know which are actually used in the manifest (OVF comes
3187 * first in an OVA, then manifest).
3188 */
3189 char szErr[256];
3190 vrc = RTManifestEqualsEx(m->hTheirManifest, m->hOurManifest, NULL /*papszIgnoreEntries*/,
3191 NULL /*papszIgnoreAttrs*/,
3192 RTMANIFEST_EQUALS_IGN_MISSING_ATTRS | RTMANIFEST_EQUALS_IGN_MISSING_ENTRIES_2ND,
3193 szErr, sizeof(szErr));
3194 if (RT_SUCCESS(vrc))
3195 hrc = S_OK;
3196 else
3197 hrc = setErrorVrc(vrc, tr("Digest mismatch (%Rrc): %s"), vrc, szErr);
3198 }
3199
3200 NOREF(stack);
3201 LogFlowThisFunc(("returns %Rhrc\n", hrc));
3202 return hrc;
3203}
3204
3205/**
3206 * Helper that converts VirtualSystem attachment values into VirtualBox attachment values.
3207 * Throws HRESULT values on errors!
3208 *
3209 * @param hdc in: the HardDiskController structure to attach to.
3210 * @param ulAddressOnParent in: the AddressOnParent parameter from OVF.
3211 * @param controllerName out: the name of the storage controller to attach to (e.g. "IDE").
3212 * @param lControllerPort out: the channel (controller port) of the controller to attach to.
3213 * @param lDevice out: the device number to attach to.
3214 */
3215void Appliance::i_convertDiskAttachmentValues(const ovf::HardDiskController &hdc,
3216 uint32_t ulAddressOnParent,
3217 Utf8Str &controllerName,
3218 int32_t &lControllerPort,
3219 int32_t &lDevice)
3220{
3221 Log(("Appliance::i_convertDiskAttachmentValues: hdc.system=%d, hdc.fPrimary=%d, ulAddressOnParent=%d\n",
3222 hdc.system,
3223 hdc.fPrimary,
3224 ulAddressOnParent));
3225
3226 switch (hdc.system)
3227 {
3228 case ovf::HardDiskController::IDE:
3229 // For the IDE bus, the port parameter can be either 0 or 1, to specify the primary
3230 // or secondary IDE controller, respectively. For the primary controller of the IDE bus,
3231 // the device number can be either 0 or 1, to specify the master or the slave device,
3232 // respectively. For the secondary IDE controller, the device number is always 1 because
3233 // the master device is reserved for the CD-ROM drive.
3234 controllerName = "IDE";
3235 switch (ulAddressOnParent)
3236 {
3237 case 0: // master
3238 if (!hdc.fPrimary)
3239 {
3240 // secondary master
3241 lControllerPort = (long)1;
3242 lDevice = (long)0;
3243 }
3244 else // primary master
3245 {
3246 lControllerPort = (long)0;
3247 lDevice = (long)0;
3248 }
3249 break;
3250
3251 case 1: // slave
3252 if (!hdc.fPrimary)
3253 {
3254 // secondary slave
3255 lControllerPort = (long)1;
3256 lDevice = (long)1;
3257 }
3258 else // primary slave
3259 {
3260 lControllerPort = (long)0;
3261 lDevice = (long)1;
3262 }
3263 break;
3264
3265 // used by older VBox exports
3266 case 2: // interpret this as secondary master
3267 lControllerPort = (long)1;
3268 lDevice = (long)0;
3269 break;
3270
3271 // used by older VBox exports
3272 case 3: // interpret this as secondary slave
3273 lControllerPort = (long)1;
3274 lDevice = (long)1;
3275 break;
3276
3277 default:
3278 throw setError(VBOX_E_NOT_SUPPORTED,
3279 tr("Invalid channel %RI16 specified; IDE controllers support only 0, 1 or 2"),
3280 ulAddressOnParent);
3281 break;
3282 }
3283 break;
3284
3285 case ovf::HardDiskController::SATA:
3286 controllerName = "SATA";
3287 lControllerPort = (long)ulAddressOnParent;
3288 lDevice = (long)0;
3289 break;
3290
3291 case ovf::HardDiskController::SCSI:
3292 {
3293 if(hdc.strControllerType.compare("lsilogicsas")==0)
3294 controllerName = "SAS";
3295 else
3296 controllerName = "SCSI";
3297 lControllerPort = (long)ulAddressOnParent;
3298 lDevice = (long)0;
3299 break;
3300 }
3301
3302 default: break;
3303 }
3304
3305 Log(("=> lControllerPort=%d, lDevice=%d\n", lControllerPort, lDevice));
3306}
3307
3308/**
3309 * Imports one image.
3310 *
3311 * This is common code shared between
3312 * -- i_importMachineGeneric() for the OVF case; in that case the information comes from
3313 * the OVF virtual systems;
3314 * -- i_importVBoxMachine(); in that case, the information comes from the <vbox:Machine>
3315 * tag.
3316 *
3317 * Both ways of describing machines use the OVF disk references section, so in both cases
3318 * the caller needs to pass in the ovf::DiskImage structure from ovfreader.cpp.
3319 *
3320 * As a result, in both cases, if di.strHref is empty, we create a new image as per the OVF
3321 * spec, even though this cannot really happen in the vbox:Machine case since such data
3322 * would never have been exported.
3323 *
3324 * This advances stack.pProgress by one operation with the image's weight.
3325 *
3326 * @param di ovfreader.cpp structure describing the image from the OVF that is to be imported
3327 * @param strDstPath Where to create the target image.
3328 * @param pTargetMedium out: The newly created target medium. This also gets pushed on stack.llHardDisksCreated for cleanup.
3329 * @param stack
3330 *
3331 * @throws HRESULT
3332 */
3333void Appliance::i_importOneDiskImage(const ovf::DiskImage &di,
3334 const Utf8Str &strDstPath,
3335 ComObjPtr<Medium> &pTargetMedium,
3336 ImportStack &stack)
3337{
3338 HRESULT rc;
3339
3340 Utf8Str strAbsDstPath;
3341 int vrc = RTPathAbsExCxx(strAbsDstPath, stack.strMachineFolder, strDstPath);
3342 AssertRCStmt(vrc, throw Global::vboxStatusCodeToCOM(vrc));
3343
3344 /* Get the system properties. */
3345 SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
3346
3347 /* Keep the source file ref handy for later. */
3348 const Utf8Str &strSourceOVF = di.strHref;
3349
3350 /* Construct source file path */
3351 Utf8Str strSrcFilePath;
3352 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
3353 strSrcFilePath = strSourceOVF;
3354 else
3355 {
3356 strSrcFilePath = stack.strSourceDir;
3357 strSrcFilePath.append(RTPATH_SLASH_STR);
3358 strSrcFilePath.append(strSourceOVF);
3359 }
3360
3361 /* First of all check if the original (non-absolute) destination path is
3362 * a valid medium UUID. If so, the user wants to import the image into
3363 * an existing path. This is useful for iSCSI for example. */
3364 /** @todo r=klaus the code structure after this point is totally wrong,
3365 * full of unnecessary code duplication and other issues. 4.2 still had
3366 * the right structure for importing into existing medium objects, which
3367 * the current code can't possibly handle. */
3368 RTUUID uuid;
3369 vrc = RTUuidFromStr(&uuid, strDstPath.c_str());
3370 if (vrc == VINF_SUCCESS)
3371 {
3372 rc = mVirtualBox->i_findHardDiskById(Guid(uuid), true, &pTargetMedium);
3373 if (FAILED(rc)) throw rc;
3374 }
3375 else
3376 {
3377 RTVFSIOSTREAM hVfsIosSrc = NIL_RTVFSIOSTREAM;
3378
3379 /* check read file to GZIP compression */
3380 bool const fGzipped = di.strCompression.compare("gzip", Utf8Str::CaseInsensitive) == 0;
3381 Utf8Str strDeleteTemp;
3382 try
3383 {
3384 Utf8Str strTrgFormat = "VMDK";
3385 ComObjPtr<MediumFormat> trgFormat;
3386 Bstr bstrFormatName;
3387 ULONG lCabs = 0;
3388
3389 char *pszSuff = RTPathSuffix(strAbsDstPath.c_str());
3390 if (pszSuff != NULL)
3391 {
3392 /*
3393 * Figure out which format the user like to have. Default is VMDK
3394 * or it can be VDI if according command-line option is set
3395 */
3396
3397 /*
3398 * We need a proper target format
3399 * if target format has been changed by user via GUI import wizard
3400 * or via VBoxManage import command (option --importtovdi)
3401 * then we need properly process such format like ISO
3402 * Because there is no conversion ISO to VDI
3403 */
3404 trgFormat = pSysProps->i_mediumFormatFromExtension(++pszSuff);
3405 if (trgFormat.isNull())
3406 throw setError(E_FAIL, tr("Unsupported medium format for disk image '%s'"), di.strHref.c_str());
3407
3408 rc = trgFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
3409 if (FAILED(rc)) throw rc;
3410
3411 strTrgFormat = Utf8Str(bstrFormatName);
3412
3413 if ( m->optListImport.contains(ImportOptions_ImportToVDI)
3414 && strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) != 0)
3415 {
3416 /* change the target extension */
3417 strTrgFormat = "vdi";
3418 trgFormat = pSysProps->i_mediumFormatFromExtension(strTrgFormat);
3419 strAbsDstPath.stripSuffix();
3420 strAbsDstPath.append(".");
3421 strAbsDstPath.append(strTrgFormat.c_str());
3422 }
3423
3424 /* Check the capabilities. We need create capabilities. */
3425 lCabs = 0;
3426 com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
3427 rc = trgFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
3428
3429 if (FAILED(rc))
3430 throw rc;
3431
3432 for (ULONG j = 0; j < mediumFormatCap.size(); j++)
3433 lCabs |= mediumFormatCap[j];
3434
3435 if ( !(lCabs & MediumFormatCapabilities_CreateFixed)
3436 && !(lCabs & MediumFormatCapabilities_CreateDynamic) )
3437 throw setError(VBOX_E_NOT_SUPPORTED,
3438 tr("Could not find a valid medium format for the target disk '%s'"),
3439 strAbsDstPath.c_str());
3440 }
3441 else
3442 {
3443 throw setError(VBOX_E_FILE_ERROR,
3444 tr("The target disk '%s' has no extension "),
3445 strAbsDstPath.c_str(), VERR_INVALID_NAME);
3446 }
3447
3448 /*CD/DVD case*/
3449 if (strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) == 0)
3450 {
3451 try
3452 {
3453 if (fGzipped)
3454 i_importDecompressFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
3455 else
3456 i_importCopyFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
3457
3458 ComPtr<IMedium> pTmp;
3459 rc = mVirtualBox->OpenMedium(Bstr(strAbsDstPath).raw(),
3460 DeviceType_DVD,
3461 AccessMode_ReadWrite,
3462 false,
3463 pTmp.asOutParam());
3464 if (FAILED(rc))
3465 throw rc;
3466
3467 IMedium *iM = pTmp;
3468 pTargetMedium = static_cast<Medium*>(iM);
3469 }
3470 catch (HRESULT /*arc*/)
3471 {
3472 throw;
3473 }
3474
3475 /* Advance to the next operation. */
3476 /* operation's weight, as set up with the IProgress originally */
3477 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
3478 RTPathFilename(strSourceOVF.c_str())).raw(),
3479 di.ulSuggestedSizeMB);
3480 }
3481 else/* HDD case*/
3482 {
3483 /* Create an IMedium object. */
3484 pTargetMedium.createObject();
3485
3486 rc = pTargetMedium->init(mVirtualBox,
3487 strTrgFormat,
3488 strAbsDstPath,
3489 Guid::Empty /* media registry: none yet */,
3490 DeviceType_HardDisk);
3491 if (FAILED(rc)) throw rc;
3492
3493 ComPtr<IProgress> pProgressImport;
3494 /* If strHref is empty we have to create a new file. */
3495 if (strSourceOVF.isEmpty())
3496 {
3497 com::SafeArray<MediumVariant_T> mediumVariant;
3498 mediumVariant.push_back(MediumVariant_Standard);
3499
3500 /* Kick off the creation of a dynamic growing disk image with the given capacity. */
3501 rc = pTargetMedium->CreateBaseStorage(di.iCapacity / _1M,
3502 ComSafeArrayAsInParam(mediumVariant),
3503 pProgressImport.asOutParam());
3504 if (FAILED(rc)) throw rc;
3505
3506 /* Advance to the next operation. */
3507 /* operation's weight, as set up with the IProgress originally */
3508 stack.pProgress->SetNextOperation(BstrFmt(tr("Creating disk image '%s'"),
3509 strAbsDstPath.c_str()).raw(),
3510 di.ulSuggestedSizeMB);
3511 }
3512 else
3513 {
3514 /* We need a proper source format description */
3515 /* Which format to use? */
3516 ComObjPtr<MediumFormat> srcFormat;
3517 rc = i_findMediumFormatFromDiskImage(di, srcFormat);
3518 if (FAILED(rc))
3519 throw setError(VBOX_E_NOT_SUPPORTED,
3520 tr("Could not find a valid medium format for the source disk '%s' "
3521 "Check correctness of the image format URL in the OVF description file "
3522 "or extension of the image"),
3523 RTPathFilename(strSourceOVF.c_str()));
3524
3525 /* If gzipped, decompress the GZIP file and save a new file in the target path */
3526 if (fGzipped)
3527 {
3528 Utf8Str strTargetFilePath(strAbsDstPath);
3529 strTargetFilePath.stripFilename();
3530 strTargetFilePath.append(RTPATH_SLASH_STR);
3531 strTargetFilePath.append("temp_");
3532 strTargetFilePath.append(RTPathFilename(strSrcFilePath.c_str()));
3533 strDeleteTemp = strTargetFilePath;
3534
3535 i_importDecompressFile(stack, strSrcFilePath, strTargetFilePath, strSourceOVF.c_str());
3536
3537 /* Correct the source and the target with the actual values */
3538 strSrcFilePath = strTargetFilePath;
3539
3540 /* Open the new source file. */
3541 vrc = RTVfsIoStrmOpenNormal(strSrcFilePath.c_str(), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
3542 &hVfsIosSrc);
3543 if (RT_FAILURE(vrc))
3544 throw setErrorVrc(vrc, tr("Error opening decompressed image file '%s' (%Rrc)"),
3545 strSrcFilePath.c_str(), vrc);
3546 }
3547 else
3548 hVfsIosSrc = i_importOpenSourceFile(stack, strSrcFilePath, strSourceOVF.c_str());
3549
3550 /* Add a read ahead thread to try speed things up with concurrent reads and
3551 writes going on in different threads. */
3552 RTVFSIOSTREAM hVfsIosReadAhead;
3553 vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/,
3554 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
3555 RTVfsIoStrmRelease(hVfsIosSrc);
3556 if (RT_FAILURE(vrc))
3557 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"),
3558 strSrcFilePath.c_str(), vrc);
3559
3560 /* Start the source image cloning operation. */
3561 ComObjPtr<Medium> nullParent;
3562 ComObjPtr<Progress> pProgressImportTmp;
3563 rc = pProgressImportTmp.createObject();
3564 if (FAILED(rc)) throw rc;
3565 rc = pProgressImportTmp->init(mVirtualBox,
3566 static_cast<IAppliance*>(this),
3567 Utf8StrFmt(tr("Importing medium '%s'"),
3568 strAbsDstPath.c_str()),
3569 TRUE);
3570 if (FAILED(rc)) throw rc;
3571 pProgressImportTmp.queryInterfaceTo(pProgressImport.asOutParam());
3572 /* pProgressImportTmp is in parameter for Medium::i_importFile,
3573 * which is somewhat unusual and might be changed later. */
3574 rc = pTargetMedium->i_importFile(strSrcFilePath.c_str(),
3575 srcFormat,
3576 MediumVariant_Standard,
3577 hVfsIosReadAhead,
3578 nullParent,
3579 pProgressImportTmp,
3580 true /* aNotify */);
3581 RTVfsIoStrmRelease(hVfsIosReadAhead);
3582 hVfsIosSrc = NIL_RTVFSIOSTREAM;
3583 if (FAILED(rc))
3584 throw rc;
3585
3586 /* Advance to the next operation. */
3587 /* operation's weight, as set up with the IProgress originally */
3588 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
3589 RTPathFilename(strSourceOVF.c_str())).raw(),
3590 di.ulSuggestedSizeMB);
3591 }
3592
3593 /* Now wait for the background import operation to complete; this throws
3594 * HRESULTs on error. */
3595 stack.pProgress->WaitForOtherProgressCompletion(pProgressImport, 0 /* indefinite wait */);
3596
3597 /* The creating/importing has placed the medium in the global
3598 * media registry since the VM isn't created yet. Remove it
3599 * again to let it added to the right registry when the VM
3600 * has been created below. */
3601 pTargetMedium->i_removeRegistry(mVirtualBox->i_getGlobalRegistryId());
3602 }
3603 }
3604 catch (...)
3605 {
3606 if (strDeleteTemp.isNotEmpty())
3607 RTFileDelete(strDeleteTemp.c_str());
3608 throw;
3609 }
3610
3611 /* Make sure the source file is closed. */
3612 if (hVfsIosSrc != NIL_RTVFSIOSTREAM)
3613 RTVfsIoStrmRelease(hVfsIosSrc);
3614
3615 /*
3616 * Delete the temp gunzip result, if any.
3617 */
3618 if (strDeleteTemp.isNotEmpty())
3619 {
3620 vrc = RTFileDelete(strSrcFilePath.c_str());
3621 if (RT_FAILURE(vrc))
3622 setWarning(VBOX_E_FILE_ERROR,
3623 tr("Failed to delete the temporary file '%s' (%Rrc)"), strSrcFilePath.c_str(), vrc);
3624 }
3625 }
3626}
3627
3628/**
3629 * Imports one OVF virtual system (described by the given ovf::VirtualSystem and VirtualSystemDescription)
3630 * into VirtualBox by creating an IMachine instance, which is returned.
3631 *
3632 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
3633 * up any leftovers from this function. For this, the given ImportStack instance has received information
3634 * about what needs cleaning up (to support rollback).
3635 *
3636 * @param vsysThis OVF virtual system (machine) to import.
3637 * @param vsdescThis Matching virtual system description (machine) to import.
3638 * @param pNewMachine out: Newly created machine.
3639 * @param stack Cleanup stack for when this throws.
3640 */
3641void Appliance::i_importMachineGeneric(const ovf::VirtualSystem &vsysThis,
3642 ComObjPtr<VirtualSystemDescription> &vsdescThis,
3643 ComPtr<IMachine> &pNewMachine,
3644 ImportStack &stack)
3645{
3646 LogFlowFuncEnter();
3647 HRESULT rc;
3648
3649 // Get the instance of IGuestOSType which matches our string guest OS type so we
3650 // can use recommended defaults for the new machine where OVF doesn't provide any
3651 ComPtr<IGuestOSType> osType;
3652 rc = mVirtualBox->GetGuestOSType(Bstr(stack.strOsTypeVBox).raw(), osType.asOutParam());
3653 if (FAILED(rc)) throw rc;
3654
3655 /* Create the machine */
3656 SafeArray<BSTR> groups; /* no groups, or maybe one group... */
3657 if (!stack.strPrimaryGroup.isEmpty() && stack.strPrimaryGroup != "/")
3658 Bstr(stack.strPrimaryGroup).detachTo(groups.appendedRaw());
3659 rc = mVirtualBox->CreateMachine(Bstr(stack.strSettingsFilename).raw(),
3660 Bstr(stack.strNameVBox).raw(),
3661 ComSafeArrayAsInParam(groups),
3662 Bstr(stack.strOsTypeVBox).raw(),
3663 NULL, /* aCreateFlags */
3664 pNewMachine.asOutParam());
3665 if (FAILED(rc)) throw rc;
3666
3667 // set the description
3668 if (!stack.strDescription.isEmpty())
3669 {
3670 rc = pNewMachine->COMSETTER(Description)(Bstr(stack.strDescription).raw());
3671 if (FAILED(rc)) throw rc;
3672 }
3673
3674 // CPU count
3675 rc = pNewMachine->COMSETTER(CPUCount)(stack.cCPUs);
3676 if (FAILED(rc)) throw rc;
3677
3678 if (stack.fForceHWVirt)
3679 {
3680 rc = pNewMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, TRUE);
3681 if (FAILED(rc)) throw rc;
3682 }
3683
3684 // RAM
3685 rc = pNewMachine->COMSETTER(MemorySize)(stack.ulMemorySizeMB);
3686 if (FAILED(rc)) throw rc;
3687
3688 /* VRAM */
3689 /* Get the recommended VRAM for this guest OS type */
3690 ULONG vramVBox;
3691 rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox);
3692 if (FAILED(rc)) throw rc;
3693
3694 /* Set the VRAM */
3695 rc = pNewMachine->COMSETTER(VRAMSize)(vramVBox);
3696 if (FAILED(rc)) throw rc;
3697
3698 // I/O APIC: Generic OVF has no setting for this. Enable it if we
3699 // import a Windows VM because if if Windows was installed without IOAPIC,
3700 // it will not mind finding an one later on, but if Windows was installed
3701 // _with_ an IOAPIC, it will bluescreen if it's not found
3702 if (!stack.fForceIOAPIC)
3703 {
3704 Bstr bstrFamilyId;
3705 rc = osType->COMGETTER(FamilyId)(bstrFamilyId.asOutParam());
3706 if (FAILED(rc)) throw rc;
3707 if (bstrFamilyId == "Windows")
3708 stack.fForceIOAPIC = true;
3709 }
3710
3711 if (stack.fForceIOAPIC)
3712 {
3713 ComPtr<IBIOSSettings> pBIOSSettings;
3714 rc = pNewMachine->COMGETTER(BIOSSettings)(pBIOSSettings.asOutParam());
3715 if (FAILED(rc)) throw rc;
3716
3717 rc = pBIOSSettings->COMSETTER(IOAPICEnabled)(TRUE);
3718 if (FAILED(rc)) throw rc;
3719 }
3720
3721 if (!stack.strAudioAdapter.isEmpty())
3722 if (stack.strAudioAdapter.compare("null", Utf8Str::CaseInsensitive) != 0)
3723 {
3724 uint32_t audio = RTStrToUInt32(stack.strAudioAdapter.c_str()); // should be 0 for AC97
3725 ComPtr<IAudioAdapter> audioAdapter;
3726 rc = pNewMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam());
3727 if (FAILED(rc)) throw rc;
3728 rc = audioAdapter->COMSETTER(Enabled)(true);
3729 if (FAILED(rc)) throw rc;
3730 rc = audioAdapter->COMSETTER(AudioController)(static_cast<AudioControllerType_T>(audio));
3731 if (FAILED(rc)) throw rc;
3732 }
3733
3734#ifdef VBOX_WITH_USB
3735 /* USB Controller */
3736 if (stack.fUSBEnabled)
3737 {
3738 ComPtr<IUSBController> usbController;
3739 rc = pNewMachine->AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, usbController.asOutParam());
3740 if (FAILED(rc)) throw rc;
3741 }
3742#endif /* VBOX_WITH_USB */
3743
3744 /* Change the network adapters */
3745 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
3746
3747 std::list<VirtualSystemDescriptionEntry*> vsdeNW = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
3748 if (vsdeNW.empty())
3749 {
3750 /* No network adapters, so we have to disable our default one */
3751 ComPtr<INetworkAdapter> nwVBox;
3752 rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam());
3753 if (FAILED(rc)) throw rc;
3754 rc = nwVBox->COMSETTER(Enabled)(false);
3755 if (FAILED(rc)) throw rc;
3756 }
3757 else if (vsdeNW.size() > maxNetworkAdapters)
3758 throw setError(VBOX_E_FILE_ERROR,
3759 tr("Too many network adapters: OVF requests %d network adapters, "
3760 "but VirtualBox only supports %d"),
3761 vsdeNW.size(), maxNetworkAdapters);
3762 else
3763 {
3764 list<VirtualSystemDescriptionEntry*>::const_iterator nwIt;
3765 size_t a = 0;
3766 for (nwIt = vsdeNW.begin();
3767 nwIt != vsdeNW.end();
3768 ++nwIt, ++a)
3769 {
3770 const VirtualSystemDescriptionEntry* pvsys = *nwIt;
3771
3772 const Utf8Str &nwTypeVBox = pvsys->strVBoxCurrent;
3773 uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str());
3774 ComPtr<INetworkAdapter> pNetworkAdapter;
3775 rc = pNewMachine->GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
3776 if (FAILED(rc)) throw rc;
3777 /* Enable the network card & set the adapter type */
3778 rc = pNetworkAdapter->COMSETTER(Enabled)(true);
3779 if (FAILED(rc)) throw rc;
3780 rc = pNetworkAdapter->COMSETTER(AdapterType)(static_cast<NetworkAdapterType_T>(tt1));
3781 if (FAILED(rc)) throw rc;
3782
3783 // default is NAT; change to "bridged" if extra conf says so
3784 if (pvsys->strExtraConfigCurrent.endsWith("type=Bridged", Utf8Str::CaseInsensitive))
3785 {
3786 /* Attach to the right interface */
3787 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged);
3788 if (FAILED(rc)) throw rc;
3789 ComPtr<IHost> host;
3790 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
3791 if (FAILED(rc)) throw rc;
3792 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
3793 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
3794 if (FAILED(rc)) throw rc;
3795 // We search for the first host network interface which
3796 // is usable for bridged networking
3797 for (size_t j = 0;
3798 j < nwInterfaces.size();
3799 ++j)
3800 {
3801 HostNetworkInterfaceType_T itype;
3802 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
3803 if (FAILED(rc)) throw rc;
3804 if (itype == HostNetworkInterfaceType_Bridged)
3805 {
3806 Bstr name;
3807 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
3808 if (FAILED(rc)) throw rc;
3809 /* Set the interface name to attach to */
3810 rc = pNetworkAdapter->COMSETTER(BridgedInterface)(name.raw());
3811 if (FAILED(rc)) throw rc;
3812 break;
3813 }
3814 }
3815 }
3816 /* Next test for host only interfaces */
3817 else if (pvsys->strExtraConfigCurrent.endsWith("type=HostOnly", Utf8Str::CaseInsensitive))
3818 {
3819 /* Attach to the right interface */
3820 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly);
3821 if (FAILED(rc)) throw rc;
3822 ComPtr<IHost> host;
3823 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
3824 if (FAILED(rc)) throw rc;
3825 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
3826 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
3827 if (FAILED(rc)) throw rc;
3828 // We search for the first host network interface which
3829 // is usable for host only networking
3830 for (size_t j = 0;
3831 j < nwInterfaces.size();
3832 ++j)
3833 {
3834 HostNetworkInterfaceType_T itype;
3835 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
3836 if (FAILED(rc)) throw rc;
3837 if (itype == HostNetworkInterfaceType_HostOnly)
3838 {
3839 Bstr name;
3840 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
3841 if (FAILED(rc)) throw rc;
3842 /* Set the interface name to attach to */
3843 rc = pNetworkAdapter->COMSETTER(HostOnlyInterface)(name.raw());
3844 if (FAILED(rc)) throw rc;
3845 break;
3846 }
3847 }
3848 }
3849 /* Next test for internal interfaces */
3850 else if (pvsys->strExtraConfigCurrent.endsWith("type=Internal", Utf8Str::CaseInsensitive))
3851 {
3852 /* Attach to the right interface */
3853 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Internal);
3854 if (FAILED(rc)) throw rc;
3855 }
3856 /* Next test for Generic interfaces */
3857 else if (pvsys->strExtraConfigCurrent.endsWith("type=Generic", Utf8Str::CaseInsensitive))
3858 {
3859 /* Attach to the right interface */
3860 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Generic);
3861 if (FAILED(rc)) throw rc;
3862 }
3863
3864 /* Next test for NAT network interfaces */
3865 else if (pvsys->strExtraConfigCurrent.endsWith("type=NATNetwork", Utf8Str::CaseInsensitive))
3866 {
3867 /* Attach to the right interface */
3868 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork);
3869 if (FAILED(rc)) throw rc;
3870 com::SafeIfaceArray<INATNetwork> nwNATNetworks;
3871 rc = mVirtualBox->COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(nwNATNetworks));
3872 if (FAILED(rc)) throw rc;
3873 // Pick the first NAT network (if there is any)
3874 if (nwNATNetworks.size())
3875 {
3876 Bstr name;
3877 rc = nwNATNetworks[0]->COMGETTER(NetworkName)(name.asOutParam());
3878 if (FAILED(rc)) throw rc;
3879 /* Set the NAT network name to attach to */
3880 rc = pNetworkAdapter->COMSETTER(NATNetwork)(name.raw());
3881 if (FAILED(rc)) throw rc;
3882 break;
3883 }
3884 }
3885 }
3886 }
3887
3888 // Storage controller IDE
3889 std::list<VirtualSystemDescriptionEntry*> vsdeHDCIDE =
3890 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerIDE);
3891 /*
3892 * In OVF (at least VMware's version of it), an IDE controller has two ports,
3893 * so VirtualBox's single IDE controller with two channels and two ports each counts as
3894 * two OVF IDE controllers -- so we accept one or two such IDE controllers
3895 */
3896 size_t cIDEControllers = vsdeHDCIDE.size();
3897 if (cIDEControllers > 2)
3898 throw setError(VBOX_E_FILE_ERROR,
3899 tr("Too many IDE controllers in OVF; import facility only supports two"));
3900 if (!vsdeHDCIDE.empty())
3901 {
3902 // one or two IDE controllers present in OVF: add one VirtualBox controller
3903 ComPtr<IStorageController> pController;
3904 rc = pNewMachine->AddStorageController(Bstr("IDE").raw(), StorageBus_IDE, pController.asOutParam());
3905 if (FAILED(rc)) throw rc;
3906
3907 const char *pcszIDEType = vsdeHDCIDE.front()->strVBoxCurrent.c_str();
3908 if (!strcmp(pcszIDEType, "PIIX3"))
3909 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX3);
3910 else if (!strcmp(pcszIDEType, "PIIX4"))
3911 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX4);
3912 else if (!strcmp(pcszIDEType, "ICH6"))
3913 rc = pController->COMSETTER(ControllerType)(StorageControllerType_ICH6);
3914 else
3915 throw setError(VBOX_E_FILE_ERROR,
3916 tr("Invalid IDE controller type \"%s\""),
3917 pcszIDEType);
3918 if (FAILED(rc)) throw rc;
3919 }
3920
3921 /* Storage controller SATA */
3922 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSATA =
3923 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSATA);
3924 if (vsdeHDCSATA.size() > 1)
3925 throw setError(VBOX_E_FILE_ERROR,
3926 tr("Too many SATA controllers in OVF; import facility only supports one"));
3927 if (!vsdeHDCSATA.empty())
3928 {
3929 ComPtr<IStorageController> pController;
3930 const Utf8Str &hdcVBox = vsdeHDCSATA.front()->strVBoxCurrent;
3931 if (hdcVBox == "AHCI")
3932 {
3933 rc = pNewMachine->AddStorageController(Bstr("SATA").raw(),
3934 StorageBus_SATA,
3935 pController.asOutParam());
3936 if (FAILED(rc)) throw rc;
3937 }
3938 else
3939 throw setError(VBOX_E_FILE_ERROR,
3940 tr("Invalid SATA controller type \"%s\""),
3941 hdcVBox.c_str());
3942 }
3943
3944 /* Storage controller SCSI */
3945 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSCSI =
3946 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI);
3947 if (vsdeHDCSCSI.size() > 1)
3948 throw setError(VBOX_E_FILE_ERROR,
3949 tr("Too many SCSI controllers in OVF; import facility only supports one"));
3950 if (!vsdeHDCSCSI.empty())
3951 {
3952 ComPtr<IStorageController> pController;
3953 Utf8Str strName("SCSI");
3954 StorageBus_T busType = StorageBus_SCSI;
3955 StorageControllerType_T controllerType;
3956 const Utf8Str &hdcVBox = vsdeHDCSCSI.front()->strVBoxCurrent;
3957 if (hdcVBox == "LsiLogic")
3958 controllerType = StorageControllerType_LsiLogic;
3959 else if (hdcVBox == "LsiLogicSas")
3960 {
3961 // OVF treats LsiLogicSas as a SCSI controller but VBox considers it a class of its own
3962 strName = "SAS";
3963 busType = StorageBus_SAS;
3964 controllerType = StorageControllerType_LsiLogicSas;
3965 }
3966 else if (hdcVBox == "BusLogic")
3967 controllerType = StorageControllerType_BusLogic;
3968 else
3969 throw setError(VBOX_E_FILE_ERROR,
3970 tr("Invalid SCSI controller type \"%s\""),
3971 hdcVBox.c_str());
3972
3973 rc = pNewMachine->AddStorageController(Bstr(strName).raw(), busType, pController.asOutParam());
3974 if (FAILED(rc)) throw rc;
3975 rc = pController->COMSETTER(ControllerType)(controllerType);
3976 if (FAILED(rc)) throw rc;
3977 }
3978
3979 /* Storage controller SAS */
3980 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSAS =
3981 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSAS);
3982 if (vsdeHDCSAS.size() > 1)
3983 throw setError(VBOX_E_FILE_ERROR,
3984 tr("Too many SAS controllers in OVF; import facility only supports one"));
3985 if (!vsdeHDCSAS.empty())
3986 {
3987 ComPtr<IStorageController> pController;
3988 rc = pNewMachine->AddStorageController(Bstr(L"SAS").raw(),
3989 StorageBus_SAS,
3990 pController.asOutParam());
3991 if (FAILED(rc)) throw rc;
3992 rc = pController->COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas);
3993 if (FAILED(rc)) throw rc;
3994 }
3995
3996 /* Now its time to register the machine before we add any storage devices */
3997 rc = mVirtualBox->RegisterMachine(pNewMachine);
3998 if (FAILED(rc)) throw rc;
3999
4000 // store new machine for roll-back in case of errors
4001 Bstr bstrNewMachineId;
4002 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
4003 if (FAILED(rc)) throw rc;
4004 Guid uuidNewMachine(bstrNewMachineId);
4005 m->llGuidsMachinesCreated.push_back(uuidNewMachine);
4006
4007 // Add floppies and CD-ROMs to the appropriate controllers.
4008 std::list<VirtualSystemDescriptionEntry*> vsdeFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy);
4009 if (vsdeFloppy.size() > 1)
4010 throw setError(VBOX_E_FILE_ERROR,
4011 tr("Too many floppy controllers in OVF; import facility only supports one"));
4012 std::list<VirtualSystemDescriptionEntry*> vsdeCDROM = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
4013 if ( !vsdeFloppy.empty()
4014 || !vsdeCDROM.empty()
4015 )
4016 {
4017 // If there's an error here we need to close the session, so
4018 // we need another try/catch block.
4019
4020 try
4021 {
4022 // to attach things we need to open a session for the new machine
4023 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
4024 if (FAILED(rc)) throw rc;
4025 stack.fSessionOpen = true;
4026
4027 ComPtr<IMachine> sMachine;
4028 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
4029 if (FAILED(rc)) throw rc;
4030
4031 // floppy first
4032 if (vsdeFloppy.size() == 1)
4033 {
4034 ComPtr<IStorageController> pController;
4035 rc = sMachine->AddStorageController(Bstr("Floppy").raw(),
4036 StorageBus_Floppy,
4037 pController.asOutParam());
4038 if (FAILED(rc)) throw rc;
4039
4040 Bstr bstrName;
4041 rc = pController->COMGETTER(Name)(bstrName.asOutParam());
4042 if (FAILED(rc)) throw rc;
4043
4044 // this is for rollback later
4045 MyHardDiskAttachment mhda;
4046 mhda.pMachine = pNewMachine;
4047 mhda.controllerName = bstrName;
4048 mhda.lControllerPort = 0;
4049 mhda.lDevice = 0;
4050
4051 Log(("Attaching floppy\n"));
4052
4053 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),
4054 mhda.lControllerPort,
4055 mhda.lDevice,
4056 DeviceType_Floppy,
4057 NULL);
4058 if (FAILED(rc)) throw rc;
4059
4060 stack.llHardDiskAttachments.push_back(mhda);
4061 }
4062
4063 rc = sMachine->SaveSettings();
4064 if (FAILED(rc)) throw rc;
4065
4066 // only now that we're done with all storage devices, close the session
4067 rc = stack.pSession->UnlockMachine();
4068 if (FAILED(rc)) throw rc;
4069 stack.fSessionOpen = false;
4070 }
4071 catch(HRESULT aRC)
4072 {
4073 com::ErrorInfo info;
4074
4075 if (stack.fSessionOpen)
4076 stack.pSession->UnlockMachine();
4077
4078 if (info.isFullAvailable())
4079 throw setError(aRC, Utf8Str(info.getText()).c_str());
4080 else
4081 throw setError(aRC, "Unknown error during OVF import");
4082 }
4083 }
4084
4085 // create the storage devices & connect them to the appropriate controllers
4086 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
4087 if (!avsdeHDs.empty())
4088 {
4089 // If there's an error here we need to close the session, so
4090 // we need another try/catch block.
4091 try
4092 {
4093#ifdef LOG_ENABLED
4094 if (LogIsEnabled())
4095 {
4096 size_t i = 0;
4097 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4098 itHD != avsdeHDs.end(); ++itHD, i++)
4099 Log(("avsdeHDs[%zu]: strRef=%s strOvf=%s\n", i, (*itHD)->strRef.c_str(), (*itHD)->strOvf.c_str()));
4100 i = 0;
4101 for (ovf::DiskImagesMap::const_iterator itDisk = stack.mapDisks.begin(); itDisk != stack.mapDisks.end(); ++itDisk)
4102 Log(("mapDisks[%zu]: strDiskId=%s strHref=%s\n",
4103 i, itDisk->second.strDiskId.c_str(), itDisk->second.strHref.c_str()));
4104
4105 }
4106#endif
4107
4108 // to attach things we need to open a session for the new machine
4109 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
4110 if (FAILED(rc)) throw rc;
4111 stack.fSessionOpen = true;
4112
4113 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
4114 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
4115 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
4116 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
4117
4118
4119 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
4120 std::set<RTCString> disksResolvedNames;
4121
4122 uint32_t cImportedDisks = 0;
4123
4124 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
4125 {
4126/** @todo r=bird: Most of the code here is duplicated in the other machine
4127 * import method, factor out. */
4128 ovf::DiskImage diCurrent = oit->second;
4129
4130 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
4131 /* Iterate over all given images of the virtual system
4132 * description. We need to find the target image path,
4133 * which could be changed by the user. */
4134 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
4135 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4136 itHD != avsdeHDs.end();
4137 ++itHD)
4138 {
4139 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4140 if (vsdeHD->strRef == diCurrent.strDiskId)
4141 {
4142 vsdeTargetHD = vsdeHD;
4143 break;
4144 }
4145 }
4146 if (!vsdeTargetHD)
4147 {
4148 /* possible case if an image belongs to other virtual system (OVF package with multiple VMs inside) */
4149 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
4150 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
4151 NOREF(vmNameEntry);
4152 ++oit;
4153 continue;
4154 }
4155
4156 //diCurrent.strDiskId contains the image identifier (e.g. "vmdisk1"), which should exist
4157 //in the virtual system's images map under that ID and also in the global images map
4158 ovf::VirtualDisksMap::const_iterator itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
4159 if (itVDisk == vsysThis.mapVirtualDisks.end())
4160 throw setError(E_FAIL,
4161 tr("Internal inconsistency looking up disk image '%s'"),
4162 diCurrent.strHref.c_str());
4163
4164 /*
4165 * preliminary check availability of the image
4166 * This step is useful if image is placed in the OVA (TAR) package
4167 */
4168 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
4169 {
4170 /* It means that we possibly have imported the storage earlier on the previous loop steps*/
4171 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
4172 if (h != disksResolvedNames.end())
4173 {
4174 /* Yes, image name was found, we can skip it*/
4175 ++oit;
4176 continue;
4177 }
4178l_skipped:
4179 rc = i_preCheckImageAvailability(stack);
4180 if (SUCCEEDED(rc))
4181 {
4182 /* current opened file isn't the same as passed one */
4183 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
4184 {
4185 /* availableImage contains the image file reference (e.g. "disk1.vmdk"), which should
4186 * exist in the global images map.
4187 * And find the image from the OVF's disk list */
4188 ovf::DiskImagesMap::const_iterator itDiskImage;
4189 for (itDiskImage = stack.mapDisks.begin();
4190 itDiskImage != stack.mapDisks.end();
4191 itDiskImage++)
4192 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
4193 Utf8Str::CaseInsensitive) == 0)
4194 break;
4195 if (itDiskImage == stack.mapDisks.end())
4196 {
4197 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
4198 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
4199 goto l_skipped;
4200 }
4201
4202 /* replace with a new found image */
4203 diCurrent = *(&itDiskImage->second);
4204
4205 /*
4206 * Again iterate over all given images of the virtual system
4207 * description using the found image
4208 */
4209 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4210 itHD != avsdeHDs.end();
4211 ++itHD)
4212 {
4213 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4214 if (vsdeHD->strRef == diCurrent.strDiskId)
4215 {
4216 vsdeTargetHD = vsdeHD;
4217 break;
4218 }
4219 }
4220
4221 /*
4222 * in this case it's an error because something is wrong with the OVF description file.
4223 * May be VBox imports OVA package with wrong file sequence inside the archive.
4224 */
4225 if (!vsdeTargetHD)
4226 throw setError(E_FAIL,
4227 tr("Internal inconsistency looking up disk image '%s'"),
4228 diCurrent.strHref.c_str());
4229
4230 itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
4231 if (itVDisk == vsysThis.mapVirtualDisks.end())
4232 throw setError(E_FAIL,
4233 tr("Internal inconsistency looking up disk image '%s'"),
4234 diCurrent.strHref.c_str());
4235 }
4236 else
4237 {
4238 ++oit;
4239 }
4240 }
4241 else
4242 {
4243 ++oit;
4244 continue;
4245 }
4246 }
4247 else
4248 {
4249 /* just continue with normal files */
4250 ++oit;
4251 }
4252
4253 /* very important to store image name for the next checks */
4254 disksResolvedNames.insert(diCurrent.strHref);
4255////// end of duplicated code.
4256 const ovf::VirtualDisk &ovfVdisk = itVDisk->second;
4257
4258 ComObjPtr<Medium> pTargetMedium;
4259 if (stack.locInfo.storageType == VFSType_Cloud)
4260 {
4261 /* We have already all disks prepared (converted and registered in the VBox)
4262 * and in the correct place (VM machine folder).
4263 * so what is needed is to get the disk uuid from VirtualDisk::strDiskId
4264 * and find the Medium object with this uuid.
4265 * next just attach the Medium object to new VM.
4266 * VirtualDisk::strDiskId is filled in the */
4267
4268 Guid id(ovfVdisk.strDiskId);
4269 rc = mVirtualBox->i_findHardDiskById(id, false, &pTargetMedium);
4270 if (FAILED(rc))
4271 throw rc;
4272 }
4273 else
4274 {
4275 i_importOneDiskImage(diCurrent,
4276 vsdeTargetHD->strVBoxCurrent,
4277 pTargetMedium,
4278 stack);
4279 }
4280
4281 // now use the new uuid to attach the medium to our new machine
4282 ComPtr<IMachine> sMachine;
4283 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
4284 if (FAILED(rc))
4285 throw rc;
4286
4287 // find the hard disk controller to which we should attach
4288 ovf::HardDiskController hdc = (*vsysThis.mapControllers.find(ovfVdisk.idController)).second;
4289
4290 // this is for rollback later
4291 MyHardDiskAttachment mhda;
4292 mhda.pMachine = pNewMachine;
4293
4294 i_convertDiskAttachmentValues(hdc,
4295 ovfVdisk.ulAddressOnParent,
4296 mhda.controllerName,
4297 mhda.lControllerPort,
4298 mhda.lDevice);
4299
4300 Log(("Attaching disk %s to port %d on device %d\n",
4301 vsdeTargetHD->strVBoxCurrent.c_str(), mhda.lControllerPort, mhda.lDevice));
4302
4303 DeviceType_T devType = DeviceType_Null;
4304 rc = pTargetMedium->COMGETTER(DeviceType)(&devType);
4305 if (FAILED(rc))
4306 throw rc;
4307
4308 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
4309 mhda.lControllerPort, // long controllerPort
4310 mhda.lDevice, // long device
4311 devType, // DeviceType_T type
4312 pTargetMedium);
4313 if (FAILED(rc))
4314 throw rc;
4315
4316 stack.llHardDiskAttachments.push_back(mhda);
4317
4318 rc = sMachine->SaveSettings();
4319 if (FAILED(rc))
4320 throw rc;
4321
4322 ++cImportedDisks;
4323
4324 } // end while(oit != stack.mapDisks.end())
4325
4326 /*
4327 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
4328 */
4329 if(cImportedDisks < avsdeHDs.size())
4330 {
4331 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
4332 vmNameEntry->strOvf.c_str()));
4333 }
4334
4335 // only now that we're done with all disks, close the session
4336 rc = stack.pSession->UnlockMachine();
4337 if (FAILED(rc))
4338 throw rc;
4339 stack.fSessionOpen = false;
4340 }
4341 catch(HRESULT aRC)
4342 {
4343 com::ErrorInfo info;
4344 if (stack.fSessionOpen)
4345 stack.pSession->UnlockMachine();
4346
4347 if (info.isFullAvailable())
4348 throw setError(aRC, Utf8Str(info.getText()).c_str());
4349 else
4350 throw setError(aRC, "Unknown error during OVF import");
4351 }
4352 }
4353 LogFlowFuncLeave();
4354}
4355
4356/**
4357 * Imports one OVF virtual system (described by a vbox:Machine tag represented by the given config
4358 * structure) into VirtualBox by creating an IMachine instance, which is returned.
4359 *
4360 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
4361 * up any leftovers from this function. For this, the given ImportStack instance has received information
4362 * about what needs cleaning up (to support rollback).
4363 *
4364 * The machine config stored in the settings::MachineConfigFile structure contains the UUIDs of
4365 * the disk attachments used by the machine when it was exported. We also add vbox:uuid attributes
4366 * to the OVF disks sections so we can look them up. While importing these UUIDs into a second host
4367 * will most probably work, reimporting them into the same host will cause conflicts, so we always
4368 * generate new ones on import. This involves the following:
4369 *
4370 * 1) Scan the machine config for disk attachments.
4371 *
4372 * 2) For each disk attachment found, look up the OVF disk image from the disk references section
4373 * and import the disk into VirtualBox, which creates a new UUID for it. In the machine config,
4374 * replace the old UUID with the new one.
4375 *
4376 * 3) Change the machine config according to the OVF virtual system descriptions, in case the
4377 * caller has modified them using setFinalValues().
4378 *
4379 * 4) Create the VirtualBox machine with the modfified machine config.
4380 *
4381 * @param vsdescThis
4382 * @param pReturnNewMachine
4383 * @param stack
4384 */
4385void Appliance::i_importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
4386 ComPtr<IMachine> &pReturnNewMachine,
4387 ImportStack &stack)
4388{
4389 LogFlowFuncEnter();
4390 Assert(vsdescThis->m->pConfig);
4391
4392 HRESULT rc = S_OK;
4393
4394 settings::MachineConfigFile &config = *vsdescThis->m->pConfig;
4395
4396 /*
4397 * step 1): modify machine config according to OVF config, in case the user
4398 * has modified them using setFinalValues()
4399 */
4400
4401 /* OS Type */
4402 config.machineUserData.strOsType = stack.strOsTypeVBox;
4403 /* Groups */
4404 if (stack.strPrimaryGroup.isEmpty() || stack.strPrimaryGroup == "/")
4405 {
4406 config.machineUserData.llGroups.clear();
4407 config.machineUserData.llGroups.push_back("/");
4408 }
4409 else
4410 {
4411 /* Replace the primary group if there is one, otherwise add it. */
4412 if (config.machineUserData.llGroups.size())
4413 config.machineUserData.llGroups.pop_front();
4414 config.machineUserData.llGroups.push_front(stack.strPrimaryGroup);
4415 }
4416 /* Description */
4417 config.machineUserData.strDescription = stack.strDescription;
4418 /* CPU count & extented attributes */
4419 config.hardwareMachine.cCPUs = stack.cCPUs;
4420 if (stack.fForceIOAPIC)
4421 config.hardwareMachine.fHardwareVirt = true;
4422 if (stack.fForceIOAPIC)
4423 config.hardwareMachine.biosSettings.fIOAPICEnabled = true;
4424 /* RAM size */
4425 config.hardwareMachine.ulMemorySizeMB = stack.ulMemorySizeMB;
4426
4427/*
4428 <const name="HardDiskControllerIDE" value="14" />
4429 <const name="HardDiskControllerSATA" value="15" />
4430 <const name="HardDiskControllerSCSI" value="16" />
4431 <const name="HardDiskControllerSAS" value="17" />
4432*/
4433
4434#ifdef VBOX_WITH_USB
4435 /* USB controller */
4436 if (stack.fUSBEnabled)
4437 {
4438 /** @todo r=klaus add support for arbitrary USB controller types, this can't handle
4439 * multiple controllers due to its design anyway */
4440 /* Usually the OHCI controller is enabled already, need to check. But
4441 * do this only if there is no xHCI controller. */
4442 bool fOHCIEnabled = false;
4443 bool fXHCIEnabled = false;
4444 settings::USBControllerList &llUSBControllers = config.hardwareMachine.usbSettings.llUSBControllers;
4445 settings::USBControllerList::iterator it;
4446 for (it = llUSBControllers.begin(); it != llUSBControllers.end(); ++it)
4447 {
4448 if (it->enmType == USBControllerType_OHCI)
4449 fOHCIEnabled = true;
4450 if (it->enmType == USBControllerType_XHCI)
4451 fXHCIEnabled = true;
4452 }
4453
4454 if (!fXHCIEnabled && !fOHCIEnabled)
4455 {
4456 settings::USBController ctrl;
4457 ctrl.strName = "OHCI";
4458 ctrl.enmType = USBControllerType_OHCI;
4459
4460 llUSBControllers.push_back(ctrl);
4461 }
4462 }
4463 else
4464 config.hardwareMachine.usbSettings.llUSBControllers.clear();
4465#endif
4466 /* Audio adapter */
4467 if (stack.strAudioAdapter.isNotEmpty())
4468 {
4469 config.hardwareMachine.audioAdapter.fEnabled = true;
4470 config.hardwareMachine.audioAdapter.controllerType = (AudioControllerType_T)stack.strAudioAdapter.toUInt32();
4471 }
4472 else
4473 config.hardwareMachine.audioAdapter.fEnabled = false;
4474 /* Network adapter */
4475 settings::NetworkAdaptersList &llNetworkAdapters = config.hardwareMachine.llNetworkAdapters;
4476 /* First disable all network cards, they will be enabled below again. */
4477 settings::NetworkAdaptersList::iterator it1;
4478 bool fKeepAllMACs = m->optListImport.contains(ImportOptions_KeepAllMACs);
4479 bool fKeepNATMACs = m->optListImport.contains(ImportOptions_KeepNATMACs);
4480 for (it1 = llNetworkAdapters.begin(); it1 != llNetworkAdapters.end(); ++it1)
4481 {
4482 it1->fEnabled = false;
4483 if (!( fKeepAllMACs
4484 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NAT)
4485 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NATNetwork)))
4486 /* Force generation of new MAC address below. */
4487 it1->strMACAddress.setNull();
4488 }
4489 /* Now iterate over all network entries. */
4490 std::list<VirtualSystemDescriptionEntry*> avsdeNWs = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
4491 if (!avsdeNWs.empty())
4492 {
4493 /* Iterate through all network adapter entries and search for the
4494 * corresponding one in the machine config. If one is found, configure
4495 * it based on the user settings. */
4496 list<VirtualSystemDescriptionEntry*>::const_iterator itNW;
4497 for (itNW = avsdeNWs.begin();
4498 itNW != avsdeNWs.end();
4499 ++itNW)
4500 {
4501 VirtualSystemDescriptionEntry *vsdeNW = *itNW;
4502 if ( vsdeNW->strExtraConfigCurrent.startsWith("slot=", Utf8Str::CaseInsensitive)
4503 && vsdeNW->strExtraConfigCurrent.length() > 6)
4504 {
4505 uint32_t iSlot = vsdeNW->strExtraConfigCurrent.substr(5).toUInt32();
4506 /* Iterate through all network adapters in the machine config. */
4507 for (it1 = llNetworkAdapters.begin();
4508 it1 != llNetworkAdapters.end();
4509 ++it1)
4510 {
4511 /* Compare the slots. */
4512 if (it1->ulSlot == iSlot)
4513 {
4514 it1->fEnabled = true;
4515 if (it1->strMACAddress.isEmpty())
4516 Host::i_generateMACAddress(it1->strMACAddress);
4517 it1->type = (NetworkAdapterType_T)vsdeNW->strVBoxCurrent.toUInt32();
4518 break;
4519 }
4520 }
4521 }
4522 }
4523 }
4524
4525 /* Floppy controller */
4526 bool fFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy).size() > 0;
4527 /* DVD controller */
4528 bool fDVD = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM).size() > 0;
4529 /* Iterate over all storage controller check the attachments and remove
4530 * them when necessary. Also detect broken configs with more than one
4531 * attachment. Old VirtualBox versions (prior to 3.2.10) had all disk
4532 * attachments pointing to the last hard disk image, which causes import
4533 * failures. A long fixed bug, however the OVF files are long lived. */
4534 settings::StorageControllersList &llControllers = config.hardwareMachine.storage.llStorageControllers;
4535 Guid hdUuid;
4536 uint32_t cDisks = 0;
4537 bool fInconsistent = false;
4538 bool fRepairDuplicate = false;
4539 settings::StorageControllersList::iterator it3;
4540 for (it3 = llControllers.begin();
4541 it3 != llControllers.end();
4542 ++it3)
4543 {
4544 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
4545 settings::AttachedDevicesList::iterator it4 = llAttachments.begin();
4546 while (it4 != llAttachments.end())
4547 {
4548 if ( ( !fDVD
4549 && it4->deviceType == DeviceType_DVD)
4550 ||
4551 ( !fFloppy
4552 && it4->deviceType == DeviceType_Floppy))
4553 {
4554 it4 = llAttachments.erase(it4);
4555 continue;
4556 }
4557 else if (it4->deviceType == DeviceType_HardDisk)
4558 {
4559 const Guid &thisUuid = it4->uuid;
4560 cDisks++;
4561 if (cDisks == 1)
4562 {
4563 if (hdUuid.isZero())
4564 hdUuid = thisUuid;
4565 else
4566 fInconsistent = true;
4567 }
4568 else
4569 {
4570 if (thisUuid.isZero())
4571 fInconsistent = true;
4572 else if (thisUuid == hdUuid)
4573 fRepairDuplicate = true;
4574 }
4575 }
4576 ++it4;
4577 }
4578 }
4579 /* paranoia... */
4580 if (fInconsistent || cDisks == 1)
4581 fRepairDuplicate = false;
4582
4583 /*
4584 * step 2: scan the machine config for media attachments
4585 */
4586 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
4587 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
4588 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
4589 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
4590
4591 /* Get all hard disk descriptions. */
4592 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
4593 std::list<VirtualSystemDescriptionEntry*>::iterator avsdeHDsIt = avsdeHDs.begin();
4594 /* paranoia - if there is no 1:1 match do not try to repair. */
4595 if (cDisks != avsdeHDs.size())
4596 fRepairDuplicate = false;
4597
4598 // there must be an image in the OVF disk structs with the same UUID
4599
4600 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
4601 std::set<RTCString> disksResolvedNames;
4602
4603 uint32_t cImportedDisks = 0;
4604
4605 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
4606 {
4607/** @todo r=bird: Most of the code here is duplicated in the other machine
4608 * import method, factor out. */
4609 ovf::DiskImage diCurrent = oit->second;
4610
4611 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
4612
4613 /* Iterate over all given disk images of the virtual system
4614 * disks description. We need to find the target disk path,
4615 * which could be changed by the user. */
4616 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
4617 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4618 itHD != avsdeHDs.end();
4619 ++itHD)
4620 {
4621 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4622 if (vsdeHD->strRef == oit->first)
4623 {
4624 vsdeTargetHD = vsdeHD;
4625 break;
4626 }
4627 }
4628 if (!vsdeTargetHD)
4629 {
4630 /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
4631 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
4632 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
4633 NOREF(vmNameEntry);
4634 ++oit;
4635 continue;
4636 }
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646 /*
4647 * preliminary check availability of the image
4648 * This step is useful if image is placed in the OVA (TAR) package
4649 */
4650 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
4651 {
4652 /* It means that we possibly have imported the storage earlier on a previous loop step. */
4653 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
4654 if (h != disksResolvedNames.end())
4655 {
4656 /* Yes, disk name was found, we can skip it*/
4657 ++oit;
4658 continue;
4659 }
4660l_skipped:
4661 rc = i_preCheckImageAvailability(stack);
4662 if (SUCCEEDED(rc))
4663 {
4664 /* current opened file isn't the same as passed one */
4665 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
4666 {
4667 // availableImage contains the disk identifier (e.g. "vmdisk1"), which should exist
4668 // in the virtual system's disks map under that ID and also in the global images map
4669 // and find the disk from the OVF's disk list
4670 ovf::DiskImagesMap::const_iterator itDiskImage;
4671 for (itDiskImage = stack.mapDisks.begin();
4672 itDiskImage != stack.mapDisks.end();
4673 itDiskImage++)
4674 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
4675 Utf8Str::CaseInsensitive) == 0)
4676 break;
4677 if (itDiskImage == stack.mapDisks.end())
4678 {
4679 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
4680 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
4681 goto l_skipped;
4682 }
4683 //throw setError(E_FAIL,
4684 // tr("Internal inconsistency looking up disk image '%s'. "
4685 // "Check compliance OVA package structure and file names "
4686 // "references in the section <References> in the OVF file."),
4687 // stack.pszOvaLookAheadName);
4688
4689 /* replace with a new found disk image */
4690 diCurrent = *(&itDiskImage->second);
4691
4692 /*
4693 * Again iterate over all given disk images of the virtual system
4694 * disks description using the found disk image
4695 */
4696 vsdeTargetHD = NULL;
4697 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4698 itHD != avsdeHDs.end();
4699 ++itHD)
4700 {
4701 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4702 if (vsdeHD->strRef == diCurrent.strDiskId)
4703 {
4704 vsdeTargetHD = vsdeHD;
4705 break;
4706 }
4707 }
4708
4709 /*
4710 * in this case it's an error because something is wrong with the OVF description file.
4711 * May be VBox imports OVA package with wrong file sequence inside the archive.
4712 */
4713 if (!vsdeTargetHD)
4714 throw setError(E_FAIL,
4715 tr("Internal inconsistency looking up disk image '%s'"),
4716 diCurrent.strHref.c_str());
4717
4718
4719
4720
4721
4722 }
4723 else
4724 {
4725 ++oit;
4726 }
4727 }
4728 else
4729 {
4730 ++oit;
4731 continue;
4732 }
4733 }
4734 else
4735 {
4736 /* just continue with normal files*/
4737 ++oit;
4738 }
4739
4740 /* Important! to store disk name for the next checks */
4741 disksResolvedNames.insert(diCurrent.strHref);
4742////// end of duplicated code.
4743 // there must be an image in the OVF disk structs with the same UUID
4744 bool fFound = false;
4745 Utf8Str strUuid;
4746
4747 // for each storage controller...
4748 for (settings::StorageControllersList::iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
4749 sit != config.hardwareMachine.storage.llStorageControllers.end();
4750 ++sit)
4751 {
4752 settings::StorageController &sc = *sit;
4753
4754 // for each medium attachment to this controller...
4755 for (settings::AttachedDevicesList::iterator dit = sc.llAttachedDevices.begin();
4756 dit != sc.llAttachedDevices.end();
4757 ++dit)
4758 {
4759 settings::AttachedDevice &d = *dit;
4760
4761 if (d.uuid.isZero())
4762 // empty DVD and floppy media
4763 continue;
4764
4765 // When repairing a broken VirtualBox xml config section (written
4766 // by VirtualBox versions earlier than 3.2.10) assume the disks
4767 // show up in the same order as in the OVF description.
4768 if (fRepairDuplicate)
4769 {
4770 VirtualSystemDescriptionEntry *vsdeHD = *avsdeHDsIt;
4771 ovf::DiskImagesMap::const_iterator itDiskImage = stack.mapDisks.find(vsdeHD->strRef);
4772 if (itDiskImage != stack.mapDisks.end())
4773 {
4774 const ovf::DiskImage &di = itDiskImage->second;
4775 d.uuid = Guid(di.uuidVBox);
4776 }
4777 ++avsdeHDsIt;
4778 }
4779
4780 // convert the Guid to string
4781 strUuid = d.uuid.toString();
4782
4783 if (diCurrent.uuidVBox != strUuid)
4784 {
4785 continue;
4786 }
4787
4788 /*
4789 * step 3: import disk
4790 */
4791 ComObjPtr<Medium> pTargetMedium;
4792 i_importOneDiskImage(diCurrent,
4793 vsdeTargetHD->strVBoxCurrent,
4794 pTargetMedium,
4795 stack);
4796
4797 // ... and replace the old UUID in the machine config with the one of
4798 // the imported disk that was just created
4799 Bstr hdId;
4800 rc = pTargetMedium->COMGETTER(Id)(hdId.asOutParam());
4801 if (FAILED(rc)) throw rc;
4802
4803 /*
4804 * 1. saving original UUID for restoring in case of failure.
4805 * 2. replacement of original UUID by new UUID in the current VM config (settings::MachineConfigFile).
4806 */
4807 {
4808 rc = stack.saveOriginalUUIDOfAttachedDevice(d, Utf8Str(hdId));
4809 d.uuid = hdId;
4810 }
4811
4812 fFound = true;
4813 break;
4814 } // for (settings::AttachedDevicesList::const_iterator dit = sc.llAttachedDevices.begin();
4815 } // for (settings::StorageControllersList::const_iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
4816
4817 // no disk with such a UUID found:
4818 if (!fFound)
4819 throw setError(E_FAIL,
4820 tr("<vbox:Machine> element in OVF contains a medium attachment for the disk image %s "
4821 "but the OVF describes no such image"),
4822 strUuid.c_str());
4823
4824 ++cImportedDisks;
4825
4826 }// while(oit != stack.mapDisks.end())
4827
4828
4829 /*
4830 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
4831 */
4832 if(cImportedDisks < avsdeHDs.size())
4833 {
4834 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
4835 vmNameEntry->strOvf.c_str()));
4836 }
4837
4838 /*
4839 * step 4): create the machine and have it import the config
4840 */
4841
4842 ComObjPtr<Machine> pNewMachine;
4843 rc = pNewMachine.createObject();
4844 if (FAILED(rc)) throw rc;
4845
4846 // this magic constructor fills the new machine object with the MachineConfig
4847 // instance that we created from the vbox:Machine
4848 rc = pNewMachine->init(mVirtualBox,
4849 stack.strNameVBox,// name from OVF preparations; can be suffixed to avoid duplicates
4850 stack.strSettingsFilename,
4851 config); // the whole machine config
4852 if (FAILED(rc)) throw rc;
4853
4854 pReturnNewMachine = ComPtr<IMachine>(pNewMachine);
4855
4856 // and register it
4857 rc = mVirtualBox->RegisterMachine(pNewMachine);
4858 if (FAILED(rc)) throw rc;
4859
4860 // store new machine for roll-back in case of errors
4861 Bstr bstrNewMachineId;
4862 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
4863 if (FAILED(rc)) throw rc;
4864 m->llGuidsMachinesCreated.push_back(Guid(bstrNewMachineId));
4865
4866 LogFlowFuncLeave();
4867}
4868
4869/**
4870 * @throws HRESULT errors.
4871 */
4872void Appliance::i_importMachines(ImportStack &stack)
4873{
4874 // this is safe to access because this thread only gets started
4875 const ovf::OVFReader &reader = *m->pReader;
4876
4877 // create a session for the machine + disks we manipulate below
4878 HRESULT rc = stack.pSession.createInprocObject(CLSID_Session);
4879 ComAssertComRCThrowRC(rc);
4880
4881 list<ovf::VirtualSystem>::const_iterator it;
4882 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it1;
4883 /* Iterate through all virtual systems of that appliance */
4884 size_t i = 0;
4885 for (it = reader.m_llVirtualSystems.begin(), it1 = m->virtualSystemDescriptions.begin();
4886 it != reader.m_llVirtualSystems.end() && it1 != m->virtualSystemDescriptions.end();
4887 ++it, ++it1, ++i)
4888 {
4889 const ovf::VirtualSystem &vsysThis = *it;
4890 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it1);
4891
4892 ComPtr<IMachine> pNewMachine;
4893
4894 // there are two ways in which we can create a vbox machine from OVF:
4895 // -- either this OVF was written by vbox 3.2 or later, in which case there is a <vbox:Machine> element
4896 // in the <VirtualSystem>; then the VirtualSystemDescription::Data has a settings::MachineConfigFile
4897 // with all the machine config pretty-parsed;
4898 // -- or this is an OVF from an older vbox or an external source, and then we need to translate the
4899 // VirtualSystemDescriptionEntry and do import work
4900
4901 // Even for the vbox:Machine case, there are a number of configuration items that will be taken from
4902 // the OVF because otherwise the "override import parameters" mechanism in the GUI won't work.
4903
4904 // VM name
4905 std::list<VirtualSystemDescriptionEntry*> vsdeName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
4906 if (vsdeName.size() < 1)
4907 throw setError(VBOX_E_FILE_ERROR,
4908 tr("Missing VM name"));
4909 stack.strNameVBox = vsdeName.front()->strVBoxCurrent;
4910
4911 // Primary group, which is entirely optional.
4912 stack.strPrimaryGroup.setNull();
4913 std::list<VirtualSystemDescriptionEntry*> vsdePrimaryGroup = vsdescThis->i_findByType(VirtualSystemDescriptionType_PrimaryGroup);
4914 if (vsdePrimaryGroup.size() >= 1)
4915 {
4916 stack.strPrimaryGroup = vsdePrimaryGroup.front()->strVBoxCurrent;
4917 if (stack.strPrimaryGroup.isEmpty())
4918 stack.strPrimaryGroup = "/";
4919 }
4920
4921 // Draw the right conclusions from the (possibly modified) VM settings
4922 // file name and base folder. If the VM settings file name is modified,
4923 // it takes precedence, otherwise it is recreated from the base folder
4924 // and the primary group.
4925 stack.strSettingsFilename.setNull();
4926 std::list<VirtualSystemDescriptionEntry*> vsdeSettingsFile = vsdescThis->i_findByType(VirtualSystemDescriptionType_SettingsFile);
4927 if (vsdeSettingsFile.size() >= 1)
4928 {
4929 VirtualSystemDescriptionEntry *vsdeSF1 = vsdeSettingsFile.front();
4930 if (vsdeSF1->strVBoxCurrent != vsdeSF1->strVBoxSuggested)
4931 stack.strSettingsFilename = vsdeSF1->strVBoxCurrent;
4932 }
4933 if (stack.strSettingsFilename.isEmpty())
4934 {
4935 Utf8Str strBaseFolder;
4936 std::list<VirtualSystemDescriptionEntry*> vsdeBaseFolder = vsdescThis->i_findByType(VirtualSystemDescriptionType_BaseFolder);
4937 if (vsdeBaseFolder.size() >= 1)
4938 strBaseFolder = vsdeBaseFolder.front()->strVBoxCurrent;
4939 Bstr bstrSettingsFilename;
4940 rc = mVirtualBox->ComposeMachineFilename(Bstr(stack.strNameVBox).raw(),
4941 Bstr(stack.strPrimaryGroup).raw(),
4942 NULL /* aCreateFlags */,
4943 Bstr(strBaseFolder).raw(),
4944 bstrSettingsFilename.asOutParam());
4945 if (FAILED(rc)) throw rc;
4946 stack.strSettingsFilename = bstrSettingsFilename;
4947 }
4948
4949 // Determine the machine folder from the settings file.
4950 LogFunc(("i=%zu strName=%s strSettingsFilename=%s\n", i, stack.strNameVBox.c_str(), stack.strSettingsFilename.c_str()));
4951 stack.strMachineFolder = stack.strSettingsFilename;
4952 stack.strMachineFolder.stripFilename();
4953
4954 // guest OS type
4955 std::list<VirtualSystemDescriptionEntry*> vsdeOS;
4956 vsdeOS = vsdescThis->i_findByType(VirtualSystemDescriptionType_OS);
4957 if (vsdeOS.size() < 1)
4958 throw setError(VBOX_E_FILE_ERROR,
4959 tr("Missing guest OS type"));
4960 stack.strOsTypeVBox = vsdeOS.front()->strVBoxCurrent;
4961
4962 // CPU count
4963 std::list<VirtualSystemDescriptionEntry*> vsdeCPU = vsdescThis->i_findByType(VirtualSystemDescriptionType_CPU);
4964 if (vsdeCPU.size() != 1)
4965 throw setError(VBOX_E_FILE_ERROR, tr("CPU count missing"));
4966
4967 stack.cCPUs = vsdeCPU.front()->strVBoxCurrent.toUInt32();
4968 // We need HWVirt & IO-APIC if more than one CPU is requested
4969 if (stack.cCPUs > 1)
4970 {
4971 stack.fForceHWVirt = true;
4972 stack.fForceIOAPIC = true;
4973 }
4974
4975 // RAM
4976 std::list<VirtualSystemDescriptionEntry*> vsdeRAM = vsdescThis->i_findByType(VirtualSystemDescriptionType_Memory);
4977 if (vsdeRAM.size() != 1)
4978 throw setError(VBOX_E_FILE_ERROR, tr("RAM size missing"));
4979 stack.ulMemorySizeMB = (ULONG)vsdeRAM.front()->strVBoxCurrent.toUInt64();
4980
4981#ifdef VBOX_WITH_USB
4982 // USB controller
4983 std::list<VirtualSystemDescriptionEntry*> vsdeUSBController =
4984 vsdescThis->i_findByType(VirtualSystemDescriptionType_USBController);
4985 // USB support is enabled if there's at least one such entry; to disable USB support,
4986 // the type of the USB item would have been changed to "ignore"
4987 stack.fUSBEnabled = !vsdeUSBController.empty();
4988#endif
4989 // audio adapter
4990 std::list<VirtualSystemDescriptionEntry*> vsdeAudioAdapter =
4991 vsdescThis->i_findByType(VirtualSystemDescriptionType_SoundCard);
4992 /** @todo we support one audio adapter only */
4993 if (!vsdeAudioAdapter.empty())
4994 stack.strAudioAdapter = vsdeAudioAdapter.front()->strVBoxCurrent;
4995
4996 // for the description of the new machine, always use the OVF entry, the user may have changed it in the import config
4997 std::list<VirtualSystemDescriptionEntry*> vsdeDescription =
4998 vsdescThis->i_findByType(VirtualSystemDescriptionType_Description);
4999 if (!vsdeDescription.empty())
5000 stack.strDescription = vsdeDescription.front()->strVBoxCurrent;
5001
5002 // import vbox:machine or OVF now
5003 if (vsdescThis->m->pConfig)
5004 // vbox:Machine config
5005 i_importVBoxMachine(vsdescThis, pNewMachine, stack);
5006 else
5007 // generic OVF config
5008 i_importMachineGeneric(vsysThis, vsdescThis, pNewMachine, stack);
5009
5010 } // for (it = pAppliance->m->llVirtualSystems.begin() ...
5011}
5012
5013HRESULT Appliance::ImportStack::saveOriginalUUIDOfAttachedDevice(settings::AttachedDevice &device,
5014 const Utf8Str &newlyUuid)
5015{
5016 HRESULT rc = S_OK;
5017
5018 /* save for restoring */
5019 mapNewUUIDsToOriginalUUIDs.insert(std::make_pair(newlyUuid, device.uuid.toString()));
5020
5021 return rc;
5022}
5023
5024HRESULT Appliance::ImportStack::restoreOriginalUUIDOfAttachedDevice(settings::MachineConfigFile *config)
5025{
5026 HRESULT rc = S_OK;
5027
5028 settings::StorageControllersList &llControllers = config->hardwareMachine.storage.llStorageControllers;
5029 settings::StorageControllersList::iterator itscl;
5030 for (itscl = llControllers.begin();
5031 itscl != llControllers.end();
5032 ++itscl)
5033 {
5034 settings::AttachedDevicesList &llAttachments = itscl->llAttachedDevices;
5035 settings::AttachedDevicesList::iterator itadl = llAttachments.begin();
5036 while (itadl != llAttachments.end())
5037 {
5038 std::map<Utf8Str , Utf8Str>::iterator it =
5039 mapNewUUIDsToOriginalUUIDs.find(itadl->uuid.toString());
5040 if(it!=mapNewUUIDsToOriginalUUIDs.end())
5041 {
5042 Utf8Str uuidOriginal = it->second;
5043 itadl->uuid = Guid(uuidOriginal);
5044 mapNewUUIDsToOriginalUUIDs.erase(it->first);
5045 }
5046 ++itadl;
5047 }
5048 }
5049
5050 return rc;
5051}
5052
5053/**
5054 * @throws Nothing
5055 */
5056RTVFSIOSTREAM Appliance::ImportStack::claimOvaLookAHead(void)
5057{
5058 RTVFSIOSTREAM hVfsIos = this->hVfsIosOvaLookAhead;
5059 this->hVfsIosOvaLookAhead = NIL_RTVFSIOSTREAM;
5060 /* We don't free the name since it may be referenced in error messages and such. */
5061 return hVfsIos;
5062}
5063
Note: See TracBrowser for help on using the repository browser.

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