VirtualBox

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

Last change on this file since 88205 was 87278, checked in by vboxsync, 4 years ago

bugref:9907. Added support for cloud-init script.

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