VirtualBox

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

Last change on this file since 107438 was 107438, checked in by vboxsync, 4 weeks ago

Main/ApplianceImplImport.cpp: Fixed a warning found by Parfait. ​However, this is needs a more major review + cleanup, see @todos. jiraref:VBP-1424

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