VirtualBox

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

Last change on this file since 86728 was 86509, checked in by vboxsync, 4 years ago

VBoxSVC/Appliance: Fixed IMachine reference leak in Appliance::i_searchUniqueVMName(). bugref:9841

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