VirtualBox

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

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

Main/Appliance: Correctly handle appliance import when the VM is already in a group (i.e. created by VirtualBox, having <Machine> tag in the OVF file). Additionally use the usual root group notation instead of empty strings.

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

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