VirtualBox

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

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

FE/VBoxManage: Remove the now unused VBoxManageHelp build target and the VBOX_ONLY_DOCS #ifdef's in the code, ​bugref:9186

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