VirtualBox

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

Last change on this file since 79043 was 78858, checked in by vboxsync, 6 years ago

bugref:9416. Fixed the missed call of interpret() in the local OVF import. The check of presence the items "cloudprofile" and "cloudinstanceid" in the std::map ArgsMap was fixed.

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