VirtualBox

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

Last change on this file was 101472, checked in by vboxsync, 7 months ago

FE/VBoxManage,FE/Qt,Main/{VirtualBox.xidl,Appliance}: Add the ability to
export and import VMs which contain an NVMe storage controller.
bugref:10159 ticketref:19320

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

© 2023 Oracle
ContactPrivacy policyTerms of Use