VirtualBox

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

Last change on this file since 100841 was 99608, checked in by vboxsync, 20 months ago

fixed possible narrowing type from 'long unsigned int' to 'unsigned int'

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

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