VirtualBox

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

Last change on this file since 84033 was 84033, checked in by vboxsync, 5 years ago

bugref:9699. small fix.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 114.9 KB
Line 
1/* $Id: VBoxManageAppliance.cpp 84033 2020-04-28 10:06:54Z 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#include <revision-generated.h> /* VBOX_SVN_REV - PCH prevents putting it in DEFS. */
37
38#include <list>
39#include <map>
40#endif /* !VBOX_ONLY_DOCS */
41
42#include <iprt/getopt.h>
43#include <iprt/ctype.h>
44#include <iprt/path.h>
45#include <iprt/file.h>
46#include <iprt/err.h>
47#include <iprt/zip.h>
48#include <iprt/stream.h>
49#include <iprt/vfs.h>
50#include <iprt/manifest.h>
51#include <iprt/crypto/digest.h>
52#include <iprt/crypto/x509.h>
53#include <iprt/crypto/pkcs7.h>
54#include <iprt/crypto/store.h>
55#include <iprt/crypto/spc.h>
56#include <iprt/crypto/key.h>
57#include <iprt/crypto/pkix.h>
58
59
60
61#include "VBoxManage.h"
62using namespace com;
63
64
65// funcs
66///////////////////////////////////////////////////////////////////////////////
67
68typedef std::map<Utf8Str, Utf8Str> ArgsMap; // pairs of strings like "vmname" => "newvmname"
69typedef std::map<uint32_t, ArgsMap> ArgsMapsMap; // map of maps, one for each virtual system, sorted by index
70
71typedef std::map<uint32_t, bool> IgnoresMap; // pairs of numeric description entry indices
72typedef std::map<uint32_t, IgnoresMap> IgnoresMapsMap; // map of maps, one for each virtual system, sorted by index
73
74static bool findArgValue(Utf8Str &strOut,
75 ArgsMap *pmapArgs,
76 const Utf8Str &strKey)
77{
78 if (pmapArgs)
79 {
80 ArgsMap::iterator it;
81 it = pmapArgs->find(strKey);
82 if (it != pmapArgs->end())
83 {
84 strOut = it->second;
85 pmapArgs->erase(it);
86 return true;
87 }
88 }
89
90 return false;
91}
92
93static int parseImportOptions(const char *psz, com::SafeArray<ImportOptions_T> *options)
94{
95 int rc = VINF_SUCCESS;
96 while (psz && *psz && RT_SUCCESS(rc))
97 {
98 size_t len;
99 const char *pszComma = strchr(psz, ',');
100 if (pszComma)
101 len = pszComma - psz;
102 else
103 len = strlen(psz);
104 if (len > 0)
105 {
106 if (!RTStrNICmp(psz, "KeepAllMACs", len))
107 options->push_back(ImportOptions_KeepAllMACs);
108 else if (!RTStrNICmp(psz, "KeepNATMACs", len))
109 options->push_back(ImportOptions_KeepNATMACs);
110 else if (!RTStrNICmp(psz, "ImportToVDI", len))
111 options->push_back(ImportOptions_ImportToVDI);
112 else
113 rc = VERR_PARSE_ERROR;
114 }
115 if (pszComma)
116 psz += len + 1;
117 else
118 psz += len;
119 }
120
121 return rc;
122}
123
124static const RTGETOPTDEF g_aImportApplianceOptions[] =
125{
126 { "--dry-run", 'n', RTGETOPT_REQ_NOTHING },
127 { "-dry-run", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
128 { "--dryrun", 'n', RTGETOPT_REQ_NOTHING },
129 { "-dryrun", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
130 { "--detailed-progress", 'P', RTGETOPT_REQ_NOTHING },
131 { "-detailed-progress", 'P', RTGETOPT_REQ_NOTHING }, // deprecated
132 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
133 { "-vsys", 's', RTGETOPT_REQ_UINT32 }, // deprecated
134 { "--ostype", 'o', RTGETOPT_REQ_STRING },
135 { "-ostype", 'o', RTGETOPT_REQ_STRING }, // deprecated
136 { "--vmname", 'V', RTGETOPT_REQ_STRING },
137 { "-vmname", 'V', RTGETOPT_REQ_STRING }, // deprecated
138 { "--settingsfile", 'S', RTGETOPT_REQ_STRING },
139 { "--basefolder", 'p', RTGETOPT_REQ_STRING },
140 { "--group", 'g', RTGETOPT_REQ_STRING },
141 { "--memory", 'm', RTGETOPT_REQ_STRING },
142 { "-memory", 'm', RTGETOPT_REQ_STRING }, // deprecated
143 { "--cpus", 'c', RTGETOPT_REQ_STRING },
144 { "--description", 'd', RTGETOPT_REQ_STRING },
145 { "--eula", 'L', RTGETOPT_REQ_STRING },
146 { "-eula", 'L', RTGETOPT_REQ_STRING }, // deprecated
147 { "--unit", 'u', RTGETOPT_REQ_UINT32 },
148 { "-unit", 'u', RTGETOPT_REQ_UINT32 }, // deprecated
149 { "--ignore", 'x', RTGETOPT_REQ_NOTHING },
150 { "-ignore", 'x', RTGETOPT_REQ_NOTHING }, // deprecated
151 { "--scsitype", 'T', RTGETOPT_REQ_UINT32 },
152 { "-scsitype", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
153 { "--type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
154 { "-type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
155#if 0 /* Changing the controller is fully valid, but the current design on how
156 the params are evaluated here doesn't allow two parameter for one
157 unit. The target disk path is more important. I leave it for future
158 improvments. */
159 { "--controller", 'C', RTGETOPT_REQ_STRING },
160#endif
161 { "--disk", 'D', RTGETOPT_REQ_STRING },
162 { "--options", 'O', RTGETOPT_REQ_STRING },
163
164 { "--cloud", 'j', RTGETOPT_REQ_NOTHING},
165 { "--cloudprofile", 'k', RTGETOPT_REQ_STRING },
166 { "--cloudinstanceid", 'l', RTGETOPT_REQ_STRING },
167 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING }
168};
169
170enum actionType
171{
172 NOT_SET, LOCAL, CLOUD
173} actionType;
174
175RTEXITCODE handleImportAppliance(HandlerArg *arg)
176{
177 HRESULT rc = S_OK;
178 bool fCloud = false; // the default
179 actionType = NOT_SET;
180 Utf8Str strOvfFilename;
181 bool fExecute = true; // if true, then we actually do the import
182 com::SafeArray<ImportOptions_T> options;
183 uint32_t ulCurVsys = (uint32_t)-1;
184 uint32_t ulCurUnit = (uint32_t)-1;
185 // for each --vsys X command, maintain a map of command line items
186 // (we'll parse them later after interpreting the OVF, when we can
187 // actually check whether they make sense semantically)
188 ArgsMapsMap mapArgsMapsPerVsys;
189 IgnoresMapsMap mapIgnoresMapsPerVsys;
190
191 int c;
192 RTGETOPTUNION ValueUnion;
193 RTGETOPTSTATE GetState;
194 // start at 0 because main() has hacked both the argc and argv given to us
195 RTGetOptInit(&GetState, arg->argc, arg->argv, g_aImportApplianceOptions, RT_ELEMENTS(g_aImportApplianceOptions),
196 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
197 while ((c = RTGetOpt(&GetState, &ValueUnion)))
198 {
199 switch (c)
200 {
201 case 'n': // --dry-run
202 fExecute = false;
203 break;
204
205 case 'P': // --detailed-progress
206 g_fDetailedProgress = true;
207 break;
208
209 case 's': // --vsys
210 if (fCloud == false && actionType == NOT_SET)
211 actionType = LOCAL;
212
213 if (actionType != LOCAL)
214 return errorSyntax(USAGE_EXPORTAPPLIANCE,
215 "Option \"%s\" can't be used together with \"--cloud\" argument.",
216 GetState.pDef->pszLong);
217
218 ulCurVsys = ValueUnion.u32;
219 ulCurUnit = (uint32_t)-1;
220 break;
221
222 case 'o': // --ostype
223 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
224 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
225 mapArgsMapsPerVsys[ulCurVsys]["ostype"] = ValueUnion.psz;
226 break;
227
228 case 'V': // --vmname
229 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
230 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
231 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
232 break;
233
234 case 'S': // --settingsfile
235 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
236 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
237 mapArgsMapsPerVsys[ulCurVsys]["settingsfile"] = ValueUnion.psz;
238 break;
239
240 case 'p': // --basefolder
241 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
242 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
243 mapArgsMapsPerVsys[ulCurVsys]["basefolder"] = ValueUnion.psz;
244 break;
245
246 case 'g': // --group
247 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
248 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
249 mapArgsMapsPerVsys[ulCurVsys]["group"] = ValueUnion.psz;
250 break;
251
252 case 'd': // --description
253 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
254 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
255 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
256 break;
257
258 case 'L': // --eula
259 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
260 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
261 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
262 break;
263
264 case 'm': // --memory
265 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
266 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
267 mapArgsMapsPerVsys[ulCurVsys]["memory"] = ValueUnion.psz;
268 break;
269
270 case 'c': // --cpus
271 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
272 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
273 mapArgsMapsPerVsys[ulCurVsys]["cpus"] = ValueUnion.psz;
274 break;
275
276 case 'u': // --unit
277 ulCurUnit = ValueUnion.u32;
278 break;
279
280 case 'x': // --ignore
281 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
282 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
283 if (ulCurUnit == (uint32_t)-1)
284 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
285 mapIgnoresMapsPerVsys[ulCurVsys][ulCurUnit] = true;
286 break;
287
288 case 'T': // --scsitype
289 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
290 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
291 if (ulCurUnit == (uint32_t)-1)
292 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
293 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("scsitype%u", ulCurUnit)] = ValueUnion.psz;
294 break;
295
296 case 'C': // --controller
297 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
298 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
299 if (ulCurUnit == (uint32_t)-1)
300 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
301 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("controller%u", ulCurUnit)] = ValueUnion.psz;
302 break;
303
304 case 'D': // --disk
305 if (actionType == LOCAL && ulCurVsys == (uint32_t)-1)
306 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
307 if (ulCurUnit == (uint32_t)-1)
308 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong);
309 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("disk%u", ulCurUnit)] = ValueUnion.psz;
310 break;
311
312 case 'O': // --options
313 if (RT_FAILURE(parseImportOptions(ValueUnion.psz, &options)))
314 return errorArgument("Invalid import options '%s'\n", ValueUnion.psz);
315 break;
316
317 /*--cloud and --vsys are orthogonal, only one must be presented*/
318 case 'j': // --cloud
319 if (fCloud == false && actionType == NOT_SET)
320 {
321 fCloud = true;
322 actionType = CLOUD;
323 }
324
325 if (actionType != CLOUD)
326 return errorSyntax(USAGE_IMPORTAPPLIANCE,
327 "Option \"%s\" can't be used together with \"--vsys\" argument.",
328 GetState.pDef->pszLong);
329
330 ulCurVsys = 0;
331 break;
332
333 /* Cloud export settings */
334 case 'k': // --cloudprofile
335 if (actionType != CLOUD)
336 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
337 GetState.pDef->pszLong);
338 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
339 break;
340
341 case 'l': // --cloudinstanceid
342 if (actionType != CLOUD)
343 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
344 GetState.pDef->pszLong);
345 mapArgsMapsPerVsys[ulCurVsys]["cloudinstanceid"] = ValueUnion.psz;
346 break;
347
348 case 'B': // --cloudbucket
349 if (actionType != CLOUD)
350 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
351 GetState.pDef->pszLong);
352 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
353 break;
354
355 case VINF_GETOPT_NOT_OPTION:
356 if (strOvfFilename.isEmpty())
357 strOvfFilename = ValueUnion.psz;
358 else
359 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid parameter '%s'", ValueUnion.psz);
360 break;
361
362 default:
363 if (c > 0)
364 {
365 if (RT_C_IS_PRINT(c))
366 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option -%c", c);
367 else
368 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option case %i", c);
369 }
370 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
371 return errorSyntax(USAGE_IMPORTAPPLIANCE, "unknown option: %s\n", ValueUnion.psz);
372 else if (ValueUnion.pDef)
373 return errorSyntax(USAGE_IMPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
374 else
375 return errorSyntax(USAGE_IMPORTAPPLIANCE, "error: %Rrs", c);
376 }
377 }
378
379 /* Last check after parsing all arguments */
380 if (strOvfFilename.isNotEmpty())
381 {
382 if (actionType == NOT_SET)
383 {
384 if (fCloud)
385 actionType = CLOUD;
386 else
387 actionType = LOCAL;
388 }
389 }
390 else
391 return errorSyntax(USAGE_IMPORTAPPLIANCE, "Not enough arguments for \"import\" command.");
392
393 do
394 {
395 ComPtr<IAppliance> pAppliance;
396 CHECK_ERROR_BREAK(arg->virtualBox, CreateAppliance(pAppliance.asOutParam()));
397 //in the case of Cloud, append the instance id here because later it's harder to do
398 if (actionType == CLOUD)
399 {
400 try
401 {
402 /* Check presence of cloudprofile and cloudinstanceid in the map.
403 * If there isn't the exception is triggered. It's standard std:map logic.*/
404 ArgsMap a = mapArgsMapsPerVsys[ulCurVsys];
405 a.at("cloudprofile");
406 a.at("cloudinstanceid");
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 (actionType == 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::ErrorInfo info0(pAppliance, COM_IIDOF(IAppliance));
447
448 com::SafeArray<BSTR> aWarnings;
449 if (SUCCEEDED(pAppliance->GetWarnings(ComSafeArrayAsOutParam(aWarnings))))
450 {
451 size_t cWarnings = aWarnings.size();
452 for (unsigned i = 0; i < cWarnings; ++i)
453 {
454 Bstr bstrWarning(aWarnings[i]);
455 RTMsgWarning("%ls.", bstrWarning.raw());
456 }
457 }
458
459 if (FAILED(rc)) // during interpret, after printing warnings
460 {
461 com::GluePrintErrorInfo(info0);
462 com::GluePrintErrorContext("Interpret", __FILE__, __LINE__);
463 break;
464 }
465
466 RTStrmPrintf(g_pStdErr, "OK.\n");
467
468 // fetch all disks
469 com::SafeArray<BSTR> retDisks;
470 CHECK_ERROR_BREAK(pAppliance,
471 COMGETTER(Disks)(ComSafeArrayAsOutParam(retDisks)));
472 if (retDisks.size() > 0)
473 {
474 RTPrintf("Disks:\n");
475 for (unsigned i = 0; i < retDisks.size(); i++)
476 RTPrintf(" %ls\n", retDisks[i]);
477 RTPrintf("\n");
478 }
479
480 // fetch virtual system descriptions
481 CHECK_ERROR_BREAK(pAppliance,
482 COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions)));
483
484 cVirtualSystemDescriptions = aVirtualSystemDescriptions.size();
485
486 // match command line arguments with virtual system descriptions;
487 // this is only to sort out invalid indices at this time
488 ArgsMapsMap::const_iterator it;
489 for (it = mapArgsMapsPerVsys.begin();
490 it != mapArgsMapsPerVsys.end();
491 ++it)
492 {
493 uint32_t ulVsys = it->first;
494 if (ulVsys >= cVirtualSystemDescriptions)
495 return errorSyntax(USAGE_IMPORTAPPLIANCE,
496 "Invalid index %RI32 with -vsys option; the OVF contains only %zu virtual system(s).",
497 ulVsys, cVirtualSystemDescriptions);
498 }
499 }
500 else if (actionType == CLOUD)
501 {
502 /* In the Cloud case the call of interpret() isn't needed because there isn't any OVF XML file.
503 * All info is got from the Cloud and VSD is filled inside IAppliance::read(). */
504 // fetch virtual system descriptions
505 CHECK_ERROR_BREAK(pAppliance,
506 COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions)));
507
508 cVirtualSystemDescriptions = aVirtualSystemDescriptions.size();
509 }
510
511 uint32_t cLicensesInTheWay = 0;
512
513 // dump virtual system descriptions and match command-line arguments
514 if (cVirtualSystemDescriptions > 0)
515 {
516 for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
517 {
518 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
519 com::SafeArray<BSTR> aRefs;
520 com::SafeArray<BSTR> aOvfValues;
521 com::SafeArray<BSTR> aVBoxValues;
522 com::SafeArray<BSTR> aExtraConfigValues;
523 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
524 GetDescription(ComSafeArrayAsOutParam(retTypes),
525 ComSafeArrayAsOutParam(aRefs),
526 ComSafeArrayAsOutParam(aOvfValues),
527 ComSafeArrayAsOutParam(aVBoxValues),
528 ComSafeArrayAsOutParam(aExtraConfigValues)));
529
530 RTPrintf("Virtual system %u:\n", i);
531
532 // look up the corresponding command line options, if any
533 ArgsMap *pmapArgs = NULL;
534 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
535 if (itm != mapArgsMapsPerVsys.end())
536 pmapArgs = &itm->second;
537
538 // this collects the final values for setFinalValues()
539 com::SafeArray<BOOL> aEnabled(retTypes.size());
540 com::SafeArray<BSTR> aFinalValues(retTypes.size());
541
542 for (unsigned a = 0; a < retTypes.size(); ++a)
543 {
544 VirtualSystemDescriptionType_T t = retTypes[a];
545
546 Utf8Str strOverride;
547
548 Bstr bstrFinalValue = aVBoxValues[a];
549
550 bool fIgnoreThis = mapIgnoresMapsPerVsys[i][a];
551
552 aEnabled[a] = true;
553
554 switch (t)
555 {
556 case VirtualSystemDescriptionType_OS:
557 if (findArgValue(strOverride, pmapArgs, "ostype"))
558 {
559 bstrFinalValue = strOverride;
560 RTPrintf("%2u: OS type specified with --ostype: \"%ls\"\n",
561 a, bstrFinalValue.raw());
562 }
563 else
564 RTPrintf("%2u: Suggested OS type: \"%ls\""
565 "\n (change with \"--vsys %u --ostype <type>\"; use \"list ostypes\" to list all possible values)\n",
566 a, bstrFinalValue.raw(), i);
567 break;
568
569 case VirtualSystemDescriptionType_Name:
570 if (findArgValue(strOverride, pmapArgs, "vmname"))
571 {
572 bstrFinalValue = strOverride;
573 RTPrintf("%2u: VM name specified with --vmname: \"%ls\"\n",
574 a, bstrFinalValue.raw());
575 }
576 else
577 RTPrintf("%2u: Suggested VM name \"%ls\""
578 "\n (change with \"--vsys %u --vmname <name>\")\n",
579 a, bstrFinalValue.raw(), i);
580 break;
581
582 case VirtualSystemDescriptionType_Product:
583 RTPrintf("%2u: Product (ignored): %ls\n",
584 a, aVBoxValues[a]);
585 break;
586
587 case VirtualSystemDescriptionType_ProductUrl:
588 RTPrintf("%2u: ProductUrl (ignored): %ls\n",
589 a, aVBoxValues[a]);
590 break;
591
592 case VirtualSystemDescriptionType_Vendor:
593 RTPrintf("%2u: Vendor (ignored): %ls\n",
594 a, aVBoxValues[a]);
595 break;
596
597 case VirtualSystemDescriptionType_VendorUrl:
598 RTPrintf("%2u: VendorUrl (ignored): %ls\n",
599 a, aVBoxValues[a]);
600 break;
601
602 case VirtualSystemDescriptionType_Version:
603 RTPrintf("%2u: Version (ignored): %ls\n",
604 a, aVBoxValues[a]);
605 break;
606
607 case VirtualSystemDescriptionType_Description:
608 if (findArgValue(strOverride, pmapArgs, "description"))
609 {
610 bstrFinalValue = strOverride;
611 RTPrintf("%2u: Description specified with --description: \"%ls\"\n",
612 a, bstrFinalValue.raw());
613 }
614 else
615 RTPrintf("%2u: Description \"%ls\""
616 "\n (change with \"--vsys %u --description <desc>\")\n",
617 a, bstrFinalValue.raw(), i);
618 break;
619
620 case VirtualSystemDescriptionType_License:
621 ++cLicensesInTheWay;
622 if (findArgValue(strOverride, pmapArgs, "eula"))
623 {
624 if (strOverride == "show")
625 {
626 RTPrintf("%2u: End-user license agreement"
627 "\n (accept with \"--vsys %u --eula accept\"):"
628 "\n\n%ls\n\n",
629 a, i, bstrFinalValue.raw());
630 }
631 else if (strOverride == "accept")
632 {
633 RTPrintf("%2u: End-user license agreement (accepted)\n",
634 a);
635 --cLicensesInTheWay;
636 }
637 else
638 return errorSyntax(USAGE_IMPORTAPPLIANCE,
639 "Argument to --eula must be either \"show\" or \"accept\".");
640 }
641 else
642 RTPrintf("%2u: End-user license agreement"
643 "\n (display with \"--vsys %u --eula show\";"
644 "\n accept with \"--vsys %u --eula accept\")\n",
645 a, i, i);
646 break;
647
648 case VirtualSystemDescriptionType_CPU:
649 if (findArgValue(strOverride, pmapArgs, "cpus"))
650 {
651 uint32_t cCPUs;
652 if ( strOverride.toInt(cCPUs) == VINF_SUCCESS
653 && cCPUs >= VMM_MIN_CPU_COUNT
654 && cCPUs <= VMM_MAX_CPU_COUNT
655 )
656 {
657 bstrFinalValue = strOverride;
658 RTPrintf("%2u: No. of CPUs specified with --cpus: %ls\n",
659 a, bstrFinalValue.raw());
660 }
661 else
662 return errorSyntax(USAGE_IMPORTAPPLIANCE,
663 "Argument to --cpus option must be a number greater than %d and less than %d.",
664 VMM_MIN_CPU_COUNT - 1, VMM_MAX_CPU_COUNT + 1);
665 }
666 else
667 RTPrintf("%2u: Number of CPUs: %ls\n (change with \"--vsys %u --cpus <n>\")\n",
668 a, bstrFinalValue.raw(), i);
669 break;
670
671 case VirtualSystemDescriptionType_Memory:
672 {
673 if (findArgValue(strOverride, pmapArgs, "memory"))
674 {
675 uint32_t ulMemMB;
676 if (VINF_SUCCESS == strOverride.toInt(ulMemMB))
677 {
678 bstrFinalValue = strOverride;
679 RTPrintf("%2u: Guest memory specified with --memory: %ls MB\n",
680 a, bstrFinalValue.raw());
681 }
682 else
683 return errorSyntax(USAGE_IMPORTAPPLIANCE,
684 "Argument to --memory option must be a non-negative number.");
685 }
686 else
687 RTPrintf("%2u: Guest memory: %ls MB\n (change with \"--vsys %u --memory <MB>\")\n",
688 a, bstrFinalValue.raw(), i);
689 break;
690 }
691
692 case VirtualSystemDescriptionType_HardDiskControllerIDE:
693 if (fIgnoreThis)
694 {
695 RTPrintf("%2u: IDE controller, type %ls -- disabled\n",
696 a,
697 aVBoxValues[a]);
698 aEnabled[a] = false;
699 }
700 else
701 RTPrintf("%2u: IDE controller, type %ls"
702 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
703 a,
704 aVBoxValues[a],
705 i, a);
706 break;
707
708 case VirtualSystemDescriptionType_HardDiskControllerSATA:
709 if (fIgnoreThis)
710 {
711 RTPrintf("%2u: SATA controller, type %ls -- disabled\n",
712 a,
713 aVBoxValues[a]);
714 aEnabled[a] = false;
715 }
716 else
717 RTPrintf("%2u: SATA controller, type %ls"
718 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
719 a,
720 aVBoxValues[a],
721 i, a);
722 break;
723
724 case VirtualSystemDescriptionType_HardDiskControllerSAS:
725 if (fIgnoreThis)
726 {
727 RTPrintf("%2u: SAS controller, type %ls -- disabled\n",
728 a,
729 aVBoxValues[a]);
730 aEnabled[a] = false;
731 }
732 else
733 RTPrintf("%2u: SAS controller, type %ls"
734 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
735 a,
736 aVBoxValues[a],
737 i, a);
738 break;
739
740 case VirtualSystemDescriptionType_HardDiskControllerSCSI:
741 if (fIgnoreThis)
742 {
743 RTPrintf("%2u: SCSI controller, type %ls -- disabled\n",
744 a,
745 aVBoxValues[a]);
746 aEnabled[a] = false;
747 }
748 else
749 {
750 Utf8StrFmt strTypeArg("scsitype%u", a);
751 if (findArgValue(strOverride, pmapArgs, strTypeArg))
752 {
753 bstrFinalValue = strOverride;
754 RTPrintf("%2u: SCSI controller, type set with --unit %u --scsitype: \"%ls\"\n",
755 a,
756 a,
757 bstrFinalValue.raw());
758 }
759 else
760 RTPrintf("%2u: SCSI controller, type %ls"
761 "\n (change with \"--vsys %u --unit %u --scsitype {BusLogic|LsiLogic}\";"
762 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
763 a,
764 aVBoxValues[a],
765 i, a, i, a);
766 }
767 break;
768
769 case VirtualSystemDescriptionType_HardDiskImage:
770 if (fIgnoreThis)
771 {
772 RTPrintf("%2u: Hard disk image: source image=%ls -- disabled\n",
773 a,
774 aOvfValues[a]);
775 aEnabled[a] = false;
776 }
777 else
778 {
779 Utf8StrFmt strTypeArg("disk%u", a);
780 RTCList<ImportOptions_T> optionsList = options.toList();
781
782 bstrFinalValue = aVBoxValues[a];
783
784 if (findArgValue(strOverride, pmapArgs, strTypeArg))
785 {
786 if (!optionsList.contains(ImportOptions_ImportToVDI))
787 {
788 RTUUID uuid;
789 /* Check if this is a uuid. If so, don't touch. */
790 int vrc = RTUuidFromStr(&uuid, strOverride.c_str());
791 if (vrc != VINF_SUCCESS)
792 {
793 /* Make the path absolute. */
794 if (!RTPathStartsWithRoot(strOverride.c_str()))
795 {
796 char pszPwd[RTPATH_MAX];
797 vrc = RTPathGetCurrent(pszPwd, RTPATH_MAX);
798 if (RT_SUCCESS(vrc))
799 strOverride = Utf8Str(pszPwd).append(RTPATH_SLASH).append(strOverride);
800 }
801 }
802 bstrFinalValue = strOverride;
803 }
804 else
805 {
806 //print some error about incompatible command-line arguments
807 return errorSyntax(USAGE_IMPORTAPPLIANCE,
808 "Option --ImportToVDI shall not be used together with "
809 "manually set target path.");
810
811 }
812
813 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
814 a,
815 aOvfValues[a],
816 bstrFinalValue.raw(),
817 aExtraConfigValues[a]);
818 }
819#if 0 /* Changing the controller is fully valid, but the current design on how
820 the params are evaluated here doesn't allow two parameter for one
821 unit. The target disk path is more important I leave it for future
822 improvments. */
823 Utf8StrFmt strTypeArg("controller%u", a);
824 if (findArgValue(strOverride, pmapArgs, strTypeArg))
825 {
826 // strOverride now has the controller index as a number, but we
827 // need a "controller=X" format string
828 strOverride = Utf8StrFmt("controller=%s", strOverride.c_str());
829 Bstr bstrExtraConfigValue = strOverride;
830 bstrExtraConfigValue.detachTo(&aExtraConfigValues[a]);
831 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n",
832 a,
833 aOvfValues[a],
834 aVBoxValues[a],
835 aExtraConfigValues[a]);
836 }
837#endif
838 else
839 {
840 strOverride = aVBoxValues[a];
841
842 /*
843 * Current solution isn't optimal.
844 * Better way is to provide API call for function
845 * Appliance::i_findMediumFormatFromDiskImage()
846 * and creating one new function which returns
847 * struct ovf::DiskImage for currently processed disk.
848 */
849
850 /*
851 * if user wants to convert all imported disks to VDI format
852 * we need to replace files extensions to "vdi"
853 * except CD/DVD disks
854 */
855 if (optionsList.contains(ImportOptions_ImportToVDI))
856 {
857 ComPtr<IVirtualBox> pVirtualBox = arg->virtualBox;
858 ComPtr<ISystemProperties> systemProperties;
859 com::SafeIfaceArray<IMediumFormat> mediumFormats;
860 Bstr bstrFormatName;
861
862 CHECK_ERROR(pVirtualBox,
863 COMGETTER(SystemProperties)(systemProperties.asOutParam()));
864
865 CHECK_ERROR(systemProperties,
866 COMGETTER(MediumFormats)(ComSafeArrayAsOutParam(mediumFormats)));
867
868 /* go through all supported media formats and store files extensions only for RAW */
869 com::SafeArray<BSTR> extensions;
870
871 for (unsigned j = 0; j < mediumFormats.size(); ++j)
872 {
873 com::SafeArray<DeviceType_T> deviceType;
874 ComPtr<IMediumFormat> mediumFormat = mediumFormats[j];
875 CHECK_ERROR(mediumFormat, COMGETTER(Name)(bstrFormatName.asOutParam()));
876 Utf8Str strFormatName = Utf8Str(bstrFormatName);
877
878 if (strFormatName.compare("RAW", Utf8Str::CaseInsensitive) == 0)
879 {
880 /* getting files extensions for "RAW" format */
881 CHECK_ERROR(mediumFormat,
882 DescribeFileExtensions(ComSafeArrayAsOutParam(extensions),
883 ComSafeArrayAsOutParam(deviceType)));
884 break;
885 }
886 }
887
888 /* go through files extensions for RAW format and compare them with
889 * extension of current file
890 */
891 bool fReplace = true;
892
893 const char *pszExtension = RTPathSuffix(strOverride.c_str());
894 if (pszExtension)
895 pszExtension++;
896
897 for (unsigned j = 0; j < extensions.size(); ++j)
898 {
899 Bstr bstrExt(extensions[j]);
900 Utf8Str strExtension(bstrExt);
901 if(strExtension.compare(pszExtension, Utf8Str::CaseInsensitive) == 0)
902 {
903 fReplace = false;
904 break;
905 }
906 }
907
908 if (fReplace)
909 {
910 strOverride = strOverride.stripSuffix();
911 strOverride = strOverride.append(".").append("vdi");
912 }
913 }
914
915 bstrFinalValue = strOverride;
916
917 RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls"
918 "\n (change target path with \"--vsys %u --unit %u --disk path\";"
919 "\n disable with \"--vsys %u --unit %u --ignore\")\n",
920 a,
921 aOvfValues[a],
922 bstrFinalValue.raw(),
923 aExtraConfigValues[a],
924 i, a, i, a);
925 }
926 }
927 break;
928
929 case VirtualSystemDescriptionType_CDROM:
930 if (fIgnoreThis)
931 {
932 RTPrintf("%2u: CD-ROM -- disabled\n",
933 a);
934 aEnabled[a] = false;
935 }
936 else
937 RTPrintf("%2u: CD-ROM"
938 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
939 a, i, a);
940 break;
941
942 case VirtualSystemDescriptionType_Floppy:
943 if (fIgnoreThis)
944 {
945 RTPrintf("%2u: Floppy -- disabled\n",
946 a);
947 aEnabled[a] = false;
948 }
949 else
950 RTPrintf("%2u: Floppy"
951 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
952 a, i, a);
953 break;
954
955 case VirtualSystemDescriptionType_NetworkAdapter:
956 RTPrintf("%2u: Network adapter: orig %ls, config %ls, extra %ls\n", /// @todo implement once we have a plan for the back-end
957 a,
958 aOvfValues[a],
959 aVBoxValues[a],
960 aExtraConfigValues[a]);
961 break;
962
963 case VirtualSystemDescriptionType_USBController:
964 if (fIgnoreThis)
965 {
966 RTPrintf("%2u: USB controller -- disabled\n",
967 a);
968 aEnabled[a] = false;
969 }
970 else
971 RTPrintf("%2u: USB controller"
972 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
973 a, i, a);
974 break;
975
976 case VirtualSystemDescriptionType_SoundCard:
977 if (fIgnoreThis)
978 {
979 RTPrintf("%2u: Sound card \"%ls\" -- disabled\n",
980 a,
981 aOvfValues[a]);
982 aEnabled[a] = false;
983 }
984 else
985 RTPrintf("%2u: Sound card (appliance expects \"%ls\", can change on import)"
986 "\n (disable with \"--vsys %u --unit %u --ignore\")\n",
987 a,
988 aOvfValues[a],
989 i,
990 a);
991 break;
992
993 case VirtualSystemDescriptionType_SettingsFile:
994 if (findArgValue(strOverride, pmapArgs, "settingsfile"))
995 {
996 bstrFinalValue = strOverride;
997 RTPrintf("%2u: VM settings file name specified with --settingsfile: \"%ls\"\n",
998 a, bstrFinalValue.raw());
999 }
1000 else
1001 RTPrintf("%2u: Suggested VM settings file name \"%ls\""
1002 "\n (change with \"--vsys %u --settingsfile <filename>\")\n",
1003 a, bstrFinalValue.raw(), i);
1004 break;
1005
1006 case VirtualSystemDescriptionType_BaseFolder:
1007 if (findArgValue(strOverride, pmapArgs, "basefolder"))
1008 {
1009 bstrFinalValue = strOverride;
1010 RTPrintf("%2u: VM base folder specified with --basefolder: \"%ls\"\n",
1011 a, bstrFinalValue.raw());
1012 }
1013 else
1014 RTPrintf("%2u: Suggested VM base folder \"%ls\""
1015 "\n (change with \"--vsys %u --basefolder <path>\")\n",
1016 a, bstrFinalValue.raw(), i);
1017 break;
1018
1019 case VirtualSystemDescriptionType_PrimaryGroup:
1020 if (findArgValue(strOverride, pmapArgs, "group"))
1021 {
1022 bstrFinalValue = strOverride;
1023 RTPrintf("%2u: VM group specified with --group: \"%ls\"\n",
1024 a, bstrFinalValue.raw());
1025 }
1026 else
1027 RTPrintf("%2u: Suggested VM group \"%ls\""
1028 "\n (change with \"--vsys %u --group <group>\")\n",
1029 a, bstrFinalValue.raw(), i);
1030 break;
1031
1032 case VirtualSystemDescriptionType_CloudInstanceShape:
1033 RTPrintf("%2u: Suggested cloud shape \"%ls\"\n",
1034 a, bstrFinalValue.raw());
1035 break;
1036
1037 case VirtualSystemDescriptionType_CloudBucket:
1038 if (findArgValue(strOverride, pmapArgs, "cloudbucket"))
1039 {
1040 bstrFinalValue = strOverride;
1041 RTPrintf("%2u: Cloud bucket id specified with --cloudbucket: \"%ls\"\n",
1042 a, bstrFinalValue.raw());
1043 }
1044 else
1045 RTPrintf("%2u: Suggested cloud bucket id \"%ls\""
1046 "\n (change with \"--cloud %u --cloudbucket <id>\")\n",
1047 a, bstrFinalValue.raw(), i);
1048 break;
1049
1050 case VirtualSystemDescriptionType_CloudProfileName:
1051 if (findArgValue(strOverride, pmapArgs, "cloudprofile"))
1052 {
1053 bstrFinalValue = strOverride;
1054 RTPrintf("%2u: Cloud profile name specified with --cloudprofile: \"%ls\"\n",
1055 a, bstrFinalValue.raw());
1056 }
1057 else
1058 RTPrintf("%2u: Suggested cloud profile name \"%ls\""
1059 "\n (change with \"--cloud %u --cloudprofile <id>\")\n",
1060 a, bstrFinalValue.raw(), i);
1061 break;
1062
1063 case VirtualSystemDescriptionType_CloudInstanceId:
1064 if (findArgValue(strOverride, pmapArgs, "cloudinstanceid"))
1065 {
1066 bstrFinalValue = strOverride;
1067 RTPrintf("%2u: Cloud instance id specified with --cloudinstanceid: \"%ls\"\n",
1068 a, bstrFinalValue.raw());
1069 }
1070 else
1071 RTPrintf("%2u: Suggested cloud instance id \"%ls\""
1072 "\n (change with \"--cloud %u --cloudinstanceid <id>\")\n",
1073 a, bstrFinalValue.raw(), i);
1074 break;
1075
1076 case VirtualSystemDescriptionType_CloudImageId:
1077 RTPrintf("%2u: Suggested cloud base image id \"%ls\"\n",
1078 a, bstrFinalValue.raw());
1079 break;
1080 case VirtualSystemDescriptionType_CloudDomain:
1081 case VirtualSystemDescriptionType_CloudBootDiskSize:
1082 case VirtualSystemDescriptionType_CloudOCIVCN:
1083 case VirtualSystemDescriptionType_CloudPublicIP:
1084 case VirtualSystemDescriptionType_CloudOCISubnet:
1085 case VirtualSystemDescriptionType_CloudKeepObject:
1086 case VirtualSystemDescriptionType_CloudLaunchInstance:
1087 case VirtualSystemDescriptionType_CloudInstanceState:
1088 case VirtualSystemDescriptionType_CloudImageState:
1089 case VirtualSystemDescriptionType_Miscellaneous:
1090 case VirtualSystemDescriptionType_CloudInstanceDisplayName:
1091 case VirtualSystemDescriptionType_CloudImageDisplayName:
1092 case VirtualSystemDescriptionType_CloudOCILaunchMode:
1093 case VirtualSystemDescriptionType_CloudPrivateIP:
1094 case VirtualSystemDescriptionType_CloudBootVolumeId:
1095 case VirtualSystemDescriptionType_CloudOCIVCNCompartment:
1096 case VirtualSystemDescriptionType_CloudOCISubnetCompartment:
1097 case VirtualSystemDescriptionType_CloudPublicSSHKey:
1098 case VirtualSystemDescriptionType_BootingFirmware:
1099 /** @todo VirtualSystemDescriptionType_Miscellaneous? */
1100 break;
1101
1102 case VirtualSystemDescriptionType_Ignore:
1103#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
1104 case VirtualSystemDescriptionType_32BitHack:
1105#endif
1106 break;
1107 }
1108
1109 bstrFinalValue.detachTo(&aFinalValues[a]);
1110 }
1111
1112 if (fExecute)
1113 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
1114 SetFinalValues(ComSafeArrayAsInParam(aEnabled),
1115 ComSafeArrayAsInParam(aFinalValues),
1116 ComSafeArrayAsInParam(aExtraConfigValues)));
1117
1118 } // for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
1119
1120 if (cLicensesInTheWay == 1)
1121 RTMsgError("Cannot import until the license agreement listed above is accepted.");
1122 else if (cLicensesInTheWay > 1)
1123 RTMsgError("Cannot import until the %c license agreements listed above are accepted.", cLicensesInTheWay);
1124
1125 if (!cLicensesInTheWay && fExecute)
1126 {
1127 // go!
1128 ComPtr<IProgress> progress;
1129 CHECK_ERROR_BREAK(pAppliance,
1130 ImportMachines(ComSafeArrayAsInParam(options), progress.asOutParam()));
1131
1132 rc = showProgress(progress);
1133 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance import failed"), RTEXITCODE_FAILURE);
1134
1135 if (SUCCEEDED(rc))
1136 RTPrintf("Successfully imported the appliance.\n");
1137 }
1138 } // end if (aVirtualSystemDescriptions.size() > 0)
1139 } while (0);
1140
1141 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1142}
1143
1144static int parseExportOptions(const char *psz, com::SafeArray<ExportOptions_T> *options)
1145{
1146 int rc = VINF_SUCCESS;
1147 while (psz && *psz && RT_SUCCESS(rc))
1148 {
1149 size_t len;
1150 const char *pszComma = strchr(psz, ',');
1151 if (pszComma)
1152 len = pszComma - psz;
1153 else
1154 len = strlen(psz);
1155 if (len > 0)
1156 {
1157 if (!RTStrNICmp(psz, "CreateManifest", len))
1158 options->push_back(ExportOptions_CreateManifest);
1159 else if (!RTStrNICmp(psz, "manifest", len))
1160 options->push_back(ExportOptions_CreateManifest);
1161 else if (!RTStrNICmp(psz, "ExportDVDImages", len))
1162 options->push_back(ExportOptions_ExportDVDImages);
1163 else if (!RTStrNICmp(psz, "iso", len))
1164 options->push_back(ExportOptions_ExportDVDImages);
1165 else if (!RTStrNICmp(psz, "StripAllMACs", len))
1166 options->push_back(ExportOptions_StripAllMACs);
1167 else if (!RTStrNICmp(psz, "nomacs", len))
1168 options->push_back(ExportOptions_StripAllMACs);
1169 else if (!RTStrNICmp(psz, "StripAllNonNATMACs", len))
1170 options->push_back(ExportOptions_StripAllNonNATMACs);
1171 else if (!RTStrNICmp(psz, "nomacsbutnat", len))
1172 options->push_back(ExportOptions_StripAllNonNATMACs);
1173 else
1174 rc = VERR_PARSE_ERROR;
1175 }
1176 if (pszComma)
1177 psz += len + 1;
1178 else
1179 psz += len;
1180 }
1181
1182 return rc;
1183}
1184
1185static const RTGETOPTDEF g_aExportOptions[] =
1186{
1187 { "--output", 'o', RTGETOPT_REQ_STRING },
1188 { "--legacy09", 'l', RTGETOPT_REQ_NOTHING },
1189 { "--ovf09", 'l', RTGETOPT_REQ_NOTHING },
1190 { "--ovf10", '1', RTGETOPT_REQ_NOTHING },
1191 { "--ovf20", '2', RTGETOPT_REQ_NOTHING },
1192 { "--opc10", 'c', RTGETOPT_REQ_NOTHING },
1193 { "--manifest", 'm', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1194 { "--iso", 'I', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1195 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
1196 { "--vmname", 'V', RTGETOPT_REQ_STRING },
1197 { "--product", 'p', RTGETOPT_REQ_STRING },
1198 { "--producturl", 'P', RTGETOPT_REQ_STRING },
1199 { "--vendor", 'n', RTGETOPT_REQ_STRING },
1200 { "--vendorurl", 'N', RTGETOPT_REQ_STRING },
1201 { "--version", 'v', RTGETOPT_REQ_STRING },
1202 { "--description", 'd', RTGETOPT_REQ_STRING },
1203 { "--eula", 'e', RTGETOPT_REQ_STRING },
1204 { "--eulafile", 'E', RTGETOPT_REQ_STRING },
1205 { "--options", 'O', RTGETOPT_REQ_STRING },
1206 { "--cloud", 'C', RTGETOPT_REQ_UINT32 },
1207 { "--cloudshape", 'S', RTGETOPT_REQ_STRING },
1208 { "--clouddomain", 'D', RTGETOPT_REQ_STRING },
1209 { "--clouddisksize", 'R', RTGETOPT_REQ_STRING },
1210 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING },
1211 { "--cloudocivcn", 'Q', RTGETOPT_REQ_STRING },
1212 { "--cloudpublicip", 'A', RTGETOPT_REQ_STRING },
1213 { "--cloudprofile", 'F', RTGETOPT_REQ_STRING },
1214 { "--cloudocisubnet", 'T', RTGETOPT_REQ_STRING },
1215 { "--cloudkeepobject", 'K', RTGETOPT_REQ_STRING },
1216 { "--cloudlaunchinstance", 'L', RTGETOPT_REQ_STRING },
1217 { "--cloudlaunchmode", 'M', RTGETOPT_REQ_STRING },
1218 { "--cloudprivateip", 'i', RTGETOPT_REQ_STRING },
1219};
1220
1221RTEXITCODE handleExportAppliance(HandlerArg *a)
1222{
1223 HRESULT rc = S_OK;
1224
1225 Utf8Str strOutputFile;
1226 Utf8Str strOvfFormat("ovf-1.0"); // the default export version
1227 bool fManifest = false; // the default
1228 bool fCloud = false; // the default
1229 actionType = NOT_SET;
1230 bool fExportISOImages = false; // the default
1231 com::SafeArray<ExportOptions_T> options;
1232 std::list< ComPtr<IMachine> > llMachines;
1233
1234 uint32_t ulCurVsys = (uint32_t)-1;
1235 // for each --vsys X command, maintain a map of command line items
1236 ArgsMapsMap mapArgsMapsPerVsys;
1237 do
1238 {
1239 int c;
1240
1241 RTGETOPTUNION ValueUnion;
1242 RTGETOPTSTATE GetState;
1243 // start at 0 because main() has hacked both the argc and argv given to us
1244 RTGetOptInit(&GetState, a->argc, a->argv, g_aExportOptions,
1245 RT_ELEMENTS(g_aExportOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
1246
1247 Utf8Str strProductUrl;
1248 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1249 {
1250 switch (c)
1251 {
1252 case 'o': // --output
1253 if (strOutputFile.length())
1254 return errorSyntax(USAGE_EXPORTAPPLIANCE, "You can only specify --output once.");
1255 else
1256 strOutputFile = ValueUnion.psz;
1257 break;
1258
1259 case 'l': // --legacy09/--ovf09
1260 strOvfFormat = "ovf-0.9";
1261 break;
1262
1263 case '1': // --ovf10
1264 strOvfFormat = "ovf-1.0";
1265 break;
1266
1267 case '2': // --ovf20
1268 strOvfFormat = "ovf-2.0";
1269 break;
1270
1271 case 'c': // --opc
1272 strOvfFormat = "opc-1.0";
1273 break;
1274
1275 case 'I': // --iso
1276 fExportISOImages = true;
1277 break;
1278
1279 case 'm': // --manifest
1280 fManifest = true;
1281 break;
1282
1283 case 's': // --vsys
1284 if (fCloud == false && actionType == NOT_SET)
1285 actionType = LOCAL;
1286
1287 if (actionType != LOCAL)
1288 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1289 "Option \"%s\" can't be used together with \"--cloud\" argument.",
1290 GetState.pDef->pszLong);
1291
1292 ulCurVsys = ValueUnion.u32;
1293 break;
1294
1295 case 'V': // --vmname
1296 if (actionType == NOT_SET || ulCurVsys == (uint32_t)-1)
1297 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys or --cloud argument.",
1298 GetState.pDef->pszLong);
1299 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
1300 break;
1301
1302 case 'p': // --product
1303 if (actionType != LOCAL)
1304 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1305 mapArgsMapsPerVsys[ulCurVsys]["product"] = ValueUnion.psz;
1306 break;
1307
1308 case 'P': // --producturl
1309 if (actionType != LOCAL)
1310 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1311 mapArgsMapsPerVsys[ulCurVsys]["producturl"] = ValueUnion.psz;
1312 break;
1313
1314 case 'n': // --vendor
1315 if (actionType != LOCAL)
1316 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1317 mapArgsMapsPerVsys[ulCurVsys]["vendor"] = ValueUnion.psz;
1318 break;
1319
1320 case 'N': // --vendorurl
1321 if (actionType != LOCAL)
1322 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1323 mapArgsMapsPerVsys[ulCurVsys]["vendorurl"] = ValueUnion.psz;
1324 break;
1325
1326 case 'v': // --version
1327 if (actionType != LOCAL)
1328 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1329 mapArgsMapsPerVsys[ulCurVsys]["version"] = ValueUnion.psz;
1330 break;
1331
1332 case 'd': // --description
1333 if (actionType != LOCAL)
1334 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1335 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
1336 break;
1337
1338 case 'e': // --eula
1339 if (actionType != LOCAL)
1340 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1341 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
1342 break;
1343
1344 case 'E': // --eulafile
1345 if (actionType != LOCAL)
1346 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong);
1347 mapArgsMapsPerVsys[ulCurVsys]["eulafile"] = ValueUnion.psz;
1348 break;
1349
1350 case 'O': // --options
1351 if (RT_FAILURE(parseExportOptions(ValueUnion.psz, &options)))
1352 return errorArgument("Invalid export options '%s'\n", ValueUnion.psz);
1353 break;
1354
1355 /*--cloud and --vsys are orthogonal, only one must be presented*/
1356 case 'C': // --cloud
1357 if (fCloud == false && actionType == NOT_SET)
1358 {
1359 fCloud = true;
1360 actionType = CLOUD;
1361 }
1362
1363 if (actionType != CLOUD)
1364 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1365 "Option \"%s\" can't be used together with \"--vsys\" argument.",
1366 GetState.pDef->pszLong);
1367
1368 ulCurVsys = ValueUnion.u32;
1369 break;
1370
1371 /* Cloud export settings */
1372 case 'S': // --cloudshape
1373 if (actionType != CLOUD)
1374 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1375 GetState.pDef->pszLong);
1376 mapArgsMapsPerVsys[ulCurVsys]["cloudshape"] = ValueUnion.psz;
1377 break;
1378
1379 case 'D': // --clouddomain
1380 if (actionType != CLOUD)
1381 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1382 GetState.pDef->pszLong);
1383 mapArgsMapsPerVsys[ulCurVsys]["clouddomain"] = ValueUnion.psz;
1384 break;
1385
1386 case 'R': // --clouddisksize
1387 if (actionType != CLOUD)
1388 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1389 GetState.pDef->pszLong);
1390 mapArgsMapsPerVsys[ulCurVsys]["clouddisksize"] = ValueUnion.psz;
1391 break;
1392
1393 case 'B': // --cloudbucket
1394 if (actionType != CLOUD)
1395 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1396 GetState.pDef->pszLong);
1397 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
1398 break;
1399
1400 case 'Q': // --cloudocivcn
1401 if (actionType != CLOUD)
1402 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1403 GetState.pDef->pszLong);
1404 mapArgsMapsPerVsys[ulCurVsys]["cloudocivcn"] = ValueUnion.psz;
1405 break;
1406
1407 case 'A': // --cloudpublicip
1408 if (actionType != CLOUD)
1409 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1410 GetState.pDef->pszLong);
1411 mapArgsMapsPerVsys[ulCurVsys]["cloudpublicip"] = ValueUnion.psz;
1412 break;
1413
1414 case 'i': /* --cloudprivateip */
1415 if (actionType != CLOUD)
1416 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1417 GetState.pDef->pszLong);
1418 mapArgsMapsPerVsys[ulCurVsys]["cloudprivateip"] = ValueUnion.psz;
1419 break;
1420
1421 case 'F': // --cloudprofile
1422 if (actionType != CLOUD)
1423 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1424 GetState.pDef->pszLong);
1425 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
1426 break;
1427
1428 case 'T': // --cloudocisubnet
1429 if (actionType != CLOUD)
1430 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1431 GetState.pDef->pszLong);
1432 mapArgsMapsPerVsys[ulCurVsys]["cloudocisubnet"] = ValueUnion.psz;
1433 break;
1434
1435 case 'K': // --cloudkeepobject
1436 if (actionType != CLOUD)
1437 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1438 GetState.pDef->pszLong);
1439 mapArgsMapsPerVsys[ulCurVsys]["cloudkeepobject"] = ValueUnion.psz;
1440 break;
1441
1442 case 'L': // --cloudlaunchinstance
1443 if (actionType != CLOUD)
1444 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1445 GetState.pDef->pszLong);
1446 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchinstance"] = ValueUnion.psz;
1447 break;
1448
1449 case 'M': /* --cloudlaunchmode */
1450 if (actionType != CLOUD)
1451 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.",
1452 GetState.pDef->pszLong);
1453 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchmode"] = ValueUnion.psz;
1454 break;
1455
1456 case VINF_GETOPT_NOT_OPTION:
1457 {
1458 Utf8Str strMachine(ValueUnion.psz);
1459 // must be machine: try UUID or name
1460 ComPtr<IMachine> machine;
1461 CHECK_ERROR_BREAK(a->virtualBox, FindMachine(Bstr(strMachine).raw(),
1462 machine.asOutParam()));
1463 if (machine)
1464 llMachines.push_back(machine);
1465 break;
1466 }
1467
1468 default:
1469 if (c > 0)
1470 {
1471 if (RT_C_IS_GRAPH(c))
1472 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: -%c", c);
1473 else
1474 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: %i", c);
1475 }
1476 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
1477 return errorSyntax(USAGE_EXPORTAPPLIANCE, "unknown option: %s", ValueUnion.psz);
1478 else if (ValueUnion.pDef)
1479 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
1480 else
1481 return errorSyntax(USAGE_EXPORTAPPLIANCE, "%Rrs", c);
1482 }
1483
1484 if (FAILED(rc))
1485 break;
1486 }
1487
1488 if (FAILED(rc))
1489 break;
1490
1491 if (llMachines.empty())
1492 return errorSyntax(USAGE_EXPORTAPPLIANCE, "At least one machine must be specified with the export command.");
1493
1494 /* Last check after parsing all arguments */
1495 if (strOutputFile.isNotEmpty())
1496 {
1497 if (actionType == NOT_SET)
1498 {
1499 if (fCloud)
1500 actionType = CLOUD;
1501 else
1502 actionType = LOCAL;
1503 }
1504 }
1505 else
1506 return errorSyntax(USAGE_EXPORTAPPLIANCE, "Missing --output argument with export command.");
1507
1508 // match command line arguments with the machines count
1509 // this is only to sort out invalid indices at this time
1510 ArgsMapsMap::const_iterator it;
1511 for (it = mapArgsMapsPerVsys.begin();
1512 it != mapArgsMapsPerVsys.end();
1513 ++it)
1514 {
1515 uint32_t ulVsys = it->first;
1516 if (ulVsys >= llMachines.size())
1517 return errorSyntax(USAGE_EXPORTAPPLIANCE,
1518 "Invalid index %RI32 with -vsys option; you specified only %zu virtual system(s).",
1519 ulVsys, llMachines.size());
1520 }
1521
1522 ComPtr<IAppliance> pAppliance;
1523 CHECK_ERROR_BREAK(a->virtualBox, CreateAppliance(pAppliance.asOutParam()));
1524
1525 char *pszAbsFilePath = 0;
1526 if (strOutputFile.startsWith("S3://", RTCString::CaseInsensitive) ||
1527 strOutputFile.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
1528 strOutputFile.startsWith("webdav://", RTCString::CaseInsensitive) ||
1529 strOutputFile.startsWith("OCI://", RTCString::CaseInsensitive))
1530 pszAbsFilePath = RTStrDup(strOutputFile.c_str());
1531 else
1532 pszAbsFilePath = RTPathAbsDup(strOutputFile.c_str());
1533
1534 /*
1535 * The first stage - export machine/s to the Cloud or into the
1536 * OVA/OVF format on the local host.
1537 */
1538
1539 /* VSDList is needed for the second stage where we launch the cloud instances if it was requested by user */
1540 std::list< ComPtr<IVirtualSystemDescription> > VSDList;
1541 std::list< ComPtr<IMachine> >::iterator itM;
1542 uint32_t i=0;
1543 for (itM = llMachines.begin();
1544 itM != llMachines.end();
1545 ++itM, ++i)
1546 {
1547 ComPtr<IMachine> pMachine = *itM;
1548 ComPtr<IVirtualSystemDescription> pVSD;
1549 CHECK_ERROR_BREAK(pMachine, ExportTo(pAppliance, Bstr(pszAbsFilePath).raw(), pVSD.asOutParam()));
1550
1551 // Add additional info to the virtual system description if the user wants so
1552 ArgsMap *pmapArgs = NULL;
1553 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
1554 if (itm != mapArgsMapsPerVsys.end())
1555 pmapArgs = &itm->second;
1556 if (pmapArgs)
1557 {
1558 ArgsMap::iterator itD;
1559 for (itD = pmapArgs->begin();
1560 itD != pmapArgs->end();
1561 ++itD)
1562 {
1563 if (itD->first == "vmname")
1564 {
1565 //remove default value if user has specified new name (default value is set in the ExportTo())
1566// pVSD->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
1567 pVSD->AddDescription(VirtualSystemDescriptionType_Name,
1568 Bstr(itD->second).raw(), NULL);
1569 }
1570 else if (itD->first == "product")
1571 pVSD->AddDescription(VirtualSystemDescriptionType_Product,
1572 Bstr(itD->second).raw(), NULL);
1573 else if (itD->first == "producturl")
1574 pVSD->AddDescription(VirtualSystemDescriptionType_ProductUrl,
1575 Bstr(itD->second).raw(), NULL);
1576 else if (itD->first == "vendor")
1577 pVSD->AddDescription(VirtualSystemDescriptionType_Vendor,
1578 Bstr(itD->second).raw(), NULL);
1579 else if (itD->first == "vendorurl")
1580 pVSD->AddDescription(VirtualSystemDescriptionType_VendorUrl,
1581 Bstr(itD->second).raw(), NULL);
1582 else if (itD->first == "version")
1583 pVSD->AddDescription(VirtualSystemDescriptionType_Version,
1584 Bstr(itD->second).raw(), NULL);
1585 else if (itD->first == "description")
1586 pVSD->AddDescription(VirtualSystemDescriptionType_Description,
1587 Bstr(itD->second).raw(), NULL);
1588 else if (itD->first == "eula")
1589 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1590 Bstr(itD->second).raw(), NULL);
1591 else if (itD->first == "eulafile")
1592 {
1593 Utf8Str strContent;
1594 void *pvFile;
1595 size_t cbFile;
1596 int irc = RTFileReadAll(itD->second.c_str(), &pvFile, &cbFile);
1597 if (RT_SUCCESS(irc))
1598 {
1599 Bstr bstrContent((char*)pvFile, cbFile);
1600 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1601 bstrContent.raw(), NULL);
1602 RTFileReadAllFree(pvFile, cbFile);
1603 }
1604 else
1605 {
1606 RTMsgError("Cannot read license file \"%s\" which should be included in the virtual system %u.",
1607 itD->second.c_str(), i);
1608 return RTEXITCODE_FAILURE;
1609 }
1610 }
1611 /* add cloud export settings */
1612 else if (itD->first == "cloudshape")
1613 pVSD->AddDescription(VirtualSystemDescriptionType_CloudInstanceShape,
1614 Bstr(itD->second).raw(), NULL);
1615 else if (itD->first == "clouddomain")
1616 pVSD->AddDescription(VirtualSystemDescriptionType_CloudDomain,
1617 Bstr(itD->second).raw(), NULL);
1618 else if (itD->first == "clouddisksize")
1619 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBootDiskSize,
1620 Bstr(itD->second).raw(), NULL);
1621 else if (itD->first == "cloudbucket")
1622 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBucket,
1623 Bstr(itD->second).raw(), NULL);
1624 else if (itD->first == "cloudocivcn")
1625 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCIVCN,
1626 Bstr(itD->second).raw(), NULL);
1627 else if (itD->first == "cloudpublicip")
1628 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPublicIP,
1629 Bstr(itD->second).raw(), NULL);
1630 else if (itD->first == "cloudprivateip")
1631 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPrivateIP,
1632 Bstr(itD->second).raw(), NULL);
1633 else if (itD->first == "cloudprofile")
1634 pVSD->AddDescription(VirtualSystemDescriptionType_CloudProfileName,
1635 Bstr(itD->second).raw(), NULL);
1636 else if (itD->first == "cloudocisubnet")
1637 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCISubnet,
1638 Bstr(itD->second).raw(), NULL);
1639 else if (itD->first == "cloudkeepobject")
1640 pVSD->AddDescription(VirtualSystemDescriptionType_CloudKeepObject,
1641 Bstr(itD->second).raw(), NULL);
1642 else if (itD->first == "cloudlaunchmode")
1643 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCILaunchMode,
1644 Bstr(itD->second).raw(), NULL);
1645 else if (itD->first == "cloudlaunchinstance")
1646 pVSD->AddDescription(VirtualSystemDescriptionType_CloudLaunchInstance,
1647 Bstr(itD->second).raw(), NULL);
1648 }
1649 }
1650
1651 VSDList.push_back(pVSD);//store vsd for the possible second stage
1652 }
1653
1654 if (FAILED(rc))
1655 break;
1656
1657 /* Query required passwords and supply them to the appliance. */
1658 com::SafeArray<BSTR> aIdentifiers;
1659
1660 CHECK_ERROR_BREAK(pAppliance, GetPasswordIds(ComSafeArrayAsOutParam(aIdentifiers)));
1661
1662 if (aIdentifiers.size() > 0)
1663 {
1664 com::SafeArray<BSTR> aPasswords(aIdentifiers.size());
1665 RTPrintf("Enter the passwords for the following identifiers to export the apppliance:\n");
1666 for (unsigned idxId = 0; idxId < aIdentifiers.size(); idxId++)
1667 {
1668 com::Utf8Str strPassword;
1669 Bstr bstrPassword;
1670 Bstr bstrId = aIdentifiers[idxId];
1671
1672 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Password ID %s:", Utf8Str(bstrId).c_str());
1673 if (rcExit == RTEXITCODE_FAILURE)
1674 {
1675 RTStrFree(pszAbsFilePath);
1676 return rcExit;
1677 }
1678
1679 bstrPassword = strPassword;
1680 bstrPassword.detachTo(&aPasswords[idxId]);
1681 }
1682
1683 CHECK_ERROR_BREAK(pAppliance, AddPasswords(ComSafeArrayAsInParam(aIdentifiers),
1684 ComSafeArrayAsInParam(aPasswords)));
1685 }
1686
1687 if (fManifest)
1688 options.push_back(ExportOptions_CreateManifest);
1689
1690 if (fExportISOImages)
1691 options.push_back(ExportOptions_ExportDVDImages);
1692
1693 ComPtr<IProgress> progress;
1694 CHECK_ERROR_BREAK(pAppliance, Write(Bstr(strOvfFormat).raw(),
1695 ComSafeArrayAsInParam(options),
1696 Bstr(pszAbsFilePath).raw(),
1697 progress.asOutParam()));
1698 RTStrFree(pszAbsFilePath);
1699
1700 rc = showProgress(progress);
1701 CHECK_PROGRESS_ERROR_RET(progress, ("Appliance write failed"), RTEXITCODE_FAILURE);
1702
1703 if (SUCCEEDED(rc))
1704 RTPrintf("Successfully exported %d machine(s).\n", llMachines.size());
1705
1706 /*
1707 * The second stage for the cloud case
1708 */
1709 if (actionType == CLOUD)
1710 {
1711 /* Launch the exported VM if the appropriate flag had been set on the first stage */
1712 for (std::list< ComPtr<IVirtualSystemDescription> >::iterator itVSD = VSDList.begin();
1713 itVSD != VSDList.end();
1714 ++itVSD)
1715 {
1716 ComPtr<IVirtualSystemDescription> pVSD = *itVSD;
1717
1718 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
1719 com::SafeArray<BSTR> aRefs;
1720 com::SafeArray<BSTR> aOvfValues;
1721 com::SafeArray<BSTR> aVBoxValues;
1722 com::SafeArray<BSTR> aExtraConfigValues;
1723
1724 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudLaunchInstance,
1725 ComSafeArrayAsOutParam(retTypes),
1726 ComSafeArrayAsOutParam(aRefs),
1727 ComSafeArrayAsOutParam(aOvfValues),
1728 ComSafeArrayAsOutParam(aVBoxValues),
1729 ComSafeArrayAsOutParam(aExtraConfigValues)));
1730
1731 Utf8Str flagCloudLaunchInstance(Bstr(aVBoxValues[0]).raw());
1732 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1733
1734 if (flagCloudLaunchInstance.equals("true"))
1735 {
1736 /* Getting the short provider name */
1737 Bstr bstrCloudProviderShortName(strOutputFile.c_str(), strOutputFile.find("://"));
1738
1739 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
1740 ComPtr<ICloudProviderManager> pCloudProviderManager;
1741 CHECK_ERROR_BREAK(pVirtualBox, COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()));
1742
1743 ComPtr<ICloudProvider> pCloudProvider;
1744 CHECK_ERROR_BREAK(pCloudProviderManager,
1745 GetProviderByShortName(bstrCloudProviderShortName.raw(), pCloudProvider.asOutParam()));
1746
1747 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudProfileName,
1748 ComSafeArrayAsOutParam(retTypes),
1749 ComSafeArrayAsOutParam(aRefs),
1750 ComSafeArrayAsOutParam(aOvfValues),
1751 ComSafeArrayAsOutParam(aVBoxValues),
1752 ComSafeArrayAsOutParam(aExtraConfigValues)));
1753
1754 ComPtr<ICloudProfile> pCloudProfile;
1755 CHECK_ERROR_BREAK(pCloudProvider, GetProfileByName(Bstr(aVBoxValues[0]).raw(), pCloudProfile.asOutParam()));
1756 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1757
1758 ComObjPtr<ICloudClient> oCloudClient;
1759 CHECK_ERROR_BREAK(pCloudProfile, CreateCloudClient(oCloudClient.asOutParam()));
1760 RTPrintf("Creating a cloud instance...\n");
1761
1762 ComPtr<IProgress> progress1;
1763 CHECK_ERROR_BREAK(oCloudClient, LaunchVM(pVSD, progress1.asOutParam()));
1764 rc = showProgress(progress1);
1765 CHECK_PROGRESS_ERROR_RET(progress1, ("Creating the cloud instance failed"), RTEXITCODE_FAILURE);
1766
1767 if (SUCCEEDED(rc))
1768 {
1769 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudInstanceId,
1770 ComSafeArrayAsOutParam(retTypes),
1771 ComSafeArrayAsOutParam(aRefs),
1772 ComSafeArrayAsOutParam(aOvfValues),
1773 ComSafeArrayAsOutParam(aVBoxValues),
1774 ComSafeArrayAsOutParam(aExtraConfigValues)));
1775
1776 RTPrintf("A cloud instance with id '%s' (provider '%s') was created\n",
1777 Utf8Str(Bstr(aVBoxValues[0]).raw()).c_str(),
1778 Utf8Str(bstrCloudProviderShortName.raw()).c_str());
1779 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
1780 }
1781 }
1782 }
1783 }
1784 } while (0);
1785
1786 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1787}
1788
1789
1790RTEXITCODE handleSignAppliance(HandlerArg *arg)
1791{
1792 HRESULT hrc = S_OK;
1793
1794 static const RTGETOPTDEF s_aOptions[] =
1795 {
1796 { "--private-key-form", 'k', RTGETOPT_REQ_STRING },
1797 { "--private-key-password", 'p', RTGETOPT_REQ_STRING },
1798 { "--private-key-password-file",'f', RTGETOPT_REQ_STRING },
1799 { "--cert-file", 'c', RTGETOPT_REQ_STRING },
1800 { "--intermediate-cert-file", 'i', RTGETOPT_REQ_STRING },
1801 { "--out-cert", 'o', RTGETOPT_REQ_NOTHING },
1802 { "--pkcs7", 's', RTGETOPT_REQ_NOTHING },
1803 { "--no-pkcs7", 'S', RTGETOPT_REQ_NOTHING },
1804 { "--force", 'F', RTGETOPT_REQ_NOTHING },
1805 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
1806 { "help", 1001, RTGETOPT_REQ_NOTHING },
1807 { "--help", 1002, RTGETOPT_REQ_NOTHING }
1808 };
1809
1810 RTGETOPTSTATE GetState;
1811 RTGETOPTUNION ValueUnion;
1812
1813 int rc = RTGetOptInit(&GetState, arg->argc, arg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
1814 AssertRCReturn(rc, RTEXITCODE_FAILURE);
1815 if (arg->argc == 1)
1816 {
1817 RTPrintf("Empty command parameter list, show help.\n");
1818 printHelp(g_pStdOut);
1819 return RTEXITCODE_SUCCESS;
1820 }
1821
1822 Utf8Str strOvfFilename;
1823 Utf8Str strPrivateKeyForm;
1824 Utf8Str strPrivateKeyPassword;
1825 Utf8Str strPrivateKeyPasswordFile;
1826 Utf8Str strX509CertificateFile;
1827 Utf8Str strInterimCertificateFile;
1828 bool fOutCert = false; // the default
1829 bool fPKCS7 = false; // the default
1830 bool fResign = false; // the default
1831 bool fDry = false; // the default
1832 com::SafeArray<BSTR> parameters;
1833
1834 int c;
1835 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
1836 {
1837 switch (c)
1838 {
1839 case 'k':
1840 strPrivateKeyForm=ValueUnion.psz;
1841 break;
1842
1843 case 'p':
1844 strPrivateKeyPassword=ValueUnion.psz;
1845 break;
1846
1847 case 'f':
1848 strPrivateKeyPasswordFile=ValueUnion.psz;
1849 break;
1850
1851 case 'c':
1852 strX509CertificateFile=ValueUnion.psz;
1853 break;
1854
1855 case 'i':
1856 strInterimCertificateFile=ValueUnion.psz;
1857 break;
1858
1859 case 'o':
1860 fOutCert = true;
1861 break;
1862
1863 case 's':
1864 fPKCS7 = true;
1865 break;
1866
1867 case 'F':
1868 fResign = true;
1869 break;
1870
1871 case 'd':
1872 fDry = true;
1873 break;
1874
1875 case 1001:
1876 case 1002:
1877 printHelp(g_pStdOut);
1878 return RTEXITCODE_SUCCESS;
1879
1880 case VINF_GETOPT_NOT_OPTION:
1881 if (strOvfFilename.isEmpty())
1882 strOvfFilename = ValueUnion.psz;
1883 else
1884 return errorGetOpt(c, &ValueUnion);
1885 break;
1886
1887 default:
1888 return errorGetOpt(c, &ValueUnion);
1889 }
1890 }
1891
1892 Utf8Str strManifestData;
1893 Utf8Str strManifestName;
1894 Utf8Str strAppliancePath;
1895 Utf8Str strApplianceFullPath;
1896
1897 do
1898 {
1899 ComPtr<IAppliance> pAppliance;
1900 CHECK_ERROR_BREAK(arg->virtualBox, CreateAppliance(pAppliance.asOutParam()));
1901
1902 char *pszAbsFilePath = RTPathAbsDup(strOvfFilename.c_str());
1903
1904 ComPtr<IProgress> progressRead;
1905 CHECK_ERROR_BREAK(pAppliance, Read(Bstr(pszAbsFilePath).raw(), progressRead.asOutParam()));
1906 RTStrFree(pszAbsFilePath);
1907
1908 hrc = showProgress(progressRead);
1909 CHECK_PROGRESS_ERROR_RET(progressRead, ("Appliance read failed"), RTEXITCODE_FAILURE);
1910
1911 /* fetch the path, there is stuff like username/password removed if any */
1912 Bstr path;
1913 CHECK_ERROR_BREAK(pAppliance, COMGETTER(Path)(path.asOutParam()));
1914
1915 strAppliancePath = path;
1916 strApplianceFullPath = strAppliancePath;
1917 strAppliancePath.stripFilename();
1918
1919 RTPrintf("The original OVA package folder is %s\n\n", strAppliancePath.c_str());
1920
1921 /* fetch the manifest */
1922 Bstr manifest;
1923 Bstr manifestName;
1924 CHECK_ERROR_BREAK(pAppliance, GetManifest(manifest.asOutParam(), manifestName.asOutParam()));
1925
1926 strManifestData = manifest;
1927 strManifestName = manifestName;
1928
1929 if (strManifestName.isEmpty() || strManifestData.isEmpty())
1930 {
1931 RTPrintf("Manifest file wasn't found in the OVA package %s\n\n", strApplianceFullPath.c_str());
1932 hrc = E_FAIL;
1933 }
1934
1935 }while (0);
1936
1937 if (FAILED(hrc))
1938 return RTEXITCODE_FAILURE;
1939
1940
1941 /* Read the private key */
1942 RTCRKEY hPrivateKey;
1943 RTERRINFO ErrInfo;
1944 uint32_t fFlags = 0;
1945
1946 if (strPrivateKeyForm.equalsIgnoreCase("pem"))
1947 fFlags = RTCRPEMREADFILE_F_VALID_MASK;//|RTCRKEYFROM_F_VALID_MASK;
1948
1949 /* check the key file existence */
1950 if (!RTFileExists(strPrivateKeyPasswordFile.c_str()))
1951 return RTMsgErrorExit(RTEXITCODE_FAILURE, "The file %s with a private key wasn't found",
1952 strPrivateKeyPasswordFile.c_str());
1953
1954 rc = RTCrKeyCreateFromFile(&hPrivateKey, 0, strPrivateKeyPasswordFile.c_str(), strPrivateKeyPassword.c_str(), &ErrInfo);
1955 if (RT_SUCCESS(rc))
1956 {
1957 RTPrintf("Reading the private key from %s was done.\n\n", strPrivateKeyPasswordFile.c_str());
1958
1959 /* check the certificate file existence */
1960 if (!RTFileExists(strX509CertificateFile.c_str()))
1961 {
1962 RTCrKeyRelease(hPrivateKey);
1963 return RTMsgErrorExit(RTEXITCODE_FAILURE, "The file %s with a X509 certificate wasn't found",
1964 strX509CertificateFile.c_str());
1965 }
1966
1967 /* Read the certificate */
1968 RTCRX509CERTIFICATE Certificate;
1969 rc = RTCrX509Certificate_ReadFromFile(&Certificate, strX509CertificateFile.c_str(), 0, &g_RTAsn1DefaultAllocator,
1970 &ErrInfo);
1971 if (RT_FAILURE(rc))
1972 {
1973 RTCrKeyRelease(hPrivateKey);
1974 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error reading certificate from %s: %Rrc - %s",
1975 strX509CertificateFile.c_str(), rc, ErrInfo.pszMsg);
1976 }
1977
1978 RTPrintf("Reading the certificate from %s was done.\n\n", strX509CertificateFile.c_str());
1979
1980 /*
1981 * Get the manifest from Appliance and sign it.
1982 * We have the private key, the password, the certificate.
1983 * Also it's needed a digist algorithm SHA1/SHA256/SHA512.
1984 * OVF2.0 standard proposes to use SHA256.
1985 */
1986
1987 RTDIGESTTYPE digestType = RTDIGESTTYPE_SHA256;
1988 RTMANIFEST hManifest;
1989 RTCRDIGEST hDigest;
1990
1991 rc = RTManifestCreate(0 /*fFlags*/, &hManifest);
1992 if (RT_FAILURE(rc))
1993 {
1994 RTCrKeyRelease(hPrivateKey);
1995 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error manifest creation: %Rrc", rc);
1996 }
1997
1998 /* Calc the digest of the manifest using the algorithm found above. */
1999 rc = RTCrDigestCreateByType(&hDigest, digestType);
2000 if (RT_SUCCESS(rc))
2001 {
2002 rc = RTCrDigestUpdate(hDigest, strManifestData.c_str(), strManifestData.length());
2003 if (RT_SUCCESS(rc))
2004 {
2005 uint8_t const * bHash = RTCrDigestGetHash(hDigest);
2006
2007 char szDigest[_4K];
2008 rc = RTSha256ToString(bHash, szDigest, sizeof(szDigest));
2009 RTPrintf("The digest of manifest is:\n%s\n\n", szDigest);
2010
2011 PCRTASN1DYNTYPE pParameters = NULL;
2012
2013 char signatureBuf[_16K];
2014 size_t cbSignature = sizeof(signatureBuf);
2015 rc= RTCrPkixPubKeySignDigest(&Certificate.SignatureAlgorithm.Algorithm,
2016 hPrivateKey,
2017 pParameters,
2018 hDigest,
2019 0,
2020 signatureBuf,
2021 &cbSignature,
2022 &ErrInfo);
2023 if (RT_SUCCESS(rc))
2024 {
2025 char szSignature[_4K];
2026 RTStrPrintHexBytes(szSignature, sizeof(szSignature), signatureBuf, cbSignature, 0 /*fFlags*/);
2027
2028 /* Verify the signature back using the public key information from the certificate */
2029 rc = RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(&Certificate.TbsCertificate.SubjectPublicKeyInfo,
2030 signatureBuf, cbSignature, hDigest, &ErrInfo);
2031 if (RT_FAILURE(rc))
2032 {
2033 /* Dont' forget */
2034 RTCrDigestRelease(hDigest);
2035 RTCrKeyRelease(hPrivateKey);
2036 if (rc == VERR_CR_PKIX_SIGNATURE_MISMATCH)
2037 return RTMsgErrorExit(RTEXITCODE_FAILURE, "The manifest signature does not match");
2038
2039 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error validating the manifest signature (%Rrc, %s)",
2040 rc, ErrInfo.pszMsg);
2041 }
2042 else
2043 RTPrintf("The manifest signature was validated successfully\n\n");
2044
2045 /*
2046 * Preparing the digest signature according to OVF2.0 standard.
2047 * Only SHA1 and SHA256 are supported for now.
2048 */
2049 Utf8Str strDigestSignature;
2050
2051 switch (digestType)
2052 {
2053 case RTDIGESTTYPE_SHA1:
2054 strDigestSignature.append("SHA1");
2055 break;
2056 case RTDIGESTTYPE_SHA256:
2057 default:
2058 strDigestSignature.append("SHA256");
2059 break;
2060 }
2061
2062 strDigestSignature.append('(').append(strManifestName).append(')').append(" = ");
2063 strDigestSignature.append(szSignature);
2064
2065 /*
2066 * Especially add a new line character to avoid placing the certificate on the same line
2067 * with the digest signature.
2068 */
2069 strDigestSignature.append('\n');
2070
2071 RTPrintf("The signed digest is:\n%s\n", strDigestSignature.c_str());
2072
2073 /* Just stop here in the case of dry-run scenario */
2074 if (fDry)
2075 {
2076 /* Dont' forget */
2077 RTCrDigestRelease(hDigest);
2078 RTCrKeyRelease(hPrivateKey);
2079 return RTEXITCODE_SUCCESS;
2080 }
2081
2082 /* Make up the certificate name */
2083 const char *pszSuffix = strrchr(strManifestName.c_str(), '.');
2084 Utf8Str strOVFCertificateName = Utf8Str(strManifestName.c_str(), pszSuffix - strManifestName.c_str());
2085 strOVFCertificateName.append(".cert");
2086
2087 /* Create a memory I/O stream and write the digest signature and the certificate to it. */
2088 RTVFSIOSTREAM hVfsIosOVFCertificate;
2089
2090 rc = RTVfsMemIoStrmCreate(NIL_RTVFSIOSTREAM, _1K, &hVfsIosOVFCertificate);
2091 if (RT_SUCCESS(rc))
2092 {
2093 size_t cbWritten = 0;
2094 rc = RTVfsIoStrmWrite(hVfsIosOVFCertificate, strDigestSignature.c_str(), strDigestSignature.length(),
2095 true /*fBlocking*/, &cbWritten);
2096 if (RT_SUCCESS(rc))
2097 {
2098 Utf8Str strX509CertificateContent;
2099 /* Open and read the passed certificate file as a standard file */
2100 RTVFSFILE hVfsOriginalX509Certificate;
2101 rc = RTVfsFileOpenNormal(strX509CertificateFile.c_str(),
2102 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE,
2103 &hVfsOriginalX509Certificate);
2104 if (RT_SUCCESS(rc))
2105 {
2106 for (;;)
2107 {
2108 char abBuf[_4K];
2109 size_t cbRead;
2110 rc = RTVfsFileRead(hVfsOriginalX509Certificate, abBuf, sizeof(abBuf), &cbRead);
2111 if (RT_SUCCESS(rc))
2112 {
2113 bool const fEof = rc == VINF_EOF;
2114 strX509CertificateContent.append(abBuf, cbRead);
2115 if (fEof)
2116 break;
2117 }
2118 else
2119 break;
2120 }
2121 }
2122 else
2123 RTPrintf("Reading the certificate from the file %s failed (%Rrc)",
2124 strX509CertificateFile.c_str(), rc);
2125
2126 /* Dont' forget */
2127 RTVfsFileRelease(hVfsOriginalX509Certificate);
2128
2129 if (RT_SUCCESS(rc))
2130 {
2131 cbWritten = 0;
2132 /* Write out the certificate into the stream */
2133 rc = RTVfsIoStrmWrite(hVfsIosOVFCertificate, strX509CertificateContent.c_str(),
2134 strX509CertificateContent.length(), true /*fBlocking*/, &cbWritten);
2135
2136 if (RT_FAILURE(rc))
2137 RTPrintf("RTVfsIoStrmWrite failed on adding the certificate into the stream (%Rrc)", rc);
2138 }
2139 else
2140 RTPrintf("Reading the certificate from the file %s failed (%Rrc)",
2141 strX509CertificateFile.c_str(), rc);
2142 }
2143 else
2144 RTPrintf("RTVfsIoStrmWrite failed on adding the digest into the stream (%Rrc)", rc);
2145 }
2146 else
2147 RTPrintf("RTVfsMemIoStrmCreate failed (%Rrc)", rc);
2148
2149 if (RT_FAILURE(rc))
2150 {
2151 /* Dont' forget */
2152 RTVfsIoStrmRelease(hVfsIosOVFCertificate);
2153 return RTEXITCODE_FAILURE;
2154 }
2155
2156
2157 /* Make up new appliance name */
2158 const char *pszSuffix1 = strrchr(strManifestName.c_str(), '.');
2159 Utf8Str strSignedOVAName(strAppliancePath);
2160 strSignedOVAName.append(RTPATH_DELIMITER).append(strManifestName.c_str(),
2161 pszSuffix1 - strManifestName.c_str());
2162 strSignedOVAName.append("-signed.ova");
2163
2164 RTPrintf("The path for new OVA signed package is '%s'\n\n", strSignedOVAName.c_str());
2165
2166 /*
2167 * Open new OVA file (it's a standard TAR file) as a file stream
2168 */
2169 RTVFSIOSTREAM hVfsIosSignedOVAPackage;
2170 rc = RTVfsIoStrmOpenNormal(strSignedOVAName.c_str(),
2171 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
2172 &hVfsIosSignedOVAPackage);
2173 if (RT_SUCCESS(rc))
2174 {
2175 RTVFSFSSTREAM hVfsFssOVADest;
2176 rc = RTZipTarFsStreamToIoStream(hVfsIosSignedOVAPackage, RTZIPTARFORMAT_USTAR,
2177 0 /*fFlags*/, &hVfsFssOVADest);
2178 RTVfsIoStrmRelease(hVfsIosSignedOVAPackage);
2179
2180 if (RT_SUCCESS(rc))
2181 {
2182 bool fCertPresence = false;
2183 RTZipTarFsStreamSetFileMode(hVfsFssOVADest, 0660, 0440);
2184
2185 /*
2186 * Open the original OVA file (it's a standard TAR file) as a file stream
2187 */
2188 RTVFSIOSTREAM hVfsIosOVASrc;
2189 rc = RTVfsIoStrmOpenNormal(strApplianceFullPath.c_str(),
2190 RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
2191 &hVfsIosOVASrc);
2192 if (RT_SUCCESS(rc))
2193 {
2194 RTVFSFSSTREAM hVfsFssOVASrc;
2195 rc = RTZipTarFsStreamFromIoStream(hVfsIosOVASrc, 0 /*fFlags*/, &hVfsFssOVASrc);
2196 /* Dont' forget */
2197 RTVfsIoStrmRelease(hVfsIosOVASrc);
2198
2199 if (RT_SUCCESS(rc))
2200 {
2201 for (;;)
2202 {
2203 char *pszName = NULL;
2204 RTVFSOBJTYPE enmType;
2205 RTVFSOBJ hVfsObj;
2206 hrc = S_OK;
2207 rc = RTVfsFsStrmNext(hVfsFssOVASrc, &pszName, &enmType, &hVfsObj);
2208 if (RT_FAILURE(rc))
2209 {
2210 if (rc != VERR_EOF)
2211 RTPrintf("Error reading the OVA file '%s' (%Rrc)",
2212 strApplianceFullPath.c_str(), rc);
2213 else
2214 rc = VINF_SUCCESS;
2215
2216 break;
2217 }
2218
2219 /*
2220 * in the case when the certificate has been already presented in the OVA package
2221 */
2222 if (strOVFCertificateName.equals(pszName))
2223 {
2224 RTPrintf("Some certificate has already presented in the OVA package\n\n");
2225 fCertPresence = true;//remember for later usage
2226 /* if the flag --force has been set just skip it and go further */
2227 if (fResign)
2228 continue;
2229 }
2230
2231 /** todo: some progress object should be added here to display the action progress */
2232 /* Read the input stream and add the content into the output stream */
2233 rc = RTVfsFsStrmAdd(hVfsFssOVADest, pszName, hVfsObj, 0 /*fFlags*/);
2234 if (RT_FAILURE(rc))
2235 RTPrintf("RTVfsFsStrmAdd failed for the %s (%Rrc)", pszName, rc);
2236
2237 /* Free resources */
2238 RTVfsObjRelease(hVfsObj);
2239 RTStrFree(pszName);
2240
2241 if (RT_FAILURE(rc))
2242 {
2243 RTPrintf("Error writing to new OVA package '%s' (%Rrc)",
2244 strSignedOVAName.c_str(), rc);
2245 break;
2246 }
2247 }
2248
2249 /* Dont' forget */
2250 RTVfsFsStrmRelease(hVfsFssOVASrc);
2251 }
2252 else
2253 RTPrintf("Error reading the OVA file '%s' (%Rrc)", strApplianceFullPath.c_str(), rc);
2254 }
2255 else
2256 RTPrintf("Error opening the OVA file '%s' (%Rrc)", strApplianceFullPath.c_str(), rc);
2257
2258 /*
2259 * Now add the digest signature into new OVA package
2260 */
2261 if (RT_SUCCESS(rc))
2262 {
2263 /* Add only if no cetificate or the flag fResign was set and certificate is presented */
2264 if ( !fCertPresence || (fCertPresence && fResign) )
2265 {
2266 size_t cbWritten;
2267 size_t cbRead;
2268 RTFOFF off = RTVfsIoStrmTell(hVfsIosOVFCertificate);
2269 void *pvBuf = RTMemAlloc(off);
2270 size_t cbBuf = off;
2271
2272 rc = RTVfsIoStrmReadAt(hVfsIosOVFCertificate, 0, pvBuf, cbBuf,
2273 true /*fBlocking*/, &cbRead);
2274 if (RT_SUCCESS(rc))
2275 {
2276 RTVFSIOSTREAM hVfsIosSrc;
2277 rc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pvBuf, cbRead, &hVfsIosSrc);
2278 RTVFSOBJ hVfsObjCert = RTVfsObjFromIoStream(hVfsIosSrc);
2279 RTVfsIoStrmRelease(hVfsIosSrc);
2280
2281 RTZipTarFsStreamSetOwner(hVfsFssOVADest, VBOX_VERSION_MAJOR, "vboxovf20");
2282 RTZipTarFsStreamSetGroup(hVfsFssOVADest, VBOX_VERSION_MINOR,
2283 "vbox_v" RT_XSTR(VBOX_VERSION_MAJOR) "."
2284 RT_XSTR(VBOX_VERSION_MINOR) "."
2285 RT_XSTR(VBOX_VERSION_BUILD) "r"
2286 RT_XSTR(VBOX_SVN_REV) "\0");
2287
2288 /* Write out the certificate into the stream */
2289 rc = RTVfsFsStrmAdd(hVfsFssOVADest, strOVFCertificateName.c_str(),
2290 hVfsObjCert, 0 /*fFlags*/);
2291 if (RT_FAILURE(rc))
2292 RTPrintf("RTVfsFsStrmAdd failed for the %s (%Rrc)",
2293 strOVFCertificateName.c_str(), rc);
2294
2295 /* Dont' forget */
2296 RTVfsObjRelease(hVfsObjCert);
2297
2298 /* Save the OVA certificate file next to the original OVA package */
2299 if (fOutCert)
2300 {
2301 Utf8Str strCertificateFileFullPath(strAppliancePath);
2302 strCertificateFileFullPath.append(RTPATH_DELIMITER).
2303 append(strOVFCertificateName.c_str());
2304
2305 RTVFSIOSTREAM hVfsIosCertFile;
2306 rc = RTVfsIoStrmOpenNormal(strCertificateFileFullPath.c_str(),
2307 RTFILE_O_CREATE_REPLACE |
2308 RTFILE_O_WRITE |
2309 RTFILE_O_DENY_NONE,
2310 &hVfsIosCertFile);
2311 if (RT_SUCCESS(rc))
2312 {
2313 /* Write out the certificate into the file */
2314 rc = RTVfsIoStrmWrite(hVfsIosCertFile, pvBuf,
2315 cbBuf, true /*fBlocking*/, &cbWritten);
2316 if (RT_FAILURE(rc))
2317 RTPrintf("Error writing the certificate file '%s' (%Rrc)",
2318 strCertificateFileFullPath.c_str(), rc);
2319
2320 /* Dont' forget */
2321 RTVfsIoStrmFlush(hVfsIosCertFile);
2322 RTVfsIoStrmRelease(hVfsIosCertFile);
2323 }
2324 else
2325 RTPrintf("Error opening the certificate file '%s' (%Rrc)",
2326 strCertificateFileFullPath.c_str(), rc);
2327 }
2328 }
2329 else
2330 RTPrintf("Error writing the certificate file '%s' (%Rrc)",
2331 strOVFCertificateName.c_str(), rc);
2332
2333 /* Dont' forget */
2334 RTMemFree(pvBuf);
2335 }
2336 }
2337
2338 /* Dont' forget */
2339 RTVfsFsStrmRelease(hVfsFssOVADest);
2340 }
2341 else
2342 RTPrintf("Failed create TAR creator for '%s' (%Rrc)", strSignedOVAName.c_str(), rc);
2343 }
2344 else
2345 RTPrintf("Error opening new OVA signed package '%s'\n", strSignedOVAName.c_str());
2346
2347 /* Dont' forget */
2348 RTVfsIoStrmRelease(hVfsIosOVFCertificate);
2349 }
2350 else
2351 RTPrintf("Error signing the digest of manifest: %Rrc", rc);
2352 }
2353 else
2354 RTPrintf("Error updating the digest: %Rrc", rc);
2355 }
2356 else
2357 RTPrintf("Error digest creation: %Rrc", rc);
2358
2359 /* Don't forget */
2360 RTCrDigestRelease(hDigest);
2361 RTCrKeyRelease(hPrivateKey);
2362 }
2363 else
2364 RTPrintf("Error reading the private key from %s: %Rrc - %s", strPrivateKeyPasswordFile.c_str(), rc, ErrInfo.pszMsg);
2365
2366 /* Dont' forget */
2367 if (RT_SUCCESS(rc))
2368 hrc = S_OK;
2369
2370 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2371}
2372
2373#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