VirtualBox

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

Last change on this file since 60328 was 60328, checked in by vboxsync, 9 years ago

bugref:8249. Improvement ICertificate interface.

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

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