VirtualBox

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

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

Main/Appliance: Allow users to specify a different storage controller
and/or controller port for hard disks when importing a VM. bugref:5027

Follow-up build-fix due to a missing cast when assigning an unsigned
integer to a signed integer (flagged by [-Werror,-Wsign-conversion]).

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