VirtualBox

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

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

VBoxManage,manual: Reworking the signova code. Incomplete. bugref:9699

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 106.7 KB
Line 
1/* $Id: VBoxManageAppliance.cpp 84151 2020-05-05 19:25:25Z vboxsync $ */
2/** @file
3 * VBoxManage - The appliance-related commands.
4 */
5
6/*
7 * Copyright (C) 2009-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#ifndef VBOX_ONLY_DOCS
19
20
21/*********************************************************************************************************************************
22* Header Files *
23*********************************************************************************************************************************/
24#ifndef VBOX_ONLY_DOCS
25#include <VBox/com/com.h>
26#include <VBox/com/string.h>
27#include <VBox/com/Guid.h>
28#include <VBox/com/array.h>
29#include <VBox/com/ErrorInfo.h>
30#include <VBox/com/errorprint.h>
31#include <VBox/com/VirtualBox.h>
32#include <VBox/log.h>
33#include <VBox/param.h>
34
35#include <VBox/version.h>
36#include <revision-generated.h> /* VBOX_SVN_REV - PCH prevents putting it in DEFS. */
37
38#include <list>
39#include <map>
40#endif /* !VBOX_ONLY_DOCS */
41
42#include <iprt/getopt.h>
43#include <iprt/ctype.h>
44#include <iprt/path.h>
45#include <iprt/file.h>
46#include <iprt/err.h>
47#include <iprt/zip.h>
48#include <iprt/stream.h>
49#include <iprt/vfs.h>
50#include <iprt/manifest.h>
51#include <iprt/crypto/digest.h>
52#include <iprt/crypto/x509.h>
53#include <iprt/crypto/pkcs7.h>
54#include <iprt/crypto/store.h>
55#include <iprt/crypto/spc.h>
56#include <iprt/crypto/key.h>
57#include <iprt/crypto/pkix.h>
58
59
60
61#include "VBoxManage.h"
62using namespace com;
63
64
65// funcs
66///////////////////////////////////////////////////////////////////////////////
67
68typedef std::map<Utf8Str, Utf8Str> ArgsMap; // pairs of strings like "vmname" => "newvmname"
69typedef std::map<uint32_t, ArgsMap> ArgsMapsMap; // map of maps, one for each virtual system, sorted by index
70
71typedef std::map<uint32_t, bool> IgnoresMap; // pairs of numeric description entry indices
72typedef std::map<uint32_t, IgnoresMap> IgnoresMapsMap; // map of maps, one for each virtual system, sorted by index
73
74static bool findArgValue(Utf8Str &strOut,
75 ArgsMap *pmapArgs,
76 const Utf8Str &strKey)
77{
78 if (pmapArgs)
79 {
80 ArgsMap::iterator it;
81 it = pmapArgs->find(strKey);
82 if (it != pmapArgs->end())
83 {
84 strOut = it->second;
85 pmapArgs->erase(it);
86 return true;
87 }
88 }
89
90 return false;
91}
92
93static int parseImportOptions(const char *psz, com::SafeArray<ImportOptions_T> *options)
94{
95 int rc = VINF_SUCCESS;
96 while (psz && *psz && RT_SUCCESS(rc))
97 {
98 size_t len;
99 const char *pszComma = strchr(psz, ',');
100 if (pszComma)
101 len = pszComma - psz;
102 else
103 len = strlen(psz);
104 if (len > 0)
105 {
106 if (!RTStrNICmp(psz, "KeepAllMACs", len))
107 options->push_back(ImportOptions_KeepAllMACs);
108 else if (!RTStrNICmp(psz, "KeepNATMACs", len))
109 options->push_back(ImportOptions_KeepNATMACs);
110 else if (!RTStrNICmp(psz, "ImportToVDI", len))
111 options->push_back(ImportOptions_ImportToVDI);
112 else
113 rc = VERR_PARSE_ERROR;
114 }
115 if (pszComma)
116 psz += len + 1;
117 else
118 psz += len;
119 }
120
121 return rc;
122}
123
124static const RTGETOPTDEF g_aImportApplianceOptions[] =
125{
126 { "--dry-run", 'n', RTGETOPT_REQ_NOTHING },
127 { "-dry-run", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
128 { "--dryrun", 'n', RTGETOPT_REQ_NOTHING },
129 { "-dryrun", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
130 { "--detailed-progress", 'P', RTGETOPT_REQ_NOTHING },
131 { "-detailed-progress", 'P', RTGETOPT_REQ_NOTHING }, // deprecated
132 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
133 { "-vsys", 's', RTGETOPT_REQ_UINT32 }, // deprecated
134 { "--ostype", 'o', RTGETOPT_REQ_STRING },
135 { "-ostype", 'o', RTGETOPT_REQ_STRING }, // deprecated
136 { "--vmname", 'V', RTGETOPT_REQ_STRING },
137 { "-vmname", 'V', RTGETOPT_REQ_STRING }, // deprecated
138 { "--settingsfile", 'S', RTGETOPT_REQ_STRING },
139 { "--basefolder", 'p', RTGETOPT_REQ_STRING },
140 { "--group", 'g', RTGETOPT_REQ_STRING },
141 { "--memory", 'm', RTGETOPT_REQ_STRING },
142 { "-memory", 'm', RTGETOPT_REQ_STRING }, // deprecated
143 { "--cpus", 'c', RTGETOPT_REQ_STRING },
144 { "--description", 'd', RTGETOPT_REQ_STRING },
145 { "--eula", 'L', RTGETOPT_REQ_STRING },
146 { "-eula", 'L', RTGETOPT_REQ_STRING }, // deprecated
147 { "--unit", 'u', RTGETOPT_REQ_UINT32 },
148 { "-unit", 'u', RTGETOPT_REQ_UINT32 }, // deprecated
149 { "--ignore", 'x', RTGETOPT_REQ_NOTHING },
150 { "-ignore", 'x', RTGETOPT_REQ_NOTHING }, // deprecated
151 { "--scsitype", 'T', RTGETOPT_REQ_UINT32 },
152 { "-scsitype", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
153 { "--type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
154 { "-type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
155#if 0 /* Changing the controller is fully valid, but the current design on how
156 the params are evaluated here doesn't allow two parameter for one
157 unit. The target disk path is more important. I leave it for future
158 improvments. */
159 { "--controller", 'C', RTGETOPT_REQ_STRING },
160#endif
161 { "--disk", 'D', RTGETOPT_REQ_STRING },
162 { "--options", 'O', RTGETOPT_REQ_STRING },
163
164 { "--cloud", 'j', RTGETOPT_REQ_NOTHING},
165 { "--cloudprofile", 'k', RTGETOPT_REQ_STRING },
166 { "--cloudinstanceid", 'l', RTGETOPT_REQ_STRING },
167 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING }
168};
169
170enum actionType
171{
172 NOT_SET, LOCAL, CLOUD
173} actionType;
174
175RTEXITCODE handleImportAppliance(HandlerArg *arg)
176{
177 HRESULT rc = S_OK;
178 bool fCloud = false; // the default
179 actionType = NOT_SET;
180 Utf8Str strOvfFilename;
181 bool fExecute = true; // if true, then we actually do the import
182 com::SafeArray<ImportOptions_T> options;
183 uint32_t ulCurVsys = (uint32_t)-1;
184 uint32_t ulCurUnit = (uint32_t)-1;
185 // for each --vsys X command, maintain a map of command line items
186 // (we'll parse them later after interpreting the OVF, when we can
187 // actually check whether they make sense semantically)
188 ArgsMapsMap mapArgsMapsPerVsys;
189 IgnoresMapsMap mapIgnoresMapsPerVsys;
190
191 int c;
192 RTGETOPTUNION ValueUnion;
193 RTGETOPTSTATE GetState;
194 // start at 0 because main() has hacked both the argc and argv given to us
195 RTGetOptInit(&GetState, arg->argc, arg->argv, g_aImportApplianceOptions, RT_ELEMENTS(g_aImportApplianceOptions),
196 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
197 while ((c = RTGetOpt(&GetState, &ValueUnion)))
198 {
199 switch (c)
200 {
201 case 'n': // --dry-run
202 fExecute = false;
203 break;
204
205 case 'P': // --detailed-progress
206 g_fDetailedProgress = true;
207 break;
208
209 case 's': // --vsys
210 if (fCloud == false && actionType == NOT_SET)
211 actionType = LOCAL;
212
213 if (actionType != LOCAL)
214 return errorSyntax(USAGE_EXPORTAPPLIANCE,
215 "Option \"%s\" can't be used together with \"--cloud\" argument.",
216 GetState.pDef->pszLong);
217
218 ulCurVsys = ValueUnion.u32;
219 ulCurUnit = (uint32_t)-1;
220 break;
221
222 case 'o': // --ostype
223 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
224 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
225 mapArgsMapsPerVsys[ulCurVsys]["ostype"] = ValueUnion.psz;
226 break;
227
228 case 'V': // --vmname
229 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
230 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
231 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
232 break;
233
234 case 'S': // --settingsfile
235 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
236 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
237 mapArgsMapsPerVsys[ulCurVsys]["settingsfile"] = ValueUnion.psz;
238 break;
239
240 case 'p': // --basefolder
241 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
242 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
243 mapArgsMapsPerVsys[ulCurVsys]["basefolder"] = ValueUnion.psz;
244 break;
245
246 case 'g': // --group
247 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
248 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
249 mapArgsMapsPerVsys[ulCurVsys]["group"] = ValueUnion.psz;
250 break;
251
252 case 'd': // --description
253 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
254 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
255 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
256 break;
257
258 case 'L': // --eula
259 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
260 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
261 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
262 break;
263
264 case 'm': // --memory
265 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
266 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
267 mapArgsMapsPerVsys[ulCurVsys]["memory"] = ValueUnion.psz;
268 break;
269
270 case 'c': // --cpus
271 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
272 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
273 mapArgsMapsPerVsys[ulCurVsys]["cpus"] = ValueUnion.psz;
274 break;
275
276 case 'u': // --unit
277 ulCurUnit = ValueUnion.u32;
278 break;
279
280 case 'x': // --ignore
281 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
282 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
283 if (ulCurUnit == (uint32_t)-1)
284 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
285 mapIgnoresMapsPerVsys[ulCurVsys][ulCurUnit] = true;
286 break;
287
288 case 'T': // --scsitype
289 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
290 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
291 if (ulCurUnit == (uint32_t)-1)
292 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
293 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("scsitype%u", ulCurUnit)] = ValueUnion.psz;
294 break;
295
296 case 'C': // --controller
297 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
298 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
299 if (ulCurUnit == (uint32_t)-1)
300 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
301 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("controller%u", ulCurUnit)] = ValueUnion.psz;
302 break;
303
304 case 'D': // --disk
305 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
306 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
307 if (ulCurUnit == (uint32_t)-1)
308 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
309 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("disk%u", ulCurUnit)] = ValueUnion.psz;
310 break;
311
312 case 'O': // --options
313 if (RT_FAILURE(parseImportOptions(ValueUnion.psz, &options)))
314 return errorArgument("Invalid import options '%s'\n", ValueUnion.psz);
315 break;
316
317 /*--cloud and --vsys are orthogonal, only one must be presented*/
318 case 'j': // --cloud
319 if (fCloud == false && actionType == NOT_SET)
320 {
321 fCloud = true;
322 actionType = CLOUD;
323 }
324
325 if (actionType != CLOUD)
326 return errorSyntax(USAGE_IMPORTAPPLIANCE,
327 "Option \"%s\" can't be used together with \"--vsys\" argument.",
328 GetState.pDef->pszLong);
329
330 ulCurVsys = 0;
331 break;
332
333 /* Cloud export settings */
334 case 'k': // --cloudprofile
335 if (actionType != CLOUD)
336 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
337 GetState.pDef->pszLong);
338 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
339 break;
340
341 case 'l': // --cloudinstanceid
342 if (actionType != CLOUD)
343 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
344 GetState.pDef->pszLong);
345 mapArgsMapsPerVsys[ulCurVsys]["cloudinstanceid"] = ValueUnion.psz;
346 break;
347
348 case 'B': // --cloudbucket
349 if (actionType != CLOUD)
350 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
351 GetState.pDef->pszLong);
352 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
353 break;
354
355 case VINF_GETOPT_NOT_OPTION:
356 if (strOvfFilename.isEmpty())
357 strOvfFilename = ValueUnion.psz;
358 else
359 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid parameter '%s'", ValueUnion.psz);
360 break;
361
362 default:
363 if (c > 0)
364 {
365 if (RT_C_IS_PRINT(c))
366 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option -%c", c);
367 else
368 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option case %i", c);
369 }
370 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
371 return errorSyntax(USAGE_IMPORTAPPLIANCE, "unknown option: %s\n", ValueUnion.psz);
372 else if (ValueUnion.pDef)
373 return errorSyntax(USAGE_IMPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
374 else
375 return errorSyntax(USAGE_IMPORTAPPLIANCE, "error: %Rrs", c);
376 }
377 }
378
379 /* Last check after parsing all arguments */
380 if (strOvfFilename.isNotEmpty())
381 {
382 if (actionType == NOT_SET)
383 {
384 if (fCloud)
385 actionType = CLOUD;
386 else
387 actionType = LOCAL;
388 }
389 }
390 else
391 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Not enough arguments for \"import\" command.");
392
393 do
394 {
395 ComPtr<IAppliance> pAppliance;
396 CHECK_ERROR_BREAK(arg->virtualBox, CreateAppliance(pAppliance.asOutParam()));
397 //in the case of Cloud, append the instance id here because later it's harder to do
398 if (actionType == CLOUD)
399 {
400 try
401 {
402 /* Check presence of cloudprofile and cloudinstanceid in the map.
403 * If there isn't the exception is triggered. It's standard std:map logic.*/
404 ArgsMap a = mapArgsMapsPerVsys[ulCurVsys];
405 a.at("cloudprofile");
406 a.at("cloudinstanceid");
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_HardDiskImage:
770 if (fIgnoreThis)
771 {
772 RTPrintf("%2u: Hard disk image: source image=%ls -- disabled\n",
773 a,
774 aOvfValues[a]);
775 aEnabled[a] = false;
776 }
777 else
778 {
779 Utf8StrFmt strTypeArg("disk%u", a);
780 RTCList<ImportOptions_T> optionsList = options.toList();
781
782 bstrFinalValue = aVBoxValues[a];
783
784 if (findArgValue(strOverride, pmapArgs, strTypeArg))
785 {
786 if (!optionsList.contains(ImportOptions_ImportToVDI))
787 {
788 RTUUID uuid;
789 /* Check if this is a uuid. If so, don't touch. */
790 int vrc = RTUuidFromStr(&uuid, strOverride.c_str());
791 if (vrc != VINF_SUCCESS)
792 {
793 /* Make the path absolute. */
794 if (!RTPathStartsWithRoot(strOverride.c_str()))
795 {
796 char pszPwd[RTPATH_MAX];
797 vrc = RTPathGetCurrent(pszPwd, RTPATH_MAX);
798 if (RT_SUCCESS(vrc))
799 strOverride = Utf8Str(pszPwd).append(RTPATH_SLASH).append(strOverride);
800 }
801 }
802 bstrFinalValue = strOverride;
803 }
804 else
805 {
806 //print some error about incompatible command-line arguments
807 return errorSyntax(USAGE_IMPORTAPPLIANCE,
808 "Option --ImportToVDI shall not be used together with "
809 "manually set target path.");
810
811 }
812
813 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
814 a,
815 aOvfValues[a],
816 bstrFinalValue.raw(),
817 aExtraConfigValues[a]);
818 }
819#if 0 /* Changing the controller is fully valid, but the current design on how
820 the params are evaluated here doesn't allow two parameter for one
821 unit. The target disk path is more important I leave it for future
822 improvments. */
823 Utf8StrFmt strTypeArg("controller%u", a);
824 if (findArgValue(strOverride, pmapArgs, strTypeArg))
825 {
826 // strOverride now has the controller index as a number, but we
827 // need a "controller=X" format string
828 strOverride = Utf8StrFmt("controller=%s", strOverride.c_str());
829 Bstr bstrExtraConfigValue = strOverride;
830 bstrExtraConfigValue.detachTo(&aExtraConfigValues[a]);
831 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
832 a,
833 aOvfValues[a],
834 aVBoxValues[a],
835 aExtraConfigValues[a]);
836 }
837#endif
838 else
839 {
840 strOverride = aVBoxValues[a];
841
842 /*
843 * Current solution isn't optimal.
844 * Better way is to provide API call for function
845 * Appliance::i_findMediumFormatFromDiskImage()
846 * and creating one new function which returns
847 * struct ovf::DiskImage for currently processed disk.
848 */
849
850 /*
851 * if user wants to convert all imported disks to VDI format
852 * we need to replace files extensions to "vdi"
853 * except CD/DVD disks
854 */
855 if (optionsList.contains(ImportOptions_ImportToVDI))
856 {
857 ComPtr<IVirtualBox> pVirtualBox = arg->virtualBox;
858 ComPtr<ISystemProperties> systemProperties;
859 com::SafeIfaceArray<IMediumFormat> mediumFormats;
860 Bstr bstrFormatName;
861
862 CHECK_ERROR(pVirtualBox,
863 COMGETTER(SystemProperties)(systemProperties.asOutParam()));
864
865 CHECK_ERROR(systemProperties,
866 COMGETTER(MediumFormats)(ComSafeArrayAsOutParam(mediumFormats)));
867
868 /* go through all supported media formats and store files extensions only for RAW */
869 com::SafeArray<BSTR> extensions;
870
871 for (unsigned j = 0; j < mediumFormats.size(); ++j)
872 {
873 com::SafeArray<DeviceType_T> deviceType;
874 ComPtr<IMediumFormat> mediumFormat = mediumFormats[j];
875 CHECK_ERROR(mediumFormat, COMGETTER(Name)(bstrFormatName.asOutParam()));
876 Utf8Str strFormatName = Utf8Str(bstrFormatName);
877
878 if (strFormatName.compare("RAW", Utf8Str::CaseInsensitive) == 0)
879 {
880 /* getting files extensions for "RAW" format */
881 CHECK_ERROR(mediumFormat,
882 DescribeFileExtensions(ComSafeArrayAsOutParam(extensions),
883 ComSafeArrayAsOutParam(deviceType)));
884 break;
885 }
886 }
887
888 /* go through files extensions for RAW format and compare them with
889 * extension of current file
890 */
891 bool fReplace = true;
892
893 const char *pszExtension = RTPathSuffix(strOverride.c_str());
894 if (pszExtension)
895 pszExtension++;
896
897 for (unsigned j = 0; j < extensions.size(); ++j)
898 {
899 Bstr bstrExt(extensions[j]);
900 Utf8Str strExtension(bstrExt);
901 if(strExtension.compare(pszExtension, Utf8Str::CaseInsensitive) == 0)
902 {
903 fReplace = false;
904 break;
905 }
906 }
907
908 if (fReplace)
909 {
910 strOverride = strOverride.stripSuffix();
911 strOverride = strOverride.append(".").append("vdi");
912 }
913 }
914
915 bstrFinalValue = strOverride;
916
917 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls"
918 "\n (change target path with \"--vsys %u --unit %u --disk path\";"
919 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
920 a,
921 aOvfValues[a],
922 bstrFinalValue.raw(),
923 aExtraConfigValues[a],
924 i, a, i, a);
925 }
926 }
927 break;
928
929 case VirtualSystemDescriptionType_CDROM:
930 if (fIgnoreThis)
931 {
932 RTPrintf("%2u: CD-ROM -- disabled\n",
933 a);
934 aEnabled[a] = false;
935 }
936 else
937 RTPrintf("%2u: CD-ROM"
938 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
939 a, i, a);
940 break;
941
942 case VirtualSystemDescriptionType_Floppy:
943 if (fIgnoreThis)
944 {
945 RTPrintf("%2u: Floppy -- disabled\n",
946 a);
947 aEnabled[a] = false;
948 }
949 else
950 RTPrintf("%2u: Floppy"
951 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
952 a, i, a);
953 break;
954
955 case VirtualSystemDescriptionType_NetworkAdapter:
956 RTPrintf("%2u: Network adapter: orig %ls, config %ls, extra %ls\n", /// @todo implement once we have a plan for the back-end
957 a,
958 aOvfValues[a],
959 aVBoxValues[a],
960 aExtraConfigValues[a]);
961 break;
962
963 case VirtualSystemDescriptionType_USBController:
964 if (fIgnoreThis)
965 {
966 RTPrintf("%2u: USB controller -- disabled\n",
967 a);
968 aEnabled[a] = false;
969 }
970 else
971 RTPrintf("%2u: USB controller"
972 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
973 a, i, a);
974 break;
975
976 case VirtualSystemDescriptionType_SoundCard:
977 if (fIgnoreThis)
978 {
979 RTPrintf("%2u: Sound card \"%ls\" -- disabled\n",
980 a,
981 aOvfValues[a]);
982 aEnabled[a] = false;
983 }
984 else
985 RTPrintf("%2u: Sound card (appliance expects \"%ls\", can change on import)"
986 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
987 a,
988 aOvfValues[a],
989 i,
990 a);
991 break;
992
993 case VirtualSystemDescriptionType_SettingsFile:
994 if (findArgValue(strOverride, pmapArgs, "settingsfile"))
995 {
996 bstrFinalValue = strOverride;
997 RTPrintf("%2u: VM settings file name specified with --settingsfile: \"%ls\"\n",
998 a, bstrFinalValue.raw());
999 }
1000 else
1001 RTPrintf("%2u: Suggested VM settings file name \"%ls\""
1002 "\n (change with \"--vsys %u --settingsfile <filename>\")\n",
1003 a, bstrFinalValue.raw(), i);
1004 break;
1005
1006 case VirtualSystemDescriptionType_BaseFolder:
1007 if (findArgValue(strOverride, pmapArgs, "basefolder"))
1008 {
1009 bstrFinalValue = strOverride;
1010 RTPrintf("%2u: VM base folder specified with --basefolder: \"%ls\"\n",
1011 a, bstrFinalValue.raw());
1012 }
1013 else
1014 RTPrintf("%2u: Suggested VM base folder \"%ls\""
1015 "\n (change with \"--vsys %u --basefolder <path>\")\n",
1016 a, bstrFinalValue.raw(), i);
1017 break;
1018
1019 case VirtualSystemDescriptionType_PrimaryGroup:
1020 if (findArgValue(strOverride, pmapArgs, "group"))
1021 {
1022 bstrFinalValue = strOverride;
1023 RTPrintf("%2u: VM group specified with --group: \"%ls\"\n",
1024 a, bstrFinalValue.raw());
1025 }
1026 else
1027 RTPrintf("%2u: Suggested VM group \"%ls\""
1028 "\n (change with \"--vsys %u --group <group>\")\n",
1029 a, bstrFinalValue.raw(), i);
1030 break;
1031
1032 case VirtualSystemDescriptionType_CloudInstanceShape:
1033 RTPrintf("%2u: Suggested cloud shape \"%ls\"\n",
1034 a, bstrFinalValue.raw());
1035 break;
1036
1037 case VirtualSystemDescriptionType_CloudBucket:
1038 if (findArgValue(strOverride, pmapArgs, "cloudbucket"))
1039 {
1040 bstrFinalValue = strOverride;
1041 RTPrintf("%2u: Cloud bucket id specified with --cloudbucket: \"%ls\"\n",
1042 a, bstrFinalValue.raw());
1043 }
1044 else
1045 RTPrintf("%2u: Suggested cloud bucket id \"%ls\""
1046 "\n (change with \"--cloud %u --cloudbucket <id>\")\n",
1047 a, bstrFinalValue.raw(), i);
1048 break;
1049
1050 case VirtualSystemDescriptionType_CloudProfileName:
1051 if (findArgValue(strOverride, pmapArgs, "cloudprofile"))
1052 {
1053 bstrFinalValue = strOverride;
1054 RTPrintf("%2u: Cloud profile name specified with --cloudprofile: \"%ls\"\n",
1055 a, bstrFinalValue.raw());
1056 }
1057 else
1058 RTPrintf("%2u: Suggested cloud profile name \"%ls\""
1059 "\n (change with \"--cloud %u --cloudprofile <id>\")\n",
1060 a, bstrFinalValue.raw(), i);
1061 break;
1062
1063 case VirtualSystemDescriptionType_CloudInstanceId:
1064 if (findArgValue(strOverride, pmapArgs, "cloudinstanceid"))
1065 {
1066 bstrFinalValue = strOverride;
1067 RTPrintf("%2u: Cloud instance id specified with --cloudinstanceid: \"%ls\"\n",
1068 a, bstrFinalValue.raw());
1069 }
1070 else
1071 RTPrintf("%2u: Suggested cloud instance id \"%ls\""
1072 "\n (change with \"--cloud %u --cloudinstanceid <id>\")\n",
1073 a, bstrFinalValue.raw(), i);
1074 break;
1075
1076 case VirtualSystemDescriptionType_CloudImageId:
1077 RTPrintf("%2u: Suggested cloud base image id \"%ls\"\n",
1078 a, bstrFinalValue.raw());
1079 break;
1080 case VirtualSystemDescriptionType_CloudDomain:
1081 case VirtualSystemDescriptionType_CloudBootDiskSize:
1082 case VirtualSystemDescriptionType_CloudOCIVCN:
1083 case VirtualSystemDescriptionType_CloudPublicIP:
1084 case VirtualSystemDescriptionType_CloudOCISubnet:
1085 case VirtualSystemDescriptionType_CloudKeepObject:
1086 case VirtualSystemDescriptionType_CloudLaunchInstance:
1087 case VirtualSystemDescriptionType_CloudInstanceState:
1088 case VirtualSystemDescriptionType_CloudImageState:
1089 case VirtualSystemDescriptionType_Miscellaneous:
1090 case VirtualSystemDescriptionType_CloudInstanceDisplayName:
1091 case VirtualSystemDescriptionType_CloudImageDisplayName:
1092 case VirtualSystemDescriptionType_CloudOCILaunchMode:
1093 case VirtualSystemDescriptionType_CloudPrivateIP:
1094 case VirtualSystemDescriptionType_CloudBootVolumeId:
1095 case VirtualSystemDescriptionType_CloudOCIVCNCompartment:
1096 case VirtualSystemDescriptionType_CloudOCISubnetCompartment:
1097 case VirtualSystemDescriptionType_CloudPublicSSHKey:
1098 case VirtualSystemDescriptionType_BootingFirmware:
1099 /** @todo VirtualSystemDescriptionType_Miscellaneous? */
1100 break;
1101
1102 case VirtualSystemDescriptionType_Ignore:
1103#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
1104 case VirtualSystemDescriptionType_32BitHack:
1105#endif
1106 break;
1107 }
1108
1109 bstrFinalValue.detachTo(&aFinalValues[a]);
1110 }
1111
1112 if (fExecute)
1113 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
1114 SetFinalValues(ComSafeArrayAsInParam(aEnabled),
1115 ComSafeArrayAsInParam(aFinalValues),
1116 ComSafeArrayAsInParam(aExtraConfigValues)));
1117
1118 } // for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
1119
1120 if (cLicensesInTheWay == 1)
1121 RTMsgError("Cannot import until the license agreement listed above is accepted.");
1122 else if (cLicensesInTheWay > 1)
1123 RTMsgError("Cannot import until the %c license agreements listed above are accepted.", cLicensesInTheWay);
1124
1125 if (!cLicensesInTheWay && fExecute)
1126 {
1127 // go!
1128 ComPtr<IProgress> progress;
1129 CHECK_ERROR_BREAK(pAppliance,
1130 ImportMachines(ComSafeArrayAsInParam(options), progress.asOutParam()));
1131
1132 rc = showProgress(progress);
1133 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance import failed"), RTEXITCODE_FAILURE);
1134
1135 if (SUCCEEDED(rc))
1136 RTPrintf("Successfully imported the appliance.\n");
1137 }
1138 } // end if (aVirtualSystemDescriptions.size() > 0)
1139 } while (0);
1140
1141 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1142}
1143
1144static int parseExportOptions(const char *psz, com::SafeArray<ExportOptions_T> *options)
1145{
1146 int rc = VINF_SUCCESS;
1147 while (psz && *psz && RT_SUCCESS(rc))
1148 {
1149 size_t len;
1150 const char *pszComma = strchr(psz, ',');
1151 if (pszComma)
1152 len = pszComma - psz;
1153 else
1154 len = strlen(psz);
1155 if (len > 0)
1156 {
1157 if (!RTStrNICmp(psz, "CreateManifest", len))
1158 options->push_back(ExportOptions_CreateManifest);
1159 else if (!RTStrNICmp(psz, "manifest", len))
1160 options->push_back(ExportOptions_CreateManifest);
1161 else if (!RTStrNICmp(psz, "ExportDVDImages", len))
1162 options->push_back(ExportOptions_ExportDVDImages);
1163 else if (!RTStrNICmp(psz, "iso", len))
1164 options->push_back(ExportOptions_ExportDVDImages);
1165 else if (!RTStrNICmp(psz, "StripAllMACs", len))
1166 options->push_back(ExportOptions_StripAllMACs);
1167 else if (!RTStrNICmp(psz, "nomacs", len))
1168 options->push_back(ExportOptions_StripAllMACs);
1169 else if (!RTStrNICmp(psz, "StripAllNonNATMACs", len))
1170 options->push_back(ExportOptions_StripAllNonNATMACs);
1171 else if (!RTStrNICmp(psz, "nomacsbutnat", len))
1172 options->push_back(ExportOptions_StripAllNonNATMACs);
1173 else
1174 rc = VERR_PARSE_ERROR;
1175 }
1176 if (pszComma)
1177 psz += len + 1;
1178 else
1179 psz += len;
1180 }
1181
1182 return rc;
1183}
1184
1185static const RTGETOPTDEF g_aExportOptions[] =
1186{
1187 { "--output", 'o', RTGETOPT_REQ_STRING },
1188 { "--legacy09", 'l', RTGETOPT_REQ_NOTHING },
1189 { "--ovf09", 'l', RTGETOPT_REQ_NOTHING },
1190 { "--ovf10", '1', RTGETOPT_REQ_NOTHING },
1191 { "--ovf20", '2', RTGETOPT_REQ_NOTHING },
1192 { "--opc10", 'c', RTGETOPT_REQ_NOTHING },
1193 { "--manifest", 'm', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1194 { "--iso", 'I', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1195 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
1196 { "--vmname", 'V', RTGETOPT_REQ_STRING },
1197 { "--product", 'p', RTGETOPT_REQ_STRING },
1198 { "--producturl", 'P', RTGETOPT_REQ_STRING },
1199 { "--vendor", 'n', RTGETOPT_REQ_STRING },
1200 { "--vendorurl", 'N', RTGETOPT_REQ_STRING },
1201 { "--version", 'v', RTGETOPT_REQ_STRING },
1202 { "--description", 'd', RTGETOPT_REQ_STRING },
1203 { "--eula", 'e', RTGETOPT_REQ_STRING },
1204 { "--eulafile", 'E', RTGETOPT_REQ_STRING },
1205 { "--options", 'O', RTGETOPT_REQ_STRING },
1206 { "--cloud", 'C', RTGETOPT_REQ_UINT32 },
1207 { "--cloudshape", 'S', RTGETOPT_REQ_STRING },
1208 { "--clouddomain", 'D', RTGETOPT_REQ_STRING },
1209 { "--clouddisksize", 'R', RTGETOPT_REQ_STRING },
1210 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING },
1211 { "--cloudocivcn", 'Q', RTGETOPT_REQ_STRING },
1212 { "--cloudpublicip", 'A', RTGETOPT_REQ_STRING },
1213 { "--cloudprofile", 'F', RTGETOPT_REQ_STRING },
1214 { "--cloudocisubnet", 'T', RTGETOPT_REQ_STRING },
1215 { "--cloudkeepobject", 'K', RTGETOPT_REQ_STRING },
1216 { "--cloudlaunchinstance", 'L', RTGETOPT_REQ_STRING },
1217 { "--cloudlaunchmode", 'M', RTGETOPT_REQ_STRING },
1218 { "--cloudprivateip", 'i', RTGETOPT_REQ_STRING },
1219};
1220
1221RTEXITCODE handleExportAppliance(HandlerArg *a)
1222{
1223 HRESULT rc = S_OK;
1224
1225 Utf8Str strOutputFile;
1226 Utf8Str strOvfFormat("ovf-1.0"); // the default export version
1227 bool fManifest = false; // the default
1228 bool fCloud = false; // the default
1229 actionType = NOT_SET;
1230 bool fExportISOImages = false; // the default
1231 com::SafeArray<ExportOptions_T> options;
1232 std::list< ComPtr<IMachine> > llMachines;
1233
1234 uint32_t ulCurVsys = (uint32_t)-1;
1235 // for each --vsys X command, maintain a map of command line items
1236 ArgsMapsMap mapArgsMapsPerVsys;
1237 do
1238 {
1239 int c;
1240
1241 RTGETOPTUNION ValueUnion;
1242 RTGETOPTSTATE GetState;
1243 // start at 0 because main() has hacked both the argc and argv given to us
1244 RTGetOptInit(&GetState, a->argc, a->argv, g_aExportOptions,
1245 RT_ELEMENTS(g_aExportOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
1246
1247 Utf8Str strProductUrl;
1248 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1249 {
1250 switch (c)
1251 {
1252 case 'o': // --output
1253 if (strOutputFile.length())
1254 return errorSyntax(USAGE_EXPORTAPPLIANCE, "You can only specify --output once.");
1255 else
1256 strOutputFile = ValueUnion.psz;
1257 break;
1258
1259 case 'l': // --legacy09/--ovf09
1260 strOvfFormat = "ovf-0.9";
1261 break;
1262
1263 case '1': // --ovf10
1264 strOvfFormat = "ovf-1.0";
1265 break;
1266
1267 case '2': // --ovf20
1268 strOvfFormat = "ovf-2.0";
1269 break;
1270
1271 case 'c': // --opc
1272 strOvfFormat = "opc-1.0";
1273 break;
1274
1275 case 'I': // --iso
1276 fExportISOImages = true;
1277 break;
1278
1279 case 'm': // --manifest
1280 fManifest = true;
1281 break;
1282
1283 case 's': // --vsys
1284 if (fCloud == false && actionType == NOT_SET)
1285 actionType = LOCAL;
1286
1287 if (actionType != LOCAL)
1288 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1289 "Option \"%s\" can't be used together with \"--cloud\" argument.",
1290 GetState.pDef->pszLong);
1291
1292 ulCurVsys = ValueUnion.u32;
1293 break;
1294
1295 case 'V': // --vmname
1296 if (actionType == NOT_SET || ulCurVsys == (uint32_t)-1)
1297 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys or --cloud argument.",
1298 GetState.pDef->pszLong);
1299 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
1300 break;
1301
1302 case 'p': // --product
1303 if (actionType != LOCAL)
1304 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1305 mapArgsMapsPerVsys[ulCurVsys]["product"] = ValueUnion.psz;
1306 break;
1307
1308 case 'P': // --producturl
1309 if (actionType != LOCAL)
1310 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1311 mapArgsMapsPerVsys[ulCurVsys]["producturl"] = ValueUnion.psz;
1312 break;
1313
1314 case 'n': // --vendor
1315 if (actionType != LOCAL)
1316 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1317 mapArgsMapsPerVsys[ulCurVsys]["vendor"] = ValueUnion.psz;
1318 break;
1319
1320 case 'N': // --vendorurl
1321 if (actionType != LOCAL)
1322 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1323 mapArgsMapsPerVsys[ulCurVsys]["vendorurl"] = ValueUnion.psz;
1324 break;
1325
1326 case 'v': // --version
1327 if (actionType != LOCAL)
1328 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1329 mapArgsMapsPerVsys[ulCurVsys]["version"] = ValueUnion.psz;
1330 break;
1331
1332 case 'd': // --description
1333 if (actionType != LOCAL)
1334 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1335 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
1336 break;
1337
1338 case 'e': // --eula
1339 if (actionType != LOCAL)
1340 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1341 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
1342 break;
1343
1344 case 'E': // --eulafile
1345 if (actionType != LOCAL)
1346 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1347 mapArgsMapsPerVsys[ulCurVsys]["eulafile"] = ValueUnion.psz;
1348 break;
1349
1350 case 'O': // --options
1351 if (RT_FAILURE(parseExportOptions(ValueUnion.psz, &options)))
1352 return errorArgument("Invalid export options '%s'\n", ValueUnion.psz);
1353 break;
1354
1355 /*--cloud and --vsys are orthogonal, only one must be presented*/
1356 case 'C': // --cloud
1357 if (fCloud == false && actionType == NOT_SET)
1358 {
1359 fCloud = true;
1360 actionType = CLOUD;
1361 }
1362
1363 if (actionType != CLOUD)
1364 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1365 "Option \"%s\" can't be used together with \"--vsys\" argument.",
1366 GetState.pDef->pszLong);
1367
1368 ulCurVsys = ValueUnion.u32;
1369 break;
1370
1371 /* Cloud export settings */
1372 case 'S': // --cloudshape
1373 if (actionType != CLOUD)
1374 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1375 GetState.pDef->pszLong);
1376 mapArgsMapsPerVsys[ulCurVsys]["cloudshape"] = ValueUnion.psz;
1377 break;
1378
1379 case 'D': // --clouddomain
1380 if (actionType != CLOUD)
1381 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1382 GetState.pDef->pszLong);
1383 mapArgsMapsPerVsys[ulCurVsys]["clouddomain"] = ValueUnion.psz;
1384 break;
1385
1386 case 'R': // --clouddisksize
1387 if (actionType != CLOUD)
1388 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1389 GetState.pDef->pszLong);
1390 mapArgsMapsPerVsys[ulCurVsys]["clouddisksize"] = ValueUnion.psz;
1391 break;
1392
1393 case 'B': // --cloudbucket
1394 if (actionType != CLOUD)
1395 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1396 GetState.pDef->pszLong);
1397 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
1398 break;
1399
1400 case 'Q': // --cloudocivcn
1401 if (actionType != CLOUD)
1402 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1403 GetState.pDef->pszLong);
1404 mapArgsMapsPerVsys[ulCurVsys]["cloudocivcn"] = ValueUnion.psz;
1405 break;
1406
1407 case 'A': // --cloudpublicip
1408 if (actionType != CLOUD)
1409 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1410 GetState.pDef->pszLong);
1411 mapArgsMapsPerVsys[ulCurVsys]["cloudpublicip"] = ValueUnion.psz;
1412 break;
1413
1414 case 'i': /* --cloudprivateip */
1415 if (actionType != CLOUD)
1416 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1417 GetState.pDef->pszLong);
1418 mapArgsMapsPerVsys[ulCurVsys]["cloudprivateip"] = ValueUnion.psz;
1419 break;
1420
1421 case 'F': // --cloudprofile
1422 if (actionType != CLOUD)
1423 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1424 GetState.pDef->pszLong);
1425 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
1426 break;
1427
1428 case 'T': // --cloudocisubnet
1429 if (actionType != CLOUD)
1430 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1431 GetState.pDef->pszLong);
1432 mapArgsMapsPerVsys[ulCurVsys]["cloudocisubnet"] = ValueUnion.psz;
1433 break;
1434
1435 case 'K': // --cloudkeepobject
1436 if (actionType != CLOUD)
1437 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1438 GetState.pDef->pszLong);
1439 mapArgsMapsPerVsys[ulCurVsys]["cloudkeepobject"] = ValueUnion.psz;
1440 break;
1441
1442 case 'L': // --cloudlaunchinstance
1443 if (actionType != CLOUD)
1444 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1445 GetState.pDef->pszLong);
1446 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchinstance"] = ValueUnion.psz;
1447 break;
1448
1449 case 'M': /* --cloudlaunchmode */
1450 if (actionType != CLOUD)
1451 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1452 GetState.pDef->pszLong);
1453 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchmode"] = ValueUnion.psz;
1454 break;
1455
1456 case VINF_GETOPT_NOT_OPTION:
1457 {
1458 Utf8Str strMachine(ValueUnion.psz);
1459 // must be machine: try UUID or name
1460 ComPtr<IMachine> machine;
1461 CHECK_ERROR_BREAK(a->virtualBox, FindMachine(Bstr(strMachine).raw(),
1462 machine.asOutParam()));
1463 if (machine)
1464 llMachines.push_back(machine);
1465 break;
1466 }
1467
1468 default:
1469 if (c > 0)
1470 {
1471 if (RT_C_IS_GRAPH(c))
1472 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: -%c", c);
1473 else
1474 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: %i", c);
1475 }
1476 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
1477 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unknown option: %s", ValueUnion.psz);
1478 else if (ValueUnion.pDef)
1479 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
1480 else
1481 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%Rrs", c);
1482 }
1483
1484 if (FAILED(rc))
1485 break;
1486 }
1487
1488 if (FAILED(rc))
1489 break;
1490
1491 if (llMachines.empty())
1492 return errorSyntax(USAGE_EXPORTAPPLIANCE, "At least one machine must be specified with the export command.");
1493
1494 /* Last check after parsing all arguments */
1495 if (strOutputFile.isNotEmpty())
1496 {
1497 if (actionType == NOT_SET)
1498 {
1499 if (fCloud)
1500 actionType = CLOUD;
1501 else
1502 actionType = LOCAL;
1503 }
1504 }
1505 else
1506 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Missing --output argument with export command.");
1507
1508 // match command line arguments with the machines count
1509 // this is only to sort out invalid indices at this time
1510 ArgsMapsMap::const_iterator it;
1511 for (it = mapArgsMapsPerVsys.begin();
1512 it != mapArgsMapsPerVsys.end();
1513 ++it)
1514 {
1515 uint32_t ulVsys = it->first;
1516 if (ulVsys >= llMachines.size())
1517 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1518 "Invalid index %RI32 with -vsys option; you specified only %zu virtual system(s).",
1519 ulVsys, llMachines.size());
1520 }
1521
1522 ComPtr<IAppliance> pAppliance;
1523 CHECK_ERROR_BREAK(a->virtualBox, CreateAppliance(pAppliance.asOutParam()));
1524
1525 char *pszAbsFilePath = 0;
1526 if (strOutputFile.startsWith("S3://", RTCString::CaseInsensitive) ||
1527 strOutputFile.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
1528 strOutputFile.startsWith("webdav://", RTCString::CaseInsensitive) ||
1529 strOutputFile.startsWith("OCI://", RTCString::CaseInsensitive))
1530 pszAbsFilePath = RTStrDup(strOutputFile.c_str());
1531 else
1532 pszAbsFilePath = RTPathAbsDup(strOutputFile.c_str());
1533
1534 /*
1535 * The first stage - export machine/s to the Cloud or into the
1536 * OVA/OVF format on the local host.
1537 */
1538
1539 /* VSDList is needed for the second stage where we launch the cloud instances if it was requested by user */
1540 std::list< ComPtr<IVirtualSystemDescription> > VSDList;
1541 std::list< ComPtr<IMachine> >::iterator itM;
1542 uint32_t i=0;
1543 for (itM = llMachines.begin();
1544 itM != llMachines.end();
1545 ++itM, ++i)
1546 {
1547 ComPtr<IMachine> pMachine = *itM;
1548 ComPtr<IVirtualSystemDescription> pVSD;
1549 CHECK_ERROR_BREAK(pMachine, ExportTo(pAppliance, Bstr(pszAbsFilePath).raw(), pVSD.asOutParam()));
1550
1551 // Add additional info to the virtual system description if the user wants so
1552 ArgsMap *pmapArgs = NULL;
1553 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
1554 if (itm != mapArgsMapsPerVsys.end())
1555 pmapArgs = &itm->second;
1556 if (pmapArgs)
1557 {
1558 ArgsMap::iterator itD;
1559 for (itD = pmapArgs->begin();
1560 itD != pmapArgs->end();
1561 ++itD)
1562 {
1563 if (itD->first == "vmname")
1564 {
1565 //remove default value if user has specified new name (default value is set in the ExportTo())
1566// pVSD->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
1567 pVSD->AddDescription(VirtualSystemDescriptionType_Name,
1568 Bstr(itD->second).raw(), NULL);
1569 }
1570 else if (itD->first == "product")
1571 pVSD->AddDescription(VirtualSystemDescriptionType_Product,
1572 Bstr(itD->second).raw(), NULL);
1573 else if (itD->first == "producturl")
1574 pVSD->AddDescription(VirtualSystemDescriptionType_ProductUrl,
1575 Bstr(itD->second).raw(), NULL);
1576 else if (itD->first == "vendor")
1577 pVSD->AddDescription(VirtualSystemDescriptionType_Vendor,
1578 Bstr(itD->second).raw(), NULL);
1579 else if (itD->first == "vendorurl")
1580 pVSD->AddDescription(VirtualSystemDescriptionType_VendorUrl,
1581 Bstr(itD->second).raw(), NULL);
1582 else if (itD->first == "version")
1583 pVSD->AddDescription(VirtualSystemDescriptionType_Version,
1584 Bstr(itD->second).raw(), NULL);
1585 else if (itD->first == "description")
1586 pVSD->AddDescription(VirtualSystemDescriptionType_Description,
1587 Bstr(itD->second).raw(), NULL);
1588 else if (itD->first == "eula")
1589 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1590 Bstr(itD->second).raw(), NULL);
1591 else if (itD->first == "eulafile")
1592 {
1593 Utf8Str strContent;
1594 void *pvFile;
1595 size_t cbFile;
1596 int irc = RTFileReadAll(itD->second.c_str(), &pvFile, &cbFile);
1597 if (RT_SUCCESS(irc))
1598 {
1599 Bstr bstrContent((char*)pvFile, cbFile);
1600 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1601 bstrContent.raw(), NULL);
1602 RTFileReadAllFree(pvFile, cbFile);
1603 }
1604 else
1605 {
1606 RTMsgError("Cannot read license file \"%s\" which should be included in the virtual system %u.",
1607 itD->second.c_str(), i);
1608 return RTEXITCODE_FAILURE;
1609 }
1610 }
1611 /* add cloud export settings */
1612 else if (itD->first == "cloudshape")
1613 pVSD->AddDescription(VirtualSystemDescriptionType_CloudInstanceShape,
1614 Bstr(itD->second).raw(), NULL);
1615 else if (itD->first == "clouddomain")
1616 pVSD->AddDescription(VirtualSystemDescriptionType_CloudDomain,
1617 Bstr(itD->second).raw(), NULL);
1618 else if (itD->first == "clouddisksize")
1619 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBootDiskSize,
1620 Bstr(itD->second).raw(), NULL);
1621 else if (itD->first == "cloudbucket")
1622 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBucket,
1623 Bstr(itD->second).raw(), NULL);
1624 else if (itD->first == "cloudocivcn")
1625 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCIVCN,
1626 Bstr(itD->second).raw(), NULL);
1627 else if (itD->first == "cloudpublicip")
1628 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPublicIP,
1629 Bstr(itD->second).raw(), NULL);
1630 else if (itD->first == "cloudprivateip")
1631 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPrivateIP,
1632 Bstr(itD->second).raw(), NULL);
1633 else if (itD->first == "cloudprofile")
1634 pVSD->AddDescription(VirtualSystemDescriptionType_CloudProfileName,
1635 Bstr(itD->second).raw(), NULL);
1636 else if (itD->first == "cloudocisubnet")
1637 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCISubnet,
1638 Bstr(itD->second).raw(), NULL);
1639 else if (itD->first == "cloudkeepobject")
1640 pVSD->AddDescription(VirtualSystemDescriptionType_CloudKeepObject,
1641 Bstr(itD->second).raw(), NULL);
1642 else if (itD->first == "cloudlaunchmode")
1643 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCILaunchMode,
1644 Bstr(itD->second).raw(), NULL);
1645 else if (itD->first == "cloudlaunchinstance")
1646 pVSD->AddDescription(VirtualSystemDescriptionType_CloudLaunchInstance,
1647 Bstr(itD->second).raw(), NULL);
1648 }
1649 }
1650
1651 VSDList.push_back(pVSD);//store vsd for the possible second stage
1652 }
1653
1654 if (FAILED(rc))
1655 break;
1656
1657 /* Query required passwords and supply them to the appliance. */
1658 com::SafeArray<BSTR> aIdentifiers;
1659
1660 CHECK_ERROR_BREAK(pAppliance, GetPasswordIds(ComSafeArrayAsOutParam(aIdentifiers)));
1661
1662 if (aIdentifiers.size() > 0)
1663 {
1664 com::SafeArray<BSTR> aPasswords(aIdentifiers.size());
1665 RTPrintf("Enter the passwords for the following identifiers to export the apppliance:\n");
1666 for (unsigned idxId = 0; idxId < aIdentifiers.size(); idxId++)
1667 {
1668 com::Utf8Str strPassword;
1669 Bstr bstrPassword;
1670 Bstr bstrId = aIdentifiers[idxId];
1671
1672 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Password ID %s:", Utf8Str(bstrId).c_str());
1673 if (rcExit == RTEXITCODE_FAILURE)
1674 {
1675 RTStrFree(pszAbsFilePath);
1676 return rcExit;
1677 }
1678
1679 bstrPassword = strPassword;
1680 bstrPassword.detachTo(&aPasswords[idxId]);
1681 }
1682
1683 CHECK_ERROR_BREAK(pAppliance, AddPasswords(ComSafeArrayAsInParam(aIdentifiers),
1684 ComSafeArrayAsInParam(aPasswords)));
1685 }
1686
1687 if (fManifest)
1688 options.push_back(ExportOptions_CreateManifest);
1689
1690 if (fExportISOImages)
1691 options.push_back(ExportOptions_ExportDVDImages);
1692
1693 ComPtr<IProgress> progress;
1694 CHECK_ERROR_BREAK(pAppliance, Write(Bstr(strOvfFormat).raw(),
1695 ComSafeArrayAsInParam(options),
1696 Bstr(pszAbsFilePath).raw(),
1697 progress.asOutParam()));
1698 RTStrFree(pszAbsFilePath);
1699
1700 rc = showProgress(progress);
1701 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance write failed"), RTEXITCODE_FAILURE);
1702
1703 if (SUCCEEDED(rc))
1704 RTPrintf("Successfully exported %d machine(s).\n", llMachines.size());
1705
1706 /*
1707 * The second stage for the cloud case
1708 */
1709 if (actionType == CLOUD)
1710 {
1711 /* Launch the exported VM if the appropriate flag had been set on the first stage */
1712 for (std::list< ComPtr<IVirtualSystemDescription> >::iterator itVSD = VSDList.begin();
1713 itVSD != VSDList.end();
1714 ++itVSD)
1715 {
1716 ComPtr<IVirtualSystemDescription> pVSD = *itVSD;
1717
1718 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
1719 com::SafeArray<BSTR> aRefs;
1720 com::SafeArray<BSTR> aOvfValues;
1721 com::SafeArray<BSTR> aVBoxValues;
1722 com::SafeArray<BSTR> aExtraConfigValues;
1723
1724 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudLaunchInstance,
1725 ComSafeArrayAsOutParam(retTypes),
1726 ComSafeArrayAsOutParam(aRefs),
1727 ComSafeArrayAsOutParam(aOvfValues),
1728 ComSafeArrayAsOutParam(aVBoxValues),
1729 ComSafeArrayAsOutParam(aExtraConfigValues)));
1730
1731 Utf8Str flagCloudLaunchInstance(Bstr(aVBoxValues[0]).raw());
1732 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1733
1734 if (flagCloudLaunchInstance.equals("true"))
1735 {
1736 /* Getting the short provider name */
1737 Bstr bstrCloudProviderShortName(strOutputFile.c_str(), strOutputFile.find("://"));
1738
1739 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
1740 ComPtr<ICloudProviderManager> pCloudProviderManager;
1741 CHECK_ERROR_BREAK(pVirtualBox, COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()));
1742
1743 ComPtr<ICloudProvider> pCloudProvider;
1744 CHECK_ERROR_BREAK(pCloudProviderManager,
1745 GetProviderByShortName(bstrCloudProviderShortName.raw(), pCloudProvider.asOutParam()));
1746
1747 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudProfileName,
1748 ComSafeArrayAsOutParam(retTypes),
1749 ComSafeArrayAsOutParam(aRefs),
1750 ComSafeArrayAsOutParam(aOvfValues),
1751 ComSafeArrayAsOutParam(aVBoxValues),
1752 ComSafeArrayAsOutParam(aExtraConfigValues)));
1753
1754 ComPtr<ICloudProfile> pCloudProfile;
1755 CHECK_ERROR_BREAK(pCloudProvider, GetProfileByName(Bstr(aVBoxValues[0]).raw(), pCloudProfile.asOutParam()));
1756 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1757
1758 ComObjPtr<ICloudClient> oCloudClient;
1759 CHECK_ERROR_BREAK(pCloudProfile, CreateCloudClient(oCloudClient.asOutParam()));
1760 RTPrintf("Creating a cloud instance...\n");
1761
1762 ComPtr<IProgress> progress1;
1763 CHECK_ERROR_BREAK(oCloudClient, LaunchVM(pVSD, progress1.asOutParam()));
1764 rc = showProgress(progress1);
1765 CHECK_PROGRESS_ERROR_RET(progress1, ("Creating the cloud instance failed"), RTEXITCODE_FAILURE);
1766
1767 if (SUCCEEDED(rc))
1768 {
1769 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudInstanceId,
1770 ComSafeArrayAsOutParam(retTypes),
1771 ComSafeArrayAsOutParam(aRefs),
1772 ComSafeArrayAsOutParam(aOvfValues),
1773 ComSafeArrayAsOutParam(aVBoxValues),
1774 ComSafeArrayAsOutParam(aExtraConfigValues)));
1775
1776 RTPrintf("A cloud instance with id '%s' (provider '%s') was created\n",
1777 Utf8Str(Bstr(aVBoxValues[0]).raw()).c_str(),
1778 Utf8Str(bstrCloudProviderShortName.raw()).c_str());
1779 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1780 }
1781 }
1782 }
1783 }
1784 } while (0);
1785
1786 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1787}
1788
1789
1790/*********************************************************************************************************************************
1791* signova *
1792*********************************************************************************************************************************/
1793
1794/**
1795 * Reads the OVA and saves the manifest and signed status.
1796 *
1797 * @returns VBox status code (fully messaged).
1798 * @param pszOva The name of the OVA.
1799 * @param iVerbosity The noise level.
1800 * @param pStrName Where to return the manifest name.
1801 * @param phVfsManifest Where to return the manifest file handle (in mem).
1802 * @param pStrSignatureName Where to return the cert-file name if already
1803 * signed.
1804 */
1805static int getManifestFromOva(const char *pszOva, unsigned iVerbosity,
1806 Utf8Str *pStrName, PRTVFSFILE phVfsManifest, Utf8Str *pStrSignatureName)
1807{
1808 /*
1809 * Clear return values.
1810 */
1811 *phVfsManifest = NIL_RTVFSFILE;
1812 pStrName->setNull();
1813 pStrSignatureName->setNull();
1814
1815 /*
1816 * Open the file as a tar file system stream.
1817 */
1818 RTVFSIOSTREAM hVfsIosOva;
1819 int rc = RTVfsIoStrmOpenNormal(pszOva, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsIosOva);
1820 if (RT_FAILURE(rc))
1821 return RTMsgErrorExitFailure("Failed to open OVA '%s' for reading: %Rrc", pszOva, rc);
1822
1823 RTVFSFSSTREAM hVfsFssIn;
1824 rc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssIn);
1825 RTVfsIoStrmRelease(hVfsIosOva);
1826 if (RT_FAILURE(rc))
1827 return RTMsgErrorExitFailure("Failed to open OVA '%s' as a TAR file: %Rrc", pszOva, rc);
1828
1829 /*
1830 * Scan the objects in the stream and locate the manifest and any existing cert file.
1831 */
1832 if (iVerbosity >= 2)
1833 RTMsgInfo("Scanning OVA '%s' for a manifest and signature...", pszOva);
1834 for (;;)
1835 {
1836 /*
1837 * Retrive the next object.
1838 */
1839 char *pszName;
1840 RTVFSOBJTYPE enmType;
1841 RTVFSOBJ hVfsObj;
1842 rc = RTVfsFsStrmNext(hVfsFssIn, &pszName, &enmType, &hVfsObj);
1843 if (RT_FAILURE(rc))
1844 {
1845 if (rc == VERR_EOF)
1846 rc = VINF_SUCCESS;
1847 else
1848 RTMsgError("RTVfsFsStrmNext returned %Rrc", rc);
1849 break;
1850 }
1851
1852 if (iVerbosity > 2)
1853 RTMsgInfo(" %s %s\n",
1854 enmType == RTVFSOBJTYPE_IO_STREAM || enmType == RTVFSOBJTYPE_FILE ? "file"
1855 : enmType == RTVFSOBJTYPE_DIR ? "dir " : "unk ",
1856 pszName);
1857
1858 /*
1859 * Should we process this entry?
1860 */
1861 if ( enmType == RTVFSOBJTYPE_IO_STREAM
1862 || enmType == RTVFSOBJTYPE_FILE)
1863 {
1864 const char *pszSuffix = RTPathSuffix(pszName);
1865 if (pszSuffix && RTStrICmpAscii(pszSuffix, ".mf") == 0)
1866 {
1867 if (*phVfsManifest != NIL_RTVFSFILE)
1868 rc = RTMsgErrorRc(VERR_DUPLICATE, "OVA contains multiple manifests! first: %s second: %s",
1869 pStrName->c_str(), pszName);
1870 else
1871 {
1872 if (iVerbosity >= 2)
1873 RTMsgInfo("Found manifest file: %s", pszName);
1874 rc = pStrName->assignNoThrow(pszName);
1875 if (RT_SUCCESS(rc))
1876 {
1877 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
1878 Assert(hVfsIos != NIL_RTVFSIOSTREAM);
1879 rc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, phVfsManifest);
1880 RTVfsIoStrmRelease(hVfsIos); /* consumes stream handle. */
1881 if (RT_FAILURE(rc))
1882 rc = RTMsgErrorRc(VERR_DUPLICATE, "Failed to memorize the manifest: %Rrc", rc);
1883 }
1884 else
1885 RTMsgError("Out of memory!");
1886 }
1887 }
1888 else if (pszSuffix && RTStrICmpAscii(pszSuffix, ".cert") == 0)
1889 {
1890 if (iVerbosity >= 2)
1891 RTMsgInfo("Found existing signature file: %s", pszName);
1892 rc = pStrSignatureName->assignNoThrow(pszName);
1893 }
1894 }
1895
1896 /*
1897 * Release the current object and string.
1898 */
1899 RTVfsObjRelease(hVfsObj);
1900 RTStrFree(pszName);
1901 if (RT_FAILURE(rc))
1902 break;
1903 }
1904
1905 RTVfsFsStrmRelease(hVfsFssIn);
1906
1907 /*
1908 * Complain if no manifest.
1909 */
1910 if (RT_SUCCESS(rc) && *phVfsManifest == NIL_RTVFSFILE)
1911 rc = RTMsgErrorRc(VERR_NOT_FOUND, "The OVA contains no manifest and cannot be signed!");
1912
1913 return rc;
1914}
1915
1916
1917/**
1918 * Creates a PKCS\#7 signature and appends it to the signature file in PEM
1919 * format.
1920 */
1921static int doAddPkcs7Signature(PCRTCRX509CERTIFICATE pCertificate, RTCRKEY hPrivateKey,
1922 unsigned cIntermediateCerts, const char **papszIntermediateCerts,
1923 RTCRDIGEST hDigest, PRTERRINFOSTATIC pErrInfo, RTVFSFILE hVfsFileSignature)
1924{
1925 RT_NOREF(pCertificate, hPrivateKey, cIntermediateCerts, papszIntermediateCerts, hDigest, pErrInfo, hVfsFileSignature);
1926 RTPrintf("TODO: doAddPkcs7Signature\n");
1927 return VINF_SUCCESS;
1928}
1929
1930
1931/**
1932 * Appends the certificate in PEM format to the given VFS file.
1933 */
1934static int doWriteCertificate(RTVFSFILE hVfsFileSignature, PCRTCRX509CERTIFICATE pCertificate)
1935{
1936 RT_NOREF(hVfsFileSignature, pCertificate);
1937 RTPrintf("TODO: doWriteCertificate\n");
1938 return VINF_SUCCESS;
1939}
1940
1941
1942/**
1943 * Performs the OVA signing, producing an in-memory cert-file.
1944 */
1945static int doTheOvaSigning(PCRTCRX509CERTIFICATE pCertificate, RTCRKEY hPrivateKey,
1946 const char *pszManifestName, RTVFSFILE hVfsFileManifest,
1947 bool fPkcs7, unsigned cIntermediateCerts, const char **papszIntermediateCerts,
1948 PRTERRINFOSTATIC pErrInfo, PRTVFSFILE phVfsFileSignature)
1949{
1950 /*
1951 * We currently hardcode the digest algorithm to SHA-256.
1952 */
1953 /** @todo fall back on SHA-1 if the key is too small for SHA-256. */
1954 PCRTASN1OBJID const pObjId = &pCertificate->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm;
1955 RTCRDIGEST hDigest = NIL_RTCRDIGEST;
1956 int rc = RTCrDigestCreateByType(&hDigest, RTDIGESTTYPE_SHA256);
1957 if (RT_FAILURE(rc))
1958 return RTMsgErrorRc(rc, "Failed to create digest for %s: %Rrc", pObjId->szObjId, rc);
1959
1960 /* Figure out the digest type name for the .cert file: */
1961 RTDIGESTTYPE const enmDigestType = RTCrDigestGetType(hDigest);
1962 const char *pszDigestType;
1963 switch (enmDigestType)
1964 {
1965 case RTDIGESTTYPE_SHA1: pszDigestType = "SHA1"; break;
1966 case RTDIGESTTYPE_SHA256: pszDigestType = "SHA256"; break;
1967 case RTDIGESTTYPE_SHA512: pszDigestType = "SHA512"; break;
1968 default:
1969 RTCrDigestRelease(hDigest);
1970 return RTMsgErrorRc(VERR_INVALID_PARAMETER,
1971 "Unsupported digest type: %s", RTCrDigestTypeToName(enmDigestType));
1972 }
1973
1974 /*
1975 * Digest the manifest file.
1976 */
1977 rc = RTCrDigestUpdateFromVfsFile(hDigest, hVfsFileManifest, true /*fRewindFile*/);
1978 if (RT_SUCCESS(rc))
1979 rc = RTCrDigestFinal(hDigest, NULL, 0);
1980 if (RT_SUCCESS(rc))
1981 {
1982 /*
1983 * Sign the digest. Two passes, first to figure the signature size, the
1984 * second to do the actual signing.
1985 */
1986 PCRTASN1OBJID const pAlgorithm = &pCertificate->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm;
1987 PCRTASN1DYNTYPE const pAlgoParams = &pCertificate->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Parameters;
1988 size_t cbSignature = 0;
1989 rc = RTCrPkixPubKeySignDigest(pAlgorithm, hPrivateKey, pAlgoParams, hDigest, 0 /*fFlags*/,
1990 NULL /*pvSignature*/, &cbSignature, RTErrInfoInitStatic(pErrInfo));
1991 if (rc == VERR_BUFFER_OVERFLOW)
1992 {
1993 void *pvSignature = RTMemAllocZ(cbSignature);
1994 if (pvSignature)
1995 {
1996 rc = RTCrPkixPubKeySignDigest(pAlgorithm, hPrivateKey, pAlgoParams, hDigest, 0,
1997 pvSignature, &cbSignature, RTErrInfoInitStatic(pErrInfo));
1998 if (RT_SUCCESS(rc))
1999 {
2000 /*
2001 * Verify the signature using the certificate to make sure we've
2002 * been given the right private key.
2003 */
2004 rc = RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(&pCertificate->TbsCertificate.SubjectPublicKeyInfo,
2005 pvSignature, cbSignature, hDigest,
2006 RTErrInfoInitStatic(pErrInfo));
2007 if (RT_SUCCESS(rc))
2008 {
2009 /*
2010 * Create the output file.
2011 */
2012 RTVFSFILE hVfsFileSignature;
2013 rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, _8K, &hVfsFileSignature);
2014 if (RT_SUCCESS(rc))
2015 {
2016 rc = RTVfsFilePrintf(hVfsFileSignature, "%s(%s) = %.*Rhxs\n\n",
2017 pszDigestType, pszManifestName, cbSignature, pvSignature);
2018 if (RT_SUCCESS(rc))
2019 rc = doWriteCertificate(hVfsFileSignature, pCertificate);
2020 else
2021 RTMsgError("Failed to produce signature file: %Rrc", rc);
2022 if (RT_SUCCESS(rc) && fPkcs7)
2023 rc = doAddPkcs7Signature(pCertificate, hPrivateKey, cIntermediateCerts, papszIntermediateCerts,
2024 hDigest, pErrInfo, hVfsFileSignature);
2025 if (RT_SUCCESS(rc))
2026 {
2027 /*
2028 * Success.
2029 */
2030 *phVfsFileSignature = hVfsFileSignature;
2031 }
2032 else
2033 RTVfsFileRelease(hVfsFileSignature);
2034 }
2035 else
2036 RTMsgError("RTVfsMemFileCreate failed: %Rrc", rc);
2037 }
2038 else
2039 RTMsgError("Encountered a problem when validating the signature we just created: %Rrc%#RTeim\n"
2040 "Plase make sure the certificate and private key matches.", rc, &pErrInfo->Core);
2041 }
2042 else
2043 RTMsgError("2nd RTCrPkixPubKeySignDigest call failed: %Rrc%#RTeim", rc, pErrInfo->Core);
2044 RTMemFree(pvSignature);
2045 }
2046 else
2047 rc = RTMsgErrorRc(VERR_NO_MEMORY, "Out of memory!");
2048 }
2049 else
2050 RTMsgError("RTCrPkixPubKeySignDigest failed: %Rrc%#RTeim", rc, pErrInfo->Core);
2051 }
2052 else
2053 RTMsgError("Failed to create digest %s: %Rrc", RTCrDigestTypeToName(enmDigestType), rc);
2054 RTCrDigestRelease(hDigest);
2055 return rc;
2056}
2057
2058
2059/**
2060 * Alters the OVA file, adding (or replacing) the @a pszSignatureName member.
2061 */
2062static int updateTheOvaSignature(const char *pszOva, const char *pszSignatureName, RTVFSFILE hVfsFileSignature, bool fIsSigned)
2063{
2064 RT_NOREF(pszOva, pszSignatureName, hVfsFileSignature, fIsSigned);
2065
2066 RTMsgError("TODO: updateTheOvaSignature");
2067 return VINF_SUCCESS;
2068}
2069
2070
2071/**
2072 * Handles the 'ovasign' command.
2073 */
2074RTEXITCODE handleSignAppliance(HandlerArg *arg)
2075{
2076 /*
2077 * Parse arguments.
2078 */
2079 static const RTGETOPTDEF s_aOptions[] =
2080 {
2081 { "--certificate", 'c', RTGETOPT_REQ_STRING },
2082 { "--private-key", 'k', RTGETOPT_REQ_STRING },
2083 { "--private-key-password", 'p', RTGETOPT_REQ_STRING },
2084 { "--private-key-password-file",'P', RTGETOPT_REQ_STRING },
2085 { "--pkcs7", '7', RTGETOPT_REQ_NOTHING },
2086 { "--no-pkcs7", 'n', RTGETOPT_REQ_NOTHING },
2087 { "--intermediate-cert-file", 'i', RTGETOPT_REQ_STRING },
2088 { "--force", 'f', RTGETOPT_REQ_NOTHING },
2089 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
2090 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
2091 { "--dry-run", 'D', RTGETOPT_REQ_NOTHING },
2092 };
2093
2094 RTGETOPTSTATE GetState;
2095 int rc = RTGetOptInit(&GetState, arg->argc, arg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
2096 AssertRCReturn(rc, RTEXITCODE_FAILURE);
2097
2098 const char *pszOva = NULL;
2099 const char *pszCertificate = NULL;
2100 const char *pszPrivateKey = NULL;
2101 Utf8Str strPrivateKeyPassword;
2102 bool fPkcs7 = false;
2103 unsigned cIntermediateCerts = 0;
2104 const char *apszIntermediateCerts[32];
2105 bool fReSign = false;
2106 unsigned iVerbosity = 1;
2107
2108 bool fDryRun = false;
2109
2110 int c;
2111 RTGETOPTUNION ValueUnion;
2112 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2113 {
2114 switch (c)
2115 {
2116 case 'c':
2117 pszCertificate = ValueUnion.psz;
2118 break;
2119
2120 case 'k':
2121 pszPrivateKey = ValueUnion.psz;
2122 break;
2123
2124 case 'p':
2125 if (strPrivateKeyPassword.isNotEmpty())
2126 RTMsgWarning("Password is given more than once.");
2127 strPrivateKeyPassword = ValueUnion.psz;
2128 break;
2129
2130 case 'P':
2131 {
2132 if (strPrivateKeyPassword.isNotEmpty())
2133 RTMsgWarning("Password is given more than once.");
2134 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPrivateKeyPassword);
2135 if (rcExit == RTEXITCODE_SUCCESS)
2136 break;
2137 return rcExit;
2138 }
2139
2140 case '7':
2141 fPkcs7 = true;
2142 break;
2143
2144 case 'n':
2145 fPkcs7 = false;
2146 break;
2147
2148 case 'i':
2149 if (cIntermediateCerts >= RT_ELEMENTS(apszIntermediateCerts))
2150 return RTMsgErrorExitFailure("Too many intermediate certificates: max %zu",
2151 RT_ELEMENTS(apszIntermediateCerts));
2152 apszIntermediateCerts[cIntermediateCerts++] = ValueUnion.psz;
2153 break;
2154
2155 case 'f':
2156 fReSign = true;
2157 break;
2158
2159 case 'v':
2160 iVerbosity++;
2161 break;
2162
2163 case 'q':
2164 iVerbosity = 0;
2165 break;
2166
2167 case 'D':
2168 fDryRun = true;
2169 break;
2170
2171 case VINF_GETOPT_NOT_OPTION:
2172 if (!pszOva)
2173 {
2174 pszOva = ValueUnion.psz;
2175 break;
2176 }
2177 RT_FALL_THRU();
2178 default:
2179 return errorGetOpt(c, &ValueUnion);
2180 }
2181 }
2182
2183 /* Required paramaters: */
2184 if (!pszOva || !*pszOva)
2185 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No OVA file was specified!");
2186 if (!pszCertificate || !*pszCertificate)
2187 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No signing certificate (--certificate=<file>) was specified!");
2188 if (!pszPrivateKey || !*pszPrivateKey)
2189 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No signing private key (--private-key=<file>) was specified!");
2190
2191 /* Check that input files exists before we commence: */
2192 if (!RTFileExists(pszOva))
2193 return RTMsgErrorExitFailure("The specified OVA file was not found: %s", pszOva);
2194 if (!RTFileExists(pszCertificate))
2195 return RTMsgErrorExitFailure("The specified certificate file was not found: %s", pszCertificate);
2196 if (!RTFileExists(pszPrivateKey))
2197 return RTMsgErrorExitFailure("The specified private key file was not found: %s", pszPrivateKey);
2198
2199 /*
2200 * Read the certificate and private key.
2201 */
2202 RTERRINFOSTATIC ErrInfo;
2203 RTCRX509CERTIFICATE Certificate;
2204 rc = RTCrX509Certificate_ReadFromFile(&Certificate, pszCertificate, 0, &g_RTAsn1DefaultAllocator,
2205 RTErrInfoInitStatic(&ErrInfo));
2206 if (RT_FAILURE(rc))
2207 return RTMsgErrorExitFailure("Error reading certificate from '%s': %Rrc%#RTeim", pszCertificate, rc, &ErrInfo.Core);
2208
2209 RTCRKEY hPrivateKey = NIL_RTCRKEY;
2210 rc = RTCrKeyCreateFromFile(&hPrivateKey, 0 /*fFlags*/, pszPrivateKey, strPrivateKeyPassword.c_str(),
2211 RTErrInfoInitStatic(&ErrInfo));
2212 if (RT_SUCCESS(rc))
2213 {
2214 if (iVerbosity > 1)
2215 RTMsgInfo("Successfully read the certificate and private key.");
2216
2217 /*
2218 * Extract the manifest from the OVA and check whether it is already signed.
2219 */
2220 Utf8Str strManifestName;
2221 Utf8Str strSignatureName;
2222 RTVFSFILE hVfsFileManifest = NIL_RTVFSFILE;
2223 rc = getManifestFromOva(pszOva, iVerbosity, &strManifestName, &hVfsFileManifest, &strSignatureName);
2224 if (RT_SUCCESS(rc))
2225 {
2226 bool const fIsSigned = strSignatureName.isNotEmpty();
2227 if (!fIsSigned || fReSign)
2228 {
2229 /*
2230 * Do the signing and create the signature file.
2231 */
2232 RTVFSFILE hVfsFileSignature = NIL_RTVFSFILE;
2233 rc = doTheOvaSigning(&Certificate, hPrivateKey, strManifestName.c_str(), hVfsFileManifest,
2234 fPkcs7, cIntermediateCerts, apszIntermediateCerts,
2235 &ErrInfo, &hVfsFileSignature);
2236
2237 /*
2238 * Construct the signature filename if there isn't one already:
2239 */
2240 if (RT_SUCCESS(rc) && strSignatureName.isEmpty())
2241 {
2242 rc = strSignatureName.assignNoThrow(strManifestName);
2243 if (RT_SUCCESS(rc))
2244 rc = strSignatureName.stripSuffix().appendNoThrow(".cert");
2245 }
2246 if (RT_SUCCESS(rc) && !fDryRun)
2247 {
2248 /*
2249 * Update the OVA.
2250 */
2251 rc = updateTheOvaSignature(pszOva, strSignatureName.c_str(), hVfsFileSignature, fIsSigned);
2252 }
2253 }
2254 else
2255 rc = RTMsgErrorRc(VERR_ALREADY_EXISTS,
2256 "The OVA is already signed! (Use the --force option to force re-signing it.)");
2257 }
2258 RTCrKeyRelease(hPrivateKey);
2259 }
2260 else
2261 RTPrintf("Error reading the private key from %s: %Rrc%#RTeim", pszPrivateKey, rc, &ErrInfo.Core);
2262 RTCrX509Certificate_Delete(&Certificate);
2263
2264 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2265}
2266
2267#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