VirtualBox

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

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

Main/src-server/ApplianceImplImport.cpp: Some unused assignment parfait warning fixes, bugref:3409

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