VirtualBox

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

Last change on this file since 101057 was 101057, checked in by vboxsync, 8 months ago

Main: Renamed IPlatformProperties::getStorageBusForStorageControllerType + getStorageControllerTypesForStorageBus -> getStorageBusForControllerType + getStorageControllerTypesForBus, to fix DTrace probe name length restrictions. bugref:10384

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

© 2023 Oracle
ContactPrivacy policyTerms of Use