VirtualBox

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

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

Main/Appliance::i_readImpl: Fixes for regressions introduced by r130427 (bugref:130427) and cleaning up the code, getting rid of the very silly way of returning status codes by exception. Untested.

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