VirtualBox

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

Last change on this file since 99533 was 99523, checked in by vboxsync, 2 years ago

bugref:10314. Reverted back r154561 partially. RAM unit is MB.

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

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