VirtualBox

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

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

Main/Appliance: Teach importing new tricks: importing to specific location (by settings file name or base folder) and also importing straight into a group. Lots of cleanup and minor fixing (bad code quality due to lots of copy/paste, and what's worse is that the original code was broken already, using the variables inconsistently), plus some smallish coding style cleaup. Much more needed. Also fixed the incomplete use of the VM name on expert (the one in the VBox XML was not changed, and it's the preferred name on import).
VBoxManage: small updates to reflect the new features (and actually offer setting the VM name on export, which is something the GUI could do for a long time).

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