VirtualBox

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

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

Runtime/RTS3: Retire unused implementation, can be resurrected if required

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