VirtualBox

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

Last change on this file since 99844 was 99604, checked in by vboxsync, 20 months ago

bugref:10314. bugref:10278. Reverted VSD RAM unit to bytes and fixed other places where MB unit was used.

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