VirtualBox

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

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

VBoxManage: rc -> vrc/hrc. Make scm check. bugref:10223

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

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