VirtualBox

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

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

Main: Use setErrorBoth when we've got a VBox status code handy. (The COM status codes aren't too specfic and this may help us decode error messages and provide an alternative to strstr for API clients. setErrorBoth isn't new, btw.)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 177.3 KB
Line 
1/* $Id: ApplianceImplImport.cpp 73003 2018-07-09 11:09:32Z 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 // disk 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 /* Hard disk 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 hard disk 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 /* Hard disks */
647 if (vsysThis.mapVirtualDisks.size() > 0)
648 {
649 ovf::VirtualDisksMap::const_iterator itVD;
650 /* Iterate through all hard disks ()*/
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 disk 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 of disk has.
670 * URI must have inside section <Disk> .
671 * But there aren't strong requirements about correspondence one URI for one disk virtual format.
672 * So possibly, we aren't able to recognize some URIs.
673 */
674
675 ComObjPtr<MediumFormat> mediumFormat;
676 rc = i_findMediumFormatFromDiskImage(di, mediumFormat);
677 if (FAILED(rc))
678 throw rc;
679
680 Bstr bstrFormatName;
681 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
682 if (FAILED(rc))
683 throw rc;
684 Utf8Str vdf = Utf8Str(bstrFormatName);
685
686 /// @todo
687 // - figure out all possible vmdk formats we also support
688 // - figure out if there is a url specifier for vhd already
689 // - we need a url specifier for the vdi format
690
691 Utf8Str strFilename = di.strHref;
692 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 }
698 else if (vdf.compare("RAW", Utf8Str::CaseInsensitive) == 0)
699 {
700 /* If the href is empty use the VM name as filename */
701 if (!strFilename.length())
702 strFilename = Utf8StrFmt("%s.iso", hd.strDiskId.c_str());
703 }
704 else
705 throw setError(VBOX_E_FILE_ERROR,
706 tr("Unsupported format for virtual disk image %s in OVF: \"%s\""),
707 di.strHref.c_str(),
708 di.strFormat.c_str());
709
710 /*
711 * Remove last extension from the file name if the file is compressed
712 */
713 if (di.strCompression.compare("gzip", Utf8Str::CaseInsensitive)==0)
714 strFilename.stripSuffix();
715
716 i_searchUniqueDiskImageFilePath(strMachineFolder, strFilename);
717
718 /* find the description for the hard disk controller
719 * that has the same ID as hd.idController */
720 const VirtualSystemDescriptionEntry *pController;
721 if (!(pController = pNewDesc->i_findControllerFromID(hd.idController)))
722 throw setError(E_FAIL,
723 tr("Cannot find hard disk controller with OVF instance ID %RI32 "
724 "to which disk \"%s\" should be attached"),
725 hd.idController,
726 di.strHref.c_str());
727
728 /* controller to attach to, and the bus within that controller */
729 Utf8StrFmt strExtraConfig("controller=%RI16;channel=%RI16",
730 pController->ulIndex,
731 hd.ulAddressOnParent);
732 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
733 hd.strDiskId,
734 di.strHref,
735 strFilename,
736 di.ulSuggestedSizeMB,
737 strExtraConfig);
738 }
739 }
740
741 m->virtualSystemDescriptions.push_back(pNewDesc);
742 }
743 }
744 catch (HRESULT aRC)
745 {
746 /* On error we clear the list & return */
747 m->virtualSystemDescriptions.clear();
748 rc = aRC;
749 }
750
751 // reset the appliance state
752 alock.acquire();
753 m->state = Data::ApplianceIdle;
754
755 return rc;
756}
757
758/**
759 * Public method implementation. This creates one or more new machines according to the
760 * VirtualSystemScription instances created by Appliance::Interpret().
761 * Thread implementation is in Appliance::i_importImpl().
762 * @param aOptions Import options.
763 * @param aProgress Progress object.
764 * @return
765 */
766HRESULT Appliance::importMachines(const std::vector<ImportOptions_T> &aOptions,
767 ComPtr<IProgress> &aProgress)
768{
769 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
770
771 if (aOptions.size())
772 {
773 m->optListImport.setCapacity(aOptions.size());
774 for (size_t i = 0; i < aOptions.size(); ++i)
775 {
776 m->optListImport.insert(i, aOptions[i]);
777 }
778 }
779
780 AssertReturn(!( m->optListImport.contains(ImportOptions_KeepAllMACs)
781 && m->optListImport.contains(ImportOptions_KeepNATMACs) )
782 , E_INVALIDARG);
783
784 // do not allow entering this method if the appliance is busy reading or writing
785 if (!i_isApplianceIdle())
786 return E_ACCESSDENIED;
787
788 if (!m->pReader)
789 return setError(E_FAIL,
790 tr("Cannot import machines without reading it first (call read() before i_importMachines())"));
791
792 ComObjPtr<Progress> progress;
793 HRESULT rc = S_OK;
794 try
795 {
796 rc = i_importImpl(m->locInfo, progress);
797 }
798 catch (HRESULT aRC)
799 {
800 rc = aRC;
801 }
802
803 if (SUCCEEDED(rc))
804 /* Return progress to the caller */
805 progress.queryInterfaceTo(aProgress.asOutParam());
806
807 return rc;
808}
809
810////////////////////////////////////////////////////////////////////////////////
811//
812// Appliance private methods
813//
814////////////////////////////////////////////////////////////////////////////////
815
816/**
817 * Ensures that there is a look-ahead object ready.
818 *
819 * @returns true if there's an object handy, false if end-of-stream.
820 * @throws HRESULT if the next object isn't a regular file. Sets error info
821 * (which is why it's a method on Appliance and not the
822 * ImportStack).
823 */
824bool Appliance::i_importEnsureOvaLookAhead(ImportStack &stack)
825{
826 Assert(stack.hVfsFssOva != NULL);
827 if (stack.hVfsIosOvaLookAhead == NIL_RTVFSIOSTREAM)
828 {
829 RTStrFree(stack.pszOvaLookAheadName);
830 stack.pszOvaLookAheadName = NULL;
831
832 RTVFSOBJTYPE enmType;
833 RTVFSOBJ hVfsObj;
834 int vrc = RTVfsFsStrmNext(stack.hVfsFssOva, &stack.pszOvaLookAheadName, &enmType, &hVfsObj);
835 if (RT_SUCCESS(vrc))
836 {
837 stack.hVfsIosOvaLookAhead = RTVfsObjToIoStream(hVfsObj);
838 RTVfsObjRelease(hVfsObj);
839 if ( ( enmType != RTVFSOBJTYPE_FILE
840 && enmType != RTVFSOBJTYPE_IO_STREAM)
841 || stack.hVfsIosOvaLookAhead == NIL_RTVFSIOSTREAM)
842 throw setError(VBOX_E_FILE_ERROR,
843 tr("Malformed OVA. '%s' is not a regular file (%d)."), stack.pszOvaLookAheadName, enmType);
844 }
845 else if (vrc == VERR_EOF)
846 return false;
847 else
848 throw setErrorVrc(vrc, tr("RTVfsFsStrmNext failed (%Rrc)"), vrc);
849 }
850 return true;
851}
852
853HRESULT Appliance::i_preCheckImageAvailability(ImportStack &stack)
854{
855 if (i_importEnsureOvaLookAhead(stack))
856 return S_OK;
857 throw setError(VBOX_E_FILE_ERROR, tr("Unexpected end of OVA package"));
858 /** @todo r=bird: dunno why this bother returning a value and the caller
859 * having a special 'continue' case for it. It always threw all non-OK
860 * status codes. It's possibly to handle out of order stuff, so that
861 * needs adding to the testcase! */
862}
863
864/**
865 * Opens a source file (for reading obviously).
866 *
867 * @param stack
868 * @param rstrSrcPath The source file to open.
869 * @param pszManifestEntry The manifest entry of the source file. This is
870 * used when constructing our manifest using a pass
871 * thru.
872 * @returns I/O stream handle to the source file.
873 * @throws HRESULT error status, error info set.
874 */
875RTVFSIOSTREAM Appliance::i_importOpenSourceFile(ImportStack &stack, Utf8Str const &rstrSrcPath, const char *pszManifestEntry)
876{
877 /*
878 * Open the source file. Special considerations for OVAs.
879 */
880 RTVFSIOSTREAM hVfsIosSrc;
881 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
882 {
883 for (uint32_t i = 0;; i++)
884 {
885 if (!i_importEnsureOvaLookAhead(stack))
886 throw setErrorBoth(VBOX_E_FILE_ERROR, VERR_EOF,
887 tr("Unexpected end of OVA / internal error - missing '%s' (skipped %u)"),
888 rstrSrcPath.c_str(), i);
889 if (RTStrICmp(stack.pszOvaLookAheadName, rstrSrcPath.c_str()) == 0)
890 break;
891
892 /* release the current object, loop to get the next. */
893 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
894 }
895 hVfsIosSrc = stack.claimOvaLookAHead();
896 }
897 else
898 {
899 int vrc = RTVfsIoStrmOpenNormal(rstrSrcPath.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosSrc);
900 if (RT_FAILURE(vrc))
901 throw setErrorVrc(vrc, tr("Error opening '%s' for reading (%Rrc)"), rstrSrcPath.c_str(), vrc);
902 }
903
904 /*
905 * Digest calculation filtering.
906 */
907 hVfsIosSrc = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosSrc, pszManifestEntry);
908 if (hVfsIosSrc == NIL_RTVFSIOSTREAM)
909 throw E_FAIL;
910
911 return hVfsIosSrc;
912}
913
914/**
915 * Creates the destination file and fills it with bytes from the source stream.
916 *
917 * This assumes that we digest the source when fDigestTypes is non-zero, and
918 * thus calls RTManifestPtIosAddEntryNow when done.
919 *
920 * @param rstrDstPath The path to the destination file. Missing path
921 * components will be created.
922 * @param hVfsIosSrc The source I/O stream.
923 * @param rstrSrcLogNm The name of the source for logging and error
924 * messages.
925 * @returns COM status code.
926 * @throws Nothing (as the caller has VFS handles to release).
927 */
928HRESULT Appliance::i_importCreateAndWriteDestinationFile(Utf8Str const &rstrDstPath, RTVFSIOSTREAM hVfsIosSrc,
929 Utf8Str const &rstrSrcLogNm)
930{
931 int vrc;
932
933 /*
934 * Create the output file, including necessary paths.
935 * Any existing file will be overwritten.
936 */
937 HRESULT hrc = VirtualBox::i_ensureFilePathExists(rstrDstPath, true /*fCreate*/);
938 if (SUCCEEDED(hrc))
939 {
940 RTVFSIOSTREAM hVfsIosDst;
941 vrc = RTVfsIoStrmOpenNormal(rstrDstPath.c_str(),
942 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL,
943 &hVfsIosDst);
944 if (RT_SUCCESS(vrc))
945 {
946 /*
947 * Pump the bytes thru. If we fail, delete the output file.
948 */
949 vrc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
950 if (RT_SUCCESS(vrc))
951 hrc = S_OK;
952 else
953 hrc = setErrorVrc(vrc, tr("Error occured decompressing '%s' to '%s' (%Rrc)"),
954 rstrSrcLogNm.c_str(), rstrDstPath.c_str(), vrc);
955 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosDst);
956 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
957 if (RT_FAILURE(vrc))
958 RTFileDelete(rstrDstPath.c_str());
959 }
960 else
961 hrc = setErrorVrc(vrc, tr("Error opening destionation image '%s' for writing (%Rrc)"), rstrDstPath.c_str(), vrc);
962 }
963 return hrc;
964}
965
966
967/**
968 *
969 * @param stack Import stack.
970 * @param rstrSrcPath Source path.
971 * @param rstrDstPath Destination path.
972 * @param pszManifestEntry The manifest entry of the source file. This is
973 * used when constructing our manifest using a pass
974 * thru.
975 * @throws HRESULT error status, error info set.
976 */
977void Appliance::i_importCopyFile(ImportStack &stack, Utf8Str const &rstrSrcPath, Utf8Str const &rstrDstPath,
978 const char *pszManifestEntry)
979{
980 /*
981 * Open the file (throws error) and add a read ahead thread so we can do
982 * concurrent reads (+digest) and writes.
983 */
984 RTVFSIOSTREAM hVfsIosSrc = i_importOpenSourceFile(stack, rstrSrcPath, pszManifestEntry);
985 RTVFSIOSTREAM hVfsIosReadAhead;
986 int vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/, 0 /*cbBuffers=default*/,
987 &hVfsIosReadAhead);
988 if (RT_FAILURE(vrc))
989 {
990 RTVfsIoStrmRelease(hVfsIosSrc);
991 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
992 }
993
994 /*
995 * Write the destination file (nothrow).
996 */
997 HRESULT hrc = i_importCreateAndWriteDestinationFile(rstrDstPath, hVfsIosReadAhead, rstrSrcPath);
998 RTVfsIoStrmRelease(hVfsIosReadAhead);
999
1000 /*
1001 * Before releasing the source stream, make sure we've successfully added
1002 * the digest to our manifest.
1003 */
1004 if (SUCCEEDED(hrc) && m->fDigestTypes)
1005 {
1006 vrc = RTManifestPtIosAddEntryNow(hVfsIosSrc);
1007 if (RT_FAILURE(vrc))
1008 hrc = setErrorVrc(vrc, tr("RTManifestPtIosAddEntryNow failed with %Rrc"), vrc);
1009 }
1010
1011 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosSrc);
1012 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
1013 if (SUCCEEDED(hrc))
1014 return;
1015 throw hrc;
1016}
1017
1018/**
1019 *
1020 * @param stack
1021 * @param rstrSrcPath
1022 * @param rstrDstPath
1023 * @param pszManifestEntry The manifest entry of the source file. This is
1024 * used when constructing our manifest using a pass
1025 * thru.
1026 * @throws HRESULT error status, error info set.
1027 */
1028void Appliance::i_importDecompressFile(ImportStack &stack, Utf8Str const &rstrSrcPath, Utf8Str const &rstrDstPath,
1029 const char *pszManifestEntry)
1030{
1031 RTVFSIOSTREAM hVfsIosSrcCompressed = i_importOpenSourceFile(stack, rstrSrcPath, pszManifestEntry);
1032
1033 /*
1034 * Add a read ahead thread here. This means reading and digest calculation
1035 * is done on one thread, while unpacking and writing is one on this thread.
1036 */
1037 RTVFSIOSTREAM hVfsIosReadAhead;
1038 int vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrcCompressed, 0 /*fFlags*/, 0 /*cBuffers=default*/,
1039 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
1040 if (RT_FAILURE(vrc))
1041 {
1042 RTVfsIoStrmRelease(hVfsIosSrcCompressed);
1043 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
1044 }
1045
1046 /*
1047 * Add decompression step.
1048 */
1049 RTVFSIOSTREAM hVfsIosSrc;
1050 vrc = RTZipGzipDecompressIoStream(hVfsIosReadAhead, 0, &hVfsIosSrc);
1051 RTVfsIoStrmRelease(hVfsIosReadAhead);
1052 if (RT_FAILURE(vrc))
1053 {
1054 RTVfsIoStrmRelease(hVfsIosSrcCompressed);
1055 throw setErrorVrc(vrc, tr("Error initializing gzip decompression for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
1056 }
1057
1058 /*
1059 * Write the stream to the destination file (nothrow).
1060 */
1061 HRESULT hrc = i_importCreateAndWriteDestinationFile(rstrDstPath, hVfsIosSrc, rstrSrcPath);
1062
1063 /*
1064 * Before releasing the source stream, make sure we've successfully added
1065 * the digest to our manifest.
1066 */
1067 if (SUCCEEDED(hrc) && m->fDigestTypes)
1068 {
1069 vrc = RTManifestPtIosAddEntryNow(hVfsIosSrcCompressed);
1070 if (RT_FAILURE(vrc))
1071 hrc = setErrorVrc(vrc, tr("RTManifestPtIosAddEntryNow failed with %Rrc"), vrc);
1072 }
1073
1074 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosSrc);
1075 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
1076
1077 cRefs = RTVfsIoStrmRelease(hVfsIosSrcCompressed);
1078 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
1079
1080 if (SUCCEEDED(hrc))
1081 return;
1082 throw hrc;
1083}
1084
1085/*******************************************************************************
1086 * Read stuff
1087 ******************************************************************************/
1088
1089/**
1090 * Implementation for reading an OVF (via task).
1091 *
1092 * This starts a new thread which will call
1093 * Appliance::taskThreadImportOrExport() which will then call readFS(). This
1094 * will then open the OVF with ovfreader.cpp.
1095 *
1096 * This is in a separate private method because it is used from two locations:
1097 *
1098 * 1) from the public Appliance::Read().
1099 *
1100 * 2) in a second worker thread; in that case, Appliance::ImportMachines() called Appliance::i_importImpl(), which
1101 * called Appliance::readFSOVA(), which called Appliance::i_importImpl(), which then called this again.
1102 *
1103 * @param aLocInfo The OVF location.
1104 * @param aProgress Where to return the progress object.
1105 * @throws COM error codes will be thrown.
1106 */
1107void Appliance::i_readImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
1108{
1109 BstrFmt bstrDesc = BstrFmt(tr("Reading appliance '%s'"),
1110 aLocInfo.strPath.c_str());
1111 HRESULT rc;
1112 /* Create the progress object */
1113 aProgress.createObject();
1114 if (aLocInfo.storageType == VFSType_File)
1115 /* 1 operation only */
1116 rc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this),
1117 bstrDesc.raw(),
1118 TRUE /* aCancelable */);
1119 else
1120 /* 4/5 is downloading, 1/5 is reading */
1121 rc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this),
1122 bstrDesc.raw(),
1123 TRUE /* aCancelable */,
1124 2, // ULONG cOperations,
1125 5, // ULONG ulTotalOperationsWeight,
1126 BstrFmt(tr("Download appliance '%s'"),
1127 aLocInfo.strPath.c_str()).raw(), // CBSTR bstrFirstOperationDescription,
1128 4); // ULONG ulFirstOperationWeight,
1129 if (FAILED(rc)) throw rc;
1130
1131 /* Initialize our worker task */
1132 TaskOVF *task = NULL;
1133 try
1134 {
1135 task = new TaskOVF(this, TaskOVF::Read, aLocInfo, aProgress);
1136 }
1137 catch (...)
1138 {
1139 throw setError(VBOX_E_OBJECT_NOT_FOUND,
1140 tr("Could not create TaskOVF object for reading the OVF from disk"));
1141 }
1142
1143 rc = task->createThread();
1144 if (FAILED(rc)) throw rc;
1145}
1146
1147/**
1148 * Actual worker code for reading an OVF from disk. This is called from Appliance::taskThreadImportOrExport()
1149 * and therefore runs on the OVF read worker thread. This opens the OVF with ovfreader.cpp.
1150 *
1151 * This runs in one context:
1152 *
1153 * 1) in a first worker thread; in that case, Appliance::Read() called Appliance::readImpl();
1154 *
1155 * @param pTask
1156 * @return
1157 */
1158HRESULT Appliance::i_readFS(TaskOVF *pTask)
1159{
1160 LogFlowFuncEnter();
1161 LogFlowFunc(("Appliance %p\n", this));
1162
1163 AutoCaller autoCaller(this);
1164 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1165
1166 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
1167
1168 HRESULT rc;
1169 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
1170 rc = i_readFSOVF(pTask);
1171 else
1172 rc = i_readFSOVA(pTask);
1173
1174 LogFlowFunc(("rc=%Rhrc\n", rc));
1175 LogFlowFuncLeave();
1176
1177 return rc;
1178}
1179
1180HRESULT Appliance::i_readFSOVF(TaskOVF *pTask)
1181{
1182 LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
1183
1184 /*
1185 * Allocate a buffer for filenames and prep it for suffix appending.
1186 */
1187 char *pszNameBuf = (char *)alloca(pTask->locInfo.strPath.length() + 16);
1188 AssertReturn(pszNameBuf, VERR_NO_TMP_MEMORY);
1189 memcpy(pszNameBuf, pTask->locInfo.strPath.c_str(), pTask->locInfo.strPath.length() + 1);
1190 RTPathStripSuffix(pszNameBuf);
1191 size_t const cchBaseName = strlen(pszNameBuf);
1192
1193 /*
1194 * Open the OVF file first since that is what this is all about.
1195 */
1196 RTVFSIOSTREAM hIosOvf;
1197 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
1198 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosOvf);
1199 if (RT_FAILURE(vrc))
1200 return setErrorVrc(vrc, tr("Failed to open OVF file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
1201
1202 HRESULT hrc = i_readOVFFile(pTask, hIosOvf, RTPathFilename(pTask->locInfo.strPath.c_str())); /* consumes hIosOvf */
1203 if (FAILED(hrc))
1204 return hrc;
1205
1206 /*
1207 * Try open the manifest file (for signature purposes and to determine digest type(s)).
1208 */
1209 RTVFSIOSTREAM hIosMf;
1210 strcpy(&pszNameBuf[cchBaseName], ".mf");
1211 vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosMf);
1212 if (RT_SUCCESS(vrc))
1213 {
1214 const char * const pszFilenamePart = RTPathFilename(pszNameBuf);
1215 hrc = i_readManifestFile(pTask, hIosMf /*consumed*/, pszFilenamePart);
1216 if (FAILED(hrc))
1217 return hrc;
1218
1219 /*
1220 * Check for the signature file.
1221 */
1222 RTVFSIOSTREAM hIosCert;
1223 strcpy(&pszNameBuf[cchBaseName], ".cert");
1224 vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosCert);
1225 if (RT_SUCCESS(vrc))
1226 {
1227 hrc = i_readSignatureFile(pTask, hIosCert /*consumed*/, pszFilenamePart);
1228 if (FAILED(hrc))
1229 return hrc;
1230 }
1231 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
1232 return setErrorVrc(vrc, tr("Failed to open the signature file '%s' (%Rrc)"), pszNameBuf, vrc);
1233
1234 }
1235 else if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
1236 {
1237 m->fDeterminedDigestTypes = true;
1238 m->fDigestTypes = 0;
1239 }
1240 else
1241 return setErrorVrc(vrc, tr("Failed to open the manifest file '%s' (%Rrc)"), pszNameBuf, vrc);
1242
1243 /*
1244 * Do tail processing (check the signature).
1245 */
1246 hrc = i_readTailProcessing(pTask);
1247
1248 LogFlowFunc(("returns %Rhrc\n", hrc));
1249 return hrc;
1250}
1251
1252HRESULT Appliance::i_readFSOVA(TaskOVF *pTask)
1253{
1254 LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
1255
1256 /*
1257 * Open the tar file as file stream.
1258 */
1259 RTVFSIOSTREAM hVfsIosOva;
1260 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
1261 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
1262 if (RT_FAILURE(vrc))
1263 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
1264
1265 RTVFSFSSTREAM hVfsFssOva;
1266 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
1267 RTVfsIoStrmRelease(hVfsIosOva);
1268 if (RT_FAILURE(vrc))
1269 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
1270
1271 /*
1272 * Since jumping thru an OVA file with seekable disk backing is rather
1273 * efficient, we can process .ovf, .mf and .cert files here without any
1274 * strict ordering restrictions.
1275 *
1276 * (Technically, the .ovf-file comes first, while the manifest and its
1277 * optional signature file either follows immediately or at the very end of
1278 * the OVA. The manifest is optional.)
1279 */
1280 char *pszOvfNameBase = NULL;
1281 size_t cchOvfNameBase = 0; NOREF(cchOvfNameBase);
1282 unsigned cLeftToFind = 3;
1283 HRESULT hrc = S_OK;
1284 do
1285 {
1286 char *pszName = NULL;
1287 RTVFSOBJTYPE enmType;
1288 RTVFSOBJ hVfsObj;
1289 vrc = RTVfsFsStrmNext(hVfsFssOva, &pszName, &enmType, &hVfsObj);
1290 if (RT_FAILURE(vrc))
1291 {
1292 if (vrc != VERR_EOF)
1293 hrc = setErrorVrc(vrc, tr("Error reading OVA '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
1294 break;
1295 }
1296
1297 /* We only care about entries that are files. Get the I/O stream handle for them. */
1298 if ( enmType == RTVFSOBJTYPE_IO_STREAM
1299 || enmType == RTVFSOBJTYPE_FILE)
1300 {
1301 /* Find the suffix and check if this is a possibly interesting file. */
1302 char *pszSuffix = strrchr(pszName, '.');
1303 if ( pszSuffix
1304 && ( RTStrICmp(pszSuffix + 1, "ovf") == 0
1305 || RTStrICmp(pszSuffix + 1, "mf") == 0
1306 || RTStrICmp(pszSuffix + 1, "cert") == 0) )
1307 {
1308 /* Match the OVF base name. */
1309 *pszSuffix = '\0';
1310 if ( pszOvfNameBase == NULL
1311 || RTStrICmp(pszName, pszOvfNameBase) == 0)
1312 {
1313 *pszSuffix = '.';
1314
1315 /* Since we're pretty sure we'll be processing this file, get the I/O stream. */
1316 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
1317 Assert(hVfsIos != NIL_RTVFSIOSTREAM);
1318
1319 /* Check for the OVF (should come first). */
1320 if (RTStrICmp(pszSuffix + 1, "ovf") == 0)
1321 {
1322 if (pszOvfNameBase == NULL)
1323 {
1324 hrc = i_readOVFFile(pTask, hVfsIos, pszName);
1325 hVfsIos = NIL_RTVFSIOSTREAM;
1326
1327 /* Set the base name. */
1328 *pszSuffix = '\0';
1329 pszOvfNameBase = pszName;
1330 cchOvfNameBase = strlen(pszName);
1331 pszName = NULL;
1332 cLeftToFind--;
1333 }
1334 else
1335 LogRel(("i_readFSOVA: '%s' contains more than one OVF file ('%s'), picking the first one\n",
1336 pTask->locInfo.strPath.c_str(), pszName));
1337 }
1338 /* Check for manifest. */
1339 else if (RTStrICmp(pszSuffix + 1, "mf") == 0)
1340 {
1341 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
1342 {
1343 hrc = i_readManifestFile(pTask, hVfsIos, pszName);
1344 hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
1345 cLeftToFind--;
1346 }
1347 else
1348 LogRel(("i_readFSOVA: '%s' contains more than one manifest file ('%s'), picking the first one\n",
1349 pTask->locInfo.strPath.c_str(), pszName));
1350 }
1351 /* Check for signature. */
1352 else if (RTStrICmp(pszSuffix + 1, "cert") == 0)
1353 {
1354 if (!m->fSignerCertLoaded)
1355 {
1356 hrc = i_readSignatureFile(pTask, hVfsIos, pszName);
1357 hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
1358 cLeftToFind--;
1359 }
1360 else
1361 LogRel(("i_readFSOVA: '%s' contains more than one signature file ('%s'), picking the first one\n",
1362 pTask->locInfo.strPath.c_str(), pszName));
1363 }
1364 else
1365 AssertFailed();
1366 if (hVfsIos != NIL_RTVFSIOSTREAM)
1367 RTVfsIoStrmRelease(hVfsIos);
1368 }
1369 }
1370 }
1371 RTVfsObjRelease(hVfsObj);
1372 RTStrFree(pszName);
1373 } while (cLeftToFind > 0 && SUCCEEDED(hrc));
1374
1375 RTVfsFsStrmRelease(hVfsFssOva);
1376 RTStrFree(pszOvfNameBase);
1377
1378 /*
1379 * Check that we found and OVF file.
1380 */
1381 if (SUCCEEDED(hrc) && !pszOvfNameBase)
1382 hrc = setError(VBOX_E_FILE_ERROR, tr("OVA '%s' does not contain an .ovf-file"), pTask->locInfo.strPath.c_str());
1383 if (SUCCEEDED(hrc))
1384 {
1385 /*
1386 * Do tail processing (check the signature).
1387 */
1388 hrc = i_readTailProcessing(pTask);
1389 }
1390 LogFlowFunc(("returns %Rhrc\n", hrc));
1391 return hrc;
1392}
1393
1394/**
1395 * Reads & parses the OVF file.
1396 *
1397 * @param pTask The read task.
1398 * @param hVfsIosOvf The I/O stream for the OVF. The reference is
1399 * always consumed.
1400 * @param pszManifestEntry The manifest entry name.
1401 * @returns COM status code, error info set.
1402 * @throws Nothing
1403 */
1404HRESULT Appliance::i_readOVFFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosOvf, const char *pszManifestEntry)
1405{
1406 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszManifestEntry));
1407
1408 /*
1409 * Set the OVF manifest entry name (needed for tweaking the manifest
1410 * validation during import).
1411 */
1412 try { m->strOvfManifestEntry = pszManifestEntry; }
1413 catch (...) { return E_OUTOFMEMORY; }
1414
1415 /*
1416 * Set up digest calculation.
1417 */
1418 hVfsIosOvf = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosOvf, pszManifestEntry);
1419 if (hVfsIosOvf == NIL_RTVFSIOSTREAM)
1420 return VBOX_E_FILE_ERROR;
1421
1422 /*
1423 * Read the OVF into a memory buffer and parse it.
1424 */
1425 void *pvBufferedOvf;
1426 size_t cbBufferedOvf;
1427 int vrc = RTVfsIoStrmReadAll(hVfsIosOvf, &pvBufferedOvf, &cbBufferedOvf);
1428 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosOvf); /* consumes stream handle. */
1429 NOREF(cRefs);
1430 Assert(cRefs == 0);
1431 if (RT_FAILURE(vrc))
1432 return setErrorVrc(vrc, tr("Could not read the OVF file for '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
1433
1434 HRESULT hrc;
1435 try
1436 {
1437 m->pReader = new ovf::OVFReader(pvBufferedOvf, cbBufferedOvf, pTask->locInfo.strPath);
1438 hrc = S_OK;
1439 }
1440 catch (RTCError &rXcpt) // includes all XML exceptions
1441 {
1442 hrc = setError(VBOX_E_FILE_ERROR, rXcpt.what());
1443 }
1444 catch (HRESULT aRC)
1445 {
1446 hrc = aRC;
1447 }
1448 catch (...)
1449 {
1450 hrc = E_FAIL;
1451 }
1452 LogFlowFunc(("OVFReader(%s) -> rc=%Rhrc\n", pTask->locInfo.strPath.c_str(), hrc));
1453
1454 RTVfsIoStrmReadAllFree(pvBufferedOvf, cbBufferedOvf);
1455 if (SUCCEEDED(hrc))
1456 {
1457 /*
1458 * If we see an OVF v2.0 envelope, select only the SHA-256 digest.
1459 */
1460 if ( !m->fDeterminedDigestTypes
1461 && m->pReader->m_envelopeData.getOVFVersion() == ovf::OVFVersion_2_0)
1462 m->fDigestTypes &= ~RTMANIFEST_ATTR_SHA256;
1463 }
1464
1465 return hrc;
1466}
1467
1468/**
1469 * Reads & parses the manifest file.
1470 *
1471 * @param pTask The read task.
1472 * @param hVfsIosMf The I/O stream for the manifest file. The
1473 * reference is always consumed.
1474 * @param pszSubFileNm The manifest filename (no path) for error
1475 * messages and logging.
1476 * @returns COM status code, error info set.
1477 * @throws Nothing
1478 */
1479HRESULT Appliance::i_readManifestFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosMf, const char *pszSubFileNm)
1480{
1481 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
1482
1483 /*
1484 * Copy the manifest into a memory backed file so we can later do signature
1485 * validation indepentend of the algorithms used by the signature.
1486 */
1487 int vrc = RTVfsMemorizeIoStreamAsFile(hVfsIosMf, RTFILE_O_READ, &m->hMemFileTheirManifest);
1488 RTVfsIoStrmRelease(hVfsIosMf); /* consumes stream handle. */
1489 if (RT_FAILURE(vrc))
1490 return setErrorVrc(vrc, tr("Error reading the manifest file '%s' for '%s' (%Rrc)"),
1491 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
1492
1493 /*
1494 * Parse the manifest.
1495 */
1496 Assert(m->hTheirManifest == NIL_RTMANIFEST);
1497 vrc = RTManifestCreate(0 /*fFlags*/, &m->hTheirManifest);
1498 AssertStmt(RT_SUCCESS(vrc), Global::vboxStatusCodeToCOM(vrc));
1499
1500 char szErr[256];
1501 RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(m->hMemFileTheirManifest);
1502 vrc = RTManifestReadStandardEx(m->hTheirManifest, hVfsIos, szErr, sizeof(szErr));
1503 RTVfsIoStrmRelease(hVfsIos);
1504 if (RT_FAILURE(vrc))
1505 throw setErrorVrc(vrc, tr("Failed to parse manifest file '%s' for '%s' (%Rrc): %s"),
1506 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, szErr);
1507
1508 /*
1509 * Check which digest files are used.
1510 * Note! the file could be empty, in which case fDigestTypes is set to 0.
1511 */
1512 vrc = RTManifestQueryAllAttrTypes(m->hTheirManifest, true /*fEntriesOnly*/, &m->fDigestTypes);
1513 AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc));
1514 m->fDeterminedDigestTypes = true;
1515
1516 return S_OK;
1517}
1518
1519/**
1520 * Reads the signature & certificate file.
1521 *
1522 * @param pTask The read task.
1523 * @param hVfsIosCert The I/O stream for the signature file. The
1524 * reference is always consumed.
1525 * @param pszSubFileNm The signature filename (no path) for error
1526 * messages and logging. Used to construct
1527 * .mf-file name.
1528 * @returns COM status code, error info set.
1529 * @throws Nothing
1530 */
1531HRESULT Appliance::i_readSignatureFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosCert, const char *pszSubFileNm)
1532{
1533 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
1534
1535 /*
1536 * Construct the manifest filename from pszSubFileNm.
1537 */
1538 Utf8Str strManifestName;
1539 try
1540 {
1541 const char *pszSuffix = strrchr(pszSubFileNm, '.');
1542 AssertReturn(pszSuffix, E_FAIL);
1543 strManifestName = Utf8Str(pszSubFileNm, pszSuffix - pszSubFileNm);
1544 strManifestName.append(".mf");
1545 }
1546 catch (...)
1547 {
1548 return E_OUTOFMEMORY;
1549 }
1550
1551 /*
1552 * Copy the manifest into a memory buffer. We'll do the signature processing
1553 * later to not force any specific order in the OVAs or any other archive we
1554 * may be accessing later.
1555 */
1556 void *pvSignature;
1557 size_t cbSignature;
1558 int vrc = RTVfsIoStrmReadAll(hVfsIosCert, &pvSignature, &cbSignature);
1559 RTVfsIoStrmRelease(hVfsIosCert); /* consumes stream handle. */
1560 if (RT_FAILURE(vrc))
1561 return setErrorVrc(vrc, tr("Error reading the signature file '%s' for '%s' (%Rrc)"),
1562 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
1563
1564 /*
1565 * Parse the signing certificate. Unlike the manifest parser we use below,
1566 * this API ignores parse of the file that aren't relevant.
1567 */
1568 RTERRINFOSTATIC StaticErrInfo;
1569 vrc = RTCrX509Certificate_ReadFromBuffer(&m->SignerCert, pvSignature, cbSignature,
1570 RTCRX509CERT_READ_F_PEM_ONLY,
1571 &g_RTAsn1DefaultAllocator, RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
1572 HRESULT hrc;
1573 if (RT_SUCCESS(vrc))
1574 {
1575 m->fSignerCertLoaded = true;
1576 m->fCertificateIsSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->SignerCert);
1577
1578 /*
1579 * Find the start of the certificate part of the file, so we can avoid
1580 * upsetting the manifest parser with it.
1581 */
1582 char *pszSplit = (char *)RTCrPemFindFirstSectionInContent(pvSignature, cbSignature,
1583 g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers);
1584 if (pszSplit)
1585 while ( pszSplit != (char *)pvSignature
1586 && pszSplit[-1] != '\n'
1587 && pszSplit[-1] != '\r')
1588 pszSplit--;
1589 else
1590 {
1591 AssertLogRelMsgFailed(("Failed to find BEGIN CERTIFICATE markers in '%s'::'%s' - impossible unless it's a DER encoded certificate!",
1592 pTask->locInfo.strPath.c_str(), pszSubFileNm));
1593 pszSplit = (char *)pvSignature + cbSignature;
1594 }
1595 *pszSplit = '\0';
1596
1597 /*
1598 * Now, read the manifest part. We use the IPRT manifest reader here
1599 * to avoid duplicating code and be somewhat flexible wrt the digest
1600 * type choosen by the signer.
1601 */
1602 RTMANIFEST hSignedDigestManifest;
1603 vrc = RTManifestCreate(0 /*fFlags*/, &hSignedDigestManifest);
1604 if (RT_SUCCESS(vrc))
1605 {
1606 RTVFSIOSTREAM hVfsIosTmp;
1607 vrc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pvSignature, pszSplit - (char *)pvSignature, &hVfsIosTmp);
1608 if (RT_SUCCESS(vrc))
1609 {
1610 vrc = RTManifestReadStandardEx(hSignedDigestManifest, hVfsIosTmp, StaticErrInfo.szMsg, sizeof(StaticErrInfo.szMsg));
1611 RTVfsIoStrmRelease(hVfsIosTmp);
1612 if (RT_SUCCESS(vrc))
1613 {
1614 /*
1615 * Get signed digest, we prefer SHA-2, so explicitly query those first.
1616 */
1617 uint32_t fDigestType;
1618 char szSignedDigest[_8K + 1];
1619 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
1620 RTMANIFEST_ATTR_SHA512 | RTMANIFEST_ATTR_SHA256,
1621 szSignedDigest, sizeof(szSignedDigest), &fDigestType);
1622 if (vrc == VERR_MANIFEST_ATTR_TYPE_NOT_FOUND)
1623 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
1624 RTMANIFEST_ATTR_ANY, szSignedDigest, sizeof(szSignedDigest), &fDigestType);
1625 if (RT_SUCCESS(vrc))
1626 {
1627 const char *pszSignedDigest = RTStrStrip(szSignedDigest);
1628 size_t cbSignedDigest = strlen(pszSignedDigest) / 2;
1629 uint8_t abSignedDigest[sizeof(szSignedDigest) / 2];
1630 vrc = RTStrConvertHexBytes(szSignedDigest, abSignedDigest, cbSignedDigest, 0 /*fFlags*/);
1631 if (RT_SUCCESS(vrc))
1632 {
1633 /*
1634 * Convert it to RTDIGESTTYPE_XXX and save the binary value for later use.
1635 */
1636 switch (fDigestType)
1637 {
1638 case RTMANIFEST_ATTR_SHA1: m->enmSignedDigestType = RTDIGESTTYPE_SHA1; break;
1639 case RTMANIFEST_ATTR_SHA256: m->enmSignedDigestType = RTDIGESTTYPE_SHA256; break;
1640 case RTMANIFEST_ATTR_SHA512: m->enmSignedDigestType = RTDIGESTTYPE_SHA512; break;
1641 case RTMANIFEST_ATTR_MD5: m->enmSignedDigestType = RTDIGESTTYPE_MD5; break;
1642 default: AssertFailed(); m->enmSignedDigestType = RTDIGESTTYPE_INVALID; break;
1643 }
1644 if (m->enmSignedDigestType != RTDIGESTTYPE_INVALID)
1645 {
1646 m->pbSignedDigest = (uint8_t *)RTMemDup(abSignedDigest, cbSignedDigest);
1647 m->cbSignedDigest = cbSignedDigest;
1648 hrc = S_OK;
1649 }
1650 else
1651 hrc = setError(E_FAIL, tr("Unsupported signed digest type (%#x)"), fDigestType);
1652 }
1653 else
1654 hrc = setErrorVrc(vrc, tr("Error reading signed manifest digest: %Rrc"), vrc);
1655 }
1656 else if (vrc == VERR_NOT_FOUND)
1657 hrc = setErrorVrc(vrc, tr("Could not locate signed digest for '%s' in the cert-file for '%s'"),
1658 strManifestName.c_str(), pTask->locInfo.strPath.c_str());
1659 else
1660 hrc = setErrorVrc(vrc, tr("RTManifestEntryQueryAttr failed unexpectedly: %Rrc"), vrc);
1661 }
1662 else
1663 hrc = setErrorVrc(vrc, tr("Error parsing the .cert-file for '%s': %s"),
1664 pTask->locInfo.strPath.c_str(), StaticErrInfo.szMsg);
1665 }
1666 else
1667 hrc = E_OUTOFMEMORY;
1668 RTManifestRelease(hSignedDigestManifest);
1669 }
1670 else
1671 hrc = E_OUTOFMEMORY;
1672 }
1673 else if (vrc == VERR_NOT_FOUND || vrc == VERR_EOF)
1674 hrc = setErrorBoth(E_FAIL, vrc, tr("Malformed .cert-file for '%s': Signer's certificate not found (%Rrc)"),
1675 pTask->locInfo.strPath.c_str(), vrc);
1676 else
1677 hrc = setErrorVrc(vrc, tr("Error reading the signer's certificate from '%s' for '%s' (%Rrc): %s"),
1678 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
1679
1680 RTVfsIoStrmReadAllFree(pvSignature, cbSignature);
1681 LogFlowFunc(("returns %Rhrc (%Rrc)\n", hrc, vrc));
1682 return hrc;
1683}
1684
1685
1686/**
1687 * Does tail processing after the files have been read in.
1688 *
1689 * @param pTask The read task.
1690 * @returns COM status.
1691 * @throws Nothing!
1692 */
1693HRESULT Appliance::i_readTailProcessing(TaskOVF *pTask)
1694{
1695 /*
1696 * Parse and validate the signature file.
1697 *
1698 * The signature file has two parts, manifest part and a PEM encoded
1699 * certificate. The former contains an entry for the manifest file with a
1700 * digest that is encrypted with the certificate in the latter part.
1701 */
1702 if (m->pbSignedDigest)
1703 {
1704 /* Since we're validating the digest of the manifest, there have to be
1705 a manifest. We cannot allow a the manifest to be missing. */
1706 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
1707 return setError(VBOX_E_FILE_ERROR, tr("Found .cert-file but no .mf-file for '%s'"), pTask->locInfo.strPath.c_str());
1708
1709 /*
1710 * Validate the signed digest.
1711 *
1712 * It's possible we should allow the user to ignore signature
1713 * mismatches, but for now it is a solid show stopper.
1714 */
1715 HRESULT hrc;
1716 RTERRINFOSTATIC StaticErrInfo;
1717
1718 /* Calc the digest of the manifest using the algorithm found above. */
1719 RTCRDIGEST hDigest;
1720 int vrc = RTCrDigestCreateByType(&hDigest, m->enmSignedDigestType);
1721 if (RT_SUCCESS(vrc))
1722 {
1723 vrc = RTCrDigestUpdateFromVfsFile(hDigest, m->hMemFileTheirManifest, true /*fRewindFile*/);
1724 if (RT_SUCCESS(vrc))
1725 {
1726 /* Compare the signed digest with the one we just calculated. (This
1727 API will do the verification twice, once using IPRT's own crypto
1728 and once using OpenSSL. Both must OK it for success.) */
1729 vrc = RTCrPkixPubKeyVerifySignedDigest(&m->SignerCert.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm,
1730 &m->SignerCert.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Parameters,
1731 &m->SignerCert.TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey,
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 disk 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 disks 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 hard disk 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 disk 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 disk 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 disk's weight.
2390 *
2391 * @param di ovfreader.cpp structure describing the disk image from the OVF that is to be imported
2392 * @param strDstPath Where to create the target image.
2393 * @param pTargetHD out: The newly created target disk. 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> &pTargetHD,
2399 ImportStack &stack)
2400{
2401 char *pszAbsDstPath = RTPathAbsExDup(stack.strMachineFolder.c_str(),
2402 strDstPath.c_str());
2403 Utf8Str strAbsDstPath(pszAbsDstPath);
2404 RTStrFree(pszAbsDstPath);
2405 pszAbsDstPath = NULL;
2406
2407 ComObjPtr<Progress> pProgress;
2408 pProgress.createObject();
2409 HRESULT rc = pProgress->init(mVirtualBox,
2410 static_cast<IAppliance*>(this),
2411 BstrFmt(tr("Creating medium '%s'"),
2412 strAbsDstPath.c_str()).raw(),
2413 TRUE);
2414 if (FAILED(rc)) throw rc;
2415
2416 /* Get the system properties. */
2417 SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
2418
2419 /* Keep the source file ref handy for later. */
2420 const Utf8Str &strSourceOVF = di.strHref;
2421
2422 /* Construct source file path */
2423 Utf8Str strSrcFilePath;
2424 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
2425 strSrcFilePath = strSourceOVF;
2426 else
2427 {
2428 strSrcFilePath = stack.strSourceDir;
2429 strSrcFilePath.append(RTPATH_SLASH_STR);
2430 strSrcFilePath.append(strSourceOVF);
2431 }
2432
2433 /* First of all check if the original (non-absolute) destination path is
2434 * a valid hard disk UUID. If so, the user wants to import the disk into
2435 * an existing path. This is useful for iSCSI for example. */
2436 RTUUID uuid;
2437 int vrc = RTUuidFromStr(&uuid, strDstPath.c_str());
2438 if (vrc == VINF_SUCCESS)
2439 {
2440 rc = mVirtualBox->i_findHardDiskById(Guid(uuid), true, &pTargetHD);
2441 if (FAILED(rc)) throw rc;
2442 }
2443 else
2444 {
2445 RTVFSIOSTREAM hVfsIosSrc = NIL_RTVFSIOSTREAM;
2446
2447 /* check read file to GZIP compression */
2448 bool const fGzipped = di.strCompression.compare("gzip",Utf8Str::CaseInsensitive) == 0;
2449 Utf8Str strDeleteTemp;
2450 try
2451 {
2452 Utf8Str strTrgFormat = "VMDK";
2453 ComObjPtr<MediumFormat> trgFormat;
2454 Bstr bstrFormatName;
2455 ULONG lCabs = 0;
2456
2457 char *pszSuff = RTPathSuffix(strAbsDstPath.c_str());
2458 if (pszSuff != NULL)
2459 {
2460 /*
2461 * Figure out which format the user like to have. Default is VMDK
2462 * or it can be VDI if according command-line option is set
2463 */
2464
2465 /*
2466 * We need a proper target format
2467 * if target format has been changed by user via GUI import wizard
2468 * or via VBoxManage import command (option --importtovdi)
2469 * then we need properly process such format like ISO
2470 * Because there is no conversion ISO to VDI
2471 */
2472 trgFormat = pSysProps->i_mediumFormatFromExtension(++pszSuff);
2473 if (trgFormat.isNull())
2474 throw setError(E_FAIL, tr("Unsupported medium format for disk image '%s'"), di.strHref.c_str());
2475
2476 rc = trgFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
2477 if (FAILED(rc)) throw rc;
2478
2479 strTrgFormat = Utf8Str(bstrFormatName);
2480
2481 if ( m->optListImport.contains(ImportOptions_ImportToVDI)
2482 && strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) != 0)
2483 {
2484 /* change the target extension */
2485 strTrgFormat = "vdi";
2486 trgFormat = pSysProps->i_mediumFormatFromExtension(strTrgFormat);
2487 strAbsDstPath.stripSuffix();
2488 strAbsDstPath.append(".");
2489 strAbsDstPath.append(strTrgFormat.c_str());
2490 }
2491
2492 /* Check the capabilities. We need create capabilities. */
2493 lCabs = 0;
2494 com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
2495 rc = trgFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
2496
2497 if (FAILED(rc))
2498 throw rc;
2499
2500 for (ULONG j = 0; j < mediumFormatCap.size(); j++)
2501 lCabs |= mediumFormatCap[j];
2502
2503 if ( !(lCabs & MediumFormatCapabilities_CreateFixed)
2504 && !(lCabs & MediumFormatCapabilities_CreateDynamic) )
2505 throw setError(VBOX_E_NOT_SUPPORTED,
2506 tr("Could not find a valid medium format for the target disk '%s'"),
2507 strAbsDstPath.c_str());
2508 }
2509 else
2510 {
2511 throw setError(VBOX_E_FILE_ERROR,
2512 tr("The target disk '%s' has no extension "),
2513 strAbsDstPath.c_str(), VERR_INVALID_NAME);
2514 }
2515
2516 /* Create an IMedium object. */
2517 pTargetHD.createObject();
2518
2519 /*CD/DVD case*/
2520 if (strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) == 0)
2521 {
2522 try
2523 {
2524 if (fGzipped)
2525 i_importDecompressFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
2526 else
2527 i_importCopyFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
2528 }
2529 catch (HRESULT /*arc*/)
2530 {
2531 throw;
2532 }
2533
2534 /* Advance to the next operation. */
2535 /* operation's weight, as set up with the IProgress originally */
2536 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
2537 RTPathFilename(strSourceOVF.c_str())).raw(),
2538 di.ulSuggestedSizeMB);
2539 }
2540 else/* HDD case*/
2541 {
2542 rc = pTargetHD->init(mVirtualBox,
2543 strTrgFormat,
2544 strAbsDstPath,
2545 Guid::Empty /* media registry: none yet */,
2546 DeviceType_HardDisk);
2547 if (FAILED(rc)) throw rc;
2548
2549 /* Now create an empty hard disk. */
2550 rc = mVirtualBox->CreateMedium(Bstr(strTrgFormat).raw(),
2551 Bstr(strAbsDstPath).raw(),
2552 AccessMode_ReadWrite, DeviceType_HardDisk,
2553 ComPtr<IMedium>(pTargetHD).asOutParam());
2554 if (FAILED(rc)) throw rc;
2555
2556 /* If strHref is empty we have to create a new file. */
2557 if (strSourceOVF.isEmpty())
2558 {
2559 com::SafeArray<MediumVariant_T> mediumVariant;
2560 mediumVariant.push_back(MediumVariant_Standard);
2561
2562 /* Kick of the creation of a dynamic growing disk image with the given capacity. */
2563 rc = pTargetHD->CreateBaseStorage(di.iCapacity / _1M,
2564 ComSafeArrayAsInParam(mediumVariant),
2565 ComPtr<IProgress>(pProgress).asOutParam());
2566 if (FAILED(rc)) throw rc;
2567
2568 /* Advance to the next operation. */
2569 /* operation's weight, as set up with the IProgress originally */
2570 stack.pProgress->SetNextOperation(BstrFmt(tr("Creating disk image '%s'"),
2571 strAbsDstPath.c_str()).raw(),
2572 di.ulSuggestedSizeMB);
2573 }
2574 else
2575 {
2576 /* We need a proper source format description */
2577 /* Which format to use? */
2578 ComObjPtr<MediumFormat> srcFormat;
2579 rc = i_findMediumFormatFromDiskImage(di, srcFormat);
2580 if (FAILED(rc))
2581 throw setError(VBOX_E_NOT_SUPPORTED,
2582 tr("Could not find a valid medium format for the source disk '%s' "
2583 "Check correctness of the image format URL in the OVF description file "
2584 "or extension of the image"),
2585 RTPathFilename(strSourceOVF.c_str()));
2586
2587 /* If gzipped, decompress the GZIP file and save a new file in the target path */
2588 if (fGzipped)
2589 {
2590 Utf8Str strTargetFilePath(strAbsDstPath);
2591 strTargetFilePath.stripFilename();
2592 strTargetFilePath.append(RTPATH_SLASH_STR);
2593 strTargetFilePath.append("temp_");
2594 strTargetFilePath.append(RTPathFilename(strSrcFilePath.c_str()));
2595 strDeleteTemp = strTargetFilePath;
2596
2597 i_importDecompressFile(stack, strSrcFilePath, strTargetFilePath, strSourceOVF.c_str());
2598
2599 /* Correct the source and the target with the actual values */
2600 strSrcFilePath = strTargetFilePath;
2601
2602 /* Open the new source file. */
2603 vrc = RTVfsIoStrmOpenNormal(strSrcFilePath.c_str(), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
2604 &hVfsIosSrc);
2605 if (RT_FAILURE(vrc))
2606 throw setErrorVrc(vrc, tr("Error opening decompressed image file '%s' (%Rrc)"),
2607 strSrcFilePath.c_str(), vrc);
2608 }
2609 else
2610 hVfsIosSrc = i_importOpenSourceFile(stack, strSrcFilePath, strSourceOVF.c_str());
2611
2612 /* Add a read ahead thread to try speed things up with concurrent reads and
2613 writes going on in different threads. */
2614 RTVFSIOSTREAM hVfsIosReadAhead;
2615 vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/,
2616 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
2617 RTVfsIoStrmRelease(hVfsIosSrc);
2618 if (RT_FAILURE(vrc))
2619 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"),
2620 strSrcFilePath.c_str(), vrc);
2621
2622 /* Start the source image cloning operation. */
2623 ComObjPtr<Medium> nullParent;
2624 rc = pTargetHD->i_importFile(strSrcFilePath.c_str(),
2625 srcFormat,
2626 MediumVariant_Standard,
2627 hVfsIosReadAhead,
2628 nullParent,
2629 pProgress);
2630 RTVfsIoStrmRelease(hVfsIosReadAhead);
2631 hVfsIosSrc = NIL_RTVFSIOSTREAM;
2632 if (FAILED(rc))
2633 throw rc;
2634
2635 /* Advance to the next operation. */
2636 /* operation's weight, as set up with the IProgress originally */
2637 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
2638 RTPathFilename(strSourceOVF.c_str())).raw(),
2639 di.ulSuggestedSizeMB);
2640 }
2641
2642 /* Now wait for the background disk operation to complete; this throws
2643 * HRESULTs on error. */
2644 ComPtr<IProgress> pp(pProgress);
2645 i_waitForAsyncProgress(stack.pProgress, pp);
2646 }
2647 }
2648 catch (...)
2649 {
2650 if (strDeleteTemp.isNotEmpty())
2651 RTFileDelete(strDeleteTemp.c_str());
2652 throw;
2653 }
2654
2655 /* Make sure the source file is closed. */
2656 if (hVfsIosSrc != NIL_RTVFSIOSTREAM)
2657 RTVfsIoStrmRelease(hVfsIosSrc);
2658
2659 /*
2660 * Delete the temp gunzip result, if any.
2661 */
2662 if (strDeleteTemp.isNotEmpty())
2663 {
2664 vrc = RTFileDelete(strSrcFilePath.c_str());
2665 if (RT_FAILURE(vrc))
2666 setWarning(VBOX_E_FILE_ERROR,
2667 tr("Failed to delete the temporary file '%s' (%Rrc)"), strSrcFilePath.c_str(), vrc);
2668 }
2669 }
2670}
2671
2672/**
2673 * Imports one OVF virtual system (described by the given ovf::VirtualSystem and VirtualSystemDescription)
2674 * into VirtualBox by creating an IMachine instance, which is returned.
2675 *
2676 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
2677 * up any leftovers from this function. For this, the given ImportStack instance has received information
2678 * about what needs cleaning up (to support rollback).
2679 *
2680 * @param vsysThis OVF virtual system (machine) to import.
2681 * @param vsdescThis Matching virtual system description (machine) to import.
2682 * @param pNewMachine out: Newly created machine.
2683 * @param stack Cleanup stack for when this throws.
2684 */
2685void Appliance::i_importMachineGeneric(const ovf::VirtualSystem &vsysThis,
2686 ComObjPtr<VirtualSystemDescription> &vsdescThis,
2687 ComPtr<IMachine> &pNewMachine,
2688 ImportStack &stack)
2689{
2690 LogFlowFuncEnter();
2691 HRESULT rc;
2692
2693 // Get the instance of IGuestOSType which matches our string guest OS type so we
2694 // can use recommended defaults for the new machine where OVF doesn't provide any
2695 ComPtr<IGuestOSType> osType;
2696 rc = mVirtualBox->GetGuestOSType(Bstr(stack.strOsTypeVBox).raw(), osType.asOutParam());
2697 if (FAILED(rc)) throw rc;
2698
2699 /* Create the machine */
2700 SafeArray<BSTR> groups; /* no groups, or maybe one group... */
2701 if (!stack.strPrimaryGroup.isEmpty() && stack.strPrimaryGroup != "/")
2702 Bstr(stack.strPrimaryGroup).detachTo(groups.appendedRaw());
2703 rc = mVirtualBox->CreateMachine(Bstr(stack.strSettingsFilename).raw(),
2704 Bstr(stack.strNameVBox).raw(),
2705 ComSafeArrayAsInParam(groups),
2706 Bstr(stack.strOsTypeVBox).raw(),
2707 NULL, /* aCreateFlags */
2708 pNewMachine.asOutParam());
2709 if (FAILED(rc)) throw rc;
2710
2711 // set the description
2712 if (!stack.strDescription.isEmpty())
2713 {
2714 rc = pNewMachine->COMSETTER(Description)(Bstr(stack.strDescription).raw());
2715 if (FAILED(rc)) throw rc;
2716 }
2717
2718 // CPU count
2719 rc = pNewMachine->COMSETTER(CPUCount)(stack.cCPUs);
2720 if (FAILED(rc)) throw rc;
2721
2722 if (stack.fForceHWVirt)
2723 {
2724 rc = pNewMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, TRUE);
2725 if (FAILED(rc)) throw rc;
2726 }
2727
2728 // RAM
2729 rc = pNewMachine->COMSETTER(MemorySize)(stack.ulMemorySizeMB);
2730 if (FAILED(rc)) throw rc;
2731
2732 /* VRAM */
2733 /* Get the recommended VRAM for this guest OS type */
2734 ULONG vramVBox;
2735 rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox);
2736 if (FAILED(rc)) throw rc;
2737
2738 /* Set the VRAM */
2739 rc = pNewMachine->COMSETTER(VRAMSize)(vramVBox);
2740 if (FAILED(rc)) throw rc;
2741
2742 // I/O APIC: Generic OVF has no setting for this. Enable it if we
2743 // import a Windows VM because if if Windows was installed without IOAPIC,
2744 // it will not mind finding an one later on, but if Windows was installed
2745 // _with_ an IOAPIC, it will bluescreen if it's not found
2746 if (!stack.fForceIOAPIC)
2747 {
2748 Bstr bstrFamilyId;
2749 rc = osType->COMGETTER(FamilyId)(bstrFamilyId.asOutParam());
2750 if (FAILED(rc)) throw rc;
2751 if (bstrFamilyId == "Windows")
2752 stack.fForceIOAPIC = true;
2753 }
2754
2755 if (stack.fForceIOAPIC)
2756 {
2757 ComPtr<IBIOSSettings> pBIOSSettings;
2758 rc = pNewMachine->COMGETTER(BIOSSettings)(pBIOSSettings.asOutParam());
2759 if (FAILED(rc)) throw rc;
2760
2761 rc = pBIOSSettings->COMSETTER(IOAPICEnabled)(TRUE);
2762 if (FAILED(rc)) throw rc;
2763 }
2764
2765 if (!stack.strAudioAdapter.isEmpty())
2766 if (stack.strAudioAdapter.compare("null", Utf8Str::CaseInsensitive) != 0)
2767 {
2768 uint32_t audio = RTStrToUInt32(stack.strAudioAdapter.c_str()); // should be 0 for AC97
2769 ComPtr<IAudioAdapter> audioAdapter;
2770 rc = pNewMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam());
2771 if (FAILED(rc)) throw rc;
2772 rc = audioAdapter->COMSETTER(Enabled)(true);
2773 if (FAILED(rc)) throw rc;
2774 rc = audioAdapter->COMSETTER(AudioController)(static_cast<AudioControllerType_T>(audio));
2775 if (FAILED(rc)) throw rc;
2776 }
2777
2778#ifdef VBOX_WITH_USB
2779 /* USB Controller */
2780 if (stack.fUSBEnabled)
2781 {
2782 ComPtr<IUSBController> usbController;
2783 rc = pNewMachine->AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, usbController.asOutParam());
2784 if (FAILED(rc)) throw rc;
2785 }
2786#endif /* VBOX_WITH_USB */
2787
2788 /* Change the network adapters */
2789 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
2790
2791 std::list<VirtualSystemDescriptionEntry*> vsdeNW = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
2792 if (vsdeNW.empty())
2793 {
2794 /* No network adapters, so we have to disable our default one */
2795 ComPtr<INetworkAdapter> nwVBox;
2796 rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam());
2797 if (FAILED(rc)) throw rc;
2798 rc = nwVBox->COMSETTER(Enabled)(false);
2799 if (FAILED(rc)) throw rc;
2800 }
2801 else if (vsdeNW.size() > maxNetworkAdapters)
2802 throw setError(VBOX_E_FILE_ERROR,
2803 tr("Too many network adapters: OVF requests %d network adapters, "
2804 "but VirtualBox only supports %d"),
2805 vsdeNW.size(), maxNetworkAdapters);
2806 else
2807 {
2808 list<VirtualSystemDescriptionEntry*>::const_iterator nwIt;
2809 size_t a = 0;
2810 for (nwIt = vsdeNW.begin();
2811 nwIt != vsdeNW.end();
2812 ++nwIt, ++a)
2813 {
2814 const VirtualSystemDescriptionEntry* pvsys = *nwIt;
2815
2816 const Utf8Str &nwTypeVBox = pvsys->strVBoxCurrent;
2817 uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str());
2818 ComPtr<INetworkAdapter> pNetworkAdapter;
2819 rc = pNewMachine->GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
2820 if (FAILED(rc)) throw rc;
2821 /* Enable the network card & set the adapter type */
2822 rc = pNetworkAdapter->COMSETTER(Enabled)(true);
2823 if (FAILED(rc)) throw rc;
2824 rc = pNetworkAdapter->COMSETTER(AdapterType)(static_cast<NetworkAdapterType_T>(tt1));
2825 if (FAILED(rc)) throw rc;
2826
2827 // default is NAT; change to "bridged" if extra conf says so
2828 if (pvsys->strExtraConfigCurrent.endsWith("type=Bridged", Utf8Str::CaseInsensitive))
2829 {
2830 /* Attach to the right interface */
2831 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged);
2832 if (FAILED(rc)) throw rc;
2833 ComPtr<IHost> host;
2834 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
2835 if (FAILED(rc)) throw rc;
2836 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
2837 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
2838 if (FAILED(rc)) throw rc;
2839 // We search for the first host network interface which
2840 // is usable for bridged networking
2841 for (size_t j = 0;
2842 j < nwInterfaces.size();
2843 ++j)
2844 {
2845 HostNetworkInterfaceType_T itype;
2846 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
2847 if (FAILED(rc)) throw rc;
2848 if (itype == HostNetworkInterfaceType_Bridged)
2849 {
2850 Bstr name;
2851 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
2852 if (FAILED(rc)) throw rc;
2853 /* Set the interface name to attach to */
2854 rc = pNetworkAdapter->COMSETTER(BridgedInterface)(name.raw());
2855 if (FAILED(rc)) throw rc;
2856 break;
2857 }
2858 }
2859 }
2860 /* Next test for host only interfaces */
2861 else if (pvsys->strExtraConfigCurrent.endsWith("type=HostOnly", Utf8Str::CaseInsensitive))
2862 {
2863 /* Attach to the right interface */
2864 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly);
2865 if (FAILED(rc)) throw rc;
2866 ComPtr<IHost> host;
2867 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
2868 if (FAILED(rc)) throw rc;
2869 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
2870 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
2871 if (FAILED(rc)) throw rc;
2872 // We search for the first host network interface which
2873 // is usable for host only networking
2874 for (size_t j = 0;
2875 j < nwInterfaces.size();
2876 ++j)
2877 {
2878 HostNetworkInterfaceType_T itype;
2879 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
2880 if (FAILED(rc)) throw rc;
2881 if (itype == HostNetworkInterfaceType_HostOnly)
2882 {
2883 Bstr name;
2884 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
2885 if (FAILED(rc)) throw rc;
2886 /* Set the interface name to attach to */
2887 rc = pNetworkAdapter->COMSETTER(HostOnlyInterface)(name.raw());
2888 if (FAILED(rc)) throw rc;
2889 break;
2890 }
2891 }
2892 }
2893 /* Next test for internal interfaces */
2894 else if (pvsys->strExtraConfigCurrent.endsWith("type=Internal", Utf8Str::CaseInsensitive))
2895 {
2896 /* Attach to the right interface */
2897 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Internal);
2898 if (FAILED(rc)) throw rc;
2899 }
2900 /* Next test for Generic interfaces */
2901 else if (pvsys->strExtraConfigCurrent.endsWith("type=Generic", Utf8Str::CaseInsensitive))
2902 {
2903 /* Attach to the right interface */
2904 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Generic);
2905 if (FAILED(rc)) throw rc;
2906 }
2907
2908 /* Next test for NAT network interfaces */
2909 else if (pvsys->strExtraConfigCurrent.endsWith("type=NATNetwork", Utf8Str::CaseInsensitive))
2910 {
2911 /* Attach to the right interface */
2912 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork);
2913 if (FAILED(rc)) throw rc;
2914 com::SafeIfaceArray<INATNetwork> nwNATNetworks;
2915 rc = mVirtualBox->COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(nwNATNetworks));
2916 if (FAILED(rc)) throw rc;
2917 // Pick the first NAT network (if there is any)
2918 if (nwNATNetworks.size())
2919 {
2920 Bstr name;
2921 rc = nwNATNetworks[0]->COMGETTER(NetworkName)(name.asOutParam());
2922 if (FAILED(rc)) throw rc;
2923 /* Set the NAT network name to attach to */
2924 rc = pNetworkAdapter->COMSETTER(NATNetwork)(name.raw());
2925 if (FAILED(rc)) throw rc;
2926 break;
2927 }
2928 }
2929 }
2930 }
2931
2932 // IDE Hard disk controller
2933 std::list<VirtualSystemDescriptionEntry*> vsdeHDCIDE =
2934 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerIDE);
2935 /*
2936 * In OVF (at least VMware's version of it), an IDE controller has two ports,
2937 * so VirtualBox's single IDE controller with two channels and two ports each counts as
2938 * two OVF IDE controllers -- so we accept one or two such IDE controllers
2939 */
2940 size_t cIDEControllers = vsdeHDCIDE.size();
2941 if (cIDEControllers > 2)
2942 throw setError(VBOX_E_FILE_ERROR,
2943 tr("Too many IDE controllers in OVF; import facility only supports two"));
2944 if (!vsdeHDCIDE.empty())
2945 {
2946 // one or two IDE controllers present in OVF: add one VirtualBox controller
2947 ComPtr<IStorageController> pController;
2948 rc = pNewMachine->AddStorageController(Bstr("IDE").raw(), StorageBus_IDE, pController.asOutParam());
2949 if (FAILED(rc)) throw rc;
2950
2951 const char *pcszIDEType = vsdeHDCIDE.front()->strVBoxCurrent.c_str();
2952 if (!strcmp(pcszIDEType, "PIIX3"))
2953 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX3);
2954 else if (!strcmp(pcszIDEType, "PIIX4"))
2955 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX4);
2956 else if (!strcmp(pcszIDEType, "ICH6"))
2957 rc = pController->COMSETTER(ControllerType)(StorageControllerType_ICH6);
2958 else
2959 throw setError(VBOX_E_FILE_ERROR,
2960 tr("Invalid IDE controller type \"%s\""),
2961 pcszIDEType);
2962 if (FAILED(rc)) throw rc;
2963 }
2964
2965 /* Hard disk controller SATA */
2966 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSATA =
2967 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSATA);
2968 if (vsdeHDCSATA.size() > 1)
2969 throw setError(VBOX_E_FILE_ERROR,
2970 tr("Too many SATA controllers in OVF; import facility only supports one"));
2971 if (!vsdeHDCSATA.empty())
2972 {
2973 ComPtr<IStorageController> pController;
2974 const Utf8Str &hdcVBox = vsdeHDCSATA.front()->strVBoxCurrent;
2975 if (hdcVBox == "AHCI")
2976 {
2977 rc = pNewMachine->AddStorageController(Bstr("SATA").raw(),
2978 StorageBus_SATA,
2979 pController.asOutParam());
2980 if (FAILED(rc)) throw rc;
2981 }
2982 else
2983 throw setError(VBOX_E_FILE_ERROR,
2984 tr("Invalid SATA controller type \"%s\""),
2985 hdcVBox.c_str());
2986 }
2987
2988 /* Hard disk controller SCSI */
2989 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSCSI =
2990 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI);
2991 if (vsdeHDCSCSI.size() > 1)
2992 throw setError(VBOX_E_FILE_ERROR,
2993 tr("Too many SCSI controllers in OVF; import facility only supports one"));
2994 if (!vsdeHDCSCSI.empty())
2995 {
2996 ComPtr<IStorageController> pController;
2997 Utf8Str strName("SCSI");
2998 StorageBus_T busType = StorageBus_SCSI;
2999 StorageControllerType_T controllerType;
3000 const Utf8Str &hdcVBox = vsdeHDCSCSI.front()->strVBoxCurrent;
3001 if (hdcVBox == "LsiLogic")
3002 controllerType = StorageControllerType_LsiLogic;
3003 else if (hdcVBox == "LsiLogicSas")
3004 {
3005 // OVF treats LsiLogicSas as a SCSI controller but VBox considers it a class of its own
3006 strName = "SAS";
3007 busType = StorageBus_SAS;
3008 controllerType = StorageControllerType_LsiLogicSas;
3009 }
3010 else if (hdcVBox == "BusLogic")
3011 controllerType = StorageControllerType_BusLogic;
3012 else
3013 throw setError(VBOX_E_FILE_ERROR,
3014 tr("Invalid SCSI controller type \"%s\""),
3015 hdcVBox.c_str());
3016
3017 rc = pNewMachine->AddStorageController(Bstr(strName).raw(), busType, pController.asOutParam());
3018 if (FAILED(rc)) throw rc;
3019 rc = pController->COMSETTER(ControllerType)(controllerType);
3020 if (FAILED(rc)) throw rc;
3021 }
3022
3023 /* Hard disk controller SAS */
3024 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSAS =
3025 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSAS);
3026 if (vsdeHDCSAS.size() > 1)
3027 throw setError(VBOX_E_FILE_ERROR,
3028 tr("Too many SAS controllers in OVF; import facility only supports one"));
3029 if (!vsdeHDCSAS.empty())
3030 {
3031 ComPtr<IStorageController> pController;
3032 rc = pNewMachine->AddStorageController(Bstr(L"SAS").raw(),
3033 StorageBus_SAS,
3034 pController.asOutParam());
3035 if (FAILED(rc)) throw rc;
3036 rc = pController->COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas);
3037 if (FAILED(rc)) throw rc;
3038 }
3039
3040 /* Now its time to register the machine before we add any hard disks */
3041 rc = mVirtualBox->RegisterMachine(pNewMachine);
3042 if (FAILED(rc)) throw rc;
3043
3044 // store new machine for roll-back in case of errors
3045 Bstr bstrNewMachineId;
3046 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
3047 if (FAILED(rc)) throw rc;
3048 Guid uuidNewMachine(bstrNewMachineId);
3049 m->llGuidsMachinesCreated.push_back(uuidNewMachine);
3050
3051 // Add floppies and CD-ROMs to the appropriate controllers.
3052 std::list<VirtualSystemDescriptionEntry*> vsdeFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy);
3053 if (vsdeFloppy.size() > 1)
3054 throw setError(VBOX_E_FILE_ERROR,
3055 tr("Too many floppy controllers in OVF; import facility only supports one"));
3056 std::list<VirtualSystemDescriptionEntry*> vsdeCDROM = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
3057 if ( !vsdeFloppy.empty()
3058 || !vsdeCDROM.empty()
3059 )
3060 {
3061 // If there's an error here we need to close the session, so
3062 // we need another try/catch block.
3063
3064 try
3065 {
3066 // to attach things we need to open a session for the new machine
3067 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
3068 if (FAILED(rc)) throw rc;
3069 stack.fSessionOpen = true;
3070
3071 ComPtr<IMachine> sMachine;
3072 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
3073 if (FAILED(rc)) throw rc;
3074
3075 // floppy first
3076 if (vsdeFloppy.size() == 1)
3077 {
3078 ComPtr<IStorageController> pController;
3079 rc = sMachine->AddStorageController(Bstr("Floppy").raw(),
3080 StorageBus_Floppy,
3081 pController.asOutParam());
3082 if (FAILED(rc)) throw rc;
3083
3084 Bstr bstrName;
3085 rc = pController->COMGETTER(Name)(bstrName.asOutParam());
3086 if (FAILED(rc)) throw rc;
3087
3088 // this is for rollback later
3089 MyHardDiskAttachment mhda;
3090 mhda.pMachine = pNewMachine;
3091 mhda.controllerName = bstrName;
3092 mhda.lControllerPort = 0;
3093 mhda.lDevice = 0;
3094
3095 Log(("Attaching floppy\n"));
3096
3097 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),
3098 mhda.lControllerPort,
3099 mhda.lDevice,
3100 DeviceType_Floppy,
3101 NULL);
3102 if (FAILED(rc)) throw rc;
3103
3104 stack.llHardDiskAttachments.push_back(mhda);
3105 }
3106
3107 rc = sMachine->SaveSettings();
3108 if (FAILED(rc)) throw rc;
3109
3110 // only now that we're done with all disks, close the session
3111 rc = stack.pSession->UnlockMachine();
3112 if (FAILED(rc)) throw rc;
3113 stack.fSessionOpen = false;
3114 }
3115 catch(HRESULT aRC)
3116 {
3117 com::ErrorInfo info;
3118
3119 if (stack.fSessionOpen)
3120 stack.pSession->UnlockMachine();
3121
3122 if (info.isFullAvailable())
3123 throw setError(aRC, Utf8Str(info.getText()).c_str());
3124 else
3125 throw setError(aRC, "Unknown error during OVF import");
3126 }
3127 }
3128
3129 // create the hard disks & connect them to the appropriate controllers
3130 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
3131 if (!avsdeHDs.empty())
3132 {
3133 // If there's an error here we need to close the session, so
3134 // we need another try/catch block.
3135 try
3136 {
3137#ifdef LOG_ENABLED
3138 if (LogIsEnabled())
3139 {
3140 size_t i = 0;
3141 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3142 itHD != avsdeHDs.end(); ++itHD, i++)
3143 Log(("avsdeHDs[%zu]: strRef=%s strOvf=%s\n", i, (*itHD)->strRef.c_str(), (*itHD)->strOvf.c_str()));
3144 i = 0;
3145 for (ovf::DiskImagesMap::const_iterator itDisk = stack.mapDisks.begin(); itDisk != stack.mapDisks.end(); ++itDisk)
3146 Log(("mapDisks[%zu]: strDiskId=%s strHref=%s\n",
3147 i, itDisk->second.strDiskId.c_str(), itDisk->second.strHref.c_str()));
3148
3149 }
3150#endif
3151
3152 // to attach things we need to open a session for the new machine
3153 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
3154 if (FAILED(rc)) throw rc;
3155 stack.fSessionOpen = true;
3156
3157 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
3158 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
3159 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
3160 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
3161
3162
3163 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
3164 std::set<RTCString> disksResolvedNames;
3165
3166 uint32_t cImportedDisks = 0;
3167
3168 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
3169 {
3170/** @todo r=bird: Most of the code here is duplicated in the other machine
3171 * import method, factor out. */
3172 ovf::DiskImage diCurrent = oit->second;
3173
3174 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
3175 /* Iterate over all given disk images of the virtual system
3176 * disks description. We need to find the target disk path,
3177 * which could be changed by the user. */
3178 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
3179 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3180 itHD != avsdeHDs.end();
3181 ++itHD)
3182 {
3183 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
3184 if (vsdeHD->strRef == diCurrent.strDiskId)
3185 {
3186 vsdeTargetHD = vsdeHD;
3187 break;
3188 }
3189 }
3190 if (!vsdeTargetHD)
3191 {
3192 /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
3193 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
3194 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
3195 NOREF(vmNameEntry);
3196 ++oit;
3197 continue;
3198 }
3199
3200 //diCurrent.strDiskId contains the disk identifier (e.g. "vmdisk1"), which should exist
3201 //in the virtual system's disks map under that ID and also in the global images map
3202 ovf::VirtualDisksMap::const_iterator itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
3203 if (itVDisk == vsysThis.mapVirtualDisks.end())
3204 throw setError(E_FAIL,
3205 tr("Internal inconsistency looking up disk image '%s'"),
3206 diCurrent.strHref.c_str());
3207
3208 /*
3209 * preliminary check availability of the image
3210 * This step is useful if image is placed in the OVA (TAR) package
3211 */
3212 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
3213 {
3214 /* It means that we possibly have imported the storage earlier on the previous loop steps*/
3215 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
3216 if (h != disksResolvedNames.end())
3217 {
3218 /* Yes, disk name was found, we can skip it*/
3219 ++oit;
3220 continue;
3221 }
3222l_skipped:
3223 rc = i_preCheckImageAvailability(stack);
3224 if (SUCCEEDED(rc))
3225 {
3226 /* current opened file isn't the same as passed one */
3227 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
3228 {
3229 /* availableImage contains the disk file reference (e.g. "disk1.vmdk"), which should
3230 * exist in the global images map.
3231 * And find the disk from the OVF's disk list */
3232 ovf::DiskImagesMap::const_iterator itDiskImage;
3233 for (itDiskImage = stack.mapDisks.begin();
3234 itDiskImage != stack.mapDisks.end();
3235 itDiskImage++)
3236 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
3237 Utf8Str::CaseInsensitive) == 0)
3238 break;
3239 if (itDiskImage == stack.mapDisks.end())
3240 {
3241 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
3242 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
3243 goto l_skipped;
3244 }
3245
3246 /* replace with a new found disk image */
3247 diCurrent = *(&itDiskImage->second);
3248
3249 /*
3250 * Again iterate over all given disk images of the virtual system
3251 * disks description using the found disk image
3252 */
3253 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3254 itHD != avsdeHDs.end();
3255 ++itHD)
3256 {
3257 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
3258 if (vsdeHD->strRef == diCurrent.strDiskId)
3259 {
3260 vsdeTargetHD = vsdeHD;
3261 break;
3262 }
3263 }
3264
3265 /*
3266 * in this case it's an error because something is wrong with the OVF description file.
3267 * May be VBox imports OVA package with wrong file sequence inside the archive.
3268 */
3269 if (!vsdeTargetHD)
3270 throw setError(E_FAIL,
3271 tr("Internal inconsistency looking up disk image '%s'"),
3272 diCurrent.strHref.c_str());
3273
3274 itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
3275 if (itVDisk == vsysThis.mapVirtualDisks.end())
3276 throw setError(E_FAIL,
3277 tr("Internal inconsistency looking up disk image '%s'"),
3278 diCurrent.strHref.c_str());
3279 }
3280 else
3281 {
3282 ++oit;
3283 }
3284 }
3285 else
3286 {
3287 ++oit;
3288 continue;
3289 }
3290 }
3291 else
3292 {
3293 /* just continue with normal files*/
3294 ++oit;
3295 }
3296
3297 /* very important to store disk name for the next checks */
3298 disksResolvedNames.insert(diCurrent.strHref);
3299////// end of duplicated code.
3300 const ovf::VirtualDisk &ovfVdisk = itVDisk->second;
3301
3302 ComObjPtr<Medium> pTargetHD;
3303
3304 Utf8Str savedVBoxCurrent = vsdeTargetHD->strVBoxCurrent;
3305
3306 i_importOneDiskImage(diCurrent,
3307 vsdeTargetHD->strVBoxCurrent,
3308 pTargetHD,
3309 stack);
3310
3311 // now use the new uuid to attach the disk image to our new machine
3312 ComPtr<IMachine> sMachine;
3313 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
3314 if (FAILED(rc))
3315 throw rc;
3316
3317 // find the hard disk controller to which we should attach
3318 ovf::HardDiskController hdc = (*vsysThis.mapControllers.find(ovfVdisk.idController)).second;
3319
3320 // this is for rollback later
3321 MyHardDiskAttachment mhda;
3322 mhda.pMachine = pNewMachine;
3323
3324 i_convertDiskAttachmentValues(hdc,
3325 ovfVdisk.ulAddressOnParent,
3326 mhda.controllerName,
3327 mhda.lControllerPort,
3328 mhda.lDevice);
3329
3330 Log(("Attaching disk %s to port %d on device %d\n",
3331 vsdeTargetHD->strVBoxCurrent.c_str(), mhda.lControllerPort, mhda.lDevice));
3332
3333 ComObjPtr<MediumFormat> mediumFormat;
3334 rc = i_findMediumFormatFromDiskImage(diCurrent, mediumFormat);
3335 if (FAILED(rc))
3336 throw rc;
3337
3338 Bstr bstrFormatName;
3339 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
3340 if (FAILED(rc))
3341 throw rc;
3342
3343 Utf8Str vdf = Utf8Str(bstrFormatName);
3344
3345 if (vdf.compare("RAW", Utf8Str::CaseInsensitive) == 0)
3346 {
3347 ComPtr<IMedium> dvdImage(pTargetHD);
3348
3349 rc = mVirtualBox->OpenMedium(Bstr(vsdeTargetHD->strVBoxCurrent).raw(),
3350 DeviceType_DVD,
3351 AccessMode_ReadWrite,
3352 false,
3353 dvdImage.asOutParam());
3354
3355 if (FAILED(rc))
3356 throw rc;
3357
3358 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
3359 mhda.lControllerPort, // long controllerPort
3360 mhda.lDevice, // long device
3361 DeviceType_DVD, // DeviceType_T type
3362 dvdImage);
3363 if (FAILED(rc))
3364 throw rc;
3365 }
3366 else
3367 {
3368 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
3369 mhda.lControllerPort, // long controllerPort
3370 mhda.lDevice, // long device
3371 DeviceType_HardDisk, // DeviceType_T type
3372 pTargetHD);
3373
3374 if (FAILED(rc))
3375 throw rc;
3376 }
3377
3378 stack.llHardDiskAttachments.push_back(mhda);
3379
3380 rc = sMachine->SaveSettings();
3381 if (FAILED(rc))
3382 throw rc;
3383
3384 /* restore */
3385 vsdeTargetHD->strVBoxCurrent = savedVBoxCurrent;
3386
3387 ++cImportedDisks;
3388
3389 } // end while(oit != stack.mapDisks.end())
3390
3391 /*
3392 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
3393 */
3394 if(cImportedDisks < avsdeHDs.size())
3395 {
3396 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
3397 vmNameEntry->strOvf.c_str()));
3398 }
3399
3400 // only now that we're done with all disks, close the session
3401 rc = stack.pSession->UnlockMachine();
3402 if (FAILED(rc))
3403 throw rc;
3404 stack.fSessionOpen = false;
3405 }
3406 catch(HRESULT aRC)
3407 {
3408 com::ErrorInfo info;
3409 if (stack.fSessionOpen)
3410 stack.pSession->UnlockMachine();
3411
3412 if (info.isFullAvailable())
3413 throw setError(aRC, Utf8Str(info.getText()).c_str());
3414 else
3415 throw setError(aRC, "Unknown error during OVF import");
3416 }
3417 }
3418 LogFlowFuncLeave();
3419}
3420
3421/**
3422 * Imports one OVF virtual system (described by a vbox:Machine tag represented by the given config
3423 * structure) into VirtualBox by creating an IMachine instance, which is returned.
3424 *
3425 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
3426 * up any leftovers from this function. For this, the given ImportStack instance has received information
3427 * about what needs cleaning up (to support rollback).
3428 *
3429 * The machine config stored in the settings::MachineConfigFile structure contains the UUIDs of
3430 * the disk attachments used by the machine when it was exported. We also add vbox:uuid attributes
3431 * to the OVF disks sections so we can look them up. While importing these UUIDs into a second host
3432 * will most probably work, reimporting them into the same host will cause conflicts, so we always
3433 * generate new ones on import. This involves the following:
3434 *
3435 * 1) Scan the machine config for disk attachments.
3436 *
3437 * 2) For each disk attachment found, look up the OVF disk image from the disk references section
3438 * and import the disk into VirtualBox, which creates a new UUID for it. In the machine config,
3439 * replace the old UUID with the new one.
3440 *
3441 * 3) Change the machine config according to the OVF virtual system descriptions, in case the
3442 * caller has modified them using setFinalValues().
3443 *
3444 * 4) Create the VirtualBox machine with the modfified machine config.
3445 *
3446 * @param vsdescThis
3447 * @param pReturnNewMachine
3448 * @param stack
3449 */
3450void Appliance::i_importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
3451 ComPtr<IMachine> &pReturnNewMachine,
3452 ImportStack &stack)
3453{
3454 LogFlowFuncEnter();
3455 Assert(vsdescThis->m->pConfig);
3456
3457 HRESULT rc = S_OK;
3458
3459 settings::MachineConfigFile &config = *vsdescThis->m->pConfig;
3460
3461 /*
3462 * step 1): modify machine config according to OVF config, in case the user
3463 * has modified them using setFinalValues()
3464 */
3465
3466 /* OS Type */
3467 config.machineUserData.strOsType = stack.strOsTypeVBox;
3468 /* Groups */
3469 if (stack.strPrimaryGroup.isEmpty() || stack.strPrimaryGroup == "/")
3470 {
3471 config.machineUserData.llGroups.clear();
3472 config.machineUserData.llGroups.push_back("/");
3473 }
3474 else
3475 {
3476 /* Replace the primary group if there is one, otherwise add it. */
3477 if (config.machineUserData.llGroups.size())
3478 config.machineUserData.llGroups.pop_front();
3479 config.machineUserData.llGroups.push_front(stack.strPrimaryGroup);
3480 }
3481 /* Description */
3482 config.machineUserData.strDescription = stack.strDescription;
3483 /* CPU count & extented attributes */
3484 config.hardwareMachine.cCPUs = stack.cCPUs;
3485 if (stack.fForceIOAPIC)
3486 config.hardwareMachine.fHardwareVirt = true;
3487 if (stack.fForceIOAPIC)
3488 config.hardwareMachine.biosSettings.fIOAPICEnabled = true;
3489 /* RAM size */
3490 config.hardwareMachine.ulMemorySizeMB = stack.ulMemorySizeMB;
3491
3492/*
3493 <const name="HardDiskControllerIDE" value="14" />
3494 <const name="HardDiskControllerSATA" value="15" />
3495 <const name="HardDiskControllerSCSI" value="16" />
3496 <const name="HardDiskControllerSAS" value="17" />
3497*/
3498
3499#ifdef VBOX_WITH_USB
3500 /* USB controller */
3501 if (stack.fUSBEnabled)
3502 {
3503 /** @todo r=klaus add support for arbitrary USB controller types, this can't handle
3504 * multiple controllers due to its design anyway */
3505 /* Usually the OHCI controller is enabled already, need to check. But
3506 * do this only if there is no xHCI controller. */
3507 bool fOHCIEnabled = false;
3508 bool fXHCIEnabled = false;
3509 settings::USBControllerList &llUSBControllers = config.hardwareMachine.usbSettings.llUSBControllers;
3510 settings::USBControllerList::iterator it;
3511 for (it = llUSBControllers.begin(); it != llUSBControllers.end(); ++it)
3512 {
3513 if (it->enmType == USBControllerType_OHCI)
3514 fOHCIEnabled = true;
3515 if (it->enmType == USBControllerType_XHCI)
3516 fXHCIEnabled = true;
3517 }
3518
3519 if (!fXHCIEnabled && !fOHCIEnabled)
3520 {
3521 settings::USBController ctrl;
3522 ctrl.strName = "OHCI";
3523 ctrl.enmType = USBControllerType_OHCI;
3524
3525 llUSBControllers.push_back(ctrl);
3526 }
3527 }
3528 else
3529 config.hardwareMachine.usbSettings.llUSBControllers.clear();
3530#endif
3531 /* Audio adapter */
3532 if (stack.strAudioAdapter.isNotEmpty())
3533 {
3534 config.hardwareMachine.audioAdapter.fEnabled = true;
3535 config.hardwareMachine.audioAdapter.controllerType = (AudioControllerType_T)stack.strAudioAdapter.toUInt32();
3536 }
3537 else
3538 config.hardwareMachine.audioAdapter.fEnabled = false;
3539 /* Network adapter */
3540 settings::NetworkAdaptersList &llNetworkAdapters = config.hardwareMachine.llNetworkAdapters;
3541 /* First disable all network cards, they will be enabled below again. */
3542 settings::NetworkAdaptersList::iterator it1;
3543 bool fKeepAllMACs = m->optListImport.contains(ImportOptions_KeepAllMACs);
3544 bool fKeepNATMACs = m->optListImport.contains(ImportOptions_KeepNATMACs);
3545 for (it1 = llNetworkAdapters.begin(); it1 != llNetworkAdapters.end(); ++it1)
3546 {
3547 it1->fEnabled = false;
3548 if (!( fKeepAllMACs
3549 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NAT)
3550 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NATNetwork)))
3551 /* Force generation of new MAC address below. */
3552 it1->strMACAddress.setNull();
3553 }
3554 /* Now iterate over all network entries. */
3555 std::list<VirtualSystemDescriptionEntry*> avsdeNWs = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
3556 if (!avsdeNWs.empty())
3557 {
3558 /* Iterate through all network adapter entries and search for the
3559 * corresponding one in the machine config. If one is found, configure
3560 * it based on the user settings. */
3561 list<VirtualSystemDescriptionEntry*>::const_iterator itNW;
3562 for (itNW = avsdeNWs.begin();
3563 itNW != avsdeNWs.end();
3564 ++itNW)
3565 {
3566 VirtualSystemDescriptionEntry *vsdeNW = *itNW;
3567 if ( vsdeNW->strExtraConfigCurrent.startsWith("slot=", Utf8Str::CaseInsensitive)
3568 && vsdeNW->strExtraConfigCurrent.length() > 6)
3569 {
3570 uint32_t iSlot = vsdeNW->strExtraConfigCurrent.substr(5).toUInt32();
3571 /* Iterate through all network adapters in the machine config. */
3572 for (it1 = llNetworkAdapters.begin();
3573 it1 != llNetworkAdapters.end();
3574 ++it1)
3575 {
3576 /* Compare the slots. */
3577 if (it1->ulSlot == iSlot)
3578 {
3579 it1->fEnabled = true;
3580 if (it1->strMACAddress.isEmpty())
3581 Host::i_generateMACAddress(it1->strMACAddress);
3582 it1->type = (NetworkAdapterType_T)vsdeNW->strVBoxCurrent.toUInt32();
3583 break;
3584 }
3585 }
3586 }
3587 }
3588 }
3589
3590 /* Floppy controller */
3591 bool fFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy).size() > 0;
3592 /* DVD controller */
3593 bool fDVD = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM).size() > 0;
3594 /* Iterate over all storage controller check the attachments and remove
3595 * them when necessary. Also detect broken configs with more than one
3596 * attachment. Old VirtualBox versions (prior to 3.2.10) had all disk
3597 * attachments pointing to the last hard disk image, which causes import
3598 * failures. A long fixed bug, however the OVF files are long lived. */
3599 settings::StorageControllersList &llControllers = config.hardwareMachine.storage.llStorageControllers;
3600 Guid hdUuid;
3601 uint32_t cDisks = 0;
3602 bool fInconsistent = false;
3603 bool fRepairDuplicate = false;
3604 settings::StorageControllersList::iterator it3;
3605 for (it3 = llControllers.begin();
3606 it3 != llControllers.end();
3607 ++it3)
3608 {
3609 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
3610 settings::AttachedDevicesList::iterator it4 = llAttachments.begin();
3611 while (it4 != llAttachments.end())
3612 {
3613 if ( ( !fDVD
3614 && it4->deviceType == DeviceType_DVD)
3615 ||
3616 ( !fFloppy
3617 && it4->deviceType == DeviceType_Floppy))
3618 {
3619 it4 = llAttachments.erase(it4);
3620 continue;
3621 }
3622 else if (it4->deviceType == DeviceType_HardDisk)
3623 {
3624 const Guid &thisUuid = it4->uuid;
3625 cDisks++;
3626 if (cDisks == 1)
3627 {
3628 if (hdUuid.isZero())
3629 hdUuid = thisUuid;
3630 else
3631 fInconsistent = true;
3632 }
3633 else
3634 {
3635 if (thisUuid.isZero())
3636 fInconsistent = true;
3637 else if (thisUuid == hdUuid)
3638 fRepairDuplicate = true;
3639 }
3640 }
3641 ++it4;
3642 }
3643 }
3644 /* paranoia... */
3645 if (fInconsistent || cDisks == 1)
3646 fRepairDuplicate = false;
3647
3648 /*
3649 * step 2: scan the machine config for media attachments
3650 */
3651 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
3652 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
3653 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
3654 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
3655
3656 /* Get all hard disk descriptions. */
3657 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
3658 std::list<VirtualSystemDescriptionEntry*>::iterator avsdeHDsIt = avsdeHDs.begin();
3659 /* paranoia - if there is no 1:1 match do not try to repair. */
3660 if (cDisks != avsdeHDs.size())
3661 fRepairDuplicate = false;
3662
3663 // there must be an image in the OVF disk structs with the same UUID
3664
3665 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
3666 std::set<RTCString> disksResolvedNames;
3667
3668 uint32_t cImportedDisks = 0;
3669
3670 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
3671 {
3672/** @todo r=bird: Most of the code here is duplicated in the other machine
3673 * import method, factor out. */
3674 ovf::DiskImage diCurrent = oit->second;
3675
3676 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
3677
3678 /* Iterate over all given disk images of the virtual system
3679 * disks description. We need to find the target disk path,
3680 * which could be changed by the user. */
3681 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
3682 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3683 itHD != avsdeHDs.end();
3684 ++itHD)
3685 {
3686 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
3687 if (vsdeHD->strRef == oit->first)
3688 {
3689 vsdeTargetHD = vsdeHD;
3690 break;
3691 }
3692 }
3693 if (!vsdeTargetHD)
3694 {
3695 /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
3696 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
3697 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
3698 NOREF(vmNameEntry);
3699 ++oit;
3700 continue;
3701 }
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711 /*
3712 * preliminary check availability of the image
3713 * This step is useful if image is placed in the OVA (TAR) package
3714 */
3715 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
3716 {
3717 /* It means that we possibly have imported the storage earlier on a previous loop step. */
3718 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
3719 if (h != disksResolvedNames.end())
3720 {
3721 /* Yes, disk name was found, we can skip it*/
3722 ++oit;
3723 continue;
3724 }
3725l_skipped:
3726 rc = i_preCheckImageAvailability(stack);
3727 if (SUCCEEDED(rc))
3728 {
3729 /* current opened file isn't the same as passed one */
3730 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
3731 {
3732 // availableImage contains the disk identifier (e.g. "vmdisk1"), which should exist
3733 // in the virtual system's disks map under that ID and also in the global images map
3734 // and find the disk from the OVF's disk list
3735 ovf::DiskImagesMap::const_iterator itDiskImage;
3736 for (itDiskImage = stack.mapDisks.begin();
3737 itDiskImage != stack.mapDisks.end();
3738 itDiskImage++)
3739 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
3740 Utf8Str::CaseInsensitive) == 0)
3741 break;
3742 if (itDiskImage == stack.mapDisks.end())
3743 {
3744 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
3745 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
3746 goto l_skipped;
3747 }
3748 //throw setError(E_FAIL,
3749 // tr("Internal inconsistency looking up disk image '%s'. "
3750 // "Check compliance OVA package structure and file names "
3751 // "references in the section <References> in the OVF file."),
3752 // stack.pszOvaLookAheadName);
3753
3754 /* replace with a new found disk image */
3755 diCurrent = *(&itDiskImage->second);
3756
3757 /*
3758 * Again iterate over all given disk images of the virtual system
3759 * disks description using the found disk image
3760 */
3761 vsdeTargetHD = NULL;
3762 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3763 itHD != avsdeHDs.end();
3764 ++itHD)
3765 {
3766 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
3767 if (vsdeHD->strRef == diCurrent.strDiskId)
3768 {
3769 vsdeTargetHD = vsdeHD;
3770 break;
3771 }
3772 }
3773
3774 /*
3775 * in this case it's an error because something is wrong with the OVF description file.
3776 * May be VBox imports OVA package with wrong file sequence inside the archive.
3777 */
3778 if (!vsdeTargetHD)
3779 throw setError(E_FAIL,
3780 tr("Internal inconsistency looking up disk image '%s'"),
3781 diCurrent.strHref.c_str());
3782
3783
3784
3785
3786
3787 }
3788 else
3789 {
3790 ++oit;
3791 }
3792 }
3793 else
3794 {
3795 ++oit;
3796 continue;
3797 }
3798 }
3799 else
3800 {
3801 /* just continue with normal files*/
3802 ++oit;
3803 }
3804
3805 /* Important! to store disk name for the next checks */
3806 disksResolvedNames.insert(diCurrent.strHref);
3807////// end of duplicated code.
3808 // there must be an image in the OVF disk structs with the same UUID
3809 bool fFound = false;
3810 Utf8Str strUuid;
3811
3812 // for each storage controller...
3813 for (settings::StorageControllersList::iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
3814 sit != config.hardwareMachine.storage.llStorageControllers.end();
3815 ++sit)
3816 {
3817 settings::StorageController &sc = *sit;
3818
3819 // find the OVF virtual system description entry for this storage controller
3820/** @todo
3821 * r=bird: What on earh this is switch supposed to do? (I've added the default:break;, so don't
3822 * get confused by it.) Kind of looks like it's supposed to do something error handling related
3823 * in the default case...
3824 */
3825 switch (sc.storageBus)
3826 {
3827 case StorageBus_SATA:
3828 break;
3829 case StorageBus_SCSI:
3830 break;
3831 case StorageBus_IDE:
3832 break;
3833 case StorageBus_SAS:
3834 break;
3835 default: break; /* Shut up MSC. */
3836 }
3837
3838 // for each medium attachment to this controller...
3839 for (settings::AttachedDevicesList::iterator dit = sc.llAttachedDevices.begin();
3840 dit != sc.llAttachedDevices.end();
3841 ++dit)
3842 {
3843 settings::AttachedDevice &d = *dit;
3844
3845 if (d.uuid.isZero())
3846 // empty DVD and floppy media
3847 continue;
3848
3849 // When repairing a broken VirtualBox xml config section (written
3850 // by VirtualBox versions earlier than 3.2.10) assume the disks
3851 // show up in the same order as in the OVF description.
3852 if (fRepairDuplicate)
3853 {
3854 VirtualSystemDescriptionEntry *vsdeHD = *avsdeHDsIt;
3855 ovf::DiskImagesMap::const_iterator itDiskImage = stack.mapDisks.find(vsdeHD->strRef);
3856 if (itDiskImage != stack.mapDisks.end())
3857 {
3858 const ovf::DiskImage &di = itDiskImage->second;
3859 d.uuid = Guid(di.uuidVBox);
3860 }
3861 ++avsdeHDsIt;
3862 }
3863
3864 // convert the Guid to string
3865 strUuid = d.uuid.toString();
3866
3867 if (diCurrent.uuidVBox != strUuid)
3868 {
3869 continue;
3870 }
3871
3872 /*
3873 * step 3: import disk
3874 */
3875 Utf8Str savedVBoxCurrent = vsdeTargetHD->strVBoxCurrent;
3876 ComObjPtr<Medium> pTargetHD;
3877
3878 i_importOneDiskImage(diCurrent,
3879 vsdeTargetHD->strVBoxCurrent,
3880 pTargetHD,
3881 stack);
3882
3883 Bstr hdId;
3884
3885 ComObjPtr<MediumFormat> mediumFormat;
3886 rc = i_findMediumFormatFromDiskImage(diCurrent, mediumFormat);
3887 if (FAILED(rc))
3888 throw rc;
3889
3890 Bstr bstrFormatName;
3891 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
3892 if (FAILED(rc))
3893 throw rc;
3894
3895 Utf8Str vdf = Utf8Str(bstrFormatName);
3896
3897 if (vdf.compare("RAW", Utf8Str::CaseInsensitive) == 0)
3898 {
3899 ComPtr<IMedium> dvdImage(pTargetHD);
3900
3901 rc = mVirtualBox->OpenMedium(Bstr(vsdeTargetHD->strVBoxCurrent).raw(),
3902 DeviceType_DVD,
3903 AccessMode_ReadWrite,
3904 false,
3905 dvdImage.asOutParam());
3906
3907 if (FAILED(rc)) throw rc;
3908
3909 // ... and replace the old UUID in the machine config with the one of
3910 // the imported disk that was just created
3911 rc = dvdImage->COMGETTER(Id)(hdId.asOutParam());
3912 if (FAILED(rc)) throw rc;
3913 }
3914 else
3915 {
3916 // ... and replace the old UUID in the machine config with the one of
3917 // the imported disk that was just created
3918 rc = pTargetHD->COMGETTER(Id)(hdId.asOutParam());
3919 if (FAILED(rc)) throw rc;
3920 }
3921
3922 /* restore */
3923 vsdeTargetHD->strVBoxCurrent = savedVBoxCurrent;
3924
3925 /*
3926 * 1. saving original UUID for restoring in case of failure.
3927 * 2. replacement of original UUID by new UUID in the current VM config (settings::MachineConfigFile).
3928 */
3929 {
3930 rc = stack.saveOriginalUUIDOfAttachedDevice(d, Utf8Str(hdId));
3931 d.uuid = hdId;
3932 }
3933
3934 fFound = true;
3935 break;
3936 } // for (settings::AttachedDevicesList::const_iterator dit = sc.llAttachedDevices.begin();
3937 } // for (settings::StorageControllersList::const_iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
3938
3939 // no disk with such a UUID found:
3940 if (!fFound)
3941 throw setError(E_FAIL,
3942 tr("<vbox:Machine> element in OVF contains a medium attachment for the disk image %s "
3943 "but the OVF describes no such image"),
3944 strUuid.c_str());
3945
3946 ++cImportedDisks;
3947
3948 }// while(oit != stack.mapDisks.end())
3949
3950
3951 /*
3952 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
3953 */
3954 if(cImportedDisks < avsdeHDs.size())
3955 {
3956 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
3957 vmNameEntry->strOvf.c_str()));
3958 }
3959
3960 /*
3961 * step 4): create the machine and have it import the config
3962 */
3963
3964 ComObjPtr<Machine> pNewMachine;
3965 rc = pNewMachine.createObject();
3966 if (FAILED(rc)) throw rc;
3967
3968 // this magic constructor fills the new machine object with the MachineConfig
3969 // instance that we created from the vbox:Machine
3970 rc = pNewMachine->init(mVirtualBox,
3971 stack.strNameVBox,// name from OVF preparations; can be suffixed to avoid duplicates
3972 stack.strSettingsFilename,
3973 config); // the whole machine config
3974 if (FAILED(rc)) throw rc;
3975
3976 pReturnNewMachine = ComPtr<IMachine>(pNewMachine);
3977
3978 // and register it
3979 rc = mVirtualBox->RegisterMachine(pNewMachine);
3980 if (FAILED(rc)) throw rc;
3981
3982 // store new machine for roll-back in case of errors
3983 Bstr bstrNewMachineId;
3984 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
3985 if (FAILED(rc)) throw rc;
3986 m->llGuidsMachinesCreated.push_back(Guid(bstrNewMachineId));
3987
3988 LogFlowFuncLeave();
3989}
3990
3991/**
3992 * @throws HRESULT errors.
3993 */
3994void Appliance::i_importMachines(ImportStack &stack)
3995{
3996 // this is safe to access because this thread only gets started
3997 const ovf::OVFReader &reader = *m->pReader;
3998
3999 // create a session for the machine + disks we manipulate below
4000 HRESULT rc = stack.pSession.createInprocObject(CLSID_Session);
4001 ComAssertComRCThrowRC(rc);
4002
4003 list<ovf::VirtualSystem>::const_iterator it;
4004 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it1;
4005 /* Iterate through all virtual systems of that appliance */
4006 size_t i = 0;
4007 for (it = reader.m_llVirtualSystems.begin(), it1 = m->virtualSystemDescriptions.begin();
4008 it != reader.m_llVirtualSystems.end() && it1 != m->virtualSystemDescriptions.end();
4009 ++it, ++it1, ++i)
4010 {
4011 const ovf::VirtualSystem &vsysThis = *it;
4012 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it1);
4013
4014 ComPtr<IMachine> pNewMachine;
4015
4016 // there are two ways in which we can create a vbox machine from OVF:
4017 // -- either this OVF was written by vbox 3.2 or later, in which case there is a <vbox:Machine> element
4018 // in the <VirtualSystem>; then the VirtualSystemDescription::Data has a settings::MachineConfigFile
4019 // with all the machine config pretty-parsed;
4020 // -- or this is an OVF from an older vbox or an external source, and then we need to translate the
4021 // VirtualSystemDescriptionEntry and do import work
4022
4023 // Even for the vbox:Machine case, there are a number of configuration items that will be taken from
4024 // the OVF because otherwise the "override import parameters" mechanism in the GUI won't work.
4025
4026 // VM name
4027 std::list<VirtualSystemDescriptionEntry*> vsdeName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
4028 if (vsdeName.size() < 1)
4029 throw setError(VBOX_E_FILE_ERROR,
4030 tr("Missing VM name"));
4031 stack.strNameVBox = vsdeName.front()->strVBoxCurrent;
4032
4033 // Primary group, which is entirely optional.
4034 stack.strPrimaryGroup.setNull();
4035 std::list<VirtualSystemDescriptionEntry*> vsdePrimaryGroup = vsdescThis->i_findByType(VirtualSystemDescriptionType_PrimaryGroup);
4036 if (vsdePrimaryGroup.size() >= 1)
4037 {
4038 stack.strPrimaryGroup = vsdePrimaryGroup.front()->strVBoxCurrent;
4039 if (stack.strPrimaryGroup.isEmpty())
4040 stack.strPrimaryGroup = "/";
4041 }
4042
4043 // Draw the right conclusions from the (possibly modified) VM settings
4044 // file name and base folder. If the VM settings file name is modified,
4045 // it takes precedence, otherwise it is recreated from the base folder
4046 // and the primary group.
4047 stack.strSettingsFilename.setNull();
4048 std::list<VirtualSystemDescriptionEntry*> vsdeSettingsFile = vsdescThis->i_findByType(VirtualSystemDescriptionType_SettingsFile);
4049 if (vsdeSettingsFile.size() >= 1)
4050 {
4051 VirtualSystemDescriptionEntry *vsdeSF1 = vsdeSettingsFile.front();
4052 if (vsdeSF1->strVBoxCurrent != vsdeSF1->strVBoxSuggested)
4053 stack.strSettingsFilename = vsdeSF1->strVBoxCurrent;
4054 }
4055 if (stack.strSettingsFilename.isEmpty())
4056 {
4057 Utf8Str strBaseFolder;
4058 std::list<VirtualSystemDescriptionEntry*> vsdeBaseFolder = vsdescThis->i_findByType(VirtualSystemDescriptionType_BaseFolder);
4059 if (vsdeBaseFolder.size() >= 1)
4060 strBaseFolder = vsdeBaseFolder.front()->strVBoxCurrent;
4061 Bstr bstrSettingsFilename;
4062 rc = mVirtualBox->ComposeMachineFilename(Bstr(stack.strNameVBox).raw(),
4063 Bstr(stack.strPrimaryGroup).raw(),
4064 NULL /* aCreateFlags */,
4065 Bstr(strBaseFolder).raw(),
4066 bstrSettingsFilename.asOutParam());
4067 if (FAILED(rc)) throw rc;
4068 stack.strSettingsFilename = bstrSettingsFilename;
4069 }
4070
4071 // Determine the machine folder from the settings file.
4072 LogFunc(("i=%zu strName=%s strSettingsFilename=%s\n", i, stack.strNameVBox.c_str(), stack.strSettingsFilename.c_str()));
4073 stack.strMachineFolder = stack.strSettingsFilename;
4074 stack.strMachineFolder.stripFilename();
4075
4076 // guest OS type
4077 std::list<VirtualSystemDescriptionEntry*> vsdeOS;
4078 vsdeOS = vsdescThis->i_findByType(VirtualSystemDescriptionType_OS);
4079 if (vsdeOS.size() < 1)
4080 throw setError(VBOX_E_FILE_ERROR,
4081 tr("Missing guest OS type"));
4082 stack.strOsTypeVBox = vsdeOS.front()->strVBoxCurrent;
4083
4084 // CPU count
4085 std::list<VirtualSystemDescriptionEntry*> vsdeCPU = vsdescThis->i_findByType(VirtualSystemDescriptionType_CPU);
4086 if (vsdeCPU.size() != 1)
4087 throw setError(VBOX_E_FILE_ERROR, tr("CPU count missing"));
4088
4089 stack.cCPUs = vsdeCPU.front()->strVBoxCurrent.toUInt32();
4090 // We need HWVirt & IO-APIC if more than one CPU is requested
4091 if (stack.cCPUs > 1)
4092 {
4093 stack.fForceHWVirt = true;
4094 stack.fForceIOAPIC = true;
4095 }
4096
4097 // RAM
4098 std::list<VirtualSystemDescriptionEntry*> vsdeRAM = vsdescThis->i_findByType(VirtualSystemDescriptionType_Memory);
4099 if (vsdeRAM.size() != 1)
4100 throw setError(VBOX_E_FILE_ERROR, tr("RAM size missing"));
4101 stack.ulMemorySizeMB = (ULONG)vsdeRAM.front()->strVBoxCurrent.toUInt64();
4102
4103#ifdef VBOX_WITH_USB
4104 // USB controller
4105 std::list<VirtualSystemDescriptionEntry*> vsdeUSBController =
4106 vsdescThis->i_findByType(VirtualSystemDescriptionType_USBController);
4107 // USB support is enabled if there's at least one such entry; to disable USB support,
4108 // the type of the USB item would have been changed to "ignore"
4109 stack.fUSBEnabled = !vsdeUSBController.empty();
4110#endif
4111 // audio adapter
4112 std::list<VirtualSystemDescriptionEntry*> vsdeAudioAdapter =
4113 vsdescThis->i_findByType(VirtualSystemDescriptionType_SoundCard);
4114 /** @todo we support one audio adapter only */
4115 if (!vsdeAudioAdapter.empty())
4116 stack.strAudioAdapter = vsdeAudioAdapter.front()->strVBoxCurrent;
4117
4118 // for the description of the new machine, always use the OVF entry, the user may have changed it in the import config
4119 std::list<VirtualSystemDescriptionEntry*> vsdeDescription =
4120 vsdescThis->i_findByType(VirtualSystemDescriptionType_Description);
4121 if (!vsdeDescription.empty())
4122 stack.strDescription = vsdeDescription.front()->strVBoxCurrent;
4123
4124 // import vbox:machine or OVF now
4125 if (vsdescThis->m->pConfig)
4126 // vbox:Machine config
4127 i_importVBoxMachine(vsdescThis, pNewMachine, stack);
4128 else
4129 // generic OVF config
4130 i_importMachineGeneric(vsysThis, vsdescThis, pNewMachine, stack);
4131
4132 } // for (it = pAppliance->m->llVirtualSystems.begin() ...
4133}
4134
4135HRESULT Appliance::ImportStack::saveOriginalUUIDOfAttachedDevice(settings::AttachedDevice &device,
4136 const Utf8Str &newlyUuid)
4137{
4138 HRESULT rc = S_OK;
4139
4140 /* save for restoring */
4141 mapNewUUIDsToOriginalUUIDs.insert(std::make_pair(newlyUuid, device.uuid.toString()));
4142
4143 return rc;
4144}
4145
4146HRESULT Appliance::ImportStack::restoreOriginalUUIDOfAttachedDevice(settings::MachineConfigFile *config)
4147{
4148 HRESULT rc = S_OK;
4149
4150 settings::StorageControllersList &llControllers = config->hardwareMachine.storage.llStorageControllers;
4151 settings::StorageControllersList::iterator itscl;
4152 for (itscl = llControllers.begin();
4153 itscl != llControllers.end();
4154 ++itscl)
4155 {
4156 settings::AttachedDevicesList &llAttachments = itscl->llAttachedDevices;
4157 settings::AttachedDevicesList::iterator itadl = llAttachments.begin();
4158 while (itadl != llAttachments.end())
4159 {
4160 std::map<Utf8Str , Utf8Str>::iterator it =
4161 mapNewUUIDsToOriginalUUIDs.find(itadl->uuid.toString());
4162 if(it!=mapNewUUIDsToOriginalUUIDs.end())
4163 {
4164 Utf8Str uuidOriginal = it->second;
4165 itadl->uuid = Guid(uuidOriginal);
4166 mapNewUUIDsToOriginalUUIDs.erase(it->first);
4167 }
4168 ++itadl;
4169 }
4170 }
4171
4172 return rc;
4173}
4174
4175/**
4176 * @throws Nothing
4177 */
4178RTVFSIOSTREAM Appliance::ImportStack::claimOvaLookAHead(void)
4179{
4180 RTVFSIOSTREAM hVfsIos = this->hVfsIosOvaLookAhead;
4181 this->hVfsIosOvaLookAhead = NIL_RTVFSIOSTREAM;
4182 /* We don't free the name since it may be referenced in error messages and such. */
4183 return hVfsIos;
4184}
4185
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