VirtualBox

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

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

bugref:9416. Used RTRandU64() instead the class com::Guid to generate suffix for VM name.

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