VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxManage/VBoxManageAppliance.cpp@ 80585

Last change on this file since 80585 was 80426, checked in by vboxsync, 5 years ago

OCI: add CloudPrivateIP and CloudParavirtualized to VSD type enum.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 81.3 KB
Line 
1/* $Id: VBoxManageAppliance.cpp 80426 2019-08-26 15:13:16Z vboxsync $ */
2/** @file
3 * VBoxManage - The appliance-related commands.
4 */
5
6/*
7 * Copyright (C) 2009-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#ifndef VBOX_ONLY_DOCS
19
20
21/*********************************************************************************************************************************
22* Header Files *
23*********************************************************************************************************************************/
24#ifndef VBOX_ONLY_DOCS
25#include <VBox/com/com.h>
26#include <VBox/com/string.h>
27#include <VBox/com/Guid.h>
28#include <VBox/com/array.h>
29#include <VBox/com/ErrorInfo.h>
30#include <VBox/com/errorprint.h>
31#include <VBox/com/VirtualBox.h>
32
33#include <list>
34#include <map>
35#endif /* !VBOX_ONLY_DOCS */
36
37#include <iprt/stream.h>
38#include <iprt/getopt.h>
39#include <iprt/ctype.h>
40#include <iprt/path.h>
41#include <iprt/file.h>
42
43#include <VBox/log.h>
44#include <VBox/param.h>
45
46#include "VBoxManage.h"
47using namespace com;
48
49
50// funcs
51///////////////////////////////////////////////////////////////////////////////
52
53typedef std::map<Utf8Str, Utf8Str> ArgsMap; // pairs of strings like "vmname" => "newvmname"
54typedef std::map<uint32_t, ArgsMap> ArgsMapsMap; // map of maps, one for each virtual system, sorted by index
55
56typedef std::map<uint32_t, bool> IgnoresMap; // pairs of numeric description entry indices
57typedef std::map<uint32_t, IgnoresMap> IgnoresMapsMap; // map of maps, one for each virtual system, sorted by index
58
59static bool findArgValue(Utf8Str &strOut,
60 ArgsMap *pmapArgs,
61 const Utf8Str &strKey)
62{
63 if (pmapArgs)
64 {
65 ArgsMap::iterator it;
66 it = pmapArgs->find(strKey);
67 if (it != pmapArgs->end())
68 {
69 strOut = it->second;
70 pmapArgs->erase(it);
71 return true;
72 }
73 }
74
75 return false;
76}
77
78static int parseImportOptions(const char *psz, com::SafeArray<ImportOptions_T> *options)
79{
80 int rc = VINF_SUCCESS;
81 while (psz && *psz && RT_SUCCESS(rc))
82 {
83 size_t len;
84 const char *pszComma = strchr(psz, ',');
85 if (pszComma)
86 len = pszComma - psz;
87 else
88 len = strlen(psz);
89 if (len > 0)
90 {
91 if (!RTStrNICmp(psz, "KeepAllMACs", len))
92 options->push_back(ImportOptions_KeepAllMACs);
93 else if (!RTStrNICmp(psz, "KeepNATMACs", len))
94 options->push_back(ImportOptions_KeepNATMACs);
95 else if (!RTStrNICmp(psz, "ImportToVDI", len))
96 options->push_back(ImportOptions_ImportToVDI);
97 else
98 rc = VERR_PARSE_ERROR;
99 }
100 if (pszComma)
101 psz += len + 1;
102 else
103 psz += len;
104 }
105
106 return rc;
107}
108
109static const RTGETOPTDEF g_aImportApplianceOptions[] =
110{
111 { "--dry-run", 'n', RTGETOPT_REQ_NOTHING },
112 { "-dry-run", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
113 { "--dryrun", 'n', RTGETOPT_REQ_NOTHING },
114 { "-dryrun", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
115 { "--detailed-progress", 'P', RTGETOPT_REQ_NOTHING },
116 { "-detailed-progress", 'P', RTGETOPT_REQ_NOTHING }, // deprecated
117 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
118 { "-vsys", 's', RTGETOPT_REQ_UINT32 }, // deprecated
119 { "--ostype", 'o', RTGETOPT_REQ_STRING },
120 { "-ostype", 'o', RTGETOPT_REQ_STRING }, // deprecated
121 { "--vmname", 'V', RTGETOPT_REQ_STRING },
122 { "-vmname", 'V', RTGETOPT_REQ_STRING }, // deprecated
123 { "--settingsfile", 'S', RTGETOPT_REQ_STRING },
124 { "--basefolder", 'p', RTGETOPT_REQ_STRING },
125 { "--group", 'g', RTGETOPT_REQ_STRING },
126 { "--memory", 'm', RTGETOPT_REQ_STRING },
127 { "-memory", 'm', RTGETOPT_REQ_STRING }, // deprecated
128 { "--cpus", 'c', RTGETOPT_REQ_STRING },
129 { "--description", 'd', RTGETOPT_REQ_STRING },
130 { "--eula", 'L', RTGETOPT_REQ_STRING },
131 { "-eula", 'L', RTGETOPT_REQ_STRING }, // deprecated
132 { "--unit", 'u', RTGETOPT_REQ_UINT32 },
133 { "-unit", 'u', RTGETOPT_REQ_UINT32 }, // deprecated
134 { "--ignore", 'x', RTGETOPT_REQ_NOTHING },
135 { "-ignore", 'x', RTGETOPT_REQ_NOTHING }, // deprecated
136 { "--scsitype", 'T', RTGETOPT_REQ_UINT32 },
137 { "-scsitype", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
138 { "--type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
139 { "-type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
140#if 0 /* Changing the controller is fully valid, but the current design on how
141 the params are evaluated here doesn't allow two parameter for one
142 unit. The target disk path is more important. I leave it for future
143 improvments. */
144 { "--controller", 'C', RTGETOPT_REQ_STRING },
145#endif
146 { "--disk", 'D', RTGETOPT_REQ_STRING },
147 { "--options", 'O', RTGETOPT_REQ_STRING },
148
149 { "--cloud", 'j', RTGETOPT_REQ_NOTHING},
150 { "--cloudprofile", 'k', RTGETOPT_REQ_STRING },
151 { "--cloudinstanceid", 'l', RTGETOPT_REQ_STRING },
152 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING }
153};
154
155enum actionType
156{
157 NOT_SET, LOCAL, CLOUD
158} actionType;
159
160RTEXITCODE handleImportAppliance(HandlerArg *arg)
161{
162 HRESULT rc = S_OK;
163 bool fCloud = false; // the default
164 actionType = NOT_SET;
165 Utf8Str strOvfFilename;
166 bool fExecute = true; // if true, then we actually do the import
167 com::SafeArray<ImportOptions_T> options;
168 uint32_t ulCurVsys = (uint32_t)-1;
169 uint32_t ulCurUnit = (uint32_t)-1;
170 // for each --vsys X command, maintain a map of command line items
171 // (we'll parse them later after interpreting the OVF, when we can
172 // actually check whether they make sense semantically)
173 ArgsMapsMap mapArgsMapsPerVsys;
174 IgnoresMapsMap mapIgnoresMapsPerVsys;
175
176 int c;
177 RTGETOPTUNION ValueUnion;
178 RTGETOPTSTATE GetState;
179 // start at 0 because main() has hacked both the argc and argv given to us
180 RTGetOptInit(&GetState, arg->argc, arg->argv, g_aImportApplianceOptions, RT_ELEMENTS(g_aImportApplianceOptions),
181 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
182 while ((c = RTGetOpt(&GetState, &ValueUnion)))
183 {
184 switch (c)
185 {
186 case 'n': // --dry-run
187 fExecute = false;
188 break;
189
190 case 'P': // --detailed-progress
191 g_fDetailedProgress = true;
192 break;
193
194 case 's': // --vsys
195 if (fCloud == false && actionType == NOT_SET)
196 actionType = LOCAL;
197
198 if (actionType != LOCAL)
199 return errorSyntax(USAGE_EXPORTAPPLIANCE,
200 "Option \"%s\" can't be used together with \"--cloud\" argument.",
201 GetState.pDef->pszLong);
202
203 ulCurVsys = ValueUnion.u32;
204 ulCurUnit = (uint32_t)-1;
205 break;
206
207 case 'o': // --ostype
208 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
209 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
210 mapArgsMapsPerVsys[ulCurVsys]["ostype"] = ValueUnion.psz;
211 break;
212
213 case 'V': // --vmname
214 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
215 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
216 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
217 break;
218
219 case 'S': // --settingsfile
220 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
221 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
222 mapArgsMapsPerVsys[ulCurVsys]["settingsfile"] = ValueUnion.psz;
223 break;
224
225 case 'p': // --basefolder
226 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
227 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
228 mapArgsMapsPerVsys[ulCurVsys]["basefolder"] = ValueUnion.psz;
229 break;
230
231 case 'g': // --group
232 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
233 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
234 mapArgsMapsPerVsys[ulCurVsys]["group"] = ValueUnion.psz;
235 break;
236
237 case 'd': // --description
238 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
239 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
240 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
241 break;
242
243 case 'L': // --eula
244 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
245 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
246 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
247 break;
248
249 case 'm': // --memory
250 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
251 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
252 mapArgsMapsPerVsys[ulCurVsys]["memory"] = ValueUnion.psz;
253 break;
254
255 case 'c': // --cpus
256 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
257 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
258 mapArgsMapsPerVsys[ulCurVsys]["cpus"] = ValueUnion.psz;
259 break;
260
261 case 'u': // --unit
262 ulCurUnit = ValueUnion.u32;
263 break;
264
265 case 'x': // --ignore
266 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
267 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
268 if (ulCurUnit == (uint32_t)-1)
269 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
270 mapIgnoresMapsPerVsys[ulCurVsys][ulCurUnit] = true;
271 break;
272
273 case 'T': // --scsitype
274 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
275 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
276 if (ulCurUnit == (uint32_t)-1)
277 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
278 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("scsitype%u", ulCurUnit)] = ValueUnion.psz;
279 break;
280
281 case 'C': // --controller
282 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
283 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
284 if (ulCurUnit == (uint32_t)-1)
285 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
286 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("controller%u", ulCurUnit)] = ValueUnion.psz;
287 break;
288
289 case 'D': // --disk
290 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
291 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
292 if (ulCurUnit == (uint32_t)-1)
293 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
294 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("disk%u", ulCurUnit)] = ValueUnion.psz;
295 break;
296
297 case 'O': // --options
298 if (RT_FAILURE(parseImportOptions(ValueUnion.psz, &options)))
299 return errorArgument("Invalid import options '%s'\n", ValueUnion.psz);
300 break;
301
302 /*--cloud and --vsys are orthogonal, only one must be presented*/
303 case 'j': // --cloud
304 if (fCloud == false && actionType == NOT_SET)
305 {
306 fCloud = true;
307 actionType = CLOUD;
308 }
309
310 if (actionType != CLOUD)
311 return errorSyntax(USAGE_IMPORTAPPLIANCE,
312 "Option \"%s\" can't be used together with \"--vsys\" argument.",
313 GetState.pDef->pszLong);
314
315 ulCurVsys = 0;
316 break;
317
318 /* Cloud export settings */
319 case 'k': // --cloudprofile
320 if (actionType != CLOUD)
321 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
322 GetState.pDef->pszLong);
323 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
324 break;
325
326 case 'l': // --cloudinstanceid
327 if (actionType != CLOUD)
328 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
329 GetState.pDef->pszLong);
330 mapArgsMapsPerVsys[ulCurVsys]["cloudinstanceid"] = ValueUnion.psz;
331 break;
332
333 case 'B': // --cloudbucket
334 if (actionType != CLOUD)
335 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
336 GetState.pDef->pszLong);
337 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
338 break;
339
340 case VINF_GETOPT_NOT_OPTION:
341 if (strOvfFilename.isEmpty())
342 strOvfFilename = ValueUnion.psz;
343 else
344 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid parameter '%s'", ValueUnion.psz);
345 break;
346
347 default:
348 if (c > 0)
349 {
350 if (RT_C_IS_PRINT(c))
351 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option -%c", c);
352 else
353 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option case %i", c);
354 }
355 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
356 return errorSyntax(USAGE_IMPORTAPPLIANCE, "unknown option: %s\n", ValueUnion.psz);
357 else if (ValueUnion.pDef)
358 return errorSyntax(USAGE_IMPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
359 else
360 return errorSyntax(USAGE_IMPORTAPPLIANCE, "error: %Rrs", c);
361 }
362 }
363
364 if (actionType == LOCAL && strOvfFilename.isEmpty())
365 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Not enough arguments for \"import\" command.");
366
367 do
368 {
369 ComPtr<IAppliance> pAppliance;
370 CHECK_ERROR_BREAK(arg->virtualBox, CreateAppliance(pAppliance.asOutParam()));
371
372 //in the case of Cloud, append the instance id here because later it's harder to do
373 if (actionType == CLOUD)
374 {
375 try
376 {
377 /* Check presence of cloudprofile and cloudinstanceid in the map.
378 * If there isn't the exception is triggered. It's standard std:map logic.*/
379 ArgsMap a = mapArgsMapsPerVsys[ulCurVsys];
380 a.at("cloudprofile");
381 a.at("cloudinstanceid");
382 } catch (...)
383 {
384 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Not enough arguments for import from the Cloud.");
385 }
386
387 strOvfFilename.append(mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"]);
388 strOvfFilename.append("/");
389 strOvfFilename.append(mapArgsMapsPerVsys[ulCurVsys]["cloudinstanceid"]);
390 }
391
392 char *pszAbsFilePath;
393 if (strOvfFilename.startsWith("S3://", RTCString::CaseInsensitive) ||
394 strOvfFilename.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
395 strOvfFilename.startsWith("webdav://", RTCString::CaseInsensitive) ||
396 strOvfFilename.startsWith("OCI://", RTCString::CaseInsensitive))
397 pszAbsFilePath = RTStrDup(strOvfFilename.c_str());
398 else
399 pszAbsFilePath = RTPathAbsDup(strOvfFilename.c_str());
400
401 ComPtr<IProgress> progressRead;
402 CHECK_ERROR_BREAK(pAppliance, Read(Bstr(pszAbsFilePath).raw(),
403 progressRead.asOutParam()));
404 RTStrFree(pszAbsFilePath);
405
406 rc = showProgress(progressRead);
407 CHECK_PROGRESS_ERROR_RET(progressRead, ("Appliance read failed"), RTEXITCODE_FAILURE);
408
409 Bstr path; /* fetch the path, there is stuff like username/password removed if any */
410 CHECK_ERROR_BREAK(pAppliance, COMGETTER(Path)(path.asOutParam()));
411
412 size_t cVirtualSystemDescriptions = 0;
413 com::SafeIfaceArray<IVirtualSystemDescription> aVirtualSystemDescriptions;
414
415 if (actionType == LOCAL)
416 {
417 // call interpret(); this can yield both warnings and errors, so we need
418 // to tinker with the error info a bit
419 RTStrmPrintf(g_pStdErr, "Interpreting %ls...\n", path.raw());
420 rc = pAppliance->Interpret();
421 com::ErrorInfo info0(pAppliance, COM_IIDOF(IAppliance));
422
423 com::SafeArray<BSTR> aWarnings;
424 if (SUCCEEDED(pAppliance->GetWarnings(ComSafeArrayAsOutParam(aWarnings))))
425 {
426 size_t cWarnings = aWarnings.size();
427 for (unsigned i = 0; i < cWarnings; ++i)
428 {
429 Bstr bstrWarning(aWarnings[i]);
430 RTMsgWarning("%ls.", bstrWarning.raw());
431 }
432 }
433
434 if (FAILED(rc)) // during interpret, after printing warnings
435 {
436 com::GluePrintErrorInfo(info0);
437 com::GluePrintErrorContext("Interpret", __FILE__, __LINE__);
438 break;
439 }
440
441 RTStrmPrintf(g_pStdErr, "OK.\n");
442
443 // fetch all disks
444 com::SafeArray<BSTR> retDisks;
445 CHECK_ERROR_BREAK(pAppliance,
446 COMGETTER(Disks)(ComSafeArrayAsOutParam(retDisks)));
447 if (retDisks.size() > 0)
448 {
449 RTPrintf("Disks:\n");
450 for (unsigned i = 0; i < retDisks.size(); i++)
451 RTPrintf(" %ls\n", retDisks[i]);
452 RTPrintf("\n");
453 }
454
455 // fetch virtual system descriptions
456 CHECK_ERROR_BREAK(pAppliance,
457 COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions)));
458
459 cVirtualSystemDescriptions = aVirtualSystemDescriptions.size();
460
461 // match command line arguments with virtual system descriptions;
462 // this is only to sort out invalid indices at this time
463 ArgsMapsMap::const_iterator it;
464 for (it = mapArgsMapsPerVsys.begin();
465 it != mapArgsMapsPerVsys.end();
466 ++it)
467 {
468 uint32_t ulVsys = it->first;
469 if (ulVsys >= cVirtualSystemDescriptions)
470 return errorSyntax(USAGE_IMPORTAPPLIANCE,
471 "Invalid index %RI32 with -vsys option; the OVF contains only %zu virtual system(s).",
472 ulVsys, cVirtualSystemDescriptions);
473 }
474 }
475 else if (actionType == CLOUD)
476 {
477 /* In the Cloud case the call of interpret() isn't needed because there isn't any OVF XML file.
478 * All info is got from the Cloud and VSD is filled inside IAppliance::read(). */
479 // fetch virtual system descriptions
480 CHECK_ERROR_BREAK(pAppliance,
481 COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions)));
482
483 cVirtualSystemDescriptions = aVirtualSystemDescriptions.size();
484 }
485
486 uint32_t cLicensesInTheWay = 0;
487
488 // dump virtual system descriptions and match command-line arguments
489 if (cVirtualSystemDescriptions > 0)
490 {
491 for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
492 {
493 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
494 com::SafeArray<BSTR> aRefs;
495 com::SafeArray<BSTR> aOvfValues;
496 com::SafeArray<BSTR> aVBoxValues;
497 com::SafeArray<BSTR> aExtraConfigValues;
498 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
499 GetDescription(ComSafeArrayAsOutParam(retTypes),
500 ComSafeArrayAsOutParam(aRefs),
501 ComSafeArrayAsOutParam(aOvfValues),
502 ComSafeArrayAsOutParam(aVBoxValues),
503 ComSafeArrayAsOutParam(aExtraConfigValues)));
504
505 RTPrintf("Virtual system %u:\n", i);
506
507 // look up the corresponding command line options, if any
508 ArgsMap *pmapArgs = NULL;
509 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
510 if (itm != mapArgsMapsPerVsys.end())
511 pmapArgs = &itm->second;
512
513 // this collects the final values for setFinalValues()
514 com::SafeArray<BOOL> aEnabled(retTypes.size());
515 com::SafeArray<BSTR> aFinalValues(retTypes.size());
516
517 for (unsigned a = 0; a < retTypes.size(); ++a)
518 {
519 VirtualSystemDescriptionType_T t = retTypes[a];
520
521 Utf8Str strOverride;
522
523 Bstr bstrFinalValue = aVBoxValues[a];
524
525 bool fIgnoreThis = mapIgnoresMapsPerVsys[i][a];
526
527 aEnabled[a] = true;
528
529 switch (t)
530 {
531 case VirtualSystemDescriptionType_OS:
532 if (findArgValue(strOverride, pmapArgs, "ostype"))
533 {
534 bstrFinalValue = strOverride;
535 RTPrintf("%2u: OS type specified with --ostype: \"%ls\"\n",
536 a, bstrFinalValue.raw());
537 }
538 else
539 RTPrintf("%2u: Suggested OS type: \"%ls\""
540 "\n (change with \"--vsys %u --ostype <type>\"; use \"list ostypes\" to list all possible values)\n",
541 a, bstrFinalValue.raw(), i);
542 break;
543
544 case VirtualSystemDescriptionType_Name:
545 if (findArgValue(strOverride, pmapArgs, "vmname"))
546 {
547 bstrFinalValue = strOverride;
548 RTPrintf("%2u: VM name specified with --vmname: \"%ls\"\n",
549 a, bstrFinalValue.raw());
550 }
551 else
552 RTPrintf("%2u: Suggested VM name \"%ls\""
553 "\n (change with \"--vsys %u --vmname <name>\")\n",
554 a, bstrFinalValue.raw(), i);
555 break;
556
557 case VirtualSystemDescriptionType_Product:
558 RTPrintf("%2u: Product (ignored): %ls\n",
559 a, aVBoxValues[a]);
560 break;
561
562 case VirtualSystemDescriptionType_ProductUrl:
563 RTPrintf("%2u: ProductUrl (ignored): %ls\n",
564 a, aVBoxValues[a]);
565 break;
566
567 case VirtualSystemDescriptionType_Vendor:
568 RTPrintf("%2u: Vendor (ignored): %ls\n",
569 a, aVBoxValues[a]);
570 break;
571
572 case VirtualSystemDescriptionType_VendorUrl:
573 RTPrintf("%2u: VendorUrl (ignored): %ls\n",
574 a, aVBoxValues[a]);
575 break;
576
577 case VirtualSystemDescriptionType_Version:
578 RTPrintf("%2u: Version (ignored): %ls\n",
579 a, aVBoxValues[a]);
580 break;
581
582 case VirtualSystemDescriptionType_Description:
583 if (findArgValue(strOverride, pmapArgs, "description"))
584 {
585 bstrFinalValue = strOverride;
586 RTPrintf("%2u: Description specified with --description: \"%ls\"\n",
587 a, bstrFinalValue.raw());
588 }
589 else
590 RTPrintf("%2u: Description \"%ls\""
591 "\n (change with \"--vsys %u --description <desc>\")\n",
592 a, bstrFinalValue.raw(), i);
593 break;
594
595 case VirtualSystemDescriptionType_License:
596 ++cLicensesInTheWay;
597 if (findArgValue(strOverride, pmapArgs, "eula"))
598 {
599 if (strOverride == "show")
600 {
601 RTPrintf("%2u: End-user license agreement"
602 "\n (accept with \"--vsys %u --eula accept\"):"
603 "\n\n%ls\n\n",
604 a, i, bstrFinalValue.raw());
605 }
606 else if (strOverride == "accept")
607 {
608 RTPrintf("%2u: End-user license agreement (accepted)\n",
609 a);
610 --cLicensesInTheWay;
611 }
612 else
613 return errorSyntax(USAGE_IMPORTAPPLIANCE,
614 "Argument to --eula must be either \"show\" or \"accept\".");
615 }
616 else
617 RTPrintf("%2u: End-user license agreement"
618 "\n (display with \"--vsys %u --eula show\";"
619 "\n accept with \"--vsys %u --eula accept\")\n",
620 a, i, i);
621 break;
622
623 case VirtualSystemDescriptionType_CPU:
624 if (findArgValue(strOverride, pmapArgs, "cpus"))
625 {
626 uint32_t cCPUs;
627 if ( strOverride.toInt(cCPUs) == VINF_SUCCESS
628 && cCPUs >= VMM_MIN_CPU_COUNT
629 && cCPUs <= VMM_MAX_CPU_COUNT
630 )
631 {
632 bstrFinalValue = strOverride;
633 RTPrintf("%2u: No. of CPUs specified with --cpus: %ls\n",
634 a, bstrFinalValue.raw());
635 }
636 else
637 return errorSyntax(USAGE_IMPORTAPPLIANCE,
638 "Argument to --cpus option must be a number greater than %d and less than %d.",
639 VMM_MIN_CPU_COUNT - 1, VMM_MAX_CPU_COUNT + 1);
640 }
641 else
642 RTPrintf("%2u: Number of CPUs: %ls\n (change with \"--vsys %u --cpus <n>\")\n",
643 a, bstrFinalValue.raw(), i);
644 break;
645
646 case VirtualSystemDescriptionType_Memory:
647 {
648 if (findArgValue(strOverride, pmapArgs, "memory"))
649 {
650 uint32_t ulMemMB;
651 if (VINF_SUCCESS == strOverride.toInt(ulMemMB))
652 {
653 bstrFinalValue = strOverride;
654 RTPrintf("%2u: Guest memory specified with --memory: %ls MB\n",
655 a, bstrFinalValue.raw());
656 }
657 else
658 return errorSyntax(USAGE_IMPORTAPPLIANCE,
659 "Argument to --memory option must be a non-negative number.");
660 }
661 else
662 RTPrintf("%2u: Guest memory: %ls MB\n (change with \"--vsys %u --memory <MB>\")\n",
663 a, bstrFinalValue.raw(), i);
664 break;
665 }
666
667 case VirtualSystemDescriptionType_HardDiskControllerIDE:
668 if (fIgnoreThis)
669 {
670 RTPrintf("%2u: IDE controller, type %ls -- disabled\n",
671 a,
672 aVBoxValues[a]);
673 aEnabled[a] = false;
674 }
675 else
676 RTPrintf("%2u: IDE controller, type %ls"
677 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
678 a,
679 aVBoxValues[a],
680 i, a);
681 break;
682
683 case VirtualSystemDescriptionType_HardDiskControllerSATA:
684 if (fIgnoreThis)
685 {
686 RTPrintf("%2u: SATA controller, type %ls -- disabled\n",
687 a,
688 aVBoxValues[a]);
689 aEnabled[a] = false;
690 }
691 else
692 RTPrintf("%2u: SATA controller, type %ls"
693 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
694 a,
695 aVBoxValues[a],
696 i, a);
697 break;
698
699 case VirtualSystemDescriptionType_HardDiskControllerSAS:
700 if (fIgnoreThis)
701 {
702 RTPrintf("%2u: SAS controller, type %ls -- disabled\n",
703 a,
704 aVBoxValues[a]);
705 aEnabled[a] = false;
706 }
707 else
708 RTPrintf("%2u: SAS controller, type %ls"
709 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
710 a,
711 aVBoxValues[a],
712 i, a);
713 break;
714
715 case VirtualSystemDescriptionType_HardDiskControllerSCSI:
716 if (fIgnoreThis)
717 {
718 RTPrintf("%2u: SCSI controller, type %ls -- disabled\n",
719 a,
720 aVBoxValues[a]);
721 aEnabled[a] = false;
722 }
723 else
724 {
725 Utf8StrFmt strTypeArg("scsitype%u", a);
726 if (findArgValue(strOverride, pmapArgs, strTypeArg))
727 {
728 bstrFinalValue = strOverride;
729 RTPrintf("%2u: SCSI controller, type set with --unit %u --scsitype: \"%ls\"\n",
730 a,
731 a,
732 bstrFinalValue.raw());
733 }
734 else
735 RTPrintf("%2u: SCSI controller, type %ls"
736 "\n (change with \"--vsys %u --unit %u --scsitype {BusLogic|LsiLogic}\";"
737 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
738 a,
739 aVBoxValues[a],
740 i, a, i, a);
741 }
742 break;
743
744 case VirtualSystemDescriptionType_HardDiskImage:
745 if (fIgnoreThis)
746 {
747 RTPrintf("%2u: Hard disk image: source image=%ls -- disabled\n",
748 a,
749 aOvfValues[a]);
750 aEnabled[a] = false;
751 }
752 else
753 {
754 Utf8StrFmt strTypeArg("disk%u", a);
755 RTCList<ImportOptions_T> optionsList = options.toList();
756
757 bstrFinalValue = aVBoxValues[a];
758
759 if (findArgValue(strOverride, pmapArgs, strTypeArg))
760 {
761 if (!optionsList.contains(ImportOptions_ImportToVDI))
762 {
763 RTUUID uuid;
764 /* Check if this is a uuid. If so, don't touch. */
765 int vrc = RTUuidFromStr(&uuid, strOverride.c_str());
766 if (vrc != VINF_SUCCESS)
767 {
768 /* Make the path absolute. */
769 if (!RTPathStartsWithRoot(strOverride.c_str()))
770 {
771 char pszPwd[RTPATH_MAX];
772 vrc = RTPathGetCurrent(pszPwd, RTPATH_MAX);
773 if (RT_SUCCESS(vrc))
774 strOverride = Utf8Str(pszPwd).append(RTPATH_SLASH).append(strOverride);
775 }
776 }
777 bstrFinalValue = strOverride;
778 }
779 else
780 {
781 //print some error about incompatible command-line arguments
782 return errorSyntax(USAGE_IMPORTAPPLIANCE,
783 "Option --ImportToVDI shall not be used together with "
784 "manually set target path.");
785
786 }
787
788 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
789 a,
790 aOvfValues[a],
791 bstrFinalValue.raw(),
792 aExtraConfigValues[a]);
793 }
794#if 0 /* Changing the controller is fully valid, but the current design on how
795 the params are evaluated here doesn't allow two parameter for one
796 unit. The target disk path is more important I leave it for future
797 improvments. */
798 Utf8StrFmt strTypeArg("controller%u", a);
799 if (findArgValue(strOverride, pmapArgs, strTypeArg))
800 {
801 // strOverride now has the controller index as a number, but we
802 // need a "controller=X" format string
803 strOverride = Utf8StrFmt("controller=%s", strOverride.c_str());
804 Bstr bstrExtraConfigValue = strOverride;
805 bstrExtraConfigValue.detachTo(&aExtraConfigValues[a]);
806 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
807 a,
808 aOvfValues[a],
809 aVBoxValues[a],
810 aExtraConfigValues[a]);
811 }
812#endif
813 else
814 {
815 strOverride = aVBoxValues[a];
816
817 /*
818 * Current solution isn't optimal.
819 * Better way is to provide API call for function
820 * Appliance::i_findMediumFormatFromDiskImage()
821 * and creating one new function which returns
822 * struct ovf::DiskImage for currently processed disk.
823 */
824
825 /*
826 * if user wants to convert all imported disks to VDI format
827 * we need to replace files extensions to "vdi"
828 * except CD/DVD disks
829 */
830 if (optionsList.contains(ImportOptions_ImportToVDI))
831 {
832 ComPtr<IVirtualBox> pVirtualBox = arg->virtualBox;
833 ComPtr<ISystemProperties> systemProperties;
834 com::SafeIfaceArray<IMediumFormat> mediumFormats;
835 Bstr bstrFormatName;
836
837 CHECK_ERROR(pVirtualBox,
838 COMGETTER(SystemProperties)(systemProperties.asOutParam()));
839
840 CHECK_ERROR(systemProperties,
841 COMGETTER(MediumFormats)(ComSafeArrayAsOutParam(mediumFormats)));
842
843 /* go through all supported media formats and store files extensions only for RAW */
844 com::SafeArray<BSTR> extensions;
845
846 for (unsigned j = 0; j < mediumFormats.size(); ++j)
847 {
848 com::SafeArray<DeviceType_T> deviceType;
849 ComPtr<IMediumFormat> mediumFormat = mediumFormats[j];
850 CHECK_ERROR(mediumFormat, COMGETTER(Name)(bstrFormatName.asOutParam()));
851 Utf8Str strFormatName = Utf8Str(bstrFormatName);
852
853 if (strFormatName.compare("RAW", Utf8Str::CaseInsensitive) == 0)
854 {
855 /* getting files extensions for "RAW" format */
856 CHECK_ERROR(mediumFormat,
857 DescribeFileExtensions(ComSafeArrayAsOutParam(extensions),
858 ComSafeArrayAsOutParam(deviceType)));
859 break;
860 }
861 }
862
863 /* go through files extensions for RAW format and compare them with
864 * extension of current file
865 */
866 bool fReplace = true;
867
868 const char *pszExtension = RTPathSuffix(strOverride.c_str());
869 if (pszExtension)
870 pszExtension++;
871
872 for (unsigned j = 0; j < extensions.size(); ++j)
873 {
874 Bstr bstrExt(extensions[j]);
875 Utf8Str strExtension(bstrExt);
876 if(strExtension.compare(pszExtension, Utf8Str::CaseInsensitive) == 0)
877 {
878 fReplace = false;
879 break;
880 }
881 }
882
883 if (fReplace)
884 {
885 strOverride = strOverride.stripSuffix();
886 strOverride = strOverride.append(".").append("vdi");
887 }
888 }
889
890 bstrFinalValue = strOverride;
891
892 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls"
893 "\n (change target path with \"--vsys %u --unit %u --disk path\";"
894 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
895 a,
896 aOvfValues[a],
897 bstrFinalValue.raw(),
898 aExtraConfigValues[a],
899 i, a, i, a);
900 }
901 }
902 break;
903
904 case VirtualSystemDescriptionType_CDROM:
905 if (fIgnoreThis)
906 {
907 RTPrintf("%2u: CD-ROM -- disabled\n",
908 a);
909 aEnabled[a] = false;
910 }
911 else
912 RTPrintf("%2u: CD-ROM"
913 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
914 a, i, a);
915 break;
916
917 case VirtualSystemDescriptionType_Floppy:
918 if (fIgnoreThis)
919 {
920 RTPrintf("%2u: Floppy -- disabled\n",
921 a);
922 aEnabled[a] = false;
923 }
924 else
925 RTPrintf("%2u: Floppy"
926 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
927 a, i, a);
928 break;
929
930 case VirtualSystemDescriptionType_NetworkAdapter:
931 RTPrintf("%2u: Network adapter: orig %ls, config %ls, extra %ls\n", /// @todo implement once we have a plan for the back-end
932 a,
933 aOvfValues[a],
934 aVBoxValues[a],
935 aExtraConfigValues[a]);
936 break;
937
938 case VirtualSystemDescriptionType_USBController:
939 if (fIgnoreThis)
940 {
941 RTPrintf("%2u: USB controller -- disabled\n",
942 a);
943 aEnabled[a] = false;
944 }
945 else
946 RTPrintf("%2u: USB controller"
947 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
948 a, i, a);
949 break;
950
951 case VirtualSystemDescriptionType_SoundCard:
952 if (fIgnoreThis)
953 {
954 RTPrintf("%2u: Sound card \"%ls\" -- disabled\n",
955 a,
956 aOvfValues[a]);
957 aEnabled[a] = false;
958 }
959 else
960 RTPrintf("%2u: Sound card (appliance expects \"%ls\", can change on import)"
961 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
962 a,
963 aOvfValues[a],
964 i,
965 a);
966 break;
967
968 case VirtualSystemDescriptionType_SettingsFile:
969 if (findArgValue(strOverride, pmapArgs, "settingsfile"))
970 {
971 bstrFinalValue = strOverride;
972 RTPrintf("%2u: VM settings file name specified with --settingsfile: \"%ls\"\n",
973 a, bstrFinalValue.raw());
974 }
975 else
976 RTPrintf("%2u: Suggested VM settings file name \"%ls\""
977 "\n (change with \"--vsys %u --settingsfile <filename>\")\n",
978 a, bstrFinalValue.raw(), i);
979 break;
980
981 case VirtualSystemDescriptionType_BaseFolder:
982 if (findArgValue(strOverride, pmapArgs, "basefolder"))
983 {
984 bstrFinalValue = strOverride;
985 RTPrintf("%2u: VM base folder specified with --basefolder: \"%ls\"\n",
986 a, bstrFinalValue.raw());
987 }
988 else
989 RTPrintf("%2u: Suggested VM base folder \"%ls\""
990 "\n (change with \"--vsys %u --basefolder <path>\")\n",
991 a, bstrFinalValue.raw(), i);
992 break;
993
994 case VirtualSystemDescriptionType_PrimaryGroup:
995 if (findArgValue(strOverride, pmapArgs, "group"))
996 {
997 bstrFinalValue = strOverride;
998 RTPrintf("%2u: VM group specified with --group: \"%ls\"\n",
999 a, bstrFinalValue.raw());
1000 }
1001 else
1002 RTPrintf("%2u: Suggested VM group \"%ls\""
1003 "\n (change with \"--vsys %u --group <group>\")\n",
1004 a, bstrFinalValue.raw(), i);
1005 break;
1006
1007 case VirtualSystemDescriptionType_CloudInstanceShape:
1008 RTPrintf("%2u: Suggested cloud shape \"%ls\"\n",
1009 a, bstrFinalValue.raw());
1010 break;
1011
1012 case VirtualSystemDescriptionType_CloudBucket:
1013 if (findArgValue(strOverride, pmapArgs, "cloudbucket"))
1014 {
1015 bstrFinalValue = strOverride;
1016 RTPrintf("%2u: Cloud bucket id specified with --cloudbucket: \"%ls\"\n",
1017 a, bstrFinalValue.raw());
1018 }
1019 else
1020 RTPrintf("%2u: Suggested cloud bucket id \"%ls\""
1021 "\n (change with \"--cloud %u --cloudbucket <id>\")\n",
1022 a, bstrFinalValue.raw(), i);
1023 break;
1024
1025 case VirtualSystemDescriptionType_CloudProfileName:
1026 if (findArgValue(strOverride, pmapArgs, "cloudprofile"))
1027 {
1028 bstrFinalValue = strOverride;
1029 RTPrintf("%2u: Cloud profile name specified with --cloudprofile: \"%ls\"\n",
1030 a, bstrFinalValue.raw());
1031 }
1032 else
1033 RTPrintf("%2u: Suggested cloud profile name \"%ls\""
1034 "\n (change with \"--cloud %u --cloudprofile <id>\")\n",
1035 a, bstrFinalValue.raw(), i);
1036 break;
1037
1038 case VirtualSystemDescriptionType_CloudInstanceId:
1039 if (findArgValue(strOverride, pmapArgs, "cloudinstanceid"))
1040 {
1041 bstrFinalValue = strOverride;
1042 RTPrintf("%2u: Cloud instance id specified with --cloudinstanceid: \"%ls\"\n",
1043 a, bstrFinalValue.raw());
1044 }
1045 else
1046 RTPrintf("%2u: Suggested cloud instance id \"%ls\""
1047 "\n (change with \"--cloud %u --cloudinstanceid <id>\")\n",
1048 a, bstrFinalValue.raw(), i);
1049 break;
1050
1051 case VirtualSystemDescriptionType_CloudImageId:
1052 RTPrintf("%2u: Suggested cloud base image id \"%ls\"\n",
1053 a, bstrFinalValue.raw());
1054 break;
1055 case VirtualSystemDescriptionType_CloudDomain:
1056 case VirtualSystemDescriptionType_CloudBootDiskSize:
1057 case VirtualSystemDescriptionType_CloudOCIVCN:
1058 case VirtualSystemDescriptionType_CloudPublicIP:
1059 case VirtualSystemDescriptionType_CloudOCISubnet:
1060 case VirtualSystemDescriptionType_CloudKeepObject:
1061 case VirtualSystemDescriptionType_CloudLaunchInstance:
1062 case VirtualSystemDescriptionType_CloudInstanceState:
1063 case VirtualSystemDescriptionType_CloudImageState:
1064 case VirtualSystemDescriptionType_Miscellaneous:
1065 case VirtualSystemDescriptionType_CloudInstanceDisplayName:
1066 case VirtualSystemDescriptionType_CloudImageDisplayName:
1067 case VirtualSystemDescriptionType_CloudParavirtualized:
1068 case VirtualSystemDescriptionType_CloudPrivateIP:
1069 /** @todo VirtualSystemDescriptionType_Miscellaneous? */
1070 break;
1071
1072 case VirtualSystemDescriptionType_Ignore:
1073#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
1074 case VirtualSystemDescriptionType_32BitHack:
1075#endif
1076 break;
1077 }
1078
1079 bstrFinalValue.detachTo(&aFinalValues[a]);
1080 }
1081
1082 if (fExecute)
1083 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
1084 SetFinalValues(ComSafeArrayAsInParam(aEnabled),
1085 ComSafeArrayAsInParam(aFinalValues),
1086 ComSafeArrayAsInParam(aExtraConfigValues)));
1087
1088 } // for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
1089
1090 if (cLicensesInTheWay == 1)
1091 RTMsgError("Cannot import until the license agreement listed above is accepted.");
1092 else if (cLicensesInTheWay > 1)
1093 RTMsgError("Cannot import until the %c license agreements listed above are accepted.", cLicensesInTheWay);
1094
1095 if (!cLicensesInTheWay && fExecute)
1096 {
1097 // go!
1098 ComPtr<IProgress> progress;
1099 CHECK_ERROR_BREAK(pAppliance,
1100 ImportMachines(ComSafeArrayAsInParam(options), progress.asOutParam()));
1101
1102 rc = showProgress(progress);
1103 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance import failed"), RTEXITCODE_FAILURE);
1104
1105 if (SUCCEEDED(rc))
1106 RTPrintf("Successfully imported the appliance.\n");
1107 }
1108 } // end if (aVirtualSystemDescriptions.size() > 0)
1109 } while (0);
1110
1111 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1112}
1113
1114static int parseExportOptions(const char *psz, com::SafeArray<ExportOptions_T> *options)
1115{
1116 int rc = VINF_SUCCESS;
1117 while (psz && *psz && RT_SUCCESS(rc))
1118 {
1119 size_t len;
1120 const char *pszComma = strchr(psz, ',');
1121 if (pszComma)
1122 len = pszComma - psz;
1123 else
1124 len = strlen(psz);
1125 if (len > 0)
1126 {
1127 if (!RTStrNICmp(psz, "CreateManifest", len))
1128 options->push_back(ExportOptions_CreateManifest);
1129 else if (!RTStrNICmp(psz, "manifest", len))
1130 options->push_back(ExportOptions_CreateManifest);
1131 else if (!RTStrNICmp(psz, "ExportDVDImages", len))
1132 options->push_back(ExportOptions_ExportDVDImages);
1133 else if (!RTStrNICmp(psz, "iso", len))
1134 options->push_back(ExportOptions_ExportDVDImages);
1135 else if (!RTStrNICmp(psz, "StripAllMACs", len))
1136 options->push_back(ExportOptions_StripAllMACs);
1137 else if (!RTStrNICmp(psz, "nomacs", len))
1138 options->push_back(ExportOptions_StripAllMACs);
1139 else if (!RTStrNICmp(psz, "StripAllNonNATMACs", len))
1140 options->push_back(ExportOptions_StripAllNonNATMACs);
1141 else if (!RTStrNICmp(psz, "nomacsbutnat", len))
1142 options->push_back(ExportOptions_StripAllNonNATMACs);
1143 else
1144 rc = VERR_PARSE_ERROR;
1145 }
1146 if (pszComma)
1147 psz += len + 1;
1148 else
1149 psz += len;
1150 }
1151
1152 return rc;
1153}
1154
1155static const RTGETOPTDEF g_aExportOptions[] =
1156{
1157 { "--output", 'o', RTGETOPT_REQ_STRING },
1158 { "--legacy09", 'l', RTGETOPT_REQ_NOTHING },
1159 { "--ovf09", 'l', RTGETOPT_REQ_NOTHING },
1160 { "--ovf10", '1', RTGETOPT_REQ_NOTHING },
1161 { "--ovf20", '2', RTGETOPT_REQ_NOTHING },
1162 { "--opc10", 'c', RTGETOPT_REQ_NOTHING },
1163 { "--manifest", 'm', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1164 { "--iso", 'I', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1165 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
1166 { "--vmname", 'V', RTGETOPT_REQ_STRING },
1167 { "--product", 'p', RTGETOPT_REQ_STRING },
1168 { "--producturl", 'P', RTGETOPT_REQ_STRING },
1169 { "--vendor", 'n', RTGETOPT_REQ_STRING },
1170 { "--vendorurl", 'N', RTGETOPT_REQ_STRING },
1171 { "--version", 'v', RTGETOPT_REQ_STRING },
1172 { "--description", 'd', RTGETOPT_REQ_STRING },
1173 { "--eula", 'e', RTGETOPT_REQ_STRING },
1174 { "--eulafile", 'E', RTGETOPT_REQ_STRING },
1175 { "--options", 'O', RTGETOPT_REQ_STRING },
1176 { "--cloud", 'C', RTGETOPT_REQ_UINT32 },
1177 { "--cloudshape", 'S', RTGETOPT_REQ_STRING },
1178 { "--clouddomain", 'D', RTGETOPT_REQ_STRING },
1179 { "--clouddisksize", 'R', RTGETOPT_REQ_STRING },
1180 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING },
1181 { "--cloudocivcn", 'Q', RTGETOPT_REQ_STRING },
1182 { "--cloudpublicip", 'A', RTGETOPT_REQ_STRING },
1183 { "--cloudprofile", 'F', RTGETOPT_REQ_STRING },
1184 { "--cloudocisubnet", 'T', RTGETOPT_REQ_STRING },
1185 { "--cloudkeepobject", 'K', RTGETOPT_REQ_STRING },
1186 { "--cloudlaunchinstance", 'L', RTGETOPT_REQ_STRING },
1187};
1188
1189RTEXITCODE handleExportAppliance(HandlerArg *a)
1190{
1191 HRESULT rc = S_OK;
1192
1193 Utf8Str strOutputFile;
1194 Utf8Str strOvfFormat("ovf-1.0"); // the default export version
1195 bool fManifest = false; // the default
1196 bool fCloud = false; // the default
1197 actionType = NOT_SET;
1198 bool fExportISOImages = false; // the default
1199 com::SafeArray<ExportOptions_T> options;
1200 std::list< ComPtr<IMachine> > llMachines;
1201
1202 uint32_t ulCurVsys = (uint32_t)-1;
1203 // for each --vsys X command, maintain a map of command line items
1204 ArgsMapsMap mapArgsMapsPerVsys;
1205 do
1206 {
1207 int c;
1208
1209 RTGETOPTUNION ValueUnion;
1210 RTGETOPTSTATE GetState;
1211 // start at 0 because main() has hacked both the argc and argv given to us
1212 RTGetOptInit(&GetState, a->argc, a->argv, g_aExportOptions,
1213 RT_ELEMENTS(g_aExportOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
1214
1215 Utf8Str strProductUrl;
1216 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1217 {
1218 switch (c)
1219 {
1220 case 'o': // --output
1221 if (strOutputFile.length())
1222 return errorSyntax(USAGE_EXPORTAPPLIANCE, "You can only specify --output once.");
1223 else
1224 strOutputFile = ValueUnion.psz;
1225 break;
1226
1227 case 'l': // --legacy09/--ovf09
1228 strOvfFormat = "ovf-0.9";
1229 break;
1230
1231 case '1': // --ovf10
1232 strOvfFormat = "ovf-1.0";
1233 break;
1234
1235 case '2': // --ovf20
1236 strOvfFormat = "ovf-2.0";
1237 break;
1238
1239 case 'c': // --opc
1240 strOvfFormat = "opc-1.0";
1241 break;
1242
1243 case 'I': // --iso
1244 fExportISOImages = true;
1245 break;
1246
1247 case 'm': // --manifest
1248 fManifest = true;
1249 break;
1250
1251 case 's': // --vsys
1252 if (fCloud == false && actionType == NOT_SET)
1253 actionType = LOCAL;
1254
1255 if (actionType != LOCAL)
1256 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1257 "Option \"%s\" can't be used together with \"--cloud\" argument.",
1258 GetState.pDef->pszLong);
1259
1260 ulCurVsys = ValueUnion.u32;
1261 break;
1262
1263 case 'V': // --vmname
1264 if (actionType == NOT_SET || ulCurVsys == (uint32_t)-1)
1265 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys or --cloud argument.",
1266 GetState.pDef->pszLong);
1267 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
1268 break;
1269
1270 case 'p': // --product
1271 if (actionType != LOCAL)
1272 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1273 mapArgsMapsPerVsys[ulCurVsys]["product"] = ValueUnion.psz;
1274 break;
1275
1276 case 'P': // --producturl
1277 if (actionType != LOCAL)
1278 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1279 mapArgsMapsPerVsys[ulCurVsys]["producturl"] = ValueUnion.psz;
1280 break;
1281
1282 case 'n': // --vendor
1283 if (actionType != LOCAL)
1284 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1285 mapArgsMapsPerVsys[ulCurVsys]["vendor"] = ValueUnion.psz;
1286 break;
1287
1288 case 'N': // --vendorurl
1289 if (actionType != LOCAL)
1290 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1291 mapArgsMapsPerVsys[ulCurVsys]["vendorurl"] = ValueUnion.psz;
1292 break;
1293
1294 case 'v': // --version
1295 if (actionType != LOCAL)
1296 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1297 mapArgsMapsPerVsys[ulCurVsys]["version"] = ValueUnion.psz;
1298 break;
1299
1300 case 'd': // --description
1301 if (actionType != LOCAL)
1302 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1303 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
1304 break;
1305
1306 case 'e': // --eula
1307 if (actionType != LOCAL)
1308 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1309 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
1310 break;
1311
1312 case 'E': // --eulafile
1313 if (actionType != LOCAL)
1314 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1315 mapArgsMapsPerVsys[ulCurVsys]["eulafile"] = ValueUnion.psz;
1316 break;
1317
1318 case 'O': // --options
1319 if (RT_FAILURE(parseExportOptions(ValueUnion.psz, &options)))
1320 return errorArgument("Invalid export options '%s'\n", ValueUnion.psz);
1321 break;
1322
1323 /*--cloud and --vsys are orthogonal, only one must be presented*/
1324 case 'C': // --cloud
1325 if (fCloud == false && actionType == NOT_SET)
1326 {
1327 fCloud = true;
1328 actionType = CLOUD;
1329 }
1330
1331 if (actionType != CLOUD)
1332 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1333 "Option \"%s\" can't be used together with \"--vsys\" argument.",
1334 GetState.pDef->pszLong);
1335
1336 ulCurVsys = ValueUnion.u32;
1337 break;
1338
1339 /* Cloud export settings */
1340 case 'S': // --cloudshape
1341 if (actionType != CLOUD)
1342 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1343 GetState.pDef->pszLong);
1344 mapArgsMapsPerVsys[ulCurVsys]["cloudshape"] = ValueUnion.psz;
1345 break;
1346
1347 case 'D': // --clouddomain
1348 if (actionType != CLOUD)
1349 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1350 GetState.pDef->pszLong);
1351 mapArgsMapsPerVsys[ulCurVsys]["clouddomain"] = ValueUnion.psz;
1352 break;
1353
1354 case 'R': // --clouddisksize
1355 if (actionType != CLOUD)
1356 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1357 GetState.pDef->pszLong);
1358 mapArgsMapsPerVsys[ulCurVsys]["clouddisksize"] = ValueUnion.psz;
1359 break;
1360
1361 case 'B': // --cloudbucket
1362 if (actionType != CLOUD)
1363 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1364 GetState.pDef->pszLong);
1365 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
1366 break;
1367
1368 case 'Q': // --cloudocivcn
1369 if (actionType != CLOUD)
1370 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1371 GetState.pDef->pszLong);
1372 mapArgsMapsPerVsys[ulCurVsys]["cloudocivcn"] = ValueUnion.psz;
1373 break;
1374
1375 case 'A': // --cloudpublicip
1376 if (actionType != CLOUD)
1377 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1378 GetState.pDef->pszLong);
1379 mapArgsMapsPerVsys[ulCurVsys]["cloudpublicip"] = ValueUnion.psz;
1380 break;
1381
1382 case 'F': // --cloudprofile
1383 if (actionType != CLOUD)
1384 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1385 GetState.pDef->pszLong);
1386 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
1387 break;
1388
1389 case 'T': // --cloudocisubnet
1390 if (actionType != CLOUD)
1391 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1392 GetState.pDef->pszLong);
1393 mapArgsMapsPerVsys[ulCurVsys]["cloudocisubnet"] = ValueUnion.psz;
1394 break;
1395
1396 case 'K': // --cloudkeepobject
1397 if (actionType != CLOUD)
1398 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1399 GetState.pDef->pszLong);
1400 mapArgsMapsPerVsys[ulCurVsys]["cloudkeepobject"] = ValueUnion.psz;
1401 break;
1402
1403 case 'L': // --cloudlaunchinstance
1404 if (actionType != CLOUD)
1405 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1406 GetState.pDef->pszLong);
1407 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchinstance"] = ValueUnion.psz;
1408 break;
1409
1410 case VINF_GETOPT_NOT_OPTION:
1411 {
1412 Utf8Str strMachine(ValueUnion.psz);
1413 // must be machine: try UUID or name
1414 ComPtr<IMachine> machine;
1415 CHECK_ERROR_BREAK(a->virtualBox, FindMachine(Bstr(strMachine).raw(),
1416 machine.asOutParam()));
1417 if (machine)
1418 llMachines.push_back(machine);
1419 break;
1420 }
1421
1422 default:
1423 if (c > 0)
1424 {
1425 if (RT_C_IS_GRAPH(c))
1426 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: -%c", c);
1427 else
1428 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: %i", c);
1429 }
1430 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
1431 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unknown option: %s", ValueUnion.psz);
1432 else if (ValueUnion.pDef)
1433 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
1434 else
1435 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%Rrs", c);
1436 }
1437
1438 if (FAILED(rc))
1439 break;
1440 }
1441
1442 if (FAILED(rc))
1443 break;
1444
1445 if (llMachines.empty())
1446 return errorSyntax(USAGE_EXPORTAPPLIANCE, "At least one machine must be specified with the export command.");
1447 if (!strOutputFile.length())
1448 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Missing --output argument with export command.");
1449
1450 // match command line arguments with the machines count
1451 // this is only to sort out invalid indices at this time
1452 ArgsMapsMap::const_iterator it;
1453 for (it = mapArgsMapsPerVsys.begin();
1454 it != mapArgsMapsPerVsys.end();
1455 ++it)
1456 {
1457 uint32_t ulVsys = it->first;
1458 if (ulVsys >= llMachines.size())
1459 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1460 "Invalid index %RI32 with -vsys option; you specified only %zu virtual system(s).",
1461 ulVsys, llMachines.size());
1462 }
1463
1464 ComPtr<IAppliance> pAppliance;
1465 CHECK_ERROR_BREAK(a->virtualBox, CreateAppliance(pAppliance.asOutParam()));
1466
1467 char *pszAbsFilePath = 0;
1468 if (strOutputFile.startsWith("S3://", RTCString::CaseInsensitive) ||
1469 strOutputFile.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
1470 strOutputFile.startsWith("webdav://", RTCString::CaseInsensitive) ||
1471 strOutputFile.startsWith("OCI://", RTCString::CaseInsensitive))
1472 pszAbsFilePath = RTStrDup(strOutputFile.c_str());
1473 else
1474 pszAbsFilePath = RTPathAbsDup(strOutputFile.c_str());
1475
1476 std::list< ComPtr<IMachine> >::iterator itM;
1477 uint32_t i=0;
1478 for (itM = llMachines.begin();
1479 itM != llMachines.end();
1480 ++itM, ++i)
1481 {
1482 ComPtr<IMachine> pMachine = *itM;
1483 ComPtr<IVirtualSystemDescription> pVSD;
1484 CHECK_ERROR_BREAK(pMachine, ExportTo(pAppliance, Bstr(pszAbsFilePath).raw(), pVSD.asOutParam()));
1485
1486 // Add additional info to the virtual system description if the user wants so
1487 ArgsMap *pmapArgs = NULL;
1488 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
1489 if (itm != mapArgsMapsPerVsys.end())
1490 pmapArgs = &itm->second;
1491 if (pmapArgs)
1492 {
1493 ArgsMap::iterator itD;
1494 for (itD = pmapArgs->begin();
1495 itD != pmapArgs->end();
1496 ++itD)
1497 {
1498 if (itD->first == "vmname")
1499 {
1500 //remove default value if user has specified new name (default value is set in the ExportTo())
1501 pVSD->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
1502 pVSD->AddDescription(VirtualSystemDescriptionType_Name,
1503 Bstr(itD->second).raw(),
1504 Bstr(itD->second).raw());
1505 }
1506 else if (itD->first == "product")
1507 pVSD->AddDescription(VirtualSystemDescriptionType_Product,
1508 Bstr(itD->second).raw(),
1509 Bstr(itD->second).raw());
1510 else if (itD->first == "producturl")
1511 pVSD->AddDescription(VirtualSystemDescriptionType_ProductUrl,
1512 Bstr(itD->second).raw(),
1513 Bstr(itD->second).raw());
1514 else if (itD->first == "vendor")
1515 pVSD->AddDescription(VirtualSystemDescriptionType_Vendor,
1516 Bstr(itD->second).raw(),
1517 Bstr(itD->second).raw());
1518 else if (itD->first == "vendorurl")
1519 pVSD->AddDescription(VirtualSystemDescriptionType_VendorUrl,
1520 Bstr(itD->second).raw(),
1521 Bstr(itD->second).raw());
1522 else if (itD->first == "version")
1523 pVSD->AddDescription(VirtualSystemDescriptionType_Version,
1524 Bstr(itD->second).raw(),
1525 Bstr(itD->second).raw());
1526 else if (itD->first == "description")
1527 pVSD->AddDescription(VirtualSystemDescriptionType_Description,
1528 Bstr(itD->second).raw(),
1529 Bstr(itD->second).raw());
1530 else if (itD->first == "eula")
1531 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1532 Bstr(itD->second).raw(),
1533 Bstr(itD->second).raw());
1534 else if (itD->first == "eulafile")
1535 {
1536 Utf8Str strContent;
1537 void *pvFile;
1538 size_t cbFile;
1539 int irc = RTFileReadAll(itD->second.c_str(), &pvFile, &cbFile);
1540 if (RT_SUCCESS(irc))
1541 {
1542 Bstr bstrContent((char*)pvFile, cbFile);
1543 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1544 bstrContent.raw(),
1545 bstrContent.raw());
1546 RTFileReadAllFree(pvFile, cbFile);
1547 }
1548 else
1549 {
1550 RTMsgError("Cannot read license file \"%s\" which should be included in the virtual system %u.",
1551 itD->second.c_str(), i);
1552 return RTEXITCODE_FAILURE;
1553 }
1554 }
1555 /* add cloud export settings */
1556 else if (itD->first == "cloudshape")
1557 pVSD->AddDescription(VirtualSystemDescriptionType_CloudInstanceShape,
1558 Bstr(itD->second).raw(),
1559 Bstr(itD->second).raw());
1560 else if (itD->first == "clouddomain")
1561 pVSD->AddDescription(VirtualSystemDescriptionType_CloudDomain,
1562 Bstr(itD->second).raw(),
1563 Bstr(itD->second).raw());
1564 else if (itD->first == "clouddisksize")
1565 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBootDiskSize,
1566 Bstr(itD->second).raw(),
1567 Bstr(itD->second).raw());
1568 else if (itD->first == "cloudbucket")
1569 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBucket,
1570 Bstr(itD->second).raw(),
1571 Bstr(itD->second).raw());
1572 else if (itD->first == "cloudocivcn")
1573 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCIVCN,
1574 Bstr(itD->second).raw(),
1575 Bstr(itD->second).raw());
1576 else if (itD->first == "cloudpublicip")
1577 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPublicIP,
1578 Bstr(itD->second).raw(),
1579 Bstr(itD->second).raw());
1580 else if (itD->first == "cloudprofile")
1581 pVSD->AddDescription(VirtualSystemDescriptionType_CloudProfileName,
1582 Bstr(itD->second).raw(),
1583 Bstr(itD->second).raw());
1584 else if (itD->first == "cloudocisubnet")
1585 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCISubnet,
1586 Bstr(itD->second).raw(),
1587 Bstr(itD->second).raw());
1588 else if (itD->first == "cloudkeepobject")
1589 pVSD->AddDescription(VirtualSystemDescriptionType_CloudKeepObject,
1590 Bstr(itD->second).raw(),
1591 Bstr(itD->second).raw());
1592 else if (itD->first == "cloudlaunchinstance")
1593 pVSD->AddDescription(VirtualSystemDescriptionType_CloudLaunchInstance,
1594 Bstr(itD->second).raw(),
1595 Bstr(itD->second).raw());
1596 }
1597 }
1598 }
1599
1600 if (FAILED(rc))
1601 break;
1602
1603 /* Query required passwords and supply them to the appliance. */
1604 com::SafeArray<BSTR> aIdentifiers;
1605
1606 CHECK_ERROR_BREAK(pAppliance, GetPasswordIds(ComSafeArrayAsOutParam(aIdentifiers)));
1607
1608 if (aIdentifiers.size() > 0)
1609 {
1610 com::SafeArray<BSTR> aPasswords(aIdentifiers.size());
1611 RTPrintf("Enter the passwords for the following identifiers to export the apppliance:\n");
1612 for (unsigned idxId = 0; idxId < aIdentifiers.size(); idxId++)
1613 {
1614 com::Utf8Str strPassword;
1615 Bstr bstrPassword;
1616 Bstr bstrId = aIdentifiers[idxId];
1617
1618 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Password ID %s:", Utf8Str(bstrId).c_str());
1619 if (rcExit == RTEXITCODE_FAILURE)
1620 {
1621 RTStrFree(pszAbsFilePath);
1622 return rcExit;
1623 }
1624
1625 bstrPassword = strPassword;
1626 bstrPassword.detachTo(&aPasswords[idxId]);
1627 }
1628
1629 CHECK_ERROR_BREAK(pAppliance, AddPasswords(ComSafeArrayAsInParam(aIdentifiers),
1630 ComSafeArrayAsInParam(aPasswords)));
1631 }
1632
1633 if (fManifest)
1634 options.push_back(ExportOptions_CreateManifest);
1635
1636 if (fExportISOImages)
1637 options.push_back(ExportOptions_ExportDVDImages);
1638
1639 ComPtr<IProgress> progress;
1640 CHECK_ERROR_BREAK(pAppliance, Write(Bstr(strOvfFormat).raw(),
1641 ComSafeArrayAsInParam(options),
1642 Bstr(pszAbsFilePath).raw(),
1643 progress.asOutParam()));
1644 RTStrFree(pszAbsFilePath);
1645
1646 rc = showProgress(progress);
1647 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance write failed"), RTEXITCODE_FAILURE);
1648
1649 if (SUCCEEDED(rc))
1650 RTPrintf("Successfully exported %d machine(s).\n", llMachines.size());
1651
1652 } while (0);
1653
1654 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1655}
1656
1657#endif /* !VBOX_ONLY_DOCS */
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