VirtualBox

source: vbox/trunk/src/VBox/Main/ApplianceImplImport.cpp@ 28155

Last change on this file since 28155 was 28152, checked in by vboxsync, 15 years ago

Main/OVF: optimizations, cleanup

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 87.6 KB
Line 
1/* $Id: ApplianceImplImport.cpp 28152 2010-04-09 17:38:56Z vboxsync $ */
2/** @file
3 *
4 * IAppliance and IVirtualSystem COM class implementations.
5 */
6
7/*
8 * Copyright (C) 2008-2010 Sun Microsystems, Inc.
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 *
18 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
19 * Clara, CA 95054 USA or visit http://www.sun.com if you need
20 * additional information or have any questions.
21 */
22
23#include <iprt/path.h>
24#include <iprt/dir.h>
25#include <iprt/file.h>
26#include <iprt/s3.h>
27#include <iprt/sha.h>
28#include <iprt/manifest.h>
29
30#include <VBox/com/array.h>
31
32#include "ApplianceImpl.h"
33#include "VirtualBoxImpl.h"
34#include "GuestOSTypeImpl.h"
35#include "ProgressImpl.h"
36#include "MachineImpl.h"
37
38#include "AutoCaller.h"
39#include "Logging.h"
40
41#include "ApplianceImplPrivate.h"
42
43#include <VBox/param.h>
44#include <VBox/version.h>
45#include <VBox/settings.h>
46
47using namespace std;
48
49////////////////////////////////////////////////////////////////////////////////
50//
51// IAppliance public methods
52//
53////////////////////////////////////////////////////////////////////////////////
54
55/**
56 * Public method implementation.
57 * @param path
58 * @return
59 */
60STDMETHODIMP Appliance::Read(IN_BSTR path, IProgress **aProgress)
61{
62 if (!path) return E_POINTER;
63 CheckComArgOutPointerValid(aProgress);
64
65 AutoCaller autoCaller(this);
66 if (FAILED(autoCaller.rc())) return autoCaller.rc();
67
68 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
69
70 if (!isApplianceIdle())
71 return E_ACCESSDENIED;
72
73 if (m->pReader)
74 {
75 delete m->pReader;
76 m->pReader = NULL;
77 }
78
79 // see if we can handle this file; for now we insist it has an ".ovf" extension
80 Utf8Str strPath (path);
81 if (!strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
82 return setError(VBOX_E_FILE_ERROR,
83 tr("Appliance file must have .ovf extension"));
84
85 ComObjPtr<Progress> progress;
86 HRESULT rc = S_OK;
87 try
88 {
89 /* Parse all necessary info out of the URI */
90 parseURI(strPath, m->locInfo);
91 rc = readImpl(m->locInfo, progress);
92 }
93 catch (HRESULT aRC)
94 {
95 rc = aRC;
96 }
97
98 if (SUCCEEDED(rc))
99 /* Return progress to the caller */
100 progress.queryInterfaceTo(aProgress);
101
102 return S_OK;
103}
104
105/**
106 * Public method implementation.
107 * @return
108 */
109STDMETHODIMP Appliance::Interpret()
110{
111 // @todo:
112 // - don't use COM methods but the methods directly (faster, but needs appropriate locking of that objects itself (s. HardDisk))
113 // - Appropriate handle errors like not supported file formats
114 AutoCaller autoCaller(this);
115 if (FAILED(autoCaller.rc())) return autoCaller.rc();
116
117 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
118
119 if (!isApplianceIdle())
120 return E_ACCESSDENIED;
121
122 HRESULT rc = S_OK;
123
124 /* Clear any previous virtual system descriptions */
125 m->virtualSystemDescriptions.clear();
126
127 /* We need the default path for storing disk images */
128 ComPtr<ISystemProperties> systemProps;
129 rc = mVirtualBox->COMGETTER(SystemProperties)(systemProps.asOutParam());
130 if (FAILED(rc)) return rc;
131 Bstr bstrDefaultHardDiskLocation;
132 rc = systemProps->COMGETTER(DefaultHardDiskFolder)(bstrDefaultHardDiskLocation.asOutParam());
133 if (FAILED(rc)) return rc;
134
135 if (!m->pReader)
136 return setError(E_FAIL,
137 tr("Cannot interpret appliance without reading it first (call read() before interpret())"));
138
139 // Change the appliance state so we can safely leave the lock while doing time-consuming
140 // disk imports; also the below method calls do all kinds of locking which conflicts with
141 // the appliance object lock
142 m->state = Data::ApplianceImporting;
143 alock.release();
144
145 /* Try/catch so we can clean up on error */
146 try
147 {
148 list<ovf::VirtualSystem>::const_iterator it;
149 /* Iterate through all virtual systems */
150 for (it = m->pReader->m_llVirtualSystems.begin();
151 it != m->pReader->m_llVirtualSystems.end();
152 ++it)
153 {
154 const ovf::VirtualSystem &vsysThis = *it;
155
156 ComObjPtr<VirtualSystemDescription> pNewDesc;
157 rc = pNewDesc.createObject();
158 if (FAILED(rc)) throw rc;
159 rc = pNewDesc->init();
160 if (FAILED(rc)) throw rc;
161
162 // if the virtual system in OVF had a <vbox:Machine> element, have the
163 // VirtualBox settings code parse that XML now
164 if (vsysThis.pelmVboxMachine)
165 pNewDesc->importVboxMachineXML(*vsysThis.pelmVboxMachine);
166
167 /* Guest OS type */
168 Utf8Str strOsTypeVBox,
169 strCIMOSType = Utf8StrFmt("%RI32", (uint32_t)vsysThis.cimos);
170 convertCIMOSType2VBoxOSType(strOsTypeVBox, vsysThis.cimos, vsysThis.strCimosDesc);
171 pNewDesc->addEntry(VirtualSystemDescriptionType_OS,
172 "",
173 strCIMOSType,
174 strOsTypeVBox);
175
176 /* VM name */
177 /* If the there isn't any name specified create a default one out of
178 * the OS type */
179 Utf8Str nameVBox = vsysThis.strName;
180 if (nameVBox.isEmpty())
181 nameVBox = strOsTypeVBox;
182 searchUniqueVMName(nameVBox);
183 pNewDesc->addEntry(VirtualSystemDescriptionType_Name,
184 "",
185 vsysThis.strName,
186 nameVBox);
187
188 /* VM Product */
189 if (!vsysThis.strProduct.isEmpty())
190 pNewDesc->addEntry(VirtualSystemDescriptionType_Product,
191 "",
192 vsysThis.strProduct,
193 vsysThis.strProduct);
194
195 /* VM Vendor */
196 if (!vsysThis.strVendor.isEmpty())
197 pNewDesc->addEntry(VirtualSystemDescriptionType_Vendor,
198 "",
199 vsysThis.strVendor,
200 vsysThis.strVendor);
201
202 /* VM Version */
203 if (!vsysThis.strVersion.isEmpty())
204 pNewDesc->addEntry(VirtualSystemDescriptionType_Version,
205 "",
206 vsysThis.strVersion,
207 vsysThis.strVersion);
208
209 /* VM ProductUrl */
210 if (!vsysThis.strProductUrl.isEmpty())
211 pNewDesc->addEntry(VirtualSystemDescriptionType_ProductUrl,
212 "",
213 vsysThis.strProductUrl,
214 vsysThis.strProductUrl);
215
216 /* VM VendorUrl */
217 if (!vsysThis.strVendorUrl.isEmpty())
218 pNewDesc->addEntry(VirtualSystemDescriptionType_VendorUrl,
219 "",
220 vsysThis.strVendorUrl,
221 vsysThis.strVendorUrl);
222
223 /* VM description */
224 if (!vsysThis.strDescription.isEmpty())
225 pNewDesc->addEntry(VirtualSystemDescriptionType_Description,
226 "",
227 vsysThis.strDescription,
228 vsysThis.strDescription);
229
230 /* VM license */
231 if (!vsysThis.strLicenseText.isEmpty())
232 pNewDesc->addEntry(VirtualSystemDescriptionType_License,
233 "",
234 vsysThis.strLicenseText,
235 vsysThis.strLicenseText);
236
237 /* Now that we know the OS type, get our internal defaults based on that. */
238 ComPtr<IGuestOSType> pGuestOSType;
239 rc = mVirtualBox->GetGuestOSType(Bstr(strOsTypeVBox), pGuestOSType.asOutParam());
240 if (FAILED(rc)) throw rc;
241
242 /* CPU count */
243 ULONG cpuCountVBox = vsysThis.cCPUs;
244 /* Check for the constrains */
245 if (cpuCountVBox > SchemaDefs::MaxCPUCount)
246 {
247 addWarning(tr("The virtual system \"%s\" claims support for %u CPU's, but VirtualBox has support for max %u CPU's only."),
248 vsysThis.strName.c_str(), cpuCountVBox, SchemaDefs::MaxCPUCount);
249 cpuCountVBox = SchemaDefs::MaxCPUCount;
250 }
251 if (vsysThis.cCPUs == 0)
252 cpuCountVBox = 1;
253 pNewDesc->addEntry(VirtualSystemDescriptionType_CPU,
254 "",
255 Utf8StrFmt("%RI32", (uint32_t)vsysThis.cCPUs),
256 Utf8StrFmt("%RI32", (uint32_t)cpuCountVBox));
257
258 /* RAM */
259 uint64_t ullMemSizeVBox = vsysThis.ullMemorySize / _1M;
260 /* Check for the constrains */
261 if ( ullMemSizeVBox != 0
262 && ( ullMemSizeVBox < MM_RAM_MIN_IN_MB
263 || ullMemSizeVBox > MM_RAM_MAX_IN_MB
264 )
265 )
266 {
267 addWarning(tr("The virtual system \"%s\" claims support for %llu MB RAM size, but VirtualBox has support for min %u & max %u MB RAM size only."),
268 vsysThis.strName.c_str(), ullMemSizeVBox, MM_RAM_MIN_IN_MB, MM_RAM_MAX_IN_MB);
269 ullMemSizeVBox = RT_MIN(RT_MAX(ullMemSizeVBox, MM_RAM_MIN_IN_MB), MM_RAM_MAX_IN_MB);
270 }
271 if (vsysThis.ullMemorySize == 0)
272 {
273 /* If the RAM of the OVF is zero, use our predefined values */
274 ULONG memSizeVBox2;
275 rc = pGuestOSType->COMGETTER(RecommendedRAM)(&memSizeVBox2);
276 if (FAILED(rc)) throw rc;
277 /* VBox stores that in MByte */
278 ullMemSizeVBox = (uint64_t)memSizeVBox2;
279 }
280 pNewDesc->addEntry(VirtualSystemDescriptionType_Memory,
281 "",
282 Utf8StrFmt("%RI64", (uint64_t)vsysThis.ullMemorySize),
283 Utf8StrFmt("%RI64", (uint64_t)ullMemSizeVBox));
284
285 /* Audio */
286 if (!vsysThis.strSoundCardType.isEmpty())
287 /* Currently we set the AC97 always.
288 @todo: figure out the hardware which could be possible */
289 pNewDesc->addEntry(VirtualSystemDescriptionType_SoundCard,
290 "",
291 vsysThis.strSoundCardType,
292 Utf8StrFmt("%RI32", (uint32_t)AudioControllerType_AC97));
293
294#ifdef VBOX_WITH_USB
295 /* USB Controller */
296 if (vsysThis.fHasUsbController)
297 pNewDesc->addEntry(VirtualSystemDescriptionType_USBController, "", "", "");
298#endif /* VBOX_WITH_USB */
299
300 /* Network Controller */
301 size_t cEthernetAdapters = vsysThis.llEthernetAdapters.size();
302 if (cEthernetAdapters > 0)
303 {
304 /* Check for the constrains */
305 if (cEthernetAdapters > SchemaDefs::NetworkAdapterCount)
306 addWarning(tr("The virtual system \"%s\" claims support for %zu network adapters, but VirtualBox has support for max %u network adapter only."),
307 vsysThis.strName.c_str(), cEthernetAdapters, SchemaDefs::NetworkAdapterCount);
308
309 /* Get the default network adapter type for the selected guest OS */
310 NetworkAdapterType_T defaultAdapterVBox = NetworkAdapterType_Am79C970A;
311 rc = pGuestOSType->COMGETTER(AdapterType)(&defaultAdapterVBox);
312 if (FAILED(rc)) throw rc;
313
314 ovf::EthernetAdaptersList::const_iterator itEA;
315 /* Iterate through all abstract networks. We support 8 network
316 * adapters at the maximum, so the first 8 will be added only. */
317 size_t a = 0;
318 for (itEA = vsysThis.llEthernetAdapters.begin();
319 itEA != vsysThis.llEthernetAdapters.end() && a < SchemaDefs::NetworkAdapterCount;
320 ++itEA, ++a)
321 {
322 const ovf::EthernetAdapter &ea = *itEA; // logical network to connect to
323 Utf8Str strNetwork = ea.strNetworkName;
324 // make sure it's one of these two
325 if ( (strNetwork.compare("Null", Utf8Str::CaseInsensitive))
326 && (strNetwork.compare("NAT", Utf8Str::CaseInsensitive))
327 && (strNetwork.compare("Bridged", Utf8Str::CaseInsensitive))
328 && (strNetwork.compare("Internal", Utf8Str::CaseInsensitive))
329 && (strNetwork.compare("HostOnly", Utf8Str::CaseInsensitive))
330 )
331 strNetwork = "Bridged"; // VMware assumes this is the default apparently
332
333 /* Figure out the hardware type */
334 NetworkAdapterType_T nwAdapterVBox = defaultAdapterVBox;
335 if (!ea.strAdapterType.compare("PCNet32", Utf8Str::CaseInsensitive))
336 {
337 /* If the default adapter is already one of the two
338 * PCNet adapters use the default one. If not use the
339 * Am79C970A as fallback. */
340 if (!(defaultAdapterVBox == NetworkAdapterType_Am79C970A ||
341 defaultAdapterVBox == NetworkAdapterType_Am79C973))
342 nwAdapterVBox = NetworkAdapterType_Am79C970A;
343 }
344#ifdef VBOX_WITH_E1000
345 /* VMWare accidentally write this with VirtualCenter 3.5,
346 so make sure in this case always to use the VMWare one */
347 else if (!ea.strAdapterType.compare("E10000", Utf8Str::CaseInsensitive))
348 nwAdapterVBox = NetworkAdapterType_I82545EM;
349 else if (!ea.strAdapterType.compare("E1000", Utf8Str::CaseInsensitive))
350 {
351 /* Check if this OVF was written by VirtualBox */
352 if (Utf8Str(vsysThis.strVirtualSystemType).contains("virtualbox", Utf8Str::CaseInsensitive))
353 {
354 /* If the default adapter is already one of the three
355 * E1000 adapters use the default one. If not use the
356 * I82545EM as fallback. */
357 if (!(defaultAdapterVBox == NetworkAdapterType_I82540EM ||
358 defaultAdapterVBox == NetworkAdapterType_I82543GC ||
359 defaultAdapterVBox == NetworkAdapterType_I82545EM))
360 nwAdapterVBox = NetworkAdapterType_I82540EM;
361 }
362 else
363 /* Always use this one since it's what VMware uses */
364 nwAdapterVBox = NetworkAdapterType_I82545EM;
365 }
366#endif /* VBOX_WITH_E1000 */
367
368 pNewDesc->addEntry(VirtualSystemDescriptionType_NetworkAdapter,
369 "", // ref
370 ea.strNetworkName, // orig
371 Utf8StrFmt("%RI32", (uint32_t)nwAdapterVBox), // conf
372 0,
373 Utf8StrFmt("type=%s", strNetwork.c_str())); // extra conf
374 }
375 }
376
377 /* Floppy Drive */
378 if (vsysThis.fHasFloppyDrive)
379 pNewDesc->addEntry(VirtualSystemDescriptionType_Floppy, "", "", "");
380
381 /* CD Drive */
382 if (vsysThis.fHasCdromDrive)
383 pNewDesc->addEntry(VirtualSystemDescriptionType_CDROM, "", "", "");
384
385 /* Hard disk Controller */
386 uint16_t cIDEused = 0;
387 uint16_t cSATAused = 0; NOREF(cSATAused);
388 uint16_t cSCSIused = 0; NOREF(cSCSIused);
389 ovf::ControllersMap::const_iterator hdcIt;
390 /* Iterate through all hard disk controllers */
391 for (hdcIt = vsysThis.mapControllers.begin();
392 hdcIt != vsysThis.mapControllers.end();
393 ++hdcIt)
394 {
395 const ovf::HardDiskController &hdc = hdcIt->second;
396 Utf8Str strControllerID = Utf8StrFmt("%RI32", (uint32_t)hdc.idController);
397
398 switch (hdc.system)
399 {
400 case ovf::HardDiskController::IDE:
401 {
402 /* Check for the constrains */
403 /* @todo: I'm very confused! Are these bits *one* controller or
404 is every port/bus declared as an extra controller. */
405 if (cIDEused < 4)
406 {
407 // @todo: figure out the IDE types
408 /* Use PIIX4 as default */
409 Utf8Str strType = "PIIX4";
410 if (!hdc.strControllerType.compare("PIIX3", Utf8Str::CaseInsensitive))
411 strType = "PIIX3";
412 else if (!hdc.strControllerType.compare("ICH6", Utf8Str::CaseInsensitive))
413 strType = "ICH6";
414 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
415 strControllerID,
416 hdc.strControllerType,
417 strType);
418 }
419 else
420 {
421 /* Warn only once */
422 if (cIDEused == 1)
423 addWarning(tr("The virtual \"%s\" system requests support for more than one IDE controller, but VirtualBox has support for only one."),
424 vsysThis.strName.c_str());
425
426 }
427 ++cIDEused;
428 break;
429 }
430
431 case ovf::HardDiskController::SATA:
432 {
433#ifdef VBOX_WITH_AHCI
434 /* Check for the constrains */
435 if (cSATAused < 1)
436 {
437 // @todo: figure out the SATA types
438 /* We only support a plain AHCI controller, so use them always */
439 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA,
440 strControllerID,
441 hdc.strControllerType,
442 "AHCI");
443 }
444 else
445 {
446 /* Warn only once */
447 if (cSATAused == 1)
448 addWarning(tr("The virtual system \"%s\" requests support for more than one SATA controller, but VirtualBox has support for only one"),
449 vsysThis.strName.c_str());
450
451 }
452 ++cSATAused;
453 break;
454#else /* !VBOX_WITH_AHCI */
455 addWarning(tr("The virtual system \"%s\" requests at least one SATA controller but this version of VirtualBox does not provide a SATA controller emulation"),
456 vsysThis.strName.c_str());
457#endif /* !VBOX_WITH_AHCI */
458 }
459
460 case ovf::HardDiskController::SCSI:
461 {
462#ifdef VBOX_WITH_LSILOGIC
463 /* Check for the constrains */
464 if (cSCSIused < 1)
465 {
466 Utf8Str hdcController = "LsiLogic";
467 if (!hdc.strControllerType.compare("BusLogic", Utf8Str::CaseInsensitive))
468 hdcController = "BusLogic";
469 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskControllerSCSI,
470 strControllerID,
471 hdc.strControllerType,
472 hdcController);
473 }
474 else
475 addWarning(tr("The virtual system \"%s\" requests support for an additional SCSI controller of type \"%s\" with ID %s, but VirtualBox presently supports only one SCSI controller."),
476 vsysThis.strName.c_str(),
477 hdc.strControllerType.c_str(),
478 strControllerID.c_str());
479 ++cSCSIused;
480 break;
481#else /* !VBOX_WITH_LSILOGIC */
482 addWarning(tr("The virtual system \"%s\" requests at least one SATA controller but this version of VirtualBox does not provide a SCSI controller emulation"),
483 vsysThis.strName.c_str());
484#endif /* !VBOX_WITH_LSILOGIC */
485 }
486 }
487 }
488
489 /* Hard disks */
490 if (vsysThis.mapVirtualDisks.size() > 0)
491 {
492 ovf::VirtualDisksMap::const_iterator itVD;
493 /* Iterate through all hard disks ()*/
494 for (itVD = vsysThis.mapVirtualDisks.begin();
495 itVD != vsysThis.mapVirtualDisks.end();
496 ++itVD)
497 {
498 const ovf::VirtualDisk &hd = itVD->second;
499 /* Get the associated disk image */
500 const ovf::DiskImage &di = m->pReader->m_mapDisks[hd.strDiskId];
501
502 // @todo:
503 // - figure out all possible vmdk formats we also support
504 // - figure out if there is a url specifier for vhd already
505 // - we need a url specifier for the vdi format
506 if ( di.strFormat.compare("http://www.vmware.com/specifications/vmdk.html#sparse", Utf8Str::CaseInsensitive)
507 || di.strFormat.compare("http://www.vmware.com/specifications/vmdk.html#compressed", Utf8Str::CaseInsensitive))
508 {
509 /* If the href is empty use the VM name as filename */
510 Utf8Str strFilename = di.strHref;
511 if (!strFilename.length())
512 strFilename = Utf8StrFmt("%s.vmdk", nameVBox.c_str());
513 /* Construct a unique target path */
514 Utf8StrFmt strPath("%ls%c%s",
515 bstrDefaultHardDiskLocation.raw(),
516 RTPATH_DELIMITER,
517 strFilename.c_str());
518 searchUniqueDiskImageFilePath(strPath);
519
520 /* find the description for the hard disk controller
521 * that has the same ID as hd.idController */
522 const VirtualSystemDescriptionEntry *pController;
523 if (!(pController = pNewDesc->findControllerFromID(hd.idController)))
524 throw setError(E_FAIL,
525 tr("Cannot find hard disk controller with OVF instance ID %RI32 to which disk \"%s\" should be attached"),
526 hd.idController,
527 di.strHref.c_str());
528
529 /* controller to attach to, and the bus within that controller */
530 Utf8StrFmt strExtraConfig("controller=%RI16;channel=%RI16",
531 pController->ulIndex,
532 hd.ulAddressOnParent);
533 ULONG ulSize = 0;
534 if (di.iCapacity != -1)
535 ulSize = (ULONG)(di.iCapacity / _1M);
536 else if (di.iPopulatedSize != -1)
537 ulSize = (ULONG)(di.iPopulatedSize / _1M);
538 else if (di.iSize != -1)
539 ulSize = (ULONG)(di.iSize / _1M);
540 if (ulSize == 0)
541 ulSize = 10000; // assume 10 GB, this is for the progress bar only anyway
542 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskImage,
543 hd.strDiskId,
544 di.strHref,
545 strPath,
546 ulSize,
547 strExtraConfig);
548 }
549 else
550 throw setError(VBOX_E_FILE_ERROR,
551 tr("Unsupported format for virtual disk image in OVF: \"%s\"", di.strFormat.c_str()));
552 }
553 }
554
555 m->virtualSystemDescriptions.push_back(pNewDesc);
556 }
557 }
558 catch (HRESULT aRC)
559 {
560 /* On error we clear the list & return */
561 m->virtualSystemDescriptions.clear();
562 rc = aRC;
563 }
564
565 // reset the appliance state
566 alock.acquire();
567 m->state = Data::ApplianceIdle;
568
569 return rc;
570}
571
572/**
573 * Public method implementation.
574 * @param aProgress
575 * @return
576 */
577STDMETHODIMP Appliance::ImportMachines(IProgress **aProgress)
578{
579 CheckComArgOutPointerValid(aProgress);
580
581 AutoCaller autoCaller(this);
582 if (FAILED(autoCaller.rc())) return autoCaller.rc();
583
584 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
585
586 // do not allow entering this method if the appliance is busy reading or writing
587 if (!isApplianceIdle())
588 return E_ACCESSDENIED;
589
590 if (!m->pReader)
591 return setError(E_FAIL,
592 tr("Cannot import machines without reading it first (call read() before importMachines())"));
593
594 ComObjPtr<Progress> progress;
595 HRESULT rc = S_OK;
596 try
597 {
598 rc = importImpl(m->locInfo, progress);
599 }
600 catch (HRESULT aRC)
601 {
602 rc = aRC;
603 }
604
605 if (SUCCEEDED(rc))
606 /* Return progress to the caller */
607 progress.queryInterfaceTo(aProgress);
608
609 return rc;
610}
611
612////////////////////////////////////////////////////////////////////////////////
613//
614// Appliance private methods
615//
616////////////////////////////////////////////////////////////////////////////////
617
618/**
619 * Implementation for reading an OVF. This starts a new thread which will call
620 * Appliance::taskThreadImportOrExport() which will then call readFS() or readS3().
621 *
622 * This is in a separate private method because it is used from two locations:
623 *
624 * 1) from the public Appliance::Read().
625 * 2) from Appliance::readS3(), which got called from a previous instance of Appliance::taskThreadImportOrExport().
626 *
627 * @param aLocInfo
628 * @param aProgress
629 * @return
630 */
631HRESULT Appliance::readImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
632{
633 BstrFmt bstrDesc = BstrFmt(tr("Read appliance '%s'"),
634 aLocInfo.strPath.c_str());
635 HRESULT rc;
636 /* Create the progress object */
637 aProgress.createObject();
638 if (aLocInfo.storageType == VFSType_File)
639 /* 1 operation only */
640 rc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this),
641 bstrDesc,
642 TRUE /* aCancelable */);
643 else
644 /* 4/5 is downloading, 1/5 is reading */
645 rc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this),
646 bstrDesc,
647 TRUE /* aCancelable */,
648 2, // ULONG cOperations,
649 5, // ULONG ulTotalOperationsWeight,
650 BstrFmt(tr("Download appliance '%s'"),
651 aLocInfo.strPath.c_str()), // CBSTR bstrFirstOperationDescription,
652 4); // ULONG ulFirstOperationWeight,
653 if (FAILED(rc)) throw rc;
654
655 /* Initialize our worker task */
656 std::auto_ptr<TaskOVF> task(new TaskOVF(this, TaskOVF::Read, aLocInfo, aProgress));
657
658 rc = task->startThread();
659 if (FAILED(rc)) throw rc;
660
661 /* Don't destruct on success */
662 task.release();
663
664 return rc;
665}
666
667/**
668 * Actual worker code for reading an OVF from disk. This is called from Appliance::taskThreadImportOrExport()
669 * and therefore runs on the OVF read worker thread. This runs in two contexts:
670 *
671 * 1) in a first worker thread; in that case, Appliance::Read() called Appliance::readImpl();
672 *
673 * 2) in a second worker thread; in that case, Appliance::Read() called Appliance::readImpl(), which
674 * called Appliance::readS3(), which called Appliance::readImpl(), which then called this.
675 *
676 * @param pTask
677 * @return
678 */
679HRESULT Appliance::readFS(const LocationInfo &locInfo)
680{
681 LogFlowFuncEnter();
682 LogFlowFunc(("Appliance %p\n", this));
683
684 AutoCaller autoCaller(this);
685 if (FAILED(autoCaller.rc())) return autoCaller.rc();
686
687 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
688
689 HRESULT rc = S_OK;
690
691 try
692 {
693 /* Read & parse the XML structure of the OVF file */
694 m->pReader = new ovf::OVFReader(locInfo.strPath);
695 /* Create the SHA1 sum of the OVF file for later validation */
696 char *pszDigest;
697 int vrc = RTSha1Digest(locInfo.strPath.c_str(), &pszDigest);
698 if (RT_FAILURE(vrc))
699 throw setError(VBOX_E_FILE_ERROR,
700 tr("Couldn't calculate SHA1 digest for file '%s' (%Rrc)"),
701 RTPathFilename(locInfo.strPath.c_str()), vrc);
702 m->strOVFSHA1Digest = pszDigest;
703 RTStrFree(pszDigest);
704 }
705 catch(xml::Error &x)
706 {
707 rc = setError(VBOX_E_FILE_ERROR,
708 x.what());
709 }
710 catch(HRESULT aRC)
711 {
712 rc = aRC;
713 }
714
715 LogFlowFunc(("rc=%Rhrc\n", rc));
716 LogFlowFuncLeave();
717
718 return rc;
719}
720
721/**
722 * Worker code for reading OVF from the cloud. This is called from Appliance::taskThreadImportOrExport()
723 * in S3 mode and therefore runs on the OVF read worker thread. This then starts a second worker
724 * thread to create temporary files (see Appliance::readFS()).
725 *
726 * @param pTask
727 * @return
728 */
729HRESULT Appliance::readS3(TaskOVF *pTask)
730{
731 LogFlowFuncEnter();
732 LogFlowFunc(("Appliance %p\n", this));
733
734 AutoCaller autoCaller(this);
735 if (FAILED(autoCaller.rc())) return autoCaller.rc();
736
737 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
738
739 HRESULT rc = S_OK;
740 int vrc = VINF_SUCCESS;
741 RTS3 hS3 = NIL_RTS3;
742 char szOSTmpDir[RTPATH_MAX];
743 RTPathTemp(szOSTmpDir, sizeof(szOSTmpDir));
744 /* The template for the temporary directory created below */
745 char *pszTmpDir;
746 RTStrAPrintf(&pszTmpDir, "%s"RTPATH_SLASH_STR"vbox-ovf-XXXXXX", szOSTmpDir);
747 list< pair<Utf8Str, ULONG> > filesList;
748 Utf8Str strTmpOvf;
749
750 try
751 {
752 /* Extract the bucket */
753 Utf8Str tmpPath = pTask->locInfo.strPath;
754 Utf8Str bucket;
755 parseBucket(tmpPath, bucket);
756
757 /* We need a temporary directory which we can put the OVF file & all
758 * disk images in */
759 vrc = RTDirCreateTemp(pszTmpDir);
760 if (RT_FAILURE(vrc))
761 throw setError(VBOX_E_FILE_ERROR,
762 tr("Cannot create temporary directory '%s'"), pszTmpDir);
763
764 /* The temporary name of the target OVF file */
765 strTmpOvf = Utf8StrFmt("%s/%s", pszTmpDir, RTPathFilename(tmpPath.c_str()));
766
767 /* Next we have to download the OVF */
768 vrc = RTS3Create(&hS3, pTask->locInfo.strUsername.c_str(), pTask->locInfo.strPassword.c_str(), pTask->locInfo.strHostname.c_str(), "virtualbox-agent/"VBOX_VERSION_STRING);
769 if (RT_FAILURE(vrc))
770 throw setError(VBOX_E_IPRT_ERROR,
771 tr("Cannot create S3 service handler"));
772 RTS3SetProgressCallback(hS3, pTask->updateProgress, &pTask);
773
774 /* Get it */
775 char *pszFilename = RTPathFilename(strTmpOvf.c_str());
776 vrc = RTS3GetKey(hS3, bucket.c_str(), pszFilename, strTmpOvf.c_str());
777 if (RT_FAILURE(vrc))
778 {
779 if (vrc == VERR_S3_CANCELED)
780 throw S_OK; /* todo: !!!!!!!!!!!!! */
781 else if (vrc == VERR_S3_ACCESS_DENIED)
782 throw setError(E_ACCESSDENIED,
783 tr("Cannot download file '%s' from S3 storage server (Access denied). Make sure that your credentials are right. Also check that your host clock is properly synced"), pszFilename);
784 else if (vrc == VERR_S3_NOT_FOUND)
785 throw setError(VBOX_E_FILE_ERROR,
786 tr("Cannot download file '%s' from S3 storage server (File not found)"), pszFilename);
787 else
788 throw setError(VBOX_E_IPRT_ERROR,
789 tr("Cannot download file '%s' from S3 storage server (%Rrc)"), pszFilename, vrc);
790 }
791
792 /* Close the connection early */
793 RTS3Destroy(hS3);
794 hS3 = NIL_RTS3;
795
796 if (!pTask->pProgress.isNull())
797 pTask->pProgress->SetNextOperation(Bstr(tr("Reading")), 1);
798
799 /* Prepare the temporary reading of the OVF */
800 ComObjPtr<Progress> progress;
801 LocationInfo li;
802 li.strPath = strTmpOvf;
803 /* Start the reading from the fs */
804 rc = readImpl(li, progress);
805 if (FAILED(rc)) throw rc;
806
807 /* Unlock the appliance for the reading thread */
808 appLock.release();
809 /* Wait until the reading is done, but report the progress back to the
810 caller */
811 ComPtr<IProgress> progressInt(progress);
812 waitForAsyncProgress(pTask->pProgress, progressInt); /* Any errors will be thrown */
813
814 /* Again lock the appliance for the next steps */
815 appLock.acquire();
816 }
817 catch(HRESULT aRC)
818 {
819 rc = aRC;
820 }
821 /* Cleanup */
822 RTS3Destroy(hS3);
823 /* Delete all files which where temporary created */
824 if (RTPathExists(strTmpOvf.c_str()))
825 {
826 vrc = RTFileDelete(strTmpOvf.c_str());
827 if (RT_FAILURE(vrc))
828 rc = setError(VBOX_E_FILE_ERROR,
829 tr("Cannot delete file '%s' (%Rrc)"), strTmpOvf.c_str(), vrc);
830 }
831 /* Delete the temporary directory */
832 if (RTPathExists(pszTmpDir))
833 {
834 vrc = RTDirRemove(pszTmpDir);
835 if (RT_FAILURE(vrc))
836 rc = setError(VBOX_E_FILE_ERROR,
837 tr("Cannot delete temporary directory '%s' (%Rrc)"), pszTmpDir, vrc);
838 }
839 if (pszTmpDir)
840 RTStrFree(pszTmpDir);
841
842 LogFlowFunc(("rc=%Rhrc\n", rc));
843 LogFlowFuncLeave();
844
845 return rc;
846}
847
848/**
849 * Helper that converts VirtualSystem attachment values into VirtualBox attachment values.
850 * Throws HRESULT values on errors!
851 *
852 * @param hdc
853 * @param vd
854 * @param mhda
855 */
856void Appliance::convertDiskAttachmentValues(const ovf::HardDiskController &hdc,
857 uint32_t ulAddressOnParent,
858 Bstr &controllerType,
859 int32_t &lChannel,
860 int32_t &lDevice)
861{
862 switch (hdc.system)
863 {
864 case ovf::HardDiskController::IDE:
865 // For the IDE bus, the channel parameter can be either 0 or 1, to specify the primary
866 // or secondary IDE controller, respectively. For the primary controller of the IDE bus,
867 // the device number can be either 0 or 1, to specify the master or the slave device,
868 // respectively. For the secondary IDE controller, the device number is always 1 because
869 // the master device is reserved for the CD-ROM drive.
870 controllerType = Bstr("IDE Controller");
871 switch (ulAddressOnParent)
872 {
873 case 0: // interpret this as primary master
874 lChannel = (long)0;
875 lDevice = (long)0;
876 break;
877
878 case 1: // interpret this as primary slave
879 lChannel = (long)0;
880 lDevice = (long)1;
881 break;
882
883 case 2: // interpret this as secondary master
884 lChannel = (long)1;
885 lDevice = (long)0;
886 break;
887
888 case 3: // interpret this as secondary slave
889 lChannel = (long)1;
890 lDevice = (long)1;
891 break;
892
893 default:
894 throw setError(VBOX_E_NOT_SUPPORTED,
895 tr("Invalid channel %RI16 specified; IDE controllers support only 0, 1 or 2"), ulAddressOnParent);
896 break;
897 }
898 break;
899
900 case ovf::HardDiskController::SATA:
901 controllerType = Bstr("SATA Controller");
902 lChannel = (long)ulAddressOnParent;
903 lDevice = (long)0;
904 break;
905
906 case ovf::HardDiskController::SCSI:
907 controllerType = Bstr("SCSI Controller");
908 lChannel = (long)ulAddressOnParent;
909 lDevice = (long)0;
910 break;
911
912 default: break;
913 }
914}
915
916/**
917 * Implementation for importing OVF data into VirtualBox. This starts a new thread which will call
918 * Appliance::taskThreadImportOrExport().
919 *
920 * This is in a separate private method because it is used from two locations:
921 *
922 * 1) from the public Appliance::ImportMachines().
923 * 2) from Appliance::importS3(), which got called from a previous instance of Appliance::taskThreadImportOrExport().
924 *
925 * @param aLocInfo
926 * @param aProgress
927 * @return
928 */
929HRESULT Appliance::importImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
930{
931 Bstr progressDesc = BstrFmt(tr("Import appliance '%s'"),
932 aLocInfo.strPath.c_str());
933 HRESULT rc = S_OK;
934
935 /* todo: This progress init stuff should be done a little bit more generic */
936 if (aLocInfo.storageType == VFSType_File)
937 rc = setUpProgressFS(aProgress, progressDesc);
938 else
939 rc = setUpProgressImportS3(aProgress, progressDesc);
940 if (FAILED(rc)) throw rc;
941
942 /* Initialize our worker task */
943 std::auto_ptr<TaskOVF> task(new TaskOVF(this, TaskOVF::Import, aLocInfo, aProgress));
944
945 rc = task->startThread();
946 if (FAILED(rc)) throw rc;
947
948 /* Don't destruct on success */
949 task.release();
950
951 return rc;
952}
953
954/**
955 * Used by Appliance::importMachineGeneric() to store
956 * input parameters and rollback information.
957 */
958struct Appliance::ImportStack
959{
960 // input pointers
961 const LocationInfo &locInfo; // ptr to location info from Appliance::importFS()
962 const ovf::DiskImagesMap &mapDisks; // ptr to disks map in OVF
963 ComObjPtr<Progress> &pProgress; // progress object passed into Appliance::importFS()
964
965 // session (not initially created)
966 ComPtr<ISession> pSession; // session opened in Appliance::importFS() for machine manipulation
967 bool fSessionOpen; // true if the pSession is currently open and needs closing
968
969 // a list of images that we created/imported; this is initially empty
970 // and will be cleaned up on errors
971 list<MyHardDiskAttachment> llHardDiskAttachments; // disks that were attached
972 list< ComPtr<IMedium> > llHardDisksCreated; // media that were created
973 list<Bstr> llMachinesRegistered; // machines that were registered; list of string UUIDs
974
975 ImportStack(const LocationInfo &aLocInfo,
976 const ovf::DiskImagesMap &aMapDisks,
977 ComObjPtr<Progress> &aProgress)
978 : locInfo(aLocInfo),
979 mapDisks(aMapDisks),
980 pProgress(aProgress),
981 fSessionOpen(false)
982 {
983 }
984};
985
986/**
987 * Actual worker code for importing OVF data into VirtualBox. This is called from Appliance::taskThreadImportOrExport()
988 * and therefore runs on the OVF import worker thread. This runs in two contexts:
989 *
990 * 1) in a first worker thread; in that case, Appliance::ImportMachines() called Appliance::importImpl();
991 *
992 * 2) in a second worker thread; in that case, Appliance::ImportMachines() called Appliance::importImpl(), which
993 * called Appliance::importS3(), which called Appliance::importImpl(), which then called this.
994 *
995 * @param pTask
996 * @return
997 */
998HRESULT Appliance::importFS(const LocationInfo &locInfo,
999 ComObjPtr<Progress> &pProgress)
1000{
1001 LogFlowFuncEnter();
1002 LogFlowFunc(("Appliance %p\n", this));
1003
1004 AutoCaller autoCaller(this);
1005 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1006
1007 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
1008
1009 if (!isApplianceIdle())
1010 return E_ACCESSDENIED;
1011
1012 Assert(!pProgress.isNull());
1013
1014 // Change the appliance state so we can safely leave the lock while doing time-consuming
1015 // disk imports; also the below method calls do all kinds of locking which conflicts with
1016 // the appliance object lock
1017 m->state = Data::ApplianceImporting;
1018 appLock.release();
1019
1020 HRESULT rc = S_OK;
1021
1022 const ovf::OVFReader &reader = *m->pReader;
1023 // this is safe to access because this thread only gets started
1024 // if pReader != NULL
1025
1026 // rollback for errors:
1027 ImportStack stack(locInfo, reader.m_mapDisks, pProgress);
1028 /* If a manifest file exists, verify the content. Therefore we need all
1029 * files which are referenced by the OVF & the OVF itself */
1030 Utf8Str strMfFile = manifestFileName(locInfo.strPath);
1031 list<Utf8Str> filesList;
1032 if (RTPathExists(strMfFile.c_str()))
1033 {
1034 Utf8Str strSrcDir(locInfo.strPath);
1035 strSrcDir.stripFilename();
1036 /* Add every disks of every virtual system to an internal list */
1037 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it;
1038 for (it = m->virtualSystemDescriptions.begin();
1039 it != m->virtualSystemDescriptions.end();
1040 ++it)
1041 {
1042 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it);
1043 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskImage);
1044 std::list<VirtualSystemDescriptionEntry*>::const_iterator itH;
1045 for (itH = avsdeHDs.begin();
1046 itH != avsdeHDs.end();
1047 ++itH)
1048 {
1049 VirtualSystemDescriptionEntry *vsdeHD = *itH;
1050 /* Find the disk from the OVF's disk list */
1051 ovf::DiskImagesMap::const_iterator itDiskImage = reader.m_mapDisks.find(vsdeHD->strRef);
1052 const ovf::DiskImage &di = itDiskImage->second;
1053 Utf8StrFmt strSrcFilePath("%s%c%s", strSrcDir.c_str(), RTPATH_DELIMITER, di.strHref.c_str());
1054 filesList.push_back(strSrcFilePath);
1055 }
1056 }
1057 /* Create the test list */
1058 PRTMANIFESTTEST pTestList = (PRTMANIFESTTEST)RTMemAllocZ(sizeof(RTMANIFESTTEST)*(filesList.size()+1));
1059 pTestList[0].pszTestFile = (char*)locInfo.strPath.c_str();
1060 pTestList[0].pszTestDigest = (char*)m->strOVFSHA1Digest.c_str();
1061 int vrc = VINF_SUCCESS;
1062 size_t i = 1;
1063 list<Utf8Str>::const_iterator it1;
1064 for (it1 = filesList.begin();
1065 it1 != filesList.end();
1066 ++it1, ++i)
1067 {
1068 char* pszDigest;
1069 vrc = RTSha1Digest((*it1).c_str(), &pszDigest);
1070 pTestList[i].pszTestFile = (char*)(*it1).c_str();
1071 pTestList[i].pszTestDigest = pszDigest;
1072 }
1073 size_t cIndexOnError;
1074 vrc = RTManifestVerify(strMfFile.c_str(), pTestList, filesList.size() + 1, &cIndexOnError);
1075 if (vrc == VERR_MANIFEST_DIGEST_MISMATCH)
1076 rc = setError(VBOX_E_FILE_ERROR,
1077 tr("The SHA1 digest of '%s' doesn't match to the one in '%s'"),
1078 RTPathFilename(pTestList[cIndexOnError].pszTestFile),
1079 RTPathFilename(strMfFile.c_str()));
1080 else if (RT_FAILURE(vrc))
1081 rc = setError(VBOX_E_FILE_ERROR,
1082 tr("Couldn't verify the content of '%s' against the available files (%Rrc)"),
1083 RTPathFilename(strMfFile.c_str()),
1084 vrc);
1085 /* Cleanup */
1086 for (size_t j = 1;
1087 j < filesList.size();
1088 ++j)
1089 RTStrFree(pTestList[j].pszTestDigest);
1090 RTMemFree(pTestList);
1091 if (FAILED(rc)) return rc;
1092 }
1093
1094 rc = stack.pSession.createInprocObject(CLSID_Session);
1095 if (FAILED(rc)) return rc;
1096
1097 list<ovf::VirtualSystem>::const_iterator it;
1098 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it1;
1099 /* Iterate through all virtual systems of that appliance */
1100 size_t i = 0;
1101 for (it = reader.m_llVirtualSystems.begin(),
1102 it1 = m->virtualSystemDescriptions.begin();
1103 it != reader.m_llVirtualSystems.end();
1104 ++it, ++it1, ++i)
1105 {
1106 const ovf::VirtualSystem &vsysThis = *it;
1107 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it1);
1108
1109 ComPtr<IMachine> pNewMachine;
1110
1111 /* Catch possible errors */
1112 try
1113 {
1114 // there are two ways in which we can create a vbox machine from OVF:
1115 // -- either this OVF was written by vbox 3.2 or later, in which case there is a <vbox:Machine> element
1116 // in the <VirtualSystem>; then the VirtualSystemDescription::Data has a settings::MachineConfigFile
1117 // with all the machine config pretty-parsed;
1118 // -- or this is an OVF from an older vbox or an external source, and then we need to translate the
1119 // VirtualSystemDescriptionEntry and do import work
1120
1121 // @todo r=dj make this selection configurable at run-time, and from the GUI as well
1122
1123 if (vsdescThis->m->pConfig)
1124 importVBoxMachine(vsdescThis, pNewMachine, stack);
1125 else
1126 importMachineGeneric(vsysThis, vsdescThis, pNewMachine, stack);
1127 }
1128 catch (HRESULT aRC)
1129 {
1130 rc = aRC;
1131 }
1132
1133 if (FAILED(rc))
1134 break;
1135
1136 } // for (it = pAppliance->m->llVirtualSystems.begin(),
1137
1138 if (FAILED(rc))
1139 {
1140 // with _whatever_ error we've had, do a complete roll-back of
1141 // machines and disks we've created; unfortunately this is
1142 // not so trivially done...
1143
1144 HRESULT rc2;
1145 // detach all hard disks from all machines we created
1146 list<MyHardDiskAttachment>::iterator itM;
1147 for (itM = stack.llHardDiskAttachments.begin();
1148 itM != stack.llHardDiskAttachments.end();
1149 ++itM)
1150 {
1151 const MyHardDiskAttachment &mhda = *itM;
1152 Bstr bstrUuid(mhda.bstrUuid); // make a copy, Windows can't handle const Bstr
1153 rc2 = mVirtualBox->OpenSession(stack.pSession, bstrUuid);
1154 if (SUCCEEDED(rc2))
1155 {
1156 ComPtr<IMachine> sMachine;
1157 rc2 = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
1158 if (SUCCEEDED(rc2))
1159 {
1160 rc2 = sMachine->DetachDevice(Bstr(mhda.controllerType), mhda.lChannel, mhda.lDevice);
1161 rc2 = sMachine->SaveSettings();
1162 }
1163 stack.pSession->Close();
1164 }
1165 }
1166
1167 // now clean up all hard disks we created
1168 list< ComPtr<IMedium> >::iterator itHD;
1169 for (itHD = stack.llHardDisksCreated.begin();
1170 itHD != stack.llHardDisksCreated.end();
1171 ++itHD)
1172 {
1173 ComPtr<IMedium> pDisk = *itHD;
1174 ComPtr<IProgress> pProgress2;
1175 rc2 = pDisk->DeleteStorage(pProgress2.asOutParam());
1176 rc2 = pProgress2->WaitForCompletion(-1);
1177 }
1178
1179 // finally, deregister and remove all machines
1180 list<Bstr>::iterator itID;
1181 for (itID = stack.llMachinesRegistered.begin();
1182 itID != stack.llMachinesRegistered.end();
1183 ++itID)
1184 {
1185 Bstr bstrGuid = *itID; // make a copy, Windows can't handle const Bstr
1186 ComPtr<IMachine> failedMachine;
1187 rc2 = mVirtualBox->UnregisterMachine(bstrGuid, failedMachine.asOutParam());
1188 if (SUCCEEDED(rc2))
1189 rc2 = failedMachine->DeleteSettings();
1190 }
1191 }
1192
1193 // restore the appliance state
1194 appLock.acquire();
1195 m->state = Data::ApplianceIdle;
1196
1197 LogFlowFunc(("rc=%Rhrc\n", rc));
1198 LogFlowFuncLeave();
1199
1200 return rc;
1201}
1202
1203/**
1204 * Imports one OVF virtual system (described by the given ovf::VirtualSystem and VirtualSystemDescription)
1205 * into VirtualBox by creating an IMachine instance, which is returned.
1206 *
1207 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
1208 * up any leftovers from this function. For this, the given ImportStack instance has received information
1209 * about what needs cleaning up (to support rollback).
1210 *
1211 * @param locInfo
1212 * @param vsysThis
1213 * @param vsdescThis
1214 * @param pNewMachine
1215 * @param stack
1216 */
1217void Appliance::importMachineGeneric(const ovf::VirtualSystem &vsysThis,
1218 ComObjPtr<VirtualSystemDescription> &vsdescThis,
1219 ComPtr<IMachine> &pNewMachine,
1220 ImportStack &stack)
1221{
1222 /* Guest OS type */
1223 std::list<VirtualSystemDescriptionEntry*> vsdeOS;
1224 vsdeOS = vsdescThis->findByType(VirtualSystemDescriptionType_OS);
1225 if (vsdeOS.size() < 1)
1226 throw setError(VBOX_E_FILE_ERROR,
1227 tr("Missing guest OS type"));
1228 const Utf8Str &strOsTypeVBox = vsdeOS.front()->strVbox;
1229
1230 /* Now that we know the base system get our internal defaults based on that. */
1231 ComPtr<IGuestOSType> osType;
1232 HRESULT rc = mVirtualBox->GetGuestOSType(Bstr(strOsTypeVBox), osType.asOutParam());
1233 if (FAILED(rc)) throw rc;
1234
1235 /* Create the machine */
1236 /* First get the name */
1237 std::list<VirtualSystemDescriptionEntry*> vsdeName = vsdescThis->findByType(VirtualSystemDescriptionType_Name);
1238 if (vsdeName.size() < 1)
1239 throw setError(VBOX_E_FILE_ERROR,
1240 tr("Missing VM name"));
1241 const Utf8Str &strNameVBox = vsdeName.front()->strVbox;
1242 rc = mVirtualBox->CreateMachine(Bstr(strNameVBox),
1243 Bstr(strOsTypeVBox),
1244 NULL,
1245 NULL,
1246 FALSE,
1247 pNewMachine.asOutParam());
1248 if (FAILED(rc)) throw rc;
1249
1250 // and the description
1251 std::list<VirtualSystemDescriptionEntry*> vsdeDescription = vsdescThis->findByType(VirtualSystemDescriptionType_Description);
1252 if (vsdeDescription.size())
1253 {
1254 const Utf8Str &strDescription = vsdeDescription.front()->strVbox;
1255 rc = pNewMachine->COMSETTER(Description)(Bstr(strDescription));
1256 if (FAILED(rc)) throw rc;
1257 }
1258
1259 /* CPU count */
1260 std::list<VirtualSystemDescriptionEntry*> vsdeCPU = vsdescThis->findByType(VirtualSystemDescriptionType_CPU);
1261 ComAssertMsgThrow(vsdeCPU.size() == 1, ("CPU count missing"), E_FAIL);
1262 const Utf8Str &cpuVBox = vsdeCPU.front()->strVbox;
1263 ULONG tmpCount = (ULONG)RTStrToUInt64(cpuVBox.c_str());
1264 rc = pNewMachine->COMSETTER(CPUCount)(tmpCount);
1265 if (FAILED(rc)) throw rc;
1266 bool fEnableIOApic = false;
1267 /* We need HWVirt & IO-APIC if more than one CPU is requested */
1268 if (tmpCount > 1)
1269 {
1270 rc = pNewMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, TRUE);
1271 if (FAILED(rc)) throw rc;
1272
1273 fEnableIOApic = true;
1274 }
1275
1276 /* RAM */
1277 std::list<VirtualSystemDescriptionEntry*> vsdeRAM = vsdescThis->findByType(VirtualSystemDescriptionType_Memory);
1278 ComAssertMsgThrow(vsdeRAM.size() == 1, ("RAM size missing"), E_FAIL);
1279 const Utf8Str &memoryVBox = vsdeRAM.front()->strVbox;
1280 ULONG tt = (ULONG)RTStrToUInt64(memoryVBox.c_str());
1281 rc = pNewMachine->COMSETTER(MemorySize)(tt);
1282 if (FAILED(rc)) throw rc;
1283
1284 /* VRAM */
1285 /* Get the recommended VRAM for this guest OS type */
1286 ULONG vramVBox;
1287 rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox);
1288 if (FAILED(rc)) throw rc;
1289
1290 /* Set the VRAM */
1291 rc = pNewMachine->COMSETTER(VRAMSize)(vramVBox);
1292 if (FAILED(rc)) throw rc;
1293
1294 // I/O APIC: Generic OVF has no setting for this. Enable it if we
1295 // import a Windows VM because if if Windows was installed without IOAPIC,
1296 // it will not mind finding an one later on, but if Windows was installed
1297 // _with_ an IOAPIC, it will bluescreen if it's not found
1298 if (!fEnableIOApic)
1299 {
1300 Bstr bstrFamilyId;
1301 rc = osType->COMGETTER(FamilyId)(bstrFamilyId.asOutParam());
1302 if (FAILED(rc)) throw rc;
1303 if (bstrFamilyId == "Windows")
1304 fEnableIOApic = true;
1305 }
1306
1307 if (fEnableIOApic)
1308 {
1309 ComPtr<IBIOSSettings> pBIOSSettings;
1310 rc = pNewMachine->COMGETTER(BIOSSettings)(pBIOSSettings.asOutParam());
1311 if (FAILED(rc)) throw rc;
1312
1313 rc = pBIOSSettings->COMSETTER(IOAPICEnabled)(TRUE);
1314 if (FAILED(rc)) throw rc;
1315 }
1316
1317 /* Audio Adapter */
1318 std::list<VirtualSystemDescriptionEntry*> vsdeAudioAdapter = vsdescThis->findByType(VirtualSystemDescriptionType_SoundCard);
1319 /* @todo: we support one audio adapter only */
1320 if (vsdeAudioAdapter.size() > 0)
1321 {
1322 const Utf8Str& audioAdapterVBox = vsdeAudioAdapter.front()->strVbox;
1323 if (audioAdapterVBox.compare("null", Utf8Str::CaseInsensitive) != 0)
1324 {
1325 uint32_t audio = RTStrToUInt32(audioAdapterVBox.c_str());
1326 ComPtr<IAudioAdapter> audioAdapter;
1327 rc = pNewMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam());
1328 if (FAILED(rc)) throw rc;
1329 rc = audioAdapter->COMSETTER(Enabled)(true);
1330 if (FAILED(rc)) throw rc;
1331 rc = audioAdapter->COMSETTER(AudioController)(static_cast<AudioControllerType_T>(audio));
1332 if (FAILED(rc)) throw rc;
1333 }
1334 }
1335
1336#ifdef VBOX_WITH_USB
1337 /* USB Controller */
1338 std::list<VirtualSystemDescriptionEntry*> vsdeUSBController = vsdescThis->findByType(VirtualSystemDescriptionType_USBController);
1339 // USB support is enabled if there's at least one such entry; to disable USB support,
1340 // the type of the USB item would have been changed to "ignore"
1341 bool fUSBEnabled = vsdeUSBController.size() > 0;
1342
1343 ComPtr<IUSBController> usbController;
1344 rc = pNewMachine->COMGETTER(USBController)(usbController.asOutParam());
1345 if (FAILED(rc)) throw rc;
1346 rc = usbController->COMSETTER(Enabled)(fUSBEnabled);
1347 if (FAILED(rc)) throw rc;
1348#endif /* VBOX_WITH_USB */
1349
1350 /* Change the network adapters */
1351 std::list<VirtualSystemDescriptionEntry*> vsdeNW = vsdescThis->findByType(VirtualSystemDescriptionType_NetworkAdapter);
1352 if (vsdeNW.size() == 0)
1353 {
1354 /* No network adapters, so we have to disable our default one */
1355 ComPtr<INetworkAdapter> nwVBox;
1356 rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam());
1357 if (FAILED(rc)) throw rc;
1358 rc = nwVBox->COMSETTER(Enabled)(false);
1359 if (FAILED(rc)) throw rc;
1360 }
1361 else if (vsdeNW.size() > SchemaDefs::NetworkAdapterCount)
1362 throw setError(VBOX_E_FILE_ERROR,
1363 tr("Too many network adapters: OVF requests %d network adapters, but VirtualBox only supports %d"),
1364 vsdeNW.size(), SchemaDefs::NetworkAdapterCount);
1365 else
1366 {
1367 list<VirtualSystemDescriptionEntry*>::const_iterator nwIt;
1368 size_t a = 0;
1369 for (nwIt = vsdeNW.begin();
1370 nwIt != vsdeNW.end();
1371 ++nwIt, ++a)
1372 {
1373 const VirtualSystemDescriptionEntry* pvsys = *nwIt;
1374
1375 const Utf8Str &nwTypeVBox = pvsys->strVbox;
1376 uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str());
1377 ComPtr<INetworkAdapter> pNetworkAdapter;
1378 rc = pNewMachine->GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
1379 if (FAILED(rc)) throw rc;
1380 /* Enable the network card & set the adapter type */
1381 rc = pNetworkAdapter->COMSETTER(Enabled)(true);
1382 if (FAILED(rc)) throw rc;
1383 rc = pNetworkAdapter->COMSETTER(AdapterType)(static_cast<NetworkAdapterType_T>(tt1));
1384 if (FAILED(rc)) throw rc;
1385
1386 // default is NAT; change to "bridged" if extra conf says so
1387 if (!pvsys->strExtraConfig.compare("type=Bridged", Utf8Str::CaseInsensitive))
1388 {
1389 /* Attach to the right interface */
1390 rc = pNetworkAdapter->AttachToBridgedInterface();
1391 if (FAILED(rc)) throw rc;
1392 ComPtr<IHost> host;
1393 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
1394 if (FAILED(rc)) throw rc;
1395 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
1396 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
1397 if (FAILED(rc)) throw rc;
1398 // We search for the first host network interface which
1399 // is usable for bridged networking
1400 for (size_t j = 0;
1401 j < nwInterfaces.size();
1402 ++j)
1403 {
1404 HostNetworkInterfaceType_T itype;
1405 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
1406 if (FAILED(rc)) throw rc;
1407 if (itype == HostNetworkInterfaceType_Bridged)
1408 {
1409 Bstr name;
1410 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
1411 if (FAILED(rc)) throw rc;
1412 /* Set the interface name to attach to */
1413 pNetworkAdapter->COMSETTER(HostInterface)(name);
1414 if (FAILED(rc)) throw rc;
1415 break;
1416 }
1417 }
1418 }
1419 /* Next test for host only interfaces */
1420 else if (!pvsys->strExtraConfig.compare("type=HostOnly", Utf8Str::CaseInsensitive))
1421 {
1422 /* Attach to the right interface */
1423 rc = pNetworkAdapter->AttachToHostOnlyInterface();
1424 if (FAILED(rc)) throw rc;
1425 ComPtr<IHost> host;
1426 rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
1427 if (FAILED(rc)) throw rc;
1428 com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
1429 rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
1430 if (FAILED(rc)) throw rc;
1431 // We search for the first host network interface which
1432 // is usable for host only networking
1433 for (size_t j = 0;
1434 j < nwInterfaces.size();
1435 ++j)
1436 {
1437 HostNetworkInterfaceType_T itype;
1438 rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
1439 if (FAILED(rc)) throw rc;
1440 if (itype == HostNetworkInterfaceType_HostOnly)
1441 {
1442 Bstr name;
1443 rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
1444 if (FAILED(rc)) throw rc;
1445 /* Set the interface name to attach to */
1446 pNetworkAdapter->COMSETTER(HostInterface)(name);
1447 if (FAILED(rc)) throw rc;
1448 break;
1449 }
1450 }
1451 }
1452 }
1453 }
1454
1455 /* Hard disk controller IDE */
1456 std::list<VirtualSystemDescriptionEntry*> vsdeHDCIDE = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskControllerIDE);
1457 if (vsdeHDCIDE.size() > 1)
1458 throw setError(VBOX_E_FILE_ERROR,
1459 tr("Too many IDE controllers in OVF; import facility only supports one"));
1460 if (vsdeHDCIDE.size() == 1)
1461 {
1462 ComPtr<IStorageController> pController;
1463 rc = pNewMachine->AddStorageController(Bstr("IDE Controller"), StorageBus_IDE, pController.asOutParam());
1464 if (FAILED(rc)) throw rc;
1465
1466 const char *pcszIDEType = vsdeHDCIDE.front()->strVbox.c_str();
1467 if (!strcmp(pcszIDEType, "PIIX3"))
1468 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX3);
1469 else if (!strcmp(pcszIDEType, "PIIX4"))
1470 rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX4);
1471 else if (!strcmp(pcszIDEType, "ICH6"))
1472 rc = pController->COMSETTER(ControllerType)(StorageControllerType_ICH6);
1473 else
1474 throw setError(VBOX_E_FILE_ERROR,
1475 tr("Invalid IDE controller type \"%s\""),
1476 pcszIDEType);
1477 if (FAILED(rc)) throw rc;
1478 }
1479#ifdef VBOX_WITH_AHCI
1480 /* Hard disk controller SATA */
1481 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSATA = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskControllerSATA);
1482 if (vsdeHDCSATA.size() > 1)
1483 throw setError(VBOX_E_FILE_ERROR,
1484 tr("Too many SATA controllers in OVF; import facility only supports one"));
1485 if (vsdeHDCSATA.size() > 0)
1486 {
1487 ComPtr<IStorageController> pController;
1488 const Utf8Str &hdcVBox = vsdeHDCSATA.front()->strVbox;
1489 if (hdcVBox == "AHCI")
1490 {
1491 rc = pNewMachine->AddStorageController(Bstr("SATA Controller"), StorageBus_SATA, pController.asOutParam());
1492 if (FAILED(rc)) throw rc;
1493 }
1494 else
1495 throw setError(VBOX_E_FILE_ERROR,
1496 tr("Invalid SATA controller type \"%s\""),
1497 hdcVBox.c_str());
1498 }
1499#endif /* VBOX_WITH_AHCI */
1500
1501#ifdef VBOX_WITH_LSILOGIC
1502 /* Hard disk controller SCSI */
1503 std::list<VirtualSystemDescriptionEntry*> vsdeHDCSCSI = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI);
1504 if (vsdeHDCSCSI.size() > 1)
1505 throw setError(VBOX_E_FILE_ERROR,
1506 tr("Too many SCSI controllers in OVF; import facility only supports one"));
1507 if (vsdeHDCSCSI.size() > 0)
1508 {
1509 ComPtr<IStorageController> pController;
1510 StorageControllerType_T controllerType;
1511 const Utf8Str &hdcVBox = vsdeHDCSCSI.front()->strVbox;
1512 if (hdcVBox == "LsiLogic")
1513 controllerType = StorageControllerType_LsiLogic;
1514 else if (hdcVBox == "BusLogic")
1515 controllerType = StorageControllerType_BusLogic;
1516 else
1517 throw setError(VBOX_E_FILE_ERROR,
1518 tr("Invalid SCSI controller type \"%s\""),
1519 hdcVBox.c_str());
1520
1521 rc = pNewMachine->AddStorageController(Bstr("SCSI Controller"), StorageBus_SCSI, pController.asOutParam());
1522 if (FAILED(rc)) throw rc;
1523 rc = pController->COMSETTER(ControllerType)(controllerType);
1524 if (FAILED(rc)) throw rc;
1525 }
1526#endif /* VBOX_WITH_LSILOGIC */
1527
1528 /* Now its time to register the machine before we add any hard disks */
1529 rc = mVirtualBox->RegisterMachine(pNewMachine);
1530 if (FAILED(rc)) throw rc;
1531
1532 // store new machine for roll-back in case of errors
1533 Bstr bstrNewMachineId;
1534 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
1535 if (FAILED(rc)) throw rc;
1536 stack.llMachinesRegistered.push_back(bstrNewMachineId);
1537
1538 // Add floppies and CD-ROMs to the appropriate controllers.
1539 std::list<VirtualSystemDescriptionEntry*> vsdeFloppy = vsdescThis->findByType(VirtualSystemDescriptionType_Floppy);
1540 if (vsdeFloppy.size() > 1)
1541 throw setError(VBOX_E_FILE_ERROR,
1542 tr("Too many floppy controllers in OVF; import facility only supports one"));
1543 std::list<VirtualSystemDescriptionEntry*> vsdeCDROM = vsdescThis->findByType(VirtualSystemDescriptionType_CDROM);
1544 if ( (vsdeFloppy.size() > 0)
1545 || (vsdeCDROM.size() > 0)
1546 )
1547 {
1548 // If there's an error here we need to close the session, so
1549 // we need another try/catch block.
1550
1551 try
1552 {
1553 /* In order to attach things we need to open a session
1554 * for the new machine */
1555 rc = mVirtualBox->OpenSession(stack.pSession, bstrNewMachineId);
1556 if (FAILED(rc)) throw rc;
1557 stack.fSessionOpen = true;
1558
1559 ComPtr<IMachine> sMachine;
1560 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
1561 if (FAILED(rc)) throw rc;
1562
1563 // floppy first
1564 if (vsdeFloppy.size() == 1)
1565 {
1566 ComPtr<IStorageController> pController;
1567 rc = sMachine->AddStorageController(Bstr("Floppy Controller"), StorageBus_Floppy, pController.asOutParam());
1568 if (FAILED(rc)) throw rc;
1569
1570 Bstr bstrName;
1571 rc = pController->COMGETTER(Name)(bstrName.asOutParam());
1572 if (FAILED(rc)) throw rc;
1573
1574 // this is for rollback later
1575 MyHardDiskAttachment mhda;
1576 mhda.bstrUuid = bstrNewMachineId;
1577 mhda.pMachine = pNewMachine;
1578 mhda.controllerType = bstrName;
1579 mhda.lChannel = 0;
1580 mhda.lDevice = 0;
1581
1582 Log(("Attaching floppy\n"));
1583
1584 rc = sMachine->AttachDevice(mhda.controllerType,
1585 mhda.lChannel,
1586 mhda.lDevice,
1587 DeviceType_Floppy,
1588 NULL);
1589 if (FAILED(rc)) throw rc;
1590
1591 stack.llHardDiskAttachments.push_back(mhda);
1592 }
1593
1594 // CD-ROMs next
1595 for (std::list<VirtualSystemDescriptionEntry*>::const_iterator jt = vsdeCDROM.begin();
1596 jt != vsdeCDROM.end();
1597 ++jt)
1598 {
1599 // for now always attach to secondary master on IDE controller;
1600 // there seems to be no useful information in OVF where else to
1601 // attach jt (@todo test with latest versions of OVF software)
1602
1603 // find the IDE controller
1604 const ovf::HardDiskController *pController = NULL;
1605 for (ovf::ControllersMap::const_iterator kt = vsysThis.mapControllers.begin();
1606 kt != vsysThis.mapControllers.end();
1607 ++kt)
1608 {
1609 if (kt->second.system == ovf::HardDiskController::IDE)
1610 {
1611 pController = &kt->second;
1612 }
1613 }
1614
1615 if (!pController)
1616 throw setError(VBOX_E_FILE_ERROR,
1617 tr("OVF wants a CD-ROM drive but cannot find IDE controller, which is required in this version of VirtualBox"));
1618
1619 // this is for rollback later
1620 MyHardDiskAttachment mhda;
1621 mhda.bstrUuid = bstrNewMachineId;
1622 mhda.pMachine = pNewMachine;
1623
1624 convertDiskAttachmentValues(*pController,
1625 2, // interpreted as secondary master
1626 mhda.controllerType, // Bstr
1627 mhda.lChannel,
1628 mhda.lDevice);
1629
1630 Log(("Attaching CD-ROM to channel %d on device %d\n", mhda.lChannel, mhda.lDevice));
1631
1632 rc = sMachine->AttachDevice(mhda.controllerType,
1633 mhda.lChannel,
1634 mhda.lDevice,
1635 DeviceType_DVD,
1636 NULL);
1637 if (FAILED(rc)) throw rc;
1638
1639 stack.llHardDiskAttachments.push_back(mhda);
1640 } // end for (itHD = avsdeHDs.begin();
1641
1642 rc = sMachine->SaveSettings();
1643 if (FAILED(rc)) throw rc;
1644
1645 // only now that we're done with all disks, close the session
1646 rc = stack.pSession->Close();
1647 if (FAILED(rc)) throw rc;
1648 stack.fSessionOpen = false;
1649 }
1650 catch(HRESULT /* aRC */)
1651 {
1652 if (stack.fSessionOpen)
1653 stack.pSession->Close();
1654
1655 throw;
1656 }
1657 }
1658
1659 /* Create the hard disks & connect them to the appropriate controllers. */
1660 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskImage);
1661 if (avsdeHDs.size() > 0)
1662 {
1663 // If there's an error here we need to close the session, so
1664 // we need another try/catch block.
1665 ComPtr<IMedium> srcHdVBox;
1666 bool fSourceHdNeedsClosing = false;
1667
1668 try
1669 {
1670 /* In order to attach hard disks we need to open a session
1671 * for the new machine */
1672 rc = mVirtualBox->OpenSession(stack.pSession, bstrNewMachineId);
1673 if (FAILED(rc)) throw rc;
1674 stack.fSessionOpen = true;
1675
1676 /* The disk image has to be on the same place as the OVF file. So
1677 * strip the filename out of the full file path. */
1678 Utf8Str strSrcDir(stack.locInfo.strPath);
1679 strSrcDir.stripFilename();
1680
1681 /* Iterate over all given disk images */
1682 list<VirtualSystemDescriptionEntry*>::const_iterator itHD;
1683 for (itHD = avsdeHDs.begin();
1684 itHD != avsdeHDs.end();
1685 ++itHD)
1686 {
1687 VirtualSystemDescriptionEntry *vsdeHD = *itHD;
1688
1689 /* Check if the destination file exists already or the
1690 * destination path is empty. */
1691 if ( vsdeHD->strVbox.isEmpty()
1692 || RTPathExists(vsdeHD->strVbox.c_str())
1693 )
1694 /* This isn't allowed */
1695 throw setError(VBOX_E_FILE_ERROR,
1696 tr("Destination file '%s' exists"),
1697 vsdeHD->strVbox.c_str());
1698
1699 /* Find the disk from the OVF's disk list */
1700 ovf::DiskImagesMap::const_iterator itDiskImage = stack.mapDisks.find(vsdeHD->strRef);
1701 // vsdeHD->strRef contains the disk identifier (e.g. "vmdisk1"), which should exist
1702 // in the virtual system's disks map under that ID and also in the global images map
1703 ovf::VirtualDisksMap::const_iterator itVirtualDisk = vsysThis.mapVirtualDisks.find(vsdeHD->strRef);
1704
1705 if ( itDiskImage == stack.mapDisks.end()
1706 || itVirtualDisk == vsysThis.mapVirtualDisks.end()
1707 )
1708 throw setError(E_FAIL,
1709 tr("Internal inconsistency looking up disk images."));
1710
1711 const ovf::DiskImage &di = itDiskImage->second;
1712 const ovf::VirtualDisk &vd = itVirtualDisk->second;
1713
1714 /* Make sure all target directories exists */
1715 rc = VirtualBox::ensureFilePathExists(vsdeHD->strVbox.c_str());
1716 if (FAILED(rc))
1717 throw rc;
1718
1719 // subprogress object for hard disk
1720 ComPtr<IProgress> pProgress2;
1721
1722 ComPtr<IMedium> dstHdVBox;
1723 /* If strHref is empty we have to create a new file */
1724 if (di.strHref.isEmpty())
1725 {
1726 /* Which format to use? */
1727 Bstr srcFormat = L"VDI";
1728 if ( di.strFormat.compare("http://www.vmware.com/specifications/vmdk.html#sparse", Utf8Str::CaseInsensitive)
1729 || di.strFormat.compare("http://www.vmware.com/specifications/vmdk.html#compressed", Utf8Str::CaseInsensitive))
1730 srcFormat = L"VMDK";
1731 /* Create an empty hard disk */
1732 rc = mVirtualBox->CreateHardDisk(srcFormat, Bstr(vsdeHD->strVbox), dstHdVBox.asOutParam());
1733 if (FAILED(rc)) throw rc;
1734
1735 /* Create a dynamic growing disk image with the given capacity */
1736 rc = dstHdVBox->CreateBaseStorage(di.iCapacity / _1M, MediumVariant_Standard, pProgress2.asOutParam());
1737 if (FAILED(rc)) throw rc;
1738
1739 /* Advance to the next operation */
1740 stack.pProgress->SetNextOperation(BstrFmt(tr("Creating virtual disk image '%s'"), vsdeHD->strVbox.c_str()),
1741 vsdeHD->ulSizeMB); // operation's weight, as set up with the IProgress originally
1742 }
1743 else
1744 {
1745 /* Construct the source file path */
1746 Utf8StrFmt strSrcFilePath("%s%c%s", strSrcDir.c_str(), RTPATH_DELIMITER, di.strHref.c_str());
1747 /* Check if the source file exists */
1748 if (!RTPathExists(strSrcFilePath.c_str()))
1749 /* This isn't allowed */
1750 throw setError(VBOX_E_FILE_ERROR,
1751 tr("Source virtual disk image file '%s' doesn't exist"),
1752 strSrcFilePath.c_str());
1753
1754 // Clone the disk image (this is necessary cause the id has
1755 // to be recreated for the case the same hard disk is
1756 // attached already from a previous import)
1757
1758 // First open the existing disk image
1759 rc = mVirtualBox->OpenHardDisk(Bstr(strSrcFilePath),
1760 AccessMode_ReadOnly,
1761 false,
1762 NULL,
1763 false,
1764 NULL,
1765 srcHdVBox.asOutParam());
1766 if (FAILED(rc)) throw rc;
1767 fSourceHdNeedsClosing = true;
1768
1769 /* We need the format description of the source disk image */
1770 Bstr srcFormat;
1771 rc = srcHdVBox->COMGETTER(Format)(srcFormat.asOutParam());
1772 if (FAILED(rc)) throw rc;
1773 /* Create a new hard disk interface for the destination disk image */
1774 rc = mVirtualBox->CreateHardDisk(srcFormat, Bstr(vsdeHD->strVbox), dstHdVBox.asOutParam());
1775 if (FAILED(rc)) throw rc;
1776 /* Clone the source disk image */
1777 rc = srcHdVBox->CloneTo(dstHdVBox, MediumVariant_Standard, NULL, pProgress2.asOutParam());
1778 if (FAILED(rc)) throw rc;
1779
1780 /* Advance to the next operation */
1781 stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"), strSrcFilePath.c_str()),
1782 vsdeHD->ulSizeMB); // operation's weight, as set up with the IProgress originally);
1783 }
1784
1785 // now wait for the background disk operation to complete; this throws HRESULTs on error
1786 waitForAsyncProgress(stack.pProgress, pProgress2);
1787
1788 if (fSourceHdNeedsClosing)
1789 {
1790 rc = srcHdVBox->Close();
1791 if (FAILED(rc)) throw rc;
1792 fSourceHdNeedsClosing = false;
1793 }
1794
1795 stack.llHardDisksCreated.push_back(dstHdVBox);
1796 /* Now use the new uuid to attach the disk image to our new machine */
1797 ComPtr<IMachine> sMachine;
1798 rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
1799 if (FAILED(rc)) throw rc;
1800 Bstr hdId;
1801 rc = dstHdVBox->COMGETTER(Id)(hdId.asOutParam());
1802 if (FAILED(rc)) throw rc;
1803
1804 /* For now we assume we have one controller of every type only */
1805 ovf::HardDiskController hdc = (*vsysThis.mapControllers.find(vd.idController)).second;
1806
1807 // this is for rollback later
1808 MyHardDiskAttachment mhda;
1809 mhda.bstrUuid = bstrNewMachineId;
1810 mhda.pMachine = pNewMachine;
1811
1812 convertDiskAttachmentValues(hdc,
1813 vd.ulAddressOnParent,
1814 mhda.controllerType, // Bstr
1815 mhda.lChannel,
1816 mhda.lDevice);
1817
1818 Log(("Attaching disk %s to channel %d on device %d\n", vsdeHD->strVbox.c_str(), mhda.lChannel, mhda.lDevice));
1819
1820 rc = sMachine->AttachDevice(mhda.controllerType,
1821 mhda.lChannel,
1822 mhda.lDevice,
1823 DeviceType_HardDisk,
1824 hdId);
1825 if (FAILED(rc)) throw rc;
1826
1827 stack.llHardDiskAttachments.push_back(mhda);
1828
1829 rc = sMachine->SaveSettings();
1830 if (FAILED(rc)) throw rc;
1831 } // end for (itHD = avsdeHDs.begin();
1832
1833 // only now that we're done with all disks, close the session
1834 rc = stack.pSession->Close();
1835 if (FAILED(rc)) throw rc;
1836 stack.fSessionOpen = false;
1837 }
1838 catch(HRESULT /* aRC */)
1839 {
1840 if (fSourceHdNeedsClosing)
1841 srcHdVBox->Close();
1842
1843 if (stack.fSessionOpen)
1844 stack.pSession->Close();
1845
1846 throw;
1847 }
1848 }
1849}
1850
1851/**
1852 * Imports one OVF virtual system (described by a vbox:Machine tag represented by the given config
1853 * structure) into VirtualBox by creating an IMachine instance, which is returned.
1854 *
1855 * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
1856 * up any leftovers from this function. For this, the given ImportStack instance has received information
1857 * about what needs cleaning up (to support rollback).
1858 *
1859 * @param config
1860 * @param pNewMachine
1861 * @param stack
1862 */
1863void Appliance::importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
1864 ComPtr<IMachine> &pReturnNewMachine,
1865 ImportStack &stack)
1866{
1867 Assert(vsdescThis->m->pConfig);
1868 const settings::MachineConfigFile &config = *vsdescThis->m->pConfig;
1869
1870 // use the name that we computed in the OVF fields to avoid duplicates
1871 std::list<VirtualSystemDescriptionEntry*> vsdeName = vsdescThis->findByType(VirtualSystemDescriptionType_Name);
1872 if (vsdeName.size() < 1)
1873 throw setError(VBOX_E_FILE_ERROR,
1874 tr("Missing VM name"));
1875 const Utf8Str &strNameVBox = vsdeName.front()->strVbox;
1876
1877 ComObjPtr<Machine> pNewMachine;
1878 HRESULT rc = pNewMachine.createObject();
1879 if (FAILED(rc)) throw rc;
1880
1881 // this magic constructor fills the new machine object with the MachineConfig
1882 // instance that we created from the vbox:Machine
1883 rc = pNewMachine->init(mVirtualBox,
1884 strNameVBox, // name from just above (can be suffixed to avoid duplicates)
1885 config); // the whole machine config
1886 if (FAILED(rc)) throw rc;
1887
1888 // return the new machine as an IMachine
1889 IMachine *p;
1890 rc = pNewMachine.queryInterfaceTo(&p);
1891 if (FAILED(rc)) throw rc;
1892 pReturnNewMachine = p;
1893
1894 // and register it
1895 rc = mVirtualBox->RegisterMachine(pNewMachine);
1896 if (FAILED(rc)) throw rc;
1897
1898 // store new machine for roll-back in case of errors
1899 Bstr bstrNewMachineId;
1900 rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
1901 if (FAILED(rc)) throw rc;
1902 stack.llMachinesRegistered.push_back(bstrNewMachineId);
1903}
1904
1905/**
1906 * Worker code for importing OVF from the cloud. This is called from Appliance::taskThreadImportOrExport()
1907 * in S3 mode and therefore runs on the OVF import worker thread. This then starts a second worker
1908 * thread to import from temporary files (see Appliance::importFS()).
1909 * @param pTask
1910 * @return
1911 */
1912HRESULT Appliance::importS3(TaskOVF *pTask)
1913{
1914 LogFlowFuncEnter();
1915 LogFlowFunc(("Appliance %p\n", this));
1916
1917 AutoCaller autoCaller(this);
1918 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1919
1920 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
1921
1922 int vrc = VINF_SUCCESS;
1923 RTS3 hS3 = NIL_RTS3;
1924 char szOSTmpDir[RTPATH_MAX];
1925 RTPathTemp(szOSTmpDir, sizeof(szOSTmpDir));
1926 /* The template for the temporary directory created below */
1927 char *pszTmpDir;
1928 RTStrAPrintf(&pszTmpDir, "%s"RTPATH_SLASH_STR"vbox-ovf-XXXXXX", szOSTmpDir);
1929 list< pair<Utf8Str, ULONG> > filesList;
1930
1931 HRESULT rc = S_OK;
1932 try
1933 {
1934 /* Extract the bucket */
1935 Utf8Str tmpPath = pTask->locInfo.strPath;
1936 Utf8Str bucket;
1937 parseBucket(tmpPath, bucket);
1938
1939 /* We need a temporary directory which we can put the all disk images
1940 * in */
1941 vrc = RTDirCreateTemp(pszTmpDir);
1942 if (RT_FAILURE(vrc))
1943 throw setError(VBOX_E_FILE_ERROR,
1944 tr("Cannot create temporary directory '%s'"), pszTmpDir);
1945
1946 /* Add every disks of every virtual system to an internal list */
1947 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it;
1948 for (it = m->virtualSystemDescriptions.begin();
1949 it != m->virtualSystemDescriptions.end();
1950 ++it)
1951 {
1952 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it);
1953 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskImage);
1954 std::list<VirtualSystemDescriptionEntry*>::const_iterator itH;
1955 for (itH = avsdeHDs.begin();
1956 itH != avsdeHDs.end();
1957 ++itH)
1958 {
1959 const Utf8Str &strTargetFile = (*itH)->strOvf;
1960 if (!strTargetFile.isEmpty())
1961 {
1962 /* The temporary name of the target disk file */
1963 Utf8StrFmt strTmpDisk("%s/%s", pszTmpDir, RTPathFilename(strTargetFile.c_str()));
1964 filesList.push_back(pair<Utf8Str, ULONG>(strTmpDisk, (*itH)->ulSizeMB));
1965 }
1966 }
1967 }
1968
1969 /* Next we have to download the disk images */
1970 vrc = RTS3Create(&hS3, pTask->locInfo.strUsername.c_str(), pTask->locInfo.strPassword.c_str(), pTask->locInfo.strHostname.c_str(), "virtualbox-agent/"VBOX_VERSION_STRING);
1971 if (RT_FAILURE(vrc))
1972 throw setError(VBOX_E_IPRT_ERROR,
1973 tr("Cannot create S3 service handler"));
1974 RTS3SetProgressCallback(hS3, pTask->updateProgress, &pTask);
1975
1976 /* Download all files */
1977 for (list< pair<Utf8Str, ULONG> >::const_iterator it1 = filesList.begin(); it1 != filesList.end(); ++it1)
1978 {
1979 const pair<Utf8Str, ULONG> &s = (*it1);
1980 const Utf8Str &strSrcFile = s.first;
1981 /* Construct the source file name */
1982 char *pszFilename = RTPathFilename(strSrcFile.c_str());
1983 /* Advance to the next operation */
1984 if (!pTask->pProgress.isNull())
1985 pTask->pProgress->SetNextOperation(BstrFmt(tr("Downloading file '%s'"), pszFilename), s.second);
1986
1987 vrc = RTS3GetKey(hS3, bucket.c_str(), pszFilename, strSrcFile.c_str());
1988 if (RT_FAILURE(vrc))
1989 {
1990 if (vrc == VERR_S3_CANCELED)
1991 throw S_OK; /* todo: !!!!!!!!!!!!! */
1992 else if (vrc == VERR_S3_ACCESS_DENIED)
1993 throw setError(E_ACCESSDENIED,
1994 tr("Cannot download file '%s' from S3 storage server (Access denied). Make sure that your credentials are right. Also check that your host clock is properly synced"), pszFilename);
1995 else if (vrc == VERR_S3_NOT_FOUND)
1996 throw setError(VBOX_E_FILE_ERROR,
1997 tr("Cannot download file '%s' from S3 storage server (File not found)"), pszFilename);
1998 else
1999 throw setError(VBOX_E_IPRT_ERROR,
2000 tr("Cannot download file '%s' from S3 storage server (%Rrc)"), pszFilename, vrc);
2001 }
2002 }
2003
2004 /* Provide a OVF file (haven't to exist) so the import routine can
2005 * figure out where the disk images/manifest file are located. */
2006 Utf8StrFmt strTmpOvf("%s/%s", pszTmpDir, RTPathFilename(tmpPath.c_str()));
2007 /* Now check if there is an manifest file. This is optional. */
2008 Utf8Str strManifestFile = manifestFileName(strTmpOvf);
2009 char *pszFilename = RTPathFilename(strManifestFile.c_str());
2010 if (!pTask->pProgress.isNull())
2011 pTask->pProgress->SetNextOperation(BstrFmt(tr("Downloading file '%s'"), pszFilename), 1);
2012
2013 /* Try to download it. If the error is VERR_S3_NOT_FOUND, it isn't fatal. */
2014 vrc = RTS3GetKey(hS3, bucket.c_str(), pszFilename, strManifestFile.c_str());
2015 if (RT_SUCCESS(vrc))
2016 filesList.push_back(pair<Utf8Str, ULONG>(strManifestFile, 0));
2017 else if (RT_FAILURE(vrc))
2018 {
2019 if (vrc == VERR_S3_CANCELED)
2020 throw S_OK; /* todo: !!!!!!!!!!!!! */
2021 else if (vrc == VERR_S3_NOT_FOUND)
2022 vrc = VINF_SUCCESS; /* Not found is ok */
2023 else if (vrc == VERR_S3_ACCESS_DENIED)
2024 throw setError(E_ACCESSDENIED,
2025 tr("Cannot download file '%s' from S3 storage server (Access denied). Make sure that your credentials are right. Also check that your host clock is properly synced"), pszFilename);
2026 else
2027 throw setError(VBOX_E_IPRT_ERROR,
2028 tr("Cannot download file '%s' from S3 storage server (%Rrc)"), pszFilename, vrc);
2029 }
2030
2031 /* Close the connection early */
2032 RTS3Destroy(hS3);
2033 hS3 = NIL_RTS3;
2034
2035 if (!pTask->pProgress.isNull())
2036 pTask->pProgress->SetNextOperation(BstrFmt(tr("Importing appliance")), m->ulWeightPerOperation);
2037
2038 ComObjPtr<Progress> progress;
2039 /* Import the whole temporary OVF & the disk images */
2040 LocationInfo li;
2041 li.strPath = strTmpOvf;
2042 rc = importImpl(li, progress);
2043 if (FAILED(rc)) throw rc;
2044
2045 /* Unlock the appliance for the fs import thread */
2046 appLock.release();
2047 /* Wait until the import is done, but report the progress back to the
2048 caller */
2049 ComPtr<IProgress> progressInt(progress);
2050 waitForAsyncProgress(pTask->pProgress, progressInt); /* Any errors will be thrown */
2051
2052 /* Again lock the appliance for the next steps */
2053 appLock.acquire();
2054 }
2055 catch(HRESULT aRC)
2056 {
2057 rc = aRC;
2058 }
2059 /* Cleanup */
2060 RTS3Destroy(hS3);
2061 /* Delete all files which where temporary created */
2062 for (list< pair<Utf8Str, ULONG> >::const_iterator it1 = filesList.begin(); it1 != filesList.end(); ++it1)
2063 {
2064 const char *pszFilePath = (*it1).first.c_str();
2065 if (RTPathExists(pszFilePath))
2066 {
2067 vrc = RTFileDelete(pszFilePath);
2068 if (RT_FAILURE(vrc))
2069 rc = setError(VBOX_E_FILE_ERROR,
2070 tr("Cannot delete file '%s' (%Rrc)"), pszFilePath, vrc);
2071 }
2072 }
2073 /* Delete the temporary directory */
2074 if (RTPathExists(pszTmpDir))
2075 {
2076 vrc = RTDirRemove(pszTmpDir);
2077 if (RT_FAILURE(vrc))
2078 rc = setError(VBOX_E_FILE_ERROR,
2079 tr("Cannot delete temporary directory '%s' (%Rrc)"), pszTmpDir, vrc);
2080 }
2081 if (pszTmpDir)
2082 RTStrFree(pszTmpDir);
2083
2084 LogFlowFunc(("rc=%Rhrc\n", rc));
2085 LogFlowFuncLeave();
2086
2087 return rc;
2088}
2089
Note: See TracBrowser for help on using the repository browser.

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