VirtualBox

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

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

doc, Frontends/VBoxManage: Integrate several refentry documentation pieces, bringing them up to date (making sure everything documented is actually understood). Remove the corresponding old style documentation and also the manually written help text in VBoxManage. Also fixes the synopsis printing when showing a syntax error message for new style documentation.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette