VirtualBox

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

Last change on this file since 85672 was 85283, checked in by vboxsync, 4 years ago

Main/ApplianceImpl.cpp: Signed/unsigned conversion issues in i_searchUniqueImageFilePath. Renamed it to i_ensureUniqueImageFilePath as it's not really searching anything. bugref:9790

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 254.5 KB
Line 
1/* $Id: ApplianceImplImport.cpp 85283 2020-07-12 14:54:15Z 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 pNewMachine out: Newly created machine.
4321 * @param stack Cleanup stack for when this throws.
4322 */
4323void Appliance::i_importMachineGeneric(const ovf::VirtualSystem &vsysThis,
4324 ComObjPtr<VirtualSystemDescription> &vsdescThis,
4325 ComPtr<IMachine> &pNewMachine,
4326 ImportStack &stack)
4327{
4328 LogFlowFuncEnter();
4329 HRESULT rc;
4330
4331 // Get the instance of IGuestOSType which matches our string guest OS type so we
4332 // can use recommended defaults for the new machine where OVF doesn't provide any
4333 ComPtr<IGuestOSType> osType;
4334 rc = mVirtualBox->GetGuestOSType(Bstr(stack.strOsTypeVBox).raw(), osType.asOutParam());
4335 if (FAILED(rc)) throw rc;
4336
4337 /* Create the machine */
4338 SafeArray<BSTR> groups; /* no groups, or maybe one group... */
4339 if (!stack.strPrimaryGroup.isEmpty() && stack.strPrimaryGroup != "/")
4340 Bstr(stack.strPrimaryGroup).detachTo(groups.appendedRaw());
4341 rc = mVirtualBox->CreateMachine(Bstr(stack.strSettingsFilename).raw(),
4342 Bstr(stack.strNameVBox).raw(),
4343 ComSafeArrayAsInParam(groups),
4344 Bstr(stack.strOsTypeVBox).raw(),
4345 NULL, /* aCreateFlags */
4346 pNewMachine.asOutParam());
4347 if (FAILED(rc)) throw rc;
4348
4349 // set the description
4350 if (!stack.strDescription.isEmpty())
4351 {
4352 rc = pNewMachine->COMSETTER(Description)(Bstr(stack.strDescription).raw());
4353 if (FAILED(rc)) throw rc;
4354 }
4355
4356 // CPU count
4357 rc = pNewMachine->COMSETTER(CPUCount)(stack.cCPUs);
4358 if (FAILED(rc)) throw rc;
4359
4360 if (stack.fForceHWVirt)
4361 {
4362 rc = pNewMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, TRUE);
4363 if (FAILED(rc)) throw rc;
4364 }
4365
4366 // RAM
4367 rc = pNewMachine->COMSETTER(MemorySize)(stack.ulMemorySizeMB);
4368 if (FAILED(rc)) throw rc;
4369
4370 /* VRAM */
4371 /* Get the recommended VRAM for this guest OS type */
4372 ULONG vramVBox;
4373 rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox);
4374 if (FAILED(rc)) throw rc;
4375
4376 /* Set the VRAM */
4377 ComPtr<IGraphicsAdapter> pGraphicsAdapter;
4378 rc = pNewMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
4379 if (FAILED(rc)) throw rc;
4380 rc = pGraphicsAdapter->COMSETTER(VRAMSize)(vramVBox);
4381 if (FAILED(rc)) throw rc;
4382
4383 // I/O APIC: Generic OVF has no setting for this. Enable it if we
4384 // import a Windows VM because if if Windows was installed without IOAPIC,
4385 // it will not mind finding an one later on, but if Windows was installed
4386 // _with_ an IOAPIC, it will bluescreen if it's not found
4387 if (!stack.fForceIOAPIC)
4388 {
4389 Bstr bstrFamilyId;
4390 rc = osType->COMGETTER(FamilyId)(bstrFamilyId.asOutParam());
4391 if (FAILED(rc)) throw rc;
4392 if (bstrFamilyId == "Windows")
4393 stack.fForceIOAPIC = true;
4394 }
4395
4396 if (stack.fForceIOAPIC)
4397 {
4398 ComPtr<IBIOSSettings> pBIOSSettings;
4399 rc = pNewMachine->COMGETTER(BIOSSettings)(pBIOSSettings.asOutParam());
4400 if (FAILED(rc)) throw rc;
4401
4402 rc = pBIOSSettings->COMSETTER(IOAPICEnabled)(TRUE);
4403 if (FAILED(rc)) throw rc;
4404 }
4405
4406 if (stack.strFirmwareType.isNotEmpty())
4407 {
4408 FirmwareType_T firmwareType = FirmwareType_BIOS;
4409 if (stack.strFirmwareType.contains("EFI"))
4410 {
4411 if (stack.strFirmwareType.contains("32"))
4412 firmwareType = FirmwareType_EFI32;
4413 if (stack.strFirmwareType.contains("64"))
4414 firmwareType = FirmwareType_EFI64;
4415 else
4416 firmwareType = FirmwareType_EFI;
4417 }
4418 rc = pNewMachine->COMSETTER(FirmwareType)(firmwareType);
4419 if (FAILED(rc)) throw rc;
4420 }
4421
4422 if (!stack.strAudioAdapter.isEmpty())
4423 if (stack.strAudioAdapter.compare("null", Utf8Str::CaseInsensitive) != 0)
4424 {
4425 uint32_t audio = RTStrToUInt32(stack.strAudioAdapter.c_str()); // should be 0 for AC97
4426 ComPtr<IAudioAdapter> audioAdapter;
4427 rc = pNewMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam());
4428 if (FAILED(rc)) throw rc;
4429 rc = audioAdapter->COMSETTER(Enabled)(true);
4430 if (FAILED(rc)) throw rc;
4431 rc = audioAdapter->COMSETTER(AudioController)(static_cast<AudioControllerType_T>(audio));
4432 if (FAILED(rc)) throw rc;
4433 }
4434
4435#ifdef VBOX_WITH_USB
4436 /* USB Controller */
4437 if (stack.fUSBEnabled)
4438 {
4439 ComPtr<IUSBController> usbController;
4440 rc = pNewMachine->AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, usbController.asOutParam());
4441 if (FAILED(rc)) throw rc;
4442 }
4443#endif /* VBOX_WITH_USB */
4444
4445 /* Change the network adapters */
4446 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
4447
4448 std::list<VirtualSystemDescriptionEntry*> vsdeNW = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
4449 if (vsdeNW.empty())
4450 {
4451 /* No network adapters, so we have to disable our default one */
4452 ComPtr<INetworkAdapter> nwVBox;
4453 rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam());
4454 if (FAILED(rc)) throw rc;
4455 rc = nwVBox->COMSETTER(Enabled)(false);
4456 if (FAILED(rc)) throw rc;
4457 }
4458 else if (vsdeNW.size() > maxNetworkAdapters)
4459 throw setError(VBOX_E_FILE_ERROR,
4460 tr("Too many network adapters: OVF requests %d network adapters, "
4461 "but VirtualBox only supports %d"),
4462 vsdeNW.size(), maxNetworkAdapters);
4463 else
4464 {
4465 list<VirtualSystemDescriptionEntry*>::const_iterator nwIt;
4466 size_t a = 0;
4467 for (nwIt = vsdeNW.begin();
4468 nwIt != vsdeNW.end();
4469 ++nwIt, ++a)
4470 {
4471 const VirtualSystemDescriptionEntry* pvsys = *nwIt;
4472
4473 const Utf8Str &nwTypeVBox = pvsys->strVBoxCurrent;
4474 uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str());
4475 ComPtr<INetworkAdapter> pNetworkAdapter;
4476 rc = pNewMachine->GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
4477 if (FAILED(rc)) throw rc;
4478 /* Enable the network card & set the adapter type */
4479 rc = pNetworkAdapter->COMSETTER(Enabled)(true);
4480 if (FAILED(rc)) throw rc;
4481 rc = pNetworkAdapter->COMSETTER(AdapterType)(static_cast<NetworkAdapterType_T>(tt1));
4482 if (FAILED(rc)) throw rc;
4483
4484 // default is NAT; change to "bridged" if extra conf says so
4485 if (pvsys->strExtraConfigCurrent.endsWith("type=Bridged", Utf8Str::CaseInsensitive))
4486 {
4487 /* Attach to the right interface */
4488 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged);
4489 if (FAILED(rc)) throw rc;
4490 ComPtr<IHost> host;
4491 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
4492 if (FAILED(rc)) throw rc;
4493 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
4494 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
4495 if (FAILED(rc)) throw rc;
4496 // We search for the first host network interface which
4497 // is usable for bridged networking
4498 for (size_t j = 0;
4499 j < nwInterfaces.size();
4500 ++j)
4501 {
4502 HostNetworkInterfaceType_T itype;
4503 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
4504 if (FAILED(rc)) throw rc;
4505 if (itype == HostNetworkInterfaceType_Bridged)
4506 {
4507 Bstr name;
4508 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
4509 if (FAILED(rc)) throw rc;
4510 /* Set the interface name to attach to */
4511 rc = pNetworkAdapter->COMSETTER(BridgedInterface)(name.raw());
4512 if (FAILED(rc)) throw rc;
4513 break;
4514 }
4515 }
4516 }
4517 /* Next test for host only interfaces */
4518 else if (pvsys->strExtraConfigCurrent.endsWith("type=HostOnly", Utf8Str::CaseInsensitive))
4519 {
4520 /* Attach to the right interface */
4521 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly);
4522 if (FAILED(rc)) throw rc;
4523 ComPtr<IHost> host;
4524 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
4525 if (FAILED(rc)) throw rc;
4526 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
4527 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
4528 if (FAILED(rc)) throw rc;
4529 // We search for the first host network interface which
4530 // is usable for host only networking
4531 for (size_t j = 0;
4532 j < nwInterfaces.size();
4533 ++j)
4534 {
4535 HostNetworkInterfaceType_T itype;
4536 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
4537 if (FAILED(rc)) throw rc;
4538 if (itype == HostNetworkInterfaceType_HostOnly)
4539 {
4540 Bstr name;
4541 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
4542 if (FAILED(rc)) throw rc;
4543 /* Set the interface name to attach to */
4544 rc = pNetworkAdapter->COMSETTER(HostOnlyInterface)(name.raw());
4545 if (FAILED(rc)) throw rc;
4546 break;
4547 }
4548 }
4549 }
4550 /* Next test for internal interfaces */
4551 else if (pvsys->strExtraConfigCurrent.endsWith("type=Internal", Utf8Str::CaseInsensitive))
4552 {
4553 /* Attach to the right interface */
4554 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Internal);
4555 if (FAILED(rc)) throw rc;
4556 }
4557 /* Next test for Generic interfaces */
4558 else if (pvsys->strExtraConfigCurrent.endsWith("type=Generic", Utf8Str::CaseInsensitive))
4559 {
4560 /* Attach to the right interface */
4561 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Generic);
4562 if (FAILED(rc)) throw rc;
4563 }
4564
4565 /* Next test for NAT network interfaces */
4566 else if (pvsys->strExtraConfigCurrent.endsWith("type=NATNetwork", Utf8Str::CaseInsensitive))
4567 {
4568 /* Attach to the right interface */
4569 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork);
4570 if (FAILED(rc)) throw rc;
4571 com::SafeIfaceArray<INATNetwork> nwNATNetworks;
4572 rc = mVirtualBox->COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(nwNATNetworks));
4573 if (FAILED(rc)) throw rc;
4574 // Pick the first NAT network (if there is any)
4575 if (nwNATNetworks.size())
4576 {
4577 Bstr name;
4578 rc = nwNATNetworks[0]->COMGETTER(NetworkName)(name.asOutParam());
4579 if (FAILED(rc)) throw rc;
4580 /* Set the NAT network name to attach to */
4581 rc = pNetworkAdapter->COMSETTER(NATNetwork)(name.raw());
4582 if (FAILED(rc)) throw rc;
4583 break;
4584 }
4585 }
4586 }
4587 }
4588
4589 // Storage controller IDE
4590 std::list<VirtualSystemDescriptionEntry*> vsdeHDCIDE =
4591 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerIDE);
4592 /*
4593 * In OVF (at least VMware's version of it), an IDE controller has two ports,
4594 * so VirtualBox's single IDE controller with two channels and two ports each counts as
4595 * two OVF IDE controllers -- so we accept one or two such IDE controllers
4596 */
4597 size_t cIDEControllers = vsdeHDCIDE.size();
4598 if (cIDEControllers > 2)
4599 throw setError(VBOX_E_FILE_ERROR,
4600 tr("Too many IDE controllers in OVF; import facility only supports two"));
4601 if (!vsdeHDCIDE.empty())
4602 {
4603 // one or two IDE controllers present in OVF: add one VirtualBox controller
4604 ComPtr<IStorageController> pController;
4605 rc = pNewMachine->AddStorageController(Bstr("IDE").raw(), StorageBus_IDE, pController.asOutParam());
4606 if (FAILED(rc)) throw rc;
4607
4608 const char *pcszIDEType = vsdeHDCIDE.front()->strVBoxCurrent.c_str();
4609 if (!strcmp(pcszIDEType, "PIIX3"))
4610 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX3);
4611 else if (!strcmp(pcszIDEType, "PIIX4"))
4612 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX4);
4613 else if (!strcmp(pcszIDEType, "ICH6"))
4614 rc = pController->COMSETTER(ControllerType)(StorageControllerType_ICH6);
4615 else
4616 throw setError(VBOX_E_FILE_ERROR,
4617 tr("Invalid IDE controller type \"%s\""),
4618 pcszIDEType);
4619 if (FAILED(rc)) throw rc;
4620 }
4621
4622 /* Storage controller SATA */
4623 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSATA =
4624 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSATA);
4625 if (vsdeHDCSATA.size() > 1)
4626 throw setError(VBOX_E_FILE_ERROR,
4627 tr("Too many SATA controllers in OVF; import facility only supports one"));
4628 if (!vsdeHDCSATA.empty())
4629 {
4630 ComPtr<IStorageController> pController;
4631 const Utf8Str &hdcVBox = vsdeHDCSATA.front()->strVBoxCurrent;
4632 if (hdcVBox == "AHCI")
4633 {
4634 rc = pNewMachine->AddStorageController(Bstr("SATA").raw(),
4635 StorageBus_SATA,
4636 pController.asOutParam());
4637 if (FAILED(rc)) throw rc;
4638 }
4639 else
4640 throw setError(VBOX_E_FILE_ERROR,
4641 tr("Invalid SATA controller type \"%s\""),
4642 hdcVBox.c_str());
4643 }
4644
4645 /* Storage controller SCSI */
4646 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSCSI =
4647 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI);
4648 if (vsdeHDCSCSI.size() > 1)
4649 throw setError(VBOX_E_FILE_ERROR,
4650 tr("Too many SCSI controllers in OVF; import facility only supports one"));
4651 if (!vsdeHDCSCSI.empty())
4652 {
4653 ComPtr<IStorageController> pController;
4654 Utf8Str strName("SCSI");
4655 StorageBus_T busType = StorageBus_SCSI;
4656 StorageControllerType_T controllerType;
4657 const Utf8Str &hdcVBox = vsdeHDCSCSI.front()->strVBoxCurrent;
4658 if (hdcVBox == "LsiLogic")
4659 controllerType = StorageControllerType_LsiLogic;
4660 else if (hdcVBox == "LsiLogicSas")
4661 {
4662 // OVF treats LsiLogicSas as a SCSI controller but VBox considers it a class of its own
4663 strName = "SAS";
4664 busType = StorageBus_SAS;
4665 controllerType = StorageControllerType_LsiLogicSas;
4666 }
4667 else if (hdcVBox == "BusLogic")
4668 controllerType = StorageControllerType_BusLogic;
4669 else
4670 throw setError(VBOX_E_FILE_ERROR,
4671 tr("Invalid SCSI controller type \"%s\""),
4672 hdcVBox.c_str());
4673
4674 rc = pNewMachine->AddStorageController(Bstr(strName).raw(), busType, pController.asOutParam());
4675 if (FAILED(rc)) throw rc;
4676 rc = pController->COMSETTER(ControllerType)(controllerType);
4677 if (FAILED(rc)) throw rc;
4678 }
4679
4680 /* Storage controller SAS */
4681 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSAS =
4682 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSAS);
4683 if (vsdeHDCSAS.size() > 1)
4684 throw setError(VBOX_E_FILE_ERROR,
4685 tr("Too many SAS controllers in OVF; import facility only supports one"));
4686 if (!vsdeHDCSAS.empty())
4687 {
4688 ComPtr<IStorageController> pController;
4689 rc = pNewMachine->AddStorageController(Bstr(L"SAS").raw(),
4690 StorageBus_SAS,
4691 pController.asOutParam());
4692 if (FAILED(rc)) throw rc;
4693 rc = pController->COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas);
4694 if (FAILED(rc)) throw rc;
4695 }
4696
4697
4698 /* Storage controller VirtioSCSI */
4699 std::list<VirtualSystemDescriptionEntry*> vsdeHDCVirtioSCSI =
4700 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI);
4701 if (vsdeHDCVirtioSCSI.size() > 1)
4702 throw setError(VBOX_E_FILE_ERROR,
4703 tr("Too many VirtioSCSI controllers in OVF; import facility only supports one"));
4704 if (!vsdeHDCVirtioSCSI.empty())
4705 {
4706 ComPtr<IStorageController> pController;
4707 Utf8Str strName("VirtioSCSI");
4708 const Utf8Str &hdcVBox = vsdeHDCVirtioSCSI.front()->strVBoxCurrent;
4709 if (hdcVBox == "VirtioSCSI")
4710 {
4711 rc = pNewMachine->AddStorageController(Bstr(strName).raw(),
4712 StorageBus_VirtioSCSI,
4713 pController.asOutParam());
4714 if (FAILED(rc)) throw rc;
4715
4716 rc = pController->COMSETTER(ControllerType)(StorageControllerType_VirtioSCSI);
4717 if (FAILED(rc)) throw rc;
4718 }
4719 else
4720 throw setError(VBOX_E_FILE_ERROR,
4721 tr("Invalid VirtioSCSI controller type \"%s\""),
4722 hdcVBox.c_str());
4723 }
4724
4725 /* Now its time to register the machine before we add any storage devices */
4726 rc = mVirtualBox->RegisterMachine(pNewMachine);
4727 if (FAILED(rc)) throw rc;
4728
4729 // store new machine for roll-back in case of errors
4730 Bstr bstrNewMachineId;
4731 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
4732 if (FAILED(rc)) throw rc;
4733 Guid uuidNewMachine(bstrNewMachineId);
4734 m->llGuidsMachinesCreated.push_back(uuidNewMachine);
4735
4736 // Add floppies and CD-ROMs to the appropriate controllers.
4737 std::list<VirtualSystemDescriptionEntry*> vsdeFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy);
4738 if (vsdeFloppy.size() > 1)
4739 throw setError(VBOX_E_FILE_ERROR,
4740 tr("Too many floppy controllers in OVF; import facility only supports one"));
4741 std::list<VirtualSystemDescriptionEntry*> vsdeCDROM = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
4742 if ( !vsdeFloppy.empty()
4743 || !vsdeCDROM.empty()
4744 )
4745 {
4746 // If there's an error here we need to close the session, so
4747 // we need another try/catch block.
4748
4749 try
4750 {
4751 // to attach things we need to open a session for the new machine
4752 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
4753 if (FAILED(rc)) throw rc;
4754 stack.fSessionOpen = true;
4755
4756 ComPtr<IMachine> sMachine;
4757 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
4758 if (FAILED(rc)) throw rc;
4759
4760 // floppy first
4761 if (vsdeFloppy.size() == 1)
4762 {
4763 ComPtr<IStorageController> pController;
4764 rc = sMachine->AddStorageController(Bstr("Floppy").raw(),
4765 StorageBus_Floppy,
4766 pController.asOutParam());
4767 if (FAILED(rc)) throw rc;
4768
4769 Bstr bstrName;
4770 rc = pController->COMGETTER(Name)(bstrName.asOutParam());
4771 if (FAILED(rc)) throw rc;
4772
4773 // this is for rollback later
4774 MyHardDiskAttachment mhda;
4775 mhda.pMachine = pNewMachine;
4776 mhda.controllerName = bstrName;
4777 mhda.lControllerPort = 0;
4778 mhda.lDevice = 0;
4779
4780 Log(("Attaching floppy\n"));
4781
4782 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),
4783 mhda.lControllerPort,
4784 mhda.lDevice,
4785 DeviceType_Floppy,
4786 NULL);
4787 if (FAILED(rc)) throw rc;
4788
4789 stack.llHardDiskAttachments.push_back(mhda);
4790 }
4791
4792 rc = sMachine->SaveSettings();
4793 if (FAILED(rc)) throw rc;
4794
4795 // only now that we're done with all storage devices, close the session
4796 rc = stack.pSession->UnlockMachine();
4797 if (FAILED(rc)) throw rc;
4798 stack.fSessionOpen = false;
4799 }
4800 catch(HRESULT aRC)
4801 {
4802 com::ErrorInfo info;
4803
4804 if (stack.fSessionOpen)
4805 stack.pSession->UnlockMachine();
4806
4807 if (info.isFullAvailable())
4808 throw setError(aRC, Utf8Str(info.getText()).c_str());
4809 else
4810 throw setError(aRC, "Unknown error during OVF import");
4811 }
4812 }
4813
4814 // create the storage devices & connect them to the appropriate controllers
4815 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
4816 if (!avsdeHDs.empty())
4817 {
4818 // If there's an error here we need to close the session, so
4819 // we need another try/catch block.
4820 try
4821 {
4822#ifdef LOG_ENABLED
4823 if (LogIsEnabled())
4824 {
4825 size_t i = 0;
4826 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4827 itHD != avsdeHDs.end(); ++itHD, i++)
4828 Log(("avsdeHDs[%zu]: strRef=%s strOvf=%s\n", i, (*itHD)->strRef.c_str(), (*itHD)->strOvf.c_str()));
4829 i = 0;
4830 for (ovf::DiskImagesMap::const_iterator itDisk = stack.mapDisks.begin(); itDisk != stack.mapDisks.end(); ++itDisk)
4831 Log(("mapDisks[%zu]: strDiskId=%s strHref=%s\n",
4832 i, itDisk->second.strDiskId.c_str(), itDisk->second.strHref.c_str()));
4833
4834 }
4835#endif
4836
4837 // to attach things we need to open a session for the new machine
4838 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
4839 if (FAILED(rc)) throw rc;
4840 stack.fSessionOpen = true;
4841
4842 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
4843 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
4844 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
4845 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
4846
4847
4848 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
4849 std::set<RTCString> disksResolvedNames;
4850
4851 uint32_t cImportedDisks = 0;
4852
4853 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
4854 {
4855/** @todo r=bird: Most of the code here is duplicated in the other machine
4856 * import method, factor out. */
4857 ovf::DiskImage diCurrent = oit->second;
4858
4859 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
4860 /* Iterate over all given images of the virtual system
4861 * description. We need to find the target image path,
4862 * which could be changed by the user. */
4863 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
4864 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4865 itHD != avsdeHDs.end();
4866 ++itHD)
4867 {
4868 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4869 if (vsdeHD->strRef == diCurrent.strDiskId)
4870 {
4871 vsdeTargetHD = vsdeHD;
4872 break;
4873 }
4874 }
4875 if (!vsdeTargetHD)
4876 {
4877 /* possible case if an image belongs to other virtual system (OVF package with multiple VMs inside) */
4878 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
4879 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
4880 NOREF(vmNameEntry);
4881 ++oit;
4882 continue;
4883 }
4884
4885 //diCurrent.strDiskId contains the image identifier (e.g. "vmdisk1"), which should exist
4886 //in the virtual system's images map under that ID and also in the global images map
4887 ovf::VirtualDisksMap::const_iterator itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
4888 if (itVDisk == vsysThis.mapVirtualDisks.end())
4889 throw setError(E_FAIL,
4890 tr("Internal inconsistency looking up disk image '%s'"),
4891 diCurrent.strHref.c_str());
4892
4893 /*
4894 * preliminary check availability of the image
4895 * This step is useful if image is placed in the OVA (TAR) package
4896 */
4897 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
4898 {
4899 /* It means that we possibly have imported the storage earlier on the previous loop steps*/
4900 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
4901 if (h != disksResolvedNames.end())
4902 {
4903 /* Yes, image name was found, we can skip it*/
4904 ++oit;
4905 continue;
4906 }
4907l_skipped:
4908 rc = i_preCheckImageAvailability(stack);
4909 if (SUCCEEDED(rc))
4910 {
4911 /* current opened file isn't the same as passed one */
4912 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
4913 {
4914 /* availableImage contains the image file reference (e.g. "disk1.vmdk"), which should
4915 * exist in the global images map.
4916 * And find the image from the OVF's disk list */
4917 ovf::DiskImagesMap::const_iterator itDiskImage;
4918 for (itDiskImage = stack.mapDisks.begin();
4919 itDiskImage != stack.mapDisks.end();
4920 itDiskImage++)
4921 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
4922 Utf8Str::CaseInsensitive) == 0)
4923 break;
4924 if (itDiskImage == stack.mapDisks.end())
4925 {
4926 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
4927 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
4928 goto l_skipped;
4929 }
4930
4931 /* replace with a new found image */
4932 diCurrent = *(&itDiskImage->second);
4933
4934 /*
4935 * Again iterate over all given images of the virtual system
4936 * description using the found image
4937 */
4938 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4939 itHD != avsdeHDs.end();
4940 ++itHD)
4941 {
4942 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4943 if (vsdeHD->strRef == diCurrent.strDiskId)
4944 {
4945 vsdeTargetHD = vsdeHD;
4946 break;
4947 }
4948 }
4949
4950 /*
4951 * in this case it's an error because something is wrong with the OVF description file.
4952 * May be VBox imports OVA package with wrong file sequence inside the archive.
4953 */
4954 if (!vsdeTargetHD)
4955 throw setError(E_FAIL,
4956 tr("Internal inconsistency looking up disk image '%s'"),
4957 diCurrent.strHref.c_str());
4958
4959 itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
4960 if (itVDisk == vsysThis.mapVirtualDisks.end())
4961 throw setError(E_FAIL,
4962 tr("Internal inconsistency looking up disk image '%s'"),
4963 diCurrent.strHref.c_str());
4964 }
4965 else
4966 {
4967 ++oit;
4968 }
4969 }
4970 else
4971 {
4972 ++oit;
4973 continue;
4974 }
4975 }
4976 else
4977 {
4978 /* just continue with normal files */
4979 ++oit;
4980 }
4981
4982 /* very important to store image name for the next checks */
4983 disksResolvedNames.insert(diCurrent.strHref);
4984////// end of duplicated code.
4985 const ovf::VirtualDisk &ovfVdisk = itVDisk->second;
4986
4987 ComObjPtr<Medium> pTargetMedium;
4988 if (stack.locInfo.storageType == VFSType_Cloud)
4989 {
4990 /* We have already all disks prepared (converted and registered in the VBox)
4991 * and in the correct place (VM machine folder).
4992 * so what is needed is to get the disk uuid from VirtualDisk::strDiskId
4993 * and find the Medium object with this uuid.
4994 * next just attach the Medium object to new VM.
4995 * VirtualDisk::strDiskId is filled in the */
4996
4997 Guid id(ovfVdisk.strDiskId);
4998 rc = mVirtualBox->i_findHardDiskById(id, false, &pTargetMedium);
4999 if (FAILED(rc))
5000 throw rc;
5001 }
5002 else
5003 {
5004 i_importOneDiskImage(diCurrent,
5005 vsdeTargetHD->strVBoxCurrent,
5006 pTargetMedium,
5007 stack);
5008 }
5009
5010 // now use the new uuid to attach the medium to our new machine
5011 ComPtr<IMachine> sMachine;
5012 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
5013 if (FAILED(rc))
5014 throw rc;
5015
5016 // find the hard disk controller to which we should attach
5017 ovf::HardDiskController hdc = (*vsysThis.mapControllers.find(ovfVdisk.idController)).second;
5018
5019 // this is for rollback later
5020 MyHardDiskAttachment mhda;
5021 mhda.pMachine = pNewMachine;
5022
5023 i_convertDiskAttachmentValues(hdc,
5024 ovfVdisk.ulAddressOnParent,
5025 mhda.controllerName,
5026 mhda.lControllerPort,
5027 mhda.lDevice);
5028
5029 Log(("Attaching disk %s to port %d on device %d\n",
5030 vsdeTargetHD->strVBoxCurrent.c_str(), mhda.lControllerPort, mhda.lDevice));
5031
5032 DeviceType_T devType = DeviceType_Null;
5033 rc = pTargetMedium->COMGETTER(DeviceType)(&devType);
5034 if (FAILED(rc))
5035 throw rc;
5036
5037 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
5038 mhda.lControllerPort, // long controllerPort
5039 mhda.lDevice, // long device
5040 devType, // DeviceType_T type
5041 pTargetMedium);
5042 if (FAILED(rc))
5043 throw rc;
5044
5045 stack.llHardDiskAttachments.push_back(mhda);
5046
5047 rc = sMachine->SaveSettings();
5048 if (FAILED(rc))
5049 throw rc;
5050
5051 ++cImportedDisks;
5052
5053 } // end while(oit != stack.mapDisks.end())
5054
5055 /*
5056 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
5057 */
5058 if(cImportedDisks < avsdeHDs.size())
5059 {
5060 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
5061 vmNameEntry->strOvf.c_str()));
5062 }
5063
5064 // only now that we're done with all disks, close the session
5065 rc = stack.pSession->UnlockMachine();
5066 if (FAILED(rc))
5067 throw rc;
5068 stack.fSessionOpen = false;
5069 }
5070 catch(HRESULT aRC)
5071 {
5072 com::ErrorInfo info;
5073 if (stack.fSessionOpen)
5074 stack.pSession->UnlockMachine();
5075
5076 if (info.isFullAvailable())
5077 throw setError(aRC, Utf8Str(info.getText()).c_str());
5078 else
5079 throw setError(aRC, "Unknown error during OVF import");
5080 }
5081 }
5082 LogFlowFuncLeave();
5083}
5084
5085/**
5086 * Imports one OVF virtual system (described by a vbox:Machine tag represented by the given config
5087 * structure) into VirtualBox by creating an IMachine instance, which is returned.
5088 *
5089 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
5090 * up any leftovers from this function. For this, the given ImportStack instance has received information
5091 * about what needs cleaning up (to support rollback).
5092 *
5093 * The machine config stored in the settings::MachineConfigFile structure contains the UUIDs of
5094 * the disk attachments used by the machine when it was exported. We also add vbox:uuid attributes
5095 * to the OVF disks sections so we can look them up. While importing these UUIDs into a second host
5096 * will most probably work, reimporting them into the same host will cause conflicts, so we always
5097 * generate new ones on import. This involves the following:
5098 *
5099 * 1) Scan the machine config for disk attachments.
5100 *
5101 * 2) For each disk attachment found, look up the OVF disk image from the disk references section
5102 * and import the disk into VirtualBox, which creates a new UUID for it. In the machine config,
5103 * replace the old UUID with the new one.
5104 *
5105 * 3) Change the machine config according to the OVF virtual system descriptions, in case the
5106 * caller has modified them using setFinalValues().
5107 *
5108 * 4) Create the VirtualBox machine with the modfified machine config.
5109 *
5110 * @param vsdescThis
5111 * @param pReturnNewMachine
5112 * @param stack
5113 */
5114void Appliance::i_importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
5115 ComPtr<IMachine> &pReturnNewMachine,
5116 ImportStack &stack)
5117{
5118 LogFlowFuncEnter();
5119 Assert(vsdescThis->m->pConfig);
5120
5121 HRESULT rc = S_OK;
5122
5123 settings::MachineConfigFile &config = *vsdescThis->m->pConfig;
5124
5125 /*
5126 * step 1): modify machine config according to OVF config, in case the user
5127 * has modified them using setFinalValues()
5128 */
5129
5130 /* OS Type */
5131 config.machineUserData.strOsType = stack.strOsTypeVBox;
5132 /* Groups */
5133 if (stack.strPrimaryGroup.isEmpty() || stack.strPrimaryGroup == "/")
5134 {
5135 config.machineUserData.llGroups.clear();
5136 config.machineUserData.llGroups.push_back("/");
5137 }
5138 else
5139 {
5140 /* Replace the primary group if there is one, otherwise add it. */
5141 if (config.machineUserData.llGroups.size())
5142 config.machineUserData.llGroups.pop_front();
5143 config.machineUserData.llGroups.push_front(stack.strPrimaryGroup);
5144 }
5145 /* Description */
5146 config.machineUserData.strDescription = stack.strDescription;
5147 /* CPU count & extented attributes */
5148 config.hardwareMachine.cCPUs = stack.cCPUs;
5149 if (stack.fForceIOAPIC)
5150 config.hardwareMachine.fHardwareVirt = true;
5151 if (stack.fForceIOAPIC)
5152 config.hardwareMachine.biosSettings.fIOAPICEnabled = true;
5153 /* RAM size */
5154 config.hardwareMachine.ulMemorySizeMB = stack.ulMemorySizeMB;
5155
5156/*
5157 <const name="HardDiskControllerIDE" value="14" />
5158 <const name="HardDiskControllerSATA" value="15" />
5159 <const name="HardDiskControllerSCSI" value="16" />
5160 <const name="HardDiskControllerSAS" value="17" />
5161*/
5162
5163#ifdef VBOX_WITH_USB
5164 /* USB controller */
5165 if (stack.fUSBEnabled)
5166 {
5167 /** @todo r=klaus add support for arbitrary USB controller types, this can't handle
5168 * multiple controllers due to its design anyway */
5169 /* Usually the OHCI controller is enabled already, need to check. But
5170 * do this only if there is no xHCI controller. */
5171 bool fOHCIEnabled = false;
5172 bool fXHCIEnabled = false;
5173 settings::USBControllerList &llUSBControllers = config.hardwareMachine.usbSettings.llUSBControllers;
5174 settings::USBControllerList::iterator it;
5175 for (it = llUSBControllers.begin(); it != llUSBControllers.end(); ++it)
5176 {
5177 if (it->enmType == USBControllerType_OHCI)
5178 fOHCIEnabled = true;
5179 if (it->enmType == USBControllerType_XHCI)
5180 fXHCIEnabled = true;
5181 }
5182
5183 if (!fXHCIEnabled && !fOHCIEnabled)
5184 {
5185 settings::USBController ctrl;
5186 ctrl.strName = "OHCI";
5187 ctrl.enmType = USBControllerType_OHCI;
5188
5189 llUSBControllers.push_back(ctrl);
5190 }
5191 }
5192 else
5193 config.hardwareMachine.usbSettings.llUSBControllers.clear();
5194#endif
5195 /* Audio adapter */
5196 if (stack.strAudioAdapter.isNotEmpty())
5197 {
5198 config.hardwareMachine.audioAdapter.fEnabled = true;
5199 config.hardwareMachine.audioAdapter.controllerType = (AudioControllerType_T)stack.strAudioAdapter.toUInt32();
5200 }
5201 else
5202 config.hardwareMachine.audioAdapter.fEnabled = false;
5203 /* Network adapter */
5204 settings::NetworkAdaptersList &llNetworkAdapters = config.hardwareMachine.llNetworkAdapters;
5205 /* First disable all network cards, they will be enabled below again. */
5206 settings::NetworkAdaptersList::iterator it1;
5207 bool fKeepAllMACs = m->optListImport.contains(ImportOptions_KeepAllMACs);
5208 bool fKeepNATMACs = m->optListImport.contains(ImportOptions_KeepNATMACs);
5209 for (it1 = llNetworkAdapters.begin(); it1 != llNetworkAdapters.end(); ++it1)
5210 {
5211 it1->fEnabled = false;
5212 if (!( fKeepAllMACs
5213 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NAT)
5214 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NATNetwork)))
5215 /* Force generation of new MAC address below. */
5216 it1->strMACAddress.setNull();
5217 }
5218 /* Now iterate over all network entries. */
5219 std::list<VirtualSystemDescriptionEntry*> avsdeNWs = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
5220 if (!avsdeNWs.empty())
5221 {
5222 /* Iterate through all network adapter entries and search for the
5223 * corresponding one in the machine config. If one is found, configure
5224 * it based on the user settings. */
5225 list<VirtualSystemDescriptionEntry*>::const_iterator itNW;
5226 for (itNW = avsdeNWs.begin();
5227 itNW != avsdeNWs.end();
5228 ++itNW)
5229 {
5230 VirtualSystemDescriptionEntry *vsdeNW = *itNW;
5231 if ( vsdeNW->strExtraConfigCurrent.startsWith("slot=", Utf8Str::CaseInsensitive)
5232 && vsdeNW->strExtraConfigCurrent.length() > 6)
5233 {
5234 uint32_t iSlot = vsdeNW->strExtraConfigCurrent.substr(5).toUInt32();
5235 /* Iterate through all network adapters in the machine config. */
5236 for (it1 = llNetworkAdapters.begin();
5237 it1 != llNetworkAdapters.end();
5238 ++it1)
5239 {
5240 /* Compare the slots. */
5241 if (it1->ulSlot == iSlot)
5242 {
5243 it1->fEnabled = true;
5244 if (it1->strMACAddress.isEmpty())
5245 Host::i_generateMACAddress(it1->strMACAddress);
5246 it1->type = (NetworkAdapterType_T)vsdeNW->strVBoxCurrent.toUInt32();
5247 break;
5248 }
5249 }
5250 }
5251 }
5252 }
5253
5254 /* Floppy controller */
5255 bool fFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy).size() > 0;
5256 /* DVD controller */
5257 bool fDVD = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM).size() > 0;
5258 /* Iterate over all storage controller check the attachments and remove
5259 * them when necessary. Also detect broken configs with more than one
5260 * attachment. Old VirtualBox versions (prior to 3.2.10) had all disk
5261 * attachments pointing to the last hard disk image, which causes import
5262 * failures. A long fixed bug, however the OVF files are long lived. */
5263 settings::StorageControllersList &llControllers = config.hardwareMachine.storage.llStorageControllers;
5264 Guid hdUuid;
5265 uint32_t cDisks = 0;
5266 bool fInconsistent = false;
5267 bool fRepairDuplicate = false;
5268 settings::StorageControllersList::iterator it3;
5269 for (it3 = llControllers.begin();
5270 it3 != llControllers.end();
5271 ++it3)
5272 {
5273 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
5274 settings::AttachedDevicesList::iterator it4 = llAttachments.begin();
5275 while (it4 != llAttachments.end())
5276 {
5277 if ( ( !fDVD
5278 && it4->deviceType == DeviceType_DVD)
5279 ||
5280 ( !fFloppy
5281 && it4->deviceType == DeviceType_Floppy))
5282 {
5283 it4 = llAttachments.erase(it4);
5284 continue;
5285 }
5286 else if (it4->deviceType == DeviceType_HardDisk)
5287 {
5288 const Guid &thisUuid = it4->uuid;
5289 cDisks++;
5290 if (cDisks == 1)
5291 {
5292 if (hdUuid.isZero())
5293 hdUuid = thisUuid;
5294 else
5295 fInconsistent = true;
5296 }
5297 else
5298 {
5299 if (thisUuid.isZero())
5300 fInconsistent = true;
5301 else if (thisUuid == hdUuid)
5302 fRepairDuplicate = true;
5303 }
5304 }
5305 ++it4;
5306 }
5307 }
5308 /* paranoia... */
5309 if (fInconsistent || cDisks == 1)
5310 fRepairDuplicate = false;
5311
5312 /*
5313 * step 2: scan the machine config for media attachments
5314 */
5315 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
5316 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
5317 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
5318 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
5319
5320 /* Get all hard disk descriptions. */
5321 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
5322 std::list<VirtualSystemDescriptionEntry*>::iterator avsdeHDsIt = avsdeHDs.begin();
5323 /* paranoia - if there is no 1:1 match do not try to repair. */
5324 if (cDisks != avsdeHDs.size())
5325 fRepairDuplicate = false;
5326
5327 // there must be an image in the OVF disk structs with the same UUID
5328
5329 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
5330 std::set<RTCString> disksResolvedNames;
5331
5332 uint32_t cImportedDisks = 0;
5333
5334 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
5335 {
5336/** @todo r=bird: Most of the code here is duplicated in the other machine
5337 * import method, factor out. */
5338 ovf::DiskImage diCurrent = oit->second;
5339
5340 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
5341
5342 /* Iterate over all given disk images of the virtual system
5343 * disks description. We need to find the target disk path,
5344 * which could be changed by the user. */
5345 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
5346 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
5347 itHD != avsdeHDs.end();
5348 ++itHD)
5349 {
5350 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
5351 if (vsdeHD->strRef == oit->first)
5352 {
5353 vsdeTargetHD = vsdeHD;
5354 break;
5355 }
5356 }
5357 if (!vsdeTargetHD)
5358 {
5359 /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
5360 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
5361 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
5362 NOREF(vmNameEntry);
5363 ++oit;
5364 continue;
5365 }
5366
5367
5368
5369
5370
5371
5372
5373
5374
5375 /*
5376 * preliminary check availability of the image
5377 * This step is useful if image is placed in the OVA (TAR) package
5378 */
5379 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
5380 {
5381 /* It means that we possibly have imported the storage earlier on a previous loop step. */
5382 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
5383 if (h != disksResolvedNames.end())
5384 {
5385 /* Yes, disk name was found, we can skip it*/
5386 ++oit;
5387 continue;
5388 }
5389l_skipped:
5390 rc = i_preCheckImageAvailability(stack);
5391 if (SUCCEEDED(rc))
5392 {
5393 /* current opened file isn't the same as passed one */
5394 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
5395 {
5396 // availableImage contains the disk identifier (e.g. "vmdisk1"), which should exist
5397 // in the virtual system's disks map under that ID and also in the global images map
5398 // and find the disk from the OVF's disk list
5399 ovf::DiskImagesMap::const_iterator itDiskImage;
5400 for (itDiskImage = stack.mapDisks.begin();
5401 itDiskImage != stack.mapDisks.end();
5402 itDiskImage++)
5403 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
5404 Utf8Str::CaseInsensitive) == 0)
5405 break;
5406 if (itDiskImage == stack.mapDisks.end())
5407 {
5408 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
5409 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
5410 goto l_skipped;
5411 }
5412 //throw setError(E_FAIL,
5413 // tr("Internal inconsistency looking up disk image '%s'. "
5414 // "Check compliance OVA package structure and file names "
5415 // "references in the section <References> in the OVF file."),
5416 // stack.pszOvaLookAheadName);
5417
5418 /* replace with a new found disk image */
5419 diCurrent = *(&itDiskImage->second);
5420
5421 /*
5422 * Again iterate over all given disk images of the virtual system
5423 * disks description using the found disk image
5424 */
5425 vsdeTargetHD = NULL;
5426 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
5427 itHD != avsdeHDs.end();
5428 ++itHD)
5429 {
5430 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
5431 if (vsdeHD->strRef == diCurrent.strDiskId)
5432 {
5433 vsdeTargetHD = vsdeHD;
5434 break;
5435 }
5436 }
5437
5438 /*
5439 * in this case it's an error because something is wrong with the OVF description file.
5440 * May be VBox imports OVA package with wrong file sequence inside the archive.
5441 */
5442 if (!vsdeTargetHD)
5443 throw setError(E_FAIL,
5444 tr("Internal inconsistency looking up disk image '%s'"),
5445 diCurrent.strHref.c_str());
5446
5447
5448
5449
5450
5451 }
5452 else
5453 {
5454 ++oit;
5455 }
5456 }
5457 else
5458 {
5459 ++oit;
5460 continue;
5461 }
5462 }
5463 else
5464 {
5465 /* just continue with normal files*/
5466 ++oit;
5467 }
5468
5469 /* Important! to store disk name for the next checks */
5470 disksResolvedNames.insert(diCurrent.strHref);
5471////// end of duplicated code.
5472 // there must be an image in the OVF disk structs with the same UUID
5473 bool fFound = false;
5474 Utf8Str strUuid;
5475
5476 // for each storage controller...
5477 for (settings::StorageControllersList::iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
5478 sit != config.hardwareMachine.storage.llStorageControllers.end();
5479 ++sit)
5480 {
5481 settings::StorageController &sc = *sit;
5482
5483 // for each medium attachment to this controller...
5484 for (settings::AttachedDevicesList::iterator dit = sc.llAttachedDevices.begin();
5485 dit != sc.llAttachedDevices.end();
5486 ++dit)
5487 {
5488 settings::AttachedDevice &d = *dit;
5489
5490 if (d.uuid.isZero())
5491 // empty DVD and floppy media
5492 continue;
5493
5494 // When repairing a broken VirtualBox xml config section (written
5495 // by VirtualBox versions earlier than 3.2.10) assume the disks
5496 // show up in the same order as in the OVF description.
5497 if (fRepairDuplicate)
5498 {
5499 VirtualSystemDescriptionEntry *vsdeHD = *avsdeHDsIt;
5500 ovf::DiskImagesMap::const_iterator itDiskImage = stack.mapDisks.find(vsdeHD->strRef);
5501 if (itDiskImage != stack.mapDisks.end())
5502 {
5503 const ovf::DiskImage &di = itDiskImage->second;
5504 d.uuid = Guid(di.uuidVBox);
5505 }
5506 ++avsdeHDsIt;
5507 }
5508
5509 // convert the Guid to string
5510 strUuid = d.uuid.toString();
5511
5512 if (diCurrent.uuidVBox != strUuid)
5513 {
5514 continue;
5515 }
5516
5517 /*
5518 * step 3: import disk
5519 */
5520 ComObjPtr<Medium> pTargetMedium;
5521 i_importOneDiskImage(diCurrent,
5522 vsdeTargetHD->strVBoxCurrent,
5523 pTargetMedium,
5524 stack);
5525
5526 // ... and replace the old UUID in the machine config with the one of
5527 // the imported disk that was just created
5528 Bstr hdId;
5529 rc = pTargetMedium->COMGETTER(Id)(hdId.asOutParam());
5530 if (FAILED(rc)) throw rc;
5531
5532 /*
5533 * 1. saving original UUID for restoring in case of failure.
5534 * 2. replacement of original UUID by new UUID in the current VM config (settings::MachineConfigFile).
5535 */
5536 {
5537 rc = stack.saveOriginalUUIDOfAttachedDevice(d, Utf8Str(hdId));
5538 d.uuid = hdId;
5539 }
5540
5541 fFound = true;
5542 break;
5543 } // for (settings::AttachedDevicesList::const_iterator dit = sc.llAttachedDevices.begin();
5544 } // for (settings::StorageControllersList::const_iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
5545
5546 // no disk with such a UUID found:
5547 if (!fFound)
5548 throw setError(E_FAIL,
5549 tr("<vbox:Machine> element in OVF contains a medium attachment for the disk image %s "
5550 "but the OVF describes no such image"),
5551 strUuid.c_str());
5552
5553 ++cImportedDisks;
5554
5555 }// while(oit != stack.mapDisks.end())
5556
5557
5558 /*
5559 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
5560 */
5561 if(cImportedDisks < avsdeHDs.size())
5562 {
5563 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
5564 vmNameEntry->strOvf.c_str()));
5565 }
5566
5567 /*
5568 * step 4): create the machine and have it import the config
5569 */
5570
5571 ComObjPtr<Machine> pNewMachine;
5572 rc = pNewMachine.createObject();
5573 if (FAILED(rc)) throw rc;
5574
5575 // this magic constructor fills the new machine object with the MachineConfig
5576 // instance that we created from the vbox:Machine
5577 rc = pNewMachine->init(mVirtualBox,
5578 stack.strNameVBox,// name from OVF preparations; can be suffixed to avoid duplicates
5579 stack.strSettingsFilename,
5580 config); // the whole machine config
5581 if (FAILED(rc)) throw rc;
5582
5583 pReturnNewMachine = ComPtr<IMachine>(pNewMachine);
5584
5585 // and register it
5586 rc = mVirtualBox->RegisterMachine(pNewMachine);
5587 if (FAILED(rc)) throw rc;
5588
5589 // store new machine for roll-back in case of errors
5590 Bstr bstrNewMachineId;
5591 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
5592 if (FAILED(rc)) throw rc;
5593 m->llGuidsMachinesCreated.push_back(Guid(bstrNewMachineId));
5594
5595 LogFlowFuncLeave();
5596}
5597
5598/**
5599 * @throws HRESULT errors.
5600 */
5601void Appliance::i_importMachines(ImportStack &stack)
5602{
5603 // this is safe to access because this thread only gets started
5604 const ovf::OVFReader &reader = *m->pReader;
5605
5606 // create a session for the machine + disks we manipulate below
5607 HRESULT rc = stack.pSession.createInprocObject(CLSID_Session);
5608 ComAssertComRCThrowRC(rc);
5609
5610 list<ovf::VirtualSystem>::const_iterator it;
5611 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it1;
5612 /* Iterate through all virtual systems of that appliance */
5613 size_t i = 0;
5614 for (it = reader.m_llVirtualSystems.begin(), it1 = m->virtualSystemDescriptions.begin();
5615 it != reader.m_llVirtualSystems.end() && it1 != m->virtualSystemDescriptions.end();
5616 ++it, ++it1, ++i)
5617 {
5618 const ovf::VirtualSystem &vsysThis = *it;
5619 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it1);
5620
5621 ComPtr<IMachine> pNewMachine;
5622
5623 // there are two ways in which we can create a vbox machine from OVF:
5624 // -- either this OVF was written by vbox 3.2 or later, in which case there is a <vbox:Machine> element
5625 // in the <VirtualSystem>; then the VirtualSystemDescription::Data has a settings::MachineConfigFile
5626 // with all the machine config pretty-parsed;
5627 // -- or this is an OVF from an older vbox or an external source, and then we need to translate the
5628 // VirtualSystemDescriptionEntry and do import work
5629
5630 // Even for the vbox:Machine case, there are a number of configuration items that will be taken from
5631 // the OVF because otherwise the "override import parameters" mechanism in the GUI won't work.
5632
5633 // VM name
5634 std::list<VirtualSystemDescriptionEntry*> vsdeName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
5635 if (vsdeName.size() < 1)
5636 throw setError(VBOX_E_FILE_ERROR,
5637 tr("Missing VM name"));
5638 stack.strNameVBox = vsdeName.front()->strVBoxCurrent;
5639
5640 // Primary group, which is entirely optional.
5641 stack.strPrimaryGroup.setNull();
5642 std::list<VirtualSystemDescriptionEntry*> vsdePrimaryGroup = vsdescThis->i_findByType(VirtualSystemDescriptionType_PrimaryGroup);
5643 if (vsdePrimaryGroup.size() >= 1)
5644 {
5645 stack.strPrimaryGroup = vsdePrimaryGroup.front()->strVBoxCurrent;
5646 if (stack.strPrimaryGroup.isEmpty())
5647 stack.strPrimaryGroup = "/";
5648 }
5649
5650 // Draw the right conclusions from the (possibly modified) VM settings
5651 // file name and base folder. If the VM settings file name is modified,
5652 // it takes precedence, otherwise it is recreated from the base folder
5653 // and the primary group.
5654 stack.strSettingsFilename.setNull();
5655 std::list<VirtualSystemDescriptionEntry*> vsdeSettingsFile = vsdescThis->i_findByType(VirtualSystemDescriptionType_SettingsFile);
5656 if (vsdeSettingsFile.size() >= 1)
5657 {
5658 VirtualSystemDescriptionEntry *vsdeSF1 = vsdeSettingsFile.front();
5659 if (vsdeSF1->strVBoxCurrent != vsdeSF1->strVBoxSuggested)
5660 stack.strSettingsFilename = vsdeSF1->strVBoxCurrent;
5661 }
5662 if (stack.strSettingsFilename.isEmpty())
5663 {
5664 Utf8Str strBaseFolder;
5665 std::list<VirtualSystemDescriptionEntry*> vsdeBaseFolder = vsdescThis->i_findByType(VirtualSystemDescriptionType_BaseFolder);
5666 if (vsdeBaseFolder.size() >= 1)
5667 strBaseFolder = vsdeBaseFolder.front()->strVBoxCurrent;
5668 Bstr bstrSettingsFilename;
5669 rc = mVirtualBox->ComposeMachineFilename(Bstr(stack.strNameVBox).raw(),
5670 Bstr(stack.strPrimaryGroup).raw(),
5671 NULL /* aCreateFlags */,
5672 Bstr(strBaseFolder).raw(),
5673 bstrSettingsFilename.asOutParam());
5674 if (FAILED(rc)) throw rc;
5675 stack.strSettingsFilename = bstrSettingsFilename;
5676 }
5677
5678 // Determine the machine folder from the settings file.
5679 LogFunc(("i=%zu strName=%s strSettingsFilename=%s\n", i, stack.strNameVBox.c_str(), stack.strSettingsFilename.c_str()));
5680 stack.strMachineFolder = stack.strSettingsFilename;
5681 stack.strMachineFolder.stripFilename();
5682
5683 // guest OS type
5684 std::list<VirtualSystemDescriptionEntry*> vsdeOS;
5685 vsdeOS = vsdescThis->i_findByType(VirtualSystemDescriptionType_OS);
5686 if (vsdeOS.size() < 1)
5687 throw setError(VBOX_E_FILE_ERROR,
5688 tr("Missing guest OS type"));
5689 stack.strOsTypeVBox = vsdeOS.front()->strVBoxCurrent;
5690
5691 // Firmware
5692 std::list<VirtualSystemDescriptionEntry*> firmware = vsdescThis->i_findByType(VirtualSystemDescriptionType_BootingFirmware);
5693 if (firmware.size() != 1)
5694 stack.strFirmwareType = "BIOS";//try default BIOS type
5695 else
5696 stack.strFirmwareType = firmware.front()->strVBoxCurrent;
5697
5698 // CPU count
5699 std::list<VirtualSystemDescriptionEntry*> vsdeCPU = vsdescThis->i_findByType(VirtualSystemDescriptionType_CPU);
5700 if (vsdeCPU.size() != 1)
5701 throw setError(VBOX_E_FILE_ERROR, tr("CPU count missing"));
5702
5703 stack.cCPUs = vsdeCPU.front()->strVBoxCurrent.toUInt32();
5704 // We need HWVirt & IO-APIC if more than one CPU is requested
5705 if (stack.cCPUs > 1)
5706 {
5707 stack.fForceHWVirt = true;
5708 stack.fForceIOAPIC = true;
5709 }
5710
5711 // RAM
5712 std::list<VirtualSystemDescriptionEntry*> vsdeRAM = vsdescThis->i_findByType(VirtualSystemDescriptionType_Memory);
5713 if (vsdeRAM.size() != 1)
5714 throw setError(VBOX_E_FILE_ERROR, tr("RAM size missing"));
5715 stack.ulMemorySizeMB = (ULONG)vsdeRAM.front()->strVBoxCurrent.toUInt64();
5716
5717#ifdef VBOX_WITH_USB
5718 // USB controller
5719 std::list<VirtualSystemDescriptionEntry*> vsdeUSBController =
5720 vsdescThis->i_findByType(VirtualSystemDescriptionType_USBController);
5721 // USB support is enabled if there's at least one such entry; to disable USB support,
5722 // the type of the USB item would have been changed to "ignore"
5723 stack.fUSBEnabled = !vsdeUSBController.empty();
5724#endif
5725 // audio adapter
5726 std::list<VirtualSystemDescriptionEntry*> vsdeAudioAdapter =
5727 vsdescThis->i_findByType(VirtualSystemDescriptionType_SoundCard);
5728 /** @todo we support one audio adapter only */
5729 if (!vsdeAudioAdapter.empty())
5730 stack.strAudioAdapter = vsdeAudioAdapter.front()->strVBoxCurrent;
5731
5732 // for the description of the new machine, always use the OVF entry, the user may have changed it in the import config
5733 std::list<VirtualSystemDescriptionEntry*> vsdeDescription =
5734 vsdescThis->i_findByType(VirtualSystemDescriptionType_Description);
5735 if (!vsdeDescription.empty())
5736 stack.strDescription = vsdeDescription.front()->strVBoxCurrent;
5737
5738 // import vbox:machine or OVF now
5739 if (vsdescThis->m->pConfig)
5740 // vbox:Machine config
5741 i_importVBoxMachine(vsdescThis, pNewMachine, stack);
5742 else
5743 // generic OVF config
5744 i_importMachineGeneric(vsysThis, vsdescThis, pNewMachine, stack);
5745
5746 } // for (it = pAppliance->m->llVirtualSystems.begin() ...
5747}
5748
5749HRESULT Appliance::ImportStack::saveOriginalUUIDOfAttachedDevice(settings::AttachedDevice &device,
5750 const Utf8Str &newlyUuid)
5751{
5752 HRESULT rc = S_OK;
5753
5754 /* save for restoring */
5755 mapNewUUIDsToOriginalUUIDs.insert(std::make_pair(newlyUuid, device.uuid.toString()));
5756
5757 return rc;
5758}
5759
5760HRESULT Appliance::ImportStack::restoreOriginalUUIDOfAttachedDevice(settings::MachineConfigFile *config)
5761{
5762 HRESULT rc = S_OK;
5763
5764 settings::StorageControllersList &llControllers = config->hardwareMachine.storage.llStorageControllers;
5765 settings::StorageControllersList::iterator itscl;
5766 for (itscl = llControllers.begin();
5767 itscl != llControllers.end();
5768 ++itscl)
5769 {
5770 settings::AttachedDevicesList &llAttachments = itscl->llAttachedDevices;
5771 settings::AttachedDevicesList::iterator itadl = llAttachments.begin();
5772 while (itadl != llAttachments.end())
5773 {
5774 std::map<Utf8Str , Utf8Str>::iterator it =
5775 mapNewUUIDsToOriginalUUIDs.find(itadl->uuid.toString());
5776 if(it!=mapNewUUIDsToOriginalUUIDs.end())
5777 {
5778 Utf8Str uuidOriginal = it->second;
5779 itadl->uuid = Guid(uuidOriginal);
5780 mapNewUUIDsToOriginalUUIDs.erase(it->first);
5781 }
5782 ++itadl;
5783 }
5784 }
5785
5786 return rc;
5787}
5788
5789/**
5790 * @throws Nothing
5791 */
5792RTVFSIOSTREAM Appliance::ImportStack::claimOvaLookAHead(void)
5793{
5794 RTVFSIOSTREAM hVfsIos = this->hVfsIosOvaLookAhead;
5795 this->hVfsIosOvaLookAhead = NIL_RTVFSIOSTREAM;
5796 /* We don't free the name since it may be referenced in error messages and such. */
5797 return hVfsIos;
5798}
5799
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