VirtualBox

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

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

Handle new VSD types in switch with no default

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 81.2 KB
Line 
1/* $Id: VBoxManageAppliance.cpp 79646 2019-07-09 16:29:00Z 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 /** @todo VirtualSystemDescriptionType_Miscellaneous? */
1068 break;
1069
1070 case VirtualSystemDescriptionType_Ignore:
1071#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
1072 case VirtualSystemDescriptionType_32BitHack:
1073#endif
1074 break;
1075 }
1076
1077 bstrFinalValue.detachTo(&aFinalValues[a]);
1078 }
1079
1080 if (fExecute)
1081 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
1082 SetFinalValues(ComSafeArrayAsInParam(aEnabled),
1083 ComSafeArrayAsInParam(aFinalValues),
1084 ComSafeArrayAsInParam(aExtraConfigValues)));
1085
1086 } // for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
1087
1088 if (cLicensesInTheWay == 1)
1089 RTMsgError("Cannot import until the license agreement listed above is accepted.");
1090 else if (cLicensesInTheWay > 1)
1091 RTMsgError("Cannot import until the %c license agreements listed above are accepted.", cLicensesInTheWay);
1092
1093 if (!cLicensesInTheWay && fExecute)
1094 {
1095 // go!
1096 ComPtr<IProgress> progress;
1097 CHECK_ERROR_BREAK(pAppliance,
1098 ImportMachines(ComSafeArrayAsInParam(options), progress.asOutParam()));
1099
1100 rc = showProgress(progress);
1101 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance import failed"), RTEXITCODE_FAILURE);
1102
1103 if (SUCCEEDED(rc))
1104 RTPrintf("Successfully imported the appliance.\n");
1105 }
1106 } // end if (aVirtualSystemDescriptions.size() > 0)
1107 } while (0);
1108
1109 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1110}
1111
1112static int parseExportOptions(const char *psz, com::SafeArray<ExportOptions_T> *options)
1113{
1114 int rc = VINF_SUCCESS;
1115 while (psz && *psz && RT_SUCCESS(rc))
1116 {
1117 size_t len;
1118 const char *pszComma = strchr(psz, ',');
1119 if (pszComma)
1120 len = pszComma - psz;
1121 else
1122 len = strlen(psz);
1123 if (len > 0)
1124 {
1125 if (!RTStrNICmp(psz, "CreateManifest", len))
1126 options->push_back(ExportOptions_CreateManifest);
1127 else if (!RTStrNICmp(psz, "manifest", len))
1128 options->push_back(ExportOptions_CreateManifest);
1129 else if (!RTStrNICmp(psz, "ExportDVDImages", len))
1130 options->push_back(ExportOptions_ExportDVDImages);
1131 else if (!RTStrNICmp(psz, "iso", len))
1132 options->push_back(ExportOptions_ExportDVDImages);
1133 else if (!RTStrNICmp(psz, "StripAllMACs", len))
1134 options->push_back(ExportOptions_StripAllMACs);
1135 else if (!RTStrNICmp(psz, "nomacs", len))
1136 options->push_back(ExportOptions_StripAllMACs);
1137 else if (!RTStrNICmp(psz, "StripAllNonNATMACs", len))
1138 options->push_back(ExportOptions_StripAllNonNATMACs);
1139 else if (!RTStrNICmp(psz, "nomacsbutnat", len))
1140 options->push_back(ExportOptions_StripAllNonNATMACs);
1141 else
1142 rc = VERR_PARSE_ERROR;
1143 }
1144 if (pszComma)
1145 psz += len + 1;
1146 else
1147 psz += len;
1148 }
1149
1150 return rc;
1151}
1152
1153static const RTGETOPTDEF g_aExportOptions[] =
1154{
1155 { "--output", 'o', RTGETOPT_REQ_STRING },
1156 { "--legacy09", 'l', RTGETOPT_REQ_NOTHING },
1157 { "--ovf09", 'l', RTGETOPT_REQ_NOTHING },
1158 { "--ovf10", '1', RTGETOPT_REQ_NOTHING },
1159 { "--ovf20", '2', RTGETOPT_REQ_NOTHING },
1160 { "--opc10", 'c', RTGETOPT_REQ_NOTHING },
1161 { "--manifest", 'm', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1162 { "--iso", 'I', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1163 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
1164 { "--vmname", 'V', RTGETOPT_REQ_STRING },
1165 { "--product", 'p', RTGETOPT_REQ_STRING },
1166 { "--producturl", 'P', RTGETOPT_REQ_STRING },
1167 { "--vendor", 'n', RTGETOPT_REQ_STRING },
1168 { "--vendorurl", 'N', RTGETOPT_REQ_STRING },
1169 { "--version", 'v', RTGETOPT_REQ_STRING },
1170 { "--description", 'd', RTGETOPT_REQ_STRING },
1171 { "--eula", 'e', RTGETOPT_REQ_STRING },
1172 { "--eulafile", 'E', RTGETOPT_REQ_STRING },
1173 { "--options", 'O', RTGETOPT_REQ_STRING },
1174 { "--cloud", 'C', RTGETOPT_REQ_UINT32 },
1175 { "--cloudshape", 'S', RTGETOPT_REQ_STRING },
1176 { "--clouddomain", 'D', RTGETOPT_REQ_STRING },
1177 { "--clouddisksize", 'R', RTGETOPT_REQ_STRING },
1178 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING },
1179 { "--cloudocivcn", 'Q', RTGETOPT_REQ_STRING },
1180 { "--cloudpublicip", 'A', RTGETOPT_REQ_STRING },
1181 { "--cloudprofile", 'F', RTGETOPT_REQ_STRING },
1182 { "--cloudocisubnet", 'T', RTGETOPT_REQ_STRING },
1183 { "--cloudkeepobject", 'K', RTGETOPT_REQ_STRING },
1184 { "--cloudlaunchinstance", 'L', RTGETOPT_REQ_STRING },
1185};
1186
1187RTEXITCODE handleExportAppliance(HandlerArg *a)
1188{
1189 HRESULT rc = S_OK;
1190
1191 Utf8Str strOutputFile;
1192 Utf8Str strOvfFormat("ovf-1.0"); // the default export version
1193 bool fManifest = false; // the default
1194 bool fCloud = false; // the default
1195 actionType = NOT_SET;
1196 bool fExportISOImages = false; // the default
1197 com::SafeArray<ExportOptions_T> options;
1198 std::list< ComPtr<IMachine> > llMachines;
1199
1200 uint32_t ulCurVsys = (uint32_t)-1;
1201 // for each --vsys X command, maintain a map of command line items
1202 ArgsMapsMap mapArgsMapsPerVsys;
1203 do
1204 {
1205 int c;
1206
1207 RTGETOPTUNION ValueUnion;
1208 RTGETOPTSTATE GetState;
1209 // start at 0 because main() has hacked both the argc and argv given to us
1210 RTGetOptInit(&GetState, a->argc, a->argv, g_aExportOptions,
1211 RT_ELEMENTS(g_aExportOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
1212
1213 Utf8Str strProductUrl;
1214 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1215 {
1216 switch (c)
1217 {
1218 case 'o': // --output
1219 if (strOutputFile.length())
1220 return errorSyntax(USAGE_EXPORTAPPLIANCE, "You can only specify --output once.");
1221 else
1222 strOutputFile = ValueUnion.psz;
1223 break;
1224
1225 case 'l': // --legacy09/--ovf09
1226 strOvfFormat = "ovf-0.9";
1227 break;
1228
1229 case '1': // --ovf10
1230 strOvfFormat = "ovf-1.0";
1231 break;
1232
1233 case '2': // --ovf20
1234 strOvfFormat = "ovf-2.0";
1235 break;
1236
1237 case 'c': // --opc
1238 strOvfFormat = "opc-1.0";
1239 break;
1240
1241 case 'I': // --iso
1242 fExportISOImages = true;
1243 break;
1244
1245 case 'm': // --manifest
1246 fManifest = true;
1247 break;
1248
1249 case 's': // --vsys
1250 if (fCloud == false && actionType == NOT_SET)
1251 actionType = LOCAL;
1252
1253 if (actionType != LOCAL)
1254 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1255 "Option \"%s\" can't be used together with \"--cloud\" argument.",
1256 GetState.pDef->pszLong);
1257
1258 ulCurVsys = ValueUnion.u32;
1259 break;
1260
1261 case 'V': // --vmname
1262 if (actionType == NOT_SET || ulCurVsys == (uint32_t)-1)
1263 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys or --cloud argument.",
1264 GetState.pDef->pszLong);
1265 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
1266 break;
1267
1268 case 'p': // --product
1269 if (actionType != LOCAL)
1270 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1271 mapArgsMapsPerVsys[ulCurVsys]["product"] = ValueUnion.psz;
1272 break;
1273
1274 case 'P': // --producturl
1275 if (actionType != LOCAL)
1276 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1277 mapArgsMapsPerVsys[ulCurVsys]["producturl"] = ValueUnion.psz;
1278 break;
1279
1280 case 'n': // --vendor
1281 if (actionType != LOCAL)
1282 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1283 mapArgsMapsPerVsys[ulCurVsys]["vendor"] = ValueUnion.psz;
1284 break;
1285
1286 case 'N': // --vendorurl
1287 if (actionType != LOCAL)
1288 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1289 mapArgsMapsPerVsys[ulCurVsys]["vendorurl"] = ValueUnion.psz;
1290 break;
1291
1292 case 'v': // --version
1293 if (actionType != LOCAL)
1294 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1295 mapArgsMapsPerVsys[ulCurVsys]["version"] = ValueUnion.psz;
1296 break;
1297
1298 case 'd': // --description
1299 if (actionType != LOCAL)
1300 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1301 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
1302 break;
1303
1304 case 'e': // --eula
1305 if (actionType != LOCAL)
1306 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1307 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
1308 break;
1309
1310 case 'E': // --eulafile
1311 if (actionType != LOCAL)
1312 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1313 mapArgsMapsPerVsys[ulCurVsys]["eulafile"] = ValueUnion.psz;
1314 break;
1315
1316 case 'O': // --options
1317 if (RT_FAILURE(parseExportOptions(ValueUnion.psz, &options)))
1318 return errorArgument("Invalid export options '%s'\n", ValueUnion.psz);
1319 break;
1320
1321 /*--cloud and --vsys are orthogonal, only one must be presented*/
1322 case 'C': // --cloud
1323 if (fCloud == false && actionType == NOT_SET)
1324 {
1325 fCloud = true;
1326 actionType = CLOUD;
1327 }
1328
1329 if (actionType != CLOUD)
1330 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1331 "Option \"%s\" can't be used together with \"--vsys\" argument.",
1332 GetState.pDef->pszLong);
1333
1334 ulCurVsys = ValueUnion.u32;
1335 break;
1336
1337 /* Cloud export settings */
1338 case 'S': // --cloudshape
1339 if (actionType != CLOUD)
1340 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1341 GetState.pDef->pszLong);
1342 mapArgsMapsPerVsys[ulCurVsys]["cloudshape"] = ValueUnion.psz;
1343 break;
1344
1345 case 'D': // --clouddomain
1346 if (actionType != CLOUD)
1347 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1348 GetState.pDef->pszLong);
1349 mapArgsMapsPerVsys[ulCurVsys]["clouddomain"] = ValueUnion.psz;
1350 break;
1351
1352 case 'R': // --clouddisksize
1353 if (actionType != CLOUD)
1354 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1355 GetState.pDef->pszLong);
1356 mapArgsMapsPerVsys[ulCurVsys]["clouddisksize"] = ValueUnion.psz;
1357 break;
1358
1359 case 'B': // --cloudbucket
1360 if (actionType != CLOUD)
1361 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1362 GetState.pDef->pszLong);
1363 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
1364 break;
1365
1366 case 'Q': // --cloudocivcn
1367 if (actionType != CLOUD)
1368 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1369 GetState.pDef->pszLong);
1370 mapArgsMapsPerVsys[ulCurVsys]["cloudocivcn"] = ValueUnion.psz;
1371 break;
1372
1373 case 'A': // --cloudpublicip
1374 if (actionType != CLOUD)
1375 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1376 GetState.pDef->pszLong);
1377 mapArgsMapsPerVsys[ulCurVsys]["cloudpublicip"] = ValueUnion.psz;
1378 break;
1379
1380 case 'F': // --cloudprofile
1381 if (actionType != CLOUD)
1382 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1383 GetState.pDef->pszLong);
1384 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
1385 break;
1386
1387 case 'T': // --cloudocisubnet
1388 if (actionType != CLOUD)
1389 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1390 GetState.pDef->pszLong);
1391 mapArgsMapsPerVsys[ulCurVsys]["cloudocisubnet"] = ValueUnion.psz;
1392 break;
1393
1394 case 'K': // --cloudkeepobject
1395 if (actionType != CLOUD)
1396 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1397 GetState.pDef->pszLong);
1398 mapArgsMapsPerVsys[ulCurVsys]["cloudkeepobject"] = ValueUnion.psz;
1399 break;
1400
1401 case 'L': // --cloudlaunchinstance
1402 if (actionType != CLOUD)
1403 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1404 GetState.pDef->pszLong);
1405 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchinstance"] = ValueUnion.psz;
1406 break;
1407
1408 case VINF_GETOPT_NOT_OPTION:
1409 {
1410 Utf8Str strMachine(ValueUnion.psz);
1411 // must be machine: try UUID or name
1412 ComPtr<IMachine> machine;
1413 CHECK_ERROR_BREAK(a->virtualBox, FindMachine(Bstr(strMachine).raw(),
1414 machine.asOutParam()));
1415 if (machine)
1416 llMachines.push_back(machine);
1417 break;
1418 }
1419
1420 default:
1421 if (c > 0)
1422 {
1423 if (RT_C_IS_GRAPH(c))
1424 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: -%c", c);
1425 else
1426 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: %i", c);
1427 }
1428 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
1429 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unknown option: %s", ValueUnion.psz);
1430 else if (ValueUnion.pDef)
1431 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
1432 else
1433 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%Rrs", c);
1434 }
1435
1436 if (FAILED(rc))
1437 break;
1438 }
1439
1440 if (FAILED(rc))
1441 break;
1442
1443 if (llMachines.empty())
1444 return errorSyntax(USAGE_EXPORTAPPLIANCE, "At least one machine must be specified with the export command.");
1445 if (!strOutputFile.length())
1446 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Missing --output argument with export command.");
1447
1448 // match command line arguments with the machines count
1449 // this is only to sort out invalid indices at this time
1450 ArgsMapsMap::const_iterator it;
1451 for (it = mapArgsMapsPerVsys.begin();
1452 it != mapArgsMapsPerVsys.end();
1453 ++it)
1454 {
1455 uint32_t ulVsys = it->first;
1456 if (ulVsys >= llMachines.size())
1457 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1458 "Invalid index %RI32 with -vsys option; you specified only %zu virtual system(s).",
1459 ulVsys, llMachines.size());
1460 }
1461
1462 ComPtr<IAppliance> pAppliance;
1463 CHECK_ERROR_BREAK(a->virtualBox, CreateAppliance(pAppliance.asOutParam()));
1464
1465 char *pszAbsFilePath = 0;
1466 if (strOutputFile.startsWith("S3://", RTCString::CaseInsensitive) ||
1467 strOutputFile.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
1468 strOutputFile.startsWith("webdav://", RTCString::CaseInsensitive) ||
1469 strOutputFile.startsWith("OCI://", RTCString::CaseInsensitive))
1470 pszAbsFilePath = RTStrDup(strOutputFile.c_str());
1471 else
1472 pszAbsFilePath = RTPathAbsDup(strOutputFile.c_str());
1473
1474 std::list< ComPtr<IMachine> >::iterator itM;
1475 uint32_t i=0;
1476 for (itM = llMachines.begin();
1477 itM != llMachines.end();
1478 ++itM, ++i)
1479 {
1480 ComPtr<IMachine> pMachine = *itM;
1481 ComPtr<IVirtualSystemDescription> pVSD;
1482 CHECK_ERROR_BREAK(pMachine, ExportTo(pAppliance, Bstr(pszAbsFilePath).raw(), pVSD.asOutParam()));
1483
1484 // Add additional info to the virtual system description if the user wants so
1485 ArgsMap *pmapArgs = NULL;
1486 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
1487 if (itm != mapArgsMapsPerVsys.end())
1488 pmapArgs = &itm->second;
1489 if (pmapArgs)
1490 {
1491 ArgsMap::iterator itD;
1492 for (itD = pmapArgs->begin();
1493 itD != pmapArgs->end();
1494 ++itD)
1495 {
1496 if (itD->first == "vmname")
1497 {
1498 //remove default value if user has specified new name (default value is set in the ExportTo())
1499 pVSD->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
1500 pVSD->AddDescription(VirtualSystemDescriptionType_Name,
1501 Bstr(itD->second).raw(),
1502 Bstr(itD->second).raw());
1503 }
1504 else if (itD->first == "product")
1505 pVSD->AddDescription(VirtualSystemDescriptionType_Product,
1506 Bstr(itD->second).raw(),
1507 Bstr(itD->second).raw());
1508 else if (itD->first == "producturl")
1509 pVSD->AddDescription(VirtualSystemDescriptionType_ProductUrl,
1510 Bstr(itD->second).raw(),
1511 Bstr(itD->second).raw());
1512 else if (itD->first == "vendor")
1513 pVSD->AddDescription(VirtualSystemDescriptionType_Vendor,
1514 Bstr(itD->second).raw(),
1515 Bstr(itD->second).raw());
1516 else if (itD->first == "vendorurl")
1517 pVSD->AddDescription(VirtualSystemDescriptionType_VendorUrl,
1518 Bstr(itD->second).raw(),
1519 Bstr(itD->second).raw());
1520 else if (itD->first == "version")
1521 pVSD->AddDescription(VirtualSystemDescriptionType_Version,
1522 Bstr(itD->second).raw(),
1523 Bstr(itD->second).raw());
1524 else if (itD->first == "description")
1525 pVSD->AddDescription(VirtualSystemDescriptionType_Description,
1526 Bstr(itD->second).raw(),
1527 Bstr(itD->second).raw());
1528 else if (itD->first == "eula")
1529 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1530 Bstr(itD->second).raw(),
1531 Bstr(itD->second).raw());
1532 else if (itD->first == "eulafile")
1533 {
1534 Utf8Str strContent;
1535 void *pvFile;
1536 size_t cbFile;
1537 int irc = RTFileReadAll(itD->second.c_str(), &pvFile, &cbFile);
1538 if (RT_SUCCESS(irc))
1539 {
1540 Bstr bstrContent((char*)pvFile, cbFile);
1541 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1542 bstrContent.raw(),
1543 bstrContent.raw());
1544 RTFileReadAllFree(pvFile, cbFile);
1545 }
1546 else
1547 {
1548 RTMsgError("Cannot read license file \"%s\" which should be included in the virtual system %u.",
1549 itD->second.c_str(), i);
1550 return RTEXITCODE_FAILURE;
1551 }
1552 }
1553 /* add cloud export settings */
1554 else if (itD->first == "cloudshape")
1555 pVSD->AddDescription(VirtualSystemDescriptionType_CloudInstanceShape,
1556 Bstr(itD->second).raw(),
1557 Bstr(itD->second).raw());
1558 else if (itD->first == "clouddomain")
1559 pVSD->AddDescription(VirtualSystemDescriptionType_CloudDomain,
1560 Bstr(itD->second).raw(),
1561 Bstr(itD->second).raw());
1562 else if (itD->first == "clouddisksize")
1563 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBootDiskSize,
1564 Bstr(itD->second).raw(),
1565 Bstr(itD->second).raw());
1566 else if (itD->first == "cloudbucket")
1567 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBucket,
1568 Bstr(itD->second).raw(),
1569 Bstr(itD->second).raw());
1570 else if (itD->first == "cloudocivcn")
1571 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCIVCN,
1572 Bstr(itD->second).raw(),
1573 Bstr(itD->second).raw());
1574 else if (itD->first == "cloudpublicip")
1575 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPublicIP,
1576 Bstr(itD->second).raw(),
1577 Bstr(itD->second).raw());
1578 else if (itD->first == "cloudprofile")
1579 pVSD->AddDescription(VirtualSystemDescriptionType_CloudProfileName,
1580 Bstr(itD->second).raw(),
1581 Bstr(itD->second).raw());
1582 else if (itD->first == "cloudocisubnet")
1583 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCISubnet,
1584 Bstr(itD->second).raw(),
1585 Bstr(itD->second).raw());
1586 else if (itD->first == "cloudkeepobject")
1587 pVSD->AddDescription(VirtualSystemDescriptionType_CloudKeepObject,
1588 Bstr(itD->second).raw(),
1589 Bstr(itD->second).raw());
1590 else if (itD->first == "cloudlaunchinstance")
1591 pVSD->AddDescription(VirtualSystemDescriptionType_CloudLaunchInstance,
1592 Bstr(itD->second).raw(),
1593 Bstr(itD->second).raw());
1594 }
1595 }
1596 }
1597
1598 if (FAILED(rc))
1599 break;
1600
1601 /* Query required passwords and supply them to the appliance. */
1602 com::SafeArray<BSTR> aIdentifiers;
1603
1604 CHECK_ERROR_BREAK(pAppliance, GetPasswordIds(ComSafeArrayAsOutParam(aIdentifiers)));
1605
1606 if (aIdentifiers.size() > 0)
1607 {
1608 com::SafeArray<BSTR> aPasswords(aIdentifiers.size());
1609 RTPrintf("Enter the passwords for the following identifiers to export the apppliance:\n");
1610 for (unsigned idxId = 0; idxId < aIdentifiers.size(); idxId++)
1611 {
1612 com::Utf8Str strPassword;
1613 Bstr bstrPassword;
1614 Bstr bstrId = aIdentifiers[idxId];
1615
1616 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Password ID %s:", Utf8Str(bstrId).c_str());
1617 if (rcExit == RTEXITCODE_FAILURE)
1618 {
1619 RTStrFree(pszAbsFilePath);
1620 return rcExit;
1621 }
1622
1623 bstrPassword = strPassword;
1624 bstrPassword.detachTo(&aPasswords[idxId]);
1625 }
1626
1627 CHECK_ERROR_BREAK(pAppliance, AddPasswords(ComSafeArrayAsInParam(aIdentifiers),
1628 ComSafeArrayAsInParam(aPasswords)));
1629 }
1630
1631 if (fManifest)
1632 options.push_back(ExportOptions_CreateManifest);
1633
1634 if (fExportISOImages)
1635 options.push_back(ExportOptions_ExportDVDImages);
1636
1637 ComPtr<IProgress> progress;
1638 CHECK_ERROR_BREAK(pAppliance, Write(Bstr(strOvfFormat).raw(),
1639 ComSafeArrayAsInParam(options),
1640 Bstr(pszAbsFilePath).raw(),
1641 progress.asOutParam()));
1642 RTStrFree(pszAbsFilePath);
1643
1644 rc = showProgress(progress);
1645 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance write failed"), RTEXITCODE_FAILURE);
1646
1647 if (SUCCEEDED(rc))
1648 RTPrintf("Successfully exported %d machine(s).\n", llMachines.size());
1649
1650 } while (0);
1651
1652 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1653}
1654
1655#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