VirtualBox

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

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

ApplianceImplImport.cpp: Use RTCrPkixPubKeyVerifySignedDigest so we get double verification.

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

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