VirtualBox

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

Last change on this file since 107447 was 107447, checked in by vboxsync, 3 weeks ago

Main/src-server/ApplianceImplImport.cpp: Don't do the string -> uint64_t conversion multiple times (macro expands it multiple times), and use RT_CLAMP instead of nesting RT_MIN/RT_MAX to make the code easier to digest, bugref:3409

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