VirtualBox

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

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

scm copyright and license note update

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