VirtualBox

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

Last change on this file since 66900 was 65887, checked in by vboxsync, 8 years ago

Main/Appliance: support importing more than 10 NICs if the OVF contains the VirtualBox config and it is set to ICH9 chipset (needs more than one digit, and grabbing all numbers works as the string continues with ";type=NAT" or so)

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