VirtualBox

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

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

Main: Don't use Logging.h.

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