VirtualBox

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

Last change on this file since 29080 was 28957, checked in by vboxsync, 15 years ago

Main/OVF: on export, fix vmdk format string spec and sort elements from rasd: namespace alphabetically (public bug #6612)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 93.8 KB
Line 
1/* $Id: ApplianceImplImport.cpp 28957 2010-05-02 18:42:58Z 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/interfaces/specifications/vmdk.html#streamOptimized", Utf8Str::CaseInsensitive)
494 || di.strFormat.compare("http://www.vmware.com/specifications/vmdk.html#compressed", Utf8Str::CaseInsensitive)
495 || di.strFormat.compare("http://www.vmware.com/interfaces/specifications/vmdk.html#compressed", Utf8Str::CaseInsensitive)
496 )
497 {
498 /* If the href is empty use the VM name as filename */
499 Utf8Str strFilename = di.strHref;
500 if (!strFilename.length())
501 strFilename = Utf8StrFmt("%s.vmdk", nameVBox.c_str());
502 /* Construct a unique target path */
503 Utf8StrFmt strPath("%s%c%s",
504 strDefaultHardDiskFolder.raw(),
505 RTPATH_DELIMITER,
506 strFilename.c_str());
507 searchUniqueDiskImageFilePath(strPath);
508
509 /* find the description for the hard disk controller
510 * that has the same ID as hd.idController */
511 const VirtualSystemDescriptionEntry *pController;
512 if (!(pController = pNewDesc->findControllerFromID(hd.idController)))
513 throw setError(E_FAIL,
514 tr("Cannot find hard disk controller with OVF instance ID %RI32 to which disk \"%s\" should be attached"),
515 hd.idController,
516 di.strHref.c_str());
517
518 /* controller to attach to, and the bus within that controller */
519 Utf8StrFmt strExtraConfig("controller=%RI16;channel=%RI16",
520 pController->ulIndex,
521 hd.ulAddressOnParent);
522 pNewDesc->addEntry(VirtualSystemDescriptionType_HardDiskImage,
523 hd.strDiskId,
524 di.strHref,
525 strPath,
526 di.ulSuggestedSizeMB,
527 strExtraConfig);
528 }
529 else
530 throw setError(VBOX_E_FILE_ERROR,
531 tr("Unsupported format for virtual disk image in OVF: \"%s\"", di.strFormat.c_str()));
532 }
533 }
534
535 m->virtualSystemDescriptions.push_back(pNewDesc);
536 }
537 }
538 catch (HRESULT aRC)
539 {
540 /* On error we clear the list & return */
541 m->virtualSystemDescriptions.clear();
542 rc = aRC;
543 }
544
545 // reset the appliance state
546 alock.acquire();
547 m->state = Data::ApplianceIdle;
548
549 return rc;
550}
551
552/**
553 * Public method implementation.
554 * @param aProgress
555 * @return
556 */
557STDMETHODIMP Appliance::ImportMachines(IProgress **aProgress)
558{
559 CheckComArgOutPointerValid(aProgress);
560
561 AutoCaller autoCaller(this);
562 if (FAILED(autoCaller.rc())) return autoCaller.rc();
563
564 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
565
566 // do not allow entering this method if the appliance is busy reading or writing
567 if (!isApplianceIdle())
568 return E_ACCESSDENIED;
569
570 if (!m->pReader)
571 return setError(E_FAIL,
572 tr("Cannot import machines without reading it first (call read() before importMachines())"));
573
574 ComObjPtr<Progress> progress;
575 HRESULT rc = S_OK;
576 try
577 {
578 rc = importImpl(m->locInfo, progress);
579 }
580 catch (HRESULT aRC)
581 {
582 rc = aRC;
583 }
584
585 if (SUCCEEDED(rc))
586 /* Return progress to the caller */
587 progress.queryInterfaceTo(aProgress);
588
589 return rc;
590}
591
592////////////////////////////////////////////////////////////////////////////////
593//
594// Appliance private methods
595//
596////////////////////////////////////////////////////////////////////////////////
597
598/**
599 * Implementation for reading an OVF. This starts a new thread which will call
600 * Appliance::taskThreadImportOrExport() which will then call readFS() or readS3().
601 *
602 * This is in a separate private method because it is used from two locations:
603 *
604 * 1) from the public Appliance::Read().
605 * 2) from Appliance::readS3(), which got called from a previous instance of Appliance::taskThreadImportOrExport().
606 *
607 * @param aLocInfo
608 * @param aProgress
609 * @return
610 */
611HRESULT Appliance::readImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
612{
613 BstrFmt bstrDesc = BstrFmt(tr("Reading appliance '%s'"),
614 aLocInfo.strPath.c_str());
615 HRESULT rc;
616 /* Create the progress object */
617 aProgress.createObject();
618 if (aLocInfo.storageType == VFSType_File)
619 /* 1 operation only */
620 rc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this),
621 bstrDesc,
622 TRUE /* aCancelable */);
623 else
624 /* 4/5 is downloading, 1/5 is reading */
625 rc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this),
626 bstrDesc,
627 TRUE /* aCancelable */,
628 2, // ULONG cOperations,
629 5, // ULONG ulTotalOperationsWeight,
630 BstrFmt(tr("Download appliance '%s'"),
631 aLocInfo.strPath.c_str()), // CBSTR bstrFirstOperationDescription,
632 4); // ULONG ulFirstOperationWeight,
633 if (FAILED(rc)) throw rc;
634
635 /* Initialize our worker task */
636 std::auto_ptr<TaskOVF> task(new TaskOVF(this, TaskOVF::Read, aLocInfo, aProgress));
637
638 rc = task->startThread();
639 if (FAILED(rc)) throw rc;
640
641 /* Don't destruct on success */
642 task.release();
643
644 return rc;
645}
646
647/**
648 * Actual worker code for reading an OVF from disk. This is called from Appliance::taskThreadImportOrExport()
649 * and therefore runs on the OVF read worker thread. This runs in two contexts:
650 *
651 * 1) in a first worker thread; in that case, Appliance::Read() called Appliance::readImpl();
652 *
653 * 2) in a second worker thread; in that case, Appliance::Read() called Appliance::readImpl(), which
654 * called Appliance::readS3(), which called Appliance::readImpl(), which then called this.
655 *
656 * @param pTask
657 * @return
658 */
659HRESULT Appliance::readFS(const LocationInfo &locInfo)
660{
661 LogFlowFuncEnter();
662 LogFlowFunc(("Appliance %p\n", this));
663
664 AutoCaller autoCaller(this);
665 if (FAILED(autoCaller.rc())) return autoCaller.rc();
666
667 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
668
669 HRESULT rc = S_OK;
670
671 try
672 {
673 /* Read & parse the XML structure of the OVF file */
674 m->pReader = new ovf::OVFReader(locInfo.strPath);
675 /* Create the SHA1 sum of the OVF file for later validation */
676 char *pszDigest;
677 int vrc = RTSha1Digest(locInfo.strPath.c_str(), &pszDigest);
678 if (RT_FAILURE(vrc))
679 throw setError(VBOX_E_FILE_ERROR,
680 tr("Couldn't calculate SHA1 digest for file '%s' (%Rrc)"),
681 RTPathFilename(locInfo.strPath.c_str()), vrc);
682 m->strOVFSHA1Digest = pszDigest;
683 RTStrFree(pszDigest);
684 }
685 catch(xml::Error &x)
686 {
687 rc = setError(VBOX_E_FILE_ERROR,
688 x.what());
689 }
690 catch(HRESULT aRC)
691 {
692 rc = aRC;
693 }
694
695 LogFlowFunc(("rc=%Rhrc\n", rc));
696 LogFlowFuncLeave();
697
698 return rc;
699}
700
701/**
702 * Worker code for reading OVF from the cloud. This is called from Appliance::taskThreadImportOrExport()
703 * in S3 mode and therefore runs on the OVF read worker thread. This then starts a second worker
704 * thread to create temporary files (see Appliance::readFS()).
705 *
706 * @param pTask
707 * @return
708 */
709HRESULT Appliance::readS3(TaskOVF *pTask)
710{
711 LogFlowFuncEnter();
712 LogFlowFunc(("Appliance %p\n", this));
713
714 AutoCaller autoCaller(this);
715 if (FAILED(autoCaller.rc())) return autoCaller.rc();
716
717 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
718
719 HRESULT rc = S_OK;
720 int vrc = VINF_SUCCESS;
721 RTS3 hS3 = NIL_RTS3;
722 char szOSTmpDir[RTPATH_MAX];
723 RTPathTemp(szOSTmpDir, sizeof(szOSTmpDir));
724 /* The template for the temporary directory created below */
725 char *pszTmpDir;
726 RTStrAPrintf(&pszTmpDir, "%s"RTPATH_SLASH_STR"vbox-ovf-XXXXXX", szOSTmpDir);
727 list< pair<Utf8Str, ULONG> > filesList;
728 Utf8Str strTmpOvf;
729
730 try
731 {
732 /* Extract the bucket */
733 Utf8Str tmpPath = pTask->locInfo.strPath;
734 Utf8Str bucket;
735 parseBucket(tmpPath, bucket);
736
737 /* We need a temporary directory which we can put the OVF file & all
738 * disk images in */
739 vrc = RTDirCreateTemp(pszTmpDir);
740 if (RT_FAILURE(vrc))
741 throw setError(VBOX_E_FILE_ERROR,
742 tr("Cannot create temporary directory '%s'"), pszTmpDir);
743
744 /* The temporary name of the target OVF file */
745 strTmpOvf = Utf8StrFmt("%s/%s", pszTmpDir, RTPathFilename(tmpPath.c_str()));
746
747 /* Next we have to download the OVF */
748 vrc = RTS3Create(&hS3, pTask->locInfo.strUsername.c_str(), pTask->locInfo.strPassword.c_str(), pTask->locInfo.strHostname.c_str(), "virtualbox-agent/"VBOX_VERSION_STRING);
749 if (RT_FAILURE(vrc))
750 throw setError(VBOX_E_IPRT_ERROR,
751 tr("Cannot create S3 service handler"));
752 RTS3SetProgressCallback(hS3, pTask->updateProgress, &pTask);
753
754 /* Get it */
755 char *pszFilename = RTPathFilename(strTmpOvf.c_str());
756 vrc = RTS3GetKey(hS3, bucket.c_str(), pszFilename, strTmpOvf.c_str());
757 if (RT_FAILURE(vrc))
758 {
759 if (vrc == VERR_S3_CANCELED)
760 throw S_OK; /* todo: !!!!!!!!!!!!! */
761 else if (vrc == VERR_S3_ACCESS_DENIED)
762 throw setError(E_ACCESSDENIED,
763 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);
764 else if (vrc == VERR_S3_NOT_FOUND)
765 throw setError(VBOX_E_FILE_ERROR,
766 tr("Cannot download file '%s' from S3 storage server (File not found)"), pszFilename);
767 else
768 throw setError(VBOX_E_IPRT_ERROR,
769 tr("Cannot download file '%s' from S3 storage server (%Rrc)"), pszFilename, vrc);
770 }
771
772 /* Close the connection early */
773 RTS3Destroy(hS3);
774 hS3 = NIL_RTS3;
775
776 if (!pTask->pProgress.isNull())
777 pTask->pProgress->SetNextOperation(Bstr(tr("Reading")), 1);
778
779 /* Prepare the temporary reading of the OVF */
780 ComObjPtr<Progress> progress;
781 LocationInfo li;
782 li.strPath = strTmpOvf;
783 /* Start the reading from the fs */
784 rc = readImpl(li, progress);
785 if (FAILED(rc)) throw rc;
786
787 /* Unlock the appliance for the reading thread */
788 appLock.release();
789 /* Wait until the reading is done, but report the progress back to the
790 caller */
791 ComPtr<IProgress> progressInt(progress);
792 waitForAsyncProgress(pTask->pProgress, progressInt); /* Any errors will be thrown */
793
794 /* Again lock the appliance for the next steps */
795 appLock.acquire();
796 }
797 catch(HRESULT aRC)
798 {
799 rc = aRC;
800 }
801 /* Cleanup */
802 RTS3Destroy(hS3);
803 /* Delete all files which where temporary created */
804 if (RTPathExists(strTmpOvf.c_str()))
805 {
806 vrc = RTFileDelete(strTmpOvf.c_str());
807 if (RT_FAILURE(vrc))
808 rc = setError(VBOX_E_FILE_ERROR,
809 tr("Cannot delete file '%s' (%Rrc)"), strTmpOvf.c_str(), vrc);
810 }
811 /* Delete the temporary directory */
812 if (RTPathExists(pszTmpDir))
813 {
814 vrc = RTDirRemove(pszTmpDir);
815 if (RT_FAILURE(vrc))
816 rc = setError(VBOX_E_FILE_ERROR,
817 tr("Cannot delete temporary directory '%s' (%Rrc)"), pszTmpDir, vrc);
818 }
819 if (pszTmpDir)
820 RTStrFree(pszTmpDir);
821
822 LogFlowFunc(("rc=%Rhrc\n", rc));
823 LogFlowFuncLeave();
824
825 return rc;
826}
827
828/**
829 * Helper that converts VirtualSystem attachment values into VirtualBox attachment values.
830 * Throws HRESULT values on errors!
831 *
832 * @param hdc in: the HardDiskController structure to attach to.
833 * @param ulAddressOnParent in: the AddressOnParent parameter from OVF.
834 * @param controllerType out: the name of the hard disk controller to attach to (e.g. "IDE Controller").
835 * @param lChannel out: the channel (controller port) of the controller to attach to.
836 * @param lDevice out: the device number to attach to.
837 */
838void Appliance::convertDiskAttachmentValues(const ovf::HardDiskController &hdc,
839 uint32_t ulAddressOnParent,
840 Bstr &controllerType,
841 int32_t &lChannel,
842 int32_t &lDevice)
843{
844 switch (hdc.system)
845 {
846 case ovf::HardDiskController::IDE:
847 // For the IDE bus, the channel parameter can be either 0 or 1, to specify the primary
848 // or secondary IDE controller, respectively. For the primary controller of the IDE bus,
849 // the device number can be either 0 or 1, to specify the master or the slave device,
850 // respectively. For the secondary IDE controller, the device number is always 1 because
851 // the master device is reserved for the CD-ROM drive.
852 controllerType = Bstr("IDE Controller");
853 switch (ulAddressOnParent)
854 {
855 case 0: // master
856 if (hdc.ulAddress == 1)
857 {
858 // IDE controller has address 1: then it was exported from VMware and is the secondary controller
859 lChannel = (long)1;
860 lDevice = (long)0;
861 }
862 else // interpret this as primary master
863 {
864 lChannel = (long)0;
865 lDevice = (long)0;
866 }
867 break;
868
869 case 1: // slave
870 if (hdc.ulAddress == 1)
871 {
872 // IDE controller has address 1: then it was exported from VMware and is the secondary controller
873 lChannel = (long)1;
874 lDevice = (long)1;
875 }
876 else // interpret this as primary slave
877 {
878 lChannel = (long)0;
879 lDevice = (long)1;
880 }
881 break;
882
883 // used by older VBox exports
884 case 2: // interpret this as secondary master
885 lChannel = (long)1;
886 lDevice = (long)0;
887 break;
888
889 // used by older VBox exports
890 case 3: // interpret this as secondary slave
891 lChannel = (long)1;
892 lDevice = (long)1;
893 break;
894
895 default:
896 throw setError(VBOX_E_NOT_SUPPORTED,
897 tr("Invalid channel %RI16 specified; IDE controllers support only 0, 1 or 2"), ulAddressOnParent);
898 break;
899 }
900 break;
901
902 case ovf::HardDiskController::SATA:
903 controllerType = Bstr("SATA Controller");
904 lChannel = (long)ulAddressOnParent;
905 lDevice = (long)0;
906 break;
907
908 case ovf::HardDiskController::SCSI:
909 controllerType = Bstr("SCSI Controller");
910 lChannel = (long)ulAddressOnParent;
911 lDevice = (long)0;
912 break;
913
914 default: break;
915 }
916}
917
918/**
919 * Implementation for importing OVF data into VirtualBox. This starts a new thread which will call
920 * Appliance::taskThreadImportOrExport().
921 *
922 * This is in a separate private method because it is used from two locations:
923 *
924 * 1) from the public Appliance::ImportMachines().
925 * 2) from Appliance::importS3(), which got called from a previous instance of Appliance::taskThreadImportOrExport().
926 *
927 * @param aLocInfo
928 * @param aProgress
929 * @return
930 */
931HRESULT Appliance::importImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
932{
933 Bstr progressDesc = BstrFmt(tr("Importing appliance '%s'"),
934 aLocInfo.strPath.c_str());
935 HRESULT rc = S_OK;
936
937 rc = setUpProgress(aProgress,
938 progressDesc,
939 (aLocInfo.storageType == VFSType_File) ? Regular : ImportS3);
940 if (FAILED(rc)) throw rc;
941
942 /* Initialize our worker task */
943 std::auto_ptr<TaskOVF> task(new TaskOVF(this, TaskOVF::Import, aLocInfo, aProgress));
944
945 rc = task->startThread();
946 if (FAILED(rc)) throw rc;
947
948 /* Don't destruct on success */
949 task.release();
950
951 return rc;
952}
953
954/**
955 * Used by Appliance::importMachineGeneric() to store
956 * input parameters and rollback information.
957 */
958struct Appliance::ImportStack
959{
960 // input pointers
961 const LocationInfo &locInfo; // ptr to location info from Appliance::importFS()
962 Utf8Str strSourceDir; // directory where source files reside
963 const ovf::DiskImagesMap &mapDisks; // ptr to disks map in OVF
964 ComObjPtr<Progress> &pProgress; // progress object passed into Appliance::importFS()
965
966 // session (not initially created)
967 ComPtr<ISession> pSession; // session opened in Appliance::importFS() for machine manipulation
968 bool fSessionOpen; // true if the pSession is currently open and needs closing
969
970 // a list of images that we created/imported; this is initially empty
971 // and will be cleaned up on errors
972 list<MyHardDiskAttachment> llHardDiskAttachments; // disks that were attached
973 list< ComPtr<IMedium> > llHardDisksCreated; // media that were created
974 list<Bstr> llMachinesRegistered; // machines that were registered; list of string UUIDs
975
976 ImportStack(const LocationInfo &aLocInfo,
977 const ovf::DiskImagesMap &aMapDisks,
978 ComObjPtr<Progress> &aProgress)
979 : locInfo(aLocInfo),
980 mapDisks(aMapDisks),
981 pProgress(aProgress),
982 fSessionOpen(false)
983 {
984 // disk images have to be on the same place as the OVF file. So
985 // strip the filename out of the full file path
986 strSourceDir = aLocInfo.strPath;
987 strSourceDir.stripFilename();
988 }
989};
990
991/**
992 * Checks if a manifest file exists in the given location and, if so, verifies
993 * that the relevant files (the OVF XML and the disks referenced by it, as
994 * represented by the VirtualSystemDescription instances contained in this appliance)
995 * match it. Requires a previous read() and interpret().
996 *
997 * @param locInfo
998 * @param reader
999 * @return
1000 */
1001HRESULT Appliance::manifestVerify(const LocationInfo &locInfo,
1002 const ovf::OVFReader &reader)
1003{
1004 HRESULT rc = S_OK;
1005
1006 Utf8Str strMfFile = manifestFileName(locInfo.strPath);
1007 if (RTPathExists(strMfFile.c_str()))
1008 {
1009 list<Utf8Str> filesList;
1010 Utf8Str strSrcDir(locInfo.strPath);
1011 strSrcDir.stripFilename();
1012 // add every disks of every virtual system to an internal list
1013 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it;
1014 for (it = m->virtualSystemDescriptions.begin();
1015 it != m->virtualSystemDescriptions.end();
1016 ++it)
1017 {
1018 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it);
1019 std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->findByType(VirtualSystemDescriptionType_HardDiskImage);
1020 std::list<VirtualSystemDescriptionEntry*>::const_iterator itH;
1021 for (itH = avsdeHDs.begin();
1022 itH != avsdeHDs.end();
1023 ++itH)
1024 {
1025 VirtualSystemDescriptionEntry *vsdeHD = *itH;
1026 // find the disk from the OVF's disk list
1027 ovf::DiskImagesMap::const_iterator itDiskImage = reader.m_mapDisks.find(vsdeHD->strRef);
1028 const ovf::DiskImage &di = itDiskImage->second;
1029 Utf8StrFmt strSrcFilePath("%s%c%s", strSrcDir.c_str(), RTPATH_DELIMITER, di.strHref.c_str());
1030 filesList.push_back(strSrcFilePath);
1031 }
1032 }
1033
1034 // create the test list
1035 PRTMANIFESTTEST pTestList = (PRTMANIFESTTEST)RTMemAllocZ(sizeof(RTMANIFESTTEST) * (filesList.size() + 1));
1036 pTestList[0].pszTestFile = (char*)locInfo.strPath.c_str();
1037 pTestList[0].pszTestDigest = (char*)m->strOVFSHA1Digest.c_str();
1038 int vrc = VINF_SUCCESS;
1039 size_t i = 1;
1040 list<Utf8Str>::const_iterator it1;
1041 for (it1 = filesList.begin();
1042 it1 != filesList.end();
1043 ++it1, ++i)
1044 {
1045 char* pszDigest;
1046 vrc = RTSha1Digest((*it1).c_str(), &pszDigest);
1047 pTestList[i].pszTestFile = (char*)(*it1).c_str();
1048 pTestList[i].pszTestDigest = pszDigest;
1049 }
1050
1051 // this call can take a very long time
1052 size_t cIndexOnError;
1053 vrc = RTManifestVerify(strMfFile.c_str(),
1054 pTestList,
1055 filesList.size() + 1,
1056 &cIndexOnError);
1057
1058 // clean up
1059 for (size_t j = 1;
1060 j < filesList.size();
1061 ++j)
1062 RTStrFree(pTestList[j].pszTestDigest);
1063 RTMemFree(pTestList);
1064
1065 if (vrc == VERR_MANIFEST_DIGEST_MISMATCH)
1066 rc = setError(VBOX_E_FILE_ERROR,
1067 tr("The SHA1 digest of '%s' does not match the one in '%s'"),
1068 RTPathFilename(pTestList[cIndexOnError].pszTestFile),
1069 RTPathFilename(strMfFile.c_str()));
1070 else if (RT_FAILURE(vrc))
1071 rc = setError(VBOX_E_FILE_ERROR,
1072 tr("Could not verify the content of '%s' against the available files (%Rrc)"),
1073 RTPathFilename(strMfFile.c_str()),
1074 vrc);
1075 }
1076
1077 return rc;
1078}
1079
1080/**
1081 * Actual worker code for importing OVF data into VirtualBox. This is called from Appliance::taskThreadImportOrExport()
1082 * and therefore runs on the OVF import worker thread. This runs in two contexts:
1083 *
1084 * 1) in a first worker thread; in that case, Appliance::ImportMachines() called Appliance::importImpl();
1085 *
1086 * 2) in a second worker thread; in that case, Appliance::ImportMachines() called Appliance::importImpl(), which
1087 * called Appliance::importS3(), which called Appliance::importImpl(), which then called this.
1088 *
1089 * @param pTask
1090 * @return
1091 */
1092HRESULT Appliance::importFS(const LocationInfo &locInfo,
1093 ComObjPtr<Progress> &pProgress)
1094{
1095 LogFlowFuncEnter();
1096 LogFlowFunc(("Appliance %p\n", this));
1097
1098 AutoCaller autoCaller(this);
1099 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1100
1101 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
1102
1103 if (!isApplianceIdle())
1104 return E_ACCESSDENIED;
1105
1106 Assert(!pProgress.isNull());
1107
1108 // Change the appliance state so we can safely leave the lock while doing time-consuming
1109 // disk imports; also the below method calls do all kinds of locking which conflicts with
1110 // the appliance object lock
1111 m->state = Data::ApplianceImporting;
1112 appLock.release();
1113
1114 HRESULT rc = S_OK;
1115
1116 const ovf::OVFReader &reader = *m->pReader;
1117 // this is safe to access because this thread only gets started
1118 // if pReader != NULL
1119
1120 // rollback for errors:
1121 ImportStack stack(locInfo, reader.m_mapDisks, pProgress);
1122
1123 try
1124 {
1125 // if a manifest file exists, verify the content; we then need all files which are referenced by the OVF & the OVF itself
1126 rc = manifestVerify(locInfo, reader);
1127 if (FAILED(rc)) throw rc;
1128
1129 // create a session for the machine + disks we manipulate below
1130 rc = stack.pSession.createInprocObject(CLSID_Session);
1131 if (FAILED(rc)) throw rc;
1132
1133 list<ovf::VirtualSystem>::const_iterator it;
1134 list< ComObjPtr<VirtualSystemDescription> >::const_iterator it1;
1135 /* Iterate through all virtual systems of that appliance */
1136 size_t i = 0;
1137 for (it = reader.m_llVirtualSystems.begin(),
1138 it1 = m->virtualSystemDescriptions.begin();
1139 it != reader.m_llVirtualSystems.end();
1140 ++it, ++it1, ++i)
1141 {
1142 const ovf::VirtualSystem &vsysThis = *it;
1143 ComObjPtr<VirtualSystemDescription> vsdescThis = (*it1);
1144
1145 ComPtr<IMachine> pNewMachine;
1146
1147 // there are two ways in which we can create a vbox machine from OVF:
1148 // -- either this OVF was written by vbox 3.2 or later, in which case there is a <vbox:Machine> element
1149 // in the <VirtualSystem>; then the VirtualSystemDescription::Data has a settings::MachineConfigFile
1150 // with all the machine config pretty-parsed;
1151 // -- or this is an OVF from an older vbox or an external source, and then we need to translate the
1152 // VirtualSystemDescriptionEntry and do import work
1153
1154 // @todo r=dj make this selection configurable at run-time, and from the GUI as well
1155
1156 if (vsdescThis->m->pConfig)
1157 importVBoxMachine(vsdescThis, pNewMachine, stack);
1158 else
1159 importMachineGeneric(vsysThis, vsdescThis, pNewMachine, stack);
1160
1161 } // for (it = pAppliance->m->llVirtualSystems.begin() ...
1162 }
1163 catch (HRESULT rc2)
1164 {
1165 rc = rc2;
1166 }
1167
1168 if (FAILED(rc))
1169 {
1170 // with _whatever_ error we've had, do a complete roll-back of
1171 // machines and disks we've created; unfortunately this is
1172 // not so trivially done...
1173
1174 HRESULT rc2;
1175 // detach all hard disks from all machines we created
1176 list<MyHardDiskAttachment>::iterator itM;
1177 for (itM = stack.llHardDiskAttachments.begin();
1178 itM != stack.llHardDiskAttachments.end();
1179 ++itM)
1180 {
1181 const MyHardDiskAttachment &mhda = *itM;
1182 Bstr bstrUuid(mhda.bstrUuid); // make a copy, Windows can't handle const Bstr
1183 rc2 = mVirtualBox->OpenSession(stack.pSession, bstrUuid);
1184 if (SUCCEEDED(rc2))
1185 {
1186 ComPtr<IMachine> sMachine;
1187 rc2 = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
1188 if (SUCCEEDED(rc2))
1189 {
1190 rc2 = sMachine->DetachDevice(Bstr(mhda.controllerType), mhda.lChannel, mhda.lDevice);
1191 rc2 = sMachine->SaveSettings();
1192 }
1193 stack.pSession->Close();
1194 }
1195 }
1196
1197 // now clean up all hard disks we created
1198 list< ComPtr<IMedium> >::iterator itHD;
1199 for (itHD = stack.llHardDisksCreated.begin();
1200 itHD != stack.llHardDisksCreated.end();
1201 ++itHD)
1202 {
1203 ComPtr<IMedium> pDisk = *itHD;
1204 ComPtr<IProgress> pProgress2;
1205 rc2 = pDisk->DeleteStorage(pProgress2.asOutParam());
1206 rc2 = pProgress2->WaitForCompletion(-1);
1207 }
1208
1209 // finally, deregister and remove all machines
1210 list<Bstr>::iterator itID;
1211 for (itID = stack.llMachinesRegistered.begin();
1212 itID != stack.llMachinesRegistered.end();
1213 ++itID)
1214 {
1215 Bstr bstrGuid = *itID; // make a copy, Windows can't handle const Bstr
1216 ComPtr<IMachine> failedMachine;
1217 rc2 = mVirtualBox->UnregisterMachine(bstrGuid, failedMachine.asOutParam());
1218 if (SUCCEEDED(rc2))
1219 rc2 = failedMachine->DeleteSettings();
1220 }
1221 }
1222
1223 // restore the appliance state
1224 appLock.acquire();
1225 m->state = Data::ApplianceIdle;
1226
1227 LogFlowFunc(("rc=%Rhrc\n", rc));
1228 LogFlowFuncLeave();
1229
1230 return rc;
1231}
1232
1233/**
1234 * Imports one disk image. This is common code shared between
1235 * -- importMachineGeneric() for the OVF case; in that case the information comes from
1236 * the OVF virtual systems;
1237 * -- importVBoxMachine(); in that case, the information comes from the <vbox:Machine>
1238 * tag.
1239 *
1240 * Both ways of describing machines use the OVF disk references section, so in both cases
1241 * the caller needs to pass in the ovf::DiskImage structure from ovfreader.cpp.
1242 *
1243 * As a result, in both cases, if di.strHref is empty, we create a new disk as per the OVF
1244 * spec, even though this cannot really happen in the vbox:Machine case since such data
1245 * would never have been exported.
1246 *
1247 * This advances stack.pProgress by one operation with the disk's weight.
1248 *
1249 * @param di ovfreader.cpp structure describing the disk image from the OVF that is to be imported
1250 * @param ulSizeMB Size of the disk image (for progress reporting)
1251 * @param strTargetPath Where to create the target image.
1252 * @param pTargetHD out: The newly created target disk. This also gets pushed on stack.llHardDisksCreated for cleanup.
1253 * @param stack
1254 */
1255void Appliance::importOneDiskImage(const ovf::DiskImage &di,
1256 const Utf8Str &strTargetPath,
1257 ComPtr<IMedium> &pTargetHD,
1258 ImportStack &stack)
1259{
1260 ComPtr<IMedium> pSourceHD;
1261 bool fSourceHdNeedsClosing = false;
1262
1263 try
1264 {
1265 // destination file must not exist
1266 if ( strTargetPath.isEmpty()
1267 || RTPathExists(strTargetPath.c_str())
1268 )
1269 /* This isn't allowed */
1270 throw setError(VBOX_E_FILE_ERROR,
1271 tr("Destination file '%s' exists"),
1272 strTargetPath.c_str());
1273
1274 const Utf8Str &strSourceOVF = di.strHref;
1275
1276 // Make sure target directory exists
1277 HRESULT rc = VirtualBox::ensureFilePathExists(strTargetPath.c_str());
1278 if (FAILED(rc))
1279 throw rc;
1280
1281 // subprogress object for hard disk
1282 ComPtr<IProgress> pProgress2;
1283
1284 /* If strHref is empty we have to create a new file */
1285 if (strSourceOVF.isEmpty())
1286 {
1287 // which format to use?
1288 Bstr srcFormat = L"VDI";
1289 if ( di.strFormat.compare("http://www.vmware.com/specifications/vmdk.html#sparse", Utf8Str::CaseInsensitive)
1290 || di.strFormat.compare("http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized", Utf8Str::CaseInsensitive)
1291 || di.strFormat.compare("http://www.vmware.com/specifications/vmdk.html#compressed", Utf8Str::CaseInsensitive)
1292 || di.strFormat.compare("http://www.vmware.com/interfaces/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.

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