VirtualBox

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

Last change on this file since 72899 was 72525, checked in by vboxsync, 7 years ago

Main/Appliance: Since "stack" is reused for all VMs in a multi-appliance import, certain optional variables need clearing otherwise stale data is used. Caused import failures.

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

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