VirtualBox

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

Last change on this file since 96407 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

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