VirtualBox

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

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

scm fixes.

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