VirtualBox

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

Last change on this file since 49725 was 49103, checked in by vboxsync, 11 years ago

pr6927. ISO images are skipped by default during export. The command "VBoxManage export" has a new argument "--iso" to explicitly export ISO images.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 50.6 KB
Line 
1/* $Id: VBoxManageAppliance.cpp 49103 2013-10-15 06:20:39Z vboxsync $ */
2/** @file
3 * VBoxManage - The appliance-related commands.
4 */
5
6/*
7 * Copyright (C) 2009-2013 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* Header Files *
22*******************************************************************************/
23#ifndef VBOX_ONLY_DOCS
24#include <VBox/com/com.h>
25#include <VBox/com/string.h>
26#include <VBox/com/Guid.h>
27#include <VBox/com/array.h>
28#include <VBox/com/ErrorInfo.h>
29#include <VBox/com/errorprint.h>
30#include <VBox/com/VirtualBox.h>
31
32#include <list>
33#include <map>
34#endif /* !VBOX_ONLY_DOCS */
35
36#include <iprt/stream.h>
37#include <iprt/getopt.h>
38#include <iprt/ctype.h>
39#include <iprt/path.h>
40#include <iprt/file.h>
41
42#include <VBox/log.h>
43#include <VBox/param.h>
44
45#include "VBoxManage.h"
46using namespace com;
47
48
49// funcs
50///////////////////////////////////////////////////////////////////////////////
51
52typedef std::map<Utf8Str, Utf8Str> ArgsMap; // pairs of strings like "vmname" => "newvmname"
53typedef std::map<uint32_t, ArgsMap> ArgsMapsMap; // map of maps, one for each virtual system, sorted by index
54
55typedef std::map<uint32_t, bool> IgnoresMap; // pairs of numeric description entry indices
56typedef std::map<uint32_t, IgnoresMap> IgnoresMapsMap; // map of maps, one for each virtual system, sorted by index
57
58static bool findArgValue(Utf8Str &strOut,
59 ArgsMap *pmapArgs,
60 const Utf8Str &strKey)
61{
62 if (pmapArgs)
63 {
64 ArgsMap::iterator it;
65 it = pmapArgs->find(strKey);
66 if (it != pmapArgs->end())
67 {
68 strOut = it->second;
69 pmapArgs->erase(it);
70 return true;
71 }
72 }
73
74 return false;
75}
76
77static int parseImportOptions(const char *psz, com::SafeArray<ImportOptions_T> *options)
78{
79 int rc = VINF_SUCCESS;
80 while (psz && *psz && RT_SUCCESS(rc))
81 {
82 size_t len;
83 const char *pszComma = strchr(psz, ',');
84 if (pszComma)
85 len = pszComma - psz;
86 else
87 len = strlen(psz);
88 if (len > 0)
89 {
90 if (!RTStrNICmp(psz, "KeepAllMACs", len))
91 options->push_back(ImportOptions_KeepAllMACs);
92 else if (!RTStrNICmp(psz, "KeepNATMACs", len))
93 options->push_back(ImportOptions_KeepNATMACs);
94 else
95 rc = VERR_PARSE_ERROR;
96 }
97 if (pszComma)
98 psz += len + 1;
99 else
100 psz += len;
101 }
102
103 return rc;
104}
105
106static const RTGETOPTDEF g_aImportApplianceOptions[] =
107{
108 { "--dry-run", 'n', RTGETOPT_REQ_NOTHING },
109 { "-dry-run", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
110 { "--dryrun", 'n', RTGETOPT_REQ_NOTHING },
111 { "-dryrun", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
112 { "--detailed-progress", 'P', RTGETOPT_REQ_NOTHING },
113 { "-detailed-progress", 'P', RTGETOPT_REQ_NOTHING }, // deprecated
114 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
115 { "-vsys", 's', RTGETOPT_REQ_UINT32 }, // deprecated
116 { "--ostype", 'o', RTGETOPT_REQ_STRING },
117 { "-ostype", 'o', RTGETOPT_REQ_STRING }, // deprecated
118 { "--vmname", 'V', RTGETOPT_REQ_STRING },
119 { "-vmname", 'V', RTGETOPT_REQ_STRING }, // deprecated
120 { "--memory", 'm', RTGETOPT_REQ_STRING },
121 { "-memory", 'm', RTGETOPT_REQ_STRING }, // deprecated
122 { "--cpus", 'c', RTGETOPT_REQ_STRING },
123 { "--description", 'd', RTGETOPT_REQ_STRING },
124 { "--eula", 'L', RTGETOPT_REQ_STRING },
125 { "-eula", 'L', RTGETOPT_REQ_STRING }, // deprecated
126 { "--unit", 'u', RTGETOPT_REQ_UINT32 },
127 { "-unit", 'u', RTGETOPT_REQ_UINT32 }, // deprecated
128 { "--ignore", 'x', RTGETOPT_REQ_NOTHING },
129 { "-ignore", 'x', RTGETOPT_REQ_NOTHING }, // deprecated
130 { "--scsitype", 'T', RTGETOPT_REQ_UINT32 },
131 { "-scsitype", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
132 { "--type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
133 { "-type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
134#if 0 /* Changing the controller is fully valid, but the current design on how
135 the params are evaluated here doesn't allow two parameter for one
136 unit. The target disk path is more important. I leave it for future
137 improvments. */
138 { "--controller", 'C', RTGETOPT_REQ_STRING },
139#endif
140 { "--disk", 'D', RTGETOPT_REQ_STRING },
141 { "--options", 'O', RTGETOPT_REQ_STRING },
142};
143
144int handleImportAppliance(HandlerArg *arg)
145{
146 HRESULT rc = S_OK;
147
148 Utf8Str strOvfFilename;
149 bool fExecute = true; // if true, then we actually do the import
150 com::SafeArray<ImportOptions_T> options;
151 uint32_t ulCurVsys = (uint32_t)-1;
152 uint32_t ulCurUnit = (uint32_t)-1;
153 // for each --vsys X command, maintain a map of command line items
154 // (we'll parse them later after interpreting the OVF, when we can
155 // actually check whether they make sense semantically)
156 ArgsMapsMap mapArgsMapsPerVsys;
157 IgnoresMapsMap mapIgnoresMapsPerVsys;
158
159 int c;
160 RTGETOPTUNION ValueUnion;
161 RTGETOPTSTATE GetState;
162 // start at 0 because main() has hacked both the argc and argv given to us
163 RTGetOptInit(&GetState, arg->argc, arg->argv, g_aImportApplianceOptions, RT_ELEMENTS(g_aImportApplianceOptions),
164 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
165 while ((c = RTGetOpt(&GetState, &ValueUnion)))
166 {
167 switch (c)
168 {
169 case 'n': // --dry-run
170 fExecute = false;
171 break;
172
173 case 'P': // --detailed-progress
174 g_fDetailedProgress = true;
175 break;
176
177 case 's': // --vsys
178 ulCurVsys = ValueUnion.u32;
179 ulCurUnit = (uint32_t)-1;
180 break;
181
182 case 'o': // --ostype
183 if (ulCurVsys == (uint32_t)-1)
184 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
185 mapArgsMapsPerVsys[ulCurVsys]["ostype"] = ValueUnion.psz;
186 break;
187
188 case 'V': // --vmname
189 if (ulCurVsys == (uint32_t)-1)
190 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
191 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
192 break;
193
194 case 'd': // --description
195 if (ulCurVsys == (uint32_t)-1)
196 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
197 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
198 break;
199
200 case 'L': // --eula
201 if (ulCurVsys == (uint32_t)-1)
202 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
203 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
204 break;
205
206 case 'm': // --memory
207 if (ulCurVsys == (uint32_t)-1)
208 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
209 mapArgsMapsPerVsys[ulCurVsys]["memory"] = ValueUnion.psz;
210 break;
211
212 case 'c': // --cpus
213 if (ulCurVsys == (uint32_t)-1)
214 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
215 mapArgsMapsPerVsys[ulCurVsys]["cpus"] = ValueUnion.psz;
216 break;
217
218 case 'u': // --unit
219 ulCurUnit = ValueUnion.u32;
220 break;
221
222 case 'x': // --ignore
223 if (ulCurVsys == (uint32_t)-1)
224 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
225 if (ulCurUnit == (uint32_t)-1)
226 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
227 mapIgnoresMapsPerVsys[ulCurVsys][ulCurUnit] = true;
228 break;
229
230 case 'T': // --scsitype
231 if (ulCurVsys == (uint32_t)-1)
232 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
233 if (ulCurUnit == (uint32_t)-1)
234 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
235 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("scsitype%u", ulCurUnit)] = ValueUnion.psz;
236 break;
237
238 case 'C': // --controller
239 if (ulCurVsys == (uint32_t)-1)
240 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
241 if (ulCurUnit == (uint32_t)-1)
242 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
243 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("controller%u", ulCurUnit)] = ValueUnion.psz;
244 break;
245
246 case 'D': // --disk
247 if (ulCurVsys == (uint32_t)-1)
248 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
249 if (ulCurUnit == (uint32_t)-1)
250 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
251 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("disk%u", ulCurUnit)] = ValueUnion.psz;
252 break;
253
254 case 'O': // --options
255 if (RT_FAILURE(parseImportOptions(ValueUnion.psz, &options)))
256 return errorArgument("Invalid import options '%s'\n", ValueUnion.psz);
257 break;
258
259 case VINF_GETOPT_NOT_OPTION:
260 if (strOvfFilename.isEmpty())
261 strOvfFilename = ValueUnion.psz;
262 else
263 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid parameter '%s'", ValueUnion.psz);
264 break;
265
266 default:
267 if (c > 0)
268 {
269 if (RT_C_IS_PRINT(c))
270 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option -%c", c);
271 else
272 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option case %i", c);
273 }
274 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
275 return errorSyntax(USAGE_IMPORTAPPLIANCE, "unknown option: %s\n", ValueUnion.psz);
276 else if (ValueUnion.pDef)
277 return errorSyntax(USAGE_IMPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
278 else
279 return errorSyntax(USAGE_IMPORTAPPLIANCE, "error: %Rrs", c);
280 }
281 }
282
283 if (strOvfFilename.isEmpty())
284 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Not enough arguments for \"import\" command.");
285
286 do
287 {
288 ComPtr<IAppliance> pAppliance;
289 CHECK_ERROR_BREAK(arg->virtualBox, CreateAppliance(pAppliance.asOutParam()));
290
291 char *pszAbsFilePath;
292 if (strOvfFilename.startsWith("S3://", RTCString::CaseInsensitive) ||
293 strOvfFilename.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
294 strOvfFilename.startsWith("webdav://", RTCString::CaseInsensitive))
295 pszAbsFilePath = RTStrDup(strOvfFilename.c_str());
296 else
297 pszAbsFilePath = RTPathAbsDup(strOvfFilename.c_str());
298 ComPtr<IProgress> progressRead;
299 CHECK_ERROR_BREAK(pAppliance, Read(Bstr(pszAbsFilePath).raw(),
300 progressRead.asOutParam()));
301 RTStrFree(pszAbsFilePath);
302
303 rc = showProgress(progressRead);
304 CHECK_PROGRESS_ERROR_RET(progressRead, ("Appliance read failed"), RTEXITCODE_FAILURE);
305
306 Bstr path; /* fetch the path, there is stuff like username/password removed if any */
307 CHECK_ERROR_BREAK(pAppliance, COMGETTER(Path)(path.asOutParam()));
308 // call interpret(); this can yield both warnings and errors, so we need
309 // to tinker with the error info a bit
310 RTStrmPrintf(g_pStdErr, "Interpreting %ls...\n", path.raw());
311 rc = pAppliance->Interpret();
312 com::ErrorInfo info0(pAppliance, COM_IIDOF(IAppliance));
313
314 com::SafeArray<BSTR> aWarnings;
315 if (SUCCEEDED(pAppliance->GetWarnings(ComSafeArrayAsOutParam(aWarnings))))
316 {
317 size_t cWarnings = aWarnings.size();
318 for (unsigned i = 0; i < cWarnings; ++i)
319 {
320 Bstr bstrWarning(aWarnings[i]);
321 RTMsgWarning("%ls.", bstrWarning.raw());
322 }
323 }
324
325 if (FAILED(rc)) // during interpret, after printing warnings
326 {
327 com::GluePrintErrorInfo(info0);
328 com::GluePrintErrorContext("Interpret", __FILE__, __LINE__);
329 break;
330 }
331
332 RTStrmPrintf(g_pStdErr, "OK.\n");
333
334 // fetch all disks
335 com::SafeArray<BSTR> retDisks;
336 CHECK_ERROR_BREAK(pAppliance,
337 COMGETTER(Disks)(ComSafeArrayAsOutParam(retDisks)));
338 if (retDisks.size() > 0)
339 {
340 RTPrintf("Disks:");
341 for (unsigned i = 0; i < retDisks.size(); i++)
342 RTPrintf(" %ls", retDisks[i]);
343 RTPrintf("\n");
344 }
345
346 // fetch virtual system descriptions
347 com::SafeIfaceArray<IVirtualSystemDescription> aVirtualSystemDescriptions;
348 CHECK_ERROR_BREAK(pAppliance,
349 COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions)));
350
351 size_t cVirtualSystemDescriptions = aVirtualSystemDescriptions.size();
352
353 // match command line arguments with virtual system descriptions;
354 // this is only to sort out invalid indices at this time
355 ArgsMapsMap::const_iterator it;
356 for (it = mapArgsMapsPerVsys.begin();
357 it != mapArgsMapsPerVsys.end();
358 ++it)
359 {
360 uint32_t ulVsys = it->first;
361 if (ulVsys >= cVirtualSystemDescriptions)
362 return errorSyntax(USAGE_IMPORTAPPLIANCE,
363 "Invalid index %RI32 with -vsys option; the OVF contains only %zu virtual system(s).",
364 ulVsys, cVirtualSystemDescriptions);
365 }
366
367 uint32_t cLicensesInTheWay = 0;
368
369 // dump virtual system descriptions and match command-line arguments
370 if (cVirtualSystemDescriptions > 0)
371 {
372 for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
373 {
374 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
375 com::SafeArray<BSTR> aRefs;
376 com::SafeArray<BSTR> aOvfValues;
377 com::SafeArray<BSTR> aVboxValues;
378 com::SafeArray<BSTR> aExtraConfigValues;
379 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
380 GetDescription(ComSafeArrayAsOutParam(retTypes),
381 ComSafeArrayAsOutParam(aRefs),
382 ComSafeArrayAsOutParam(aOvfValues),
383 ComSafeArrayAsOutParam(aVboxValues),
384 ComSafeArrayAsOutParam(aExtraConfigValues)));
385
386 RTPrintf("Virtual system %u:\n", i);
387
388 // look up the corresponding command line options, if any
389 ArgsMap *pmapArgs = NULL;
390 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
391 if (itm != mapArgsMapsPerVsys.end())
392 pmapArgs = &itm->second;
393
394 // this collects the final values for setFinalValues()
395 com::SafeArray<BOOL> aEnabled(retTypes.size());
396 com::SafeArray<BSTR> aFinalValues(retTypes.size());
397
398 for (unsigned a = 0; a < retTypes.size(); ++a)
399 {
400 VirtualSystemDescriptionType_T t = retTypes[a];
401
402 Utf8Str strOverride;
403
404 Bstr bstrFinalValue = aVboxValues[a];
405
406 bool fIgnoreThis = mapIgnoresMapsPerVsys[i][a];
407
408 aEnabled[a] = true;
409
410 switch (t)
411 {
412 case VirtualSystemDescriptionType_OS:
413 if (findArgValue(strOverride, pmapArgs, "ostype"))
414 {
415 bstrFinalValue = strOverride;
416 RTPrintf("%2u: OS type specified with --ostype: \"%ls\"\n",
417 a, bstrFinalValue.raw());
418 }
419 else
420 RTPrintf("%2u: Suggested OS type: \"%ls\""
421 "\n (change with \"--vsys %u --ostype <type>\"; use \"list ostypes\" to list all possible values)\n",
422 a, bstrFinalValue.raw(), i);
423 break;
424
425 case VirtualSystemDescriptionType_Name:
426 if (findArgValue(strOverride, pmapArgs, "vmname"))
427 {
428 bstrFinalValue = strOverride;
429 RTPrintf("%2u: VM name specified with --vmname: \"%ls\"\n",
430 a, bstrFinalValue.raw());
431 }
432 else
433 RTPrintf("%2u: Suggested VM name \"%ls\""
434 "\n (change with \"--vsys %u --vmname <name>\")\n",
435 a, bstrFinalValue.raw(), i);
436 break;
437
438 case VirtualSystemDescriptionType_Product:
439 RTPrintf("%2u: Product (ignored): %ls\n",
440 a, aVboxValues[a]);
441 break;
442
443 case VirtualSystemDescriptionType_ProductUrl:
444 RTPrintf("%2u: ProductUrl (ignored): %ls\n",
445 a, aVboxValues[a]);
446 break;
447
448 case VirtualSystemDescriptionType_Vendor:
449 RTPrintf("%2u: Vendor (ignored): %ls\n",
450 a, aVboxValues[a]);
451 break;
452
453 case VirtualSystemDescriptionType_VendorUrl:
454 RTPrintf("%2u: VendorUrl (ignored): %ls\n",
455 a, aVboxValues[a]);
456 break;
457
458 case VirtualSystemDescriptionType_Version:
459 RTPrintf("%2u: Version (ignored): %ls\n",
460 a, aVboxValues[a]);
461 break;
462
463 case VirtualSystemDescriptionType_Description:
464 if (findArgValue(strOverride, pmapArgs, "description"))
465 {
466 bstrFinalValue = strOverride;
467 RTPrintf("%2u: Description specified with --description: \"%ls\"\n",
468 a, bstrFinalValue.raw());
469 }
470 else
471 RTPrintf("%2u: Description \"%ls\""
472 "\n (change with \"--vsys %u --description <desc>\")\n",
473 a, bstrFinalValue.raw(), i);
474 break;
475
476 case VirtualSystemDescriptionType_License:
477 ++cLicensesInTheWay;
478 if (findArgValue(strOverride, pmapArgs, "eula"))
479 {
480 if (strOverride == "show")
481 {
482 RTPrintf("%2u: End-user license agreement"
483 "\n (accept with \"--vsys %u --eula accept\"):"
484 "\n\n%ls\n\n",
485 a, i, bstrFinalValue.raw());
486 }
487 else if (strOverride == "accept")
488 {
489 RTPrintf("%2u: End-user license agreement (accepted)\n",
490 a);
491 --cLicensesInTheWay;
492 }
493 else
494 return errorSyntax(USAGE_IMPORTAPPLIANCE,
495 "Argument to --eula must be either \"show\" or \"accept\".");
496 }
497 else
498 RTPrintf("%2u: End-user license agreement"
499 "\n (display with \"--vsys %u --eula show\";"
500 "\n accept with \"--vsys %u --eula accept\")\n",
501 a, i, i);
502 break;
503
504 case VirtualSystemDescriptionType_CPU:
505 if (findArgValue(strOverride, pmapArgs, "cpus"))
506 {
507 uint32_t cCPUs;
508 if ( strOverride.toInt(cCPUs) == VINF_SUCCESS
509 && cCPUs >= VMM_MIN_CPU_COUNT
510 && cCPUs <= VMM_MAX_CPU_COUNT
511 )
512 {
513 bstrFinalValue = strOverride;
514 RTPrintf("%2u: No. of CPUs specified with --cpus: %ls\n",
515 a, bstrFinalValue.raw());
516 }
517 else
518 return errorSyntax(USAGE_IMPORTAPPLIANCE,
519 "Argument to --cpus option must be a number greater than %d and less than %d.",
520 VMM_MIN_CPU_COUNT - 1, VMM_MAX_CPU_COUNT + 1);
521 }
522 else
523 RTPrintf("%2u: Number of CPUs: %ls\n (change with \"--vsys %u --cpus <n>\")\n",
524 a, bstrFinalValue.raw(), i);
525 break;
526
527 case VirtualSystemDescriptionType_Memory:
528 {
529 if (findArgValue(strOverride, pmapArgs, "memory"))
530 {
531 uint32_t ulMemMB;
532 if (VINF_SUCCESS == strOverride.toInt(ulMemMB))
533 {
534 bstrFinalValue = strOverride;
535 RTPrintf("%2u: Guest memory specified with --memory: %ls MB\n",
536 a, bstrFinalValue.raw());
537 }
538 else
539 return errorSyntax(USAGE_IMPORTAPPLIANCE,
540 "Argument to --memory option must be a non-negative number.");
541 }
542 else
543 RTPrintf("%2u: Guest memory: %ls MB\n (change with \"--vsys %u --memory <MB>\")\n",
544 a, bstrFinalValue.raw(), i);
545 }
546 break;
547
548 case VirtualSystemDescriptionType_HardDiskControllerIDE:
549 if (fIgnoreThis)
550 {
551 RTPrintf("%2u: IDE controller, type %ls -- disabled\n",
552 a,
553 aVboxValues[a]);
554 aEnabled[a] = false;
555 }
556 else
557 RTPrintf("%2u: IDE controller, type %ls"
558 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
559 a,
560 aVboxValues[a],
561 i, a);
562 break;
563
564 case VirtualSystemDescriptionType_HardDiskControllerSATA:
565 if (fIgnoreThis)
566 {
567 RTPrintf("%2u: SATA controller, type %ls -- disabled\n",
568 a,
569 aVboxValues[a]);
570 aEnabled[a] = false;
571 }
572 else
573 RTPrintf("%2u: SATA controller, type %ls"
574 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
575 a,
576 aVboxValues[a],
577 i, a);
578 break;
579
580 case VirtualSystemDescriptionType_HardDiskControllerSAS:
581 if (fIgnoreThis)
582 {
583 RTPrintf("%2u: SAS controller, type %ls -- disabled\n",
584 a,
585 aVboxValues[a]);
586 aEnabled[a] = false;
587 }
588 else
589 RTPrintf("%2u: SAS controller, type %ls"
590 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
591 a,
592 aVboxValues[a],
593 i, a);
594 break;
595
596 case VirtualSystemDescriptionType_HardDiskControllerSCSI:
597 if (fIgnoreThis)
598 {
599 RTPrintf("%2u: SCSI controller, type %ls -- disabled\n",
600 a,
601 aVboxValues[a]);
602 aEnabled[a] = false;
603 }
604 else
605 {
606 Utf8StrFmt strTypeArg("scsitype%u", a);
607 if (findArgValue(strOverride, pmapArgs, strTypeArg))
608 {
609 bstrFinalValue = strOverride;
610 RTPrintf("%2u: SCSI controller, type set with --unit %u --scsitype: \"%ls\"\n",
611 a,
612 a,
613 bstrFinalValue.raw());
614 }
615 else
616 RTPrintf("%2u: SCSI controller, type %ls"
617 "\n (change with \"--vsys %u --unit %u --scsitype {BusLogic|LsiLogic}\";"
618 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
619 a,
620 aVboxValues[a],
621 i, a, i, a);
622 }
623 break;
624
625 case VirtualSystemDescriptionType_HardDiskImage:
626 if (fIgnoreThis)
627 {
628 RTPrintf("%2u: Hard disk image: source image=%ls -- disabled\n",
629 a,
630 aOvfValues[a]);
631 aEnabled[a] = false;
632 }
633 else
634 {
635 Utf8StrFmt strTypeArg("disk%u", a);
636 if (findArgValue(strOverride, pmapArgs, strTypeArg))
637 {
638 RTUUID uuid;
639 /* Check if this is a uuid. If so, don't touch. */
640 int vrc = RTUuidFromStr(&uuid, strOverride.c_str());
641 if (vrc != VINF_SUCCESS)
642 {
643 /* Make the path absolute. */
644 if (!RTPathStartsWithRoot(strOverride.c_str()))
645 {
646 char pszPwd[RTPATH_MAX];
647 vrc = RTPathGetCurrent(pszPwd, RTPATH_MAX);
648 if (RT_SUCCESS(vrc))
649 strOverride = Utf8Str(pszPwd).append(RTPATH_SLASH).append(strOverride);
650 }
651 }
652 bstrFinalValue = strOverride;
653 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
654 a,
655 aOvfValues[a],
656 bstrFinalValue.raw(),
657 aExtraConfigValues[a]);
658 }
659#if 0 /* Changing the controller is fully valid, but the current design on how
660 the params are evaluated here doesn't allow two parameter for one
661 unit. The target disk path is more important I leave it for future
662 improvments. */
663 Utf8StrFmt strTypeArg("controller%u", a);
664 if (findArgValue(strOverride, pmapArgs, strTypeArg))
665 {
666 // strOverride now has the controller index as a number, but we
667 // need a "controller=X" format string
668 strOverride = Utf8StrFmt("controller=%s", strOverride.c_str());
669 Bstr bstrExtraConfigValue = strOverride;
670 bstrExtraConfigValue.detachTo(&aExtraConfigValues[a]);
671 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
672 a,
673 aOvfValues[a],
674 aVboxValues[a],
675 aExtraConfigValues[a]);
676 }
677#endif
678 else
679 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls"
680 "\n (change target path with \"--vsys %u --unit %u --disk path\";"
681 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
682 a,
683 aOvfValues[a],
684 aVboxValues[a],
685 aExtraConfigValues[a],
686 i, a, i, a);
687 }
688 break;
689
690 case VirtualSystemDescriptionType_CDROM:
691 if (fIgnoreThis)
692 {
693 RTPrintf("%2u: CD-ROM -- disabled\n",
694 a);
695 aEnabled[a] = false;
696 }
697 else
698 RTPrintf("%2u: CD-ROM"
699 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
700 a, i, a);
701 break;
702
703 case VirtualSystemDescriptionType_Floppy:
704 if (fIgnoreThis)
705 {
706 RTPrintf("%2u: Floppy -- disabled\n",
707 a);
708 aEnabled[a] = false;
709 }
710 else
711 RTPrintf("%2u: Floppy"
712 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
713 a, i, a);
714 break;
715
716 case VirtualSystemDescriptionType_NetworkAdapter:
717 RTPrintf("%2u: Network adapter: orig %ls, config %ls, extra %ls\n", // @todo implement once we have a plan for the back-end
718 a,
719 aOvfValues[a],
720 aVboxValues[a],
721 aExtraConfigValues[a]);
722 break;
723
724 case VirtualSystemDescriptionType_USBController:
725 if (fIgnoreThis)
726 {
727 RTPrintf("%2u: USB controller -- disabled\n",
728 a);
729 aEnabled[a] = false;
730 }
731 else
732 RTPrintf("%2u: USB controller"
733 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
734 a, i, a);
735 break;
736
737 case VirtualSystemDescriptionType_SoundCard:
738 if (fIgnoreThis)
739 {
740 RTPrintf("%2u: Sound card \"%ls\" -- disabled\n",
741 a,
742 aOvfValues[a]);
743 aEnabled[a] = false;
744 }
745 else
746 RTPrintf("%2u: Sound card (appliance expects \"%ls\", can change on import)"
747 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
748 a,
749 aOvfValues[a],
750 i,
751 a);
752 break;
753 }
754
755 bstrFinalValue.detachTo(&aFinalValues[a]);
756 }
757
758 if (fExecute)
759 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
760 SetFinalValues(ComSafeArrayAsInParam(aEnabled),
761 ComSafeArrayAsInParam(aFinalValues),
762 ComSafeArrayAsInParam(aExtraConfigValues)));
763
764 } // for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
765
766 if (cLicensesInTheWay == 1)
767 RTMsgError("Cannot import until the license agreement listed above is accepted.");
768 else if (cLicensesInTheWay > 1)
769 RTMsgError("Cannot import until the %c license agreements listed above are accepted.", cLicensesInTheWay);
770
771 if (!cLicensesInTheWay && fExecute)
772 {
773 // go!
774 ComPtr<IProgress> progress;
775 CHECK_ERROR_BREAK(pAppliance,
776 ImportMachines(ComSafeArrayAsInParam(options), progress.asOutParam()));
777
778 rc = showProgress(progress);
779 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance import failed"), RTEXITCODE_FAILURE);
780
781 if (SUCCEEDED(rc))
782 RTPrintf("Successfully imported the appliance.\n");
783 }
784 } // end if (aVirtualSystemDescriptions.size() > 0)
785 } while (0);
786
787 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
788}
789
790static const RTGETOPTDEF g_aExportOptions[]
791 = {
792 { "--output", 'o', RTGETOPT_REQ_STRING },
793 { "--legacy09", 'l', RTGETOPT_REQ_NOTHING },
794 { "--ovf09", 'l', RTGETOPT_REQ_NOTHING },
795 { "--ovf10", '1', RTGETOPT_REQ_NOTHING },
796 { "--ovf20", '2', RTGETOPT_REQ_NOTHING },
797 { "--manifest", 'm', RTGETOPT_REQ_NOTHING },
798 { "--iso", 'I', RTGETOPT_REQ_NOTHING },
799 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
800 { "--product", 'p', RTGETOPT_REQ_STRING },
801 { "--producturl", 'P', RTGETOPT_REQ_STRING },
802 { "--vendor", 'n', RTGETOPT_REQ_STRING },
803 { "--vendorurl", 'N', RTGETOPT_REQ_STRING },
804 { "--version", 'v', RTGETOPT_REQ_STRING },
805 { "--description", 'd', RTGETOPT_REQ_STRING },
806 { "--eula", 'e', RTGETOPT_REQ_STRING },
807 { "--eulafile", 'E', RTGETOPT_REQ_STRING },
808 };
809
810int handleExportAppliance(HandlerArg *a)
811{
812 HRESULT rc = S_OK;
813
814 Utf8Str strOutputFile;
815 Utf8Str strOvfFormat("ovf-1.0"); // the default export version
816 bool fManifest = false; // the default
817 bool fExportISOImages = false; // the default
818 std::list< ComPtr<IMachine> > llMachines;
819
820 uint32_t ulCurVsys = (uint32_t)-1;
821 // for each --vsys X command, maintain a map of command line items
822 ArgsMapsMap mapArgsMapsPerVsys;
823 do
824 {
825 int c;
826
827 RTGETOPTUNION ValueUnion;
828 RTGETOPTSTATE GetState;
829 // start at 0 because main() has hacked both the argc and argv given to us
830 RTGetOptInit(&GetState, a->argc, a->argv, g_aExportOptions,
831 RT_ELEMENTS(g_aExportOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
832
833 Utf8Str strProductUrl;
834 while ((c = RTGetOpt(&GetState, &ValueUnion)))
835 {
836 switch (c)
837 {
838 case 'o': // --output
839 if (strOutputFile.length())
840 return errorSyntax(USAGE_EXPORTAPPLIANCE, "You can only specify --output once.");
841 else
842 strOutputFile = ValueUnion.psz;
843 break;
844
845 case 'l': // --legacy09/--ovf09
846 strOvfFormat = "ovf-0.9";
847 break;
848
849 case '1': // --ovf10
850 strOvfFormat = "ovf-1.0";
851 break;
852
853 case '2': // --ovf20
854 strOvfFormat = "ovf-2.0";
855 break;
856
857 case 'I': // --iso
858 fExportISOImages = true;
859 break;
860
861 case 'm': // --manifest
862 fManifest = true;
863 break;
864
865 case 's': // --vsys
866 ulCurVsys = ValueUnion.u32;
867 break;
868
869 case 'p': // --product
870 if (ulCurVsys == (uint32_t)-1)
871 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
872 mapArgsMapsPerVsys[ulCurVsys]["product"] = ValueUnion.psz;
873 break;
874
875 case 'P': // --producturl
876 if (ulCurVsys == (uint32_t)-1)
877 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
878 mapArgsMapsPerVsys[ulCurVsys]["producturl"] = ValueUnion.psz;
879 break;
880
881 case 'n': // --vendor
882 if (ulCurVsys == (uint32_t)-1)
883 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
884 mapArgsMapsPerVsys[ulCurVsys]["vendor"] = ValueUnion.psz;
885 break;
886
887 case 'N': // --vendorurl
888 if (ulCurVsys == (uint32_t)-1)
889 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
890 mapArgsMapsPerVsys[ulCurVsys]["vendorurl"] = ValueUnion.psz;
891 break;
892
893 case 'v': // --version
894 if (ulCurVsys == (uint32_t)-1)
895 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
896 mapArgsMapsPerVsys[ulCurVsys]["version"] = ValueUnion.psz;
897 break;
898
899 case 'd': // --description
900 if (ulCurVsys == (uint32_t)-1)
901 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
902 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
903 break;
904
905 case 'e': // --eula
906 if (ulCurVsys == (uint32_t)-1)
907 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
908 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
909 break;
910
911 case 'E': // --eulafile
912 if (ulCurVsys == (uint32_t)-1)
913 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
914 mapArgsMapsPerVsys[ulCurVsys]["eulafile"] = ValueUnion.psz;
915 break;
916
917 case VINF_GETOPT_NOT_OPTION:
918 {
919 Utf8Str strMachine(ValueUnion.psz);
920 // must be machine: try UUID or name
921 ComPtr<IMachine> machine;
922 CHECK_ERROR_BREAK(a->virtualBox, FindMachine(Bstr(strMachine).raw(),
923 machine.asOutParam()));
924 if (machine)
925 llMachines.push_back(machine);
926 break;
927 }
928
929 default:
930 if (c > 0)
931 {
932 if (RT_C_IS_GRAPH(c))
933 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: -%c", c);
934 else
935 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: %i", c);
936 }
937 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
938 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unknown option: %s", ValueUnion.psz);
939 else if (ValueUnion.pDef)
940 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
941 else
942 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%Rrs", c);
943 }
944
945 if (FAILED(rc))
946 break;
947 }
948
949 if (FAILED(rc))
950 break;
951
952 if (llMachines.size() == 0)
953 return errorSyntax(USAGE_EXPORTAPPLIANCE, "At least one machine must be specified with the export command.");
954 if (!strOutputFile.length())
955 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Missing --output argument with export command.");
956
957 // match command line arguments with the machines count
958 // this is only to sort out invalid indices at this time
959 ArgsMapsMap::const_iterator it;
960 for (it = mapArgsMapsPerVsys.begin();
961 it != mapArgsMapsPerVsys.end();
962 ++it)
963 {
964 uint32_t ulVsys = it->first;
965 if (ulVsys >= llMachines.size())
966 return errorSyntax(USAGE_EXPORTAPPLIANCE,
967 "Invalid index %RI32 with -vsys option; you specified only %zu virtual system(s).",
968 ulVsys, llMachines.size());
969 }
970
971 ComPtr<IAppliance> pAppliance;
972 CHECK_ERROR_BREAK(a->virtualBox, CreateAppliance(pAppliance.asOutParam()));
973
974 char *pszAbsFilePath = 0;
975 if (strOutputFile.startsWith("S3://", RTCString::CaseInsensitive) ||
976 strOutputFile.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
977 strOutputFile.startsWith("webdav://", RTCString::CaseInsensitive))
978 pszAbsFilePath = RTStrDup(strOutputFile.c_str());
979 else
980 pszAbsFilePath = RTPathAbsDup(strOutputFile.c_str());
981
982 std::list< ComPtr<IMachine> >::iterator itM;
983 uint32_t i=0;
984 for (itM = llMachines.begin();
985 itM != llMachines.end();
986 ++itM, ++i)
987 {
988 ComPtr<IMachine> pMachine = *itM;
989 ComPtr<IVirtualSystemDescription> pVSD;
990 CHECK_ERROR_BREAK(pMachine, ExportTo(pAppliance, Bstr(pszAbsFilePath).raw(), pVSD.asOutParam()));
991 // Add additional info to the virtual system description if the user wants so
992 ArgsMap *pmapArgs = NULL;
993 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
994 if (itm != mapArgsMapsPerVsys.end())
995 pmapArgs = &itm->second;
996 if (pmapArgs)
997 {
998 ArgsMap::iterator itD;
999 for (itD = pmapArgs->begin();
1000 itD != pmapArgs->end();
1001 ++itD)
1002 {
1003 if (itD->first == "product")
1004 pVSD->AddDescription(VirtualSystemDescriptionType_Product,
1005 Bstr(itD->second).raw(),
1006 Bstr(itD->second).raw());
1007 else if (itD->first == "producturl")
1008 pVSD->AddDescription(VirtualSystemDescriptionType_ProductUrl,
1009 Bstr(itD->second).raw(),
1010 Bstr(itD->second).raw());
1011 else if (itD->first == "vendor")
1012 pVSD->AddDescription(VirtualSystemDescriptionType_Vendor,
1013 Bstr(itD->second).raw(),
1014 Bstr(itD->second).raw());
1015 else if (itD->first == "vendorurl")
1016 pVSD->AddDescription(VirtualSystemDescriptionType_VendorUrl,
1017 Bstr(itD->second).raw(),
1018 Bstr(itD->second).raw());
1019 else if (itD->first == "version")
1020 pVSD->AddDescription(VirtualSystemDescriptionType_Version,
1021 Bstr(itD->second).raw(),
1022 Bstr(itD->second).raw());
1023 else if (itD->first == "description")
1024 pVSD->AddDescription(VirtualSystemDescriptionType_Description,
1025 Bstr(itD->second).raw(),
1026 Bstr(itD->second).raw());
1027 else if (itD->first == "eula")
1028 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1029 Bstr(itD->second).raw(),
1030 Bstr(itD->second).raw());
1031 else if (itD->first == "eulafile")
1032 {
1033 Utf8Str strContent;
1034 void *pvFile;
1035 size_t cbFile;
1036 int irc = RTFileReadAll(itD->second.c_str(), &pvFile, &cbFile);
1037 if (RT_SUCCESS(irc))
1038 {
1039 Bstr bstrContent((char*)pvFile, cbFile);
1040 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1041 bstrContent.raw(),
1042 bstrContent.raw());
1043 RTFileReadAllFree(pvFile, cbFile);
1044 }
1045 else
1046 {
1047 RTMsgError("Cannot read license file \"%s\" which should be included in the virtual system %u.",
1048 itD->second.c_str(), i);
1049 return 1;
1050 }
1051 }
1052 }
1053 }
1054 }
1055
1056 if (FAILED(rc))
1057 break;
1058
1059 com::SafeArray<ExportOptions_T> options;
1060 if (fManifest)
1061 options.push_back(ExportOptions_CreateManifest);
1062
1063 if (fExportISOImages)
1064 options.push_back(ExportOptions_ExportDVDImages);
1065
1066 ComPtr<IProgress> progress;
1067 CHECK_ERROR_BREAK(pAppliance, Write(Bstr(strOvfFormat).raw(),
1068 ComSafeArrayAsInParam(options),
1069 Bstr(pszAbsFilePath).raw(),
1070 progress.asOutParam()));
1071 RTStrFree(pszAbsFilePath);
1072
1073 rc = showProgress(progress);
1074 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance write failed"), RTEXITCODE_FAILURE);
1075
1076 if (SUCCEEDED(rc))
1077 RTPrintf("Successfully exported %d machine(s).\n", llMachines.size());
1078
1079 } while (0);
1080
1081 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1082}
1083
1084#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