VirtualBox

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

Last change on this file since 84532 was 84532, checked in by vboxsync, 5 years ago

bugref:9745. Added basic support of virtio-scsi controller into OVF export\import.

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

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