VirtualBox

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

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

Main/Appliance: Implemented verifying PKCS#7/CMS signing certificates. Needs proper testing, though. bugref:9699

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 250.6 KB
Line 
1/* $Id: ApplianceImplImport.cpp 84340 2020-05-18 17:37:30Z vboxsync $ */
2/** @file
3 * IAppliance and IVirtualSystem COM class implementations.
4 */
5
6/*
7 * Copyright (C) 2008-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_MAIN_APPLIANCE
19#include <iprt/alloca.h>
20#include <iprt/path.h>
21#include <iprt/cpp/path.h>
22#include <iprt/dir.h>
23#include <iprt/file.h>
24#include <iprt/s3.h>
25#include <iprt/sha.h>
26#include <iprt/manifest.h>
27#include <iprt/tar.h>
28#include <iprt/zip.h>
29#include <iprt/stream.h>
30#include <iprt/crypto/digest.h>
31#include <iprt/crypto/pkix.h>
32#include <iprt/crypto/store.h>
33#include <iprt/crypto/x509.h>
34
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 strVMName.append("__").append(newId.toString());
1434 vsd->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
1435 vsd->AddDescription(VirtualSystemDescriptionType_Name,
1436 Bstr(strVMName).raw(),
1437 NULL);
1438 /* No check again because it would be weird if a VM with such unique name exists */
1439 }
1440
1441 /* Check the target path. If the path exists and folder isn't empty return an error */
1442 {
1443 Bstr bstrSettingsFilename;
1444 /* Based on the VM name, create a target machine path. */
1445 hrc = mVirtualBox->ComposeMachineFilename(Bstr(strVMName).raw(),
1446 Bstr("/").raw(),
1447 NULL /* aCreateFlags */,
1448 NULL /* aBaseFolder */,
1449 bstrSettingsFilename.asOutParam());
1450 if (FAILED(hrc))
1451 break;
1452
1453 Utf8Str strMachineFolder(bstrSettingsFilename);
1454 strMachineFolder.stripFilename();
1455
1456 RTFSOBJINFO dirInfo;
1457 vrc = RTPathQueryInfo(strMachineFolder.c_str(), &dirInfo, RTFSOBJATTRADD_NOTHING);
1458 if (RT_SUCCESS(vrc))
1459 {
1460 size_t counter = 0;
1461 RTDIR hDir;
1462 vrc = RTDirOpen(&hDir, strMachineFolder.c_str());
1463 if (RT_SUCCESS(vrc))
1464 {
1465 RTDIRENTRY DirEntry;
1466 while (RT_SUCCESS(RTDirRead(hDir, &DirEntry, NULL)))
1467 {
1468 if (RTDirEntryIsStdDotLink(&DirEntry))
1469 continue;
1470 ++counter;
1471 }
1472
1473 if ( hDir != NULL)
1474 vrc = RTDirClose(hDir);
1475 }
1476 else
1477 return setErrorVrc(vrc, tr("Can't open folder %s"), strMachineFolder.c_str());
1478
1479 if (counter > 0)
1480 {
1481 return setErrorVrc(VERR_ALREADY_EXISTS, tr("The target folder %s has already contained some"
1482 " files (%d items). Clear the folder from the files or choose another folder"),
1483 strMachineFolder.c_str(), counter);
1484 }
1485 }
1486 }
1487
1488 Utf8Str strInsId;
1489 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId)//aVBoxValues is set in this #define
1490 if (aVBoxValues.size() == 0)
1491 return setErrorVrc(VERR_NOT_FOUND, "%s: Cloud Instance Id wasn't found", __FUNCTION__);
1492
1493 strInsId = aVBoxValues[0];
1494
1495 LogRel(("%s: calling CloudClient::ImportInstance\n", __FUNCTION__));
1496
1497 /* Here it's strongly supposed that cloud import produces ONE object on the disk.
1498 * Because it much easier to manage one object in any case.
1499 * In the case when cloud import creates several object on the disk all of them
1500 * must be combined together into one object by cloud client.
1501 * The most simple way is to create a TAR archive. */
1502 hrc = cloudClient->ImportInstance(m->virtualSystemDescriptions.front(),
1503 pProgress);
1504 if (FAILED(hrc))
1505 {
1506 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (cloud phase) failed. "
1507 "Used cloud instance is \'%s\'\n", __FUNCTION__, strInsId.c_str());
1508
1509 LogRel((strLastActualErrorDesc.c_str()));
1510 hrc = setError(hrc, strLastActualErrorDesc.c_str());
1511 break;
1512 }
1513
1514 } while (0);
1515 }
1516 else
1517 {
1518 hrc = setErrorVrc(VERR_NOT_SUPPORTED, tr("Import from Cloud isn't supported for more than one VM instance."));
1519 return hrc;
1520 }
1521
1522
1523 HRESULT original_hrc = hrc;//save the original result
1524
1525 /* In any case we delete the cloud leavings which may exist after the first phase (cloud phase).
1526 * Should they be deleted in the OCICloudClient::importInstance()?
1527 * Because deleting them here is not easy as it in the importInstance(). */
1528 {
1529 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId)//aVBoxValues is set in this #define
1530 if (aVBoxValues.size() == 0)
1531 hrc = setErrorVrc(VERR_NOT_FOUND, tr("%s: Cloud cleanup action - the instance wasn't found"), __FUNCTION__);
1532 else
1533 {
1534 vsdData = aVBoxValues[0];
1535
1536 /** @todo
1537 * future function which will eliminate the temporary objects created during the first phase.
1538 * hrc = cloud.EliminateImportLeavings(aVBoxValues[0], pProgress); */
1539/*
1540 if (FAILED(hrc))
1541 {
1542 hrc = setError(hrc, tr("Some leavings may exist in the Cloud."));
1543 LogRel(("%s: Cleanup action - the leavings in the %s after import the "
1544 "instance %s may not have been deleted\n",
1545 __FUNCTION__, strProviderName.c_str(), vsdData.c_str()));
1546 }
1547 else
1548 LogRel(("%s: Cleanup action - the leavings in the %s after import the "
1549 "instance %s have been deleted\n",
1550 __FUNCTION__, strProviderName.c_str(), vsdData.c_str()));
1551*/
1552 }
1553
1554 /* Because during the cleanup phase the hrc may have the good result
1555 * Thus we restore the original error in the case when the cleanup phase was successful
1556 * Otherwise we return not the original error but the last error in the cleanup phase */
1557 hrc = original_hrc;
1558 }
1559
1560 if (FAILED(hrc))
1561 {
1562 Utf8Str generalRollBackErrorMessage("Rollback action for Import Cloud operation failed. "
1563 "Some leavings may exist on the local disk or in the Cloud.");
1564 /*
1565 * Roll-back actions.
1566 * we finish here if:
1567 * 1. Getting the object from the Cloud has been failed.
1568 * 2. Something is wrong with getting data from ComPtr<IVirtualSystemDescription> vsd.
1569 * 3. More than 1 VirtualSystemDescription is presented in the list m->virtualSystemDescriptions.
1570 * Maximum what we have there are:
1571 * 1. The downloaded object, so just check the presence and delete it if one exists
1572 */
1573
1574 {
1575 if (!fKeepDownloadedObject)
1576 {
1577 /* small explanation here, the image here points out to the whole downloaded object (not to the image only)
1578 * filled during the first cloud import stage (in the ICloudClient::importInstance()) */
1579 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage)//aVBoxValues is set in this #define
1580 if (aVBoxValues.size() == 0)
1581 hrc = setErrorVrc(VERR_NOT_FOUND, generalRollBackErrorMessage.c_str());
1582 else
1583 {
1584 vsdData = aVBoxValues[0];
1585 //try to delete the downloaded object
1586 bool fExist = RTPathExists(vsdData.c_str());
1587 if (fExist)
1588 {
1589 vrc = RTFileDelete(vsdData.c_str());
1590 if (RT_FAILURE(vrc))
1591 {
1592 hrc = setErrorVrc(vrc, generalRollBackErrorMessage.c_str());
1593 LogRel(("%s: Rollback action - the object %s hasn't been deleted\n", __FUNCTION__, vsdData.c_str()));
1594 }
1595 else
1596 LogRel(("%s: Rollback action - the object %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
1597 }
1598 }
1599 }
1600 }
1601
1602 /* Because during the rollback phase the hrc may have the good result
1603 * Thus we restore the original error in the case when the rollback phase was successful
1604 * Otherwise we return not the original error but the last error in the rollback phase */
1605 hrc = original_hrc;
1606 }
1607 else
1608 {
1609 Utf8Str strMachineFolder;
1610 Utf8Str strAbsSrcPath;
1611 Utf8Str strGroup("/");//default VM group
1612 Utf8Str strTargetFormat("VMDK");//default image format
1613 Bstr bstrSettingsFilename;
1614 SystemProperties *pSysProps = NULL;
1615 RTCList<Utf8Str> extraCreatedFiles;/* All extra created files, it's used during cleanup */
1616
1617 /* Put all VFS* declaration here because they are needed to be release by the corresponding
1618 RTVfs***Release functions in the case of exception */
1619 RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
1620 RTVFSFSSTREAM hVfsFssObject = NIL_RTVFSFSSTREAM;
1621 RTVFSIOSTREAM hVfsIosCurr = NIL_RTVFSIOSTREAM;
1622
1623 try
1624 {
1625 /* Small explanation here, the image here points out to the whole downloaded object (not to the image only)
1626 * filled during the first cloud import stage (in the ICloudClient::importInstance()) */
1627 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage)//aVBoxValues is set in this #define
1628 if (aVBoxValues.size() == 0)
1629 throw setErrorVrc(VERR_NOT_FOUND, "%s: The description of the downloaded object wasn't found", __FUNCTION__);
1630
1631 strAbsSrcPath = aVBoxValues[0];
1632
1633 /* Based on the VM name, create a target machine path. */
1634 hrc = mVirtualBox->ComposeMachineFilename(Bstr(strVMName).raw(),
1635 Bstr(strGroup).raw(),
1636 NULL /* aCreateFlags */,
1637 NULL /* aBaseFolder */,
1638 bstrSettingsFilename.asOutParam());
1639 if (FAILED(hrc)) throw hrc;
1640
1641 strMachineFolder = bstrSettingsFilename;
1642 strMachineFolder.stripFilename();
1643
1644 /* Get the system properties. */
1645 pSysProps = mVirtualBox->i_getSystemProperties();
1646 if (pSysProps == NULL)
1647 throw VBOX_E_OBJECT_NOT_FOUND;
1648
1649 ComObjPtr<MediumFormat> trgFormat;
1650 trgFormat = pSysProps->i_mediumFormatFromExtension(strTargetFormat);
1651 if (trgFormat.isNull())
1652 throw VBOX_E_OBJECT_NOT_FOUND;
1653
1654 /* Continue and create new VM using data from VSD and downloaded object.
1655 * The downloaded images should be converted to VDI/VMDK if they have another format */
1656 Utf8Str strInstId("default cloud instance id");
1657 {
1658 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId)//aVBoxValues is set in this #define
1659 if (aVBoxValues.size() != 0)//paranoia but anyway...
1660 strInstId = aVBoxValues[0];
1661 LogRel(("%s: Importing cloud instance %s\n", __FUNCTION__, strInstId.c_str()));
1662 }
1663
1664 /* Processing the downloaded object (prepare for the local import) */
1665 RTVFSIOSTREAM hVfsIosSrc;
1666 vrc = RTVfsIoStrmOpenNormal(strAbsSrcPath.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosSrc);
1667 if (RT_FAILURE(vrc))
1668 {
1669 strLastActualErrorDesc = Utf8StrFmt("Error opening '%s' for reading (%Rrc)\n", strAbsSrcPath.c_str(), vrc);
1670 throw setErrorVrc(vrc, strLastActualErrorDesc.c_str());
1671 }
1672
1673 vrc = RTZipTarFsStreamFromIoStream(hVfsIosSrc, 0 /*fFlags*/, &hVfsFssObject);
1674 RTVfsIoStrmRelease(hVfsIosSrc);
1675 if (RT_FAILURE(vrc))
1676 {
1677 strLastActualErrorDesc = Utf8StrFmt("Error reading the downloaded file '%s' (%Rrc)", strAbsSrcPath.c_str(), vrc);
1678 throw setErrorVrc(vrc, strLastActualErrorDesc.c_str());
1679 }
1680
1681 /* Create a new virtual system and work directly on the list copy. */
1682 m->pReader->m_llVirtualSystems.push_back(ovf::VirtualSystem());
1683 ovf::VirtualSystem &vsys = m->pReader->m_llVirtualSystems.back();
1684
1685 /* Try to re-use some OVF stuff here */
1686 {
1687 vsys.strName = strVMName;
1688 uint32_t cpus = 1;
1689 {
1690 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CPU)//aVBoxValues is set in this #define
1691 if (aVBoxValues.size() != 0)
1692 {
1693 vsdData = aVBoxValues[0];
1694 cpus = vsdData.toUInt32();
1695 }
1696 vsys.cCPUs = (uint16_t)cpus;
1697 LogRel(("%s: Number of CPUs is %s\n", __FUNCTION__, vsdData.c_str()));
1698 }
1699
1700 ULONG memory;//Mb
1701 pGuestOSType->COMGETTER(RecommendedRAM)(&memory);
1702 {
1703 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Memory)//aVBoxValues is set in this #define
1704 if (aVBoxValues.size() != 0)
1705 {
1706 vsdData = aVBoxValues[0];
1707 if (memory > vsdData.toUInt32())
1708 memory = vsdData.toUInt32();
1709 }
1710 vsys.ullMemorySize = memory;
1711 LogRel(("%s: Size of RAM is %d MB\n", __FUNCTION__, vsys.ullMemorySize));
1712 }
1713
1714 {
1715 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Description)//aVBoxValues is set in this #define
1716 if (aVBoxValues.size() != 0)
1717 {
1718 vsdData = aVBoxValues[0];
1719 vsys.strDescription = vsdData;
1720 }
1721 LogRel(("%s: VM description \'%s\'\n", __FUNCTION__, vsdData.c_str()));
1722 }
1723
1724 {
1725 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_OS)//aVBoxValues is set in this #define
1726 if (aVBoxValues.size() != 0)
1727 strOsType = aVBoxValues[0];
1728 vsys.strTypeVBox = strOsType;
1729 LogRel(("%s: OS type is %s\n", __FUNCTION__, strOsType.c_str()));
1730 }
1731
1732 ovf::EthernetAdapter ea;
1733 {
1734 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_NetworkAdapter)//aVBoxValues is set in this #define
1735 if (aVBoxValues.size() != 0)
1736 {
1737 ea.strAdapterType = (Utf8Str)(aVBoxValues[0]);
1738 ea.strNetworkName = "NAT";//default
1739 vsys.llEthernetAdapters.push_back(ea);
1740 LogRel(("%s: Network adapter type is %s\n", __FUNCTION__, ea.strAdapterType.c_str()));
1741 }
1742 else
1743 {
1744 NetworkAdapterType_T defaultAdapterType = NetworkAdapterType_Am79C970A;
1745 pGuestOSType->COMGETTER(AdapterType)(&defaultAdapterType);
1746 Utf8StrFmt dat("%RU32", (uint32_t)defaultAdapterType);
1747 vsd->AddDescription(VirtualSystemDescriptionType_NetworkAdapter,
1748 Bstr(dat).raw(),
1749 Bstr(Utf8Str("NAT")).raw());
1750 }
1751 }
1752
1753 ovf::HardDiskController hdc;
1754 {
1755 //It's thought that SATA is supported by any OS types
1756 hdc.system = ovf::HardDiskController::SATA;
1757 hdc.idController = 0;
1758
1759 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskControllerSATA)//aVBoxValues is set in this #define
1760 if (aVBoxValues.size() != 0)
1761 hdc.strControllerType = (Utf8Str)(aVBoxValues[0]);
1762 else
1763 hdc.strControllerType = "AHCI";
1764
1765 LogRel(("%s: Hard disk controller type is %s\n", __FUNCTION__, hdc.strControllerType.c_str()));
1766 vsys.mapControllers[hdc.idController] = hdc;
1767
1768 if (aVBoxValues.size() == 0)
1769 {
1770 /* we should do it here because it'll be used later in the OVF logic (inside i_importMachines()) */
1771 vsd->AddDescription(VirtualSystemDescriptionType_HardDiskControllerSATA,
1772 Bstr(hdc.strControllerType).raw(),
1773 NULL);
1774 }
1775 }
1776
1777 {
1778 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_SoundCard)//aVBoxValues is set in this #define
1779 if (aVBoxValues.size() != 0)
1780 vsys.strSoundCardType = (Utf8Str)(aVBoxValues[0]);
1781 else
1782 {
1783 AudioControllerType_T defaultAudioController;
1784 pGuestOSType->COMGETTER(RecommendedAudioController)(&defaultAudioController);
1785 vsys.strSoundCardType = Utf8StrFmt("%RU32", (uint32_t)defaultAudioController);//"ensoniq1371";//"AC97";
1786 vsd->AddDescription(VirtualSystemDescriptionType_SoundCard,
1787 Bstr(vsys.strSoundCardType).raw(),
1788 NULL);
1789 }
1790
1791 LogRel(("%s: Sound card is %s\n", __FUNCTION__, vsys.strSoundCardType.c_str()));
1792 }
1793
1794 vsys.fHasFloppyDrive = false;
1795 vsys.fHasCdromDrive = false;
1796 vsys.fHasUsbController = true;
1797 }
1798
1799 unsigned currImageObjectNum = 0;
1800 hrc = S_OK;
1801 do
1802 {
1803 char *pszName = NULL;
1804 RTVFSOBJTYPE enmType;
1805 vrc = RTVfsFsStrmNext(hVfsFssObject, &pszName, &enmType, &hVfsObj);
1806 if (RT_FAILURE(vrc))
1807 {
1808 if (vrc != VERR_EOF)
1809 {
1810 hrc = setErrorVrc(vrc, tr("%s: Error reading '%s' (%Rrc)"), __FUNCTION__, strAbsSrcPath.c_str(), vrc);
1811 throw hrc;
1812 }
1813 break;
1814 }
1815
1816 /* We only care about entries that are files. Get the I/O stream handle for them. */
1817 if ( enmType == RTVFSOBJTYPE_IO_STREAM
1818 || enmType == RTVFSOBJTYPE_FILE)
1819 {
1820 /* Find the suffix and check if this is a possibly interesting file. */
1821 char *pszSuffix = RTStrToLower(strrchr(pszName, '.'));
1822
1823 /* Get the I/O stream. */
1824 hVfsIosCurr = RTVfsObjToIoStream(hVfsObj);
1825 Assert(hVfsIosCurr != NIL_RTVFSIOSTREAM);
1826
1827 /* Get the source medium format */
1828 ComObjPtr<MediumFormat> srcFormat;
1829 srcFormat = pSysProps->i_mediumFormatFromExtension(pszSuffix + 1);
1830
1831 /* unknown image format so just extract a file without any processing */
1832 if (srcFormat == NULL)
1833 {
1834 /* Read the file into a memory buffer */
1835 void *pvBuffered;
1836 size_t cbBuffered;
1837 RTVFSFILE hVfsDstFile = NIL_RTVFSFILE;
1838 try
1839 {
1840 vrc = RTVfsIoStrmReadAll(hVfsIosCurr, &pvBuffered, &cbBuffered);
1841 RTVfsIoStrmRelease(hVfsIosCurr);
1842 hVfsIosCurr = NIL_RTVFSIOSTREAM;
1843 if (RT_FAILURE(vrc))
1844 throw setErrorVrc(vrc, tr("Could not read the file '%s' (%Rrc)"), strAbsSrcPath.c_str(), vrc);
1845
1846 Utf8StrFmt strAbsDstPath("%s%s%s", strMachineFolder.c_str(), RTPATH_SLASH_STR, pszName);
1847
1848 /* Simple logic - just try to get dir info, in case of absent try to create one.
1849 No deep errors analysis */
1850 RTFSOBJINFO dirInfo;
1851 vrc = RTPathQueryInfo(strMachineFolder.c_str(), &dirInfo, RTFSOBJATTRADD_NOTHING);
1852 if (RT_FAILURE(vrc))
1853 {
1854 if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
1855 {
1856 vrc = RTDirCreateFullPath(strMachineFolder.c_str(), 0755);
1857 if (RT_FAILURE(vrc))
1858 throw setErrorVrc(vrc, tr("Could not create the directory '%s' (%Rrc)"),
1859 strMachineFolder.c_str(), vrc);
1860 }
1861 else
1862 throw setErrorVrc(vrc, tr("Error during getting info about the directory '%s' (%Rrc)"),
1863 strMachineFolder.c_str(), vrc);
1864 }
1865
1866 /* Write the file on the disk */
1867 vrc = RTVfsFileOpenNormal(strAbsDstPath.c_str(),
1868 RTFILE_O_WRITE | RTFILE_O_DENY_ALL | RTFILE_O_CREATE,
1869 &hVfsDstFile);
1870 if (RT_FAILURE(vrc))
1871 throw setErrorVrc(vrc, tr("Could not create the file '%s' (%Rrc)"), strAbsDstPath.c_str(), vrc);
1872
1873 size_t cbWritten;
1874 vrc = RTVfsFileWrite(hVfsDstFile, pvBuffered, cbBuffered, &cbWritten);
1875 if (RT_FAILURE(vrc))
1876 throw setErrorVrc(vrc, tr("Could not write into the file '%s' (%Rrc)"), strAbsDstPath.c_str(), vrc);
1877
1878 /* Remember this file */
1879 extraCreatedFiles.append(strAbsDstPath);
1880 }
1881 catch (HRESULT aRc)
1882 {
1883 hrc = aRc;
1884 strLastActualErrorDesc = Utf8StrFmt("%s: Processing the downloaded object was failed. "
1885 "The exception (%Rrc)\n", __FUNCTION__, hrc);
1886 LogRel((strLastActualErrorDesc.c_str()));
1887 }
1888 catch (int aRc)
1889 {
1890 hrc = setErrorVrc(aRc);
1891 strLastActualErrorDesc = Utf8StrFmt("%s: Processing the downloaded object was failed. "
1892 "The exception (%Rrc)\n", __FUNCTION__, aRc);
1893 LogRel((strLastActualErrorDesc.c_str()));
1894 }
1895 catch (...)
1896 {
1897 hrc = VERR_UNEXPECTED_EXCEPTION;
1898 strLastActualErrorDesc = Utf8StrFmt("%s: Processing the downloaded object was failed. "
1899 "The exception (%Rrc)\n", __FUNCTION__, hrc);
1900 LogRel((strLastActualErrorDesc.c_str()));
1901 }
1902 }
1903 else
1904 {
1905 /* Just skip the rest images if they exist. Only the first image is used as the base image. */
1906 if (currImageObjectNum >= 1)
1907 continue;
1908
1909 /* Image format is supported by VBox so extract the file and try to convert
1910 * one to the default format (which is VMDK for now) */
1911 Utf8Str z(bstrSettingsFilename);
1912 Utf8StrFmt strAbsDstPath("%s_%d.%s",
1913 z.stripSuffix().c_str(),
1914 currImageObjectNum,
1915 strTargetFormat.c_str());
1916
1917 hrc = mVirtualBox->i_findHardDiskByLocation(strAbsDstPath, false, NULL);
1918 if (SUCCEEDED(hrc))
1919 throw setError(VERR_ALREADY_EXISTS, tr("The hard disk '%s' already exists."), strAbsDstPath.c_str());
1920
1921 /* Create an IMedium object. */
1922 ComObjPtr<Medium> pTargetMedium;
1923 pTargetMedium.createObject();
1924 hrc = pTargetMedium->init(mVirtualBox,
1925 strTargetFormat,
1926 strAbsDstPath,
1927 Guid::Empty /* media registry: none yet */,
1928 DeviceType_HardDisk);
1929 if (FAILED(hrc))
1930 throw hrc;
1931
1932 pTask->pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"), pszName).raw(),
1933 200);
1934 ComObjPtr<Medium> nullParent;
1935 ComPtr<IProgress> pProgressImport;
1936 ComObjPtr<Progress> pProgressImportTmp;
1937 hrc = pProgressImportTmp.createObject();
1938 if (FAILED(hrc))
1939 throw hrc;
1940
1941 hrc = pProgressImportTmp->init(mVirtualBox,
1942 static_cast<IAppliance*>(this),
1943 Utf8StrFmt(tr("Importing medium '%s'"),
1944 pszName),
1945 TRUE);
1946 if (FAILED(hrc))
1947 throw hrc;
1948
1949 pProgressImportTmp.queryInterfaceTo(pProgressImport.asOutParam());
1950
1951 hrc = pTargetMedium->i_importFile(pszName,
1952 srcFormat,
1953 MediumVariant_Standard,
1954 hVfsIosCurr,
1955 nullParent,
1956 pProgressImportTmp,
1957 true /* aNotify */);
1958 RTVfsIoStrmRelease(hVfsIosCurr);
1959 hVfsIosCurr = NIL_RTVFSIOSTREAM;
1960 /* Now wait for the background import operation to complete;
1961 * this throws HRESULTs on error. */
1962 hrc = pTask->pProgress->WaitForOtherProgressCompletion(pProgressImport, 0 /* indefinite wait */);
1963
1964 /* Try to re-use some OVF stuff here */
1965 if (SUCCEEDED(hrc))
1966 {
1967 /* Small trick here.
1968 * We add new item into the actual VSD after successful conversion.
1969 * There is no need to delete any previous records describing the images in the VSD
1970 * because later in the code the search of the images in the VSD will use such records
1971 * with the actual image id (d.strDiskId = pTargetMedium->i_getId().toString()) which is
1972 * used as a key for m->pReader->m_mapDisks, vsys.mapVirtualDisks.
1973 * So all 3 objects are tied via the image id.
1974 * In the OVF case we already have all such records in the VSD after reading OVF
1975 * description file (read() and interpret() functions).*/
1976 ovf::DiskImage d;
1977 d.strDiskId = pTargetMedium->i_getId().toString();
1978 d.strHref = pTargetMedium->i_getLocationFull();
1979 d.strFormat = pTargetMedium->i_getFormat();
1980 d.iSize = pTargetMedium->i_getSize();
1981 d.ulSuggestedSizeMB = (uint32_t)(d.iSize/_1M);
1982
1983 m->pReader->m_mapDisks[d.strDiskId] = d;
1984
1985 ComObjPtr<VirtualSystemDescription> vsdescThis = m->virtualSystemDescriptions.front();
1986
1987 /* It's needed here to use the internal function i_addEntry() instead of the API function
1988 * addDescription() because we should pass the d.strDiskId for the proper handling this
1989 * disk later in the i_importMachineGeneric():
1990 * - find the line like this "if (vsdeHD->strRef == diCurrent.strDiskId)".
1991 * if those code can be eliminated then addDescription() will be used. */
1992 vsdescThis->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
1993 d.strDiskId,
1994 d.strHref,
1995 d.strHref,
1996 d.ulSuggestedSizeMB);
1997
1998 ovf::VirtualDisk vd;
1999 vd.idController = vsys.mapControllers[0].idController;
2000 vd.ulAddressOnParent = 0;
2001 vd.strDiskId = d.strDiskId;
2002 vsys.mapVirtualDisks[vd.strDiskId] = vd;
2003
2004 ++currImageObjectNum;
2005 }
2006 }
2007
2008 RTVfsIoStrmRelease(hVfsIosCurr);
2009 hVfsIosCurr = NIL_RTVFSIOSTREAM;
2010 }
2011
2012 RTVfsObjRelease(hVfsObj);
2013 hVfsObj = NIL_RTVFSOBJ;
2014
2015 RTStrFree(pszName);
2016
2017 } while (SUCCEEDED(hrc));
2018
2019 RTVfsFsStrmRelease(hVfsFssObject);
2020 hVfsFssObject = NIL_RTVFSFSSTREAM;
2021
2022 if (SUCCEEDED(hrc))
2023 {
2024 pTask->pProgress->SetNextOperation(BstrFmt(tr("Creating new VM '%s'"), strVMName.c_str()).raw(), 50);
2025 /* Create the import stack to comply OVF logic.
2026 * Before we filled some other data structures which are needed by OVF logic too.*/
2027 ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, NIL_RTVFSFSSTREAM);
2028 i_importMachines(stack);
2029 }
2030
2031 }
2032 catch (HRESULT aRc)
2033 {
2034 hrc = aRc;
2035 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
2036 "The exception (%Rrc)\n", __FUNCTION__, hrc);
2037 LogRel((strLastActualErrorDesc.c_str()));
2038 }
2039 catch (int aRc)
2040 {
2041 hrc = setErrorVrc(aRc);
2042 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
2043 "The exception (%Rrc)\n", __FUNCTION__, aRc);
2044 LogRel((strLastActualErrorDesc.c_str()));
2045 }
2046 catch (...)
2047 {
2048 hrc = VERR_UNRESOLVED_ERROR;
2049 strLastActualErrorDesc = Utf8StrFmt("%s: Cloud import (local phase) failed. "
2050 "The exception (%Rrc)\n", __FUNCTION__, hrc);
2051 LogRel((strLastActualErrorDesc.c_str()));
2052 }
2053
2054 LogRel(("%s: Cloud import (local phase) final result (%Rrc).\n", __FUNCTION__, hrc));
2055
2056 /* Try to free VFS stuff because some of them might not be released due to the exception */
2057 if (hVfsIosCurr != NIL_RTVFSIOSTREAM)
2058 RTVfsIoStrmRelease(hVfsIosCurr);
2059 if (hVfsObj != NIL_RTVFSOBJ)
2060 RTVfsObjRelease(hVfsObj);
2061 if (hVfsFssObject != NIL_RTVFSFSSTREAM)
2062 RTVfsFsStrmRelease(hVfsFssObject);
2063
2064 /* Small explanation here.
2065 * After adding extracted files into the actual VSD the returned list will contain not only the
2066 * record about the downloaded object but also the records about the extracted files from this object.
2067 * It's needed to go through this list to find the record about the downloaded object.
2068 * But it was the first record added into the list, so aVBoxValues[0] should be correct here.
2069 */
2070 GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage)//aVBoxValues is set in this #define
2071 if (!fKeepDownloadedObject)
2072 {
2073 if (aVBoxValues.size() != 0)
2074 {
2075 vsdData = aVBoxValues[0];
2076 //try to delete the downloaded object
2077 bool fExist = RTPathExists(vsdData.c_str());
2078 if (fExist)
2079 {
2080 vrc = RTFileDelete(vsdData.c_str());
2081 if (RT_FAILURE(vrc))
2082 LogRel(("%s: Cleanup action - the downloaded object %s hasn't been deleted\n", __FUNCTION__, vsdData.c_str()));
2083 else
2084 LogRel(("%s: Cleanup action - the downloaded object %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
2085 }
2086 }
2087 }
2088
2089 if (FAILED(hrc))
2090 {
2091 /* What to do here?
2092 * For now:
2093 * - check the registration of created VM and delete one.
2094 * - check the list of imported images, detach them and next delete if they have still registered in the VBox.
2095 * - check some other leavings and delete them if they exist.
2096 */
2097
2098 /* It's not needed to call "pTask->pProgress->SetNextOperation(BstrFmt("The cleanup phase").raw(), 50)" here
2099 * because, WaitForOtherProgressCompletion() calls the SetNextOperation() iternally.
2100 * At least, it's strange that the operation description is set to the previous value. */
2101
2102 ComPtr<IMachine> pMachine;
2103 Utf8Str machineNameOrId = strVMName;
2104
2105 /* m->llGuidsMachinesCreated is filled in the i_importMachineGeneric()/i_importVBoxMachine()
2106 * after successful registration of new VM */
2107 if (!m->llGuidsMachinesCreated.empty())
2108 machineNameOrId = m->llGuidsMachinesCreated.front().toString();
2109
2110 hrc = mVirtualBox->FindMachine(Bstr(machineNameOrId).raw(), pMachine.asOutParam());
2111
2112 if (SUCCEEDED(hrc))
2113 {
2114 LogRel(("%s: Cleanup action - the VM with the name(or id) %s was found\n", __FUNCTION__, machineNameOrId.c_str()));
2115 SafeIfaceArray<IMedium> aMedia;
2116 hrc = pMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
2117 if (SUCCEEDED(hrc))
2118 {
2119 LogRel(("%s: Cleanup action - the VM %s has been unregistered\n", __FUNCTION__, machineNameOrId.c_str()));
2120 ComPtr<IProgress> pProgress1;
2121 hrc = pMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress1.asOutParam());
2122 pTask->pProgress->WaitForOtherProgressCompletion(pProgress1, 0 /* indefinite wait */);
2123
2124 LogRel(("%s: Cleanup action - the VM config file and the attached images have been deleted\n",
2125 __FUNCTION__));
2126 }
2127 }
2128 else
2129 {
2130 /* Re-check the items in the array with the images names (paths).
2131 * if the import fails before creation VM, then VM won't be found
2132 * -> VM can't be unregistered and the images can't be deleted.
2133 * The rest items in the array aVBoxValues are the images which might
2134 * have still been registered in the VBox.
2135 * So go through the array and detach-unregister-delete those images */
2136
2137 /* have to get write lock as the whole find/update sequence must be done
2138 * in one critical section, otherwise there are races which can lead to
2139 * multiple Medium objects with the same content */
2140
2141 AutoWriteLock treeLock(mVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
2142
2143 for (size_t i = 1; i < aVBoxValues.size(); ++i)
2144 {
2145 vsdData = aVBoxValues[i];
2146 ComObjPtr<Medium> poHardDisk;
2147 hrc = mVirtualBox->i_findHardDiskByLocation(vsdData, false, &poHardDisk);
2148 if (SUCCEEDED(hrc))
2149 {
2150 hrc = mVirtualBox->i_unregisterMedium((Medium*)(poHardDisk));
2151 if (SUCCEEDED(hrc))
2152 {
2153 ComPtr<IProgress> pProgress1;
2154 hrc = poHardDisk->DeleteStorage(pProgress1.asOutParam());
2155 pTask->pProgress->WaitForOtherProgressCompletion(pProgress1, 0 /* indefinite wait */);
2156 }
2157 if (SUCCEEDED(hrc))
2158 LogRel(("%s: Cleanup action - the image %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
2159 }
2160 else if (hrc == VBOX_E_OBJECT_NOT_FOUND)
2161 {
2162 LogRel(("%s: Cleanup action - the image %s wasn't found. Nothing to delete.\n", __FUNCTION__, vsdData.c_str()));
2163 hrc = S_OK;
2164 }
2165
2166 }
2167 }
2168
2169 /* Deletion of all additional files which were created during unpacking the downloaded object */
2170 for (size_t i = 0; i < extraCreatedFiles.size(); ++i)
2171 {
2172 vrc = RTFileDelete(extraCreatedFiles.at(i).c_str());
2173 if (RT_FAILURE(vrc))
2174 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
2175 else
2176 LogRel(("%s: Cleanup action - file %s has been deleted\n", __FUNCTION__, extraCreatedFiles.at(i).c_str()));
2177 }
2178
2179 /* Deletion of the other files in the VM folder and the folder itself */
2180 {
2181 RTDIR hDir;
2182 vrc = RTDirOpen(&hDir, strMachineFolder.c_str());
2183 if (RT_SUCCESS(vrc))
2184 {
2185 for (;;)
2186 {
2187 RTDIRENTRYEX Entry;
2188 vrc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
2189 if (RT_FAILURE(vrc))
2190 {
2191 AssertLogRelMsg(vrc == VERR_NO_MORE_FILES, ("%Rrc\n", vrc));
2192 break;
2193 }
2194 if (RTFS_IS_FILE(Entry.Info.Attr.fMode))
2195 {
2196 vrc = RTFileDelete(Entry.szName);
2197 if (RT_FAILURE(vrc))
2198 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
2199 else
2200 LogRel(("%s: Cleanup action - file %s has been deleted\n", __FUNCTION__, Entry.szName));
2201 }
2202 }
2203 RTDirClose(hDir);
2204 }
2205
2206 vrc = RTDirRemove(strMachineFolder.c_str());
2207 if (RT_FAILURE(vrc))
2208 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
2209 }
2210
2211 if (FAILED(hrc))
2212 LogRel(("%s: Cleanup action - some leavings still may exist in the folder %s\n",
2213 __FUNCTION__, strMachineFolder.c_str()));
2214 }
2215 else
2216 {
2217 /* See explanation in the Appliance::i_importImpl() where Progress was setup */
2218 ULONG operationCount;
2219 ULONG currOperation;
2220 pTask->pProgress->COMGETTER(OperationCount)(&operationCount);
2221 pTask->pProgress->COMGETTER(Operation)(&currOperation);
2222 while (++currOperation < operationCount)
2223 {
2224 pTask->pProgress->SetNextOperation(BstrFmt("Skipping the cleanup phase. All right.").raw(), 1);
2225 LogRel(("%s: Skipping the cleanup step %d\n", __FUNCTION__, currOperation));
2226 }
2227 }
2228 }
2229
2230 LogFlowFunc(("rc=%Rhrc\n", hrc));
2231 LogFlowFuncLeave();
2232 return hrc;
2233}
2234
2235/**
2236 * Actual worker code for reading an OVF from disk. This is called from Appliance::taskThreadImportOrExport()
2237 * and therefore runs on the OVF read worker thread. This opens the OVF with ovfreader.cpp.
2238 *
2239 * This runs in one context:
2240 *
2241 * 1) in a first worker thread; in that case, Appliance::Read() called Appliance::readImpl();
2242 *
2243 * @param pTask
2244 * @return
2245 */
2246HRESULT Appliance::i_readFS(TaskOVF *pTask)
2247{
2248 LogFlowFuncEnter();
2249 LogFlowFunc(("Appliance %p\n", this));
2250
2251 AutoCaller autoCaller(this);
2252 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2253
2254 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
2255
2256 HRESULT rc;
2257 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
2258 rc = i_readFSOVF(pTask);
2259 else
2260 rc = i_readFSOVA(pTask);
2261
2262 LogFlowFunc(("rc=%Rhrc\n", rc));
2263 LogFlowFuncLeave();
2264
2265 return rc;
2266}
2267
2268HRESULT Appliance::i_readFSOVF(TaskOVF *pTask)
2269{
2270 LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
2271
2272 /*
2273 * Allocate a buffer for filenames and prep it for suffix appending.
2274 */
2275 char *pszNameBuf = (char *)alloca(pTask->locInfo.strPath.length() + 16);
2276 AssertReturn(pszNameBuf, VERR_NO_TMP_MEMORY);
2277 memcpy(pszNameBuf, pTask->locInfo.strPath.c_str(), pTask->locInfo.strPath.length() + 1);
2278 RTPathStripSuffix(pszNameBuf);
2279 size_t const cchBaseName = strlen(pszNameBuf);
2280
2281 /*
2282 * Open the OVF file first since that is what this is all about.
2283 */
2284 RTVFSIOSTREAM hIosOvf;
2285 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
2286 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosOvf);
2287 if (RT_FAILURE(vrc))
2288 return setErrorVrc(vrc, tr("Failed to open OVF file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2289
2290 HRESULT hrc = i_readOVFFile(pTask, hIosOvf, RTPathFilename(pTask->locInfo.strPath.c_str())); /* consumes hIosOvf */
2291 if (FAILED(hrc))
2292 return hrc;
2293
2294 /*
2295 * Try open the manifest file (for signature purposes and to determine digest type(s)).
2296 */
2297 RTVFSIOSTREAM hIosMf;
2298 strcpy(&pszNameBuf[cchBaseName], ".mf");
2299 vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosMf);
2300 if (RT_SUCCESS(vrc))
2301 {
2302 const char * const pszFilenamePart = RTPathFilename(pszNameBuf);
2303 hrc = i_readManifestFile(pTask, hIosMf /*consumed*/, pszFilenamePart);
2304 if (FAILED(hrc))
2305 return hrc;
2306
2307 /*
2308 * Check for the signature file.
2309 */
2310 RTVFSIOSTREAM hIosCert;
2311 strcpy(&pszNameBuf[cchBaseName], ".cert");
2312 vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosCert);
2313 if (RT_SUCCESS(vrc))
2314 {
2315 hrc = i_readSignatureFile(pTask, hIosCert /*consumed*/, pszFilenamePart);
2316 if (FAILED(hrc))
2317 return hrc;
2318 }
2319 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
2320 return setErrorVrc(vrc, tr("Failed to open the signature file '%s' (%Rrc)"), pszNameBuf, vrc);
2321
2322 }
2323 else if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
2324 {
2325 m->fDeterminedDigestTypes = true;
2326 m->fDigestTypes = 0;
2327 }
2328 else
2329 return setErrorVrc(vrc, tr("Failed to open the manifest file '%s' (%Rrc)"), pszNameBuf, vrc);
2330
2331 /*
2332 * Do tail processing (check the signature).
2333 */
2334 hrc = i_readTailProcessing(pTask);
2335
2336 LogFlowFunc(("returns %Rhrc\n", hrc));
2337 return hrc;
2338}
2339
2340HRESULT Appliance::i_readFSOVA(TaskOVF *pTask)
2341{
2342 LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
2343
2344 /*
2345 * Open the tar file as file stream.
2346 */
2347 RTVFSIOSTREAM hVfsIosOva;
2348 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
2349 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
2350 if (RT_FAILURE(vrc))
2351 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2352
2353 RTVFSFSSTREAM hVfsFssOva;
2354 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
2355 RTVfsIoStrmRelease(hVfsIosOva);
2356 if (RT_FAILURE(vrc))
2357 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2358
2359 /*
2360 * Since jumping thru an OVA file with seekable disk backing is rather
2361 * efficient, we can process .ovf, .mf and .cert files here without any
2362 * strict ordering restrictions.
2363 *
2364 * (Technically, the .ovf-file comes first, while the manifest and its
2365 * optional signature file either follows immediately or at the very end of
2366 * the OVA. The manifest is optional.)
2367 */
2368 char *pszOvfNameBase = NULL;
2369 size_t cchOvfNameBase = 0; NOREF(cchOvfNameBase);
2370 unsigned cLeftToFind = 3;
2371 HRESULT hrc = S_OK;
2372 do
2373 {
2374 char *pszName = NULL;
2375 RTVFSOBJTYPE enmType;
2376 RTVFSOBJ hVfsObj;
2377 vrc = RTVfsFsStrmNext(hVfsFssOva, &pszName, &enmType, &hVfsObj);
2378 if (RT_FAILURE(vrc))
2379 {
2380 if (vrc != VERR_EOF)
2381 hrc = setErrorVrc(vrc, tr("Error reading OVA '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2382 break;
2383 }
2384
2385 /* We only care about entries that are files. Get the I/O stream handle for them. */
2386 if ( enmType == RTVFSOBJTYPE_IO_STREAM
2387 || enmType == RTVFSOBJTYPE_FILE)
2388 {
2389 /* Find the suffix and check if this is a possibly interesting file. */
2390 char *pszSuffix = strrchr(pszName, '.');
2391 if ( pszSuffix
2392 && ( RTStrICmp(pszSuffix + 1, "ovf") == 0
2393 || RTStrICmp(pszSuffix + 1, "mf") == 0
2394 || RTStrICmp(pszSuffix + 1, "cert") == 0) )
2395 {
2396 /* Match the OVF base name. */
2397 *pszSuffix = '\0';
2398 if ( pszOvfNameBase == NULL
2399 || RTStrICmp(pszName, pszOvfNameBase) == 0)
2400 {
2401 *pszSuffix = '.';
2402
2403 /* Since we're pretty sure we'll be processing this file, get the I/O stream. */
2404 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
2405 Assert(hVfsIos != NIL_RTVFSIOSTREAM);
2406
2407 /* Check for the OVF (should come first). */
2408 if (RTStrICmp(pszSuffix + 1, "ovf") == 0)
2409 {
2410 if (pszOvfNameBase == NULL)
2411 {
2412 hrc = i_readOVFFile(pTask, hVfsIos, pszName);
2413 hVfsIos = NIL_RTVFSIOSTREAM;
2414
2415 /* Set the base name. */
2416 *pszSuffix = '\0';
2417 pszOvfNameBase = pszName;
2418 cchOvfNameBase = strlen(pszName);
2419 pszName = NULL;
2420 cLeftToFind--;
2421 }
2422 else
2423 LogRel(("i_readFSOVA: '%s' contains more than one OVF file ('%s'), picking the first one\n",
2424 pTask->locInfo.strPath.c_str(), pszName));
2425 }
2426 /* Check for manifest. */
2427 else if (RTStrICmp(pszSuffix + 1, "mf") == 0)
2428 {
2429 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
2430 {
2431 hrc = i_readManifestFile(pTask, hVfsIos, pszName);
2432 hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
2433 cLeftToFind--;
2434 }
2435 else
2436 LogRel(("i_readFSOVA: '%s' contains more than one manifest file ('%s'), picking the first one\n",
2437 pTask->locInfo.strPath.c_str(), pszName));
2438 }
2439 /* Check for signature. */
2440 else if (RTStrICmp(pszSuffix + 1, "cert") == 0)
2441 {
2442 if (!m->fSignerCertLoaded)
2443 {
2444 hrc = i_readSignatureFile(pTask, hVfsIos, pszName);
2445 hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
2446 cLeftToFind--;
2447 }
2448 else
2449 LogRel(("i_readFSOVA: '%s' contains more than one signature file ('%s'), picking the first one\n",
2450 pTask->locInfo.strPath.c_str(), pszName));
2451 }
2452 else
2453 AssertFailed();
2454 if (hVfsIos != NIL_RTVFSIOSTREAM)
2455 RTVfsIoStrmRelease(hVfsIos);
2456 }
2457 }
2458 }
2459 RTVfsObjRelease(hVfsObj);
2460 RTStrFree(pszName);
2461 } while (cLeftToFind > 0 && SUCCEEDED(hrc));
2462
2463 RTVfsFsStrmRelease(hVfsFssOva);
2464 RTStrFree(pszOvfNameBase);
2465
2466 /*
2467 * Check that we found and OVF file.
2468 */
2469 if (SUCCEEDED(hrc) && !pszOvfNameBase)
2470 hrc = setError(VBOX_E_FILE_ERROR, tr("OVA '%s' does not contain an .ovf-file"), pTask->locInfo.strPath.c_str());
2471 if (SUCCEEDED(hrc))
2472 {
2473 /*
2474 * Do tail processing (check the signature).
2475 */
2476 hrc = i_readTailProcessing(pTask);
2477 }
2478 LogFlowFunc(("returns %Rhrc\n", hrc));
2479 return hrc;
2480}
2481
2482/**
2483 * Reads & parses the OVF file.
2484 *
2485 * @param pTask The read task.
2486 * @param hVfsIosOvf The I/O stream for the OVF. The reference is
2487 * always consumed.
2488 * @param pszManifestEntry The manifest entry name.
2489 * @returns COM status code, error info set.
2490 * @throws Nothing
2491 */
2492HRESULT Appliance::i_readOVFFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosOvf, const char *pszManifestEntry)
2493{
2494 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszManifestEntry));
2495
2496 /*
2497 * Set the OVF manifest entry name (needed for tweaking the manifest
2498 * validation during import).
2499 */
2500 try { m->strOvfManifestEntry = pszManifestEntry; }
2501 catch (...) { return E_OUTOFMEMORY; }
2502
2503 /*
2504 * Set up digest calculation.
2505 */
2506 hVfsIosOvf = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosOvf, pszManifestEntry);
2507 if (hVfsIosOvf == NIL_RTVFSIOSTREAM)
2508 return VBOX_E_FILE_ERROR;
2509
2510 /*
2511 * Read the OVF into a memory buffer and parse it.
2512 */
2513 void *pvBufferedOvf;
2514 size_t cbBufferedOvf;
2515 int vrc = RTVfsIoStrmReadAll(hVfsIosOvf, &pvBufferedOvf, &cbBufferedOvf);
2516 uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosOvf); /* consumes stream handle. */
2517 NOREF(cRefs);
2518 Assert(cRefs == 0);
2519 if (RT_FAILURE(vrc))
2520 return setErrorVrc(vrc, tr("Could not read the OVF file for '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
2521
2522 HRESULT hrc;
2523 try
2524 {
2525 m->pReader = new ovf::OVFReader(pvBufferedOvf, cbBufferedOvf, pTask->locInfo.strPath);
2526 hrc = S_OK;
2527 }
2528 catch (RTCError &rXcpt) // includes all XML exceptions
2529 {
2530 hrc = setError(VBOX_E_FILE_ERROR, rXcpt.what());
2531 }
2532 catch (HRESULT aRC)
2533 {
2534 hrc = aRC;
2535 }
2536 catch (...)
2537 {
2538 hrc = E_FAIL;
2539 }
2540 LogFlowFunc(("OVFReader(%s) -> rc=%Rhrc\n", pTask->locInfo.strPath.c_str(), hrc));
2541
2542 RTVfsIoStrmReadAllFree(pvBufferedOvf, cbBufferedOvf);
2543 if (SUCCEEDED(hrc))
2544 {
2545 /*
2546 * If we see an OVF v2.0 envelope, select only the SHA-256 digest.
2547 */
2548 if ( !m->fDeterminedDigestTypes
2549 && m->pReader->m_envelopeData.getOVFVersion() == ovf::OVFVersion_2_0)
2550 m->fDigestTypes &= ~RTMANIFEST_ATTR_SHA256;
2551 }
2552
2553 return hrc;
2554}
2555
2556/**
2557 * Reads & parses the manifest file.
2558 *
2559 * @param pTask The read task.
2560 * @param hVfsIosMf The I/O stream for the manifest file. The
2561 * reference is always consumed.
2562 * @param pszSubFileNm The manifest filename (no path) for error
2563 * messages and logging.
2564 * @returns COM status code, error info set.
2565 * @throws Nothing
2566 */
2567HRESULT Appliance::i_readManifestFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosMf, const char *pszSubFileNm)
2568{
2569 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
2570
2571 /*
2572 * Copy the manifest into a memory backed file so we can later do signature
2573 * validation indepentend of the algorithms used by the signature.
2574 */
2575 int vrc = RTVfsMemorizeIoStreamAsFile(hVfsIosMf, RTFILE_O_READ, &m->hMemFileTheirManifest);
2576 RTVfsIoStrmRelease(hVfsIosMf); /* consumes stream handle. */
2577 if (RT_FAILURE(vrc))
2578 return setErrorVrc(vrc, tr("Error reading the manifest file '%s' for '%s' (%Rrc)"),
2579 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
2580
2581 /*
2582 * Parse the manifest.
2583 */
2584 Assert(m->hTheirManifest == NIL_RTMANIFEST);
2585 vrc = RTManifestCreate(0 /*fFlags*/, &m->hTheirManifest);
2586 AssertStmt(RT_SUCCESS(vrc), Global::vboxStatusCodeToCOM(vrc));
2587
2588 char szErr[256];
2589 RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(m->hMemFileTheirManifest);
2590 vrc = RTManifestReadStandardEx(m->hTheirManifest, hVfsIos, szErr, sizeof(szErr));
2591 RTVfsIoStrmRelease(hVfsIos);
2592 if (RT_FAILURE(vrc))
2593 return setErrorVrc(vrc, tr("Failed to parse manifest file '%s' for '%s' (%Rrc): %s"),
2594 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, szErr);
2595
2596 /*
2597 * Check which digest files are used.
2598 * Note! the file could be empty, in which case fDigestTypes is set to 0.
2599 */
2600 vrc = RTManifestQueryAllAttrTypes(m->hTheirManifest, true /*fEntriesOnly*/, &m->fDigestTypes);
2601 AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc));
2602 m->fDeterminedDigestTypes = true;
2603
2604 return S_OK;
2605}
2606
2607/**
2608 * Reads the signature & certificate file.
2609 *
2610 * @param pTask The read task.
2611 * @param hVfsIosCert The I/O stream for the signature file. The
2612 * reference is always consumed.
2613 * @param pszSubFileNm The signature filename (no path) for error
2614 * messages and logging. Used to construct
2615 * .mf-file name.
2616 * @returns COM status code, error info set.
2617 * @throws Nothing
2618 */
2619HRESULT Appliance::i_readSignatureFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosCert, const char *pszSubFileNm)
2620{
2621 LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
2622
2623 /*
2624 * Construct the manifest filename from pszSubFileNm.
2625 */
2626 Utf8Str strManifestName;
2627 try
2628 {
2629 const char *pszSuffix = strrchr(pszSubFileNm, '.');
2630 AssertReturn(pszSuffix, E_FAIL);
2631 strManifestName = Utf8Str(pszSubFileNm, pszSuffix - pszSubFileNm);
2632 strManifestName.append(".mf");
2633 }
2634 catch (...)
2635 {
2636 return E_OUTOFMEMORY;
2637 }
2638
2639 /*
2640 * Copy the manifest into a memory buffer. We'll do the signature processing
2641 * later to not force any specific order in the OVAs or any other archive we
2642 * may be accessing later.
2643 */
2644 void *pvSignature;
2645 size_t cbSignature;
2646 int vrc = RTVfsIoStrmReadAll(hVfsIosCert, &pvSignature, &cbSignature);
2647 RTVfsIoStrmRelease(hVfsIosCert); /* consumes stream handle. */
2648 if (RT_FAILURE(vrc))
2649 return setErrorVrc(vrc, tr("Error reading the signature file '%s' for '%s' (%Rrc)"),
2650 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
2651
2652 /*
2653 * Parse the signing certificate. Unlike the manifest parser we use below,
2654 * this API ignores parts of the file that aren't relevant.
2655 */
2656 RTERRINFOSTATIC StaticErrInfo;
2657 vrc = RTCrX509Certificate_ReadFromBuffer(&m->SignerCert, pvSignature, cbSignature,
2658 RTCRX509CERT_READ_F_PEM_ONLY,
2659 &g_RTAsn1DefaultAllocator, RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
2660 HRESULT hrc;
2661 if (RT_SUCCESS(vrc))
2662 {
2663 m->fSignerCertLoaded = true;
2664 m->fCertificateIsSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->SignerCert);
2665
2666 /*
2667 * Find the start of the certificate part of the file, so we can avoid
2668 * upsetting the manifest parser with it.
2669 */
2670 char *pszSplit = (char *)RTCrPemFindFirstSectionInContent(pvSignature, cbSignature,
2671 g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers);
2672 if (pszSplit)
2673 while ( pszSplit != (char *)pvSignature
2674 && pszSplit[-1] != '\n'
2675 && pszSplit[-1] != '\r')
2676 pszSplit--;
2677 else
2678 {
2679 AssertLogRelMsgFailed(("Failed to find BEGIN CERTIFICATE markers in '%s'::'%s' - impossible unless it's a DER encoded certificate!",
2680 pTask->locInfo.strPath.c_str(), pszSubFileNm));
2681 pszSplit = (char *)pvSignature + cbSignature;
2682 }
2683 char const chSaved = *pszSplit;
2684 *pszSplit = '\0';
2685
2686 /*
2687 * Now, read the manifest part. We use the IPRT manifest reader here
2688 * to avoid duplicating code and be somewhat flexible wrt the digest
2689 * type choosen by the signer.
2690 */
2691 RTMANIFEST hSignedDigestManifest;
2692 vrc = RTManifestCreate(0 /*fFlags*/, &hSignedDigestManifest);
2693 if (RT_SUCCESS(vrc))
2694 {
2695 RTVFSIOSTREAM hVfsIosTmp;
2696 vrc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pvSignature, pszSplit - (char *)pvSignature, &hVfsIosTmp);
2697 if (RT_SUCCESS(vrc))
2698 {
2699 vrc = RTManifestReadStandardEx(hSignedDigestManifest, hVfsIosTmp, StaticErrInfo.szMsg, sizeof(StaticErrInfo.szMsg));
2700 RTVfsIoStrmRelease(hVfsIosTmp);
2701 if (RT_SUCCESS(vrc))
2702 {
2703 /*
2704 * Get signed digest, we prefer SHA-2, so explicitly query those first.
2705 */
2706 uint32_t fDigestType;
2707 char szSignedDigest[_8K + 1];
2708 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
2709 RTMANIFEST_ATTR_SHA512 | RTMANIFEST_ATTR_SHA256,
2710 szSignedDigest, sizeof(szSignedDigest), &fDigestType);
2711 if (vrc == VERR_MANIFEST_ATTR_TYPE_NOT_FOUND)
2712 vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
2713 RTMANIFEST_ATTR_ANY, szSignedDigest, sizeof(szSignedDigest), &fDigestType);
2714 if (RT_SUCCESS(vrc))
2715 {
2716 const char *pszSignedDigest = RTStrStrip(szSignedDigest);
2717 size_t cbSignedDigest = strlen(pszSignedDigest) / 2;
2718 uint8_t abSignedDigest[sizeof(szSignedDigest) / 2];
2719 vrc = RTStrConvertHexBytes(szSignedDigest, abSignedDigest, cbSignedDigest, 0 /*fFlags*/);
2720 if (RT_SUCCESS(vrc))
2721 {
2722 /*
2723 * Convert it to RTDIGESTTYPE_XXX and save the binary value for later use.
2724 */
2725 switch (fDigestType)
2726 {
2727 case RTMANIFEST_ATTR_SHA1: m->enmSignedDigestType = RTDIGESTTYPE_SHA1; break;
2728 case RTMANIFEST_ATTR_SHA256: m->enmSignedDigestType = RTDIGESTTYPE_SHA256; break;
2729 case RTMANIFEST_ATTR_SHA512: m->enmSignedDigestType = RTDIGESTTYPE_SHA512; break;
2730 case RTMANIFEST_ATTR_MD5: m->enmSignedDigestType = RTDIGESTTYPE_MD5; break;
2731 default: AssertFailed(); m->enmSignedDigestType = RTDIGESTTYPE_INVALID; break;
2732 }
2733 if (m->enmSignedDigestType != RTDIGESTTYPE_INVALID)
2734 {
2735 m->pbSignedDigest = (uint8_t *)RTMemDup(abSignedDigest, cbSignedDigest);
2736 m->cbSignedDigest = cbSignedDigest;
2737 hrc = S_OK;
2738 }
2739 else
2740 hrc = setError(E_FAIL, tr("Unsupported signed digest type (%#x)"), fDigestType);
2741 }
2742 else
2743 hrc = setErrorVrc(vrc, tr("Error reading signed manifest digest: %Rrc"), vrc);
2744 }
2745 else if (vrc == VERR_NOT_FOUND)
2746 hrc = setErrorVrc(vrc, tr("Could not locate signed digest for '%s' in the cert-file for '%s'"),
2747 strManifestName.c_str(), pTask->locInfo.strPath.c_str());
2748 else
2749 hrc = setErrorVrc(vrc, tr("RTManifestEntryQueryAttr failed unexpectedly: %Rrc"), vrc);
2750 }
2751 else
2752 hrc = setErrorVrc(vrc, tr("Error parsing the .cert-file for '%s': %s"),
2753 pTask->locInfo.strPath.c_str(), StaticErrInfo.szMsg);
2754 }
2755 else
2756 hrc = E_OUTOFMEMORY;
2757 RTManifestRelease(hSignedDigestManifest);
2758 }
2759 else
2760 hrc = E_OUTOFMEMORY;
2761
2762 /*
2763 * Look for the additional for PKCS#7/CMS signature we produce when we sign stuff.
2764 */
2765 if (SUCCEEDED(hrc))
2766 {
2767 *pszSplit = chSaved;
2768 vrc = RTCrPkcs7_ReadFromBuffer(&m->ContentInfo, pvSignature, cbSignature, RTCRPKCS7_READ_F_PEM_ONLY,
2769 &g_RTAsn1DefaultAllocator, NULL /*pfCmsLabeled*/,
2770 RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
2771 if (RT_SUCCESS(vrc))
2772 m->fContentInfoLoaded = true;
2773 else if (vrc != VERR_NOT_FOUND)
2774 hrc = setErrorVrc(vrc, tr("Error reading the PKCS#7/CMS signature from '%s' for '%s' (%Rrc): %s"),
2775 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
2776 }
2777 }
2778 else if (vrc == VERR_NOT_FOUND || vrc == VERR_EOF)
2779 hrc = setErrorBoth(E_FAIL, vrc, tr("Malformed .cert-file for '%s': Signer's certificate not found (%Rrc)"),
2780 pTask->locInfo.strPath.c_str(), vrc);
2781 else
2782 hrc = setErrorVrc(vrc, tr("Error reading the signer's certificate from '%s' for '%s' (%Rrc): %s"),
2783 pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
2784
2785 RTVfsIoStrmReadAllFree(pvSignature, cbSignature);
2786 LogFlowFunc(("returns %Rhrc (%Rrc)\n", hrc, vrc));
2787 return hrc;
2788}
2789
2790
2791/**
2792 * Does tail processing after the files have been read in.
2793 *
2794 * @param pTask The read task.
2795 * @returns COM status.
2796 * @throws Nothing!
2797 */
2798HRESULT Appliance::i_readTailProcessing(TaskOVF *pTask)
2799{
2800 /*
2801 * Parse and validate the signature file.
2802 *
2803 * The signature file nominally has two parts, manifest part and a PEM
2804 * encoded certificate. The former contains an entry for the manifest file
2805 * with a digest that is encrypted with the certificate in the latter part.
2806 *
2807 * When an appliance is signed by VirtualBox, a PKCS#7/CMS signedData part
2808 * is added by default, supplying more info than the bits mandated by the
2809 * OVF specs. We will validate both the signedData and the standard OVF
2810 * signature. Another requirement is that the first signedData signer
2811 * uses the same certificate as the regular OVF signature, allowing us to
2812 * only do path building for the signedData with the additional info it
2813 * ships with.
2814 */
2815 if (m->pbSignedDigest)
2816 {
2817 /* Since we're validating the digest of the manifest, there have to be
2818 a manifest. We cannot allow a the manifest to be missing. */
2819 if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
2820 return setError(VBOX_E_FILE_ERROR, tr("Found .cert-file but no .mf-file for '%s'"), pTask->locInfo.strPath.c_str());
2821
2822 /*
2823 * Validate the signed digest.
2824 *
2825 * It's possible we should allow the user to ignore signature
2826 * mismatches, but for now it is a solid show stopper.
2827 */
2828 HRESULT hrc;
2829 RTERRINFOSTATIC StaticErrInfo;
2830
2831 /* Calc the digest of the manifest using the algorithm found above. */
2832 RTCRDIGEST hDigest;
2833 int vrc = RTCrDigestCreateByType(&hDigest, m->enmSignedDigestType);
2834 if (RT_SUCCESS(vrc))
2835 {
2836 vrc = RTCrDigestUpdateFromVfsFile(hDigest, m->hMemFileTheirManifest, true /*fRewindFile*/);
2837 if (RT_SUCCESS(vrc))
2838 {
2839 /* Compare the signed digest with the one we just calculated. (This
2840 API will do the verification twice, once using IPRT's own crypto
2841 and once using OpenSSL. Both must OK it for success.) */
2842 vrc = RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(&m->SignerCert.TbsCertificate.SubjectPublicKeyInfo,
2843 m->pbSignedDigest, m->cbSignedDigest, hDigest,
2844 RTErrInfoInitStatic(&StaticErrInfo));
2845 if (RT_SUCCESS(vrc))
2846 {
2847 m->fSignatureValid = true;
2848 hrc = S_OK;
2849 }
2850 else if (vrc == VERR_CR_PKIX_SIGNATURE_MISMATCH)
2851 hrc = setErrorVrc(vrc, tr("The manifest signature does not match"));
2852 else
2853 hrc = setErrorVrc(vrc,
2854 tr("Error validating the manifest signature (%Rrc, %s)"), vrc, StaticErrInfo.Core.pszMsg);
2855 }
2856 else
2857 hrc = setErrorVrc(vrc, tr("RTCrDigestUpdateFromVfsFile failed: %Rrc"), vrc);
2858 RTCrDigestRelease(hDigest);
2859 }
2860 else
2861 hrc = setErrorVrc(vrc, tr("RTCrDigestCreateByType failed: %Rrc"), vrc);
2862
2863 /*
2864 * If we have a PKCS#7/CMS signature, validate it and check that the
2865 * certificate matches the first signerInfo entry.
2866 */
2867 HRESULT hrc2 = i_readTailProcessingSignedData(&StaticErrInfo);
2868 if (FAILED(hrc2) && SUCCEEDED(hrc))
2869 hrc = hrc2;
2870
2871 /*
2872 * Validate the certificate.
2873 *
2874 * We don't fail here if we cannot validate the certificate, we postpone
2875 * that till the import stage, so that we can allow the user to ignore it.
2876 *
2877 * The certificate validity time is deliberately left as warnings as the
2878 * OVF specification does not provision for any timestamping of the
2879 * signature. This is course a security concern, but the whole signing
2880 * of OVFs is currently weirdly trusting (self signed * certs), so this
2881 * is the least of our current problems.
2882 *
2883 * While we try build and verify certificate paths properly, the
2884 * "neighbours" quietly ignores this and seems only to check the signature
2885 * and not whether the certificate is trusted. Also, we don't currently
2886 * complain about self-signed certificates either (ditto "neighbours").
2887 * The OVF creator is also a bit restricted wrt to helping us build the
2888 * path as he cannot supply intermediate certificates. Anyway, we issue
2889 * warnings (goes to /dev/null, am I right?) for self-signed certificates
2890 * and certificates we cannot build and verify a root path for.
2891 *
2892 * (The OVF sillibuggers should've used PKCS#7, CMS or something else
2893 * that's already been standardized instead of combining manifests with
2894 * certificate PEM files in some very restrictive manner! I wonder if
2895 * we could add a PKCS#7 section to the .cert file in addition to the CERT
2896 * and manifest stuff dictated by the standard. Would depend on how others
2897 * deal with it.)
2898 */
2899 Assert(!m->fCertificateValid);
2900 Assert(m->fCertificateMissingPath);
2901 Assert(!m->fCertificateValidTime);
2902 Assert(m->strCertError.isEmpty());
2903 Assert(m->fCertificateIsSelfSigned == RTCrX509Certificate_IsSelfSigned(&m->SignerCert));
2904
2905 /* We'll always needs the trusted cert store. */
2906 hrc2 = S_OK;
2907 RTCRSTORE hTrustedCerts;
2908 vrc = RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(&hTrustedCerts, RTErrInfoInitStatic(&StaticErrInfo));
2909 if (RT_SUCCESS(vrc))
2910 {
2911 /* If we don't have a PKCS7/CMS signature or if it uses a different
2912 certificate, we try our best to validate the OVF certificate. */
2913 if (!m->fContentInfoOkay || !m->fContentInfoSameCert)
2914 {
2915 if (m->fCertificateIsSelfSigned)
2916 hrc2 = i_readTailProcessingVerifySelfSignedOvfCert(pTask, hTrustedCerts, &StaticErrInfo);
2917 else
2918 hrc2 = i_readTailProcessingVerifyIssuedOvfCert(pTask, hTrustedCerts, &StaticErrInfo);
2919 }
2920
2921 /* If there is a PKCS7/CMS signature, we always verify its certificates. */
2922 if (m->fContentInfoOkay)
2923 {
2924 void *pvData = NULL;
2925 size_t cbData = 0;
2926 HRESULT hrc3 = i_readTailProcessingGetManifestData(&pvData, &cbData);
2927 if (SUCCEEDED(hrc3))
2928 {
2929 hrc3 = i_readTailProcessingVerifyContentInfoCerts(pvData, cbData, hTrustedCerts, &StaticErrInfo);
2930 RTMemTmpFree(pvData);
2931 }
2932 if (FAILED(hrc3) && SUCCEEDED(hrc2))
2933 hrc2 = hrc3;
2934 }
2935 RTCrStoreRelease(hTrustedCerts);
2936 }
2937 else
2938 hrc2 = setErrorBoth(E_FAIL, vrc,
2939 tr("Failed to query trusted CAs and Certificates from the system and for the current user (%Rrc%RTeim)"),
2940 vrc, &StaticErrInfo.Core);
2941
2942 /* Merge statuses from signature and certificate validation, prefering the signature one. */
2943 if (SUCCEEDED(hrc) && FAILED(hrc2))
2944 hrc = hrc2;
2945 if (FAILED(hrc))
2946 return hrc;
2947 }
2948
2949 /** @todo provide details about the signatory, signature, etc. */
2950 if (m->fSignerCertLoaded)
2951 {
2952 /** @todo PKCS7/CMS certs too */
2953 m->ptrCertificateInfo.createObject();
2954 m->ptrCertificateInfo->initCertificate(&m->SignerCert,
2955 m->fCertificateValid && !m->fCertificateMissingPath,
2956 !m->fCertificateValidTime);
2957 }
2958
2959 /*
2960 * If there is a manifest, check that the OVF digest matches up (if present).
2961 */
2962
2963 NOREF(pTask);
2964 return S_OK;
2965}
2966
2967/**
2968 * Reads hMemFileTheirManifest into a memory buffer so it can be passed to
2969 * RTCrPkcs7VerifySignedDataWithExternalData.
2970 *
2971 * Use RTMemTmpFree to free the memory.
2972 */
2973HRESULT Appliance::i_readTailProcessingGetManifestData(void **ppvData, size_t *pcbData)
2974{
2975 uint64_t cbData;
2976 int vrc = RTVfsFileQuerySize(m->hMemFileTheirManifest, &cbData);
2977 AssertRCReturn(vrc, setErrorVrc(vrc, "RTVfsFileQuerySize"));
2978
2979 void *pvData = RTMemTmpAllocZ((size_t)cbData);
2980 AssertPtrReturn(pvData, E_OUTOFMEMORY);
2981
2982 vrc = RTVfsFileReadAt(m->hMemFileTheirManifest, 0, pvData, (size_t)cbData, NULL);
2983 AssertRCReturnStmt(vrc, RTMemTmpFree(pvData), setErrorVrc(vrc, "RTVfsFileReadAt"));
2984
2985 *pcbData = (size_t)cbData;
2986 *ppvData = pvData;
2987 return S_OK;
2988}
2989
2990/**
2991 * Worker for i_readTailProcessing that validates the signedData.
2992 *
2993 * If we have a PKCS#7/CMS signature:
2994 * - validate it
2995 * - check that the OVF certificate matches the first signerInfo entry
2996 * - verify the signature, but leave the certificate path validation for
2997 * later.
2998 *
2999 * @param pErrInfo Static error info buffer (not for returning, just for
3000 * avoiding wasting stack).
3001 * @returns COM status.
3002 * @throws Nothing!
3003 */
3004HRESULT Appliance::i_readTailProcessingSignedData(PRTERRINFOSTATIC pErrInfo)
3005{
3006 m->fContentInfoOkay = false;
3007 m->fContentInfoSameCert = false;
3008 m->fContentInfoValidSignature = false;
3009
3010 if (!m->fContentInfoLoaded)
3011 return S_OK;
3012
3013 /*
3014 * Validate it.
3015 */
3016 HRESULT hrc = S_OK;
3017 PCRTCRPKCS7SIGNEDDATA pSignedData = m->ContentInfo.u.pSignedData;
3018 if (!RTCrPkcs7ContentInfo_IsSignedData(&m->ContentInfo))
3019 i_addWarning(tr("Invalid PKCS#7/CMS type: %s, expected %s (signedData)"),
3020 m->ContentInfo.ContentType.szObjId, RTCRPKCS7SIGNEDDATA_OID);
3021 else if (RTAsn1ObjId_CompareWithString(&pSignedData->ContentInfo.ContentType, RTCR_PKCS7_DATA_OID) != 0)
3022 i_addWarning(tr("Invalid PKCS#7/CMS inner type: %s, expected %s (data)"),
3023 pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID);
3024 else if (RTAsn1OctetString_IsPresent(&pSignedData->ContentInfo.Content))
3025 i_addWarning(tr("Invalid PKCS#7/CMS data: embedded (%u bytes), expected external"),
3026 pSignedData->ContentInfo.Content.Asn1Core.cb);
3027 else if (pSignedData->SignerInfos.cItems == 0)
3028 i_addWarning(tr("Invalid PKCS#7/CMS: No signers"));
3029 else
3030 {
3031 m->fContentInfoOkay = true;
3032
3033 /*
3034 * Same certificate as the OVF signature?
3035 */
3036 PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[0];
3037 if ( RTCrX509Name_Compare(&pSignerInfo->IssuerAndSerialNumber.Name, &m->SignerCert.TbsCertificate.Issuer) == 0
3038 && RTAsn1Integer_Compare(&pSignerInfo->IssuerAndSerialNumber.SerialNumber,
3039 &m->SignerCert.TbsCertificate.SerialNumber) == 0)
3040 m->fContentInfoSameCert = true;
3041 else
3042 i_addWarning(tr("Invalid PKCS#7/CMS: Using a different certificate"));
3043
3044 /*
3045 * Then perform a validation of the signatures, but first without
3046 * validating the certificate trust paths yet.
3047 */
3048 RTCRSTORE hTrustedCerts = NIL_RTCRSTORE;
3049 int vrc = RTCrStoreCreateInMem(&hTrustedCerts, 1);
3050 AssertRCReturn(vrc, setErrorVrc(vrc, tr("RTCrStoreCreateInMem failed: %Rrc"), vrc));
3051
3052 vrc = RTCrStoreCertAddX509(hTrustedCerts, 0, &m->SignerCert, RTErrInfoInitStatic(pErrInfo));
3053 if (RT_SUCCESS(vrc))
3054 {
3055 void *pvData = NULL;
3056 size_t cbData = 0;
3057 hrc = i_readTailProcessingGetManifestData(&pvData, &cbData);
3058 if (SUCCEEDED(hrc))
3059 {
3060 RTTIMESPEC Now;
3061 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_TRUST_ALL_CERTS,
3062 NIL_RTCRSTORE /*hAdditionalCerts*/, hTrustedCerts,
3063 RTTimeNow(&Now), NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
3064 pvData, cbData, RTErrInfoInitStatic(pErrInfo));
3065 if (RT_SUCCESS(vrc))
3066 m->fContentInfoValidSignature = true;
3067 else
3068 i_addWarning(tr("Failed to validate PKCS#7/CMS signature: %Rrc%RTeim"), vrc, &pErrInfo->Core);
3069 RTMemTmpFree(pvData);
3070 }
3071 }
3072 else
3073 hrc = setErrorVrc(vrc, tr("RTCrStoreCertAddX509 failed: %Rrc%RTeim"), vrc, &pErrInfo->Core);
3074 RTCrStoreRelease(hTrustedCerts);
3075 }
3076
3077 return hrc;
3078}
3079
3080
3081/**
3082 * Worker for i_readTailProcessing that verifies a self signed certificate when
3083 * no PKCS\#7/CMS signature using the same certificate is present.
3084 */
3085HRESULT Appliance::i_readTailProcessingVerifySelfSignedOvfCert(TaskOVF *pTask, RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo)
3086{
3087 /*
3088 * It's a self signed certificate. We assume the frontend will
3089 * present this fact to the user and give a choice whether this
3090 * is acceptable. But, first make sure it makes internal sense.
3091 */
3092 m->fCertificateMissingPath = true;
3093 PCRTCRCERTCTX pCertCtx = RTCrStoreCertByIssuerAndSerialNo(hTrustedStore, &m->SignerCert.TbsCertificate.Issuer,
3094 &m->SignerCert.TbsCertificate.SerialNumber);
3095 if (pCertCtx)
3096 {
3097 if (pCertCtx->pCert && RTCrX509Certificate_Compare(pCertCtx->pCert, &m->SignerCert) == 0)
3098 m->fCertificateMissingPath = true;
3099 RTCrCertCtxRelease(pCertCtx);
3100 }
3101
3102 int vrc = RTCrX509Certificate_VerifySignatureSelfSigned(&m->SignerCert, RTErrInfoInitStatic(pErrInfo));
3103 if (RT_SUCCESS(vrc))
3104 {
3105 m->fCertificateValid = true;
3106
3107 /* Check whether the certificate is currently valid, just warn if not. */
3108 RTTIMESPEC Now;
3109 m->fCertificateValidTime = RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now));
3110 if (m->fCertificateValidTime)
3111 {
3112 m->fCertificateValidTime = true;
3113 i_addWarning(tr("A self signed certificate was used to sign '%s'"), pTask->locInfo.strPath.c_str());
3114 }
3115 else
3116 i_addWarning(tr("Self signed certificate used to sign '%s' is not currently valid"),
3117 pTask->locInfo.strPath.c_str());
3118 }
3119 else
3120 {
3121 m->strCertError.printfNoThrow(tr("Verification of the self signed certificate failed (%Rrc%#RTeim)"),
3122 vrc, &pErrInfo->Core);
3123 i_addWarning(tr("Verification of the self signed certificate used to sign '%s' failed (%Rrc)%RTeim"),
3124 pTask->locInfo.strPath.c_str(), vrc, &pErrInfo->Core);
3125 }
3126
3127 /* Just warn if it's not a CA. Self-signed certificates are
3128 hardly trustworthy to start with without the user's consent. */
3129 if ( !m->SignerCert.TbsCertificate.T3.pBasicConstraints
3130 || !m->SignerCert.TbsCertificate.T3.pBasicConstraints->CA.fValue)
3131 i_addWarning(tr("Self signed certificate used to sign '%s' is not marked as certificate authority (CA)"),
3132 pTask->locInfo.strPath.c_str());
3133
3134 return S_OK;
3135}
3136
3137/**
3138 * Worker for i_readTailProcessing that verfies a non-self-issued OVF
3139 * certificate when no PKCS\#7/CMS signature using the same certificate is
3140 * present.
3141 */
3142HRESULT Appliance::i_readTailProcessingVerifyIssuedOvfCert(TaskOVF *pTask, RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo)
3143{
3144 /*
3145 * The certificate is not self-signed. Use the system certificate
3146 * stores to try build a path that validates successfully.
3147 */
3148 HRESULT hrc = S_OK;
3149 RTCRX509CERTPATHS hCertPaths;
3150 int vrc = RTCrX509CertPathsCreate(&hCertPaths, &m->SignerCert);
3151 if (RT_SUCCESS(vrc))
3152 {
3153 /* Get trusted certificates from the system and add them to the path finding mission. */
3154 vrc = RTCrX509CertPathsSetTrustedStore(hCertPaths, hTrustedStore);
3155 if (RT_FAILURE(vrc))
3156 hrc = setErrorBoth(E_FAIL, vrc, tr("RTCrX509CertPathsSetTrustedStore failed (%Rrc)"), vrc);
3157
3158 /* Add untrusted intermediate certificates. */
3159 if (RT_SUCCESS(vrc))
3160 {
3161 /// @todo RTCrX509CertPathsSetUntrustedStore(hCertPaths, hAdditionalCerts);
3162 /// We should look for intermediate certificates on the system, at least.
3163 }
3164 if (RT_SUCCESS(vrc))
3165 {
3166 /*
3167 * Do the building and verification of certificate paths.
3168 */
3169 vrc = RTCrX509CertPathsBuild(hCertPaths, RTErrInfoInitStatic(pErrInfo));
3170 if (RT_SUCCESS(vrc))
3171 {
3172 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(pErrInfo));
3173 if (RT_SUCCESS(vrc))
3174 {
3175 /*
3176 * Mark the certificate as good.
3177 */
3178 /** @todo check the certificate purpose? If so, share with self-signed. */
3179 m->fCertificateValid = true;
3180 m->fCertificateMissingPath = false;
3181
3182 /*
3183 * We add a warning if the certificate path isn't valid at the current
3184 * time. Since the time is only considered during path validation and we
3185 * can repeat the validation process (but not building), it's easy to check.
3186 */
3187 RTTIMESPEC Now;
3188 vrc = RTCrX509CertPathsSetValidTimeSpec(hCertPaths, RTTimeNow(&Now));
3189 if (RT_SUCCESS(vrc))
3190 {
3191 vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(pErrInfo));
3192 if (RT_SUCCESS(vrc))
3193 m->fCertificateValidTime = true;
3194 else
3195 i_addWarning(tr("The certificate used to sign '%s' (or a certificate in the path) is not currently valid (%Rrc)"),
3196 pTask->locInfo.strPath.c_str(), vrc);
3197 }
3198 else
3199 hrc = setErrorVrc(vrc, "RTCrX509CertPathsSetValidTimeSpec failed: %Rrc", vrc);
3200 }
3201 else if (vrc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS)
3202 {
3203 m->fCertificateValid = true;
3204 i_addWarning(tr("No trusted certificate paths"));
3205
3206 /* Add another warning if the pathless certificate is not valid at present. */
3207 RTTIMESPEC Now;
3208 if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
3209 m->fCertificateValidTime = true;
3210 else
3211 i_addWarning(tr("The certificate used to sign '%s' is not currently valid"),
3212 pTask->locInfo.strPath.c_str());
3213 }
3214 else
3215 hrc = setErrorBoth(E_FAIL, vrc, tr("Certificate path validation failed (%Rrc%RTeim)"), vrc, &pErrInfo->Core);
3216 }
3217 else
3218 hrc = setErrorBoth(E_FAIL, vrc, tr("Certificate path building failed (%Rrc%RTeim)"), vrc, &pErrInfo->Core);
3219 }
3220 RTCrX509CertPathsRelease(hCertPaths);
3221 }
3222 else
3223 hrc = setErrorVrc(vrc, tr("RTCrX509CertPathsCreate failed: %Rrc"), vrc);
3224 return hrc;
3225}
3226
3227/**
3228 * Helper for i_readTailProcessingVerifySignerInfo that reports a verfication
3229 * failure.
3230 *
3231 * @returns S_OK
3232 */
3233HRESULT Appliance::i_readTailProcessingVerifyContentInfoFailOne(const char *pszSignature, int vrc, PRTERRINFOSTATIC pErrInfo)
3234{
3235 i_addWarning(tr("%s verification failed: %Rrc%RTeim"), pszSignature, vrc, &pErrInfo->Core);
3236 if (m->strCertError.isEmpty())
3237 m->strCertError.printfNoThrow(tr("%s verification failed: %Rrc%RTeim"), pszSignature, vrc, &pErrInfo->Core);
3238 return S_OK;
3239}
3240
3241/**
3242 * Worker for i_readTailProcessingVerifyContentInfoCerts that analyzes why the
3243 * standard verification of a signer info entry failed (@a vrc & @a pErrInfo).
3244 *
3245 * There are a couple of things we might want try to investigate deeper here:
3246 * 1. Untrusted signing certificate, often self-signed.
3247 * 2. Untrusted timstamp signing certificate.
3248 * 3. Certificate not valid at the current time and there isn't a
3249 * timestamp counter signature.
3250 *
3251 * That said, it is difficult to get an accurate fix and report on the
3252 * issues here since there are a number of error sources, so just try identify
3253 * the more typical cases.
3254 *
3255 * @note Caller cleans up *phTrustedStore2 if not NIL.
3256 */
3257HRESULT Appliance::i_readTailProcessingVerifyAnalyzeSignerInfo(void const *pvData, size_t cbData, RTCRSTORE hTrustedStore,
3258 uint32_t iSigner, PRTTIMESPEC pNow, int vrc,
3259 PRTERRINFOSTATIC pErrInfo, PRTCRSTORE phTrustedStore2)
3260{
3261 PRTCRPKCS7SIGNEDDATA const pSignedData = m->ContentInfo.u.pSignedData;
3262 PRTCRPKCS7SIGNERINFO const pSigner = pSignedData->SignerInfos.papItems[iSigner];
3263
3264 /*
3265 * Error/warning message prefix:
3266 */
3267 const char *pszSignature;
3268 if (iSigner == 0 && m->fContentInfoSameCert)
3269 pszSignature = tr("OVF & PKCS#7/CMS signature");
3270 else
3271 pszSignature = tr("PKCS#7/CMS signature");
3272 char szSignatureBuf[64];
3273 if (pSignedData->SignerInfos.cItems > 1)
3274 {
3275 RTStrPrintf(szSignatureBuf, sizeof(szSignatureBuf), tr("%s #%u"), pszSignature, iSigner + 1);
3276 pszSignature = szSignatureBuf;
3277 }
3278
3279 /*
3280 * Don't try handle weird stuff:
3281 */
3282 /** @todo Are there more statuses we can deal with here? */
3283 if ( vrc != VERR_CR_X509_CPV_NOT_VALID_AT_TIME
3284 && vrc != VERR_CR_X509_NO_TRUST_ANCHOR)
3285 return i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrc, pErrInfo);
3286
3287 /*
3288 * Find the signing certificate.
3289 * We require the certificate to be included in the signed data here.
3290 */
3291 PCRTCRX509CERTIFICATE pSigningCert;
3292 pSigningCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates,
3293 &pSigner->IssuerAndSerialNumber.Name,
3294 &pSigner->IssuerAndSerialNumber.SerialNumber);
3295 if (!pSigningCert)
3296 {
3297 i_addWarning(tr("PKCS#7/CMS signature #%u does not include the signing certificate"), iSigner + 1);
3298 if (m->strCertError.isEmpty())
3299 m->strCertError.printfNoThrow(tr("PKCS#7/CMS signature #%u does not include the signing certificate"), iSigner + 1);
3300 return S_OK;
3301 }
3302
3303 PCRTCRCERTCTX const pCertCtxTrusted = RTCrStoreCertByIssuerAndSerialNo(hTrustedStore, &pSigner->IssuerAndSerialNumber.Name,
3304 &pSigner->IssuerAndSerialNumber.SerialNumber);
3305 bool const fSelfSigned = RTCrX509Certificate_IsSelfSigned(pSigningCert);
3306
3307 /*
3308 * Add warning about untrusted self-signed certificate:
3309 */
3310 if (fSelfSigned && !pCertCtxTrusted)
3311 i_addWarning(tr("%s: Untrusted self-signed certificate"), pszSignature);
3312
3313 /*
3314 * Start by eliminating signing time issues (2 + 3) first as primary problem.
3315 * Keep the error info and status for later failures.
3316 */
3317 char szTime[RTTIME_STR_LEN];
3318 RTTIMESPEC Now2 = *pNow;
3319 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
3320 | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
3321 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner), NIL_RTCRSTORE,
3322 hTrustedStore, &Now2, NULL, NULL,
3323 pvData, cbData, RTErrInfoInitStatic(pErrInfo));
3324 if (RT_SUCCESS(vrc))
3325 {
3326 /* Okay, is it an untrusted time signing certificate or just signing time in general? */
3327 RTTIMESPEC Now3 = *pNow;
3328 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
3329 | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
3330 | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
3331 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner), NIL_RTCRSTORE,
3332 hTrustedStore, &Now3, NULL, NULL, pvData, cbData, NULL);
3333 if (RT_SUCCESS(vrc))
3334 i_addWarning(tr("%s: Untrusted timestamp (%s)"), pszSignature, RTTimeSpecToString(&Now3, szTime, sizeof(szTime)));
3335 else
3336 i_addWarning(tr("%s: Not valid at current time, but validates fine for untrusted signing time (%s)"),
3337 pszSignature, RTTimeSpecToString(&Now2, szTime, sizeof(szTime)));
3338 return S_OK;
3339 }
3340
3341 /* If we've got a trusted signing certificate (unlikely, but whatever), we can stop already.
3342 If we haven't got a self-signed certificate, stop too as messaging becomes complicated otherwise. */
3343 if (pCertCtxTrusted || !fSelfSigned)
3344 return i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrc, pErrInfo);
3345
3346 int const vrcErrInfo = vrc;
3347
3348 /*
3349 * Create a new trust store that includes the signing certificate
3350 * to see what that changes.
3351 */
3352 vrc = RTCrStoreCreateInMemEx(phTrustedStore2, 1, hTrustedStore);
3353 AssertRCReturn(vrc, setErrorVrc(vrc, "RTCrStoreCreateInMemEx"));
3354 vrc = RTCrStoreCertAddX509(*phTrustedStore2, 0, (PRTCRX509CERTIFICATE)pSigningCert, NULL);
3355 AssertRCReturn(vrc, setErrorVrc(vrc, "RTCrStoreCertAddX509/%u", iSigner));
3356
3357 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo,
3358 RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
3359 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner), NIL_RTCRSTORE,
3360 *phTrustedStore2, pNow, NULL, NULL, pvData, cbData, NULL);
3361 if (RT_SUCCESS(vrc))
3362 {
3363 if (!fSelfSigned)
3364 i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrcErrInfo, pErrInfo);
3365 return S_OK;
3366 }
3367
3368 /*
3369 * Time problems too? Repeat what we did above, but with the modified trust store.
3370 */
3371 Now2 = *pNow;
3372 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
3373 | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
3374 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner), NIL_RTCRSTORE,
3375 *phTrustedStore2, pNow, NULL, NULL, pvData, cbData, NULL);
3376 if (RT_SUCCESS(vrc))
3377 {
3378 /* Okay, is it an untrusted time signing certificate or just signing time in general? */
3379 RTTIMESPEC Now3 = *pNow;
3380 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
3381 | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
3382 | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
3383 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner), NIL_RTCRSTORE,
3384 *phTrustedStore2, &Now3, NULL, NULL, pvData, cbData, NULL);
3385 if (RT_SUCCESS(vrc))
3386 i_addWarning(tr("%s: Untrusted timestamp (%s)"), pszSignature, RTTimeSpecToString(&Now3, szTime, sizeof(szTime)));
3387 else
3388 i_addWarning(tr("%s: Not valid at current time, but validates fine for untrusted signing time (%s)"),
3389 pszSignature, RTTimeSpecToString(&Now2, szTime, sizeof(szTime)));
3390 }
3391 else
3392 i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrcErrInfo, pErrInfo);
3393
3394 return S_OK;
3395}
3396
3397/**
3398 * Verify the signing certificates used to sign the PKCS\#7/CMS signature.
3399 *
3400 * ASSUMES that we've previously verified the PKCS\#7/CMS stuff in
3401 * trust-all-certs-without-question mode and it's just the certificate
3402 * validation that can fail now.
3403 */
3404HRESULT Appliance::i_readTailProcessingVerifyContentInfoCerts(void const *pvData, size_t cbData,
3405 RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo)
3406{
3407 /*
3408 * Just do a run and see what happens (note we've already verified
3409 * the data signatures, which just leaves certificates and paths).
3410 */
3411 RTTIMESPEC Now;
3412 int vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo,
3413 RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY,
3414 NIL_RTCRSTORE /*hAdditionalCerts*/, hTrustedStore,
3415 RTTimeNow(&Now), NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
3416 pvData, cbData, RTErrInfoInitStatic(pErrInfo));
3417 if (RT_SUCCESS(vrc))
3418 m->fContentInfoVerifiedOkay = true;
3419 else
3420 {
3421 /*
3422 * Deal with each of the signatures separately to try figure out
3423 * more exactly what's going wrong.
3424 */
3425 uint32_t cVerifiedOkay = 0;
3426 PRTCRPKCS7SIGNEDDATA pSignedData = m->ContentInfo.u.pSignedData;
3427 for (uint32_t iSigner = 0; iSigner < pSignedData->SignerInfos.cItems; iSigner++)
3428 {
3429 vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo,
3430 RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
3431 | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner),
3432 NIL_RTCRSTORE /*hAdditionalCerts*/, hTrustedStore,
3433 &Now, NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
3434 pvData, cbData, RTErrInfoInitStatic(pErrInfo));
3435 if (RT_SUCCESS(vrc))
3436 cVerifiedOkay++;
3437 else
3438 {
3439 RTCRSTORE hTrustedStore2 = NIL_RTCRSTORE;
3440 HRESULT hrc = i_readTailProcessingVerifyAnalyzeSignerInfo(pvData, cbData, hTrustedStore, iSigner, &Now,
3441 vrc, pErrInfo, &hTrustedStore2);
3442 RTCrStoreRelease(hTrustedStore2);
3443 if (FAILED(hrc))
3444 return hrc;
3445 }
3446 }
3447
3448 if ( pSignedData->SignerInfos.cItems > 1
3449 && pSignedData->SignerInfos.cItems != cVerifiedOkay)
3450 i_addWarning(tr("%u out of %u PKCS#7/CMS signatures verfified okay"),
3451 cVerifiedOkay, pSignedData->SignerInfos.cItems);
3452 }
3453
3454 return S_OK;
3455}
3456
3457
3458
3459/*******************************************************************************
3460 * Import stuff
3461 ******************************************************************************/
3462
3463/**
3464 * Implementation for importing OVF data into VirtualBox. This starts a new thread which will call
3465 * Appliance::taskThreadImportOrExport().
3466 *
3467 * This creates one or more new machines according to the VirtualSystemScription instances created by
3468 * Appliance::Interpret().
3469 *
3470 * This is in a separate private method because it is used from one location:
3471 *
3472 * 1) from the public Appliance::ImportMachines().
3473 *
3474 * @param locInfo
3475 * @param progress
3476 * @return
3477 */
3478HRESULT Appliance::i_importImpl(const LocationInfo &locInfo,
3479 ComObjPtr<Progress> &progress)
3480{
3481 HRESULT rc;
3482
3483 /* Initialize our worker task */
3484 ThreadTask *pTask;
3485 if (locInfo.storageType != VFSType_Cloud)
3486 {
3487 rc = i_setUpProgress(progress, Utf8StrFmt(tr("Importing appliance '%s'"), locInfo.strPath.c_str()),
3488 locInfo.storageType == VFSType_File ? ImportFile : ImportS3);
3489 if (FAILED(rc))
3490 return setError(rc, tr("Failed to create task for importing appliance into VirtualBox"));
3491 try
3492 {
3493 pTask = new TaskOVF(this, TaskOVF::Import, locInfo, progress);
3494 }
3495 catch (std::bad_alloc &)
3496 {
3497 return E_OUTOFMEMORY;
3498 }
3499 }
3500 else
3501 {
3502 if (locInfo.strProvider.equals("OCI"))
3503 {
3504 /*
3505 * 1. Create a custom image from the instance:
3506 * - 2 operations (starting and waiting)
3507 * 2. Import the custom image into the Object Storage (OCI format - TAR file with QCOW2 image and JSON file):
3508 * - 2 operations (starting and waiting)
3509 * 3. Download the object from the Object Storage:
3510 * - 1 operation (starting and downloadind is one operation)
3511 * 4. Open the object, extract an image and convert one to VDI:
3512 * - 1 operation (extracting and conversion are piped) because only 1 base bootable image is imported for now
3513 * 5. Create VM with user settings and attach the converted image to VM:
3514 * - 1 operation.
3515 * 6. Cleanup phase.
3516 * - 1 to N operations.
3517 * The number of the correct Progress operations are much tricky here.
3518 * Whether Machine::deleteConfig() is called or Medium::deleteStorage() is called in the loop.
3519 * Both require a new Progress object. To work with these functions the original Progress object uses
3520 * the function Progress::waitForOtherProgressCompletion().
3521 *
3522 * Some speculation here...
3523 * Total: 2+2+1(cloud) + 1+1(local) + 1+1+1(cleanup) = 10 operations
3524 * or
3525 * Total: 2+2+1(cloud) + 1+1(local) + 1(cleanup) = 8 operations
3526 * if VM wasn't created we would have only 1 registered image for cleanup.
3527 *
3528 * Weight "#define"s for the Cloud operations are located in the file OCICloudClient.h.
3529 * Weight of cloud import operations (1-3 items from above):
3530 * Total = 750 = 25+75(start and wait)+25+375(start and wait)+250(download)
3531 *
3532 * Weight of local import operations (4-5 items from above):
3533 * Total = 150 = 100 (extract and convert) + 50 (create VM, attach disks)
3534 *
3535 * Weight of local cleanup operations (6 item from above):
3536 * Some speculation here...
3537 * Total = 3 = 1 (1 image) + 1 (1 setting file)+ 1 (1 prev setting file) - quick operations
3538 * or
3539 * Total = 1 (1 image) if VM wasn't created we would have only 1 registered image for now.
3540 */
3541 try
3542 {
3543 rc = progress.createObject();
3544 if (SUCCEEDED(rc))
3545 rc = progress->init(mVirtualBox, static_cast<IAppliance *>(this),
3546 Utf8Str(tr("Importing VM from Cloud...")),
3547 TRUE /* aCancelable */,
3548 10, // ULONG cOperations,
3549 1000, // ULONG ulTotalOperationsWeight,
3550 Utf8Str(tr("Start import VM from the Cloud...")), // aFirstOperationDescription
3551 25); // ULONG ulFirstOperationWeight
3552 if (SUCCEEDED(rc))
3553 pTask = new TaskCloud(this, TaskCloud::Import, locInfo, progress);
3554 else
3555 pTask = NULL; /* shut up vcc */
3556 }
3557 catch (std::bad_alloc &)
3558 {
3559 return E_OUTOFMEMORY;
3560 }
3561 if (FAILED(rc))
3562 return setError(rc, tr("Failed to create task for importing appliance into VirtualBox"));
3563 }
3564 else
3565 return setError(E_NOTIMPL, tr("Only \"OCI\" cloud provider is supported for now. \"%s\" isn't supported."),
3566 locInfo.strProvider.c_str());
3567 }
3568
3569 /*
3570 * Start the task thread.
3571 */
3572 rc = pTask->createThread();
3573 pTask = NULL;
3574 if (SUCCEEDED(rc))
3575 return rc;
3576 return setError(rc, tr("Failed to start thread for importing appliance into VirtualBox"));
3577}
3578
3579/**
3580 * Actual worker code for importing OVF data into VirtualBox.
3581 *
3582 * This is called from Appliance::taskThreadImportOrExport() and therefore runs
3583 * on the OVF import worker thread. This creates one or more new machines
3584 * according to the VirtualSystemScription instances created by
3585 * Appliance::Interpret().
3586 *
3587 * This runs in two contexts:
3588 *
3589 * 1) in a first worker thread; in that case, Appliance::ImportMachines() called
3590 * Appliance::i_importImpl();
3591 *
3592 * 2) in a second worker thread; in that case, Appliance::ImportMachines()
3593 * called Appliance::i_importImpl(), which called Appliance::i_importFSOVA(),
3594 * which called Appliance::i_importImpl(), which then called this again.
3595 *
3596 * @param pTask The OVF task data.
3597 * @return COM status code.
3598 */
3599HRESULT Appliance::i_importFS(TaskOVF *pTask)
3600{
3601 LogFlowFuncEnter();
3602 LogFlowFunc(("Appliance %p\n", this));
3603
3604 /* Change the appliance state so we can safely leave the lock while doing
3605 * time-consuming image imports; also the below method calls do all kinds of
3606 * locking which conflicts with the appliance object lock. */
3607 AutoWriteLock writeLock(this COMMA_LOCKVAL_SRC_POS);
3608 /* Check if the appliance is currently busy. */
3609 if (!i_isApplianceIdle())
3610 return E_ACCESSDENIED;
3611 /* Set the internal state to importing. */
3612 m->state = ApplianceImporting;
3613
3614 HRESULT rc = S_OK;
3615
3616 /* Clear the list of imported machines, if any */
3617 m->llGuidsMachinesCreated.clear();
3618
3619 if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
3620 rc = i_importFSOVF(pTask, writeLock);
3621 else
3622 rc = i_importFSOVA(pTask, writeLock);
3623 if (FAILED(rc))
3624 {
3625 /* With _whatever_ error we've had, do a complete roll-back of
3626 * machines and images we've created */
3627 writeLock.release();
3628 ErrorInfoKeeper eik;
3629 for (list<Guid>::iterator itID = m->llGuidsMachinesCreated.begin();
3630 itID != m->llGuidsMachinesCreated.end();
3631 ++itID)
3632 {
3633 Guid guid = *itID;
3634 Bstr bstrGuid = guid.toUtf16();
3635 ComPtr<IMachine> failedMachine;
3636 HRESULT rc2 = mVirtualBox->FindMachine(bstrGuid.raw(), failedMachine.asOutParam());
3637 if (SUCCEEDED(rc2))
3638 {
3639 SafeIfaceArray<IMedium> aMedia;
3640 rc2 = failedMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
3641 ComPtr<IProgress> pProgress2;
3642 rc2 = failedMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress2.asOutParam());
3643 pProgress2->WaitForCompletion(-1);
3644 }
3645 }
3646 writeLock.acquire();
3647 }
3648
3649 /* Reset the state so others can call methods again */
3650 m->state = ApplianceIdle;
3651
3652 LogFlowFunc(("rc=%Rhrc\n", rc));
3653 LogFlowFuncLeave();
3654 return rc;
3655}
3656
3657HRESULT Appliance::i_importFSOVF(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
3658{
3659 return i_importDoIt(pTask, rWriteLock);
3660}
3661
3662HRESULT Appliance::i_importFSOVA(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
3663{
3664 LogFlowFuncEnter();
3665
3666 /*
3667 * Open the tar file as file stream.
3668 */
3669 RTVFSIOSTREAM hVfsIosOva;
3670 int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
3671 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
3672 if (RT_FAILURE(vrc))
3673 return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
3674
3675 RTVFSFSSTREAM hVfsFssOva;
3676 vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
3677 RTVfsIoStrmRelease(hVfsIosOva);
3678 if (RT_FAILURE(vrc))
3679 return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
3680
3681 /*
3682 * Join paths with the i_importFSOVF code.
3683 *
3684 * Note! We don't need to skip the OVF, manifest or signature files, as the
3685 * i_importMachineGeneric, i_importVBoxMachine and i_importOpenSourceFile
3686 * code will deal with this (as there could be other files in the OVA
3687 * that we don't process, like 'de-DE-resources.xml' in EXAMPLE 1,
3688 * Appendix D.1, OVF v2.1.0).
3689 */
3690 HRESULT hrc = i_importDoIt(pTask, rWriteLock, hVfsFssOva);
3691
3692 RTVfsFsStrmRelease(hVfsFssOva);
3693
3694 LogFlowFunc(("returns %Rhrc\n", hrc));
3695 return hrc;
3696}
3697
3698/**
3699 * Does the actual importing after the caller has made the source accessible.
3700 *
3701 * @param pTask The import task.
3702 * @param rWriteLock The write lock the caller's caller is holding,
3703 * will be released for some reason.
3704 * @param hVfsFssOva The file system stream if OVA, NIL if not.
3705 * @returns COM status code.
3706 * @throws Nothing.
3707 */
3708HRESULT Appliance::i_importDoIt(TaskOVF *pTask, AutoWriteLockBase &rWriteLock, RTVFSFSSTREAM hVfsFssOva /*= NIL_RTVFSFSSTREAM*/)
3709{
3710 rWriteLock.release();
3711
3712 HRESULT hrc = E_FAIL;
3713 try
3714 {
3715 /*
3716 * Create the import stack for the rollback on errors.
3717 */
3718 ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, hVfsFssOva);
3719
3720 try
3721 {
3722 /* Do the importing. */
3723 i_importMachines(stack);
3724
3725 /* We should've processed all the files now, so compare. */
3726 hrc = i_verifyManifestFile(stack);
3727
3728 /* If everything was successful so far check if some extension
3729 * pack wants to do file sanity checking. */
3730 if (SUCCEEDED(hrc))
3731 {
3732 /** @todo */;
3733 }
3734 }
3735 catch (HRESULT hrcXcpt)
3736 {
3737 hrc = hrcXcpt;
3738 }
3739 catch (...)
3740 {
3741 AssertFailed();
3742 hrc = E_FAIL;
3743 }
3744 if (FAILED(hrc))
3745 {
3746 /*
3747 * Restoring original UUID from OVF description file.
3748 * During import VBox creates new UUIDs for imported images and
3749 * assigns them to the images. In case of failure we have to restore
3750 * the original UUIDs because those new UUIDs are obsolete now and
3751 * won't be used anymore.
3752 */
3753 ErrorInfoKeeper eik; /* paranoia */
3754 list< ComObjPtr<VirtualSystemDescription> >::const_iterator itvsd;
3755 /* Iterate through all virtual systems of that appliance */
3756 for (itvsd = m->virtualSystemDescriptions.begin();
3757 itvsd != m->virtualSystemDescriptions.end();
3758 ++itvsd)
3759 {
3760 ComObjPtr<VirtualSystemDescription> vsdescThis = (*itvsd);
3761 settings::MachineConfigFile *pConfig = vsdescThis->m->pConfig;
3762 if(vsdescThis->m->pConfig!=NULL)
3763 stack.restoreOriginalUUIDOfAttachedDevice(pConfig);
3764 }
3765 }
3766 }
3767 catch (...)
3768 {
3769 hrc = E_FAIL;
3770 AssertFailed();
3771 }
3772
3773 rWriteLock.acquire();
3774 return hrc;
3775}
3776
3777/**
3778 * Undocumented, you figure it from the name.
3779 *
3780 * @returns Undocumented
3781 * @param stack Undocumented.
3782 */
3783HRESULT Appliance::i_verifyManifestFile(ImportStack &stack)
3784{
3785 LogFlowThisFuncEnter();
3786 HRESULT hrc;
3787 int vrc;
3788
3789 /*
3790 * No manifest is fine, it always matches.
3791 */
3792 if (m->hTheirManifest == NIL_RTMANIFEST)
3793 hrc = S_OK;
3794 else
3795 {
3796 /*
3797 * Hack: If the manifest we just read doesn't have a digest for the OVF, copy
3798 * it from the manifest we got from the caller.
3799 * @bugref{6022#c119}
3800 */
3801 if ( !RTManifestEntryExists(m->hTheirManifest, m->strOvfManifestEntry.c_str())
3802 && RTManifestEntryExists(m->hOurManifest, m->strOvfManifestEntry.c_str()) )
3803 {
3804 uint32_t fType = 0;
3805 char szDigest[512 + 1];
3806 vrc = RTManifestEntryQueryAttr(m->hOurManifest, m->strOvfManifestEntry.c_str(), NULL, RTMANIFEST_ATTR_ANY,
3807 szDigest, sizeof(szDigest), &fType);
3808 if (RT_SUCCESS(vrc))
3809 vrc = RTManifestEntrySetAttr(m->hTheirManifest, m->strOvfManifestEntry.c_str(),
3810 NULL /*pszAttr*/, szDigest, fType);
3811 if (RT_FAILURE(vrc))
3812 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Error fudging missing OVF digest in manifest: %Rrc"), vrc);
3813 }
3814
3815 /*
3816 * Compare with the digests we've created while read/processing the import.
3817 *
3818 * We specify the RTMANIFEST_EQUALS_IGN_MISSING_ATTRS to ignore attributes
3819 * (SHA1, SHA256, etc) that are only present in one of the manifests, as long
3820 * as each entry has at least one common attribute that we can check. This
3821 * is important for the OVF in OVAs, for which we generates several digests
3822 * since we don't know which are actually used in the manifest (OVF comes
3823 * first in an OVA, then manifest).
3824 */
3825 char szErr[256];
3826 vrc = RTManifestEqualsEx(m->hTheirManifest, m->hOurManifest, NULL /*papszIgnoreEntries*/,
3827 NULL /*papszIgnoreAttrs*/,
3828 RTMANIFEST_EQUALS_IGN_MISSING_ATTRS | RTMANIFEST_EQUALS_IGN_MISSING_ENTRIES_2ND,
3829 szErr, sizeof(szErr));
3830 if (RT_SUCCESS(vrc))
3831 hrc = S_OK;
3832 else
3833 hrc = setErrorVrc(vrc, tr("Digest mismatch (%Rrc): %s"), vrc, szErr);
3834 }
3835
3836 NOREF(stack);
3837 LogFlowThisFunc(("returns %Rhrc\n", hrc));
3838 return hrc;
3839}
3840
3841/**
3842 * Helper that converts VirtualSystem attachment values into VirtualBox attachment values.
3843 * Throws HRESULT values on errors!
3844 *
3845 * @param hdc in: the HardDiskController structure to attach to.
3846 * @param ulAddressOnParent in: the AddressOnParent parameter from OVF.
3847 * @param controllerName out: the name of the storage controller to attach to (e.g. "IDE").
3848 * @param lControllerPort out: the channel (controller port) of the controller to attach to.
3849 * @param lDevice out: the device number to attach to.
3850 */
3851void Appliance::i_convertDiskAttachmentValues(const ovf::HardDiskController &hdc,
3852 uint32_t ulAddressOnParent,
3853 Utf8Str &controllerName,
3854 int32_t &lControllerPort,
3855 int32_t &lDevice)
3856{
3857 Log(("Appliance::i_convertDiskAttachmentValues: hdc.system=%d, hdc.fPrimary=%d, ulAddressOnParent=%d\n",
3858 hdc.system,
3859 hdc.fPrimary,
3860 ulAddressOnParent));
3861
3862 switch (hdc.system)
3863 {
3864 case ovf::HardDiskController::IDE:
3865 // For the IDE bus, the port parameter can be either 0 or 1, to specify the primary
3866 // or secondary IDE controller, respectively. For the primary controller of the IDE bus,
3867 // the device number can be either 0 or 1, to specify the master or the slave device,
3868 // respectively. For the secondary IDE controller, the device number is always 1 because
3869 // the master device is reserved for the CD-ROM drive.
3870 controllerName = "IDE";
3871 switch (ulAddressOnParent)
3872 {
3873 case 0: // master
3874 if (!hdc.fPrimary)
3875 {
3876 // secondary master
3877 lControllerPort = (long)1;
3878 lDevice = (long)0;
3879 }
3880 else // primary master
3881 {
3882 lControllerPort = (long)0;
3883 lDevice = (long)0;
3884 }
3885 break;
3886
3887 case 1: // slave
3888 if (!hdc.fPrimary)
3889 {
3890 // secondary slave
3891 lControllerPort = (long)1;
3892 lDevice = (long)1;
3893 }
3894 else // primary slave
3895 {
3896 lControllerPort = (long)0;
3897 lDevice = (long)1;
3898 }
3899 break;
3900
3901 // used by older VBox exports
3902 case 2: // interpret this as secondary master
3903 lControllerPort = (long)1;
3904 lDevice = (long)0;
3905 break;
3906
3907 // used by older VBox exports
3908 case 3: // interpret this as secondary slave
3909 lControllerPort = (long)1;
3910 lDevice = (long)1;
3911 break;
3912
3913 default:
3914 throw setError(VBOX_E_NOT_SUPPORTED,
3915 tr("Invalid channel %RI16 specified; IDE controllers support only 0, 1 or 2"),
3916 ulAddressOnParent);
3917 break;
3918 }
3919 break;
3920
3921 case ovf::HardDiskController::SATA:
3922 controllerName = "SATA";
3923 lControllerPort = (long)ulAddressOnParent;
3924 lDevice = (long)0;
3925 break;
3926
3927 case ovf::HardDiskController::SCSI:
3928 {
3929 if(hdc.strControllerType.compare("lsilogicsas")==0)
3930 controllerName = "SAS";
3931 else
3932 controllerName = "SCSI";
3933 lControllerPort = (long)ulAddressOnParent;
3934 lDevice = (long)0;
3935 break;
3936 }
3937
3938 default: break;
3939 }
3940
3941 Log(("=> lControllerPort=%d, lDevice=%d\n", lControllerPort, lDevice));
3942}
3943
3944/**
3945 * Imports one image.
3946 *
3947 * This is common code shared between
3948 * -- i_importMachineGeneric() for the OVF case; in that case the information comes from
3949 * the OVF virtual systems;
3950 * -- i_importVBoxMachine(); in that case, the information comes from the <vbox:Machine>
3951 * tag.
3952 *
3953 * Both ways of describing machines use the OVF disk references section, so in both cases
3954 * the caller needs to pass in the ovf::DiskImage structure from ovfreader.cpp.
3955 *
3956 * As a result, in both cases, if di.strHref is empty, we create a new image as per the OVF
3957 * spec, even though this cannot really happen in the vbox:Machine case since such data
3958 * would never have been exported.
3959 *
3960 * This advances stack.pProgress by one operation with the image's weight.
3961 *
3962 * @param di ovfreader.cpp structure describing the image from the OVF that is to be imported
3963 * @param strDstPath Where to create the target image.
3964 * @param pTargetMedium out: The newly created target medium. This also gets pushed on stack.llHardDisksCreated for cleanup.
3965 * @param stack
3966 *
3967 * @throws HRESULT
3968 */
3969void Appliance::i_importOneDiskImage(const ovf::DiskImage &di,
3970 const Utf8Str &strDstPath,
3971 ComObjPtr<Medium> &pTargetMedium,
3972 ImportStack &stack)
3973{
3974 HRESULT rc;
3975
3976 Utf8Str strAbsDstPath;
3977 int vrc = RTPathAbsExCxx(strAbsDstPath, stack.strMachineFolder, strDstPath);
3978 AssertRCStmt(vrc, throw Global::vboxStatusCodeToCOM(vrc));
3979
3980 /* Get the system properties. */
3981 SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
3982
3983 /* Keep the source file ref handy for later. */
3984 const Utf8Str &strSourceOVF = di.strHref;
3985
3986 /* Construct source file path */
3987 Utf8Str strSrcFilePath;
3988 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
3989 strSrcFilePath = strSourceOVF;
3990 else
3991 {
3992 strSrcFilePath = stack.strSourceDir;
3993 strSrcFilePath.append(RTPATH_SLASH_STR);
3994 strSrcFilePath.append(strSourceOVF);
3995 }
3996
3997 /* First of all check if the original (non-absolute) destination path is
3998 * a valid medium UUID. If so, the user wants to import the image into
3999 * an existing path. This is useful for iSCSI for example. */
4000 /** @todo r=klaus the code structure after this point is totally wrong,
4001 * full of unnecessary code duplication and other issues. 4.2 still had
4002 * the right structure for importing into existing medium objects, which
4003 * the current code can't possibly handle. */
4004 RTUUID uuid;
4005 vrc = RTUuidFromStr(&uuid, strDstPath.c_str());
4006 if (vrc == VINF_SUCCESS)
4007 {
4008 rc = mVirtualBox->i_findHardDiskById(Guid(uuid), true, &pTargetMedium);
4009 if (FAILED(rc)) throw rc;
4010 }
4011 else
4012 {
4013 RTVFSIOSTREAM hVfsIosSrc = NIL_RTVFSIOSTREAM;
4014
4015 /* check read file to GZIP compression */
4016 bool const fGzipped = di.strCompression.compare("gzip", Utf8Str::CaseInsensitive) == 0;
4017 Utf8Str strDeleteTemp;
4018 try
4019 {
4020 Utf8Str strTrgFormat = "VMDK";
4021 ComObjPtr<MediumFormat> trgFormat;
4022 Bstr bstrFormatName;
4023 ULONG lCabs = 0;
4024
4025 char *pszSuff = RTPathSuffix(strAbsDstPath.c_str());
4026 if (pszSuff != NULL)
4027 {
4028 /*
4029 * Figure out which format the user like to have. Default is VMDK
4030 * or it can be VDI if according command-line option is set
4031 */
4032
4033 /*
4034 * We need a proper target format
4035 * if target format has been changed by user via GUI import wizard
4036 * or via VBoxManage import command (option --importtovdi)
4037 * then we need properly process such format like ISO
4038 * Because there is no conversion ISO to VDI
4039 */
4040 trgFormat = pSysProps->i_mediumFormatFromExtension(++pszSuff);
4041 if (trgFormat.isNull())
4042 throw setError(E_FAIL, tr("Unsupported medium format for disk image '%s'"), di.strHref.c_str());
4043
4044 rc = trgFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
4045 if (FAILED(rc)) throw rc;
4046
4047 strTrgFormat = Utf8Str(bstrFormatName);
4048
4049 if ( m->optListImport.contains(ImportOptions_ImportToVDI)
4050 && strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) != 0)
4051 {
4052 /* change the target extension */
4053 strTrgFormat = "vdi";
4054 trgFormat = pSysProps->i_mediumFormatFromExtension(strTrgFormat);
4055 strAbsDstPath.stripSuffix();
4056 strAbsDstPath.append(".");
4057 strAbsDstPath.append(strTrgFormat.c_str());
4058 }
4059
4060 /* Check the capabilities. We need create capabilities. */
4061 lCabs = 0;
4062 com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
4063 rc = trgFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
4064
4065 if (FAILED(rc))
4066 throw rc;
4067
4068 for (ULONG j = 0; j < mediumFormatCap.size(); j++)
4069 lCabs |= mediumFormatCap[j];
4070
4071 if ( !(lCabs & MediumFormatCapabilities_CreateFixed)
4072 && !(lCabs & MediumFormatCapabilities_CreateDynamic) )
4073 throw setError(VBOX_E_NOT_SUPPORTED,
4074 tr("Could not find a valid medium format for the target disk '%s'"),
4075 strAbsDstPath.c_str());
4076 }
4077 else
4078 {
4079 throw setError(VBOX_E_FILE_ERROR,
4080 tr("The target disk '%s' has no extension "),
4081 strAbsDstPath.c_str(), VERR_INVALID_NAME);
4082 }
4083
4084 /*CD/DVD case*/
4085 if (strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) == 0)
4086 {
4087 try
4088 {
4089 if (fGzipped)
4090 i_importDecompressFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
4091 else
4092 i_importCopyFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
4093
4094 ComPtr<IMedium> pTmp;
4095 rc = mVirtualBox->OpenMedium(Bstr(strAbsDstPath).raw(),
4096 DeviceType_DVD,
4097 AccessMode_ReadWrite,
4098 false,
4099 pTmp.asOutParam());
4100 if (FAILED(rc))
4101 throw rc;
4102
4103 IMedium *iM = pTmp;
4104 pTargetMedium = static_cast<Medium*>(iM);
4105 }
4106 catch (HRESULT /*arc*/)
4107 {
4108 throw;
4109 }
4110
4111 /* Advance to the next operation. */
4112 /* operation's weight, as set up with the IProgress originally */
4113 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
4114 RTPathFilename(strSourceOVF.c_str())).raw(),
4115 di.ulSuggestedSizeMB);
4116 }
4117 else/* HDD case*/
4118 {
4119 /* Create an IMedium object. */
4120 pTargetMedium.createObject();
4121
4122 rc = pTargetMedium->init(mVirtualBox,
4123 strTrgFormat,
4124 strAbsDstPath,
4125 Guid::Empty /* media registry: none yet */,
4126 DeviceType_HardDisk);
4127 if (FAILED(rc)) throw rc;
4128
4129 ComPtr<IProgress> pProgressImport;
4130 /* If strHref is empty we have to create a new file. */
4131 if (strSourceOVF.isEmpty())
4132 {
4133 com::SafeArray<MediumVariant_T> mediumVariant;
4134 mediumVariant.push_back(MediumVariant_Standard);
4135
4136 /* Kick off the creation of a dynamic growing disk image with the given capacity. */
4137 rc = pTargetMedium->CreateBaseStorage(di.iCapacity / _1M,
4138 ComSafeArrayAsInParam(mediumVariant),
4139 pProgressImport.asOutParam());
4140 if (FAILED(rc)) throw rc;
4141
4142 /* Advance to the next operation. */
4143 /* operation's weight, as set up with the IProgress originally */
4144 stack.pProgress->SetNextOperation(BstrFmt(tr("Creating disk image '%s'"),
4145 strAbsDstPath.c_str()).raw(),
4146 di.ulSuggestedSizeMB);
4147 }
4148 else
4149 {
4150 /* We need a proper source format description */
4151 /* Which format to use? */
4152 ComObjPtr<MediumFormat> srcFormat;
4153 rc = i_findMediumFormatFromDiskImage(di, srcFormat);
4154 if (FAILED(rc))
4155 throw setError(VBOX_E_NOT_SUPPORTED,
4156 tr("Could not find a valid medium format for the source disk '%s' "
4157 "Check correctness of the image format URL in the OVF description file "
4158 "or extension of the image"),
4159 RTPathFilename(strSourceOVF.c_str()));
4160
4161 /* If gzipped, decompress the GZIP file and save a new file in the target path */
4162 if (fGzipped)
4163 {
4164 Utf8Str strTargetFilePath(strAbsDstPath);
4165 strTargetFilePath.stripFilename();
4166 strTargetFilePath.append(RTPATH_SLASH_STR);
4167 strTargetFilePath.append("temp_");
4168 strTargetFilePath.append(RTPathFilename(strSrcFilePath.c_str()));
4169 strDeleteTemp = strTargetFilePath;
4170
4171 i_importDecompressFile(stack, strSrcFilePath, strTargetFilePath, strSourceOVF.c_str());
4172
4173 /* Correct the source and the target with the actual values */
4174 strSrcFilePath = strTargetFilePath;
4175
4176 /* Open the new source file. */
4177 vrc = RTVfsIoStrmOpenNormal(strSrcFilePath.c_str(), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
4178 &hVfsIosSrc);
4179 if (RT_FAILURE(vrc))
4180 throw setErrorVrc(vrc, tr("Error opening decompressed image file '%s' (%Rrc)"),
4181 strSrcFilePath.c_str(), vrc);
4182 }
4183 else
4184 hVfsIosSrc = i_importOpenSourceFile(stack, strSrcFilePath, strSourceOVF.c_str());
4185
4186 /* Add a read ahead thread to try speed things up with concurrent reads and
4187 writes going on in different threads. */
4188 RTVFSIOSTREAM hVfsIosReadAhead;
4189 vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/,
4190 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
4191 RTVfsIoStrmRelease(hVfsIosSrc);
4192 if (RT_FAILURE(vrc))
4193 throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"),
4194 strSrcFilePath.c_str(), vrc);
4195
4196 /* Start the source image cloning operation. */
4197 ComObjPtr<Medium> nullParent;
4198 ComObjPtr<Progress> pProgressImportTmp;
4199 rc = pProgressImportTmp.createObject();
4200 if (FAILED(rc)) throw rc;
4201 rc = pProgressImportTmp->init(mVirtualBox,
4202 static_cast<IAppliance*>(this),
4203 Utf8StrFmt(tr("Importing medium '%s'"),
4204 strAbsDstPath.c_str()),
4205 TRUE);
4206 if (FAILED(rc)) throw rc;
4207 pProgressImportTmp.queryInterfaceTo(pProgressImport.asOutParam());
4208 /* pProgressImportTmp is in parameter for Medium::i_importFile,
4209 * which is somewhat unusual and might be changed later. */
4210 rc = pTargetMedium->i_importFile(strSrcFilePath.c_str(),
4211 srcFormat,
4212 MediumVariant_Standard,
4213 hVfsIosReadAhead,
4214 nullParent,
4215 pProgressImportTmp,
4216 true /* aNotify */);
4217 RTVfsIoStrmRelease(hVfsIosReadAhead);
4218 hVfsIosSrc = NIL_RTVFSIOSTREAM;
4219 if (FAILED(rc))
4220 throw rc;
4221
4222 /* Advance to the next operation. */
4223 /* operation's weight, as set up with the IProgress originally */
4224 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
4225 RTPathFilename(strSourceOVF.c_str())).raw(),
4226 di.ulSuggestedSizeMB);
4227 }
4228
4229 /* Now wait for the background import operation to complete; this throws
4230 * HRESULTs on error. */
4231 stack.pProgress->WaitForOtherProgressCompletion(pProgressImport, 0 /* indefinite wait */);
4232
4233 /* The creating/importing has placed the medium in the global
4234 * media registry since the VM isn't created yet. Remove it
4235 * again to let it added to the right registry when the VM
4236 * has been created below. */
4237 pTargetMedium->i_removeRegistry(mVirtualBox->i_getGlobalRegistryId());
4238 }
4239 }
4240 catch (...)
4241 {
4242 if (strDeleteTemp.isNotEmpty())
4243 RTFileDelete(strDeleteTemp.c_str());
4244 throw;
4245 }
4246
4247 /* Make sure the source file is closed. */
4248 if (hVfsIosSrc != NIL_RTVFSIOSTREAM)
4249 RTVfsIoStrmRelease(hVfsIosSrc);
4250
4251 /*
4252 * Delete the temp gunzip result, if any.
4253 */
4254 if (strDeleteTemp.isNotEmpty())
4255 {
4256 vrc = RTFileDelete(strSrcFilePath.c_str());
4257 if (RT_FAILURE(vrc))
4258 setWarning(VBOX_E_FILE_ERROR,
4259 tr("Failed to delete the temporary file '%s' (%Rrc)"), strSrcFilePath.c_str(), vrc);
4260 }
4261 }
4262}
4263
4264/**
4265 * Imports one OVF virtual system (described by the given ovf::VirtualSystem and VirtualSystemDescription)
4266 * into VirtualBox by creating an IMachine instance, which is returned.
4267 *
4268 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
4269 * up any leftovers from this function. For this, the given ImportStack instance has received information
4270 * about what needs cleaning up (to support rollback).
4271 *
4272 * @param vsysThis OVF virtual system (machine) to import.
4273 * @param vsdescThis Matching virtual system description (machine) to import.
4274 * @param pNewMachine out: Newly created machine.
4275 * @param stack Cleanup stack for when this throws.
4276 */
4277void Appliance::i_importMachineGeneric(const ovf::VirtualSystem &vsysThis,
4278 ComObjPtr<VirtualSystemDescription> &vsdescThis,
4279 ComPtr<IMachine> &pNewMachine,
4280 ImportStack &stack)
4281{
4282 LogFlowFuncEnter();
4283 HRESULT rc;
4284
4285 // Get the instance of IGuestOSType which matches our string guest OS type so we
4286 // can use recommended defaults for the new machine where OVF doesn't provide any
4287 ComPtr<IGuestOSType> osType;
4288 rc = mVirtualBox->GetGuestOSType(Bstr(stack.strOsTypeVBox).raw(), osType.asOutParam());
4289 if (FAILED(rc)) throw rc;
4290
4291 /* Create the machine */
4292 SafeArray<BSTR> groups; /* no groups, or maybe one group... */
4293 if (!stack.strPrimaryGroup.isEmpty() && stack.strPrimaryGroup != "/")
4294 Bstr(stack.strPrimaryGroup).detachTo(groups.appendedRaw());
4295 rc = mVirtualBox->CreateMachine(Bstr(stack.strSettingsFilename).raw(),
4296 Bstr(stack.strNameVBox).raw(),
4297 ComSafeArrayAsInParam(groups),
4298 Bstr(stack.strOsTypeVBox).raw(),
4299 NULL, /* aCreateFlags */
4300 pNewMachine.asOutParam());
4301 if (FAILED(rc)) throw rc;
4302
4303 // set the description
4304 if (!stack.strDescription.isEmpty())
4305 {
4306 rc = pNewMachine->COMSETTER(Description)(Bstr(stack.strDescription).raw());
4307 if (FAILED(rc)) throw rc;
4308 }
4309
4310 // CPU count
4311 rc = pNewMachine->COMSETTER(CPUCount)(stack.cCPUs);
4312 if (FAILED(rc)) throw rc;
4313
4314 if (stack.fForceHWVirt)
4315 {
4316 rc = pNewMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, TRUE);
4317 if (FAILED(rc)) throw rc;
4318 }
4319
4320 // RAM
4321 rc = pNewMachine->COMSETTER(MemorySize)(stack.ulMemorySizeMB);
4322 if (FAILED(rc)) throw rc;
4323
4324 /* VRAM */
4325 /* Get the recommended VRAM for this guest OS type */
4326 ULONG vramVBox;
4327 rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox);
4328 if (FAILED(rc)) throw rc;
4329
4330 /* Set the VRAM */
4331 ComPtr<IGraphicsAdapter> pGraphicsAdapter;
4332 rc = pNewMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
4333 if (FAILED(rc)) throw rc;
4334 rc = pGraphicsAdapter->COMSETTER(VRAMSize)(vramVBox);
4335 if (FAILED(rc)) throw rc;
4336
4337 // I/O APIC: Generic OVF has no setting for this. Enable it if we
4338 // import a Windows VM because if if Windows was installed without IOAPIC,
4339 // it will not mind finding an one later on, but if Windows was installed
4340 // _with_ an IOAPIC, it will bluescreen if it's not found
4341 if (!stack.fForceIOAPIC)
4342 {
4343 Bstr bstrFamilyId;
4344 rc = osType->COMGETTER(FamilyId)(bstrFamilyId.asOutParam());
4345 if (FAILED(rc)) throw rc;
4346 if (bstrFamilyId == "Windows")
4347 stack.fForceIOAPIC = true;
4348 }
4349
4350 if (stack.fForceIOAPIC)
4351 {
4352 ComPtr<IBIOSSettings> pBIOSSettings;
4353 rc = pNewMachine->COMGETTER(BIOSSettings)(pBIOSSettings.asOutParam());
4354 if (FAILED(rc)) throw rc;
4355
4356 rc = pBIOSSettings->COMSETTER(IOAPICEnabled)(TRUE);
4357 if (FAILED(rc)) throw rc;
4358 }
4359
4360 if (stack.strFirmwareType.isNotEmpty())
4361 {
4362 FirmwareType_T firmwareType = FirmwareType_BIOS;
4363 if (stack.strFirmwareType.contains("EFI"))
4364 {
4365 if (stack.strFirmwareType.contains("32"))
4366 firmwareType = FirmwareType_EFI32;
4367 if (stack.strFirmwareType.contains("64"))
4368 firmwareType = FirmwareType_EFI64;
4369 else
4370 firmwareType = FirmwareType_EFI;
4371 }
4372 rc = pNewMachine->COMSETTER(FirmwareType)(firmwareType);
4373 if (FAILED(rc)) throw rc;
4374 }
4375
4376 if (!stack.strAudioAdapter.isEmpty())
4377 if (stack.strAudioAdapter.compare("null", Utf8Str::CaseInsensitive) != 0)
4378 {
4379 uint32_t audio = RTStrToUInt32(stack.strAudioAdapter.c_str()); // should be 0 for AC97
4380 ComPtr<IAudioAdapter> audioAdapter;
4381 rc = pNewMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam());
4382 if (FAILED(rc)) throw rc;
4383 rc = audioAdapter->COMSETTER(Enabled)(true);
4384 if (FAILED(rc)) throw rc;
4385 rc = audioAdapter->COMSETTER(AudioController)(static_cast<AudioControllerType_T>(audio));
4386 if (FAILED(rc)) throw rc;
4387 }
4388
4389#ifdef VBOX_WITH_USB
4390 /* USB Controller */
4391 if (stack.fUSBEnabled)
4392 {
4393 ComPtr<IUSBController> usbController;
4394 rc = pNewMachine->AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, usbController.asOutParam());
4395 if (FAILED(rc)) throw rc;
4396 }
4397#endif /* VBOX_WITH_USB */
4398
4399 /* Change the network adapters */
4400 uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
4401
4402 std::list<VirtualSystemDescriptionEntry*> vsdeNW = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
4403 if (vsdeNW.empty())
4404 {
4405 /* No network adapters, so we have to disable our default one */
4406 ComPtr<INetworkAdapter> nwVBox;
4407 rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam());
4408 if (FAILED(rc)) throw rc;
4409 rc = nwVBox->COMSETTER(Enabled)(false);
4410 if (FAILED(rc)) throw rc;
4411 }
4412 else if (vsdeNW.size() > maxNetworkAdapters)
4413 throw setError(VBOX_E_FILE_ERROR,
4414 tr("Too many network adapters: OVF requests %d network adapters, "
4415 "but VirtualBox only supports %d"),
4416 vsdeNW.size(), maxNetworkAdapters);
4417 else
4418 {
4419 list<VirtualSystemDescriptionEntry*>::const_iterator nwIt;
4420 size_t a = 0;
4421 for (nwIt = vsdeNW.begin();
4422 nwIt != vsdeNW.end();
4423 ++nwIt, ++a)
4424 {
4425 const VirtualSystemDescriptionEntry* pvsys = *nwIt;
4426
4427 const Utf8Str &nwTypeVBox = pvsys->strVBoxCurrent;
4428 uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str());
4429 ComPtr<INetworkAdapter> pNetworkAdapter;
4430 rc = pNewMachine->GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
4431 if (FAILED(rc)) throw rc;
4432 /* Enable the network card & set the adapter type */
4433 rc = pNetworkAdapter->COMSETTER(Enabled)(true);
4434 if (FAILED(rc)) throw rc;
4435 rc = pNetworkAdapter->COMSETTER(AdapterType)(static_cast<NetworkAdapterType_T>(tt1));
4436 if (FAILED(rc)) throw rc;
4437
4438 // default is NAT; change to "bridged" if extra conf says so
4439 if (pvsys->strExtraConfigCurrent.endsWith("type=Bridged", Utf8Str::CaseInsensitive))
4440 {
4441 /* Attach to the right interface */
4442 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged);
4443 if (FAILED(rc)) throw rc;
4444 ComPtr<IHost> host;
4445 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
4446 if (FAILED(rc)) throw rc;
4447 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
4448 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
4449 if (FAILED(rc)) throw rc;
4450 // We search for the first host network interface which
4451 // is usable for bridged networking
4452 for (size_t j = 0;
4453 j < nwInterfaces.size();
4454 ++j)
4455 {
4456 HostNetworkInterfaceType_T itype;
4457 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
4458 if (FAILED(rc)) throw rc;
4459 if (itype == HostNetworkInterfaceType_Bridged)
4460 {
4461 Bstr name;
4462 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
4463 if (FAILED(rc)) throw rc;
4464 /* Set the interface name to attach to */
4465 rc = pNetworkAdapter->COMSETTER(BridgedInterface)(name.raw());
4466 if (FAILED(rc)) throw rc;
4467 break;
4468 }
4469 }
4470 }
4471 /* Next test for host only interfaces */
4472 else if (pvsys->strExtraConfigCurrent.endsWith("type=HostOnly", Utf8Str::CaseInsensitive))
4473 {
4474 /* Attach to the right interface */
4475 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly);
4476 if (FAILED(rc)) throw rc;
4477 ComPtr<IHost> host;
4478 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
4479 if (FAILED(rc)) throw rc;
4480 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
4481 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
4482 if (FAILED(rc)) throw rc;
4483 // We search for the first host network interface which
4484 // is usable for host only networking
4485 for (size_t j = 0;
4486 j < nwInterfaces.size();
4487 ++j)
4488 {
4489 HostNetworkInterfaceType_T itype;
4490 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
4491 if (FAILED(rc)) throw rc;
4492 if (itype == HostNetworkInterfaceType_HostOnly)
4493 {
4494 Bstr name;
4495 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
4496 if (FAILED(rc)) throw rc;
4497 /* Set the interface name to attach to */
4498 rc = pNetworkAdapter->COMSETTER(HostOnlyInterface)(name.raw());
4499 if (FAILED(rc)) throw rc;
4500 break;
4501 }
4502 }
4503 }
4504 /* Next test for internal interfaces */
4505 else if (pvsys->strExtraConfigCurrent.endsWith("type=Internal", Utf8Str::CaseInsensitive))
4506 {
4507 /* Attach to the right interface */
4508 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Internal);
4509 if (FAILED(rc)) throw rc;
4510 }
4511 /* Next test for Generic interfaces */
4512 else if (pvsys->strExtraConfigCurrent.endsWith("type=Generic", Utf8Str::CaseInsensitive))
4513 {
4514 /* Attach to the right interface */
4515 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Generic);
4516 if (FAILED(rc)) throw rc;
4517 }
4518
4519 /* Next test for NAT network interfaces */
4520 else if (pvsys->strExtraConfigCurrent.endsWith("type=NATNetwork", Utf8Str::CaseInsensitive))
4521 {
4522 /* Attach to the right interface */
4523 rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork);
4524 if (FAILED(rc)) throw rc;
4525 com::SafeIfaceArray<INATNetwork> nwNATNetworks;
4526 rc = mVirtualBox->COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(nwNATNetworks));
4527 if (FAILED(rc)) throw rc;
4528 // Pick the first NAT network (if there is any)
4529 if (nwNATNetworks.size())
4530 {
4531 Bstr name;
4532 rc = nwNATNetworks[0]->COMGETTER(NetworkName)(name.asOutParam());
4533 if (FAILED(rc)) throw rc;
4534 /* Set the NAT network name to attach to */
4535 rc = pNetworkAdapter->COMSETTER(NATNetwork)(name.raw());
4536 if (FAILED(rc)) throw rc;
4537 break;
4538 }
4539 }
4540 }
4541 }
4542
4543 // Storage controller IDE
4544 std::list<VirtualSystemDescriptionEntry*> vsdeHDCIDE =
4545 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerIDE);
4546 /*
4547 * In OVF (at least VMware's version of it), an IDE controller has two ports,
4548 * so VirtualBox's single IDE controller with two channels and two ports each counts as
4549 * two OVF IDE controllers -- so we accept one or two such IDE controllers
4550 */
4551 size_t cIDEControllers = vsdeHDCIDE.size();
4552 if (cIDEControllers > 2)
4553 throw setError(VBOX_E_FILE_ERROR,
4554 tr("Too many IDE controllers in OVF; import facility only supports two"));
4555 if (!vsdeHDCIDE.empty())
4556 {
4557 // one or two IDE controllers present in OVF: add one VirtualBox controller
4558 ComPtr<IStorageController> pController;
4559 rc = pNewMachine->AddStorageController(Bstr("IDE").raw(), StorageBus_IDE, pController.asOutParam());
4560 if (FAILED(rc)) throw rc;
4561
4562 const char *pcszIDEType = vsdeHDCIDE.front()->strVBoxCurrent.c_str();
4563 if (!strcmp(pcszIDEType, "PIIX3"))
4564 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX3);
4565 else if (!strcmp(pcszIDEType, "PIIX4"))
4566 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX4);
4567 else if (!strcmp(pcszIDEType, "ICH6"))
4568 rc = pController->COMSETTER(ControllerType)(StorageControllerType_ICH6);
4569 else
4570 throw setError(VBOX_E_FILE_ERROR,
4571 tr("Invalid IDE controller type \"%s\""),
4572 pcszIDEType);
4573 if (FAILED(rc)) throw rc;
4574 }
4575
4576 /* Storage controller SATA */
4577 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSATA =
4578 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSATA);
4579 if (vsdeHDCSATA.size() > 1)
4580 throw setError(VBOX_E_FILE_ERROR,
4581 tr("Too many SATA controllers in OVF; import facility only supports one"));
4582 if (!vsdeHDCSATA.empty())
4583 {
4584 ComPtr<IStorageController> pController;
4585 const Utf8Str &hdcVBox = vsdeHDCSATA.front()->strVBoxCurrent;
4586 if (hdcVBox == "AHCI")
4587 {
4588 rc = pNewMachine->AddStorageController(Bstr("SATA").raw(),
4589 StorageBus_SATA,
4590 pController.asOutParam());
4591 if (FAILED(rc)) throw rc;
4592 }
4593 else
4594 throw setError(VBOX_E_FILE_ERROR,
4595 tr("Invalid SATA controller type \"%s\""),
4596 hdcVBox.c_str());
4597 }
4598
4599 /* Storage controller SCSI */
4600 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSCSI =
4601 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI);
4602 if (vsdeHDCSCSI.size() > 1)
4603 throw setError(VBOX_E_FILE_ERROR,
4604 tr("Too many SCSI controllers in OVF; import facility only supports one"));
4605 if (!vsdeHDCSCSI.empty())
4606 {
4607 ComPtr<IStorageController> pController;
4608 Utf8Str strName("SCSI");
4609 StorageBus_T busType = StorageBus_SCSI;
4610 StorageControllerType_T controllerType;
4611 const Utf8Str &hdcVBox = vsdeHDCSCSI.front()->strVBoxCurrent;
4612 if (hdcVBox == "LsiLogic")
4613 controllerType = StorageControllerType_LsiLogic;
4614 else if (hdcVBox == "LsiLogicSas")
4615 {
4616 // OVF treats LsiLogicSas as a SCSI controller but VBox considers it a class of its own
4617 strName = "SAS";
4618 busType = StorageBus_SAS;
4619 controllerType = StorageControllerType_LsiLogicSas;
4620 }
4621 else if (hdcVBox == "BusLogic")
4622 controllerType = StorageControllerType_BusLogic;
4623 else
4624 throw setError(VBOX_E_FILE_ERROR,
4625 tr("Invalid SCSI controller type \"%s\""),
4626 hdcVBox.c_str());
4627
4628 rc = pNewMachine->AddStorageController(Bstr(strName).raw(), busType, pController.asOutParam());
4629 if (FAILED(rc)) throw rc;
4630 rc = pController->COMSETTER(ControllerType)(controllerType);
4631 if (FAILED(rc)) throw rc;
4632 }
4633
4634 /* Storage controller SAS */
4635 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSAS =
4636 vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSAS);
4637 if (vsdeHDCSAS.size() > 1)
4638 throw setError(VBOX_E_FILE_ERROR,
4639 tr("Too many SAS controllers in OVF; import facility only supports one"));
4640 if (!vsdeHDCSAS.empty())
4641 {
4642 ComPtr<IStorageController> pController;
4643 rc = pNewMachine->AddStorageController(Bstr(L"SAS").raw(),
4644 StorageBus_SAS,
4645 pController.asOutParam());
4646 if (FAILED(rc)) throw rc;
4647 rc = pController->COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas);
4648 if (FAILED(rc)) throw rc;
4649 }
4650
4651 /* Now its time to register the machine before we add any storage devices */
4652 rc = mVirtualBox->RegisterMachine(pNewMachine);
4653 if (FAILED(rc)) throw rc;
4654
4655 // store new machine for roll-back in case of errors
4656 Bstr bstrNewMachineId;
4657 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
4658 if (FAILED(rc)) throw rc;
4659 Guid uuidNewMachine(bstrNewMachineId);
4660 m->llGuidsMachinesCreated.push_back(uuidNewMachine);
4661
4662 // Add floppies and CD-ROMs to the appropriate controllers.
4663 std::list<VirtualSystemDescriptionEntry*> vsdeFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy);
4664 if (vsdeFloppy.size() > 1)
4665 throw setError(VBOX_E_FILE_ERROR,
4666 tr("Too many floppy controllers in OVF; import facility only supports one"));
4667 std::list<VirtualSystemDescriptionEntry*> vsdeCDROM = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
4668 if ( !vsdeFloppy.empty()
4669 || !vsdeCDROM.empty()
4670 )
4671 {
4672 // If there's an error here we need to close the session, so
4673 // we need another try/catch block.
4674
4675 try
4676 {
4677 // to attach things we need to open a session for the new machine
4678 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
4679 if (FAILED(rc)) throw rc;
4680 stack.fSessionOpen = true;
4681
4682 ComPtr<IMachine> sMachine;
4683 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
4684 if (FAILED(rc)) throw rc;
4685
4686 // floppy first
4687 if (vsdeFloppy.size() == 1)
4688 {
4689 ComPtr<IStorageController> pController;
4690 rc = sMachine->AddStorageController(Bstr("Floppy").raw(),
4691 StorageBus_Floppy,
4692 pController.asOutParam());
4693 if (FAILED(rc)) throw rc;
4694
4695 Bstr bstrName;
4696 rc = pController->COMGETTER(Name)(bstrName.asOutParam());
4697 if (FAILED(rc)) throw rc;
4698
4699 // this is for rollback later
4700 MyHardDiskAttachment mhda;
4701 mhda.pMachine = pNewMachine;
4702 mhda.controllerName = bstrName;
4703 mhda.lControllerPort = 0;
4704 mhda.lDevice = 0;
4705
4706 Log(("Attaching floppy\n"));
4707
4708 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),
4709 mhda.lControllerPort,
4710 mhda.lDevice,
4711 DeviceType_Floppy,
4712 NULL);
4713 if (FAILED(rc)) throw rc;
4714
4715 stack.llHardDiskAttachments.push_back(mhda);
4716 }
4717
4718 rc = sMachine->SaveSettings();
4719 if (FAILED(rc)) throw rc;
4720
4721 // only now that we're done with all storage devices, close the session
4722 rc = stack.pSession->UnlockMachine();
4723 if (FAILED(rc)) throw rc;
4724 stack.fSessionOpen = false;
4725 }
4726 catch(HRESULT aRC)
4727 {
4728 com::ErrorInfo info;
4729
4730 if (stack.fSessionOpen)
4731 stack.pSession->UnlockMachine();
4732
4733 if (info.isFullAvailable())
4734 throw setError(aRC, Utf8Str(info.getText()).c_str());
4735 else
4736 throw setError(aRC, "Unknown error during OVF import");
4737 }
4738 }
4739
4740 // create the storage devices & connect them to the appropriate controllers
4741 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
4742 if (!avsdeHDs.empty())
4743 {
4744 // If there's an error here we need to close the session, so
4745 // we need another try/catch block.
4746 try
4747 {
4748#ifdef LOG_ENABLED
4749 if (LogIsEnabled())
4750 {
4751 size_t i = 0;
4752 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4753 itHD != avsdeHDs.end(); ++itHD, i++)
4754 Log(("avsdeHDs[%zu]: strRef=%s strOvf=%s\n", i, (*itHD)->strRef.c_str(), (*itHD)->strOvf.c_str()));
4755 i = 0;
4756 for (ovf::DiskImagesMap::const_iterator itDisk = stack.mapDisks.begin(); itDisk != stack.mapDisks.end(); ++itDisk)
4757 Log(("mapDisks[%zu]: strDiskId=%s strHref=%s\n",
4758 i, itDisk->second.strDiskId.c_str(), itDisk->second.strHref.c_str()));
4759
4760 }
4761#endif
4762
4763 // to attach things we need to open a session for the new machine
4764 rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
4765 if (FAILED(rc)) throw rc;
4766 stack.fSessionOpen = true;
4767
4768 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
4769 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
4770 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
4771 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
4772
4773
4774 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
4775 std::set<RTCString> disksResolvedNames;
4776
4777 uint32_t cImportedDisks = 0;
4778
4779 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
4780 {
4781/** @todo r=bird: Most of the code here is duplicated in the other machine
4782 * import method, factor out. */
4783 ovf::DiskImage diCurrent = oit->second;
4784
4785 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
4786 /* Iterate over all given images of the virtual system
4787 * description. We need to find the target image path,
4788 * which could be changed by the user. */
4789 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
4790 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4791 itHD != avsdeHDs.end();
4792 ++itHD)
4793 {
4794 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4795 if (vsdeHD->strRef == diCurrent.strDiskId)
4796 {
4797 vsdeTargetHD = vsdeHD;
4798 break;
4799 }
4800 }
4801 if (!vsdeTargetHD)
4802 {
4803 /* possible case if an image belongs to other virtual system (OVF package with multiple VMs inside) */
4804 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
4805 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
4806 NOREF(vmNameEntry);
4807 ++oit;
4808 continue;
4809 }
4810
4811 //diCurrent.strDiskId contains the image identifier (e.g. "vmdisk1"), which should exist
4812 //in the virtual system's images map under that ID and also in the global images map
4813 ovf::VirtualDisksMap::const_iterator itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
4814 if (itVDisk == vsysThis.mapVirtualDisks.end())
4815 throw setError(E_FAIL,
4816 tr("Internal inconsistency looking up disk image '%s'"),
4817 diCurrent.strHref.c_str());
4818
4819 /*
4820 * preliminary check availability of the image
4821 * This step is useful if image is placed in the OVA (TAR) package
4822 */
4823 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
4824 {
4825 /* It means that we possibly have imported the storage earlier on the previous loop steps*/
4826 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
4827 if (h != disksResolvedNames.end())
4828 {
4829 /* Yes, image name was found, we can skip it*/
4830 ++oit;
4831 continue;
4832 }
4833l_skipped:
4834 rc = i_preCheckImageAvailability(stack);
4835 if (SUCCEEDED(rc))
4836 {
4837 /* current opened file isn't the same as passed one */
4838 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
4839 {
4840 /* availableImage contains the image file reference (e.g. "disk1.vmdk"), which should
4841 * exist in the global images map.
4842 * And find the image from the OVF's disk list */
4843 ovf::DiskImagesMap::const_iterator itDiskImage;
4844 for (itDiskImage = stack.mapDisks.begin();
4845 itDiskImage != stack.mapDisks.end();
4846 itDiskImage++)
4847 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
4848 Utf8Str::CaseInsensitive) == 0)
4849 break;
4850 if (itDiskImage == stack.mapDisks.end())
4851 {
4852 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
4853 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
4854 goto l_skipped;
4855 }
4856
4857 /* replace with a new found image */
4858 diCurrent = *(&itDiskImage->second);
4859
4860 /*
4861 * Again iterate over all given images of the virtual system
4862 * description using the found image
4863 */
4864 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
4865 itHD != avsdeHDs.end();
4866 ++itHD)
4867 {
4868 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
4869 if (vsdeHD->strRef == diCurrent.strDiskId)
4870 {
4871 vsdeTargetHD = vsdeHD;
4872 break;
4873 }
4874 }
4875
4876 /*
4877 * in this case it's an error because something is wrong with the OVF description file.
4878 * May be VBox imports OVA package with wrong file sequence inside the archive.
4879 */
4880 if (!vsdeTargetHD)
4881 throw setError(E_FAIL,
4882 tr("Internal inconsistency looking up disk image '%s'"),
4883 diCurrent.strHref.c_str());
4884
4885 itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
4886 if (itVDisk == vsysThis.mapVirtualDisks.end())
4887 throw setError(E_FAIL,
4888 tr("Internal inconsistency looking up disk image '%s'"),
4889 diCurrent.strHref.c_str());
4890 }
4891 else
4892 {
4893 ++oit;
4894 }
4895 }
4896 else
4897 {
4898 ++oit;
4899 continue;
4900 }
4901 }
4902 else
4903 {
4904 /* just continue with normal files */
4905 ++oit;
4906 }
4907
4908 /* very important to store image name for the next checks */
4909 disksResolvedNames.insert(diCurrent.strHref);
4910////// end of duplicated code.
4911 const ovf::VirtualDisk &ovfVdisk = itVDisk->second;
4912
4913 ComObjPtr<Medium> pTargetMedium;
4914 if (stack.locInfo.storageType == VFSType_Cloud)
4915 {
4916 /* We have already all disks prepared (converted and registered in the VBox)
4917 * and in the correct place (VM machine folder).
4918 * so what is needed is to get the disk uuid from VirtualDisk::strDiskId
4919 * and find the Medium object with this uuid.
4920 * next just attach the Medium object to new VM.
4921 * VirtualDisk::strDiskId is filled in the */
4922
4923 Guid id(ovfVdisk.strDiskId);
4924 rc = mVirtualBox->i_findHardDiskById(id, false, &pTargetMedium);
4925 if (FAILED(rc))
4926 throw rc;
4927 }
4928 else
4929 {
4930 i_importOneDiskImage(diCurrent,
4931 vsdeTargetHD->strVBoxCurrent,
4932 pTargetMedium,
4933 stack);
4934 }
4935
4936 // now use the new uuid to attach the medium to our new machine
4937 ComPtr<IMachine> sMachine;
4938 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
4939 if (FAILED(rc))
4940 throw rc;
4941
4942 // find the hard disk controller to which we should attach
4943 ovf::HardDiskController hdc = (*vsysThis.mapControllers.find(ovfVdisk.idController)).second;
4944
4945 // this is for rollback later
4946 MyHardDiskAttachment mhda;
4947 mhda.pMachine = pNewMachine;
4948
4949 i_convertDiskAttachmentValues(hdc,
4950 ovfVdisk.ulAddressOnParent,
4951 mhda.controllerName,
4952 mhda.lControllerPort,
4953 mhda.lDevice);
4954
4955 Log(("Attaching disk %s to port %d on device %d\n",
4956 vsdeTargetHD->strVBoxCurrent.c_str(), mhda.lControllerPort, mhda.lDevice));
4957
4958 DeviceType_T devType = DeviceType_Null;
4959 rc = pTargetMedium->COMGETTER(DeviceType)(&devType);
4960 if (FAILED(rc))
4961 throw rc;
4962
4963 rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
4964 mhda.lControllerPort, // long controllerPort
4965 mhda.lDevice, // long device
4966 devType, // DeviceType_T type
4967 pTargetMedium);
4968 if (FAILED(rc))
4969 throw rc;
4970
4971 stack.llHardDiskAttachments.push_back(mhda);
4972
4973 rc = sMachine->SaveSettings();
4974 if (FAILED(rc))
4975 throw rc;
4976
4977 ++cImportedDisks;
4978
4979 } // end while(oit != stack.mapDisks.end())
4980
4981 /*
4982 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
4983 */
4984 if(cImportedDisks < avsdeHDs.size())
4985 {
4986 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
4987 vmNameEntry->strOvf.c_str()));
4988 }
4989
4990 // only now that we're done with all disks, close the session
4991 rc = stack.pSession->UnlockMachine();
4992 if (FAILED(rc))
4993 throw rc;
4994 stack.fSessionOpen = false;
4995 }
4996 catch(HRESULT aRC)
4997 {
4998 com::ErrorInfo info;
4999 if (stack.fSessionOpen)
5000 stack.pSession->UnlockMachine();
5001
5002 if (info.isFullAvailable())
5003 throw setError(aRC, Utf8Str(info.getText()).c_str());
5004 else
5005 throw setError(aRC, "Unknown error during OVF import");
5006 }
5007 }
5008 LogFlowFuncLeave();
5009}
5010
5011/**
5012 * Imports one OVF virtual system (described by a vbox:Machine tag represented by the given config
5013 * structure) into VirtualBox by creating an IMachine instance, which is returned.
5014 *
5015 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
5016 * up any leftovers from this function. For this, the given ImportStack instance has received information
5017 * about what needs cleaning up (to support rollback).
5018 *
5019 * The machine config stored in the settings::MachineConfigFile structure contains the UUIDs of
5020 * the disk attachments used by the machine when it was exported. We also add vbox:uuid attributes
5021 * to the OVF disks sections so we can look them up. While importing these UUIDs into a second host
5022 * will most probably work, reimporting them into the same host will cause conflicts, so we always
5023 * generate new ones on import. This involves the following:
5024 *
5025 * 1) Scan the machine config for disk attachments.
5026 *
5027 * 2) For each disk attachment found, look up the OVF disk image from the disk references section
5028 * and import the disk into VirtualBox, which creates a new UUID for it. In the machine config,
5029 * replace the old UUID with the new one.
5030 *
5031 * 3) Change the machine config according to the OVF virtual system descriptions, in case the
5032 * caller has modified them using setFinalValues().
5033 *
5034 * 4) Create the VirtualBox machine with the modfified machine config.
5035 *
5036 * @param vsdescThis
5037 * @param pReturnNewMachine
5038 * @param stack
5039 */
5040void Appliance::i_importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
5041 ComPtr<IMachine> &pReturnNewMachine,
5042 ImportStack &stack)
5043{
5044 LogFlowFuncEnter();
5045 Assert(vsdescThis->m->pConfig);
5046
5047 HRESULT rc = S_OK;
5048
5049 settings::MachineConfigFile &config = *vsdescThis->m->pConfig;
5050
5051 /*
5052 * step 1): modify machine config according to OVF config, in case the user
5053 * has modified them using setFinalValues()
5054 */
5055
5056 /* OS Type */
5057 config.machineUserData.strOsType = stack.strOsTypeVBox;
5058 /* Groups */
5059 if (stack.strPrimaryGroup.isEmpty() || stack.strPrimaryGroup == "/")
5060 {
5061 config.machineUserData.llGroups.clear();
5062 config.machineUserData.llGroups.push_back("/");
5063 }
5064 else
5065 {
5066 /* Replace the primary group if there is one, otherwise add it. */
5067 if (config.machineUserData.llGroups.size())
5068 config.machineUserData.llGroups.pop_front();
5069 config.machineUserData.llGroups.push_front(stack.strPrimaryGroup);
5070 }
5071 /* Description */
5072 config.machineUserData.strDescription = stack.strDescription;
5073 /* CPU count & extented attributes */
5074 config.hardwareMachine.cCPUs = stack.cCPUs;
5075 if (stack.fForceIOAPIC)
5076 config.hardwareMachine.fHardwareVirt = true;
5077 if (stack.fForceIOAPIC)
5078 config.hardwareMachine.biosSettings.fIOAPICEnabled = true;
5079 /* RAM size */
5080 config.hardwareMachine.ulMemorySizeMB = stack.ulMemorySizeMB;
5081
5082/*
5083 <const name="HardDiskControllerIDE" value="14" />
5084 <const name="HardDiskControllerSATA" value="15" />
5085 <const name="HardDiskControllerSCSI" value="16" />
5086 <const name="HardDiskControllerSAS" value="17" />
5087*/
5088
5089#ifdef VBOX_WITH_USB
5090 /* USB controller */
5091 if (stack.fUSBEnabled)
5092 {
5093 /** @todo r=klaus add support for arbitrary USB controller types, this can't handle
5094 * multiple controllers due to its design anyway */
5095 /* Usually the OHCI controller is enabled already, need to check. But
5096 * do this only if there is no xHCI controller. */
5097 bool fOHCIEnabled = false;
5098 bool fXHCIEnabled = false;
5099 settings::USBControllerList &llUSBControllers = config.hardwareMachine.usbSettings.llUSBControllers;
5100 settings::USBControllerList::iterator it;
5101 for (it = llUSBControllers.begin(); it != llUSBControllers.end(); ++it)
5102 {
5103 if (it->enmType == USBControllerType_OHCI)
5104 fOHCIEnabled = true;
5105 if (it->enmType == USBControllerType_XHCI)
5106 fXHCIEnabled = true;
5107 }
5108
5109 if (!fXHCIEnabled && !fOHCIEnabled)
5110 {
5111 settings::USBController ctrl;
5112 ctrl.strName = "OHCI";
5113 ctrl.enmType = USBControllerType_OHCI;
5114
5115 llUSBControllers.push_back(ctrl);
5116 }
5117 }
5118 else
5119 config.hardwareMachine.usbSettings.llUSBControllers.clear();
5120#endif
5121 /* Audio adapter */
5122 if (stack.strAudioAdapter.isNotEmpty())
5123 {
5124 config.hardwareMachine.audioAdapter.fEnabled = true;
5125 config.hardwareMachine.audioAdapter.controllerType = (AudioControllerType_T)stack.strAudioAdapter.toUInt32();
5126 }
5127 else
5128 config.hardwareMachine.audioAdapter.fEnabled = false;
5129 /* Network adapter */
5130 settings::NetworkAdaptersList &llNetworkAdapters = config.hardwareMachine.llNetworkAdapters;
5131 /* First disable all network cards, they will be enabled below again. */
5132 settings::NetworkAdaptersList::iterator it1;
5133 bool fKeepAllMACs = m->optListImport.contains(ImportOptions_KeepAllMACs);
5134 bool fKeepNATMACs = m->optListImport.contains(ImportOptions_KeepNATMACs);
5135 for (it1 = llNetworkAdapters.begin(); it1 != llNetworkAdapters.end(); ++it1)
5136 {
5137 it1->fEnabled = false;
5138 if (!( fKeepAllMACs
5139 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NAT)
5140 || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NATNetwork)))
5141 /* Force generation of new MAC address below. */
5142 it1->strMACAddress.setNull();
5143 }
5144 /* Now iterate over all network entries. */
5145 std::list<VirtualSystemDescriptionEntry*> avsdeNWs = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
5146 if (!avsdeNWs.empty())
5147 {
5148 /* Iterate through all network adapter entries and search for the
5149 * corresponding one in the machine config. If one is found, configure
5150 * it based on the user settings. */
5151 list<VirtualSystemDescriptionEntry*>::const_iterator itNW;
5152 for (itNW = avsdeNWs.begin();
5153 itNW != avsdeNWs.end();
5154 ++itNW)
5155 {
5156 VirtualSystemDescriptionEntry *vsdeNW = *itNW;
5157 if ( vsdeNW->strExtraConfigCurrent.startsWith("slot=", Utf8Str::CaseInsensitive)
5158 && vsdeNW->strExtraConfigCurrent.length() > 6)
5159 {
5160 uint32_t iSlot = vsdeNW->strExtraConfigCurrent.substr(5).toUInt32();
5161 /* Iterate through all network adapters in the machine config. */
5162 for (it1 = llNetworkAdapters.begin();
5163 it1 != llNetworkAdapters.end();
5164 ++it1)
5165 {
5166 /* Compare the slots. */
5167 if (it1->ulSlot == iSlot)
5168 {
5169 it1->fEnabled = true;
5170 if (it1->strMACAddress.isEmpty())
5171 Host::i_generateMACAddress(it1->strMACAddress);
5172 it1->type = (NetworkAdapterType_T)vsdeNW->strVBoxCurrent.toUInt32();
5173 break;
5174 }
5175 }
5176 }
5177 }
5178 }
5179
5180 /* Floppy controller */
5181 bool fFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy).size() > 0;
5182 /* DVD controller */
5183 bool fDVD = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM).size() > 0;
5184 /* Iterate over all storage controller check the attachments and remove
5185 * them when necessary. Also detect broken configs with more than one
5186 * attachment. Old VirtualBox versions (prior to 3.2.10) had all disk
5187 * attachments pointing to the last hard disk image, which causes import
5188 * failures. A long fixed bug, however the OVF files are long lived. */
5189 settings::StorageControllersList &llControllers = config.hardwareMachine.storage.llStorageControllers;
5190 Guid hdUuid;
5191 uint32_t cDisks = 0;
5192 bool fInconsistent = false;
5193 bool fRepairDuplicate = false;
5194 settings::StorageControllersList::iterator it3;
5195 for (it3 = llControllers.begin();
5196 it3 != llControllers.end();
5197 ++it3)
5198 {
5199 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
5200 settings::AttachedDevicesList::iterator it4 = llAttachments.begin();
5201 while (it4 != llAttachments.end())
5202 {
5203 if ( ( !fDVD
5204 && it4->deviceType == DeviceType_DVD)
5205 ||
5206 ( !fFloppy
5207 && it4->deviceType == DeviceType_Floppy))
5208 {
5209 it4 = llAttachments.erase(it4);
5210 continue;
5211 }
5212 else if (it4->deviceType == DeviceType_HardDisk)
5213 {
5214 const Guid &thisUuid = it4->uuid;
5215 cDisks++;
5216 if (cDisks == 1)
5217 {
5218 if (hdUuid.isZero())
5219 hdUuid = thisUuid;
5220 else
5221 fInconsistent = true;
5222 }
5223 else
5224 {
5225 if (thisUuid.isZero())
5226 fInconsistent = true;
5227 else if (thisUuid == hdUuid)
5228 fRepairDuplicate = true;
5229 }
5230 }
5231 ++it4;
5232 }
5233 }
5234 /* paranoia... */
5235 if (fInconsistent || cDisks == 1)
5236 fRepairDuplicate = false;
5237
5238 /*
5239 * step 2: scan the machine config for media attachments
5240 */
5241 /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
5242 std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
5243 std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
5244 VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
5245
5246 /* Get all hard disk descriptions. */
5247 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
5248 std::list<VirtualSystemDescriptionEntry*>::iterator avsdeHDsIt = avsdeHDs.begin();
5249 /* paranoia - if there is no 1:1 match do not try to repair. */
5250 if (cDisks != avsdeHDs.size())
5251 fRepairDuplicate = false;
5252
5253 // there must be an image in the OVF disk structs with the same UUID
5254
5255 ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
5256 std::set<RTCString> disksResolvedNames;
5257
5258 uint32_t cImportedDisks = 0;
5259
5260 while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
5261 {
5262/** @todo r=bird: Most of the code here is duplicated in the other machine
5263 * import method, factor out. */
5264 ovf::DiskImage diCurrent = oit->second;
5265
5266 Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
5267
5268 /* Iterate over all given disk images of the virtual system
5269 * disks description. We need to find the target disk path,
5270 * which could be changed by the user. */
5271 VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
5272 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
5273 itHD != avsdeHDs.end();
5274 ++itHD)
5275 {
5276 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
5277 if (vsdeHD->strRef == oit->first)
5278 {
5279 vsdeTargetHD = vsdeHD;
5280 break;
5281 }
5282 }
5283 if (!vsdeTargetHD)
5284 {
5285 /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
5286 Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
5287 oit->first.c_str(), vmNameEntry->strOvf.c_str()));
5288 NOREF(vmNameEntry);
5289 ++oit;
5290 continue;
5291 }
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301 /*
5302 * preliminary check availability of the image
5303 * This step is useful if image is placed in the OVA (TAR) package
5304 */
5305 if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
5306 {
5307 /* It means that we possibly have imported the storage earlier on a previous loop step. */
5308 std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
5309 if (h != disksResolvedNames.end())
5310 {
5311 /* Yes, disk name was found, we can skip it*/
5312 ++oit;
5313 continue;
5314 }
5315l_skipped:
5316 rc = i_preCheckImageAvailability(stack);
5317 if (SUCCEEDED(rc))
5318 {
5319 /* current opened file isn't the same as passed one */
5320 if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
5321 {
5322 // availableImage contains the disk identifier (e.g. "vmdisk1"), which should exist
5323 // in the virtual system's disks map under that ID and also in the global images map
5324 // and find the disk from the OVF's disk list
5325 ovf::DiskImagesMap::const_iterator itDiskImage;
5326 for (itDiskImage = stack.mapDisks.begin();
5327 itDiskImage != stack.mapDisks.end();
5328 itDiskImage++)
5329 if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
5330 Utf8Str::CaseInsensitive) == 0)
5331 break;
5332 if (itDiskImage == stack.mapDisks.end())
5333 {
5334 LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
5335 RTVfsIoStrmRelease(stack.claimOvaLookAHead());
5336 goto l_skipped;
5337 }
5338 //throw setError(E_FAIL,
5339 // tr("Internal inconsistency looking up disk image '%s'. "
5340 // "Check compliance OVA package structure and file names "
5341 // "references in the section <References> in the OVF file."),
5342 // stack.pszOvaLookAheadName);
5343
5344 /* replace with a new found disk image */
5345 diCurrent = *(&itDiskImage->second);
5346
5347 /*
5348 * Again iterate over all given disk images of the virtual system
5349 * disks description using the found disk image
5350 */
5351 vsdeTargetHD = NULL;
5352 for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
5353 itHD != avsdeHDs.end();
5354 ++itHD)
5355 {
5356 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
5357 if (vsdeHD->strRef == diCurrent.strDiskId)
5358 {
5359 vsdeTargetHD = vsdeHD;
5360 break;
5361 }
5362 }
5363
5364 /*
5365 * in this case it's an error because something is wrong with the OVF description file.
5366 * May be VBox imports OVA package with wrong file sequence inside the archive.
5367 */
5368 if (!vsdeTargetHD)
5369 throw setError(E_FAIL,
5370 tr("Internal inconsistency looking up disk image '%s'"),
5371 diCurrent.strHref.c_str());
5372
5373
5374
5375
5376
5377 }
5378 else
5379 {
5380 ++oit;
5381 }
5382 }
5383 else
5384 {
5385 ++oit;
5386 continue;
5387 }
5388 }
5389 else
5390 {
5391 /* just continue with normal files*/
5392 ++oit;
5393 }
5394
5395 /* Important! to store disk name for the next checks */
5396 disksResolvedNames.insert(diCurrent.strHref);
5397////// end of duplicated code.
5398 // there must be an image in the OVF disk structs with the same UUID
5399 bool fFound = false;
5400 Utf8Str strUuid;
5401
5402 // for each storage controller...
5403 for (settings::StorageControllersList::iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
5404 sit != config.hardwareMachine.storage.llStorageControllers.end();
5405 ++sit)
5406 {
5407 settings::StorageController &sc = *sit;
5408
5409 // for each medium attachment to this controller...
5410 for (settings::AttachedDevicesList::iterator dit = sc.llAttachedDevices.begin();
5411 dit != sc.llAttachedDevices.end();
5412 ++dit)
5413 {
5414 settings::AttachedDevice &d = *dit;
5415
5416 if (d.uuid.isZero())
5417 // empty DVD and floppy media
5418 continue;
5419
5420 // When repairing a broken VirtualBox xml config section (written
5421 // by VirtualBox versions earlier than 3.2.10) assume the disks
5422 // show up in the same order as in the OVF description.
5423 if (fRepairDuplicate)
5424 {
5425 VirtualSystemDescriptionEntry *vsdeHD = *avsdeHDsIt;
5426 ovf::DiskImagesMap::const_iterator itDiskImage = stack.mapDisks.find(vsdeHD->strRef);
5427 if (itDiskImage != stack.mapDisks.end())
5428 {
5429 const ovf::DiskImage &di = itDiskImage->second;
5430 d.uuid = Guid(di.uuidVBox);
5431 }
5432 ++avsdeHDsIt;
5433 }
5434
5435 // convert the Guid to string
5436 strUuid = d.uuid.toString();
5437
5438 if (diCurrent.uuidVBox != strUuid)
5439 {
5440 continue;
5441 }
5442
5443 /*
5444 * step 3: import disk
5445 */
5446 ComObjPtr<Medium> pTargetMedium;
5447 i_importOneDiskImage(diCurrent,
5448 vsdeTargetHD->strVBoxCurrent,
5449 pTargetMedium,
5450 stack);
5451
5452 // ... and replace the old UUID in the machine config with the one of
5453 // the imported disk that was just created
5454 Bstr hdId;
5455 rc = pTargetMedium->COMGETTER(Id)(hdId.asOutParam());
5456 if (FAILED(rc)) throw rc;
5457
5458 /*
5459 * 1. saving original UUID for restoring in case of failure.
5460 * 2. replacement of original UUID by new UUID in the current VM config (settings::MachineConfigFile).
5461 */
5462 {
5463 rc = stack.saveOriginalUUIDOfAttachedDevice(d, Utf8Str(hdId));
5464 d.uuid = hdId;
5465 }
5466
5467 fFound = true;
5468 break;
5469 } // for (settings::AttachedDevicesList::const_iterator dit = sc.llAttachedDevices.begin();
5470 } // for (settings::StorageControllersList::const_iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
5471
5472 // no disk with such a UUID found:
5473 if (!fFound)
5474 throw setError(E_FAIL,
5475 tr("<vbox:Machine> element in OVF contains a medium attachment for the disk image %s "
5476 "but the OVF describes no such image"),
5477 strUuid.c_str());
5478
5479 ++cImportedDisks;
5480
5481 }// while(oit != stack.mapDisks.end())
5482
5483
5484 /*
5485 * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
5486 */
5487 if(cImportedDisks < avsdeHDs.size())
5488 {
5489 Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
5490 vmNameEntry->strOvf.c_str()));
5491 }
5492
5493 /*
5494 * step 4): create the machine and have it import the config
5495 */
5496
5497 ComObjPtr<Machine> pNewMachine;
5498 rc = pNewMachine.createObject();
5499 if (FAILED(rc)) throw rc;
5500
5501 // this magic constructor fills the new machine object with the MachineConfig
5502 // instance that we created from the vbox:Machine
5503 rc = pNewMachine->init(mVirtualBox,
5504 stack.strNameVBox,// name from OVF preparations; can be suffixed to avoid duplicates
5505 stack.strSettingsFilename,
5506 config); // the whole machine config
5507 if (FAILED(rc)) throw rc;
5508
5509 pReturnNewMachine = ComPtr<IMachine>(pNewMachine);
5510
5511 // and register it
5512 rc = mVirtualBox->RegisterMachine(pNewMachine);
5513 if (FAILED(rc)) throw rc;
5514
5515 // store new machine for roll-back in case of errors
5516 Bstr bstrNewMachineId;
5517 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
5518 if (FAILED(rc)) throw rc;
5519 m->llGuidsMachinesCreated.push_back(Guid(bstrNewMachineId));
5520
5521 LogFlowFuncLeave();
5522}
5523
5524/**
5525 * @throws HRESULT errors.
5526 */
5527void Appliance::i_importMachines(ImportStack &stack)
5528{
5529 // this is safe to access because this thread only gets started
5530 const ovf::OVFReader &reader = *m->pReader;
5531
5532 // create a session for the machine + disks we manipulate below
5533 HRESULT rc = stack.pSession.createInprocObject(CLSID_Session);
5534 ComAssertComRCThrowRC(rc);
5535
5536 list<ovf::VirtualSystem>::const_iterator it;
5537 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it1;
5538 /* Iterate through all virtual systems of that appliance */
5539 size_t i = 0;
5540 for (it = reader.m_llVirtualSystems.begin(), it1 = m->virtualSystemDescriptions.begin();
5541 it != reader.m_llVirtualSystems.end() && it1 != m->virtualSystemDescriptions.end();
5542 ++it, ++it1, ++i)
5543 {
5544 const ovf::VirtualSystem &vsysThis = *it;
5545 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it1);
5546
5547 ComPtr<IMachine> pNewMachine;
5548
5549 // there are two ways in which we can create a vbox machine from OVF:
5550 // -- either this OVF was written by vbox 3.2 or later, in which case there is a <vbox:Machine> element
5551 // in the <VirtualSystem>; then the VirtualSystemDescription::Data has a settings::MachineConfigFile
5552 // with all the machine config pretty-parsed;
5553 // -- or this is an OVF from an older vbox or an external source, and then we need to translate the
5554 // VirtualSystemDescriptionEntry and do import work
5555
5556 // Even for the vbox:Machine case, there are a number of configuration items that will be taken from
5557 // the OVF because otherwise the "override import parameters" mechanism in the GUI won't work.
5558
5559 // VM name
5560 std::list<VirtualSystemDescriptionEntry*> vsdeName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
5561 if (vsdeName.size() < 1)
5562 throw setError(VBOX_E_FILE_ERROR,
5563 tr("Missing VM name"));
5564 stack.strNameVBox = vsdeName.front()->strVBoxCurrent;
5565
5566 // Primary group, which is entirely optional.
5567 stack.strPrimaryGroup.setNull();
5568 std::list<VirtualSystemDescriptionEntry*> vsdePrimaryGroup = vsdescThis->i_findByType(VirtualSystemDescriptionType_PrimaryGroup);
5569 if (vsdePrimaryGroup.size() >= 1)
5570 {
5571 stack.strPrimaryGroup = vsdePrimaryGroup.front()->strVBoxCurrent;
5572 if (stack.strPrimaryGroup.isEmpty())
5573 stack.strPrimaryGroup = "/";
5574 }
5575
5576 // Draw the right conclusions from the (possibly modified) VM settings
5577 // file name and base folder. If the VM settings file name is modified,
5578 // it takes precedence, otherwise it is recreated from the base folder
5579 // and the primary group.
5580 stack.strSettingsFilename.setNull();
5581 std::list<VirtualSystemDescriptionEntry*> vsdeSettingsFile = vsdescThis->i_findByType(VirtualSystemDescriptionType_SettingsFile);
5582 if (vsdeSettingsFile.size() >= 1)
5583 {
5584 VirtualSystemDescriptionEntry *vsdeSF1 = vsdeSettingsFile.front();
5585 if (vsdeSF1->strVBoxCurrent != vsdeSF1->strVBoxSuggested)
5586 stack.strSettingsFilename = vsdeSF1->strVBoxCurrent;
5587 }
5588 if (stack.strSettingsFilename.isEmpty())
5589 {
5590 Utf8Str strBaseFolder;
5591 std::list<VirtualSystemDescriptionEntry*> vsdeBaseFolder = vsdescThis->i_findByType(VirtualSystemDescriptionType_BaseFolder);
5592 if (vsdeBaseFolder.size() >= 1)
5593 strBaseFolder = vsdeBaseFolder.front()->strVBoxCurrent;
5594 Bstr bstrSettingsFilename;
5595 rc = mVirtualBox->ComposeMachineFilename(Bstr(stack.strNameVBox).raw(),
5596 Bstr(stack.strPrimaryGroup).raw(),
5597 NULL /* aCreateFlags */,
5598 Bstr(strBaseFolder).raw(),
5599 bstrSettingsFilename.asOutParam());
5600 if (FAILED(rc)) throw rc;
5601 stack.strSettingsFilename = bstrSettingsFilename;
5602 }
5603
5604 // Determine the machine folder from the settings file.
5605 LogFunc(("i=%zu strName=%s strSettingsFilename=%s\n", i, stack.strNameVBox.c_str(), stack.strSettingsFilename.c_str()));
5606 stack.strMachineFolder = stack.strSettingsFilename;
5607 stack.strMachineFolder.stripFilename();
5608
5609 // guest OS type
5610 std::list<VirtualSystemDescriptionEntry*> vsdeOS;
5611 vsdeOS = vsdescThis->i_findByType(VirtualSystemDescriptionType_OS);
5612 if (vsdeOS.size() < 1)
5613 throw setError(VBOX_E_FILE_ERROR,
5614 tr("Missing guest OS type"));
5615 stack.strOsTypeVBox = vsdeOS.front()->strVBoxCurrent;
5616
5617 // Firmware
5618 std::list<VirtualSystemDescriptionEntry*> firmware = vsdescThis->i_findByType(VirtualSystemDescriptionType_BootingFirmware);
5619 if (firmware.size() != 1)
5620 stack.strFirmwareType = "BIOS";//try default BIOS type
5621 else
5622 stack.strFirmwareType = firmware.front()->strVBoxCurrent;
5623
5624 // CPU count
5625 std::list<VirtualSystemDescriptionEntry*> vsdeCPU = vsdescThis->i_findByType(VirtualSystemDescriptionType_CPU);
5626 if (vsdeCPU.size() != 1)
5627 throw setError(VBOX_E_FILE_ERROR, tr("CPU count missing"));
5628
5629 stack.cCPUs = vsdeCPU.front()->strVBoxCurrent.toUInt32();
5630 // We need HWVirt & IO-APIC if more than one CPU is requested
5631 if (stack.cCPUs > 1)
5632 {
5633 stack.fForceHWVirt = true;
5634 stack.fForceIOAPIC = true;
5635 }
5636
5637 // RAM
5638 std::list<VirtualSystemDescriptionEntry*> vsdeRAM = vsdescThis->i_findByType(VirtualSystemDescriptionType_Memory);
5639 if (vsdeRAM.size() != 1)
5640 throw setError(VBOX_E_FILE_ERROR, tr("RAM size missing"));
5641 stack.ulMemorySizeMB = (ULONG)vsdeRAM.front()->strVBoxCurrent.toUInt64();
5642
5643#ifdef VBOX_WITH_USB
5644 // USB controller
5645 std::list<VirtualSystemDescriptionEntry*> vsdeUSBController =
5646 vsdescThis->i_findByType(VirtualSystemDescriptionType_USBController);
5647 // USB support is enabled if there's at least one such entry; to disable USB support,
5648 // the type of the USB item would have been changed to "ignore"
5649 stack.fUSBEnabled = !vsdeUSBController.empty();
5650#endif
5651 // audio adapter
5652 std::list<VirtualSystemDescriptionEntry*> vsdeAudioAdapter =
5653 vsdescThis->i_findByType(VirtualSystemDescriptionType_SoundCard);
5654 /** @todo we support one audio adapter only */
5655 if (!vsdeAudioAdapter.empty())
5656 stack.strAudioAdapter = vsdeAudioAdapter.front()->strVBoxCurrent;
5657
5658 // for the description of the new machine, always use the OVF entry, the user may have changed it in the import config
5659 std::list<VirtualSystemDescriptionEntry*> vsdeDescription =
5660 vsdescThis->i_findByType(VirtualSystemDescriptionType_Description);
5661 if (!vsdeDescription.empty())
5662 stack.strDescription = vsdeDescription.front()->strVBoxCurrent;
5663
5664 // import vbox:machine or OVF now
5665 if (vsdescThis->m->pConfig)
5666 // vbox:Machine config
5667 i_importVBoxMachine(vsdescThis, pNewMachine, stack);
5668 else
5669 // generic OVF config
5670 i_importMachineGeneric(vsysThis, vsdescThis, pNewMachine, stack);
5671
5672 } // for (it = pAppliance->m->llVirtualSystems.begin() ...
5673}
5674
5675HRESULT Appliance::ImportStack::saveOriginalUUIDOfAttachedDevice(settings::AttachedDevice &device,
5676 const Utf8Str &newlyUuid)
5677{
5678 HRESULT rc = S_OK;
5679
5680 /* save for restoring */
5681 mapNewUUIDsToOriginalUUIDs.insert(std::make_pair(newlyUuid, device.uuid.toString()));
5682
5683 return rc;
5684}
5685
5686HRESULT Appliance::ImportStack::restoreOriginalUUIDOfAttachedDevice(settings::MachineConfigFile *config)
5687{
5688 HRESULT rc = S_OK;
5689
5690 settings::StorageControllersList &llControllers = config->hardwareMachine.storage.llStorageControllers;
5691 settings::StorageControllersList::iterator itscl;
5692 for (itscl = llControllers.begin();
5693 itscl != llControllers.end();
5694 ++itscl)
5695 {
5696 settings::AttachedDevicesList &llAttachments = itscl->llAttachedDevices;
5697 settings::AttachedDevicesList::iterator itadl = llAttachments.begin();
5698 while (itadl != llAttachments.end())
5699 {
5700 std::map<Utf8Str , Utf8Str>::iterator it =
5701 mapNewUUIDsToOriginalUUIDs.find(itadl->uuid.toString());
5702 if(it!=mapNewUUIDsToOriginalUUIDs.end())
5703 {
5704 Utf8Str uuidOriginal = it->second;
5705 itadl->uuid = Guid(uuidOriginal);
5706 mapNewUUIDsToOriginalUUIDs.erase(it->first);
5707 }
5708 ++itadl;
5709 }
5710 }
5711
5712 return rc;
5713}
5714
5715/**
5716 * @throws Nothing
5717 */
5718RTVFSIOSTREAM Appliance::ImportStack::claimOvaLookAHead(void)
5719{
5720 RTVFSIOSTREAM hVfsIos = this->hVfsIosOvaLookAhead;
5721 this->hVfsIosOvaLookAhead = NIL_RTVFSIOSTREAM;
5722 /* We don't free the name since it may be referenced in error messages and such. */
5723 return hVfsIos;
5724}
5725
Note: See TracBrowser for help on using the repository browser.

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