VirtualBox

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

Last change on this file since 101035 was 101035, checked in by vboxsync, 8 months ago

Initial commit (based draft v2 / on patch v5) for implementing platform architecture support for x86 and ARM. bugref:10384

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

© 2023 Oracle
ContactPrivacy policyTerms of Use