VirtualBox

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

Last change on this file since 93835 was 93835, checked in by vboxsync, 3 years ago

OCI: Add VSD enum values for flexible shape options. Write the values
out to the VSD in the OCIShapeFormPart. bugref:10158.

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