VirtualBox

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

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

bugref:10314. bugref:10278. Reverted VSD RAM unit to bytes and fixed other places where MB unit was used.

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

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