VirtualBox

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

Last change on this file since 97533 was 97533, checked in by vboxsync, 3 years ago

Frontends/VBoxManage,Main/Appliance: The appliance import code populates
the IVirtualSystemDescription instance corresponding to the amount of
guest memory using two different allocation units: OVFValues[] in bytes
and VBoxValues[] in megabytes. The SDK documents the allocation unit as
being in bytes for both fields so update the code to consistently use
bytes for VBoxValues[] just as is done for OVFValues[]. bugref:10314

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