VirtualBox

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

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

bugref:9436. small fixes.

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