VirtualBox

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

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

VBoxManage: Finish cleanup for 'export' and 'import'.

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