VirtualBox

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

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

Main/Appliance: Allow users to specify a different storage controller
and/or controller port for hard disks when importing a VM. bugref:5027

'VBoxManage import foo.ova -n' has always presented a --controller
option for hard disks but the code to implement this had never been
implemented. This changeset adds the --controller functionality and
also includes a --port option for changing the controller port as well.

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