VirtualBox

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

Last change on this file was 101472, checked in by vboxsync, 6 months ago

FE/VBoxManage,FE/Qt,Main/{VirtualBox.xidl,Appliance}: Add the ability to
export and import VMs which contain an NVMe storage controller.
bugref:10159 ticketref:19320

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

© 2023 Oracle
ContactPrivacy policyTerms of Use