VirtualBox

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

Last change on this file since 90441 was 90441, checked in by vboxsync, 3 years ago

Main/Appliance: Make compilers happy (and the code actually work). For some reason g++ 8.3.0 didn't show any warning.

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