VirtualBox

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

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

Main: Reworking IAppliance export to use new TAR creator. Changes protected by VBOX_WITH_NEW_TAR_CREATOR define (currently not defined).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 174.8 KB
Line 
1/* $Id: ApplianceImplImport.cpp 67184 2017-05-31 20:32:04Z vboxsync $ */
2/** @file
3 * IAppliance and IVirtualSystem COM class implementations.
4 */
5
6/*
7 * Copyright (C) 2008-2016 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include <iprt/alloca.h>
19#include <iprt/path.h>
20#include <iprt/dir.h>
21#include <iprt/file.h>
22#include <iprt/s3.h>
23#include <iprt/sha.h>
24#include <iprt/manifest.h>
25#include <iprt/tar.h>
26#include <iprt/zip.h>
27#include <iprt/stream.h>
28#include <iprt/crypto/digest.h>
29#include <iprt/crypto/pkix.h>
30#include <iprt/crypto/store.h>
31#include <iprt/crypto/x509.h>
32
33#include <VBox/vd.h>
34#include <VBox/com/array.h>
35
36#include "ApplianceImpl.h"
37#include "VirtualBoxImpl.h"
38#include "GuestOSTypeImpl.h"
39#include "ProgressImpl.h"
40#include "MachineImpl.h"
41#include "MediumImpl.h"
42#include "MediumFormatImpl.h"
43#include "SystemPropertiesImpl.h"
44#include "HostImpl.h"
45
46#include "AutoCaller.h"
47#include "Logging.h"
48
49#include "ApplianceImplPrivate.h"
50#include "CertificateImpl.h"
51
52#include <VBox/param.h>
53#include <VBox/version.h>
54#include <VBox/settings.h>
55
56#include <set>
57
58using namespace std;
59
60////////////////////////////////////////////////////////////////////////////////
61//
62// IAppliance public methods
63//
64////////////////////////////////////////////////////////////////////////////////
65
66/**
67 * Public method implementation. This opens the OVF with ovfreader.cpp.
68 * Thread implementation is in Appliance::readImpl().
69 *
70 * @param aFile File to read the appliance from.
71 * @param aProgress Progress object.
72 * @return
73 */
74HRESULT Appliance::read(const com::Utf8Str &aFile,
75 ComPtr<IProgress> &aProgress)
76{
77 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
78
79 if (!i_isApplianceIdle())
80 return E_ACCESSDENIED;
81
82 if (m->pReader)
83 {
84 delete m->pReader;
85 m->pReader = NULL;
86 }
87
88 // see if we can handle this file; for now we insist it has an ovf/ova extension
89 if ( !aFile.endsWith(".ovf", Utf8Str::CaseInsensitive)
90 && !aFile.endsWith(".ova", Utf8Str::CaseInsensitive))
91 return setError(VBOX_E_FILE_ERROR, tr("Appliance file must have .ovf or .ova extension"));
92
93 ComObjPtr<Progress> progress;
94 try
95 {
96 /* Parse all necessary info out of the URI */
97 i_parseURI(aFile, m->locInfo);
98 i_readImpl(m->locInfo, progress);
99 }
100 catch (HRESULT aRC)
101 {
102 return aRC;
103 }
104
105 /* Return progress to the caller */
106 progress.queryInterfaceTo(aProgress.asOutParam());
107 return S_OK;
108}
109
110/**
111 * Public method implementation. This looks at the output of ovfreader.cpp and creates
112 * VirtualSystemDescription instances.
113 * @return
114 */
115HRESULT Appliance::interpret()
116{
117 /// @todo
118 // - don't use COM methods but the methods directly (faster, but needs appropriate
119 // locking of that objects itself (s. HardDisk))
120 // - Appropriate handle errors like not supported file formats
121 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
122
123 if (!i_isApplianceIdle())
124 return E_ACCESSDENIED;
125
126 HRESULT rc = S_OK;
127
128 /* Clear any previous virtual system descriptions */
129 m->virtualSystemDescriptions.clear();
130
131 if (!m->pReader)
132 return setError(E_FAIL,
133 tr("Cannot interpret appliance without reading it first (call read() before interpret())"));
134
135 // Change the appliance state so we can safely leave the lock while doing time-consuming
136 // disk imports; also the below method calls do all kinds of locking which conflicts with
137 // the appliance object lock
138 m->state = Data::ApplianceImporting;
139 alock.release();
140
141 /* Try/catch so we can clean up on error */
142 try
143 {
144 list<ovf::VirtualSystem>::const_iterator it;
145 /* Iterate through all virtual systems */
146 for (it = m->pReader->m_llVirtualSystems.begin();
147 it != m->pReader->m_llVirtualSystems.end();
148 ++it)
149 {
150 const ovf::VirtualSystem &vsysThis = *it;
151
152 ComObjPtr<VirtualSystemDescription> pNewDesc;
153 rc = pNewDesc.createObject();
154 if (FAILED(rc)) throw rc;
155 rc = pNewDesc->init();
156 if (FAILED(rc)) throw rc;
157
158 // if the virtual system in OVF had a <vbox:Machine> element, have the
159 // VirtualBox settings code parse that XML now
160 if (vsysThis.pelmVBoxMachine)
161 pNewDesc->i_importVBoxMachineXML(*vsysThis.pelmVBoxMachine);
162
163 // Guest OS type
164 // This is taken from one of three places, in this order:
165 Utf8Str strOsTypeVBox;
166 Utf8StrFmt strCIMOSType("%RU32", (uint32_t)vsysThis.cimos);
167 // 1) If there is a <vbox:Machine>, then use the type from there.
168 if ( vsysThis.pelmVBoxMachine
169 && pNewDesc->m->pConfig->machineUserData.strOsType.isNotEmpty()
170 )
171 strOsTypeVBox = pNewDesc->m->pConfig->machineUserData.strOsType;
172 // 2) Otherwise, if there is OperatingSystemSection/vbox:OSType, use that one.
173 else if (vsysThis.strTypeVBox.isNotEmpty()) // OVFReader has found vbox:OSType
174 strOsTypeVBox = vsysThis.strTypeVBox;
175 // 3) Otherwise, make a best guess what the vbox type is from the OVF (CIM) OS type.
176 else
177 convertCIMOSType2VBoxOSType(strOsTypeVBox, vsysThis.cimos, vsysThis.strCimosDesc);
178 pNewDesc->i_addEntry(VirtualSystemDescriptionType_OS,
179 "",
180 strCIMOSType,
181 strOsTypeVBox);
182
183 /* VM name */
184 Utf8Str nameVBox;
185 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
186 if ( vsysThis.pelmVBoxMachine
187 && pNewDesc->m->pConfig->machineUserData.strName.isNotEmpty())
188 nameVBox = pNewDesc->m->pConfig->machineUserData.strName;
189 else
190 nameVBox = vsysThis.strName;
191 /* If there isn't any name specified create a default one out
192 * of the OS type */
193 if (nameVBox.isEmpty())
194 nameVBox = strOsTypeVBox;
195 i_searchUniqueVMName(nameVBox);
196 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Name,
197 "",
198 vsysThis.strName,
199 nameVBox);
200
201 /* Based on the VM name, create a target machine path. */
202 Bstr bstrMachineFilename;
203 rc = mVirtualBox->ComposeMachineFilename(Bstr(nameVBox).raw(),
204 NULL /* aGroup */,
205 NULL /* aCreateFlags */,
206 NULL /* aBaseFolder */,
207 bstrMachineFilename.asOutParam());
208 if (FAILED(rc)) throw rc;
209 /* Determine the machine folder from that */
210 Utf8Str strMachineFolder = Utf8Str(bstrMachineFilename).stripFilename();
211
212 /* VM Product */
213 if (!vsysThis.strProduct.isEmpty())
214 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Product,
215 "",
216 vsysThis.strProduct,
217 vsysThis.strProduct);
218
219 /* VM Vendor */
220 if (!vsysThis.strVendor.isEmpty())
221 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Vendor,
222 "",
223 vsysThis.strVendor,
224 vsysThis.strVendor);
225
226 /* VM Version */
227 if (!vsysThis.strVersion.isEmpty())
228 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Version,
229 "",
230 vsysThis.strVersion,
231 vsysThis.strVersion);
232
233 /* VM ProductUrl */
234 if (!vsysThis.strProductUrl.isEmpty())
235 pNewDesc->i_addEntry(VirtualSystemDescriptionType_ProductUrl,
236 "",
237 vsysThis.strProductUrl,
238 vsysThis.strProductUrl);
239
240 /* VM VendorUrl */
241 if (!vsysThis.strVendorUrl.isEmpty())
242 pNewDesc->i_addEntry(VirtualSystemDescriptionType_VendorUrl,
243 "",
244 vsysThis.strVendorUrl,
245 vsysThis.strVendorUrl);
246
247 /* VM description */
248 if (!vsysThis.strDescription.isEmpty())
249 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Description,
250 "",
251 vsysThis.strDescription,
252 vsysThis.strDescription);
253
254 /* VM license */
255 if (!vsysThis.strLicenseText.isEmpty())
256 pNewDesc->i_addEntry(VirtualSystemDescriptionType_License,
257 "",
258 vsysThis.strLicenseText,
259 vsysThis.strLicenseText);
260
261 /* Now that we know the OS type, get our internal defaults based on that. */
262 ComPtr<IGuestOSType> pGuestOSType;
263 rc = mVirtualBox->GetGuestOSType(Bstr(strOsTypeVBox).raw(), pGuestOSType.asOutParam());
264 if (FAILED(rc)) throw rc;
265
266 /* CPU count */
267 ULONG cpuCountVBox;
268 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
269 if ( vsysThis.pelmVBoxMachine
270 && pNewDesc->m->pConfig->hardwareMachine.cCPUs)
271 cpuCountVBox = pNewDesc->m->pConfig->hardwareMachine.cCPUs;
272 else
273 cpuCountVBox = vsysThis.cCPUs;
274 /* Check for the constraints */
275 if (cpuCountVBox > SchemaDefs::MaxCPUCount)
276 {
277 i_addWarning(tr("The virtual system \"%s\" claims support for %u CPU's, but VirtualBox has support for "
278 "max %u CPU's only."),
279 vsysThis.strName.c_str(), cpuCountVBox, SchemaDefs::MaxCPUCount);
280 cpuCountVBox = SchemaDefs::MaxCPUCount;
281 }
282 if (vsysThis.cCPUs == 0)
283 cpuCountVBox = 1;
284 pNewDesc->i_addEntry(VirtualSystemDescriptionType_CPU,
285 "",
286 Utf8StrFmt("%RU32", (uint32_t)vsysThis.cCPUs),
287 Utf8StrFmt("%RU32", (uint32_t)cpuCountVBox));
288
289 /* RAM */
290 uint64_t ullMemSizeVBox;
291 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
292 if ( vsysThis.pelmVBoxMachine
293 && pNewDesc->m->pConfig->hardwareMachine.ulMemorySizeMB)
294 ullMemSizeVBox = pNewDesc->m->pConfig->hardwareMachine.ulMemorySizeMB;
295 else
296 ullMemSizeVBox = vsysThis.ullMemorySize / _1M;
297 /* Check for the constraints */
298 if ( ullMemSizeVBox != 0
299 && ( ullMemSizeVBox < MM_RAM_MIN_IN_MB
300 || ullMemSizeVBox > MM_RAM_MAX_IN_MB
301 )
302 )
303 {
304 i_addWarning(tr("The virtual system \"%s\" claims support for %llu MB RAM size, but VirtualBox has "
305 "support for min %u & max %u MB RAM size only."),
306 vsysThis.strName.c_str(), ullMemSizeVBox, MM_RAM_MIN_IN_MB, MM_RAM_MAX_IN_MB);
307 ullMemSizeVBox = RT_MIN(RT_MAX(ullMemSizeVBox, MM_RAM_MIN_IN_MB), MM_RAM_MAX_IN_MB);
308 }
309 if (vsysThis.ullMemorySize == 0)
310 {
311 /* If the RAM of the OVF is zero, use our predefined values */
312 ULONG memSizeVBox2;
313 rc = pGuestOSType->COMGETTER(RecommendedRAM)(&memSizeVBox2);
314 if (FAILED(rc)) throw rc;
315 /* VBox stores that in MByte */
316 ullMemSizeVBox = (uint64_t)memSizeVBox2;
317 }
318 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Memory,
319 "",
320 Utf8StrFmt("%RU64", (uint64_t)vsysThis.ullMemorySize),
321 Utf8StrFmt("%RU64", (uint64_t)ullMemSizeVBox));
322
323 /* Audio */
324 Utf8Str strSoundCard;
325 Utf8Str strSoundCardOrig;
326 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
327 if ( vsysThis.pelmVBoxMachine
328 && pNewDesc->m->pConfig->hardwareMachine.audioAdapter.fEnabled)
329 {
330 strSoundCard = Utf8StrFmt("%RU32",
331 (uint32_t)pNewDesc->m->pConfig->hardwareMachine.audioAdapter.controllerType);
332 }
333 else if (vsysThis.strSoundCardType.isNotEmpty())
334 {
335 /* Set the AC97 always for the simple OVF case.
336 * @todo: figure out the hardware which could be possible */
337 strSoundCard = Utf8StrFmt("%RU32", (uint32_t)AudioControllerType_AC97);
338 strSoundCardOrig = vsysThis.strSoundCardType;
339 }
340 if (strSoundCard.isNotEmpty())
341 pNewDesc->i_addEntry(VirtualSystemDescriptionType_SoundCard,
342 "",
343 strSoundCardOrig,
344 strSoundCard);
345
346#ifdef VBOX_WITH_USB
347 /* USB Controller */
348 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
349 if ( ( vsysThis.pelmVBoxMachine
350 && pNewDesc->m->pConfig->hardwareMachine.usbSettings.llUSBControllers.size() > 0)
351 || vsysThis.fHasUsbController)
352 pNewDesc->i_addEntry(VirtualSystemDescriptionType_USBController, "", "", "");
353#endif /* VBOX_WITH_USB */
354
355 /* Network Controller */
356 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
357 if (vsysThis.pelmVBoxMachine)
358 {
359 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(pNewDesc->m->pConfig->hardwareMachine.chipsetType);
360
361 const settings::NetworkAdaptersList &llNetworkAdapters = pNewDesc->m->pConfig->hardwareMachine.llNetworkAdapters;
362 /* Check for the constrains */
363 if (llNetworkAdapters.size() > maxNetworkAdapters)
364 i_addWarning(tr("The virtual system \"%s\" claims support for %zu network adapters, but VirtualBox "
365 "has support for max %u network adapter only."),
366 vsysThis.strName.c_str(), llNetworkAdapters.size(), maxNetworkAdapters);
367 /* Iterate through all network adapters. */
368 settings::NetworkAdaptersList::const_iterator it1;
369 size_t a = 0;
370 for (it1 = llNetworkAdapters.begin();
371 it1 != llNetworkAdapters.end() && a < maxNetworkAdapters;
372 ++it1, ++a)
373 {
374 if (it1->fEnabled)
375 {
376 Utf8Str strMode = convertNetworkAttachmentTypeToString(it1->mode);
377 pNewDesc->i_addEntry(VirtualSystemDescriptionType_NetworkAdapter,
378 "", // ref
379 strMode, // orig
380 Utf8StrFmt("%RU32", (uint32_t)it1->type), // conf
381 0,
382 Utf8StrFmt("slot=%RU32;type=%s", it1->ulSlot, strMode.c_str())); // extra conf
383 }
384 }
385 }
386 /* else we use the ovf configuration. */
387 else if (vsysThis.llEthernetAdapters.size() > 0)
388 {
389 size_t cEthernetAdapters = vsysThis.llEthernetAdapters.size();
390 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
391
392 /* Check for the constrains */
393 if (cEthernetAdapters > maxNetworkAdapters)
394 i_addWarning(tr("The virtual system \"%s\" claims support for %zu network adapters, but VirtualBox "
395 "has support for max %u network adapter only."),
396 vsysThis.strName.c_str(), cEthernetAdapters, maxNetworkAdapters);
397
398 /* Get the default network adapter type for the selected guest OS */
399 NetworkAdapterType_T defaultAdapterVBox = NetworkAdapterType_Am79C970A;
400 rc = pGuestOSType->COMGETTER(AdapterType)(&defaultAdapterVBox);
401 if (FAILED(rc)) throw rc;
402
403 ovf::EthernetAdaptersList::const_iterator itEA;
404 /* Iterate through all abstract networks. Ignore network cards
405 * which exceed the limit of VirtualBox. */
406 size_t a = 0;
407 for (itEA = vsysThis.llEthernetAdapters.begin();
408 itEA != vsysThis.llEthernetAdapters.end() && a < maxNetworkAdapters;
409 ++itEA, ++a)
410 {
411 const ovf::EthernetAdapter &ea = *itEA; // logical network to connect to
412 Utf8Str strNetwork = ea.strNetworkName;
413 // make sure it's one of these two
414 if ( (strNetwork.compare("Null", Utf8Str::CaseInsensitive))
415 && (strNetwork.compare("NAT", Utf8Str::CaseInsensitive))
416 && (strNetwork.compare("Bridged", Utf8Str::CaseInsensitive))
417 && (strNetwork.compare("Internal", Utf8Str::CaseInsensitive))
418 && (strNetwork.compare("HostOnly", Utf8Str::CaseInsensitive))
419 && (strNetwork.compare("Generic", Utf8Str::CaseInsensitive))
420 )
421 strNetwork = "Bridged"; // VMware assumes this is the default apparently
422
423 /* Figure out the hardware type */
424 NetworkAdapterType_T nwAdapterVBox = defaultAdapterVBox;
425 if (!ea.strAdapterType.compare("PCNet32", Utf8Str::CaseInsensitive))
426 {
427 /* If the default adapter is already one of the two
428 * PCNet adapters use the default one. If not use the
429 * Am79C970A as fallback. */
430 if (!(defaultAdapterVBox == NetworkAdapterType_Am79C970A ||
431 defaultAdapterVBox == NetworkAdapterType_Am79C973))
432 nwAdapterVBox = NetworkAdapterType_Am79C970A;
433 }
434#ifdef VBOX_WITH_E1000
435 /* VMWare accidentally write this with VirtualCenter 3.5,
436 so make sure in this case always to use the VMWare one */
437 else if (!ea.strAdapterType.compare("E10000", Utf8Str::CaseInsensitive))
438 nwAdapterVBox = NetworkAdapterType_I82545EM;
439 else if (!ea.strAdapterType.compare("E1000", Utf8Str::CaseInsensitive))
440 {
441 /* Check if this OVF was written by VirtualBox */
442 if (Utf8Str(vsysThis.strVirtualSystemType).contains("virtualbox", Utf8Str::CaseInsensitive))
443 {
444 /* If the default adapter is already one of the three
445 * E1000 adapters use the default one. If not use the
446 * I82545EM as fallback. */
447 if (!(defaultAdapterVBox == NetworkAdapterType_I82540EM ||
448 defaultAdapterVBox == NetworkAdapterType_I82543GC ||
449 defaultAdapterVBox == NetworkAdapterType_I82545EM))
450 nwAdapterVBox = NetworkAdapterType_I82540EM;
451 }
452 else
453 /* Always use this one since it's what VMware uses */
454 nwAdapterVBox = NetworkAdapterType_I82545EM;
455 }
456#endif /* VBOX_WITH_E1000 */
457
458 pNewDesc->i_addEntry(VirtualSystemDescriptionType_NetworkAdapter,
459 "", // ref
460 ea.strNetworkName, // orig
461 Utf8StrFmt("%RU32", (uint32_t)nwAdapterVBox), // conf
462 0,
463 Utf8StrFmt("type=%s", strNetwork.c_str())); // extra conf
464 }
465 }
466
467 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
468 bool fFloppy = false;
469 bool fDVD = false;
470 if (vsysThis.pelmVBoxMachine)
471 {
472 settings::StorageControllersList &llControllers = pNewDesc->m->pConfig->hardwareMachine.storage.llStorageControllers;
473 settings::StorageControllersList::iterator it3;
474 for (it3 = llControllers.begin();
475 it3 != llControllers.end();
476 ++it3)
477 {
478 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
479 settings::AttachedDevicesList::iterator it4;
480 for (it4 = llAttachments.begin();
481 it4 != llAttachments.end();
482 ++it4)
483 {
484 fDVD |= it4->deviceType == DeviceType_DVD;
485 fFloppy |= it4->deviceType == DeviceType_Floppy;
486 if (fFloppy && fDVD)
487 break;
488 }
489 if (fFloppy && fDVD)
490 break;
491 }
492 }
493 else
494 {
495 fFloppy = vsysThis.fHasFloppyDrive;
496 fDVD = vsysThis.fHasCdromDrive;
497 }
498 /* Floppy Drive */
499 if (fFloppy)
500 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Floppy, "", "", "");
501 /* CD Drive */
502 if (fDVD)
503 pNewDesc->i_addEntry(VirtualSystemDescriptionType_CDROM, "", "", "");
504
505 /* Hard disk Controller */
506 uint16_t cIDEused = 0;
507 uint16_t cSATAused = 0; NOREF(cSATAused);
508 uint16_t cSCSIused = 0; NOREF(cSCSIused);
509 ovf::ControllersMap::const_iterator hdcIt;
510 /* Iterate through all hard disk controllers */
511 for (hdcIt = vsysThis.mapControllers.begin();
512 hdcIt != vsysThis.mapControllers.end();
513 ++hdcIt)
514 {
515 const ovf::HardDiskController &hdc = hdcIt->second;
516 Utf8Str strControllerID = Utf8StrFmt("%RI32", (uint32_t)hdc.idController);
517
518 switch (hdc.system)
519 {
520 case ovf::HardDiskController::IDE:
521 /* Check for the constrains */
522 if (cIDEused < 4)
523 {
524 /// @todo figure out the IDE types
525 /* Use PIIX4 as default */
526 Utf8Str strType = "PIIX4";
527 if (!hdc.strControllerType.compare("PIIX3", Utf8Str::CaseInsensitive))
528 strType = "PIIX3";
529 else if (!hdc.strControllerType.compare("ICH6", Utf8Str::CaseInsensitive))
530 strType = "ICH6";
531 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
532 strControllerID, // strRef
533 hdc.strControllerType, // aOvfValue
534 strType); // aVBoxValue
535 }
536 else
537 /* Warn only once */
538 if (cIDEused == 2)
539 i_addWarning(tr("The virtual \"%s\" system requests support for more than two "
540 "IDE controller channels, but VirtualBox supports only two."),
541 vsysThis.strName.c_str());
542
543 ++cIDEused;
544 break;
545
546 case ovf::HardDiskController::SATA:
547 /* Check for the constrains */
548 if (cSATAused < 1)
549 {
550 /// @todo figure out the SATA types
551 /* We only support a plain AHCI controller, so use them always */
552 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA,
553 strControllerID,
554 hdc.strControllerType,
555 "AHCI");
556 }
557 else
558 {
559 /* Warn only once */
560 if (cSATAused == 1)
561 i_addWarning(tr("The virtual system \"%s\" requests support for more than one "
562 "SATA controller, but VirtualBox has support for only one"),
563 vsysThis.strName.c_str());
564
565 }
566 ++cSATAused;
567 break;
568
569 case ovf::HardDiskController::SCSI:
570 /* Check for the constrains */
571 if (cSCSIused < 1)
572 {
573 VirtualSystemDescriptionType_T vsdet = VirtualSystemDescriptionType_HardDiskControllerSCSI;
574 Utf8Str hdcController = "LsiLogic";
575 if (!hdc.strControllerType.compare("lsilogicsas", Utf8Str::CaseInsensitive))
576 {
577 // OVF considers SAS a variant of SCSI but VirtualBox considers it a class of its own
578 vsdet = VirtualSystemDescriptionType_HardDiskControllerSAS;
579 hdcController = "LsiLogicSas";
580 }
581 else if (!hdc.strControllerType.compare("BusLogic", Utf8Str::CaseInsensitive))
582 hdcController = "BusLogic";
583 pNewDesc->i_addEntry(vsdet,
584 strControllerID,
585 hdc.strControllerType,
586 hdcController);
587 }
588 else
589 i_addWarning(tr("The virtual system \"%s\" requests support for an additional "
590 "SCSI controller of type \"%s\" with ID %s, but VirtualBox presently "
591 "supports only one SCSI controller."),
592 vsysThis.strName.c_str(),
593 hdc.strControllerType.c_str(),
594 strControllerID.c_str());
595 ++cSCSIused;
596 break;
597 }
598 }
599
600 /* Hard disks */
601 if (vsysThis.mapVirtualDisks.size() > 0)
602 {
603 ovf::VirtualDisksMap::const_iterator itVD;
604 /* Iterate through all hard disks ()*/
605 for (itVD = vsysThis.mapVirtualDisks.begin();
606 itVD != vsysThis.mapVirtualDisks.end();
607 ++itVD)
608 {
609 const ovf::VirtualDisk &hd = itVD->second;
610 /* Get the associated disk image */
611 ovf::DiskImage di;
612 std::map<RTCString, ovf::DiskImage>::iterator foundDisk;
613
614 foundDisk = m->pReader->m_mapDisks.find(hd.strDiskId);
615 if (foundDisk == m->pReader->m_mapDisks.end())
616 continue;
617 else
618 {
619 di = foundDisk->second;
620 }
621
622 /*
623 * Figure out from URI which format the image of disk has.
624 * URI must have inside section <Disk> .
625 * But there aren't strong requirements about correspondence one URI for one disk virtual format.
626 * So possibly, we aren't able to recognize some URIs.
627 */
628
629 ComObjPtr<MediumFormat> mediumFormat;
630 rc = i_findMediumFormatFromDiskImage(di, mediumFormat);
631 if (FAILED(rc))
632 throw rc;
633
634 Bstr bstrFormatName;
635 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
636 if (FAILED(rc))
637 throw rc;
638 Utf8Str vdf = Utf8Str(bstrFormatName);
639
640 /// @todo
641 // - figure out all possible vmdk formats we also support
642 // - figure out if there is a url specifier for vhd already
643 // - we need a url specifier for the vdi format
644
645 if (vdf.compare("VMDK", Utf8Str::CaseInsensitive) == 0)
646 {
647 /* If the href is empty use the VM name as filename */
648 Utf8Str strFilename = di.strHref;
649 if (!strFilename.length())
650 strFilename = Utf8StrFmt("%s.vmdk", hd.strDiskId.c_str());
651
652 Utf8Str strTargetPath = Utf8Str(strMachineFolder);
653 strTargetPath.append(RTPATH_DELIMITER).append(di.strHref);
654 /*
655 * Remove last extension from the file name if the file is compressed
656 */
657 if (di.strCompression.compare("gzip", Utf8Str::CaseInsensitive)==0)
658 {
659 strTargetPath.stripSuffix();
660 }
661
662 i_searchUniqueDiskImageFilePath(strTargetPath);
663
664 /* find the description for the hard disk controller
665 * that has the same ID as hd.idController */
666 const VirtualSystemDescriptionEntry *pController;
667 if (!(pController = pNewDesc->i_findControllerFromID(hd.idController)))
668 throw setError(E_FAIL,
669 tr("Cannot find hard disk controller with OVF instance ID %RI32 "
670 "to which disk \"%s\" should be attached"),
671 hd.idController,
672 di.strHref.c_str());
673
674 /* controller to attach to, and the bus within that controller */
675 Utf8StrFmt strExtraConfig("controller=%RI16;channel=%RI16",
676 pController->ulIndex,
677 hd.ulAddressOnParent);
678 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
679 hd.strDiskId,
680 di.strHref,
681 strTargetPath,
682 di.ulSuggestedSizeMB,
683 strExtraConfig);
684 }
685 else if (vdf.compare("RAW", Utf8Str::CaseInsensitive) == 0)
686 {
687 /* If the href is empty use the VM name as filename */
688 Utf8Str strFilename = di.strHref;
689 if (!strFilename.length())
690 strFilename = Utf8StrFmt("%s.iso", hd.strDiskId.c_str());
691
692 Utf8Str strTargetPath = Utf8Str(strMachineFolder)
693 .append(RTPATH_DELIMITER)
694 .append(di.strHref);
695 /*
696 * Remove last extension from the file name if the file is compressed
697 */
698 if (di.strCompression.compare("gzip", Utf8Str::CaseInsensitive)==0)
699 {
700 strTargetPath.stripSuffix();
701 }
702
703 i_searchUniqueDiskImageFilePath(strTargetPath);
704
705 /* find the description for the hard disk controller
706 * that has the same ID as hd.idController */
707 const VirtualSystemDescriptionEntry *pController;
708 if (!(pController = pNewDesc->i_findControllerFromID(hd.idController)))
709 throw setError(E_FAIL,
710 tr("Cannot find disk controller with OVF instance ID %RI32 "
711 "to which disk \"%s\" should be attached"),
712 hd.idController,
713 di.strHref.c_str());
714
715 /* controller to attach to, and the bus within that controller */
716 Utf8StrFmt strExtraConfig("controller=%RI16;channel=%RI16",
717 pController->ulIndex,
718 hd.ulAddressOnParent);
719 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
720 hd.strDiskId,
721 di.strHref,
722 strTargetPath,
723 di.ulSuggestedSizeMB,
724 strExtraConfig);
725 }
726 else
727 throw setError(VBOX_E_FILE_ERROR,
728 tr("Unsupported format for virtual disk image %s in OVF: \"%s\""),
729 di.strHref.c_str(),
730 di.strFormat.c_str());
731 }
732 }
733
734 m->virtualSystemDescriptions.push_back(pNewDesc);
735 }
736 }
737 catch (HRESULT aRC)
738 {
739 /* On error we clear the list & return */
740 m->virtualSystemDescriptions.clear();
741 rc = aRC;
742 }
743
744 // reset the appliance state
745 alock.acquire();
746 m->state = Data::ApplianceIdle;
747
748 return rc;
749}
750
751/**
752 * Public method implementation. This creates one or more new machines according to the
753 * VirtualSystemScription instances created by Appliance::Interpret().
754 * Thread implementation is in Appliance::i_importImpl().
755 * @param aOptions Import options.
756 * @param aProgress Progress object.
757 * @return
758 */
759HRESULT Appliance::importMachines(const std::vector<ImportOptions_T> &aOptions,
760 ComPtr<IProgress> &aProgress)
761{
762 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
763
764 if (aOptions.size())
765 {
766 m->optListImport.setCapacity(aOptions.size());
767 for (size_t i = 0; i < aOptions.size(); ++i)
768 {
769 m->optListImport.insert(i, aOptions[i]);
770 }
771 }
772
773 AssertReturn(!( m->optListImport.contains(ImportOptions_KeepAllMACs)
774 && m->optListImport.contains(ImportOptions_KeepNATMACs) )
775 , E_INVALIDARG);
776
777 // do not allow entering this method if the appliance is busy reading or writing
778 if (!i_isApplianceIdle())
779 return E_ACCESSDENIED;
780
781 if (!m->pReader)
782 return setError(E_FAIL,
783 tr("Cannot import machines without reading it first (call read() before i_importMachines())"));
784
785 ComObjPtr<Progress> progress;
786 HRESULT rc = S_OK;
787 try
788 {
789 rc = i_importImpl(m->locInfo, progress);
790 }
791 catch (HRESULT aRC)
792 {
793 rc = aRC;
794 }
795
796 if (SUCCEEDED(rc))
797 /* Return progress to the caller */
798 progress.queryInterfaceTo(aProgress.asOutParam());
799
800 return rc;
801}
802
803////////////////////////////////////////////////////////////////////////////////
804//
805// Appliance private methods
806//
807////////////////////////////////////////////////////////////////////////////////
808
809/**
810 * Ensures that there is a look-ahead object ready.
811 *
812 * @returns true if there's an object handy, false if end-of-stream.
813 * @throws HRESULT if the next object isn't a regular file. Sets error info
814 * (which is why it's a method on Appliance and not the
815 * ImportStack).
816 */
817bool Appliance::i_importEnsureOvaLookAhead(ImportStack &stack)
818{
819 Assert(stack.hVfsFssOva != NULL);
820 if (stack.hVfsIosOvaLookAhead == NIL_RTVFSIOSTREAM)
821 {
822 RTStrFree(stack.pszOvaLookAheadName);
823 stack.pszOvaLookAheadName = NULL;
824
825 RTVFSOBJTYPE enmType;
826 RTVFSOBJ hVfsObj;
827 int vrc = RTVfsFsStrmNext(stack.hVfsFssOva, &stack.pszOvaLookAheadName, &enmType, &hVfsObj);
828 if (RT_SUCCESS(vrc))
829 {
830 stack.hVfsIosOvaLookAhead = RTVfsObjToIoStream(hVfsObj);
831 RTVfsObjRelease(hVfsObj);
832 if ( ( enmType != RTVFSOBJTYPE_FILE
833 && enmType != RTVFSOBJTYPE_IO_STREAM)
834 || stack.hVfsIosOvaLookAhead == NIL_RTVFSIOSTREAM)
835 throw setError(VBOX_E_FILE_ERROR,
836 tr("Malformed OVA. '%s' is not a regular file (%d)."), stack.pszOvaLookAheadName, enmType);
837 }
838 else if (vrc == VERR_EOF)
839 return false;
840 else
841 throw setErrorVrc(vrc, tr("RTVfsFsStrmNext failed (%Rrc)"), vrc);
842 }
843 return true;
844}
845
846HRESULT Appliance::i_preCheckImageAvailability(ImportStack &stack)
847{
848 if (i_importEnsureOvaLookAhead(stack))
849 return S_OK;
850 throw setError(VBOX_E_FILE_ERROR, tr("Unexpected end of OVA package"));
851 /** @todo r=bird: dunno why this bother returning a value and the caller
852 * having a special 'continue' case for it. It always threw all non-OK
853 * status codes. It's possibly to handle out of order stuff, so that
854 * needs adding to the testcase! */
855}
856
857/**
858 * 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#ifndef VBOX_WITH_NEW_TAR_CREATOR
1510 m->fSha256 = RT_BOOL(m->fDigestTypes & RTMANIFEST_ATTR_SHA256); /** @todo retire this member */
1511#endif
1512 return S_OK;
1513}
1514
1515/**
1516 * Reads the signature & certificate file.
1517 *
1518 * @param pTask The read task.
1519 * @param hVfsIosCert The I/O stream for the signature file. The
1520 * reference is always consumed.
1521 * @param pszSubFileNm The signature filename (no path) for error
1522 * messages and logging. Used to construct
1523 * .mf-file name.
1524 * @returns COM status code, error info set.
1525 * @throws Nothing
1526 */
1527HRESULT Appliance::i_readSignatureFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosCert, const char *pszSubFileNm)
1528{
1529 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
1530
1531 /*
1532 * Construct the manifest filename from pszSubFileNm.
1533 */
1534 Utf8Str strManifestName;
1535 try
1536 {
1537 const char *pszSuffix = strrchr(pszSubFileNm, '.');
1538 AssertReturn(pszSuffix, E_FAIL);
1539 strManifestName = Utf8Str(pszSubFileNm, pszSuffix - pszSubFileNm);
1540 strManifestName.append(".mf");
1541 }
1542 catch (...)
1543 {
1544 return E_OUTOFMEMORY;
1545 }
1546
1547 /*
1548 * Copy the manifest into a memory buffer. We'll do the signature processing
1549 * later to not force any specific order in the OVAs or any other archive we
1550 * may be accessing later.
1551 */
1552 void *pvSignature;
1553 size_t cbSignature;
1554 int vrc = RTVfsIoStrmReadAll(hVfsIosCert, &pvSignature, &cbSignature);
1555 RTVfsIoStrmRelease(hVfsIosCert); /* consumes stream handle. */
1556 if (RT_FAILURE(vrc))
1557 return setErrorVrc(vrc, tr("Error reading the signature file '%s' for '%s' (%Rrc)"),
1558 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
1559
1560 /*
1561 * Parse the signing certificate. Unlike the manifest parser we use below,
1562 * this API ignores parse of the file that aren't relevant.
1563 */
1564 RTERRINFOSTATIC StaticErrInfo;
1565 vrc = RTCrX509Certificate_ReadFromBuffer(&m->SignerCert, pvSignature, cbSignature,
1566 RTCRX509CERT_READ_F_PEM_ONLY,
1567 &g_RTAsn1DefaultAllocator, RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
1568 HRESULT hrc;
1569 if (RT_SUCCESS(vrc))
1570 {
1571 m->fSignerCertLoaded = true;
1572 m->fCertificateIsSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->SignerCert);
1573
1574 /*
1575 * Find the start of the certificate part of the file, so we can avoid
1576 * upsetting the manifest parser with it.
1577 */
1578 char *pszSplit = (char *)RTCrPemFindFirstSectionInContent(pvSignature, cbSignature,
1579 g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers);
1580 if (pszSplit)
1581 while ( pszSplit != (char *)pvSignature
1582 && pszSplit[-1] != '\n'
1583 && pszSplit[-1] != '\r')
1584 pszSplit--;
1585 else
1586 {
1587 AssertLogRelMsgFailed(("Failed to find BEGIN CERTIFICATE markers in '%s'::'%s' - impossible unless it's a DER encoded certificate!",
1588 pTask->locInfo.strPath.c_str(), pszSubFileNm));
1589 pszSplit = (char *)pvSignature + cbSignature;
1590 }
1591 *pszSplit = '\0';
1592
1593 /*
1594 * Now, read the manifest part. We use the IPRT manifest reader here
1595 * to avoid duplicating code and be somewhat flexible wrt the digest
1596 * type choosen by the signer.
1597 */
1598 RTMANIFEST hSignedDigestManifest;
1599 vrc = RTManifestCreate(0 /*fFlags*/, &hSignedDigestManifest);
1600 if (RT_SUCCESS(vrc))
1601 {
1602 RTVFSIOSTREAM hVfsIosTmp;
1603 vrc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pvSignature, pszSplit - (char *)pvSignature, &hVfsIosTmp);
1604 if (RT_SUCCESS(vrc))
1605 {
1606 vrc = RTManifestReadStandardEx(hSignedDigestManifest, hVfsIosTmp, StaticErrInfo.szMsg, sizeof(StaticErrInfo.szMsg));
1607 RTVfsIoStrmRelease(hVfsIosTmp);
1608 if (RT_SUCCESS(vrc))
1609 {
1610 /*
1611 * Get signed digest, we prefer SHA-2, so explicitly query those first.
1612 */
1613 uint32_t fDigestType;
1614 char szSignedDigest[_8K + 1];
1615 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
1616 RTMANIFEST_ATTR_SHA512 | RTMANIFEST_ATTR_SHA256,
1617 szSignedDigest, sizeof(szSignedDigest), &fDigestType);
1618 if (vrc == VERR_MANIFEST_ATTR_TYPE_NOT_FOUND)
1619 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
1620 RTMANIFEST_ATTR_ANY, szSignedDigest, sizeof(szSignedDigest), &fDigestType);
1621 if (RT_SUCCESS(vrc))
1622 {
1623 const char *pszSignedDigest = RTStrStrip(szSignedDigest);
1624 size_t cbSignedDigest = strlen(pszSignedDigest) / 2;
1625 uint8_t abSignedDigest[sizeof(szSignedDigest) / 2];
1626 vrc = RTStrConvertHexBytes(szSignedDigest, abSignedDigest, cbSignedDigest, 0 /*fFlags*/);
1627 if (RT_SUCCESS(vrc))
1628 {
1629 /*
1630 * Convert it to RTDIGESTTYPE_XXX and save the binary value for later use.
1631 */
1632 switch (fDigestType)
1633 {
1634 case RTMANIFEST_ATTR_SHA1: m->enmSignedDigestType = RTDIGESTTYPE_SHA1; break;
1635 case RTMANIFEST_ATTR_SHA256: m->enmSignedDigestType = RTDIGESTTYPE_SHA256; break;
1636 case RTMANIFEST_ATTR_SHA512: m->enmSignedDigestType = RTDIGESTTYPE_SHA512; break;
1637 case RTMANIFEST_ATTR_MD5: m->enmSignedDigestType = RTDIGESTTYPE_MD5; break;
1638 default: AssertFailed(); m->enmSignedDigestType = RTDIGESTTYPE_INVALID; break;
1639 }
1640 if (m->enmSignedDigestType != RTDIGESTTYPE_INVALID)
1641 {
1642 m->pbSignedDigest = (uint8_t *)RTMemDup(abSignedDigest, cbSignedDigest);
1643 m->cbSignedDigest = cbSignedDigest;
1644 hrc = S_OK;
1645 }
1646 else
1647 hrc = setError(E_FAIL, tr("Unsupported signed digest type (%#x)"), fDigestType);
1648 }
1649 else
1650 hrc = setErrorVrc(vrc, tr("Error reading signed manifest digest: %Rrc"), vrc);
1651 }
1652 else if (vrc == VERR_NOT_FOUND)
1653 hrc = setErrorVrc(vrc, tr("Could not locate signed digest for '%s' in the cert-file for '%s'"),
1654 strManifestName.c_str(), pTask->locInfo.strPath.c_str());
1655 else
1656 hrc = setErrorVrc(vrc, tr("RTManifestEntryQueryAttr failed unexpectedly: %Rrc"), vrc);
1657 }
1658 else
1659 hrc = setErrorVrc(vrc, tr("Error parsing the .cert-file for '%s': %s"),
1660 pTask->locInfo.strPath.c_str(), StaticErrInfo.szMsg);
1661 }
1662 else
1663 hrc = E_OUTOFMEMORY;
1664 RTManifestRelease(hSignedDigestManifest);
1665 }
1666 else
1667 hrc = E_OUTOFMEMORY;
1668 }
1669 else if (vrc == VERR_NOT_FOUND || vrc == VERR_EOF)
1670 hrc = setErrorBoth(E_FAIL, vrc, tr("Malformed .cert-file for '%s': Signer's certificate not found (%Rrc)"),
1671 pTask->locInfo.strPath.c_str(), vrc);
1672 else
1673 hrc = setErrorVrc(vrc, tr("Error reading the signer's certificate from '%s' for '%s' (%Rrc): %s"),
1674 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
1675
1676 RTVfsIoStrmReadAllFree(pvSignature, cbSignature);
1677 LogFlowFunc(("returns %Rhrc (%Rrc)\n", hrc, vrc));
1678 return hrc;
1679}
1680
1681
1682/**
1683 * Does tail processing after the files have been read in.
1684 *
1685 * @param pTask The read task.
1686 * @returns COM status.
1687 * @throws Nothing!
1688 */
1689HRESULT Appliance::i_readTailProcessing(TaskOVF *pTask)
1690{
1691 /*
1692 * Parse and validate the signature file.
1693 *
1694 * The signature file has two parts, manifest part and a PEM encoded
1695 * certificate. The former contains an entry for the manifest file with a
1696 * digest that is encrypted with the certificate in the latter part.
1697 */
1698 if (m->pbSignedDigest)
1699 {
1700 /* Since we're validating the digest of the manifest, there have to be
1701 a manifest. We cannot allow a the manifest to be missing. */
1702 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
1703 return setError(VBOX_E_FILE_ERROR, tr("Found .cert-file but no .mf-file for '%s'"), pTask->locInfo.strPath.c_str());
1704
1705 /*
1706 * Validate the signed digest.
1707 *
1708 * It's possible we should allow the user to ignore signature
1709 * mismatches, but for now it is a solid show stopper.
1710 */
1711 HRESULT hrc;
1712 RTERRINFOSTATIC StaticErrInfo;
1713
1714 /* Calc the digest of the manifest using the algorithm found above. */
1715 RTCRDIGEST hDigest;
1716 int vrc = RTCrDigestCreateByType(&hDigest, m->enmSignedDigestType);
1717 if (RT_SUCCESS(vrc))
1718 {
1719 vrc = RTCrDigestUpdateFromVfsFile(hDigest, m->hMemFileTheirManifest, true /*fRewindFile*/);
1720 if (RT_SUCCESS(vrc))
1721 {
1722 /* Compare the signed digest with the one we just calculated. (This
1723 API will do the verification twice, once using IPRT's own crypto
1724 and once using OpenSSL. Both must OK it for success.) */
1725 vrc = RTCrPkixPubKeyVerifySignedDigest(&m->SignerCert.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm,
1726 &m->SignerCert.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Parameters,
1727 &m->SignerCert.TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey,
1728 m->pbSignedDigest, m->cbSignedDigest, hDigest,
1729 RTErrInfoInitStatic(&StaticErrInfo));
1730 if (RT_SUCCESS(vrc))
1731 {
1732 m->fSignatureValid = true;
1733 hrc = S_OK;
1734 }
1735 else if (vrc == VERR_CR_PKIX_SIGNATURE_MISMATCH)
1736 hrc = setErrorVrc(vrc, tr("The manifest signature does not match"));
1737 else
1738 hrc = setErrorVrc(vrc,
1739 tr("Error validating the manifest signature (%Rrc, %s)"), vrc, StaticErrInfo.Core.pszMsg);
1740 }
1741 else
1742 hrc = setErrorVrc(vrc, tr("RTCrDigestUpdateFromVfsFile failed: %Rrc"), vrc);
1743 RTCrDigestRelease(hDigest);
1744 }
1745 else
1746 hrc = setErrorVrc(vrc, tr("RTCrDigestCreateByType failed: %Rrc"), vrc);
1747
1748 /*
1749 * Validate the certificate.
1750 *
1751 * We don't fail here on if we cannot validate the certificate, we postpone
1752 * that till the import stage, so that we can allow the user to ignore it.
1753 *
1754 * The certificate validity time is deliberately left as warnings as the
1755 * OVF specification does not provision for any timestamping of the
1756 * signature. This is course a security concern, but the whole signing
1757 * of OVFs is currently weirdly trusting (self signed * certs), so this
1758 * is the least of our current problems.
1759 *
1760 * While we try build and verify certificate paths properly, the
1761 * "neighbours" quietly ignores this and seems only to check the signature
1762 * and not whether the certificate is trusted. Also, we don't currently
1763 * complain about self-signed certificates either (ditto "neighbours").
1764 * The OVF creator is also a bit restricted wrt to helping us build the
1765 * path as he cannot supply intermediate certificates. Anyway, we issue
1766 * warnings (goes to /dev/null, am I right?) for self-signed certificates
1767 * and certificates we cannot build and verify a root path for.
1768 *
1769 * (The OVF sillibuggers should've used PKCS#7, CMS or something else
1770 * that's already been standardized instead of combining manifests with
1771 * certificate PEM files in some very restrictive manner! I wonder if
1772 * we could add a PKCS#7 section to the .cert file in addition to the CERT
1773 * and manifest stuff dictated by the standard. Would depend on how others
1774 * deal with it.)
1775 */
1776 Assert(!m->fCertificateValid);
1777 Assert(m->fCertificateMissingPath);
1778 Assert(!m->fCertificateValidTime);
1779 Assert(m->strCertError.isEmpty());
1780 Assert(m->fCertificateIsSelfSigned == RTCrX509Certificate_IsSelfSigned(&m->SignerCert));
1781
1782 HRESULT hrc2 = S_OK;
1783 if (m->fCertificateIsSelfSigned)
1784 {
1785 /*
1786 * It's a self signed certificate. We assume the frontend will
1787 * present this fact to the user and give a choice whether this
1788 * is acceptible. But, first make sure it makes internal sense.
1789 */
1790 m->fCertificateMissingPath = true; /** @todo need to check if the certificate is trusted by the system! */
1791 vrc = RTCrX509Certificate_VerifySignatureSelfSigned(&m->SignerCert, RTErrInfoInitStatic(&StaticErrInfo));
1792 if (RT_SUCCESS(vrc))
1793 {
1794 m->fCertificateValid = true;
1795
1796 /* Check whether the certificate is currently valid, just warn if not. */
1797 RTTIMESPEC Now;
1798 if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
1799 {
1800 m->fCertificateValidTime = true;
1801 i_addWarning(tr("A self signed certificate was used to sign '%s'"), pTask->locInfo.strPath.c_str());
1802 }
1803 else
1804 i_addWarning(tr("Self signed certificate used to sign '%s' is not currently valid"),
1805 pTask->locInfo.strPath.c_str());
1806
1807 /* Just warn if it's not a CA. Self-signed certificates are
1808 hardly trustworthy to start with without the user's consent. */
1809 if ( !m->SignerCert.TbsCertificate.T3.pBasicConstraints
1810 || !m->SignerCert.TbsCertificate.T3.pBasicConstraints->CA.fValue)
1811 i_addWarning(tr("Self signed certificate used to sign '%s' is not marked as certificate authority (CA)"),
1812 pTask->locInfo.strPath.c_str());
1813 }
1814 else
1815 {
1816 try { m->strCertError = Utf8StrFmt(tr("Verification of the self signed certificate failed (%Rrc, %s)"),
1817 vrc, StaticErrInfo.Core.pszMsg); }
1818 catch (...) { AssertFailed(); }
1819 i_addWarning(tr("Verification of the self signed certificate used to sign '%s' failed (%Rrc): %s"),
1820 pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
1821 }
1822 }
1823 else
1824 {
1825 /*
1826 * The certificate is not self-signed. Use the system certificate
1827 * stores to try build a path that validates successfully.
1828 */
1829 RTCRX509CERTPATHS hCertPaths;
1830 vrc = RTCrX509CertPathsCreate(&hCertPaths, &m->SignerCert);
1831 if (RT_SUCCESS(vrc))
1832 {
1833 /* Get trusted certificates from the system and add them to the path finding mission. */
1834 RTCRSTORE hTrustedCerts;
1835 vrc = RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(&hTrustedCerts,
1836 RTErrInfoInitStatic(&StaticErrInfo));
1837 if (RT_SUCCESS(vrc))
1838 {
1839 vrc = RTCrX509CertPathsSetTrustedStore(hCertPaths, hTrustedCerts);
1840 if (RT_FAILURE(vrc))
1841 hrc2 = setError(E_FAIL, tr("RTCrX509CertPathsSetTrustedStore failed (%Rrc)"), vrc);
1842 RTCrStoreRelease(hTrustedCerts);
1843 }
1844 else
1845 hrc2 = setError(E_FAIL,
1846 tr("Failed to query trusted CAs and Certificates from the system and for the current user (%Rrc, %s)"),
1847 vrc, StaticErrInfo.Core.pszMsg);
1848
1849 /* Add untrusted intermediate certificates. */
1850 if (RT_SUCCESS(vrc))
1851 {
1852 /// @todo RTCrX509CertPathsSetUntrustedStore(hCertPaths, hAdditionalCerts);
1853 /// By scanning for additional certificates in the .cert file? It would be
1854 /// convenient to be able to supply intermediate certificates for the user,
1855 /// right? Or would that be unacceptable as it may weaken security?
1856 ///
1857 /// Anyway, we should look for intermediate certificates on the system, at
1858 /// least.
1859 }
1860 if (RT_SUCCESS(vrc))
1861 {
1862 /*
1863 * Do the building and verification of certificate paths.
1864 */
1865 vrc = RTCrX509CertPathsBuild(hCertPaths, RTErrInfoInitStatic(&StaticErrInfo));
1866 if (RT_SUCCESS(vrc))
1867 {
1868 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(&StaticErrInfo));
1869 if (RT_SUCCESS(vrc))
1870 {
1871 /*
1872 * Mark the certificate as good.
1873 */
1874 /** @todo check the certificate purpose? If so, share with self-signed. */
1875 m->fCertificateValid = true;
1876 m->fCertificateMissingPath = false;
1877
1878 /*
1879 * We add a warning if the certificate path isn't valid at the current
1880 * time. Since the time is only considered during path validation and we
1881 * can repeat the validation process (but not building), it's easy to check.
1882 */
1883 RTTIMESPEC Now;
1884 vrc = RTCrX509CertPathsSetValidTimeSpec(hCertPaths, RTTimeNow(&Now));
1885 if (RT_SUCCESS(vrc))
1886 {
1887 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(&StaticErrInfo));
1888 if (RT_SUCCESS(vrc))
1889 m->fCertificateValidTime = true;
1890 else
1891 i_addWarning(tr("The certificate used to sign '%s' (or a certificate in the path) is not currently valid (%Rrc)"),
1892 pTask->locInfo.strPath.c_str(), vrc);
1893 }
1894 else
1895 hrc2 = setErrorVrc(vrc, "RTCrX509CertPathsSetValidTimeSpec failed: %Rrc", vrc);
1896 }
1897 else if (vrc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS)
1898 {
1899 m->fCertificateValid = true;
1900 i_addWarning(tr("No trusted certificate paths"));
1901
1902 /* Add another warning if the pathless certificate is not valid at present. */
1903 RTTIMESPEC Now;
1904 if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
1905 m->fCertificateValidTime = true;
1906 else
1907 i_addWarning(tr("The certificate used to sign '%s' is not currently valid"),
1908 pTask->locInfo.strPath.c_str());
1909 }
1910 else
1911 hrc2 = setError(E_FAIL, tr("Certificate path validation failed (%Rrc, %s)"),
1912 vrc, StaticErrInfo.Core.pszMsg);
1913 }
1914 else
1915 hrc2 = setError(E_FAIL, tr("Certificate path building failed (%Rrc, %s)"),
1916 vrc, StaticErrInfo.Core.pszMsg);
1917 }
1918 RTCrX509CertPathsRelease(hCertPaths);
1919 }
1920 else
1921 hrc2 = setErrorVrc(vrc, tr("RTCrX509CertPathsCreate failed: %Rrc"), vrc);
1922 }
1923
1924 /* Merge statuses from signature and certificate validation, prefering the signature one. */
1925 if (SUCCEEDED(hrc) && FAILED(hrc2))
1926 hrc = hrc2;
1927 if (FAILED(hrc))
1928 return hrc;
1929 }
1930
1931 /** @todo provide details about the signatory, signature, etc. */
1932 if (m->fSignerCertLoaded)
1933 {
1934 m->ptrCertificateInfo.createObject();
1935 m->ptrCertificateInfo->initCertificate(&m->SignerCert,
1936 m->fCertificateValid && !m->fCertificateMissingPath,
1937 !m->fCertificateValidTime);
1938 }
1939
1940 /*
1941 * If there is a manifest, check that the OVF digest matches up (if present).
1942 */
1943
1944 NOREF(pTask);
1945 return S_OK;
1946}
1947
1948
1949
1950/*******************************************************************************
1951 * Import stuff
1952 ******************************************************************************/
1953
1954/**
1955 * Implementation for importing OVF data into VirtualBox. This starts a new thread which will call
1956 * Appliance::taskThreadImportOrExport().
1957 *
1958 * This creates one or more new machines according to the VirtualSystemScription instances created by
1959 * Appliance::Interpret().
1960 *
1961 * This is in a separate private method because it is used from one location:
1962 *
1963 * 1) from the public Appliance::ImportMachines().
1964 *
1965 * @param locInfo
1966 * @param progress
1967 * @return
1968 */
1969HRESULT Appliance::i_importImpl(const LocationInfo &locInfo,
1970 ComObjPtr<Progress> &progress)
1971{
1972 HRESULT rc = S_OK;
1973
1974 SetUpProgressMode mode;
1975 if (locInfo.storageType == VFSType_File)
1976 mode = ImportFile;
1977 else
1978 mode = ImportS3;
1979
1980 rc = i_setUpProgress(progress,
1981 BstrFmt(tr("Importing appliance '%s'"), locInfo.strPath.c_str()),
1982 mode);
1983 if (FAILED(rc)) throw rc;
1984
1985 /* Initialize our worker task */
1986 TaskOVF* task = NULL;
1987 try
1988 {
1989 task = new TaskOVF(this, TaskOVF::Import, locInfo, progress);
1990 }
1991 catch(...)
1992 {
1993 delete task;
1994 throw rc = setError(VBOX_E_OBJECT_NOT_FOUND,
1995 tr("Could not create TaskOVF object for importing OVF data into VirtualBox"));
1996 }
1997
1998 rc = task->createThread();
1999 if (FAILED(rc)) throw rc;
2000
2001 return rc;
2002}
2003
2004/**
2005 * Actual worker code for importing OVF data into VirtualBox.
2006 *
2007 * This is called from Appliance::taskThreadImportOrExport() and therefore runs
2008 * on the OVF import worker thread. This creates one or more new machines
2009 * according to the VirtualSystemScription instances created by
2010 * Appliance::Interpret().
2011 *
2012 * This runs in two contexts:
2013 *
2014 * 1) in a first worker thread; in that case, Appliance::ImportMachines() called
2015 * Appliance::i_importImpl();
2016 *
2017 * 2) in a second worker thread; in that case, Appliance::ImportMachines()
2018 * called Appliance::i_importImpl(), which called Appliance::i_importFSOVA(),
2019 * which called Appliance::i_importImpl(), which then called this again.
2020 *
2021 * @param pTask The OVF task data.
2022 * @return COM status code.
2023 */
2024HRESULT Appliance::i_importFS(TaskOVF *pTask)
2025{
2026 LogFlowFuncEnter();
2027 LogFlowFunc(("Appliance %p\n", this));
2028
2029 /* Change the appliance state so we can safely leave the lock while doing
2030 * time-consuming disk imports; also the below method calls do all kinds of
2031 * locking which conflicts with the appliance object lock. */
2032 AutoWriteLock writeLock(this COMMA_LOCKVAL_SRC_POS);
2033 /* Check if the appliance is currently busy. */
2034 if (!i_isApplianceIdle())
2035 return E_ACCESSDENIED;
2036 /* Set the internal state to importing. */
2037 m->state = Data::ApplianceImporting;
2038
2039 HRESULT rc = S_OK;
2040
2041 /* Clear the list of imported machines, if any */
2042 m->llGuidsMachinesCreated.clear();
2043
2044 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
2045 rc = i_importFSOVF(pTask, writeLock);
2046 else
2047 rc = i_importFSOVA(pTask, writeLock);
2048 if (FAILED(rc))
2049 {
2050 /* With _whatever_ error we've had, do a complete roll-back of
2051 * machines and disks we've created */
2052 writeLock.release();
2053 ErrorInfoKeeper eik;
2054 for (list<Guid>::iterator itID = m->llGuidsMachinesCreated.begin();
2055 itID != m->llGuidsMachinesCreated.end();
2056 ++itID)
2057 {
2058 Guid guid = *itID;
2059 Bstr bstrGuid = guid.toUtf16();
2060 ComPtr<IMachine> failedMachine;
2061 HRESULT rc2 = mVirtualBox->FindMachine(bstrGuid.raw(), failedMachine.asOutParam());
2062 if (SUCCEEDED(rc2))
2063 {
2064 SafeIfaceArray<IMedium> aMedia;
2065 rc2 = failedMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
2066 ComPtr<IProgress> pProgress2;
2067 rc2 = failedMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress2.asOutParam());
2068 pProgress2->WaitForCompletion(-1);
2069 }
2070 }
2071 writeLock.acquire();
2072 }
2073
2074 /* Reset the state so others can call methods again */
2075 m->state = Data::ApplianceIdle;
2076
2077 LogFlowFunc(("rc=%Rhrc\n", rc));
2078 LogFlowFuncLeave();
2079 return rc;
2080}
2081
2082HRESULT Appliance::i_importFSOVF(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
2083{
2084 return i_importDoIt(pTask, rWriteLock);
2085}
2086
2087HRESULT Appliance::i_importFSOVA(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
2088{
2089 LogFlowFuncEnter();
2090
2091 /*
2092 * Open the tar file as file stream.
2093 */
2094 RTVFSIOSTREAM hVfsIosOva;
2095 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
2096 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
2097 if (RT_FAILURE(vrc))
2098 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2099
2100 RTVFSFSSTREAM hVfsFssOva;
2101 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
2102 RTVfsIoStrmRelease(hVfsIosOva);
2103 if (RT_FAILURE(vrc))
2104 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2105
2106 /*
2107 * Join paths with the i_importFSOVF code.
2108 *
2109 * Note! We don't need to skip the OVF, manifest or signature files, as the
2110 * i_importMachineGeneric, i_importVBoxMachine and i_importOpenSourceFile
2111 * code will deal with this (as there could be other files in the OVA
2112 * that we don't process, like 'de-DE-resources.xml' in EXAMPLE 1,
2113 * Appendix D.1, OVF v2.1.0).
2114 */
2115 HRESULT hrc = i_importDoIt(pTask, rWriteLock, hVfsFssOva);
2116
2117 RTVfsFsStrmRelease(hVfsFssOva);
2118
2119 LogFlowFunc(("returns %Rhrc\n", hrc));
2120 return hrc;
2121}
2122
2123/**
2124 * Does the actual importing after the caller has made the source accessible.
2125 *
2126 * @param pTask The import task.
2127 * @param rWriteLock The write lock the caller's caller is holding,
2128 * will be released for some reason.
2129 * @param hVfsFssOva The file system stream if OVA, NIL if not.
2130 * @returns COM status code.
2131 * @throws Nothing.
2132 */
2133HRESULT Appliance::i_importDoIt(TaskOVF *pTask, AutoWriteLockBase &rWriteLock, RTVFSFSSTREAM hVfsFssOva /*= NIL_RTVFSFSSTREAM*/)
2134{
2135 rWriteLock.release();
2136
2137 HRESULT hrc = E_FAIL;
2138 try
2139 {
2140 /*
2141 * Create the import stack for the rollback on errors.
2142 */
2143 ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, hVfsFssOva);
2144
2145 try
2146 {
2147 /* Do the importing. */
2148 i_importMachines(stack);
2149
2150 /* We should've processed all the files now, so compare. */
2151 hrc = i_verifyManifestFile(stack);
2152 }
2153 catch (HRESULT hrcXcpt)
2154 {
2155 hrc = hrcXcpt;
2156 }
2157 catch (...)
2158 {
2159 AssertFailed();
2160 hrc = E_FAIL;
2161 }
2162 if (FAILED(hrc))
2163 {
2164 /*
2165 * Restoring original UUID from OVF description file.
2166 * During import VBox creates new UUIDs for imported images and
2167 * assigns them to the images. In case of failure we have to restore
2168 * the original UUIDs because those new UUIDs are obsolete now and
2169 * won't be used anymore.
2170 */
2171 ErrorInfoKeeper eik; /* paranoia */
2172 list< ComObjPtr<VirtualSystemDescription> >::const_iterator itvsd;
2173 /* Iterate through all virtual systems of that appliance */
2174 for (itvsd = m->virtualSystemDescriptions.begin();
2175 itvsd != m->virtualSystemDescriptions.end();
2176 ++itvsd)
2177 {
2178 ComObjPtr<VirtualSystemDescription> vsdescThis = (*itvsd);
2179 settings::MachineConfigFile *pConfig = vsdescThis->m->pConfig;
2180 if(vsdescThis->m->pConfig!=NULL)
2181 stack.restoreOriginalUUIDOfAttachedDevice(pConfig);
2182 }
2183 }
2184 }
2185 catch (...)
2186 {
2187 hrc = E_FAIL;
2188 AssertFailed();
2189 }
2190
2191 rWriteLock.acquire();
2192 return hrc;
2193}
2194
2195/**
2196 * Undocumented, you figure it from the name.
2197 *
2198 * @returns Undocumented
2199 * @param stack Undocumented.
2200 */
2201HRESULT Appliance::i_verifyManifestFile(ImportStack &stack)
2202{
2203 LogFlowThisFuncEnter();
2204 HRESULT hrc;
2205 int vrc;
2206
2207 /*
2208 * No manifest is fine, it always matches.
2209 */
2210 if (m->hTheirManifest == NIL_RTMANIFEST)
2211 hrc = S_OK;
2212 else
2213 {
2214 /*
2215 * Hack: If the manifest we just read doesn't have a digest for the OVF, copy
2216 * it from the manifest we got from the caller.
2217 * @bugref{6022#c119}
2218 */
2219 if ( !RTManifestEntryExists(m->hTheirManifest, m->strOvfManifestEntry.c_str())
2220 && RTManifestEntryExists(m->hOurManifest, m->strOvfManifestEntry.c_str()) )
2221 {
2222 uint32_t fType = 0;
2223 char szDigest[512 + 1];
2224 vrc = RTManifestEntryQueryAttr(m->hOurManifest, m->strOvfManifestEntry.c_str(), NULL, RTMANIFEST_ATTR_ANY,
2225 szDigest, sizeof(szDigest), &fType);
2226 if (RT_SUCCESS(vrc))
2227 vrc = RTManifestEntrySetAttr(m->hTheirManifest, m->strOvfManifestEntry.c_str(),
2228 NULL /*pszAttr*/, szDigest, fType);
2229 if (RT_FAILURE(vrc))
2230 return setError(VBOX_E_IPRT_ERROR, tr("Error fudging missing OVF digest in manifest: %Rrc"), vrc);
2231 }
2232
2233 /*
2234 * Compare with the digests we've created while read/processing the import.
2235 *
2236 * We specify the RTMANIFEST_EQUALS_IGN_MISSING_ATTRS to ignore attributes
2237 * (SHA1, SHA256, etc) that are only present in one of the manifests, as long
2238 * as each entry has at least one common attribute that we can check. This
2239 * is important for the OVF in OVAs, for which we generates several digests
2240 * since we don't know which are actually used in the manifest (OVF comes
2241 * first in an OVA, then manifest).
2242 */
2243 char szErr[256];
2244 vrc = RTManifestEqualsEx(m->hTheirManifest, m->hOurManifest, NULL /*papszIgnoreEntries*/,
2245 NULL /*papszIgnoreAttrs*/, RTMANIFEST_EQUALS_IGN_MISSING_ATTRS, szErr, sizeof(szErr));
2246 if (RT_SUCCESS(vrc))
2247 hrc = S_OK;
2248 else
2249 hrc = setErrorVrc(vrc, tr("Digest mismatch (%Rrc): %s"), vrc, szErr);
2250 }
2251
2252 NOREF(stack);
2253 LogFlowThisFunc(("returns %Rhrc\n", hrc));
2254 return hrc;
2255}
2256
2257/**
2258 * Helper that converts VirtualSystem attachment values into VirtualBox attachment values.
2259 * Throws HRESULT values on errors!
2260 *
2261 * @param hdc in: the HardDiskController structure to attach to.
2262 * @param ulAddressOnParent in: the AddressOnParent parameter from OVF.
2263 * @param controllerName out: the name of the hard disk controller to attach to (e.g. "IDE").
2264 * @param lControllerPort out: the channel (controller port) of the controller to attach to.
2265 * @param lDevice out: the device number to attach to.
2266 */
2267void Appliance::i_convertDiskAttachmentValues(const ovf::HardDiskController &hdc,
2268 uint32_t ulAddressOnParent,
2269 Utf8Str &controllerName,
2270 int32_t &lControllerPort,
2271 int32_t &lDevice)
2272{
2273 Log(("Appliance::i_convertDiskAttachmentValues: hdc.system=%d, hdc.fPrimary=%d, ulAddressOnParent=%d\n",
2274 hdc.system,
2275 hdc.fPrimary,
2276 ulAddressOnParent));
2277
2278 switch (hdc.system)
2279 {
2280 case ovf::HardDiskController::IDE:
2281 // For the IDE bus, the port parameter can be either 0 or 1, to specify the primary
2282 // or secondary IDE controller, respectively. For the primary controller of the IDE bus,
2283 // the device number can be either 0 or 1, to specify the master or the slave device,
2284 // respectively. For the secondary IDE controller, the device number is always 1 because
2285 // the master device is reserved for the CD-ROM drive.
2286 controllerName = "IDE";
2287 switch (ulAddressOnParent)
2288 {
2289 case 0: // master
2290 if (!hdc.fPrimary)
2291 {
2292 // secondary master
2293 lControllerPort = (long)1;
2294 lDevice = (long)0;
2295 }
2296 else // primary master
2297 {
2298 lControllerPort = (long)0;
2299 lDevice = (long)0;
2300 }
2301 break;
2302
2303 case 1: // slave
2304 if (!hdc.fPrimary)
2305 {
2306 // secondary slave
2307 lControllerPort = (long)1;
2308 lDevice = (long)1;
2309 }
2310 else // primary slave
2311 {
2312 lControllerPort = (long)0;
2313 lDevice = (long)1;
2314 }
2315 break;
2316
2317 // used by older VBox exports
2318 case 2: // interpret this as secondary master
2319 lControllerPort = (long)1;
2320 lDevice = (long)0;
2321 break;
2322
2323 // used by older VBox exports
2324 case 3: // interpret this as secondary slave
2325 lControllerPort = (long)1;
2326 lDevice = (long)1;
2327 break;
2328
2329 default:
2330 throw setError(VBOX_E_NOT_SUPPORTED,
2331 tr("Invalid channel %RI16 specified; IDE controllers support only 0, 1 or 2"),
2332 ulAddressOnParent);
2333 break;
2334 }
2335 break;
2336
2337 case ovf::HardDiskController::SATA:
2338 controllerName = "SATA";
2339 lControllerPort = (long)ulAddressOnParent;
2340 lDevice = (long)0;
2341 break;
2342
2343 case ovf::HardDiskController::SCSI:
2344 {
2345 if(hdc.strControllerType.compare("lsilogicsas")==0)
2346 controllerName = "SAS";
2347 else
2348 controllerName = "SCSI";
2349 lControllerPort = (long)ulAddressOnParent;
2350 lDevice = (long)0;
2351 break;
2352 }
2353
2354 default: break;
2355 }
2356
2357 Log(("=> lControllerPort=%d, lDevice=%d\n", lControllerPort, lDevice));
2358}
2359
2360/**
2361 * Imports one disk image.
2362 *
2363 * This is common code shared between
2364 * -- i_importMachineGeneric() for the OVF case; in that case the information comes from
2365 * the OVF virtual systems;
2366 * -- i_importVBoxMachine(); in that case, the information comes from the <vbox:Machine>
2367 * tag.
2368 *
2369 * Both ways of describing machines use the OVF disk references section, so in both cases
2370 * the caller needs to pass in the ovf::DiskImage structure from ovfreader.cpp.
2371 *
2372 * As a result, in both cases, if di.strHref is empty, we create a new disk as per the OVF
2373 * spec, even though this cannot really happen in the vbox:Machine case since such data
2374 * would never have been exported.
2375 *
2376 * This advances stack.pProgress by one operation with the disk's weight.
2377 *
2378 * @param di ovfreader.cpp structure describing the disk image from the OVF that is to be imported
2379 * @param strTargetPath Where to create the target image.
2380 * @param pTargetHD out: The newly created target disk. This also gets pushed on stack.llHardDisksCreated for cleanup.
2381 * @param stack
2382 */
2383void Appliance::i_importOneDiskImage(const ovf::DiskImage &di,
2384 Utf8Str *pStrDstPath,
2385 ComObjPtr<Medium> &pTargetHD,
2386 ImportStack &stack)
2387{
2388 ComObjPtr<Progress> pProgress;
2389 pProgress.createObject();
2390 HRESULT rc = pProgress->init(mVirtualBox,
2391 static_cast<IAppliance*>(this),
2392 BstrFmt(tr("Creating medium '%s'"),
2393 pStrDstPath->c_str()).raw(),
2394 TRUE);
2395 if (FAILED(rc)) throw rc;
2396
2397 /* Get the system properties. */
2398 SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
2399
2400 /* Keep the source file ref handy for later. */
2401 const Utf8Str &strSourceOVF = di.strHref;
2402
2403 /* Construct source file path */
2404 Utf8Str strSrcFilePath;
2405 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
2406 strSrcFilePath = strSourceOVF;
2407 else
2408 {
2409 strSrcFilePath = stack.strSourceDir;
2410 strSrcFilePath.append(RTPATH_SLASH_STR);
2411 strSrcFilePath.append(strSourceOVF);
2412 }
2413
2414 /* First of all check if the path is an UUID. If so, the user like to
2415 * import the disk into an existing path. This is useful for iSCSI for
2416 * example. */
2417 RTUUID uuid;
2418 int vrc = RTUuidFromStr(&uuid, pStrDstPath->c_str());
2419 if (vrc == VINF_SUCCESS)
2420 {
2421 rc = mVirtualBox->i_findHardDiskById(Guid(uuid), true, &pTargetHD);
2422 if (FAILED(rc)) throw rc;
2423 }
2424 else
2425 {
2426 RTVFSIOSTREAM hVfsIosSrc = NIL_RTVFSIOSTREAM;
2427
2428 /* check read file to GZIP compression */
2429 bool const fGzipped = di.strCompression.compare("gzip",Utf8Str::CaseInsensitive) == 0;
2430 Utf8Str strDeleteTemp;
2431 try
2432 {
2433 Utf8Str strTrgFormat = "VMDK";
2434 ComObjPtr<MediumFormat> trgFormat;
2435 Bstr bstrFormatName;
2436 ULONG lCabs = 0;
2437
2438 char *pszSuff = RTPathSuffix(pStrDstPath->c_str());
2439 if (pszSuff != NULL)
2440 {
2441 /*
2442 * Figure out which format the user like to have. Default is VMDK
2443 * or it can be VDI if according command-line option is set
2444 */
2445
2446 /*
2447 * We need a proper target format
2448 * if target format has been changed by user via GUI import wizard
2449 * or via VBoxManage import command (option --importtovdi)
2450 * then we need properly process such format like ISO
2451 * Because there is no conversion ISO to VDI
2452 */
2453 trgFormat = pSysProps->i_mediumFormatFromExtension(++pszSuff);
2454 if (trgFormat.isNull())
2455 throw setError(E_FAIL, tr("Unsupported medium format for disk image '%s'"), di.strHref.c_str());
2456
2457 rc = trgFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
2458 if (FAILED(rc)) throw rc;
2459
2460 strTrgFormat = Utf8Str(bstrFormatName);
2461
2462 if ( m->optListImport.contains(ImportOptions_ImportToVDI)
2463 && strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) != 0)
2464 {
2465 /* change the target extension */
2466 strTrgFormat = "vdi";
2467 trgFormat = pSysProps->i_mediumFormatFromExtension(strTrgFormat);
2468 *pStrDstPath = pStrDstPath->stripSuffix();
2469 *pStrDstPath = pStrDstPath->append(".");
2470 *pStrDstPath = pStrDstPath->append(strTrgFormat.c_str());
2471 }
2472
2473 /* Check the capabilities. We need create capabilities. */
2474 lCabs = 0;
2475 com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
2476 rc = trgFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
2477
2478 if (FAILED(rc))
2479 throw rc;
2480
2481 for (ULONG j = 0; j < mediumFormatCap.size(); j++)
2482 lCabs |= mediumFormatCap[j];
2483
2484 if ( !(lCabs & MediumFormatCapabilities_CreateFixed)
2485 && !(lCabs & MediumFormatCapabilities_CreateDynamic) )
2486 throw setError(VBOX_E_NOT_SUPPORTED,
2487 tr("Could not find a valid medium format for the target disk '%s'"),
2488 pStrDstPath->c_str());
2489 }
2490 else
2491 {
2492 throw setError(VBOX_E_FILE_ERROR,
2493 tr("The target disk '%s' has no extension "),
2494 pStrDstPath->c_str(), VERR_INVALID_NAME);
2495 }
2496
2497 /* Create an IMedium object. */
2498 pTargetHD.createObject();
2499
2500 /*CD/DVD case*/
2501 if (strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) == 0)
2502 {
2503 try
2504 {
2505 if (fGzipped)
2506 i_importDecompressFile(stack, strSrcFilePath, *pStrDstPath, strSourceOVF.c_str());
2507 else
2508 i_importCopyFile(stack, strSrcFilePath, *pStrDstPath, strSourceOVF.c_str());
2509 }
2510 catch (HRESULT /*arc*/)
2511 {
2512 throw;
2513 }
2514
2515 /* Advance to the next operation. */
2516 /* operation's weight, as set up with the IProgress originally */
2517 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
2518 RTPathFilename(strSourceOVF.c_str())).raw(),
2519 di.ulSuggestedSizeMB);
2520 }
2521 else/* HDD case*/
2522 {
2523 rc = pTargetHD->init(mVirtualBox,
2524 strTrgFormat,
2525 *pStrDstPath,
2526 Guid::Empty /* media registry: none yet */,
2527 DeviceType_HardDisk);
2528 if (FAILED(rc)) throw rc;
2529
2530 /* Now create an empty hard disk. */
2531 rc = mVirtualBox->CreateMedium(Bstr(strTrgFormat).raw(),
2532 Bstr(*pStrDstPath).raw(),
2533 AccessMode_ReadWrite, DeviceType_HardDisk,
2534 ComPtr<IMedium>(pTargetHD).asOutParam());
2535 if (FAILED(rc)) throw rc;
2536
2537 /* If strHref is empty we have to create a new file. */
2538 if (strSourceOVF.isEmpty())
2539 {
2540 com::SafeArray<MediumVariant_T> mediumVariant;
2541 mediumVariant.push_back(MediumVariant_Standard);
2542
2543 /* Kick of the creation of a dynamic growing disk image with the given capacity. */
2544 rc = pTargetHD->CreateBaseStorage(di.iCapacity / _1M,
2545 ComSafeArrayAsInParam(mediumVariant),
2546 ComPtr<IProgress>(pProgress).asOutParam());
2547 if (FAILED(rc)) throw rc;
2548
2549 /* Advance to the next operation. */
2550 /* operation's weight, as set up with the IProgress originally */
2551 stack.pProgress->SetNextOperation(BstrFmt(tr("Creating disk image '%s'"),
2552 pStrDstPath->c_str()).raw(),
2553 di.ulSuggestedSizeMB);
2554 }
2555 else
2556 {
2557 /* We need a proper source format description */
2558 /* Which format to use? */
2559 ComObjPtr<MediumFormat> srcFormat;
2560 rc = i_findMediumFormatFromDiskImage(di, srcFormat);
2561 if (FAILED(rc))
2562 throw setError(VBOX_E_NOT_SUPPORTED,
2563 tr("Could not find a valid medium format for the source disk '%s' "
2564 "Check correctness of the image format URL in the OVF description file "
2565 "or extension of the image"),
2566 RTPathFilename(strSourceOVF.c_str()));
2567
2568 /* If gzipped, decompress the GZIP file and save a new file in the target path */
2569 if (fGzipped)
2570 {
2571 Utf8Str strTargetFilePath(*pStrDstPath);
2572 strTargetFilePath.stripFilename();
2573 strTargetFilePath.append(RTPATH_SLASH_STR);
2574 strTargetFilePath.append("temp_");
2575 strTargetFilePath.append(RTPathFilename(strSrcFilePath.c_str()));
2576 strDeleteTemp = strTargetFilePath;
2577
2578 i_importDecompressFile(stack, strSrcFilePath, strTargetFilePath, strSourceOVF.c_str());
2579
2580 /* Correct the source and the target with the actual values */
2581 strSrcFilePath = strTargetFilePath;
2582
2583 /* Open the new source file. */
2584 vrc = RTVfsIoStrmOpenNormal(strSrcFilePath.c_str(), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
2585 &hVfsIosSrc);
2586 if (RT_FAILURE(vrc))
2587 throw setErrorVrc(vrc, tr("Error opening decompressed image file '%s' (%Rrc)"),
2588 strSrcFilePath.c_str(), vrc);
2589 }
2590 else
2591 hVfsIosSrc = i_importOpenSourceFile(stack, strSrcFilePath, strSourceOVF.c_str());
2592
2593 /* Add a read ahead thread to try speed things up with concurrent reads and
2594 writes going on in different threads. */
2595 RTVFSIOSTREAM hVfsIosReadAhead;
2596 vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/,
2597 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
2598 RTVfsIoStrmRelease(hVfsIosSrc);
2599 if (RT_FAILURE(vrc))
2600 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"),
2601 strSrcFilePath.c_str(), vrc);
2602
2603 /* Start the source image cloning operation. */
2604 ComObjPtr<Medium> nullParent;
2605 rc = pTargetHD->i_importFile(strSrcFilePath.c_str(),
2606 srcFormat,
2607 MediumVariant_Standard,
2608 hVfsIosReadAhead,
2609 nullParent,
2610 pProgress);
2611 RTVfsIoStrmRelease(hVfsIosReadAhead);
2612 hVfsIosSrc = NIL_RTVFSIOSTREAM;
2613 if (FAILED(rc))
2614 throw rc;
2615
2616 /* Advance to the next operation. */
2617 /* operation's weight, as set up with the IProgress originally */
2618 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
2619 RTPathFilename(strSourceOVF.c_str())).raw(),
2620 di.ulSuggestedSizeMB);
2621 }
2622
2623 /* Now wait for the background disk operation to complete; this throws
2624 * HRESULTs on error. */
2625 ComPtr<IProgress> pp(pProgress);
2626 i_waitForAsyncProgress(stack.pProgress, pp);
2627 }
2628 }
2629 catch (...)
2630 {
2631 if (strDeleteTemp.isNotEmpty())
2632 RTFileDelete(strDeleteTemp.c_str());
2633 throw;
2634 }
2635
2636 /* Make sure the source file is closed. */
2637 if (hVfsIosSrc != NIL_RTVFSIOSTREAM)
2638 RTVfsIoStrmRelease(hVfsIosSrc);
2639
2640 /*
2641 * Delete the temp gunzip result, if any.
2642 */
2643 if (strDeleteTemp.isNotEmpty())
2644 {
2645 vrc = RTFileDelete(strSrcFilePath.c_str());
2646 if (RT_FAILURE(vrc))
2647 setWarning(VBOX_E_FILE_ERROR,
2648 tr("Failed to delete the temporary file '%s' (%Rrc)"), strSrcFilePath.c_str(), vrc);
2649 }
2650 }
2651}
2652
2653/**
2654 * Imports one OVF virtual system (described by the given ovf::VirtualSystem and VirtualSystemDescription)
2655 * into VirtualBox by creating an IMachine instance, which is returned.
2656 *
2657 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
2658 * up any leftovers from this function. For this, the given ImportStack instance has received information
2659 * about what needs cleaning up (to support rollback).
2660 *
2661 * @param vsysThis OVF virtual system (machine) to import.
2662 * @param vsdescThis Matching virtual system description (machine) to import.
2663 * @param pNewMachine out: Newly created machine.
2664 * @param stack Cleanup stack for when this throws.
2665 */
2666void Appliance::i_importMachineGeneric(const ovf::VirtualSystem &vsysThis,
2667 ComObjPtr<VirtualSystemDescription> &vsdescThis,
2668 ComPtr<IMachine> &pNewMachine,
2669 ImportStack &stack)
2670{
2671 LogFlowFuncEnter();
2672 HRESULT rc;
2673
2674 // Get the instance of IGuestOSType which matches our string guest OS type so we
2675 // can use recommended defaults for the new machine where OVF doesn't provide any
2676 ComPtr<IGuestOSType> osType;
2677 rc = mVirtualBox->GetGuestOSType(Bstr(stack.strOsTypeVBox).raw(), osType.asOutParam());
2678 if (FAILED(rc)) throw rc;
2679
2680 /* Create the machine */
2681 SafeArray<BSTR> groups; /* no groups */
2682 rc = mVirtualBox->CreateMachine(NULL, /* machine name: use default */
2683 Bstr(stack.strNameVBox).raw(),
2684 ComSafeArrayAsInParam(groups),
2685 Bstr(stack.strOsTypeVBox).raw(),
2686 NULL, /* aCreateFlags */
2687 pNewMachine.asOutParam());
2688 if (FAILED(rc)) throw rc;
2689
2690 // set the description
2691 if (!stack.strDescription.isEmpty())
2692 {
2693 rc = pNewMachine->COMSETTER(Description)(Bstr(stack.strDescription).raw());
2694 if (FAILED(rc)) throw rc;
2695 }
2696
2697 // CPU count
2698 rc = pNewMachine->COMSETTER(CPUCount)(stack.cCPUs);
2699 if (FAILED(rc)) throw rc;
2700
2701 if (stack.fForceHWVirt)
2702 {
2703 rc = pNewMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, TRUE);
2704 if (FAILED(rc)) throw rc;
2705 }
2706
2707 // RAM
2708 rc = pNewMachine->COMSETTER(MemorySize)(stack.ulMemorySizeMB);
2709 if (FAILED(rc)) throw rc;
2710
2711 /* VRAM */
2712 /* Get the recommended VRAM for this guest OS type */
2713 ULONG vramVBox;
2714 rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox);
2715 if (FAILED(rc)) throw rc;
2716
2717 /* Set the VRAM */
2718 rc = pNewMachine->COMSETTER(VRAMSize)(vramVBox);
2719 if (FAILED(rc)) throw rc;
2720
2721 // I/O APIC: Generic OVF has no setting for this. Enable it if we
2722 // import a Windows VM because if if Windows was installed without IOAPIC,
2723 // it will not mind finding an one later on, but if Windows was installed
2724 // _with_ an IOAPIC, it will bluescreen if it's not found
2725 if (!stack.fForceIOAPIC)
2726 {
2727 Bstr bstrFamilyId;
2728 rc = osType->COMGETTER(FamilyId)(bstrFamilyId.asOutParam());
2729 if (FAILED(rc)) throw rc;
2730 if (bstrFamilyId == "Windows")
2731 stack.fForceIOAPIC = true;
2732 }
2733
2734 if (stack.fForceIOAPIC)
2735 {
2736 ComPtr<IBIOSSettings> pBIOSSettings;
2737 rc = pNewMachine->COMGETTER(BIOSSettings)(pBIOSSettings.asOutParam());
2738 if (FAILED(rc)) throw rc;
2739
2740 rc = pBIOSSettings->COMSETTER(IOAPICEnabled)(TRUE);
2741 if (FAILED(rc)) throw rc;
2742 }
2743
2744 if (!stack.strAudioAdapter.isEmpty())
2745 if (stack.strAudioAdapter.compare("null", Utf8Str::CaseInsensitive) != 0)
2746 {
2747 uint32_t audio = RTStrToUInt32(stack.strAudioAdapter.c_str()); // should be 0 for AC97
2748 ComPtr<IAudioAdapter> audioAdapter;
2749 rc = pNewMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam());
2750 if (FAILED(rc)) throw rc;
2751 rc = audioAdapter->COMSETTER(Enabled)(true);
2752 if (FAILED(rc)) throw rc;
2753 rc = audioAdapter->COMSETTER(AudioController)(static_cast<AudioControllerType_T>(audio));
2754 if (FAILED(rc)) throw rc;
2755 }
2756
2757#ifdef VBOX_WITH_USB
2758 /* USB Controller */
2759 if (stack.fUSBEnabled)
2760 {
2761 ComPtr<IUSBController> usbController;
2762 rc = pNewMachine->AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, usbController.asOutParam());
2763 if (FAILED(rc)) throw rc;
2764 }
2765#endif /* VBOX_WITH_USB */
2766
2767 /* Change the network adapters */
2768 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
2769
2770 std::list<VirtualSystemDescriptionEntry*> vsdeNW = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
2771 if (vsdeNW.empty())
2772 {
2773 /* No network adapters, so we have to disable our default one */
2774 ComPtr<INetworkAdapter> nwVBox;
2775 rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam());
2776 if (FAILED(rc)) throw rc;
2777 rc = nwVBox->COMSETTER(Enabled)(false);
2778 if (FAILED(rc)) throw rc;
2779 }
2780 else if (vsdeNW.size() > maxNetworkAdapters)
2781 throw setError(VBOX_E_FILE_ERROR,
2782 tr("Too many network adapters: OVF requests %d network adapters, "
2783 "but VirtualBox only supports %d"),
2784 vsdeNW.size(), maxNetworkAdapters);
2785 else
2786 {
2787 list<VirtualSystemDescriptionEntry*>::const_iterator nwIt;
2788 size_t a = 0;
2789 for (nwIt = vsdeNW.begin();
2790 nwIt != vsdeNW.end();
2791 ++nwIt, ++a)
2792 {
2793 const VirtualSystemDescriptionEntry* pvsys = *nwIt;
2794
2795 const Utf8Str &nwTypeVBox = pvsys->strVBoxCurrent;
2796 uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str());
2797 ComPtr<INetworkAdapter> pNetworkAdapter;
2798 rc = pNewMachine->GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
2799 if (FAILED(rc)) throw rc;
2800 /* Enable the network card & set the adapter type */
2801 rc = pNetworkAdapter->COMSETTER(Enabled)(true);
2802 if (FAILED(rc)) throw rc;
2803 rc = pNetworkAdapter->COMSETTER(AdapterType)(static_cast<NetworkAdapterType_T>(tt1));
2804 if (FAILED(rc)) throw rc;
2805
2806 // default is NAT; change to "bridged" if extra conf says so
2807 if (pvsys->strExtraConfigCurrent.endsWith("type=Bridged", Utf8Str::CaseInsensitive))
2808 {
2809 /* Attach to the right interface */
2810 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged);
2811 if (FAILED(rc)) throw rc;
2812 ComPtr<IHost> host;
2813 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
2814 if (FAILED(rc)) throw rc;
2815 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
2816 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
2817 if (FAILED(rc)) throw rc;
2818 // We search for the first host network interface which
2819 // is usable for bridged networking
2820 for (size_t j = 0;
2821 j < nwInterfaces.size();
2822 ++j)
2823 {
2824 HostNetworkInterfaceType_T itype;
2825 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
2826 if (FAILED(rc)) throw rc;
2827 if (itype == HostNetworkInterfaceType_Bridged)
2828 {
2829 Bstr name;
2830 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
2831 if (FAILED(rc)) throw rc;
2832 /* Set the interface name to attach to */
2833 rc = pNetworkAdapter->COMSETTER(BridgedInterface)(name.raw());
2834 if (FAILED(rc)) throw rc;
2835 break;
2836 }
2837 }
2838 }
2839 /* Next test for host only interfaces */
2840 else if (pvsys->strExtraConfigCurrent.endsWith("type=HostOnly", Utf8Str::CaseInsensitive))
2841 {
2842 /* Attach to the right interface */
2843 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly);
2844 if (FAILED(rc)) throw rc;
2845 ComPtr<IHost> host;
2846 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
2847 if (FAILED(rc)) throw rc;
2848 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
2849 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
2850 if (FAILED(rc)) throw rc;
2851 // We search for the first host network interface which
2852 // is usable for host only networking
2853 for (size_t j = 0;
2854 j < nwInterfaces.size();
2855 ++j)
2856 {
2857 HostNetworkInterfaceType_T itype;
2858 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
2859 if (FAILED(rc)) throw rc;
2860 if (itype == HostNetworkInterfaceType_HostOnly)
2861 {
2862 Bstr name;
2863 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
2864 if (FAILED(rc)) throw rc;
2865 /* Set the interface name to attach to */
2866 rc = pNetworkAdapter->COMSETTER(HostOnlyInterface)(name.raw());
2867 if (FAILED(rc)) throw rc;
2868 break;
2869 }
2870 }
2871 }
2872 /* Next test for internal interfaces */
2873 else if (pvsys->strExtraConfigCurrent.endsWith("type=Internal", Utf8Str::CaseInsensitive))
2874 {
2875 /* Attach to the right interface */
2876 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Internal);
2877 if (FAILED(rc)) throw rc;
2878 }
2879 /* Next test for Generic interfaces */
2880 else if (pvsys->strExtraConfigCurrent.endsWith("type=Generic", Utf8Str::CaseInsensitive))
2881 {
2882 /* Attach to the right interface */
2883 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Generic);
2884 if (FAILED(rc)) throw rc;
2885 }
2886
2887 /* Next test for NAT network interfaces */
2888 else if (pvsys->strExtraConfigCurrent.endsWith("type=NATNetwork", Utf8Str::CaseInsensitive))
2889 {
2890 /* Attach to the right interface */
2891 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork);
2892 if (FAILED(rc)) throw rc;
2893 com::SafeIfaceArray<INATNetwork> nwNATNetworks;
2894 rc = mVirtualBox->COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(nwNATNetworks));
2895 if (FAILED(rc)) throw rc;
2896 // Pick the first NAT network (if there is any)
2897 if (nwNATNetworks.size())
2898 {
2899 Bstr name;
2900 rc = nwNATNetworks[0]->COMGETTER(NetworkName)(name.asOutParam());
2901 if (FAILED(rc)) throw rc;
2902 /* Set the NAT network name to attach to */
2903 rc = pNetworkAdapter->COMSETTER(NATNetwork)(name.raw());
2904 if (FAILED(rc)) throw rc;
2905 break;
2906 }
2907 }
2908 }
2909 }
2910
2911 // IDE Hard disk controller
2912 std::list<VirtualSystemDescriptionEntry*> vsdeHDCIDE =
2913 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerIDE);
2914 /*
2915 * In OVF (at least VMware's version of it), an IDE controller has two ports,
2916 * so VirtualBox's single IDE controller with two channels and two ports each counts as
2917 * two OVF IDE controllers -- so we accept one or two such IDE controllers
2918 */
2919 size_t cIDEControllers = vsdeHDCIDE.size();
2920 if (cIDEControllers > 2)
2921 throw setError(VBOX_E_FILE_ERROR,
2922 tr("Too many IDE controllers in OVF; import facility only supports two"));
2923 if (!vsdeHDCIDE.empty())
2924 {
2925 // one or two IDE controllers present in OVF: add one VirtualBox controller
2926 ComPtr<IStorageController> pController;
2927 rc = pNewMachine->AddStorageController(Bstr("IDE").raw(), StorageBus_IDE, pController.asOutParam());
2928 if (FAILED(rc)) throw rc;
2929
2930 const char *pcszIDEType = vsdeHDCIDE.front()->strVBoxCurrent.c_str();
2931 if (!strcmp(pcszIDEType, "PIIX3"))
2932 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX3);
2933 else if (!strcmp(pcszIDEType, "PIIX4"))
2934 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX4);
2935 else if (!strcmp(pcszIDEType, "ICH6"))
2936 rc = pController->COMSETTER(ControllerType)(StorageControllerType_ICH6);
2937 else
2938 throw setError(VBOX_E_FILE_ERROR,
2939 tr("Invalid IDE controller type \"%s\""),
2940 pcszIDEType);
2941 if (FAILED(rc)) throw rc;
2942 }
2943
2944 /* Hard disk controller SATA */
2945 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSATA =
2946 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSATA);
2947 if (vsdeHDCSATA.size() > 1)
2948 throw setError(VBOX_E_FILE_ERROR,
2949 tr("Too many SATA controllers in OVF; import facility only supports one"));
2950 if (!vsdeHDCSATA.empty())
2951 {
2952 ComPtr<IStorageController> pController;
2953 const Utf8Str &hdcVBox = vsdeHDCSATA.front()->strVBoxCurrent;
2954 if (hdcVBox == "AHCI")
2955 {
2956 rc = pNewMachine->AddStorageController(Bstr("SATA").raw(),
2957 StorageBus_SATA,
2958 pController.asOutParam());
2959 if (FAILED(rc)) throw rc;
2960 }
2961 else
2962 throw setError(VBOX_E_FILE_ERROR,
2963 tr("Invalid SATA controller type \"%s\""),
2964 hdcVBox.c_str());
2965 }
2966
2967 /* Hard disk controller SCSI */
2968 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSCSI =
2969 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI);
2970 if (vsdeHDCSCSI.size() > 1)
2971 throw setError(VBOX_E_FILE_ERROR,
2972 tr("Too many SCSI controllers in OVF; import facility only supports one"));
2973 if (!vsdeHDCSCSI.empty())
2974 {
2975 ComPtr<IStorageController> pController;
2976 Utf8Str strName("SCSI");
2977 StorageBus_T busType = StorageBus_SCSI;
2978 StorageControllerType_T controllerType;
2979 const Utf8Str &hdcVBox = vsdeHDCSCSI.front()->strVBoxCurrent;
2980 if (hdcVBox == "LsiLogic")
2981 controllerType = StorageControllerType_LsiLogic;
2982 else if (hdcVBox == "LsiLogicSas")
2983 {
2984 // OVF treats LsiLogicSas as a SCSI controller but VBox considers it a class of its own
2985 strName = "SAS";
2986 busType = StorageBus_SAS;
2987 controllerType = StorageControllerType_LsiLogicSas;
2988 }
2989 else if (hdcVBox == "BusLogic")
2990 controllerType = StorageControllerType_BusLogic;
2991 else
2992 throw setError(VBOX_E_FILE_ERROR,
2993 tr("Invalid SCSI controller type \"%s\""),
2994 hdcVBox.c_str());
2995
2996 rc = pNewMachine->AddStorageController(Bstr(strName).raw(), busType, pController.asOutParam());
2997 if (FAILED(rc)) throw rc;
2998 rc = pController->COMSETTER(ControllerType)(controllerType);
2999 if (FAILED(rc)) throw rc;
3000 }
3001
3002 /* Hard disk controller SAS */
3003 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSAS =
3004 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSAS);
3005 if (vsdeHDCSAS.size() > 1)
3006 throw setError(VBOX_E_FILE_ERROR,
3007 tr("Too many SAS controllers in OVF; import facility only supports one"));
3008 if (!vsdeHDCSAS.empty())
3009 {
3010 ComPtr<IStorageController> pController;
3011 rc = pNewMachine->AddStorageController(Bstr(L"SAS").raw(),
3012 StorageBus_SAS,
3013 pController.asOutParam());
3014 if (FAILED(rc)) throw rc;
3015 rc = pController->COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas);
3016 if (FAILED(rc)) throw rc;
3017 }
3018
3019 /* Now its time to register the machine before we add any hard disks */
3020 rc = mVirtualBox->RegisterMachine(pNewMachine);
3021 if (FAILED(rc)) throw rc;
3022
3023 // store new machine for roll-back in case of errors
3024 Bstr bstrNewMachineId;
3025 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
3026 if (FAILED(rc)) throw rc;
3027 Guid uuidNewMachine(bstrNewMachineId);
3028 m->llGuidsMachinesCreated.push_back(uuidNewMachine);
3029
3030 // Add floppies and CD-ROMs to the appropriate controllers.
3031 std::list<VirtualSystemDescriptionEntry*> vsdeFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy);
3032 if (vsdeFloppy.size() > 1)
3033 throw setError(VBOX_E_FILE_ERROR,
3034 tr("Too many floppy controllers in OVF; import facility only supports one"));
3035 std::list<VirtualSystemDescriptionEntry*> vsdeCDROM = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
3036 if ( !vsdeFloppy.empty()
3037 || !vsdeCDROM.empty()
3038 )
3039 {
3040 // If there's an error here we need to close the session, so
3041 // we need another try/catch block.
3042
3043 try
3044 {
3045 // to attach things we need to open a session for the new machine
3046 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
3047 if (FAILED(rc)) throw rc;
3048 stack.fSessionOpen = true;
3049
3050 ComPtr<IMachine> sMachine;
3051 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
3052 if (FAILED(rc)) throw rc;
3053
3054 // floppy first
3055 if (vsdeFloppy.size() == 1)
3056 {
3057 ComPtr<IStorageController> pController;
3058 rc = sMachine->AddStorageController(Bstr("Floppy").raw(),
3059 StorageBus_Floppy,
3060 pController.asOutParam());
3061 if (FAILED(rc)) throw rc;
3062
3063 Bstr bstrName;
3064 rc = pController->COMGETTER(Name)(bstrName.asOutParam());
3065 if (FAILED(rc)) throw rc;
3066
3067 // this is for rollback later
3068 MyHardDiskAttachment mhda;
3069 mhda.pMachine = pNewMachine;
3070 mhda.controllerName = bstrName;
3071 mhda.lControllerPort = 0;
3072 mhda.lDevice = 0;
3073
3074 Log(("Attaching floppy\n"));
3075
3076 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),
3077 mhda.lControllerPort,
3078 mhda.lDevice,
3079 DeviceType_Floppy,
3080 NULL);
3081 if (FAILED(rc)) throw rc;
3082
3083 stack.llHardDiskAttachments.push_back(mhda);
3084 }
3085
3086 rc = sMachine->SaveSettings();
3087 if (FAILED(rc)) throw rc;
3088
3089 // only now that we're done with all disks, close the session
3090 rc = stack.pSession->UnlockMachine();
3091 if (FAILED(rc)) throw rc;
3092 stack.fSessionOpen = false;
3093 }
3094 catch(HRESULT aRC)
3095 {
3096 com::ErrorInfo info;
3097
3098 if (stack.fSessionOpen)
3099 stack.pSession->UnlockMachine();
3100
3101 if (info.isFullAvailable())
3102 throw setError(aRC, Utf8Str(info.getText()).c_str());
3103 else
3104 throw setError(aRC, "Unknown error during OVF import");
3105 }
3106 }
3107
3108 // create the hard disks & connect them to the appropriate controllers
3109 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
3110 if (!avsdeHDs.empty())
3111 {
3112 // If there's an error here we need to close the session, so
3113 // we need another try/catch block.
3114 try
3115 {
3116#ifdef LOG_ENABLED
3117 if (LogIsEnabled())
3118 {
3119 size_t i = 0;
3120 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3121 itHD != avsdeHDs.end(); ++itHD, i++)
3122 Log(("avsdeHDs[%zu]: strRef=%s strOvf=%s\n", i, (*itHD)->strRef.c_str(), (*itHD)->strOvf.c_str()));
3123 i = 0;
3124 for (ovf::DiskImagesMap::const_iterator itDisk = stack.mapDisks.begin(); itDisk != stack.mapDisks.end(); ++itDisk)
3125 Log(("mapDisks[%zu]: strDiskId=%s strHref=%s\n",
3126 i, itDisk->second.strDiskId.c_str(), itDisk->second.strHref.c_str()));
3127
3128 }
3129#endif
3130
3131 // to attach things we need to open a session for the new machine
3132 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
3133 if (FAILED(rc)) throw rc;
3134 stack.fSessionOpen = true;
3135
3136 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
3137 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
3138 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
3139 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
3140
3141
3142 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
3143 std::set<RTCString> disksResolvedNames;
3144
3145 uint32_t cImportedDisks = 0;
3146
3147 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
3148 {
3149/** @todo r=bird: Most of the code here is duplicated in the other machine
3150 * import method, factor out. */
3151 ovf::DiskImage diCurrent = oit->second;
3152
3153 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
3154 /* Iterate over all given disk images of the virtual system
3155 * disks description. We need to find the target disk path,
3156 * which could be changed by the user. */
3157 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
3158 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3159 itHD != avsdeHDs.end();
3160 ++itHD)
3161 {
3162 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
3163 if (vsdeHD->strRef == diCurrent.strDiskId)
3164 {
3165 vsdeTargetHD = vsdeHD;
3166 break;
3167 }
3168 }
3169 if (!vsdeTargetHD)
3170 {
3171 /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
3172 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
3173 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
3174 NOREF(vmNameEntry);
3175 ++oit;
3176 continue;
3177 }
3178
3179 //diCurrent.strDiskId contains the disk identifier (e.g. "vmdisk1"), which should exist
3180 //in the virtual system's disks map under that ID and also in the global images map
3181 ovf::VirtualDisksMap::const_iterator itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
3182 if (itVDisk == vsysThis.mapVirtualDisks.end())
3183 throw setError(E_FAIL,
3184 tr("Internal inconsistency looking up disk image '%s'"),
3185 diCurrent.strHref.c_str());
3186
3187 /*
3188 * preliminary check availability of the image
3189 * This step is useful if image is placed in the OVA (TAR) package
3190 */
3191 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
3192 {
3193 /* It means that we possibly have imported the storage earlier on the previous loop steps*/
3194 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
3195 if (h != disksResolvedNames.end())
3196 {
3197 /* Yes, disk name was found, we can skip it*/
3198 ++oit;
3199 continue;
3200 }
3201l_skipped:
3202 rc = i_preCheckImageAvailability(stack);
3203 if (SUCCEEDED(rc))
3204 {
3205 /* current opened file isn't the same as passed one */
3206 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
3207 {
3208 /* availableImage contains the disk file reference (e.g. "disk1.vmdk"), which should
3209 * exist in the global images map.
3210 * And find the disk from the OVF's disk list */
3211 ovf::DiskImagesMap::const_iterator itDiskImage;
3212 for (itDiskImage = stack.mapDisks.begin();
3213 itDiskImage != stack.mapDisks.end();
3214 itDiskImage++)
3215 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
3216 Utf8Str::CaseInsensitive) == 0)
3217 break;
3218 if (itDiskImage == stack.mapDisks.end())
3219 {
3220 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
3221 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
3222 goto l_skipped;
3223 }
3224
3225 /* replace with a new found disk image */
3226 diCurrent = *(&itDiskImage->second);
3227
3228 /*
3229 * Again iterate over all given disk images of the virtual system
3230 * disks description using the found disk image
3231 */
3232 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3233 itHD != avsdeHDs.end();
3234 ++itHD)
3235 {
3236 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
3237 if (vsdeHD->strRef == diCurrent.strDiskId)
3238 {
3239 vsdeTargetHD = vsdeHD;
3240 break;
3241 }
3242 }
3243
3244 /*
3245 * in this case it's an error because something is wrong with the OVF description file.
3246 * May be VBox imports OVA package with wrong file sequence inside the archive.
3247 */
3248 if (!vsdeTargetHD)
3249 throw setError(E_FAIL,
3250 tr("Internal inconsistency looking up disk image '%s'"),
3251 diCurrent.strHref.c_str());
3252
3253 itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
3254 if (itVDisk == vsysThis.mapVirtualDisks.end())
3255 throw setError(E_FAIL,
3256 tr("Internal inconsistency looking up disk image '%s'"),
3257 diCurrent.strHref.c_str());
3258 }
3259 else
3260 {
3261 ++oit;
3262 }
3263 }
3264 else
3265 {
3266 ++oit;
3267 continue;
3268 }
3269 }
3270 else
3271 {
3272 /* just continue with normal files*/
3273 ++oit;
3274 }
3275
3276 /* very important to store disk name for the next checks */
3277 disksResolvedNames.insert(diCurrent.strHref);
3278////// end of duplicated code.
3279 const ovf::VirtualDisk &ovfVdisk = itVDisk->second;
3280
3281 ComObjPtr<Medium> pTargetHD;
3282
3283 Utf8Str savedVBoxCurrent = vsdeTargetHD->strVBoxCurrent;
3284
3285 i_importOneDiskImage(diCurrent,
3286 &vsdeTargetHD->strVBoxCurrent,
3287 pTargetHD,
3288 stack);
3289
3290 // now use the new uuid to attach the disk image to our new machine
3291 ComPtr<IMachine> sMachine;
3292 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
3293 if (FAILED(rc))
3294 throw rc;
3295
3296 // find the hard disk controller to which we should attach
3297 ovf::HardDiskController hdc = (*vsysThis.mapControllers.find(ovfVdisk.idController)).second;
3298
3299 // this is for rollback later
3300 MyHardDiskAttachment mhda;
3301 mhda.pMachine = pNewMachine;
3302
3303 i_convertDiskAttachmentValues(hdc,
3304 ovfVdisk.ulAddressOnParent,
3305 mhda.controllerName,
3306 mhda.lControllerPort,
3307 mhda.lDevice);
3308
3309 Log(("Attaching disk %s to port %d on device %d\n",
3310 vsdeTargetHD->strVBoxCurrent.c_str(), mhda.lControllerPort, mhda.lDevice));
3311
3312 ComObjPtr<MediumFormat> mediumFormat;
3313 rc = i_findMediumFormatFromDiskImage(diCurrent, mediumFormat);
3314 if (FAILED(rc))
3315 throw rc;
3316
3317 Bstr bstrFormatName;
3318 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
3319 if (FAILED(rc))
3320 throw rc;
3321
3322 Utf8Str vdf = Utf8Str(bstrFormatName);
3323
3324 if (vdf.compare("RAW", Utf8Str::CaseInsensitive) == 0)
3325 {
3326 ComPtr<IMedium> dvdImage(pTargetHD);
3327
3328 rc = mVirtualBox->OpenMedium(Bstr(vsdeTargetHD->strVBoxCurrent).raw(),
3329 DeviceType_DVD,
3330 AccessMode_ReadWrite,
3331 false,
3332 dvdImage.asOutParam());
3333
3334 if (FAILED(rc))
3335 throw rc;
3336
3337 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
3338 mhda.lControllerPort, // long controllerPort
3339 mhda.lDevice, // long device
3340 DeviceType_DVD, // DeviceType_T type
3341 dvdImage);
3342 if (FAILED(rc))
3343 throw rc;
3344 }
3345 else
3346 {
3347 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
3348 mhda.lControllerPort, // long controllerPort
3349 mhda.lDevice, // long device
3350 DeviceType_HardDisk, // DeviceType_T type
3351 pTargetHD);
3352
3353 if (FAILED(rc))
3354 throw rc;
3355 }
3356
3357 stack.llHardDiskAttachments.push_back(mhda);
3358
3359 rc = sMachine->SaveSettings();
3360 if (FAILED(rc))
3361 throw rc;
3362
3363 /* restore */
3364 vsdeTargetHD->strVBoxCurrent = savedVBoxCurrent;
3365
3366 ++cImportedDisks;
3367
3368 } // end while(oit != stack.mapDisks.end())
3369
3370 /*
3371 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
3372 */
3373 if(cImportedDisks < avsdeHDs.size())
3374 {
3375 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
3376 vmNameEntry->strOvf.c_str()));
3377 }
3378
3379 // only now that we're done with all disks, close the session
3380 rc = stack.pSession->UnlockMachine();
3381 if (FAILED(rc))
3382 throw rc;
3383 stack.fSessionOpen = false;
3384 }
3385 catch(HRESULT aRC)
3386 {
3387 com::ErrorInfo info;
3388 if (stack.fSessionOpen)
3389 stack.pSession->UnlockMachine();
3390
3391 if (info.isFullAvailable())
3392 throw setError(aRC, Utf8Str(info.getText()).c_str());
3393 else
3394 throw setError(aRC, "Unknown error during OVF import");
3395 }
3396 }
3397 LogFlowFuncLeave();
3398}
3399
3400/**
3401 * Imports one OVF virtual system (described by a vbox:Machine tag represented by the given config
3402 * structure) into VirtualBox by creating an IMachine instance, which is returned.
3403 *
3404 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
3405 * up any leftovers from this function. For this, the given ImportStack instance has received information
3406 * about what needs cleaning up (to support rollback).
3407 *
3408 * The machine config stored in the settings::MachineConfigFile structure contains the UUIDs of
3409 * the disk attachments used by the machine when it was exported. We also add vbox:uuid attributes
3410 * to the OVF disks sections so we can look them up. While importing these UUIDs into a second host
3411 * will most probably work, reimporting them into the same host will cause conflicts, so we always
3412 * generate new ones on import. This involves the following:
3413 *
3414 * 1) Scan the machine config for disk attachments.
3415 *
3416 * 2) For each disk attachment found, look up the OVF disk image from the disk references section
3417 * and import the disk into VirtualBox, which creates a new UUID for it. In the machine config,
3418 * replace the old UUID with the new one.
3419 *
3420 * 3) Change the machine config according to the OVF virtual system descriptions, in case the
3421 * caller has modified them using setFinalValues().
3422 *
3423 * 4) Create the VirtualBox machine with the modfified machine config.
3424 *
3425 * @param vsdescThis
3426 * @param pReturnNewMachine
3427 * @param stack
3428 */
3429void Appliance::i_importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
3430 ComPtr<IMachine> &pReturnNewMachine,
3431 ImportStack &stack)
3432{
3433 LogFlowFuncEnter();
3434 Assert(vsdescThis->m->pConfig);
3435
3436 HRESULT rc = S_OK;
3437
3438 settings::MachineConfigFile &config = *vsdescThis->m->pConfig;
3439
3440 /*
3441 * step 1): modify machine config according to OVF config, in case the user
3442 * has modified them using setFinalValues()
3443 */
3444
3445 /* OS Type */
3446 config.machineUserData.strOsType = stack.strOsTypeVBox;
3447 /* Description */
3448 config.machineUserData.strDescription = stack.strDescription;
3449 /* CPU count & extented attributes */
3450 config.hardwareMachine.cCPUs = stack.cCPUs;
3451 if (stack.fForceIOAPIC)
3452 config.hardwareMachine.fHardwareVirt = true;
3453 if (stack.fForceIOAPIC)
3454 config.hardwareMachine.biosSettings.fIOAPICEnabled = true;
3455 /* RAM size */
3456 config.hardwareMachine.ulMemorySizeMB = stack.ulMemorySizeMB;
3457
3458/*
3459 <const name="HardDiskControllerIDE" value="14" />
3460 <const name="HardDiskControllerSATA" value="15" />
3461 <const name="HardDiskControllerSCSI" value="16" />
3462 <const name="HardDiskControllerSAS" value="17" />
3463*/
3464
3465#ifdef VBOX_WITH_USB
3466 /* USB controller */
3467 if (stack.fUSBEnabled)
3468 {
3469 /** @todo r=klaus add support for arbitrary USB controller types, this can't handle
3470 * multiple controllers due to its design anyway */
3471 /* usually the OHCI controller is enabled already, need to check */
3472 bool fOHCIEnabled = false;
3473 settings::USBControllerList &llUSBControllers = config.hardwareMachine.usbSettings.llUSBControllers;
3474 settings::USBControllerList::iterator it;
3475 for (it = llUSBControllers.begin(); it != llUSBControllers.end(); ++it)
3476 {
3477 if (it->enmType == USBControllerType_OHCI)
3478 {
3479 fOHCIEnabled = true;
3480 break;
3481 }
3482 }
3483
3484 if (!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.

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