VirtualBox

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

Last change on this file since 73743 was 73743, checked in by vboxsync, 7 years ago

Main/Progress+Appliance+Machine: Turn IProgress::waitForAsyncProgressCompletion into an internal method. It is not needed by any client code outside the API, it's simply is too special. Also include the error propagation which it originally skipped (leding to reduntant code in the calling code). Remove a replicated version of it from the Appliance code which seems to be the half-forgotten ancestor of the method in IProgress. Adapt the users of the method to the new internal method, which made the code easier to read.

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

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