VirtualBox

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

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

Main/OVF: fix IDE controller export broken by r60336

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