VirtualBox

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

Last change on this file since 29532 was 29422, checked in by vboxsync, 15 years ago

Main/OVF: fix IDE channel confusion introduced with earlier OVF two-channel IDE fix (trunk regression)

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