VirtualBox

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

Last change on this file since 107355 was 107198, checked in by vboxsync, 2 months ago

FE/VBoxManage,Main/Appliance: Add the ability to export and import VMs
which contain an NVMe storage controller. Follow-up changes to include
support for moving a disk to an NVMe controller during import and also
add some missing NVMe support in Appliance::i_importMachineGeneric().
bugref:10159

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

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