VirtualBox

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

Last change on this file since 84472 was 84472, checked in by vboxsync, 5 years ago

ApplianceImplImport.cpp: r=bird @todo. bugref:9416

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 252.1 KB
Line 
1/* $Id: ApplianceImplImport.cpp 84472 2020-05-23 12:53:37Z vboxsync $ */
2/** @file
3 * IAppliance and IVirtualSystem COM class implementations.
4 */
5
6/*
7 * Copyright (C) 2008-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_MAIN_APPLIANCE
19#include <iprt/alloca.h>
20#include <iprt/path.h>
21#include <iprt/cpp/path.h>
22#include <iprt/dir.h>
23#include <iprt/file.h>
24#include <iprt/s3.h>
25#include <iprt/sha.h>
26#include <iprt/manifest.h>
27#include <iprt/tar.h>
28#include <iprt/zip.h>
29#include <iprt/stream.h>
30#include <iprt/crypto/digest.h>
31#include <iprt/crypto/pkix.h>
32#include <iprt/crypto/store.h>
33#include <iprt/crypto/x509.h>
34
35#include <VBox/vd.h>
36#include <VBox/com/array.h>
37
38#include "ApplianceImpl.h"
39#include "VirtualBoxImpl.h"
40#include "GuestOSTypeImpl.h"
41#include "ProgressImpl.h"
42#include "MachineImpl.h"
43#include "MediumImpl.h"
44#include "MediumFormatImpl.h"
45#include "SystemPropertiesImpl.h"
46#include "HostImpl.h"
47
48#include "AutoCaller.h"
49#include "LoggingNew.h"
50
51#include "ApplianceImplPrivate.h"
52#include "CertificateImpl.h"
53#include "ovfreader.h"
54
55#include <VBox/param.h>
56#include <VBox/version.h>
57#include <VBox/settings.h>
58
59#include <set>
60
61using namespace std;
62
63////////////////////////////////////////////////////////////////////////////////
64//
65// IAppliance public methods
66//
67////////////////////////////////////////////////////////////////////////////////
68
69/**
70 * Public method implementation. This opens the OVF with ovfreader.cpp.
71 * Thread implementation is in Appliance::readImpl().
72 *
73 * @param aFile File to read the appliance from.
74 * @param aProgress Progress object.
75 * @return
76 */
77HRESULT Appliance::read(const com::Utf8Str &aFile,
78 ComPtr<IProgress> &aProgress)
79{
80 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
81
82 if (!i_isApplianceIdle())
83 return E_ACCESSDENIED;
84
85 if (m->pReader)
86 {
87 delete m->pReader;
88 m->pReader = NULL;
89 }
90
91 /* Parse all necessary info out of the URI (please not how stupid utterly wasteful
92 this status & allocation error throwing is): */
93 try
94 {
95 i_parseURI(aFile, m->locInfo); /* may trhow rc. */
96 }
97 catch (HRESULT aRC)
98 {
99 return aRC;
100 }
101 catch (std::bad_alloc &)
102 {
103 return E_OUTOFMEMORY;
104 }
105
106 // see if we can handle this file; for now we insist it has an ovf/ova extension
107 if ( m->locInfo.storageType == VFSType_File
108 && !aFile.endsWith(".ovf", Utf8Str::CaseInsensitive)
109 && !aFile.endsWith(".ova", Utf8Str::CaseInsensitive))
110 return setError(VBOX_E_FILE_ERROR, tr("Appliance file must have .ovf or .ova extension"));
111
112 ComObjPtr<Progress> progress;
113 HRESULT hrc = i_readImpl(m->locInfo, progress);
114 if (SUCCEEDED(hrc))
115 progress.queryInterfaceTo(aProgress.asOutParam());
116 return hrc;
117}
118
119/**
120 * Public method implementation. This looks at the output of ovfreader.cpp and creates
121 * VirtualSystemDescription instances.
122 * @return
123 */
124HRESULT Appliance::interpret()
125{
126 /// @todo
127 // - don't use COM methods but the methods directly (faster, but needs appropriate
128 // locking of that objects itself (s. HardDisk))
129 // - Appropriate handle errors like not supported file formats
130 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
131
132 if (!i_isApplianceIdle())
133 return E_ACCESSDENIED;
134
135 HRESULT rc = S_OK;
136
137 /* Clear any previous virtual system descriptions */
138 m->virtualSystemDescriptions.clear();
139
140 if (m->locInfo.storageType == VFSType_File && !m->pReader)
141 return setError(E_FAIL,
142 tr("Cannot interpret appliance without reading it first (call read() before interpret())"));
143
144 // Change the appliance state so we can safely leave the lock while doing time-consuming
145 // medium imports; also the below method calls do all kinds of locking which conflicts with
146 // the appliance object lock
147 m->state = ApplianceImporting;
148 alock.release();
149
150 /* Try/catch so we can clean up on error */
151 try
152 {
153 list<ovf::VirtualSystem>::const_iterator it;
154 /* Iterate through all virtual systems */
155 for (it = m->pReader->m_llVirtualSystems.begin();
156 it != m->pReader->m_llVirtualSystems.end();
157 ++it)
158 {
159 const ovf::VirtualSystem &vsysThis = *it;
160
161 ComObjPtr<VirtualSystemDescription> pNewDesc;
162 rc = pNewDesc.createObject();
163 if (FAILED(rc)) throw rc;
164 rc = pNewDesc->init();
165 if (FAILED(rc)) throw rc;
166
167 // if the virtual system in OVF had a <vbox:Machine> element, have the
168 // VirtualBox settings code parse that XML now
169 if (vsysThis.pelmVBoxMachine)
170 pNewDesc->i_importVBoxMachineXML(*vsysThis.pelmVBoxMachine);
171
172 // Guest OS type
173 // This is taken from one of three places, in this order:
174 Utf8Str strOsTypeVBox;
175 Utf8StrFmt strCIMOSType("%RU32", (uint32_t)vsysThis.cimos);
176 // 1) If there is a <vbox:Machine>, then use the type from there.
177 if ( vsysThis.pelmVBoxMachine
178 && pNewDesc->m->pConfig->machineUserData.strOsType.isNotEmpty()
179 )
180 strOsTypeVBox = pNewDesc->m->pConfig->machineUserData.strOsType;
181 // 2) Otherwise, if there is OperatingSystemSection/vbox:OSType, use that one.
182 else if (vsysThis.strTypeVBox.isNotEmpty()) // OVFReader has found vbox:OSType
183 strOsTypeVBox = vsysThis.strTypeVBox;
184 // 3) Otherwise, make a best guess what the vbox type is from the OVF (CIM) OS type.
185 else
186 convertCIMOSType2VBoxOSType(strOsTypeVBox, vsysThis.cimos, vsysThis.strCimosDesc);
187 pNewDesc->i_addEntry(VirtualSystemDescriptionType_OS,
188 "",
189 strCIMOSType,
190 strOsTypeVBox);
191
192 /* VM name */
193 Utf8Str nameVBox;
194 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
195 if ( vsysThis.pelmVBoxMachine
196 && pNewDesc->m->pConfig->machineUserData.strName.isNotEmpty())
197 nameVBox = pNewDesc->m->pConfig->machineUserData.strName;
198 else
199 nameVBox = vsysThis.strName;
200 /* If there isn't any name specified create a default one out
201 * of the OS type */
202 if (nameVBox.isEmpty())
203 nameVBox = strOsTypeVBox;
204 i_searchUniqueVMName(nameVBox);
205 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Name,
206 "",
207 vsysThis.strName,
208 nameVBox);
209
210 /* VM Primary Group */
211 Utf8Str strPrimaryGroup;
212 if ( vsysThis.pelmVBoxMachine
213 && pNewDesc->m->pConfig->machineUserData.llGroups.size())
214 strPrimaryGroup = pNewDesc->m->pConfig->machineUserData.llGroups.front();
215 if (strPrimaryGroup.isEmpty())
216 strPrimaryGroup = "/";
217 pNewDesc->i_addEntry(VirtualSystemDescriptionType_PrimaryGroup,
218 "",
219 "" /* no direct OVF correspondence */,
220 strPrimaryGroup);
221
222 /* Based on the VM name, create a target machine path. */
223 Bstr bstrSettingsFilename;
224 rc = mVirtualBox->ComposeMachineFilename(Bstr(nameVBox).raw(),
225 Bstr(strPrimaryGroup).raw(),
226 NULL /* aCreateFlags */,
227 NULL /* aBaseFolder */,
228 bstrSettingsFilename.asOutParam());
229 if (FAILED(rc)) throw rc;
230 Utf8Str strMachineFolder(bstrSettingsFilename);
231 strMachineFolder.stripFilename();
232
233#if 1
234 /* The import logic should work exactly the same whether the
235 * following 2 items are present or not, but of course it may have
236 * an influence on the exact presentation of the import settings
237 * of an API client. */
238 Utf8Str strSettingsFilename(bstrSettingsFilename);
239 pNewDesc->i_addEntry(VirtualSystemDescriptionType_SettingsFile,
240 "",
241 "" /* no direct OVF correspondence */,
242 strSettingsFilename);
243 Utf8Str strBaseFolder;
244 mVirtualBox->i_getDefaultMachineFolder(strBaseFolder);
245 pNewDesc->i_addEntry(VirtualSystemDescriptionType_BaseFolder,
246 "",
247 "" /* no direct OVF correspondence */,
248 strBaseFolder);
249#endif
250
251 /* VM Product */
252 if (!vsysThis.strProduct.isEmpty())
253 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Product,
254 "",
255 vsysThis.strProduct,
256 vsysThis.strProduct);
257
258 /* VM Vendor */
259 if (!vsysThis.strVendor.isEmpty())
260 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Vendor,
261 "",
262 vsysThis.strVendor,
263 vsysThis.strVendor);
264
265 /* VM Version */
266 if (!vsysThis.strVersion.isEmpty())
267 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Version,
268 "",
269 vsysThis.strVersion,
270 vsysThis.strVersion);
271
272 /* VM ProductUrl */
273 if (!vsysThis.strProductUrl.isEmpty())
274 pNewDesc->i_addEntry(VirtualSystemDescriptionType_ProductUrl,
275 "",
276 vsysThis.strProductUrl,
277 vsysThis.strProductUrl);
278
279 /* VM VendorUrl */
280 if (!vsysThis.strVendorUrl.isEmpty())
281 pNewDesc->i_addEntry(VirtualSystemDescriptionType_VendorUrl,
282 "",
283 vsysThis.strVendorUrl,
284 vsysThis.strVendorUrl);
285
286 /* VM description */
287 if (!vsysThis.strDescription.isEmpty())
288 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Description,
289 "",
290 vsysThis.strDescription,
291 vsysThis.strDescription);
292
293 /* VM license */
294 if (!vsysThis.strLicenseText.isEmpty())
295 pNewDesc->i_addEntry(VirtualSystemDescriptionType_License,
296 "",
297 vsysThis.strLicenseText,
298 vsysThis.strLicenseText);
299
300 /* Now that we know the OS type, get our internal defaults based on
301 * that, if it is known (otherwise pGuestOSType will be NULL). */
302 ComPtr<IGuestOSType> pGuestOSType;
303 mVirtualBox->GetGuestOSType(Bstr(strOsTypeVBox).raw(), pGuestOSType.asOutParam());
304
305 /* CPU count */
306 ULONG cpuCountVBox;
307 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
308 if ( vsysThis.pelmVBoxMachine
309 && pNewDesc->m->pConfig->hardwareMachine.cCPUs)
310 cpuCountVBox = pNewDesc->m->pConfig->hardwareMachine.cCPUs;
311 else
312 cpuCountVBox = vsysThis.cCPUs;
313 /* Check for the constraints */
314 if (cpuCountVBox > SchemaDefs::MaxCPUCount)
315 {
316 i_addWarning(tr("The virtual system \"%s\" claims support for %u CPU's, but VirtualBox has support for "
317 "max %u CPU's only."),
318 vsysThis.strName.c_str(), cpuCountVBox, SchemaDefs::MaxCPUCount);
319 cpuCountVBox = SchemaDefs::MaxCPUCount;
320 }
321 if (vsysThis.cCPUs == 0)
322 cpuCountVBox = 1;
323 pNewDesc->i_addEntry(VirtualSystemDescriptionType_CPU,
324 "",
325 Utf8StrFmt("%RU32", (uint32_t)vsysThis.cCPUs),
326 Utf8StrFmt("%RU32", (uint32_t)cpuCountVBox));
327
328 /* RAM */
329 uint64_t ullMemSizeVBox;
330 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
331 if ( vsysThis.pelmVBoxMachine
332 && pNewDesc->m->pConfig->hardwareMachine.ulMemorySizeMB)
333 ullMemSizeVBox = pNewDesc->m->pConfig->hardwareMachine.ulMemorySizeMB;
334 else
335 ullMemSizeVBox = vsysThis.ullMemorySize / _1M;
336 /* Check for the constraints */
337 if ( ullMemSizeVBox != 0
338 && ( ullMemSizeVBox < MM_RAM_MIN_IN_MB
339 || ullMemSizeVBox > MM_RAM_MAX_IN_MB
340 )
341 )
342 {
343 i_addWarning(tr("The virtual system \"%s\" claims support for %llu MB RAM size, but VirtualBox has "
344 "support for min %u & max %u MB RAM size only."),
345 vsysThis.strName.c_str(), ullMemSizeVBox, MM_RAM_MIN_IN_MB, MM_RAM_MAX_IN_MB);
346 ullMemSizeVBox = RT_MIN(RT_MAX(ullMemSizeVBox, MM_RAM_MIN_IN_MB), MM_RAM_MAX_IN_MB);
347 }
348 if (vsysThis.ullMemorySize == 0)
349 {
350 /* If the RAM of the OVF is zero, use our predefined values */
351 ULONG memSizeVBox2;
352 if (!pGuestOSType.isNull())
353 {
354 rc = pGuestOSType->COMGETTER(RecommendedRAM)(&memSizeVBox2);
355 if (FAILED(rc)) throw rc;
356 }
357 else
358 memSizeVBox2 = 1024;
359 /* VBox stores that in MByte */
360 ullMemSizeVBox = (uint64_t)memSizeVBox2;
361 }
362 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Memory,
363 "",
364 Utf8StrFmt("%RU64", (uint64_t)vsysThis.ullMemorySize),
365 Utf8StrFmt("%RU64", (uint64_t)ullMemSizeVBox));
366
367 /* Audio */
368 Utf8Str strSoundCard;
369 Utf8Str strSoundCardOrig;
370 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
371 if ( vsysThis.pelmVBoxMachine
372 && pNewDesc->m->pConfig->hardwareMachine.audioAdapter.fEnabled)
373 {
374 strSoundCard = Utf8StrFmt("%RU32",
375 (uint32_t)pNewDesc->m->pConfig->hardwareMachine.audioAdapter.controllerType);
376 }
377 else if (vsysThis.strSoundCardType.isNotEmpty())
378 {
379 /* Set the AC97 always for the simple OVF case.
380 * @todo: figure out the hardware which could be possible */
381 strSoundCard = Utf8StrFmt("%RU32", (uint32_t)AudioControllerType_AC97);
382 strSoundCardOrig = vsysThis.strSoundCardType;
383 }
384 if (strSoundCard.isNotEmpty())
385 pNewDesc->i_addEntry(VirtualSystemDescriptionType_SoundCard,
386 "",
387 strSoundCardOrig,
388 strSoundCard);
389
390#ifdef VBOX_WITH_USB
391 /* USB Controller */
392 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
393 if ( ( vsysThis.pelmVBoxMachine
394 && pNewDesc->m->pConfig->hardwareMachine.usbSettings.llUSBControllers.size() > 0)
395 || vsysThis.fHasUsbController)
396 pNewDesc->i_addEntry(VirtualSystemDescriptionType_USBController, "", "", "");
397#endif /* VBOX_WITH_USB */
398
399 /* Network Controller */
400 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
401 if (vsysThis.pelmVBoxMachine)
402 {
403 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(pNewDesc->m->pConfig->hardwareMachine.chipsetType);
404
405 const settings::NetworkAdaptersList &llNetworkAdapters = pNewDesc->m->pConfig->hardwareMachine.llNetworkAdapters;
406 /* Check for the constrains */
407 if (llNetworkAdapters.size() > maxNetworkAdapters)
408 i_addWarning(tr("The virtual system \"%s\" claims support for %zu network adapters, but VirtualBox "
409 "has support for max %u network adapter only."),
410 vsysThis.strName.c_str(), llNetworkAdapters.size(), maxNetworkAdapters);
411 /* Iterate through all network adapters. */
412 settings::NetworkAdaptersList::const_iterator it1;
413 size_t a = 0;
414 for (it1 = llNetworkAdapters.begin();
415 it1 != llNetworkAdapters.end() && a < maxNetworkAdapters;
416 ++it1, ++a)
417 {
418 if (it1->fEnabled)
419 {
420 Utf8Str strMode = convertNetworkAttachmentTypeToString(it1->mode);
421 pNewDesc->i_addEntry(VirtualSystemDescriptionType_NetworkAdapter,
422 "", // ref
423 strMode, // orig
424 Utf8StrFmt("%RU32", (uint32_t)it1->type), // conf
425 0,
426 Utf8StrFmt("slot=%RU32;type=%s", it1->ulSlot, strMode.c_str())); // extra conf
427 }
428 }
429 }
430 /* else we use the ovf configuration. */
431 else if (vsysThis.llEthernetAdapters.size() > 0)
432 {
433 size_t cEthernetAdapters = vsysThis.llEthernetAdapters.size();
434 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
435
436 /* Check for the constrains */
437 if (cEthernetAdapters > maxNetworkAdapters)
438 i_addWarning(tr("The virtual system \"%s\" claims support for %zu network adapters, but VirtualBox "
439 "has support for max %u network adapter only."),
440 vsysThis.strName.c_str(), cEthernetAdapters, maxNetworkAdapters);
441
442 /* Get the default network adapter type for the selected guest OS */
443 NetworkAdapterType_T defaultAdapterVBox = NetworkAdapterType_Am79C970A;
444 if (!pGuestOSType.isNull())
445 {
446 rc = pGuestOSType->COMGETTER(AdapterType)(&defaultAdapterVBox);
447 if (FAILED(rc)) throw rc;
448 }
449 else
450 {
451#ifdef VBOX_WITH_E1000
452 defaultAdapterVBox = NetworkAdapterType_I82540EM;
453#else
454 defaultAdapterVBox = NetworkAdapterType_Am79C973A;
455#endif
456 }
457
458 ovf::EthernetAdaptersList::const_iterator itEA;
459 /* Iterate through all abstract networks. Ignore network cards
460 * which exceed the limit of VirtualBox. */
461 size_t a = 0;
462 for (itEA = vsysThis.llEthernetAdapters.begin();
463 itEA != vsysThis.llEthernetAdapters.end() && a < maxNetworkAdapters;
464 ++itEA, ++a)
465 {
466 const ovf::EthernetAdapter &ea = *itEA; // logical network to connect to
467 Utf8Str strNetwork = ea.strNetworkName;
468 // make sure it's one of these two
469 if ( (strNetwork.compare("Null", Utf8Str::CaseInsensitive))
470 && (strNetwork.compare("NAT", Utf8Str::CaseInsensitive))
471 && (strNetwork.compare("Bridged", Utf8Str::CaseInsensitive))
472 && (strNetwork.compare("Internal", Utf8Str::CaseInsensitive))
473 && (strNetwork.compare("HostOnly", Utf8Str::CaseInsensitive))
474 && (strNetwork.compare("Generic", Utf8Str::CaseInsensitive))
475 )
476 strNetwork = "Bridged"; // VMware assumes this is the default apparently
477
478 /* Figure out the hardware type */
479 NetworkAdapterType_T nwAdapterVBox = defaultAdapterVBox;
480 if (!ea.strAdapterType.compare("PCNet32", Utf8Str::CaseInsensitive))
481 {
482 /* If the default adapter is already one of the two
483 * PCNet adapters use the default one. If not use the
484 * Am79C970A as fallback. */
485 if (!(defaultAdapterVBox == NetworkAdapterType_Am79C970A ||
486 defaultAdapterVBox == NetworkAdapterType_Am79C973))
487 nwAdapterVBox = NetworkAdapterType_Am79C970A;
488 }
489#ifdef VBOX_WITH_E1000
490 /* VMWare accidentally write this with VirtualCenter 3.5,
491 so make sure in this case always to use the VMWare one */
492 else if (!ea.strAdapterType.compare("E10000", Utf8Str::CaseInsensitive))
493 nwAdapterVBox = NetworkAdapterType_I82545EM;
494 else if (!ea.strAdapterType.compare("E1000", Utf8Str::CaseInsensitive))
495 {
496 /* Check if this OVF was written by VirtualBox */
497 if (Utf8Str(vsysThis.strVirtualSystemType).contains("virtualbox", Utf8Str::CaseInsensitive))
498 {
499 /* If the default adapter is already one of the three
500 * E1000 adapters use the default one. If not use the
501 * I82545EM as fallback. */
502 if (!(defaultAdapterVBox == NetworkAdapterType_I82540EM ||
503 defaultAdapterVBox == NetworkAdapterType_I82543GC ||
504 defaultAdapterVBox == NetworkAdapterType_I82545EM))
505 nwAdapterVBox = NetworkAdapterType_I82540EM;
506 }
507 else
508 /* Always use this one since it's what VMware uses */
509 nwAdapterVBox = NetworkAdapterType_I82545EM;
510 }
511#endif /* VBOX_WITH_E1000 */
512
513 pNewDesc->i_addEntry(VirtualSystemDescriptionType_NetworkAdapter,
514 "", // ref
515 ea.strNetworkName, // orig
516 Utf8StrFmt("%RU32", (uint32_t)nwAdapterVBox), // conf
517 0,
518 Utf8StrFmt("type=%s", strNetwork.c_str())); // extra conf
519 }
520 }
521
522 /* If there is a <vbox:Machine>, we always prefer the setting from there. */
523 bool fFloppy = false;
524 bool fDVD = false;
525 if (vsysThis.pelmVBoxMachine)
526 {
527 settings::StorageControllersList &llControllers = pNewDesc->m->pConfig->hardwareMachine.storage.llStorageControllers;
528 settings::StorageControllersList::iterator it3;
529 for (it3 = llControllers.begin();
530 it3 != llControllers.end();
531 ++it3)
532 {
533 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
534 settings::AttachedDevicesList::iterator it4;
535 for (it4 = llAttachments.begin();
536 it4 != llAttachments.end();
537 ++it4)
538 {
539 fDVD |= it4->deviceType == DeviceType_DVD;
540 fFloppy |= it4->deviceType == DeviceType_Floppy;
541 if (fFloppy && fDVD)
542 break;
543 }
544 if (fFloppy && fDVD)
545 break;
546 }
547 }
548 else
549 {
550 fFloppy = vsysThis.fHasFloppyDrive;
551 fDVD = vsysThis.fHasCdromDrive;
552 }
553 /* Floppy Drive */
554 if (fFloppy)
555 pNewDesc->i_addEntry(VirtualSystemDescriptionType_Floppy, "", "", "");
556 /* CD Drive */
557 if (fDVD)
558 pNewDesc->i_addEntry(VirtualSystemDescriptionType_CDROM, "", "", "");
559
560 /* Storage Controller */
561 uint16_t cIDEused = 0;
562 uint16_t cSATAused = 0; NOREF(cSATAused);
563 uint16_t cSCSIused = 0; NOREF(cSCSIused);
564 ovf::ControllersMap::const_iterator hdcIt;
565 /* Iterate through all storage controllers */
566 for (hdcIt = vsysThis.mapControllers.begin();
567 hdcIt != vsysThis.mapControllers.end();
568 ++hdcIt)
569 {
570 const ovf::HardDiskController &hdc = hdcIt->second;
571 Utf8Str strControllerID = Utf8StrFmt("%RI32", (uint32_t)hdc.idController);
572
573 switch (hdc.system)
574 {
575 case ovf::HardDiskController::IDE:
576 /* Check for the constrains */
577 if (cIDEused < 4)
578 {
579 /// @todo figure out the IDE types
580 /* Use PIIX4 as default */
581 Utf8Str strType = "PIIX4";
582 if (!hdc.strControllerType.compare("PIIX3", Utf8Str::CaseInsensitive))
583 strType = "PIIX3";
584 else if (!hdc.strControllerType.compare("ICH6", Utf8Str::CaseInsensitive))
585 strType = "ICH6";
586 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
587 strControllerID, // strRef
588 hdc.strControllerType, // aOvfValue
589 strType); // aVBoxValue
590 }
591 else
592 /* Warn only once */
593 if (cIDEused == 2)
594 i_addWarning(tr("The virtual \"%s\" system requests support for more than two "
595 "IDE controller channels, but VirtualBox supports only two."),
596 vsysThis.strName.c_str());
597
598 ++cIDEused;
599 break;
600
601 case ovf::HardDiskController::SATA:
602 /* Check for the constrains */
603 if (cSATAused < 1)
604 {
605 /// @todo figure out the SATA types
606 /* We only support a plain AHCI controller, so use them always */
607 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA,
608 strControllerID,
609 hdc.strControllerType,
610 "AHCI");
611 }
612 else
613 {
614 /* Warn only once */
615 if (cSATAused == 1)
616 i_addWarning(tr("The virtual system \"%s\" requests support for more than one "
617 "SATA controller, but VirtualBox has support for only one"),
618 vsysThis.strName.c_str());
619
620 }
621 ++cSATAused;
622 break;
623
624 case ovf::HardDiskController::SCSI:
625 /* Check for the constrains */
626 if (cSCSIused < 1)
627 {
628 VirtualSystemDescriptionType_T vsdet = VirtualSystemDescriptionType_HardDiskControllerSCSI;
629 Utf8Str hdcController = "LsiLogic";
630 if (!hdc.strControllerType.compare("lsilogicsas", Utf8Str::CaseInsensitive))
631 {
632 // OVF considers SAS a variant of SCSI but VirtualBox considers it a class of its own
633 vsdet = VirtualSystemDescriptionType_HardDiskControllerSAS;
634 hdcController = "LsiLogicSas";
635 }
636 else if (!hdc.strControllerType.compare("BusLogic", Utf8Str::CaseInsensitive))
637 hdcController = "BusLogic";
638 pNewDesc->i_addEntry(vsdet,
639 strControllerID,
640 hdc.strControllerType,
641 hdcController);
642 }
643 else
644 i_addWarning(tr("The virtual system \"%s\" requests support for an additional "
645 "SCSI controller of type \"%s\" with ID %s, but VirtualBox presently "
646 "supports only one SCSI controller."),
647 vsysThis.strName.c_str(),
648 hdc.strControllerType.c_str(),
649 strControllerID.c_str());
650 ++cSCSIused;
651 break;
652 }
653 }
654
655 /* Storage devices (hard disks/DVDs/...) */
656 if (vsysThis.mapVirtualDisks.size() > 0)
657 {
658 ovf::VirtualDisksMap::const_iterator itVD;
659 /* Iterate through all storage devices */
660 for (itVD = vsysThis.mapVirtualDisks.begin();
661 itVD != vsysThis.mapVirtualDisks.end();
662 ++itVD)
663 {
664 const ovf::VirtualDisk &hd = itVD->second;
665 /* Get the associated image */
666 ovf::DiskImage di;
667 std::map<RTCString, ovf::DiskImage>::iterator foundDisk;
668
669 foundDisk = m->pReader->m_mapDisks.find(hd.strDiskId);
670 if (foundDisk == m->pReader->m_mapDisks.end())
671 continue;
672 else
673 {
674 di = foundDisk->second;
675 }
676
677 /*
678 * Figure out from URI which format the image has.
679 * There is no strict mapping of image URI to image format.
680 * It's possible we aren't able to recognize some URIs.
681 */
682
683 ComObjPtr<MediumFormat> mediumFormat;
684 rc = i_findMediumFormatFromDiskImage(di, mediumFormat);
685 if (FAILED(rc))
686 throw rc;
687
688 Bstr bstrFormatName;
689 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
690 if (FAILED(rc))
691 throw rc;
692 Utf8Str vdf = Utf8Str(bstrFormatName);
693
694 /// @todo
695 // - figure out all possible vmdk formats we also support
696 // - figure out if there is a url specifier for vhd already
697 // - we need a url specifier for the vdi format
698
699 Utf8Str strFilename = di.strHref;
700 DeviceType_T devType = DeviceType_Null;
701 if (vdf.compare("VMDK", Utf8Str::CaseInsensitive) == 0)
702 {
703 /* If the href is empty use the VM name as filename */
704 if (!strFilename.length())
705 strFilename = Utf8StrFmt("%s.vmdk", hd.strDiskId.c_str());
706 devType = DeviceType_HardDisk;
707 }
708 else if (vdf.compare("RAW", Utf8Str::CaseInsensitive) == 0)
709 {
710 /* If the href is empty use the VM name as filename */
711 if (!strFilename.length())
712 strFilename = Utf8StrFmt("%s.iso", hd.strDiskId.c_str());
713 devType = DeviceType_DVD;
714 }
715 else
716 throw setError(VBOX_E_FILE_ERROR,
717 tr("Unsupported format for virtual disk image %s in OVF: \"%s\""),
718 di.strHref.c_str(),
719 di.strFormat.c_str());
720
721 /*
722 * Remove last extension from the file name if the file is compressed
723 */
724 if (di.strCompression.compare("gzip", Utf8Str::CaseInsensitive)==0)
725 strFilename.stripSuffix();
726
727 i_searchUniqueImageFilePath(strMachineFolder, devType, strFilename); /** @todo check the return code! */
728
729 /* find the description for the storage controller
730 * that has the same ID as hd.idController */
731 const VirtualSystemDescriptionEntry *pController;
732 if (!(pController = pNewDesc->i_findControllerFromID(hd.idController)))
733 throw setError(E_FAIL,
734 tr("Cannot find storage controller with OVF instance ID %RI32 "
735 "to which medium \"%s\" should be attached"),
736 hd.idController,
737 di.strHref.c_str());
738
739 /* controller to attach to, and the bus within that controller */
740 Utf8StrFmt strExtraConfig("controller=%RI16;channel=%RI16",
741 pController->ulIndex,
742 hd.ulAddressOnParent);
743 pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
744 hd.strDiskId,
745 di.strHref,
746 strFilename,
747 di.ulSuggestedSizeMB,
748 strExtraConfig);
749 }
750 }
751
752 m->virtualSystemDescriptions.push_back(pNewDesc);
753 }
754 }
755 catch (HRESULT aRC)
756 {
757 /* On error we clear the list & return */
758 m->virtualSystemDescriptions.clear();
759 rc = aRC;
760 }
761
762 // reset the appliance state
763 alock.acquire();
764 m->state = ApplianceIdle;
765
766 return rc;
767}
768
769/**
770 * Public method implementation. This creates one or more new machines according to the
771 * VirtualSystemScription instances created by Appliance::Interpret().
772 * Thread implementation is in Appliance::i_importImpl().
773 * @param aOptions Import options.
774 * @param aProgress Progress object.
775 * @return
776 */
777HRESULT Appliance::importMachines(const std::vector<ImportOptions_T> &aOptions,
778 ComPtr<IProgress> &aProgress)
779{
780 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
781
782 if (aOptions.size())
783 {
784 try
785 {
786 m->optListImport.setCapacity(aOptions.size());
787 for (size_t i = 0; i < aOptions.size(); ++i)
788 m->optListImport.insert(i, aOptions[i]);
789 }
790 catch (std::bad_alloc &)
791 {
792 return E_OUTOFMEMORY;
793 }
794 }
795
796 AssertReturn(!( m->optListImport.contains(ImportOptions_KeepAllMACs)
797 && m->optListImport.contains(ImportOptions_KeepNATMACs) )
798 , E_INVALIDARG);
799
800 // do not allow entering this method if the appliance is busy reading or writing
801 if (!i_isApplianceIdle())
802 return E_ACCESSDENIED;
803
804 //check for the local import only. For import from the Cloud m->pReader is always NULL.
805 if (m->locInfo.storageType == VFSType_File && !m->pReader)
806 return setError(E_FAIL,
807 tr("Cannot import machines without reading it first (call read() before i_importMachines())"));
808
809 ComObjPtr<Progress> progress;
810 HRESULT hrc = i_importImpl(m->locInfo, progress);
811 if (SUCCEEDED(hrc))
812 progress.queryInterfaceTo(aProgress.asOutParam());
813
814 return hrc;
815}
816
817////////////////////////////////////////////////////////////////////////////////
818//
819// Appliance private methods
820//
821////////////////////////////////////////////////////////////////////////////////
822
823/**
824 * Ensures that there is a look-ahead object ready.
825 *
826 * @returns true if there's an object handy, false if end-of-stream.
827 * @throws HRESULT if the next object isn't a regular file. Sets error info
828 * (which is why it's a method on Appliance and not the
829 * ImportStack).
830 */
831bool Appliance::i_importEnsureOvaLookAhead(ImportStack &stack)
832{
833 Assert(stack.hVfsFssOva != NULL);
834 if (stack.hVfsIosOvaLookAhead == NIL_RTVFSIOSTREAM)
835 {
836 RTStrFree(stack.pszOvaLookAheadName);
837 stack.pszOvaLookAheadName = NULL;
838
839 RTVFSOBJTYPE enmType;
840 RTVFSOBJ hVfsObj;
841 int vrc = RTVfsFsStrmNext(stack.hVfsFssOva, &stack.pszOvaLookAheadName, &enmType, &hVfsObj);
842 if (RT_SUCCESS(vrc))
843 {
844 stack.hVfsIosOvaLookAhead = RTVfsObjToIoStream(hVfsObj);
845 RTVfsObjRelease(hVfsObj);
846 if ( ( enmType != RTVFSOBJTYPE_FILE
847 && enmType != RTVFSOBJTYPE_IO_STREAM)
848 || stack.hVfsIosOvaLookAhead == NIL_RTVFSIOSTREAM)
849 throw setError(VBOX_E_FILE_ERROR,
850 tr("Malformed OVA. '%s' is not a regular file (%d)."), stack.pszOvaLookAheadName, enmType);
851 }
852 else if (vrc == VERR_EOF)
853 return false;
854 else
855 throw setErrorVrc(vrc, tr("RTVfsFsStrmNext failed (%Rrc)"), vrc);
856 }
857 return true;
858}
859
860HRESULT Appliance::i_preCheckImageAvailability(ImportStack &stack)
861{
862 if (i_importEnsureOvaLookAhead(stack))
863 return S_OK;
864 throw setError(VBOX_E_FILE_ERROR, tr("Unexpected end of OVA package"));
865 /** @todo r=bird: dunno why this bother returning a value and the caller
866 * having a special 'continue' case for it. It always threw all non-OK
867 * status codes. It's possibly to handle out of order stuff, so that
868 * needs adding to the testcase! */
869}
870
871/**
872 * Opens a source file (for reading obviously).
873 *
874 * @param stack
875 * @param rstrSrcPath The source file to open.
876 * @param pszManifestEntry The manifest entry of the source file. This is
877 * used when constructing our manifest using a pass
878 * thru.
879 * @returns I/O stream handle to the source file.
880 * @throws HRESULT error status, error info set.
881 */
882RTVFSIOSTREAM Appliance::i_importOpenSourceFile(ImportStack &stack, Utf8Str const &rstrSrcPath, const char *pszManifestEntry)
883{
884 /*
885 * Open the source file. Special considerations for OVAs.
886 */
887 RTVFSIOSTREAM hVfsIosSrc;
888 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
889 {
890 for (uint32_t i = 0;; i++)
891 {
892 if (!i_importEnsureOvaLookAhead(stack))
893 throw setErrorBoth(VBOX_E_FILE_ERROR, VERR_EOF,
894 tr("Unexpected end of OVA / internal error - missing '%s' (skipped %u)"),
895 rstrSrcPath.c_str(), i);
896 if (RTStrICmp(stack.pszOvaLookAheadName, rstrSrcPath.c_str()) == 0)
897 break;
898
899 /* release the current object, loop to get the next. */
900 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
901 }
902 hVfsIosSrc = stack.claimOvaLookAHead();
903 }
904 else
905 {
906 int vrc = RTVfsIoStrmOpenNormal(rstrSrcPath.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosSrc);
907 if (RT_FAILURE(vrc))
908 throw setErrorVrc(vrc, tr("Error opening '%s' for reading (%Rrc)"), rstrSrcPath.c_str(), vrc);
909 }
910
911 /*
912 * Digest calculation filtering.
913 */
914 hVfsIosSrc = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosSrc, pszManifestEntry);
915 if (hVfsIosSrc == NIL_RTVFSIOSTREAM)
916 throw E_FAIL;
917
918 return hVfsIosSrc;
919}
920
921/**
922 * Creates the destination file and fills it with bytes from the source stream.
923 *
924 * This assumes that we digest the source when fDigestTypes is non-zero, and
925 * thus calls RTManifestPtIosAddEntryNow when done.
926 *
927 * @param rstrDstPath The path to the destination file. Missing path
928 * components will be created.
929 * @param hVfsIosSrc The source I/O stream.
930 * @param rstrSrcLogNm The name of the source for logging and error
931 * messages.
932 * @returns COM status code.
933 * @throws Nothing (as the caller has VFS handles to release).
934 */
935HRESULT Appliance::i_importCreateAndWriteDestinationFile(Utf8Str const &rstrDstPath, RTVFSIOSTREAM hVfsIosSrc,
936 Utf8Str const &rstrSrcLogNm)
937{
938 int vrc;
939
940 /*
941 * Create the output file, including necessary paths.
942 * Any existing file will be overwritten.
943 */
944 HRESULT hrc = VirtualBox::i_ensureFilePathExists(rstrDstPath, true /*fCreate*/);
945 if (SUCCEEDED(hrc))
946 {
947 RTVFSIOSTREAM hVfsIosDst;
948 vrc = RTVfsIoStrmOpenNormal(rstrDstPath.c_str(),
949 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL,
950 &hVfsIosDst);
951 if (RT_SUCCESS(vrc))
952 {
953 /*
954 * Pump the bytes thru. If we fail, delete the output file.
955 */
956 vrc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
957 if (RT_SUCCESS(vrc))
958 hrc = S_OK;
959 else
960 hrc = setErrorVrc(vrc, tr("Error occured decompressing '%s' to '%s' (%Rrc)"),
961 rstrSrcLogNm.c_str(), rstrDstPath.c_str(), vrc);
962 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosDst);
963 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
964 if (RT_FAILURE(vrc))
965 RTFileDelete(rstrDstPath.c_str());
966 }
967 else
968 hrc = setErrorVrc(vrc, tr("Error opening destionation image '%s' for writing (%Rrc)"), rstrDstPath.c_str(), vrc);
969 }
970 return hrc;
971}
972
973
974/**
975 *
976 * @param stack Import stack.
977 * @param rstrSrcPath Source path.
978 * @param rstrDstPath Destination path.
979 * @param pszManifestEntry The manifest entry of the source file. This is
980 * used when constructing our manifest using a pass
981 * thru.
982 * @throws HRESULT error status, error info set.
983 */
984void Appliance::i_importCopyFile(ImportStack &stack, Utf8Str const &rstrSrcPath, Utf8Str const &rstrDstPath,
985 const char *pszManifestEntry)
986{
987 /*
988 * Open the file (throws error) and add a read ahead thread so we can do
989 * concurrent reads (+digest) and writes.
990 */
991 RTVFSIOSTREAM hVfsIosSrc = i_importOpenSourceFile(stack, rstrSrcPath, pszManifestEntry);
992 RTVFSIOSTREAM hVfsIosReadAhead;
993 int vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/, 0 /*cbBuffers=default*/,
994 &hVfsIosReadAhead);
995 if (RT_FAILURE(vrc))
996 {
997 RTVfsIoStrmRelease(hVfsIosSrc);
998 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
999 }
1000
1001 /*
1002 * Write the destination file (nothrow).
1003 */
1004 HRESULT hrc = i_importCreateAndWriteDestinationFile(rstrDstPath, hVfsIosReadAhead, rstrSrcPath);
1005 RTVfsIoStrmRelease(hVfsIosReadAhead);
1006
1007 /*
1008 * Before releasing the source stream, make sure we've successfully added
1009 * the digest to our manifest.
1010 */
1011 if (SUCCEEDED(hrc) && m->fDigestTypes)
1012 {
1013 vrc = RTManifestPtIosAddEntryNow(hVfsIosSrc);
1014 if (RT_FAILURE(vrc))
1015 hrc = setErrorVrc(vrc, tr("RTManifestPtIosAddEntryNow failed with %Rrc"), vrc);
1016 }
1017
1018 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosSrc);
1019 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
1020 if (SUCCEEDED(hrc))
1021 return;
1022 throw hrc;
1023}
1024
1025/**
1026 *
1027 * @param stack
1028 * @param rstrSrcPath
1029 * @param rstrDstPath
1030 * @param pszManifestEntry The manifest entry of the source file. This is
1031 * used when constructing our manifest using a pass
1032 * thru.
1033 * @throws HRESULT error status, error info set.
1034 */
1035void Appliance::i_importDecompressFile(ImportStack &stack, Utf8Str const &rstrSrcPath, Utf8Str const &rstrDstPath,
1036 const char *pszManifestEntry)
1037{
1038 RTVFSIOSTREAM hVfsIosSrcCompressed = i_importOpenSourceFile(stack, rstrSrcPath, pszManifestEntry);
1039
1040 /*
1041 * Add a read ahead thread here. This means reading and digest calculation
1042 * is done on one thread, while unpacking and writing is one on this thread.
1043 */
1044 RTVFSIOSTREAM hVfsIosReadAhead;
1045 int vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrcCompressed, 0 /*fFlags*/, 0 /*cBuffers=default*/,
1046 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
1047 if (RT_FAILURE(vrc))
1048 {
1049 RTVfsIoStrmRelease(hVfsIosSrcCompressed);
1050 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
1051 }
1052
1053 /*
1054 * Add decompression step.
1055 */
1056 RTVFSIOSTREAM hVfsIosSrc;
1057 vrc = RTZipGzipDecompressIoStream(hVfsIosReadAhead, 0, &hVfsIosSrc);
1058 RTVfsIoStrmRelease(hVfsIosReadAhead);
1059 if (RT_FAILURE(vrc))
1060 {
1061 RTVfsIoStrmRelease(hVfsIosSrcCompressed);
1062 throw setErrorVrc(vrc, tr("Error initializing gzip decompression for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
1063 }
1064
1065 /*
1066 * Write the stream to the destination file (nothrow).
1067 */
1068 HRESULT hrc = i_importCreateAndWriteDestinationFile(rstrDstPath, hVfsIosSrc, rstrSrcPath);
1069
1070 /*
1071 * Before releasing the source stream, make sure we've successfully added
1072 * the digest to our manifest.
1073 */
1074 if (SUCCEEDED(hrc) && m->fDigestTypes)
1075 {
1076 vrc = RTManifestPtIosAddEntryNow(hVfsIosSrcCompressed);
1077 if (RT_FAILURE(vrc))
1078 hrc = setErrorVrc(vrc, tr("RTManifestPtIosAddEntryNow failed with %Rrc"), vrc);
1079 }
1080
1081 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosSrc);
1082 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
1083
1084 cRefs = RTVfsIoStrmRelease(hVfsIosSrcCompressed);
1085 AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
1086
1087 if (SUCCEEDED(hrc))
1088 return;
1089 throw hrc;
1090}
1091
1092/*******************************************************************************
1093 * Read stuff
1094 ******************************************************************************/
1095
1096/**
1097 * Implementation for reading an OVF (via task).
1098 *
1099 * This starts a new thread which will call
1100 * Appliance::taskThreadImportOrExport() which will then call readFS(). This
1101 * will then open the OVF with ovfreader.cpp.
1102 *
1103 * This is in a separate private method because it is used from two locations:
1104 *
1105 * 1) from the public Appliance::Read().
1106 *
1107 * 2) in a second worker thread; in that case, Appliance::ImportMachines() called Appliance::i_importImpl(), which
1108 * called Appliance::readFSOVA(), which called Appliance::i_importImpl(), which then called this again.
1109 *
1110 * @returns COM status with error info set.
1111 * @param aLocInfo The OVF location.
1112 * @param aProgress Where to return the progress object.
1113 */
1114HRESULT Appliance::i_readImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
1115{
1116 /*
1117 * Create the progress object.
1118 */
1119 HRESULT hrc;
1120 aProgress.createObject();
1121 try
1122 {
1123 if (aLocInfo.storageType == VFSType_Cloud)
1124 {
1125 /* 1 operation only */
1126 hrc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this),
1127 Utf8Str(tr("Getting cloud instance information")), TRUE /* aCancelable */);
1128
1129 /* Create an empty ovf::OVFReader for manual filling it.
1130 * It's not a normal usage case, but we try to re-use some OVF stuff to friend
1131 * the cloud import with OVF import.
1132 * In the standard case the ovf::OVFReader is created in the Appliance::i_readOVFFile().
1133 * We need the existing m->pReader for Appliance::i_importCloudImpl() where we re-use OVF logic. */
1134 m->pReader = new ovf::OVFReader();
1135 }
1136 else
1137 {
1138 Utf8StrFmt strDesc(tr("Reading appliance '%s'"), aLocInfo.strPath.c_str());
1139 if (aLocInfo.storageType == VFSType_File)
1140 /* 1 operation only */
1141 hrc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this), strDesc, TRUE /* aCancelable */);
1142 else
1143 /* 4/5 is downloading, 1/5 is reading */
1144 hrc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this), strDesc, TRUE /* aCancelable */,
1145 2, // ULONG cOperations,
1146 5, // ULONG ulTotalOperationsWeight,
1147 Utf8StrFmt(tr("Download appliance '%s'"),
1148 aLocInfo.strPath.c_str()), // CBSTR bstrFirstOperationDescription,
1149 4); // ULONG ulFirstOperationWeight,
1150 }
1151 }
1152 catch (std::bad_alloc &) /* Utf8Str/Utf8StrFmt */
1153 {
1154 return E_OUTOFMEMORY;
1155 }
1156 if (FAILED(hrc))
1157 return hrc;
1158
1159 /*
1160 * Initialize the worker task.
1161 */
1162 ThreadTask *pTask;
1163 try
1164 {
1165 if (aLocInfo.storageType == VFSType_Cloud)
1166 pTask = new TaskCloud(this, TaskCloud::ReadData, aLocInfo, aProgress);
1167 else
1168 pTask = new TaskOVF(this, TaskOVF::Read, aLocInfo, aProgress);
1169 }
1170 catch (std::bad_alloc &)
1171 {
1172 return E_OUTOFMEMORY;
1173 }
1174
1175 /*
1176 * Kick off the worker thread.
1177 */
1178 hrc = pTask->createThread();
1179 pTask = NULL; /* Note! createThread has consumed the task.*/
1180 if (SUCCEEDED(hrc))
1181 return hrc;
1182 return setError(hrc, tr("Failed to create thread for reading appliance data"));
1183}
1184
1185HRESULT Appliance::i_gettingCloudData(TaskCloud *pTask)
1186{
1187 LogFlowFuncEnter();
1188 LogFlowFunc(("Appliance %p\n", this));
1189
1190 AutoCaller autoCaller(this);
1191 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1192
1193 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
1194
1195 HRESULT hrc = S_OK;
1196
1197 try
1198 {
1199 Utf8Str strBasename(pTask->locInfo.strPath);
1200 RTCList<RTCString, RTCString *> parts = strBasename.split("/" );
1201 if (parts.size() != 2)//profile + instance id
1202 {
1203 return setErrorVrc(VERR_MISMATCH, tr("%s: The profile name or instance id are absent or"
1204 "contain unsupported characters.", __FUNCTION__));
1205 }
1206
1207 //Get information about the passed cloud instance
1208 ComPtr<ICloudProviderManager> cpm;
1209 hrc = mVirtualBox->COMGETTER(CloudProviderManager)(cpm.asOutParam());
1210 if (FAILED(hrc))
1211 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud provider manager object wasn't found"), __FUNCTION__);
1212
1213 Utf8Str strProviderName = pTask->locInfo.strProvider;
1214 ComPtr<ICloudProvider> cloudProvider;
1215 ComPtr<ICloudProfile> cloudProfile;
1216 hrc = cpm->GetProviderByShortName(Bstr(strProviderName.c_str()).raw(), cloudProvider.asOutParam());
1217
1218 if (FAILED(hrc))
1219 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud provider object wasn't found"), __FUNCTION__);
1220
1221 Utf8Str profileName(parts.at(0));//profile
1222 if (profileName.isEmpty())
1223 return setErrorVrc(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud user profile name wasn't found"), __FUNCTION__);
1224
1225 hrc = cloudProvider->GetProfileByName(Bstr(parts.at(0)).raw(), cloudProfile.asOutParam());
1226 if (FAILED(hrc))
1227 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud profile object wasn't found"), __FUNCTION__);
1228
1229 ComObjPtr<ICloudClient> cloudClient;
1230 hrc = cloudProfile->CreateCloudClient(cloudClient.asOutParam());
1231 if (FAILED(hrc))
1232 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud client object wasn't found"), __FUNCTION__);
1233
1234 m->virtualSystemDescriptions.clear();//clear all for assurance before creating new
1235 std::vector<ComPtr<IVirtualSystemDescription> > vsdArray;
1236 ULONG requestedVSDnums = 1;
1237 ULONG newVSDnums = 0;
1238 hrc = createVirtualSystemDescriptions(requestedVSDnums, &newVSDnums);
1239 if (FAILED(hrc)) throw hrc;
1240 if (requestedVSDnums != newVSDnums)
1241 throw setErrorVrc(VERR_MISMATCH, tr("%s: Requested and created numbers of VSD are differ."), __FUNCTION__);
1242
1243 hrc = getVirtualSystemDescriptions(vsdArray);
1244 if (FAILED(hrc)) throw hrc;
1245 ComPtr<IVirtualSystemDescription> instanceDescription = vsdArray[0];
1246
1247 LogRel(("%s: calling CloudClient::GetInstanceInfo()\n", __FUNCTION__));
1248
1249 ComPtr<IProgress> pProgress;
1250 hrc = cloudClient->GetInstanceInfo(Bstr(parts.at(1)).raw(), instanceDescription, pProgress.asOutParam());
1251 if (FAILED(hrc)) throw hrc;
1252 hrc = pTask->pProgress->WaitForOtherProgressCompletion(pProgress, 60000);//timeout 1 min = 60000 millisec
1253 if (FAILED(hrc)) throw hrc;
1254
1255 // set cloud profile
1256 instanceDescription->AddDescription(VirtualSystemDescriptionType_CloudProfileName,
1257 Bstr(profileName).raw(),
1258 NULL);
1259
1260 Utf8StrFmt strSetting("VM with id %s imported from the cloud provider %s",
1261 parts.at(1).c_str(), strProviderName.c_str());
1262 // set description
1263 instanceDescription->AddDescription(VirtualSystemDescriptionType_Description,
1264 Bstr(strSetting).raw(),
1265 NULL);
1266 }
1267 catch (HRESULT arc)
1268 {
1269 LogFlowFunc(("arc=%Rhrc\n", arc));
1270 hrc = arc;
1271 }
1272
1273 LogFlowFunc(("rc=%Rhrc\n", hrc));
1274 LogFlowFuncLeave();
1275
1276 return hrc;
1277}
1278
1279void Appliance::i_setApplianceState(const ApplianceState &state)
1280{
1281 AutoWriteLock writeLock(this COMMA_LOCKVAL_SRC_POS);
1282 m->state = state;
1283 writeLock.release();
1284}
1285
1286/**
1287 * Actual worker code for import from the Cloud
1288 *
1289 * @param pTask
1290 * @return
1291 */
1292HRESULT Appliance::i_importCloudImpl(TaskCloud *pTask)
1293{
1294 LogFlowFuncEnter();
1295 LogFlowFunc(("Appliance %p\n", this));
1296
1297 int vrc = VINF_SUCCESS;
1298 HRESULT hrc = S_OK;
1299 bool fKeepDownloadedObject = false;//in the future should be passed from the caller
1300 Utf8Str strLastActualErrorDesc("No errors");
1301
1302 /* Clear the list of imported machines, if any */
1303 m->llGuidsMachinesCreated.clear();
1304
1305 ComPtr<ICloudProviderManager> cpm;
1306 hrc = mVirtualBox->COMGETTER(CloudProviderManager)(cpm.asOutParam());
1307 if (FAILED(hrc))
1308 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud provider manager object wasn't found"), __FUNCTION__);
1309
1310 Utf8Str strProviderName = pTask->locInfo.strProvider;
1311 ComPtr<ICloudProvider> cloudProvider;
1312 ComPtr<ICloudProfile> cloudProfile;
1313 hrc = cpm->GetProviderByShortName(Bstr(strProviderName.c_str()).raw(), cloudProvider.asOutParam());
1314
1315 if (FAILED(hrc))
1316 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud provider object wasn't found"), __FUNCTION__);
1317
1318 /* Get the actual VSD, only one VSD object can be there for now so just call the function front() */
1319 ComPtr<IVirtualSystemDescription> vsd = m->virtualSystemDescriptions.front();
1320
1321 Utf8Str vsdData;
1322 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
1323 com::SafeArray<BSTR> aRefs;
1324 com::SafeArray<BSTR> aOvfValues;
1325 com::SafeArray<BSTR> aVBoxValues;
1326 com::SafeArray<BSTR> aExtraConfigValues;
1327
1328/*
1329 * local #define for better reading the code
1330 * uses only the previously locally declared variable names
1331 * set hrc as the result of operation
1332 */
1333#define GET_VSD_DESCRIPTION_BY_TYPE(aParamType) \
1334 retTypes.setNull(); \
1335 aRefs.setNull(); \
1336 aOvfValues.setNull(); \
1337 aVBoxValues.setNull(); \
1338 aExtraConfigValues.setNull(); \
1339 vsd->GetDescriptionByType(aParamType, \
1340 ComSafeArrayAsOutParam(retTypes), \
1341 ComSafeArrayAsOutParam(aRefs), \
1342 ComSafeArrayAsOutParam(aOvfValues), \
1343 ComSafeArrayAsOutParam(aVBoxValues), \
1344 ComSafeArrayAsOutParam(aExtraConfigValues)); \
1345
1346
1347 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudProfileName)
1348 if (aVBoxValues.size() == 0)
1349 return setErrorVrc(VERR_NOT_FOUND, tr("%s: Cloud user profile name wasn't found"), __FUNCTION__);
1350
1351 Utf8Str profileName(aVBoxValues[0]);
1352 if (profileName.isEmpty())
1353 return setErrorVrc(VERR_INVALID_STATE, tr("%s: Cloud user profile name is empty"), __FUNCTION__);
1354
1355 hrc = cloudProvider->GetProfileByName(aVBoxValues[0], cloudProfile.asOutParam());
1356 if (FAILED(hrc))
1357 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud profile object wasn't found"), __FUNCTION__);
1358
1359 ComObjPtr<ICloudClient> cloudClient;
1360 hrc = cloudProfile->CreateCloudClient(cloudClient.asOutParam());
1361 if (FAILED(hrc))
1362 return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud client object wasn't found"), __FUNCTION__);
1363
1364 ComPtr<IProgress> pProgress;
1365 hrc = pTask->pProgress.queryInterfaceTo(pProgress.asOutParam());
1366 if (FAILED(hrc))
1367 return hrc;
1368
1369 Utf8Str strOsType;
1370 ComPtr<IGuestOSType> pGuestOSType;
1371 {
1372 VBOXOSTYPE guestOsType = VBOXOSTYPE_Unknown;
1373 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_OS)//aVBoxValues is set in this #define
1374 if (aVBoxValues.size() != 0)
1375 {
1376 strOsType = aVBoxValues[0];
1377 /* Check the OS type */
1378 uint32_t const idxOSType = Global::getOSTypeIndexFromId(strOsType.c_str());
1379 guestOsType = idxOSType < Global::cOSTypes ? Global::sOSTypes[idxOSType].osType : VBOXOSTYPE_Unknown;
1380
1381 /* Case when some invalid OS type or garbage was passed. Set to VBOXOSTYPE_Unknown. */
1382 if (idxOSType > Global::cOSTypes)
1383 {
1384 strOsType = Global::OSTypeId(guestOsType);
1385 vsd->RemoveDescriptionByType(VirtualSystemDescriptionType_OS);
1386 vsd->AddDescription(VirtualSystemDescriptionType_OS,
1387 Bstr(strOsType).raw(),
1388 NULL);
1389 }
1390 }
1391 /* Case when no OS type was passed. Set to VBOXOSTYPE_Unknown. */
1392 else
1393 {
1394 strOsType = Global::OSTypeId(guestOsType);
1395 vsd->AddDescription(VirtualSystemDescriptionType_OS,
1396 Bstr(strOsType).raw(),
1397 NULL);
1398 }
1399
1400 LogRel(("%s: OS type is %s\n", __FUNCTION__, strOsType.c_str()));
1401
1402 /* We can get some default settings from GuestOSType when it's needed */
1403 hrc = mVirtualBox->GetGuestOSType(Bstr(strOsType).raw(), pGuestOSType.asOutParam());
1404 if (FAILED(hrc))
1405 return hrc;
1406 }
1407
1408 /* Should be defined here because it's used later, at least when ComposeMachineFilename() is called */
1409 Utf8Str strVMName("VM_exported_from_cloud");
1410
1411 if (m->virtualSystemDescriptions.size() == 1)
1412 {
1413 do
1414 {
1415 ComPtr<IVirtualBox> VBox(mVirtualBox);
1416
1417 {
1418 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Name)//aVBoxValues is set in this #define
1419 if (aVBoxValues.size() != 0)//paranoia but anyway...
1420 strVMName = aVBoxValues[0];
1421 LogRel(("%s: VM name is %s\n", __FUNCTION__, strVMName.c_str()));
1422 }
1423
1424// i_searchUniqueVMName(strVMName);//internally calls setError() in the case of absent the registered VM with such name
1425
1426 ComPtr<IMachine> machine;
1427 hrc = mVirtualBox->FindMachine(Bstr(strVMName.c_str()).raw(), machine.asOutParam());
1428 if (SUCCEEDED(hrc))
1429 {
1430 /** @todo r=bird: Please try find a less convoluted way of adding some random
1431 * number to the name, we've got RTRandU64() for instance which you
1432 * could combine with strVMName.appendPrintfNoThrow.
1433 *
1434 * The code below is also accessing heap after it has been freed.
1435 * Guid.toString() returns a Utf8Str object which expires and is deleted
1436 * once strrchr returns.
1437 */
1438 /* what to do? create a new name from the old one with some suffix? */
1439 com::Guid newId;
1440 newId.create();
1441 /*
1442 * GUID has the string form "00000000-0000-0000-0000-00000000000".
1443 * Find the last part and append only it. The last 12 characters.
1444 */
1445 const char* cpLast = strrchr(newId.toString().c_str(), '-');
1446 strVMName.append("__").append(cpLast+1);
1447
1448 vsd->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
1449 vsd->AddDescription(VirtualSystemDescriptionType_Name,
1450 Bstr(strVMName).raw(),
1451 NULL);
1452 /* No check again because it would be weird if a VM with such unique name exists */
1453 }
1454
1455 /* Check the target path. If the path exists and folder isn't empty return an error */
1456 {
1457 Bstr bstrSettingsFilename;
1458 /* Based on the VM name, create a target machine path. */
1459 hrc = mVirtualBox->ComposeMachineFilename(Bstr(strVMName).raw(),
1460 Bstr("/").raw(),
1461 NULL /* aCreateFlags */,
1462 NULL /* aBaseFolder */,
1463 bstrSettingsFilename.asOutParam());
1464 if (FAILED(hrc))
1465 break;
1466
1467 Utf8Str strMachineFolder(bstrSettingsFilename);
1468 strMachineFolder.stripFilename();
1469
1470 RTFSOBJINFO dirInfo;
1471 vrc = RTPathQueryInfo(strMachineFolder.c_str(), &dirInfo, RTFSOBJATTRADD_NOTHING);
1472 if (RT_SUCCESS(vrc))
1473 {
1474 size_t counter = 0;
1475 RTDIR hDir;
1476 vrc = RTDirOpen(&hDir, strMachineFolder.c_str());
1477 if (RT_SUCCESS(vrc))
1478 {
1479 RTDIRENTRY DirEntry;
1480 while (RT_SUCCESS(RTDirRead(hDir, &DirEntry, NULL)))
1481 {
1482 if (RTDirEntryIsStdDotLink(&DirEntry))
1483 continue;
1484 ++counter;
1485 }
1486
1487 if ( hDir != NULL)
1488 vrc = RTDirClose(hDir);
1489 }
1490 else
1491 return setErrorVrc(vrc, tr("Can't open folder %s"), strMachineFolder.c_str());
1492
1493 if (counter > 0)
1494 {
1495 return setErrorVrc(VERR_ALREADY_EXISTS, tr("The target folder %s has already contained some"
1496 " files (%d items). Clear the folder from the files or choose another folder"),
1497 strMachineFolder.c_str(), counter);
1498 }
1499 }
1500 }
1501
1502 Utf8Str strInsId;
1503 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId)//aVBoxValues is set in this #define
1504 if (aVBoxValues.size() == 0)
1505 return setErrorVrc(VERR_NOT_FOUND, "%s: Cloud Instance Id wasn't found", __FUNCTION__);
1506
1507 strInsId = aVBoxValues[0];
1508
1509 LogRel(("%s: calling CloudClient::ImportInstance\n", __FUNCTION__));
1510
1511 /* Here it's strongly supposed that cloud import produces ONE object on the disk.
1512 * Because it much easier to manage one object in any case.
1513 * In the case when cloud import creates several object on the disk all of them
1514 * must be combined together into one object by cloud client.
1515 * The most simple way is to create a TAR archive. */
1516 hrc = cloudClient->ImportInstance(m->virtualSystemDescriptions.front(),
1517 pProgress);
1518 if (FAILED(hrc))
1519 {
1520 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (cloud phase) failed. "
1521 "Used cloud instance is \'%s\'\n", __FUNCTION__, strInsId.c_str());
1522
1523 LogRel((strLastActualErrorDesc.c_str()));
1524 hrc = setError(hrc, strLastActualErrorDesc.c_str());
1525 break;
1526 }
1527
1528 } while (0);
1529 }
1530 else
1531 {
1532 hrc = setErrorVrc(VERR_NOT_SUPPORTED, tr("Import from Cloud isn't supported for more than one VM instance."));
1533 return hrc;
1534 }
1535
1536
1537 HRESULT original_hrc = hrc;//save the original result
1538
1539 /* In any case we delete the cloud leavings which may exist after the first phase (cloud phase).
1540 * Should they be deleted in the OCICloudClient::importInstance()?
1541 * Because deleting them here is not easy as it in the importInstance(). */
1542 {
1543 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId)//aVBoxValues is set in this #define
1544 if (aVBoxValues.size() == 0)
1545 hrc = setErrorVrc(VERR_NOT_FOUND, tr("%s: Cloud cleanup action - the instance wasn't found"), __FUNCTION__);
1546 else
1547 {
1548 vsdData = aVBoxValues[0];
1549
1550 /** @todo
1551 * future function which will eliminate the temporary objects created during the first phase.
1552 * hrc = cloud.EliminateImportLeavings(aVBoxValues[0], pProgress); */
1553/*
1554 if (FAILED(hrc))
1555 {
1556 hrc = setError(hrc, tr("Some leavings may exist in the Cloud."));
1557 LogRel(("%s: Cleanup action - the leavings in the %s after import the "
1558 "instance %s may not have been deleted\n",
1559 __FUNCTION__, strProviderName.c_str(), vsdData.c_str()));
1560 }
1561 else
1562 LogRel(("%s: Cleanup action - the leavings in the %s after import the "
1563 "instance %s have been deleted\n",
1564 __FUNCTION__, strProviderName.c_str(), vsdData.c_str()));
1565*/
1566 }
1567
1568 /* Because during the cleanup phase the hrc may have the good result
1569 * Thus we restore the original error in the case when the cleanup phase was successful
1570 * Otherwise we return not the original error but the last error in the cleanup phase */
1571 hrc = original_hrc;
1572 }
1573
1574 if (FAILED(hrc))
1575 {
1576 Utf8Str generalRollBackErrorMessage("Rollback action for Import Cloud operation failed. "
1577 "Some leavings may exist on the local disk or in the Cloud.");
1578 /*
1579 * Roll-back actions.
1580 * we finish here if:
1581 * 1. Getting the object from the Cloud has been failed.
1582 * 2. Something is wrong with getting data from ComPtr<IVirtualSystemDescription> vsd.
1583 * 3. More than 1 VirtualSystemDescription is presented in the list m->virtualSystemDescriptions.
1584 * Maximum what we have there are:
1585 * 1. The downloaded object, so just check the presence and delete it if one exists
1586 */
1587
1588 {
1589 if (!fKeepDownloadedObject)
1590 {
1591 /* small explanation here, the image here points out to the whole downloaded object (not to the image only)
1592 * filled during the first cloud import stage (in the ICloudClient::importInstance()) */
1593 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage)//aVBoxValues is set in this #define
1594 if (aVBoxValues.size() == 0)
1595 hrc = setErrorVrc(VERR_NOT_FOUND, generalRollBackErrorMessage.c_str());
1596 else
1597 {
1598 vsdData = aVBoxValues[0];
1599 //try to delete the downloaded object
1600 bool fExist = RTPathExists(vsdData.c_str());
1601 if (fExist)
1602 {
1603 vrc = RTFileDelete(vsdData.c_str());
1604 if (RT_FAILURE(vrc))
1605 {
1606 hrc = setErrorVrc(vrc, generalRollBackErrorMessage.c_str());
1607 LogRel(("%s: Rollback action - the object %s hasn't been deleted\n", __FUNCTION__, vsdData.c_str()));
1608 }
1609 else
1610 LogRel(("%s: Rollback action - the object %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
1611 }
1612 }
1613 }
1614 }
1615
1616 /* Because during the rollback phase the hrc may have the good result
1617 * Thus we restore the original error in the case when the rollback phase was successful
1618 * Otherwise we return not the original error but the last error in the rollback phase */
1619 hrc = original_hrc;
1620 }
1621 else
1622 {
1623 Utf8Str strMachineFolder;
1624 Utf8Str strAbsSrcPath;
1625 Utf8Str strGroup("/");//default VM group
1626 Utf8Str strTargetFormat("VMDK");//default image format
1627 Bstr bstrSettingsFilename;
1628 SystemProperties *pSysProps = NULL;
1629 RTCList<Utf8Str> extraCreatedFiles;/* All extra created files, it's used during cleanup */
1630
1631 /* Put all VFS* declaration here because they are needed to be release by the corresponding
1632 RTVfs***Release functions in the case of exception */
1633 RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
1634 RTVFSFSSTREAM hVfsFssObject = NIL_RTVFSFSSTREAM;
1635 RTVFSIOSTREAM hVfsIosCurr = NIL_RTVFSIOSTREAM;
1636
1637 try
1638 {
1639 /* Small explanation here, the image here points out to the whole downloaded object (not to the image only)
1640 * filled during the first cloud import stage (in the ICloudClient::importInstance()) */
1641 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage)//aVBoxValues is set in this #define
1642 if (aVBoxValues.size() == 0)
1643 throw setErrorVrc(VERR_NOT_FOUND, "%s: The description of the downloaded object wasn't found", __FUNCTION__);
1644
1645 strAbsSrcPath = aVBoxValues[0];
1646
1647 /* Based on the VM name, create a target machine path. */
1648 hrc = mVirtualBox->ComposeMachineFilename(Bstr(strVMName).raw(),
1649 Bstr(strGroup).raw(),
1650 NULL /* aCreateFlags */,
1651 NULL /* aBaseFolder */,
1652 bstrSettingsFilename.asOutParam());
1653 if (FAILED(hrc)) throw hrc;
1654
1655 strMachineFolder = bstrSettingsFilename;
1656 strMachineFolder.stripFilename();
1657
1658 /* Get the system properties. */
1659 pSysProps = mVirtualBox->i_getSystemProperties();
1660 if (pSysProps == NULL)
1661 throw VBOX_E_OBJECT_NOT_FOUND;
1662
1663 ComObjPtr<MediumFormat> trgFormat;
1664 trgFormat = pSysProps->i_mediumFormatFromExtension(strTargetFormat);
1665 if (trgFormat.isNull())
1666 throw VBOX_E_OBJECT_NOT_FOUND;
1667
1668 /* Continue and create new VM using data from VSD and downloaded object.
1669 * The downloaded images should be converted to VDI/VMDK if they have another format */
1670 Utf8Str strInstId("default cloud instance id");
1671 {
1672 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId)//aVBoxValues is set in this #define
1673 if (aVBoxValues.size() != 0)//paranoia but anyway...
1674 strInstId = aVBoxValues[0];
1675 LogRel(("%s: Importing cloud instance %s\n", __FUNCTION__, strInstId.c_str()));
1676 }
1677
1678 /* Processing the downloaded object (prepare for the local import) */
1679 RTVFSIOSTREAM hVfsIosSrc;
1680 vrc = RTVfsIoStrmOpenNormal(strAbsSrcPath.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosSrc);
1681 if (RT_FAILURE(vrc))
1682 {
1683 strLastActualErrorDesc = Utf8StrFmt("Error opening '%s' for reading (%Rrc)\n", strAbsSrcPath.c_str(), vrc);
1684 throw setErrorVrc(vrc, strLastActualErrorDesc.c_str());
1685 }
1686
1687 vrc = RTZipTarFsStreamFromIoStream(hVfsIosSrc, 0 /*fFlags*/, &hVfsFssObject);
1688 RTVfsIoStrmRelease(hVfsIosSrc);
1689 if (RT_FAILURE(vrc))
1690 {
1691 strLastActualErrorDesc = Utf8StrFmt("Error reading the downloaded file '%s' (%Rrc)", strAbsSrcPath.c_str(), vrc);
1692 throw setErrorVrc(vrc, strLastActualErrorDesc.c_str());
1693 }
1694
1695 /* Create a new virtual system and work directly on the list copy. */
1696 m->pReader->m_llVirtualSystems.push_back(ovf::VirtualSystem());
1697 ovf::VirtualSystem &vsys = m->pReader->m_llVirtualSystems.back();
1698
1699 /* Try to re-use some OVF stuff here */
1700 {
1701 vsys.strName = strVMName;
1702 uint32_t cpus = 1;
1703 {
1704 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CPU)//aVBoxValues is set in this #define
1705 if (aVBoxValues.size() != 0)
1706 {
1707 vsdData = aVBoxValues[0];
1708 cpus = vsdData.toUInt32();
1709 }
1710 vsys.cCPUs = (uint16_t)cpus;
1711 LogRel(("%s: Number of CPUs is %s\n", __FUNCTION__, vsdData.c_str()));
1712 }
1713
1714 ULONG memory;//Mb
1715 pGuestOSType->COMGETTER(RecommendedRAM)(&memory);
1716 {
1717 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Memory)//aVBoxValues is set in this #define
1718 if (aVBoxValues.size() != 0)
1719 {
1720 vsdData = aVBoxValues[0];
1721 if (memory > vsdData.toUInt32())
1722 memory = vsdData.toUInt32();
1723 }
1724 vsys.ullMemorySize = memory;
1725 LogRel(("%s: Size of RAM is %d MB\n", __FUNCTION__, vsys.ullMemorySize));
1726 }
1727
1728 {
1729 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Description)//aVBoxValues is set in this #define
1730 if (aVBoxValues.size() != 0)
1731 {
1732 vsdData = aVBoxValues[0];
1733 vsys.strDescription = vsdData;
1734 }
1735 LogRel(("%s: VM description \'%s\'\n", __FUNCTION__, vsdData.c_str()));
1736 }
1737
1738 {
1739 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_OS)//aVBoxValues is set in this #define
1740 if (aVBoxValues.size() != 0)
1741 strOsType = aVBoxValues[0];
1742 vsys.strTypeVBox = strOsType;
1743 LogRel(("%s: OS type is %s\n", __FUNCTION__, strOsType.c_str()));
1744 }
1745
1746 ovf::EthernetAdapter ea;
1747 {
1748 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_NetworkAdapter)//aVBoxValues is set in this #define
1749 if (aVBoxValues.size() != 0)
1750 {
1751 ea.strAdapterType = (Utf8Str)(aVBoxValues[0]);
1752 ea.strNetworkName = "NAT";//default
1753 vsys.llEthernetAdapters.push_back(ea);
1754 LogRel(("%s: Network adapter type is %s\n", __FUNCTION__, ea.strAdapterType.c_str()));
1755 }
1756 else
1757 {
1758 NetworkAdapterType_T defaultAdapterType = NetworkAdapterType_Am79C970A;
1759 pGuestOSType->COMGETTER(AdapterType)(&defaultAdapterType);
1760 Utf8StrFmt dat("%RU32", (uint32_t)defaultAdapterType);
1761 vsd->AddDescription(VirtualSystemDescriptionType_NetworkAdapter,
1762 Bstr(dat).raw(),
1763 Bstr(Utf8Str("NAT")).raw());
1764 }
1765 }
1766
1767 ovf::HardDiskController hdc;
1768 {
1769 //It's thought that SATA is supported by any OS types
1770 hdc.system = ovf::HardDiskController::SATA;
1771 hdc.idController = 0;
1772
1773 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskControllerSATA)//aVBoxValues is set in this #define
1774 if (aVBoxValues.size() != 0)
1775 hdc.strControllerType = (Utf8Str)(aVBoxValues[0]);
1776 else
1777 hdc.strControllerType = "AHCI";
1778
1779 LogRel(("%s: Hard disk controller type is %s\n", __FUNCTION__, hdc.strControllerType.c_str()));
1780 vsys.mapControllers[hdc.idController] = hdc;
1781
1782 if (aVBoxValues.size() == 0)
1783 {
1784 /* we should do it here because it'll be used later in the OVF logic (inside i_importMachines()) */
1785 vsd->AddDescription(VirtualSystemDescriptionType_HardDiskControllerSATA,
1786 Bstr(hdc.strControllerType).raw(),
1787 NULL);
1788 }
1789 }
1790
1791 {
1792 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_SoundCard)//aVBoxValues is set in this #define
1793 if (aVBoxValues.size() != 0)
1794 vsys.strSoundCardType = (Utf8Str)(aVBoxValues[0]);
1795 else
1796 {
1797 AudioControllerType_T defaultAudioController;
1798 pGuestOSType->COMGETTER(RecommendedAudioController)(&defaultAudioController);
1799 vsys.strSoundCardType = Utf8StrFmt("%RU32", (uint32_t)defaultAudioController);//"ensoniq1371";//"AC97";
1800 vsd->AddDescription(VirtualSystemDescriptionType_SoundCard,
1801 Bstr(vsys.strSoundCardType).raw(),
1802 NULL);
1803 }
1804
1805 LogRel(("%s: Sound card is %s\n", __FUNCTION__, vsys.strSoundCardType.c_str()));
1806 }
1807
1808 vsys.fHasFloppyDrive = false;
1809 vsys.fHasCdromDrive = false;
1810 vsys.fHasUsbController = true;
1811 }
1812
1813 unsigned currImageObjectNum = 0;
1814 hrc = S_OK;
1815 do
1816 {
1817 char *pszName = NULL;
1818 RTVFSOBJTYPE enmType;
1819 vrc = RTVfsFsStrmNext(hVfsFssObject, &pszName, &enmType, &hVfsObj);
1820 if (RT_FAILURE(vrc))
1821 {
1822 if (vrc != VERR_EOF)
1823 {
1824 hrc = setErrorVrc(vrc, tr("%s: Error reading '%s' (%Rrc)"), __FUNCTION__, strAbsSrcPath.c_str(), vrc);
1825 throw hrc;
1826 }
1827 break;
1828 }
1829
1830 /* We only care about entries that are files. Get the I/O stream handle for them. */
1831 if ( enmType == RTVFSOBJTYPE_IO_STREAM
1832 || enmType == RTVFSOBJTYPE_FILE)
1833 {
1834 /* Find the suffix and check if this is a possibly interesting file. */
1835 char *pszSuffix = RTStrToLower(strrchr(pszName, '.'));
1836
1837 /* Get the I/O stream. */
1838 hVfsIosCurr = RTVfsObjToIoStream(hVfsObj);
1839 Assert(hVfsIosCurr != NIL_RTVFSIOSTREAM);
1840
1841 /* Get the source medium format */
1842 ComObjPtr<MediumFormat> srcFormat;
1843 srcFormat = pSysProps->i_mediumFormatFromExtension(pszSuffix + 1);
1844
1845 /* unknown image format so just extract a file without any processing */
1846 if (srcFormat == NULL)
1847 {
1848 /* Read the file into a memory buffer */
1849 void *pvBuffered;
1850 size_t cbBuffered;
1851 RTVFSFILE hVfsDstFile = NIL_RTVFSFILE;
1852 try
1853 {
1854 vrc = RTVfsIoStrmReadAll(hVfsIosCurr, &pvBuffered, &cbBuffered);
1855 RTVfsIoStrmRelease(hVfsIosCurr);
1856 hVfsIosCurr = NIL_RTVFSIOSTREAM;
1857 if (RT_FAILURE(vrc))
1858 throw setErrorVrc(vrc, tr("Could not read the file '%s' (%Rrc)"), strAbsSrcPath.c_str(), vrc);
1859
1860 Utf8StrFmt strAbsDstPath("%s%s%s", strMachineFolder.c_str(), RTPATH_SLASH_STR, pszName);
1861
1862 /* Simple logic - just try to get dir info, in case of absent try to create one.
1863 No deep errors analysis */
1864 RTFSOBJINFO dirInfo;
1865 vrc = RTPathQueryInfo(strMachineFolder.c_str(), &dirInfo, RTFSOBJATTRADD_NOTHING);
1866 if (RT_FAILURE(vrc))
1867 {
1868 if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
1869 {
1870 vrc = RTDirCreateFullPath(strMachineFolder.c_str(), 0755);
1871 if (RT_FAILURE(vrc))
1872 throw setErrorVrc(vrc, tr("Could not create the directory '%s' (%Rrc)"),
1873 strMachineFolder.c_str(), vrc);
1874 }
1875 else
1876 throw setErrorVrc(vrc, tr("Error during getting info about the directory '%s' (%Rrc)"),
1877 strMachineFolder.c_str(), vrc);
1878 }
1879
1880 /* Write the file on the disk */
1881 vrc = RTVfsFileOpenNormal(strAbsDstPath.c_str(),
1882 RTFILE_O_WRITE | RTFILE_O_DENY_ALL | RTFILE_O_CREATE,
1883 &hVfsDstFile);
1884 if (RT_FAILURE(vrc))
1885 throw setErrorVrc(vrc, tr("Could not create the file '%s' (%Rrc)"), strAbsDstPath.c_str(), vrc);
1886
1887 size_t cbWritten;
1888 vrc = RTVfsFileWrite(hVfsDstFile, pvBuffered, cbBuffered, &cbWritten);
1889 if (RT_FAILURE(vrc))
1890 throw setErrorVrc(vrc, tr("Could not write into the file '%s' (%Rrc)"), strAbsDstPath.c_str(), vrc);
1891
1892 /* Remember this file */
1893 extraCreatedFiles.append(strAbsDstPath);
1894 }
1895 catch (HRESULT aRc)
1896 {
1897 hrc = aRc;
1898 strLastActualErrorDesc = Utf8StrFmt("%s: Processing the downloaded object was failed. "
1899 "The exception (%Rrc)\n", __FUNCTION__, hrc);
1900 LogRel((strLastActualErrorDesc.c_str()));
1901 }
1902 catch (int aRc)
1903 {
1904 hrc = setErrorVrc(aRc);
1905 strLastActualErrorDesc = Utf8StrFmt("%s: Processing the downloaded object was failed. "
1906 "The exception (%Rrc)\n", __FUNCTION__, aRc);
1907 LogRel((strLastActualErrorDesc.c_str()));
1908 }
1909 catch (...)
1910 {
1911 hrc = VERR_UNEXPECTED_EXCEPTION;
1912 strLastActualErrorDesc = Utf8StrFmt("%s: Processing the downloaded object was failed. "
1913 "The exception (%Rrc)\n", __FUNCTION__, hrc);
1914 LogRel((strLastActualErrorDesc.c_str()));
1915 }
1916 }
1917 else
1918 {
1919 /* Just skip the rest images if they exist. Only the first image is used as the base image. */
1920 if (currImageObjectNum >= 1)
1921 continue;
1922
1923 /* Image format is supported by VBox so extract the file and try to convert
1924 * one to the default format (which is VMDK for now) */
1925 Utf8Str z(bstrSettingsFilename);
1926 Utf8StrFmt strAbsDstPath("%s_%d.%s",
1927 z.stripSuffix().c_str(),
1928 currImageObjectNum,
1929 strTargetFormat.c_str());
1930
1931 hrc = mVirtualBox->i_findHardDiskByLocation(strAbsDstPath, false, NULL);
1932 if (SUCCEEDED(hrc))
1933 throw setError(VERR_ALREADY_EXISTS, tr("The hard disk '%s' already exists."), strAbsDstPath.c_str());
1934
1935 /* Create an IMedium object. */
1936 ComObjPtr<Medium> pTargetMedium;
1937 pTargetMedium.createObject();
1938 hrc = pTargetMedium->init(mVirtualBox,
1939 strTargetFormat,
1940 strAbsDstPath,
1941 Guid::Empty /* media registry: none yet */,
1942 DeviceType_HardDisk);
1943 if (FAILED(hrc))
1944 throw hrc;
1945
1946 pTask->pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"), pszName).raw(),
1947 200);
1948 ComObjPtr<Medium> nullParent;
1949 ComPtr<IProgress> pProgressImport;
1950 ComObjPtr<Progress> pProgressImportTmp;
1951 hrc = pProgressImportTmp.createObject();
1952 if (FAILED(hrc))
1953 throw hrc;
1954
1955 hrc = pProgressImportTmp->init(mVirtualBox,
1956 static_cast<IAppliance*>(this),
1957 Utf8StrFmt(tr("Importing medium '%s'"),
1958 pszName),
1959 TRUE);
1960 if (FAILED(hrc))
1961 throw hrc;
1962
1963 pProgressImportTmp.queryInterfaceTo(pProgressImport.asOutParam());
1964
1965 hrc = pTargetMedium->i_importFile(pszName,
1966 srcFormat,
1967 MediumVariant_Standard,
1968 hVfsIosCurr,
1969 nullParent,
1970 pProgressImportTmp,
1971 true /* aNotify */);
1972 RTVfsIoStrmRelease(hVfsIosCurr);
1973 hVfsIosCurr = NIL_RTVFSIOSTREAM;
1974 /* Now wait for the background import operation to complete;
1975 * this throws HRESULTs on error. */
1976 hrc = pTask->pProgress->WaitForOtherProgressCompletion(pProgressImport, 0 /* indefinite wait */);
1977
1978 /* Try to re-use some OVF stuff here */
1979 if (SUCCEEDED(hrc))
1980 {
1981 /* Small trick here.
1982 * We add new item into the actual VSD after successful conversion.
1983 * There is no need to delete any previous records describing the images in the VSD
1984 * because later in the code the search of the images in the VSD will use such records
1985 * with the actual image id (d.strDiskId = pTargetMedium->i_getId().toString()) which is
1986 * used as a key for m->pReader->m_mapDisks, vsys.mapVirtualDisks.
1987 * So all 3 objects are tied via the image id.
1988 * In the OVF case we already have all such records in the VSD after reading OVF
1989 * description file (read() and interpret() functions).*/
1990 ovf::DiskImage d;
1991 d.strDiskId = pTargetMedium->i_getId().toString();
1992 d.strHref = pTargetMedium->i_getLocationFull();
1993 d.strFormat = pTargetMedium->i_getFormat();
1994 d.iSize = pTargetMedium->i_getSize();
1995 d.ulSuggestedSizeMB = (uint32_t)(d.iSize/_1M);
1996
1997 m->pReader->m_mapDisks[d.strDiskId] = d;
1998
1999 ComObjPtr<VirtualSystemDescription> vsdescThis = m->virtualSystemDescriptions.front();
2000
2001 /* It's needed here to use the internal function i_addEntry() instead of the API function
2002 * addDescription() because we should pass the d.strDiskId for the proper handling this
2003 * disk later in the i_importMachineGeneric():
2004 * - find the line like this "if (vsdeHD->strRef == diCurrent.strDiskId)".
2005 * if those code can be eliminated then addDescription() will be used. */
2006 vsdescThis->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
2007 d.strDiskId,
2008 d.strHref,
2009 d.strHref,
2010 d.ulSuggestedSizeMB);
2011
2012 ovf::VirtualDisk vd;
2013 vd.idController = vsys.mapControllers[0].idController;
2014 vd.ulAddressOnParent = 0;
2015 vd.strDiskId = d.strDiskId;
2016 vsys.mapVirtualDisks[vd.strDiskId] = vd;
2017
2018 ++currImageObjectNum;
2019 }
2020 }
2021
2022 RTVfsIoStrmRelease(hVfsIosCurr);
2023 hVfsIosCurr = NIL_RTVFSIOSTREAM;
2024 }
2025
2026 RTVfsObjRelease(hVfsObj);
2027 hVfsObj = NIL_RTVFSOBJ;
2028
2029 RTStrFree(pszName);
2030
2031 } while (SUCCEEDED(hrc));
2032
2033 RTVfsFsStrmRelease(hVfsFssObject);
2034 hVfsFssObject = NIL_RTVFSFSSTREAM;
2035
2036 if (SUCCEEDED(hrc))
2037 {
2038 pTask->pProgress->SetNextOperation(BstrFmt(tr("Creating new VM '%s'"), strVMName.c_str()).raw(), 50);
2039 /* Create the import stack to comply OVF logic.
2040 * Before we filled some other data structures which are needed by OVF logic too.*/
2041 ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, NIL_RTVFSFSSTREAM);
2042 i_importMachines(stack);
2043 }
2044
2045 }
2046 catch (HRESULT aRc)
2047 {
2048 hrc = aRc;
2049 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
2050 "The exception (%Rrc)\n", __FUNCTION__, hrc);
2051 LogRel((strLastActualErrorDesc.c_str()));
2052 }
2053 catch (int aRc)
2054 {
2055 hrc = setErrorVrc(aRc);
2056 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
2057 "The exception (%Rrc)\n", __FUNCTION__, aRc);
2058 LogRel((strLastActualErrorDesc.c_str()));
2059 }
2060 catch (...)
2061 {
2062 hrc = VERR_UNRESOLVED_ERROR;
2063 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
2064 "The exception (%Rrc)\n", __FUNCTION__, hrc);
2065 LogRel((strLastActualErrorDesc.c_str()));
2066 }
2067
2068 LogRel(("%s: Cloud import (local phase) final result (%Rrc).\n", __FUNCTION__, hrc));
2069
2070 /* Try to free VFS stuff because some of them might not be released due to the exception */
2071 if (hVfsIosCurr != NIL_RTVFSIOSTREAM)
2072 RTVfsIoStrmRelease(hVfsIosCurr);
2073 if (hVfsObj != NIL_RTVFSOBJ)
2074 RTVfsObjRelease(hVfsObj);
2075 if (hVfsFssObject != NIL_RTVFSFSSTREAM)
2076 RTVfsFsStrmRelease(hVfsFssObject);
2077
2078 /* Small explanation here.
2079 * After adding extracted files into the actual VSD the returned list will contain not only the
2080 * record about the downloaded object but also the records about the extracted files from this object.
2081 * It's needed to go through this list to find the record about the downloaded object.
2082 * But it was the first record added into the list, so aVBoxValues[0] should be correct here.
2083 */
2084 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage)//aVBoxValues is set in this #define
2085 if (!fKeepDownloadedObject)
2086 {
2087 if (aVBoxValues.size() != 0)
2088 {
2089 vsdData = aVBoxValues[0];
2090 //try to delete the downloaded object
2091 bool fExist = RTPathExists(vsdData.c_str());
2092 if (fExist)
2093 {
2094 vrc = RTFileDelete(vsdData.c_str());
2095 if (RT_FAILURE(vrc))
2096 LogRel(("%s: Cleanup action - the downloaded object %s hasn't been deleted\n", __FUNCTION__, vsdData.c_str()));
2097 else
2098 LogRel(("%s: Cleanup action - the downloaded object %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
2099 }
2100 }
2101 }
2102
2103 if (FAILED(hrc))
2104 {
2105 /* What to do here?
2106 * For now:
2107 * - check the registration of created VM and delete one.
2108 * - check the list of imported images, detach them and next delete if they have still registered in the VBox.
2109 * - check some other leavings and delete them if they exist.
2110 */
2111
2112 /* It's not needed to call "pTask->pProgress->SetNextOperation(BstrFmt("The cleanup phase").raw(), 50)" here
2113 * because, WaitForOtherProgressCompletion() calls the SetNextOperation() iternally.
2114 * At least, it's strange that the operation description is set to the previous value. */
2115
2116 ComPtr<IMachine> pMachine;
2117 Utf8Str machineNameOrId = strVMName;
2118
2119 /* m->llGuidsMachinesCreated is filled in the i_importMachineGeneric()/i_importVBoxMachine()
2120 * after successful registration of new VM */
2121 if (!m->llGuidsMachinesCreated.empty())
2122 machineNameOrId = m->llGuidsMachinesCreated.front().toString();
2123
2124 hrc = mVirtualBox->FindMachine(Bstr(machineNameOrId).raw(), pMachine.asOutParam());
2125
2126 if (SUCCEEDED(hrc))
2127 {
2128 LogRel(("%s: Cleanup action - the VM with the name(or id) %s was found\n", __FUNCTION__, machineNameOrId.c_str()));
2129 SafeIfaceArray<IMedium> aMedia;
2130 hrc = pMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
2131 if (SUCCEEDED(hrc))
2132 {
2133 LogRel(("%s: Cleanup action - the VM %s has been unregistered\n", __FUNCTION__, machineNameOrId.c_str()));
2134 ComPtr<IProgress> pProgress1;
2135 hrc = pMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress1.asOutParam());
2136 pTask->pProgress->WaitForOtherProgressCompletion(pProgress1, 0 /* indefinite wait */);
2137
2138 LogRel(("%s: Cleanup action - the VM config file and the attached images have been deleted\n",
2139 __FUNCTION__));
2140 }
2141 }
2142 else
2143 {
2144 /* Re-check the items in the array with the images names (paths).
2145 * if the import fails before creation VM, then VM won't be found
2146 * -> VM can't be unregistered and the images can't be deleted.
2147 * The rest items in the array aVBoxValues are the images which might
2148 * have still been registered in the VBox.
2149 * So go through the array and detach-unregister-delete those images */
2150
2151 /* have to get write lock as the whole find/update sequence must be done
2152 * in one critical section, otherwise there are races which can lead to
2153 * multiple Medium objects with the same content */
2154
2155 AutoWriteLock treeLock(mVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
2156
2157 for (size_t i = 1; i < aVBoxValues.size(); ++i)
2158 {
2159 vsdData = aVBoxValues[i];
2160 ComObjPtr<Medium> poHardDisk;
2161 hrc = mVirtualBox->i_findHardDiskByLocation(vsdData, false, &poHardDisk);
2162 if (SUCCEEDED(hrc))
2163 {
2164 hrc = mVirtualBox->i_unregisterMedium((Medium*)(poHardDisk));
2165 if (SUCCEEDED(hrc))
2166 {
2167 ComPtr<IProgress> pProgress1;
2168 hrc = poHardDisk->DeleteStorage(pProgress1.asOutParam());
2169 pTask->pProgress->WaitForOtherProgressCompletion(pProgress1, 0 /* indefinite wait */);
2170 }
2171 if (SUCCEEDED(hrc))
2172 LogRel(("%s: Cleanup action - the image %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
2173 }
2174 else if (hrc == VBOX_E_OBJECT_NOT_FOUND)
2175 {
2176 LogRel(("%s: Cleanup action - the image %s wasn't found. Nothing to delete.\n", __FUNCTION__, vsdData.c_str()));
2177 hrc = S_OK;
2178 }
2179
2180 }
2181 }
2182
2183 /* Deletion of all additional files which were created during unpacking the downloaded object */
2184 for (size_t i = 0; i < extraCreatedFiles.size(); ++i)
2185 {
2186 vrc = RTFileDelete(extraCreatedFiles.at(i).c_str());
2187 if (RT_FAILURE(vrc))
2188 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
2189 else
2190 LogRel(("%s: Cleanup action - file %s has been deleted\n", __FUNCTION__, extraCreatedFiles.at(i).c_str()));
2191 }
2192
2193 /* Deletion of the other files in the VM folder and the folder itself */
2194 {
2195 RTDIR hDir;
2196 vrc = RTDirOpen(&hDir, strMachineFolder.c_str());
2197 if (RT_SUCCESS(vrc))
2198 {
2199 for (;;)
2200 {
2201 RTDIRENTRYEX Entry;
2202 vrc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2203 if (RT_FAILURE(vrc))
2204 {
2205 AssertLogRelMsg(vrc == VERR_NO_MORE_FILES, ("%Rrc\n", vrc));
2206 break;
2207 }
2208 if (RTFS_IS_FILE(Entry.Info.Attr.fMode))
2209 {
2210 vrc = RTFileDelete(Entry.szName);
2211 if (RT_FAILURE(vrc))
2212 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
2213 else
2214 LogRel(("%s: Cleanup action - file %s has been deleted\n", __FUNCTION__, Entry.szName));
2215 }
2216 }
2217 RTDirClose(hDir);
2218 }
2219
2220 vrc = RTDirRemove(strMachineFolder.c_str());
2221 if (RT_FAILURE(vrc))
2222 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
2223 }
2224
2225 if (FAILED(hrc))
2226 LogRel(("%s: Cleanup action - some leavings still may exist in the folder %s\n",
2227 __FUNCTION__, strMachineFolder.c_str()));
2228 }
2229 else
2230 {
2231 /* See explanation in the Appliance::i_importImpl() where Progress was setup */
2232 ULONG operationCount;
2233 ULONG currOperation;
2234 pTask->pProgress->COMGETTER(OperationCount)(&operationCount);
2235 pTask->pProgress->COMGETTER(Operation)(&currOperation);
2236 while (++currOperation < operationCount)
2237 {
2238 pTask->pProgress->SetNextOperation(BstrFmt("Skipping the cleanup phase. All right.").raw(), 1);
2239 LogRel(("%s: Skipping the cleanup step %d\n", __FUNCTION__, currOperation));
2240 }
2241 }
2242 }
2243
2244 LogFlowFunc(("rc=%Rhrc\n", hrc));
2245 LogFlowFuncLeave();
2246 return hrc;
2247}
2248
2249/**
2250 * Actual worker code for reading an OVF from disk. This is called from Appliance::taskThreadImportOrExport()
2251 * and therefore runs on the OVF read worker thread. This opens the OVF with ovfreader.cpp.
2252 *
2253 * This runs in one context:
2254 *
2255 * 1) in a first worker thread; in that case, Appliance::Read() called Appliance::readImpl();
2256 *
2257 * @param pTask
2258 * @return
2259 */
2260HRESULT Appliance::i_readFS(TaskOVF *pTask)
2261{
2262 LogFlowFuncEnter();
2263 LogFlowFunc(("Appliance %p\n", this));
2264
2265 AutoCaller autoCaller(this);
2266 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2267
2268 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
2269
2270 HRESULT rc;
2271 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
2272 rc = i_readFSOVF(pTask);
2273 else
2274 rc = i_readFSOVA(pTask);
2275
2276 LogFlowFunc(("rc=%Rhrc\n", rc));
2277 LogFlowFuncLeave();
2278
2279 return rc;
2280}
2281
2282HRESULT Appliance::i_readFSOVF(TaskOVF *pTask)
2283{
2284 LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
2285
2286 /*
2287 * Allocate a buffer for filenames and prep it for suffix appending.
2288 */
2289 char *pszNameBuf = (char *)alloca(pTask->locInfo.strPath.length() + 16);
2290 AssertReturn(pszNameBuf, VERR_NO_TMP_MEMORY);
2291 memcpy(pszNameBuf, pTask->locInfo.strPath.c_str(), pTask->locInfo.strPath.length() + 1);
2292 RTPathStripSuffix(pszNameBuf);
2293 size_t const cchBaseName = strlen(pszNameBuf);
2294
2295 /*
2296 * Open the OVF file first since that is what this is all about.
2297 */
2298 RTVFSIOSTREAM hIosOvf;
2299 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
2300 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosOvf);
2301 if (RT_FAILURE(vrc))
2302 return setErrorVrc(vrc, tr("Failed to open OVF file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2303
2304 HRESULT hrc = i_readOVFFile(pTask, hIosOvf, RTPathFilename(pTask->locInfo.strPath.c_str())); /* consumes hIosOvf */
2305 if (FAILED(hrc))
2306 return hrc;
2307
2308 /*
2309 * Try open the manifest file (for signature purposes and to determine digest type(s)).
2310 */
2311 RTVFSIOSTREAM hIosMf;
2312 strcpy(&pszNameBuf[cchBaseName], ".mf");
2313 vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosMf);
2314 if (RT_SUCCESS(vrc))
2315 {
2316 const char * const pszFilenamePart = RTPathFilename(pszNameBuf);
2317 hrc = i_readManifestFile(pTask, hIosMf /*consumed*/, pszFilenamePart);
2318 if (FAILED(hrc))
2319 return hrc;
2320
2321 /*
2322 * Check for the signature file.
2323 */
2324 RTVFSIOSTREAM hIosCert;
2325 strcpy(&pszNameBuf[cchBaseName], ".cert");
2326 vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosCert);
2327 if (RT_SUCCESS(vrc))
2328 {
2329 hrc = i_readSignatureFile(pTask, hIosCert /*consumed*/, pszFilenamePart);
2330 if (FAILED(hrc))
2331 return hrc;
2332 }
2333 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
2334 return setErrorVrc(vrc, tr("Failed to open the signature file '%s' (%Rrc)"), pszNameBuf, vrc);
2335
2336 }
2337 else if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
2338 {
2339 m->fDeterminedDigestTypes = true;
2340 m->fDigestTypes = 0;
2341 }
2342 else
2343 return setErrorVrc(vrc, tr("Failed to open the manifest file '%s' (%Rrc)"), pszNameBuf, vrc);
2344
2345 /*
2346 * Do tail processing (check the signature).
2347 */
2348 hrc = i_readTailProcessing(pTask);
2349
2350 LogFlowFunc(("returns %Rhrc\n", hrc));
2351 return hrc;
2352}
2353
2354HRESULT Appliance::i_readFSOVA(TaskOVF *pTask)
2355{
2356 LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
2357
2358 /*
2359 * Open the tar file as file stream.
2360 */
2361 RTVFSIOSTREAM hVfsIosOva;
2362 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
2363 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
2364 if (RT_FAILURE(vrc))
2365 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2366
2367 RTVFSFSSTREAM hVfsFssOva;
2368 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
2369 RTVfsIoStrmRelease(hVfsIosOva);
2370 if (RT_FAILURE(vrc))
2371 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2372
2373 /*
2374 * Since jumping thru an OVA file with seekable disk backing is rather
2375 * efficient, we can process .ovf, .mf and .cert files here without any
2376 * strict ordering restrictions.
2377 *
2378 * (Technically, the .ovf-file comes first, while the manifest and its
2379 * optional signature file either follows immediately or at the very end of
2380 * the OVA. The manifest is optional.)
2381 */
2382 char *pszOvfNameBase = NULL;
2383 size_t cchOvfNameBase = 0; NOREF(cchOvfNameBase);
2384 unsigned cLeftToFind = 3;
2385 HRESULT hrc = S_OK;
2386 do
2387 {
2388 char *pszName = NULL;
2389 RTVFSOBJTYPE enmType;
2390 RTVFSOBJ hVfsObj;
2391 vrc = RTVfsFsStrmNext(hVfsFssOva, &pszName, &enmType, &hVfsObj);
2392 if (RT_FAILURE(vrc))
2393 {
2394 if (vrc != VERR_EOF)
2395 hrc = setErrorVrc(vrc, tr("Error reading OVA '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2396 break;
2397 }
2398
2399 /* We only care about entries that are files. Get the I/O stream handle for them. */
2400 if ( enmType == RTVFSOBJTYPE_IO_STREAM
2401 || enmType == RTVFSOBJTYPE_FILE)
2402 {
2403 /* Find the suffix and check if this is a possibly interesting file. */
2404 char *pszSuffix = strrchr(pszName, '.');
2405 if ( pszSuffix
2406 && ( RTStrICmp(pszSuffix + 1, "ovf") == 0
2407 || RTStrICmp(pszSuffix + 1, "mf") == 0
2408 || RTStrICmp(pszSuffix + 1, "cert") == 0) )
2409 {
2410 /* Match the OVF base name. */
2411 *pszSuffix = '\0';
2412 if ( pszOvfNameBase == NULL
2413 || RTStrICmp(pszName, pszOvfNameBase) == 0)
2414 {
2415 *pszSuffix = '.';
2416
2417 /* Since we're pretty sure we'll be processing this file, get the I/O stream. */
2418 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
2419 Assert(hVfsIos != NIL_RTVFSIOSTREAM);
2420
2421 /* Check for the OVF (should come first). */
2422 if (RTStrICmp(pszSuffix + 1, "ovf") == 0)
2423 {
2424 if (pszOvfNameBase == NULL)
2425 {
2426 hrc = i_readOVFFile(pTask, hVfsIos, pszName);
2427 hVfsIos = NIL_RTVFSIOSTREAM;
2428
2429 /* Set the base name. */
2430 *pszSuffix = '\0';
2431 pszOvfNameBase = pszName;
2432 cchOvfNameBase = strlen(pszName);
2433 pszName = NULL;
2434 cLeftToFind--;
2435 }
2436 else
2437 LogRel(("i_readFSOVA: '%s' contains more than one OVF file ('%s'), picking the first one\n",
2438 pTask->locInfo.strPath.c_str(), pszName));
2439 }
2440 /* Check for manifest. */
2441 else if (RTStrICmp(pszSuffix + 1, "mf") == 0)
2442 {
2443 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
2444 {
2445 hrc = i_readManifestFile(pTask, hVfsIos, pszName);
2446 hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
2447 cLeftToFind--;
2448 }
2449 else
2450 LogRel(("i_readFSOVA: '%s' contains more than one manifest file ('%s'), picking the first one\n",
2451 pTask->locInfo.strPath.c_str(), pszName));
2452 }
2453 /* Check for signature. */
2454 else if (RTStrICmp(pszSuffix + 1, "cert") == 0)
2455 {
2456 if (!m->fSignerCertLoaded)
2457 {
2458 hrc = i_readSignatureFile(pTask, hVfsIos, pszName);
2459 hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
2460 cLeftToFind--;
2461 }
2462 else
2463 LogRel(("i_readFSOVA: '%s' contains more than one signature file ('%s'), picking the first one\n",
2464 pTask->locInfo.strPath.c_str(), pszName));
2465 }
2466 else
2467 AssertFailed();
2468 if (hVfsIos != NIL_RTVFSIOSTREAM)
2469 RTVfsIoStrmRelease(hVfsIos);
2470 }
2471 }
2472 }
2473 RTVfsObjRelease(hVfsObj);
2474 RTStrFree(pszName);
2475 } while (cLeftToFind > 0 && SUCCEEDED(hrc));
2476
2477 RTVfsFsStrmRelease(hVfsFssOva);
2478 RTStrFree(pszOvfNameBase);
2479
2480 /*
2481 * Check that we found and OVF file.
2482 */
2483 if (SUCCEEDED(hrc) && !pszOvfNameBase)
2484 hrc = setError(VBOX_E_FILE_ERROR, tr("OVA '%s' does not contain an .ovf-file"), pTask->locInfo.strPath.c_str());
2485 if (SUCCEEDED(hrc))
2486 {
2487 /*
2488 * Do tail processing (check the signature).
2489 */
2490 hrc = i_readTailProcessing(pTask);
2491 }
2492 LogFlowFunc(("returns %Rhrc\n", hrc));
2493 return hrc;
2494}
2495
2496/**
2497 * Reads & parses the OVF file.
2498 *
2499 * @param pTask The read task.
2500 * @param hVfsIosOvf The I/O stream for the OVF. The reference is
2501 * always consumed.
2502 * @param pszManifestEntry The manifest entry name.
2503 * @returns COM status code, error info set.
2504 * @throws Nothing
2505 */
2506HRESULT Appliance::i_readOVFFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosOvf, const char *pszManifestEntry)
2507{
2508 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszManifestEntry));
2509
2510 /*
2511 * Set the OVF manifest entry name (needed for tweaking the manifest
2512 * validation during import).
2513 */
2514 try { m->strOvfManifestEntry = pszManifestEntry; }
2515 catch (...) { return E_OUTOFMEMORY; }
2516
2517 /*
2518 * Set up digest calculation.
2519 */
2520 hVfsIosOvf = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosOvf, pszManifestEntry);
2521 if (hVfsIosOvf == NIL_RTVFSIOSTREAM)
2522 return VBOX_E_FILE_ERROR;
2523
2524 /*
2525 * Read the OVF into a memory buffer and parse it.
2526 */
2527 void *pvBufferedOvf;
2528 size_t cbBufferedOvf;
2529 int vrc = RTVfsIoStrmReadAll(hVfsIosOvf, &pvBufferedOvf, &cbBufferedOvf);
2530 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosOvf); /* consumes stream handle. */
2531 NOREF(cRefs);
2532 Assert(cRefs == 0);
2533 if (RT_FAILURE(vrc))
2534 return setErrorVrc(vrc, tr("Could not read the OVF file for '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2535
2536 HRESULT hrc;
2537 try
2538 {
2539 m->pReader = new ovf::OVFReader(pvBufferedOvf, cbBufferedOvf, pTask->locInfo.strPath);
2540 hrc = S_OK;
2541 }
2542 catch (RTCError &rXcpt) // includes all XML exceptions
2543 {
2544 hrc = setError(VBOX_E_FILE_ERROR, rXcpt.what());
2545 }
2546 catch (HRESULT aRC)
2547 {
2548 hrc = aRC;
2549 }
2550 catch (...)
2551 {
2552 hrc = E_FAIL;
2553 }
2554 LogFlowFunc(("OVFReader(%s) -> rc=%Rhrc\n", pTask->locInfo.strPath.c_str(), hrc));
2555
2556 RTVfsIoStrmReadAllFree(pvBufferedOvf, cbBufferedOvf);
2557 if (SUCCEEDED(hrc))
2558 {
2559 /*
2560 * If we see an OVF v2.0 envelope, select only the SHA-256 digest.
2561 */
2562 if ( !m->fDeterminedDigestTypes
2563 && m->pReader->m_envelopeData.getOVFVersion() == ovf::OVFVersion_2_0)
2564 m->fDigestTypes &= ~RTMANIFEST_ATTR_SHA256;
2565 }
2566
2567 return hrc;
2568}
2569
2570/**
2571 * Reads & parses the manifest file.
2572 *
2573 * @param pTask The read task.
2574 * @param hVfsIosMf The I/O stream for the manifest file. The
2575 * reference is always consumed.
2576 * @param pszSubFileNm The manifest filename (no path) for error
2577 * messages and logging.
2578 * @returns COM status code, error info set.
2579 * @throws Nothing
2580 */
2581HRESULT Appliance::i_readManifestFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosMf, const char *pszSubFileNm)
2582{
2583 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
2584
2585 /*
2586 * Copy the manifest into a memory backed file so we can later do signature
2587 * validation indepentend of the algorithms used by the signature.
2588 */
2589 int vrc = RTVfsMemorizeIoStreamAsFile(hVfsIosMf, RTFILE_O_READ, &m->hMemFileTheirManifest);
2590 RTVfsIoStrmRelease(hVfsIosMf); /* consumes stream handle. */
2591 if (RT_FAILURE(vrc))
2592 return setErrorVrc(vrc, tr("Error reading the manifest file '%s' for '%s' (%Rrc)"),
2593 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
2594
2595 /*
2596 * Parse the manifest.
2597 */
2598 Assert(m->hTheirManifest == NIL_RTMANIFEST);
2599 vrc = RTManifestCreate(0 /*fFlags*/, &m->hTheirManifest);
2600 AssertStmt(RT_SUCCESS(vrc), Global::vboxStatusCodeToCOM(vrc));
2601
2602 char szErr[256];
2603 RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(m->hMemFileTheirManifest);
2604 vrc = RTManifestReadStandardEx(m->hTheirManifest, hVfsIos, szErr, sizeof(szErr));
2605 RTVfsIoStrmRelease(hVfsIos);
2606 if (RT_FAILURE(vrc))
2607 return setErrorVrc(vrc, tr("Failed to parse manifest file '%s' for '%s' (%Rrc): %s"),
2608 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, szErr);
2609
2610 /*
2611 * Check which digest files are used.
2612 * Note! the file could be empty, in which case fDigestTypes is set to 0.
2613 */
2614 vrc = RTManifestQueryAllAttrTypes(m->hTheirManifest, true /*fEntriesOnly*/, &m->fDigestTypes);
2615 AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc));
2616 m->fDeterminedDigestTypes = true;
2617
2618 return S_OK;
2619}
2620
2621/**
2622 * Reads the signature & certificate file.
2623 *
2624 * @param pTask The read task.
2625 * @param hVfsIosCert The I/O stream for the signature file. The
2626 * reference is always consumed.
2627 * @param pszSubFileNm The signature filename (no path) for error
2628 * messages and logging. Used to construct
2629 * .mf-file name.
2630 * @returns COM status code, error info set.
2631 * @throws Nothing
2632 */
2633HRESULT Appliance::i_readSignatureFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosCert, const char *pszSubFileNm)
2634{
2635 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
2636
2637 /*
2638 * Construct the manifest filename from pszSubFileNm.
2639 */
2640 Utf8Str strManifestName;
2641 try
2642 {
2643 const char *pszSuffix = strrchr(pszSubFileNm, '.');
2644 AssertReturn(pszSuffix, E_FAIL);
2645 strManifestName = Utf8Str(pszSubFileNm, pszSuffix - pszSubFileNm);
2646 strManifestName.append(".mf");
2647 }
2648 catch (...)
2649 {
2650 return E_OUTOFMEMORY;
2651 }
2652
2653 /*
2654 * Copy the manifest into a memory buffer. We'll do the signature processing
2655 * later to not force any specific order in the OVAs or any other archive we
2656 * may be accessing later.
2657 */
2658 void *pvSignature;
2659 size_t cbSignature;
2660 int vrc = RTVfsIoStrmReadAll(hVfsIosCert, &pvSignature, &cbSignature);
2661 RTVfsIoStrmRelease(hVfsIosCert); /* consumes stream handle. */
2662 if (RT_FAILURE(vrc))
2663 return setErrorVrc(vrc, tr("Error reading the signature file '%s' for '%s' (%Rrc)"),
2664 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
2665
2666 /*
2667 * Parse the signing certificate. Unlike the manifest parser we use below,
2668 * this API ignores parts of the file that aren't relevant.
2669 */
2670 RTERRINFOSTATIC StaticErrInfo;
2671 vrc = RTCrX509Certificate_ReadFromBuffer(&m->SignerCert, pvSignature, cbSignature,
2672 RTCRX509CERT_READ_F_PEM_ONLY,
2673 &g_RTAsn1DefaultAllocator, RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
2674 HRESULT hrc;
2675 if (RT_SUCCESS(vrc))
2676 {
2677 m->fSignerCertLoaded = true;
2678 m->fCertificateIsSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->SignerCert);
2679
2680 /*
2681 * Find the start of the certificate part of the file, so we can avoid
2682 * upsetting the manifest parser with it.
2683 */
2684 char *pszSplit = (char *)RTCrPemFindFirstSectionInContent(pvSignature, cbSignature,
2685 g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers);
2686 if (pszSplit)
2687 while ( pszSplit != (char *)pvSignature
2688 && pszSplit[-1] != '\n'
2689 && pszSplit[-1] != '\r')
2690 pszSplit--;
2691 else
2692 {
2693 AssertLogRelMsgFailed(("Failed to find BEGIN CERTIFICATE markers in '%s'::'%s' - impossible unless it's a DER encoded certificate!",
2694 pTask->locInfo.strPath.c_str(), pszSubFileNm));
2695 pszSplit = (char *)pvSignature + cbSignature;
2696 }
2697 char const chSaved = *pszSplit;
2698 *pszSplit = '\0';
2699
2700 /*
2701 * Now, read the manifest part. We use the IPRT manifest reader here
2702 * to avoid duplicating code and be somewhat flexible wrt the digest
2703 * type choosen by the signer.
2704 */
2705 RTMANIFEST hSignedDigestManifest;
2706 vrc = RTManifestCreate(0 /*fFlags*/, &hSignedDigestManifest);
2707 if (RT_SUCCESS(vrc))
2708 {
2709 RTVFSIOSTREAM hVfsIosTmp;
2710 vrc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pvSignature, pszSplit - (char *)pvSignature, &hVfsIosTmp);
2711 if (RT_SUCCESS(vrc))
2712 {
2713 vrc = RTManifestReadStandardEx(hSignedDigestManifest, hVfsIosTmp, StaticErrInfo.szMsg, sizeof(StaticErrInfo.szMsg));
2714 RTVfsIoStrmRelease(hVfsIosTmp);
2715 if (RT_SUCCESS(vrc))
2716 {
2717 /*
2718 * Get signed digest, we prefer SHA-2, so explicitly query those first.
2719 */
2720 uint32_t fDigestType;
2721 char szSignedDigest[_8K + 1];
2722 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
2723 RTMANIFEST_ATTR_SHA512 | RTMANIFEST_ATTR_SHA256,
2724 szSignedDigest, sizeof(szSignedDigest), &fDigestType);
2725 if (vrc == VERR_MANIFEST_ATTR_TYPE_NOT_FOUND)
2726 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
2727 RTMANIFEST_ATTR_ANY, szSignedDigest, sizeof(szSignedDigest), &fDigestType);
2728 if (RT_SUCCESS(vrc))
2729 {
2730 const char *pszSignedDigest = RTStrStrip(szSignedDigest);
2731 size_t cbSignedDigest = strlen(pszSignedDigest) / 2;
2732 uint8_t abSignedDigest[sizeof(szSignedDigest) / 2];
2733 vrc = RTStrConvertHexBytes(szSignedDigest, abSignedDigest, cbSignedDigest, 0 /*fFlags*/);
2734 if (RT_SUCCESS(vrc))
2735 {
2736 /*
2737 * Convert it to RTDIGESTTYPE_XXX and save the binary value for later use.
2738 */
2739 switch (fDigestType)
2740 {
2741 case RTMANIFEST_ATTR_SHA1: m->enmSignedDigestType = RTDIGESTTYPE_SHA1; break;
2742 case RTMANIFEST_ATTR_SHA256: m->enmSignedDigestType = RTDIGESTTYPE_SHA256; break;
2743 case RTMANIFEST_ATTR_SHA512: m->enmSignedDigestType = RTDIGESTTYPE_SHA512; break;
2744 case RTMANIFEST_ATTR_MD5: m->enmSignedDigestType = RTDIGESTTYPE_MD5; break;
2745 default: AssertFailed(); m->enmSignedDigestType = RTDIGESTTYPE_INVALID; break;
2746 }
2747 if (m->enmSignedDigestType != RTDIGESTTYPE_INVALID)
2748 {
2749 m->pbSignedDigest = (uint8_t *)RTMemDup(abSignedDigest, cbSignedDigest);
2750 m->cbSignedDigest = cbSignedDigest;
2751 hrc = S_OK;
2752 }
2753 else
2754 hrc = setError(E_FAIL, tr("Unsupported signed digest type (%#x)"), fDigestType);
2755 }
2756 else
2757 hrc = setErrorVrc(vrc, tr("Error reading signed manifest digest: %Rrc"), vrc);
2758 }
2759 else if (vrc == VERR_NOT_FOUND)
2760 hrc = setErrorVrc(vrc, tr("Could not locate signed digest for '%s' in the cert-file for '%s'"),
2761 strManifestName.c_str(), pTask->locInfo.strPath.c_str());
2762 else
2763 hrc = setErrorVrc(vrc, tr("RTManifestEntryQueryAttr failed unexpectedly: %Rrc"), vrc);
2764 }
2765 else
2766 hrc = setErrorVrc(vrc, tr("Error parsing the .cert-file for '%s': %s"),
2767 pTask->locInfo.strPath.c_str(), StaticErrInfo.szMsg);
2768 }
2769 else
2770 hrc = E_OUTOFMEMORY;
2771 RTManifestRelease(hSignedDigestManifest);
2772 }
2773 else
2774 hrc = E_OUTOFMEMORY;
2775
2776 /*
2777 * Look for the additional for PKCS#7/CMS signature we produce when we sign stuff.
2778 */
2779 if (SUCCEEDED(hrc))
2780 {
2781 *pszSplit = chSaved;
2782 vrc = RTCrPkcs7_ReadFromBuffer(&m->ContentInfo, pvSignature, cbSignature, RTCRPKCS7_READ_F_PEM_ONLY,
2783 &g_RTAsn1DefaultAllocator, NULL /*pfCmsLabeled*/,
2784 RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
2785 if (RT_SUCCESS(vrc))
2786 m->fContentInfoLoaded = true;
2787 else if (vrc != VERR_NOT_FOUND)
2788 hrc = setErrorVrc(vrc, tr("Error reading the PKCS#7/CMS signature from '%s' for '%s' (%Rrc): %s"),
2789 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
2790 }
2791 }
2792 else if (vrc == VERR_NOT_FOUND || vrc == VERR_EOF)
2793 hrc = setErrorBoth(E_FAIL, vrc, tr("Malformed .cert-file for '%s': Signer's certificate not found (%Rrc)"),
2794 pTask->locInfo.strPath.c_str(), vrc);
2795 else
2796 hrc = setErrorVrc(vrc, tr("Error reading the signer's certificate from '%s' for '%s' (%Rrc): %s"),
2797 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
2798
2799 RTVfsIoStrmReadAllFree(pvSignature, cbSignature);
2800 LogFlowFunc(("returns %Rhrc (%Rrc)\n", hrc, vrc));
2801 return hrc;
2802}
2803
2804
2805/**
2806 * Does tail processing after the files have been read in.
2807 *
2808 * @param pTask The read task.
2809 * @returns COM status.
2810 * @throws Nothing!
2811 */
2812HRESULT Appliance::i_readTailProcessing(TaskOVF *pTask)
2813{
2814 /*
2815 * Parse and validate the signature file.
2816 *
2817 * The signature file nominally has two parts, manifest part and a PEM
2818 * encoded certificate. The former contains an entry for the manifest file
2819 * with a digest that is encrypted with the certificate in the latter part.
2820 *
2821 * When an appliance is signed by VirtualBox, a PKCS#7/CMS signedData part
2822 * is added by default, supplying more info than the bits mandated by the
2823 * OVF specs. We will validate both the signedData and the standard OVF
2824 * signature. Another requirement is that the first signedData signer
2825 * uses the same certificate as the regular OVF signature, allowing us to
2826 * only do path building for the signedData with the additional info it
2827 * ships with.
2828 */
2829 if (m->pbSignedDigest)
2830 {
2831 /* Since we're validating the digest of the manifest, there have to be
2832 a manifest. We cannot allow a the manifest to be missing. */
2833 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
2834 return setError(VBOX_E_FILE_ERROR, tr("Found .cert-file but no .mf-file for '%s'"), pTask->locInfo.strPath.c_str());
2835
2836 /*
2837 * Validate the signed digest.
2838 *
2839 * It's possible we should allow the user to ignore signature
2840 * mismatches, but for now it is a solid show stopper.
2841 */
2842 HRESULT hrc;
2843 RTERRINFOSTATIC StaticErrInfo;
2844
2845 /* Calc the digest of the manifest using the algorithm found above. */
2846 RTCRDIGEST hDigest;
2847 int vrc = RTCrDigestCreateByType(&hDigest, m->enmSignedDigestType);
2848 if (RT_SUCCESS(vrc))
2849 {
2850 vrc = RTCrDigestUpdateFromVfsFile(hDigest, m->hMemFileTheirManifest, true /*fRewindFile*/);
2851 if (RT_SUCCESS(vrc))
2852 {
2853 /* Compare the signed digest with the one we just calculated. (This
2854 API will do the verification twice, once using IPRT's own crypto
2855 and once using OpenSSL. Both must OK it for success.) */
2856 vrc = RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(&m->SignerCert.TbsCertificate.SubjectPublicKeyInfo,
2857 m->pbSignedDigest, m->cbSignedDigest, hDigest,
2858 RTErrInfoInitStatic(&StaticErrInfo));
2859 if (RT_SUCCESS(vrc))
2860 {
2861 m->fSignatureValid = true;
2862 hrc = S_OK;
2863 }
2864 else if (vrc == VERR_CR_PKIX_SIGNATURE_MISMATCH)
2865 hrc = setErrorVrc(vrc, tr("The manifest signature does not match"));
2866 else
2867 hrc = setErrorVrc(vrc,
2868 tr("Error validating the manifest signature (%Rrc, %s)"), vrc, StaticErrInfo.Core.pszMsg);
2869 }
2870 else
2871 hrc = setErrorVrc(vrc, tr("RTCrDigestUpdateFromVfsFile failed: %Rrc"), vrc);
2872 RTCrDigestRelease(hDigest);
2873 }
2874 else
2875 hrc = setErrorVrc(vrc, tr("RTCrDigestCreateByType failed: %Rrc"), vrc);
2876
2877 /*
2878 * If we have a PKCS#7/CMS signature, validate it and check that the
2879 * certificate matches the first signerInfo entry.
2880 */
2881 HRESULT hrc2 = i_readTailProcessingSignedData(&StaticErrInfo);
2882 if (FAILED(hrc2) && SUCCEEDED(hrc))
2883 hrc = hrc2;
2884
2885 /*
2886 * Validate the certificate.
2887 *
2888 * We don't fail here if we cannot validate the certificate, we postpone
2889 * that till the import stage, so that we can allow the user to ignore it.
2890 *
2891 * The certificate validity time is deliberately left as warnings as the
2892 * OVF specification does not provision for any timestamping of the
2893 * signature. This is course a security concern, but the whole signing
2894 * of OVFs is currently weirdly trusting (self signed * certs), so this
2895 * is the least of our current problems.
2896 *
2897 * While we try build and verify certificate paths properly, the
2898 * "neighbours" quietly ignores this and seems only to check the signature
2899 * and not whether the certificate is trusted. Also, we don't currently
2900 * complain about self-signed certificates either (ditto "neighbours").
2901 * The OVF creator is also a bit restricted wrt to helping us build the
2902 * path as he cannot supply intermediate certificates. Anyway, we issue
2903 * warnings (goes to /dev/null, am I right?) for self-signed certificates
2904 * and certificates we cannot build and verify a root path for.
2905 *
2906 * (The OVF sillibuggers should've used PKCS#7, CMS or something else
2907 * that's already been standardized instead of combining manifests with
2908 * certificate PEM files in some very restrictive manner! I wonder if
2909 * we could add a PKCS#7 section to the .cert file in addition to the CERT
2910 * and manifest stuff dictated by the standard. Would depend on how others
2911 * deal with it.)
2912 */
2913 Assert(!m->fCertificateValid);
2914 Assert(m->fCertificateMissingPath);
2915 Assert(!m->fCertificateValidTime);
2916 Assert(m->strCertError.isEmpty());
2917 Assert(m->fCertificateIsSelfSigned == RTCrX509Certificate_IsSelfSigned(&m->SignerCert));
2918
2919 /* We'll always needs the trusted cert store. */
2920 hrc2 = S_OK;
2921 RTCRSTORE hTrustedCerts;
2922 vrc = RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(&hTrustedCerts, RTErrInfoInitStatic(&StaticErrInfo));
2923 if (RT_SUCCESS(vrc))
2924 {
2925 /* If we don't have a PKCS7/CMS signature or if it uses a different
2926 certificate, we try our best to validate the OVF certificate. */
2927 if (!m->fContentInfoOkay || !m->fContentInfoSameCert)
2928 {
2929 if (m->fCertificateIsSelfSigned)
2930 hrc2 = i_readTailProcessingVerifySelfSignedOvfCert(pTask, hTrustedCerts, &StaticErrInfo);
2931 else
2932 hrc2 = i_readTailProcessingVerifyIssuedOvfCert(pTask, hTrustedCerts, &StaticErrInfo);
2933 }
2934
2935 /* If there is a PKCS7/CMS signature, we always verify its certificates. */
2936 if (m->fContentInfoOkay)
2937 {
2938 void *pvData = NULL;
2939 size_t cbData = 0;
2940 HRESULT hrc3 = i_readTailProcessingGetManifestData(&pvData, &cbData);
2941 if (SUCCEEDED(hrc3))
2942 {
2943 hrc3 = i_readTailProcessingVerifyContentInfoCerts(pvData, cbData, hTrustedCerts, &StaticErrInfo);
2944 RTMemTmpFree(pvData);
2945 }
2946 if (FAILED(hrc3) && SUCCEEDED(hrc2))
2947 hrc2 = hrc3;
2948 }
2949 RTCrStoreRelease(hTrustedCerts);
2950 }
2951 else
2952 hrc2 = setErrorBoth(E_FAIL, vrc,
2953 tr("Failed to query trusted CAs and Certificates from the system and for the current user (%Rrc%RTeim)"),
2954 vrc, &StaticErrInfo.Core);
2955
2956 /* Merge statuses from signature and certificate validation, prefering the signature one. */
2957 if (SUCCEEDED(hrc) && FAILED(hrc2))
2958 hrc = hrc2;
2959 if (FAILED(hrc))
2960 return hrc;
2961 }
2962
2963 /** @todo provide details about the signatory, signature, etc. */
2964 if (m->fSignerCertLoaded)
2965 {
2966 /** @todo PKCS7/CMS certs too */
2967 m->ptrCertificateInfo.createObject();
2968 m->ptrCertificateInfo->initCertificate(&m->SignerCert,
2969 m->fCertificateValid && !m->fCertificateMissingPath,
2970 !m->fCertificateValidTime);
2971 }
2972
2973 /*
2974 * If there is a manifest, check that the OVF digest matches up (if present).
2975 */
2976
2977 NOREF(pTask);
2978 return S_OK;
2979}
2980
2981/**
2982 * Reads hMemFileTheirManifest into a memory buffer so it can be passed to
2983 * RTCrPkcs7VerifySignedDataWithExternalData.
2984 *
2985 * Use RTMemTmpFree to free the memory.
2986 */
2987HRESULT Appliance::i_readTailProcessingGetManifestData(void **ppvData, size_t *pcbData)
2988{
2989 uint64_t cbData;
2990 int vrc = RTVfsFileQuerySize(m->hMemFileTheirManifest, &cbData);
2991 AssertRCReturn(vrc, setErrorVrc(vrc, "RTVfsFileQuerySize"));
2992
2993 void *pvData = RTMemTmpAllocZ((size_t)cbData);
2994 AssertPtrReturn(pvData, E_OUTOFMEMORY);
2995
2996 vrc = RTVfsFileReadAt(m->hMemFileTheirManifest, 0, pvData, (size_t)cbData, NULL);
2997 AssertRCReturnStmt(vrc, RTMemTmpFree(pvData), setErrorVrc(vrc, "RTVfsFileReadAt"));
2998
2999 *pcbData = (size_t)cbData;
3000 *ppvData = pvData;
3001 return S_OK;
3002}
3003
3004/**
3005 * Worker for i_readTailProcessing that validates the signedData.
3006 *
3007 * If we have a PKCS#7/CMS signature:
3008 * - validate it
3009 * - check that the OVF certificate matches the first signerInfo entry
3010 * - verify the signature, but leave the certificate path validation for
3011 * later.
3012 *
3013 * @param pErrInfo Static error info buffer (not for returning, just for
3014 * avoiding wasting stack).
3015 * @returns COM status.
3016 * @throws Nothing!
3017 */
3018HRESULT Appliance::i_readTailProcessingSignedData(PRTERRINFOSTATIC pErrInfo)
3019{
3020 m->fContentInfoOkay = false;
3021 m->fContentInfoSameCert = false;
3022 m->fContentInfoValidSignature = false;
3023
3024 if (!m->fContentInfoLoaded)
3025 return S_OK;
3026
3027 /*
3028 * Validate it.
3029 */
3030 HRESULT hrc = S_OK;
3031 PCRTCRPKCS7SIGNEDDATA pSignedData = m->ContentInfo.u.pSignedData;
3032 if (!RTCrPkcs7ContentInfo_IsSignedData(&m->ContentInfo))
3033 i_addWarning(tr("Invalid PKCS#7/CMS type: %s, expected %s (signedData)"),
3034 m->ContentInfo.ContentType.szObjId, RTCRPKCS7SIGNEDDATA_OID);
3035 else if (RTAsn1ObjId_CompareWithString(&pSignedData->ContentInfo.ContentType, RTCR_PKCS7_DATA_OID) != 0)
3036 i_addWarning(tr("Invalid PKCS#7/CMS inner type: %s, expected %s (data)"),
3037 pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID);
3038 else if (RTAsn1OctetString_IsPresent(&pSignedData->ContentInfo.Content))
3039 i_addWarning(tr("Invalid PKCS#7/CMS data: embedded (%u bytes), expected external"),
3040 pSignedData->ContentInfo.Content.Asn1Core.cb);
3041 else if (pSignedData->SignerInfos.cItems == 0)
3042 i_addWarning(tr("Invalid PKCS#7/CMS: No signers"));
3043 else
3044 {
3045 m->fContentInfoOkay = true;
3046
3047 /*
3048 * Same certificate as the OVF signature?
3049 */
3050 PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[0];
3051 if ( RTCrX509Name_Compare(&pSignerInfo->IssuerAndSerialNumber.Name, &m->SignerCert.TbsCertificate.Issuer) == 0
3052 && RTAsn1Integer_Compare(&pSignerInfo->IssuerAndSerialNumber.SerialNumber,
3053 &m->SignerCert.TbsCertificate.SerialNumber) == 0)
3054 m->fContentInfoSameCert = true;
3055 else
3056 i_addWarning(tr("Invalid PKCS#7/CMS: Using a different certificate"));
3057
3058 /*
3059 * Then perform a validation of the signatures, but first without
3060 * validating the certificate trust paths yet.
3061 */
3062 RTCRSTORE hTrustedCerts = NIL_RTCRSTORE;
3063 int vrc = RTCrStoreCreateInMem(&hTrustedCerts, 1);
3064 AssertRCReturn(vrc, setErrorVrc(vrc, tr("RTCrStoreCreateInMem failed: %Rrc"), vrc));
3065
3066 vrc = RTCrStoreCertAddX509(hTrustedCerts, 0, &m->SignerCert, RTErrInfoInitStatic(pErrInfo));
3067 if (RT_SUCCESS(vrc))
3068 {
3069 void *pvData = NULL;
3070 size_t cbData = 0;
3071 hrc = i_readTailProcessingGetManifestData(&pvData, &cbData);
3072 if (SUCCEEDED(hrc))
3073 {
3074 RTTIMESPEC Now;
3075 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_TRUST_ALL_CERTS,
3076 NIL_RTCRSTORE /*hAdditionalCerts*/, hTrustedCerts,
3077 RTTimeNow(&Now), NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
3078 pvData, cbData, RTErrInfoInitStatic(pErrInfo));
3079 if (RT_SUCCESS(vrc))
3080 m->fContentInfoValidSignature = true;
3081 else
3082 i_addWarning(tr("Failed to validate PKCS#7/CMS signature: %Rrc%RTeim"), vrc, &pErrInfo->Core);
3083 RTMemTmpFree(pvData);
3084 }
3085 }
3086 else
3087 hrc = setErrorVrc(vrc, tr("RTCrStoreCertAddX509 failed: %Rrc%RTeim"), vrc, &pErrInfo->Core);
3088 RTCrStoreRelease(hTrustedCerts);
3089 }
3090
3091 return hrc;
3092}
3093
3094
3095/**
3096 * Worker for i_readTailProcessing that verifies a self signed certificate when
3097 * no PKCS\#7/CMS signature using the same certificate is present.
3098 */
3099HRESULT Appliance::i_readTailProcessingVerifySelfSignedOvfCert(TaskOVF *pTask, RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo)
3100{
3101 /*
3102 * It's a self signed certificate. We assume the frontend will
3103 * present this fact to the user and give a choice whether this
3104 * is acceptable. But, first make sure it makes internal sense.
3105 */
3106 m->fCertificateMissingPath = true;
3107 PCRTCRCERTCTX pCertCtx = RTCrStoreCertByIssuerAndSerialNo(hTrustedStore, &m->SignerCert.TbsCertificate.Issuer,
3108 &m->SignerCert.TbsCertificate.SerialNumber);
3109 if (pCertCtx)
3110 {
3111 if (pCertCtx->pCert && RTCrX509Certificate_Compare(pCertCtx->pCert, &m->SignerCert) == 0)
3112 m->fCertificateMissingPath = true;
3113 RTCrCertCtxRelease(pCertCtx);
3114 }
3115
3116 int vrc = RTCrX509Certificate_VerifySignatureSelfSigned(&m->SignerCert, RTErrInfoInitStatic(pErrInfo));
3117 if (RT_SUCCESS(vrc))
3118 {
3119 m->fCertificateValid = true;
3120
3121 /* Check whether the certificate is currently valid, just warn if not. */
3122 RTTIMESPEC Now;
3123 m->fCertificateValidTime = RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now));
3124 if (m->fCertificateValidTime)
3125 {
3126 m->fCertificateValidTime = true;
3127 i_addWarning(tr("A self signed certificate was used to sign '%s'"), pTask->locInfo.strPath.c_str());
3128 }
3129 else
3130 i_addWarning(tr("Self signed certificate used to sign '%s' is not currently valid"),
3131 pTask->locInfo.strPath.c_str());
3132 }
3133 else
3134 {
3135 m->strCertError.printfNoThrow(tr("Verification of the self signed certificate failed (%Rrc%#RTeim)"),
3136 vrc, &pErrInfo->Core);
3137 i_addWarning(tr("Verification of the self signed certificate used to sign '%s' failed (%Rrc)%RTeim"),
3138 pTask->locInfo.strPath.c_str(), vrc, &pErrInfo->Core);
3139 }
3140
3141 /* Just warn if it's not a CA. Self-signed certificates are
3142 hardly trustworthy to start with without the user's consent. */
3143 if ( !m->SignerCert.TbsCertificate.T3.pBasicConstraints
3144 || !m->SignerCert.TbsCertificate.T3.pBasicConstraints->CA.fValue)
3145 i_addWarning(tr("Self signed certificate used to sign '%s' is not marked as certificate authority (CA)"),
3146 pTask->locInfo.strPath.c_str());
3147
3148 return S_OK;
3149}
3150
3151/**
3152 * Worker for i_readTailProcessing that verfies a non-self-issued OVF
3153 * certificate when no PKCS\#7/CMS signature using the same certificate is
3154 * present.
3155 */
3156HRESULT Appliance::i_readTailProcessingVerifyIssuedOvfCert(TaskOVF *pTask, RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo)
3157{
3158 /*
3159 * The certificate is not self-signed. Use the system certificate
3160 * stores to try build a path that validates successfully.
3161 */
3162 HRESULT hrc = S_OK;
3163 RTCRX509CERTPATHS hCertPaths;
3164 int vrc = RTCrX509CertPathsCreate(&hCertPaths, &m->SignerCert);
3165 if (RT_SUCCESS(vrc))
3166 {
3167 /* Get trusted certificates from the system and add them to the path finding mission. */
3168 vrc = RTCrX509CertPathsSetTrustedStore(hCertPaths, hTrustedStore);
3169 if (RT_FAILURE(vrc))
3170 hrc = setErrorBoth(E_FAIL, vrc, tr("RTCrX509CertPathsSetTrustedStore failed (%Rrc)"), vrc);
3171
3172 /* Add untrusted intermediate certificates. */
3173 if (RT_SUCCESS(vrc))
3174 {
3175 /// @todo RTCrX509CertPathsSetUntrustedStore(hCertPaths, hAdditionalCerts);
3176 /// We should look for intermediate certificates on the system, at least.
3177 }
3178 if (RT_SUCCESS(vrc))
3179 {
3180 /*
3181 * Do the building and verification of certificate paths.
3182 */
3183 vrc = RTCrX509CertPathsBuild(hCertPaths, RTErrInfoInitStatic(pErrInfo));
3184 if (RT_SUCCESS(vrc))
3185 {
3186 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(pErrInfo));
3187 if (RT_SUCCESS(vrc))
3188 {
3189 /*
3190 * Mark the certificate as good.
3191 */
3192 /** @todo check the certificate purpose? If so, share with self-signed. */
3193 m->fCertificateValid = true;
3194 m->fCertificateMissingPath = false;
3195
3196 /*
3197 * We add a warning if the certificate path isn't valid at the current
3198 * time. Since the time is only considered during path validation and we
3199 * can repeat the validation process (but not building), it's easy to check.
3200 */
3201 RTTIMESPEC Now;
3202 vrc = RTCrX509CertPathsSetValidTimeSpec(hCertPaths, RTTimeNow(&Now));
3203 if (RT_SUCCESS(vrc))
3204 {
3205 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(pErrInfo));
3206 if (RT_SUCCESS(vrc))
3207 m->fCertificateValidTime = true;
3208 else
3209 i_addWarning(tr("The certificate used to sign '%s' (or a certificate in the path) is not currently valid (%Rrc)"),
3210 pTask->locInfo.strPath.c_str(), vrc);
3211 }
3212 else
3213 hrc = setErrorVrc(vrc, "RTCrX509CertPathsSetValidTimeSpec failed: %Rrc", vrc);
3214 }
3215 else if (vrc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS)
3216 {
3217 m->fCertificateValid = true;
3218 i_addWarning(tr("No trusted certificate paths"));
3219
3220 /* Add another warning if the pathless certificate is not valid at present. */
3221 RTTIMESPEC Now;
3222 if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
3223 m->fCertificateValidTime = true;
3224 else
3225 i_addWarning(tr("The certificate used to sign '%s' is not currently valid"),
3226 pTask->locInfo.strPath.c_str());
3227 }
3228 else
3229 hrc = setErrorBoth(E_FAIL, vrc, tr("Certificate path validation failed (%Rrc%RTeim)"), vrc, &pErrInfo->Core);
3230 }
3231 else
3232 hrc = setErrorBoth(E_FAIL, vrc, tr("Certificate path building failed (%Rrc%RTeim)"), vrc, &pErrInfo->Core);
3233 }
3234 RTCrX509CertPathsRelease(hCertPaths);
3235 }
3236 else
3237 hrc = setErrorVrc(vrc, tr("RTCrX509CertPathsCreate failed: %Rrc"), vrc);
3238 return hrc;
3239}
3240
3241/**
3242 * Helper for i_readTailProcessingVerifySignerInfo that reports a verfication
3243 * failure.
3244 *
3245 * @returns S_OK
3246 */
3247HRESULT Appliance::i_readTailProcessingVerifyContentInfoFailOne(const char *pszSignature, int vrc, PRTERRINFOSTATIC pErrInfo)
3248{
3249 i_addWarning(tr("%s verification failed: %Rrc%RTeim"), pszSignature, vrc, &pErrInfo->Core);
3250 if (m->strCertError.isEmpty())
3251 m->strCertError.printfNoThrow(tr("%s verification failed: %Rrc%RTeim"), pszSignature, vrc, &pErrInfo->Core);
3252 return S_OK;
3253}
3254
3255/**
3256 * Worker for i_readTailProcessingVerifyContentInfoCerts that analyzes why the
3257 * standard verification of a signer info entry failed (@a vrc & @a pErrInfo).
3258 *
3259 * There are a couple of things we might want try to investigate deeper here:
3260 * 1. Untrusted signing certificate, often self-signed.
3261 * 2. Untrusted timstamp signing certificate.
3262 * 3. Certificate not valid at the current time and there isn't a
3263 * timestamp counter signature.
3264 *
3265 * That said, it is difficult to get an accurate fix and report on the
3266 * issues here since there are a number of error sources, so just try identify
3267 * the more typical cases.
3268 *
3269 * @note Caller cleans up *phTrustedStore2 if not NIL.
3270 */
3271HRESULT Appliance::i_readTailProcessingVerifyAnalyzeSignerInfo(void const *pvData, size_t cbData, RTCRSTORE hTrustedStore,
3272 uint32_t iSigner, PRTTIMESPEC pNow, int vrc,
3273 PRTERRINFOSTATIC pErrInfo, PRTCRSTORE phTrustedStore2)
3274{
3275 PRTCRPKCS7SIGNEDDATA const pSignedData = m->ContentInfo.u.pSignedData;
3276 PRTCRPKCS7SIGNERINFO const pSigner = pSignedData->SignerInfos.papItems[iSigner];
3277
3278 /*
3279 * Error/warning message prefix:
3280 */
3281 const char *pszSignature;
3282 if (iSigner == 0 && m->fContentInfoSameCert)
3283 pszSignature = tr("OVF & PKCS#7/CMS signature");
3284 else
3285 pszSignature = tr("PKCS#7/CMS signature");
3286 char szSignatureBuf[64];
3287 if (pSignedData->SignerInfos.cItems > 1)
3288 {
3289 RTStrPrintf(szSignatureBuf, sizeof(szSignatureBuf), tr("%s #%u"), pszSignature, iSigner + 1);
3290 pszSignature = szSignatureBuf;
3291 }
3292
3293 /*
3294 * Don't try handle weird stuff:
3295 */
3296 /** @todo Are there more statuses we can deal with here? */
3297 if ( vrc != VERR_CR_X509_CPV_NOT_VALID_AT_TIME
3298 && vrc != VERR_CR_X509_NO_TRUST_ANCHOR)
3299 return i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrc, pErrInfo);
3300
3301 /*
3302 * Find the signing certificate.
3303 * We require the certificate to be included in the signed data here.
3304 */
3305 PCRTCRX509CERTIFICATE pSigningCert;
3306 pSigningCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates,
3307 &pSigner->IssuerAndSerialNumber.Name,
3308 &pSigner->IssuerAndSerialNumber.SerialNumber);
3309 if (!pSigningCert)
3310 {
3311 i_addWarning(tr("PKCS#7/CMS signature #%u does not include the signing certificate"), iSigner + 1);
3312 if (m->strCertError.isEmpty())
3313 m->strCertError.printfNoThrow(tr("PKCS#7/CMS signature #%u does not include the signing certificate"), iSigner + 1);
3314 return S_OK;
3315 }
3316
3317 PCRTCRCERTCTX const pCertCtxTrusted = RTCrStoreCertByIssuerAndSerialNo(hTrustedStore, &pSigner->IssuerAndSerialNumber.Name,
3318 &pSigner->IssuerAndSerialNumber.SerialNumber);
3319 bool const fSelfSigned = RTCrX509Certificate_IsSelfSigned(pSigningCert);
3320
3321 /*
3322 * Add warning about untrusted self-signed certificate:
3323 */
3324 if (fSelfSigned && !pCertCtxTrusted)
3325 i_addWarning(tr("%s: Untrusted self-signed certificate"), pszSignature);
3326
3327 /*
3328 * Start by eliminating signing time issues (2 + 3) first as primary problem.
3329 * Keep the error info and status for later failures.
3330 */
3331 char szTime[RTTIME_STR_LEN];
3332 RTTIMESPEC Now2 = *pNow;
3333 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
3334 | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
3335 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
3336 | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, NIL_RTCRSTORE,
3337 hTrustedStore, &Now2, NULL, NULL,
3338 pvData, cbData, RTErrInfoInitStatic(pErrInfo));
3339 if (RT_SUCCESS(vrc))
3340 {
3341 /* Okay, is it an untrusted time signing certificate or just signing time in general? */
3342 RTTIMESPEC Now3 = *pNow;
3343 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
3344 | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
3345 | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
3346 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
3347 | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, NIL_RTCRSTORE,
3348 hTrustedStore, &Now3, NULL, NULL, pvData, cbData, NULL);
3349 if (RT_SUCCESS(vrc))
3350 i_addWarning(tr("%s: Untrusted timestamp (%s)"), pszSignature, RTTimeSpecToString(&Now3, szTime, sizeof(szTime)));
3351 else
3352 i_addWarning(tr("%s: Not valid at current time, but validates fine for untrusted signing time (%s)"),
3353 pszSignature, RTTimeSpecToString(&Now2, szTime, sizeof(szTime)));
3354 return S_OK;
3355 }
3356
3357 /* If we've got a trusted signing certificate (unlikely, but whatever), we can stop already.
3358 If we haven't got a self-signed certificate, stop too as messaging becomes complicated otherwise. */
3359 if (pCertCtxTrusted || !fSelfSigned)
3360 return i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrc, pErrInfo);
3361
3362 int const vrcErrInfo = vrc;
3363
3364 /*
3365 * Create a new trust store that includes the signing certificate
3366 * to see what that changes.
3367 */
3368 vrc = RTCrStoreCreateInMemEx(phTrustedStore2, 1, hTrustedStore);
3369 AssertRCReturn(vrc, setErrorVrc(vrc, "RTCrStoreCreateInMemEx"));
3370 vrc = RTCrStoreCertAddX509(*phTrustedStore2, 0, (PRTCRX509CERTIFICATE)pSigningCert, NULL);
3371 AssertRCReturn(vrc, setErrorVrc(vrc, "RTCrStoreCertAddX509/%u", iSigner));
3372
3373 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo,
3374 RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
3375 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
3376 | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, NIL_RTCRSTORE,
3377 *phTrustedStore2, pNow, NULL, NULL, pvData, cbData, NULL);
3378 if (RT_SUCCESS(vrc))
3379 {
3380 if (!fSelfSigned)
3381 i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrcErrInfo, pErrInfo);
3382 return S_OK;
3383 }
3384
3385 /*
3386 * Time problems too? Repeat what we did above, but with the modified trust store.
3387 */
3388 Now2 = *pNow;
3389 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
3390 | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
3391 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
3392 | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, NIL_RTCRSTORE,
3393 *phTrustedStore2, pNow, NULL, NULL, pvData, cbData, NULL);
3394 if (RT_SUCCESS(vrc))
3395 {
3396 /* Okay, is it an untrusted time signing certificate or just signing time in general? */
3397 RTTIMESPEC Now3 = *pNow;
3398 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
3399 | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
3400 | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
3401 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
3402 | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, NIL_RTCRSTORE,
3403 *phTrustedStore2, &Now3, NULL, NULL, pvData, cbData, NULL);
3404 if (RT_SUCCESS(vrc))
3405 i_addWarning(tr("%s: Untrusted timestamp (%s)"), pszSignature, RTTimeSpecToString(&Now3, szTime, sizeof(szTime)));
3406 else
3407 i_addWarning(tr("%s: Not valid at current time, but validates fine for untrusted signing time (%s)"),
3408 pszSignature, RTTimeSpecToString(&Now2, szTime, sizeof(szTime)));
3409 }
3410 else
3411 i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrcErrInfo, pErrInfo);
3412
3413 return S_OK;
3414}
3415
3416/**
3417 * Verify the signing certificates used to sign the PKCS\#7/CMS signature.
3418 *
3419 * ASSUMES that we've previously verified the PKCS\#7/CMS stuff in
3420 * trust-all-certs-without-question mode and it's just the certificate
3421 * validation that can fail now.
3422 */
3423HRESULT Appliance::i_readTailProcessingVerifyContentInfoCerts(void const *pvData, size_t cbData,
3424 RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo)
3425{
3426 /*
3427 * Just do a run and see what happens (note we've already verified
3428 * the data signatures, which just leaves certificates and paths).
3429 */
3430 RTTIMESPEC Now;
3431 int vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo,
3432 RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
3433 | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS,
3434 NIL_RTCRSTORE /*hAdditionalCerts*/, hTrustedStore,
3435 RTTimeNow(&Now), NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
3436 pvData, cbData, RTErrInfoInitStatic(pErrInfo));
3437 if (RT_SUCCESS(vrc))
3438 m->fContentInfoVerifiedOkay = true;
3439 else
3440 {
3441 /*
3442 * Deal with each of the signatures separately to try figure out
3443 * more exactly what's going wrong.
3444 */
3445 uint32_t cVerifiedOkay = 0;
3446 PRTCRPKCS7SIGNEDDATA pSignedData = m->ContentInfo.u.pSignedData;
3447 for (uint32_t iSigner = 0; iSigner < pSignedData->SignerInfos.cItems; iSigner++)
3448 {
3449 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo,
3450 RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
3451 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
3452 | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS,
3453 NIL_RTCRSTORE /*hAdditionalCerts*/, hTrustedStore,
3454 &Now, NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
3455 pvData, cbData, RTErrInfoInitStatic(pErrInfo));
3456 if (RT_SUCCESS(vrc))
3457 cVerifiedOkay++;
3458 else
3459 {
3460 RTCRSTORE hTrustedStore2 = NIL_RTCRSTORE;
3461 HRESULT hrc = i_readTailProcessingVerifyAnalyzeSignerInfo(pvData, cbData, hTrustedStore, iSigner, &Now,
3462 vrc, pErrInfo, &hTrustedStore2);
3463 RTCrStoreRelease(hTrustedStore2);
3464 if (FAILED(hrc))
3465 return hrc;
3466 }
3467 }
3468
3469 if ( pSignedData->SignerInfos.cItems > 1
3470 && pSignedData->SignerInfos.cItems != cVerifiedOkay)
3471 i_addWarning(tr("%u out of %u PKCS#7/CMS signatures verfified okay"),
3472 cVerifiedOkay, pSignedData->SignerInfos.cItems);
3473 }
3474
3475 return S_OK;
3476}
3477
3478
3479
3480/*******************************************************************************
3481 * Import stuff
3482 ******************************************************************************/
3483
3484/**
3485 * Implementation for importing OVF data into VirtualBox. This starts a new thread which will call
3486 * Appliance::taskThreadImportOrExport().
3487 *
3488 * This creates one or more new machines according to the VirtualSystemScription instances created by
3489 * Appliance::Interpret().
3490 *
3491 * This is in a separate private method because it is used from one location:
3492 *
3493 * 1) from the public Appliance::ImportMachines().
3494 *
3495 * @param locInfo
3496 * @param progress
3497 * @return
3498 */
3499HRESULT Appliance::i_importImpl(const LocationInfo &locInfo,
3500 ComObjPtr<Progress> &progress)
3501{
3502 HRESULT rc;
3503
3504 /* Initialize our worker task */
3505 ThreadTask *pTask;
3506 if (locInfo.storageType != VFSType_Cloud)
3507 {
3508 rc = i_setUpProgress(progress, Utf8StrFmt(tr("Importing appliance '%s'"), locInfo.strPath.c_str()),
3509 locInfo.storageType == VFSType_File ? ImportFile : ImportS3);
3510 if (FAILED(rc))
3511 return setError(rc, tr("Failed to create task for importing appliance into VirtualBox"));
3512 try
3513 {
3514 pTask = new TaskOVF(this, TaskOVF::Import, locInfo, progress);
3515 }
3516 catch (std::bad_alloc &)
3517 {
3518 return E_OUTOFMEMORY;
3519 }
3520 }
3521 else
3522 {
3523 if (locInfo.strProvider.equals("OCI"))
3524 {
3525 /*
3526 * 1. Create a custom image from the instance:
3527 * - 2 operations (starting and waiting)
3528 * 2. Import the custom image into the Object Storage (OCI format - TAR file with QCOW2 image and JSON file):
3529 * - 2 operations (starting and waiting)
3530 * 3. Download the object from the Object Storage:
3531 * - 1 operation (starting and downloadind is one operation)
3532 * 4. Open the object, extract an image and convert one to VDI:
3533 * - 1 operation (extracting and conversion are piped) because only 1 base bootable image is imported for now
3534 * 5. Create VM with user settings and attach the converted image to VM:
3535 * - 1 operation.
3536 * 6. Cleanup phase.
3537 * - 1 to N operations.
3538 * The number of the correct Progress operations are much tricky here.
3539 * Whether Machine::deleteConfig() is called or Medium::deleteStorage() is called in the loop.
3540 * Both require a new Progress object. To work with these functions the original Progress object uses
3541 * the function Progress::waitForOtherProgressCompletion().
3542 *
3543 * Some speculation here...
3544 * Total: 2+2+1(cloud) + 1+1(local) + 1+1+1(cleanup) = 10 operations
3545 * or
3546 * Total: 2+2+1(cloud) + 1+1(local) + 1(cleanup) = 8 operations
3547 * if VM wasn't created we would have only 1 registered image for cleanup.
3548 *
3549 * Weight "#define"s for the Cloud operations are located in the file OCICloudClient.h.
3550 * Weight of cloud import operations (1-3 items from above):
3551 * Total = 750 = 25+75(start and wait)+25+375(start and wait)+250(download)
3552 *
3553 * Weight of local import operations (4-5 items from above):
3554 * Total = 150 = 100 (extract and convert) + 50 (create VM, attach disks)
3555 *
3556 * Weight of local cleanup operations (6 item from above):
3557 * Some speculation here...
3558 * Total = 3 = 1 (1 image) + 1 (1 setting file)+ 1 (1 prev setting file) - quick operations
3559 * or
3560 * Total = 1 (1 image) if VM wasn't created we would have only 1 registered image for now.
3561 */
3562 try
3563 {
3564 rc = progress.createObject();
3565 if (SUCCEEDED(rc))
3566 rc = progress->init(mVirtualBox, static_cast<IAppliance *>(this),
3567 Utf8Str(tr("Importing VM from Cloud...")),
3568 TRUE /* aCancelable */,
3569 10, // ULONG cOperations,
3570 1000, // ULONG ulTotalOperationsWeight,
3571 Utf8Str(tr("Start import VM from the Cloud...")), // aFirstOperationDescription
3572 25); // ULONG ulFirstOperationWeight
3573 if (SUCCEEDED(rc))
3574 pTask = new TaskCloud(this, TaskCloud::Import, locInfo, progress);
3575 else
3576 pTask = NULL; /* shut up vcc */
3577 }
3578 catch (std::bad_alloc &)
3579 {
3580 return E_OUTOFMEMORY;
3581 }
3582 if (FAILED(rc))
3583 return setError(rc, tr("Failed to create task for importing appliance into VirtualBox"));
3584 }
3585 else
3586 return setError(E_NOTIMPL, tr("Only \"OCI\" cloud provider is supported for now. \"%s\" isn't supported."),
3587 locInfo.strProvider.c_str());
3588 }
3589
3590 /*
3591 * Start the task thread.
3592 */
3593 rc = pTask->createThread();
3594 pTask = NULL;
3595 if (SUCCEEDED(rc))
3596 return rc;
3597 return setError(rc, tr("Failed to start thread for importing appliance into VirtualBox"));
3598}
3599
3600/**
3601 * Actual worker code for importing OVF data into VirtualBox.
3602 *
3603 * This is called from Appliance::taskThreadImportOrExport() and therefore runs
3604 * on the OVF import worker thread. This creates one or more new machines
3605 * according to the VirtualSystemScription instances created by
3606 * Appliance::Interpret().
3607 *
3608 * This runs in two contexts:
3609 *
3610 * 1) in a first worker thread; in that case, Appliance::ImportMachines() called
3611 * Appliance::i_importImpl();
3612 *
3613 * 2) in a second worker thread; in that case, Appliance::ImportMachines()
3614 * called Appliance::i_importImpl(), which called Appliance::i_importFSOVA(),
3615 * which called Appliance::i_importImpl(), which then called this again.
3616 *
3617 * @param pTask The OVF task data.
3618 * @return COM status code.
3619 */
3620HRESULT Appliance::i_importFS(TaskOVF *pTask)
3621{
3622 LogFlowFuncEnter();
3623 LogFlowFunc(("Appliance %p\n", this));
3624
3625 /* Change the appliance state so we can safely leave the lock while doing
3626 * time-consuming image imports; also the below method calls do all kinds of
3627 * locking which conflicts with the appliance object lock. */
3628 AutoWriteLock writeLock(this COMMA_LOCKVAL_SRC_POS);
3629 /* Check if the appliance is currently busy. */
3630 if (!i_isApplianceIdle())
3631 return E_ACCESSDENIED;
3632 /* Set the internal state to importing. */
3633 m->state = ApplianceImporting;
3634
3635 HRESULT rc = S_OK;
3636
3637 /* Clear the list of imported machines, if any */
3638 m->llGuidsMachinesCreated.clear();
3639
3640 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
3641 rc = i_importFSOVF(pTask, writeLock);
3642 else
3643 rc = i_importFSOVA(pTask, writeLock);
3644 if (FAILED(rc))
3645 {
3646 /* With _whatever_ error we've had, do a complete roll-back of
3647 * machines and images we've created */
3648 writeLock.release();
3649 ErrorInfoKeeper eik;
3650 for (list<Guid>::iterator itID = m->llGuidsMachinesCreated.begin();
3651 itID != m->llGuidsMachinesCreated.end();
3652 ++itID)
3653 {
3654 Guid guid = *itID;
3655 Bstr bstrGuid = guid.toUtf16();
3656 ComPtr<IMachine> failedMachine;
3657 HRESULT rc2 = mVirtualBox->FindMachine(bstrGuid.raw(), failedMachine.asOutParam());
3658 if (SUCCEEDED(rc2))
3659 {
3660 SafeIfaceArray<IMedium> aMedia;
3661 rc2 = failedMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
3662 ComPtr<IProgress> pProgress2;
3663 rc2 = failedMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress2.asOutParam());
3664 pProgress2->WaitForCompletion(-1);
3665 }
3666 }
3667 writeLock.acquire();
3668 }
3669
3670 /* Reset the state so others can call methods again */
3671 m->state = ApplianceIdle;
3672
3673 LogFlowFunc(("rc=%Rhrc\n", rc));
3674 LogFlowFuncLeave();
3675 return rc;
3676}
3677
3678HRESULT Appliance::i_importFSOVF(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
3679{
3680 return i_importDoIt(pTask, rWriteLock);
3681}
3682
3683HRESULT Appliance::i_importFSOVA(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
3684{
3685 LogFlowFuncEnter();
3686
3687 /*
3688 * Open the tar file as file stream.
3689 */
3690 RTVFSIOSTREAM hVfsIosOva;
3691 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
3692 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
3693 if (RT_FAILURE(vrc))
3694 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
3695
3696 RTVFSFSSTREAM hVfsFssOva;
3697 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
3698 RTVfsIoStrmRelease(hVfsIosOva);
3699 if (RT_FAILURE(vrc))
3700 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
3701
3702 /*
3703 * Join paths with the i_importFSOVF code.
3704 *
3705 * Note! We don't need to skip the OVF, manifest or signature files, as the
3706 * i_importMachineGeneric, i_importVBoxMachine and i_importOpenSourceFile
3707 * code will deal with this (as there could be other files in the OVA
3708 * that we don't process, like 'de-DE-resources.xml' in EXAMPLE 1,
3709 * Appendix D.1, OVF v2.1.0).
3710 */
3711 HRESULT hrc = i_importDoIt(pTask, rWriteLock, hVfsFssOva);
3712
3713 RTVfsFsStrmRelease(hVfsFssOva);
3714
3715 LogFlowFunc(("returns %Rhrc\n", hrc));
3716 return hrc;
3717}
3718
3719/**
3720 * Does the actual importing after the caller has made the source accessible.
3721 *
3722 * @param pTask The import task.
3723 * @param rWriteLock The write lock the caller's caller is holding,
3724 * will be released for some reason.
3725 * @param hVfsFssOva The file system stream if OVA, NIL if not.
3726 * @returns COM status code.
3727 * @throws Nothing.
3728 */
3729HRESULT Appliance::i_importDoIt(TaskOVF *pTask, AutoWriteLockBase &rWriteLock, RTVFSFSSTREAM hVfsFssOva /*= NIL_RTVFSFSSTREAM*/)
3730{
3731 rWriteLock.release();
3732
3733 HRESULT hrc = E_FAIL;
3734 try
3735 {
3736 /*
3737 * Create the import stack for the rollback on errors.
3738 */
3739 ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, hVfsFssOva);
3740
3741 try
3742 {
3743 /* Do the importing. */
3744 i_importMachines(stack);
3745
3746 /* We should've processed all the files now, so compare. */
3747 hrc = i_verifyManifestFile(stack);
3748
3749 /* If everything was successful so far check if some extension
3750 * pack wants to do file sanity checking. */
3751 if (SUCCEEDED(hrc))
3752 {
3753 /** @todo */;
3754 }
3755 }
3756 catch (HRESULT hrcXcpt)
3757 {
3758 hrc = hrcXcpt;
3759 }
3760 catch (...)
3761 {
3762 AssertFailed();
3763 hrc = E_FAIL;
3764 }
3765 if (FAILED(hrc))
3766 {
3767 /*
3768 * Restoring original UUID from OVF description file.
3769 * During import VBox creates new UUIDs for imported images and
3770 * assigns them to the images. In case of failure we have to restore
3771 * the original UUIDs because those new UUIDs are obsolete now and
3772 * won't be used anymore.
3773 */
3774 ErrorInfoKeeper eik; /* paranoia */
3775 list< ComObjPtr<VirtualSystemDescription> >::const_iterator itvsd;
3776 /* Iterate through all virtual systems of that appliance */
3777 for (itvsd = m->virtualSystemDescriptions.begin();
3778 itvsd != m->virtualSystemDescriptions.end();
3779 ++itvsd)
3780 {
3781 ComObjPtr<VirtualSystemDescription> vsdescThis = (*itvsd);
3782 settings::MachineConfigFile *pConfig = vsdescThis->m->pConfig;
3783 if(vsdescThis->m->pConfig!=NULL)
3784 stack.restoreOriginalUUIDOfAttachedDevice(pConfig);
3785 }
3786 }
3787 }
3788 catch (...)
3789 {
3790 hrc = E_FAIL;
3791 AssertFailed();
3792 }
3793
3794 rWriteLock.acquire();
3795 return hrc;
3796}
3797
3798/**
3799 * Undocumented, you figure it from the name.
3800 *
3801 * @returns Undocumented
3802 * @param stack Undocumented.
3803 */
3804HRESULT Appliance::i_verifyManifestFile(ImportStack &stack)
3805{
3806 LogFlowThisFuncEnter();
3807 HRESULT hrc;
3808 int vrc;
3809
3810 /*
3811 * No manifest is fine, it always matches.
3812 */
3813 if (m->hTheirManifest == NIL_RTMANIFEST)
3814 hrc = S_OK;
3815 else
3816 {
3817 /*
3818 * Hack: If the manifest we just read doesn't have a digest for the OVF, copy
3819 * it from the manifest we got from the caller.
3820 * @bugref{6022#c119}
3821 */
3822 if ( !RTManifestEntryExists(m->hTheirManifest, m->strOvfManifestEntry.c_str())
3823 && RTManifestEntryExists(m->hOurManifest, m->strOvfManifestEntry.c_str()) )
3824 {
3825 uint32_t fType = 0;
3826 char szDigest[512 + 1];
3827 vrc = RTManifestEntryQueryAttr(m->hOurManifest, m->strOvfManifestEntry.c_str(), NULL, RTMANIFEST_ATTR_ANY,
3828 szDigest, sizeof(szDigest), &fType);
3829 if (RT_SUCCESS(vrc))
3830 vrc = RTManifestEntrySetAttr(m->hTheirManifest, m->strOvfManifestEntry.c_str(),
3831 NULL /*pszAttr*/, szDigest, fType);
3832 if (RT_FAILURE(vrc))
3833 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Error fudging missing OVF digest in manifest: %Rrc"), vrc);
3834 }
3835
3836 /*
3837 * Compare with the digests we've created while read/processing the import.
3838 *
3839 * We specify the RTMANIFEST_EQUALS_IGN_MISSING_ATTRS to ignore attributes
3840 * (SHA1, SHA256, etc) that are only present in one of the manifests, as long
3841 * as each entry has at least one common attribute that we can check. This
3842 * is important for the OVF in OVAs, for which we generates several digests
3843 * since we don't know which are actually used in the manifest (OVF comes
3844 * first in an OVA, then manifest).
3845 */
3846 char szErr[256];
3847 vrc = RTManifestEqualsEx(m->hTheirManifest, m->hOurManifest, NULL /*papszIgnoreEntries*/,
3848 NULL /*papszIgnoreAttrs*/,
3849 RTMANIFEST_EQUALS_IGN_MISSING_ATTRS | RTMANIFEST_EQUALS_IGN_MISSING_ENTRIES_2ND,
3850 szErr, sizeof(szErr));
3851 if (RT_SUCCESS(vrc))
3852 hrc = S_OK;
3853 else
3854 hrc = setErrorVrc(vrc, tr("Digest mismatch (%Rrc): %s"), vrc, szErr);
3855 }
3856
3857 NOREF(stack);
3858 LogFlowThisFunc(("returns %Rhrc\n", hrc));
3859 return hrc;
3860}
3861
3862/**
3863 * Helper that converts VirtualSystem attachment values into VirtualBox attachment values.
3864 * Throws HRESULT values on errors!
3865 *
3866 * @param hdc in: the HardDiskController structure to attach to.
3867 * @param ulAddressOnParent in: the AddressOnParent parameter from OVF.
3868 * @param controllerName out: the name of the storage controller to attach to (e.g. "IDE").
3869 * @param lControllerPort out: the channel (controller port) of the controller to attach to.
3870 * @param lDevice out: the device number to attach to.
3871 */
3872void Appliance::i_convertDiskAttachmentValues(const ovf::HardDiskController &hdc,
3873 uint32_t ulAddressOnParent,
3874 Utf8Str &controllerName,
3875 int32_t &lControllerPort,
3876 int32_t &lDevice)
3877{
3878 Log(("Appliance::i_convertDiskAttachmentValues: hdc.system=%d, hdc.fPrimary=%d, ulAddressOnParent=%d\n",
3879 hdc.system,
3880 hdc.fPrimary,
3881 ulAddressOnParent));
3882
3883 switch (hdc.system)
3884 {
3885 case ovf::HardDiskController::IDE:
3886 // For the IDE bus, the port parameter can be either 0 or 1, to specify the primary
3887 // or secondary IDE controller, respectively. For the primary controller of the IDE bus,
3888 // the device number can be either 0 or 1, to specify the master or the slave device,
3889 // respectively. For the secondary IDE controller, the device number is always 1 because
3890 // the master device is reserved for the CD-ROM drive.
3891 controllerName = "IDE";
3892 switch (ulAddressOnParent)
3893 {
3894 case 0: // master
3895 if (!hdc.fPrimary)
3896 {
3897 // secondary master
3898 lControllerPort = (long)1;
3899 lDevice = (long)0;
3900 }
3901 else // primary master
3902 {
3903 lControllerPort = (long)0;
3904 lDevice = (long)0;
3905 }
3906 break;
3907
3908 case 1: // slave
3909 if (!hdc.fPrimary)
3910 {
3911 // secondary slave
3912 lControllerPort = (long)1;
3913 lDevice = (long)1;
3914 }
3915 else // primary slave
3916 {
3917 lControllerPort = (long)0;
3918 lDevice = (long)1;
3919 }
3920 break;
3921
3922 // used by older VBox exports
3923 case 2: // interpret this as secondary master
3924 lControllerPort = (long)1;
3925 lDevice = (long)0;
3926 break;
3927
3928 // used by older VBox exports
3929 case 3: // interpret this as secondary slave
3930 lControllerPort = (long)1;
3931 lDevice = (long)1;
3932 break;
3933
3934 default:
3935 throw setError(VBOX_E_NOT_SUPPORTED,
3936 tr("Invalid channel %RI16 specified; IDE controllers support only 0, 1 or 2"),
3937 ulAddressOnParent);
3938 break;
3939 }
3940 break;
3941
3942 case ovf::HardDiskController::SATA:
3943 controllerName = "SATA";
3944 lControllerPort = (long)ulAddressOnParent;
3945 lDevice = (long)0;
3946 break;
3947
3948 case ovf::HardDiskController::SCSI:
3949 {
3950 if(hdc.strControllerType.compare("lsilogicsas")==0)
3951 controllerName = "SAS";
3952 else
3953 controllerName = "SCSI";
3954 lControllerPort = (long)ulAddressOnParent;
3955 lDevice = (long)0;
3956 break;
3957 }
3958
3959 default: break;
3960 }
3961
3962 Log(("=> lControllerPort=%d, lDevice=%d\n", lControllerPort, lDevice));
3963}
3964
3965/**
3966 * Imports one image.
3967 *
3968 * This is common code shared between
3969 * -- i_importMachineGeneric() for the OVF case; in that case the information comes from
3970 * the OVF virtual systems;
3971 * -- i_importVBoxMachine(); in that case, the information comes from the <vbox:Machine>
3972 * tag.
3973 *
3974 * Both ways of describing machines use the OVF disk references section, so in both cases
3975 * the caller needs to pass in the ovf::DiskImage structure from ovfreader.cpp.
3976 *
3977 * As a result, in both cases, if di.strHref is empty, we create a new image as per the OVF
3978 * spec, even though this cannot really happen in the vbox:Machine case since such data
3979 * would never have been exported.
3980 *
3981 * This advances stack.pProgress by one operation with the image's weight.
3982 *
3983 * @param di ovfreader.cpp structure describing the image from the OVF that is to be imported
3984 * @param strDstPath Where to create the target image.
3985 * @param pTargetMedium out: The newly created target medium. This also gets pushed on stack.llHardDisksCreated for cleanup.
3986 * @param stack
3987 *
3988 * @throws HRESULT
3989 */
3990void Appliance::i_importOneDiskImage(const ovf::DiskImage &di,
3991 const Utf8Str &strDstPath,
3992 ComObjPtr<Medium> &pTargetMedium,
3993 ImportStack &stack)
3994{
3995 HRESULT rc;
3996
3997 Utf8Str strAbsDstPath;
3998 int vrc = RTPathAbsExCxx(strAbsDstPath, stack.strMachineFolder, strDstPath);
3999 AssertRCStmt(vrc, throw Global::vboxStatusCodeToCOM(vrc));
4000
4001 /* Get the system properties. */
4002 SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
4003
4004 /* Keep the source file ref handy for later. */
4005 const Utf8Str &strSourceOVF = di.strHref;
4006
4007 /* Construct source file path */
4008 Utf8Str strSrcFilePath;
4009 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
4010 strSrcFilePath = strSourceOVF;
4011 else
4012 {
4013 strSrcFilePath = stack.strSourceDir;
4014 strSrcFilePath.append(RTPATH_SLASH_STR);
4015 strSrcFilePath.append(strSourceOVF);
4016 }
4017
4018 /* First of all check if the original (non-absolute) destination path is
4019 * a valid medium UUID. If so, the user wants to import the image into
4020 * an existing path. This is useful for iSCSI for example. */
4021 /** @todo r=klaus the code structure after this point is totally wrong,
4022 * full of unnecessary code duplication and other issues. 4.2 still had
4023 * the right structure for importing into existing medium objects, which
4024 * the current code can't possibly handle. */
4025 RTUUID uuid;
4026 vrc = RTUuidFromStr(&uuid, strDstPath.c_str());
4027 if (vrc == VINF_SUCCESS)
4028 {
4029 rc = mVirtualBox->i_findHardDiskById(Guid(uuid), true, &pTargetMedium);
4030 if (FAILED(rc)) throw rc;
4031 }
4032 else
4033 {
4034 RTVFSIOSTREAM hVfsIosSrc = NIL_RTVFSIOSTREAM;
4035
4036 /* check read file to GZIP compression */
4037 bool const fGzipped = di.strCompression.compare("gzip", Utf8Str::CaseInsensitive) == 0;
4038 Utf8Str strDeleteTemp;
4039 try
4040 {
4041 Utf8Str strTrgFormat = "VMDK";
4042 ComObjPtr<MediumFormat> trgFormat;
4043 Bstr bstrFormatName;
4044 ULONG lCabs = 0;
4045
4046 char *pszSuff = RTPathSuffix(strAbsDstPath.c_str());
4047 if (pszSuff != NULL)
4048 {
4049 /*
4050 * Figure out which format the user like to have. Default is VMDK
4051 * or it can be VDI if according command-line option is set
4052 */
4053
4054 /*
4055 * We need a proper target format
4056 * if target format has been changed by user via GUI import wizard
4057 * or via VBoxManage import command (option --importtovdi)
4058 * then we need properly process such format like ISO
4059 * Because there is no conversion ISO to VDI
4060 */
4061 trgFormat = pSysProps->i_mediumFormatFromExtension(++pszSuff);
4062 if (trgFormat.isNull())
4063 throw setError(E_FAIL, tr("Unsupported medium format for disk image '%s'"), di.strHref.c_str());
4064
4065 rc = trgFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
4066 if (FAILED(rc)) throw rc;
4067
4068 strTrgFormat = Utf8Str(bstrFormatName);
4069
4070 if ( m->optListImport.contains(ImportOptions_ImportToVDI)
4071 && strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) != 0)
4072 {
4073 /* change the target extension */
4074 strTrgFormat = "vdi";
4075 trgFormat = pSysProps->i_mediumFormatFromExtension(strTrgFormat);
4076 strAbsDstPath.stripSuffix();
4077 strAbsDstPath.append(".");
4078 strAbsDstPath.append(strTrgFormat.c_str());
4079 }
4080
4081 /* Check the capabilities. We need create capabilities. */
4082 lCabs = 0;
4083 com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
4084 rc = trgFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
4085
4086 if (FAILED(rc))
4087 throw rc;
4088
4089 for (ULONG j = 0; j < mediumFormatCap.size(); j++)
4090 lCabs |= mediumFormatCap[j];
4091
4092 if ( !(lCabs & MediumFormatCapabilities_CreateFixed)
4093 && !(lCabs & MediumFormatCapabilities_CreateDynamic) )
4094 throw setError(VBOX_E_NOT_SUPPORTED,
4095 tr("Could not find a valid medium format for the target disk '%s'"),
4096 strAbsDstPath.c_str());
4097 }
4098 else
4099 {
4100 throw setError(VBOX_E_FILE_ERROR,
4101 tr("The target disk '%s' has no extension "),
4102 strAbsDstPath.c_str(), VERR_INVALID_NAME);
4103 }
4104
4105 /*CD/DVD case*/
4106 if (strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) == 0)
4107 {
4108 try
4109 {
4110 if (fGzipped)
4111 i_importDecompressFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
4112 else
4113 i_importCopyFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
4114
4115 ComPtr<IMedium> pTmp;
4116 rc = mVirtualBox->OpenMedium(Bstr(strAbsDstPath).raw(),
4117 DeviceType_DVD,
4118 AccessMode_ReadWrite,
4119 false,
4120 pTmp.asOutParam());
4121 if (FAILED(rc))
4122 throw rc;
4123
4124 IMedium *iM = pTmp;
4125 pTargetMedium = static_cast<Medium*>(iM);
4126 }
4127 catch (HRESULT /*arc*/)
4128 {
4129 throw;
4130 }
4131
4132 /* Advance to the next operation. */
4133 /* operation's weight, as set up with the IProgress originally */
4134 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
4135 RTPathFilename(strSourceOVF.c_str())).raw(),
4136 di.ulSuggestedSizeMB);
4137 }
4138 else/* HDD case*/
4139 {
4140 /* Create an IMedium object. */
4141 pTargetMedium.createObject();
4142
4143 rc = pTargetMedium->init(mVirtualBox,
4144 strTrgFormat,
4145 strAbsDstPath,
4146 Guid::Empty /* media registry: none yet */,
4147 DeviceType_HardDisk);
4148 if (FAILED(rc)) throw rc;
4149
4150 ComPtr<IProgress> pProgressImport;
4151 /* If strHref is empty we have to create a new file. */
4152 if (strSourceOVF.isEmpty())
4153 {
4154 com::SafeArray<MediumVariant_T> mediumVariant;
4155 mediumVariant.push_back(MediumVariant_Standard);
4156
4157 /* Kick off the creation of a dynamic growing disk image with the given capacity. */
4158 rc = pTargetMedium->CreateBaseStorage(di.iCapacity / _1M,
4159 ComSafeArrayAsInParam(mediumVariant),
4160 pProgressImport.asOutParam());
4161 if (FAILED(rc)) throw rc;
4162
4163 /* Advance to the next operation. */
4164 /* operation's weight, as set up with the IProgress originally */
4165 stack.pProgress->SetNextOperation(BstrFmt(tr("Creating disk image '%s'"),
4166 strAbsDstPath.c_str()).raw(),
4167 di.ulSuggestedSizeMB);
4168 }
4169 else
4170 {
4171 /* We need a proper source format description */
4172 /* Which format to use? */
4173 ComObjPtr<MediumFormat> srcFormat;
4174 rc = i_findMediumFormatFromDiskImage(di, srcFormat);
4175 if (FAILED(rc))
4176 throw setError(VBOX_E_NOT_SUPPORTED,
4177 tr("Could not find a valid medium format for the source disk '%s' "
4178 "Check correctness of the image format URL in the OVF description file "
4179 "or extension of the image"),
4180 RTPathFilename(strSourceOVF.c_str()));
4181
4182 /* If gzipped, decompress the GZIP file and save a new file in the target path */
4183 if (fGzipped)
4184 {
4185 Utf8Str strTargetFilePath(strAbsDstPath);
4186 strTargetFilePath.stripFilename();
4187 strTargetFilePath.append(RTPATH_SLASH_STR);
4188 strTargetFilePath.append("temp_");
4189 strTargetFilePath.append(RTPathFilename(strSrcFilePath.c_str()));
4190 strDeleteTemp = strTargetFilePath;
4191
4192 i_importDecompressFile(stack, strSrcFilePath, strTargetFilePath, strSourceOVF.c_str());
4193
4194 /* Correct the source and the target with the actual values */
4195 strSrcFilePath = strTargetFilePath;
4196
4197 /* Open the new source file. */
4198 vrc = RTVfsIoStrmOpenNormal(strSrcFilePath.c_str(), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
4199 &hVfsIosSrc);
4200 if (RT_FAILURE(vrc))
4201 throw setErrorVrc(vrc, tr("Error opening decompressed image file '%s' (%Rrc)"),
4202 strSrcFilePath.c_str(), vrc);
4203 }
4204 else
4205 hVfsIosSrc = i_importOpenSourceFile(stack, strSrcFilePath, strSourceOVF.c_str());
4206
4207 /* Add a read ahead thread to try speed things up with concurrent reads and
4208 writes going on in different threads. */
4209 RTVFSIOSTREAM hVfsIosReadAhead;
4210 vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/,
4211 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
4212 RTVfsIoStrmRelease(hVfsIosSrc);
4213 if (RT_FAILURE(vrc))
4214 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"),
4215 strSrcFilePath.c_str(), vrc);
4216
4217 /* Start the source image cloning operation. */
4218 ComObjPtr<Medium> nullParent;
4219 ComObjPtr<Progress> pProgressImportTmp;
4220 rc = pProgressImportTmp.createObject();
4221 if (FAILED(rc)) throw rc;
4222 rc = pProgressImportTmp->init(mVirtualBox,
4223 static_cast<IAppliance*>(this),
4224 Utf8StrFmt(tr("Importing medium '%s'"),
4225 strAbsDstPath.c_str()),
4226 TRUE);
4227 if (FAILED(rc)) throw rc;
4228 pProgressImportTmp.queryInterfaceTo(pProgressImport.asOutParam());
4229 /* pProgressImportTmp is in parameter for Medium::i_importFile,
4230 * which is somewhat unusual and might be changed later. */
4231 rc = pTargetMedium->i_importFile(strSrcFilePath.c_str(),
4232 srcFormat,
4233 MediumVariant_Standard,
4234 hVfsIosReadAhead,
4235 nullParent,
4236 pProgressImportTmp,
4237 true /* aNotify */);
4238 RTVfsIoStrmRelease(hVfsIosReadAhead);
4239 hVfsIosSrc = NIL_RTVFSIOSTREAM;
4240 if (FAILED(rc))
4241 throw rc;
4242
4243 /* Advance to the next operation. */
4244 /* operation's weight, as set up with the IProgress originally */
4245 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
4246 RTPathFilename(strSourceOVF.c_str())).raw(),
4247 di.ulSuggestedSizeMB);
4248 }
4249
4250 /* Now wait for the background import operation to complete; this throws
4251 * HRESULTs on error. */
4252 stack.pProgress->WaitForOtherProgressCompletion(pProgressImport, 0 /* indefinite wait */);
4253
4254 /* The creating/importing has placed the medium in the global
4255 * media registry since the VM isn't created yet. Remove it
4256 * again to let it added to the right registry when the VM
4257 * has been created below. */
4258 pTargetMedium->i_removeRegistry(mVirtualBox->i_getGlobalRegistryId());
4259 }
4260 }
4261 catch (...)
4262 {
4263 if (strDeleteTemp.isNotEmpty())
4264 RTFileDelete(strDeleteTemp.c_str());
4265 throw;
4266 }
4267
4268 /* Make sure the source file is closed. */
4269 if (hVfsIosSrc != NIL_RTVFSIOSTREAM)
4270 RTVfsIoStrmRelease(hVfsIosSrc);
4271
4272 /*
4273 * Delete the temp gunzip result, if any.
4274 */
4275 if (strDeleteTemp.isNotEmpty())
4276 {
4277 vrc = RTFileDelete(strSrcFilePath.c_str());
4278 if (RT_FAILURE(vrc))
4279 setWarning(VBOX_E_FILE_ERROR,
4280 tr("Failed to delete the temporary file '%s' (%Rrc)"), strSrcFilePath.c_str(), vrc);
4281 }
4282 }
4283}
4284
4285/**
4286 * Imports one OVF virtual system (described by the given ovf::VirtualSystem and VirtualSystemDescription)
4287 * into VirtualBox by creating an IMachine instance, which is returned.
4288 *
4289 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
4290 * up any leftovers from this function. For this, the given ImportStack instance has received information
4291 * about what needs cleaning up (to support rollback).
4292 *
4293 * @param vsysThis OVF virtual system (machine) to import.
4294 * @param vsdescThis Matching virtual system description (machine) to import.
4295 * @param pNewMachine out: Newly created machine.
4296 * @param stack Cleanup stack for when this throws.
4297 */
4298void Appliance::i_importMachineGeneric(const ovf::VirtualSystem &vsysThis,
4299 ComObjPtr<VirtualSystemDescription> &vsdescThis,
4300 ComPtr<IMachine> &pNewMachine,
4301 ImportStack &stack)
4302{
4303 LogFlowFuncEnter();
4304 HRESULT rc;
4305
4306 // Get the instance of IGuestOSType which matches our string guest OS type so we
4307 // can use recommended defaults for the new machine where OVF doesn't provide any
4308 ComPtr<IGuestOSType> osType;
4309 rc = mVirtualBox->GetGuestOSType(Bstr(stack.strOsTypeVBox).raw(), osType.asOutParam());
4310 if (FAILED(rc)) throw rc;
4311
4312 /* Create the machine */
4313 SafeArray<BSTR> groups; /* no groups, or maybe one group... */
4314 if (!stack.strPrimaryGroup.isEmpty() && stack.strPrimaryGroup != "/")
4315 Bstr(stack.strPrimaryGroup).detachTo(groups.appendedRaw());
4316 rc = mVirtualBox->CreateMachine(Bstr(stack.strSettingsFilename).raw(),
4317 Bstr(stack.strNameVBox).raw(),
4318 ComSafeArrayAsInParam(groups),
4319 Bstr(stack.strOsTypeVBox).raw(),
4320 NULL, /* aCreateFlags */
4321 pNewMachine.asOutParam());
4322 if (FAILED(rc)) throw rc;
4323
4324 // set the description
4325 if (!stack.strDescription.isEmpty())
4326 {
4327 rc = pNewMachine->COMSETTER(Description)(Bstr(stack.strDescription).raw());
4328 if (FAILED(rc)) throw rc;
4329 }
4330
4331 // CPU count
4332 rc = pNewMachine->COMSETTER(CPUCount)(stack.cCPUs);
4333 if (FAILED(rc)) throw rc;
4334
4335 if (stack.fForceHWVirt)
4336 {
4337 rc = pNewMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, TRUE);
4338 if (FAILED(rc)) throw rc;
4339 }
4340
4341 // RAM
4342 rc = pNewMachine->COMSETTER(MemorySize)(stack.ulMemorySizeMB);
4343 if (FAILED(rc)) throw rc;
4344
4345 /* VRAM */
4346 /* Get the recommended VRAM for this guest OS type */
4347 ULONG vramVBox;
4348 rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox);
4349 if (FAILED(rc)) throw rc;
4350
4351 /* Set the VRAM */
4352 ComPtr<IGraphicsAdapter> pGraphicsAdapter;
4353 rc = pNewMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
4354 if (FAILED(rc)) throw rc;
4355 rc = pGraphicsAdapter->COMSETTER(VRAMSize)(vramVBox);
4356 if (FAILED(rc)) throw rc;
4357
4358 // I/O APIC: Generic OVF has no setting for this. Enable it if we
4359 // import a Windows VM because if if Windows was installed without IOAPIC,
4360 // it will not mind finding an one later on, but if Windows was installed
4361 // _with_ an IOAPIC, it will bluescreen if it's not found
4362 if (!stack.fForceIOAPIC)
4363 {
4364 Bstr bstrFamilyId;
4365 rc = osType->COMGETTER(FamilyId)(bstrFamilyId.asOutParam());
4366 if (FAILED(rc)) throw rc;
4367 if (bstrFamilyId == "Windows")
4368 stack.fForceIOAPIC = true;
4369 }
4370
4371 if (stack.fForceIOAPIC)
4372 {
4373 ComPtr<IBIOSSettings> pBIOSSettings;
4374 rc = pNewMachine->COMGETTER(BIOSSettings)(pBIOSSettings.asOutParam());
4375 if (FAILED(rc)) throw rc;
4376
4377 rc = pBIOSSettings->COMSETTER(IOAPICEnabled)(TRUE);
4378 if (FAILED(rc)) throw rc;
4379 }
4380
4381 if (stack.strFirmwareType.isNotEmpty())
4382 {
4383 FirmwareType_T firmwareType = FirmwareType_BIOS;
4384 if (stack.strFirmwareType.contains("EFI"))
4385 {
4386 if (stack.strFirmwareType.contains("32"))
4387 firmwareType = FirmwareType_EFI32;
4388 if (stack.strFirmwareType.contains("64"))
4389 firmwareType = FirmwareType_EFI64;
4390 else
4391 firmwareType = FirmwareType_EFI;
4392 }
4393 rc = pNewMachine->COMSETTER(FirmwareType)(firmwareType);
4394 if (FAILED(rc)) throw rc;
4395 }
4396
4397 if (!stack.strAudioAdapter.isEmpty())
4398 if (stack.strAudioAdapter.compare("null", Utf8Str::CaseInsensitive) != 0)
4399 {
4400 uint32_t audio = RTStrToUInt32(stack.strAudioAdapter.c_str()); // should be 0 for AC97
4401 ComPtr<IAudioAdapter> audioAdapter;
4402 rc = pNewMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam());
4403 if (FAILED(rc)) throw rc;
4404 rc = audioAdapter->COMSETTER(Enabled)(true);
4405 if (FAILED(rc)) throw rc;
4406 rc = audioAdapter->COMSETTER(AudioController)(static_cast<AudioControllerType_T>(audio));
4407 if (FAILED(rc)) throw rc;
4408 }
4409
4410#ifdef VBOX_WITH_USB
4411 /* USB Controller */
4412 if (stack.fUSBEnabled)
4413 {
4414 ComPtr<IUSBController> usbController;
4415 rc = pNewMachine->AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, usbController.asOutParam());
4416 if (FAILED(rc)) throw rc;
4417 }
4418#endif /* VBOX_WITH_USB */
4419
4420 /* Change the network adapters */
4421 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
4422
4423 std::list<VirtualSystemDescriptionEntry*> vsdeNW = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
4424 if (vsdeNW.empty())
4425 {
4426 /* No network adapters, so we have to disable our default one */
4427 ComPtr<INetworkAdapter> nwVBox;
4428 rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam());
4429 if (FAILED(rc)) throw rc;
4430 rc = nwVBox->COMSETTER(Enabled)(false);
4431 if (FAILED(rc)) throw rc;
4432 }
4433 else if (vsdeNW.size() > maxNetworkAdapters)
4434 throw setError(VBOX_E_FILE_ERROR,
4435 tr("Too many network adapters: OVF requests %d network adapters, "
4436 "but VirtualBox only supports %d"),
4437 vsdeNW.size(), maxNetworkAdapters);
4438 else
4439 {
4440 list<VirtualSystemDescriptionEntry*>::const_iterator nwIt;
4441 size_t a = 0;
4442 for (nwIt = vsdeNW.begin();
4443 nwIt != vsdeNW.end();
4444 ++nwIt, ++a)
4445 {
4446 const VirtualSystemDescriptionEntry* pvsys = *nwIt;
4447
4448 const Utf8Str &nwTypeVBox = pvsys->strVBoxCurrent;
4449 uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str());
4450 ComPtr<INetworkAdapter> pNetworkAdapter;
4451 rc = pNewMachine->GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
4452 if (FAILED(rc)) throw rc;
4453 /* Enable the network card & set the adapter type */
4454 rc = pNetworkAdapter->COMSETTER(Enabled)(true);
4455 if (FAILED(rc)) throw rc;
4456 rc = pNetworkAdapter->COMSETTER(AdapterType)(static_cast<NetworkAdapterType_T>(tt1));
4457 if (FAILED(rc)) throw rc;
4458
4459 // default is NAT; change to "bridged" if extra conf says so
4460 if (pvsys->strExtraConfigCurrent.endsWith("type=Bridged", Utf8Str::CaseInsensitive))
4461 {
4462 /* Attach to the right interface */
4463 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged);
4464 if (FAILED(rc)) throw rc;
4465 ComPtr<IHost> host;
4466 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
4467 if (FAILED(rc)) throw rc;
4468 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
4469 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
4470 if (FAILED(rc)) throw rc;
4471 // We search for the first host network interface which
4472 // is usable for bridged networking
4473 for (size_t j = 0;
4474 j < nwInterfaces.size();
4475 ++j)
4476 {
4477 HostNetworkInterfaceType_T itype;
4478 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
4479 if (FAILED(rc)) throw rc;
4480 if (itype == HostNetworkInterfaceType_Bridged)
4481 {
4482 Bstr name;
4483 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
4484 if (FAILED(rc)) throw rc;
4485 /* Set the interface name to attach to */
4486 rc = pNetworkAdapter->COMSETTER(BridgedInterface)(name.raw());
4487 if (FAILED(rc)) throw rc;
4488 break;
4489 }
4490 }
4491 }
4492 /* Next test for host only interfaces */
4493 else if (pvsys->strExtraConfigCurrent.endsWith("type=HostOnly", Utf8Str::CaseInsensitive))
4494 {
4495 /* Attach to the right interface */
4496 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly);
4497 if (FAILED(rc)) throw rc;
4498 ComPtr<IHost> host;
4499 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
4500 if (FAILED(rc)) throw rc;
4501 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
4502 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
4503 if (FAILED(rc)) throw rc;
4504 // We search for the first host network interface which
4505 // is usable for host only networking
4506 for (size_t j = 0;
4507 j < nwInterfaces.size();
4508 ++j)
4509 {
4510 HostNetworkInterfaceType_T itype;
4511 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
4512 if (FAILED(rc)) throw rc;
4513 if (itype == HostNetworkInterfaceType_HostOnly)
4514 {
4515 Bstr name;
4516 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
4517 if (FAILED(rc)) throw rc;
4518 /* Set the interface name to attach to */
4519 rc = pNetworkAdapter->COMSETTER(HostOnlyInterface)(name.raw());
4520 if (FAILED(rc)) throw rc;
4521 break;
4522 }
4523 }
4524 }
4525 /* Next test for internal interfaces */
4526 else if (pvsys->strExtraConfigCurrent.endsWith("type=Internal", Utf8Str::CaseInsensitive))
4527 {
4528 /* Attach to the right interface */
4529 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Internal);
4530 if (FAILED(rc)) throw rc;
4531 }
4532 /* Next test for Generic interfaces */
4533 else if (pvsys->strExtraConfigCurrent.endsWith("type=Generic", Utf8Str::CaseInsensitive))
4534 {
4535 /* Attach to the right interface */
4536 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Generic);
4537 if (FAILED(rc)) throw rc;
4538 }
4539
4540 /* Next test for NAT network interfaces */
4541 else if (pvsys->strExtraConfigCurrent.endsWith("type=NATNetwork", Utf8Str::CaseInsensitive))
4542 {
4543 /* Attach to the right interface */
4544 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork);
4545 if (FAILED(rc)) throw rc;
4546 com::SafeIfaceArray<INATNetwork> nwNATNetworks;
4547 rc = mVirtualBox->COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(nwNATNetworks));
4548 if (FAILED(rc)) throw rc;
4549 // Pick the first NAT network (if there is any)
4550 if (nwNATNetworks.size())
4551 {
4552 Bstr name;
4553 rc = nwNATNetworks[0]->COMGETTER(NetworkName)(name.asOutParam());
4554 if (FAILED(rc)) throw rc;
4555 /* Set the NAT network name to attach to */
4556 rc = pNetworkAdapter->COMSETTER(NATNetwork)(name.raw());
4557 if (FAILED(rc)) throw rc;
4558 break;
4559 }
4560 }
4561 }
4562 }
4563
4564 // Storage controller IDE
4565 std::list<VirtualSystemDescriptionEntry*> vsdeHDCIDE =
4566 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerIDE);
4567 /*
4568 * In OVF (at least VMware's version of it), an IDE controller has two ports,
4569 * so VirtualBox's single IDE controller with two channels and two ports each counts as
4570 * two OVF IDE controllers -- so we accept one or two such IDE controllers
4571 */
4572 size_t cIDEControllers = vsdeHDCIDE.size();
4573 if (cIDEControllers > 2)
4574 throw setError(VBOX_E_FILE_ERROR,
4575 tr("Too many IDE controllers in OVF; import facility only supports two"));
4576 if (!vsdeHDCIDE.empty())
4577 {
4578 // one or two IDE controllers present in OVF: add one VirtualBox controller
4579 ComPtr<IStorageController> pController;
4580 rc = pNewMachine->AddStorageController(Bstr("IDE").raw(), StorageBus_IDE, pController.asOutParam());
4581 if (FAILED(rc)) throw rc;
4582
4583 const char *pcszIDEType = vsdeHDCIDE.front()->strVBoxCurrent.c_str();
4584 if (!strcmp(pcszIDEType, "PIIX3"))
4585 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX3);
4586 else if (!strcmp(pcszIDEType, "PIIX4"))
4587 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX4);
4588 else if (!strcmp(pcszIDEType, "ICH6"))
4589 rc = pController->COMSETTER(ControllerType)(StorageControllerType_ICH6);
4590 else
4591 throw setError(VBOX_E_FILE_ERROR,
4592 tr("Invalid IDE controller type \"%s\""),
4593 pcszIDEType);
4594 if (FAILED(rc)) throw rc;
4595 }
4596
4597 /* Storage controller SATA */
4598 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSATA =
4599 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSATA);
4600 if (vsdeHDCSATA.size() > 1)
4601 throw setError(VBOX_E_FILE_ERROR,
4602 tr("Too many SATA controllers in OVF; import facility only supports one"));
4603 if (!vsdeHDCSATA.empty())
4604 {
4605 ComPtr<IStorageController> pController;
4606 const Utf8Str &hdcVBox = vsdeHDCSATA.front()->strVBoxCurrent;
4607 if (hdcVBox == "AHCI")
4608 {
4609 rc = pNewMachine->AddStorageController(Bstr("SATA").raw(),
4610 StorageBus_SATA,
4611 pController.asOutParam());
4612 if (FAILED(rc)) throw rc;
4613 }
4614 else
4615 throw setError(VBOX_E_FILE_ERROR,
4616 tr("Invalid SATA controller type \"%s\""),
4617 hdcVBox.c_str());
4618 }
4619
4620 /* Storage controller SCSI */
4621 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSCSI =
4622 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI);
4623 if (vsdeHDCSCSI.size() > 1)
4624 throw setError(VBOX_E_FILE_ERROR,
4625 tr("Too many SCSI controllers in OVF; import facility only supports one"));
4626 if (!vsdeHDCSCSI.empty())
4627 {
4628 ComPtr<IStorageController> pController;
4629 Utf8Str strName("SCSI");
4630 StorageBus_T busType = StorageBus_SCSI;
4631 StorageControllerType_T controllerType;
4632 const Utf8Str &hdcVBox = vsdeHDCSCSI.front()->strVBoxCurrent;
4633 if (hdcVBox == "LsiLogic")
4634 controllerType = StorageControllerType_LsiLogic;
4635 else if (hdcVBox == "LsiLogicSas")
4636 {
4637 // OVF treats LsiLogicSas as a SCSI controller but VBox considers it a class of its own
4638 strName = "SAS";
4639 busType = StorageBus_SAS;
4640 controllerType = StorageControllerType_LsiLogicSas;
4641 }
4642 else if (hdcVBox == "BusLogic")
4643 controllerType = StorageControllerType_BusLogic;
4644 else
4645 throw setError(VBOX_E_FILE_ERROR,
4646 tr("Invalid SCSI controller type \"%s\""),
4647 hdcVBox.c_str());
4648
4649 rc = pNewMachine->AddStorageController(Bstr(strName).raw(), busType, pController.asOutParam());
4650 if (FAILED(rc)) throw rc;
4651 rc = pController->COMSETTER(ControllerType)(controllerType);
4652 if (FAILED(rc)) throw rc;
4653 }
4654
4655 /* Storage controller SAS */
4656 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSAS =
4657 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSAS);
4658 if (vsdeHDCSAS.size() > 1)
4659 throw setError(VBOX_E_FILE_ERROR,
4660 tr("Too many SAS controllers in OVF; import facility only supports one"));
4661 if (!vsdeHDCSAS.empty())
4662 {
4663 ComPtr<IStorageController> pController;
4664 rc = pNewMachine->AddStorageController(Bstr(L"SAS").raw(),
4665 StorageBus_SAS,
4666 pController.asOutParam());
4667 if (FAILED(rc)) throw rc;
4668 rc = pController->COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas);
4669 if (FAILED(rc)) throw rc;
4670 }
4671
4672 /* Now its time to register the machine before we add any storage devices */
4673 rc = mVirtualBox->RegisterMachine(pNewMachine);
4674 if (FAILED(rc)) throw rc;
4675
4676 // store new machine for roll-back in case of errors
4677 Bstr bstrNewMachineId;
4678 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
4679 if (FAILED(rc)) throw rc;
4680 Guid uuidNewMachine(bstrNewMachineId);
4681 m->llGuidsMachinesCreated.push_back(uuidNewMachine);
4682
4683 // Add floppies and CD-ROMs to the appropriate controllers.
4684 std::list<VirtualSystemDescriptionEntry*> vsdeFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy);
4685 if (vsdeFloppy.size() > 1)
4686 throw setError(VBOX_E_FILE_ERROR,
4687 tr("Too many floppy controllers in OVF; import facility only supports one"));
4688 std::list<VirtualSystemDescriptionEntry*> vsdeCDROM = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
4689 if ( !vsdeFloppy.empty()
4690 || !vsdeCDROM.empty()
4691 )
4692 {
4693 // If there's an error here we need to close the session, so
4694 // we need another try/catch block.
4695
4696 try
4697 {
4698 // to attach things we need to open a session for the new machine
4699 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
4700 if (FAILED(rc)) throw rc;
4701 stack.fSessionOpen = true;
4702
4703 ComPtr<IMachine> sMachine;
4704 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
4705 if (FAILED(rc)) throw rc;
4706
4707 // floppy first
4708 if (vsdeFloppy.size() == 1)
4709 {
4710 ComPtr<IStorageController> pController;
4711 rc = sMachine->AddStorageController(Bstr("Floppy").raw(),
4712 StorageBus_Floppy,
4713 pController.asOutParam());
4714 if (FAILED(rc)) throw rc;
4715
4716 Bstr bstrName;
4717 rc = pController->COMGETTER(Name)(bstrName.asOutParam());
4718 if (FAILED(rc)) throw rc;
4719
4720 // this is for rollback later
4721 MyHardDiskAttachment mhda;
4722 mhda.pMachine = pNewMachine;
4723 mhda.controllerName = bstrName;
4724 mhda.lControllerPort = 0;
4725 mhda.lDevice = 0;
4726
4727 Log(("Attaching floppy\n"));
4728
4729 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),
4730 mhda.lControllerPort,
4731 mhda.lDevice,
4732 DeviceType_Floppy,
4733 NULL);
4734 if (FAILED(rc)) throw rc;
4735
4736 stack.llHardDiskAttachments.push_back(mhda);
4737 }
4738
4739 rc = sMachine->SaveSettings();
4740 if (FAILED(rc)) throw rc;
4741
4742 // only now that we're done with all storage devices, close the session
4743 rc = stack.pSession->UnlockMachine();
4744 if (FAILED(rc)) throw rc;
4745 stack.fSessionOpen = false;
4746 }
4747 catch(HRESULT aRC)
4748 {
4749 com::ErrorInfo info;
4750
4751 if (stack.fSessionOpen)
4752 stack.pSession->UnlockMachine();
4753
4754 if (info.isFullAvailable())
4755 throw setError(aRC, Utf8Str(info.getText()).c_str());
4756 else
4757 throw setError(aRC, "Unknown error during OVF import");
4758 }
4759 }
4760
4761 // create the storage devices & connect them to the appropriate controllers
4762 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
4763 if (!avsdeHDs.empty())
4764 {
4765 // If there's an error here we need to close the session, so
4766 // we need another try/catch block.
4767 try
4768 {
4769#ifdef LOG_ENABLED
4770 if (LogIsEnabled())
4771 {
4772 size_t i = 0;
4773 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4774 itHD != avsdeHDs.end(); ++itHD, i++)
4775 Log(("avsdeHDs[%zu]: strRef=%s strOvf=%s\n", i, (*itHD)->strRef.c_str(), (*itHD)->strOvf.c_str()));
4776 i = 0;
4777 for (ovf::DiskImagesMap::const_iterator itDisk = stack.mapDisks.begin(); itDisk != stack.mapDisks.end(); ++itDisk)
4778 Log(("mapDisks[%zu]: strDiskId=%s strHref=%s\n",
4779 i, itDisk->second.strDiskId.c_str(), itDisk->second.strHref.c_str()));
4780
4781 }
4782#endif
4783
4784 // to attach things we need to open a session for the new machine
4785 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
4786 if (FAILED(rc)) throw rc;
4787 stack.fSessionOpen = true;
4788
4789 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
4790 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
4791 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
4792 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
4793
4794
4795 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
4796 std::set<RTCString> disksResolvedNames;
4797
4798 uint32_t cImportedDisks = 0;
4799
4800 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
4801 {
4802/** @todo r=bird: Most of the code here is duplicated in the other machine
4803 * import method, factor out. */
4804 ovf::DiskImage diCurrent = oit->second;
4805
4806 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
4807 /* Iterate over all given images of the virtual system
4808 * description. We need to find the target image path,
4809 * which could be changed by the user. */
4810 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
4811 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4812 itHD != avsdeHDs.end();
4813 ++itHD)
4814 {
4815 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4816 if (vsdeHD->strRef == diCurrent.strDiskId)
4817 {
4818 vsdeTargetHD = vsdeHD;
4819 break;
4820 }
4821 }
4822 if (!vsdeTargetHD)
4823 {
4824 /* possible case if an image belongs to other virtual system (OVF package with multiple VMs inside) */
4825 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
4826 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
4827 NOREF(vmNameEntry);
4828 ++oit;
4829 continue;
4830 }
4831
4832 //diCurrent.strDiskId contains the image identifier (e.g. "vmdisk1"), which should exist
4833 //in the virtual system's images map under that ID and also in the global images map
4834 ovf::VirtualDisksMap::const_iterator itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
4835 if (itVDisk == vsysThis.mapVirtualDisks.end())
4836 throw setError(E_FAIL,
4837 tr("Internal inconsistency looking up disk image '%s'"),
4838 diCurrent.strHref.c_str());
4839
4840 /*
4841 * preliminary check availability of the image
4842 * This step is useful if image is placed in the OVA (TAR) package
4843 */
4844 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
4845 {
4846 /* It means that we possibly have imported the storage earlier on the previous loop steps*/
4847 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
4848 if (h != disksResolvedNames.end())
4849 {
4850 /* Yes, image name was found, we can skip it*/
4851 ++oit;
4852 continue;
4853 }
4854l_skipped:
4855 rc = i_preCheckImageAvailability(stack);
4856 if (SUCCEEDED(rc))
4857 {
4858 /* current opened file isn't the same as passed one */
4859 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
4860 {
4861 /* availableImage contains the image file reference (e.g. "disk1.vmdk"), which should
4862 * exist in the global images map.
4863 * And find the image from the OVF's disk list */
4864 ovf::DiskImagesMap::const_iterator itDiskImage;
4865 for (itDiskImage = stack.mapDisks.begin();
4866 itDiskImage != stack.mapDisks.end();
4867 itDiskImage++)
4868 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
4869 Utf8Str::CaseInsensitive) == 0)
4870 break;
4871 if (itDiskImage == stack.mapDisks.end())
4872 {
4873 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
4874 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
4875 goto l_skipped;
4876 }
4877
4878 /* replace with a new found image */
4879 diCurrent = *(&itDiskImage->second);
4880
4881 /*
4882 * Again iterate over all given images of the virtual system
4883 * description using the found image
4884 */
4885 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4886 itHD != avsdeHDs.end();
4887 ++itHD)
4888 {
4889 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4890 if (vsdeHD->strRef == diCurrent.strDiskId)
4891 {
4892 vsdeTargetHD = vsdeHD;
4893 break;
4894 }
4895 }
4896
4897 /*
4898 * in this case it's an error because something is wrong with the OVF description file.
4899 * May be VBox imports OVA package with wrong file sequence inside the archive.
4900 */
4901 if (!vsdeTargetHD)
4902 throw setError(E_FAIL,
4903 tr("Internal inconsistency looking up disk image '%s'"),
4904 diCurrent.strHref.c_str());
4905
4906 itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
4907 if (itVDisk == vsysThis.mapVirtualDisks.end())
4908 throw setError(E_FAIL,
4909 tr("Internal inconsistency looking up disk image '%s'"),
4910 diCurrent.strHref.c_str());
4911 }
4912 else
4913 {
4914 ++oit;
4915 }
4916 }
4917 else
4918 {
4919 ++oit;
4920 continue;
4921 }
4922 }
4923 else
4924 {
4925 /* just continue with normal files */
4926 ++oit;
4927 }
4928
4929 /* very important to store image name for the next checks */
4930 disksResolvedNames.insert(diCurrent.strHref);
4931////// end of duplicated code.
4932 const ovf::VirtualDisk &ovfVdisk = itVDisk->second;
4933
4934 ComObjPtr<Medium> pTargetMedium;
4935 if (stack.locInfo.storageType == VFSType_Cloud)
4936 {
4937 /* We have already all disks prepared (converted and registered in the VBox)
4938 * and in the correct place (VM machine folder).
4939 * so what is needed is to get the disk uuid from VirtualDisk::strDiskId
4940 * and find the Medium object with this uuid.
4941 * next just attach the Medium object to new VM.
4942 * VirtualDisk::strDiskId is filled in the */
4943
4944 Guid id(ovfVdisk.strDiskId);
4945 rc = mVirtualBox->i_findHardDiskById(id, false, &pTargetMedium);
4946 if (FAILED(rc))
4947 throw rc;
4948 }
4949 else
4950 {
4951 i_importOneDiskImage(diCurrent,
4952 vsdeTargetHD->strVBoxCurrent,
4953 pTargetMedium,
4954 stack);
4955 }
4956
4957 // now use the new uuid to attach the medium to our new machine
4958 ComPtr<IMachine> sMachine;
4959 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
4960 if (FAILED(rc))
4961 throw rc;
4962
4963 // find the hard disk controller to which we should attach
4964 ovf::HardDiskController hdc = (*vsysThis.mapControllers.find(ovfVdisk.idController)).second;
4965
4966 // this is for rollback later
4967 MyHardDiskAttachment mhda;
4968 mhda.pMachine = pNewMachine;
4969
4970 i_convertDiskAttachmentValues(hdc,
4971 ovfVdisk.ulAddressOnParent,
4972 mhda.controllerName,
4973 mhda.lControllerPort,
4974 mhda.lDevice);
4975
4976 Log(("Attaching disk %s to port %d on device %d\n",
4977 vsdeTargetHD->strVBoxCurrent.c_str(), mhda.lControllerPort, mhda.lDevice));
4978
4979 DeviceType_T devType = DeviceType_Null;
4980 rc = pTargetMedium->COMGETTER(DeviceType)(&devType);
4981 if (FAILED(rc))
4982 throw rc;
4983
4984 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
4985 mhda.lControllerPort, // long controllerPort
4986 mhda.lDevice, // long device
4987 devType, // DeviceType_T type
4988 pTargetMedium);
4989 if (FAILED(rc))
4990 throw rc;
4991
4992 stack.llHardDiskAttachments.push_back(mhda);
4993
4994 rc = sMachine->SaveSettings();
4995 if (FAILED(rc))
4996 throw rc;
4997
4998 ++cImportedDisks;
4999
5000 } // end while(oit != stack.mapDisks.end())
5001
5002 /*
5003 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
5004 */
5005 if(cImportedDisks < avsdeHDs.size())
5006 {
5007 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
5008 vmNameEntry->strOvf.c_str()));
5009 }
5010
5011 // only now that we're done with all disks, close the session
5012 rc = stack.pSession->UnlockMachine();
5013 if (FAILED(rc))
5014 throw rc;
5015 stack.fSessionOpen = false;
5016 }
5017 catch(HRESULT aRC)
5018 {
5019 com::ErrorInfo info;
5020 if (stack.fSessionOpen)
5021 stack.pSession->UnlockMachine();
5022
5023 if (info.isFullAvailable())
5024 throw setError(aRC, Utf8Str(info.getText()).c_str());
5025 else
5026 throw setError(aRC, "Unknown error during OVF import");
5027 }
5028 }
5029 LogFlowFuncLeave();
5030}
5031
5032/**
5033 * Imports one OVF virtual system (described by a vbox:Machine tag represented by the given config
5034 * structure) into VirtualBox by creating an IMachine instance, which is returned.
5035 *
5036 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
5037 * up any leftovers from this function. For this, the given ImportStack instance has received information
5038 * about what needs cleaning up (to support rollback).
5039 *
5040 * The machine config stored in the settings::MachineConfigFile structure contains the UUIDs of
5041 * the disk attachments used by the machine when it was exported. We also add vbox:uuid attributes
5042 * to the OVF disks sections so we can look them up. While importing these UUIDs into a second host
5043 * will most probably work, reimporting them into the same host will cause conflicts, so we always
5044 * generate new ones on import. This involves the following:
5045 *
5046 * 1) Scan the machine config for disk attachments.
5047 *
5048 * 2) For each disk attachment found, look up the OVF disk image from the disk references section
5049 * and import the disk into VirtualBox, which creates a new UUID for it. In the machine config,
5050 * replace the old UUID with the new one.
5051 *
5052 * 3) Change the machine config according to the OVF virtual system descriptions, in case the
5053 * caller has modified them using setFinalValues().
5054 *
5055 * 4) Create the VirtualBox machine with the modfified machine config.
5056 *
5057 * @param vsdescThis
5058 * @param pReturnNewMachine
5059 * @param stack
5060 */
5061void Appliance::i_importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
5062 ComPtr<IMachine> &pReturnNewMachine,
5063 ImportStack &stack)
5064{
5065 LogFlowFuncEnter();
5066 Assert(vsdescThis->m->pConfig);
5067
5068 HRESULT rc = S_OK;
5069
5070 settings::MachineConfigFile &config = *vsdescThis->m->pConfig;
5071
5072 /*
5073 * step 1): modify machine config according to OVF config, in case the user
5074 * has modified them using setFinalValues()
5075 */
5076
5077 /* OS Type */
5078 config.machineUserData.strOsType = stack.strOsTypeVBox;
5079 /* Groups */
5080 if (stack.strPrimaryGroup.isEmpty() || stack.strPrimaryGroup == "/")
5081 {
5082 config.machineUserData.llGroups.clear();
5083 config.machineUserData.llGroups.push_back("/");
5084 }
5085 else
5086 {
5087 /* Replace the primary group if there is one, otherwise add it. */
5088 if (config.machineUserData.llGroups.size())
5089 config.machineUserData.llGroups.pop_front();
5090 config.machineUserData.llGroups.push_front(stack.strPrimaryGroup);
5091 }
5092 /* Description */
5093 config.machineUserData.strDescription = stack.strDescription;
5094 /* CPU count & extented attributes */
5095 config.hardwareMachine.cCPUs = stack.cCPUs;
5096 if (stack.fForceIOAPIC)
5097 config.hardwareMachine.fHardwareVirt = true;
5098 if (stack.fForceIOAPIC)
5099 config.hardwareMachine.biosSettings.fIOAPICEnabled = true;
5100 /* RAM size */
5101 config.hardwareMachine.ulMemorySizeMB = stack.ulMemorySizeMB;
5102
5103/*
5104 <const name="HardDiskControllerIDE" value="14" />
5105 <const name="HardDiskControllerSATA" value="15" />
5106 <const name="HardDiskControllerSCSI" value="16" />
5107 <const name="HardDiskControllerSAS" value="17" />
5108*/
5109
5110#ifdef VBOX_WITH_USB
5111 /* USB controller */
5112 if (stack.fUSBEnabled)
5113 {
5114 /** @todo r=klaus add support for arbitrary USB controller types, this can't handle
5115 * multiple controllers due to its design anyway */
5116 /* Usually the OHCI controller is enabled already, need to check. But
5117 * do this only if there is no xHCI controller. */
5118 bool fOHCIEnabled = false;
5119 bool fXHCIEnabled = false;
5120 settings::USBControllerList &llUSBControllers = config.hardwareMachine.usbSettings.llUSBControllers;
5121 settings::USBControllerList::iterator it;
5122 for (it = llUSBControllers.begin(); it != llUSBControllers.end(); ++it)
5123 {
5124 if (it->enmType == USBControllerType_OHCI)
5125 fOHCIEnabled = true;
5126 if (it->enmType == USBControllerType_XHCI)
5127 fXHCIEnabled = true;
5128 }
5129
5130 if (!fXHCIEnabled && !fOHCIEnabled)
5131 {
5132 settings::USBController ctrl;
5133 ctrl.strName = "OHCI";
5134 ctrl.enmType = USBControllerType_OHCI;
5135
5136 llUSBControllers.push_back(ctrl);
5137 }
5138 }
5139 else
5140 config.hardwareMachine.usbSettings.llUSBControllers.clear();
5141#endif
5142 /* Audio adapter */
5143 if (stack.strAudioAdapter.isNotEmpty())
5144 {
5145 config.hardwareMachine.audioAdapter.fEnabled = true;
5146 config.hardwareMachine.audioAdapter.controllerType = (AudioControllerType_T)stack.strAudioAdapter.toUInt32();
5147 }
5148 else
5149 config.hardwareMachine.audioAdapter.fEnabled = false;
5150 /* Network adapter */
5151 settings::NetworkAdaptersList &llNetworkAdapters = config.hardwareMachine.llNetworkAdapters;
5152 /* First disable all network cards, they will be enabled below again. */
5153 settings::NetworkAdaptersList::iterator it1;
5154 bool fKeepAllMACs = m->optListImport.contains(ImportOptions_KeepAllMACs);
5155 bool fKeepNATMACs = m->optListImport.contains(ImportOptions_KeepNATMACs);
5156 for (it1 = llNetworkAdapters.begin(); it1 != llNetworkAdapters.end(); ++it1)
5157 {
5158 it1->fEnabled = false;
5159 if (!( fKeepAllMACs
5160 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NAT)
5161 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NATNetwork)))
5162 /* Force generation of new MAC address below. */
5163 it1->strMACAddress.setNull();
5164 }
5165 /* Now iterate over all network entries. */
5166 std::list<VirtualSystemDescriptionEntry*> avsdeNWs = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
5167 if (!avsdeNWs.empty())
5168 {
5169 /* Iterate through all network adapter entries and search for the
5170 * corresponding one in the machine config. If one is found, configure
5171 * it based on the user settings. */
5172 list<VirtualSystemDescriptionEntry*>::const_iterator itNW;
5173 for (itNW = avsdeNWs.begin();
5174 itNW != avsdeNWs.end();
5175 ++itNW)
5176 {
5177 VirtualSystemDescriptionEntry *vsdeNW = *itNW;
5178 if ( vsdeNW->strExtraConfigCurrent.startsWith("slot=", Utf8Str::CaseInsensitive)
5179 && vsdeNW->strExtraConfigCurrent.length() > 6)
5180 {
5181 uint32_t iSlot = vsdeNW->strExtraConfigCurrent.substr(5).toUInt32();
5182 /* Iterate through all network adapters in the machine config. */
5183 for (it1 = llNetworkAdapters.begin();
5184 it1 != llNetworkAdapters.end();
5185 ++it1)
5186 {
5187 /* Compare the slots. */
5188 if (it1->ulSlot == iSlot)
5189 {
5190 it1->fEnabled = true;
5191 if (it1->strMACAddress.isEmpty())
5192 Host::i_generateMACAddress(it1->strMACAddress);
5193 it1->type = (NetworkAdapterType_T)vsdeNW->strVBoxCurrent.toUInt32();
5194 break;
5195 }
5196 }
5197 }
5198 }
5199 }
5200
5201 /* Floppy controller */
5202 bool fFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy).size() > 0;
5203 /* DVD controller */
5204 bool fDVD = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM).size() > 0;
5205 /* Iterate over all storage controller check the attachments and remove
5206 * them when necessary. Also detect broken configs with more than one
5207 * attachment. Old VirtualBox versions (prior to 3.2.10) had all disk
5208 * attachments pointing to the last hard disk image, which causes import
5209 * failures. A long fixed bug, however the OVF files are long lived. */
5210 settings::StorageControllersList &llControllers = config.hardwareMachine.storage.llStorageControllers;
5211 Guid hdUuid;
5212 uint32_t cDisks = 0;
5213 bool fInconsistent = false;
5214 bool fRepairDuplicate = false;
5215 settings::StorageControllersList::iterator it3;
5216 for (it3 = llControllers.begin();
5217 it3 != llControllers.end();
5218 ++it3)
5219 {
5220 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
5221 settings::AttachedDevicesList::iterator it4 = llAttachments.begin();
5222 while (it4 != llAttachments.end())
5223 {
5224 if ( ( !fDVD
5225 && it4->deviceType == DeviceType_DVD)
5226 ||
5227 ( !fFloppy
5228 && it4->deviceType == DeviceType_Floppy))
5229 {
5230 it4 = llAttachments.erase(it4);
5231 continue;
5232 }
5233 else if (it4->deviceType == DeviceType_HardDisk)
5234 {
5235 const Guid &thisUuid = it4->uuid;
5236 cDisks++;
5237 if (cDisks == 1)
5238 {
5239 if (hdUuid.isZero())
5240 hdUuid = thisUuid;
5241 else
5242 fInconsistent = true;
5243 }
5244 else
5245 {
5246 if (thisUuid.isZero())
5247 fInconsistent = true;
5248 else if (thisUuid == hdUuid)
5249 fRepairDuplicate = true;
5250 }
5251 }
5252 ++it4;
5253 }
5254 }
5255 /* paranoia... */
5256 if (fInconsistent || cDisks == 1)
5257 fRepairDuplicate = false;
5258
5259 /*
5260 * step 2: scan the machine config for media attachments
5261 */
5262 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
5263 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
5264 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
5265 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
5266
5267 /* Get all hard disk descriptions. */
5268 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
5269 std::list<VirtualSystemDescriptionEntry*>::iterator avsdeHDsIt = avsdeHDs.begin();
5270 /* paranoia - if there is no 1:1 match do not try to repair. */
5271 if (cDisks != avsdeHDs.size())
5272 fRepairDuplicate = false;
5273
5274 // there must be an image in the OVF disk structs with the same UUID
5275
5276 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
5277 std::set<RTCString> disksResolvedNames;
5278
5279 uint32_t cImportedDisks = 0;
5280
5281 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
5282 {
5283/** @todo r=bird: Most of the code here is duplicated in the other machine
5284 * import method, factor out. */
5285 ovf::DiskImage diCurrent = oit->second;
5286
5287 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
5288
5289 /* Iterate over all given disk images of the virtual system
5290 * disks description. We need to find the target disk path,
5291 * which could be changed by the user. */
5292 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
5293 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
5294 itHD != avsdeHDs.end();
5295 ++itHD)
5296 {
5297 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
5298 if (vsdeHD->strRef == oit->first)
5299 {
5300 vsdeTargetHD = vsdeHD;
5301 break;
5302 }
5303 }
5304 if (!vsdeTargetHD)
5305 {
5306 /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
5307 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
5308 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
5309 NOREF(vmNameEntry);
5310 ++oit;
5311 continue;
5312 }
5313
5314
5315
5316
5317
5318
5319
5320
5321
5322 /*
5323 * preliminary check availability of the image
5324 * This step is useful if image is placed in the OVA (TAR) package
5325 */
5326 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
5327 {
5328 /* It means that we possibly have imported the storage earlier on a previous loop step. */
5329 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
5330 if (h != disksResolvedNames.end())
5331 {
5332 /* Yes, disk name was found, we can skip it*/
5333 ++oit;
5334 continue;
5335 }
5336l_skipped:
5337 rc = i_preCheckImageAvailability(stack);
5338 if (SUCCEEDED(rc))
5339 {
5340 /* current opened file isn't the same as passed one */
5341 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
5342 {
5343 // availableImage contains the disk identifier (e.g. "vmdisk1"), which should exist
5344 // in the virtual system's disks map under that ID and also in the global images map
5345 // and find the disk from the OVF's disk list
5346 ovf::DiskImagesMap::const_iterator itDiskImage;
5347 for (itDiskImage = stack.mapDisks.begin();
5348 itDiskImage != stack.mapDisks.end();
5349 itDiskImage++)
5350 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
5351 Utf8Str::CaseInsensitive) == 0)
5352 break;
5353 if (itDiskImage == stack.mapDisks.end())
5354 {
5355 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
5356 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
5357 goto l_skipped;
5358 }
5359 //throw setError(E_FAIL,
5360 // tr("Internal inconsistency looking up disk image '%s'. "
5361 // "Check compliance OVA package structure and file names "
5362 // "references in the section <References> in the OVF file."),
5363 // stack.pszOvaLookAheadName);
5364
5365 /* replace with a new found disk image */
5366 diCurrent = *(&itDiskImage->second);
5367
5368 /*
5369 * Again iterate over all given disk images of the virtual system
5370 * disks description using the found disk image
5371 */
5372 vsdeTargetHD = NULL;
5373 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
5374 itHD != avsdeHDs.end();
5375 ++itHD)
5376 {
5377 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
5378 if (vsdeHD->strRef == diCurrent.strDiskId)
5379 {
5380 vsdeTargetHD = vsdeHD;
5381 break;
5382 }
5383 }
5384
5385 /*
5386 * in this case it's an error because something is wrong with the OVF description file.
5387 * May be VBox imports OVA package with wrong file sequence inside the archive.
5388 */
5389 if (!vsdeTargetHD)
5390 throw setError(E_FAIL,
5391 tr("Internal inconsistency looking up disk image '%s'"),
5392 diCurrent.strHref.c_str());
5393
5394
5395
5396
5397
5398 }
5399 else
5400 {
5401 ++oit;
5402 }
5403 }
5404 else
5405 {
5406 ++oit;
5407 continue;
5408 }
5409 }
5410 else
5411 {
5412 /* just continue with normal files*/
5413 ++oit;
5414 }
5415
5416 /* Important! to store disk name for the next checks */
5417 disksResolvedNames.insert(diCurrent.strHref);
5418////// end of duplicated code.
5419 // there must be an image in the OVF disk structs with the same UUID
5420 bool fFound = false;
5421 Utf8Str strUuid;
5422
5423 // for each storage controller...
5424 for (settings::StorageControllersList::iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
5425 sit != config.hardwareMachine.storage.llStorageControllers.end();
5426 ++sit)
5427 {
5428 settings::StorageController &sc = *sit;
5429
5430 // for each medium attachment to this controller...
5431 for (settings::AttachedDevicesList::iterator dit = sc.llAttachedDevices.begin();
5432 dit != sc.llAttachedDevices.end();
5433 ++dit)
5434 {
5435 settings::AttachedDevice &d = *dit;
5436
5437 if (d.uuid.isZero())
5438 // empty DVD and floppy media
5439 continue;
5440
5441 // When repairing a broken VirtualBox xml config section (written
5442 // by VirtualBox versions earlier than 3.2.10) assume the disks
5443 // show up in the same order as in the OVF description.
5444 if (fRepairDuplicate)
5445 {
5446 VirtualSystemDescriptionEntry *vsdeHD = *avsdeHDsIt;
5447 ovf::DiskImagesMap::const_iterator itDiskImage = stack.mapDisks.find(vsdeHD->strRef);
5448 if (itDiskImage != stack.mapDisks.end())
5449 {
5450 const ovf::DiskImage &di = itDiskImage->second;
5451 d.uuid = Guid(di.uuidVBox);
5452 }
5453 ++avsdeHDsIt;
5454 }
5455
5456 // convert the Guid to string
5457 strUuid = d.uuid.toString();
5458
5459 if (diCurrent.uuidVBox != strUuid)
5460 {
5461 continue;
5462 }
5463
5464 /*
5465 * step 3: import disk
5466 */
5467 ComObjPtr<Medium> pTargetMedium;
5468 i_importOneDiskImage(diCurrent,
5469 vsdeTargetHD->strVBoxCurrent,
5470 pTargetMedium,
5471 stack);
5472
5473 // ... and replace the old UUID in the machine config with the one of
5474 // the imported disk that was just created
5475 Bstr hdId;
5476 rc = pTargetMedium->COMGETTER(Id)(hdId.asOutParam());
5477 if (FAILED(rc)) throw rc;
5478
5479 /*
5480 * 1. saving original UUID for restoring in case of failure.
5481 * 2. replacement of original UUID by new UUID in the current VM config (settings::MachineConfigFile).
5482 */
5483 {
5484 rc = stack.saveOriginalUUIDOfAttachedDevice(d, Utf8Str(hdId));
5485 d.uuid = hdId;
5486 }
5487
5488 fFound = true;
5489 break;
5490 } // for (settings::AttachedDevicesList::const_iterator dit = sc.llAttachedDevices.begin();
5491 } // for (settings::StorageControllersList::const_iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
5492
5493 // no disk with such a UUID found:
5494 if (!fFound)
5495 throw setError(E_FAIL,
5496 tr("<vbox:Machine> element in OVF contains a medium attachment for the disk image %s "
5497 "but the OVF describes no such image"),
5498 strUuid.c_str());
5499
5500 ++cImportedDisks;
5501
5502 }// while(oit != stack.mapDisks.end())
5503
5504
5505 /*
5506 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
5507 */
5508 if(cImportedDisks < avsdeHDs.size())
5509 {
5510 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
5511 vmNameEntry->strOvf.c_str()));
5512 }
5513
5514 /*
5515 * step 4): create the machine and have it import the config
5516 */
5517
5518 ComObjPtr<Machine> pNewMachine;
5519 rc = pNewMachine.createObject();
5520 if (FAILED(rc)) throw rc;
5521
5522 // this magic constructor fills the new machine object with the MachineConfig
5523 // instance that we created from the vbox:Machine
5524 rc = pNewMachine->init(mVirtualBox,
5525 stack.strNameVBox,// name from OVF preparations; can be suffixed to avoid duplicates
5526 stack.strSettingsFilename,
5527 config); // the whole machine config
5528 if (FAILED(rc)) throw rc;
5529
5530 pReturnNewMachine = ComPtr<IMachine>(pNewMachine);
5531
5532 // and register it
5533 rc = mVirtualBox->RegisterMachine(pNewMachine);
5534 if (FAILED(rc)) throw rc;
5535
5536 // store new machine for roll-back in case of errors
5537 Bstr bstrNewMachineId;
5538 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
5539 if (FAILED(rc)) throw rc;
5540 m->llGuidsMachinesCreated.push_back(Guid(bstrNewMachineId));
5541
5542 LogFlowFuncLeave();
5543}
5544
5545/**
5546 * @throws HRESULT errors.
5547 */
5548void Appliance::i_importMachines(ImportStack &stack)
5549{
5550 // this is safe to access because this thread only gets started
5551 const ovf::OVFReader &reader = *m->pReader;
5552
5553 // create a session for the machine + disks we manipulate below
5554 HRESULT rc = stack.pSession.createInprocObject(CLSID_Session);
5555 ComAssertComRCThrowRC(rc);
5556
5557 list<ovf::VirtualSystem>::const_iterator it;
5558 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it1;
5559 /* Iterate through all virtual systems of that appliance */
5560 size_t i = 0;
5561 for (it = reader.m_llVirtualSystems.begin(), it1 = m->virtualSystemDescriptions.begin();
5562 it != reader.m_llVirtualSystems.end() && it1 != m->virtualSystemDescriptions.end();
5563 ++it, ++it1, ++i)
5564 {
5565 const ovf::VirtualSystem &vsysThis = *it;
5566 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it1);
5567
5568 ComPtr<IMachine> pNewMachine;
5569
5570 // there are two ways in which we can create a vbox machine from OVF:
5571 // -- either this OVF was written by vbox 3.2 or later, in which case there is a <vbox:Machine> element
5572 // in the <VirtualSystem>; then the VirtualSystemDescription::Data has a settings::MachineConfigFile
5573 // with all the machine config pretty-parsed;
5574 // -- or this is an OVF from an older vbox or an external source, and then we need to translate the
5575 // VirtualSystemDescriptionEntry and do import work
5576
5577 // Even for the vbox:Machine case, there are a number of configuration items that will be taken from
5578 // the OVF because otherwise the "override import parameters" mechanism in the GUI won't work.
5579
5580 // VM name
5581 std::list<VirtualSystemDescriptionEntry*> vsdeName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
5582 if (vsdeName.size() < 1)
5583 throw setError(VBOX_E_FILE_ERROR,
5584 tr("Missing VM name"));
5585 stack.strNameVBox = vsdeName.front()->strVBoxCurrent;
5586
5587 // Primary group, which is entirely optional.
5588 stack.strPrimaryGroup.setNull();
5589 std::list<VirtualSystemDescriptionEntry*> vsdePrimaryGroup = vsdescThis->i_findByType(VirtualSystemDescriptionType_PrimaryGroup);
5590 if (vsdePrimaryGroup.size() >= 1)
5591 {
5592 stack.strPrimaryGroup = vsdePrimaryGroup.front()->strVBoxCurrent;
5593 if (stack.strPrimaryGroup.isEmpty())
5594 stack.strPrimaryGroup = "/";
5595 }
5596
5597 // Draw the right conclusions from the (possibly modified) VM settings
5598 // file name and base folder. If the VM settings file name is modified,
5599 // it takes precedence, otherwise it is recreated from the base folder
5600 // and the primary group.
5601 stack.strSettingsFilename.setNull();
5602 std::list<VirtualSystemDescriptionEntry*> vsdeSettingsFile = vsdescThis->i_findByType(VirtualSystemDescriptionType_SettingsFile);
5603 if (vsdeSettingsFile.size() >= 1)
5604 {
5605 VirtualSystemDescriptionEntry *vsdeSF1 = vsdeSettingsFile.front();
5606 if (vsdeSF1->strVBoxCurrent != vsdeSF1->strVBoxSuggested)
5607 stack.strSettingsFilename = vsdeSF1->strVBoxCurrent;
5608 }
5609 if (stack.strSettingsFilename.isEmpty())
5610 {
5611 Utf8Str strBaseFolder;
5612 std::list<VirtualSystemDescriptionEntry*> vsdeBaseFolder = vsdescThis->i_findByType(VirtualSystemDescriptionType_BaseFolder);
5613 if (vsdeBaseFolder.size() >= 1)
5614 strBaseFolder = vsdeBaseFolder.front()->strVBoxCurrent;
5615 Bstr bstrSettingsFilename;
5616 rc = mVirtualBox->ComposeMachineFilename(Bstr(stack.strNameVBox).raw(),
5617 Bstr(stack.strPrimaryGroup).raw(),
5618 NULL /* aCreateFlags */,
5619 Bstr(strBaseFolder).raw(),
5620 bstrSettingsFilename.asOutParam());
5621 if (FAILED(rc)) throw rc;
5622 stack.strSettingsFilename = bstrSettingsFilename;
5623 }
5624
5625 // Determine the machine folder from the settings file.
5626 LogFunc(("i=%zu strName=%s strSettingsFilename=%s\n", i, stack.strNameVBox.c_str(), stack.strSettingsFilename.c_str()));
5627 stack.strMachineFolder = stack.strSettingsFilename;
5628 stack.strMachineFolder.stripFilename();
5629
5630 // guest OS type
5631 std::list<VirtualSystemDescriptionEntry*> vsdeOS;
5632 vsdeOS = vsdescThis->i_findByType(VirtualSystemDescriptionType_OS);
5633 if (vsdeOS.size() < 1)
5634 throw setError(VBOX_E_FILE_ERROR,
5635 tr("Missing guest OS type"));
5636 stack.strOsTypeVBox = vsdeOS.front()->strVBoxCurrent;
5637
5638 // Firmware
5639 std::list<VirtualSystemDescriptionEntry*> firmware = vsdescThis->i_findByType(VirtualSystemDescriptionType_BootingFirmware);
5640 if (firmware.size() != 1)
5641 stack.strFirmwareType = "BIOS";//try default BIOS type
5642 else
5643 stack.strFirmwareType = firmware.front()->strVBoxCurrent;
5644
5645 // CPU count
5646 std::list<VirtualSystemDescriptionEntry*> vsdeCPU = vsdescThis->i_findByType(VirtualSystemDescriptionType_CPU);
5647 if (vsdeCPU.size() != 1)
5648 throw setError(VBOX_E_FILE_ERROR, tr("CPU count missing"));
5649
5650 stack.cCPUs = vsdeCPU.front()->strVBoxCurrent.toUInt32();
5651 // We need HWVirt & IO-APIC if more than one CPU is requested
5652 if (stack.cCPUs > 1)
5653 {
5654 stack.fForceHWVirt = true;
5655 stack.fForceIOAPIC = true;
5656 }
5657
5658 // RAM
5659 std::list<VirtualSystemDescriptionEntry*> vsdeRAM = vsdescThis->i_findByType(VirtualSystemDescriptionType_Memory);
5660 if (vsdeRAM.size() != 1)
5661 throw setError(VBOX_E_FILE_ERROR, tr("RAM size missing"));
5662 stack.ulMemorySizeMB = (ULONG)vsdeRAM.front()->strVBoxCurrent.toUInt64();
5663
5664#ifdef VBOX_WITH_USB
5665 // USB controller
5666 std::list<VirtualSystemDescriptionEntry*> vsdeUSBController =
5667 vsdescThis->i_findByType(VirtualSystemDescriptionType_USBController);
5668 // USB support is enabled if there's at least one such entry; to disable USB support,
5669 // the type of the USB item would have been changed to "ignore"
5670 stack.fUSBEnabled = !vsdeUSBController.empty();
5671#endif
5672 // audio adapter
5673 std::list<VirtualSystemDescriptionEntry*> vsdeAudioAdapter =
5674 vsdescThis->i_findByType(VirtualSystemDescriptionType_SoundCard);
5675 /** @todo we support one audio adapter only */
5676 if (!vsdeAudioAdapter.empty())
5677 stack.strAudioAdapter = vsdeAudioAdapter.front()->strVBoxCurrent;
5678
5679 // for the description of the new machine, always use the OVF entry, the user may have changed it in the import config
5680 std::list<VirtualSystemDescriptionEntry*> vsdeDescription =
5681 vsdescThis->i_findByType(VirtualSystemDescriptionType_Description);
5682 if (!vsdeDescription.empty())
5683 stack.strDescription = vsdeDescription.front()->strVBoxCurrent;
5684
5685 // import vbox:machine or OVF now
5686 if (vsdescThis->m->pConfig)
5687 // vbox:Machine config
5688 i_importVBoxMachine(vsdescThis, pNewMachine, stack);
5689 else
5690 // generic OVF config
5691 i_importMachineGeneric(vsysThis, vsdescThis, pNewMachine, stack);
5692
5693 } // for (it = pAppliance->m->llVirtualSystems.begin() ...
5694}
5695
5696HRESULT Appliance::ImportStack::saveOriginalUUIDOfAttachedDevice(settings::AttachedDevice &device,
5697 const Utf8Str &newlyUuid)
5698{
5699 HRESULT rc = S_OK;
5700
5701 /* save for restoring */
5702 mapNewUUIDsToOriginalUUIDs.insert(std::make_pair(newlyUuid, device.uuid.toString()));
5703
5704 return rc;
5705}
5706
5707HRESULT Appliance::ImportStack::restoreOriginalUUIDOfAttachedDevice(settings::MachineConfigFile *config)
5708{
5709 HRESULT rc = S_OK;
5710
5711 settings::StorageControllersList &llControllers = config->hardwareMachine.storage.llStorageControllers;
5712 settings::StorageControllersList::iterator itscl;
5713 for (itscl = llControllers.begin();
5714 itscl != llControllers.end();
5715 ++itscl)
5716 {
5717 settings::AttachedDevicesList &llAttachments = itscl->llAttachedDevices;
5718 settings::AttachedDevicesList::iterator itadl = llAttachments.begin();
5719 while (itadl != llAttachments.end())
5720 {
5721 std::map<Utf8Str , Utf8Str>::iterator it =
5722 mapNewUUIDsToOriginalUUIDs.find(itadl->uuid.toString());
5723 if(it!=mapNewUUIDsToOriginalUUIDs.end())
5724 {
5725 Utf8Str uuidOriginal = it->second;
5726 itadl->uuid = Guid(uuidOriginal);
5727 mapNewUUIDsToOriginalUUIDs.erase(it->first);
5728 }
5729 ++itadl;
5730 }
5731 }
5732
5733 return rc;
5734}
5735
5736/**
5737 * @throws Nothing
5738 */
5739RTVFSIOSTREAM Appliance::ImportStack::claimOvaLookAHead(void)
5740{
5741 RTVFSIOSTREAM hVfsIos = this->hVfsIosOvaLookAhead;
5742 this->hVfsIosOvaLookAhead = NIL_RTVFSIOSTREAM;
5743 /* We don't free the name since it may be referenced in error messages and such. */
5744 return hVfsIos;
5745}
5746
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