VirtualBox

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

Last change on this file since 107296 was 107198, checked in by vboxsync, 2 months ago

FE/VBoxManage,Main/Appliance: Add the ability to export and import VMs
which contain an NVMe storage controller. Follow-up changes to include
support for moving a disk to an NVMe controller during import and also
add some missing NVMe support in Appliance::i_importMachineGeneric().
bugref:10159

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

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