VirtualBox

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

Last change on this file since 84589 was 84534, checked in by vboxsync, 5 years ago

bugref:9745. Added support of controller VirtioSCSI.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 122.9 KB
Line 
1/* $Id: VBoxManageAppliance.cpp 84534 2020-05-26 10:48:28Z vboxsync $ */
2/** @file
3 * VBoxManage - The appliance-related commands.
4 */
5
6/*
7 * Copyright (C) 2009-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#ifndef VBOX_ONLY_DOCS
19
20
21/*********************************************************************************************************************************
22* Header Files *
23*********************************************************************************************************************************/
24#ifndef VBOX_ONLY_DOCS
25#include <VBox/com/com.h>
26#include <VBox/com/string.h>
27#include <VBox/com/Guid.h>
28#include <VBox/com/array.h>
29#include <VBox/com/ErrorInfo.h>
30#include <VBox/com/errorprint.h>
31#include <VBox/com/VirtualBox.h>
32#include <VBox/log.h>
33#include <VBox/param.h>
34
35#include <VBox/version.h>
36#include <revision-generated.h> /* VBOX_SVN_REV - PCH prevents putting it in DEFS. */
37
38#include <list>
39#include <map>
40#endif /* !VBOX_ONLY_DOCS */
41
42#include <iprt/getopt.h>
43#include <iprt/ctype.h>
44#include <iprt/path.h>
45#include <iprt/file.h>
46#include <iprt/err.h>
47#include <iprt/zip.h>
48#include <iprt/stream.h>
49#include <iprt/vfs.h>
50#include <iprt/manifest.h>
51#include <iprt/crypto/digest.h>
52#include <iprt/crypto/x509.h>
53#include <iprt/crypto/pkcs7.h>
54#include <iprt/crypto/store.h>
55#include <iprt/crypto/spc.h>
56#include <iprt/crypto/key.h>
57#include <iprt/crypto/pkix.h>
58
59
60
61#include "VBoxManage.h"
62using namespace com;
63
64
65// funcs
66///////////////////////////////////////////////////////////////////////////////
67
68typedef std::map<Utf8Str, Utf8Str> ArgsMap; // pairs of strings like "vmname" => "newvmname"
69typedef std::map<uint32_t, ArgsMap> ArgsMapsMap; // map of maps, one for each virtual system, sorted by index
70
71typedef std::map<uint32_t, bool> IgnoresMap; // pairs of numeric description entry indices
72typedef std::map<uint32_t, IgnoresMap> IgnoresMapsMap; // map of maps, one for each virtual system, sorted by index
73
74static bool findArgValue(Utf8Str &strOut,
75 ArgsMap *pmapArgs,
76 const Utf8Str &strKey)
77{
78 if (pmapArgs)
79 {
80 ArgsMap::iterator it;
81 it = pmapArgs->find(strKey);
82 if (it != pmapArgs->end())
83 {
84 strOut = it->second;
85 pmapArgs->erase(it);
86 return true;
87 }
88 }
89
90 return false;
91}
92
93static int parseImportOptions(const char *psz, com::SafeArray<ImportOptions_T> *options)
94{
95 int rc = VINF_SUCCESS;
96 while (psz && *psz && RT_SUCCESS(rc))
97 {
98 size_t len;
99 const char *pszComma = strchr(psz, ',');
100 if (pszComma)
101 len = pszComma - psz;
102 else
103 len = strlen(psz);
104 if (len > 0)
105 {
106 if (!RTStrNICmp(psz, "KeepAllMACs", len))
107 options->push_back(ImportOptions_KeepAllMACs);
108 else if (!RTStrNICmp(psz, "KeepNATMACs", len))
109 options->push_back(ImportOptions_KeepNATMACs);
110 else if (!RTStrNICmp(psz, "ImportToVDI", len))
111 options->push_back(ImportOptions_ImportToVDI);
112 else
113 rc = VERR_PARSE_ERROR;
114 }
115 if (pszComma)
116 psz += len + 1;
117 else
118 psz += len;
119 }
120
121 return rc;
122}
123
124static const RTGETOPTDEF g_aImportApplianceOptions[] =
125{
126 { "--dry-run", 'n', RTGETOPT_REQ_NOTHING },
127 { "-dry-run", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
128 { "--dryrun", 'n', RTGETOPT_REQ_NOTHING },
129 { "-dryrun", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
130 { "--detailed-progress", 'P', RTGETOPT_REQ_NOTHING },
131 { "-detailed-progress", 'P', RTGETOPT_REQ_NOTHING }, // deprecated
132 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
133 { "-vsys", 's', RTGETOPT_REQ_UINT32 }, // deprecated
134 { "--ostype", 'o', RTGETOPT_REQ_STRING },
135 { "-ostype", 'o', RTGETOPT_REQ_STRING }, // deprecated
136 { "--vmname", 'V', RTGETOPT_REQ_STRING },
137 { "-vmname", 'V', RTGETOPT_REQ_STRING }, // deprecated
138 { "--settingsfile", 'S', RTGETOPT_REQ_STRING },
139 { "--basefolder", 'p', RTGETOPT_REQ_STRING },
140 { "--group", 'g', RTGETOPT_REQ_STRING },
141 { "--memory", 'm', RTGETOPT_REQ_STRING },
142 { "-memory", 'm', RTGETOPT_REQ_STRING }, // deprecated
143 { "--cpus", 'c', RTGETOPT_REQ_STRING },
144 { "--description", 'd', RTGETOPT_REQ_STRING },
145 { "--eula", 'L', RTGETOPT_REQ_STRING },
146 { "-eula", 'L', RTGETOPT_REQ_STRING }, // deprecated
147 { "--unit", 'u', RTGETOPT_REQ_UINT32 },
148 { "-unit", 'u', RTGETOPT_REQ_UINT32 }, // deprecated
149 { "--ignore", 'x', RTGETOPT_REQ_NOTHING },
150 { "-ignore", 'x', RTGETOPT_REQ_NOTHING }, // deprecated
151 { "--scsitype", 'T', RTGETOPT_REQ_UINT32 },
152 { "-scsitype", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
153 { "--type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
154 { "-type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
155#if 0 /* Changing the controller is fully valid, but the current design on how
156 the params are evaluated here doesn't allow two parameter for one
157 unit. The target disk path is more important. I leave it for future
158 improvments. */
159 { "--controller", 'C', RTGETOPT_REQ_STRING },
160#endif
161 { "--disk", 'D', RTGETOPT_REQ_STRING },
162 { "--options", 'O', RTGETOPT_REQ_STRING },
163
164 { "--cloud", 'j', RTGETOPT_REQ_NOTHING},
165 { "--cloudprofile", 'k', RTGETOPT_REQ_STRING },
166 { "--cloudinstanceid", 'l', RTGETOPT_REQ_STRING },
167 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING }
168};
169
170enum actionType
171{
172 NOT_SET, LOCAL, CLOUD
173} actionType;
174
175RTEXITCODE handleImportAppliance(HandlerArg *arg)
176{
177 HRESULT rc = S_OK;
178 bool fCloud = false; // the default
179 actionType = NOT_SET;
180 Utf8Str strOvfFilename;
181 bool fExecute = true; // if true, then we actually do the import
182 com::SafeArray<ImportOptions_T> options;
183 uint32_t ulCurVsys = (uint32_t)-1;
184 uint32_t ulCurUnit = (uint32_t)-1;
185 // for each --vsys X command, maintain a map of command line items
186 // (we'll parse them later after interpreting the OVF, when we can
187 // actually check whether they make sense semantically)
188 ArgsMapsMap mapArgsMapsPerVsys;
189 IgnoresMapsMap mapIgnoresMapsPerVsys;
190
191 int c;
192 RTGETOPTUNION ValueUnion;
193 RTGETOPTSTATE GetState;
194 // start at 0 because main() has hacked both the argc and argv given to us
195 RTGetOptInit(&GetState, arg->argc, arg->argv, g_aImportApplianceOptions, RT_ELEMENTS(g_aImportApplianceOptions),
196 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
197 while ((c = RTGetOpt(&GetState, &ValueUnion)))
198 {
199 switch (c)
200 {
201 case 'n': // --dry-run
202 fExecute = false;
203 break;
204
205 case 'P': // --detailed-progress
206 g_fDetailedProgress = true;
207 break;
208
209 case 's': // --vsys
210 if (fCloud == false && actionType == NOT_SET)
211 actionType = LOCAL;
212
213 if (actionType != LOCAL)
214 return errorSyntax(USAGE_EXPORTAPPLIANCE,
215 "Option \"%s\" can't be used together with \"--cloud\" argument.",
216 GetState.pDef->pszLong);
217
218 ulCurVsys = ValueUnion.u32;
219 ulCurUnit = (uint32_t)-1;
220 break;
221
222 case 'o': // --ostype
223 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
224 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
225 mapArgsMapsPerVsys[ulCurVsys]["ostype"] = ValueUnion.psz;
226 break;
227
228 case 'V': // --vmname
229 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
230 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
231 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
232 break;
233
234 case 'S': // --settingsfile
235 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
236 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
237 mapArgsMapsPerVsys[ulCurVsys]["settingsfile"] = ValueUnion.psz;
238 break;
239
240 case 'p': // --basefolder
241 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
242 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
243 mapArgsMapsPerVsys[ulCurVsys]["basefolder"] = ValueUnion.psz;
244 break;
245
246 case 'g': // --group
247 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
248 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
249 mapArgsMapsPerVsys[ulCurVsys]["group"] = ValueUnion.psz;
250 break;
251
252 case 'd': // --description
253 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
254 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
255 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
256 break;
257
258 case 'L': // --eula
259 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
260 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
261 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
262 break;
263
264 case 'm': // --memory
265 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
266 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
267 mapArgsMapsPerVsys[ulCurVsys]["memory"] = ValueUnion.psz;
268 break;
269
270 case 'c': // --cpus
271 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
272 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
273 mapArgsMapsPerVsys[ulCurVsys]["cpus"] = ValueUnion.psz;
274 break;
275
276 case 'u': // --unit
277 ulCurUnit = ValueUnion.u32;
278 break;
279
280 case 'x': // --ignore
281 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
282 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
283 if (ulCurUnit == (uint32_t)-1)
284 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
285 mapIgnoresMapsPerVsys[ulCurVsys][ulCurUnit] = true;
286 break;
287
288 case 'T': // --scsitype
289 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
290 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
291 if (ulCurUnit == (uint32_t)-1)
292 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
293 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("scsitype%u", ulCurUnit)] = ValueUnion.psz;
294 break;
295
296 case 'C': // --controller
297 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
298 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
299 if (ulCurUnit == (uint32_t)-1)
300 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
301 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("controller%u", ulCurUnit)] = ValueUnion.psz;
302 break;
303
304 case 'D': // --disk
305 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
306 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
307 if (ulCurUnit == (uint32_t)-1)
308 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
309 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("disk%u", ulCurUnit)] = ValueUnion.psz;
310 break;
311
312 case 'O': // --options
313 if (RT_FAILURE(parseImportOptions(ValueUnion.psz, &options)))
314 return errorArgument("Invalid import options '%s'\n", ValueUnion.psz);
315 break;
316
317 /*--cloud and --vsys are orthogonal, only one must be presented*/
318 case 'j': // --cloud
319 if (fCloud == false && actionType == NOT_SET)
320 {
321 fCloud = true;
322 actionType = CLOUD;
323 }
324
325 if (actionType != CLOUD)
326 return errorSyntax(USAGE_IMPORTAPPLIANCE,
327 "Option \"%s\" can't be used together with \"--vsys\" argument.",
328 GetState.pDef->pszLong);
329
330 ulCurVsys = 0;
331 break;
332
333 /* Cloud export settings */
334 case 'k': // --cloudprofile
335 if (actionType != CLOUD)
336 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
337 GetState.pDef->pszLong);
338 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
339 break;
340
341 case 'l': // --cloudinstanceid
342 if (actionType != CLOUD)
343 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
344 GetState.pDef->pszLong);
345 mapArgsMapsPerVsys[ulCurVsys]["cloudinstanceid"] = ValueUnion.psz;
346 break;
347
348 case 'B': // --cloudbucket
349 if (actionType != CLOUD)
350 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
351 GetState.pDef->pszLong);
352 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
353 break;
354
355 case VINF_GETOPT_NOT_OPTION:
356 if (strOvfFilename.isEmpty())
357 strOvfFilename = ValueUnion.psz;
358 else
359 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid parameter '%s'", ValueUnion.psz);
360 break;
361
362 default:
363 if (c > 0)
364 {
365 if (RT_C_IS_PRINT(c))
366 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option -%c", c);
367 else
368 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option case %i", c);
369 }
370 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
371 return errorSyntax(USAGE_IMPORTAPPLIANCE, "unknown option: %s\n", ValueUnion.psz);
372 else if (ValueUnion.pDef)
373 return errorSyntax(USAGE_IMPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
374 else
375 return errorSyntax(USAGE_IMPORTAPPLIANCE, "error: %Rrs", c);
376 }
377 }
378
379 /* Last check after parsing all arguments */
380 if (strOvfFilename.isNotEmpty())
381 {
382 if (actionType == NOT_SET)
383 {
384 if (fCloud)
385 actionType = CLOUD;
386 else
387 actionType = LOCAL;
388 }
389 }
390 else
391 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Not enough arguments for \"import\" command.");
392
393 do
394 {
395 ComPtr<IAppliance> pAppliance;
396 CHECK_ERROR_BREAK(arg->virtualBox, CreateAppliance(pAppliance.asOutParam()));
397 //in the case of Cloud, append the instance id here because later it's harder to do
398 if (actionType == CLOUD)
399 {
400 try
401 {
402 /* Check presence of cloudprofile and cloudinstanceid in the map.
403 * If there isn't the exception is triggered. It's standard std:map logic.*/
404 ArgsMap a = mapArgsMapsPerVsys[ulCurVsys];
405 (void)a.at("cloudprofile");
406 (void)a.at("cloudinstanceid");
407 }
408 catch (...)
409 {
410 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Not enough arguments for import from the Cloud.");
411 }
412
413 strOvfFilename.append(mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"]);
414 strOvfFilename.append("/");
415 strOvfFilename.append(mapArgsMapsPerVsys[ulCurVsys]["cloudinstanceid"]);
416 }
417
418 char *pszAbsFilePath;
419 if (strOvfFilename.startsWith("S3://", RTCString::CaseInsensitive) ||
420 strOvfFilename.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
421 strOvfFilename.startsWith("webdav://", RTCString::CaseInsensitive) ||
422 strOvfFilename.startsWith("OCI://", RTCString::CaseInsensitive))
423 pszAbsFilePath = RTStrDup(strOvfFilename.c_str());
424 else
425 pszAbsFilePath = RTPathAbsDup(strOvfFilename.c_str());
426
427 ComPtr<IProgress> progressRead;
428 CHECK_ERROR_BREAK(pAppliance, Read(Bstr(pszAbsFilePath).raw(),
429 progressRead.asOutParam()));
430 RTStrFree(pszAbsFilePath);
431
432 rc = showProgress(progressRead);
433 CHECK_PROGRESS_ERROR_RET(progressRead, ("Appliance read failed"), RTEXITCODE_FAILURE);
434
435 Bstr path; /* fetch the path, there is stuff like username/password removed if any */
436 CHECK_ERROR_BREAK(pAppliance, COMGETTER(Path)(path.asOutParam()));
437
438 size_t cVirtualSystemDescriptions = 0;
439 com::SafeIfaceArray<IVirtualSystemDescription> aVirtualSystemDescriptions;
440
441 if (actionType == LOCAL)
442 {
443 // call interpret(); this can yield both warnings and errors, so we need
444 // to tinker with the error info a bit
445 RTStrmPrintf(g_pStdErr, "Interpreting %ls...\n", path.raw());
446 rc = pAppliance->Interpret();
447 com::ErrorInfo info0(pAppliance, COM_IIDOF(IAppliance));
448
449 com::SafeArray<BSTR> aWarnings;
450 if (SUCCEEDED(pAppliance->GetWarnings(ComSafeArrayAsOutParam(aWarnings))))
451 {
452 size_t cWarnings = aWarnings.size();
453 for (unsigned i = 0; i < cWarnings; ++i)
454 {
455 Bstr bstrWarning(aWarnings[i]);
456 RTMsgWarning("%ls.", bstrWarning.raw());
457 }
458 }
459
460 if (FAILED(rc)) // during interpret, after printing warnings
461 {
462 com::GluePrintErrorInfo(info0);
463 com::GluePrintErrorContext("Interpret", __FILE__, __LINE__);
464 break;
465 }
466
467 RTStrmPrintf(g_pStdErr, "OK.\n");
468
469 // fetch all disks
470 com::SafeArray<BSTR> retDisks;
471 CHECK_ERROR_BREAK(pAppliance,
472 COMGETTER(Disks)(ComSafeArrayAsOutParam(retDisks)));
473 if (retDisks.size() > 0)
474 {
475 RTPrintf("Disks:\n");
476 for (unsigned i = 0; i < retDisks.size(); i++)
477 RTPrintf(" %ls\n", retDisks[i]);
478 RTPrintf("\n");
479 }
480
481 // fetch virtual system descriptions
482 CHECK_ERROR_BREAK(pAppliance,
483 COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions)));
484
485 cVirtualSystemDescriptions = aVirtualSystemDescriptions.size();
486
487 // match command line arguments with virtual system descriptions;
488 // this is only to sort out invalid indices at this time
489 ArgsMapsMap::const_iterator it;
490 for (it = mapArgsMapsPerVsys.begin();
491 it != mapArgsMapsPerVsys.end();
492 ++it)
493 {
494 uint32_t ulVsys = it->first;
495 if (ulVsys >= cVirtualSystemDescriptions)
496 return errorSyntax(USAGE_IMPORTAPPLIANCE,
497 "Invalid index %RI32 with -vsys option; the OVF contains only %zu virtual system(s).",
498 ulVsys, cVirtualSystemDescriptions);
499 }
500 }
501 else if (actionType == CLOUD)
502 {
503 /* In the Cloud case the call of interpret() isn't needed because there isn't any OVF XML file.
504 * All info is got from the Cloud and VSD is filled inside IAppliance::read(). */
505 // fetch virtual system descriptions
506 CHECK_ERROR_BREAK(pAppliance,
507 COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions)));
508
509 cVirtualSystemDescriptions = aVirtualSystemDescriptions.size();
510 }
511
512 uint32_t cLicensesInTheWay = 0;
513
514 // dump virtual system descriptions and match command-line arguments
515 if (cVirtualSystemDescriptions > 0)
516 {
517 for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
518 {
519 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
520 com::SafeArray<BSTR> aRefs;
521 com::SafeArray<BSTR> aOvfValues;
522 com::SafeArray<BSTR> aVBoxValues;
523 com::SafeArray<BSTR> aExtraConfigValues;
524 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
525 GetDescription(ComSafeArrayAsOutParam(retTypes),
526 ComSafeArrayAsOutParam(aRefs),
527 ComSafeArrayAsOutParam(aOvfValues),
528 ComSafeArrayAsOutParam(aVBoxValues),
529 ComSafeArrayAsOutParam(aExtraConfigValues)));
530
531 RTPrintf("Virtual system %u:\n", i);
532
533 // look up the corresponding command line options, if any
534 ArgsMap *pmapArgs = NULL;
535 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
536 if (itm != mapArgsMapsPerVsys.end())
537 pmapArgs = &itm->second;
538
539 // this collects the final values for setFinalValues()
540 com::SafeArray<BOOL> aEnabled(retTypes.size());
541 com::SafeArray<BSTR> aFinalValues(retTypes.size());
542
543 for (unsigned a = 0; a < retTypes.size(); ++a)
544 {
545 VirtualSystemDescriptionType_T t = retTypes[a];
546
547 Utf8Str strOverride;
548
549 Bstr bstrFinalValue = aVBoxValues[a];
550
551 bool fIgnoreThis = mapIgnoresMapsPerVsys[i][a];
552
553 aEnabled[a] = true;
554
555 switch (t)
556 {
557 case VirtualSystemDescriptionType_OS:
558 if (findArgValue(strOverride, pmapArgs, "ostype"))
559 {
560 bstrFinalValue = strOverride;
561 RTPrintf("%2u: OS type specified with --ostype: \"%ls\"\n",
562 a, bstrFinalValue.raw());
563 }
564 else
565 RTPrintf("%2u: Suggested OS type: \"%ls\""
566 "\n (change with \"--vsys %u --ostype <type>\"; use \"list ostypes\" to list all possible values)\n",
567 a, bstrFinalValue.raw(), i);
568 break;
569
570 case VirtualSystemDescriptionType_Name:
571 if (findArgValue(strOverride, pmapArgs, "vmname"))
572 {
573 bstrFinalValue = strOverride;
574 RTPrintf("%2u: VM name specified with --vmname: \"%ls\"\n",
575 a, bstrFinalValue.raw());
576 }
577 else
578 RTPrintf("%2u: Suggested VM name \"%ls\""
579 "\n (change with \"--vsys %u --vmname <name>\")\n",
580 a, bstrFinalValue.raw(), i);
581 break;
582
583 case VirtualSystemDescriptionType_Product:
584 RTPrintf("%2u: Product (ignored): %ls\n",
585 a, aVBoxValues[a]);
586 break;
587
588 case VirtualSystemDescriptionType_ProductUrl:
589 RTPrintf("%2u: ProductUrl (ignored): %ls\n",
590 a, aVBoxValues[a]);
591 break;
592
593 case VirtualSystemDescriptionType_Vendor:
594 RTPrintf("%2u: Vendor (ignored): %ls\n",
595 a, aVBoxValues[a]);
596 break;
597
598 case VirtualSystemDescriptionType_VendorUrl:
599 RTPrintf("%2u: VendorUrl (ignored): %ls\n",
600 a, aVBoxValues[a]);
601 break;
602
603 case VirtualSystemDescriptionType_Version:
604 RTPrintf("%2u: Version (ignored): %ls\n",
605 a, aVBoxValues[a]);
606 break;
607
608 case VirtualSystemDescriptionType_Description:
609 if (findArgValue(strOverride, pmapArgs, "description"))
610 {
611 bstrFinalValue = strOverride;
612 RTPrintf("%2u: Description specified with --description: \"%ls\"\n",
613 a, bstrFinalValue.raw());
614 }
615 else
616 RTPrintf("%2u: Description \"%ls\""
617 "\n (change with \"--vsys %u --description <desc>\")\n",
618 a, bstrFinalValue.raw(), i);
619 break;
620
621 case VirtualSystemDescriptionType_License:
622 ++cLicensesInTheWay;
623 if (findArgValue(strOverride, pmapArgs, "eula"))
624 {
625 if (strOverride == "show")
626 {
627 RTPrintf("%2u: End-user license agreement"
628 "\n (accept with \"--vsys %u --eula accept\"):"
629 "\n\n%ls\n\n",
630 a, i, bstrFinalValue.raw());
631 }
632 else if (strOverride == "accept")
633 {
634 RTPrintf("%2u: End-user license agreement (accepted)\n",
635 a);
636 --cLicensesInTheWay;
637 }
638 else
639 return errorSyntax(USAGE_IMPORTAPPLIANCE,
640 "Argument to --eula must be either \"show\" or \"accept\".");
641 }
642 else
643 RTPrintf("%2u: End-user license agreement"
644 "\n (display with \"--vsys %u --eula show\";"
645 "\n accept with \"--vsys %u --eula accept\")\n",
646 a, i, i);
647 break;
648
649 case VirtualSystemDescriptionType_CPU:
650 if (findArgValue(strOverride, pmapArgs, "cpus"))
651 {
652 uint32_t cCPUs;
653 if ( strOverride.toInt(cCPUs) == VINF_SUCCESS
654 && cCPUs >= VMM_MIN_CPU_COUNT
655 && cCPUs <= VMM_MAX_CPU_COUNT
656 )
657 {
658 bstrFinalValue = strOverride;
659 RTPrintf("%2u: No. of CPUs specified with --cpus: %ls\n",
660 a, bstrFinalValue.raw());
661 }
662 else
663 return errorSyntax(USAGE_IMPORTAPPLIANCE,
664 "Argument to --cpus option must be a number greater than %d and less than %d.",
665 VMM_MIN_CPU_COUNT - 1, VMM_MAX_CPU_COUNT + 1);
666 }
667 else
668 RTPrintf("%2u: Number of CPUs: %ls\n (change with \"--vsys %u --cpus <n>\")\n",
669 a, bstrFinalValue.raw(), i);
670 break;
671
672 case VirtualSystemDescriptionType_Memory:
673 {
674 if (findArgValue(strOverride, pmapArgs, "memory"))
675 {
676 uint32_t ulMemMB;
677 if (VINF_SUCCESS == strOverride.toInt(ulMemMB))
678 {
679 bstrFinalValue = strOverride;
680 RTPrintf("%2u: Guest memory specified with --memory: %ls MB\n",
681 a, bstrFinalValue.raw());
682 }
683 else
684 return errorSyntax(USAGE_IMPORTAPPLIANCE,
685 "Argument to --memory option must be a non-negative number.");
686 }
687 else
688 RTPrintf("%2u: Guest memory: %ls MB\n (change with \"--vsys %u --memory <MB>\")\n",
689 a, bstrFinalValue.raw(), i);
690 break;
691 }
692
693 case VirtualSystemDescriptionType_HardDiskControllerIDE:
694 if (fIgnoreThis)
695 {
696 RTPrintf("%2u: IDE controller, type %ls -- disabled\n",
697 a,
698 aVBoxValues[a]);
699 aEnabled[a] = false;
700 }
701 else
702 RTPrintf("%2u: IDE controller, type %ls"
703 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
704 a,
705 aVBoxValues[a],
706 i, a);
707 break;
708
709 case VirtualSystemDescriptionType_HardDiskControllerSATA:
710 if (fIgnoreThis)
711 {
712 RTPrintf("%2u: SATA controller, type %ls -- disabled\n",
713 a,
714 aVBoxValues[a]);
715 aEnabled[a] = false;
716 }
717 else
718 RTPrintf("%2u: SATA controller, type %ls"
719 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
720 a,
721 aVBoxValues[a],
722 i, a);
723 break;
724
725 case VirtualSystemDescriptionType_HardDiskControllerSAS:
726 if (fIgnoreThis)
727 {
728 RTPrintf("%2u: SAS controller, type %ls -- disabled\n",
729 a,
730 aVBoxValues[a]);
731 aEnabled[a] = false;
732 }
733 else
734 RTPrintf("%2u: SAS controller, type %ls"
735 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
736 a,
737 aVBoxValues[a],
738 i, a);
739 break;
740
741 case VirtualSystemDescriptionType_HardDiskControllerSCSI:
742 if (fIgnoreThis)
743 {
744 RTPrintf("%2u: SCSI controller, type %ls -- disabled\n",
745 a,
746 aVBoxValues[a]);
747 aEnabled[a] = false;
748 }
749 else
750 {
751 Utf8StrFmt strTypeArg("scsitype%u", a);
752 if (findArgValue(strOverride, pmapArgs, strTypeArg))
753 {
754 bstrFinalValue = strOverride;
755 RTPrintf("%2u: SCSI controller, type set with --unit %u --scsitype: \"%ls\"\n",
756 a,
757 a,
758 bstrFinalValue.raw());
759 }
760 else
761 RTPrintf("%2u: SCSI controller, type %ls"
762 "\n (change with \"--vsys %u --unit %u --scsitype {BusLogic|LsiLogic}\";"
763 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
764 a,
765 aVBoxValues[a],
766 i, a, i, a);
767 }
768 break;
769
770 case VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI:
771 if (fIgnoreThis)
772 {
773 RTPrintf("%2u: VirtioSCSI controller, type %ls -- disabled\n",
774 a,
775 aVBoxValues[a]);
776 aEnabled[a] = false;
777 }
778 else
779 RTPrintf("%2u: VirtioSCSI controller, type %ls"
780 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
781 a,
782 aVBoxValues[a],
783 i, a);
784 break;
785
786 case VirtualSystemDescriptionType_HardDiskImage:
787 if (fIgnoreThis)
788 {
789 RTPrintf("%2u: Hard disk image: source image=%ls -- disabled\n",
790 a,
791 aOvfValues[a]);
792 aEnabled[a] = false;
793 }
794 else
795 {
796 Utf8StrFmt strTypeArg("disk%u", a);
797 RTCList<ImportOptions_T> optionsList = options.toList();
798
799 bstrFinalValue = aVBoxValues[a];
800
801 if (findArgValue(strOverride, pmapArgs, strTypeArg))
802 {
803 if (!optionsList.contains(ImportOptions_ImportToVDI))
804 {
805 RTUUID uuid;
806 /* Check if this is a uuid. If so, don't touch. */
807 int vrc = RTUuidFromStr(&uuid, strOverride.c_str());
808 if (vrc != VINF_SUCCESS)
809 {
810 /* Make the path absolute. */
811 if (!RTPathStartsWithRoot(strOverride.c_str()))
812 {
813 char pszPwd[RTPATH_MAX];
814 vrc = RTPathGetCurrent(pszPwd, RTPATH_MAX);
815 if (RT_SUCCESS(vrc))
816 strOverride = Utf8Str(pszPwd).append(RTPATH_SLASH).append(strOverride);
817 }
818 }
819 bstrFinalValue = strOverride;
820 }
821 else
822 {
823 //print some error about incompatible command-line arguments
824 return errorSyntax(USAGE_IMPORTAPPLIANCE,
825 "Option --ImportToVDI shall not be used together with "
826 "manually set target path.");
827
828 }
829
830 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
831 a,
832 aOvfValues[a],
833 bstrFinalValue.raw(),
834 aExtraConfigValues[a]);
835 }
836#if 0 /* Changing the controller is fully valid, but the current design on how
837 the params are evaluated here doesn't allow two parameter for one
838 unit. The target disk path is more important I leave it for future
839 improvments. */
840 Utf8StrFmt strTypeArg("controller%u", a);
841 if (findArgValue(strOverride, pmapArgs, strTypeArg))
842 {
843 // strOverride now has the controller index as a number, but we
844 // need a "controller=X" format string
845 strOverride = Utf8StrFmt("controller=%s", strOverride.c_str());
846 Bstr bstrExtraConfigValue = strOverride;
847 bstrExtraConfigValue.detachTo(&aExtraConfigValues[a]);
848 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
849 a,
850 aOvfValues[a],
851 aVBoxValues[a],
852 aExtraConfigValues[a]);
853 }
854#endif
855 else
856 {
857 strOverride = aVBoxValues[a];
858
859 /*
860 * Current solution isn't optimal.
861 * Better way is to provide API call for function
862 * Appliance::i_findMediumFormatFromDiskImage()
863 * and creating one new function which returns
864 * struct ovf::DiskImage for currently processed disk.
865 */
866
867 /*
868 * if user wants to convert all imported disks to VDI format
869 * we need to replace files extensions to "vdi"
870 * except CD/DVD disks
871 */
872 if (optionsList.contains(ImportOptions_ImportToVDI))
873 {
874 ComPtr<IVirtualBox> pVirtualBox = arg->virtualBox;
875 ComPtr<ISystemProperties> systemProperties;
876 com::SafeIfaceArray<IMediumFormat> mediumFormats;
877 Bstr bstrFormatName;
878
879 CHECK_ERROR(pVirtualBox,
880 COMGETTER(SystemProperties)(systemProperties.asOutParam()));
881
882 CHECK_ERROR(systemProperties,
883 COMGETTER(MediumFormats)(ComSafeArrayAsOutParam(mediumFormats)));
884
885 /* go through all supported media formats and store files extensions only for RAW */
886 com::SafeArray<BSTR> extensions;
887
888 for (unsigned j = 0; j < mediumFormats.size(); ++j)
889 {
890 com::SafeArray<DeviceType_T> deviceType;
891 ComPtr<IMediumFormat> mediumFormat = mediumFormats[j];
892 CHECK_ERROR(mediumFormat, COMGETTER(Name)(bstrFormatName.asOutParam()));
893 Utf8Str strFormatName = Utf8Str(bstrFormatName);
894
895 if (strFormatName.compare("RAW", Utf8Str::CaseInsensitive) == 0)
896 {
897 /* getting files extensions for "RAW" format */
898 CHECK_ERROR(mediumFormat,
899 DescribeFileExtensions(ComSafeArrayAsOutParam(extensions),
900 ComSafeArrayAsOutParam(deviceType)));
901 break;
902 }
903 }
904
905 /* go through files extensions for RAW format and compare them with
906 * extension of current file
907 */
908 bool fReplace = true;
909
910 const char *pszExtension = RTPathSuffix(strOverride.c_str());
911 if (pszExtension)
912 pszExtension++;
913
914 for (unsigned j = 0; j < extensions.size(); ++j)
915 {
916 Bstr bstrExt(extensions[j]);
917 Utf8Str strExtension(bstrExt);
918 if(strExtension.compare(pszExtension, Utf8Str::CaseInsensitive) == 0)
919 {
920 fReplace = false;
921 break;
922 }
923 }
924
925 if (fReplace)
926 {
927 strOverride = strOverride.stripSuffix();
928 strOverride = strOverride.append(".").append("vdi");
929 }
930 }
931
932 bstrFinalValue = strOverride;
933
934 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls"
935 "\n (change target path with \"--vsys %u --unit %u --disk path\";"
936 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
937 a,
938 aOvfValues[a],
939 bstrFinalValue.raw(),
940 aExtraConfigValues[a],
941 i, a, i, a);
942 }
943 }
944 break;
945
946 case VirtualSystemDescriptionType_CDROM:
947 if (fIgnoreThis)
948 {
949 RTPrintf("%2u: CD-ROM -- disabled\n",
950 a);
951 aEnabled[a] = false;
952 }
953 else
954 RTPrintf("%2u: CD-ROM"
955 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
956 a, i, a);
957 break;
958
959 case VirtualSystemDescriptionType_Floppy:
960 if (fIgnoreThis)
961 {
962 RTPrintf("%2u: Floppy -- disabled\n",
963 a);
964 aEnabled[a] = false;
965 }
966 else
967 RTPrintf("%2u: Floppy"
968 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
969 a, i, a);
970 break;
971
972 case VirtualSystemDescriptionType_NetworkAdapter:
973 RTPrintf("%2u: Network adapter: orig %ls, config %ls, extra %ls\n", /// @todo implement once we have a plan for the back-end
974 a,
975 aOvfValues[a],
976 aVBoxValues[a],
977 aExtraConfigValues[a]);
978 break;
979
980 case VirtualSystemDescriptionType_USBController:
981 if (fIgnoreThis)
982 {
983 RTPrintf("%2u: USB controller -- disabled\n",
984 a);
985 aEnabled[a] = false;
986 }
987 else
988 RTPrintf("%2u: USB controller"
989 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
990 a, i, a);
991 break;
992
993 case VirtualSystemDescriptionType_SoundCard:
994 if (fIgnoreThis)
995 {
996 RTPrintf("%2u: Sound card \"%ls\" -- disabled\n",
997 a,
998 aOvfValues[a]);
999 aEnabled[a] = false;
1000 }
1001 else
1002 RTPrintf("%2u: Sound card (appliance expects \"%ls\", can change on import)"
1003 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
1004 a,
1005 aOvfValues[a],
1006 i,
1007 a);
1008 break;
1009
1010 case VirtualSystemDescriptionType_SettingsFile:
1011 if (findArgValue(strOverride, pmapArgs, "settingsfile"))
1012 {
1013 bstrFinalValue = strOverride;
1014 RTPrintf("%2u: VM settings file name specified with --settingsfile: \"%ls\"\n",
1015 a, bstrFinalValue.raw());
1016 }
1017 else
1018 RTPrintf("%2u: Suggested VM settings file name \"%ls\""
1019 "\n (change with \"--vsys %u --settingsfile <filename>\")\n",
1020 a, bstrFinalValue.raw(), i);
1021 break;
1022
1023 case VirtualSystemDescriptionType_BaseFolder:
1024 if (findArgValue(strOverride, pmapArgs, "basefolder"))
1025 {
1026 bstrFinalValue = strOverride;
1027 RTPrintf("%2u: VM base folder specified with --basefolder: \"%ls\"\n",
1028 a, bstrFinalValue.raw());
1029 }
1030 else
1031 RTPrintf("%2u: Suggested VM base folder \"%ls\""
1032 "\n (change with \"--vsys %u --basefolder <path>\")\n",
1033 a, bstrFinalValue.raw(), i);
1034 break;
1035
1036 case VirtualSystemDescriptionType_PrimaryGroup:
1037 if (findArgValue(strOverride, pmapArgs, "group"))
1038 {
1039 bstrFinalValue = strOverride;
1040 RTPrintf("%2u: VM group specified with --group: \"%ls\"\n",
1041 a, bstrFinalValue.raw());
1042 }
1043 else
1044 RTPrintf("%2u: Suggested VM group \"%ls\""
1045 "\n (change with \"--vsys %u --group <group>\")\n",
1046 a, bstrFinalValue.raw(), i);
1047 break;
1048
1049 case VirtualSystemDescriptionType_CloudInstanceShape:
1050 RTPrintf("%2u: Suggested cloud shape \"%ls\"\n",
1051 a, bstrFinalValue.raw());
1052 break;
1053
1054 case VirtualSystemDescriptionType_CloudBucket:
1055 if (findArgValue(strOverride, pmapArgs, "cloudbucket"))
1056 {
1057 bstrFinalValue = strOverride;
1058 RTPrintf("%2u: Cloud bucket id specified with --cloudbucket: \"%ls\"\n",
1059 a, bstrFinalValue.raw());
1060 }
1061 else
1062 RTPrintf("%2u: Suggested cloud bucket id \"%ls\""
1063 "\n (change with \"--cloud %u --cloudbucket <id>\")\n",
1064 a, bstrFinalValue.raw(), i);
1065 break;
1066
1067 case VirtualSystemDescriptionType_CloudProfileName:
1068 if (findArgValue(strOverride, pmapArgs, "cloudprofile"))
1069 {
1070 bstrFinalValue = strOverride;
1071 RTPrintf("%2u: Cloud profile name specified with --cloudprofile: \"%ls\"\n",
1072 a, bstrFinalValue.raw());
1073 }
1074 else
1075 RTPrintf("%2u: Suggested cloud profile name \"%ls\""
1076 "\n (change with \"--cloud %u --cloudprofile <id>\")\n",
1077 a, bstrFinalValue.raw(), i);
1078 break;
1079
1080 case VirtualSystemDescriptionType_CloudInstanceId:
1081 if (findArgValue(strOverride, pmapArgs, "cloudinstanceid"))
1082 {
1083 bstrFinalValue = strOverride;
1084 RTPrintf("%2u: Cloud instance id specified with --cloudinstanceid: \"%ls\"\n",
1085 a, bstrFinalValue.raw());
1086 }
1087 else
1088 RTPrintf("%2u: Suggested cloud instance id \"%ls\""
1089 "\n (change with \"--cloud %u --cloudinstanceid <id>\")\n",
1090 a, bstrFinalValue.raw(), i);
1091 break;
1092
1093 case VirtualSystemDescriptionType_CloudImageId:
1094 RTPrintf("%2u: Suggested cloud base image id \"%ls\"\n",
1095 a, bstrFinalValue.raw());
1096 break;
1097 case VirtualSystemDescriptionType_CloudDomain:
1098 case VirtualSystemDescriptionType_CloudBootDiskSize:
1099 case VirtualSystemDescriptionType_CloudOCIVCN:
1100 case VirtualSystemDescriptionType_CloudPublicIP:
1101 case VirtualSystemDescriptionType_CloudOCISubnet:
1102 case VirtualSystemDescriptionType_CloudKeepObject:
1103 case VirtualSystemDescriptionType_CloudLaunchInstance:
1104 case VirtualSystemDescriptionType_CloudInstanceState:
1105 case VirtualSystemDescriptionType_CloudImageState:
1106 case VirtualSystemDescriptionType_Miscellaneous:
1107 case VirtualSystemDescriptionType_CloudInstanceDisplayName:
1108 case VirtualSystemDescriptionType_CloudImageDisplayName:
1109 case VirtualSystemDescriptionType_CloudOCILaunchMode:
1110 case VirtualSystemDescriptionType_CloudPrivateIP:
1111 case VirtualSystemDescriptionType_CloudBootVolumeId:
1112 case VirtualSystemDescriptionType_CloudOCIVCNCompartment:
1113 case VirtualSystemDescriptionType_CloudOCISubnetCompartment:
1114 case VirtualSystemDescriptionType_CloudPublicSSHKey:
1115 case VirtualSystemDescriptionType_BootingFirmware:
1116 /** @todo VirtualSystemDescriptionType_Miscellaneous? */
1117 break;
1118
1119 case VirtualSystemDescriptionType_Ignore:
1120#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
1121 case VirtualSystemDescriptionType_32BitHack:
1122#endif
1123 break;
1124 }
1125
1126 bstrFinalValue.detachTo(&aFinalValues[a]);
1127 }
1128
1129 if (fExecute)
1130 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
1131 SetFinalValues(ComSafeArrayAsInParam(aEnabled),
1132 ComSafeArrayAsInParam(aFinalValues),
1133 ComSafeArrayAsInParam(aExtraConfigValues)));
1134
1135 } // for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
1136
1137 if (cLicensesInTheWay == 1)
1138 RTMsgError("Cannot import until the license agreement listed above is accepted.");
1139 else if (cLicensesInTheWay > 1)
1140 RTMsgError("Cannot import until the %c license agreements listed above are accepted.", cLicensesInTheWay);
1141
1142 if (!cLicensesInTheWay && fExecute)
1143 {
1144 // go!
1145 ComPtr<IProgress> progress;
1146 CHECK_ERROR_BREAK(pAppliance,
1147 ImportMachines(ComSafeArrayAsInParam(options), progress.asOutParam()));
1148
1149 rc = showProgress(progress);
1150 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance import failed"), RTEXITCODE_FAILURE);
1151
1152 if (SUCCEEDED(rc))
1153 RTPrintf("Successfully imported the appliance.\n");
1154 }
1155 } // end if (aVirtualSystemDescriptions.size() > 0)
1156 } while (0);
1157
1158 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1159}
1160
1161static int parseExportOptions(const char *psz, com::SafeArray<ExportOptions_T> *options)
1162{
1163 int rc = VINF_SUCCESS;
1164 while (psz && *psz && RT_SUCCESS(rc))
1165 {
1166 size_t len;
1167 const char *pszComma = strchr(psz, ',');
1168 if (pszComma)
1169 len = pszComma - psz;
1170 else
1171 len = strlen(psz);
1172 if (len > 0)
1173 {
1174 if (!RTStrNICmp(psz, "CreateManifest", len))
1175 options->push_back(ExportOptions_CreateManifest);
1176 else if (!RTStrNICmp(psz, "manifest", len))
1177 options->push_back(ExportOptions_CreateManifest);
1178 else if (!RTStrNICmp(psz, "ExportDVDImages", len))
1179 options->push_back(ExportOptions_ExportDVDImages);
1180 else if (!RTStrNICmp(psz, "iso", len))
1181 options->push_back(ExportOptions_ExportDVDImages);
1182 else if (!RTStrNICmp(psz, "StripAllMACs", len))
1183 options->push_back(ExportOptions_StripAllMACs);
1184 else if (!RTStrNICmp(psz, "nomacs", len))
1185 options->push_back(ExportOptions_StripAllMACs);
1186 else if (!RTStrNICmp(psz, "StripAllNonNATMACs", len))
1187 options->push_back(ExportOptions_StripAllNonNATMACs);
1188 else if (!RTStrNICmp(psz, "nomacsbutnat", len))
1189 options->push_back(ExportOptions_StripAllNonNATMACs);
1190 else
1191 rc = VERR_PARSE_ERROR;
1192 }
1193 if (pszComma)
1194 psz += len + 1;
1195 else
1196 psz += len;
1197 }
1198
1199 return rc;
1200}
1201
1202static const RTGETOPTDEF g_aExportOptions[] =
1203{
1204 { "--output", 'o', RTGETOPT_REQ_STRING },
1205 { "--legacy09", 'l', RTGETOPT_REQ_NOTHING },
1206 { "--ovf09", 'l', RTGETOPT_REQ_NOTHING },
1207 { "--ovf10", '1', RTGETOPT_REQ_NOTHING },
1208 { "--ovf20", '2', RTGETOPT_REQ_NOTHING },
1209 { "--opc10", 'c', RTGETOPT_REQ_NOTHING },
1210 { "--manifest", 'm', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1211 { "--iso", 'I', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1212 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
1213 { "--vmname", 'V', RTGETOPT_REQ_STRING },
1214 { "--product", 'p', RTGETOPT_REQ_STRING },
1215 { "--producturl", 'P', RTGETOPT_REQ_STRING },
1216 { "--vendor", 'n', RTGETOPT_REQ_STRING },
1217 { "--vendorurl", 'N', RTGETOPT_REQ_STRING },
1218 { "--version", 'v', RTGETOPT_REQ_STRING },
1219 { "--description", 'd', RTGETOPT_REQ_STRING },
1220 { "--eula", 'e', RTGETOPT_REQ_STRING },
1221 { "--eulafile", 'E', RTGETOPT_REQ_STRING },
1222 { "--options", 'O', RTGETOPT_REQ_STRING },
1223 { "--cloud", 'C', RTGETOPT_REQ_UINT32 },
1224 { "--cloudshape", 'S', RTGETOPT_REQ_STRING },
1225 { "--clouddomain", 'D', RTGETOPT_REQ_STRING },
1226 { "--clouddisksize", 'R', RTGETOPT_REQ_STRING },
1227 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING },
1228 { "--cloudocivcn", 'Q', RTGETOPT_REQ_STRING },
1229 { "--cloudpublicip", 'A', RTGETOPT_REQ_STRING },
1230 { "--cloudprofile", 'F', RTGETOPT_REQ_STRING },
1231 { "--cloudocisubnet", 'T', RTGETOPT_REQ_STRING },
1232 { "--cloudkeepobject", 'K', RTGETOPT_REQ_STRING },
1233 { "--cloudlaunchinstance", 'L', RTGETOPT_REQ_STRING },
1234 { "--cloudlaunchmode", 'M', RTGETOPT_REQ_STRING },
1235 { "--cloudprivateip", 'i', RTGETOPT_REQ_STRING },
1236};
1237
1238RTEXITCODE handleExportAppliance(HandlerArg *a)
1239{
1240 HRESULT rc = S_OK;
1241
1242 Utf8Str strOutputFile;
1243 Utf8Str strOvfFormat("ovf-1.0"); // the default export version
1244 bool fManifest = false; // the default
1245 bool fCloud = false; // the default
1246 actionType = NOT_SET;
1247 bool fExportISOImages = false; // the default
1248 com::SafeArray<ExportOptions_T> options;
1249 std::list< ComPtr<IMachine> > llMachines;
1250
1251 uint32_t ulCurVsys = (uint32_t)-1;
1252 // for each --vsys X command, maintain a map of command line items
1253 ArgsMapsMap mapArgsMapsPerVsys;
1254 do
1255 {
1256 int c;
1257
1258 RTGETOPTUNION ValueUnion;
1259 RTGETOPTSTATE GetState;
1260 // start at 0 because main() has hacked both the argc and argv given to us
1261 RTGetOptInit(&GetState, a->argc, a->argv, g_aExportOptions,
1262 RT_ELEMENTS(g_aExportOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
1263
1264 Utf8Str strProductUrl;
1265 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1266 {
1267 switch (c)
1268 {
1269 case 'o': // --output
1270 if (strOutputFile.length())
1271 return errorSyntax(USAGE_EXPORTAPPLIANCE, "You can only specify --output once.");
1272 else
1273 strOutputFile = ValueUnion.psz;
1274 break;
1275
1276 case 'l': // --legacy09/--ovf09
1277 strOvfFormat = "ovf-0.9";
1278 break;
1279
1280 case '1': // --ovf10
1281 strOvfFormat = "ovf-1.0";
1282 break;
1283
1284 case '2': // --ovf20
1285 strOvfFormat = "ovf-2.0";
1286 break;
1287
1288 case 'c': // --opc
1289 strOvfFormat = "opc-1.0";
1290 break;
1291
1292 case 'I': // --iso
1293 fExportISOImages = true;
1294 break;
1295
1296 case 'm': // --manifest
1297 fManifest = true;
1298 break;
1299
1300 case 's': // --vsys
1301 if (fCloud == false && actionType == NOT_SET)
1302 actionType = LOCAL;
1303
1304 if (actionType != LOCAL)
1305 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1306 "Option \"%s\" can't be used together with \"--cloud\" argument.",
1307 GetState.pDef->pszLong);
1308
1309 ulCurVsys = ValueUnion.u32;
1310 break;
1311
1312 case 'V': // --vmname
1313 if (actionType == NOT_SET || ulCurVsys == (uint32_t)-1)
1314 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys or --cloud argument.",
1315 GetState.pDef->pszLong);
1316 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
1317 break;
1318
1319 case 'p': // --product
1320 if (actionType != LOCAL)
1321 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1322 mapArgsMapsPerVsys[ulCurVsys]["product"] = ValueUnion.psz;
1323 break;
1324
1325 case 'P': // --producturl
1326 if (actionType != LOCAL)
1327 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1328 mapArgsMapsPerVsys[ulCurVsys]["producturl"] = ValueUnion.psz;
1329 break;
1330
1331 case 'n': // --vendor
1332 if (actionType != LOCAL)
1333 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1334 mapArgsMapsPerVsys[ulCurVsys]["vendor"] = ValueUnion.psz;
1335 break;
1336
1337 case 'N': // --vendorurl
1338 if (actionType != LOCAL)
1339 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1340 mapArgsMapsPerVsys[ulCurVsys]["vendorurl"] = ValueUnion.psz;
1341 break;
1342
1343 case 'v': // --version
1344 if (actionType != LOCAL)
1345 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1346 mapArgsMapsPerVsys[ulCurVsys]["version"] = ValueUnion.psz;
1347 break;
1348
1349 case 'd': // --description
1350 if (actionType != LOCAL)
1351 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1352 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
1353 break;
1354
1355 case 'e': // --eula
1356 if (actionType != LOCAL)
1357 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1358 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
1359 break;
1360
1361 case 'E': // --eulafile
1362 if (actionType != LOCAL)
1363 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1364 mapArgsMapsPerVsys[ulCurVsys]["eulafile"] = ValueUnion.psz;
1365 break;
1366
1367 case 'O': // --options
1368 if (RT_FAILURE(parseExportOptions(ValueUnion.psz, &options)))
1369 return errorArgument("Invalid export options '%s'\n", ValueUnion.psz);
1370 break;
1371
1372 /*--cloud and --vsys are orthogonal, only one must be presented*/
1373 case 'C': // --cloud
1374 if (fCloud == false && actionType == NOT_SET)
1375 {
1376 fCloud = true;
1377 actionType = CLOUD;
1378 }
1379
1380 if (actionType != CLOUD)
1381 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1382 "Option \"%s\" can't be used together with \"--vsys\" argument.",
1383 GetState.pDef->pszLong);
1384
1385 ulCurVsys = ValueUnion.u32;
1386 break;
1387
1388 /* Cloud export settings */
1389 case 'S': // --cloudshape
1390 if (actionType != CLOUD)
1391 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1392 GetState.pDef->pszLong);
1393 mapArgsMapsPerVsys[ulCurVsys]["cloudshape"] = ValueUnion.psz;
1394 break;
1395
1396 case 'D': // --clouddomain
1397 if (actionType != CLOUD)
1398 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1399 GetState.pDef->pszLong);
1400 mapArgsMapsPerVsys[ulCurVsys]["clouddomain"] = ValueUnion.psz;
1401 break;
1402
1403 case 'R': // --clouddisksize
1404 if (actionType != CLOUD)
1405 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1406 GetState.pDef->pszLong);
1407 mapArgsMapsPerVsys[ulCurVsys]["clouddisksize"] = ValueUnion.psz;
1408 break;
1409
1410 case 'B': // --cloudbucket
1411 if (actionType != CLOUD)
1412 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1413 GetState.pDef->pszLong);
1414 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
1415 break;
1416
1417 case 'Q': // --cloudocivcn
1418 if (actionType != CLOUD)
1419 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1420 GetState.pDef->pszLong);
1421 mapArgsMapsPerVsys[ulCurVsys]["cloudocivcn"] = ValueUnion.psz;
1422 break;
1423
1424 case 'A': // --cloudpublicip
1425 if (actionType != CLOUD)
1426 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1427 GetState.pDef->pszLong);
1428 mapArgsMapsPerVsys[ulCurVsys]["cloudpublicip"] = ValueUnion.psz;
1429 break;
1430
1431 case 'i': /* --cloudprivateip */
1432 if (actionType != CLOUD)
1433 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1434 GetState.pDef->pszLong);
1435 mapArgsMapsPerVsys[ulCurVsys]["cloudprivateip"] = ValueUnion.psz;
1436 break;
1437
1438 case 'F': // --cloudprofile
1439 if (actionType != CLOUD)
1440 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1441 GetState.pDef->pszLong);
1442 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
1443 break;
1444
1445 case 'T': // --cloudocisubnet
1446 if (actionType != CLOUD)
1447 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1448 GetState.pDef->pszLong);
1449 mapArgsMapsPerVsys[ulCurVsys]["cloudocisubnet"] = ValueUnion.psz;
1450 break;
1451
1452 case 'K': // --cloudkeepobject
1453 if (actionType != CLOUD)
1454 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1455 GetState.pDef->pszLong);
1456 mapArgsMapsPerVsys[ulCurVsys]["cloudkeepobject"] = ValueUnion.psz;
1457 break;
1458
1459 case 'L': // --cloudlaunchinstance
1460 if (actionType != CLOUD)
1461 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1462 GetState.pDef->pszLong);
1463 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchinstance"] = ValueUnion.psz;
1464 break;
1465
1466 case 'M': /* --cloudlaunchmode */
1467 if (actionType != CLOUD)
1468 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1469 GetState.pDef->pszLong);
1470 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchmode"] = ValueUnion.psz;
1471 break;
1472
1473 case VINF_GETOPT_NOT_OPTION:
1474 {
1475 Utf8Str strMachine(ValueUnion.psz);
1476 // must be machine: try UUID or name
1477 ComPtr<IMachine> machine;
1478 CHECK_ERROR_BREAK(a->virtualBox, FindMachine(Bstr(strMachine).raw(),
1479 machine.asOutParam()));
1480 if (machine)
1481 llMachines.push_back(machine);
1482 break;
1483 }
1484
1485 default:
1486 if (c > 0)
1487 {
1488 if (RT_C_IS_GRAPH(c))
1489 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: -%c", c);
1490 else
1491 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: %i", c);
1492 }
1493 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
1494 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unknown option: %s", ValueUnion.psz);
1495 else if (ValueUnion.pDef)
1496 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
1497 else
1498 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%Rrs", c);
1499 }
1500
1501 if (FAILED(rc))
1502 break;
1503 }
1504
1505 if (FAILED(rc))
1506 break;
1507
1508 if (llMachines.empty())
1509 return errorSyntax(USAGE_EXPORTAPPLIANCE, "At least one machine must be specified with the export command.");
1510
1511 /* Last check after parsing all arguments */
1512 if (strOutputFile.isNotEmpty())
1513 {
1514 if (actionType == NOT_SET)
1515 {
1516 if (fCloud)
1517 actionType = CLOUD;
1518 else
1519 actionType = LOCAL;
1520 }
1521 }
1522 else
1523 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Missing --output argument with export command.");
1524
1525 // match command line arguments with the machines count
1526 // this is only to sort out invalid indices at this time
1527 ArgsMapsMap::const_iterator it;
1528 for (it = mapArgsMapsPerVsys.begin();
1529 it != mapArgsMapsPerVsys.end();
1530 ++it)
1531 {
1532 uint32_t ulVsys = it->first;
1533 if (ulVsys >= llMachines.size())
1534 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1535 "Invalid index %RI32 with -vsys option; you specified only %zu virtual system(s).",
1536 ulVsys, llMachines.size());
1537 }
1538
1539 ComPtr<IAppliance> pAppliance;
1540 CHECK_ERROR_BREAK(a->virtualBox, CreateAppliance(pAppliance.asOutParam()));
1541
1542 char *pszAbsFilePath = 0;
1543 if (strOutputFile.startsWith("S3://", RTCString::CaseInsensitive) ||
1544 strOutputFile.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
1545 strOutputFile.startsWith("webdav://", RTCString::CaseInsensitive) ||
1546 strOutputFile.startsWith("OCI://", RTCString::CaseInsensitive))
1547 pszAbsFilePath = RTStrDup(strOutputFile.c_str());
1548 else
1549 pszAbsFilePath = RTPathAbsDup(strOutputFile.c_str());
1550
1551 /*
1552 * The first stage - export machine/s to the Cloud or into the
1553 * OVA/OVF format on the local host.
1554 */
1555
1556 /* VSDList is needed for the second stage where we launch the cloud instances if it was requested by user */
1557 std::list< ComPtr<IVirtualSystemDescription> > VSDList;
1558 std::list< ComPtr<IMachine> >::iterator itM;
1559 uint32_t i=0;
1560 for (itM = llMachines.begin();
1561 itM != llMachines.end();
1562 ++itM, ++i)
1563 {
1564 ComPtr<IMachine> pMachine = *itM;
1565 ComPtr<IVirtualSystemDescription> pVSD;
1566 CHECK_ERROR_BREAK(pMachine, ExportTo(pAppliance, Bstr(pszAbsFilePath).raw(), pVSD.asOutParam()));
1567
1568 // Add additional info to the virtual system description if the user wants so
1569 ArgsMap *pmapArgs = NULL;
1570 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
1571 if (itm != mapArgsMapsPerVsys.end())
1572 pmapArgs = &itm->second;
1573 if (pmapArgs)
1574 {
1575 ArgsMap::iterator itD;
1576 for (itD = pmapArgs->begin();
1577 itD != pmapArgs->end();
1578 ++itD)
1579 {
1580 if (itD->first == "vmname")
1581 {
1582 //remove default value if user has specified new name (default value is set in the ExportTo())
1583// pVSD->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
1584 pVSD->AddDescription(VirtualSystemDescriptionType_Name,
1585 Bstr(itD->second).raw(), NULL);
1586 }
1587 else if (itD->first == "product")
1588 pVSD->AddDescription(VirtualSystemDescriptionType_Product,
1589 Bstr(itD->second).raw(), NULL);
1590 else if (itD->first == "producturl")
1591 pVSD->AddDescription(VirtualSystemDescriptionType_ProductUrl,
1592 Bstr(itD->second).raw(), NULL);
1593 else if (itD->first == "vendor")
1594 pVSD->AddDescription(VirtualSystemDescriptionType_Vendor,
1595 Bstr(itD->second).raw(), NULL);
1596 else if (itD->first == "vendorurl")
1597 pVSD->AddDescription(VirtualSystemDescriptionType_VendorUrl,
1598 Bstr(itD->second).raw(), NULL);
1599 else if (itD->first == "version")
1600 pVSD->AddDescription(VirtualSystemDescriptionType_Version,
1601 Bstr(itD->second).raw(), NULL);
1602 else if (itD->first == "description")
1603 pVSD->AddDescription(VirtualSystemDescriptionType_Description,
1604 Bstr(itD->second).raw(), NULL);
1605 else if (itD->first == "eula")
1606 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1607 Bstr(itD->second).raw(), NULL);
1608 else if (itD->first == "eulafile")
1609 {
1610 Utf8Str strContent;
1611 void *pvFile;
1612 size_t cbFile;
1613 int irc = RTFileReadAll(itD->second.c_str(), &pvFile, &cbFile);
1614 if (RT_SUCCESS(irc))
1615 {
1616 Bstr bstrContent((char*)pvFile, cbFile);
1617 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1618 bstrContent.raw(), NULL);
1619 RTFileReadAllFree(pvFile, cbFile);
1620 }
1621 else
1622 {
1623 RTMsgError("Cannot read license file \"%s\" which should be included in the virtual system %u.",
1624 itD->second.c_str(), i);
1625 return RTEXITCODE_FAILURE;
1626 }
1627 }
1628 /* add cloud export settings */
1629 else if (itD->first == "cloudshape")
1630 pVSD->AddDescription(VirtualSystemDescriptionType_CloudInstanceShape,
1631 Bstr(itD->second).raw(), NULL);
1632 else if (itD->first == "clouddomain")
1633 pVSD->AddDescription(VirtualSystemDescriptionType_CloudDomain,
1634 Bstr(itD->second).raw(), NULL);
1635 else if (itD->first == "clouddisksize")
1636 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBootDiskSize,
1637 Bstr(itD->second).raw(), NULL);
1638 else if (itD->first == "cloudbucket")
1639 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBucket,
1640 Bstr(itD->second).raw(), NULL);
1641 else if (itD->first == "cloudocivcn")
1642 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCIVCN,
1643 Bstr(itD->second).raw(), NULL);
1644 else if (itD->first == "cloudpublicip")
1645 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPublicIP,
1646 Bstr(itD->second).raw(), NULL);
1647 else if (itD->first == "cloudprivateip")
1648 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPrivateIP,
1649 Bstr(itD->second).raw(), NULL);
1650 else if (itD->first == "cloudprofile")
1651 pVSD->AddDescription(VirtualSystemDescriptionType_CloudProfileName,
1652 Bstr(itD->second).raw(), NULL);
1653 else if (itD->first == "cloudocisubnet")
1654 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCISubnet,
1655 Bstr(itD->second).raw(), NULL);
1656 else if (itD->first == "cloudkeepobject")
1657 pVSD->AddDescription(VirtualSystemDescriptionType_CloudKeepObject,
1658 Bstr(itD->second).raw(), NULL);
1659 else if (itD->first == "cloudlaunchmode")
1660 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCILaunchMode,
1661 Bstr(itD->second).raw(), NULL);
1662 else if (itD->first == "cloudlaunchinstance")
1663 pVSD->AddDescription(VirtualSystemDescriptionType_CloudLaunchInstance,
1664 Bstr(itD->second).raw(), NULL);
1665 }
1666 }
1667
1668 VSDList.push_back(pVSD);//store vsd for the possible second stage
1669 }
1670
1671 if (FAILED(rc))
1672 break;
1673
1674 /* Query required passwords and supply them to the appliance. */
1675 com::SafeArray<BSTR> aIdentifiers;
1676
1677 CHECK_ERROR_BREAK(pAppliance, GetPasswordIds(ComSafeArrayAsOutParam(aIdentifiers)));
1678
1679 if (aIdentifiers.size() > 0)
1680 {
1681 com::SafeArray<BSTR> aPasswords(aIdentifiers.size());
1682 RTPrintf("Enter the passwords for the following identifiers to export the apppliance:\n");
1683 for (unsigned idxId = 0; idxId < aIdentifiers.size(); idxId++)
1684 {
1685 com::Utf8Str strPassword;
1686 Bstr bstrPassword;
1687 Bstr bstrId = aIdentifiers[idxId];
1688
1689 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Password ID %s:", Utf8Str(bstrId).c_str());
1690 if (rcExit == RTEXITCODE_FAILURE)
1691 {
1692 RTStrFree(pszAbsFilePath);
1693 return rcExit;
1694 }
1695
1696 bstrPassword = strPassword;
1697 bstrPassword.detachTo(&aPasswords[idxId]);
1698 }
1699
1700 CHECK_ERROR_BREAK(pAppliance, AddPasswords(ComSafeArrayAsInParam(aIdentifiers),
1701 ComSafeArrayAsInParam(aPasswords)));
1702 }
1703
1704 if (fManifest)
1705 options.push_back(ExportOptions_CreateManifest);
1706
1707 if (fExportISOImages)
1708 options.push_back(ExportOptions_ExportDVDImages);
1709
1710 ComPtr<IProgress> progress;
1711 CHECK_ERROR_BREAK(pAppliance, Write(Bstr(strOvfFormat).raw(),
1712 ComSafeArrayAsInParam(options),
1713 Bstr(pszAbsFilePath).raw(),
1714 progress.asOutParam()));
1715 RTStrFree(pszAbsFilePath);
1716
1717 rc = showProgress(progress);
1718 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance write failed"), RTEXITCODE_FAILURE);
1719
1720 if (SUCCEEDED(rc))
1721 RTPrintf("Successfully exported %d machine(s).\n", llMachines.size());
1722
1723 /*
1724 * The second stage for the cloud case
1725 */
1726 if (actionType == CLOUD)
1727 {
1728 /* Launch the exported VM if the appropriate flag had been set on the first stage */
1729 for (std::list< ComPtr<IVirtualSystemDescription> >::iterator itVSD = VSDList.begin();
1730 itVSD != VSDList.end();
1731 ++itVSD)
1732 {
1733 ComPtr<IVirtualSystemDescription> pVSD = *itVSD;
1734
1735 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
1736 com::SafeArray<BSTR> aRefs;
1737 com::SafeArray<BSTR> aOvfValues;
1738 com::SafeArray<BSTR> aVBoxValues;
1739 com::SafeArray<BSTR> aExtraConfigValues;
1740
1741 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudLaunchInstance,
1742 ComSafeArrayAsOutParam(retTypes),
1743 ComSafeArrayAsOutParam(aRefs),
1744 ComSafeArrayAsOutParam(aOvfValues),
1745 ComSafeArrayAsOutParam(aVBoxValues),
1746 ComSafeArrayAsOutParam(aExtraConfigValues)));
1747
1748 Utf8Str flagCloudLaunchInstance(Bstr(aVBoxValues[0]).raw());
1749 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1750
1751 if (flagCloudLaunchInstance.equals("true"))
1752 {
1753 /* Getting the short provider name */
1754 Bstr bstrCloudProviderShortName(strOutputFile.c_str(), strOutputFile.find("://"));
1755
1756 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
1757 ComPtr<ICloudProviderManager> pCloudProviderManager;
1758 CHECK_ERROR_BREAK(pVirtualBox, COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()));
1759
1760 ComPtr<ICloudProvider> pCloudProvider;
1761 CHECK_ERROR_BREAK(pCloudProviderManager,
1762 GetProviderByShortName(bstrCloudProviderShortName.raw(), pCloudProvider.asOutParam()));
1763
1764 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudProfileName,
1765 ComSafeArrayAsOutParam(retTypes),
1766 ComSafeArrayAsOutParam(aRefs),
1767 ComSafeArrayAsOutParam(aOvfValues),
1768 ComSafeArrayAsOutParam(aVBoxValues),
1769 ComSafeArrayAsOutParam(aExtraConfigValues)));
1770
1771 ComPtr<ICloudProfile> pCloudProfile;
1772 CHECK_ERROR_BREAK(pCloudProvider, GetProfileByName(Bstr(aVBoxValues[0]).raw(), pCloudProfile.asOutParam()));
1773 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1774
1775 ComObjPtr<ICloudClient> oCloudClient;
1776 CHECK_ERROR_BREAK(pCloudProfile, CreateCloudClient(oCloudClient.asOutParam()));
1777 RTPrintf("Creating a cloud instance...\n");
1778
1779 ComPtr<IProgress> progress1;
1780 CHECK_ERROR_BREAK(oCloudClient, LaunchVM(pVSD, progress1.asOutParam()));
1781 rc = showProgress(progress1);
1782 CHECK_PROGRESS_ERROR_RET(progress1, ("Creating the cloud instance failed"), RTEXITCODE_FAILURE);
1783
1784 if (SUCCEEDED(rc))
1785 {
1786 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudInstanceId,
1787 ComSafeArrayAsOutParam(retTypes),
1788 ComSafeArrayAsOutParam(aRefs),
1789 ComSafeArrayAsOutParam(aOvfValues),
1790 ComSafeArrayAsOutParam(aVBoxValues),
1791 ComSafeArrayAsOutParam(aExtraConfigValues)));
1792
1793 RTPrintf("A cloud instance with id '%s' (provider '%s') was created\n",
1794 Utf8Str(Bstr(aVBoxValues[0]).raw()).c_str(),
1795 Utf8Str(bstrCloudProviderShortName.raw()).c_str());
1796 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1797 }
1798 }
1799 }
1800 }
1801 } while (0);
1802
1803 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1804}
1805
1806
1807/*********************************************************************************************************************************
1808* signova *
1809*********************************************************************************************************************************/
1810
1811/**
1812 * Reads the OVA and saves the manifest and signed status.
1813 *
1814 * @returns VBox status code (fully messaged).
1815 * @param pszOva The name of the OVA.
1816 * @param iVerbosity The noise level.
1817 * @param fReSign Whether it is acceptable to have an existing signature
1818 * in the OVA or not.
1819 * @param phVfsFssOva Where to return the OVA file system stream handle.
1820 * This has been opened for updating and we're positioned
1821 * at the end of the stream.
1822 * @param pStrManifestName Where to return the manifest name.
1823 * @param phVfsManifest Where to return the manifest file handle (copy in mem).
1824 * @param phVfsOldSignature Where to return the handle to the old signature object.
1825 *
1826 * @note Caller must clean up return values on failure too!
1827 */
1828static int openOvaAndGetManifestAndOldSignature(const char *pszOva, unsigned iVerbosity, bool fReSign,
1829 PRTVFSFSSTREAM phVfsFssOva, Utf8Str *pStrManifestName,
1830 PRTVFSFILE phVfsManifest, PRTVFSOBJ phVfsOldSignature)
1831{
1832 /*
1833 * Clear return values.
1834 */
1835 *phVfsFssOva = NIL_RTVFSFSSTREAM;
1836 pStrManifestName->setNull();
1837 *phVfsManifest = NIL_RTVFSFILE;
1838 *phVfsOldSignature = NIL_RTVFSOBJ;
1839
1840 /*
1841 * Open the file as a tar file system stream.
1842 */
1843 RTVFSFILE hVfsFileOva;
1844 int rc = RTVfsFileOpenNormal(pszOva, RTFILE_O_OPEN | RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE, &hVfsFileOva);
1845 if (RT_FAILURE(rc))
1846 return RTMsgErrorExitFailure("Failed to open OVA '%s' for updating: %Rrc", pszOva, rc);
1847
1848 RTVFSFSSTREAM hVfsFssOva;
1849 rc = RTZipTarFsStreamForFile(hVfsFileOva, RTZIPTARFORMAT_DEFAULT, RTZIPTAR_C_UPDATE, &hVfsFssOva);
1850 RTVfsFileRelease(hVfsFileOva);
1851 if (RT_FAILURE(rc))
1852 return RTMsgErrorExitFailure("Failed to open OVA '%s' as a TAR file: %Rrc", pszOva, rc);
1853 *phVfsFssOva = hVfsFssOva;
1854
1855 /*
1856 * Scan the objects in the stream and locate the manifest and any existing cert file.
1857 */
1858 if (iVerbosity >= 2)
1859 RTMsgInfo("Scanning OVA '%s' for a manifest and signature...", pszOva);
1860 char *pszSignatureName = NULL;
1861 for (;;)
1862 {
1863 /*
1864 * Retrive the next object.
1865 */
1866 char *pszName;
1867 RTVFSOBJTYPE enmType;
1868 RTVFSOBJ hVfsObj;
1869 rc = RTVfsFsStrmNext(hVfsFssOva, &pszName, &enmType, &hVfsObj);
1870 if (RT_FAILURE(rc))
1871 {
1872 if (rc == VERR_EOF)
1873 rc = VINF_SUCCESS;
1874 else
1875 RTMsgError("RTVfsFsStrmNext returned %Rrc", rc);
1876 break;
1877 }
1878
1879 if (iVerbosity > 2)
1880 RTMsgInfo(" %s %s\n", RTVfsTypeName(enmType), pszName);
1881
1882 /*
1883 * Should we process this entry?
1884 */
1885 const char *pszSuffix = RTPathSuffix(pszName);
1886 if ( pszSuffix
1887 && RTStrICmpAscii(pszSuffix, ".mf") == 0
1888 && (enmType == RTVFSOBJTYPE_IO_STREAM || enmType == RTVFSOBJTYPE_FILE))
1889 {
1890 if (*phVfsManifest != NIL_RTVFSFILE)
1891 rc = RTMsgErrorRc(VERR_DUPLICATE, "OVA contains multiple manifests! first: %s second: %s",
1892 pStrManifestName->c_str(), pszName);
1893 else if (pszSignatureName)
1894 rc = RTMsgErrorRc(VERR_WRONG_ORDER, "Unsupported OVA file ordering! Signature file ('%s') as succeeded by '%s'.",
1895 pszSignatureName, pszName);
1896 else
1897 {
1898 if (iVerbosity >= 2)
1899 RTMsgInfo("Found manifest file: %s", pszName);
1900 rc = pStrManifestName->assignNoThrow(pszName);
1901 if (RT_SUCCESS(rc))
1902 {
1903 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
1904 Assert(hVfsIos != NIL_RTVFSIOSTREAM);
1905 rc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, phVfsManifest);
1906 RTVfsIoStrmRelease(hVfsIos); /* consumes stream handle. */
1907 if (RT_FAILURE(rc))
1908 rc = RTMsgErrorRc(VERR_DUPLICATE, "Failed to memorize the manifest: %Rrc", rc);
1909 }
1910 else
1911 RTMsgError("Out of memory!");
1912 }
1913 }
1914 else if ( pszSuffix
1915 && RTStrICmpAscii(pszSuffix, ".cert") == 0
1916 && (enmType == RTVFSOBJTYPE_IO_STREAM || enmType == RTVFSOBJTYPE_FILE))
1917 {
1918 if (*phVfsOldSignature != NIL_RTVFSOBJ)
1919 rc = RTMsgErrorRc(VERR_WRONG_ORDER, "Multiple signature files! (%s)", pszName);
1920 else
1921 {
1922 if (iVerbosity >= 2)
1923 RTMsgInfo("Found existing signature file: %s", pszName);
1924 pszSignatureName = pszName;
1925 *phVfsOldSignature = hVfsObj;
1926 pszName = NULL;
1927 hVfsObj = NIL_RTVFSOBJ;
1928 }
1929 }
1930 else if (pszSignatureName)
1931 rc = RTMsgErrorRc(VERR_WRONG_ORDER, "Unsupported OVA file ordering! Signature file ('%s') as succeeded by '%s'.",
1932 pszSignatureName, pszName);
1933
1934 /*
1935 * Release the current object and string.
1936 */
1937 RTVfsObjRelease(hVfsObj);
1938 RTStrFree(pszName);
1939 if (RT_FAILURE(rc))
1940 break;
1941 }
1942
1943 /*
1944 * Complain if no manifest.
1945 */
1946 if (RT_SUCCESS(rc) && *phVfsManifest == NIL_RTVFSFILE)
1947 rc = RTMsgErrorRc(VERR_NOT_FOUND, "The OVA contains no manifest and cannot be signed!");
1948 else if (RT_SUCCESS(rc) && *phVfsOldSignature != NIL_RTVFSOBJ && !fReSign)
1949 rc = RTMsgErrorRc(VERR_ALREADY_EXISTS,
1950 "The OVA is already signed ('%s')! (Use the --force option to force re-signing it.)",
1951 pszSignatureName);
1952
1953 RTStrFree(pszSignatureName);
1954 return rc;
1955}
1956
1957
1958/**
1959 * Continues where openOvaAndGetManifestAndOldSignature() left off and writes
1960 * the signature file to the OVA.
1961 *
1962 * When @a hVfsOldSignature isn't NIL, the old signature it represent will be
1963 * replaced. The open function has already made sure there isn't anything
1964 * following the .cert file in that case.
1965 */
1966static int updateTheOvaSignature(RTVFSFSSTREAM hVfsFssOva, const char *pszOva, const char *pszSignatureName,
1967 RTVFSFILE hVfsFileSignature, RTVFSOBJ hVfsOldSignature, unsigned iVerbosity)
1968{
1969 if (iVerbosity > 1)
1970 RTMsgInfo("Writing '%s' to the OVA...", pszSignatureName);
1971
1972 /*
1973 * Truncate the file at the old signature, if present.
1974 */
1975 int rc;
1976 if (hVfsOldSignature != NIL_RTVFSOBJ)
1977 {
1978 rc = RTZipTarFsStreamTruncate(hVfsFssOva, hVfsOldSignature, false /*fAfter*/);
1979 if (RT_FAILURE(rc))
1980 return RTMsgErrorRc(rc, "RTZipTarFsStreamTruncate failed on '%s': %Rrc", pszOva, rc);
1981 }
1982
1983 /*
1984 * Append the signature file. We have to rewind it first or
1985 * we'll end up with VERR_EOF, probably not a great idea...
1986 */
1987 rc = RTVfsFileSeek(hVfsFileSignature, 0, RTFILE_SEEK_BEGIN, NULL);
1988 if (RT_FAILURE(rc))
1989 return RTMsgErrorRc(rc, "RTVfsFileSeek(hVfsFileSignature) failed: %Rrc", rc);
1990
1991 RTVFSOBJ hVfsObj = RTVfsObjFromFile(hVfsFileSignature);
1992 rc = RTVfsFsStrmAdd(hVfsFssOva, pszSignatureName, hVfsObj, 0 /*fFlags*/);
1993 RTVfsObjRelease(hVfsObj);
1994 if (RT_FAILURE(rc))
1995 return RTMsgErrorRc(rc, "RTVfsFsStrmAdd('%s') failed on '%s': %Rrc", pszSignatureName, pszOva, rc);
1996
1997 /*
1998 * Terminate the file system stream.
1999 */
2000 rc = RTVfsFsStrmEnd(hVfsFssOva);
2001 if (RT_FAILURE(rc))
2002 return RTMsgErrorRc(rc, "RTVfsFsStrmEnd failed on '%s': %Rrc", pszOva, rc);
2003
2004 return VINF_SUCCESS;
2005}
2006
2007
2008/**
2009 * Worker for doCheckPkcs7Signature.
2010 */
2011static int doCheckPkcs7SignatureWorker(PRTCRPKCS7CONTENTINFO pContentInfo, void const *pvManifest, size_t cbManifest,
2012 unsigned iVerbosity, const char *pszTag, PRTERRINFOSTATIC pErrInfo)
2013{
2014 int rc;
2015
2016 /*
2017 * It must be signedData.
2018 */
2019 if (RTCrPkcs7ContentInfo_IsSignedData(pContentInfo))
2020 {
2021 PRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData;
2022
2023 /*
2024 * Inside the signedData there must be just 'data'.
2025 */
2026 if (!strcmp(pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID))
2027 {
2028 /*
2029 * Check that things add up.
2030 */
2031 rc = RTCrPkcs7SignedData_CheckSanity(pSignedData,
2032 RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH
2033 | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT,
2034 RTErrInfoInitStatic(pErrInfo), "SD");
2035 if (RT_SUCCESS(rc))
2036 {
2037 if (iVerbosity > 2 && pszTag == NULL)
2038 RTMsgInfo(" Successfully decoded the PKCS#7/CMS signature...");
2039
2040 /*
2041 * Check that we can verify the signed data, but skip certificate validate as
2042 * we probably don't necessarily have the correct root certs handy here.
2043 */
2044 RTTIMESPEC Now;
2045 rc = RTCrPkcs7VerifySignedDataWithExternalData(pContentInfo, RTCRPKCS7VERIFY_SD_F_TRUST_ALL_CERTS,
2046 NIL_RTCRSTORE /*hAdditionalCerts*/,
2047 NIL_RTCRSTORE /*hTrustedCerts*/,
2048 RTTimeNow(&Now),
2049 NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
2050 pvManifest, cbManifest, RTErrInfoInitStatic(pErrInfo));
2051 if (RT_SUCCESS(rc))
2052 {
2053 if (iVerbosity > 1 && pszTag != NULL)
2054 RTMsgInfo(" Successfully verified the PKCS#7/CMS signature");
2055 }
2056 else
2057 rc = RTMsgErrorRc(rc, "Failed to verify the PKCS#7/CMS signature: %Rrc%RTeim", rc, &pErrInfo->Core);
2058 }
2059 else
2060 RTMsgError("RTCrPkcs7SignedData_CheckSanity failed on PKCS#7/CMS signature: %Rrc%RTeim",
2061 rc, &pErrInfo->Core);
2062
2063 }
2064 else
2065 rc = RTMsgErrorRc(VERR_WRONG_TYPE, "PKCS#7/CMS signature inner ContentType isn't 'data' but: %s",
2066 pSignedData->ContentInfo.ContentType.szObjId);
2067 }
2068 else
2069 rc = RTMsgErrorRc(VERR_WRONG_TYPE, "PKCS#7/CMD signature is not 'signedData': %s", pContentInfo->ContentType.szObjId);
2070 return rc;
2071}
2072
2073/**
2074 * For testing the decoding side.
2075 */
2076static int doCheckPkcs7Signature(void const *pvSignature, size_t cbSignature, PCRTCRX509CERTIFICATE pCertificate,
2077 RTCRSTORE hIntermediateCerts, void const *pvManifest, size_t cbManifest,
2078 unsigned iVerbosity, PRTERRINFOSTATIC pErrInfo)
2079{
2080 RT_NOREF(pCertificate, hIntermediateCerts);
2081
2082 RTASN1CURSORPRIMARY PrimaryCursor;
2083 RTAsn1CursorInitPrimary(&PrimaryCursor, pvSignature, (uint32_t)cbSignature, RTErrInfoInitStatic(pErrInfo),
2084 &g_RTAsn1DefaultAllocator, 0, "Signature");
2085
2086 RTCRPKCS7CONTENTINFO ContentInfo;
2087 RT_ZERO(ContentInfo);
2088 int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &ContentInfo, "CI");
2089 if (RT_SUCCESS(rc))
2090 {
2091 if (iVerbosity > 5)
2092 RTAsn1Dump(&ContentInfo.SeqCore.Asn1Core, 0 /*fFlags*/, 0 /*uLevel*/, RTStrmDumpPrintfV, g_pStdOut);
2093
2094 rc = doCheckPkcs7SignatureWorker(&ContentInfo, pvManifest, cbManifest, iVerbosity, NULL, pErrInfo);
2095 if (RT_SUCCESS(rc))
2096 {
2097 /*
2098 * Clone it and repeat. This is to catch IPRT paths assuming
2099 * that encoded data is always on hand.
2100 */
2101 RTCRPKCS7CONTENTINFO ContentInfo2;
2102 rc = RTCrPkcs7ContentInfo_Clone(&ContentInfo2, &ContentInfo, &g_RTAsn1DefaultAllocator);
2103 if (RT_SUCCESS(rc))
2104 {
2105 rc = doCheckPkcs7SignatureWorker(&ContentInfo2, pvManifest, cbManifest, iVerbosity, "cloned", pErrInfo);
2106 RTCrPkcs7ContentInfo_Delete(&ContentInfo2);
2107 }
2108 else
2109 rc = RTMsgErrorRc(rc, "RTCrPkcs7ContentInfo_Clone failed: %Rrc", rc);
2110 }
2111 }
2112 else
2113 RTMsgError("RTCrPkcs7ContentInfo_DecodeAsn1 failed to decode PKCS#7/CMS signature: %Rrc%RTemi", rc, &pErrInfo->Core);
2114
2115 RTCrPkcs7ContentInfo_Delete(&ContentInfo);
2116 return rc;
2117}
2118
2119
2120/**
2121 * Creates a PKCS\#7 signature and appends it to the signature file in PEM
2122 * format.
2123 */
2124static int doAddPkcs7Signature(PCRTCRX509CERTIFICATE pCertificate, RTCRKEY hPrivateKey, RTDIGESTTYPE enmDigestType,
2125 unsigned cIntermediateCerts, const char **papszIntermediateCerts, RTVFSFILE hVfsFileManifest,
2126 unsigned iVerbosity, PRTERRINFOSTATIC pErrInfo, RTVFSFILE hVfsFileSignature)
2127{
2128 /*
2129 * Add a blank line, just for good measure.
2130 */
2131 int rc = RTVfsFileWrite(hVfsFileSignature, " ", 1, NULL);
2132 if (RT_FAILURE(rc))
2133 return RTMsgErrorRc(rc, "RTVfsFileWrite/signature: %Rrc", rc);
2134
2135 /*
2136 * Read the manifest into a single memory block.
2137 */
2138 uint64_t cbManifest;
2139 rc = RTVfsFileQuerySize(hVfsFileManifest, &cbManifest);
2140 if (RT_FAILURE(rc))
2141 return RTMsgErrorRc(rc, "RTVfsFileQuerySize/manifest: %Rrc", rc);
2142 if (cbManifest > _4M)
2143 return RTMsgErrorRc(VERR_OUT_OF_RANGE, "Manifest is too big: %#RX64 bytes, max 4MiB", cbManifest);
2144
2145 void *pvManifest = RTMemAllocZ(cbManifest + 1);
2146 if (!pvManifest)
2147 return RTMsgErrorRc(VERR_NO_MEMORY, "Out of memory!");
2148
2149 rc = RTVfsFileReadAt(hVfsFileManifest, 0, pvManifest, (size_t)cbManifest, NULL);
2150 if (RT_SUCCESS(rc))
2151 {
2152 /*
2153 * Load intermediate certificates.
2154 */
2155 RTCRSTORE hIntermediateCerts = NIL_RTCRSTORE;
2156 if (cIntermediateCerts)
2157 {
2158 rc = RTCrStoreCreateInMem(&hIntermediateCerts, cIntermediateCerts);
2159 if (RT_SUCCESS(rc))
2160 {
2161 for (unsigned i = 0; i < cIntermediateCerts; i++)
2162 {
2163 const char *pszFile = papszIntermediateCerts[i];
2164 rc = RTCrStoreCertAddFromFile(hIntermediateCerts, 0 /*fFlags*/, pszFile, &pErrInfo->Core);
2165 if (RT_FAILURE(rc))
2166 {
2167 RTMsgError("RTCrStoreCertAddFromFile failed on '%s': %Rrc%#RTeim", pszFile, rc, &pErrInfo->Core);
2168 break;
2169 }
2170 }
2171 }
2172 else
2173 RTMsgError("RTCrStoreCreateInMem failed: %Rrc", rc);
2174 }
2175 if (RT_SUCCESS(rc))
2176 {
2177 /*
2178 * Do a dry run to determin the size of the signed data.
2179 */
2180 size_t cbResult = 0;
2181 rc = RTCrPkcs7SimpleSignSignedData(RTCRPKCS7SIGN_SD_F_DEATCHED | RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP,
2182 pCertificate, hPrivateKey, pvManifest, (size_t)cbManifest, enmDigestType,
2183 hIntermediateCerts, NULL /*pvResult*/, &cbResult, RTErrInfoInitStatic(pErrInfo));
2184 if (rc == VERR_BUFFER_OVERFLOW)
2185 {
2186 /*
2187 * Allocate a buffer of the right size and do the real run.
2188 */
2189 void *pvResult = RTMemAllocZ(cbResult);
2190 if (pvResult)
2191 {
2192 rc = RTCrPkcs7SimpleSignSignedData(RTCRPKCS7SIGN_SD_F_DEATCHED | RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP,
2193 pCertificate, hPrivateKey, pvManifest, (size_t)cbManifest, enmDigestType,
2194 hIntermediateCerts, pvResult, &cbResult, RTErrInfoInitStatic(pErrInfo));
2195 if (RT_SUCCESS(rc))
2196 {
2197 /*
2198 * Add it to the signature file in PEM format.
2199 */
2200 rc = (int)RTCrPemWriteBlobToVfsFile(hVfsFileSignature, pvResult, cbResult, "CMS");
2201 if (RT_SUCCESS(rc))
2202 {
2203 if (iVerbosity > 1)
2204 RTMsgInfo("Created PKCS#7/CMS signature: %zu bytes, %s.",
2205 cbResult, RTCrDigestTypeToName(enmDigestType));
2206
2207 /*
2208 * Try decode and verify the signature.
2209 */
2210 rc = doCheckPkcs7Signature(pvResult, cbResult, pCertificate, hIntermediateCerts,
2211 pvManifest, (size_t)cbManifest, iVerbosity, pErrInfo);
2212 }
2213 else
2214 RTMsgError("RTCrPemWriteBlobToVfsFile failed: %Rrc", rc);
2215 }
2216 RTMemFree(pvResult);
2217 }
2218 else
2219 rc = RTMsgErrorRc(VERR_NO_MEMORY, "Out of memory!");
2220 }
2221 else
2222 RTMsgError("RTCrPkcs7SimpleSignSignedData failed: %Rrc%#RTeim", rc, &pErrInfo->Core);
2223 }
2224 }
2225 else
2226 RTMsgError("RTVfsFileReadAt failed: %Rrc", rc);
2227 RTMemFree(pvManifest);
2228 return rc;
2229}
2230
2231
2232/**
2233 * Performs the OVA signing, producing an in-memory cert-file.
2234 */
2235static int doTheOvaSigning(PRTCRX509CERTIFICATE pCertificate, RTCRKEY hPrivateKey, RTDIGESTTYPE enmDigestType,
2236 const char *pszManifestName, RTVFSFILE hVfsFileManifest,
2237 bool fPkcs7, unsigned cIntermediateCerts, const char **papszIntermediateCerts, unsigned iVerbosity,
2238 PRTERRINFOSTATIC pErrInfo, PRTVFSFILE phVfsFileSignature)
2239{
2240 /*
2241 * Determine the digest types, preferring SHA-256 for the OVA signature
2242 * and SHA-512 for the PKCS#7/CMS one. Try use different hashes for the two.
2243 */
2244 if (enmDigestType == RTDIGESTTYPE_UNKNOWN)
2245 {
2246 if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA256, NULL))
2247 enmDigestType = RTDIGESTTYPE_SHA256;
2248 else
2249 enmDigestType = RTDIGESTTYPE_SHA1;
2250 }
2251
2252 /** @todo Use SHA-3 instead, better diversity. @bugref{9734} */
2253 RTDIGESTTYPE enmPkcs7DigestType;
2254 if ( enmDigestType == RTDIGESTTYPE_SHA1
2255 || enmDigestType == RTDIGESTTYPE_SHA256
2256 || enmDigestType == RTDIGESTTYPE_SHA224)
2257 {
2258 /* Use a SHA-512 variant: */
2259 if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA512, NULL))
2260 enmPkcs7DigestType = RTDIGESTTYPE_SHA512;
2261 else if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA384, NULL))
2262 enmPkcs7DigestType = RTDIGESTTYPE_SHA384;
2263 /// @todo openssl misses these in check_padding_md() in rsa_pmeth.c, causing
2264 /// failure in EVP_PKEY_CTX_set_signature_md() and CMS_final().
2265 //else if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA512T256, NULL))
2266 // enmPkcs7DigestType = RTDIGESTTYPE_SHA512T256;
2267 //else if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA512T224, NULL))
2268 // enmPkcs7DigestType = RTDIGESTTYPE_SHA512T224;
2269 else
2270 enmPkcs7DigestType = RTDIGESTTYPE_SHA1;
2271 }
2272 else /* The .cert file uses SHA-512, pick SHA-256 for diversity. */
2273 enmPkcs7DigestType = RTDIGESTTYPE_SHA256;
2274
2275 /*
2276 * Figure the string name for the .cert file.
2277 */
2278 const char *pszDigestType;
2279 switch (enmDigestType)
2280 {
2281 case RTDIGESTTYPE_SHA1: pszDigestType = "SHA1"; break;
2282 case RTDIGESTTYPE_SHA256: pszDigestType = "SHA256"; break;
2283 case RTDIGESTTYPE_SHA224: pszDigestType = "SHA224"; break;
2284 case RTDIGESTTYPE_SHA512: pszDigestType = "SHA512"; break;
2285 default:
2286 return RTMsgErrorRc(VERR_INVALID_PARAMETER,
2287 "Unsupported digest type: %s", RTCrDigestTypeToName(enmDigestType));
2288 }
2289
2290 /*
2291 * Digest the manifest file.
2292 */
2293 RTCRDIGEST hDigest = NIL_RTCRDIGEST;
2294 int rc = RTCrDigestCreateByType(&hDigest, enmDigestType);
2295 if (RT_FAILURE(rc))
2296 return RTMsgErrorRc(rc, "Failed to create digest for %s: %Rrc", RTCrDigestTypeToName(enmDigestType), rc);
2297
2298 rc = RTCrDigestUpdateFromVfsFile(hDigest, hVfsFileManifest, true /*fRewindFile*/);
2299 if (RT_SUCCESS(rc))
2300 rc = RTCrDigestFinal(hDigest, NULL, 0);
2301 if (RT_SUCCESS(rc))
2302 {
2303 /*
2304 * Sign the digest. Two passes, first to figure the signature size, the
2305 * second to do the actual signing.
2306 */
2307 PCRTASN1OBJID const pAlgorithm = &pCertificate->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm;
2308 PCRTASN1DYNTYPE const pAlgoParams = &pCertificate->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Parameters;
2309 size_t cbSignature = 0;
2310 rc = RTCrPkixPubKeySignDigest(pAlgorithm, hPrivateKey, pAlgoParams, hDigest, 0 /*fFlags*/,
2311 NULL /*pvSignature*/, &cbSignature, RTErrInfoInitStatic(pErrInfo));
2312 if (rc == VERR_BUFFER_OVERFLOW)
2313 {
2314 void *pvSignature = RTMemAllocZ(cbSignature);
2315 if (pvSignature)
2316 {
2317 rc = RTCrPkixPubKeySignDigest(pAlgorithm, hPrivateKey, pAlgoParams, hDigest, 0,
2318 pvSignature, &cbSignature, RTErrInfoInitStatic(pErrInfo));
2319 if (RT_SUCCESS(rc))
2320 {
2321 if (iVerbosity > 1)
2322 RTMsgInfo("Created OVA signature: %zu bytes, %s", cbSignature, RTCrDigestTypeToName(enmDigestType));
2323
2324 /*
2325 * Verify the signature using the certificate to make sure we've
2326 * been given the right private key.
2327 */
2328 rc = RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(&pCertificate->TbsCertificate.SubjectPublicKeyInfo,
2329 pvSignature, cbSignature, hDigest,
2330 RTErrInfoInitStatic(pErrInfo));
2331 if (RT_SUCCESS(rc))
2332 {
2333 if (iVerbosity > 2)
2334 RTMsgInfo(" Successfully decoded and verified the OVA signature.\n");
2335
2336 /*
2337 * Create the output file.
2338 */
2339 RTVFSFILE hVfsFileSignature;
2340 rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, _8K, &hVfsFileSignature);
2341 if (RT_SUCCESS(rc))
2342 {
2343 rc = (int)RTVfsFilePrintf(hVfsFileSignature, "%s(%s) = %#.*Rhxs\n\n",
2344 pszDigestType, pszManifestName, cbSignature, pvSignature);
2345 if (RT_SUCCESS(rc))
2346 {
2347 rc = (int)RTCrX509Certificate_WriteToVfsFile(hVfsFileSignature, pCertificate,
2348 RTErrInfoInitStatic(pErrInfo));
2349 if (RT_SUCCESS(rc))
2350 {
2351 if (fPkcs7)
2352 rc = doAddPkcs7Signature(pCertificate, hPrivateKey, enmPkcs7DigestType,
2353 cIntermediateCerts, papszIntermediateCerts, hVfsFileManifest,
2354 iVerbosity, pErrInfo, hVfsFileSignature);
2355 if (RT_SUCCESS(rc))
2356 {
2357 /*
2358 * Success.
2359 */
2360 *phVfsFileSignature = hVfsFileSignature;
2361 hVfsFileSignature = NIL_RTVFSFILE;
2362 }
2363 }
2364 else
2365 RTMsgError("Failed to write certificate to signature file: %Rrc%#RTeim", rc, &pErrInfo->Core);
2366 }
2367 else
2368 RTMsgError("Failed to produce signature file: %Rrc", rc);
2369 RTVfsFileRelease(hVfsFileSignature);
2370 }
2371 else
2372 RTMsgError("RTVfsMemFileCreate failed: %Rrc", rc);
2373 }
2374 else
2375 RTMsgError("Encountered a problem when validating the signature we just created: %Rrc%#RTeim\n"
2376 "Plase make sure the certificate and private key matches.", rc, &pErrInfo->Core);
2377 }
2378 else
2379 RTMsgError("2nd RTCrPkixPubKeySignDigest call failed: %Rrc%#RTeim", rc, pErrInfo->Core);
2380 RTMemFree(pvSignature);
2381 }
2382 else
2383 rc = RTMsgErrorRc(VERR_NO_MEMORY, "Out of memory!");
2384 }
2385 else
2386 RTMsgError("RTCrPkixPubKeySignDigest failed: %Rrc%#RTeim", rc, pErrInfo->Core);
2387 }
2388 else
2389 RTMsgError("Failed to create digest %s: %Rrc", RTCrDigestTypeToName(enmDigestType), rc);
2390 RTCrDigestRelease(hDigest);
2391 return rc;
2392}
2393
2394
2395/**
2396 * Handles the 'ovasign' command.
2397 */
2398RTEXITCODE handleSignAppliance(HandlerArg *arg)
2399{
2400 /*
2401 * Parse arguments.
2402 */
2403 static const RTGETOPTDEF s_aOptions[] =
2404 {
2405 { "--certificate", 'c', RTGETOPT_REQ_STRING },
2406 { "--private-key", 'k', RTGETOPT_REQ_STRING },
2407 { "--private-key-password", 'p', RTGETOPT_REQ_STRING },
2408 { "--private-key-password-file",'P', RTGETOPT_REQ_STRING },
2409 { "--digest-type", 'd', RTGETOPT_REQ_STRING },
2410 { "--pkcs7", '7', RTGETOPT_REQ_NOTHING },
2411 { "--cms", '7', RTGETOPT_REQ_NOTHING },
2412 { "--no-pkcs7", 'n', RTGETOPT_REQ_NOTHING },
2413 { "--no-cms", 'n', RTGETOPT_REQ_NOTHING },
2414 { "--intermediate-cert-file", 'i', RTGETOPT_REQ_STRING },
2415 { "--force", 'f', RTGETOPT_REQ_NOTHING },
2416 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
2417 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
2418 { "--dry-run", 'D', RTGETOPT_REQ_NOTHING },
2419 };
2420
2421 RTGETOPTSTATE GetState;
2422 int rc = RTGetOptInit(&GetState, arg->argc, arg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
2423 AssertRCReturn(rc, RTEXITCODE_FAILURE);
2424
2425 const char *pszOva = NULL;
2426 const char *pszCertificate = NULL;
2427 const char *pszPrivateKey = NULL;
2428 Utf8Str strPrivateKeyPassword;
2429 RTDIGESTTYPE enmDigestType = RTDIGESTTYPE_UNKNOWN;
2430 bool fPkcs7 = true;
2431 unsigned cIntermediateCerts = 0;
2432 const char *apszIntermediateCerts[32];
2433 bool fReSign = false;
2434 unsigned iVerbosity = 1;
2435 bool fDryRun = false;
2436
2437 int c;
2438 RTGETOPTUNION ValueUnion;
2439 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2440 {
2441 switch (c)
2442 {
2443 case 'c':
2444 pszCertificate = ValueUnion.psz;
2445 break;
2446
2447 case 'k':
2448 pszPrivateKey = ValueUnion.psz;
2449 break;
2450
2451 case 'p':
2452 if (strPrivateKeyPassword.isNotEmpty())
2453 RTMsgWarning("Password is given more than once.");
2454 strPrivateKeyPassword = ValueUnion.psz;
2455 break;
2456
2457 case 'P':
2458 {
2459 if (strPrivateKeyPassword.isNotEmpty())
2460 RTMsgWarning("Password is given more than once.");
2461 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPrivateKeyPassword);
2462 if (rcExit == RTEXITCODE_SUCCESS)
2463 break;
2464 return rcExit;
2465 }
2466
2467 case 'd':
2468 if ( RTStrICmp(ValueUnion.psz, "sha1") == 0
2469 || RTStrICmp(ValueUnion.psz, "sha-1") == 0)
2470 enmDigestType = RTDIGESTTYPE_SHA1;
2471 else if ( RTStrICmp(ValueUnion.psz, "sha256") == 0
2472 || RTStrICmp(ValueUnion.psz, "sha-256") == 0)
2473 enmDigestType = RTDIGESTTYPE_SHA256;
2474 else if ( RTStrICmp(ValueUnion.psz, "sha512") == 0
2475 || RTStrICmp(ValueUnion.psz, "sha-512") == 0)
2476 enmDigestType = RTDIGESTTYPE_SHA512;
2477 else
2478 return RTMsgErrorExitFailure("Unknown digest type: %s", ValueUnion.psz);
2479 break;
2480
2481 case '7':
2482 fPkcs7 = true;
2483 break;
2484
2485 case 'n':
2486 fPkcs7 = false;
2487 break;
2488
2489 case 'i':
2490 if (cIntermediateCerts >= RT_ELEMENTS(apszIntermediateCerts))
2491 return RTMsgErrorExitFailure("Too many intermediate certificates: max %zu",
2492 RT_ELEMENTS(apszIntermediateCerts));
2493 apszIntermediateCerts[cIntermediateCerts++] = ValueUnion.psz;
2494 fPkcs7 = true;
2495 break;
2496
2497 case 'f':
2498 fReSign = true;
2499 break;
2500
2501 case 'v':
2502 iVerbosity++;
2503 break;
2504
2505 case 'q':
2506 iVerbosity = 0;
2507 break;
2508
2509 case 'D':
2510 fDryRun = true;
2511 break;
2512
2513 case VINF_GETOPT_NOT_OPTION:
2514 if (!pszOva)
2515 {
2516 pszOva = ValueUnion.psz;
2517 break;
2518 }
2519 RT_FALL_THRU();
2520 default:
2521 return errorGetOpt(c, &ValueUnion);
2522 }
2523 }
2524
2525 /* Required paramaters: */
2526 if (!pszOva || !*pszOva)
2527 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No OVA file was specified!");
2528 if (!pszCertificate || !*pszCertificate)
2529 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No signing certificate (--certificate=<file>) was specified!");
2530 if (!pszPrivateKey || !*pszPrivateKey)
2531 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No signing private key (--private-key=<file>) was specified!");
2532
2533 /* Check that input files exists before we commence: */
2534 if (!RTFileExists(pszOva))
2535 return RTMsgErrorExitFailure("The specified OVA file was not found: %s", pszOva);
2536 if (!RTFileExists(pszCertificate))
2537 return RTMsgErrorExitFailure("The specified certificate file was not found: %s", pszCertificate);
2538 if (!RTFileExists(pszPrivateKey))
2539 return RTMsgErrorExitFailure("The specified private key file was not found: %s", pszPrivateKey);
2540
2541 /*
2542 * Open the OVA, read the manifest and look for any existing signature.
2543 */
2544 RTVFSFSSTREAM hVfsFssOva = NIL_RTVFSFSSTREAM;
2545 RTVFSOBJ hVfsOldSignature = NIL_RTVFSOBJ;
2546 RTVFSFILE hVfsFileManifest = NIL_RTVFSFILE;
2547 Utf8Str strManifestName;
2548 rc = openOvaAndGetManifestAndOldSignature(pszOva, iVerbosity, fReSign,
2549 &hVfsFssOva, &strManifestName, &hVfsFileManifest, &hVfsOldSignature);
2550 if (RT_SUCCESS(rc))
2551 {
2552 /*
2553 * Read the certificate and private key.
2554 */
2555 RTERRINFOSTATIC ErrInfo;
2556 RTCRX509CERTIFICATE Certificate;
2557 rc = RTCrX509Certificate_ReadFromFile(&Certificate, pszCertificate, 0, &g_RTAsn1DefaultAllocator,
2558 RTErrInfoInitStatic(&ErrInfo));
2559 if (RT_FAILURE(rc))
2560 return RTMsgErrorExitFailure("Error reading certificate from '%s': %Rrc%#RTeim", pszCertificate, rc, &ErrInfo.Core);
2561
2562 RTCRKEY hPrivateKey = NIL_RTCRKEY;
2563 rc = RTCrKeyCreateFromFile(&hPrivateKey, 0 /*fFlags*/, pszPrivateKey, strPrivateKeyPassword.c_str(),
2564 RTErrInfoInitStatic(&ErrInfo));
2565 if (RT_SUCCESS(rc))
2566 {
2567 if (iVerbosity > 1)
2568 RTMsgInfo("Successfully read the certificate and private key.");
2569
2570 /*
2571 * Do the signing and create the signature file.
2572 */
2573 RTVFSFILE hVfsFileSignature = NIL_RTVFSFILE;
2574 rc = doTheOvaSigning(&Certificate, hPrivateKey, enmDigestType, strManifestName.c_str(), hVfsFileManifest,
2575 fPkcs7, cIntermediateCerts, apszIntermediateCerts, iVerbosity, &ErrInfo, &hVfsFileSignature);
2576
2577 /*
2578 * Construct the signature filename:
2579 */
2580 if (RT_SUCCESS(rc))
2581 {
2582 Utf8Str strSignatureName;
2583 rc = strSignatureName.assignNoThrow(strManifestName);
2584 if (RT_SUCCESS(rc))
2585 rc = strSignatureName.stripSuffix().appendNoThrow(".cert");
2586 if (RT_SUCCESS(rc) && !fDryRun)
2587 {
2588 /*
2589 * Update the OVA.
2590 */
2591 rc = updateTheOvaSignature(hVfsFssOva, pszOva, strSignatureName.c_str(),
2592 hVfsFileSignature, hVfsOldSignature, iVerbosity);
2593 if (RT_SUCCESS(rc) && iVerbosity > 0)
2594 RTMsgInfo("Successfully signed '%s'.", pszOva);
2595 }
2596 }
2597 RTCrKeyRelease(hPrivateKey);
2598 }
2599 else
2600 RTPrintf("Error reading the private key from %s: %Rrc%#RTeim", pszPrivateKey, rc, &ErrInfo.Core);
2601 RTCrX509Certificate_Delete(&Certificate);
2602 }
2603
2604 RTVfsObjRelease(hVfsOldSignature);
2605 RTVfsFileRelease(hVfsFileManifest);
2606 RTVfsFsStrmRelease(hVfsFssOva);
2607
2608 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2609}
2610
2611#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