VirtualBox

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

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

Main: bugref:1909: Added initial translation to Russian of API messages. Fixed errors and plurals wherever needed. Fixed type of the plural argument in the tr()

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