VirtualBox

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

Last change on this file since 79170 was 79170, checked in by vboxsync, 6 years ago

bugref:9416. Added cleanup logic for cloud import. Added comments about cloud import progress steps.

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