VirtualBox

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

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

Main/Progress: Split into safe public interface and a private one which is used purely by API implementation code (for updating). Needed quite a few minor adjustments elsewhere.

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

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