VirtualBox

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

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

Main: bugref:6913: Added generation of medium events

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