VirtualBox

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

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

trancate the imported machine name. Use only the last 12 characters from GUID string.

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