VirtualBox

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

Last change on this file since 97698 was 97533, checked in by vboxsync, 2 years ago

Frontends/VBoxManage,Main/Appliance: The appliance import code populates
the IVirtualSystemDescription instance corresponding to the amount of
guest memory using two different allocation units: OVFValues[] in bytes
and VBoxValues[] in megabytes. The SDK documents the allocation unit as
being in bytes for both fields so update the code to consistently use
bytes for VBoxValues[] just as is done for OVFValues[]. bugref:10314

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