VirtualBox

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

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

bugref:10314. Reverted back r154561 partially. RAM unit is MB.

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

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