VirtualBox

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

Last change on this file since 37900 was 37862, checked in by vboxsync, 13 years ago

Main-OVF;FE/*: allow to specify if MAC addresses should be reinitialized on OVF/OVA import

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