VirtualBox

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

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

bugref:9527. Added 3 new values into VirtualSystemDescription: CloudInstanceMetadata, CloudInstanceFreeFormTags, CloudImageFreeFormTags.

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

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