VirtualBox

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

Last change on this file since 67954 was 67249, checked in by vboxsync, 7 years ago

Main/Appliance++: Removed non-VBOX_WITH_NEW_TAR_CREATOR code.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 174.7 KB
Line 
1/* $Id: ApplianceImplImport.cpp 67249 2017-06-02 15:24:58Z 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 return S_OK;
1510}
1511
1512/**
1513 * Reads the signature & certificate file.
1514 *
1515 * @param pTask The read task.
1516 * @param hVfsIosCert The I/O stream for the signature file. The
1517 * reference is always consumed.
1518 * @param pszSubFileNm The signature filename (no path) for error
1519 * messages and logging. Used to construct
1520 * .mf-file name.
1521 * @returns COM status code, error info set.
1522 * @throws Nothing
1523 */
1524HRESULT Appliance::i_readSignatureFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosCert, const char *pszSubFileNm)
1525{
1526 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
1527
1528 /*
1529 * Construct the manifest filename from pszSubFileNm.
1530 */
1531 Utf8Str strManifestName;
1532 try
1533 {
1534 const char *pszSuffix = strrchr(pszSubFileNm, '.');
1535 AssertReturn(pszSuffix, E_FAIL);
1536 strManifestName = Utf8Str(pszSubFileNm, pszSuffix - pszSubFileNm);
1537 strManifestName.append(".mf");
1538 }
1539 catch (...)
1540 {
1541 return E_OUTOFMEMORY;
1542 }
1543
1544 /*
1545 * Copy the manifest into a memory buffer. We'll do the signature processing
1546 * later to not force any specific order in the OVAs or any other archive we
1547 * may be accessing later.
1548 */
1549 void *pvSignature;
1550 size_t cbSignature;
1551 int vrc = RTVfsIoStrmReadAll(hVfsIosCert, &pvSignature, &cbSignature);
1552 RTVfsIoStrmRelease(hVfsIosCert); /* consumes stream handle. */
1553 if (RT_FAILURE(vrc))
1554 return setErrorVrc(vrc, tr("Error reading the signature file '%s' for '%s' (%Rrc)"),
1555 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
1556
1557 /*
1558 * Parse the signing certificate. Unlike the manifest parser we use below,
1559 * this API ignores parse of the file that aren't relevant.
1560 */
1561 RTERRINFOSTATIC StaticErrInfo;
1562 vrc = RTCrX509Certificate_ReadFromBuffer(&m->SignerCert, pvSignature, cbSignature,
1563 RTCRX509CERT_READ_F_PEM_ONLY,
1564 &g_RTAsn1DefaultAllocator, RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
1565 HRESULT hrc;
1566 if (RT_SUCCESS(vrc))
1567 {
1568 m->fSignerCertLoaded = true;
1569 m->fCertificateIsSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->SignerCert);
1570
1571 /*
1572 * Find the start of the certificate part of the file, so we can avoid
1573 * upsetting the manifest parser with it.
1574 */
1575 char *pszSplit = (char *)RTCrPemFindFirstSectionInContent(pvSignature, cbSignature,
1576 g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers);
1577 if (pszSplit)
1578 while ( pszSplit != (char *)pvSignature
1579 && pszSplit[-1] != '\n'
1580 && pszSplit[-1] != '\r')
1581 pszSplit--;
1582 else
1583 {
1584 AssertLogRelMsgFailed(("Failed to find BEGIN CERTIFICATE markers in '%s'::'%s' - impossible unless it's a DER encoded certificate!",
1585 pTask->locInfo.strPath.c_str(), pszSubFileNm));
1586 pszSplit = (char *)pvSignature + cbSignature;
1587 }
1588 *pszSplit = '\0';
1589
1590 /*
1591 * Now, read the manifest part. We use the IPRT manifest reader here
1592 * to avoid duplicating code and be somewhat flexible wrt the digest
1593 * type choosen by the signer.
1594 */
1595 RTMANIFEST hSignedDigestManifest;
1596 vrc = RTManifestCreate(0 /*fFlags*/, &hSignedDigestManifest);
1597 if (RT_SUCCESS(vrc))
1598 {
1599 RTVFSIOSTREAM hVfsIosTmp;
1600 vrc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pvSignature, pszSplit - (char *)pvSignature, &hVfsIosTmp);
1601 if (RT_SUCCESS(vrc))
1602 {
1603 vrc = RTManifestReadStandardEx(hSignedDigestManifest, hVfsIosTmp, StaticErrInfo.szMsg, sizeof(StaticErrInfo.szMsg));
1604 RTVfsIoStrmRelease(hVfsIosTmp);
1605 if (RT_SUCCESS(vrc))
1606 {
1607 /*
1608 * Get signed digest, we prefer SHA-2, so explicitly query those first.
1609 */
1610 uint32_t fDigestType;
1611 char szSignedDigest[_8K + 1];
1612 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
1613 RTMANIFEST_ATTR_SHA512 | RTMANIFEST_ATTR_SHA256,
1614 szSignedDigest, sizeof(szSignedDigest), &fDigestType);
1615 if (vrc == VERR_MANIFEST_ATTR_TYPE_NOT_FOUND)
1616 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
1617 RTMANIFEST_ATTR_ANY, szSignedDigest, sizeof(szSignedDigest), &fDigestType);
1618 if (RT_SUCCESS(vrc))
1619 {
1620 const char *pszSignedDigest = RTStrStrip(szSignedDigest);
1621 size_t cbSignedDigest = strlen(pszSignedDigest) / 2;
1622 uint8_t abSignedDigest[sizeof(szSignedDigest) / 2];
1623 vrc = RTStrConvertHexBytes(szSignedDigest, abSignedDigest, cbSignedDigest, 0 /*fFlags*/);
1624 if (RT_SUCCESS(vrc))
1625 {
1626 /*
1627 * Convert it to RTDIGESTTYPE_XXX and save the binary value for later use.
1628 */
1629 switch (fDigestType)
1630 {
1631 case RTMANIFEST_ATTR_SHA1: m->enmSignedDigestType = RTDIGESTTYPE_SHA1; break;
1632 case RTMANIFEST_ATTR_SHA256: m->enmSignedDigestType = RTDIGESTTYPE_SHA256; break;
1633 case RTMANIFEST_ATTR_SHA512: m->enmSignedDigestType = RTDIGESTTYPE_SHA512; break;
1634 case RTMANIFEST_ATTR_MD5: m->enmSignedDigestType = RTDIGESTTYPE_MD5; break;
1635 default: AssertFailed(); m->enmSignedDigestType = RTDIGESTTYPE_INVALID; break;
1636 }
1637 if (m->enmSignedDigestType != RTDIGESTTYPE_INVALID)
1638 {
1639 m->pbSignedDigest = (uint8_t *)RTMemDup(abSignedDigest, cbSignedDigest);
1640 m->cbSignedDigest = cbSignedDigest;
1641 hrc = S_OK;
1642 }
1643 else
1644 hrc = setError(E_FAIL, tr("Unsupported signed digest type (%#x)"), fDigestType);
1645 }
1646 else
1647 hrc = setErrorVrc(vrc, tr("Error reading signed manifest digest: %Rrc"), vrc);
1648 }
1649 else if (vrc == VERR_NOT_FOUND)
1650 hrc = setErrorVrc(vrc, tr("Could not locate signed digest for '%s' in the cert-file for '%s'"),
1651 strManifestName.c_str(), pTask->locInfo.strPath.c_str());
1652 else
1653 hrc = setErrorVrc(vrc, tr("RTManifestEntryQueryAttr failed unexpectedly: %Rrc"), vrc);
1654 }
1655 else
1656 hrc = setErrorVrc(vrc, tr("Error parsing the .cert-file for '%s': %s"),
1657 pTask->locInfo.strPath.c_str(), StaticErrInfo.szMsg);
1658 }
1659 else
1660 hrc = E_OUTOFMEMORY;
1661 RTManifestRelease(hSignedDigestManifest);
1662 }
1663 else
1664 hrc = E_OUTOFMEMORY;
1665 }
1666 else if (vrc == VERR_NOT_FOUND || vrc == VERR_EOF)
1667 hrc = setErrorBoth(E_FAIL, vrc, tr("Malformed .cert-file for '%s': Signer's certificate not found (%Rrc)"),
1668 pTask->locInfo.strPath.c_str(), vrc);
1669 else
1670 hrc = setErrorVrc(vrc, tr("Error reading the signer's certificate from '%s' for '%s' (%Rrc): %s"),
1671 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
1672
1673 RTVfsIoStrmReadAllFree(pvSignature, cbSignature);
1674 LogFlowFunc(("returns %Rhrc (%Rrc)\n", hrc, vrc));
1675 return hrc;
1676}
1677
1678
1679/**
1680 * Does tail processing after the files have been read in.
1681 *
1682 * @param pTask The read task.
1683 * @returns COM status.
1684 * @throws Nothing!
1685 */
1686HRESULT Appliance::i_readTailProcessing(TaskOVF *pTask)
1687{
1688 /*
1689 * Parse and validate the signature file.
1690 *
1691 * The signature file has two parts, manifest part and a PEM encoded
1692 * certificate. The former contains an entry for the manifest file with a
1693 * digest that is encrypted with the certificate in the latter part.
1694 */
1695 if (m->pbSignedDigest)
1696 {
1697 /* Since we're validating the digest of the manifest, there have to be
1698 a manifest. We cannot allow a the manifest to be missing. */
1699 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
1700 return setError(VBOX_E_FILE_ERROR, tr("Found .cert-file but no .mf-file for '%s'"), pTask->locInfo.strPath.c_str());
1701
1702 /*
1703 * Validate the signed digest.
1704 *
1705 * It's possible we should allow the user to ignore signature
1706 * mismatches, but for now it is a solid show stopper.
1707 */
1708 HRESULT hrc;
1709 RTERRINFOSTATIC StaticErrInfo;
1710
1711 /* Calc the digest of the manifest using the algorithm found above. */
1712 RTCRDIGEST hDigest;
1713 int vrc = RTCrDigestCreateByType(&hDigest, m->enmSignedDigestType);
1714 if (RT_SUCCESS(vrc))
1715 {
1716 vrc = RTCrDigestUpdateFromVfsFile(hDigest, m->hMemFileTheirManifest, true /*fRewindFile*/);
1717 if (RT_SUCCESS(vrc))
1718 {
1719 /* Compare the signed digest with the one we just calculated. (This
1720 API will do the verification twice, once using IPRT's own crypto
1721 and once using OpenSSL. Both must OK it for success.) */
1722 vrc = RTCrPkixPubKeyVerifySignedDigest(&m->SignerCert.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm,
1723 &m->SignerCert.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Parameters,
1724 &m->SignerCert.TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey,
1725 m->pbSignedDigest, m->cbSignedDigest, hDigest,
1726 RTErrInfoInitStatic(&StaticErrInfo));
1727 if (RT_SUCCESS(vrc))
1728 {
1729 m->fSignatureValid = true;
1730 hrc = S_OK;
1731 }
1732 else if (vrc == VERR_CR_PKIX_SIGNATURE_MISMATCH)
1733 hrc = setErrorVrc(vrc, tr("The manifest signature does not match"));
1734 else
1735 hrc = setErrorVrc(vrc,
1736 tr("Error validating the manifest signature (%Rrc, %s)"), vrc, StaticErrInfo.Core.pszMsg);
1737 }
1738 else
1739 hrc = setErrorVrc(vrc, tr("RTCrDigestUpdateFromVfsFile failed: %Rrc"), vrc);
1740 RTCrDigestRelease(hDigest);
1741 }
1742 else
1743 hrc = setErrorVrc(vrc, tr("RTCrDigestCreateByType failed: %Rrc"), vrc);
1744
1745 /*
1746 * Validate the certificate.
1747 *
1748 * We don't fail here on if we cannot validate the certificate, we postpone
1749 * that till the import stage, so that we can allow the user to ignore it.
1750 *
1751 * The certificate validity time is deliberately left as warnings as the
1752 * OVF specification does not provision for any timestamping of the
1753 * signature. This is course a security concern, but the whole signing
1754 * of OVFs is currently weirdly trusting (self signed * certs), so this
1755 * is the least of our current problems.
1756 *
1757 * While we try build and verify certificate paths properly, the
1758 * "neighbours" quietly ignores this and seems only to check the signature
1759 * and not whether the certificate is trusted. Also, we don't currently
1760 * complain about self-signed certificates either (ditto "neighbours").
1761 * The OVF creator is also a bit restricted wrt to helping us build the
1762 * path as he cannot supply intermediate certificates. Anyway, we issue
1763 * warnings (goes to /dev/null, am I right?) for self-signed certificates
1764 * and certificates we cannot build and verify a root path for.
1765 *
1766 * (The OVF sillibuggers should've used PKCS#7, CMS or something else
1767 * that's already been standardized instead of combining manifests with
1768 * certificate PEM files in some very restrictive manner! I wonder if
1769 * we could add a PKCS#7 section to the .cert file in addition to the CERT
1770 * and manifest stuff dictated by the standard. Would depend on how others
1771 * deal with it.)
1772 */
1773 Assert(!m->fCertificateValid);
1774 Assert(m->fCertificateMissingPath);
1775 Assert(!m->fCertificateValidTime);
1776 Assert(m->strCertError.isEmpty());
1777 Assert(m->fCertificateIsSelfSigned == RTCrX509Certificate_IsSelfSigned(&m->SignerCert));
1778
1779 HRESULT hrc2 = S_OK;
1780 if (m->fCertificateIsSelfSigned)
1781 {
1782 /*
1783 * It's a self signed certificate. We assume the frontend will
1784 * present this fact to the user and give a choice whether this
1785 * is acceptible. But, first make sure it makes internal sense.
1786 */
1787 m->fCertificateMissingPath = true; /** @todo need to check if the certificate is trusted by the system! */
1788 vrc = RTCrX509Certificate_VerifySignatureSelfSigned(&m->SignerCert, RTErrInfoInitStatic(&StaticErrInfo));
1789 if (RT_SUCCESS(vrc))
1790 {
1791 m->fCertificateValid = true;
1792
1793 /* Check whether the certificate is currently valid, just warn if not. */
1794 RTTIMESPEC Now;
1795 if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
1796 {
1797 m->fCertificateValidTime = true;
1798 i_addWarning(tr("A self signed certificate was used to sign '%s'"), pTask->locInfo.strPath.c_str());
1799 }
1800 else
1801 i_addWarning(tr("Self signed certificate used to sign '%s' is not currently valid"),
1802 pTask->locInfo.strPath.c_str());
1803
1804 /* Just warn if it's not a CA. Self-signed certificates are
1805 hardly trustworthy to start with without the user's consent. */
1806 if ( !m->SignerCert.TbsCertificate.T3.pBasicConstraints
1807 || !m->SignerCert.TbsCertificate.T3.pBasicConstraints->CA.fValue)
1808 i_addWarning(tr("Self signed certificate used to sign '%s' is not marked as certificate authority (CA)"),
1809 pTask->locInfo.strPath.c_str());
1810 }
1811 else
1812 {
1813 try { m->strCertError = Utf8StrFmt(tr("Verification of the self signed certificate failed (%Rrc, %s)"),
1814 vrc, StaticErrInfo.Core.pszMsg); }
1815 catch (...) { AssertFailed(); }
1816 i_addWarning(tr("Verification of the self signed certificate used to sign '%s' failed (%Rrc): %s"),
1817 pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
1818 }
1819 }
1820 else
1821 {
1822 /*
1823 * The certificate is not self-signed. Use the system certificate
1824 * stores to try build a path that validates successfully.
1825 */
1826 RTCRX509CERTPATHS hCertPaths;
1827 vrc = RTCrX509CertPathsCreate(&hCertPaths, &m->SignerCert);
1828 if (RT_SUCCESS(vrc))
1829 {
1830 /* Get trusted certificates from the system and add them to the path finding mission. */
1831 RTCRSTORE hTrustedCerts;
1832 vrc = RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(&hTrustedCerts,
1833 RTErrInfoInitStatic(&StaticErrInfo));
1834 if (RT_SUCCESS(vrc))
1835 {
1836 vrc = RTCrX509CertPathsSetTrustedStore(hCertPaths, hTrustedCerts);
1837 if (RT_FAILURE(vrc))
1838 hrc2 = setError(E_FAIL, tr("RTCrX509CertPathsSetTrustedStore failed (%Rrc)"), vrc);
1839 RTCrStoreRelease(hTrustedCerts);
1840 }
1841 else
1842 hrc2 = setError(E_FAIL,
1843 tr("Failed to query trusted CAs and Certificates from the system and for the current user (%Rrc, %s)"),
1844 vrc, StaticErrInfo.Core.pszMsg);
1845
1846 /* Add untrusted intermediate certificates. */
1847 if (RT_SUCCESS(vrc))
1848 {
1849 /// @todo RTCrX509CertPathsSetUntrustedStore(hCertPaths, hAdditionalCerts);
1850 /// By scanning for additional certificates in the .cert file? It would be
1851 /// convenient to be able to supply intermediate certificates for the user,
1852 /// right? Or would that be unacceptable as it may weaken security?
1853 ///
1854 /// Anyway, we should look for intermediate certificates on the system, at
1855 /// least.
1856 }
1857 if (RT_SUCCESS(vrc))
1858 {
1859 /*
1860 * Do the building and verification of certificate paths.
1861 */
1862 vrc = RTCrX509CertPathsBuild(hCertPaths, RTErrInfoInitStatic(&StaticErrInfo));
1863 if (RT_SUCCESS(vrc))
1864 {
1865 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(&StaticErrInfo));
1866 if (RT_SUCCESS(vrc))
1867 {
1868 /*
1869 * Mark the certificate as good.
1870 */
1871 /** @todo check the certificate purpose? If so, share with self-signed. */
1872 m->fCertificateValid = true;
1873 m->fCertificateMissingPath = false;
1874
1875 /*
1876 * We add a warning if the certificate path isn't valid at the current
1877 * time. Since the time is only considered during path validation and we
1878 * can repeat the validation process (but not building), it's easy to check.
1879 */
1880 RTTIMESPEC Now;
1881 vrc = RTCrX509CertPathsSetValidTimeSpec(hCertPaths, RTTimeNow(&Now));
1882 if (RT_SUCCESS(vrc))
1883 {
1884 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(&StaticErrInfo));
1885 if (RT_SUCCESS(vrc))
1886 m->fCertificateValidTime = true;
1887 else
1888 i_addWarning(tr("The certificate used to sign '%s' (or a certificate in the path) is not currently valid (%Rrc)"),
1889 pTask->locInfo.strPath.c_str(), vrc);
1890 }
1891 else
1892 hrc2 = setErrorVrc(vrc, "RTCrX509CertPathsSetValidTimeSpec failed: %Rrc", vrc);
1893 }
1894 else if (vrc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS)
1895 {
1896 m->fCertificateValid = true;
1897 i_addWarning(tr("No trusted certificate paths"));
1898
1899 /* Add another warning if the pathless certificate is not valid at present. */
1900 RTTIMESPEC Now;
1901 if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
1902 m->fCertificateValidTime = true;
1903 else
1904 i_addWarning(tr("The certificate used to sign '%s' is not currently valid"),
1905 pTask->locInfo.strPath.c_str());
1906 }
1907 else
1908 hrc2 = setError(E_FAIL, tr("Certificate path validation failed (%Rrc, %s)"),
1909 vrc, StaticErrInfo.Core.pszMsg);
1910 }
1911 else
1912 hrc2 = setError(E_FAIL, tr("Certificate path building failed (%Rrc, %s)"),
1913 vrc, StaticErrInfo.Core.pszMsg);
1914 }
1915 RTCrX509CertPathsRelease(hCertPaths);
1916 }
1917 else
1918 hrc2 = setErrorVrc(vrc, tr("RTCrX509CertPathsCreate failed: %Rrc"), vrc);
1919 }
1920
1921 /* Merge statuses from signature and certificate validation, prefering the signature one. */
1922 if (SUCCEEDED(hrc) && FAILED(hrc2))
1923 hrc = hrc2;
1924 if (FAILED(hrc))
1925 return hrc;
1926 }
1927
1928 /** @todo provide details about the signatory, signature, etc. */
1929 if (m->fSignerCertLoaded)
1930 {
1931 m->ptrCertificateInfo.createObject();
1932 m->ptrCertificateInfo->initCertificate(&m->SignerCert,
1933 m->fCertificateValid && !m->fCertificateMissingPath,
1934 !m->fCertificateValidTime);
1935 }
1936
1937 /*
1938 * If there is a manifest, check that the OVF digest matches up (if present).
1939 */
1940
1941 NOREF(pTask);
1942 return S_OK;
1943}
1944
1945
1946
1947/*******************************************************************************
1948 * Import stuff
1949 ******************************************************************************/
1950
1951/**
1952 * Implementation for importing OVF data into VirtualBox. This starts a new thread which will call
1953 * Appliance::taskThreadImportOrExport().
1954 *
1955 * This creates one or more new machines according to the VirtualSystemScription instances created by
1956 * Appliance::Interpret().
1957 *
1958 * This is in a separate private method because it is used from one location:
1959 *
1960 * 1) from the public Appliance::ImportMachines().
1961 *
1962 * @param locInfo
1963 * @param progress
1964 * @return
1965 */
1966HRESULT Appliance::i_importImpl(const LocationInfo &locInfo,
1967 ComObjPtr<Progress> &progress)
1968{
1969 HRESULT rc = S_OK;
1970
1971 SetUpProgressMode mode;
1972 if (locInfo.storageType == VFSType_File)
1973 mode = ImportFile;
1974 else
1975 mode = ImportS3;
1976
1977 rc = i_setUpProgress(progress,
1978 BstrFmt(tr("Importing appliance '%s'"), locInfo.strPath.c_str()),
1979 mode);
1980 if (FAILED(rc)) throw rc;
1981
1982 /* Initialize our worker task */
1983 TaskOVF* task = NULL;
1984 try
1985 {
1986 task = new TaskOVF(this, TaskOVF::Import, locInfo, progress);
1987 }
1988 catch(...)
1989 {
1990 delete task;
1991 throw rc = setError(VBOX_E_OBJECT_NOT_FOUND,
1992 tr("Could not create TaskOVF object for importing OVF data into VirtualBox"));
1993 }
1994
1995 rc = task->createThread();
1996 if (FAILED(rc)) throw rc;
1997
1998 return rc;
1999}
2000
2001/**
2002 * Actual worker code for importing OVF data into VirtualBox.
2003 *
2004 * This is called from Appliance::taskThreadImportOrExport() and therefore runs
2005 * on the OVF import worker thread. This creates one or more new machines
2006 * according to the VirtualSystemScription instances created by
2007 * Appliance::Interpret().
2008 *
2009 * This runs in two contexts:
2010 *
2011 * 1) in a first worker thread; in that case, Appliance::ImportMachines() called
2012 * Appliance::i_importImpl();
2013 *
2014 * 2) in a second worker thread; in that case, Appliance::ImportMachines()
2015 * called Appliance::i_importImpl(), which called Appliance::i_importFSOVA(),
2016 * which called Appliance::i_importImpl(), which then called this again.
2017 *
2018 * @param pTask The OVF task data.
2019 * @return COM status code.
2020 */
2021HRESULT Appliance::i_importFS(TaskOVF *pTask)
2022{
2023 LogFlowFuncEnter();
2024 LogFlowFunc(("Appliance %p\n", this));
2025
2026 /* Change the appliance state so we can safely leave the lock while doing
2027 * time-consuming disk imports; also the below method calls do all kinds of
2028 * locking which conflicts with the appliance object lock. */
2029 AutoWriteLock writeLock(this COMMA_LOCKVAL_SRC_POS);
2030 /* Check if the appliance is currently busy. */
2031 if (!i_isApplianceIdle())
2032 return E_ACCESSDENIED;
2033 /* Set the internal state to importing. */
2034 m->state = Data::ApplianceImporting;
2035
2036 HRESULT rc = S_OK;
2037
2038 /* Clear the list of imported machines, if any */
2039 m->llGuidsMachinesCreated.clear();
2040
2041 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
2042 rc = i_importFSOVF(pTask, writeLock);
2043 else
2044 rc = i_importFSOVA(pTask, writeLock);
2045 if (FAILED(rc))
2046 {
2047 /* With _whatever_ error we've had, do a complete roll-back of
2048 * machines and disks we've created */
2049 writeLock.release();
2050 ErrorInfoKeeper eik;
2051 for (list<Guid>::iterator itID = m->llGuidsMachinesCreated.begin();
2052 itID != m->llGuidsMachinesCreated.end();
2053 ++itID)
2054 {
2055 Guid guid = *itID;
2056 Bstr bstrGuid = guid.toUtf16();
2057 ComPtr<IMachine> failedMachine;
2058 HRESULT rc2 = mVirtualBox->FindMachine(bstrGuid.raw(), failedMachine.asOutParam());
2059 if (SUCCEEDED(rc2))
2060 {
2061 SafeIfaceArray<IMedium> aMedia;
2062 rc2 = failedMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
2063 ComPtr<IProgress> pProgress2;
2064 rc2 = failedMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress2.asOutParam());
2065 pProgress2->WaitForCompletion(-1);
2066 }
2067 }
2068 writeLock.acquire();
2069 }
2070
2071 /* Reset the state so others can call methods again */
2072 m->state = Data::ApplianceIdle;
2073
2074 LogFlowFunc(("rc=%Rhrc\n", rc));
2075 LogFlowFuncLeave();
2076 return rc;
2077}
2078
2079HRESULT Appliance::i_importFSOVF(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
2080{
2081 return i_importDoIt(pTask, rWriteLock);
2082}
2083
2084HRESULT Appliance::i_importFSOVA(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
2085{
2086 LogFlowFuncEnter();
2087
2088 /*
2089 * Open the tar file as file stream.
2090 */
2091 RTVFSIOSTREAM hVfsIosOva;
2092 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
2093 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
2094 if (RT_FAILURE(vrc))
2095 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2096
2097 RTVFSFSSTREAM hVfsFssOva;
2098 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
2099 RTVfsIoStrmRelease(hVfsIosOva);
2100 if (RT_FAILURE(vrc))
2101 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2102
2103 /*
2104 * Join paths with the i_importFSOVF code.
2105 *
2106 * Note! We don't need to skip the OVF, manifest or signature files, as the
2107 * i_importMachineGeneric, i_importVBoxMachine and i_importOpenSourceFile
2108 * code will deal with this (as there could be other files in the OVA
2109 * that we don't process, like 'de-DE-resources.xml' in EXAMPLE 1,
2110 * Appendix D.1, OVF v2.1.0).
2111 */
2112 HRESULT hrc = i_importDoIt(pTask, rWriteLock, hVfsFssOva);
2113
2114 RTVfsFsStrmRelease(hVfsFssOva);
2115
2116 LogFlowFunc(("returns %Rhrc\n", hrc));
2117 return hrc;
2118}
2119
2120/**
2121 * Does the actual importing after the caller has made the source accessible.
2122 *
2123 * @param pTask The import task.
2124 * @param rWriteLock The write lock the caller's caller is holding,
2125 * will be released for some reason.
2126 * @param hVfsFssOva The file system stream if OVA, NIL if not.
2127 * @returns COM status code.
2128 * @throws Nothing.
2129 */
2130HRESULT Appliance::i_importDoIt(TaskOVF *pTask, AutoWriteLockBase &rWriteLock, RTVFSFSSTREAM hVfsFssOva /*= NIL_RTVFSFSSTREAM*/)
2131{
2132 rWriteLock.release();
2133
2134 HRESULT hrc = E_FAIL;
2135 try
2136 {
2137 /*
2138 * Create the import stack for the rollback on errors.
2139 */
2140 ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, hVfsFssOva);
2141
2142 try
2143 {
2144 /* Do the importing. */
2145 i_importMachines(stack);
2146
2147 /* We should've processed all the files now, so compare. */
2148 hrc = i_verifyManifestFile(stack);
2149 }
2150 catch (HRESULT hrcXcpt)
2151 {
2152 hrc = hrcXcpt;
2153 }
2154 catch (...)
2155 {
2156 AssertFailed();
2157 hrc = E_FAIL;
2158 }
2159 if (FAILED(hrc))
2160 {
2161 /*
2162 * Restoring original UUID from OVF description file.
2163 * During import VBox creates new UUIDs for imported images and
2164 * assigns them to the images. In case of failure we have to restore
2165 * the original UUIDs because those new UUIDs are obsolete now and
2166 * won't be used anymore.
2167 */
2168 ErrorInfoKeeper eik; /* paranoia */
2169 list< ComObjPtr<VirtualSystemDescription> >::const_iterator itvsd;
2170 /* Iterate through all virtual systems of that appliance */
2171 for (itvsd = m->virtualSystemDescriptions.begin();
2172 itvsd != m->virtualSystemDescriptions.end();
2173 ++itvsd)
2174 {
2175 ComObjPtr<VirtualSystemDescription> vsdescThis = (*itvsd);
2176 settings::MachineConfigFile *pConfig = vsdescThis->m->pConfig;
2177 if(vsdescThis->m->pConfig!=NULL)
2178 stack.restoreOriginalUUIDOfAttachedDevice(pConfig);
2179 }
2180 }
2181 }
2182 catch (...)
2183 {
2184 hrc = E_FAIL;
2185 AssertFailed();
2186 }
2187
2188 rWriteLock.acquire();
2189 return hrc;
2190}
2191
2192/**
2193 * Undocumented, you figure it from the name.
2194 *
2195 * @returns Undocumented
2196 * @param stack Undocumented.
2197 */
2198HRESULT Appliance::i_verifyManifestFile(ImportStack &stack)
2199{
2200 LogFlowThisFuncEnter();
2201 HRESULT hrc;
2202 int vrc;
2203
2204 /*
2205 * No manifest is fine, it always matches.
2206 */
2207 if (m->hTheirManifest == NIL_RTMANIFEST)
2208 hrc = S_OK;
2209 else
2210 {
2211 /*
2212 * Hack: If the manifest we just read doesn't have a digest for the OVF, copy
2213 * it from the manifest we got from the caller.
2214 * @bugref{6022#c119}
2215 */
2216 if ( !RTManifestEntryExists(m->hTheirManifest, m->strOvfManifestEntry.c_str())
2217 && RTManifestEntryExists(m->hOurManifest, m->strOvfManifestEntry.c_str()) )
2218 {
2219 uint32_t fType = 0;
2220 char szDigest[512 + 1];
2221 vrc = RTManifestEntryQueryAttr(m->hOurManifest, m->strOvfManifestEntry.c_str(), NULL, RTMANIFEST_ATTR_ANY,
2222 szDigest, sizeof(szDigest), &fType);
2223 if (RT_SUCCESS(vrc))
2224 vrc = RTManifestEntrySetAttr(m->hTheirManifest, m->strOvfManifestEntry.c_str(),
2225 NULL /*pszAttr*/, szDigest, fType);
2226 if (RT_FAILURE(vrc))
2227 return setError(VBOX_E_IPRT_ERROR, tr("Error fudging missing OVF digest in manifest: %Rrc"), vrc);
2228 }
2229
2230 /*
2231 * Compare with the digests we've created while read/processing the import.
2232 *
2233 * We specify the RTMANIFEST_EQUALS_IGN_MISSING_ATTRS to ignore attributes
2234 * (SHA1, SHA256, etc) that are only present in one of the manifests, as long
2235 * as each entry has at least one common attribute that we can check. This
2236 * is important for the OVF in OVAs, for which we generates several digests
2237 * since we don't know which are actually used in the manifest (OVF comes
2238 * first in an OVA, then manifest).
2239 */
2240 char szErr[256];
2241 vrc = RTManifestEqualsEx(m->hTheirManifest, m->hOurManifest, NULL /*papszIgnoreEntries*/,
2242 NULL /*papszIgnoreAttrs*/, RTMANIFEST_EQUALS_IGN_MISSING_ATTRS, szErr, sizeof(szErr));
2243 if (RT_SUCCESS(vrc))
2244 hrc = S_OK;
2245 else
2246 hrc = setErrorVrc(vrc, tr("Digest mismatch (%Rrc): %s"), vrc, szErr);
2247 }
2248
2249 NOREF(stack);
2250 LogFlowThisFunc(("returns %Rhrc\n", hrc));
2251 return hrc;
2252}
2253
2254/**
2255 * Helper that converts VirtualSystem attachment values into VirtualBox attachment values.
2256 * Throws HRESULT values on errors!
2257 *
2258 * @param hdc in: the HardDiskController structure to attach to.
2259 * @param ulAddressOnParent in: the AddressOnParent parameter from OVF.
2260 * @param controllerName out: the name of the hard disk controller to attach to (e.g. "IDE").
2261 * @param lControllerPort out: the channel (controller port) of the controller to attach to.
2262 * @param lDevice out: the device number to attach to.
2263 */
2264void Appliance::i_convertDiskAttachmentValues(const ovf::HardDiskController &hdc,
2265 uint32_t ulAddressOnParent,
2266 Utf8Str &controllerName,
2267 int32_t &lControllerPort,
2268 int32_t &lDevice)
2269{
2270 Log(("Appliance::i_convertDiskAttachmentValues: hdc.system=%d, hdc.fPrimary=%d, ulAddressOnParent=%d\n",
2271 hdc.system,
2272 hdc.fPrimary,
2273 ulAddressOnParent));
2274
2275 switch (hdc.system)
2276 {
2277 case ovf::HardDiskController::IDE:
2278 // For the IDE bus, the port parameter can be either 0 or 1, to specify the primary
2279 // or secondary IDE controller, respectively. For the primary controller of the IDE bus,
2280 // the device number can be either 0 or 1, to specify the master or the slave device,
2281 // respectively. For the secondary IDE controller, the device number is always 1 because
2282 // the master device is reserved for the CD-ROM drive.
2283 controllerName = "IDE";
2284 switch (ulAddressOnParent)
2285 {
2286 case 0: // master
2287 if (!hdc.fPrimary)
2288 {
2289 // secondary master
2290 lControllerPort = (long)1;
2291 lDevice = (long)0;
2292 }
2293 else // primary master
2294 {
2295 lControllerPort = (long)0;
2296 lDevice = (long)0;
2297 }
2298 break;
2299
2300 case 1: // slave
2301 if (!hdc.fPrimary)
2302 {
2303 // secondary slave
2304 lControllerPort = (long)1;
2305 lDevice = (long)1;
2306 }
2307 else // primary slave
2308 {
2309 lControllerPort = (long)0;
2310 lDevice = (long)1;
2311 }
2312 break;
2313
2314 // used by older VBox exports
2315 case 2: // interpret this as secondary master
2316 lControllerPort = (long)1;
2317 lDevice = (long)0;
2318 break;
2319
2320 // used by older VBox exports
2321 case 3: // interpret this as secondary slave
2322 lControllerPort = (long)1;
2323 lDevice = (long)1;
2324 break;
2325
2326 default:
2327 throw setError(VBOX_E_NOT_SUPPORTED,
2328 tr("Invalid channel %RI16 specified; IDE controllers support only 0, 1 or 2"),
2329 ulAddressOnParent);
2330 break;
2331 }
2332 break;
2333
2334 case ovf::HardDiskController::SATA:
2335 controllerName = "SATA";
2336 lControllerPort = (long)ulAddressOnParent;
2337 lDevice = (long)0;
2338 break;
2339
2340 case ovf::HardDiskController::SCSI:
2341 {
2342 if(hdc.strControllerType.compare("lsilogicsas")==0)
2343 controllerName = "SAS";
2344 else
2345 controllerName = "SCSI";
2346 lControllerPort = (long)ulAddressOnParent;
2347 lDevice = (long)0;
2348 break;
2349 }
2350
2351 default: break;
2352 }
2353
2354 Log(("=> lControllerPort=%d, lDevice=%d\n", lControllerPort, lDevice));
2355}
2356
2357/**
2358 * Imports one disk image.
2359 *
2360 * This is common code shared between
2361 * -- i_importMachineGeneric() for the OVF case; in that case the information comes from
2362 * the OVF virtual systems;
2363 * -- i_importVBoxMachine(); in that case, the information comes from the <vbox:Machine>
2364 * tag.
2365 *
2366 * Both ways of describing machines use the OVF disk references section, so in both cases
2367 * the caller needs to pass in the ovf::DiskImage structure from ovfreader.cpp.
2368 *
2369 * As a result, in both cases, if di.strHref is empty, we create a new disk as per the OVF
2370 * spec, even though this cannot really happen in the vbox:Machine case since such data
2371 * would never have been exported.
2372 *
2373 * This advances stack.pProgress by one operation with the disk's weight.
2374 *
2375 * @param di ovfreader.cpp structure describing the disk image from the OVF that is to be imported
2376 * @param strTargetPath Where to create the target image.
2377 * @param pTargetHD out: The newly created target disk. This also gets pushed on stack.llHardDisksCreated for cleanup.
2378 * @param stack
2379 */
2380void Appliance::i_importOneDiskImage(const ovf::DiskImage &di,
2381 Utf8Str *pStrDstPath,
2382 ComObjPtr<Medium> &pTargetHD,
2383 ImportStack &stack)
2384{
2385 ComObjPtr<Progress> pProgress;
2386 pProgress.createObject();
2387 HRESULT rc = pProgress->init(mVirtualBox,
2388 static_cast<IAppliance*>(this),
2389 BstrFmt(tr("Creating medium '%s'"),
2390 pStrDstPath->c_str()).raw(),
2391 TRUE);
2392 if (FAILED(rc)) throw rc;
2393
2394 /* Get the system properties. */
2395 SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
2396
2397 /* Keep the source file ref handy for later. */
2398 const Utf8Str &strSourceOVF = di.strHref;
2399
2400 /* Construct source file path */
2401 Utf8Str strSrcFilePath;
2402 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
2403 strSrcFilePath = strSourceOVF;
2404 else
2405 {
2406 strSrcFilePath = stack.strSourceDir;
2407 strSrcFilePath.append(RTPATH_SLASH_STR);
2408 strSrcFilePath.append(strSourceOVF);
2409 }
2410
2411 /* First of all check if the path is an UUID. If so, the user like to
2412 * import the disk into an existing path. This is useful for iSCSI for
2413 * example. */
2414 RTUUID uuid;
2415 int vrc = RTUuidFromStr(&uuid, pStrDstPath->c_str());
2416 if (vrc == VINF_SUCCESS)
2417 {
2418 rc = mVirtualBox->i_findHardDiskById(Guid(uuid), true, &pTargetHD);
2419 if (FAILED(rc)) throw rc;
2420 }
2421 else
2422 {
2423 RTVFSIOSTREAM hVfsIosSrc = NIL_RTVFSIOSTREAM;
2424
2425 /* check read file to GZIP compression */
2426 bool const fGzipped = di.strCompression.compare("gzip",Utf8Str::CaseInsensitive) == 0;
2427 Utf8Str strDeleteTemp;
2428 try
2429 {
2430 Utf8Str strTrgFormat = "VMDK";
2431 ComObjPtr<MediumFormat> trgFormat;
2432 Bstr bstrFormatName;
2433 ULONG lCabs = 0;
2434
2435 char *pszSuff = RTPathSuffix(pStrDstPath->c_str());
2436 if (pszSuff != NULL)
2437 {
2438 /*
2439 * Figure out which format the user like to have. Default is VMDK
2440 * or it can be VDI if according command-line option is set
2441 */
2442
2443 /*
2444 * We need a proper target format
2445 * if target format has been changed by user via GUI import wizard
2446 * or via VBoxManage import command (option --importtovdi)
2447 * then we need properly process such format like ISO
2448 * Because there is no conversion ISO to VDI
2449 */
2450 trgFormat = pSysProps->i_mediumFormatFromExtension(++pszSuff);
2451 if (trgFormat.isNull())
2452 throw setError(E_FAIL, tr("Unsupported medium format for disk image '%s'"), di.strHref.c_str());
2453
2454 rc = trgFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
2455 if (FAILED(rc)) throw rc;
2456
2457 strTrgFormat = Utf8Str(bstrFormatName);
2458
2459 if ( m->optListImport.contains(ImportOptions_ImportToVDI)
2460 && strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) != 0)
2461 {
2462 /* change the target extension */
2463 strTrgFormat = "vdi";
2464 trgFormat = pSysProps->i_mediumFormatFromExtension(strTrgFormat);
2465 *pStrDstPath = pStrDstPath->stripSuffix();
2466 *pStrDstPath = pStrDstPath->append(".");
2467 *pStrDstPath = pStrDstPath->append(strTrgFormat.c_str());
2468 }
2469
2470 /* Check the capabilities. We need create capabilities. */
2471 lCabs = 0;
2472 com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
2473 rc = trgFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
2474
2475 if (FAILED(rc))
2476 throw rc;
2477
2478 for (ULONG j = 0; j < mediumFormatCap.size(); j++)
2479 lCabs |= mediumFormatCap[j];
2480
2481 if ( !(lCabs & MediumFormatCapabilities_CreateFixed)
2482 && !(lCabs & MediumFormatCapabilities_CreateDynamic) )
2483 throw setError(VBOX_E_NOT_SUPPORTED,
2484 tr("Could not find a valid medium format for the target disk '%s'"),
2485 pStrDstPath->c_str());
2486 }
2487 else
2488 {
2489 throw setError(VBOX_E_FILE_ERROR,
2490 tr("The target disk '%s' has no extension "),
2491 pStrDstPath->c_str(), VERR_INVALID_NAME);
2492 }
2493
2494 /* Create an IMedium object. */
2495 pTargetHD.createObject();
2496
2497 /*CD/DVD case*/
2498 if (strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) == 0)
2499 {
2500 try
2501 {
2502 if (fGzipped)
2503 i_importDecompressFile(stack, strSrcFilePath, *pStrDstPath, strSourceOVF.c_str());
2504 else
2505 i_importCopyFile(stack, strSrcFilePath, *pStrDstPath, strSourceOVF.c_str());
2506 }
2507 catch (HRESULT /*arc*/)
2508 {
2509 throw;
2510 }
2511
2512 /* Advance to the next operation. */
2513 /* operation's weight, as set up with the IProgress originally */
2514 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
2515 RTPathFilename(strSourceOVF.c_str())).raw(),
2516 di.ulSuggestedSizeMB);
2517 }
2518 else/* HDD case*/
2519 {
2520 rc = pTargetHD->init(mVirtualBox,
2521 strTrgFormat,
2522 *pStrDstPath,
2523 Guid::Empty /* media registry: none yet */,
2524 DeviceType_HardDisk);
2525 if (FAILED(rc)) throw rc;
2526
2527 /* Now create an empty hard disk. */
2528 rc = mVirtualBox->CreateMedium(Bstr(strTrgFormat).raw(),
2529 Bstr(*pStrDstPath).raw(),
2530 AccessMode_ReadWrite, DeviceType_HardDisk,
2531 ComPtr<IMedium>(pTargetHD).asOutParam());
2532 if (FAILED(rc)) throw rc;
2533
2534 /* If strHref is empty we have to create a new file. */
2535 if (strSourceOVF.isEmpty())
2536 {
2537 com::SafeArray<MediumVariant_T> mediumVariant;
2538 mediumVariant.push_back(MediumVariant_Standard);
2539
2540 /* Kick of the creation of a dynamic growing disk image with the given capacity. */
2541 rc = pTargetHD->CreateBaseStorage(di.iCapacity / _1M,
2542 ComSafeArrayAsInParam(mediumVariant),
2543 ComPtr<IProgress>(pProgress).asOutParam());
2544 if (FAILED(rc)) throw rc;
2545
2546 /* Advance to the next operation. */
2547 /* operation's weight, as set up with the IProgress originally */
2548 stack.pProgress->SetNextOperation(BstrFmt(tr("Creating disk image '%s'"),
2549 pStrDstPath->c_str()).raw(),
2550 di.ulSuggestedSizeMB);
2551 }
2552 else
2553 {
2554 /* We need a proper source format description */
2555 /* Which format to use? */
2556 ComObjPtr<MediumFormat> srcFormat;
2557 rc = i_findMediumFormatFromDiskImage(di, srcFormat);
2558 if (FAILED(rc))
2559 throw setError(VBOX_E_NOT_SUPPORTED,
2560 tr("Could not find a valid medium format for the source disk '%s' "
2561 "Check correctness of the image format URL in the OVF description file "
2562 "or extension of the image"),
2563 RTPathFilename(strSourceOVF.c_str()));
2564
2565 /* If gzipped, decompress the GZIP file and save a new file in the target path */
2566 if (fGzipped)
2567 {
2568 Utf8Str strTargetFilePath(*pStrDstPath);
2569 strTargetFilePath.stripFilename();
2570 strTargetFilePath.append(RTPATH_SLASH_STR);
2571 strTargetFilePath.append("temp_");
2572 strTargetFilePath.append(RTPathFilename(strSrcFilePath.c_str()));
2573 strDeleteTemp = strTargetFilePath;
2574
2575 i_importDecompressFile(stack, strSrcFilePath, strTargetFilePath, strSourceOVF.c_str());
2576
2577 /* Correct the source and the target with the actual values */
2578 strSrcFilePath = strTargetFilePath;
2579
2580 /* Open the new source file. */
2581 vrc = RTVfsIoStrmOpenNormal(strSrcFilePath.c_str(), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
2582 &hVfsIosSrc);
2583 if (RT_FAILURE(vrc))
2584 throw setErrorVrc(vrc, tr("Error opening decompressed image file '%s' (%Rrc)"),
2585 strSrcFilePath.c_str(), vrc);
2586 }
2587 else
2588 hVfsIosSrc = i_importOpenSourceFile(stack, strSrcFilePath, strSourceOVF.c_str());
2589
2590 /* Add a read ahead thread to try speed things up with concurrent reads and
2591 writes going on in different threads. */
2592 RTVFSIOSTREAM hVfsIosReadAhead;
2593 vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/,
2594 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
2595 RTVfsIoStrmRelease(hVfsIosSrc);
2596 if (RT_FAILURE(vrc))
2597 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"),
2598 strSrcFilePath.c_str(), vrc);
2599
2600 /* Start the source image cloning operation. */
2601 ComObjPtr<Medium> nullParent;
2602 rc = pTargetHD->i_importFile(strSrcFilePath.c_str(),
2603 srcFormat,
2604 MediumVariant_Standard,
2605 hVfsIosReadAhead,
2606 nullParent,
2607 pProgress);
2608 RTVfsIoStrmRelease(hVfsIosReadAhead);
2609 hVfsIosSrc = NIL_RTVFSIOSTREAM;
2610 if (FAILED(rc))
2611 throw rc;
2612
2613 /* Advance to the next operation. */
2614 /* operation's weight, as set up with the IProgress originally */
2615 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
2616 RTPathFilename(strSourceOVF.c_str())).raw(),
2617 di.ulSuggestedSizeMB);
2618 }
2619
2620 /* Now wait for the background disk operation to complete; this throws
2621 * HRESULTs on error. */
2622 ComPtr<IProgress> pp(pProgress);
2623 i_waitForAsyncProgress(stack.pProgress, pp);
2624 }
2625 }
2626 catch (...)
2627 {
2628 if (strDeleteTemp.isNotEmpty())
2629 RTFileDelete(strDeleteTemp.c_str());
2630 throw;
2631 }
2632
2633 /* Make sure the source file is closed. */
2634 if (hVfsIosSrc != NIL_RTVFSIOSTREAM)
2635 RTVfsIoStrmRelease(hVfsIosSrc);
2636
2637 /*
2638 * Delete the temp gunzip result, if any.
2639 */
2640 if (strDeleteTemp.isNotEmpty())
2641 {
2642 vrc = RTFileDelete(strSrcFilePath.c_str());
2643 if (RT_FAILURE(vrc))
2644 setWarning(VBOX_E_FILE_ERROR,
2645 tr("Failed to delete the temporary file '%s' (%Rrc)"), strSrcFilePath.c_str(), vrc);
2646 }
2647 }
2648}
2649
2650/**
2651 * Imports one OVF virtual system (described by the given ovf::VirtualSystem and VirtualSystemDescription)
2652 * into VirtualBox by creating an IMachine instance, which is returned.
2653 *
2654 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
2655 * up any leftovers from this function. For this, the given ImportStack instance has received information
2656 * about what needs cleaning up (to support rollback).
2657 *
2658 * @param vsysThis OVF virtual system (machine) to import.
2659 * @param vsdescThis Matching virtual system description (machine) to import.
2660 * @param pNewMachine out: Newly created machine.
2661 * @param stack Cleanup stack for when this throws.
2662 */
2663void Appliance::i_importMachineGeneric(const ovf::VirtualSystem &vsysThis,
2664 ComObjPtr<VirtualSystemDescription> &vsdescThis,
2665 ComPtr<IMachine> &pNewMachine,
2666 ImportStack &stack)
2667{
2668 LogFlowFuncEnter();
2669 HRESULT rc;
2670
2671 // Get the instance of IGuestOSType which matches our string guest OS type so we
2672 // can use recommended defaults for the new machine where OVF doesn't provide any
2673 ComPtr<IGuestOSType> osType;
2674 rc = mVirtualBox->GetGuestOSType(Bstr(stack.strOsTypeVBox).raw(), osType.asOutParam());
2675 if (FAILED(rc)) throw rc;
2676
2677 /* Create the machine */
2678 SafeArray<BSTR> groups; /* no groups */
2679 rc = mVirtualBox->CreateMachine(NULL, /* machine name: use default */
2680 Bstr(stack.strNameVBox).raw(),
2681 ComSafeArrayAsInParam(groups),
2682 Bstr(stack.strOsTypeVBox).raw(),
2683 NULL, /* aCreateFlags */
2684 pNewMachine.asOutParam());
2685 if (FAILED(rc)) throw rc;
2686
2687 // set the description
2688 if (!stack.strDescription.isEmpty())
2689 {
2690 rc = pNewMachine->COMSETTER(Description)(Bstr(stack.strDescription).raw());
2691 if (FAILED(rc)) throw rc;
2692 }
2693
2694 // CPU count
2695 rc = pNewMachine->COMSETTER(CPUCount)(stack.cCPUs);
2696 if (FAILED(rc)) throw rc;
2697
2698 if (stack.fForceHWVirt)
2699 {
2700 rc = pNewMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, TRUE);
2701 if (FAILED(rc)) throw rc;
2702 }
2703
2704 // RAM
2705 rc = pNewMachine->COMSETTER(MemorySize)(stack.ulMemorySizeMB);
2706 if (FAILED(rc)) throw rc;
2707
2708 /* VRAM */
2709 /* Get the recommended VRAM for this guest OS type */
2710 ULONG vramVBox;
2711 rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox);
2712 if (FAILED(rc)) throw rc;
2713
2714 /* Set the VRAM */
2715 rc = pNewMachine->COMSETTER(VRAMSize)(vramVBox);
2716 if (FAILED(rc)) throw rc;
2717
2718 // I/O APIC: Generic OVF has no setting for this. Enable it if we
2719 // import a Windows VM because if if Windows was installed without IOAPIC,
2720 // it will not mind finding an one later on, but if Windows was installed
2721 // _with_ an IOAPIC, it will bluescreen if it's not found
2722 if (!stack.fForceIOAPIC)
2723 {
2724 Bstr bstrFamilyId;
2725 rc = osType->COMGETTER(FamilyId)(bstrFamilyId.asOutParam());
2726 if (FAILED(rc)) throw rc;
2727 if (bstrFamilyId == "Windows")
2728 stack.fForceIOAPIC = true;
2729 }
2730
2731 if (stack.fForceIOAPIC)
2732 {
2733 ComPtr<IBIOSSettings> pBIOSSettings;
2734 rc = pNewMachine->COMGETTER(BIOSSettings)(pBIOSSettings.asOutParam());
2735 if (FAILED(rc)) throw rc;
2736
2737 rc = pBIOSSettings->COMSETTER(IOAPICEnabled)(TRUE);
2738 if (FAILED(rc)) throw rc;
2739 }
2740
2741 if (!stack.strAudioAdapter.isEmpty())
2742 if (stack.strAudioAdapter.compare("null", Utf8Str::CaseInsensitive) != 0)
2743 {
2744 uint32_t audio = RTStrToUInt32(stack.strAudioAdapter.c_str()); // should be 0 for AC97
2745 ComPtr<IAudioAdapter> audioAdapter;
2746 rc = pNewMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam());
2747 if (FAILED(rc)) throw rc;
2748 rc = audioAdapter->COMSETTER(Enabled)(true);
2749 if (FAILED(rc)) throw rc;
2750 rc = audioAdapter->COMSETTER(AudioController)(static_cast<AudioControllerType_T>(audio));
2751 if (FAILED(rc)) throw rc;
2752 }
2753
2754#ifdef VBOX_WITH_USB
2755 /* USB Controller */
2756 if (stack.fUSBEnabled)
2757 {
2758 ComPtr<IUSBController> usbController;
2759 rc = pNewMachine->AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, usbController.asOutParam());
2760 if (FAILED(rc)) throw rc;
2761 }
2762#endif /* VBOX_WITH_USB */
2763
2764 /* Change the network adapters */
2765 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
2766
2767 std::list<VirtualSystemDescriptionEntry*> vsdeNW = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
2768 if (vsdeNW.empty())
2769 {
2770 /* No network adapters, so we have to disable our default one */
2771 ComPtr<INetworkAdapter> nwVBox;
2772 rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam());
2773 if (FAILED(rc)) throw rc;
2774 rc = nwVBox->COMSETTER(Enabled)(false);
2775 if (FAILED(rc)) throw rc;
2776 }
2777 else if (vsdeNW.size() > maxNetworkAdapters)
2778 throw setError(VBOX_E_FILE_ERROR,
2779 tr("Too many network adapters: OVF requests %d network adapters, "
2780 "but VirtualBox only supports %d"),
2781 vsdeNW.size(), maxNetworkAdapters);
2782 else
2783 {
2784 list<VirtualSystemDescriptionEntry*>::const_iterator nwIt;
2785 size_t a = 0;
2786 for (nwIt = vsdeNW.begin();
2787 nwIt != vsdeNW.end();
2788 ++nwIt, ++a)
2789 {
2790 const VirtualSystemDescriptionEntry* pvsys = *nwIt;
2791
2792 const Utf8Str &nwTypeVBox = pvsys->strVBoxCurrent;
2793 uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str());
2794 ComPtr<INetworkAdapter> pNetworkAdapter;
2795 rc = pNewMachine->GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
2796 if (FAILED(rc)) throw rc;
2797 /* Enable the network card & set the adapter type */
2798 rc = pNetworkAdapter->COMSETTER(Enabled)(true);
2799 if (FAILED(rc)) throw rc;
2800 rc = pNetworkAdapter->COMSETTER(AdapterType)(static_cast<NetworkAdapterType_T>(tt1));
2801 if (FAILED(rc)) throw rc;
2802
2803 // default is NAT; change to "bridged" if extra conf says so
2804 if (pvsys->strExtraConfigCurrent.endsWith("type=Bridged", Utf8Str::CaseInsensitive))
2805 {
2806 /* Attach to the right interface */
2807 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged);
2808 if (FAILED(rc)) throw rc;
2809 ComPtr<IHost> host;
2810 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
2811 if (FAILED(rc)) throw rc;
2812 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
2813 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
2814 if (FAILED(rc)) throw rc;
2815 // We search for the first host network interface which
2816 // is usable for bridged networking
2817 for (size_t j = 0;
2818 j < nwInterfaces.size();
2819 ++j)
2820 {
2821 HostNetworkInterfaceType_T itype;
2822 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
2823 if (FAILED(rc)) throw rc;
2824 if (itype == HostNetworkInterfaceType_Bridged)
2825 {
2826 Bstr name;
2827 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
2828 if (FAILED(rc)) throw rc;
2829 /* Set the interface name to attach to */
2830 rc = pNetworkAdapter->COMSETTER(BridgedInterface)(name.raw());
2831 if (FAILED(rc)) throw rc;
2832 break;
2833 }
2834 }
2835 }
2836 /* Next test for host only interfaces */
2837 else if (pvsys->strExtraConfigCurrent.endsWith("type=HostOnly", Utf8Str::CaseInsensitive))
2838 {
2839 /* Attach to the right interface */
2840 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly);
2841 if (FAILED(rc)) throw rc;
2842 ComPtr<IHost> host;
2843 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
2844 if (FAILED(rc)) throw rc;
2845 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
2846 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
2847 if (FAILED(rc)) throw rc;
2848 // We search for the first host network interface which
2849 // is usable for host only networking
2850 for (size_t j = 0;
2851 j < nwInterfaces.size();
2852 ++j)
2853 {
2854 HostNetworkInterfaceType_T itype;
2855 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
2856 if (FAILED(rc)) throw rc;
2857 if (itype == HostNetworkInterfaceType_HostOnly)
2858 {
2859 Bstr name;
2860 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
2861 if (FAILED(rc)) throw rc;
2862 /* Set the interface name to attach to */
2863 rc = pNetworkAdapter->COMSETTER(HostOnlyInterface)(name.raw());
2864 if (FAILED(rc)) throw rc;
2865 break;
2866 }
2867 }
2868 }
2869 /* Next test for internal interfaces */
2870 else if (pvsys->strExtraConfigCurrent.endsWith("type=Internal", Utf8Str::CaseInsensitive))
2871 {
2872 /* Attach to the right interface */
2873 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Internal);
2874 if (FAILED(rc)) throw rc;
2875 }
2876 /* Next test for Generic interfaces */
2877 else if (pvsys->strExtraConfigCurrent.endsWith("type=Generic", Utf8Str::CaseInsensitive))
2878 {
2879 /* Attach to the right interface */
2880 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Generic);
2881 if (FAILED(rc)) throw rc;
2882 }
2883
2884 /* Next test for NAT network interfaces */
2885 else if (pvsys->strExtraConfigCurrent.endsWith("type=NATNetwork", Utf8Str::CaseInsensitive))
2886 {
2887 /* Attach to the right interface */
2888 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork);
2889 if (FAILED(rc)) throw rc;
2890 com::SafeIfaceArray<INATNetwork> nwNATNetworks;
2891 rc = mVirtualBox->COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(nwNATNetworks));
2892 if (FAILED(rc)) throw rc;
2893 // Pick the first NAT network (if there is any)
2894 if (nwNATNetworks.size())
2895 {
2896 Bstr name;
2897 rc = nwNATNetworks[0]->COMGETTER(NetworkName)(name.asOutParam());
2898 if (FAILED(rc)) throw rc;
2899 /* Set the NAT network name to attach to */
2900 rc = pNetworkAdapter->COMSETTER(NATNetwork)(name.raw());
2901 if (FAILED(rc)) throw rc;
2902 break;
2903 }
2904 }
2905 }
2906 }
2907
2908 // IDE Hard disk controller
2909 std::list<VirtualSystemDescriptionEntry*> vsdeHDCIDE =
2910 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerIDE);
2911 /*
2912 * In OVF (at least VMware's version of it), an IDE controller has two ports,
2913 * so VirtualBox's single IDE controller with two channels and two ports each counts as
2914 * two OVF IDE controllers -- so we accept one or two such IDE controllers
2915 */
2916 size_t cIDEControllers = vsdeHDCIDE.size();
2917 if (cIDEControllers > 2)
2918 throw setError(VBOX_E_FILE_ERROR,
2919 tr("Too many IDE controllers in OVF; import facility only supports two"));
2920 if (!vsdeHDCIDE.empty())
2921 {
2922 // one or two IDE controllers present in OVF: add one VirtualBox controller
2923 ComPtr<IStorageController> pController;
2924 rc = pNewMachine->AddStorageController(Bstr("IDE").raw(), StorageBus_IDE, pController.asOutParam());
2925 if (FAILED(rc)) throw rc;
2926
2927 const char *pcszIDEType = vsdeHDCIDE.front()->strVBoxCurrent.c_str();
2928 if (!strcmp(pcszIDEType, "PIIX3"))
2929 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX3);
2930 else if (!strcmp(pcszIDEType, "PIIX4"))
2931 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX4);
2932 else if (!strcmp(pcszIDEType, "ICH6"))
2933 rc = pController->COMSETTER(ControllerType)(StorageControllerType_ICH6);
2934 else
2935 throw setError(VBOX_E_FILE_ERROR,
2936 tr("Invalid IDE controller type \"%s\""),
2937 pcszIDEType);
2938 if (FAILED(rc)) throw rc;
2939 }
2940
2941 /* Hard disk controller SATA */
2942 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSATA =
2943 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSATA);
2944 if (vsdeHDCSATA.size() > 1)
2945 throw setError(VBOX_E_FILE_ERROR,
2946 tr("Too many SATA controllers in OVF; import facility only supports one"));
2947 if (!vsdeHDCSATA.empty())
2948 {
2949 ComPtr<IStorageController> pController;
2950 const Utf8Str &hdcVBox = vsdeHDCSATA.front()->strVBoxCurrent;
2951 if (hdcVBox == "AHCI")
2952 {
2953 rc = pNewMachine->AddStorageController(Bstr("SATA").raw(),
2954 StorageBus_SATA,
2955 pController.asOutParam());
2956 if (FAILED(rc)) throw rc;
2957 }
2958 else
2959 throw setError(VBOX_E_FILE_ERROR,
2960 tr("Invalid SATA controller type \"%s\""),
2961 hdcVBox.c_str());
2962 }
2963
2964 /* Hard disk controller SCSI */
2965 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSCSI =
2966 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI);
2967 if (vsdeHDCSCSI.size() > 1)
2968 throw setError(VBOX_E_FILE_ERROR,
2969 tr("Too many SCSI controllers in OVF; import facility only supports one"));
2970 if (!vsdeHDCSCSI.empty())
2971 {
2972 ComPtr<IStorageController> pController;
2973 Utf8Str strName("SCSI");
2974 StorageBus_T busType = StorageBus_SCSI;
2975 StorageControllerType_T controllerType;
2976 const Utf8Str &hdcVBox = vsdeHDCSCSI.front()->strVBoxCurrent;
2977 if (hdcVBox == "LsiLogic")
2978 controllerType = StorageControllerType_LsiLogic;
2979 else if (hdcVBox == "LsiLogicSas")
2980 {
2981 // OVF treats LsiLogicSas as a SCSI controller but VBox considers it a class of its own
2982 strName = "SAS";
2983 busType = StorageBus_SAS;
2984 controllerType = StorageControllerType_LsiLogicSas;
2985 }
2986 else if (hdcVBox == "BusLogic")
2987 controllerType = StorageControllerType_BusLogic;
2988 else
2989 throw setError(VBOX_E_FILE_ERROR,
2990 tr("Invalid SCSI controller type \"%s\""),
2991 hdcVBox.c_str());
2992
2993 rc = pNewMachine->AddStorageController(Bstr(strName).raw(), busType, pController.asOutParam());
2994 if (FAILED(rc)) throw rc;
2995 rc = pController->COMSETTER(ControllerType)(controllerType);
2996 if (FAILED(rc)) throw rc;
2997 }
2998
2999 /* Hard disk controller SAS */
3000 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSAS =
3001 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSAS);
3002 if (vsdeHDCSAS.size() > 1)
3003 throw setError(VBOX_E_FILE_ERROR,
3004 tr("Too many SAS controllers in OVF; import facility only supports one"));
3005 if (!vsdeHDCSAS.empty())
3006 {
3007 ComPtr<IStorageController> pController;
3008 rc = pNewMachine->AddStorageController(Bstr(L"SAS").raw(),
3009 StorageBus_SAS,
3010 pController.asOutParam());
3011 if (FAILED(rc)) throw rc;
3012 rc = pController->COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas);
3013 if (FAILED(rc)) throw rc;
3014 }
3015
3016 /* Now its time to register the machine before we add any hard disks */
3017 rc = mVirtualBox->RegisterMachine(pNewMachine);
3018 if (FAILED(rc)) throw rc;
3019
3020 // store new machine for roll-back in case of errors
3021 Bstr bstrNewMachineId;
3022 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
3023 if (FAILED(rc)) throw rc;
3024 Guid uuidNewMachine(bstrNewMachineId);
3025 m->llGuidsMachinesCreated.push_back(uuidNewMachine);
3026
3027 // Add floppies and CD-ROMs to the appropriate controllers.
3028 std::list<VirtualSystemDescriptionEntry*> vsdeFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy);
3029 if (vsdeFloppy.size() > 1)
3030 throw setError(VBOX_E_FILE_ERROR,
3031 tr("Too many floppy controllers in OVF; import facility only supports one"));
3032 std::list<VirtualSystemDescriptionEntry*> vsdeCDROM = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
3033 if ( !vsdeFloppy.empty()
3034 || !vsdeCDROM.empty()
3035 )
3036 {
3037 // If there's an error here we need to close the session, so
3038 // we need another try/catch block.
3039
3040 try
3041 {
3042 // to attach things we need to open a session for the new machine
3043 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
3044 if (FAILED(rc)) throw rc;
3045 stack.fSessionOpen = true;
3046
3047 ComPtr<IMachine> sMachine;
3048 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
3049 if (FAILED(rc)) throw rc;
3050
3051 // floppy first
3052 if (vsdeFloppy.size() == 1)
3053 {
3054 ComPtr<IStorageController> pController;
3055 rc = sMachine->AddStorageController(Bstr("Floppy").raw(),
3056 StorageBus_Floppy,
3057 pController.asOutParam());
3058 if (FAILED(rc)) throw rc;
3059
3060 Bstr bstrName;
3061 rc = pController->COMGETTER(Name)(bstrName.asOutParam());
3062 if (FAILED(rc)) throw rc;
3063
3064 // this is for rollback later
3065 MyHardDiskAttachment mhda;
3066 mhda.pMachine = pNewMachine;
3067 mhda.controllerName = bstrName;
3068 mhda.lControllerPort = 0;
3069 mhda.lDevice = 0;
3070
3071 Log(("Attaching floppy\n"));
3072
3073 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),
3074 mhda.lControllerPort,
3075 mhda.lDevice,
3076 DeviceType_Floppy,
3077 NULL);
3078 if (FAILED(rc)) throw rc;
3079
3080 stack.llHardDiskAttachments.push_back(mhda);
3081 }
3082
3083 rc = sMachine->SaveSettings();
3084 if (FAILED(rc)) throw rc;
3085
3086 // only now that we're done with all disks, close the session
3087 rc = stack.pSession->UnlockMachine();
3088 if (FAILED(rc)) throw rc;
3089 stack.fSessionOpen = false;
3090 }
3091 catch(HRESULT aRC)
3092 {
3093 com::ErrorInfo info;
3094
3095 if (stack.fSessionOpen)
3096 stack.pSession->UnlockMachine();
3097
3098 if (info.isFullAvailable())
3099 throw setError(aRC, Utf8Str(info.getText()).c_str());
3100 else
3101 throw setError(aRC, "Unknown error during OVF import");
3102 }
3103 }
3104
3105 // create the hard disks & connect them to the appropriate controllers
3106 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
3107 if (!avsdeHDs.empty())
3108 {
3109 // If there's an error here we need to close the session, so
3110 // we need another try/catch block.
3111 try
3112 {
3113#ifdef LOG_ENABLED
3114 if (LogIsEnabled())
3115 {
3116 size_t i = 0;
3117 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3118 itHD != avsdeHDs.end(); ++itHD, i++)
3119 Log(("avsdeHDs[%zu]: strRef=%s strOvf=%s\n", i, (*itHD)->strRef.c_str(), (*itHD)->strOvf.c_str()));
3120 i = 0;
3121 for (ovf::DiskImagesMap::const_iterator itDisk = stack.mapDisks.begin(); itDisk != stack.mapDisks.end(); ++itDisk)
3122 Log(("mapDisks[%zu]: strDiskId=%s strHref=%s\n",
3123 i, itDisk->second.strDiskId.c_str(), itDisk->second.strHref.c_str()));
3124
3125 }
3126#endif
3127
3128 // to attach things we need to open a session for the new machine
3129 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
3130 if (FAILED(rc)) throw rc;
3131 stack.fSessionOpen = true;
3132
3133 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
3134 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
3135 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
3136 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
3137
3138
3139 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
3140 std::set<RTCString> disksResolvedNames;
3141
3142 uint32_t cImportedDisks = 0;
3143
3144 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
3145 {
3146/** @todo r=bird: Most of the code here is duplicated in the other machine
3147 * import method, factor out. */
3148 ovf::DiskImage diCurrent = oit->second;
3149
3150 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
3151 /* Iterate over all given disk images of the virtual system
3152 * disks description. We need to find the target disk path,
3153 * which could be changed by the user. */
3154 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
3155 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3156 itHD != avsdeHDs.end();
3157 ++itHD)
3158 {
3159 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
3160 if (vsdeHD->strRef == diCurrent.strDiskId)
3161 {
3162 vsdeTargetHD = vsdeHD;
3163 break;
3164 }
3165 }
3166 if (!vsdeTargetHD)
3167 {
3168 /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
3169 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
3170 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
3171 NOREF(vmNameEntry);
3172 ++oit;
3173 continue;
3174 }
3175
3176 //diCurrent.strDiskId contains the disk identifier (e.g. "vmdisk1"), which should exist
3177 //in the virtual system's disks map under that ID and also in the global images map
3178 ovf::VirtualDisksMap::const_iterator itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
3179 if (itVDisk == vsysThis.mapVirtualDisks.end())
3180 throw setError(E_FAIL,
3181 tr("Internal inconsistency looking up disk image '%s'"),
3182 diCurrent.strHref.c_str());
3183
3184 /*
3185 * preliminary check availability of the image
3186 * This step is useful if image is placed in the OVA (TAR) package
3187 */
3188 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
3189 {
3190 /* It means that we possibly have imported the storage earlier on the previous loop steps*/
3191 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
3192 if (h != disksResolvedNames.end())
3193 {
3194 /* Yes, disk name was found, we can skip it*/
3195 ++oit;
3196 continue;
3197 }
3198l_skipped:
3199 rc = i_preCheckImageAvailability(stack);
3200 if (SUCCEEDED(rc))
3201 {
3202 /* current opened file isn't the same as passed one */
3203 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
3204 {
3205 /* availableImage contains the disk file reference (e.g. "disk1.vmdk"), which should
3206 * exist in the global images map.
3207 * And find the disk from the OVF's disk list */
3208 ovf::DiskImagesMap::const_iterator itDiskImage;
3209 for (itDiskImage = stack.mapDisks.begin();
3210 itDiskImage != stack.mapDisks.end();
3211 itDiskImage++)
3212 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
3213 Utf8Str::CaseInsensitive) == 0)
3214 break;
3215 if (itDiskImage == stack.mapDisks.end())
3216 {
3217 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
3218 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
3219 goto l_skipped;
3220 }
3221
3222 /* replace with a new found disk image */
3223 diCurrent = *(&itDiskImage->second);
3224
3225 /*
3226 * Again iterate over all given disk images of the virtual system
3227 * disks description using the found disk image
3228 */
3229 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3230 itHD != avsdeHDs.end();
3231 ++itHD)
3232 {
3233 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
3234 if (vsdeHD->strRef == diCurrent.strDiskId)
3235 {
3236 vsdeTargetHD = vsdeHD;
3237 break;
3238 }
3239 }
3240
3241 /*
3242 * in this case it's an error because something is wrong with the OVF description file.
3243 * May be VBox imports OVA package with wrong file sequence inside the archive.
3244 */
3245 if (!vsdeTargetHD)
3246 throw setError(E_FAIL,
3247 tr("Internal inconsistency looking up disk image '%s'"),
3248 diCurrent.strHref.c_str());
3249
3250 itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
3251 if (itVDisk == vsysThis.mapVirtualDisks.end())
3252 throw setError(E_FAIL,
3253 tr("Internal inconsistency looking up disk image '%s'"),
3254 diCurrent.strHref.c_str());
3255 }
3256 else
3257 {
3258 ++oit;
3259 }
3260 }
3261 else
3262 {
3263 ++oit;
3264 continue;
3265 }
3266 }
3267 else
3268 {
3269 /* just continue with normal files*/
3270 ++oit;
3271 }
3272
3273 /* very important to store disk name for the next checks */
3274 disksResolvedNames.insert(diCurrent.strHref);
3275////// end of duplicated code.
3276 const ovf::VirtualDisk &ovfVdisk = itVDisk->second;
3277
3278 ComObjPtr<Medium> pTargetHD;
3279
3280 Utf8Str savedVBoxCurrent = vsdeTargetHD->strVBoxCurrent;
3281
3282 i_importOneDiskImage(diCurrent,
3283 &vsdeTargetHD->strVBoxCurrent,
3284 pTargetHD,
3285 stack);
3286
3287 // now use the new uuid to attach the disk image to our new machine
3288 ComPtr<IMachine> sMachine;
3289 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
3290 if (FAILED(rc))
3291 throw rc;
3292
3293 // find the hard disk controller to which we should attach
3294 ovf::HardDiskController hdc = (*vsysThis.mapControllers.find(ovfVdisk.idController)).second;
3295
3296 // this is for rollback later
3297 MyHardDiskAttachment mhda;
3298 mhda.pMachine = pNewMachine;
3299
3300 i_convertDiskAttachmentValues(hdc,
3301 ovfVdisk.ulAddressOnParent,
3302 mhda.controllerName,
3303 mhda.lControllerPort,
3304 mhda.lDevice);
3305
3306 Log(("Attaching disk %s to port %d on device %d\n",
3307 vsdeTargetHD->strVBoxCurrent.c_str(), mhda.lControllerPort, mhda.lDevice));
3308
3309 ComObjPtr<MediumFormat> mediumFormat;
3310 rc = i_findMediumFormatFromDiskImage(diCurrent, mediumFormat);
3311 if (FAILED(rc))
3312 throw rc;
3313
3314 Bstr bstrFormatName;
3315 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
3316 if (FAILED(rc))
3317 throw rc;
3318
3319 Utf8Str vdf = Utf8Str(bstrFormatName);
3320
3321 if (vdf.compare("RAW", Utf8Str::CaseInsensitive) == 0)
3322 {
3323 ComPtr<IMedium> dvdImage(pTargetHD);
3324
3325 rc = mVirtualBox->OpenMedium(Bstr(vsdeTargetHD->strVBoxCurrent).raw(),
3326 DeviceType_DVD,
3327 AccessMode_ReadWrite,
3328 false,
3329 dvdImage.asOutParam());
3330
3331 if (FAILED(rc))
3332 throw rc;
3333
3334 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
3335 mhda.lControllerPort, // long controllerPort
3336 mhda.lDevice, // long device
3337 DeviceType_DVD, // DeviceType_T type
3338 dvdImage);
3339 if (FAILED(rc))
3340 throw rc;
3341 }
3342 else
3343 {
3344 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
3345 mhda.lControllerPort, // long controllerPort
3346 mhda.lDevice, // long device
3347 DeviceType_HardDisk, // DeviceType_T type
3348 pTargetHD);
3349
3350 if (FAILED(rc))
3351 throw rc;
3352 }
3353
3354 stack.llHardDiskAttachments.push_back(mhda);
3355
3356 rc = sMachine->SaveSettings();
3357 if (FAILED(rc))
3358 throw rc;
3359
3360 /* restore */
3361 vsdeTargetHD->strVBoxCurrent = savedVBoxCurrent;
3362
3363 ++cImportedDisks;
3364
3365 } // end while(oit != stack.mapDisks.end())
3366
3367 /*
3368 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
3369 */
3370 if(cImportedDisks < avsdeHDs.size())
3371 {
3372 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
3373 vmNameEntry->strOvf.c_str()));
3374 }
3375
3376 // only now that we're done with all disks, close the session
3377 rc = stack.pSession->UnlockMachine();
3378 if (FAILED(rc))
3379 throw rc;
3380 stack.fSessionOpen = false;
3381 }
3382 catch(HRESULT aRC)
3383 {
3384 com::ErrorInfo info;
3385 if (stack.fSessionOpen)
3386 stack.pSession->UnlockMachine();
3387
3388 if (info.isFullAvailable())
3389 throw setError(aRC, Utf8Str(info.getText()).c_str());
3390 else
3391 throw setError(aRC, "Unknown error during OVF import");
3392 }
3393 }
3394 LogFlowFuncLeave();
3395}
3396
3397/**
3398 * Imports one OVF virtual system (described by a vbox:Machine tag represented by the given config
3399 * structure) into VirtualBox by creating an IMachine instance, which is returned.
3400 *
3401 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
3402 * up any leftovers from this function. For this, the given ImportStack instance has received information
3403 * about what needs cleaning up (to support rollback).
3404 *
3405 * The machine config stored in the settings::MachineConfigFile structure contains the UUIDs of
3406 * the disk attachments used by the machine when it was exported. We also add vbox:uuid attributes
3407 * to the OVF disks sections so we can look them up. While importing these UUIDs into a second host
3408 * will most probably work, reimporting them into the same host will cause conflicts, so we always
3409 * generate new ones on import. This involves the following:
3410 *
3411 * 1) Scan the machine config for disk attachments.
3412 *
3413 * 2) For each disk attachment found, look up the OVF disk image from the disk references section
3414 * and import the disk into VirtualBox, which creates a new UUID for it. In the machine config,
3415 * replace the old UUID with the new one.
3416 *
3417 * 3) Change the machine config according to the OVF virtual system descriptions, in case the
3418 * caller has modified them using setFinalValues().
3419 *
3420 * 4) Create the VirtualBox machine with the modfified machine config.
3421 *
3422 * @param vsdescThis
3423 * @param pReturnNewMachine
3424 * @param stack
3425 */
3426void Appliance::i_importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
3427 ComPtr<IMachine> &pReturnNewMachine,
3428 ImportStack &stack)
3429{
3430 LogFlowFuncEnter();
3431 Assert(vsdescThis->m->pConfig);
3432
3433 HRESULT rc = S_OK;
3434
3435 settings::MachineConfigFile &config = *vsdescThis->m->pConfig;
3436
3437 /*
3438 * step 1): modify machine config according to OVF config, in case the user
3439 * has modified them using setFinalValues()
3440 */
3441
3442 /* OS Type */
3443 config.machineUserData.strOsType = stack.strOsTypeVBox;
3444 /* Description */
3445 config.machineUserData.strDescription = stack.strDescription;
3446 /* CPU count & extented attributes */
3447 config.hardwareMachine.cCPUs = stack.cCPUs;
3448 if (stack.fForceIOAPIC)
3449 config.hardwareMachine.fHardwareVirt = true;
3450 if (stack.fForceIOAPIC)
3451 config.hardwareMachine.biosSettings.fIOAPICEnabled = true;
3452 /* RAM size */
3453 config.hardwareMachine.ulMemorySizeMB = stack.ulMemorySizeMB;
3454
3455/*
3456 <const name="HardDiskControllerIDE" value="14" />
3457 <const name="HardDiskControllerSATA" value="15" />
3458 <const name="HardDiskControllerSCSI" value="16" />
3459 <const name="HardDiskControllerSAS" value="17" />
3460*/
3461
3462#ifdef VBOX_WITH_USB
3463 /* USB controller */
3464 if (stack.fUSBEnabled)
3465 {
3466 /** @todo r=klaus add support for arbitrary USB controller types, this can't handle
3467 * multiple controllers due to its design anyway */
3468 /* usually the OHCI controller is enabled already, need to check */
3469 bool fOHCIEnabled = false;
3470 settings::USBControllerList &llUSBControllers = config.hardwareMachine.usbSettings.llUSBControllers;
3471 settings::USBControllerList::iterator it;
3472 for (it = llUSBControllers.begin(); it != llUSBControllers.end(); ++it)
3473 {
3474 if (it->enmType == USBControllerType_OHCI)
3475 {
3476 fOHCIEnabled = true;
3477 break;
3478 }
3479 }
3480
3481 if (!fOHCIEnabled)
3482 {
3483 settings::USBController ctrl;
3484 ctrl.strName = "OHCI";
3485 ctrl.enmType = USBControllerType_OHCI;
3486
3487 llUSBControllers.push_back(ctrl);
3488 }
3489 }
3490 else
3491 config.hardwareMachine.usbSettings.llUSBControllers.clear();
3492#endif
3493 /* Audio adapter */
3494 if (stack.strAudioAdapter.isNotEmpty())
3495 {
3496 config.hardwareMachine.audioAdapter.fEnabled = true;
3497 config.hardwareMachine.audioAdapter.controllerType = (AudioControllerType_T)stack.strAudioAdapter.toUInt32();
3498 }
3499 else
3500 config.hardwareMachine.audioAdapter.fEnabled = false;
3501 /* Network adapter */
3502 settings::NetworkAdaptersList &llNetworkAdapters = config.hardwareMachine.llNetworkAdapters;
3503 /* First disable all network cards, they will be enabled below again. */
3504 settings::NetworkAdaptersList::iterator it1;
3505 bool fKeepAllMACs = m->optListImport.contains(ImportOptions_KeepAllMACs);
3506 bool fKeepNATMACs = m->optListImport.contains(ImportOptions_KeepNATMACs);
3507 for (it1 = llNetworkAdapters.begin(); it1 != llNetworkAdapters.end(); ++it1)
3508 {
3509 it1->fEnabled = false;
3510 if (!( fKeepAllMACs
3511 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NAT)
3512 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NATNetwork)))
3513 /* Force generation of new MAC address below. */
3514 it1->strMACAddress.setNull();
3515 }
3516 /* Now iterate over all network entries. */
3517 std::list<VirtualSystemDescriptionEntry*> avsdeNWs = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
3518 if (!avsdeNWs.empty())
3519 {
3520 /* Iterate through all network adapter entries and search for the
3521 * corresponding one in the machine config. If one is found, configure
3522 * it based on the user settings. */
3523 list<VirtualSystemDescriptionEntry*>::const_iterator itNW;
3524 for (itNW = avsdeNWs.begin();
3525 itNW != avsdeNWs.end();
3526 ++itNW)
3527 {
3528 VirtualSystemDescriptionEntry *vsdeNW = *itNW;
3529 if ( vsdeNW->strExtraConfigCurrent.startsWith("slot=", Utf8Str::CaseInsensitive)
3530 && vsdeNW->strExtraConfigCurrent.length() > 6)
3531 {
3532 uint32_t iSlot = vsdeNW->strExtraConfigCurrent.substr(5).toUInt32();
3533 /* Iterate through all network adapters in the machine config. */
3534 for (it1 = llNetworkAdapters.begin();
3535 it1 != llNetworkAdapters.end();
3536 ++it1)
3537 {
3538 /* Compare the slots. */
3539 if (it1->ulSlot == iSlot)
3540 {
3541 it1->fEnabled = true;
3542 if (it1->strMACAddress.isEmpty())
3543 Host::i_generateMACAddress(it1->strMACAddress);
3544 it1->type = (NetworkAdapterType_T)vsdeNW->strVBoxCurrent.toUInt32();
3545 break;
3546 }
3547 }
3548 }
3549 }
3550 }
3551
3552 /* Floppy controller */
3553 bool fFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy).size() > 0;
3554 /* DVD controller */
3555 bool fDVD = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM).size() > 0;
3556 /* Iterate over all storage controller check the attachments and remove
3557 * them when necessary. Also detect broken configs with more than one
3558 * attachment. Old VirtualBox versions (prior to 3.2.10) had all disk
3559 * attachments pointing to the last hard disk image, which causes import
3560 * failures. A long fixed bug, however the OVF files are long lived. */
3561 settings::StorageControllersList &llControllers = config.hardwareMachine.storage.llStorageControllers;
3562 Guid hdUuid;
3563 uint32_t cDisks = 0;
3564 bool fInconsistent = false;
3565 bool fRepairDuplicate = false;
3566 settings::StorageControllersList::iterator it3;
3567 for (it3 = llControllers.begin();
3568 it3 != llControllers.end();
3569 ++it3)
3570 {
3571 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
3572 settings::AttachedDevicesList::iterator it4 = llAttachments.begin();
3573 while (it4 != llAttachments.end())
3574 {
3575 if ( ( !fDVD
3576 && it4->deviceType == DeviceType_DVD)
3577 ||
3578 ( !fFloppy
3579 && it4->deviceType == DeviceType_Floppy))
3580 {
3581 it4 = llAttachments.erase(it4);
3582 continue;
3583 }
3584 else if (it4->deviceType == DeviceType_HardDisk)
3585 {
3586 const Guid &thisUuid = it4->uuid;
3587 cDisks++;
3588 if (cDisks == 1)
3589 {
3590 if (hdUuid.isZero())
3591 hdUuid = thisUuid;
3592 else
3593 fInconsistent = true;
3594 }
3595 else
3596 {
3597 if (thisUuid.isZero())
3598 fInconsistent = true;
3599 else if (thisUuid == hdUuid)
3600 fRepairDuplicate = true;
3601 }
3602 }
3603 ++it4;
3604 }
3605 }
3606 /* paranoia... */
3607 if (fInconsistent || cDisks == 1)
3608 fRepairDuplicate = false;
3609
3610 /*
3611 * step 2: scan the machine config for media attachments
3612 */
3613 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
3614 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
3615 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
3616 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
3617
3618 /* Get all hard disk descriptions. */
3619 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
3620 std::list<VirtualSystemDescriptionEntry*>::iterator avsdeHDsIt = avsdeHDs.begin();
3621 /* paranoia - if there is no 1:1 match do not try to repair. */
3622 if (cDisks != avsdeHDs.size())
3623 fRepairDuplicate = false;
3624
3625 // there must be an image in the OVF disk structs with the same UUID
3626
3627 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
3628 std::set<RTCString> disksResolvedNames;
3629
3630 uint32_t cImportedDisks = 0;
3631
3632 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
3633 {
3634/** @todo r=bird: Most of the code here is duplicated in the other machine
3635 * import method, factor out. */
3636 ovf::DiskImage diCurrent = oit->second;
3637
3638 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
3639
3640 /* Iterate over all given disk images of the virtual system
3641 * disks description. We need to find the target disk path,
3642 * which could be changed by the user. */
3643 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
3644 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3645 itHD != avsdeHDs.end();
3646 ++itHD)
3647 {
3648 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
3649 if (vsdeHD->strRef == oit->first)
3650 {
3651 vsdeTargetHD = vsdeHD;
3652 break;
3653 }
3654 }
3655 if (!vsdeTargetHD)
3656 {
3657 /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
3658 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
3659 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
3660 NOREF(vmNameEntry);
3661 ++oit;
3662 continue;
3663 }
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673 /*
3674 * preliminary check availability of the image
3675 * This step is useful if image is placed in the OVA (TAR) package
3676 */
3677 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
3678 {
3679 /* It means that we possibly have imported the storage earlier on a previous loop step. */
3680 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
3681 if (h != disksResolvedNames.end())
3682 {
3683 /* Yes, disk name was found, we can skip it*/
3684 ++oit;
3685 continue;
3686 }
3687l_skipped:
3688 rc = i_preCheckImageAvailability(stack);
3689 if (SUCCEEDED(rc))
3690 {
3691 /* current opened file isn't the same as passed one */
3692 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
3693 {
3694 // availableImage contains the disk identifier (e.g. "vmdisk1"), which should exist
3695 // in the virtual system's disks map under that ID and also in the global images map
3696 // and find the disk from the OVF's disk list
3697 ovf::DiskImagesMap::const_iterator itDiskImage;
3698 for (itDiskImage = stack.mapDisks.begin();
3699 itDiskImage != stack.mapDisks.end();
3700 itDiskImage++)
3701 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
3702 Utf8Str::CaseInsensitive) == 0)
3703 break;
3704 if (itDiskImage == stack.mapDisks.end())
3705 {
3706 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
3707 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
3708 goto l_skipped;
3709 }
3710 //throw setError(E_FAIL,
3711 // tr("Internal inconsistency looking up disk image '%s'. "
3712 // "Check compliance OVA package structure and file names "
3713 // "references in the section <References> in the OVF file."),
3714 // stack.pszOvaLookAheadName);
3715
3716 /* replace with a new found disk image */
3717 diCurrent = *(&itDiskImage->second);
3718
3719 /*
3720 * Again iterate over all given disk images of the virtual system
3721 * disks description using the found disk image
3722 */
3723 vsdeTargetHD = NULL;
3724 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
3725 itHD != avsdeHDs.end();
3726 ++itHD)
3727 {
3728 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
3729 if (vsdeHD->strRef == diCurrent.strDiskId)
3730 {
3731 vsdeTargetHD = vsdeHD;
3732 break;
3733 }
3734 }
3735
3736 /*
3737 * in this case it's an error because something is wrong with the OVF description file.
3738 * May be VBox imports OVA package with wrong file sequence inside the archive.
3739 */
3740 if (!vsdeTargetHD)
3741 throw setError(E_FAIL,
3742 tr("Internal inconsistency looking up disk image '%s'"),
3743 diCurrent.strHref.c_str());
3744
3745
3746
3747
3748
3749 }
3750 else
3751 {
3752 ++oit;
3753 }
3754 }
3755 else
3756 {
3757 ++oit;
3758 continue;
3759 }
3760 }
3761 else
3762 {
3763 /* just continue with normal files*/
3764 ++oit;
3765 }
3766
3767 /* Important! to store disk name for the next checks */
3768 disksResolvedNames.insert(diCurrent.strHref);
3769////// end of duplicated code.
3770 // there must be an image in the OVF disk structs with the same UUID
3771 bool fFound = false;
3772 Utf8Str strUuid;
3773
3774 // for each storage controller...
3775 for (settings::StorageControllersList::iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
3776 sit != config.hardwareMachine.storage.llStorageControllers.end();
3777 ++sit)
3778 {
3779 settings::StorageController &sc = *sit;
3780
3781 // find the OVF virtual system description entry for this storage controller
3782/** @todo
3783 * r=bird: What on earh this is switch supposed to do? (I've added the default:break;, so don't
3784 * get confused by it.) Kind of looks like it's supposed to do something error handling related
3785 * in the default case...
3786 */
3787 switch (sc.storageBus)
3788 {
3789 case StorageBus_SATA:
3790 break;
3791 case StorageBus_SCSI:
3792 break;
3793 case StorageBus_IDE:
3794 break;
3795 case StorageBus_SAS:
3796 break;
3797 default: break; /* Shut up MSC. */
3798 }
3799
3800 // for each medium attachment to this controller...
3801 for (settings::AttachedDevicesList::iterator dit = sc.llAttachedDevices.begin();
3802 dit != sc.llAttachedDevices.end();
3803 ++dit)
3804 {
3805 settings::AttachedDevice &d = *dit;
3806
3807 if (d.uuid.isZero())
3808 // empty DVD and floppy media
3809 continue;
3810
3811 // When repairing a broken VirtualBox xml config section (written
3812 // by VirtualBox versions earlier than 3.2.10) assume the disks
3813 // show up in the same order as in the OVF description.
3814 if (fRepairDuplicate)
3815 {
3816 VirtualSystemDescriptionEntry *vsdeHD = *avsdeHDsIt;
3817 ovf::DiskImagesMap::const_iterator itDiskImage = stack.mapDisks.find(vsdeHD->strRef);
3818 if (itDiskImage != stack.mapDisks.end())
3819 {
3820 const ovf::DiskImage &di = itDiskImage->second;
3821 d.uuid = Guid(di.uuidVBox);
3822 }
3823 ++avsdeHDsIt;
3824 }
3825
3826 // convert the Guid to string
3827 strUuid = d.uuid.toString();
3828
3829 if (diCurrent.uuidVBox != strUuid)
3830 {
3831 continue;
3832 }
3833
3834 /*
3835 * step 3: import disk
3836 */
3837 Utf8Str savedVBoxCurrent = vsdeTargetHD->strVBoxCurrent;
3838 ComObjPtr<Medium> pTargetHD;
3839
3840 i_importOneDiskImage(diCurrent,
3841 &vsdeTargetHD->strVBoxCurrent,
3842 pTargetHD,
3843 stack);
3844
3845 Bstr hdId;
3846
3847 ComObjPtr<MediumFormat> mediumFormat;
3848 rc = i_findMediumFormatFromDiskImage(diCurrent, mediumFormat);
3849 if (FAILED(rc))
3850 throw rc;
3851
3852 Bstr bstrFormatName;
3853 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
3854 if (FAILED(rc))
3855 throw rc;
3856
3857 Utf8Str vdf = Utf8Str(bstrFormatName);
3858
3859 if (vdf.compare("RAW", Utf8Str::CaseInsensitive) == 0)
3860 {
3861 ComPtr<IMedium> dvdImage(pTargetHD);
3862
3863 rc = mVirtualBox->OpenMedium(Bstr(vsdeTargetHD->strVBoxCurrent).raw(),
3864 DeviceType_DVD,
3865 AccessMode_ReadWrite,
3866 false,
3867 dvdImage.asOutParam());
3868
3869 if (FAILED(rc)) throw rc;
3870
3871 // ... and replace the old UUID in the machine config with the one of
3872 // the imported disk that was just created
3873 rc = dvdImage->COMGETTER(Id)(hdId.asOutParam());
3874 if (FAILED(rc)) throw rc;
3875 }
3876 else
3877 {
3878 // ... and replace the old UUID in the machine config with the one of
3879 // the imported disk that was just created
3880 rc = pTargetHD->COMGETTER(Id)(hdId.asOutParam());
3881 if (FAILED(rc)) throw rc;
3882 }
3883
3884 /* restore */
3885 vsdeTargetHD->strVBoxCurrent = savedVBoxCurrent;
3886
3887 /*
3888 * 1. saving original UUID for restoring in case of failure.
3889 * 2. replacement of original UUID by new UUID in the current VM config (settings::MachineConfigFile).
3890 */
3891 {
3892 rc = stack.saveOriginalUUIDOfAttachedDevice(d, Utf8Str(hdId));
3893 d.uuid = hdId;
3894 }
3895
3896 fFound = true;
3897 break;
3898 } // for (settings::AttachedDevicesList::const_iterator dit = sc.llAttachedDevices.begin();
3899 } // for (settings::StorageControllersList::const_iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
3900
3901 // no disk with such a UUID found:
3902 if (!fFound)
3903 throw setError(E_FAIL,
3904 tr("<vbox:Machine> element in OVF contains a medium attachment for the disk image %s "
3905 "but the OVF describes no such image"),
3906 strUuid.c_str());
3907
3908 ++cImportedDisks;
3909
3910 }// while(oit != stack.mapDisks.end())
3911
3912
3913 /*
3914 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
3915 */
3916 if(cImportedDisks < avsdeHDs.size())
3917 {
3918 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
3919 vmNameEntry->strOvf.c_str()));
3920 }
3921
3922 /*
3923 * step 4): create the machine and have it import the config
3924 */
3925
3926 ComObjPtr<Machine> pNewMachine;
3927 rc = pNewMachine.createObject();
3928 if (FAILED(rc)) throw rc;
3929
3930 // this magic constructor fills the new machine object with the MachineConfig
3931 // instance that we created from the vbox:Machine
3932 rc = pNewMachine->init(mVirtualBox,
3933 stack.strNameVBox,// name from OVF preparations; can be suffixed to avoid duplicates
3934 config); // the whole machine config
3935 if (FAILED(rc)) throw rc;
3936
3937 pReturnNewMachine = ComPtr<IMachine>(pNewMachine);
3938
3939 // and register it
3940 rc = mVirtualBox->RegisterMachine(pNewMachine);
3941 if (FAILED(rc)) throw rc;
3942
3943 // store new machine for roll-back in case of errors
3944 Bstr bstrNewMachineId;
3945 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
3946 if (FAILED(rc)) throw rc;
3947 m->llGuidsMachinesCreated.push_back(Guid(bstrNewMachineId));
3948
3949 LogFlowFuncLeave();
3950}
3951
3952/**
3953 * @throws HRESULT errors.
3954 */
3955void Appliance::i_importMachines(ImportStack &stack)
3956{
3957 // this is safe to access because this thread only gets started
3958 const ovf::OVFReader &reader = *m->pReader;
3959
3960 // create a session for the machine + disks we manipulate below
3961 HRESULT rc = stack.pSession.createInprocObject(CLSID_Session);
3962 ComAssertComRCThrowRC(rc);
3963
3964 list<ovf::VirtualSystem>::const_iterator it;
3965 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it1;
3966 /* Iterate through all virtual systems of that appliance */
3967 size_t i = 0;
3968 for (it = reader.m_llVirtualSystems.begin(), it1 = m->virtualSystemDescriptions.begin();
3969 it != reader.m_llVirtualSystems.end() && it1 != m->virtualSystemDescriptions.end();
3970 ++it, ++it1, ++i)
3971 {
3972 const ovf::VirtualSystem &vsysThis = *it;
3973 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it1);
3974
3975 ComPtr<IMachine> pNewMachine;
3976
3977 // there are two ways in which we can create a vbox machine from OVF:
3978 // -- either this OVF was written by vbox 3.2 or later, in which case there is a <vbox:Machine> element
3979 // in the <VirtualSystem>; then the VirtualSystemDescription::Data has a settings::MachineConfigFile
3980 // with all the machine config pretty-parsed;
3981 // -- or this is an OVF from an older vbox or an external source, and then we need to translate the
3982 // VirtualSystemDescriptionEntry and do import work
3983
3984 // Even for the vbox:Machine case, there are a number of configuration items that will be taken from
3985 // the OVF because otherwise the "override import parameters" mechanism in the GUI won't work.
3986
3987 // VM name
3988 std::list<VirtualSystemDescriptionEntry*> vsdeName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
3989 if (vsdeName.size() < 1)
3990 throw setError(VBOX_E_FILE_ERROR,
3991 tr("Missing VM name"));
3992 stack.strNameVBox = vsdeName.front()->strVBoxCurrent;
3993
3994 // have VirtualBox suggest where the filename would be placed so we can
3995 // put the disk images in the same directory
3996 Bstr bstrMachineFilename;
3997 rc = mVirtualBox->ComposeMachineFilename(Bstr(stack.strNameVBox).raw(),
3998 NULL /* aGroup */,
3999 NULL /* aCreateFlags */,
4000 NULL /* aBaseFolder */,
4001 bstrMachineFilename.asOutParam());
4002 if (FAILED(rc)) throw rc;
4003 // and determine the machine folder from that
4004 stack.strMachineFolder = bstrMachineFilename;
4005 stack.strMachineFolder.stripFilename();
4006 LogFunc(("i=%zu strName=%s bstrMachineFilename=%ls\n", i, stack.strNameVBox.c_str(), bstrMachineFilename.raw()));
4007
4008 // guest OS type
4009 std::list<VirtualSystemDescriptionEntry*> vsdeOS;
4010 vsdeOS = vsdescThis->i_findByType(VirtualSystemDescriptionType_OS);
4011 if (vsdeOS.size() < 1)
4012 throw setError(VBOX_E_FILE_ERROR,
4013 tr("Missing guest OS type"));
4014 stack.strOsTypeVBox = vsdeOS.front()->strVBoxCurrent;
4015
4016 // CPU count
4017 std::list<VirtualSystemDescriptionEntry*> vsdeCPU = vsdescThis->i_findByType(VirtualSystemDescriptionType_CPU);
4018 if (vsdeCPU.size() != 1)
4019 throw setError(VBOX_E_FILE_ERROR, tr("CPU count missing"));
4020
4021 stack.cCPUs = vsdeCPU.front()->strVBoxCurrent.toUInt32();
4022 // We need HWVirt & IO-APIC if more than one CPU is requested
4023 if (stack.cCPUs > 1)
4024 {
4025 stack.fForceHWVirt = true;
4026 stack.fForceIOAPIC = true;
4027 }
4028
4029 // RAM
4030 std::list<VirtualSystemDescriptionEntry*> vsdeRAM = vsdescThis->i_findByType(VirtualSystemDescriptionType_Memory);
4031 if (vsdeRAM.size() != 1)
4032 throw setError(VBOX_E_FILE_ERROR, tr("RAM size missing"));
4033 stack.ulMemorySizeMB = (ULONG)vsdeRAM.front()->strVBoxCurrent.toUInt64();
4034
4035#ifdef VBOX_WITH_USB
4036 // USB controller
4037 std::list<VirtualSystemDescriptionEntry*> vsdeUSBController =
4038 vsdescThis->i_findByType(VirtualSystemDescriptionType_USBController);
4039 // USB support is enabled if there's at least one such entry; to disable USB support,
4040 // the type of the USB item would have been changed to "ignore"
4041 stack.fUSBEnabled = !vsdeUSBController.empty();
4042#endif
4043 // audio adapter
4044 std::list<VirtualSystemDescriptionEntry*> vsdeAudioAdapter =
4045 vsdescThis->i_findByType(VirtualSystemDescriptionType_SoundCard);
4046 /** @todo we support one audio adapter only */
4047 if (!vsdeAudioAdapter.empty())
4048 stack.strAudioAdapter = vsdeAudioAdapter.front()->strVBoxCurrent;
4049
4050 // for the description of the new machine, always use the OVF entry, the user may have changed it in the import config
4051 std::list<VirtualSystemDescriptionEntry*> vsdeDescription =
4052 vsdescThis->i_findByType(VirtualSystemDescriptionType_Description);
4053 if (!vsdeDescription.empty())
4054 stack.strDescription = vsdeDescription.front()->strVBoxCurrent;
4055
4056 // import vbox:machine or OVF now
4057 if (vsdescThis->m->pConfig)
4058 // vbox:Machine config
4059 i_importVBoxMachine(vsdescThis, pNewMachine, stack);
4060 else
4061 // generic OVF config
4062 i_importMachineGeneric(vsysThis, vsdescThis, pNewMachine, stack);
4063
4064 } // for (it = pAppliance->m->llVirtualSystems.begin() ...
4065}
4066
4067HRESULT Appliance::ImportStack::saveOriginalUUIDOfAttachedDevice(settings::AttachedDevice &device,
4068 const Utf8Str &newlyUuid)
4069{
4070 HRESULT rc = S_OK;
4071
4072 /* save for restoring */
4073 mapNewUUIDsToOriginalUUIDs.insert(std::make_pair(newlyUuid, device.uuid.toString()));
4074
4075 return rc;
4076}
4077
4078HRESULT Appliance::ImportStack::restoreOriginalUUIDOfAttachedDevice(settings::MachineConfigFile *config)
4079{
4080 HRESULT rc = S_OK;
4081
4082 settings::StorageControllersList &llControllers = config->hardwareMachine.storage.llStorageControllers;
4083 settings::StorageControllersList::iterator itscl;
4084 for (itscl = llControllers.begin();
4085 itscl != llControllers.end();
4086 ++itscl)
4087 {
4088 settings::AttachedDevicesList &llAttachments = itscl->llAttachedDevices;
4089 settings::AttachedDevicesList::iterator itadl = llAttachments.begin();
4090 while (itadl != llAttachments.end())
4091 {
4092 std::map<Utf8Str , Utf8Str>::iterator it =
4093 mapNewUUIDsToOriginalUUIDs.find(itadl->uuid.toString());
4094 if(it!=mapNewUUIDsToOriginalUUIDs.end())
4095 {
4096 Utf8Str uuidOriginal = it->second;
4097 itadl->uuid = Guid(uuidOriginal);
4098 mapNewUUIDsToOriginalUUIDs.erase(it->first);
4099 }
4100 ++itadl;
4101 }
4102 }
4103
4104 return rc;
4105}
4106
4107/**
4108 * @throws Nothing
4109 */
4110RTVFSIOSTREAM Appliance::ImportStack::claimOvaLookAHead(void)
4111{
4112 RTVFSIOSTREAM hVfsIos = this->hVfsIosOvaLookAhead;
4113 this->hVfsIosOvaLookAhead = NIL_RTVFSIOSTREAM;
4114 /* We don't free the name since it may be referenced in error messages and such. */
4115 return hVfsIos;
4116}
4117
Note: See TracBrowser for help on using the repository browser.

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