VirtualBox

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

Last change on this file since 43421 was 40329, checked in by vboxsync, 12 years ago

Main/Runtime/VBoxManage: SHA256 support comes with OVF 2.0

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 49.5 KB
Line 
1/* $Id: VBoxManageAppliance.cpp 40329 2012-03-02 16:13:50Z 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 CHECK_PROGRESS_ERROR_RET(progressRead, ("Appliance read failed"), RTEXITCODE_FAILURE);
307
308 Bstr path; /* fetch the path, there is stuff like username/password removed if any */
309 CHECK_ERROR_BREAK(pAppliance, COMGETTER(Path)(path.asOutParam()));
310 // call interpret(); this can yield both warnings and errors, so we need
311 // to tinker with the error info a bit
312 RTStrmPrintf(g_pStdErr, "Interpreting %ls...\n", path.raw());
313 rc = pAppliance->Interpret();
314 com::ErrorInfo info0(pAppliance, COM_IIDOF(IAppliance));
315
316 com::SafeArray<BSTR> aWarnings;
317 if (SUCCEEDED(pAppliance->GetWarnings(ComSafeArrayAsOutParam(aWarnings))))
318 {
319 size_t cWarnings = aWarnings.size();
320 for (unsigned i = 0; i < cWarnings; ++i)
321 {
322 Bstr bstrWarning(aWarnings[i]);
323 RTMsgWarning("%ls.", bstrWarning.raw());
324 }
325 }
326
327 if (FAILED(rc)) // during interpret, after printing warnings
328 {
329 com::GluePrintErrorInfo(info0);
330 com::GluePrintErrorContext("Interpret", __FILE__, __LINE__);
331 break;
332 }
333
334 RTStrmPrintf(g_pStdErr, "OK.\n");
335
336 // fetch all disks
337 com::SafeArray<BSTR> retDisks;
338 CHECK_ERROR_BREAK(pAppliance,
339 COMGETTER(Disks)(ComSafeArrayAsOutParam(retDisks)));
340 if (retDisks.size() > 0)
341 {
342 RTPrintf("Disks:");
343 for (unsigned i = 0; i < retDisks.size(); i++)
344 RTPrintf(" %ls", retDisks[i]);
345 RTPrintf("\n");
346 }
347
348 // fetch virtual system descriptions
349 com::SafeIfaceArray<IVirtualSystemDescription> aVirtualSystemDescriptions;
350 CHECK_ERROR_BREAK(pAppliance,
351 COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions)));
352
353 size_t cVirtualSystemDescriptions = aVirtualSystemDescriptions.size();
354
355 // match command line arguments with virtual system descriptions;
356 // this is only to sort out invalid indices at this time
357 ArgsMapsMap::const_iterator it;
358 for (it = mapArgsMapsPerVsys.begin();
359 it != mapArgsMapsPerVsys.end();
360 ++it)
361 {
362 uint32_t ulVsys = it->first;
363 if (ulVsys >= cVirtualSystemDescriptions)
364 return errorSyntax(USAGE_IMPORTAPPLIANCE,
365 "Invalid index %RI32 with -vsys option; the OVF contains only %zu virtual system(s).",
366 ulVsys, cVirtualSystemDescriptions);
367 }
368
369 uint32_t cLicensesInTheWay = 0;
370
371 // dump virtual system descriptions and match command-line arguments
372 if (cVirtualSystemDescriptions > 0)
373 {
374 for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
375 {
376 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
377 com::SafeArray<BSTR> aRefs;
378 com::SafeArray<BSTR> aOvfValues;
379 com::SafeArray<BSTR> aVboxValues;
380 com::SafeArray<BSTR> aExtraConfigValues;
381 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
382 GetDescription(ComSafeArrayAsOutParam(retTypes),
383 ComSafeArrayAsOutParam(aRefs),
384 ComSafeArrayAsOutParam(aOvfValues),
385 ComSafeArrayAsOutParam(aVboxValues),
386 ComSafeArrayAsOutParam(aExtraConfigValues)));
387
388 RTPrintf("Virtual system %u:\n", i);
389
390 // look up the corresponding command line options, if any
391 ArgsMap *pmapArgs = NULL;
392 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
393 if (itm != mapArgsMapsPerVsys.end())
394 pmapArgs = &itm->second;
395
396 // this collects the final values for setFinalValues()
397 com::SafeArray<BOOL> aEnabled(retTypes.size());
398 com::SafeArray<BSTR> aFinalValues(retTypes.size());
399
400 for (unsigned a = 0; a < retTypes.size(); ++a)
401 {
402 VirtualSystemDescriptionType_T t = retTypes[a];
403
404 Utf8Str strOverride;
405
406 Bstr bstrFinalValue = aVboxValues[a];
407
408 bool fIgnoreThis = mapIgnoresMapsPerVsys[i][a];
409
410 aEnabled[a] = true;
411
412 switch (t)
413 {
414 case VirtualSystemDescriptionType_OS:
415 if (findArgValue(strOverride, pmapArgs, "ostype"))
416 {
417 bstrFinalValue = strOverride;
418 RTPrintf("%2u: OS type specified with --ostype: \"%ls\"\n",
419 a, bstrFinalValue.raw());
420 }
421 else
422 RTPrintf("%2u: Suggested OS type: \"%ls\""
423 "\n (change with \"--vsys %u --ostype <type>\"; use \"list ostypes\" to list all possible values)\n",
424 a, bstrFinalValue.raw(), i);
425 break;
426
427 case VirtualSystemDescriptionType_Name:
428 if (findArgValue(strOverride, pmapArgs, "vmname"))
429 {
430 bstrFinalValue = strOverride;
431 RTPrintf("%2u: VM name specified with --vmname: \"%ls\"\n",
432 a, bstrFinalValue.raw());
433 }
434 else
435 RTPrintf("%2u: Suggested VM name \"%ls\""
436 "\n (change with \"--vsys %u --vmname <name>\")\n",
437 a, bstrFinalValue.raw(), i);
438 break;
439
440 case VirtualSystemDescriptionType_Product:
441 RTPrintf("%2u: Product (ignored): %ls\n",
442 a, aVboxValues[a]);
443 break;
444
445 case VirtualSystemDescriptionType_ProductUrl:
446 RTPrintf("%2u: ProductUrl (ignored): %ls\n",
447 a, aVboxValues[a]);
448 break;
449
450 case VirtualSystemDescriptionType_Vendor:
451 RTPrintf("%2u: Vendor (ignored): %ls\n",
452 a, aVboxValues[a]);
453 break;
454
455 case VirtualSystemDescriptionType_VendorUrl:
456 RTPrintf("%2u: VendorUrl (ignored): %ls\n",
457 a, aVboxValues[a]);
458 break;
459
460 case VirtualSystemDescriptionType_Version:
461 RTPrintf("%2u: Version (ignored): %ls\n",
462 a, aVboxValues[a]);
463 break;
464
465 case VirtualSystemDescriptionType_Description:
466 if (findArgValue(strOverride, pmapArgs, "description"))
467 {
468 bstrFinalValue = strOverride;
469 RTPrintf("%2u: Description specified with --description: \"%ls\"\n",
470 a, bstrFinalValue.raw());
471 }
472 else
473 RTPrintf("%2u: Description \"%ls\""
474 "\n (change with \"--vsys %u --description <desc>\")\n",
475 a, bstrFinalValue.raw(), i);
476 break;
477
478 case VirtualSystemDescriptionType_License:
479 ++cLicensesInTheWay;
480 if (findArgValue(strOverride, pmapArgs, "eula"))
481 {
482 if (strOverride == "show")
483 {
484 RTPrintf("%2u: End-user license agreement"
485 "\n (accept with \"--vsys %u --eula accept\"):"
486 "\n\n%ls\n\n",
487 a, i, bstrFinalValue.raw());
488 }
489 else if (strOverride == "accept")
490 {
491 RTPrintf("%2u: End-user license agreement (accepted)\n",
492 a);
493 --cLicensesInTheWay;
494 }
495 else
496 return errorSyntax(USAGE_IMPORTAPPLIANCE,
497 "Argument to --eula must be either \"show\" or \"accept\".");
498 }
499 else
500 RTPrintf("%2u: End-user license agreement"
501 "\n (display with \"--vsys %u --eula show\";"
502 "\n accept with \"--vsys %u --eula accept\")\n",
503 a, i, i);
504 break;
505
506 case VirtualSystemDescriptionType_CPU:
507 if (findArgValue(strOverride, pmapArgs, "cpus"))
508 {
509 uint32_t cCPUs;
510 if ( strOverride.toInt(cCPUs) == VINF_SUCCESS
511 && cCPUs >= VMM_MIN_CPU_COUNT
512 && cCPUs <= VMM_MAX_CPU_COUNT
513 )
514 {
515 bstrFinalValue = strOverride;
516 RTPrintf("%2u: No. of CPUs specified with --cpus: %ls\n",
517 a, bstrFinalValue.raw());
518 }
519 else
520 return errorSyntax(USAGE_IMPORTAPPLIANCE,
521 "Argument to --cpus option must be a number greater than %d and less than %d.",
522 VMM_MIN_CPU_COUNT - 1, VMM_MAX_CPU_COUNT + 1);
523 }
524 else
525 RTPrintf("%2u: Number of CPUs: %ls\n (change with \"--vsys %u --cpus <n>\")\n",
526 a, bstrFinalValue.raw(), i);
527 break;
528
529 case VirtualSystemDescriptionType_Memory:
530 {
531 if (findArgValue(strOverride, pmapArgs, "memory"))
532 {
533 uint32_t ulMemMB;
534 if (VINF_SUCCESS == strOverride.toInt(ulMemMB))
535 {
536 bstrFinalValue = strOverride;
537 RTPrintf("%2u: Guest memory specified with --memory: %ls MB\n",
538 a, bstrFinalValue.raw());
539 }
540 else
541 return errorSyntax(USAGE_IMPORTAPPLIANCE,
542 "Argument to --memory option must be a non-negative number.");
543 }
544 else
545 RTPrintf("%2u: Guest memory: %ls MB\n (change with \"--vsys %u --memory <MB>\")\n",
546 a, bstrFinalValue.raw(), i);
547 }
548 break;
549
550 case VirtualSystemDescriptionType_HardDiskControllerIDE:
551 if (fIgnoreThis)
552 {
553 RTPrintf("%2u: IDE controller, type %ls -- disabled\n",
554 a,
555 aVboxValues[a]);
556 aEnabled[a] = false;
557 }
558 else
559 RTPrintf("%2u: IDE controller, type %ls"
560 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
561 a,
562 aVboxValues[a],
563 i, a);
564 break;
565
566 case VirtualSystemDescriptionType_HardDiskControllerSATA:
567 if (fIgnoreThis)
568 {
569 RTPrintf("%2u: SATA controller, type %ls -- disabled\n",
570 a,
571 aVboxValues[a]);
572 aEnabled[a] = false;
573 }
574 else
575 RTPrintf("%2u: SATA controller, type %ls"
576 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
577 a,
578 aVboxValues[a],
579 i, a);
580 break;
581
582 case VirtualSystemDescriptionType_HardDiskControllerSAS:
583 if (fIgnoreThis)
584 {
585 RTPrintf("%2u: SAS controller, type %ls -- disabled\n",
586 a,
587 aVboxValues[a]);
588 aEnabled[a] = false;
589 }
590 else
591 RTPrintf("%2u: SAS controller, type %ls"
592 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
593 a,
594 aVboxValues[a],
595 i, a);
596 break;
597
598 case VirtualSystemDescriptionType_HardDiskControllerSCSI:
599 if (fIgnoreThis)
600 {
601 RTPrintf("%2u: SCSI controller, type %ls -- disabled\n",
602 a,
603 aVboxValues[a]);
604 aEnabled[a] = false;
605 }
606 else
607 {
608 Utf8StrFmt strTypeArg("scsitype%u", a);
609 if (findArgValue(strOverride, pmapArgs, strTypeArg))
610 {
611 bstrFinalValue = strOverride;
612 RTPrintf("%2u: SCSI controller, type set with --unit %u --scsitype: \"%ls\"\n",
613 a,
614 a,
615 bstrFinalValue.raw());
616 }
617 else
618 RTPrintf("%2u: SCSI controller, type %ls"
619 "\n (change with \"--vsys %u --unit %u --scsitype {BusLogic|LsiLogic}\";"
620 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
621 a,
622 aVboxValues[a],
623 i, a, i, a);
624 }
625 break;
626
627 case VirtualSystemDescriptionType_HardDiskImage:
628 if (fIgnoreThis)
629 {
630 RTPrintf("%2u: Hard disk image: source image=%ls -- disabled\n",
631 a,
632 aOvfValues[a]);
633 aEnabled[a] = false;
634 }
635 else
636 {
637 Utf8StrFmt strTypeArg("disk%u", a);
638 if (findArgValue(strOverride, pmapArgs, strTypeArg))
639 {
640 RTUUID uuid;
641 /* Check if this is a uuid. If so, don't touch. */
642 int vrc = RTUuidFromStr(&uuid, strOverride.c_str());
643 if (vrc != VINF_SUCCESS)
644 {
645 /* Make the path absolute. */
646 if (!RTPathStartsWithRoot(strOverride.c_str()))
647 {
648 char pszPwd[RTPATH_MAX];
649 vrc = RTPathGetCurrent(pszPwd, RTPATH_MAX);
650 if (RT_SUCCESS(vrc))
651 strOverride = Utf8Str(pszPwd).append(RTPATH_SLASH).append(strOverride);
652 }
653 }
654 bstrFinalValue = strOverride;
655 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
656 a,
657 aOvfValues[a],
658 bstrFinalValue.raw(),
659 aExtraConfigValues[a]);
660 }
661#if 0 /* Changing the controller is fully valid, but the current design on how
662 the params are evaluated here doesn't allow two parameter for one
663 unit. The target disk path is more important I leave it for future
664 improvments. */
665 Utf8StrFmt strTypeArg("controller%u", a);
666 if (findArgValue(strOverride, pmapArgs, strTypeArg))
667 {
668 // strOverride now has the controller index as a number, but we
669 // need a "controller=X" format string
670 strOverride = Utf8StrFmt("controller=%s", strOverride.c_str());
671 Bstr bstrExtraConfigValue = strOverride;
672 bstrExtraConfigValue.detachTo(&aExtraConfigValues[a]);
673 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
674 a,
675 aOvfValues[a],
676 aVboxValues[a],
677 aExtraConfigValues[a]);
678 }
679#endif
680 else
681 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls"
682 "\n (change target path with \"--vsys %u --unit %u --disk path\";"
683 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
684 a,
685 aOvfValues[a],
686 aVboxValues[a],
687 aExtraConfigValues[a],
688 i, a, i, a);
689 }
690 break;
691
692 case VirtualSystemDescriptionType_CDROM:
693 if (fIgnoreThis)
694 {
695 RTPrintf("%2u: CD-ROM -- disabled\n",
696 a);
697 aEnabled[a] = false;
698 }
699 else
700 RTPrintf("%2u: CD-ROM"
701 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
702 a, i, a);
703 break;
704
705 case VirtualSystemDescriptionType_Floppy:
706 if (fIgnoreThis)
707 {
708 RTPrintf("%2u: Floppy -- disabled\n",
709 a);
710 aEnabled[a] = false;
711 }
712 else
713 RTPrintf("%2u: Floppy"
714 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
715 a, i, a);
716 break;
717
718 case VirtualSystemDescriptionType_NetworkAdapter:
719 RTPrintf("%2u: Network adapter: orig %ls, config %ls, extra %ls\n", // @todo implement once we have a plan for the back-end
720 a,
721 aOvfValues[a],
722 aVboxValues[a],
723 aExtraConfigValues[a]);
724 break;
725
726 case VirtualSystemDescriptionType_USBController:
727 if (fIgnoreThis)
728 {
729 RTPrintf("%2u: USB controller -- disabled\n",
730 a);
731 aEnabled[a] = false;
732 }
733 else
734 RTPrintf("%2u: USB controller"
735 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
736 a, i, a);
737 break;
738
739 case VirtualSystemDescriptionType_SoundCard:
740 if (fIgnoreThis)
741 {
742 RTPrintf("%2u: Sound card \"%ls\" -- disabled\n",
743 a,
744 aOvfValues[a]);
745 aEnabled[a] = false;
746 }
747 else
748 RTPrintf("%2u: Sound card (appliance expects \"%ls\", can change on import)"
749 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
750 a,
751 aOvfValues[a],
752 i,
753 a);
754 break;
755 }
756
757 bstrFinalValue.detachTo(&aFinalValues[a]);
758 }
759
760 if (fExecute)
761 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
762 SetFinalValues(ComSafeArrayAsInParam(aEnabled),
763 ComSafeArrayAsInParam(aFinalValues),
764 ComSafeArrayAsInParam(aExtraConfigValues)));
765
766 } // for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
767
768 if (cLicensesInTheWay == 1)
769 RTMsgError("Cannot import until the license agreement listed above is accepted.");
770 else if (cLicensesInTheWay > 1)
771 RTMsgError("Cannot import until the %c license agreements listed above are accepted.", cLicensesInTheWay);
772
773 if (!cLicensesInTheWay && fExecute)
774 {
775 // go!
776 ComPtr<IProgress> progress;
777 CHECK_ERROR_BREAK(pAppliance,
778 ImportMachines(ComSafeArrayAsInParam(options), progress.asOutParam()));
779
780 rc = showProgress(progress);
781 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance import failed"), RTEXITCODE_FAILURE);
782
783 if (SUCCEEDED(rc))
784 RTPrintf("Successfully imported the appliance.\n");
785 }
786 } // end if (aVirtualSystemDescriptions.size() > 0)
787 } while (0);
788
789 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
790}
791
792static const RTGETOPTDEF g_aExportOptions[]
793 = {
794 { "--output", 'o', RTGETOPT_REQ_STRING },
795 { "--legacy09", 'l', RTGETOPT_REQ_NOTHING },
796 { "--ovf09", 'l', RTGETOPT_REQ_NOTHING },
797 { "--ovf10", '1', RTGETOPT_REQ_NOTHING },
798 { "--ovf20", '2', RTGETOPT_REQ_NOTHING },
799 { "--manifest", 'm', RTGETOPT_REQ_NOTHING },
800 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
801 { "--product", 'p', RTGETOPT_REQ_STRING },
802 { "--producturl", 'P', RTGETOPT_REQ_STRING },
803 { "--vendor", 'd', RTGETOPT_REQ_STRING },
804 { "--vendorurl", 'D', RTGETOPT_REQ_STRING },
805 { "--version", 'v', 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 std::list< ComPtr<IMachine> > llMachines;
818
819 uint32_t ulCurVsys = (uint32_t)-1;
820 // for each --vsys X command, maintain a map of command line items
821 ArgsMapsMap mapArgsMapsPerVsys;
822 do
823 {
824 int c;
825
826 RTGETOPTUNION ValueUnion;
827 RTGETOPTSTATE GetState;
828 // start at 0 because main() has hacked both the argc and argv given to us
829 RTGetOptInit(&GetState, a->argc, a->argv, g_aExportOptions,
830 RT_ELEMENTS(g_aExportOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
831
832 Utf8Str strProductUrl;
833 while ((c = RTGetOpt(&GetState, &ValueUnion)))
834 {
835 switch (c)
836 {
837 case 'o': // --output
838 if (strOutputFile.length())
839 return errorSyntax(USAGE_EXPORTAPPLIANCE, "You can only specify --output once.");
840 else
841 strOutputFile = ValueUnion.psz;
842 break;
843
844 case 'l': // --legacy09/--ovf09
845 strOvfFormat = "ovf-0.9";
846 break;
847
848 case '1': // --ovf10
849 strOvfFormat = "ovf-1.0";
850 break;
851
852 case '2': // --ovf20
853 strOvfFormat = "ovf-2.0";
854 break;
855
856 case 'm': // --manifest
857 fManifest = true;
858 break;
859
860 case 's': // --vsys
861 ulCurVsys = ValueUnion.u32;
862 break;
863
864 case 'p': // --product
865 if (ulCurVsys == (uint32_t)-1)
866 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
867 mapArgsMapsPerVsys[ulCurVsys]["product"] = ValueUnion.psz;
868 break;
869
870 case 'P': // --producturl
871 if (ulCurVsys == (uint32_t)-1)
872 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
873 mapArgsMapsPerVsys[ulCurVsys]["producturl"] = ValueUnion.psz;
874 break;
875
876 case 'd': // --vendor
877 if (ulCurVsys == (uint32_t)-1)
878 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
879 mapArgsMapsPerVsys[ulCurVsys]["vendor"] = ValueUnion.psz;
880 break;
881
882 case 'D': // --vendorurl
883 if (ulCurVsys == (uint32_t)-1)
884 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
885 mapArgsMapsPerVsys[ulCurVsys]["vendorurl"] = ValueUnion.psz;
886 break;
887
888 case 'v': // --version
889 if (ulCurVsys == (uint32_t)-1)
890 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
891 mapArgsMapsPerVsys[ulCurVsys]["version"] = ValueUnion.psz;
892 break;
893
894 case 'e': // --eula
895 if (ulCurVsys == (uint32_t)-1)
896 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
897 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
898 break;
899
900 case 'E': // --eulafile
901 if (ulCurVsys == (uint32_t)-1)
902 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
903 mapArgsMapsPerVsys[ulCurVsys]["eulafile"] = ValueUnion.psz;
904 break;
905
906 case VINF_GETOPT_NOT_OPTION:
907 {
908 Utf8Str strMachine(ValueUnion.psz);
909 // must be machine: try UUID or name
910 ComPtr<IMachine> machine;
911 CHECK_ERROR_BREAK(a->virtualBox, FindMachine(Bstr(strMachine).raw(),
912 machine.asOutParam()));
913 if (machine)
914 llMachines.push_back(machine);
915 break;
916 }
917
918 default:
919 if (c > 0)
920 {
921 if (RT_C_IS_GRAPH(c))
922 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: -%c", c);
923 else
924 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: %i", c);
925 }
926 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
927 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unknown option: %s", ValueUnion.psz);
928 else if (ValueUnion.pDef)
929 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
930 else
931 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%Rrs", c);
932 }
933
934 if (FAILED(rc))
935 break;
936 }
937
938 if (FAILED(rc))
939 break;
940
941 if (llMachines.size() == 0)
942 return errorSyntax(USAGE_EXPORTAPPLIANCE, "At least one machine must be specified with the export command.");
943 if (!strOutputFile.length())
944 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Missing --output argument with export command.");
945
946 // match command line arguments with the machines count
947 // this is only to sort out invalid indices at this time
948 ArgsMapsMap::const_iterator it;
949 for (it = mapArgsMapsPerVsys.begin();
950 it != mapArgsMapsPerVsys.end();
951 ++it)
952 {
953 uint32_t ulVsys = it->first;
954 if (ulVsys >= llMachines.size())
955 return errorSyntax(USAGE_EXPORTAPPLIANCE,
956 "Invalid index %RI32 with -vsys option; you specified only %zu virtual system(s).",
957 ulVsys, llMachines.size());
958 }
959
960 ComPtr<IAppliance> pAppliance;
961 CHECK_ERROR_BREAK(a->virtualBox, CreateAppliance(pAppliance.asOutParam()));
962
963 char *pszAbsFilePath = 0;
964 if (strOutputFile.startsWith("S3://", RTCString::CaseInsensitive) ||
965 strOutputFile.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
966 strOutputFile.startsWith("webdav://", RTCString::CaseInsensitive))
967 pszAbsFilePath = RTStrDup(strOutputFile.c_str());
968 else
969 pszAbsFilePath = RTPathAbsDup(strOutputFile.c_str());
970
971 std::list< ComPtr<IMachine> >::iterator itM;
972 uint32_t i=0;
973 for (itM = llMachines.begin();
974 itM != llMachines.end();
975 ++itM, ++i)
976 {
977 ComPtr<IMachine> pMachine = *itM;
978 ComPtr<IVirtualSystemDescription> pVSD;
979 CHECK_ERROR_BREAK(pMachine, Export(pAppliance, Bstr(pszAbsFilePath).raw(), pVSD.asOutParam()));
980 // Add additional info to the virtual system description if the user wants so
981 ArgsMap *pmapArgs = NULL;
982 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
983 if (itm != mapArgsMapsPerVsys.end())
984 pmapArgs = &itm->second;
985 if (pmapArgs)
986 {
987 ArgsMap::iterator itD;
988 for (itD = pmapArgs->begin();
989 itD != pmapArgs->end();
990 ++itD)
991 {
992 if (itD->first == "product")
993 pVSD->AddDescription(VirtualSystemDescriptionType_Product,
994 Bstr(itD->second).raw(),
995 Bstr(itD->second).raw());
996 else if (itD->first == "producturl")
997 pVSD->AddDescription(VirtualSystemDescriptionType_ProductUrl,
998 Bstr(itD->second).raw(),
999 Bstr(itD->second).raw());
1000 else if (itD->first == "vendor")
1001 pVSD->AddDescription(VirtualSystemDescriptionType_Vendor,
1002 Bstr(itD->second).raw(),
1003 Bstr(itD->second).raw());
1004 else if (itD->first == "vendorurl")
1005 pVSD->AddDescription(VirtualSystemDescriptionType_VendorUrl,
1006 Bstr(itD->second).raw(),
1007 Bstr(itD->second).raw());
1008 else if (itD->first == "version")
1009 pVSD->AddDescription(VirtualSystemDescriptionType_Version,
1010 Bstr(itD->second).raw(),
1011 Bstr(itD->second).raw());
1012 else if (itD->first == "eula")
1013 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1014 Bstr(itD->second).raw(),
1015 Bstr(itD->second).raw());
1016 else if (itD->first == "eulafile")
1017 {
1018 Utf8Str strContent;
1019 void *pvFile;
1020 size_t cbFile;
1021 int irc = RTFileReadAll(itD->second.c_str(), &pvFile, &cbFile);
1022 if (RT_SUCCESS(irc))
1023 {
1024 Bstr bstrContent((char*)pvFile);
1025 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1026 bstrContent.raw(),
1027 bstrContent.raw());
1028 RTFileReadAllFree(pvFile, cbFile);
1029 }
1030 else
1031 {
1032 RTMsgError("Cannot read license file \"%s\" which should be included in the virtual system %u.",
1033 itD->second.c_str(), i);
1034 return 1;
1035 }
1036 }
1037 }
1038 }
1039 }
1040
1041 if (FAILED(rc))
1042 break;
1043
1044 ComPtr<IProgress> progress;
1045 CHECK_ERROR_BREAK(pAppliance, Write(Bstr(strOvfFormat).raw(),
1046 fManifest,
1047 Bstr(pszAbsFilePath).raw(),
1048 progress.asOutParam()));
1049 RTStrFree(pszAbsFilePath);
1050
1051 rc = showProgress(progress);
1052 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance write failed"), RTEXITCODE_FAILURE);
1053
1054 if (SUCCEEDED(rc))
1055 RTPrintf("Successfully exported %d machine(s).\n", llMachines.size());
1056
1057 } while (0);
1058
1059 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1060}
1061
1062#endif /* !VBOX_ONLY_DOCS */
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use