VirtualBox

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

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

bugref:9416. Fixed progress calculation.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 228.9 KB
Line 
1/* $Id: ApplianceImplImport.cpp 79306 2019-06-24 11:31:56Z 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 /* Just skip the rest images if they exist. Only the first image is used as the base image. */
1855 if (currImageObjectNum >= 1)
1856 continue;
1857
1858 /* Image format is supported by VBox so extract the file and try to convert
1859 * one to the default format (which is VMDK for now) */
1860 Utf8Str z(bstrSettingsFilename);
1861 Utf8StrFmt strAbsDstPath("%s_%d.%s",
1862 z.stripSuffix().c_str(),
1863 currImageObjectNum,
1864 strTargetFormat.c_str());
1865
1866 hrc = mVirtualBox->i_findHardDiskByLocation(strAbsDstPath, false, NULL);
1867 if (SUCCEEDED(hrc))
1868 throw setError(VERR_ALREADY_EXISTS, tr("The hard disk '%s' already exists."), strAbsDstPath.c_str());
1869
1870 /* Create an IMedium object. */
1871 ComObjPtr<Medium> pTargetMedium;
1872 pTargetMedium.createObject();
1873 hrc = pTargetMedium->init(mVirtualBox,
1874 strTargetFormat,
1875 strAbsDstPath,
1876 Guid::Empty /* media registry: none yet */,
1877 DeviceType_HardDisk);
1878 if (FAILED(hrc))
1879 throw hrc;
1880
1881 pTask->pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"), pszName).raw(),
1882 200);
1883 ComObjPtr<Medium> nullParent;
1884 ComPtr<IProgress> pProgressImport;
1885 ComObjPtr<Progress> pProgressImportTmp;
1886 hrc = pProgressImportTmp.createObject();
1887 if (FAILED(hrc))
1888 throw hrc;
1889
1890 hrc = pProgressImportTmp->init(mVirtualBox,
1891 static_cast<IAppliance*>(this),
1892 Utf8StrFmt(tr("Importing medium '%s'"),
1893 pszName),
1894 TRUE);
1895 if (FAILED(hrc))
1896 throw hrc;
1897
1898 pProgressImportTmp.queryInterfaceTo(pProgressImport.asOutParam());
1899
1900 hrc = pTargetMedium->i_importFile(pszName,
1901 srcFormat,
1902 MediumVariant_Standard,
1903 hVfsIosCurr,
1904 nullParent,
1905 pProgressImportTmp,
1906 true /* aNotify */);
1907 RTVfsIoStrmRelease(hVfsIosCurr);
1908 hVfsIosCurr = NIL_RTVFSIOSTREAM;
1909 /* Now wait for the background import operation to complete;
1910 * this throws HRESULTs on error. */
1911 pTask->pProgress->WaitForOtherProgressCompletion(pProgressImport, 0 /* indefinite wait */);
1912
1913 /* Try to re-use some OVF stuff here */
1914 {
1915 /* Small trick here.
1916 * We add new item into the actual VSD after successful conversion.
1917 * There is no need to delete any previous records describing the images in the VSD
1918 * because later in the code the search of the images in the VSD will use such records
1919 * with the actual image id (d.strDiskId = pTargetMedium->i_getId().toString()) which is
1920 * used as a key for m->pReader->m_mapDisks, vsys.mapVirtualDisks.
1921 * So all 3 objects are tied via the image id.
1922 * In the OVF case we already have all such records in the VSD after reading OVF
1923 * description file (read() and interpret() functions).*/
1924 ovf::DiskImage d;
1925 d.strDiskId = pTargetMedium->i_getId().toString();
1926 d.strHref = pTargetMedium->i_getLocationFull();
1927 d.strFormat = pTargetMedium->i_getFormat();
1928 d.iSize = pTargetMedium->i_getSize();
1929 d.ulSuggestedSizeMB = (uint32_t)(d.iSize/_1M);
1930
1931 m->pReader->m_mapDisks[d.strDiskId] = d;
1932
1933 ComObjPtr<VirtualSystemDescription> vsdescThis = m->virtualSystemDescriptions.front();
1934
1935 /* It's needed here to use the internal function i_addEntry() instead of the API function
1936 * addDescription() because we should pass the d.strDiskId for the proper handling this
1937 * disk later in the i_importMachineGeneric():
1938 * - find the line like this "if (vsdeHD->strRef == diCurrent.strDiskId)".
1939 * if those code can be eliminated then addDescription() will be used. */
1940 vsdescThis->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
1941 d.strDiskId,
1942 d.strHref,
1943 d.strHref,
1944 d.ulSuggestedSizeMB);
1945
1946 ovf::VirtualDisk vd;
1947 vd.idController = vsys.mapControllers[0].idController;
1948 vd.ulAddressOnParent = 0;
1949 vd.strDiskId = d.strDiskId;
1950 vsys.mapVirtualDisks[vd.strDiskId] = vd;
1951
1952 }
1953
1954 ++currImageObjectNum;
1955 }
1956
1957 RTVfsIoStrmRelease(hVfsIosCurr);
1958 hVfsIosCurr = NIL_RTVFSIOSTREAM;
1959 }
1960
1961 RTVfsObjRelease(hVfsObj);
1962 hVfsObj = NIL_RTVFSOBJ;
1963
1964 RTStrFree(pszName);
1965
1966 } while (SUCCEEDED(hrc));
1967
1968 RTVfsFsStrmRelease(hVfsFssObject);
1969 hVfsFssObject = NIL_RTVFSFSSTREAM;
1970
1971 if (SUCCEEDED(hrc))
1972 {
1973 pTask->pProgress->SetNextOperation(BstrFmt(tr("Creating new VM '%s'", strVMName.c_str())).raw(), 50);
1974 /* Create the import stack to comply OVF logic.
1975 * Before we filled some other data structures which are needed by OVF logic too.*/
1976 ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, NIL_RTVFSFSSTREAM);
1977 i_importMachines(stack);
1978 }
1979
1980 }
1981 catch (HRESULT aRc)
1982 {
1983 hrc = aRc;
1984 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
1985 "The exception (%Rrc)\n", __FUNCTION__, hrc);
1986 LogRel((strLastActualErrorDesc.c_str()));
1987 }
1988 catch (int aRc)
1989 {
1990 hrc = setErrorVrc(aRc);
1991 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
1992 "The exception (%Rrc)\n", __FUNCTION__, aRc);
1993 LogRel((strLastActualErrorDesc.c_str()));
1994 }
1995 catch (...)
1996 {
1997 hrc = VERR_UNRESOLVED_ERROR;
1998 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
1999 "The exception (%Rrc)\n", __FUNCTION__, hrc);
2000 LogRel((strLastActualErrorDesc.c_str()));
2001 }
2002
2003 LogRel(("%s: Cloud import (local phase) final result (%Rrc).\n", __FUNCTION__, hrc));
2004
2005 /* Try to free VFS stuff because some of them might not be released due to the exception */
2006 if (hVfsIosCurr != NIL_RTVFSIOSTREAM)
2007 RTVfsIoStrmRelease(hVfsIosCurr);
2008 if (hVfsObj != NIL_RTVFSOBJ)
2009 RTVfsObjRelease(hVfsObj);
2010 if (hVfsFssObject != NIL_RTVFSFSSTREAM)
2011 RTVfsFsStrmRelease(hVfsFssObject);
2012
2013 /* Small explanation here.
2014 * After adding extracted files into the actual VSD the returned list will contain not only the
2015 * record about the downloaded object but also the records about the extracted files from this object.
2016 * It's needed to go through this list to find the record about the downloaded object.
2017 * But it was the first record added into the list, so aVBoxValues[0] should be correct here.
2018 */
2019 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage)//aVBoxValues is set in this #define
2020 if (!fKeepDownloadedObject)
2021 {
2022 if (aVBoxValues.size() != 0)
2023 {
2024 vsdData = aVBoxValues[0];
2025 //try to delete the downloaded object
2026 bool fExist = RTPathExists(vsdData.c_str());
2027 if (fExist)
2028 {
2029 vrc = RTFileDelete(vsdData.c_str());
2030 if (RT_FAILURE(vrc))
2031 LogRel(("%s: Cleanup action - the downloaded object %s hasn't been deleted\n", __FUNCTION__, vsdData.c_str()));
2032 else
2033 LogRel(("%s: Cleanup action - the downloaded object %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
2034 }
2035 }
2036 }
2037
2038 if (FAILED(hrc))
2039 {
2040 /* What to do here?
2041 * For now:
2042 * - check the registration of created VM and delete one.
2043 * - check the list of imported images, detach them and next delete if they have still registered in the VBox.
2044 * - check some other leavings and delete them if they exist.
2045 */
2046
2047 /* It's not needed to call "pTask->pProgress->SetNextOperation(BstrFmt("The cleanup phase").raw(), 50)" here
2048 * because, WaitForOtherProgressCompletion() calls the SetNextOperation() iternally.
2049 * At least, it's strange that the operation description is set to the previous value. */
2050
2051 ComPtr<IMachine> pMachine;
2052 Utf8Str machineNameOrId = strVMName;
2053
2054 /* m->llGuidsMachinesCreated is filled in the i_importMachineGeneric()/i_importVBoxMachine()
2055 * after successful registration of new VM */
2056 if (!m->llGuidsMachinesCreated.empty())
2057 machineNameOrId = m->llGuidsMachinesCreated.front().toString();
2058
2059 hrc = mVirtualBox->FindMachine(Bstr(machineNameOrId).raw(), pMachine.asOutParam());
2060
2061 if (SUCCEEDED(hrc))
2062 {
2063 LogRel(("%s: Cleanup action - the VM with the name(or id) %s was found\n", __FUNCTION__, machineNameOrId.c_str()));
2064 SafeIfaceArray<IMedium> aMedia;
2065 hrc = pMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
2066 if (SUCCEEDED(hrc))
2067 {
2068 LogRel(("%s: Cleanup action - the VM %s has been unregistered\n", __FUNCTION__, machineNameOrId.c_str()));
2069 ComPtr<IProgress> pProgress1;
2070 hrc = pMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress1.asOutParam());
2071 pTask->pProgress->WaitForOtherProgressCompletion(pProgress1, 0 /* indefinite wait */);
2072
2073 LogRel(("%s: Cleanup action - the VM config file and the attached images have been deleted\n",
2074 __FUNCTION__));
2075 }
2076 }
2077 else
2078 {
2079 /* Re-check the items in the array with the images names (paths).
2080 * if the import fails before creation VM, then VM won't be found
2081 * -> VM can't be unregistered and the images can't be deleted.
2082 * The rest items in the array aVBoxValues are the images which might
2083 * have still been registered in the VBox.
2084 * So go through the array and detach-unregister-delete those images */
2085
2086 /* have to get write lock as the whole find/update sequence must be done
2087 * in one critical section, otherwise there are races which can lead to
2088 * multiple Medium objects with the same content */
2089
2090 AutoWriteLock treeLock(mVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
2091
2092 for (size_t i = 1; i < aVBoxValues.size(); ++i)
2093 {
2094 vsdData = aVBoxValues[i];
2095 ComObjPtr<Medium> poHardDisk;
2096 hrc = mVirtualBox->i_findHardDiskByLocation(vsdData, false, &poHardDisk);
2097 if (SUCCEEDED(hrc))
2098 {
2099 hrc = mVirtualBox->i_unregisterMedium((Medium*)(poHardDisk));
2100 if (SUCCEEDED(hrc))
2101 {
2102 ComPtr<IProgress> pProgress1;
2103 hrc = poHardDisk->DeleteStorage(pProgress1.asOutParam());
2104 pTask->pProgress->WaitForOtherProgressCompletion(pProgress1, 0 /* indefinite wait */);
2105 }
2106 if (SUCCEEDED(hrc))
2107 LogRel(("%s: Cleanup action - the image %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
2108 }
2109 else if (hrc == VBOX_E_OBJECT_NOT_FOUND)
2110 {
2111 LogRel(("%s: Cleanup action - the image %s wasn't found. Nothing to delete.\n", __FUNCTION__, vsdData.c_str()));
2112 hrc = S_OK;
2113 }
2114
2115 }
2116 }
2117
2118 /* Deletion of all additional files which were created during unpacking the downloaded object */
2119 for (size_t i = 0; i < extraCreatedFiles.size(); ++i)
2120 {
2121 vrc = RTFileDelete(extraCreatedFiles.at(i).c_str());
2122 if (RT_FAILURE(vrc))
2123 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
2124 else
2125 LogRel(("%s: Cleanup action - file %s has been deleted\n", __FUNCTION__, extraCreatedFiles.at(i).c_str()));
2126 }
2127
2128 /* Deletion of the other files in the VM folder and the folder itself */
2129 {
2130 RTDIR hDir;
2131 vrc = RTDirOpen(&hDir, strMachineFolder.c_str());
2132 if (RT_SUCCESS(vrc))
2133 {
2134 for (;;)
2135 {
2136 RTDIRENTRYEX Entry;
2137 vrc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2138 if (RT_FAILURE(vrc))
2139 {
2140 AssertLogRelMsg(vrc == VERR_NO_MORE_FILES, ("%Rrc\n", vrc));
2141 break;
2142 }
2143 if (RTFS_IS_FILE(Entry.Info.Attr.fMode))
2144 {
2145 vrc = RTFileDelete(Entry.szName);
2146 if (RT_FAILURE(vrc))
2147 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
2148 else
2149 LogRel(("%s: Cleanup action - file %s has been deleted\n", __FUNCTION__, Entry.szName));
2150 }
2151 }
2152 RTDirClose(hDir);
2153 }
2154
2155 vrc = RTDirRemove(strMachineFolder.c_str());
2156 if (RT_FAILURE(vrc))
2157 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
2158 }
2159
2160 if (FAILED(hrc))
2161 LogRel(("%s: Cleanup action - some leavings still may exist in the folder %s\n",
2162 __FUNCTION__, strMachineFolder.c_str()));
2163 }
2164 else
2165 {
2166 /* See explanation in the Appliance::i_importImpl() where Progress was setup */
2167 ULONG operationCount;
2168 ULONG currOperation;
2169 pTask->pProgress->COMGETTER(OperationCount)(&operationCount);
2170 pTask->pProgress->COMGETTER(Operation)(&currOperation);
2171 while (++currOperation < operationCount)
2172 {
2173 pTask->pProgress->SetNextOperation(BstrFmt("Skipping the cleanup phase. All right.").raw(), 1);
2174 LogRel(("%s: Skipping the cleanup step %d\n", __FUNCTION__, currOperation));
2175 }
2176 }
2177 }
2178
2179 LogFlowFunc(("rc=%Rhrc\n", hrc));
2180 LogFlowFuncLeave();
2181 return hrc;
2182}
2183
2184/**
2185 * Actual worker code for reading an OVF from disk. This is called from Appliance::taskThreadImportOrExport()
2186 * and therefore runs on the OVF read worker thread. This opens the OVF with ovfreader.cpp.
2187 *
2188 * This runs in one context:
2189 *
2190 * 1) in a first worker thread; in that case, Appliance::Read() called Appliance::readImpl();
2191 *
2192 * @param pTask
2193 * @return
2194 */
2195HRESULT Appliance::i_readFS(TaskOVF *pTask)
2196{
2197 LogFlowFuncEnter();
2198 LogFlowFunc(("Appliance %p\n", this));
2199
2200 AutoCaller autoCaller(this);
2201 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2202
2203 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
2204
2205 HRESULT rc;
2206 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
2207 rc = i_readFSOVF(pTask);
2208 else
2209 rc = i_readFSOVA(pTask);
2210
2211 LogFlowFunc(("rc=%Rhrc\n", rc));
2212 LogFlowFuncLeave();
2213
2214 return rc;
2215}
2216
2217HRESULT Appliance::i_readFSOVF(TaskOVF *pTask)
2218{
2219 LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
2220
2221 /*
2222 * Allocate a buffer for filenames and prep it for suffix appending.
2223 */
2224 char *pszNameBuf = (char *)alloca(pTask->locInfo.strPath.length() + 16);
2225 AssertReturn(pszNameBuf, VERR_NO_TMP_MEMORY);
2226 memcpy(pszNameBuf, pTask->locInfo.strPath.c_str(), pTask->locInfo.strPath.length() + 1);
2227 RTPathStripSuffix(pszNameBuf);
2228 size_t const cchBaseName = strlen(pszNameBuf);
2229
2230 /*
2231 * Open the OVF file first since that is what this is all about.
2232 */
2233 RTVFSIOSTREAM hIosOvf;
2234 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
2235 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosOvf);
2236 if (RT_FAILURE(vrc))
2237 return setErrorVrc(vrc, tr("Failed to open OVF file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2238
2239 HRESULT hrc = i_readOVFFile(pTask, hIosOvf, RTPathFilename(pTask->locInfo.strPath.c_str())); /* consumes hIosOvf */
2240 if (FAILED(hrc))
2241 return hrc;
2242
2243 /*
2244 * Try open the manifest file (for signature purposes and to determine digest type(s)).
2245 */
2246 RTVFSIOSTREAM hIosMf;
2247 strcpy(&pszNameBuf[cchBaseName], ".mf");
2248 vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosMf);
2249 if (RT_SUCCESS(vrc))
2250 {
2251 const char * const pszFilenamePart = RTPathFilename(pszNameBuf);
2252 hrc = i_readManifestFile(pTask, hIosMf /*consumed*/, pszFilenamePart);
2253 if (FAILED(hrc))
2254 return hrc;
2255
2256 /*
2257 * Check for the signature file.
2258 */
2259 RTVFSIOSTREAM hIosCert;
2260 strcpy(&pszNameBuf[cchBaseName], ".cert");
2261 vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosCert);
2262 if (RT_SUCCESS(vrc))
2263 {
2264 hrc = i_readSignatureFile(pTask, hIosCert /*consumed*/, pszFilenamePart);
2265 if (FAILED(hrc))
2266 return hrc;
2267 }
2268 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
2269 return setErrorVrc(vrc, tr("Failed to open the signature file '%s' (%Rrc)"), pszNameBuf, vrc);
2270
2271 }
2272 else if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
2273 {
2274 m->fDeterminedDigestTypes = true;
2275 m->fDigestTypes = 0;
2276 }
2277 else
2278 return setErrorVrc(vrc, tr("Failed to open the manifest file '%s' (%Rrc)"), pszNameBuf, vrc);
2279
2280 /*
2281 * Do tail processing (check the signature).
2282 */
2283 hrc = i_readTailProcessing(pTask);
2284
2285 LogFlowFunc(("returns %Rhrc\n", hrc));
2286 return hrc;
2287}
2288
2289HRESULT Appliance::i_readFSOVA(TaskOVF *pTask)
2290{
2291 LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
2292
2293 /*
2294 * Open the tar file as file stream.
2295 */
2296 RTVFSIOSTREAM hVfsIosOva;
2297 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
2298 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
2299 if (RT_FAILURE(vrc))
2300 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2301
2302 RTVFSFSSTREAM hVfsFssOva;
2303 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
2304 RTVfsIoStrmRelease(hVfsIosOva);
2305 if (RT_FAILURE(vrc))
2306 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2307
2308 /*
2309 * Since jumping thru an OVA file with seekable disk backing is rather
2310 * efficient, we can process .ovf, .mf and .cert files here without any
2311 * strict ordering restrictions.
2312 *
2313 * (Technically, the .ovf-file comes first, while the manifest and its
2314 * optional signature file either follows immediately or at the very end of
2315 * the OVA. The manifest is optional.)
2316 */
2317 char *pszOvfNameBase = NULL;
2318 size_t cchOvfNameBase = 0; NOREF(cchOvfNameBase);
2319 unsigned cLeftToFind = 3;
2320 HRESULT hrc = S_OK;
2321 do
2322 {
2323 char *pszName = NULL;
2324 RTVFSOBJTYPE enmType;
2325 RTVFSOBJ hVfsObj;
2326 vrc = RTVfsFsStrmNext(hVfsFssOva, &pszName, &enmType, &hVfsObj);
2327 if (RT_FAILURE(vrc))
2328 {
2329 if (vrc != VERR_EOF)
2330 hrc = setErrorVrc(vrc, tr("Error reading OVA '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2331 break;
2332 }
2333
2334 /* We only care about entries that are files. Get the I/O stream handle for them. */
2335 if ( enmType == RTVFSOBJTYPE_IO_STREAM
2336 || enmType == RTVFSOBJTYPE_FILE)
2337 {
2338 /* Find the suffix and check if this is a possibly interesting file. */
2339 char *pszSuffix = strrchr(pszName, '.');
2340 if ( pszSuffix
2341 && ( RTStrICmp(pszSuffix + 1, "ovf") == 0
2342 || RTStrICmp(pszSuffix + 1, "mf") == 0
2343 || RTStrICmp(pszSuffix + 1, "cert") == 0) )
2344 {
2345 /* Match the OVF base name. */
2346 *pszSuffix = '\0';
2347 if ( pszOvfNameBase == NULL
2348 || RTStrICmp(pszName, pszOvfNameBase) == 0)
2349 {
2350 *pszSuffix = '.';
2351
2352 /* Since we're pretty sure we'll be processing this file, get the I/O stream. */
2353 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
2354 Assert(hVfsIos != NIL_RTVFSIOSTREAM);
2355
2356 /* Check for the OVF (should come first). */
2357 if (RTStrICmp(pszSuffix + 1, "ovf") == 0)
2358 {
2359 if (pszOvfNameBase == NULL)
2360 {
2361 hrc = i_readOVFFile(pTask, hVfsIos, pszName);
2362 hVfsIos = NIL_RTVFSIOSTREAM;
2363
2364 /* Set the base name. */
2365 *pszSuffix = '\0';
2366 pszOvfNameBase = pszName;
2367 cchOvfNameBase = strlen(pszName);
2368 pszName = NULL;
2369 cLeftToFind--;
2370 }
2371 else
2372 LogRel(("i_readFSOVA: '%s' contains more than one OVF file ('%s'), picking the first one\n",
2373 pTask->locInfo.strPath.c_str(), pszName));
2374 }
2375 /* Check for manifest. */
2376 else if (RTStrICmp(pszSuffix + 1, "mf") == 0)
2377 {
2378 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
2379 {
2380 hrc = i_readManifestFile(pTask, hVfsIos, pszName);
2381 hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
2382 cLeftToFind--;
2383 }
2384 else
2385 LogRel(("i_readFSOVA: '%s' contains more than one manifest file ('%s'), picking the first one\n",
2386 pTask->locInfo.strPath.c_str(), pszName));
2387 }
2388 /* Check for signature. */
2389 else if (RTStrICmp(pszSuffix + 1, "cert") == 0)
2390 {
2391 if (!m->fSignerCertLoaded)
2392 {
2393 hrc = i_readSignatureFile(pTask, hVfsIos, pszName);
2394 hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
2395 cLeftToFind--;
2396 }
2397 else
2398 LogRel(("i_readFSOVA: '%s' contains more than one signature file ('%s'), picking the first one\n",
2399 pTask->locInfo.strPath.c_str(), pszName));
2400 }
2401 else
2402 AssertFailed();
2403 if (hVfsIos != NIL_RTVFSIOSTREAM)
2404 RTVfsIoStrmRelease(hVfsIos);
2405 }
2406 }
2407 }
2408 RTVfsObjRelease(hVfsObj);
2409 RTStrFree(pszName);
2410 } while (cLeftToFind > 0 && SUCCEEDED(hrc));
2411
2412 RTVfsFsStrmRelease(hVfsFssOva);
2413 RTStrFree(pszOvfNameBase);
2414
2415 /*
2416 * Check that we found and OVF file.
2417 */
2418 if (SUCCEEDED(hrc) && !pszOvfNameBase)
2419 hrc = setError(VBOX_E_FILE_ERROR, tr("OVA '%s' does not contain an .ovf-file"), pTask->locInfo.strPath.c_str());
2420 if (SUCCEEDED(hrc))
2421 {
2422 /*
2423 * Do tail processing (check the signature).
2424 */
2425 hrc = i_readTailProcessing(pTask);
2426 }
2427 LogFlowFunc(("returns %Rhrc\n", hrc));
2428 return hrc;
2429}
2430
2431/**
2432 * Reads & parses the OVF file.
2433 *
2434 * @param pTask The read task.
2435 * @param hVfsIosOvf The I/O stream for the OVF. The reference is
2436 * always consumed.
2437 * @param pszManifestEntry The manifest entry name.
2438 * @returns COM status code, error info set.
2439 * @throws Nothing
2440 */
2441HRESULT Appliance::i_readOVFFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosOvf, const char *pszManifestEntry)
2442{
2443 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszManifestEntry));
2444
2445 /*
2446 * Set the OVF manifest entry name (needed for tweaking the manifest
2447 * validation during import).
2448 */
2449 try { m->strOvfManifestEntry = pszManifestEntry; }
2450 catch (...) { return E_OUTOFMEMORY; }
2451
2452 /*
2453 * Set up digest calculation.
2454 */
2455 hVfsIosOvf = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosOvf, pszManifestEntry);
2456 if (hVfsIosOvf == NIL_RTVFSIOSTREAM)
2457 return VBOX_E_FILE_ERROR;
2458
2459 /*
2460 * Read the OVF into a memory buffer and parse it.
2461 */
2462 void *pvBufferedOvf;
2463 size_t cbBufferedOvf;
2464 int vrc = RTVfsIoStrmReadAll(hVfsIosOvf, &pvBufferedOvf, &cbBufferedOvf);
2465 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosOvf); /* consumes stream handle. */
2466 NOREF(cRefs);
2467 Assert(cRefs == 0);
2468 if (RT_FAILURE(vrc))
2469 return setErrorVrc(vrc, tr("Could not read the OVF file for '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2470
2471 HRESULT hrc;
2472 try
2473 {
2474 m->pReader = new ovf::OVFReader(pvBufferedOvf, cbBufferedOvf, pTask->locInfo.strPath);
2475 hrc = S_OK;
2476 }
2477 catch (RTCError &rXcpt) // includes all XML exceptions
2478 {
2479 hrc = setError(VBOX_E_FILE_ERROR, rXcpt.what());
2480 }
2481 catch (HRESULT aRC)
2482 {
2483 hrc = aRC;
2484 }
2485 catch (...)
2486 {
2487 hrc = E_FAIL;
2488 }
2489 LogFlowFunc(("OVFReader(%s) -> rc=%Rhrc\n", pTask->locInfo.strPath.c_str(), hrc));
2490
2491 RTVfsIoStrmReadAllFree(pvBufferedOvf, cbBufferedOvf);
2492 if (SUCCEEDED(hrc))
2493 {
2494 /*
2495 * If we see an OVF v2.0 envelope, select only the SHA-256 digest.
2496 */
2497 if ( !m->fDeterminedDigestTypes
2498 && m->pReader->m_envelopeData.getOVFVersion() == ovf::OVFVersion_2_0)
2499 m->fDigestTypes &= ~RTMANIFEST_ATTR_SHA256;
2500 }
2501
2502 return hrc;
2503}
2504
2505/**
2506 * Reads & parses the manifest file.
2507 *
2508 * @param pTask The read task.
2509 * @param hVfsIosMf The I/O stream for the manifest file. The
2510 * reference is always consumed.
2511 * @param pszSubFileNm The manifest filename (no path) for error
2512 * messages and logging.
2513 * @returns COM status code, error info set.
2514 * @throws Nothing
2515 */
2516HRESULT Appliance::i_readManifestFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosMf, const char *pszSubFileNm)
2517{
2518 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
2519
2520 /*
2521 * Copy the manifest into a memory backed file so we can later do signature
2522 * validation indepentend of the algorithms used by the signature.
2523 */
2524 int vrc = RTVfsMemorizeIoStreamAsFile(hVfsIosMf, RTFILE_O_READ, &m->hMemFileTheirManifest);
2525 RTVfsIoStrmRelease(hVfsIosMf); /* consumes stream handle. */
2526 if (RT_FAILURE(vrc))
2527 return setErrorVrc(vrc, tr("Error reading the manifest file '%s' for '%s' (%Rrc)"),
2528 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
2529
2530 /*
2531 * Parse the manifest.
2532 */
2533 Assert(m->hTheirManifest == NIL_RTMANIFEST);
2534 vrc = RTManifestCreate(0 /*fFlags*/, &m->hTheirManifest);
2535 AssertStmt(RT_SUCCESS(vrc), Global::vboxStatusCodeToCOM(vrc));
2536
2537 char szErr[256];
2538 RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(m->hMemFileTheirManifest);
2539 vrc = RTManifestReadStandardEx(m->hTheirManifest, hVfsIos, szErr, sizeof(szErr));
2540 RTVfsIoStrmRelease(hVfsIos);
2541 if (RT_FAILURE(vrc))
2542 throw setErrorVrc(vrc, tr("Failed to parse manifest file '%s' for '%s' (%Rrc): %s"),
2543 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, szErr);
2544
2545 /*
2546 * Check which digest files are used.
2547 * Note! the file could be empty, in which case fDigestTypes is set to 0.
2548 */
2549 vrc = RTManifestQueryAllAttrTypes(m->hTheirManifest, true /*fEntriesOnly*/, &m->fDigestTypes);
2550 AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc));
2551 m->fDeterminedDigestTypes = true;
2552
2553 return S_OK;
2554}
2555
2556/**
2557 * Reads the signature & certificate file.
2558 *
2559 * @param pTask The read task.
2560 * @param hVfsIosCert The I/O stream for the signature file. The
2561 * reference is always consumed.
2562 * @param pszSubFileNm The signature filename (no path) for error
2563 * messages and logging. Used to construct
2564 * .mf-file name.
2565 * @returns COM status code, error info set.
2566 * @throws Nothing
2567 */
2568HRESULT Appliance::i_readSignatureFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosCert, const char *pszSubFileNm)
2569{
2570 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
2571
2572 /*
2573 * Construct the manifest filename from pszSubFileNm.
2574 */
2575 Utf8Str strManifestName;
2576 try
2577 {
2578 const char *pszSuffix = strrchr(pszSubFileNm, '.');
2579 AssertReturn(pszSuffix, E_FAIL);
2580 strManifestName = Utf8Str(pszSubFileNm, pszSuffix - pszSubFileNm);
2581 strManifestName.append(".mf");
2582 }
2583 catch (...)
2584 {
2585 return E_OUTOFMEMORY;
2586 }
2587
2588 /*
2589 * Copy the manifest into a memory buffer. We'll do the signature processing
2590 * later to not force any specific order in the OVAs or any other archive we
2591 * may be accessing later.
2592 */
2593 void *pvSignature;
2594 size_t cbSignature;
2595 int vrc = RTVfsIoStrmReadAll(hVfsIosCert, &pvSignature, &cbSignature);
2596 RTVfsIoStrmRelease(hVfsIosCert); /* consumes stream handle. */
2597 if (RT_FAILURE(vrc))
2598 return setErrorVrc(vrc, tr("Error reading the signature file '%s' for '%s' (%Rrc)"),
2599 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
2600
2601 /*
2602 * Parse the signing certificate. Unlike the manifest parser we use below,
2603 * this API ignores parse of the file that aren't relevant.
2604 */
2605 RTERRINFOSTATIC StaticErrInfo;
2606 vrc = RTCrX509Certificate_ReadFromBuffer(&m->SignerCert, pvSignature, cbSignature,
2607 RTCRX509CERT_READ_F_PEM_ONLY,
2608 &g_RTAsn1DefaultAllocator, RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
2609 HRESULT hrc;
2610 if (RT_SUCCESS(vrc))
2611 {
2612 m->fSignerCertLoaded = true;
2613 m->fCertificateIsSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->SignerCert);
2614
2615 /*
2616 * Find the start of the certificate part of the file, so we can avoid
2617 * upsetting the manifest parser with it.
2618 */
2619 char *pszSplit = (char *)RTCrPemFindFirstSectionInContent(pvSignature, cbSignature,
2620 g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers);
2621 if (pszSplit)
2622 while ( pszSplit != (char *)pvSignature
2623 && pszSplit[-1] != '\n'
2624 && pszSplit[-1] != '\r')
2625 pszSplit--;
2626 else
2627 {
2628 AssertLogRelMsgFailed(("Failed to find BEGIN CERTIFICATE markers in '%s'::'%s' - impossible unless it's a DER encoded certificate!",
2629 pTask->locInfo.strPath.c_str(), pszSubFileNm));
2630 pszSplit = (char *)pvSignature + cbSignature;
2631 }
2632 *pszSplit = '\0';
2633
2634 /*
2635 * Now, read the manifest part. We use the IPRT manifest reader here
2636 * to avoid duplicating code and be somewhat flexible wrt the digest
2637 * type choosen by the signer.
2638 */
2639 RTMANIFEST hSignedDigestManifest;
2640 vrc = RTManifestCreate(0 /*fFlags*/, &hSignedDigestManifest);
2641 if (RT_SUCCESS(vrc))
2642 {
2643 RTVFSIOSTREAM hVfsIosTmp;
2644 vrc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pvSignature, pszSplit - (char *)pvSignature, &hVfsIosTmp);
2645 if (RT_SUCCESS(vrc))
2646 {
2647 vrc = RTManifestReadStandardEx(hSignedDigestManifest, hVfsIosTmp, StaticErrInfo.szMsg, sizeof(StaticErrInfo.szMsg));
2648 RTVfsIoStrmRelease(hVfsIosTmp);
2649 if (RT_SUCCESS(vrc))
2650 {
2651 /*
2652 * Get signed digest, we prefer SHA-2, so explicitly query those first.
2653 */
2654 uint32_t fDigestType;
2655 char szSignedDigest[_8K + 1];
2656 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
2657 RTMANIFEST_ATTR_SHA512 | RTMANIFEST_ATTR_SHA256,
2658 szSignedDigest, sizeof(szSignedDigest), &fDigestType);
2659 if (vrc == VERR_MANIFEST_ATTR_TYPE_NOT_FOUND)
2660 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
2661 RTMANIFEST_ATTR_ANY, szSignedDigest, sizeof(szSignedDigest), &fDigestType);
2662 if (RT_SUCCESS(vrc))
2663 {
2664 const char *pszSignedDigest = RTStrStrip(szSignedDigest);
2665 size_t cbSignedDigest = strlen(pszSignedDigest) / 2;
2666 uint8_t abSignedDigest[sizeof(szSignedDigest) / 2];
2667 vrc = RTStrConvertHexBytes(szSignedDigest, abSignedDigest, cbSignedDigest, 0 /*fFlags*/);
2668 if (RT_SUCCESS(vrc))
2669 {
2670 /*
2671 * Convert it to RTDIGESTTYPE_XXX and save the binary value for later use.
2672 */
2673 switch (fDigestType)
2674 {
2675 case RTMANIFEST_ATTR_SHA1: m->enmSignedDigestType = RTDIGESTTYPE_SHA1; break;
2676 case RTMANIFEST_ATTR_SHA256: m->enmSignedDigestType = RTDIGESTTYPE_SHA256; break;
2677 case RTMANIFEST_ATTR_SHA512: m->enmSignedDigestType = RTDIGESTTYPE_SHA512; break;
2678 case RTMANIFEST_ATTR_MD5: m->enmSignedDigestType = RTDIGESTTYPE_MD5; break;
2679 default: AssertFailed(); m->enmSignedDigestType = RTDIGESTTYPE_INVALID; break;
2680 }
2681 if (m->enmSignedDigestType != RTDIGESTTYPE_INVALID)
2682 {
2683 m->pbSignedDigest = (uint8_t *)RTMemDup(abSignedDigest, cbSignedDigest);
2684 m->cbSignedDigest = cbSignedDigest;
2685 hrc = S_OK;
2686 }
2687 else
2688 hrc = setError(E_FAIL, tr("Unsupported signed digest type (%#x)"), fDigestType);
2689 }
2690 else
2691 hrc = setErrorVrc(vrc, tr("Error reading signed manifest digest: %Rrc"), vrc);
2692 }
2693 else if (vrc == VERR_NOT_FOUND)
2694 hrc = setErrorVrc(vrc, tr("Could not locate signed digest for '%s' in the cert-file for '%s'"),
2695 strManifestName.c_str(), pTask->locInfo.strPath.c_str());
2696 else
2697 hrc = setErrorVrc(vrc, tr("RTManifestEntryQueryAttr failed unexpectedly: %Rrc"), vrc);
2698 }
2699 else
2700 hrc = setErrorVrc(vrc, tr("Error parsing the .cert-file for '%s': %s"),
2701 pTask->locInfo.strPath.c_str(), StaticErrInfo.szMsg);
2702 }
2703 else
2704 hrc = E_OUTOFMEMORY;
2705 RTManifestRelease(hSignedDigestManifest);
2706 }
2707 else
2708 hrc = E_OUTOFMEMORY;
2709 }
2710 else if (vrc == VERR_NOT_FOUND || vrc == VERR_EOF)
2711 hrc = setErrorBoth(E_FAIL, vrc, tr("Malformed .cert-file for '%s': Signer's certificate not found (%Rrc)"),
2712 pTask->locInfo.strPath.c_str(), vrc);
2713 else
2714 hrc = setErrorVrc(vrc, tr("Error reading the signer's certificate from '%s' for '%s' (%Rrc): %s"),
2715 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
2716
2717 RTVfsIoStrmReadAllFree(pvSignature, cbSignature);
2718 LogFlowFunc(("returns %Rhrc (%Rrc)\n", hrc, vrc));
2719 return hrc;
2720}
2721
2722
2723/**
2724 * Does tail processing after the files have been read in.
2725 *
2726 * @param pTask The read task.
2727 * @returns COM status.
2728 * @throws Nothing!
2729 */
2730HRESULT Appliance::i_readTailProcessing(TaskOVF *pTask)
2731{
2732 /*
2733 * Parse and validate the signature file.
2734 *
2735 * The signature file has two parts, manifest part and a PEM encoded
2736 * certificate. The former contains an entry for the manifest file with a
2737 * digest that is encrypted with the certificate in the latter part.
2738 */
2739 if (m->pbSignedDigest)
2740 {
2741 /* Since we're validating the digest of the manifest, there have to be
2742 a manifest. We cannot allow a the manifest to be missing. */
2743 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
2744 return setError(VBOX_E_FILE_ERROR, tr("Found .cert-file but no .mf-file for '%s'"), pTask->locInfo.strPath.c_str());
2745
2746 /*
2747 * Validate the signed digest.
2748 *
2749 * It's possible we should allow the user to ignore signature
2750 * mismatches, but for now it is a solid show stopper.
2751 */
2752 HRESULT hrc;
2753 RTERRINFOSTATIC StaticErrInfo;
2754
2755 /* Calc the digest of the manifest using the algorithm found above. */
2756 RTCRDIGEST hDigest;
2757 int vrc = RTCrDigestCreateByType(&hDigest, m->enmSignedDigestType);
2758 if (RT_SUCCESS(vrc))
2759 {
2760 vrc = RTCrDigestUpdateFromVfsFile(hDigest, m->hMemFileTheirManifest, true /*fRewindFile*/);
2761 if (RT_SUCCESS(vrc))
2762 {
2763 /* Compare the signed digest with the one we just calculated. (This
2764 API will do the verification twice, once using IPRT's own crypto
2765 and once using OpenSSL. Both must OK it for success.) */
2766 vrc = RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(&m->SignerCert.TbsCertificate.SubjectPublicKeyInfo,
2767 m->pbSignedDigest, m->cbSignedDigest, hDigest,
2768 RTErrInfoInitStatic(&StaticErrInfo));
2769 if (RT_SUCCESS(vrc))
2770 {
2771 m->fSignatureValid = true;
2772 hrc = S_OK;
2773 }
2774 else if (vrc == VERR_CR_PKIX_SIGNATURE_MISMATCH)
2775 hrc = setErrorVrc(vrc, tr("The manifest signature does not match"));
2776 else
2777 hrc = setErrorVrc(vrc,
2778 tr("Error validating the manifest signature (%Rrc, %s)"), vrc, StaticErrInfo.Core.pszMsg);
2779 }
2780 else
2781 hrc = setErrorVrc(vrc, tr("RTCrDigestUpdateFromVfsFile failed: %Rrc"), vrc);
2782 RTCrDigestRelease(hDigest);
2783 }
2784 else
2785 hrc = setErrorVrc(vrc, tr("RTCrDigestCreateByType failed: %Rrc"), vrc);
2786
2787 /*
2788 * Validate the certificate.
2789 *
2790 * We don't fail here on if we cannot validate the certificate, we postpone
2791 * that till the import stage, so that we can allow the user to ignore it.
2792 *
2793 * The certificate validity time is deliberately left as warnings as the
2794 * OVF specification does not provision for any timestamping of the
2795 * signature. This is course a security concern, but the whole signing
2796 * of OVFs is currently weirdly trusting (self signed * certs), so this
2797 * is the least of our current problems.
2798 *
2799 * While we try build and verify certificate paths properly, the
2800 * "neighbours" quietly ignores this and seems only to check the signature
2801 * and not whether the certificate is trusted. Also, we don't currently
2802 * complain about self-signed certificates either (ditto "neighbours").
2803 * The OVF creator is also a bit restricted wrt to helping us build the
2804 * path as he cannot supply intermediate certificates. Anyway, we issue
2805 * warnings (goes to /dev/null, am I right?) for self-signed certificates
2806 * and certificates we cannot build and verify a root path for.
2807 *
2808 * (The OVF sillibuggers should've used PKCS#7, CMS or something else
2809 * that's already been standardized instead of combining manifests with
2810 * certificate PEM files in some very restrictive manner! I wonder if
2811 * we could add a PKCS#7 section to the .cert file in addition to the CERT
2812 * and manifest stuff dictated by the standard. Would depend on how others
2813 * deal with it.)
2814 */
2815 Assert(!m->fCertificateValid);
2816 Assert(m->fCertificateMissingPath);
2817 Assert(!m->fCertificateValidTime);
2818 Assert(m->strCertError.isEmpty());
2819 Assert(m->fCertificateIsSelfSigned == RTCrX509Certificate_IsSelfSigned(&m->SignerCert));
2820
2821 HRESULT hrc2 = S_OK;
2822 if (m->fCertificateIsSelfSigned)
2823 {
2824 /*
2825 * It's a self signed certificate. We assume the frontend will
2826 * present this fact to the user and give a choice whether this
2827 * is acceptible. But, first make sure it makes internal sense.
2828 */
2829 m->fCertificateMissingPath = true; /** @todo need to check if the certificate is trusted by the system! */
2830 vrc = RTCrX509Certificate_VerifySignatureSelfSigned(&m->SignerCert, RTErrInfoInitStatic(&StaticErrInfo));
2831 if (RT_SUCCESS(vrc))
2832 {
2833 m->fCertificateValid = true;
2834
2835 /* Check whether the certificate is currently valid, just warn if not. */
2836 RTTIMESPEC Now;
2837 if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
2838 {
2839 m->fCertificateValidTime = true;
2840 i_addWarning(tr("A self signed certificate was used to sign '%s'"), pTask->locInfo.strPath.c_str());
2841 }
2842 else
2843 i_addWarning(tr("Self signed certificate used to sign '%s' is not currently valid"),
2844 pTask->locInfo.strPath.c_str());
2845
2846 /* Just warn if it's not a CA. Self-signed certificates are
2847 hardly trustworthy to start with without the user's consent. */
2848 if ( !m->SignerCert.TbsCertificate.T3.pBasicConstraints
2849 || !m->SignerCert.TbsCertificate.T3.pBasicConstraints->CA.fValue)
2850 i_addWarning(tr("Self signed certificate used to sign '%s' is not marked as certificate authority (CA)"),
2851 pTask->locInfo.strPath.c_str());
2852 }
2853 else
2854 {
2855 try { m->strCertError = Utf8StrFmt(tr("Verification of the self signed certificate failed (%Rrc, %s)"),
2856 vrc, StaticErrInfo.Core.pszMsg); }
2857 catch (...) { AssertFailed(); }
2858 i_addWarning(tr("Verification of the self signed certificate used to sign '%s' failed (%Rrc): %s"),
2859 pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
2860 }
2861 }
2862 else
2863 {
2864 /*
2865 * The certificate is not self-signed. Use the system certificate
2866 * stores to try build a path that validates successfully.
2867 */
2868 RTCRX509CERTPATHS hCertPaths;
2869 vrc = RTCrX509CertPathsCreate(&hCertPaths, &m->SignerCert);
2870 if (RT_SUCCESS(vrc))
2871 {
2872 /* Get trusted certificates from the system and add them to the path finding mission. */
2873 RTCRSTORE hTrustedCerts;
2874 vrc = RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(&hTrustedCerts,
2875 RTErrInfoInitStatic(&StaticErrInfo));
2876 if (RT_SUCCESS(vrc))
2877 {
2878 vrc = RTCrX509CertPathsSetTrustedStore(hCertPaths, hTrustedCerts);
2879 if (RT_FAILURE(vrc))
2880 hrc2 = setErrorBoth(E_FAIL, vrc, tr("RTCrX509CertPathsSetTrustedStore failed (%Rrc)"), vrc);
2881 RTCrStoreRelease(hTrustedCerts);
2882 }
2883 else
2884 hrc2 = setErrorBoth(E_FAIL, vrc,
2885 tr("Failed to query trusted CAs and Certificates from the system and for the current user (%Rrc, %s)"),
2886 vrc, StaticErrInfo.Core.pszMsg);
2887
2888 /* Add untrusted intermediate certificates. */
2889 if (RT_SUCCESS(vrc))
2890 {
2891 /// @todo RTCrX509CertPathsSetUntrustedStore(hCertPaths, hAdditionalCerts);
2892 /// By scanning for additional certificates in the .cert file? It would be
2893 /// convenient to be able to supply intermediate certificates for the user,
2894 /// right? Or would that be unacceptable as it may weaken security?
2895 ///
2896 /// Anyway, we should look for intermediate certificates on the system, at
2897 /// least.
2898 }
2899 if (RT_SUCCESS(vrc))
2900 {
2901 /*
2902 * Do the building and verification of certificate paths.
2903 */
2904 vrc = RTCrX509CertPathsBuild(hCertPaths, RTErrInfoInitStatic(&StaticErrInfo));
2905 if (RT_SUCCESS(vrc))
2906 {
2907 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(&StaticErrInfo));
2908 if (RT_SUCCESS(vrc))
2909 {
2910 /*
2911 * Mark the certificate as good.
2912 */
2913 /** @todo check the certificate purpose? If so, share with self-signed. */
2914 m->fCertificateValid = true;
2915 m->fCertificateMissingPath = false;
2916
2917 /*
2918 * We add a warning if the certificate path isn't valid at the current
2919 * time. Since the time is only considered during path validation and we
2920 * can repeat the validation process (but not building), it's easy to check.
2921 */
2922 RTTIMESPEC Now;
2923 vrc = RTCrX509CertPathsSetValidTimeSpec(hCertPaths, RTTimeNow(&Now));
2924 if (RT_SUCCESS(vrc))
2925 {
2926 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(&StaticErrInfo));
2927 if (RT_SUCCESS(vrc))
2928 m->fCertificateValidTime = true;
2929 else
2930 i_addWarning(tr("The certificate used to sign '%s' (or a certificate in the path) is not currently valid (%Rrc)"),
2931 pTask->locInfo.strPath.c_str(), vrc);
2932 }
2933 else
2934 hrc2 = setErrorVrc(vrc, "RTCrX509CertPathsSetValidTimeSpec failed: %Rrc", vrc);
2935 }
2936 else if (vrc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS)
2937 {
2938 m->fCertificateValid = true;
2939 i_addWarning(tr("No trusted certificate paths"));
2940
2941 /* Add another warning if the pathless certificate is not valid at present. */
2942 RTTIMESPEC Now;
2943 if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
2944 m->fCertificateValidTime = true;
2945 else
2946 i_addWarning(tr("The certificate used to sign '%s' is not currently valid"),
2947 pTask->locInfo.strPath.c_str());
2948 }
2949 else
2950 hrc2 = setErrorBoth(E_FAIL, vrc, tr("Certificate path validation failed (%Rrc, %s)"),
2951 vrc, StaticErrInfo.Core.pszMsg);
2952 }
2953 else
2954 hrc2 = setErrorBoth(E_FAIL, vrc, tr("Certificate path building failed (%Rrc, %s)"),
2955 vrc, StaticErrInfo.Core.pszMsg);
2956 }
2957 RTCrX509CertPathsRelease(hCertPaths);
2958 }
2959 else
2960 hrc2 = setErrorVrc(vrc, tr("RTCrX509CertPathsCreate failed: %Rrc"), vrc);
2961 }
2962
2963 /* Merge statuses from signature and certificate validation, prefering the signature one. */
2964 if (SUCCEEDED(hrc) && FAILED(hrc2))
2965 hrc = hrc2;
2966 if (FAILED(hrc))
2967 return hrc;
2968 }
2969
2970 /** @todo provide details about the signatory, signature, etc. */
2971 if (m->fSignerCertLoaded)
2972 {
2973 m->ptrCertificateInfo.createObject();
2974 m->ptrCertificateInfo->initCertificate(&m->SignerCert,
2975 m->fCertificateValid && !m->fCertificateMissingPath,
2976 !m->fCertificateValidTime);
2977 }
2978
2979 /*
2980 * If there is a manifest, check that the OVF digest matches up (if present).
2981 */
2982
2983 NOREF(pTask);
2984 return S_OK;
2985}
2986
2987
2988
2989/*******************************************************************************
2990 * Import stuff
2991 ******************************************************************************/
2992
2993/**
2994 * Implementation for importing OVF data into VirtualBox. This starts a new thread which will call
2995 * Appliance::taskThreadImportOrExport().
2996 *
2997 * This creates one or more new machines according to the VirtualSystemScription instances created by
2998 * Appliance::Interpret().
2999 *
3000 * This is in a separate private method because it is used from one location:
3001 *
3002 * 1) from the public Appliance::ImportMachines().
3003 *
3004 * @param locInfo
3005 * @param progress
3006 * @return
3007 */
3008HRESULT Appliance::i_importImpl(const LocationInfo &locInfo,
3009 ComObjPtr<Progress> &progress)
3010{
3011 HRESULT rc;
3012
3013 /* Initialize our worker task */
3014 ThreadTask *pTask;
3015 if (locInfo.storageType != VFSType_Cloud)
3016 {
3017 rc = i_setUpProgress(progress, Utf8StrFmt(tr("Importing appliance '%s'"), locInfo.strPath.c_str()),
3018 locInfo.storageType == VFSType_File ? ImportFile : ImportS3);
3019 if (FAILED(rc))
3020 return setError(rc, tr("Failed to create task for importing appliance into VirtualBox"));
3021 try
3022 {
3023 pTask = new TaskOVF(this, TaskOVF::Import, locInfo, progress);
3024 }
3025 catch (std::bad_alloc &)
3026 {
3027 return E_OUTOFMEMORY;
3028 }
3029 }
3030 else
3031 {
3032 if (locInfo.strProvider.equals("OCI"))
3033 {
3034 /*
3035 * 1. Create a custom image from the instance:
3036 * - 2 operations (starting and waiting)
3037 * 2. Import the custom image into the Object Storage (OCI format - TAR file with QCOW2 image and JSON file):
3038 * - 2 operations (starting and waiting)
3039 * 3. Download the object from the Object Storage:
3040 * - 1 operation (starting and downloadind is one operation)
3041 * 4. Open the object, extract an image and convert one to VDI:
3042 * - 1 operation (extracting and conversion are piped) because only 1 base bootable image is imported for now
3043 * 5. Create VM with user settings and attach the converted image to VM:
3044 * - 1 operation.
3045 * 6. Cleanup phase.
3046 * - 1 to N operations.
3047 * The number of the correct Progress operations are much tricky here.
3048 * Whether Machine::deleteConfig() is called or Medium::deleteStorage() is called in the loop.
3049 * Both require a new Progress object. To work with these functions the original Progress object uses
3050 * the function Progress::waitForOtherProgressCompletion().
3051 *
3052 * Some speculation here...
3053 * Total: 2+2+1(cloud) + 1+1(local) + 1+1+1(cleanup) = 10 operations
3054 * or
3055 * Total: 2+2+1(cloud) + 1+1(local) + 1(cleanup) = 8 operations
3056 * if VM wasn't created we would have only 1 registered image for cleanup.
3057 *
3058 * Weight "#define"s for the Cloud operations are located in the file OCICloudClient.h.
3059 * Weight of cloud import operations (1-3 items from above):
3060 * Total = 750 = 25+75(start and wait)+25+375(start and wait)+250(download)
3061 *
3062 * Weight of local import operations (4-5 items from above):
3063 * Total = 150 = 100 (extract and convert) + 50 (create VM, attach disks)
3064 *
3065 * Weight of local cleanup operations (6 item from above):
3066 * Some speculation here...
3067 * Total = 3 = 1 (1 image) + 1 (1 setting file)+ 1 (1 prev setting file) - quick operations
3068 * or
3069 * Total = 1 (1 image) if VM wasn't created we would have only 1 registered image for now.
3070 */
3071 try
3072 {
3073 rc = progress.createObject();
3074 if (SUCCEEDED(rc))
3075 rc = progress->init(mVirtualBox, static_cast<IAppliance *>(this),
3076 Utf8Str(tr("Importing VM from Cloud...")),
3077 TRUE /* aCancelable */,
3078 10, // ULONG cOperations,
3079 1000, // ULONG ulTotalOperationsWeight,
3080 Utf8Str(tr("Start import VM from the Cloud...")), // aFirstOperationDescription
3081 25); // ULONG ulFirstOperationWeight
3082 if (SUCCEEDED(rc))
3083 pTask = new TaskCloud(this, TaskCloud::Import, locInfo, progress);
3084 else
3085 pTask = NULL; /* shut up vcc */
3086 }
3087 catch (std::bad_alloc &)
3088 {
3089 return E_OUTOFMEMORY;
3090 }
3091 if (FAILED(rc))
3092 return setError(rc, tr("Failed to create task for importing appliance into VirtualBox"));
3093 }
3094 else
3095 return setError(E_NOTIMPL, tr("Only \"OCI\" cloud provider is supported for now. \"%s\" isn't supported."),
3096 locInfo.strProvider.c_str());
3097 }
3098
3099 /*
3100 * Start the task thread.
3101 */
3102 rc = pTask->createThread();
3103 pTask = NULL;
3104 if (SUCCEEDED(rc))
3105 return rc;
3106 return setError(rc, tr("Failed to start thread for importing appliance into VirtualBox"));
3107}
3108
3109/**
3110 * Actual worker code for importing OVF data into VirtualBox.
3111 *
3112 * This is called from Appliance::taskThreadImportOrExport() and therefore runs
3113 * on the OVF import worker thread. This creates one or more new machines
3114 * according to the VirtualSystemScription instances created by
3115 * Appliance::Interpret().
3116 *
3117 * This runs in two contexts:
3118 *
3119 * 1) in a first worker thread; in that case, Appliance::ImportMachines() called
3120 * Appliance::i_importImpl();
3121 *
3122 * 2) in a second worker thread; in that case, Appliance::ImportMachines()
3123 * called Appliance::i_importImpl(), which called Appliance::i_importFSOVA(),
3124 * which called Appliance::i_importImpl(), which then called this again.
3125 *
3126 * @param pTask The OVF task data.
3127 * @return COM status code.
3128 */
3129HRESULT Appliance::i_importFS(TaskOVF *pTask)
3130{
3131 LogFlowFuncEnter();
3132 LogFlowFunc(("Appliance %p\n", this));
3133
3134 /* Change the appliance state so we can safely leave the lock while doing
3135 * time-consuming image imports; also the below method calls do all kinds of
3136 * locking which conflicts with the appliance object lock. */
3137 AutoWriteLock writeLock(this COMMA_LOCKVAL_SRC_POS);
3138 /* Check if the appliance is currently busy. */
3139 if (!i_isApplianceIdle())
3140 return E_ACCESSDENIED;
3141 /* Set the internal state to importing. */
3142 m->state = ApplianceImporting;
3143
3144 HRESULT rc = S_OK;
3145
3146 /* Clear the list of imported machines, if any */
3147 m->llGuidsMachinesCreated.clear();
3148
3149 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
3150 rc = i_importFSOVF(pTask, writeLock);
3151 else
3152 rc = i_importFSOVA(pTask, writeLock);
3153 if (FAILED(rc))
3154 {
3155 /* With _whatever_ error we've had, do a complete roll-back of
3156 * machines and images we've created */
3157 writeLock.release();
3158 ErrorInfoKeeper eik;
3159 for (list<Guid>::iterator itID = m->llGuidsMachinesCreated.begin();
3160 itID != m->llGuidsMachinesCreated.end();
3161 ++itID)
3162 {
3163 Guid guid = *itID;
3164 Bstr bstrGuid = guid.toUtf16();
3165 ComPtr<IMachine> failedMachine;
3166 HRESULT rc2 = mVirtualBox->FindMachine(bstrGuid.raw(), failedMachine.asOutParam());
3167 if (SUCCEEDED(rc2))
3168 {
3169 SafeIfaceArray<IMedium> aMedia;
3170 rc2 = failedMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
3171 ComPtr<IProgress> pProgress2;
3172 rc2 = failedMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress2.asOutParam());
3173 pProgress2->WaitForCompletion(-1);
3174 }
3175 }
3176 writeLock.acquire();
3177 }
3178
3179 /* Reset the state so others can call methods again */
3180 m->state = ApplianceIdle;
3181
3182 LogFlowFunc(("rc=%Rhrc\n", rc));
3183 LogFlowFuncLeave();
3184 return rc;
3185}
3186
3187HRESULT Appliance::i_importFSOVF(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
3188{
3189 return i_importDoIt(pTask, rWriteLock);
3190}
3191
3192HRESULT Appliance::i_importFSOVA(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
3193{
3194 LogFlowFuncEnter();
3195
3196 /*
3197 * Open the tar file as file stream.
3198 */
3199 RTVFSIOSTREAM hVfsIosOva;
3200 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
3201 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
3202 if (RT_FAILURE(vrc))
3203 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
3204
3205 RTVFSFSSTREAM hVfsFssOva;
3206 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
3207 RTVfsIoStrmRelease(hVfsIosOva);
3208 if (RT_FAILURE(vrc))
3209 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
3210
3211 /*
3212 * Join paths with the i_importFSOVF code.
3213 *
3214 * Note! We don't need to skip the OVF, manifest or signature files, as the
3215 * i_importMachineGeneric, i_importVBoxMachine and i_importOpenSourceFile
3216 * code will deal with this (as there could be other files in the OVA
3217 * that we don't process, like 'de-DE-resources.xml' in EXAMPLE 1,
3218 * Appendix D.1, OVF v2.1.0).
3219 */
3220 HRESULT hrc = i_importDoIt(pTask, rWriteLock, hVfsFssOva);
3221
3222 RTVfsFsStrmRelease(hVfsFssOva);
3223
3224 LogFlowFunc(("returns %Rhrc\n", hrc));
3225 return hrc;
3226}
3227
3228/**
3229 * Does the actual importing after the caller has made the source accessible.
3230 *
3231 * @param pTask The import task.
3232 * @param rWriteLock The write lock the caller's caller is holding,
3233 * will be released for some reason.
3234 * @param hVfsFssOva The file system stream if OVA, NIL if not.
3235 * @returns COM status code.
3236 * @throws Nothing.
3237 */
3238HRESULT Appliance::i_importDoIt(TaskOVF *pTask, AutoWriteLockBase &rWriteLock, RTVFSFSSTREAM hVfsFssOva /*= NIL_RTVFSFSSTREAM*/)
3239{
3240 rWriteLock.release();
3241
3242 HRESULT hrc = E_FAIL;
3243 try
3244 {
3245 /*
3246 * Create the import stack for the rollback on errors.
3247 */
3248 ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, hVfsFssOva);
3249
3250 try
3251 {
3252 /* Do the importing. */
3253 i_importMachines(stack);
3254
3255 /* We should've processed all the files now, so compare. */
3256 hrc = i_verifyManifestFile(stack);
3257
3258 /* If everything was successful so far check if some extension
3259 * pack wants to do file sanity checking. */
3260 if (SUCCEEDED(hrc))
3261 {
3262 /** @todo */;
3263 }
3264 }
3265 catch (HRESULT hrcXcpt)
3266 {
3267 hrc = hrcXcpt;
3268 }
3269 catch (...)
3270 {
3271 AssertFailed();
3272 hrc = E_FAIL;
3273 }
3274 if (FAILED(hrc))
3275 {
3276 /*
3277 * Restoring original UUID from OVF description file.
3278 * During import VBox creates new UUIDs for imported images and
3279 * assigns them to the images. In case of failure we have to restore
3280 * the original UUIDs because those new UUIDs are obsolete now and
3281 * won't be used anymore.
3282 */
3283 ErrorInfoKeeper eik; /* paranoia */
3284 list< ComObjPtr<VirtualSystemDescription> >::const_iterator itvsd;
3285 /* Iterate through all virtual systems of that appliance */
3286 for (itvsd = m->virtualSystemDescriptions.begin();
3287 itvsd != m->virtualSystemDescriptions.end();
3288 ++itvsd)
3289 {
3290 ComObjPtr<VirtualSystemDescription> vsdescThis = (*itvsd);
3291 settings::MachineConfigFile *pConfig = vsdescThis->m->pConfig;
3292 if(vsdescThis->m->pConfig!=NULL)
3293 stack.restoreOriginalUUIDOfAttachedDevice(pConfig);
3294 }
3295 }
3296 }
3297 catch (...)
3298 {
3299 hrc = E_FAIL;
3300 AssertFailed();
3301 }
3302
3303 rWriteLock.acquire();
3304 return hrc;
3305}
3306
3307/**
3308 * Undocumented, you figure it from the name.
3309 *
3310 * @returns Undocumented
3311 * @param stack Undocumented.
3312 */
3313HRESULT Appliance::i_verifyManifestFile(ImportStack &stack)
3314{
3315 LogFlowThisFuncEnter();
3316 HRESULT hrc;
3317 int vrc;
3318
3319 /*
3320 * No manifest is fine, it always matches.
3321 */
3322 if (m->hTheirManifest == NIL_RTMANIFEST)
3323 hrc = S_OK;
3324 else
3325 {
3326 /*
3327 * Hack: If the manifest we just read doesn't have a digest for the OVF, copy
3328 * it from the manifest we got from the caller.
3329 * @bugref{6022#c119}
3330 */
3331 if ( !RTManifestEntryExists(m->hTheirManifest, m->strOvfManifestEntry.c_str())
3332 && RTManifestEntryExists(m->hOurManifest, m->strOvfManifestEntry.c_str()) )
3333 {
3334 uint32_t fType = 0;
3335 char szDigest[512 + 1];
3336 vrc = RTManifestEntryQueryAttr(m->hOurManifest, m->strOvfManifestEntry.c_str(), NULL, RTMANIFEST_ATTR_ANY,
3337 szDigest, sizeof(szDigest), &fType);
3338 if (RT_SUCCESS(vrc))
3339 vrc = RTManifestEntrySetAttr(m->hTheirManifest, m->strOvfManifestEntry.c_str(),
3340 NULL /*pszAttr*/, szDigest, fType);
3341 if (RT_FAILURE(vrc))
3342 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Error fudging missing OVF digest in manifest: %Rrc"), vrc);
3343 }
3344
3345 /*
3346 * Compare with the digests we've created while read/processing the import.
3347 *
3348 * We specify the RTMANIFEST_EQUALS_IGN_MISSING_ATTRS to ignore attributes
3349 * (SHA1, SHA256, etc) that are only present in one of the manifests, as long
3350 * as each entry has at least one common attribute that we can check. This
3351 * is important for the OVF in OVAs, for which we generates several digests
3352 * since we don't know which are actually used in the manifest (OVF comes
3353 * first in an OVA, then manifest).
3354 */
3355 char szErr[256];
3356 vrc = RTManifestEqualsEx(m->hTheirManifest, m->hOurManifest, NULL /*papszIgnoreEntries*/,
3357 NULL /*papszIgnoreAttrs*/,
3358 RTMANIFEST_EQUALS_IGN_MISSING_ATTRS | RTMANIFEST_EQUALS_IGN_MISSING_ENTRIES_2ND,
3359 szErr, sizeof(szErr));
3360 if (RT_SUCCESS(vrc))
3361 hrc = S_OK;
3362 else
3363 hrc = setErrorVrc(vrc, tr("Digest mismatch (%Rrc): %s"), vrc, szErr);
3364 }
3365
3366 NOREF(stack);
3367 LogFlowThisFunc(("returns %Rhrc\n", hrc));
3368 return hrc;
3369}
3370
3371/**
3372 * Helper that converts VirtualSystem attachment values into VirtualBox attachment values.
3373 * Throws HRESULT values on errors!
3374 *
3375 * @param hdc in: the HardDiskController structure to attach to.
3376 * @param ulAddressOnParent in: the AddressOnParent parameter from OVF.
3377 * @param controllerName out: the name of the storage controller to attach to (e.g. "IDE").
3378 * @param lControllerPort out: the channel (controller port) of the controller to attach to.
3379 * @param lDevice out: the device number to attach to.
3380 */
3381void Appliance::i_convertDiskAttachmentValues(const ovf::HardDiskController &hdc,
3382 uint32_t ulAddressOnParent,
3383 Utf8Str &controllerName,
3384 int32_t &lControllerPort,
3385 int32_t &lDevice)
3386{
3387 Log(("Appliance::i_convertDiskAttachmentValues: hdc.system=%d, hdc.fPrimary=%d, ulAddressOnParent=%d\n",
3388 hdc.system,
3389 hdc.fPrimary,
3390 ulAddressOnParent));
3391
3392 switch (hdc.system)
3393 {
3394 case ovf::HardDiskController::IDE:
3395 // For the IDE bus, the port parameter can be either 0 or 1, to specify the primary
3396 // or secondary IDE controller, respectively. For the primary controller of the IDE bus,
3397 // the device number can be either 0 or 1, to specify the master or the slave device,
3398 // respectively. For the secondary IDE controller, the device number is always 1 because
3399 // the master device is reserved for the CD-ROM drive.
3400 controllerName = "IDE";
3401 switch (ulAddressOnParent)
3402 {
3403 case 0: // master
3404 if (!hdc.fPrimary)
3405 {
3406 // secondary master
3407 lControllerPort = (long)1;
3408 lDevice = (long)0;
3409 }
3410 else // primary master
3411 {
3412 lControllerPort = (long)0;
3413 lDevice = (long)0;
3414 }
3415 break;
3416
3417 case 1: // slave
3418 if (!hdc.fPrimary)
3419 {
3420 // secondary slave
3421 lControllerPort = (long)1;
3422 lDevice = (long)1;
3423 }
3424 else // primary slave
3425 {
3426 lControllerPort = (long)0;
3427 lDevice = (long)1;
3428 }
3429 break;
3430
3431 // used by older VBox exports
3432 case 2: // interpret this as secondary master
3433 lControllerPort = (long)1;
3434 lDevice = (long)0;
3435 break;
3436
3437 // used by older VBox exports
3438 case 3: // interpret this as secondary slave
3439 lControllerPort = (long)1;
3440 lDevice = (long)1;
3441 break;
3442
3443 default:
3444 throw setError(VBOX_E_NOT_SUPPORTED,
3445 tr("Invalid channel %RI16 specified; IDE controllers support only 0, 1 or 2"),
3446 ulAddressOnParent);
3447 break;
3448 }
3449 break;
3450
3451 case ovf::HardDiskController::SATA:
3452 controllerName = "SATA";
3453 lControllerPort = (long)ulAddressOnParent;
3454 lDevice = (long)0;
3455 break;
3456
3457 case ovf::HardDiskController::SCSI:
3458 {
3459 if(hdc.strControllerType.compare("lsilogicsas")==0)
3460 controllerName = "SAS";
3461 else
3462 controllerName = "SCSI";
3463 lControllerPort = (long)ulAddressOnParent;
3464 lDevice = (long)0;
3465 break;
3466 }
3467
3468 default: break;
3469 }
3470
3471 Log(("=> lControllerPort=%d, lDevice=%d\n", lControllerPort, lDevice));
3472}
3473
3474/**
3475 * Imports one image.
3476 *
3477 * This is common code shared between
3478 * -- i_importMachineGeneric() for the OVF case; in that case the information comes from
3479 * the OVF virtual systems;
3480 * -- i_importVBoxMachine(); in that case, the information comes from the <vbox:Machine>
3481 * tag.
3482 *
3483 * Both ways of describing machines use the OVF disk references section, so in both cases
3484 * the caller needs to pass in the ovf::DiskImage structure from ovfreader.cpp.
3485 *
3486 * As a result, in both cases, if di.strHref is empty, we create a new image as per the OVF
3487 * spec, even though this cannot really happen in the vbox:Machine case since such data
3488 * would never have been exported.
3489 *
3490 * This advances stack.pProgress by one operation with the image's weight.
3491 *
3492 * @param di ovfreader.cpp structure describing the image from the OVF that is to be imported
3493 * @param strDstPath Where to create the target image.
3494 * @param pTargetMedium out: The newly created target medium. This also gets pushed on stack.llHardDisksCreated for cleanup.
3495 * @param stack
3496 *
3497 * @throws HRESULT
3498 */
3499void Appliance::i_importOneDiskImage(const ovf::DiskImage &di,
3500 const Utf8Str &strDstPath,
3501 ComObjPtr<Medium> &pTargetMedium,
3502 ImportStack &stack)
3503{
3504 HRESULT rc;
3505
3506 Utf8Str strAbsDstPath;
3507 int vrc = RTPathAbsExCxx(strAbsDstPath, stack.strMachineFolder, strDstPath);
3508 AssertRCStmt(vrc, throw Global::vboxStatusCodeToCOM(vrc));
3509
3510 /* Get the system properties. */
3511 SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
3512
3513 /* Keep the source file ref handy for later. */
3514 const Utf8Str &strSourceOVF = di.strHref;
3515
3516 /* Construct source file path */
3517 Utf8Str strSrcFilePath;
3518 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
3519 strSrcFilePath = strSourceOVF;
3520 else
3521 {
3522 strSrcFilePath = stack.strSourceDir;
3523 strSrcFilePath.append(RTPATH_SLASH_STR);
3524 strSrcFilePath.append(strSourceOVF);
3525 }
3526
3527 /* First of all check if the original (non-absolute) destination path is
3528 * a valid medium UUID. If so, the user wants to import the image into
3529 * an existing path. This is useful for iSCSI for example. */
3530 /** @todo r=klaus the code structure after this point is totally wrong,
3531 * full of unnecessary code duplication and other issues. 4.2 still had
3532 * the right structure for importing into existing medium objects, which
3533 * the current code can't possibly handle. */
3534 RTUUID uuid;
3535 vrc = RTUuidFromStr(&uuid, strDstPath.c_str());
3536 if (vrc == VINF_SUCCESS)
3537 {
3538 rc = mVirtualBox->i_findHardDiskById(Guid(uuid), true, &pTargetMedium);
3539 if (FAILED(rc)) throw rc;
3540 }
3541 else
3542 {
3543 RTVFSIOSTREAM hVfsIosSrc = NIL_RTVFSIOSTREAM;
3544
3545 /* check read file to GZIP compression */
3546 bool const fGzipped = di.strCompression.compare("gzip", Utf8Str::CaseInsensitive) == 0;
3547 Utf8Str strDeleteTemp;
3548 try
3549 {
3550 Utf8Str strTrgFormat = "VMDK";
3551 ComObjPtr<MediumFormat> trgFormat;
3552 Bstr bstrFormatName;
3553 ULONG lCabs = 0;
3554
3555 char *pszSuff = RTPathSuffix(strAbsDstPath.c_str());
3556 if (pszSuff != NULL)
3557 {
3558 /*
3559 * Figure out which format the user like to have. Default is VMDK
3560 * or it can be VDI if according command-line option is set
3561 */
3562
3563 /*
3564 * We need a proper target format
3565 * if target format has been changed by user via GUI import wizard
3566 * or via VBoxManage import command (option --importtovdi)
3567 * then we need properly process such format like ISO
3568 * Because there is no conversion ISO to VDI
3569 */
3570 trgFormat = pSysProps->i_mediumFormatFromExtension(++pszSuff);
3571 if (trgFormat.isNull())
3572 throw setError(E_FAIL, tr("Unsupported medium format for disk image '%s'"), di.strHref.c_str());
3573
3574 rc = trgFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
3575 if (FAILED(rc)) throw rc;
3576
3577 strTrgFormat = Utf8Str(bstrFormatName);
3578
3579 if ( m->optListImport.contains(ImportOptions_ImportToVDI)
3580 && strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) != 0)
3581 {
3582 /* change the target extension */
3583 strTrgFormat = "vdi";
3584 trgFormat = pSysProps->i_mediumFormatFromExtension(strTrgFormat);
3585 strAbsDstPath.stripSuffix();
3586 strAbsDstPath.append(".");
3587 strAbsDstPath.append(strTrgFormat.c_str());
3588 }
3589
3590 /* Check the capabilities. We need create capabilities. */
3591 lCabs = 0;
3592 com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
3593 rc = trgFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
3594
3595 if (FAILED(rc))
3596 throw rc;
3597
3598 for (ULONG j = 0; j < mediumFormatCap.size(); j++)
3599 lCabs |= mediumFormatCap[j];
3600
3601 if ( !(lCabs & MediumFormatCapabilities_CreateFixed)
3602 && !(lCabs & MediumFormatCapabilities_CreateDynamic) )
3603 throw setError(VBOX_E_NOT_SUPPORTED,
3604 tr("Could not find a valid medium format for the target disk '%s'"),
3605 strAbsDstPath.c_str());
3606 }
3607 else
3608 {
3609 throw setError(VBOX_E_FILE_ERROR,
3610 tr("The target disk '%s' has no extension "),
3611 strAbsDstPath.c_str(), VERR_INVALID_NAME);
3612 }
3613
3614 /*CD/DVD case*/
3615 if (strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) == 0)
3616 {
3617 try
3618 {
3619 if (fGzipped)
3620 i_importDecompressFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
3621 else
3622 i_importCopyFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
3623
3624 ComPtr<IMedium> pTmp;
3625 rc = mVirtualBox->OpenMedium(Bstr(strAbsDstPath).raw(),
3626 DeviceType_DVD,
3627 AccessMode_ReadWrite,
3628 false,
3629 pTmp.asOutParam());
3630 if (FAILED(rc))
3631 throw rc;
3632
3633 IMedium *iM = pTmp;
3634 pTargetMedium = static_cast<Medium*>(iM);
3635 }
3636 catch (HRESULT /*arc*/)
3637 {
3638 throw;
3639 }
3640
3641 /* Advance to the next operation. */
3642 /* operation's weight, as set up with the IProgress originally */
3643 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
3644 RTPathFilename(strSourceOVF.c_str())).raw(),
3645 di.ulSuggestedSizeMB);
3646 }
3647 else/* HDD case*/
3648 {
3649 /* Create an IMedium object. */
3650 pTargetMedium.createObject();
3651
3652 rc = pTargetMedium->init(mVirtualBox,
3653 strTrgFormat,
3654 strAbsDstPath,
3655 Guid::Empty /* media registry: none yet */,
3656 DeviceType_HardDisk);
3657 if (FAILED(rc)) throw rc;
3658
3659 ComPtr<IProgress> pProgressImport;
3660 /* If strHref is empty we have to create a new file. */
3661 if (strSourceOVF.isEmpty())
3662 {
3663 com::SafeArray<MediumVariant_T> mediumVariant;
3664 mediumVariant.push_back(MediumVariant_Standard);
3665
3666 /* Kick off the creation of a dynamic growing disk image with the given capacity. */
3667 rc = pTargetMedium->CreateBaseStorage(di.iCapacity / _1M,
3668 ComSafeArrayAsInParam(mediumVariant),
3669 pProgressImport.asOutParam());
3670 if (FAILED(rc)) throw rc;
3671
3672 /* Advance to the next operation. */
3673 /* operation's weight, as set up with the IProgress originally */
3674 stack.pProgress->SetNextOperation(BstrFmt(tr("Creating disk image '%s'"),
3675 strAbsDstPath.c_str()).raw(),
3676 di.ulSuggestedSizeMB);
3677 }
3678 else
3679 {
3680 /* We need a proper source format description */
3681 /* Which format to use? */
3682 ComObjPtr<MediumFormat> srcFormat;
3683 rc = i_findMediumFormatFromDiskImage(di, srcFormat);
3684 if (FAILED(rc))
3685 throw setError(VBOX_E_NOT_SUPPORTED,
3686 tr("Could not find a valid medium format for the source disk '%s' "
3687 "Check correctness of the image format URL in the OVF description file "
3688 "or extension of the image"),
3689 RTPathFilename(strSourceOVF.c_str()));
3690
3691 /* If gzipped, decompress the GZIP file and save a new file in the target path */
3692 if (fGzipped)
3693 {
3694 Utf8Str strTargetFilePath(strAbsDstPath);
3695 strTargetFilePath.stripFilename();
3696 strTargetFilePath.append(RTPATH_SLASH_STR);
3697 strTargetFilePath.append("temp_");
3698 strTargetFilePath.append(RTPathFilename(strSrcFilePath.c_str()));
3699 strDeleteTemp = strTargetFilePath;
3700
3701 i_importDecompressFile(stack, strSrcFilePath, strTargetFilePath, strSourceOVF.c_str());
3702
3703 /* Correct the source and the target with the actual values */
3704 strSrcFilePath = strTargetFilePath;
3705
3706 /* Open the new source file. */
3707 vrc = RTVfsIoStrmOpenNormal(strSrcFilePath.c_str(), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
3708 &hVfsIosSrc);
3709 if (RT_FAILURE(vrc))
3710 throw setErrorVrc(vrc, tr("Error opening decompressed image file '%s' (%Rrc)"),
3711 strSrcFilePath.c_str(), vrc);
3712 }
3713 else
3714 hVfsIosSrc = i_importOpenSourceFile(stack, strSrcFilePath, strSourceOVF.c_str());
3715
3716 /* Add a read ahead thread to try speed things up with concurrent reads and
3717 writes going on in different threads. */
3718 RTVFSIOSTREAM hVfsIosReadAhead;
3719 vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/,
3720 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
3721 RTVfsIoStrmRelease(hVfsIosSrc);
3722 if (RT_FAILURE(vrc))
3723 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"),
3724 strSrcFilePath.c_str(), vrc);
3725
3726 /* Start the source image cloning operation. */
3727 ComObjPtr<Medium> nullParent;
3728 ComObjPtr<Progress> pProgressImportTmp;
3729 rc = pProgressImportTmp.createObject();
3730 if (FAILED(rc)) throw rc;
3731 rc = pProgressImportTmp->init(mVirtualBox,
3732 static_cast<IAppliance*>(this),
3733 Utf8StrFmt(tr("Importing medium '%s'"),
3734 strAbsDstPath.c_str()),
3735 TRUE);
3736 if (FAILED(rc)) throw rc;
3737 pProgressImportTmp.queryInterfaceTo(pProgressImport.asOutParam());
3738 /* pProgressImportTmp is in parameter for Medium::i_importFile,
3739 * which is somewhat unusual and might be changed later. */
3740 rc = pTargetMedium->i_importFile(strSrcFilePath.c_str(),
3741 srcFormat,
3742 MediumVariant_Standard,
3743 hVfsIosReadAhead,
3744 nullParent,
3745 pProgressImportTmp,
3746 true /* aNotify */);
3747 RTVfsIoStrmRelease(hVfsIosReadAhead);
3748 hVfsIosSrc = NIL_RTVFSIOSTREAM;
3749 if (FAILED(rc))
3750 throw rc;
3751
3752 /* Advance to the next operation. */
3753 /* operation's weight, as set up with the IProgress originally */
3754 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
3755 RTPathFilename(strSourceOVF.c_str())).raw(),
3756 di.ulSuggestedSizeMB);
3757 }
3758
3759 /* Now wait for the background import operation to complete; this throws
3760 * HRESULTs on error. */
3761 stack.pProgress->WaitForOtherProgressCompletion(pProgressImport, 0 /* indefinite wait */);
3762
3763 /* The creating/importing has placed the medium in the global
3764 * media registry since the VM isn't created yet. Remove it
3765 * again to let it added to the right registry when the VM
3766 * has been created below. */
3767 pTargetMedium->i_removeRegistry(mVirtualBox->i_getGlobalRegistryId());
3768 }
3769 }
3770 catch (...)
3771 {
3772 if (strDeleteTemp.isNotEmpty())
3773 RTFileDelete(strDeleteTemp.c_str());
3774 throw;
3775 }
3776
3777 /* Make sure the source file is closed. */
3778 if (hVfsIosSrc != NIL_RTVFSIOSTREAM)
3779 RTVfsIoStrmRelease(hVfsIosSrc);
3780
3781 /*
3782 * Delete the temp gunzip result, if any.
3783 */
3784 if (strDeleteTemp.isNotEmpty())
3785 {
3786 vrc = RTFileDelete(strSrcFilePath.c_str());
3787 if (RT_FAILURE(vrc))
3788 setWarning(VBOX_E_FILE_ERROR,
3789 tr("Failed to delete the temporary file '%s' (%Rrc)"), strSrcFilePath.c_str(), vrc);
3790 }
3791 }
3792}
3793
3794/**
3795 * Imports one OVF virtual system (described by the given ovf::VirtualSystem and VirtualSystemDescription)
3796 * into VirtualBox by creating an IMachine instance, which is returned.
3797 *
3798 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
3799 * up any leftovers from this function. For this, the given ImportStack instance has received information
3800 * about what needs cleaning up (to support rollback).
3801 *
3802 * @param vsysThis OVF virtual system (machine) to import.
3803 * @param vsdescThis Matching virtual system description (machine) to import.
3804 * @param pNewMachine out: Newly created machine.
3805 * @param stack Cleanup stack for when this throws.
3806 */
3807void Appliance::i_importMachineGeneric(const ovf::VirtualSystem &vsysThis,
3808 ComObjPtr<VirtualSystemDescription> &vsdescThis,
3809 ComPtr<IMachine> &pNewMachine,
3810 ImportStack &stack)
3811{
3812 LogFlowFuncEnter();
3813 HRESULT rc;
3814
3815 // Get the instance of IGuestOSType which matches our string guest OS type so we
3816 // can use recommended defaults for the new machine where OVF doesn't provide any
3817 ComPtr<IGuestOSType> osType;
3818 rc = mVirtualBox->GetGuestOSType(Bstr(stack.strOsTypeVBox).raw(), osType.asOutParam());
3819 if (FAILED(rc)) throw rc;
3820
3821 /* Create the machine */
3822 SafeArray<BSTR> groups; /* no groups, or maybe one group... */
3823 if (!stack.strPrimaryGroup.isEmpty() && stack.strPrimaryGroup != "/")
3824 Bstr(stack.strPrimaryGroup).detachTo(groups.appendedRaw());
3825 rc = mVirtualBox->CreateMachine(Bstr(stack.strSettingsFilename).raw(),
3826 Bstr(stack.strNameVBox).raw(),
3827 ComSafeArrayAsInParam(groups),
3828 Bstr(stack.strOsTypeVBox).raw(),
3829 NULL, /* aCreateFlags */
3830 pNewMachine.asOutParam());
3831 if (FAILED(rc)) throw rc;
3832
3833 // set the description
3834 if (!stack.strDescription.isEmpty())
3835 {
3836 rc = pNewMachine->COMSETTER(Description)(Bstr(stack.strDescription).raw());
3837 if (FAILED(rc)) throw rc;
3838 }
3839
3840 // CPU count
3841 rc = pNewMachine->COMSETTER(CPUCount)(stack.cCPUs);
3842 if (FAILED(rc)) throw rc;
3843
3844 if (stack.fForceHWVirt)
3845 {
3846 rc = pNewMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, TRUE);
3847 if (FAILED(rc)) throw rc;
3848 }
3849
3850 // RAM
3851 rc = pNewMachine->COMSETTER(MemorySize)(stack.ulMemorySizeMB);
3852 if (FAILED(rc)) throw rc;
3853
3854 /* VRAM */
3855 /* Get the recommended VRAM for this guest OS type */
3856 ULONG vramVBox;
3857 rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox);
3858 if (FAILED(rc)) throw rc;
3859
3860 /* Set the VRAM */
3861 rc = pNewMachine->COMSETTER(VRAMSize)(vramVBox);
3862 if (FAILED(rc)) throw rc;
3863
3864 // I/O APIC: Generic OVF has no setting for this. Enable it if we
3865 // import a Windows VM because if if Windows was installed without IOAPIC,
3866 // it will not mind finding an one later on, but if Windows was installed
3867 // _with_ an IOAPIC, it will bluescreen if it's not found
3868 if (!stack.fForceIOAPIC)
3869 {
3870 Bstr bstrFamilyId;
3871 rc = osType->COMGETTER(FamilyId)(bstrFamilyId.asOutParam());
3872 if (FAILED(rc)) throw rc;
3873 if (bstrFamilyId == "Windows")
3874 stack.fForceIOAPIC = true;
3875 }
3876
3877 if (stack.fForceIOAPIC)
3878 {
3879 ComPtr<IBIOSSettings> pBIOSSettings;
3880 rc = pNewMachine->COMGETTER(BIOSSettings)(pBIOSSettings.asOutParam());
3881 if (FAILED(rc)) throw rc;
3882
3883 rc = pBIOSSettings->COMSETTER(IOAPICEnabled)(TRUE);
3884 if (FAILED(rc)) throw rc;
3885 }
3886
3887 if (!stack.strAudioAdapter.isEmpty())
3888 if (stack.strAudioAdapter.compare("null", Utf8Str::CaseInsensitive) != 0)
3889 {
3890 uint32_t audio = RTStrToUInt32(stack.strAudioAdapter.c_str()); // should be 0 for AC97
3891 ComPtr<IAudioAdapter> audioAdapter;
3892 rc = pNewMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam());
3893 if (FAILED(rc)) throw rc;
3894 rc = audioAdapter->COMSETTER(Enabled)(true);
3895 if (FAILED(rc)) throw rc;
3896 rc = audioAdapter->COMSETTER(AudioController)(static_cast<AudioControllerType_T>(audio));
3897 if (FAILED(rc)) throw rc;
3898 }
3899
3900#ifdef VBOX_WITH_USB
3901 /* USB Controller */
3902 if (stack.fUSBEnabled)
3903 {
3904 ComPtr<IUSBController> usbController;
3905 rc = pNewMachine->AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, usbController.asOutParam());
3906 if (FAILED(rc)) throw rc;
3907 }
3908#endif /* VBOX_WITH_USB */
3909
3910 /* Change the network adapters */
3911 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
3912
3913 std::list<VirtualSystemDescriptionEntry*> vsdeNW = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
3914 if (vsdeNW.empty())
3915 {
3916 /* No network adapters, so we have to disable our default one */
3917 ComPtr<INetworkAdapter> nwVBox;
3918 rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam());
3919 if (FAILED(rc)) throw rc;
3920 rc = nwVBox->COMSETTER(Enabled)(false);
3921 if (FAILED(rc)) throw rc;
3922 }
3923 else if (vsdeNW.size() > maxNetworkAdapters)
3924 throw setError(VBOX_E_FILE_ERROR,
3925 tr("Too many network adapters: OVF requests %d network adapters, "
3926 "but VirtualBox only supports %d"),
3927 vsdeNW.size(), maxNetworkAdapters);
3928 else
3929 {
3930 list<VirtualSystemDescriptionEntry*>::const_iterator nwIt;
3931 size_t a = 0;
3932 for (nwIt = vsdeNW.begin();
3933 nwIt != vsdeNW.end();
3934 ++nwIt, ++a)
3935 {
3936 const VirtualSystemDescriptionEntry* pvsys = *nwIt;
3937
3938 const Utf8Str &nwTypeVBox = pvsys->strVBoxCurrent;
3939 uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str());
3940 ComPtr<INetworkAdapter> pNetworkAdapter;
3941 rc = pNewMachine->GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
3942 if (FAILED(rc)) throw rc;
3943 /* Enable the network card & set the adapter type */
3944 rc = pNetworkAdapter->COMSETTER(Enabled)(true);
3945 if (FAILED(rc)) throw rc;
3946 rc = pNetworkAdapter->COMSETTER(AdapterType)(static_cast<NetworkAdapterType_T>(tt1));
3947 if (FAILED(rc)) throw rc;
3948
3949 // default is NAT; change to "bridged" if extra conf says so
3950 if (pvsys->strExtraConfigCurrent.endsWith("type=Bridged", Utf8Str::CaseInsensitive))
3951 {
3952 /* Attach to the right interface */
3953 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged);
3954 if (FAILED(rc)) throw rc;
3955 ComPtr<IHost> host;
3956 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
3957 if (FAILED(rc)) throw rc;
3958 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
3959 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
3960 if (FAILED(rc)) throw rc;
3961 // We search for the first host network interface which
3962 // is usable for bridged networking
3963 for (size_t j = 0;
3964 j < nwInterfaces.size();
3965 ++j)
3966 {
3967 HostNetworkInterfaceType_T itype;
3968 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
3969 if (FAILED(rc)) throw rc;
3970 if (itype == HostNetworkInterfaceType_Bridged)
3971 {
3972 Bstr name;
3973 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
3974 if (FAILED(rc)) throw rc;
3975 /* Set the interface name to attach to */
3976 rc = pNetworkAdapter->COMSETTER(BridgedInterface)(name.raw());
3977 if (FAILED(rc)) throw rc;
3978 break;
3979 }
3980 }
3981 }
3982 /* Next test for host only interfaces */
3983 else if (pvsys->strExtraConfigCurrent.endsWith("type=HostOnly", Utf8Str::CaseInsensitive))
3984 {
3985 /* Attach to the right interface */
3986 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly);
3987 if (FAILED(rc)) throw rc;
3988 ComPtr<IHost> host;
3989 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
3990 if (FAILED(rc)) throw rc;
3991 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
3992 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
3993 if (FAILED(rc)) throw rc;
3994 // We search for the first host network interface which
3995 // is usable for host only networking
3996 for (size_t j = 0;
3997 j < nwInterfaces.size();
3998 ++j)
3999 {
4000 HostNetworkInterfaceType_T itype;
4001 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
4002 if (FAILED(rc)) throw rc;
4003 if (itype == HostNetworkInterfaceType_HostOnly)
4004 {
4005 Bstr name;
4006 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
4007 if (FAILED(rc)) throw rc;
4008 /* Set the interface name to attach to */
4009 rc = pNetworkAdapter->COMSETTER(HostOnlyInterface)(name.raw());
4010 if (FAILED(rc)) throw rc;
4011 break;
4012 }
4013 }
4014 }
4015 /* Next test for internal interfaces */
4016 else if (pvsys->strExtraConfigCurrent.endsWith("type=Internal", Utf8Str::CaseInsensitive))
4017 {
4018 /* Attach to the right interface */
4019 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Internal);
4020 if (FAILED(rc)) throw rc;
4021 }
4022 /* Next test for Generic interfaces */
4023 else if (pvsys->strExtraConfigCurrent.endsWith("type=Generic", Utf8Str::CaseInsensitive))
4024 {
4025 /* Attach to the right interface */
4026 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Generic);
4027 if (FAILED(rc)) throw rc;
4028 }
4029
4030 /* Next test for NAT network interfaces */
4031 else if (pvsys->strExtraConfigCurrent.endsWith("type=NATNetwork", Utf8Str::CaseInsensitive))
4032 {
4033 /* Attach to the right interface */
4034 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork);
4035 if (FAILED(rc)) throw rc;
4036 com::SafeIfaceArray<INATNetwork> nwNATNetworks;
4037 rc = mVirtualBox->COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(nwNATNetworks));
4038 if (FAILED(rc)) throw rc;
4039 // Pick the first NAT network (if there is any)
4040 if (nwNATNetworks.size())
4041 {
4042 Bstr name;
4043 rc = nwNATNetworks[0]->COMGETTER(NetworkName)(name.asOutParam());
4044 if (FAILED(rc)) throw rc;
4045 /* Set the NAT network name to attach to */
4046 rc = pNetworkAdapter->COMSETTER(NATNetwork)(name.raw());
4047 if (FAILED(rc)) throw rc;
4048 break;
4049 }
4050 }
4051 }
4052 }
4053
4054 // Storage controller IDE
4055 std::list<VirtualSystemDescriptionEntry*> vsdeHDCIDE =
4056 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerIDE);
4057 /*
4058 * In OVF (at least VMware's version of it), an IDE controller has two ports,
4059 * so VirtualBox's single IDE controller with two channels and two ports each counts as
4060 * two OVF IDE controllers -- so we accept one or two such IDE controllers
4061 */
4062 size_t cIDEControllers = vsdeHDCIDE.size();
4063 if (cIDEControllers > 2)
4064 throw setError(VBOX_E_FILE_ERROR,
4065 tr("Too many IDE controllers in OVF; import facility only supports two"));
4066 if (!vsdeHDCIDE.empty())
4067 {
4068 // one or two IDE controllers present in OVF: add one VirtualBox controller
4069 ComPtr<IStorageController> pController;
4070 rc = pNewMachine->AddStorageController(Bstr("IDE").raw(), StorageBus_IDE, pController.asOutParam());
4071 if (FAILED(rc)) throw rc;
4072
4073 const char *pcszIDEType = vsdeHDCIDE.front()->strVBoxCurrent.c_str();
4074 if (!strcmp(pcszIDEType, "PIIX3"))
4075 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX3);
4076 else if (!strcmp(pcszIDEType, "PIIX4"))
4077 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX4);
4078 else if (!strcmp(pcszIDEType, "ICH6"))
4079 rc = pController->COMSETTER(ControllerType)(StorageControllerType_ICH6);
4080 else
4081 throw setError(VBOX_E_FILE_ERROR,
4082 tr("Invalid IDE controller type \"%s\""),
4083 pcszIDEType);
4084 if (FAILED(rc)) throw rc;
4085 }
4086
4087 /* Storage controller SATA */
4088 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSATA =
4089 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSATA);
4090 if (vsdeHDCSATA.size() > 1)
4091 throw setError(VBOX_E_FILE_ERROR,
4092 tr("Too many SATA controllers in OVF; import facility only supports one"));
4093 if (!vsdeHDCSATA.empty())
4094 {
4095 ComPtr<IStorageController> pController;
4096 const Utf8Str &hdcVBox = vsdeHDCSATA.front()->strVBoxCurrent;
4097 if (hdcVBox == "AHCI")
4098 {
4099 rc = pNewMachine->AddStorageController(Bstr("SATA").raw(),
4100 StorageBus_SATA,
4101 pController.asOutParam());
4102 if (FAILED(rc)) throw rc;
4103 }
4104 else
4105 throw setError(VBOX_E_FILE_ERROR,
4106 tr("Invalid SATA controller type \"%s\""),
4107 hdcVBox.c_str());
4108 }
4109
4110 /* Storage controller SCSI */
4111 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSCSI =
4112 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI);
4113 if (vsdeHDCSCSI.size() > 1)
4114 throw setError(VBOX_E_FILE_ERROR,
4115 tr("Too many SCSI controllers in OVF; import facility only supports one"));
4116 if (!vsdeHDCSCSI.empty())
4117 {
4118 ComPtr<IStorageController> pController;
4119 Utf8Str strName("SCSI");
4120 StorageBus_T busType = StorageBus_SCSI;
4121 StorageControllerType_T controllerType;
4122 const Utf8Str &hdcVBox = vsdeHDCSCSI.front()->strVBoxCurrent;
4123 if (hdcVBox == "LsiLogic")
4124 controllerType = StorageControllerType_LsiLogic;
4125 else if (hdcVBox == "LsiLogicSas")
4126 {
4127 // OVF treats LsiLogicSas as a SCSI controller but VBox considers it a class of its own
4128 strName = "SAS";
4129 busType = StorageBus_SAS;
4130 controllerType = StorageControllerType_LsiLogicSas;
4131 }
4132 else if (hdcVBox == "BusLogic")
4133 controllerType = StorageControllerType_BusLogic;
4134 else
4135 throw setError(VBOX_E_FILE_ERROR,
4136 tr("Invalid SCSI controller type \"%s\""),
4137 hdcVBox.c_str());
4138
4139 rc = pNewMachine->AddStorageController(Bstr(strName).raw(), busType, pController.asOutParam());
4140 if (FAILED(rc)) throw rc;
4141 rc = pController->COMSETTER(ControllerType)(controllerType);
4142 if (FAILED(rc)) throw rc;
4143 }
4144
4145 /* Storage controller SAS */
4146 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSAS =
4147 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSAS);
4148 if (vsdeHDCSAS.size() > 1)
4149 throw setError(VBOX_E_FILE_ERROR,
4150 tr("Too many SAS controllers in OVF; import facility only supports one"));
4151 if (!vsdeHDCSAS.empty())
4152 {
4153 ComPtr<IStorageController> pController;
4154 rc = pNewMachine->AddStorageController(Bstr(L"SAS").raw(),
4155 StorageBus_SAS,
4156 pController.asOutParam());
4157 if (FAILED(rc)) throw rc;
4158 rc = pController->COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas);
4159 if (FAILED(rc)) throw rc;
4160 }
4161
4162 /* Now its time to register the machine before we add any storage devices */
4163 rc = mVirtualBox->RegisterMachine(pNewMachine);
4164 if (FAILED(rc)) throw rc;
4165
4166 // store new machine for roll-back in case of errors
4167 Bstr bstrNewMachineId;
4168 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
4169 if (FAILED(rc)) throw rc;
4170 Guid uuidNewMachine(bstrNewMachineId);
4171 m->llGuidsMachinesCreated.push_back(uuidNewMachine);
4172
4173 // Add floppies and CD-ROMs to the appropriate controllers.
4174 std::list<VirtualSystemDescriptionEntry*> vsdeFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy);
4175 if (vsdeFloppy.size() > 1)
4176 throw setError(VBOX_E_FILE_ERROR,
4177 tr("Too many floppy controllers in OVF; import facility only supports one"));
4178 std::list<VirtualSystemDescriptionEntry*> vsdeCDROM = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
4179 if ( !vsdeFloppy.empty()
4180 || !vsdeCDROM.empty()
4181 )
4182 {
4183 // If there's an error here we need to close the session, so
4184 // we need another try/catch block.
4185
4186 try
4187 {
4188 // to attach things we need to open a session for the new machine
4189 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
4190 if (FAILED(rc)) throw rc;
4191 stack.fSessionOpen = true;
4192
4193 ComPtr<IMachine> sMachine;
4194 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
4195 if (FAILED(rc)) throw rc;
4196
4197 // floppy first
4198 if (vsdeFloppy.size() == 1)
4199 {
4200 ComPtr<IStorageController> pController;
4201 rc = sMachine->AddStorageController(Bstr("Floppy").raw(),
4202 StorageBus_Floppy,
4203 pController.asOutParam());
4204 if (FAILED(rc)) throw rc;
4205
4206 Bstr bstrName;
4207 rc = pController->COMGETTER(Name)(bstrName.asOutParam());
4208 if (FAILED(rc)) throw rc;
4209
4210 // this is for rollback later
4211 MyHardDiskAttachment mhda;
4212 mhda.pMachine = pNewMachine;
4213 mhda.controllerName = bstrName;
4214 mhda.lControllerPort = 0;
4215 mhda.lDevice = 0;
4216
4217 Log(("Attaching floppy\n"));
4218
4219 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),
4220 mhda.lControllerPort,
4221 mhda.lDevice,
4222 DeviceType_Floppy,
4223 NULL);
4224 if (FAILED(rc)) throw rc;
4225
4226 stack.llHardDiskAttachments.push_back(mhda);
4227 }
4228
4229 rc = sMachine->SaveSettings();
4230 if (FAILED(rc)) throw rc;
4231
4232 // only now that we're done with all storage devices, close the session
4233 rc = stack.pSession->UnlockMachine();
4234 if (FAILED(rc)) throw rc;
4235 stack.fSessionOpen = false;
4236 }
4237 catch(HRESULT aRC)
4238 {
4239 com::ErrorInfo info;
4240
4241 if (stack.fSessionOpen)
4242 stack.pSession->UnlockMachine();
4243
4244 if (info.isFullAvailable())
4245 throw setError(aRC, Utf8Str(info.getText()).c_str());
4246 else
4247 throw setError(aRC, "Unknown error during OVF import");
4248 }
4249 }
4250
4251 // create the storage devices & connect them to the appropriate controllers
4252 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
4253 if (!avsdeHDs.empty())
4254 {
4255 // If there's an error here we need to close the session, so
4256 // we need another try/catch block.
4257 try
4258 {
4259#ifdef LOG_ENABLED
4260 if (LogIsEnabled())
4261 {
4262 size_t i = 0;
4263 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4264 itHD != avsdeHDs.end(); ++itHD, i++)
4265 Log(("avsdeHDs[%zu]: strRef=%s strOvf=%s\n", i, (*itHD)->strRef.c_str(), (*itHD)->strOvf.c_str()));
4266 i = 0;
4267 for (ovf::DiskImagesMap::const_iterator itDisk = stack.mapDisks.begin(); itDisk != stack.mapDisks.end(); ++itDisk)
4268 Log(("mapDisks[%zu]: strDiskId=%s strHref=%s\n",
4269 i, itDisk->second.strDiskId.c_str(), itDisk->second.strHref.c_str()));
4270
4271 }
4272#endif
4273
4274 // to attach things we need to open a session for the new machine
4275 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
4276 if (FAILED(rc)) throw rc;
4277 stack.fSessionOpen = true;
4278
4279 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
4280 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
4281 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
4282 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
4283
4284
4285 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
4286 std::set<RTCString> disksResolvedNames;
4287
4288 uint32_t cImportedDisks = 0;
4289
4290 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
4291 {
4292/** @todo r=bird: Most of the code here is duplicated in the other machine
4293 * import method, factor out. */
4294 ovf::DiskImage diCurrent = oit->second;
4295
4296 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
4297 /* Iterate over all given images of the virtual system
4298 * description. We need to find the target image path,
4299 * which could be changed by the user. */
4300 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
4301 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4302 itHD != avsdeHDs.end();
4303 ++itHD)
4304 {
4305 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4306 if (vsdeHD->strRef == diCurrent.strDiskId)
4307 {
4308 vsdeTargetHD = vsdeHD;
4309 break;
4310 }
4311 }
4312 if (!vsdeTargetHD)
4313 {
4314 /* possible case if an image belongs to other virtual system (OVF package with multiple VMs inside) */
4315 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
4316 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
4317 NOREF(vmNameEntry);
4318 ++oit;
4319 continue;
4320 }
4321
4322 //diCurrent.strDiskId contains the image identifier (e.g. "vmdisk1"), which should exist
4323 //in the virtual system's images map under that ID and also in the global images map
4324 ovf::VirtualDisksMap::const_iterator itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
4325 if (itVDisk == vsysThis.mapVirtualDisks.end())
4326 throw setError(E_FAIL,
4327 tr("Internal inconsistency looking up disk image '%s'"),
4328 diCurrent.strHref.c_str());
4329
4330 /*
4331 * preliminary check availability of the image
4332 * This step is useful if image is placed in the OVA (TAR) package
4333 */
4334 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
4335 {
4336 /* It means that we possibly have imported the storage earlier on the previous loop steps*/
4337 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
4338 if (h != disksResolvedNames.end())
4339 {
4340 /* Yes, image name was found, we can skip it*/
4341 ++oit;
4342 continue;
4343 }
4344l_skipped:
4345 rc = i_preCheckImageAvailability(stack);
4346 if (SUCCEEDED(rc))
4347 {
4348 /* current opened file isn't the same as passed one */
4349 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
4350 {
4351 /* availableImage contains the image file reference (e.g. "disk1.vmdk"), which should
4352 * exist in the global images map.
4353 * And find the image from the OVF's disk list */
4354 ovf::DiskImagesMap::const_iterator itDiskImage;
4355 for (itDiskImage = stack.mapDisks.begin();
4356 itDiskImage != stack.mapDisks.end();
4357 itDiskImage++)
4358 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
4359 Utf8Str::CaseInsensitive) == 0)
4360 break;
4361 if (itDiskImage == stack.mapDisks.end())
4362 {
4363 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
4364 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
4365 goto l_skipped;
4366 }
4367
4368 /* replace with a new found image */
4369 diCurrent = *(&itDiskImage->second);
4370
4371 /*
4372 * Again iterate over all given images of the virtual system
4373 * description using the found image
4374 */
4375 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4376 itHD != avsdeHDs.end();
4377 ++itHD)
4378 {
4379 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4380 if (vsdeHD->strRef == diCurrent.strDiskId)
4381 {
4382 vsdeTargetHD = vsdeHD;
4383 break;
4384 }
4385 }
4386
4387 /*
4388 * in this case it's an error because something is wrong with the OVF description file.
4389 * May be VBox imports OVA package with wrong file sequence inside the archive.
4390 */
4391 if (!vsdeTargetHD)
4392 throw setError(E_FAIL,
4393 tr("Internal inconsistency looking up disk image '%s'"),
4394 diCurrent.strHref.c_str());
4395
4396 itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
4397 if (itVDisk == vsysThis.mapVirtualDisks.end())
4398 throw setError(E_FAIL,
4399 tr("Internal inconsistency looking up disk image '%s'"),
4400 diCurrent.strHref.c_str());
4401 }
4402 else
4403 {
4404 ++oit;
4405 }
4406 }
4407 else
4408 {
4409 ++oit;
4410 continue;
4411 }
4412 }
4413 else
4414 {
4415 /* just continue with normal files */
4416 ++oit;
4417 }
4418
4419 /* very important to store image name for the next checks */
4420 disksResolvedNames.insert(diCurrent.strHref);
4421////// end of duplicated code.
4422 const ovf::VirtualDisk &ovfVdisk = itVDisk->second;
4423
4424 ComObjPtr<Medium> pTargetMedium;
4425 if (stack.locInfo.storageType == VFSType_Cloud)
4426 {
4427 /* We have already all disks prepared (converted and registered in the VBox)
4428 * and in the correct place (VM machine folder).
4429 * so what is needed is to get the disk uuid from VirtualDisk::strDiskId
4430 * and find the Medium object with this uuid.
4431 * next just attach the Medium object to new VM.
4432 * VirtualDisk::strDiskId is filled in the */
4433
4434 Guid id(ovfVdisk.strDiskId);
4435 rc = mVirtualBox->i_findHardDiskById(id, false, &pTargetMedium);
4436 if (FAILED(rc))
4437 throw rc;
4438 }
4439 else
4440 {
4441 i_importOneDiskImage(diCurrent,
4442 vsdeTargetHD->strVBoxCurrent,
4443 pTargetMedium,
4444 stack);
4445 }
4446
4447 // now use the new uuid to attach the medium to our new machine
4448 ComPtr<IMachine> sMachine;
4449 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
4450 if (FAILED(rc))
4451 throw rc;
4452
4453 // find the hard disk controller to which we should attach
4454 ovf::HardDiskController hdc = (*vsysThis.mapControllers.find(ovfVdisk.idController)).second;
4455
4456 // this is for rollback later
4457 MyHardDiskAttachment mhda;
4458 mhda.pMachine = pNewMachine;
4459
4460 i_convertDiskAttachmentValues(hdc,
4461 ovfVdisk.ulAddressOnParent,
4462 mhda.controllerName,
4463 mhda.lControllerPort,
4464 mhda.lDevice);
4465
4466 Log(("Attaching disk %s to port %d on device %d\n",
4467 vsdeTargetHD->strVBoxCurrent.c_str(), mhda.lControllerPort, mhda.lDevice));
4468
4469 DeviceType_T devType = DeviceType_Null;
4470 rc = pTargetMedium->COMGETTER(DeviceType)(&devType);
4471 if (FAILED(rc))
4472 throw rc;
4473
4474 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
4475 mhda.lControllerPort, // long controllerPort
4476 mhda.lDevice, // long device
4477 devType, // DeviceType_T type
4478 pTargetMedium);
4479 if (FAILED(rc))
4480 throw rc;
4481
4482 stack.llHardDiskAttachments.push_back(mhda);
4483
4484 rc = sMachine->SaveSettings();
4485 if (FAILED(rc))
4486 throw rc;
4487
4488 ++cImportedDisks;
4489
4490 } // end while(oit != stack.mapDisks.end())
4491
4492 /*
4493 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
4494 */
4495 if(cImportedDisks < avsdeHDs.size())
4496 {
4497 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
4498 vmNameEntry->strOvf.c_str()));
4499 }
4500
4501 // only now that we're done with all disks, close the session
4502 rc = stack.pSession->UnlockMachine();
4503 if (FAILED(rc))
4504 throw rc;
4505 stack.fSessionOpen = false;
4506 }
4507 catch(HRESULT aRC)
4508 {
4509 com::ErrorInfo info;
4510 if (stack.fSessionOpen)
4511 stack.pSession->UnlockMachine();
4512
4513 if (info.isFullAvailable())
4514 throw setError(aRC, Utf8Str(info.getText()).c_str());
4515 else
4516 throw setError(aRC, "Unknown error during OVF import");
4517 }
4518 }
4519 LogFlowFuncLeave();
4520}
4521
4522/**
4523 * Imports one OVF virtual system (described by a vbox:Machine tag represented by the given config
4524 * structure) into VirtualBox by creating an IMachine instance, which is returned.
4525 *
4526 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
4527 * up any leftovers from this function. For this, the given ImportStack instance has received information
4528 * about what needs cleaning up (to support rollback).
4529 *
4530 * The machine config stored in the settings::MachineConfigFile structure contains the UUIDs of
4531 * the disk attachments used by the machine when it was exported. We also add vbox:uuid attributes
4532 * to the OVF disks sections so we can look them up. While importing these UUIDs into a second host
4533 * will most probably work, reimporting them into the same host will cause conflicts, so we always
4534 * generate new ones on import. This involves the following:
4535 *
4536 * 1) Scan the machine config for disk attachments.
4537 *
4538 * 2) For each disk attachment found, look up the OVF disk image from the disk references section
4539 * and import the disk into VirtualBox, which creates a new UUID for it. In the machine config,
4540 * replace the old UUID with the new one.
4541 *
4542 * 3) Change the machine config according to the OVF virtual system descriptions, in case the
4543 * caller has modified them using setFinalValues().
4544 *
4545 * 4) Create the VirtualBox machine with the modfified machine config.
4546 *
4547 * @param vsdescThis
4548 * @param pReturnNewMachine
4549 * @param stack
4550 */
4551void Appliance::i_importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
4552 ComPtr<IMachine> &pReturnNewMachine,
4553 ImportStack &stack)
4554{
4555 LogFlowFuncEnter();
4556 Assert(vsdescThis->m->pConfig);
4557
4558 HRESULT rc = S_OK;
4559
4560 settings::MachineConfigFile &config = *vsdescThis->m->pConfig;
4561
4562 /*
4563 * step 1): modify machine config according to OVF config, in case the user
4564 * has modified them using setFinalValues()
4565 */
4566
4567 /* OS Type */
4568 config.machineUserData.strOsType = stack.strOsTypeVBox;
4569 /* Groups */
4570 if (stack.strPrimaryGroup.isEmpty() || stack.strPrimaryGroup == "/")
4571 {
4572 config.machineUserData.llGroups.clear();
4573 config.machineUserData.llGroups.push_back("/");
4574 }
4575 else
4576 {
4577 /* Replace the primary group if there is one, otherwise add it. */
4578 if (config.machineUserData.llGroups.size())
4579 config.machineUserData.llGroups.pop_front();
4580 config.machineUserData.llGroups.push_front(stack.strPrimaryGroup);
4581 }
4582 /* Description */
4583 config.machineUserData.strDescription = stack.strDescription;
4584 /* CPU count & extented attributes */
4585 config.hardwareMachine.cCPUs = stack.cCPUs;
4586 if (stack.fForceIOAPIC)
4587 config.hardwareMachine.fHardwareVirt = true;
4588 if (stack.fForceIOAPIC)
4589 config.hardwareMachine.biosSettings.fIOAPICEnabled = true;
4590 /* RAM size */
4591 config.hardwareMachine.ulMemorySizeMB = stack.ulMemorySizeMB;
4592
4593/*
4594 <const name="HardDiskControllerIDE" value="14" />
4595 <const name="HardDiskControllerSATA" value="15" />
4596 <const name="HardDiskControllerSCSI" value="16" />
4597 <const name="HardDiskControllerSAS" value="17" />
4598*/
4599
4600#ifdef VBOX_WITH_USB
4601 /* USB controller */
4602 if (stack.fUSBEnabled)
4603 {
4604 /** @todo r=klaus add support for arbitrary USB controller types, this can't handle
4605 * multiple controllers due to its design anyway */
4606 /* Usually the OHCI controller is enabled already, need to check. But
4607 * do this only if there is no xHCI controller. */
4608 bool fOHCIEnabled = false;
4609 bool fXHCIEnabled = false;
4610 settings::USBControllerList &llUSBControllers = config.hardwareMachine.usbSettings.llUSBControllers;
4611 settings::USBControllerList::iterator it;
4612 for (it = llUSBControllers.begin(); it != llUSBControllers.end(); ++it)
4613 {
4614 if (it->enmType == USBControllerType_OHCI)
4615 fOHCIEnabled = true;
4616 if (it->enmType == USBControllerType_XHCI)
4617 fXHCIEnabled = true;
4618 }
4619
4620 if (!fXHCIEnabled && !fOHCIEnabled)
4621 {
4622 settings::USBController ctrl;
4623 ctrl.strName = "OHCI";
4624 ctrl.enmType = USBControllerType_OHCI;
4625
4626 llUSBControllers.push_back(ctrl);
4627 }
4628 }
4629 else
4630 config.hardwareMachine.usbSettings.llUSBControllers.clear();
4631#endif
4632 /* Audio adapter */
4633 if (stack.strAudioAdapter.isNotEmpty())
4634 {
4635 config.hardwareMachine.audioAdapter.fEnabled = true;
4636 config.hardwareMachine.audioAdapter.controllerType = (AudioControllerType_T)stack.strAudioAdapter.toUInt32();
4637 }
4638 else
4639 config.hardwareMachine.audioAdapter.fEnabled = false;
4640 /* Network adapter */
4641 settings::NetworkAdaptersList &llNetworkAdapters = config.hardwareMachine.llNetworkAdapters;
4642 /* First disable all network cards, they will be enabled below again. */
4643 settings::NetworkAdaptersList::iterator it1;
4644 bool fKeepAllMACs = m->optListImport.contains(ImportOptions_KeepAllMACs);
4645 bool fKeepNATMACs = m->optListImport.contains(ImportOptions_KeepNATMACs);
4646 for (it1 = llNetworkAdapters.begin(); it1 != llNetworkAdapters.end(); ++it1)
4647 {
4648 it1->fEnabled = false;
4649 if (!( fKeepAllMACs
4650 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NAT)
4651 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NATNetwork)))
4652 /* Force generation of new MAC address below. */
4653 it1->strMACAddress.setNull();
4654 }
4655 /* Now iterate over all network entries. */
4656 std::list<VirtualSystemDescriptionEntry*> avsdeNWs = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
4657 if (!avsdeNWs.empty())
4658 {
4659 /* Iterate through all network adapter entries and search for the
4660 * corresponding one in the machine config. If one is found, configure
4661 * it based on the user settings. */
4662 list<VirtualSystemDescriptionEntry*>::const_iterator itNW;
4663 for (itNW = avsdeNWs.begin();
4664 itNW != avsdeNWs.end();
4665 ++itNW)
4666 {
4667 VirtualSystemDescriptionEntry *vsdeNW = *itNW;
4668 if ( vsdeNW->strExtraConfigCurrent.startsWith("slot=", Utf8Str::CaseInsensitive)
4669 && vsdeNW->strExtraConfigCurrent.length() > 6)
4670 {
4671 uint32_t iSlot = vsdeNW->strExtraConfigCurrent.substr(5).toUInt32();
4672 /* Iterate through all network adapters in the machine config. */
4673 for (it1 = llNetworkAdapters.begin();
4674 it1 != llNetworkAdapters.end();
4675 ++it1)
4676 {
4677 /* Compare the slots. */
4678 if (it1->ulSlot == iSlot)
4679 {
4680 it1->fEnabled = true;
4681 if (it1->strMACAddress.isEmpty())
4682 Host::i_generateMACAddress(it1->strMACAddress);
4683 it1->type = (NetworkAdapterType_T)vsdeNW->strVBoxCurrent.toUInt32();
4684 break;
4685 }
4686 }
4687 }
4688 }
4689 }
4690
4691 /* Floppy controller */
4692 bool fFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy).size() > 0;
4693 /* DVD controller */
4694 bool fDVD = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM).size() > 0;
4695 /* Iterate over all storage controller check the attachments and remove
4696 * them when necessary. Also detect broken configs with more than one
4697 * attachment. Old VirtualBox versions (prior to 3.2.10) had all disk
4698 * attachments pointing to the last hard disk image, which causes import
4699 * failures. A long fixed bug, however the OVF files are long lived. */
4700 settings::StorageControllersList &llControllers = config.hardwareMachine.storage.llStorageControllers;
4701 Guid hdUuid;
4702 uint32_t cDisks = 0;
4703 bool fInconsistent = false;
4704 bool fRepairDuplicate = false;
4705 settings::StorageControllersList::iterator it3;
4706 for (it3 = llControllers.begin();
4707 it3 != llControllers.end();
4708 ++it3)
4709 {
4710 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
4711 settings::AttachedDevicesList::iterator it4 = llAttachments.begin();
4712 while (it4 != llAttachments.end())
4713 {
4714 if ( ( !fDVD
4715 && it4->deviceType == DeviceType_DVD)
4716 ||
4717 ( !fFloppy
4718 && it4->deviceType == DeviceType_Floppy))
4719 {
4720 it4 = llAttachments.erase(it4);
4721 continue;
4722 }
4723 else if (it4->deviceType == DeviceType_HardDisk)
4724 {
4725 const Guid &thisUuid = it4->uuid;
4726 cDisks++;
4727 if (cDisks == 1)
4728 {
4729 if (hdUuid.isZero())
4730 hdUuid = thisUuid;
4731 else
4732 fInconsistent = true;
4733 }
4734 else
4735 {
4736 if (thisUuid.isZero())
4737 fInconsistent = true;
4738 else if (thisUuid == hdUuid)
4739 fRepairDuplicate = true;
4740 }
4741 }
4742 ++it4;
4743 }
4744 }
4745 /* paranoia... */
4746 if (fInconsistent || cDisks == 1)
4747 fRepairDuplicate = false;
4748
4749 /*
4750 * step 2: scan the machine config for media attachments
4751 */
4752 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
4753 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
4754 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
4755 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
4756
4757 /* Get all hard disk descriptions. */
4758 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
4759 std::list<VirtualSystemDescriptionEntry*>::iterator avsdeHDsIt = avsdeHDs.begin();
4760 /* paranoia - if there is no 1:1 match do not try to repair. */
4761 if (cDisks != avsdeHDs.size())
4762 fRepairDuplicate = false;
4763
4764 // there must be an image in the OVF disk structs with the same UUID
4765
4766 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
4767 std::set<RTCString> disksResolvedNames;
4768
4769 uint32_t cImportedDisks = 0;
4770
4771 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
4772 {
4773/** @todo r=bird: Most of the code here is duplicated in the other machine
4774 * import method, factor out. */
4775 ovf::DiskImage diCurrent = oit->second;
4776
4777 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
4778
4779 /* Iterate over all given disk images of the virtual system
4780 * disks description. We need to find the target disk path,
4781 * which could be changed by the user. */
4782 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
4783 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4784 itHD != avsdeHDs.end();
4785 ++itHD)
4786 {
4787 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4788 if (vsdeHD->strRef == oit->first)
4789 {
4790 vsdeTargetHD = vsdeHD;
4791 break;
4792 }
4793 }
4794 if (!vsdeTargetHD)
4795 {
4796 /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
4797 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
4798 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
4799 NOREF(vmNameEntry);
4800 ++oit;
4801 continue;
4802 }
4803
4804
4805
4806
4807
4808
4809
4810
4811
4812 /*
4813 * preliminary check availability of the image
4814 * This step is useful if image is placed in the OVA (TAR) package
4815 */
4816 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
4817 {
4818 /* It means that we possibly have imported the storage earlier on a previous loop step. */
4819 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
4820 if (h != disksResolvedNames.end())
4821 {
4822 /* Yes, disk name was found, we can skip it*/
4823 ++oit;
4824 continue;
4825 }
4826l_skipped:
4827 rc = i_preCheckImageAvailability(stack);
4828 if (SUCCEEDED(rc))
4829 {
4830 /* current opened file isn't the same as passed one */
4831 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
4832 {
4833 // availableImage contains the disk identifier (e.g. "vmdisk1"), which should exist
4834 // in the virtual system's disks map under that ID and also in the global images map
4835 // and find the disk from the OVF's disk list
4836 ovf::DiskImagesMap::const_iterator itDiskImage;
4837 for (itDiskImage = stack.mapDisks.begin();
4838 itDiskImage != stack.mapDisks.end();
4839 itDiskImage++)
4840 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
4841 Utf8Str::CaseInsensitive) == 0)
4842 break;
4843 if (itDiskImage == stack.mapDisks.end())
4844 {
4845 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
4846 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
4847 goto l_skipped;
4848 }
4849 //throw setError(E_FAIL,
4850 // tr("Internal inconsistency looking up disk image '%s'. "
4851 // "Check compliance OVA package structure and file names "
4852 // "references in the section <References> in the OVF file."),
4853 // stack.pszOvaLookAheadName);
4854
4855 /* replace with a new found disk image */
4856 diCurrent = *(&itDiskImage->second);
4857
4858 /*
4859 * Again iterate over all given disk images of the virtual system
4860 * disks description using the found disk image
4861 */
4862 vsdeTargetHD = NULL;
4863 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4864 itHD != avsdeHDs.end();
4865 ++itHD)
4866 {
4867 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4868 if (vsdeHD->strRef == diCurrent.strDiskId)
4869 {
4870 vsdeTargetHD = vsdeHD;
4871 break;
4872 }
4873 }
4874
4875 /*
4876 * in this case it's an error because something is wrong with the OVF description file.
4877 * May be VBox imports OVA package with wrong file sequence inside the archive.
4878 */
4879 if (!vsdeTargetHD)
4880 throw setError(E_FAIL,
4881 tr("Internal inconsistency looking up disk image '%s'"),
4882 diCurrent.strHref.c_str());
4883
4884
4885
4886
4887
4888 }
4889 else
4890 {
4891 ++oit;
4892 }
4893 }
4894 else
4895 {
4896 ++oit;
4897 continue;
4898 }
4899 }
4900 else
4901 {
4902 /* just continue with normal files*/
4903 ++oit;
4904 }
4905
4906 /* Important! to store disk name for the next checks */
4907 disksResolvedNames.insert(diCurrent.strHref);
4908////// end of duplicated code.
4909 // there must be an image in the OVF disk structs with the same UUID
4910 bool fFound = false;
4911 Utf8Str strUuid;
4912
4913 // for each storage controller...
4914 for (settings::StorageControllersList::iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
4915 sit != config.hardwareMachine.storage.llStorageControllers.end();
4916 ++sit)
4917 {
4918 settings::StorageController &sc = *sit;
4919
4920 // for each medium attachment to this controller...
4921 for (settings::AttachedDevicesList::iterator dit = sc.llAttachedDevices.begin();
4922 dit != sc.llAttachedDevices.end();
4923 ++dit)
4924 {
4925 settings::AttachedDevice &d = *dit;
4926
4927 if (d.uuid.isZero())
4928 // empty DVD and floppy media
4929 continue;
4930
4931 // When repairing a broken VirtualBox xml config section (written
4932 // by VirtualBox versions earlier than 3.2.10) assume the disks
4933 // show up in the same order as in the OVF description.
4934 if (fRepairDuplicate)
4935 {
4936 VirtualSystemDescriptionEntry *vsdeHD = *avsdeHDsIt;
4937 ovf::DiskImagesMap::const_iterator itDiskImage = stack.mapDisks.find(vsdeHD->strRef);
4938 if (itDiskImage != stack.mapDisks.end())
4939 {
4940 const ovf::DiskImage &di = itDiskImage->second;
4941 d.uuid = Guid(di.uuidVBox);
4942 }
4943 ++avsdeHDsIt;
4944 }
4945
4946 // convert the Guid to string
4947 strUuid = d.uuid.toString();
4948
4949 if (diCurrent.uuidVBox != strUuid)
4950 {
4951 continue;
4952 }
4953
4954 /*
4955 * step 3: import disk
4956 */
4957 ComObjPtr<Medium> pTargetMedium;
4958 i_importOneDiskImage(diCurrent,
4959 vsdeTargetHD->strVBoxCurrent,
4960 pTargetMedium,
4961 stack);
4962
4963 // ... and replace the old UUID in the machine config with the one of
4964 // the imported disk that was just created
4965 Bstr hdId;
4966 rc = pTargetMedium->COMGETTER(Id)(hdId.asOutParam());
4967 if (FAILED(rc)) throw rc;
4968
4969 /*
4970 * 1. saving original UUID for restoring in case of failure.
4971 * 2. replacement of original UUID by new UUID in the current VM config (settings::MachineConfigFile).
4972 */
4973 {
4974 rc = stack.saveOriginalUUIDOfAttachedDevice(d, Utf8Str(hdId));
4975 d.uuid = hdId;
4976 }
4977
4978 fFound = true;
4979 break;
4980 } // for (settings::AttachedDevicesList::const_iterator dit = sc.llAttachedDevices.begin();
4981 } // for (settings::StorageControllersList::const_iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
4982
4983 // no disk with such a UUID found:
4984 if (!fFound)
4985 throw setError(E_FAIL,
4986 tr("<vbox:Machine> element in OVF contains a medium attachment for the disk image %s "
4987 "but the OVF describes no such image"),
4988 strUuid.c_str());
4989
4990 ++cImportedDisks;
4991
4992 }// while(oit != stack.mapDisks.end())
4993
4994
4995 /*
4996 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
4997 */
4998 if(cImportedDisks < avsdeHDs.size())
4999 {
5000 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
5001 vmNameEntry->strOvf.c_str()));
5002 }
5003
5004 /*
5005 * step 4): create the machine and have it import the config
5006 */
5007
5008 ComObjPtr<Machine> pNewMachine;
5009 rc = pNewMachine.createObject();
5010 if (FAILED(rc)) throw rc;
5011
5012 // this magic constructor fills the new machine object with the MachineConfig
5013 // instance that we created from the vbox:Machine
5014 rc = pNewMachine->init(mVirtualBox,
5015 stack.strNameVBox,// name from OVF preparations; can be suffixed to avoid duplicates
5016 stack.strSettingsFilename,
5017 config); // the whole machine config
5018 if (FAILED(rc)) throw rc;
5019
5020 pReturnNewMachine = ComPtr<IMachine>(pNewMachine);
5021
5022 // and register it
5023 rc = mVirtualBox->RegisterMachine(pNewMachine);
5024 if (FAILED(rc)) throw rc;
5025
5026 // store new machine for roll-back in case of errors
5027 Bstr bstrNewMachineId;
5028 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
5029 if (FAILED(rc)) throw rc;
5030 m->llGuidsMachinesCreated.push_back(Guid(bstrNewMachineId));
5031
5032 LogFlowFuncLeave();
5033}
5034
5035/**
5036 * @throws HRESULT errors.
5037 */
5038void Appliance::i_importMachines(ImportStack &stack)
5039{
5040 // this is safe to access because this thread only gets started
5041 const ovf::OVFReader &reader = *m->pReader;
5042
5043 // create a session for the machine + disks we manipulate below
5044 HRESULT rc = stack.pSession.createInprocObject(CLSID_Session);
5045 ComAssertComRCThrowRC(rc);
5046
5047 list<ovf::VirtualSystem>::const_iterator it;
5048 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it1;
5049 /* Iterate through all virtual systems of that appliance */
5050 size_t i = 0;
5051 for (it = reader.m_llVirtualSystems.begin(), it1 = m->virtualSystemDescriptions.begin();
5052 it != reader.m_llVirtualSystems.end() && it1 != m->virtualSystemDescriptions.end();
5053 ++it, ++it1, ++i)
5054 {
5055 const ovf::VirtualSystem &vsysThis = *it;
5056 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it1);
5057
5058 ComPtr<IMachine> pNewMachine;
5059
5060 // there are two ways in which we can create a vbox machine from OVF:
5061 // -- either this OVF was written by vbox 3.2 or later, in which case there is a <vbox:Machine> element
5062 // in the <VirtualSystem>; then the VirtualSystemDescription::Data has a settings::MachineConfigFile
5063 // with all the machine config pretty-parsed;
5064 // -- or this is an OVF from an older vbox or an external source, and then we need to translate the
5065 // VirtualSystemDescriptionEntry and do import work
5066
5067 // Even for the vbox:Machine case, there are a number of configuration items that will be taken from
5068 // the OVF because otherwise the "override import parameters" mechanism in the GUI won't work.
5069
5070 // VM name
5071 std::list<VirtualSystemDescriptionEntry*> vsdeName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
5072 if (vsdeName.size() < 1)
5073 throw setError(VBOX_E_FILE_ERROR,
5074 tr("Missing VM name"));
5075 stack.strNameVBox = vsdeName.front()->strVBoxCurrent;
5076
5077 // Primary group, which is entirely optional.
5078 stack.strPrimaryGroup.setNull();
5079 std::list<VirtualSystemDescriptionEntry*> vsdePrimaryGroup = vsdescThis->i_findByType(VirtualSystemDescriptionType_PrimaryGroup);
5080 if (vsdePrimaryGroup.size() >= 1)
5081 {
5082 stack.strPrimaryGroup = vsdePrimaryGroup.front()->strVBoxCurrent;
5083 if (stack.strPrimaryGroup.isEmpty())
5084 stack.strPrimaryGroup = "/";
5085 }
5086
5087 // Draw the right conclusions from the (possibly modified) VM settings
5088 // file name and base folder. If the VM settings file name is modified,
5089 // it takes precedence, otherwise it is recreated from the base folder
5090 // and the primary group.
5091 stack.strSettingsFilename.setNull();
5092 std::list<VirtualSystemDescriptionEntry*> vsdeSettingsFile = vsdescThis->i_findByType(VirtualSystemDescriptionType_SettingsFile);
5093 if (vsdeSettingsFile.size() >= 1)
5094 {
5095 VirtualSystemDescriptionEntry *vsdeSF1 = vsdeSettingsFile.front();
5096 if (vsdeSF1->strVBoxCurrent != vsdeSF1->strVBoxSuggested)
5097 stack.strSettingsFilename = vsdeSF1->strVBoxCurrent;
5098 }
5099 if (stack.strSettingsFilename.isEmpty())
5100 {
5101 Utf8Str strBaseFolder;
5102 std::list<VirtualSystemDescriptionEntry*> vsdeBaseFolder = vsdescThis->i_findByType(VirtualSystemDescriptionType_BaseFolder);
5103 if (vsdeBaseFolder.size() >= 1)
5104 strBaseFolder = vsdeBaseFolder.front()->strVBoxCurrent;
5105 Bstr bstrSettingsFilename;
5106 rc = mVirtualBox->ComposeMachineFilename(Bstr(stack.strNameVBox).raw(),
5107 Bstr(stack.strPrimaryGroup).raw(),
5108 NULL /* aCreateFlags */,
5109 Bstr(strBaseFolder).raw(),
5110 bstrSettingsFilename.asOutParam());
5111 if (FAILED(rc)) throw rc;
5112 stack.strSettingsFilename = bstrSettingsFilename;
5113 }
5114
5115 // Determine the machine folder from the settings file.
5116 LogFunc(("i=%zu strName=%s strSettingsFilename=%s\n", i, stack.strNameVBox.c_str(), stack.strSettingsFilename.c_str()));
5117 stack.strMachineFolder = stack.strSettingsFilename;
5118 stack.strMachineFolder.stripFilename();
5119
5120 // guest OS type
5121 std::list<VirtualSystemDescriptionEntry*> vsdeOS;
5122 vsdeOS = vsdescThis->i_findByType(VirtualSystemDescriptionType_OS);
5123 if (vsdeOS.size() < 1)
5124 throw setError(VBOX_E_FILE_ERROR,
5125 tr("Missing guest OS type"));
5126 stack.strOsTypeVBox = vsdeOS.front()->strVBoxCurrent;
5127
5128 // CPU count
5129 std::list<VirtualSystemDescriptionEntry*> vsdeCPU = vsdescThis->i_findByType(VirtualSystemDescriptionType_CPU);
5130 if (vsdeCPU.size() != 1)
5131 throw setError(VBOX_E_FILE_ERROR, tr("CPU count missing"));
5132
5133 stack.cCPUs = vsdeCPU.front()->strVBoxCurrent.toUInt32();
5134 // We need HWVirt & IO-APIC if more than one CPU is requested
5135 if (stack.cCPUs > 1)
5136 {
5137 stack.fForceHWVirt = true;
5138 stack.fForceIOAPIC = true;
5139 }
5140
5141 // RAM
5142 std::list<VirtualSystemDescriptionEntry*> vsdeRAM = vsdescThis->i_findByType(VirtualSystemDescriptionType_Memory);
5143 if (vsdeRAM.size() != 1)
5144 throw setError(VBOX_E_FILE_ERROR, tr("RAM size missing"));
5145 stack.ulMemorySizeMB = (ULONG)vsdeRAM.front()->strVBoxCurrent.toUInt64();
5146
5147#ifdef VBOX_WITH_USB
5148 // USB controller
5149 std::list<VirtualSystemDescriptionEntry*> vsdeUSBController =
5150 vsdescThis->i_findByType(VirtualSystemDescriptionType_USBController);
5151 // USB support is enabled if there's at least one such entry; to disable USB support,
5152 // the type of the USB item would have been changed to "ignore"
5153 stack.fUSBEnabled = !vsdeUSBController.empty();
5154#endif
5155 // audio adapter
5156 std::list<VirtualSystemDescriptionEntry*> vsdeAudioAdapter =
5157 vsdescThis->i_findByType(VirtualSystemDescriptionType_SoundCard);
5158 /** @todo we support one audio adapter only */
5159 if (!vsdeAudioAdapter.empty())
5160 stack.strAudioAdapter = vsdeAudioAdapter.front()->strVBoxCurrent;
5161
5162 // for the description of the new machine, always use the OVF entry, the user may have changed it in the import config
5163 std::list<VirtualSystemDescriptionEntry*> vsdeDescription =
5164 vsdescThis->i_findByType(VirtualSystemDescriptionType_Description);
5165 if (!vsdeDescription.empty())
5166 stack.strDescription = vsdeDescription.front()->strVBoxCurrent;
5167
5168 // import vbox:machine or OVF now
5169 if (vsdescThis->m->pConfig)
5170 // vbox:Machine config
5171 i_importVBoxMachine(vsdescThis, pNewMachine, stack);
5172 else
5173 // generic OVF config
5174 i_importMachineGeneric(vsysThis, vsdescThis, pNewMachine, stack);
5175
5176 } // for (it = pAppliance->m->llVirtualSystems.begin() ...
5177}
5178
5179HRESULT Appliance::ImportStack::saveOriginalUUIDOfAttachedDevice(settings::AttachedDevice &device,
5180 const Utf8Str &newlyUuid)
5181{
5182 HRESULT rc = S_OK;
5183
5184 /* save for restoring */
5185 mapNewUUIDsToOriginalUUIDs.insert(std::make_pair(newlyUuid, device.uuid.toString()));
5186
5187 return rc;
5188}
5189
5190HRESULT Appliance::ImportStack::restoreOriginalUUIDOfAttachedDevice(settings::MachineConfigFile *config)
5191{
5192 HRESULT rc = S_OK;
5193
5194 settings::StorageControllersList &llControllers = config->hardwareMachine.storage.llStorageControllers;
5195 settings::StorageControllersList::iterator itscl;
5196 for (itscl = llControllers.begin();
5197 itscl != llControllers.end();
5198 ++itscl)
5199 {
5200 settings::AttachedDevicesList &llAttachments = itscl->llAttachedDevices;
5201 settings::AttachedDevicesList::iterator itadl = llAttachments.begin();
5202 while (itadl != llAttachments.end())
5203 {
5204 std::map<Utf8Str , Utf8Str>::iterator it =
5205 mapNewUUIDsToOriginalUUIDs.find(itadl->uuid.toString());
5206 if(it!=mapNewUUIDsToOriginalUUIDs.end())
5207 {
5208 Utf8Str uuidOriginal = it->second;
5209 itadl->uuid = Guid(uuidOriginal);
5210 mapNewUUIDsToOriginalUUIDs.erase(it->first);
5211 }
5212 ++itadl;
5213 }
5214 }
5215
5216 return rc;
5217}
5218
5219/**
5220 * @throws Nothing
5221 */
5222RTVFSIOSTREAM Appliance::ImportStack::claimOvaLookAHead(void)
5223{
5224 RTVFSIOSTREAM hVfsIos = this->hVfsIosOvaLookAhead;
5225 this->hVfsIosOvaLookAhead = NIL_RTVFSIOSTREAM;
5226 /* We don't free the name since it may be referenced in error messages and such. */
5227 return hVfsIos;
5228}
5229
Note: See TracBrowser for help on using the repository browser.

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