VirtualBox

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

Last change on this file since 85551 was 85361, checked in by vboxsync, 4 years ago

Frontends/VBoxManage: remove unneeded #include in VBoxManageAppliance.cpp

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 122.8 KB
Line 
1/* $Id: VBoxManageAppliance.cpp 85361 2020-07-16 09:48:35Z 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 /** @todo VirtualSystemDescriptionType_Miscellaneous? */
1116 break;
1117
1118 case VirtualSystemDescriptionType_Ignore:
1119#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
1120 case VirtualSystemDescriptionType_32BitHack:
1121#endif
1122 break;
1123 }
1124
1125 bstrFinalValue.detachTo(&aFinalValues[a]);
1126 }
1127
1128 if (fExecute)
1129 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
1130 SetFinalValues(ComSafeArrayAsInParam(aEnabled),
1131 ComSafeArrayAsInParam(aFinalValues),
1132 ComSafeArrayAsInParam(aExtraConfigValues)));
1133
1134 } // for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
1135
1136 if (cLicensesInTheWay == 1)
1137 RTMsgError("Cannot import until the license agreement listed above is accepted.");
1138 else if (cLicensesInTheWay > 1)
1139 RTMsgError("Cannot import until the %c license agreements listed above are accepted.", cLicensesInTheWay);
1140
1141 if (!cLicensesInTheWay && fExecute)
1142 {
1143 // go!
1144 ComPtr<IProgress> progress;
1145 CHECK_ERROR_BREAK(pAppliance,
1146 ImportMachines(ComSafeArrayAsInParam(options), progress.asOutParam()));
1147
1148 rc = showProgress(progress);
1149 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance import failed"), RTEXITCODE_FAILURE);
1150
1151 if (SUCCEEDED(rc))
1152 RTPrintf("Successfully imported the appliance.\n");
1153 }
1154 } // end if (aVirtualSystemDescriptions.size() > 0)
1155 } while (0);
1156
1157 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1158}
1159
1160static int parseExportOptions(const char *psz, com::SafeArray<ExportOptions_T> *options)
1161{
1162 int rc = VINF_SUCCESS;
1163 while (psz && *psz && RT_SUCCESS(rc))
1164 {
1165 size_t len;
1166 const char *pszComma = strchr(psz, ',');
1167 if (pszComma)
1168 len = pszComma - psz;
1169 else
1170 len = strlen(psz);
1171 if (len > 0)
1172 {
1173 if (!RTStrNICmp(psz, "CreateManifest", len))
1174 options->push_back(ExportOptions_CreateManifest);
1175 else if (!RTStrNICmp(psz, "manifest", len))
1176 options->push_back(ExportOptions_CreateManifest);
1177 else if (!RTStrNICmp(psz, "ExportDVDImages", len))
1178 options->push_back(ExportOptions_ExportDVDImages);
1179 else if (!RTStrNICmp(psz, "iso", len))
1180 options->push_back(ExportOptions_ExportDVDImages);
1181 else if (!RTStrNICmp(psz, "StripAllMACs", len))
1182 options->push_back(ExportOptions_StripAllMACs);
1183 else if (!RTStrNICmp(psz, "nomacs", len))
1184 options->push_back(ExportOptions_StripAllMACs);
1185 else if (!RTStrNICmp(psz, "StripAllNonNATMACs", len))
1186 options->push_back(ExportOptions_StripAllNonNATMACs);
1187 else if (!RTStrNICmp(psz, "nomacsbutnat", len))
1188 options->push_back(ExportOptions_StripAllNonNATMACs);
1189 else
1190 rc = VERR_PARSE_ERROR;
1191 }
1192 if (pszComma)
1193 psz += len + 1;
1194 else
1195 psz += len;
1196 }
1197
1198 return rc;
1199}
1200
1201static const RTGETOPTDEF g_aExportOptions[] =
1202{
1203 { "--output", 'o', RTGETOPT_REQ_STRING },
1204 { "--legacy09", 'l', RTGETOPT_REQ_NOTHING },
1205 { "--ovf09", 'l', RTGETOPT_REQ_NOTHING },
1206 { "--ovf10", '1', RTGETOPT_REQ_NOTHING },
1207 { "--ovf20", '2', RTGETOPT_REQ_NOTHING },
1208 { "--opc10", 'c', RTGETOPT_REQ_NOTHING },
1209 { "--manifest", 'm', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1210 { "--iso", 'I', 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};
1236
1237RTEXITCODE handleExportAppliance(HandlerArg *a)
1238{
1239 HRESULT rc = S_OK;
1240
1241 Utf8Str strOutputFile;
1242 Utf8Str strOvfFormat("ovf-1.0"); // the default export version
1243 bool fManifest = false; // the default
1244 bool fCloud = false; // the default
1245 actionType = NOT_SET;
1246 bool fExportISOImages = false; // the default
1247 com::SafeArray<ExportOptions_T> options;
1248 std::list< ComPtr<IMachine> > llMachines;
1249
1250 uint32_t ulCurVsys = (uint32_t)-1;
1251 // for each --vsys X command, maintain a map of command line items
1252 ArgsMapsMap mapArgsMapsPerVsys;
1253 do
1254 {
1255 int c;
1256
1257 RTGETOPTUNION ValueUnion;
1258 RTGETOPTSTATE GetState;
1259 // start at 0 because main() has hacked both the argc and argv given to us
1260 RTGetOptInit(&GetState, a->argc, a->argv, g_aExportOptions,
1261 RT_ELEMENTS(g_aExportOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
1262
1263 Utf8Str strProductUrl;
1264 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1265 {
1266 switch (c)
1267 {
1268 case 'o': // --output
1269 if (strOutputFile.length())
1270 return errorSyntax(USAGE_EXPORTAPPLIANCE, "You can only specify --output once.");
1271 else
1272 strOutputFile = ValueUnion.psz;
1273 break;
1274
1275 case 'l': // --legacy09/--ovf09
1276 strOvfFormat = "ovf-0.9";
1277 break;
1278
1279 case '1': // --ovf10
1280 strOvfFormat = "ovf-1.0";
1281 break;
1282
1283 case '2': // --ovf20
1284 strOvfFormat = "ovf-2.0";
1285 break;
1286
1287 case 'c': // --opc
1288 strOvfFormat = "opc-1.0";
1289 break;
1290
1291 case 'I': // --iso
1292 fExportISOImages = true;
1293 break;
1294
1295 case 'm': // --manifest
1296 fManifest = true;
1297 break;
1298
1299 case 's': // --vsys
1300 if (fCloud == false && actionType == NOT_SET)
1301 actionType = LOCAL;
1302
1303 if (actionType != LOCAL)
1304 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1305 "Option \"%s\" can't be used together with \"--cloud\" argument.",
1306 GetState.pDef->pszLong);
1307
1308 ulCurVsys = ValueUnion.u32;
1309 break;
1310
1311 case 'V': // --vmname
1312 if (actionType == NOT_SET || ulCurVsys == (uint32_t)-1)
1313 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys or --cloud argument.",
1314 GetState.pDef->pszLong);
1315 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
1316 break;
1317
1318 case 'p': // --product
1319 if (actionType != LOCAL)
1320 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1321 mapArgsMapsPerVsys[ulCurVsys]["product"] = ValueUnion.psz;
1322 break;
1323
1324 case 'P': // --producturl
1325 if (actionType != LOCAL)
1326 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1327 mapArgsMapsPerVsys[ulCurVsys]["producturl"] = ValueUnion.psz;
1328 break;
1329
1330 case 'n': // --vendor
1331 if (actionType != LOCAL)
1332 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1333 mapArgsMapsPerVsys[ulCurVsys]["vendor"] = ValueUnion.psz;
1334 break;
1335
1336 case 'N': // --vendorurl
1337 if (actionType != LOCAL)
1338 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1339 mapArgsMapsPerVsys[ulCurVsys]["vendorurl"] = ValueUnion.psz;
1340 break;
1341
1342 case 'v': // --version
1343 if (actionType != LOCAL)
1344 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1345 mapArgsMapsPerVsys[ulCurVsys]["version"] = ValueUnion.psz;
1346 break;
1347
1348 case 'd': // --description
1349 if (actionType != LOCAL)
1350 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1351 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
1352 break;
1353
1354 case 'e': // --eula
1355 if (actionType != LOCAL)
1356 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1357 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
1358 break;
1359
1360 case 'E': // --eulafile
1361 if (actionType != LOCAL)
1362 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1363 mapArgsMapsPerVsys[ulCurVsys]["eulafile"] = ValueUnion.psz;
1364 break;
1365
1366 case 'O': // --options
1367 if (RT_FAILURE(parseExportOptions(ValueUnion.psz, &options)))
1368 return errorArgument("Invalid export options '%s'\n", ValueUnion.psz);
1369 break;
1370
1371 /*--cloud and --vsys are orthogonal, only one must be presented*/
1372 case 'C': // --cloud
1373 if (fCloud == false && actionType == NOT_SET)
1374 {
1375 fCloud = true;
1376 actionType = CLOUD;
1377 }
1378
1379 if (actionType != CLOUD)
1380 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1381 "Option \"%s\" can't be used together with \"--vsys\" argument.",
1382 GetState.pDef->pszLong);
1383
1384 ulCurVsys = ValueUnion.u32;
1385 break;
1386
1387 /* Cloud export settings */
1388 case 'S': // --cloudshape
1389 if (actionType != CLOUD)
1390 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1391 GetState.pDef->pszLong);
1392 mapArgsMapsPerVsys[ulCurVsys]["cloudshape"] = ValueUnion.psz;
1393 break;
1394
1395 case 'D': // --clouddomain
1396 if (actionType != CLOUD)
1397 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1398 GetState.pDef->pszLong);
1399 mapArgsMapsPerVsys[ulCurVsys]["clouddomain"] = ValueUnion.psz;
1400 break;
1401
1402 case 'R': // --clouddisksize
1403 if (actionType != CLOUD)
1404 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1405 GetState.pDef->pszLong);
1406 mapArgsMapsPerVsys[ulCurVsys]["clouddisksize"] = ValueUnion.psz;
1407 break;
1408
1409 case 'B': // --cloudbucket
1410 if (actionType != CLOUD)
1411 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1412 GetState.pDef->pszLong);
1413 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
1414 break;
1415
1416 case 'Q': // --cloudocivcn
1417 if (actionType != CLOUD)
1418 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1419 GetState.pDef->pszLong);
1420 mapArgsMapsPerVsys[ulCurVsys]["cloudocivcn"] = ValueUnion.psz;
1421 break;
1422
1423 case 'A': // --cloudpublicip
1424 if (actionType != CLOUD)
1425 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1426 GetState.pDef->pszLong);
1427 mapArgsMapsPerVsys[ulCurVsys]["cloudpublicip"] = ValueUnion.psz;
1428 break;
1429
1430 case 'i': /* --cloudprivateip */
1431 if (actionType != CLOUD)
1432 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1433 GetState.pDef->pszLong);
1434 mapArgsMapsPerVsys[ulCurVsys]["cloudprivateip"] = ValueUnion.psz;
1435 break;
1436
1437 case 'F': // --cloudprofile
1438 if (actionType != CLOUD)
1439 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1440 GetState.pDef->pszLong);
1441 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
1442 break;
1443
1444 case 'T': // --cloudocisubnet
1445 if (actionType != CLOUD)
1446 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1447 GetState.pDef->pszLong);
1448 mapArgsMapsPerVsys[ulCurVsys]["cloudocisubnet"] = ValueUnion.psz;
1449 break;
1450
1451 case 'K': // --cloudkeepobject
1452 if (actionType != CLOUD)
1453 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1454 GetState.pDef->pszLong);
1455 mapArgsMapsPerVsys[ulCurVsys]["cloudkeepobject"] = ValueUnion.psz;
1456 break;
1457
1458 case 'L': // --cloudlaunchinstance
1459 if (actionType != CLOUD)
1460 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1461 GetState.pDef->pszLong);
1462 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchinstance"] = ValueUnion.psz;
1463 break;
1464
1465 case 'M': /* --cloudlaunchmode */
1466 if (actionType != CLOUD)
1467 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1468 GetState.pDef->pszLong);
1469 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchmode"] = ValueUnion.psz;
1470 break;
1471
1472 case VINF_GETOPT_NOT_OPTION:
1473 {
1474 Utf8Str strMachine(ValueUnion.psz);
1475 // must be machine: try UUID or name
1476 ComPtr<IMachine> machine;
1477 CHECK_ERROR_BREAK(a->virtualBox, FindMachine(Bstr(strMachine).raw(),
1478 machine.asOutParam()));
1479 if (machine)
1480 llMachines.push_back(machine);
1481 break;
1482 }
1483
1484 default:
1485 if (c > 0)
1486 {
1487 if (RT_C_IS_GRAPH(c))
1488 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: -%c", c);
1489 else
1490 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: %i", c);
1491 }
1492 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
1493 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unknown option: %s", ValueUnion.psz);
1494 else if (ValueUnion.pDef)
1495 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
1496 else
1497 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%Rrs", c);
1498 }
1499
1500 if (FAILED(rc))
1501 break;
1502 }
1503
1504 if (FAILED(rc))
1505 break;
1506
1507 if (llMachines.empty())
1508 return errorSyntax(USAGE_EXPORTAPPLIANCE, "At least one machine must be specified with the export command.");
1509
1510 /* Last check after parsing all arguments */
1511 if (strOutputFile.isNotEmpty())
1512 {
1513 if (actionType == NOT_SET)
1514 {
1515 if (fCloud)
1516 actionType = CLOUD;
1517 else
1518 actionType = LOCAL;
1519 }
1520 }
1521 else
1522 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Missing --output argument with export command.");
1523
1524 // match command line arguments with the machines count
1525 // this is only to sort out invalid indices at this time
1526 ArgsMapsMap::const_iterator it;
1527 for (it = mapArgsMapsPerVsys.begin();
1528 it != mapArgsMapsPerVsys.end();
1529 ++it)
1530 {
1531 uint32_t ulVsys = it->first;
1532 if (ulVsys >= llMachines.size())
1533 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1534 "Invalid index %RI32 with -vsys option; you specified only %zu virtual system(s).",
1535 ulVsys, llMachines.size());
1536 }
1537
1538 ComPtr<IAppliance> pAppliance;
1539 CHECK_ERROR_BREAK(a->virtualBox, CreateAppliance(pAppliance.asOutParam()));
1540
1541 char *pszAbsFilePath = 0;
1542 if (strOutputFile.startsWith("S3://", RTCString::CaseInsensitive) ||
1543 strOutputFile.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
1544 strOutputFile.startsWith("webdav://", RTCString::CaseInsensitive) ||
1545 strOutputFile.startsWith("OCI://", RTCString::CaseInsensitive))
1546 pszAbsFilePath = RTStrDup(strOutputFile.c_str());
1547 else
1548 pszAbsFilePath = RTPathAbsDup(strOutputFile.c_str());
1549
1550 /*
1551 * The first stage - export machine/s to the Cloud or into the
1552 * OVA/OVF format on the local host.
1553 */
1554
1555 /* VSDList is needed for the second stage where we launch the cloud instances if it was requested by user */
1556 std::list< ComPtr<IVirtualSystemDescription> > VSDList;
1557 std::list< ComPtr<IMachine> >::iterator itM;
1558 uint32_t i=0;
1559 for (itM = llMachines.begin();
1560 itM != llMachines.end();
1561 ++itM, ++i)
1562 {
1563 ComPtr<IMachine> pMachine = *itM;
1564 ComPtr<IVirtualSystemDescription> pVSD;
1565 CHECK_ERROR_BREAK(pMachine, ExportTo(pAppliance, Bstr(pszAbsFilePath).raw(), pVSD.asOutParam()));
1566
1567 // Add additional info to the virtual system description if the user wants so
1568 ArgsMap *pmapArgs = NULL;
1569 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
1570 if (itm != mapArgsMapsPerVsys.end())
1571 pmapArgs = &itm->second;
1572 if (pmapArgs)
1573 {
1574 ArgsMap::iterator itD;
1575 for (itD = pmapArgs->begin();
1576 itD != pmapArgs->end();
1577 ++itD)
1578 {
1579 if (itD->first == "vmname")
1580 {
1581 //remove default value if user has specified new name (default value is set in the ExportTo())
1582// pVSD->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
1583 pVSD->AddDescription(VirtualSystemDescriptionType_Name,
1584 Bstr(itD->second).raw(), NULL);
1585 }
1586 else if (itD->first == "product")
1587 pVSD->AddDescription(VirtualSystemDescriptionType_Product,
1588 Bstr(itD->second).raw(), NULL);
1589 else if (itD->first == "producturl")
1590 pVSD->AddDescription(VirtualSystemDescriptionType_ProductUrl,
1591 Bstr(itD->second).raw(), NULL);
1592 else if (itD->first == "vendor")
1593 pVSD->AddDescription(VirtualSystemDescriptionType_Vendor,
1594 Bstr(itD->second).raw(), NULL);
1595 else if (itD->first == "vendorurl")
1596 pVSD->AddDescription(VirtualSystemDescriptionType_VendorUrl,
1597 Bstr(itD->second).raw(), NULL);
1598 else if (itD->first == "version")
1599 pVSD->AddDescription(VirtualSystemDescriptionType_Version,
1600 Bstr(itD->second).raw(), NULL);
1601 else if (itD->first == "description")
1602 pVSD->AddDescription(VirtualSystemDescriptionType_Description,
1603 Bstr(itD->second).raw(), NULL);
1604 else if (itD->first == "eula")
1605 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1606 Bstr(itD->second).raw(), NULL);
1607 else if (itD->first == "eulafile")
1608 {
1609 Utf8Str strContent;
1610 void *pvFile;
1611 size_t cbFile;
1612 int irc = RTFileReadAll(itD->second.c_str(), &pvFile, &cbFile);
1613 if (RT_SUCCESS(irc))
1614 {
1615 Bstr bstrContent((char*)pvFile, cbFile);
1616 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1617 bstrContent.raw(), NULL);
1618 RTFileReadAllFree(pvFile, cbFile);
1619 }
1620 else
1621 {
1622 RTMsgError("Cannot read license file \"%s\" which should be included in the virtual system %u.",
1623 itD->second.c_str(), i);
1624 return RTEXITCODE_FAILURE;
1625 }
1626 }
1627 /* add cloud export settings */
1628 else if (itD->first == "cloudshape")
1629 pVSD->AddDescription(VirtualSystemDescriptionType_CloudInstanceShape,
1630 Bstr(itD->second).raw(), NULL);
1631 else if (itD->first == "clouddomain")
1632 pVSD->AddDescription(VirtualSystemDescriptionType_CloudDomain,
1633 Bstr(itD->second).raw(), NULL);
1634 else if (itD->first == "clouddisksize")
1635 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBootDiskSize,
1636 Bstr(itD->second).raw(), NULL);
1637 else if (itD->first == "cloudbucket")
1638 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBucket,
1639 Bstr(itD->second).raw(), NULL);
1640 else if (itD->first == "cloudocivcn")
1641 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCIVCN,
1642 Bstr(itD->second).raw(), NULL);
1643 else if (itD->first == "cloudpublicip")
1644 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPublicIP,
1645 Bstr(itD->second).raw(), NULL);
1646 else if (itD->first == "cloudprivateip")
1647 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPrivateIP,
1648 Bstr(itD->second).raw(), NULL);
1649 else if (itD->first == "cloudprofile")
1650 pVSD->AddDescription(VirtualSystemDescriptionType_CloudProfileName,
1651 Bstr(itD->second).raw(), NULL);
1652 else if (itD->first == "cloudocisubnet")
1653 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCISubnet,
1654 Bstr(itD->second).raw(), NULL);
1655 else if (itD->first == "cloudkeepobject")
1656 pVSD->AddDescription(VirtualSystemDescriptionType_CloudKeepObject,
1657 Bstr(itD->second).raw(), NULL);
1658 else if (itD->first == "cloudlaunchmode")
1659 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCILaunchMode,
1660 Bstr(itD->second).raw(), NULL);
1661 else if (itD->first == "cloudlaunchinstance")
1662 pVSD->AddDescription(VirtualSystemDescriptionType_CloudLaunchInstance,
1663 Bstr(itD->second).raw(), NULL);
1664 }
1665 }
1666
1667 VSDList.push_back(pVSD);//store vsd for the possible second stage
1668 }
1669
1670 if (FAILED(rc))
1671 break;
1672
1673 /* Query required passwords and supply them to the appliance. */
1674 com::SafeArray<BSTR> aIdentifiers;
1675
1676 CHECK_ERROR_BREAK(pAppliance, GetPasswordIds(ComSafeArrayAsOutParam(aIdentifiers)));
1677
1678 if (aIdentifiers.size() > 0)
1679 {
1680 com::SafeArray<BSTR> aPasswords(aIdentifiers.size());
1681 RTPrintf("Enter the passwords for the following identifiers to export the apppliance:\n");
1682 for (unsigned idxId = 0; idxId < aIdentifiers.size(); idxId++)
1683 {
1684 com::Utf8Str strPassword;
1685 Bstr bstrPassword;
1686 Bstr bstrId = aIdentifiers[idxId];
1687
1688 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Password ID %s:", Utf8Str(bstrId).c_str());
1689 if (rcExit == RTEXITCODE_FAILURE)
1690 {
1691 RTStrFree(pszAbsFilePath);
1692 return rcExit;
1693 }
1694
1695 bstrPassword = strPassword;
1696 bstrPassword.detachTo(&aPasswords[idxId]);
1697 }
1698
1699 CHECK_ERROR_BREAK(pAppliance, AddPasswords(ComSafeArrayAsInParam(aIdentifiers),
1700 ComSafeArrayAsInParam(aPasswords)));
1701 }
1702
1703 if (fManifest)
1704 options.push_back(ExportOptions_CreateManifest);
1705
1706 if (fExportISOImages)
1707 options.push_back(ExportOptions_ExportDVDImages);
1708
1709 ComPtr<IProgress> progress;
1710 CHECK_ERROR_BREAK(pAppliance, Write(Bstr(strOvfFormat).raw(),
1711 ComSafeArrayAsInParam(options),
1712 Bstr(pszAbsFilePath).raw(),
1713 progress.asOutParam()));
1714 RTStrFree(pszAbsFilePath);
1715
1716 rc = showProgress(progress);
1717 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance write failed"), RTEXITCODE_FAILURE);
1718
1719 if (SUCCEEDED(rc))
1720 RTPrintf("Successfully exported %d machine(s).\n", llMachines.size());
1721
1722 /*
1723 * The second stage for the cloud case
1724 */
1725 if (actionType == CLOUD)
1726 {
1727 /* Launch the exported VM if the appropriate flag had been set on the first stage */
1728 for (std::list< ComPtr<IVirtualSystemDescription> >::iterator itVSD = VSDList.begin();
1729 itVSD != VSDList.end();
1730 ++itVSD)
1731 {
1732 ComPtr<IVirtualSystemDescription> pVSD = *itVSD;
1733
1734 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
1735 com::SafeArray<BSTR> aRefs;
1736 com::SafeArray<BSTR> aOvfValues;
1737 com::SafeArray<BSTR> aVBoxValues;
1738 com::SafeArray<BSTR> aExtraConfigValues;
1739
1740 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudLaunchInstance,
1741 ComSafeArrayAsOutParam(retTypes),
1742 ComSafeArrayAsOutParam(aRefs),
1743 ComSafeArrayAsOutParam(aOvfValues),
1744 ComSafeArrayAsOutParam(aVBoxValues),
1745 ComSafeArrayAsOutParam(aExtraConfigValues)));
1746
1747 Utf8Str flagCloudLaunchInstance(Bstr(aVBoxValues[0]).raw());
1748 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1749
1750 if (flagCloudLaunchInstance.equals("true"))
1751 {
1752 /* Getting the short provider name */
1753 Bstr bstrCloudProviderShortName(strOutputFile.c_str(), strOutputFile.find("://"));
1754
1755 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
1756 ComPtr<ICloudProviderManager> pCloudProviderManager;
1757 CHECK_ERROR_BREAK(pVirtualBox, COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()));
1758
1759 ComPtr<ICloudProvider> pCloudProvider;
1760 CHECK_ERROR_BREAK(pCloudProviderManager,
1761 GetProviderByShortName(bstrCloudProviderShortName.raw(), pCloudProvider.asOutParam()));
1762
1763 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudProfileName,
1764 ComSafeArrayAsOutParam(retTypes),
1765 ComSafeArrayAsOutParam(aRefs),
1766 ComSafeArrayAsOutParam(aOvfValues),
1767 ComSafeArrayAsOutParam(aVBoxValues),
1768 ComSafeArrayAsOutParam(aExtraConfigValues)));
1769
1770 ComPtr<ICloudProfile> pCloudProfile;
1771 CHECK_ERROR_BREAK(pCloudProvider, GetProfileByName(Bstr(aVBoxValues[0]).raw(), pCloudProfile.asOutParam()));
1772 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1773
1774 ComObjPtr<ICloudClient> oCloudClient;
1775 CHECK_ERROR_BREAK(pCloudProfile, CreateCloudClient(oCloudClient.asOutParam()));
1776 RTPrintf("Creating a cloud instance...\n");
1777
1778 ComPtr<IProgress> progress1;
1779 CHECK_ERROR_BREAK(oCloudClient, LaunchVM(pVSD, progress1.asOutParam()));
1780 rc = showProgress(progress1);
1781 CHECK_PROGRESS_ERROR_RET(progress1, ("Creating the cloud instance failed"), RTEXITCODE_FAILURE);
1782
1783 if (SUCCEEDED(rc))
1784 {
1785 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudInstanceId,
1786 ComSafeArrayAsOutParam(retTypes),
1787 ComSafeArrayAsOutParam(aRefs),
1788 ComSafeArrayAsOutParam(aOvfValues),
1789 ComSafeArrayAsOutParam(aVBoxValues),
1790 ComSafeArrayAsOutParam(aExtraConfigValues)));
1791
1792 RTPrintf("A cloud instance with id '%s' (provider '%s') was created\n",
1793 Utf8Str(Bstr(aVBoxValues[0]).raw()).c_str(),
1794 Utf8Str(bstrCloudProviderShortName.raw()).c_str());
1795 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1796 }
1797 }
1798 }
1799 }
1800 } while (0);
1801
1802 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1803}
1804
1805
1806/*********************************************************************************************************************************
1807* signova *
1808*********************************************************************************************************************************/
1809
1810/**
1811 * Reads the OVA and saves the manifest and signed status.
1812 *
1813 * @returns VBox status code (fully messaged).
1814 * @param pszOva The name of the OVA.
1815 * @param iVerbosity The noise level.
1816 * @param fReSign Whether it is acceptable to have an existing signature
1817 * in the OVA or not.
1818 * @param phVfsFssOva Where to return the OVA file system stream handle.
1819 * This has been opened for updating and we're positioned
1820 * at the end of the stream.
1821 * @param pStrManifestName Where to return the manifest name.
1822 * @param phVfsManifest Where to return the manifest file handle (copy in mem).
1823 * @param phVfsOldSignature Where to return the handle to the old signature object.
1824 *
1825 * @note Caller must clean up return values on failure too!
1826 */
1827static int openOvaAndGetManifestAndOldSignature(const char *pszOva, unsigned iVerbosity, bool fReSign,
1828 PRTVFSFSSTREAM phVfsFssOva, Utf8Str *pStrManifestName,
1829 PRTVFSFILE phVfsManifest, PRTVFSOBJ phVfsOldSignature)
1830{
1831 /*
1832 * Clear return values.
1833 */
1834 *phVfsFssOva = NIL_RTVFSFSSTREAM;
1835 pStrManifestName->setNull();
1836 *phVfsManifest = NIL_RTVFSFILE;
1837 *phVfsOldSignature = NIL_RTVFSOBJ;
1838
1839 /*
1840 * Open the file as a tar file system stream.
1841 */
1842 RTVFSFILE hVfsFileOva;
1843 int rc = RTVfsFileOpenNormal(pszOva, RTFILE_O_OPEN | RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE, &hVfsFileOva);
1844 if (RT_FAILURE(rc))
1845 return RTMsgErrorExitFailure("Failed to open OVA '%s' for updating: %Rrc", pszOva, rc);
1846
1847 RTVFSFSSTREAM hVfsFssOva;
1848 rc = RTZipTarFsStreamForFile(hVfsFileOva, RTZIPTARFORMAT_DEFAULT, RTZIPTAR_C_UPDATE, &hVfsFssOva);
1849 RTVfsFileRelease(hVfsFileOva);
1850 if (RT_FAILURE(rc))
1851 return RTMsgErrorExitFailure("Failed to open OVA '%s' as a TAR file: %Rrc", pszOva, rc);
1852 *phVfsFssOva = hVfsFssOva;
1853
1854 /*
1855 * Scan the objects in the stream and locate the manifest and any existing cert file.
1856 */
1857 if (iVerbosity >= 2)
1858 RTMsgInfo("Scanning OVA '%s' for a manifest and signature...", pszOva);
1859 char *pszSignatureName = NULL;
1860 for (;;)
1861 {
1862 /*
1863 * Retrive the next object.
1864 */
1865 char *pszName;
1866 RTVFSOBJTYPE enmType;
1867 RTVFSOBJ hVfsObj;
1868 rc = RTVfsFsStrmNext(hVfsFssOva, &pszName, &enmType, &hVfsObj);
1869 if (RT_FAILURE(rc))
1870 {
1871 if (rc == VERR_EOF)
1872 rc = VINF_SUCCESS;
1873 else
1874 RTMsgError("RTVfsFsStrmNext returned %Rrc", rc);
1875 break;
1876 }
1877
1878 if (iVerbosity > 2)
1879 RTMsgInfo(" %s %s\n", RTVfsTypeName(enmType), pszName);
1880
1881 /*
1882 * Should we process this entry?
1883 */
1884 const char *pszSuffix = RTPathSuffix(pszName);
1885 if ( pszSuffix
1886 && RTStrICmpAscii(pszSuffix, ".mf") == 0
1887 && (enmType == RTVFSOBJTYPE_IO_STREAM || enmType == RTVFSOBJTYPE_FILE))
1888 {
1889 if (*phVfsManifest != NIL_RTVFSFILE)
1890 rc = RTMsgErrorRc(VERR_DUPLICATE, "OVA contains multiple manifests! first: %s second: %s",
1891 pStrManifestName->c_str(), pszName);
1892 else if (pszSignatureName)
1893 rc = RTMsgErrorRc(VERR_WRONG_ORDER, "Unsupported OVA file ordering! Signature file ('%s') as succeeded by '%s'.",
1894 pszSignatureName, pszName);
1895 else
1896 {
1897 if (iVerbosity >= 2)
1898 RTMsgInfo("Found manifest file: %s", pszName);
1899 rc = pStrManifestName->assignNoThrow(pszName);
1900 if (RT_SUCCESS(rc))
1901 {
1902 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
1903 Assert(hVfsIos != NIL_RTVFSIOSTREAM);
1904 rc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, phVfsManifest);
1905 RTVfsIoStrmRelease(hVfsIos); /* consumes stream handle. */
1906 if (RT_FAILURE(rc))
1907 rc = RTMsgErrorRc(VERR_DUPLICATE, "Failed to memorize the manifest: %Rrc", rc);
1908 }
1909 else
1910 RTMsgError("Out of memory!");
1911 }
1912 }
1913 else if ( pszSuffix
1914 && RTStrICmpAscii(pszSuffix, ".cert") == 0
1915 && (enmType == RTVFSOBJTYPE_IO_STREAM || enmType == RTVFSOBJTYPE_FILE))
1916 {
1917 if (*phVfsOldSignature != NIL_RTVFSOBJ)
1918 rc = RTMsgErrorRc(VERR_WRONG_ORDER, "Multiple signature files! (%s)", pszName);
1919 else
1920 {
1921 if (iVerbosity >= 2)
1922 RTMsgInfo("Found existing signature file: %s", pszName);
1923 pszSignatureName = pszName;
1924 *phVfsOldSignature = hVfsObj;
1925 pszName = NULL;
1926 hVfsObj = NIL_RTVFSOBJ;
1927 }
1928 }
1929 else if (pszSignatureName)
1930 rc = RTMsgErrorRc(VERR_WRONG_ORDER, "Unsupported OVA file ordering! Signature file ('%s') as succeeded by '%s'.",
1931 pszSignatureName, pszName);
1932
1933 /*
1934 * Release the current object and string.
1935 */
1936 RTVfsObjRelease(hVfsObj);
1937 RTStrFree(pszName);
1938 if (RT_FAILURE(rc))
1939 break;
1940 }
1941
1942 /*
1943 * Complain if no manifest.
1944 */
1945 if (RT_SUCCESS(rc) && *phVfsManifest == NIL_RTVFSFILE)
1946 rc = RTMsgErrorRc(VERR_NOT_FOUND, "The OVA contains no manifest and cannot be signed!");
1947 else if (RT_SUCCESS(rc) && *phVfsOldSignature != NIL_RTVFSOBJ && !fReSign)
1948 rc = RTMsgErrorRc(VERR_ALREADY_EXISTS,
1949 "The OVA is already signed ('%s')! (Use the --force option to force re-signing it.)",
1950 pszSignatureName);
1951
1952 RTStrFree(pszSignatureName);
1953 return rc;
1954}
1955
1956
1957/**
1958 * Continues where openOvaAndGetManifestAndOldSignature() left off and writes
1959 * the signature file to the OVA.
1960 *
1961 * When @a hVfsOldSignature isn't NIL, the old signature it represent will be
1962 * replaced. The open function has already made sure there isn't anything
1963 * following the .cert file in that case.
1964 */
1965static int updateTheOvaSignature(RTVFSFSSTREAM hVfsFssOva, const char *pszOva, const char *pszSignatureName,
1966 RTVFSFILE hVfsFileSignature, RTVFSOBJ hVfsOldSignature, unsigned iVerbosity)
1967{
1968 if (iVerbosity > 1)
1969 RTMsgInfo("Writing '%s' to the OVA...", pszSignatureName);
1970
1971 /*
1972 * Truncate the file at the old signature, if present.
1973 */
1974 int rc;
1975 if (hVfsOldSignature != NIL_RTVFSOBJ)
1976 {
1977 rc = RTZipTarFsStreamTruncate(hVfsFssOva, hVfsOldSignature, false /*fAfter*/);
1978 if (RT_FAILURE(rc))
1979 return RTMsgErrorRc(rc, "RTZipTarFsStreamTruncate failed on '%s': %Rrc", pszOva, rc);
1980 }
1981
1982 /*
1983 * Append the signature file. We have to rewind it first or
1984 * we'll end up with VERR_EOF, probably not a great idea...
1985 */
1986 rc = RTVfsFileSeek(hVfsFileSignature, 0, RTFILE_SEEK_BEGIN, NULL);
1987 if (RT_FAILURE(rc))
1988 return RTMsgErrorRc(rc, "RTVfsFileSeek(hVfsFileSignature) failed: %Rrc", rc);
1989
1990 RTVFSOBJ hVfsObj = RTVfsObjFromFile(hVfsFileSignature);
1991 rc = RTVfsFsStrmAdd(hVfsFssOva, pszSignatureName, hVfsObj, 0 /*fFlags*/);
1992 RTVfsObjRelease(hVfsObj);
1993 if (RT_FAILURE(rc))
1994 return RTMsgErrorRc(rc, "RTVfsFsStrmAdd('%s') failed on '%s': %Rrc", pszSignatureName, pszOva, rc);
1995
1996 /*
1997 * Terminate the file system stream.
1998 */
1999 rc = RTVfsFsStrmEnd(hVfsFssOva);
2000 if (RT_FAILURE(rc))
2001 return RTMsgErrorRc(rc, "RTVfsFsStrmEnd failed on '%s': %Rrc", pszOva, rc);
2002
2003 return VINF_SUCCESS;
2004}
2005
2006
2007/**
2008 * Worker for doCheckPkcs7Signature.
2009 */
2010static int doCheckPkcs7SignatureWorker(PRTCRPKCS7CONTENTINFO pContentInfo, void const *pvManifest, size_t cbManifest,
2011 unsigned iVerbosity, const char *pszTag, PRTERRINFOSTATIC pErrInfo)
2012{
2013 int rc;
2014
2015 /*
2016 * It must be signedData.
2017 */
2018 if (RTCrPkcs7ContentInfo_IsSignedData(pContentInfo))
2019 {
2020 PRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData;
2021
2022 /*
2023 * Inside the signedData there must be just 'data'.
2024 */
2025 if (!strcmp(pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID))
2026 {
2027 /*
2028 * Check that things add up.
2029 */
2030 rc = RTCrPkcs7SignedData_CheckSanity(pSignedData,
2031 RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH
2032 | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT,
2033 RTErrInfoInitStatic(pErrInfo), "SD");
2034 if (RT_SUCCESS(rc))
2035 {
2036 if (iVerbosity > 2 && pszTag == NULL)
2037 RTMsgInfo(" Successfully decoded the PKCS#7/CMS signature...");
2038
2039 /*
2040 * Check that we can verify the signed data, but skip certificate validate as
2041 * we probably don't necessarily have the correct root certs handy here.
2042 */
2043 RTTIMESPEC Now;
2044 rc = RTCrPkcs7VerifySignedDataWithExternalData(pContentInfo, RTCRPKCS7VERIFY_SD_F_TRUST_ALL_CERTS,
2045 NIL_RTCRSTORE /*hAdditionalCerts*/,
2046 NIL_RTCRSTORE /*hTrustedCerts*/,
2047 RTTimeNow(&Now),
2048 NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
2049 pvManifest, cbManifest, RTErrInfoInitStatic(pErrInfo));
2050 if (RT_SUCCESS(rc))
2051 {
2052 if (iVerbosity > 1 && pszTag != NULL)
2053 RTMsgInfo(" Successfully verified the PKCS#7/CMS signature");
2054 }
2055 else
2056 rc = RTMsgErrorRc(rc, "Failed to verify the PKCS#7/CMS signature: %Rrc%RTeim", rc, &pErrInfo->Core);
2057 }
2058 else
2059 RTMsgError("RTCrPkcs7SignedData_CheckSanity failed on PKCS#7/CMS signature: %Rrc%RTeim",
2060 rc, &pErrInfo->Core);
2061
2062 }
2063 else
2064 rc = RTMsgErrorRc(VERR_WRONG_TYPE, "PKCS#7/CMS signature inner ContentType isn't 'data' but: %s",
2065 pSignedData->ContentInfo.ContentType.szObjId);
2066 }
2067 else
2068 rc = RTMsgErrorRc(VERR_WRONG_TYPE, "PKCS#7/CMD signature is not 'signedData': %s", pContentInfo->ContentType.szObjId);
2069 return rc;
2070}
2071
2072/**
2073 * For testing the decoding side.
2074 */
2075static int doCheckPkcs7Signature(void const *pvSignature, size_t cbSignature, PCRTCRX509CERTIFICATE pCertificate,
2076 RTCRSTORE hIntermediateCerts, void const *pvManifest, size_t cbManifest,
2077 unsigned iVerbosity, PRTERRINFOSTATIC pErrInfo)
2078{
2079 RT_NOREF(pCertificate, hIntermediateCerts);
2080
2081 RTASN1CURSORPRIMARY PrimaryCursor;
2082 RTAsn1CursorInitPrimary(&PrimaryCursor, pvSignature, (uint32_t)cbSignature, RTErrInfoInitStatic(pErrInfo),
2083 &g_RTAsn1DefaultAllocator, 0, "Signature");
2084
2085 RTCRPKCS7CONTENTINFO ContentInfo;
2086 RT_ZERO(ContentInfo);
2087 int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &ContentInfo, "CI");
2088 if (RT_SUCCESS(rc))
2089 {
2090 if (iVerbosity > 5)
2091 RTAsn1Dump(&ContentInfo.SeqCore.Asn1Core, 0 /*fFlags*/, 0 /*uLevel*/, RTStrmDumpPrintfV, g_pStdOut);
2092
2093 rc = doCheckPkcs7SignatureWorker(&ContentInfo, pvManifest, cbManifest, iVerbosity, NULL, pErrInfo);
2094 if (RT_SUCCESS(rc))
2095 {
2096 /*
2097 * Clone it and repeat. This is to catch IPRT paths assuming
2098 * that encoded data is always on hand.
2099 */
2100 RTCRPKCS7CONTENTINFO ContentInfo2;
2101 rc = RTCrPkcs7ContentInfo_Clone(&ContentInfo2, &ContentInfo, &g_RTAsn1DefaultAllocator);
2102 if (RT_SUCCESS(rc))
2103 {
2104 rc = doCheckPkcs7SignatureWorker(&ContentInfo2, pvManifest, cbManifest, iVerbosity, "cloned", pErrInfo);
2105 RTCrPkcs7ContentInfo_Delete(&ContentInfo2);
2106 }
2107 else
2108 rc = RTMsgErrorRc(rc, "RTCrPkcs7ContentInfo_Clone failed: %Rrc", rc);
2109 }
2110 }
2111 else
2112 RTMsgError("RTCrPkcs7ContentInfo_DecodeAsn1 failed to decode PKCS#7/CMS signature: %Rrc%RTemi", rc, &pErrInfo->Core);
2113
2114 RTCrPkcs7ContentInfo_Delete(&ContentInfo);
2115 return rc;
2116}
2117
2118
2119/**
2120 * Creates a PKCS\#7 signature and appends it to the signature file in PEM
2121 * format.
2122 */
2123static int doAddPkcs7Signature(PCRTCRX509CERTIFICATE pCertificate, RTCRKEY hPrivateKey, RTDIGESTTYPE enmDigestType,
2124 unsigned cIntermediateCerts, const char **papszIntermediateCerts, RTVFSFILE hVfsFileManifest,
2125 unsigned iVerbosity, PRTERRINFOSTATIC pErrInfo, RTVFSFILE hVfsFileSignature)
2126{
2127 /*
2128 * Add a blank line, just for good measure.
2129 */
2130 int rc = RTVfsFileWrite(hVfsFileSignature, " ", 1, NULL);
2131 if (RT_FAILURE(rc))
2132 return RTMsgErrorRc(rc, "RTVfsFileWrite/signature: %Rrc", rc);
2133
2134 /*
2135 * Read the manifest into a single memory block.
2136 */
2137 uint64_t cbManifest;
2138 rc = RTVfsFileQuerySize(hVfsFileManifest, &cbManifest);
2139 if (RT_FAILURE(rc))
2140 return RTMsgErrorRc(rc, "RTVfsFileQuerySize/manifest: %Rrc", rc);
2141 if (cbManifest > _4M)
2142 return RTMsgErrorRc(VERR_OUT_OF_RANGE, "Manifest is too big: %#RX64 bytes, max 4MiB", cbManifest);
2143
2144 void *pvManifest = RTMemAllocZ(cbManifest + 1);
2145 if (!pvManifest)
2146 return RTMsgErrorRc(VERR_NO_MEMORY, "Out of memory!");
2147
2148 rc = RTVfsFileReadAt(hVfsFileManifest, 0, pvManifest, (size_t)cbManifest, NULL);
2149 if (RT_SUCCESS(rc))
2150 {
2151 /*
2152 * Load intermediate certificates.
2153 */
2154 RTCRSTORE hIntermediateCerts = NIL_RTCRSTORE;
2155 if (cIntermediateCerts)
2156 {
2157 rc = RTCrStoreCreateInMem(&hIntermediateCerts, cIntermediateCerts);
2158 if (RT_SUCCESS(rc))
2159 {
2160 for (unsigned i = 0; i < cIntermediateCerts; i++)
2161 {
2162 const char *pszFile = papszIntermediateCerts[i];
2163 rc = RTCrStoreCertAddFromFile(hIntermediateCerts, 0 /*fFlags*/, pszFile, &pErrInfo->Core);
2164 if (RT_FAILURE(rc))
2165 {
2166 RTMsgError("RTCrStoreCertAddFromFile failed on '%s': %Rrc%#RTeim", pszFile, rc, &pErrInfo->Core);
2167 break;
2168 }
2169 }
2170 }
2171 else
2172 RTMsgError("RTCrStoreCreateInMem failed: %Rrc", rc);
2173 }
2174 if (RT_SUCCESS(rc))
2175 {
2176 /*
2177 * Do a dry run to determin the size of the signed data.
2178 */
2179 size_t cbResult = 0;
2180 rc = RTCrPkcs7SimpleSignSignedData(RTCRPKCS7SIGN_SD_F_DEATCHED | RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP,
2181 pCertificate, hPrivateKey, pvManifest, (size_t)cbManifest, enmDigestType,
2182 hIntermediateCerts, NULL /*pvResult*/, &cbResult, RTErrInfoInitStatic(pErrInfo));
2183 if (rc == VERR_BUFFER_OVERFLOW)
2184 {
2185 /*
2186 * Allocate a buffer of the right size and do the real run.
2187 */
2188 void *pvResult = RTMemAllocZ(cbResult);
2189 if (pvResult)
2190 {
2191 rc = RTCrPkcs7SimpleSignSignedData(RTCRPKCS7SIGN_SD_F_DEATCHED | RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP,
2192 pCertificate, hPrivateKey, pvManifest, (size_t)cbManifest, enmDigestType,
2193 hIntermediateCerts, pvResult, &cbResult, RTErrInfoInitStatic(pErrInfo));
2194 if (RT_SUCCESS(rc))
2195 {
2196 /*
2197 * Add it to the signature file in PEM format.
2198 */
2199 rc = (int)RTCrPemWriteBlobToVfsFile(hVfsFileSignature, pvResult, cbResult, "CMS");
2200 if (RT_SUCCESS(rc))
2201 {
2202 if (iVerbosity > 1)
2203 RTMsgInfo("Created PKCS#7/CMS signature: %zu bytes, %s.",
2204 cbResult, RTCrDigestTypeToName(enmDigestType));
2205
2206 /*
2207 * Try decode and verify the signature.
2208 */
2209 rc = doCheckPkcs7Signature(pvResult, cbResult, pCertificate, hIntermediateCerts,
2210 pvManifest, (size_t)cbManifest, iVerbosity, pErrInfo);
2211 }
2212 else
2213 RTMsgError("RTCrPemWriteBlobToVfsFile failed: %Rrc", rc);
2214 }
2215 RTMemFree(pvResult);
2216 }
2217 else
2218 rc = RTMsgErrorRc(VERR_NO_MEMORY, "Out of memory!");
2219 }
2220 else
2221 RTMsgError("RTCrPkcs7SimpleSignSignedData failed: %Rrc%#RTeim", rc, &pErrInfo->Core);
2222 }
2223 }
2224 else
2225 RTMsgError("RTVfsFileReadAt failed: %Rrc", rc);
2226 RTMemFree(pvManifest);
2227 return rc;
2228}
2229
2230
2231/**
2232 * Performs the OVA signing, producing an in-memory cert-file.
2233 */
2234static int doTheOvaSigning(PRTCRX509CERTIFICATE pCertificate, RTCRKEY hPrivateKey, RTDIGESTTYPE enmDigestType,
2235 const char *pszManifestName, RTVFSFILE hVfsFileManifest,
2236 bool fPkcs7, unsigned cIntermediateCerts, const char **papszIntermediateCerts, unsigned iVerbosity,
2237 PRTERRINFOSTATIC pErrInfo, PRTVFSFILE phVfsFileSignature)
2238{
2239 /*
2240 * Determine the digest types, preferring SHA-256 for the OVA signature
2241 * and SHA-512 for the PKCS#7/CMS one. Try use different hashes for the two.
2242 */
2243 if (enmDigestType == RTDIGESTTYPE_UNKNOWN)
2244 {
2245 if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA256, NULL))
2246 enmDigestType = RTDIGESTTYPE_SHA256;
2247 else
2248 enmDigestType = RTDIGESTTYPE_SHA1;
2249 }
2250
2251 /** @todo Use SHA-3 instead, better diversity. @bugref{9734} */
2252 RTDIGESTTYPE enmPkcs7DigestType;
2253 if ( enmDigestType == RTDIGESTTYPE_SHA1
2254 || enmDigestType == RTDIGESTTYPE_SHA256
2255 || enmDigestType == RTDIGESTTYPE_SHA224)
2256 {
2257 /* Use a SHA-512 variant: */
2258 if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA512, NULL))
2259 enmPkcs7DigestType = RTDIGESTTYPE_SHA512;
2260 else if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA384, NULL))
2261 enmPkcs7DigestType = RTDIGESTTYPE_SHA384;
2262 /// @todo openssl misses these in check_padding_md() in rsa_pmeth.c, causing
2263 /// failure in EVP_PKEY_CTX_set_signature_md() and CMS_final().
2264 //else if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA512T256, NULL))
2265 // enmPkcs7DigestType = RTDIGESTTYPE_SHA512T256;
2266 //else if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA512T224, NULL))
2267 // enmPkcs7DigestType = RTDIGESTTYPE_SHA512T224;
2268 else
2269 enmPkcs7DigestType = RTDIGESTTYPE_SHA1;
2270 }
2271 else /* The .cert file uses SHA-512, pick SHA-256 for diversity. */
2272 enmPkcs7DigestType = RTDIGESTTYPE_SHA256;
2273
2274 /*
2275 * Figure the string name for the .cert file.
2276 */
2277 const char *pszDigestType;
2278 switch (enmDigestType)
2279 {
2280 case RTDIGESTTYPE_SHA1: pszDigestType = "SHA1"; break;
2281 case RTDIGESTTYPE_SHA256: pszDigestType = "SHA256"; break;
2282 case RTDIGESTTYPE_SHA224: pszDigestType = "SHA224"; break;
2283 case RTDIGESTTYPE_SHA512: pszDigestType = "SHA512"; break;
2284 default:
2285 return RTMsgErrorRc(VERR_INVALID_PARAMETER,
2286 "Unsupported digest type: %s", RTCrDigestTypeToName(enmDigestType));
2287 }
2288
2289 /*
2290 * Digest the manifest file.
2291 */
2292 RTCRDIGEST hDigest = NIL_RTCRDIGEST;
2293 int rc = RTCrDigestCreateByType(&hDigest, enmDigestType);
2294 if (RT_FAILURE(rc))
2295 return RTMsgErrorRc(rc, "Failed to create digest for %s: %Rrc", RTCrDigestTypeToName(enmDigestType), rc);
2296
2297 rc = RTCrDigestUpdateFromVfsFile(hDigest, hVfsFileManifest, true /*fRewindFile*/);
2298 if (RT_SUCCESS(rc))
2299 rc = RTCrDigestFinal(hDigest, NULL, 0);
2300 if (RT_SUCCESS(rc))
2301 {
2302 /*
2303 * Sign the digest. Two passes, first to figure the signature size, the
2304 * second to do the actual signing.
2305 */
2306 PCRTASN1OBJID const pAlgorithm = &pCertificate->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm;
2307 PCRTASN1DYNTYPE const pAlgoParams = &pCertificate->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Parameters;
2308 size_t cbSignature = 0;
2309 rc = RTCrPkixPubKeySignDigest(pAlgorithm, hPrivateKey, pAlgoParams, hDigest, 0 /*fFlags*/,
2310 NULL /*pvSignature*/, &cbSignature, RTErrInfoInitStatic(pErrInfo));
2311 if (rc == VERR_BUFFER_OVERFLOW)
2312 {
2313 void *pvSignature = RTMemAllocZ(cbSignature);
2314 if (pvSignature)
2315 {
2316 rc = RTCrPkixPubKeySignDigest(pAlgorithm, hPrivateKey, pAlgoParams, hDigest, 0,
2317 pvSignature, &cbSignature, RTErrInfoInitStatic(pErrInfo));
2318 if (RT_SUCCESS(rc))
2319 {
2320 if (iVerbosity > 1)
2321 RTMsgInfo("Created OVA signature: %zu bytes, %s", cbSignature, RTCrDigestTypeToName(enmDigestType));
2322
2323 /*
2324 * Verify the signature using the certificate to make sure we've
2325 * been given the right private key.
2326 */
2327 rc = RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(&pCertificate->TbsCertificate.SubjectPublicKeyInfo,
2328 pvSignature, cbSignature, hDigest,
2329 RTErrInfoInitStatic(pErrInfo));
2330 if (RT_SUCCESS(rc))
2331 {
2332 if (iVerbosity > 2)
2333 RTMsgInfo(" Successfully decoded and verified the OVA signature.\n");
2334
2335 /*
2336 * Create the output file.
2337 */
2338 RTVFSFILE hVfsFileSignature;
2339 rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, _8K, &hVfsFileSignature);
2340 if (RT_SUCCESS(rc))
2341 {
2342 rc = (int)RTVfsFilePrintf(hVfsFileSignature, "%s(%s) = %#.*Rhxs\n\n",
2343 pszDigestType, pszManifestName, cbSignature, pvSignature);
2344 if (RT_SUCCESS(rc))
2345 {
2346 rc = (int)RTCrX509Certificate_WriteToVfsFile(hVfsFileSignature, pCertificate,
2347 RTErrInfoInitStatic(pErrInfo));
2348 if (RT_SUCCESS(rc))
2349 {
2350 if (fPkcs7)
2351 rc = doAddPkcs7Signature(pCertificate, hPrivateKey, enmPkcs7DigestType,
2352 cIntermediateCerts, papszIntermediateCerts, hVfsFileManifest,
2353 iVerbosity, pErrInfo, hVfsFileSignature);
2354 if (RT_SUCCESS(rc))
2355 {
2356 /*
2357 * Success.
2358 */
2359 *phVfsFileSignature = hVfsFileSignature;
2360 hVfsFileSignature = NIL_RTVFSFILE;
2361 }
2362 }
2363 else
2364 RTMsgError("Failed to write certificate to signature file: %Rrc%#RTeim", rc, &pErrInfo->Core);
2365 }
2366 else
2367 RTMsgError("Failed to produce signature file: %Rrc", rc);
2368 RTVfsFileRelease(hVfsFileSignature);
2369 }
2370 else
2371 RTMsgError("RTVfsMemFileCreate failed: %Rrc", rc);
2372 }
2373 else
2374 RTMsgError("Encountered a problem when validating the signature we just created: %Rrc%#RTeim\n"
2375 "Plase make sure the certificate and private key matches.", rc, &pErrInfo->Core);
2376 }
2377 else
2378 RTMsgError("2nd RTCrPkixPubKeySignDigest call failed: %Rrc%#RTeim", rc, pErrInfo->Core);
2379 RTMemFree(pvSignature);
2380 }
2381 else
2382 rc = RTMsgErrorRc(VERR_NO_MEMORY, "Out of memory!");
2383 }
2384 else
2385 RTMsgError("RTCrPkixPubKeySignDigest failed: %Rrc%#RTeim", rc, pErrInfo->Core);
2386 }
2387 else
2388 RTMsgError("Failed to create digest %s: %Rrc", RTCrDigestTypeToName(enmDigestType), rc);
2389 RTCrDigestRelease(hDigest);
2390 return rc;
2391}
2392
2393
2394/**
2395 * Handles the 'ovasign' command.
2396 */
2397RTEXITCODE handleSignAppliance(HandlerArg *arg)
2398{
2399 /*
2400 * Parse arguments.
2401 */
2402 static const RTGETOPTDEF s_aOptions[] =
2403 {
2404 { "--certificate", 'c', RTGETOPT_REQ_STRING },
2405 { "--private-key", 'k', RTGETOPT_REQ_STRING },
2406 { "--private-key-password", 'p', RTGETOPT_REQ_STRING },
2407 { "--private-key-password-file",'P', RTGETOPT_REQ_STRING },
2408 { "--digest-type", 'd', RTGETOPT_REQ_STRING },
2409 { "--pkcs7", '7', RTGETOPT_REQ_NOTHING },
2410 { "--cms", '7', RTGETOPT_REQ_NOTHING },
2411 { "--no-pkcs7", 'n', RTGETOPT_REQ_NOTHING },
2412 { "--no-cms", 'n', RTGETOPT_REQ_NOTHING },
2413 { "--intermediate-cert-file", 'i', RTGETOPT_REQ_STRING },
2414 { "--force", 'f', RTGETOPT_REQ_NOTHING },
2415 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
2416 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
2417 { "--dry-run", 'D', RTGETOPT_REQ_NOTHING },
2418 };
2419
2420 RTGETOPTSTATE GetState;
2421 int rc = RTGetOptInit(&GetState, arg->argc, arg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
2422 AssertRCReturn(rc, RTEXITCODE_FAILURE);
2423
2424 const char *pszOva = NULL;
2425 const char *pszCertificate = NULL;
2426 const char *pszPrivateKey = NULL;
2427 Utf8Str strPrivateKeyPassword;
2428 RTDIGESTTYPE enmDigestType = RTDIGESTTYPE_UNKNOWN;
2429 bool fPkcs7 = true;
2430 unsigned cIntermediateCerts = 0;
2431 const char *apszIntermediateCerts[32];
2432 bool fReSign = false;
2433 unsigned iVerbosity = 1;
2434 bool fDryRun = false;
2435
2436 int c;
2437 RTGETOPTUNION ValueUnion;
2438 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2439 {
2440 switch (c)
2441 {
2442 case 'c':
2443 pszCertificate = ValueUnion.psz;
2444 break;
2445
2446 case 'k':
2447 pszPrivateKey = ValueUnion.psz;
2448 break;
2449
2450 case 'p':
2451 if (strPrivateKeyPassword.isNotEmpty())
2452 RTMsgWarning("Password is given more than once.");
2453 strPrivateKeyPassword = ValueUnion.psz;
2454 break;
2455
2456 case 'P':
2457 {
2458 if (strPrivateKeyPassword.isNotEmpty())
2459 RTMsgWarning("Password is given more than once.");
2460 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPrivateKeyPassword);
2461 if (rcExit == RTEXITCODE_SUCCESS)
2462 break;
2463 return rcExit;
2464 }
2465
2466 case 'd':
2467 if ( RTStrICmp(ValueUnion.psz, "sha1") == 0
2468 || RTStrICmp(ValueUnion.psz, "sha-1") == 0)
2469 enmDigestType = RTDIGESTTYPE_SHA1;
2470 else if ( RTStrICmp(ValueUnion.psz, "sha256") == 0
2471 || RTStrICmp(ValueUnion.psz, "sha-256") == 0)
2472 enmDigestType = RTDIGESTTYPE_SHA256;
2473 else if ( RTStrICmp(ValueUnion.psz, "sha512") == 0
2474 || RTStrICmp(ValueUnion.psz, "sha-512") == 0)
2475 enmDigestType = RTDIGESTTYPE_SHA512;
2476 else
2477 return RTMsgErrorExitFailure("Unknown digest type: %s", ValueUnion.psz);
2478 break;
2479
2480 case '7':
2481 fPkcs7 = true;
2482 break;
2483
2484 case 'n':
2485 fPkcs7 = false;
2486 break;
2487
2488 case 'i':
2489 if (cIntermediateCerts >= RT_ELEMENTS(apszIntermediateCerts))
2490 return RTMsgErrorExitFailure("Too many intermediate certificates: max %zu",
2491 RT_ELEMENTS(apszIntermediateCerts));
2492 apszIntermediateCerts[cIntermediateCerts++] = ValueUnion.psz;
2493 fPkcs7 = true;
2494 break;
2495
2496 case 'f':
2497 fReSign = true;
2498 break;
2499
2500 case 'v':
2501 iVerbosity++;
2502 break;
2503
2504 case 'q':
2505 iVerbosity = 0;
2506 break;
2507
2508 case 'D':
2509 fDryRun = true;
2510 break;
2511
2512 case VINF_GETOPT_NOT_OPTION:
2513 if (!pszOva)
2514 {
2515 pszOva = ValueUnion.psz;
2516 break;
2517 }
2518 RT_FALL_THRU();
2519 default:
2520 return errorGetOpt(c, &ValueUnion);
2521 }
2522 }
2523
2524 /* Required paramaters: */
2525 if (!pszOva || !*pszOva)
2526 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No OVA file was specified!");
2527 if (!pszCertificate || !*pszCertificate)
2528 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No signing certificate (--certificate=<file>) was specified!");
2529 if (!pszPrivateKey || !*pszPrivateKey)
2530 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No signing private key (--private-key=<file>) was specified!");
2531
2532 /* Check that input files exists before we commence: */
2533 if (!RTFileExists(pszOva))
2534 return RTMsgErrorExitFailure("The specified OVA file was not found: %s", pszOva);
2535 if (!RTFileExists(pszCertificate))
2536 return RTMsgErrorExitFailure("The specified certificate file was not found: %s", pszCertificate);
2537 if (!RTFileExists(pszPrivateKey))
2538 return RTMsgErrorExitFailure("The specified private key file was not found: %s", pszPrivateKey);
2539
2540 /*
2541 * Open the OVA, read the manifest and look for any existing signature.
2542 */
2543 RTVFSFSSTREAM hVfsFssOva = NIL_RTVFSFSSTREAM;
2544 RTVFSOBJ hVfsOldSignature = NIL_RTVFSOBJ;
2545 RTVFSFILE hVfsFileManifest = NIL_RTVFSFILE;
2546 Utf8Str strManifestName;
2547 rc = openOvaAndGetManifestAndOldSignature(pszOva, iVerbosity, fReSign,
2548 &hVfsFssOva, &strManifestName, &hVfsFileManifest, &hVfsOldSignature);
2549 if (RT_SUCCESS(rc))
2550 {
2551 /*
2552 * Read the certificate and private key.
2553 */
2554 RTERRINFOSTATIC ErrInfo;
2555 RTCRX509CERTIFICATE Certificate;
2556 rc = RTCrX509Certificate_ReadFromFile(&Certificate, pszCertificate, 0, &g_RTAsn1DefaultAllocator,
2557 RTErrInfoInitStatic(&ErrInfo));
2558 if (RT_FAILURE(rc))
2559 return RTMsgErrorExitFailure("Error reading certificate from '%s': %Rrc%#RTeim", pszCertificate, rc, &ErrInfo.Core);
2560
2561 RTCRKEY hPrivateKey = NIL_RTCRKEY;
2562 rc = RTCrKeyCreateFromFile(&hPrivateKey, 0 /*fFlags*/, pszPrivateKey, strPrivateKeyPassword.c_str(),
2563 RTErrInfoInitStatic(&ErrInfo));
2564 if (RT_SUCCESS(rc))
2565 {
2566 if (iVerbosity > 1)
2567 RTMsgInfo("Successfully read the certificate and private key.");
2568
2569 /*
2570 * Do the signing and create the signature file.
2571 */
2572 RTVFSFILE hVfsFileSignature = NIL_RTVFSFILE;
2573 rc = doTheOvaSigning(&Certificate, hPrivateKey, enmDigestType, strManifestName.c_str(), hVfsFileManifest,
2574 fPkcs7, cIntermediateCerts, apszIntermediateCerts, iVerbosity, &ErrInfo, &hVfsFileSignature);
2575
2576 /*
2577 * Construct the signature filename:
2578 */
2579 if (RT_SUCCESS(rc))
2580 {
2581 Utf8Str strSignatureName;
2582 rc = strSignatureName.assignNoThrow(strManifestName);
2583 if (RT_SUCCESS(rc))
2584 rc = strSignatureName.stripSuffix().appendNoThrow(".cert");
2585 if (RT_SUCCESS(rc) && !fDryRun)
2586 {
2587 /*
2588 * Update the OVA.
2589 */
2590 rc = updateTheOvaSignature(hVfsFssOva, pszOva, strSignatureName.c_str(),
2591 hVfsFileSignature, hVfsOldSignature, iVerbosity);
2592 if (RT_SUCCESS(rc) && iVerbosity > 0)
2593 RTMsgInfo("Successfully signed '%s'.", pszOva);
2594 }
2595 }
2596 RTCrKeyRelease(hPrivateKey);
2597 }
2598 else
2599 RTPrintf("Error reading the private key from %s: %Rrc%#RTeim", pszPrivateKey, rc, &ErrInfo.Core);
2600 RTCrX509Certificate_Delete(&Certificate);
2601 }
2602
2603 RTVfsObjRelease(hVfsOldSignature);
2604 RTVfsFileRelease(hVfsFileManifest);
2605 RTVfsFsStrmRelease(hVfsFssOva);
2606
2607 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2608}
2609
2610#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