VirtualBox

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

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

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

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