VirtualBox

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

Last change on this file since 28800 was 28800, checked in by vboxsync, 14 years ago

Automated rebranding to Oracle copyright/license strings via filemuncher

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