VirtualBox

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

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

*: scm --update-copyright-year

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

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