VirtualBox

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

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

bugref:9416. scm fix.

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

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