VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp

Last change on this file was 103411, checked in by vboxsync, 2 months ago

FE/VBoxManage: Redo weird logic in handleRegisterVM to fix some parfait warnings, bugref:3409

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 112.5 KB
Line 
1/* $Id: VBoxManageMisc.cpp 103411 2024-02-19 07:12:34Z vboxsync $ */
2/** @file
3 * VBoxManage - VirtualBox's command-line interface.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <VBox/com/com.h>
33#include <VBox/com/string.h>
34#include <VBox/com/Guid.h>
35#include <VBox/com/array.h>
36#include <VBox/com/ErrorInfo.h>
37#include <VBox/com/errorprint.h>
38#include <VBox/com/VirtualBox.h>
39#include <VBox/com/NativeEventQueue.h>
40
41#include <iprt/asm.h>
42#include <iprt/buildconfig.h>
43#include <iprt/cidr.h>
44#include <iprt/ctype.h>
45#include <iprt/dir.h>
46#include <iprt/env.h>
47#include <iprt/file.h>
48#include <iprt/sha.h>
49#include <iprt/initterm.h>
50#include <iprt/param.h>
51#include <iprt/path.h>
52#include <iprt/cpp/path.h>
53#include <iprt/stream.h>
54#include <iprt/string.h>
55#include <iprt/stdarg.h>
56#include <iprt/thread.h>
57#include <iprt/uuid.h>
58#include <iprt/getopt.h>
59#include <iprt/ctype.h>
60#include <VBox/version.h>
61#include <VBox/log.h>
62
63#include "VBoxManage.h"
64
65#include <list>
66
67using namespace com;
68
69DECLARE_TRANSLATION_CONTEXT(Misc);
70
71static const RTGETOPTDEF g_aRegisterVMOptions[] =
72{
73 { "--password", 'p', RTGETOPT_REQ_STRING },
74};
75
76RTEXITCODE handleRegisterVM(HandlerArg *a)
77{
78 HRESULT hrc;
79
80 const char *pszVmFile = NULL;
81 const char *pszPasswordFile = NULL;
82
83 int c;
84 RTGETOPTUNION ValueUnion;
85 RTGETOPTSTATE GetState;
86 // start at 0 because main() has hacked both the argc and argv given to us
87 RTGetOptInit(&GetState, a->argc, a->argv, g_aRegisterVMOptions, RT_ELEMENTS(g_aRegisterVMOptions),
88 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
89 while ((c = RTGetOpt(&GetState, &ValueUnion)))
90 {
91 switch (c)
92 {
93 case 'p': // --password
94 pszPasswordFile = ValueUnion.psz;
95 break;
96
97 case VINF_GETOPT_NOT_OPTION:
98 if (!pszVmFile)
99 pszVmFile = ValueUnion.psz;
100 else
101 return errorSyntax(Misc::tr("Invalid parameter '%s'"), ValueUnion.psz);
102 break;
103
104 default:
105 if (c > 0)
106 {
107 if (RT_C_IS_PRINT(c))
108 return errorSyntax(Misc::tr("Invalid option -%c"), c);
109 return errorSyntax(Misc::tr("Invalid option case %i"), c);
110 }
111 if (c == VERR_GETOPT_UNKNOWN_OPTION)
112 return errorSyntax(Misc::tr("unknown option: %s\n"), ValueUnion.psz);
113 if (ValueUnion.pDef)
114 return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c);
115 return errorSyntax(Misc::tr("error: %Rrs"), c);
116 }
117 }
118
119 Utf8Str strPassword;
120
121 if (pszPasswordFile)
122 {
123 if (pszPasswordFile[0] == '-' && pszPasswordFile[1] == '\0')
124 {
125 /* Get password from console. */
126 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, Misc::tr("Enter password:"));
127 if (rcExit == RTEXITCODE_FAILURE)
128 return rcExit;
129 }
130 else
131 {
132 RTEXITCODE rcExit = readPasswordFile(pszPasswordFile, &strPassword);
133 if (rcExit == RTEXITCODE_FAILURE)
134 return RTMsgErrorExitFailure(Misc::tr("Failed to read password from file"));
135 }
136 }
137
138 ComPtr<IMachine> machine;
139 /** @todo Ugly hack to get both the API interpretation of relative paths
140 * and the client's interpretation of relative paths. Remove after the API
141 * has been redesigned. */
142 hrc = a->virtualBox->OpenMachine(Bstr(pszVmFile).raw(),
143 Bstr(strPassword).raw(),
144 machine.asOutParam());
145 if (FAILED(hrc) && !RTPathStartsWithRoot(pszVmFile))
146 {
147 char szVMFileAbs[RTPATH_MAX] = "";
148 int vrc = RTPathAbs(pszVmFile, szVMFileAbs, sizeof(szVMFileAbs));
149 if (RT_FAILURE(vrc))
150 return RTMsgErrorExitFailure(Misc::tr("Failed to convert \"%s\" to an absolute path: %Rrc"),
151 pszVmFile, vrc);
152 CHECK_ERROR(a->virtualBox, OpenMachine(Bstr(szVMFileAbs).raw(),
153 Bstr(strPassword).raw(),
154 machine.asOutParam()));
155 }
156 else if (FAILED(hrc))
157 com::GlueHandleComError(a->virtualBox,
158 "OpenMachine(Bstr(a->argv[0]).raw(), Bstr(strPassword).raw(), machine.asOutParam()))",
159 hrc, __FILE__, __LINE__);
160 if (SUCCEEDED(hrc))
161 {
162 ASSERT(machine);
163 CHECK_ERROR(a->virtualBox, RegisterMachine(machine));
164 }
165 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
166}
167
168static const RTGETOPTDEF g_aUnregisterVMOptions[] =
169{
170 { "--delete", 'd', RTGETOPT_REQ_NOTHING },
171 { "-delete", 'd', RTGETOPT_REQ_NOTHING }, // deprecated
172 { "--delete-all", 'a', RTGETOPT_REQ_NOTHING },
173 { "-delete-all", 'a', RTGETOPT_REQ_NOTHING }, // deprecated
174};
175
176RTEXITCODE handleUnregisterVM(HandlerArg *a)
177{
178 HRESULT hrc;
179 const char *VMName = NULL;
180 bool fDelete = false;
181 bool fDeleteAll = false;
182
183 int c;
184 RTGETOPTUNION ValueUnion;
185 RTGETOPTSTATE GetState;
186 // start at 0 because main() has hacked both the argc and argv given to us
187 RTGetOptInit(&GetState, a->argc, a->argv, g_aUnregisterVMOptions, RT_ELEMENTS(g_aUnregisterVMOptions),
188 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
189 while ((c = RTGetOpt(&GetState, &ValueUnion)))
190 {
191 switch (c)
192 {
193 case 'd': // --delete
194 fDelete = true;
195 break;
196
197 case 'a': // --delete-all
198 fDeleteAll = true;
199 break;
200
201 case VINF_GETOPT_NOT_OPTION:
202 if (!VMName)
203 VMName = ValueUnion.psz;
204 else
205 return errorSyntax(Misc::tr("Invalid parameter '%s'"), ValueUnion.psz);
206 break;
207
208 default:
209 if (c > 0)
210 {
211 if (RT_C_IS_PRINT(c))
212 return errorSyntax(Misc::tr("Invalid option -%c"), c);
213 return errorSyntax(Misc::tr("Invalid option case %i"), c);
214 }
215 if (c == VERR_GETOPT_UNKNOWN_OPTION)
216 return errorSyntax(Misc::tr("unknown option: %s\n"), ValueUnion.psz);
217 if (ValueUnion.pDef)
218 return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c);
219 return errorSyntax(Misc::tr("error: %Rrs"), c);
220 }
221 }
222
223 /* check for required options */
224 if (!VMName)
225 return errorSyntax(Misc::tr("VM name required"));
226
227 ComPtr<IMachine> machine;
228 CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(VMName).raw(),
229 machine.asOutParam()),
230 RTEXITCODE_FAILURE);
231 SafeIfaceArray<IMedium> aMedia;
232 CHECK_ERROR_RET(machine, Unregister(fDeleteAll ? CleanupMode_DetachAllReturnHardDisksAndVMRemovable
233 :CleanupMode_DetachAllReturnHardDisksOnly,
234 ComSafeArrayAsOutParam(aMedia)),
235 RTEXITCODE_FAILURE);
236 if (fDelete || fDeleteAll)
237 {
238 ComPtr<IProgress> pProgress;
239 CHECK_ERROR_RET(machine, DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress.asOutParam()),
240 RTEXITCODE_FAILURE);
241
242 hrc = showProgress(pProgress);
243 CHECK_PROGRESS_ERROR_RET(pProgress, (Misc::tr("Machine delete failed")), RTEXITCODE_FAILURE);
244 }
245 else
246 {
247 /* Note that the IMachine::Unregister method will return the medium
248 * reference in a sane order, which means that closing will normally
249 * succeed, unless there is still another machine which uses the
250 * medium. No harm done if we ignore the error. */
251 for (size_t i = 0; i < aMedia.size(); i++)
252 {
253 IMedium *pMedium = aMedia[i];
254 if (pMedium)
255 hrc = pMedium->Close();
256 }
257 hrc = S_OK; /** @todo r=andy Why overwriting the result from closing the medium above? */
258 }
259 return RTEXITCODE_SUCCESS;
260}
261
262static const RTGETOPTDEF g_aCreateVMOptions[] =
263{
264 { "--name", 'n', RTGETOPT_REQ_STRING },
265 { "-name", 'n', RTGETOPT_REQ_STRING },
266 { "--groups", 'g', RTGETOPT_REQ_STRING },
267 { "--basefolder", 'p', RTGETOPT_REQ_STRING },
268 { "-basefolder", 'p', RTGETOPT_REQ_STRING },
269 { "--ostype", 'o', RTGETOPT_REQ_STRING },
270 { "-ostype", 'o', RTGETOPT_REQ_STRING },
271 { "--uuid", 'u', RTGETOPT_REQ_UUID },
272 { "-uuid", 'u', RTGETOPT_REQ_UUID },
273 { "--register", 'r', RTGETOPT_REQ_NOTHING },
274 { "-register", 'r', RTGETOPT_REQ_NOTHING },
275 { "--default", 'd', RTGETOPT_REQ_NOTHING },
276 { "-default", 'd', RTGETOPT_REQ_NOTHING },
277 { "--cipher", 'c', RTGETOPT_REQ_STRING },
278 { "-cipher", 'c', RTGETOPT_REQ_STRING },
279 { "--password-id", 'i', RTGETOPT_REQ_STRING },
280 { "-password-id", 'i', RTGETOPT_REQ_STRING },
281 { "--password", 'w', RTGETOPT_REQ_STRING },
282 { "-password", 'w', RTGETOPT_REQ_STRING },
283 { "--platform-architecture", 'a', RTGETOPT_REQ_STRING },
284 { "--platform-arch", 'a', RTGETOPT_REQ_STRING }, /* Shorter. */
285};
286
287RTEXITCODE handleCreateVM(HandlerArg *a)
288{
289 HRESULT hrc;
290 PlatformArchitecture_T platformArch = PlatformArchitecture_None;
291 Bstr bstrBaseFolder;
292 Bstr bstrName;
293 Bstr bstrOsTypeId;
294 Bstr bstrUuid;
295 bool fRegister = false;
296 bool fDefault = false;
297 /* TBD. Now not used */
298 Bstr bstrDefaultFlags;
299 com::SafeArray<BSTR> groups;
300 Bstr bstrCipher;
301 Bstr bstrPasswordId;
302 const char *pszPassword = NULL;
303
304 int c;
305 RTGETOPTUNION ValueUnion;
306 RTGETOPTSTATE GetState;
307 // start at 0 because main() has hacked both the argc and argv given to us
308 RTGetOptInit(&GetState, a->argc, a->argv, g_aCreateVMOptions, RT_ELEMENTS(g_aCreateVMOptions),
309 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
310 while ((c = RTGetOpt(&GetState, &ValueUnion)))
311 {
312 switch (c)
313 {
314 case 'n': // --name
315 bstrName = ValueUnion.psz;
316 break;
317
318 case 'g': // --groups
319 parseGroups(ValueUnion.psz, &groups);
320 break;
321
322 case 'p': // --basefolder
323 bstrBaseFolder = ValueUnion.psz;
324 break;
325
326 case 'o': // --ostype
327 bstrOsTypeId = ValueUnion.psz;
328 break;
329
330 case 'u': // --uuid
331 bstrUuid = Guid(ValueUnion.Uuid).toUtf16().raw();
332 break;
333
334 case 'r': // --register
335 fRegister = true;
336 break;
337
338 case 'd': // --default
339 fDefault = true;
340 break;
341
342 case 'c': // --cipher
343 bstrCipher = ValueUnion.psz;
344 break;
345
346 case 'i': // --password-id
347 bstrPasswordId = ValueUnion.psz;
348 break;
349
350 case 'w': // --password
351 pszPassword = ValueUnion.psz;
352 break;
353
354 case 'a': // --platform-architecture
355 if (!RTStrICmp(ValueUnion.psz, "x86"))
356 platformArch = PlatformArchitecture_x86;
357 else if (!RTStrICmp(ValueUnion.psz, "arm"))
358 platformArch = PlatformArchitecture_ARM;
359 else
360 return errorArgument(Misc::tr("Invalid --platform-architecture argument '%s'"), ValueUnion.psz);
361 break;
362
363 default:
364 return errorGetOpt(c, &ValueUnion);
365 }
366 }
367
368 /* check for required options */
369 if (platformArch == PlatformArchitecture_None)
370 return errorSyntax(Misc::tr("Parameter --platform-architecture is required"));
371 if (bstrName.isEmpty())
372 return errorSyntax(Misc::tr("Parameter --name is required"));
373
374 do
375 {
376 Bstr createFlags;
377 if (!bstrUuid.isEmpty())
378 createFlags = BstrFmt("UUID=%ls", bstrUuid.raw());
379 Bstr bstrPrimaryGroup;
380 if (groups.size())
381 bstrPrimaryGroup = groups[0];
382 Bstr bstrSettingsFile;
383 CHECK_ERROR_BREAK(a->virtualBox,
384 ComposeMachineFilename(bstrName.raw(),
385 bstrPrimaryGroup.raw(),
386 createFlags.raw(),
387 bstrBaseFolder.raw(),
388 bstrSettingsFile.asOutParam()));
389 Utf8Str strPassword;
390 if (pszPassword)
391 {
392 if (!RTStrCmp(pszPassword, "-"))
393 {
394 /* Get password from console. */
395 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter the password:");
396 if (rcExit == RTEXITCODE_FAILURE)
397 return rcExit;
398 }
399 else
400 {
401 RTEXITCODE rcExit = readPasswordFile(pszPassword, &strPassword);
402 if (rcExit == RTEXITCODE_FAILURE)
403 {
404 RTMsgError("Failed to read new password from file");
405 return rcExit;
406 }
407 }
408 }
409 ComPtr<IMachine> machine;
410 CHECK_ERROR_BREAK(a->virtualBox,
411 CreateMachine(bstrSettingsFile.raw(),
412 bstrName.raw(),
413 platformArch,
414 ComSafeArrayAsInParam(groups),
415 bstrOsTypeId.raw(),
416 createFlags.raw(),
417 bstrCipher.raw(),
418 bstrPasswordId.raw(),
419 Bstr(strPassword).raw(),
420 machine.asOutParam()));
421
422 CHECK_ERROR_BREAK(machine, SaveSettings());
423 if (fDefault)
424 {
425 /* ApplyDefaults assumes the machine is already registered */
426 CHECK_ERROR_BREAK(machine, ApplyDefaults(bstrDefaultFlags.raw()));
427 CHECK_ERROR_BREAK(machine, SaveSettings());
428 }
429 if (fRegister)
430 {
431 CHECK_ERROR_BREAK(a->virtualBox, RegisterMachine(machine));
432 }
433
434 Bstr uuid;
435 CHECK_ERROR_BREAK(machine, COMGETTER(Id)(uuid.asOutParam()));
436 Bstr settingsFile;
437 CHECK_ERROR_BREAK(machine, COMGETTER(SettingsFilePath)(settingsFile.asOutParam()));
438 RTPrintf(Misc::tr("Virtual machine '%ls' is created%s.\n"
439 "UUID: %s\n"
440 "Settings file: '%ls'\n"),
441 bstrName.raw(), fRegister ? Misc::tr(" and registered") : "",
442 Utf8Str(uuid).c_str(), settingsFile.raw());
443 }
444 while (0);
445
446 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
447}
448
449static const RTGETOPTDEF g_aMoveVMOptions[] =
450{
451 { "--type", 't', RTGETOPT_REQ_STRING },
452 { "--folder", 'f', RTGETOPT_REQ_STRING },
453};
454
455RTEXITCODE handleMoveVM(HandlerArg *a)
456{
457 HRESULT hrc;
458 const char *pszSrcName = NULL;
459 const char *pszType = NULL;
460 char szTargetFolder[RTPATH_MAX];
461
462 int c;
463 int vrc = VINF_SUCCESS;
464 RTGETOPTUNION ValueUnion;
465 RTGETOPTSTATE GetState;
466
467 // start at 0 because main() has hacked both the argc and argv given to us
468 RTGetOptInit(&GetState, a->argc, a->argv, g_aMoveVMOptions, RT_ELEMENTS(g_aMoveVMOptions),
469 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
470 while ((c = RTGetOpt(&GetState, &ValueUnion)))
471 {
472 switch (c)
473 {
474 case 't': // --type
475 pszType = ValueUnion.psz;
476 break;
477
478 case 'f': // --target folder
479 if (ValueUnion.psz && ValueUnion.psz[0] != '\0')
480 {
481 vrc = RTPathAbs(ValueUnion.psz, szTargetFolder, sizeof(szTargetFolder));
482 if (RT_FAILURE(vrc))
483 return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("RTPathAbs(%s,,) failed with vrc=%Rrc"),
484 ValueUnion.psz, vrc);
485 } else {
486 szTargetFolder[0] = '\0';
487 }
488 break;
489
490 case VINF_GETOPT_NOT_OPTION:
491 if (!pszSrcName)
492 pszSrcName = ValueUnion.psz;
493 else
494 return errorSyntax(Misc::tr("Invalid parameter '%s'"), ValueUnion.psz);
495 break;
496
497 default:
498 return errorGetOpt(c, &ValueUnion);
499 }
500 }
501
502
503 if (!pszType)
504 pszType = "basic";
505
506 /* Check for required options */
507 if (!pszSrcName)
508 return errorSyntax(Misc::tr("VM name required"));
509
510 /* Get the machine object */
511 ComPtr<IMachine> srcMachine;
512 CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(pszSrcName).raw(),
513 srcMachine.asOutParam()),
514 RTEXITCODE_FAILURE);
515
516 if (srcMachine)
517 {
518 /* Start the moving */
519 ComPtr<IProgress> progress;
520
521 /* we have to open a session for this task */
522 CHECK_ERROR_RET(srcMachine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE);
523 ComPtr<IMachine> sessionMachine;
524
525 CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE);
526 CHECK_ERROR_RET(sessionMachine,
527 MoveTo(Bstr(szTargetFolder).raw(),
528 Bstr(pszType).raw(),
529 progress.asOutParam()),
530 RTEXITCODE_FAILURE);
531 hrc = showProgress(progress);
532 CHECK_PROGRESS_ERROR_RET(progress, (Misc::tr("Move VM failed")), RTEXITCODE_FAILURE);
533
534 sessionMachine.setNull();
535 CHECK_ERROR_RET(a->session, UnlockMachine(), RTEXITCODE_FAILURE);
536
537 RTPrintf(Misc::tr("Machine has been successfully moved into %s\n"),
538 szTargetFolder[0] != '\0' ? szTargetFolder : Misc::tr("the same location"));
539 }
540
541 return RTEXITCODE_SUCCESS;
542}
543
544static const RTGETOPTDEF g_aCloneVMOptions[] =
545{
546 { "--snapshot", 's', RTGETOPT_REQ_STRING },
547 { "--name", 'n', RTGETOPT_REQ_STRING },
548 { "--groups", 'g', RTGETOPT_REQ_STRING },
549 { "--mode", 'm', RTGETOPT_REQ_STRING },
550 { "--options", 'o', RTGETOPT_REQ_STRING },
551 { "--register", 'r', RTGETOPT_REQ_NOTHING },
552 { "--basefolder", 'p', RTGETOPT_REQ_STRING },
553 { "--uuid", 'u', RTGETOPT_REQ_UUID },
554};
555
556static int parseCloneMode(const char *psz, CloneMode_T *pMode)
557{
558 if (!RTStrICmp(psz, "machine"))
559 *pMode = CloneMode_MachineState;
560 else if (!RTStrICmp(psz, "machineandchildren"))
561 *pMode = CloneMode_MachineAndChildStates;
562 else if (!RTStrICmp(psz, "all"))
563 *pMode = CloneMode_AllStates;
564 else
565 return VERR_PARSE_ERROR;
566
567 return VINF_SUCCESS;
568}
569
570static int parseCloneOptions(const char *psz, com::SafeArray<CloneOptions_T> *options)
571{
572 int vrc = VINF_SUCCESS;
573 while (psz && *psz && RT_SUCCESS(vrc))
574 {
575 size_t len;
576 const char *pszComma = strchr(psz, ',');
577 if (pszComma)
578 len = pszComma - psz;
579 else
580 len = strlen(psz);
581 if (len > 0)
582 {
583 if (!RTStrNICmp(psz, "KeepAllMACs", len))
584 options->push_back(CloneOptions_KeepAllMACs);
585 else if (!RTStrNICmp(psz, "KeepNATMACs", len))
586 options->push_back(CloneOptions_KeepNATMACs);
587 else if (!RTStrNICmp(psz, "KeepDiskNames", len))
588 options->push_back(CloneOptions_KeepDiskNames);
589 else if ( !RTStrNICmp(psz, "Link", len)
590 || !RTStrNICmp(psz, "Linked", len))
591 options->push_back(CloneOptions_Link);
592 else if ( !RTStrNICmp(psz, "KeepHwUUIDs", len)
593 || !RTStrNICmp(psz, "KeepHwUUID", len))
594 options->push_back(CloneOptions_KeepHwUUIDs);
595 else
596 vrc = VERR_PARSE_ERROR;
597 }
598 if (pszComma)
599 psz += len + 1;
600 else
601 psz += len;
602 }
603
604 return vrc;
605}
606
607RTEXITCODE handleCloneVM(HandlerArg *a)
608{
609 HRESULT hrc;
610 const char *pszSrcName = NULL;
611 const char *pszSnapshotName = NULL;
612 CloneMode_T mode = CloneMode_MachineState;
613 com::SafeArray<CloneOptions_T> options;
614 const char *pszTrgName = NULL;
615 const char *pszTrgBaseFolder = NULL;
616 bool fRegister = false;
617 Bstr bstrUuid;
618 com::SafeArray<BSTR> groups;
619
620 int c;
621 RTGETOPTUNION ValueUnion;
622 RTGETOPTSTATE GetState;
623 // start at 0 because main() has hacked both the argc and argv given to us
624 RTGetOptInit(&GetState, a->argc, a->argv, g_aCloneVMOptions, RT_ELEMENTS(g_aCloneVMOptions),
625 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
626 while ((c = RTGetOpt(&GetState, &ValueUnion)))
627 {
628 switch (c)
629 {
630 case 's': // --snapshot
631 pszSnapshotName = ValueUnion.psz;
632 break;
633
634 case 'n': // --name
635 pszTrgName = ValueUnion.psz;
636 break;
637
638 case 'g': // --groups
639 parseGroups(ValueUnion.psz, &groups);
640 break;
641
642 case 'p': // --basefolder
643 pszTrgBaseFolder = ValueUnion.psz;
644 break;
645
646 case 'm': // --mode
647 if (RT_FAILURE(parseCloneMode(ValueUnion.psz, &mode)))
648 return errorArgument(Misc::tr("Invalid clone mode '%s'\n"), ValueUnion.psz);
649 break;
650
651 case 'o': // --options
652 if (RT_FAILURE(parseCloneOptions(ValueUnion.psz, &options)))
653 return errorArgument(Misc::tr("Invalid clone options '%s'\n"), ValueUnion.psz);
654 break;
655
656 case 'u': // --uuid
657 bstrUuid = Guid(ValueUnion.Uuid).toUtf16().raw();
658 break;
659
660 case 'r': // --register
661 fRegister = true;
662 break;
663
664 case VINF_GETOPT_NOT_OPTION:
665 if (!pszSrcName)
666 pszSrcName = ValueUnion.psz;
667 else
668 return errorSyntax(Misc::tr("Invalid parameter '%s'"), ValueUnion.psz);
669 break;
670
671 default:
672 return errorGetOpt(c, &ValueUnion);
673 }
674 }
675
676 /* Check for required options */
677 if (!pszSrcName)
678 return errorSyntax(Misc::tr("VM name required"));
679
680 /* Get the machine object */
681 ComPtr<IMachine> srcMachine;
682 CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(pszSrcName).raw(),
683 srcMachine.asOutParam()),
684 RTEXITCODE_FAILURE);
685
686 /* Get the platform architecture, to clone a VM which has the same architecture. */
687 ComPtr<IPlatform> platform;
688 CHECK_ERROR_RET(srcMachine, COMGETTER(Platform)(platform.asOutParam()), RTEXITCODE_FAILURE);
689 PlatformArchitecture_T platformArch;
690 CHECK_ERROR_RET(platform, COMGETTER(Architecture)(&platformArch), RTEXITCODE_FAILURE);
691
692 /* If a snapshot name/uuid was given, get the particular machine of this
693 * snapshot. */
694 if (pszSnapshotName)
695 {
696 ComPtr<ISnapshot> srcSnapshot;
697 CHECK_ERROR_RET(srcMachine, FindSnapshot(Bstr(pszSnapshotName).raw(),
698 srcSnapshot.asOutParam()),
699 RTEXITCODE_FAILURE);
700 CHECK_ERROR_RET(srcSnapshot, COMGETTER(Machine)(srcMachine.asOutParam()),
701 RTEXITCODE_FAILURE);
702 }
703
704 /* Default name necessary? */
705 if (!pszTrgName)
706 pszTrgName = RTStrAPrintf2(Misc::tr("%s Clone"), pszSrcName);
707
708 Bstr createFlags;
709 if (!bstrUuid.isEmpty())
710 createFlags = BstrFmt("UUID=%ls", bstrUuid.raw());
711 Bstr bstrPrimaryGroup;
712 if (groups.size())
713 bstrPrimaryGroup = groups[0];
714 Bstr bstrSettingsFile;
715 CHECK_ERROR_RET(a->virtualBox,
716 ComposeMachineFilename(Bstr(pszTrgName).raw(),
717 bstrPrimaryGroup.raw(),
718 createFlags.raw(),
719 Bstr(pszTrgBaseFolder).raw(),
720 bstrSettingsFile.asOutParam()),
721 RTEXITCODE_FAILURE);
722
723 ComPtr<IMachine> trgMachine;
724 CHECK_ERROR_RET(a->virtualBox, CreateMachine(bstrSettingsFile.raw(),
725 Bstr(pszTrgName).raw(),
726 platformArch,
727 ComSafeArrayAsInParam(groups),
728 NULL,
729 createFlags.raw(),
730 NULL,
731 NULL,
732 NULL,
733 trgMachine.asOutParam()),
734 RTEXITCODE_FAILURE);
735
736 /* Start the cloning */
737 ComPtr<IProgress> progress;
738 CHECK_ERROR_RET(srcMachine, CloneTo(trgMachine,
739 mode,
740 ComSafeArrayAsInParam(options),
741 progress.asOutParam()),
742 RTEXITCODE_FAILURE);
743 hrc = showProgress(progress);
744 CHECK_PROGRESS_ERROR_RET(progress, (Misc::tr("Clone VM failed")), RTEXITCODE_FAILURE);
745
746 if (fRegister)
747 CHECK_ERROR_RET(a->virtualBox, RegisterMachine(trgMachine), RTEXITCODE_FAILURE);
748
749 Bstr bstrNewName;
750 CHECK_ERROR_RET(trgMachine, COMGETTER(Name)(bstrNewName.asOutParam()), RTEXITCODE_FAILURE);
751 RTPrintf(Misc::tr("Machine has been successfully cloned as \"%ls\"\n"), bstrNewName.raw());
752
753 return RTEXITCODE_SUCCESS;
754}
755
756RTEXITCODE handleStartVM(HandlerArg *a)
757{
758 HRESULT hrc = S_OK;
759 std::list<const char *> VMs;
760 Bstr sessionType;
761 com::SafeArray<IN_BSTR> aBstrEnv;
762 const char *pszPassword = NULL;
763 const char *pszPasswordId = NULL;
764 Utf8Str strPassword;
765
766#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
767 /* make sure the VM process will by default start on the same display as VBoxManage */
768 {
769 const char *pszDisplay = RTEnvGet("DISPLAY");
770 if (pszDisplay)
771 aBstrEnv.push_back(BstrFmt("DISPLAY=%s", pszDisplay).raw());
772 const char *pszXAuth = RTEnvGet("XAUTHORITY");
773 if (pszXAuth)
774 aBstrEnv.push_back(BstrFmt("XAUTHORITY=%s", pszXAuth).raw());
775 }
776#endif
777
778 static const RTGETOPTDEF s_aStartVMOptions[] =
779 {
780 { "--type", 't', RTGETOPT_REQ_STRING },
781 { "-type", 't', RTGETOPT_REQ_STRING }, // deprecated
782 { "--putenv", 'E', RTGETOPT_REQ_STRING },
783 { "--password", 'p', RTGETOPT_REQ_STRING },
784 { "--password-id", 'i', RTGETOPT_REQ_STRING }
785 };
786 int c;
787 RTGETOPTUNION ValueUnion;
788 RTGETOPTSTATE GetState;
789 // start at 0 because main() has hacked both the argc and argv given to us
790 RTGetOptInit(&GetState, a->argc, a->argv, s_aStartVMOptions, RT_ELEMENTS(s_aStartVMOptions),
791 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
792 while ((c = RTGetOpt(&GetState, &ValueUnion)))
793 {
794 switch (c)
795 {
796 case 't': // --type
797 if (!RTStrICmp(ValueUnion.psz, "gui"))
798 {
799 sessionType = "gui";
800 }
801#ifdef VBOX_WITH_VBOXSDL
802 else if (!RTStrICmp(ValueUnion.psz, "sdl"))
803 {
804 sessionType = "sdl";
805 }
806#endif
807#ifdef VBOX_WITH_HEADLESS
808 else if (!RTStrICmp(ValueUnion.psz, "capture"))
809 {
810 sessionType = "capture";
811 }
812 else if (!RTStrICmp(ValueUnion.psz, "headless"))
813 {
814 sessionType = "headless";
815 }
816#endif
817 else
818 sessionType = ValueUnion.psz;
819 break;
820
821 case 'E': // --putenv
822 if (!RTStrStr(ValueUnion.psz, "\n"))
823 aBstrEnv.push_back(Bstr(ValueUnion.psz).raw());
824 else
825 return errorSyntax(Misc::tr("Parameter to option --putenv must not contain any newline character"));
826 break;
827
828 case 'p': // --password
829 pszPassword = ValueUnion.psz;
830 break;
831
832 case 'i': // --password-id
833 pszPasswordId = ValueUnion.psz;
834 break;
835
836 case VINF_GETOPT_NOT_OPTION:
837 VMs.push_back(ValueUnion.psz);
838 break;
839
840 default:
841 if (c > 0)
842 {
843 if (RT_C_IS_PRINT(c))
844 return errorSyntax(Misc::tr("Invalid option -%c"), c);
845 else
846 return errorSyntax(Misc::tr("Invalid option case %i"), c);
847 }
848 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
849 return errorSyntax(Misc::tr("unknown option: %s\n"), ValueUnion.psz);
850 else if (ValueUnion.pDef)
851 return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c);
852 else
853 return errorSyntax(Misc::tr("error: %Rrs"), c);
854 }
855 }
856
857 /* check for required options */
858 if (VMs.empty())
859 return errorSyntax(Misc::tr("at least one VM name or uuid required"));
860
861 if (pszPassword)
862 {
863 if (!RTStrCmp(pszPassword, "-"))
864 {
865 /* Get password from console. */
866 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter the password:");
867 if (rcExit == RTEXITCODE_FAILURE)
868 return rcExit;
869 }
870 else
871 {
872 RTEXITCODE rcExit = readPasswordFile(pszPassword, &strPassword);
873 if (rcExit == RTEXITCODE_FAILURE)
874 {
875 RTMsgError("Failed to read new password from file");
876 return rcExit;
877 }
878 }
879 }
880
881 for (std::list<const char *>::const_iterator it = VMs.begin();
882 it != VMs.end();
883 ++it)
884 {
885 HRESULT hrc2 = hrc;
886 const char *pszVM = *it;
887 ComPtr<IMachine> machine;
888 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszVM).raw(),
889 machine.asOutParam()));
890 if (machine)
891 {
892 if (pszPasswordId && strPassword.isNotEmpty())
893 {
894 CHECK_ERROR(machine, AddEncryptionPassword(Bstr(pszPasswordId).raw(), Bstr(strPassword).raw()));
895 if (hrc == VBOX_E_PASSWORD_INCORRECT)
896 RTMsgError("Password incorrect!");
897 }
898 if (SUCCEEDED(hrc))
899 {
900 ComPtr<IProgress> progress;
901 CHECK_ERROR(machine, LaunchVMProcess(a->session, sessionType.raw(),
902 ComSafeArrayAsInParam(aBstrEnv), progress.asOutParam()));
903 if (SUCCEEDED(hrc) && !progress.isNull())
904 {
905 RTPrintf("Waiting for VM \"%s\" to power on...\n", pszVM);
906 CHECK_ERROR(progress, WaitForCompletion(-1));
907 if (SUCCEEDED(hrc))
908 {
909 BOOL completed = true;
910 CHECK_ERROR(progress, COMGETTER(Completed)(&completed));
911 if (SUCCEEDED(hrc))
912 {
913 ASSERT(completed);
914
915 LONG iRc;
916 CHECK_ERROR(progress, COMGETTER(ResultCode)(&iRc));
917 if (SUCCEEDED(hrc))
918 {
919 if (SUCCEEDED(iRc))
920 RTPrintf("VM \"%s\" has been successfully started.\n", pszVM);
921 else
922 {
923 ProgressErrorInfo info(progress);
924 com::GluePrintErrorInfo(info);
925 }
926 hrc = iRc;
927 }
928 }
929 }
930 }
931 }
932 }
933
934 /* it's important to always close sessions */
935 a->session->UnlockMachine();
936
937 /* make sure that we remember the failed state */
938 if (FAILED(hrc2))
939 hrc = hrc2;
940 }
941
942 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
943}
944
945#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
946static const RTGETOPTDEF g_aSetVMEncryptionOptions[] =
947{
948 { "--new-password", 'n', RTGETOPT_REQ_STRING },
949 { "--old-password", 'o', RTGETOPT_REQ_STRING },
950 { "--cipher", 'c', RTGETOPT_REQ_STRING },
951 { "--new-password-id", 'i', RTGETOPT_REQ_STRING },
952 { "--force", 'f', RTGETOPT_REQ_NOTHING},
953};
954
955static RTEXITCODE handleSetVMEncryption(HandlerArg *a, const char *pszFilenameOrUuid)
956{
957 HRESULT hrc;
958 ComPtr<IMachine> machine;
959 const char *pszPasswordNew = NULL;
960 const char *pszPasswordOld = NULL;
961 const char *pszCipher = NULL;
962 const char *pszNewPasswordId = NULL;
963 BOOL fForce = FALSE;
964 Utf8Str strPasswordNew;
965 Utf8Str strPasswordOld;
966
967 int c;
968 RTGETOPTUNION ValueUnion;
969 RTGETOPTSTATE GetState;
970 // start at 0 because main() has hacked both the argc and argv given to us
971 RTGetOptInit(&GetState, a->argc, a->argv, g_aSetVMEncryptionOptions, RT_ELEMENTS(g_aSetVMEncryptionOptions),
972 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
973 while ((c = RTGetOpt(&GetState, &ValueUnion)))
974 {
975 switch (c)
976 {
977 case 'n': // --new-password
978 pszPasswordNew = ValueUnion.psz;
979 break;
980
981 case 'o': // --old-password
982 pszPasswordOld = ValueUnion.psz;
983 break;
984
985 case 'c': // --cipher
986 pszCipher = ValueUnion.psz;
987 break;
988
989 case 'i': // --new-password-id
990 pszNewPasswordId = ValueUnion.psz;
991 break;
992
993 case 'f': // --force
994 fForce = TRUE;
995 break;
996
997 default:
998 if (c > 0)
999 {
1000 if (RT_C_IS_PRINT(c))
1001 return errorSyntax(Misc::tr("Invalid option -%c"), c);
1002 else
1003 return errorSyntax(Misc::tr("Invalid option case %i"), c);
1004 }
1005 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
1006 return errorSyntax(Misc::tr("unknown option: %s\n"), ValueUnion.psz);
1007 else if (ValueUnion.pDef)
1008 return errorSyntax(Misc::tr("%s: %Rrs"), ValueUnion.pDef->pszLong, c);
1009 else
1010 return errorSyntax(Misc::tr("error: %Rrs"), c);
1011 }
1012 }
1013
1014 if (!pszFilenameOrUuid)
1015 return errorSyntax(Misc::tr("VM name or UUID required"));
1016
1017 if (!pszPasswordNew && !pszPasswordOld)
1018 return errorSyntax(Misc::tr("No password specified"));
1019
1020 if ( (pszPasswordNew && !pszNewPasswordId)
1021 || (!pszPasswordNew && pszNewPasswordId))
1022 return errorSyntax(Misc::tr("A new password must always have a valid identifier set at the same time"));
1023
1024 if (pszPasswordOld)
1025 {
1026 if (!RTStrCmp(pszPasswordOld, "-"))
1027 {
1028 /* Get password from console. */
1029 RTEXITCODE rcExit = readPasswordFromConsole(&strPasswordOld, "Enter old password:");
1030 if (rcExit == RTEXITCODE_FAILURE)
1031 return rcExit;
1032 }
1033 else
1034 {
1035 RTEXITCODE rcExit = readPasswordFile(pszPasswordOld, &strPasswordOld);
1036 if (rcExit == RTEXITCODE_FAILURE)
1037 {
1038 RTMsgError("Failed to read old password from file");
1039 return rcExit;
1040 }
1041 }
1042 }
1043 if (pszPasswordNew)
1044 {
1045 if (!RTStrCmp(pszPasswordNew, "-"))
1046 {
1047 /* Get password from console. */
1048 RTEXITCODE rcExit = readPasswordFromConsole(&strPasswordNew, "Enter new password:");
1049 if (rcExit == RTEXITCODE_FAILURE)
1050 return rcExit;
1051 }
1052 else
1053 {
1054 RTEXITCODE rcExit = readPasswordFile(pszPasswordNew, &strPasswordNew);
1055 if (rcExit == RTEXITCODE_FAILURE)
1056 {
1057 RTMsgError("Failed to read new password from file");
1058 return rcExit;
1059 }
1060 }
1061 }
1062
1063 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszFilenameOrUuid).raw(),
1064 machine.asOutParam()));
1065 if (machine)
1066 {
1067 ComPtr<IProgress> progress;
1068 CHECK_ERROR(machine, ChangeEncryption(Bstr(strPasswordOld).raw(), Bstr(pszCipher).raw(),
1069 Bstr(strPasswordNew).raw(), Bstr(pszNewPasswordId).raw(),
1070 fForce, progress.asOutParam()));
1071 if (SUCCEEDED(hrc))
1072 hrc = showProgress(progress);
1073 if (FAILED(hrc))
1074 {
1075 if (hrc == E_NOTIMPL)
1076 RTMsgError("Encrypt VM operation is not implemented!");
1077 else if (hrc == VBOX_E_NOT_SUPPORTED)
1078 RTMsgError("Encrypt VM operation for this cipher is not implemented yet!");
1079 else if (!progress.isNull())
1080 CHECK_PROGRESS_ERROR(progress, ("Failed to encrypt the VM"));
1081 else
1082 RTMsgError("Failed to encrypt the VM!");
1083 }
1084 }
1085 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1086}
1087
1088static RTEXITCODE handleCheckVMPassword(HandlerArg *a, const char *pszFilenameOrUuid)
1089{
1090 HRESULT hrc;
1091 ComPtr<IMachine> machine;
1092 Utf8Str strPassword;
1093
1094 if (a->argc != 1)
1095 return errorSyntax(Misc::tr("Invalid number of arguments: %d"), a->argc);
1096
1097 if (!RTStrCmp(a->argv[0], "-"))
1098 {
1099 /* Get password from console. */
1100 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter the password:");
1101 if (rcExit == RTEXITCODE_FAILURE)
1102 return rcExit;
1103 }
1104 else
1105 {
1106 RTEXITCODE rcExit = readPasswordFile(a->argv[0], &strPassword);
1107 if (rcExit == RTEXITCODE_FAILURE)
1108 {
1109 RTMsgError("Failed to read password from file");
1110 return rcExit;
1111 }
1112 }
1113
1114 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszFilenameOrUuid).raw(),
1115 machine.asOutParam()));
1116 if (machine)
1117 {
1118 CHECK_ERROR(machine, CheckEncryptionPassword(Bstr(strPassword).raw()));
1119 if (SUCCEEDED(hrc))
1120 RTPrintf("The given password is correct\n");
1121 }
1122 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1123}
1124
1125static const RTGETOPTDEF g_aAddVMOptions[] =
1126{
1127 { "--password", 'p', RTGETOPT_REQ_STRING },
1128 { "--password-id", 'i', RTGETOPT_REQ_STRING }
1129};
1130
1131static RTEXITCODE handleAddVMPassword(HandlerArg *a, const char *pszFilenameOrUuid)
1132{
1133 HRESULT hrc;
1134 ComPtr<IMachine> machine;
1135 const char *pszPassword = NULL;
1136 const char *pszPasswordId = NULL;
1137 Utf8Str strPassword;
1138
1139 int c;
1140 RTGETOPTUNION ValueUnion;
1141 RTGETOPTSTATE GetState;
1142 // start at 0 because main() has hacked both the argc and argv given to us
1143 RTGetOptInit(&GetState, a->argc, a->argv, g_aAddVMOptions, RT_ELEMENTS(g_aAddVMOptions),
1144 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
1145 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1146 {
1147 switch (c)
1148 {
1149 case 'p': // --password
1150 pszPassword = ValueUnion.psz;
1151 break;
1152
1153 case 'i': // --password-id
1154 pszPasswordId = ValueUnion.psz;
1155 break;
1156
1157 default:
1158 if (c > 0)
1159 {
1160 if (RT_C_IS_PRINT(c))
1161 return errorSyntax(Misc::tr("Invalid option -%c"), c);
1162 else
1163 return errorSyntax(Misc::tr("Invalid option case %i"), c);
1164 }
1165 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
1166 return errorSyntax(Misc::tr("unknown option: %s\n"), ValueUnion.psz);
1167 else if (ValueUnion.pDef)
1168 return errorSyntax(Misc::tr("%s: %Rrs"), ValueUnion.pDef->pszLong, c);
1169 else
1170 return errorSyntax(Misc::tr("error: %Rrs"), c);
1171 }
1172 }
1173
1174 if (!pszFilenameOrUuid)
1175 return errorSyntax(Misc::tr("VM name or UUID required"));
1176
1177 if (!pszPassword)
1178 return errorSyntax(Misc::tr("No password specified"));
1179
1180 if (!pszPasswordId)
1181 return errorSyntax(Misc::tr("No password identifier specified"));
1182
1183 if (!RTStrCmp(pszPassword, "-"))
1184 {
1185 /* Get password from console. */
1186 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter the password:");
1187 if (rcExit == RTEXITCODE_FAILURE)
1188 return rcExit;
1189 }
1190 else
1191 {
1192 RTEXITCODE rcExit = readPasswordFile(pszPassword, &strPassword);
1193 if (rcExit == RTEXITCODE_FAILURE)
1194 {
1195 RTMsgError("Failed to read new password from file");
1196 return rcExit;
1197 }
1198 }
1199
1200 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszFilenameOrUuid).raw(),
1201 machine.asOutParam()));
1202 if (machine)
1203 {
1204 ComPtr<IProgress> progress;
1205 CHECK_ERROR(machine, AddEncryptionPassword(Bstr(pszPasswordId).raw(), Bstr(strPassword).raw()));
1206 if (hrc == VBOX_E_PASSWORD_INCORRECT)
1207 RTMsgError("Password incorrect!");
1208 }
1209 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1210}
1211
1212static RTEXITCODE handleRemoveVMPassword(HandlerArg *a, const char *pszFilenameOrUuid)
1213{
1214 HRESULT hrc;
1215 ComPtr<IMachine> machine;
1216
1217 if (a->argc != 1)
1218 return errorSyntax(Misc::tr("Invalid number of arguments: %d"), a->argc);
1219
1220 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszFilenameOrUuid).raw(),
1221 machine.asOutParam()));
1222 if (machine)
1223 {
1224 CHECK_ERROR(machine, RemoveEncryptionPassword(Bstr(a->argv[0]).raw()));
1225 if (hrc == VBOX_E_INVALID_VM_STATE)
1226 RTMsgError("The machine is in online or transient state\n");
1227 }
1228 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1229}
1230
1231RTEXITCODE handleEncryptVM(HandlerArg *a)
1232{
1233 if (a->argc < 2)
1234 return errorSyntax(Misc::tr("subcommand required"));
1235
1236 HandlerArg handlerArg;
1237 handlerArg.argc = a->argc - 2;
1238 handlerArg.argv = &a->argv[2];
1239 handlerArg.virtualBox = a->virtualBox;
1240 handlerArg.session = a->session;
1241 if (!strcmp(a->argv[1], "setencryption"))
1242 return handleSetVMEncryption(&handlerArg, a->argv[0]);
1243 if (!strcmp(a->argv[1], "checkpassword"))
1244 return handleCheckVMPassword(&handlerArg, a->argv[0]);
1245 if (!strcmp(a->argv[1], "addpassword"))
1246 return handleAddVMPassword(&handlerArg, a->argv[0]);
1247 if (!strcmp(a->argv[1], "removepassword"))
1248 return handleRemoveVMPassword(&handlerArg, a->argv[0]);
1249 return errorSyntax(Misc::tr("unknown subcommand"));
1250}
1251#endif /* !VBOX_WITH_FULL_VM_ENCRYPTION */
1252
1253RTEXITCODE handleDiscardState(HandlerArg *a)
1254{
1255 HRESULT hrc;
1256
1257 if (a->argc != 1)
1258 return errorSyntax(Misc::tr("Incorrect number of parameters"));
1259
1260 ComPtr<IMachine> machine;
1261 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
1262 machine.asOutParam()));
1263 if (machine)
1264 {
1265 do
1266 {
1267 /* we have to open a session for this task */
1268 CHECK_ERROR_BREAK(machine, LockMachine(a->session, LockType_Write));
1269 do
1270 {
1271 ComPtr<IMachine> sessionMachine;
1272 CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
1273 CHECK_ERROR_BREAK(sessionMachine, DiscardSavedState(true /* fDeleteFile */));
1274 } while (0);
1275 CHECK_ERROR_BREAK(a->session, UnlockMachine());
1276 } while (0);
1277 }
1278
1279 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1280}
1281
1282RTEXITCODE handleAdoptState(HandlerArg *a)
1283{
1284 HRESULT hrc;
1285
1286 if (a->argc != 2)
1287 return errorSyntax(Misc::tr("Incorrect number of parameters"));
1288
1289 ComPtr<IMachine> machine;
1290 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
1291 machine.asOutParam()));
1292 if (machine)
1293 {
1294 char szStateFileAbs[RTPATH_MAX] = "";
1295 int vrc = RTPathAbs(a->argv[1], szStateFileAbs, sizeof(szStateFileAbs));
1296 if (RT_FAILURE(vrc))
1297 return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("Cannot convert filename \"%s\" to absolute path: %Rrc"),
1298 a->argv[0], vrc);
1299
1300 do
1301 {
1302 /* we have to open a session for this task */
1303 CHECK_ERROR_BREAK(machine, LockMachine(a->session, LockType_Write));
1304 do
1305 {
1306 ComPtr<IMachine> sessionMachine;
1307 CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
1308 CHECK_ERROR_BREAK(sessionMachine, AdoptSavedState(Bstr(szStateFileAbs).raw()));
1309 } while (0);
1310 CHECK_ERROR_BREAK(a->session, UnlockMachine());
1311 } while (0);
1312 }
1313
1314 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1315}
1316
1317RTEXITCODE handleGetExtraData(HandlerArg *a)
1318{
1319 HRESULT hrc = S_OK;
1320
1321 if (a->argc > 2 || a->argc < 1)
1322 return errorSyntax(Misc::tr("Incorrect number of parameters"));
1323
1324 /* global data? */
1325 if (!strcmp(a->argv[0], "global"))
1326 {
1327 /* enumeration? */
1328 if (a->argc < 2 || !strcmp(a->argv[1], "enumerate"))
1329 {
1330 SafeArray<BSTR> aKeys;
1331 CHECK_ERROR(a->virtualBox, GetExtraDataKeys(ComSafeArrayAsOutParam(aKeys)));
1332
1333 for (size_t i = 0;
1334 i < aKeys.size();
1335 ++i)
1336 {
1337 Bstr bstrKey(aKeys[i]);
1338 Bstr bstrValue;
1339 CHECK_ERROR(a->virtualBox, GetExtraData(bstrKey.raw(),
1340 bstrValue.asOutParam()));
1341
1342 RTPrintf(Misc::tr("Key: %ls, Value: %ls\n"), bstrKey.raw(), bstrValue.raw());
1343 }
1344 }
1345 else
1346 {
1347 Bstr value;
1348 CHECK_ERROR(a->virtualBox, GetExtraData(Bstr(a->argv[1]).raw(),
1349 value.asOutParam()));
1350 if (!value.isEmpty())
1351 RTPrintf(Misc::tr("Value: %ls\n"), value.raw());
1352 else
1353 RTPrintf(Misc::tr("No value set!\n"));
1354 }
1355 }
1356 else
1357 {
1358 ComPtr<IMachine> machine;
1359 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
1360 machine.asOutParam()));
1361 if (machine)
1362 {
1363 /* enumeration? */
1364 if (a->argc < 2 || !strcmp(a->argv[1], "enumerate"))
1365 {
1366 SafeArray<BSTR> aKeys;
1367 CHECK_ERROR(machine, GetExtraDataKeys(ComSafeArrayAsOutParam(aKeys)));
1368
1369 for (size_t i = 0;
1370 i < aKeys.size();
1371 ++i)
1372 {
1373 Bstr bstrKey(aKeys[i]);
1374 Bstr bstrValue;
1375 CHECK_ERROR(machine, GetExtraData(bstrKey.raw(),
1376 bstrValue.asOutParam()));
1377
1378 RTPrintf(Misc::tr("Key: %ls, Value: %ls\n"), bstrKey.raw(), bstrValue.raw());
1379 }
1380 }
1381 else
1382 {
1383 Bstr value;
1384 CHECK_ERROR(machine, GetExtraData(Bstr(a->argv[1]).raw(),
1385 value.asOutParam()));
1386 if (!value.isEmpty())
1387 RTPrintf(Misc::tr("Value: %ls\n"), value.raw());
1388 else
1389 RTPrintf(Misc::tr("No value set!\n"));
1390 }
1391 }
1392 }
1393 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1394}
1395
1396RTEXITCODE handleSetExtraData(HandlerArg *a)
1397{
1398 HRESULT hrc = S_OK;
1399
1400 if (a->argc < 2)
1401 return errorSyntax(Misc::tr("Not enough parameters"));
1402
1403 /* global data? */
1404 if (!strcmp(a->argv[0], "global"))
1405 {
1406 /** @todo passing NULL is deprecated */
1407 if (a->argc < 3)
1408 CHECK_ERROR(a->virtualBox, SetExtraData(Bstr(a->argv[1]).raw(),
1409 NULL));
1410 else if (a->argc == 3)
1411 CHECK_ERROR(a->virtualBox, SetExtraData(Bstr(a->argv[1]).raw(),
1412 Bstr(a->argv[2]).raw()));
1413 else
1414 return errorSyntax(Misc::tr("Too many parameters"));
1415 }
1416 else
1417 {
1418 ComPtr<IMachine> machine;
1419 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
1420 machine.asOutParam()));
1421 if (machine)
1422 {
1423 /* open an existing session for the VM */
1424 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
1425 /* get the session machine */
1426 ComPtr<IMachine> sessionMachine;
1427 CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE);
1428 /** @todo passing NULL is deprecated */
1429 if (a->argc < 3)
1430 CHECK_ERROR(sessionMachine, SetExtraData(Bstr(a->argv[1]).raw(),
1431 NULL));
1432 else if (a->argc == 3)
1433 CHECK_ERROR(sessionMachine, SetExtraData(Bstr(a->argv[1]).raw(),
1434 Bstr(a->argv[2]).raw()));
1435 else
1436 return errorSyntax(Misc::tr("Too many parameters"));
1437 }
1438 }
1439 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1440}
1441
1442RTEXITCODE handleSetProperty(HandlerArg *a) /** @todo r=andy Rename this to handle[Get|Set]SystemProperty? */
1443{
1444 HRESULT hrc;
1445
1446 /* there must be two arguments: property name and value */
1447 if (a->argc != 2)
1448 return errorSyntax(Misc::tr("Incorrect number of parameters"));
1449
1450 ComPtr<ISystemProperties> systemProperties;
1451 a->virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam());
1452
1453 ComPtr<IPlatformProperties> platformProperties;
1454 systemProperties->COMGETTER(Platform)(platformProperties.asOutParam());
1455
1456 if (!strcmp(a->argv[0], "machinefolder"))
1457 {
1458 /* reset to default? */
1459 if (!strcmp(a->argv[1], "default"))
1460 CHECK_ERROR(systemProperties, COMSETTER(DefaultMachineFolder)(NULL));
1461 else
1462 CHECK_ERROR(systemProperties, COMSETTER(DefaultMachineFolder)(Bstr(a->argv[1]).raw()));
1463 }
1464 else if (!strcmp(a->argv[0], "hwvirtexclusive"))
1465 {
1466 bool fHwVirtExclusive;
1467
1468 if (!strcmp(a->argv[1], "on"))
1469 fHwVirtExclusive = true;
1470 else if (!strcmp(a->argv[1], "off"))
1471 fHwVirtExclusive = false;
1472 else
1473 return errorArgument(Misc::tr("Invalid hwvirtexclusive argument '%s'"), a->argv[1]);
1474 CHECK_ERROR(platformProperties, COMSETTER(ExclusiveHwVirt)(fHwVirtExclusive));
1475 }
1476 else if ( !strcmp(a->argv[0], "vrdeauthlibrary")
1477 || !strcmp(a->argv[0], "vrdpauthlibrary"))
1478 {
1479 if (!strcmp(a->argv[0], "vrdpauthlibrary"))
1480 RTStrmPrintf(g_pStdErr, Misc::tr("Warning: 'vrdpauthlibrary' is deprecated. Use 'vrdeauthlibrary'.\n"));
1481
1482 /* reset to default? */
1483 if (!strcmp(a->argv[1], "default"))
1484 CHECK_ERROR(systemProperties, COMSETTER(VRDEAuthLibrary)(NULL));
1485 else
1486 CHECK_ERROR(systemProperties, COMSETTER(VRDEAuthLibrary)(Bstr(a->argv[1]).raw()));
1487 }
1488 else if (!strcmp(a->argv[0], "websrvauthlibrary"))
1489 {
1490 /* reset to default? */
1491 if (!strcmp(a->argv[1], "default"))
1492 CHECK_ERROR(systemProperties, COMSETTER(WebServiceAuthLibrary)(NULL));
1493 else
1494 CHECK_ERROR(systemProperties, COMSETTER(WebServiceAuthLibrary)(Bstr(a->argv[1]).raw()));
1495 }
1496 else if (!strcmp(a->argv[0], "vrdeextpack"))
1497 {
1498 /* disable? */
1499 if (!strcmp(a->argv[1], "null"))
1500 CHECK_ERROR(systemProperties, COMSETTER(DefaultVRDEExtPack)(NULL));
1501 else
1502 CHECK_ERROR(systemProperties, COMSETTER(DefaultVRDEExtPack)(Bstr(a->argv[1]).raw()));
1503 }
1504 else if (!strcmp(a->argv[0], "loghistorycount"))
1505 {
1506 uint32_t uVal;
1507 int vrc;
1508 vrc = RTStrToUInt32Ex(a->argv[1], NULL, 0, &uVal);
1509 if (vrc != VINF_SUCCESS)
1510 return errorArgument(Misc::tr("Error parsing Log history count '%s'"), a->argv[1]);
1511 CHECK_ERROR(systemProperties, COMSETTER(LogHistoryCount)(uVal));
1512 }
1513 else if (!strcmp(a->argv[0], "autostartdbpath"))
1514 {
1515 /* disable? */
1516 if (!strcmp(a->argv[1], "null"))
1517 CHECK_ERROR(systemProperties, COMSETTER(AutostartDatabasePath)(NULL));
1518 else
1519 CHECK_ERROR(systemProperties, COMSETTER(AutostartDatabasePath)(Bstr(a->argv[1]).raw()));
1520 }
1521 else if (!strcmp(a->argv[0], "defaultfrontend"))
1522 {
1523 Bstr bstrDefaultFrontend(a->argv[1]);
1524 if (!strcmp(a->argv[1], "default"))
1525 bstrDefaultFrontend.setNull();
1526 CHECK_ERROR(systemProperties, COMSETTER(DefaultFrontend)(bstrDefaultFrontend.raw()));
1527 }
1528 else if (!strcmp(a->argv[0], "logginglevel"))
1529 {
1530 Bstr bstrLoggingLevel(a->argv[1]);
1531 if (!strcmp(a->argv[1], "default"))
1532 bstrLoggingLevel.setNull();
1533 CHECK_ERROR(systemProperties, COMSETTER(LoggingLevel)(bstrLoggingLevel.raw()));
1534 }
1535 else if (!strcmp(a->argv[0], "proxymode"))
1536 {
1537 ProxyMode_T enmProxyMode;
1538 if (!RTStrICmpAscii(a->argv[1], "system"))
1539 enmProxyMode = ProxyMode_System;
1540 else if (!RTStrICmpAscii(a->argv[1], "noproxy"))
1541 enmProxyMode = ProxyMode_NoProxy;
1542 else if (!RTStrICmpAscii(a->argv[1], "manual"))
1543 enmProxyMode = ProxyMode_Manual;
1544 else
1545 return errorArgument(Misc::tr("Unknown proxy mode: '%s'"), a->argv[1]);
1546 CHECK_ERROR(systemProperties, COMSETTER(ProxyMode)(enmProxyMode));
1547 }
1548 else if (!strcmp(a->argv[0], "proxyurl"))
1549 {
1550 Bstr bstrProxyUrl(a->argv[1]);
1551 CHECK_ERROR(systemProperties, COMSETTER(ProxyURL)(bstrProxyUrl.raw()));
1552 }
1553#ifdef VBOX_WITH_MAIN_NLS
1554 else if (!strcmp(a->argv[0], "language"))
1555 {
1556 Bstr bstrLanguage(a->argv[1]);
1557 CHECK_ERROR(systemProperties, COMSETTER(LanguageId)(bstrLanguage.raw()));
1558
1559 /* Kudge alert! Make sure the language change notification is processed,
1560 otherwise it may arrive as (XP)COM shuts down and cause
1561 trouble in debug builds. */
1562# ifdef DEBUG
1563 uint64_t const tsStart = RTTimeNanoTS();
1564# endif
1565 unsigned cMsgs = 0;
1566 int vrc;
1567 while ( RT_SUCCESS(vrc = NativeEventQueue::getMainEventQueue()->processEventQueue(32 /*ms*/))
1568 || vrc == VERR_INTERRUPTED)
1569 cMsgs++;
1570# ifdef DEBUG
1571 RTPrintf("vrc=%Rrc cMsgs=%u nsElapsed=%'RU64\n", vrc, cMsgs, RTTimeNanoTS() - tsStart);
1572# endif
1573 }
1574#endif
1575 else
1576 return errorSyntax(Misc::tr("Invalid parameter '%s'"), a->argv[0]);
1577
1578 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1579}
1580
1581/**
1582 * sharedfolder add
1583 */
1584static RTEXITCODE handleSharedFolderAdd(HandlerArg *a)
1585{
1586 /*
1587 * Parse arguments (argv[0] == subcommand).
1588 */
1589 static const RTGETOPTDEF s_aAddOptions[] =
1590 {
1591 { "--name", 'n', RTGETOPT_REQ_STRING },
1592 { "-name", 'n', RTGETOPT_REQ_STRING }, // deprecated
1593 { "--hostpath", 'p', RTGETOPT_REQ_STRING },
1594 { "-hostpath", 'p', RTGETOPT_REQ_STRING }, // deprecated
1595 { "--readonly", 'r', RTGETOPT_REQ_NOTHING },
1596 { "-readonly", 'r', RTGETOPT_REQ_NOTHING }, // deprecated
1597 { "--transient", 't', RTGETOPT_REQ_NOTHING },
1598 { "-transient", 't', RTGETOPT_REQ_NOTHING }, // deprecated
1599 { "--automount", 'a', RTGETOPT_REQ_NOTHING },
1600 { "-automount", 'a', RTGETOPT_REQ_NOTHING }, // deprecated
1601 { "--auto-mount-point", 'm', RTGETOPT_REQ_STRING },
1602 };
1603 const char *pszMachineName = NULL;
1604 const char *pszName = NULL;
1605 const char *pszHostPath = NULL;
1606 bool fTransient = false;
1607 bool fWritable = true;
1608 bool fAutoMount = false;
1609 const char *pszAutoMountPoint = "";
1610
1611 RTGETOPTSTATE GetState;
1612 RTGetOptInit(&GetState, a->argc, a->argv, s_aAddOptions, RT_ELEMENTS(s_aAddOptions), 1 /*iFirst*/, 0 /*fFlags*/);
1613 int c;
1614 RTGETOPTUNION ValueUnion;
1615 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1616 {
1617 switch (c)
1618 {
1619 case 'n':
1620 pszName = ValueUnion.psz;
1621 break;
1622 case 'p':
1623 pszHostPath = ValueUnion.psz;
1624 break;
1625 case 'r':
1626 fWritable = false;
1627 break;
1628 case 't':
1629 fTransient = true;
1630 break;
1631 case 'a':
1632 fAutoMount = true;
1633 break;
1634 case 'm':
1635 pszAutoMountPoint = ValueUnion.psz;
1636 break;
1637 case VINF_GETOPT_NOT_OPTION:
1638 if (pszMachineName)
1639 return errorArgument(Misc::tr("Machine name is given more than once: first '%s', then '%s'"),
1640 pszMachineName, ValueUnion.psz);
1641 pszMachineName = ValueUnion.psz;
1642 break;
1643 default:
1644 return errorGetOpt(c, &ValueUnion);
1645 }
1646 }
1647
1648 if (!pszMachineName)
1649 return errorSyntax(Misc::tr("No machine was specified"));
1650
1651 if (!pszName)
1652 return errorSyntax(Misc::tr("No shared folder name (--name) was given"));
1653 if (strchr(pszName, ' '))
1654 return errorSyntax(Misc::tr("Invalid shared folder name '%s': contains space"), pszName);
1655 if (strchr(pszName, '\t'))
1656 return errorSyntax(Misc::tr("Invalid shared folder name '%s': contains tabs"), pszName);
1657 if (strchr(pszName, '\n') || strchr(pszName, '\r'))
1658 return errorSyntax(Misc::tr("Invalid shared folder name '%s': contains newline"), pszName);
1659
1660 if (!pszHostPath)
1661 return errorSyntax(Misc::tr("No host path (--hostpath) was given"));
1662 char szAbsHostPath[RTPATH_MAX];
1663 int vrc = RTPathAbs(pszHostPath, szAbsHostPath, sizeof(szAbsHostPath));
1664 if (RT_FAILURE(vrc))
1665 return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("RTAbsPath failed on '%s': %Rrc"), pszHostPath, vrc);
1666
1667 /*
1668 * Done parsing, do some work.
1669 */
1670 ComPtr<IMachine> ptrMachine;
1671 CHECK_ERROR2I_RET(a->virtualBox, FindMachine(Bstr(pszMachineName).raw(), ptrMachine.asOutParam()), RTEXITCODE_FAILURE);
1672 AssertReturn(ptrMachine.isNotNull(), RTEXITCODE_FAILURE);
1673
1674 HRESULT hrc;
1675 if (fTransient)
1676 {
1677 /* open an existing session for the VM */
1678 CHECK_ERROR2I_RET(ptrMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
1679
1680 /* get the session machine */
1681 ComPtr<IMachine> ptrSessionMachine;
1682 CHECK_ERROR2I_RET(a->session, COMGETTER(Machine)(ptrSessionMachine.asOutParam()), RTEXITCODE_FAILURE);
1683
1684 /* get the session console */
1685 ComPtr<IConsole> ptrConsole;
1686 CHECK_ERROR2I_RET(a->session, COMGETTER(Console)(ptrConsole.asOutParam()), RTEXITCODE_FAILURE);
1687 if (ptrConsole.isNull())
1688 return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("Machine '%s' is not currently running."), pszMachineName);
1689
1690 CHECK_ERROR2(hrc, ptrConsole, CreateSharedFolder(Bstr(pszName).raw(), Bstr(szAbsHostPath).raw(),
1691 fWritable, fAutoMount, Bstr(pszAutoMountPoint).raw()));
1692 a->session->UnlockMachine();
1693 }
1694 else
1695 {
1696 /* open a session for the VM */
1697 CHECK_ERROR2I_RET(ptrMachine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE);
1698
1699 /* get the mutable session machine */
1700 ComPtr<IMachine> ptrSessionMachine;
1701 CHECK_ERROR2I_RET(a->session, COMGETTER(Machine)(ptrSessionMachine.asOutParam()), RTEXITCODE_FAILURE);
1702
1703 CHECK_ERROR2(hrc, ptrSessionMachine, CreateSharedFolder(Bstr(pszName).raw(), Bstr(szAbsHostPath).raw(),
1704 fWritable, fAutoMount, Bstr(pszAutoMountPoint).raw()));
1705 if (SUCCEEDED(hrc))
1706 {
1707 CHECK_ERROR2(hrc, ptrSessionMachine, SaveSettings());
1708 }
1709
1710 a->session->UnlockMachine();
1711 }
1712
1713 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1714}
1715
1716/**
1717 * sharedfolder remove
1718 */
1719static RTEXITCODE handleSharedFolderRemove(HandlerArg *a)
1720{
1721 /*
1722 * Parse arguments (argv[0] == subcommand).
1723 */
1724 static const RTGETOPTDEF s_aRemoveOptions[] =
1725 {
1726 { "--name", 'n', RTGETOPT_REQ_STRING },
1727 { "-name", 'n', RTGETOPT_REQ_STRING }, // deprecated
1728 { "--transient", 't', RTGETOPT_REQ_NOTHING },
1729 { "-transient", 't', RTGETOPT_REQ_NOTHING }, // deprecated
1730 };
1731 const char *pszMachineName = NULL;
1732 const char *pszName = NULL;
1733 bool fTransient = false;
1734
1735 RTGETOPTSTATE GetState;
1736 RTGetOptInit(&GetState, a->argc, a->argv, s_aRemoveOptions, RT_ELEMENTS(s_aRemoveOptions), 1 /*iFirst*/, 0 /*fFlags*/);
1737 int c;
1738 RTGETOPTUNION ValueUnion;
1739 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1740 {
1741 switch (c)
1742 {
1743 case 'n':
1744 pszName = ValueUnion.psz;
1745 break;
1746 case 't':
1747 fTransient = true;
1748 break;
1749 case VINF_GETOPT_NOT_OPTION:
1750 if (pszMachineName)
1751 return errorArgument(Misc::tr("Machine name is given more than once: first '%s', then '%s'"),
1752 pszMachineName, ValueUnion.psz);
1753 pszMachineName = ValueUnion.psz;
1754 break;
1755 default:
1756 return errorGetOpt(c, &ValueUnion);
1757 }
1758 }
1759
1760 if (!pszMachineName)
1761 return errorSyntax(Misc::tr("No machine was specified"));
1762 if (!pszName)
1763 return errorSyntax(Misc::tr("No shared folder name (--name) was given"));
1764
1765 /*
1766 * Done parsing, do some real work.
1767 */
1768 ComPtr<IMachine> ptrMachine;
1769 CHECK_ERROR2I_RET(a->virtualBox, FindMachine(Bstr(pszMachineName).raw(), ptrMachine.asOutParam()), RTEXITCODE_FAILURE);
1770 AssertReturn(ptrMachine.isNotNull(), RTEXITCODE_FAILURE);
1771
1772 HRESULT hrc;
1773 if (fTransient)
1774 {
1775 /* open an existing session for the VM */
1776 CHECK_ERROR2I_RET(ptrMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
1777 /* get the session machine */
1778 ComPtr<IMachine> ptrSessionMachine;
1779 CHECK_ERROR2I_RET(a->session, COMGETTER(Machine)(ptrSessionMachine.asOutParam()), RTEXITCODE_FAILURE);
1780 /* get the session console */
1781 ComPtr<IConsole> ptrConsole;
1782 CHECK_ERROR2I_RET(a->session, COMGETTER(Console)(ptrConsole.asOutParam()), RTEXITCODE_FAILURE);
1783 if (ptrConsole.isNull())
1784 return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("Machine '%s' is not currently running.\n"), pszMachineName);
1785
1786 CHECK_ERROR2(hrc, ptrConsole, RemoveSharedFolder(Bstr(pszName).raw()));
1787
1788 a->session->UnlockMachine();
1789 }
1790 else
1791 {
1792 /* open a session for the VM */
1793 CHECK_ERROR2I_RET(ptrMachine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE);
1794
1795 /* get the mutable session machine */
1796 ComPtr<IMachine> ptrSessionMachine;
1797 CHECK_ERROR2I_RET(a->session, COMGETTER(Machine)(ptrSessionMachine.asOutParam()), RTEXITCODE_FAILURE);
1798
1799 CHECK_ERROR2(hrc, ptrSessionMachine, RemoveSharedFolder(Bstr(pszName).raw()));
1800
1801 /* commit and close the session */
1802 if (SUCCEEDED(hrc))
1803 {
1804 CHECK_ERROR2(hrc, ptrSessionMachine, SaveSettings());
1805 }
1806 a->session->UnlockMachine();
1807 }
1808
1809 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1810}
1811
1812
1813RTEXITCODE handleSharedFolder(HandlerArg *a)
1814{
1815 if (a->argc < 1)
1816 return errorSyntax(Misc::tr("Not enough parameters"));
1817
1818 if (!strcmp(a->argv[0], "add"))
1819 {
1820 setCurrentSubcommand(HELP_SCOPE_SHAREDFOLDER_ADD);
1821 return handleSharedFolderAdd(a);
1822 }
1823
1824 if (!strcmp(a->argv[0], "remove"))
1825 {
1826 setCurrentSubcommand(HELP_SCOPE_SHAREDFOLDER_REMOVE);
1827 return handleSharedFolderRemove(a);
1828 }
1829
1830 return errorUnknownSubcommand(a->argv[0]);
1831}
1832
1833RTEXITCODE handleExtPack(HandlerArg *a)
1834{
1835 if (a->argc < 1)
1836 return errorNoSubcommand();
1837
1838 ComObjPtr<IExtPackManager> ptrExtPackMgr;
1839 CHECK_ERROR2I_RET(a->virtualBox, COMGETTER(ExtensionPackManager)(ptrExtPackMgr.asOutParam()), RTEXITCODE_FAILURE);
1840
1841 RTGETOPTSTATE GetState;
1842 RTGETOPTUNION ValueUnion;
1843 int ch;
1844 HRESULT hrc = S_OK;
1845
1846 if (!strcmp(a->argv[0], "install"))
1847 {
1848 setCurrentSubcommand(HELP_SCOPE_EXTPACK_INSTALL);
1849 const char *pszName = NULL;
1850 bool fReplace = false;
1851
1852 static const RTGETOPTDEF s_aInstallOptions[] =
1853 {
1854 { "--replace", 'r', RTGETOPT_REQ_NOTHING },
1855 { "--accept-license", 'a', RTGETOPT_REQ_STRING },
1856 };
1857
1858 RTCList<RTCString> lstLicenseHashes;
1859 RTGetOptInit(&GetState, a->argc, a->argv, s_aInstallOptions, RT_ELEMENTS(s_aInstallOptions), 1, 0 /*fFlags*/);
1860 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1861 {
1862 switch (ch)
1863 {
1864 case 'r':
1865 fReplace = true;
1866 break;
1867
1868 case 'a':
1869 lstLicenseHashes.append(ValueUnion.psz);
1870 lstLicenseHashes[lstLicenseHashes.size() - 1].toLower();
1871 break;
1872
1873 case VINF_GETOPT_NOT_OPTION:
1874 if (pszName)
1875 return errorSyntax(Misc::tr("Too many extension pack names given to \"extpack uninstall\""));
1876 pszName = ValueUnion.psz;
1877 break;
1878
1879 default:
1880 return errorGetOpt(ch, &ValueUnion);
1881 }
1882 }
1883 if (!pszName)
1884 return errorSyntax(Misc::tr("No extension pack name was given to \"extpack install\""));
1885
1886 char szPath[RTPATH_MAX];
1887 int vrc = RTPathAbs(pszName, szPath, sizeof(szPath));
1888 if (RT_FAILURE(vrc))
1889 return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("RTPathAbs(%s,,) failed with vrc=%Rrc"), pszName, vrc);
1890
1891 Bstr bstrTarball(szPath);
1892 Bstr bstrName;
1893 ComPtr<IExtPackFile> ptrExtPackFile;
1894 CHECK_ERROR2I_RET(ptrExtPackMgr, OpenExtPackFile(bstrTarball.raw(), ptrExtPackFile.asOutParam()), RTEXITCODE_FAILURE);
1895 CHECK_ERROR2I_RET(ptrExtPackFile, COMGETTER(Name)(bstrName.asOutParam()), RTEXITCODE_FAILURE);
1896 BOOL fShowLicense = true;
1897 CHECK_ERROR2I_RET(ptrExtPackFile, COMGETTER(ShowLicense)(&fShowLicense), RTEXITCODE_FAILURE);
1898 if (fShowLicense)
1899 {
1900 Bstr bstrLicense;
1901 CHECK_ERROR2I_RET(ptrExtPackFile,
1902 QueryLicense(Bstr("").raw() /* PreferredLocale */,
1903 Bstr("").raw() /* PreferredLanguage */,
1904 Bstr("txt").raw() /* Format */,
1905 bstrLicense.asOutParam()), RTEXITCODE_FAILURE);
1906 Utf8Str strLicense(bstrLicense);
1907 uint8_t abHash[RTSHA256_HASH_SIZE];
1908 char szDigest[RTSHA256_DIGEST_LEN + 1];
1909 RTSha256(strLicense.c_str(), strLicense.length(), abHash);
1910 vrc = RTSha256ToString(abHash, szDigest, sizeof(szDigest));
1911 AssertRCStmt(vrc, szDigest[0] = '\0');
1912 if (lstLicenseHashes.contains(szDigest))
1913 RTPrintf(Misc::tr("License accepted.\n"));
1914 else
1915 {
1916 RTPrintf("%s\n", strLicense.c_str());
1917 RTPrintf(Misc::tr("Do you agree to these license terms and conditions (y/n)? "));
1918 ch = RTStrmGetCh(g_pStdIn);
1919 RTPrintf("\n");
1920 if (ch != 'y' && ch != 'Y')
1921 {
1922 RTPrintf(Misc::tr("Installation of \"%ls\" aborted.\n"), bstrName.raw());
1923 return RTEXITCODE_FAILURE;
1924 }
1925 if (szDigest[0])
1926 RTPrintf(Misc::tr("License accepted. For batch installation add\n"
1927 "--accept-license=%s\n"
1928 "to the VBoxManage command line.\n\n"), szDigest);
1929 }
1930 }
1931 ComPtr<IProgress> ptrProgress;
1932 CHECK_ERROR2I_RET(ptrExtPackFile, Install(fReplace, NULL, ptrProgress.asOutParam()), RTEXITCODE_FAILURE);
1933 hrc = showProgress(ptrProgress);
1934 CHECK_PROGRESS_ERROR_RET(ptrProgress, (Misc::tr("Failed to install \"%s\""), szPath), RTEXITCODE_FAILURE);
1935
1936 RTPrintf(Misc::tr("Successfully installed \"%ls\".\n"), bstrName.raw());
1937 }
1938 else if (!strcmp(a->argv[0], "uninstall"))
1939 {
1940 setCurrentSubcommand(HELP_SCOPE_EXTPACK_UNINSTALL);
1941 const char *pszName = NULL;
1942 bool fForced = false;
1943
1944 static const RTGETOPTDEF s_aUninstallOptions[] =
1945 {
1946 { "--force", 'f', RTGETOPT_REQ_NOTHING },
1947 };
1948
1949 RTGetOptInit(&GetState, a->argc, a->argv, s_aUninstallOptions, RT_ELEMENTS(s_aUninstallOptions), 1, 0);
1950 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1951 {
1952 switch (ch)
1953 {
1954 case 'f':
1955 fForced = true;
1956 break;
1957
1958 case VINF_GETOPT_NOT_OPTION:
1959 if (pszName)
1960 return errorSyntax(Misc::tr("Too many extension pack names given to \"extpack uninstall\""));
1961 pszName = ValueUnion.psz;
1962 break;
1963
1964 default:
1965 return errorGetOpt(ch, &ValueUnion);
1966 }
1967 }
1968 if (!pszName)
1969 return errorSyntax(Misc::tr("No extension pack name was given to \"extpack uninstall\""));
1970
1971 Bstr bstrName(pszName);
1972 ComPtr<IProgress> ptrProgress;
1973 CHECK_ERROR2I_RET(ptrExtPackMgr, Uninstall(bstrName.raw(), fForced, NULL, ptrProgress.asOutParam()), RTEXITCODE_FAILURE);
1974 hrc = showProgress(ptrProgress);
1975 CHECK_PROGRESS_ERROR_RET(ptrProgress, (Misc::tr("Failed to uninstall \"%s\""), pszName), RTEXITCODE_FAILURE);
1976
1977 RTPrintf(Misc::tr("Successfully uninstalled \"%s\".\n"), pszName);
1978 }
1979 else if (!strcmp(a->argv[0], "cleanup"))
1980 {
1981 setCurrentSubcommand(HELP_SCOPE_EXTPACK_CLEANUP);
1982 if (a->argc > 1)
1983 return errorTooManyParameters(&a->argv[1]);
1984 CHECK_ERROR2I_RET(ptrExtPackMgr, Cleanup(), RTEXITCODE_FAILURE);
1985 RTPrintf(Misc::tr("Successfully performed extension pack cleanup\n"));
1986 }
1987 else
1988 return errorUnknownSubcommand(a->argv[0]);
1989
1990 return RTEXITCODE_SUCCESS;
1991}
1992
1993static RTEXITCODE handleUnattendedDetect(HandlerArg *a)
1994{
1995 HRESULT hrc;
1996
1997 /*
1998 * Options. We work directly on an IUnattended instace while parsing
1999 * the options. This saves a lot of extra clutter.
2000 */
2001 bool fMachineReadable = false;
2002 char szIsoPath[RTPATH_MAX];
2003 szIsoPath[0] = '\0';
2004
2005 /*
2006 * Parse options.
2007 */
2008 static const RTGETOPTDEF s_aOptions[] =
2009 {
2010 { "--iso", 'i', RTGETOPT_REQ_STRING },
2011 { "--machine-readable", 'M', RTGETOPT_REQ_NOTHING },
2012 };
2013
2014 RTGETOPTSTATE GetState;
2015 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2016 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
2017
2018 int c;
2019 RTGETOPTUNION ValueUnion;
2020 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2021 {
2022 switch (c)
2023 {
2024 case 'i': // --iso
2025 vrc = RTPathAbs(ValueUnion.psz, szIsoPath, sizeof(szIsoPath));
2026 if (RT_FAILURE(vrc))
2027 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2028 break;
2029
2030 case 'M': // --machine-readable.
2031 fMachineReadable = true;
2032 break;
2033
2034 default:
2035 return errorGetOpt(c, &ValueUnion);
2036 }
2037 }
2038
2039 /*
2040 * Check for required stuff.
2041 */
2042 if (szIsoPath[0] == '\0')
2043 return errorSyntax(Misc::tr("No ISO specified"));
2044
2045 /*
2046 * Do the job.
2047 */
2048 ComPtr<IUnattended> ptrUnattended;
2049 CHECK_ERROR2_RET(hrc, a->virtualBox, CreateUnattendedInstaller(ptrUnattended.asOutParam()), RTEXITCODE_FAILURE);
2050 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(IsoPath)(Bstr(szIsoPath).raw()), RTEXITCODE_FAILURE);
2051 CHECK_ERROR2(hrc, ptrUnattended, DetectIsoOS());
2052 RTEXITCODE rcExit = SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2053
2054 /*
2055 * Retrieve the results.
2056 */
2057 Bstr bstrDetectedOSTypeId;
2058 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSTypeId)(bstrDetectedOSTypeId.asOutParam()), RTEXITCODE_FAILURE);
2059 Bstr bstrDetectedVersion;
2060 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSVersion)(bstrDetectedVersion.asOutParam()), RTEXITCODE_FAILURE);
2061 Bstr bstrDetectedFlavor;
2062 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSFlavor)(bstrDetectedFlavor.asOutParam()), RTEXITCODE_FAILURE);
2063 Bstr bstrDetectedLanguages;
2064 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSLanguages)(bstrDetectedLanguages.asOutParam()), RTEXITCODE_FAILURE);
2065 Bstr bstrDetectedHints;
2066 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSHints)(bstrDetectedHints.asOutParam()), RTEXITCODE_FAILURE);
2067 SafeArray<BSTR> aImageNames;
2068 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedImageNames)(ComSafeArrayAsOutParam(aImageNames)), RTEXITCODE_FAILURE);
2069 SafeArray<ULONG> aImageIndices;
2070 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedImageIndices)(ComSafeArrayAsOutParam(aImageIndices)), RTEXITCODE_FAILURE);
2071 Assert(aImageNames.size() == aImageIndices.size());
2072 BOOL fInstallSupported = FALSE;
2073 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(IsUnattendedInstallSupported)(&fInstallSupported), RTEXITCODE_FAILURE);
2074
2075 if (fMachineReadable)
2076 {
2077 outputMachineReadableString("OSTypeId", &bstrDetectedOSTypeId);
2078 outputMachineReadableString("OSVersion", &bstrDetectedVersion);
2079 outputMachineReadableString("OSFlavor", &bstrDetectedFlavor);
2080 outputMachineReadableString("OSLanguages", &bstrDetectedLanguages);
2081 outputMachineReadableString("OSHints", &bstrDetectedHints);
2082 for (size_t i = 0; i < aImageNames.size(); i++)
2083 {
2084 Bstr const bstrName = aImageNames[i];
2085 outputMachineReadableStringWithFmtName(&bstrName, false, "ImageIndex%u", aImageIndices[i]);
2086 }
2087 outputMachineReadableBool("IsInstallSupported", &fInstallSupported);
2088 }
2089 else
2090 {
2091 RTMsgInfo(Misc::tr("Detected '%s' to be:\n"), szIsoPath);
2092 RTPrintf(Misc::tr(" OS TypeId = %ls\n"
2093 " OS Version = %ls\n"
2094 " OS Flavor = %ls\n"
2095 " OS Languages = %ls\n"
2096 " OS Hints = %ls\n"),
2097 bstrDetectedOSTypeId.raw(),
2098 bstrDetectedVersion.raw(),
2099 bstrDetectedFlavor.raw(),
2100 bstrDetectedLanguages.raw(),
2101 bstrDetectedHints.raw());
2102 for (size_t i = 0; i < aImageNames.size(); i++)
2103 RTPrintf(" Image #%-3u = %ls\n", aImageIndices[i], aImageNames[i]);
2104 if (fInstallSupported)
2105 RTPrintf(Misc::tr(" Unattended installation supported = yes\n"));
2106 else
2107 RTPrintf(Misc::tr(" Unattended installation supported = no\n"));
2108 }
2109
2110 return rcExit;
2111}
2112
2113static RTEXITCODE handleUnattendedInstall(HandlerArg *a)
2114{
2115 HRESULT hrc;
2116 char szAbsPath[RTPATH_MAX];
2117
2118 /*
2119 * Options. We work directly on an IUnattended instance while parsing
2120 * the options. This saves a lot of extra clutter.
2121 */
2122 ComPtr<IUnattended> ptrUnattended;
2123 CHECK_ERROR2_RET(hrc, a->virtualBox, CreateUnattendedInstaller(ptrUnattended.asOutParam()), RTEXITCODE_FAILURE);
2124 RTCList<RTCString> arrPackageSelectionAdjustments;
2125 ComPtr<IMachine> ptrMachine;
2126 bool fDryRun = false;
2127 const char *pszSessionType = "none";
2128
2129 /*
2130 * Parse options.
2131 */
2132 enum kUnattendedInstallOpt
2133 {
2134 kUnattendedInstallOpt_AdminPassword = 1000,
2135 kUnattendedInstallOpt_AdminPasswordFile
2136 };
2137 static const RTGETOPTDEF s_aOptions[] =
2138 {
2139 { "--iso", 'i', RTGETOPT_REQ_STRING },
2140 { "--user", 'u', RTGETOPT_REQ_STRING },
2141 { "--password", 'p', RTGETOPT_REQ_STRING }, /* Keep for backwards compatibility! */
2142 { "--password-file", 'X', RTGETOPT_REQ_STRING }, /* Keep for backwards compatibility! */
2143 { "--user-password", 'p', RTGETOPT_REQ_STRING },
2144 { "--user-password-file", 'X', RTGETOPT_REQ_STRING },
2145 { "--admin-password", kUnattendedInstallOpt_AdminPassword, RTGETOPT_REQ_STRING },
2146 { "--admin-password-file", kUnattendedInstallOpt_AdminPasswordFile, RTGETOPT_REQ_STRING },
2147 { "--full-user-name", 'U', RTGETOPT_REQ_STRING },
2148 { "--key", 'k', RTGETOPT_REQ_STRING },
2149 { "--install-additions", 'A', RTGETOPT_REQ_NOTHING },
2150 { "--no-install-additions", 'N', RTGETOPT_REQ_NOTHING },
2151 { "--additions-iso", 'a', RTGETOPT_REQ_STRING },
2152 { "--install-txs", 't', RTGETOPT_REQ_NOTHING },
2153 { "--no-install-txs", 'T', RTGETOPT_REQ_NOTHING },
2154 { "--validation-kit-iso", 'K', RTGETOPT_REQ_STRING },
2155 { "--locale", 'l', RTGETOPT_REQ_STRING },
2156 { "--country", 'Y', RTGETOPT_REQ_STRING },
2157 { "--time-zone", 'z', RTGETOPT_REQ_STRING },
2158 { "--proxy", 'y', RTGETOPT_REQ_STRING },
2159 { "--hostname", 'H', RTGETOPT_REQ_STRING },
2160 { "--package-selection-adjustment", 's', RTGETOPT_REQ_STRING },
2161 { "--dry-run", 'D', RTGETOPT_REQ_NOTHING },
2162 // advance options:
2163 { "--auxiliary-base-path", 'x', RTGETOPT_REQ_STRING },
2164 { "--image-index", 'm', RTGETOPT_REQ_UINT32 },
2165 { "--script-template", 'c', RTGETOPT_REQ_STRING },
2166 { "--post-install-template", 'C', RTGETOPT_REQ_STRING },
2167 { "--post-install-command", 'P', RTGETOPT_REQ_STRING },
2168 { "--extra-install-kernel-parameters", 'I', RTGETOPT_REQ_STRING },
2169 { "--language", 'L', RTGETOPT_REQ_STRING },
2170 // start vm related options:
2171 { "--start-vm", 'S', RTGETOPT_REQ_STRING },
2172 /** @todo Add a --wait option too for waiting for the VM to shut down or
2173 * something like that...? */
2174 };
2175
2176 RTGETOPTSTATE GetState;
2177 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2178 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
2179
2180 int c;
2181 RTGETOPTUNION ValueUnion;
2182 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2183 {
2184 switch (c)
2185 {
2186 case VINF_GETOPT_NOT_OPTION:
2187 if (ptrMachine.isNotNull())
2188 return errorSyntax(Misc::tr("VM name/UUID given more than once!"));
2189 CHECK_ERROR2_RET(hrc, a->virtualBox, FindMachine(Bstr(ValueUnion.psz).raw(), ptrMachine.asOutParam()), RTEXITCODE_FAILURE);
2190 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Machine)(ptrMachine), RTEXITCODE_FAILURE);
2191 break;
2192
2193 case 'i': // --iso
2194 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2195 if (RT_FAILURE(vrc))
2196 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2197 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(IsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2198 break;
2199
2200 case 'u': // --user
2201 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(User)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2202 break;
2203
2204 case 'p': // --[user-]password
2205 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(UserPassword)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2206 break;
2207
2208 case kUnattendedInstallOpt_AdminPassword: // --admin-password
2209 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AdminPassword)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2210 break;
2211
2212 case 'X': // --[user-]password-file
2213 {
2214 Utf8Str strPassword;
2215 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
2216 if (rcExit != RTEXITCODE_SUCCESS)
2217 return rcExit;
2218 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(UserPassword)(Bstr(strPassword).raw()), RTEXITCODE_FAILURE);
2219 break;
2220 }
2221
2222 case kUnattendedInstallOpt_AdminPasswordFile:
2223 {
2224 Utf8Str strPassword;
2225 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
2226 if (rcExit != RTEXITCODE_SUCCESS)
2227 return rcExit;
2228 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AdminPassword)(Bstr(strPassword).raw()), RTEXITCODE_FAILURE);
2229 break;
2230 }
2231
2232 case 'U': // --full-user-name
2233 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(FullUserName)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2234 break;
2235
2236 case 'k': // --key
2237 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ProductKey)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2238 break;
2239
2240 case 'A': // --install-additions
2241 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallGuestAdditions)(TRUE), RTEXITCODE_FAILURE);
2242 break;
2243 case 'N': // --no-install-additions
2244 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallGuestAdditions)(FALSE), RTEXITCODE_FAILURE);
2245 break;
2246 case 'a': // --additions-iso
2247 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2248 if (RT_FAILURE(vrc))
2249 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2250 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AdditionsIsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2251 break;
2252
2253 case 't': // --install-txs
2254 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallTestExecService)(TRUE), RTEXITCODE_FAILURE);
2255 break;
2256 case 'T': // --no-install-txs
2257 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallTestExecService)(FALSE), RTEXITCODE_FAILURE);
2258 break;
2259 case 'K': // --valiation-kit-iso
2260 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2261 if (RT_FAILURE(vrc))
2262 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2263 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ValidationKitIsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2264 break;
2265
2266 case 'l': // --locale
2267 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Locale)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2268 break;
2269
2270 case 'Y': // --country
2271 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Country)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2272 break;
2273
2274 case 'z': // --time-zone;
2275 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(TimeZone)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2276 break;
2277
2278 case 'y': // --proxy
2279 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Proxy)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2280 break;
2281
2282 case 'H': // --hostname
2283 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Hostname)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2284 break;
2285
2286 case 's': // --package-selection-adjustment
2287 arrPackageSelectionAdjustments.append(ValueUnion.psz);
2288 break;
2289
2290 case 'D':
2291 fDryRun = true;
2292 break;
2293
2294 case 'x': // --auxiliary-base-path
2295 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2296 if (RT_FAILURE(vrc))
2297 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2298 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AuxiliaryBasePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2299 break;
2300
2301 case 'm': // --image-index
2302 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ImageIndex)(ValueUnion.u32), RTEXITCODE_FAILURE);
2303 break;
2304
2305 case 'c': // --script-template
2306 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2307 if (RT_FAILURE(vrc))
2308 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2309 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ScriptTemplatePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2310 break;
2311
2312 case 'C': // --post-install-script-template
2313 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2314 if (RT_FAILURE(vrc))
2315 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2316 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PostInstallScriptTemplatePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2317 break;
2318
2319 case 'P': // --post-install-command.
2320 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PostInstallCommand)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2321 break;
2322
2323 case 'I': // --extra-install-kernel-parameters
2324 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ExtraInstallKernelParameters)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2325 break;
2326
2327 case 'L': // --language
2328 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Language)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2329 break;
2330
2331 case 'S': // --start-vm
2332 pszSessionType = ValueUnion.psz;
2333 break;
2334
2335 default:
2336 return errorGetOpt(c, &ValueUnion);
2337 }
2338 }
2339
2340 /*
2341 * Check for required stuff.
2342 */
2343 if (ptrMachine.isNull())
2344 return errorSyntax(Misc::tr("Missing VM name/UUID"));
2345
2346 /*
2347 * Set accumulative attributes.
2348 */
2349 if (arrPackageSelectionAdjustments.size() == 1)
2350 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PackageSelectionAdjustments)(Bstr(arrPackageSelectionAdjustments[0]).raw()),
2351 RTEXITCODE_FAILURE);
2352 else if (arrPackageSelectionAdjustments.size() > 1)
2353 {
2354 RTCString strAdjustments;
2355 strAdjustments.join(arrPackageSelectionAdjustments, ";");
2356 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PackageSelectionAdjustments)(Bstr(strAdjustments).raw()), RTEXITCODE_FAILURE);
2357 }
2358
2359 /*
2360 * Get details about the machine so we can display them below.
2361 */
2362 Bstr bstrMachineName;
2363 CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(Name)(bstrMachineName.asOutParam()), RTEXITCODE_FAILURE);
2364 Bstr bstrUuid;
2365 CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(Id)(bstrUuid.asOutParam()), RTEXITCODE_FAILURE);
2366 BSTR bstrInstalledOS;
2367 CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(OSTypeId)(&bstrInstalledOS), RTEXITCODE_FAILURE);
2368 Utf8Str strInstalledOS(bstrInstalledOS);
2369
2370 /*
2371 * Temporarily lock the machine to check whether it's running or not.
2372 * We take this opportunity to disable the first run wizard.
2373 */
2374 CHECK_ERROR2_RET(hrc, ptrMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
2375 {
2376 ComPtr<IConsole> ptrConsole;
2377 CHECK_ERROR2(hrc, a->session, COMGETTER(Console)(ptrConsole.asOutParam()));
2378
2379 if ( ptrConsole.isNull()
2380 && SUCCEEDED(hrc)
2381 && ( RTStrICmp(pszSessionType, "gui") == 0
2382 || RTStrICmp(pszSessionType, "none") == 0))
2383 {
2384 ComPtr<IMachine> ptrSessonMachine;
2385 CHECK_ERROR2(hrc, a->session, COMGETTER(Machine)(ptrSessonMachine.asOutParam()));
2386 if (ptrSessonMachine.isNotNull())
2387 {
2388 CHECK_ERROR2(hrc, ptrSessonMachine, SetExtraData(Bstr("GUI/FirstRun").raw(), Bstr("0").raw()));
2389 }
2390 }
2391
2392 a->session->UnlockMachine();
2393 if (FAILED(hrc))
2394 return RTEXITCODE_FAILURE;
2395 if (ptrConsole.isNotNull())
2396 return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("Machine '%ls' is currently running"), bstrMachineName.raw());
2397 }
2398
2399 /*
2400 * Do the work.
2401 */
2402 RTMsgInfo(Misc::tr("%s unattended installation of %s in machine '%ls' (%ls).\n"),
2403 RTStrICmp(pszSessionType, "none") == 0 ? Misc::tr("Preparing") : Misc::tr("Starting"),
2404 strInstalledOS.c_str(), bstrMachineName.raw(), bstrUuid.raw());
2405
2406 CHECK_ERROR2_RET(hrc, ptrUnattended,Prepare(), RTEXITCODE_FAILURE);
2407 if (!fDryRun)
2408 {
2409 CHECK_ERROR2_RET(hrc, ptrUnattended, ConstructMedia(), RTEXITCODE_FAILURE);
2410 CHECK_ERROR2_RET(hrc, ptrUnattended, ReconfigureVM(), RTEXITCODE_FAILURE);
2411 }
2412
2413 /*
2414 * Retrieve and display the parameters actually used.
2415 */
2416 RTMsgInfo(Misc::tr("Using values:\n"));
2417#define SHOW_ATTR(a_Attr, a_szText, a_Type, a_szFmt) do { \
2418 a_Type Value; \
2419 HRESULT hrc2 = ptrUnattended->COMGETTER(a_Attr)(&Value); \
2420 if (SUCCEEDED(hrc2)) \
2421 RTPrintf(" %32s = " a_szFmt "\n", a_szText, Value); \
2422 else \
2423 RTPrintf(Misc::tr(" %32s = failed: %Rhrc\n"), a_szText, hrc2); \
2424 } while (0)
2425#define SHOW_STR_ATTR(a_Attr, a_szText) do { \
2426 Bstr bstrString; \
2427 HRESULT hrc2 = ptrUnattended->COMGETTER(a_Attr)(bstrString.asOutParam()); \
2428 if (SUCCEEDED(hrc2)) \
2429 RTPrintf(" %32s = %ls\n", a_szText, bstrString.raw()); \
2430 else \
2431 RTPrintf(Misc::tr(" %32s = failed: %Rhrc\n"), a_szText, hrc2); \
2432 } while (0)
2433
2434 SHOW_STR_ATTR(IsoPath, "isoPath");
2435 SHOW_STR_ATTR(User, "user");
2436 SHOW_STR_ATTR(UserPassword, "password"); /* Keep for backwards compatibility! */
2437 SHOW_STR_ATTR(UserPassword, "user-password");
2438 SHOW_STR_ATTR(AdminPassword, "admin-password");
2439 SHOW_STR_ATTR(FullUserName, "fullUserName");
2440 SHOW_STR_ATTR(ProductKey, "productKey");
2441 SHOW_STR_ATTR(AdditionsIsoPath, "additionsIsoPath");
2442 SHOW_ATTR( InstallGuestAdditions, "installGuestAdditions", BOOL, "%RTbool");
2443 SHOW_STR_ATTR(ValidationKitIsoPath, "validationKitIsoPath");
2444 SHOW_ATTR( InstallTestExecService, "installTestExecService", BOOL, "%RTbool");
2445 SHOW_STR_ATTR(Locale, "locale");
2446 SHOW_STR_ATTR(Country, "country");
2447 SHOW_STR_ATTR(TimeZone, "timeZone");
2448 SHOW_STR_ATTR(Proxy, "proxy");
2449 SHOW_STR_ATTR(Hostname, "hostname");
2450 SHOW_STR_ATTR(PackageSelectionAdjustments, "packageSelectionAdjustments");
2451 SHOW_STR_ATTR(AuxiliaryBasePath, "auxiliaryBasePath");
2452 SHOW_ATTR( ImageIndex, "imageIndex", ULONG, "%u");
2453 SHOW_STR_ATTR(ScriptTemplatePath, "scriptTemplatePath");
2454 SHOW_STR_ATTR(PostInstallScriptTemplatePath, "postInstallScriptTemplatePath");
2455 SHOW_STR_ATTR(PostInstallCommand, "postInstallCommand");
2456 SHOW_STR_ATTR(ExtraInstallKernelParameters, "extraInstallKernelParameters");
2457 SHOW_STR_ATTR(Language, "language");
2458 SHOW_STR_ATTR(DetectedOSTypeId, "detectedOSTypeId");
2459 SHOW_STR_ATTR(DetectedOSVersion, "detectedOSVersion");
2460 SHOW_STR_ATTR(DetectedOSFlavor, "detectedOSFlavor");
2461 SHOW_STR_ATTR(DetectedOSLanguages, "detectedOSLanguages");
2462 SHOW_STR_ATTR(DetectedOSHints, "detectedOSHints");
2463 {
2464 ULONG idxImage = 0;
2465 HRESULT hrc2 = ptrUnattended->COMGETTER(ImageIndex)(&idxImage);
2466 if (FAILED(hrc2))
2467 idxImage = 0;
2468 SafeArray<BSTR> aImageNames;
2469 hrc2 = ptrUnattended->COMGETTER(DetectedImageNames)(ComSafeArrayAsOutParam(aImageNames));
2470 if (SUCCEEDED(hrc2))
2471 {
2472 SafeArray<ULONG> aImageIndices;
2473 hrc2 = ptrUnattended->COMGETTER(DetectedImageIndices)(ComSafeArrayAsOutParam(aImageIndices));
2474 if (SUCCEEDED(hrc2))
2475 {
2476 Assert(aImageNames.size() == aImageIndices.size());
2477 for (size_t i = 0; i < aImageNames.size(); i++)
2478 {
2479 char szTmp[64];
2480 RTStrPrintf(szTmp, sizeof(szTmp), "detectedImage[%u]%s", i, idxImage != aImageIndices[i] ? "" : "*");
2481 RTPrintf(" %32s = #%u: %ls\n", szTmp, aImageIndices[i], aImageNames[i]);
2482 }
2483 }
2484 else
2485 RTPrintf(Misc::tr(" %32s = failed: %Rhrc\n"), "detectedImageIndices", hrc2);
2486 }
2487 else
2488 RTPrintf(Misc::tr(" %32 = failed: %Rhrc\n"), "detectedImageNames", hrc2);
2489 }
2490
2491#undef SHOW_STR_ATTR
2492#undef SHOW_ATTR
2493
2494 /* We can drop the IUnatteded object now. */
2495 ptrUnattended.setNull();
2496
2497 /*
2498 * Start the VM if requested.
2499 */
2500 if ( fDryRun
2501 || RTStrICmp(pszSessionType, "none") == 0)
2502 {
2503 if (!fDryRun)
2504 RTMsgInfo(Misc::tr("VM '%ls' (%ls) is ready to be started (e.g. VBoxManage startvm).\n"), bstrMachineName.raw(), bstrUuid.raw());
2505 hrc = S_OK;
2506 }
2507 else
2508 {
2509 com::SafeArray<IN_BSTR> aBstrEnv;
2510#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
2511 /* make sure the VM process will start on the same display as VBoxManage */
2512 const char *pszDisplay = RTEnvGet("DISPLAY");
2513 if (pszDisplay)
2514 aBstrEnv.push_back(BstrFmt("DISPLAY=%s", pszDisplay).raw());
2515 const char *pszXAuth = RTEnvGet("XAUTHORITY");
2516 if (pszXAuth)
2517 aBstrEnv.push_back(BstrFmt("XAUTHORITY=%s", pszXAuth).raw());
2518#endif
2519 ComPtr<IProgress> ptrProgress;
2520 CHECK_ERROR2(hrc, ptrMachine, LaunchVMProcess(a->session, Bstr(pszSessionType).raw(), ComSafeArrayAsInParam(aBstrEnv), ptrProgress.asOutParam()));
2521 if (SUCCEEDED(hrc) && !ptrProgress.isNull())
2522 {
2523 RTMsgInfo(Misc::tr("Waiting for VM '%ls' to power on...\n"), bstrMachineName.raw());
2524 CHECK_ERROR2(hrc, ptrProgress, WaitForCompletion(-1));
2525 if (SUCCEEDED(hrc))
2526 {
2527 BOOL fCompleted = true;
2528 CHECK_ERROR2(hrc, ptrProgress, COMGETTER(Completed)(&fCompleted));
2529 if (SUCCEEDED(hrc))
2530 {
2531 ASSERT(fCompleted);
2532
2533 LONG iRc;
2534 CHECK_ERROR2(hrc, ptrProgress, COMGETTER(ResultCode)(&iRc));
2535 if (SUCCEEDED(hrc))
2536 {
2537 if (SUCCEEDED(iRc))
2538 RTMsgInfo(Misc::tr("VM '%ls' (%ls) has been successfully started.\n"),
2539 bstrMachineName.raw(), bstrUuid.raw());
2540 else
2541 {
2542 ProgressErrorInfo info(ptrProgress);
2543 com::GluePrintErrorInfo(info);
2544 }
2545 hrc = iRc;
2546 }
2547 }
2548 }
2549 }
2550
2551 /*
2552 * Do we wait for the VM to power down?
2553 */
2554 }
2555
2556 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2557}
2558
2559
2560RTEXITCODE handleUnattended(HandlerArg *a)
2561{
2562 /*
2563 * Sub-command switch.
2564 */
2565 if (a->argc < 1)
2566 return errorNoSubcommand();
2567
2568 if (!strcmp(a->argv[0], "detect"))
2569 {
2570 setCurrentSubcommand(HELP_SCOPE_UNATTENDED_DETECT);
2571 return handleUnattendedDetect(a);
2572 }
2573
2574 if (!strcmp(a->argv[0], "install"))
2575 {
2576 setCurrentSubcommand(HELP_SCOPE_UNATTENDED_INSTALL);
2577 return handleUnattendedInstall(a);
2578 }
2579
2580 /* Consider some kind of create-vm-and-install-guest-os command. */
2581 return errorUnknownSubcommand(a->argv[0]);
2582}
2583
2584/**
2585 * Common Cloud profile options.
2586 */
2587typedef struct
2588{
2589 const char *pszProviderName;
2590 const char *pszProfileName;
2591} CLOUDPROFILECOMMONOPT;
2592typedef CLOUDPROFILECOMMONOPT *PCLOUDPROFILECOMMONOPT;
2593
2594/**
2595 * Sets the properties of cloud profile
2596 *
2597 * @returns 0 on success, 1 on failure
2598 */
2599
2600static RTEXITCODE setCloudProfileProperties(HandlerArg *a, int iFirst, PCLOUDPROFILECOMMONOPT pCommonOpts)
2601{
2602
2603 HRESULT hrc = S_OK;
2604
2605 Bstr bstrProvider(pCommonOpts->pszProviderName);
2606 Bstr bstrProfile(pCommonOpts->pszProfileName);
2607
2608 /*
2609 * Parse options.
2610 */
2611 static const RTGETOPTDEF s_aOptions[] =
2612 {
2613 { "--clouduser", 'u', RTGETOPT_REQ_STRING },
2614 { "--fingerprint", 'p', RTGETOPT_REQ_STRING },
2615 { "--keyfile", 'k', RTGETOPT_REQ_STRING },
2616 { "--passphrase", 'P', RTGETOPT_REQ_STRING },
2617 { "--tenancy", 't', RTGETOPT_REQ_STRING },
2618 { "--compartment", 'c', RTGETOPT_REQ_STRING },
2619 { "--region", 'r', RTGETOPT_REQ_STRING }
2620 };
2621
2622 RTGETOPTSTATE GetState;
2623 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0);
2624 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
2625
2626 com::SafeArray<BSTR> names;
2627 com::SafeArray<BSTR> values;
2628
2629 int c;
2630 RTGETOPTUNION ValueUnion;
2631 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2632 {
2633 switch (c)
2634 {
2635 case 'u': // --clouduser
2636 Bstr("user").detachTo(names.appendedRaw());
2637 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2638 break;
2639 case 'p': // --fingerprint
2640 Bstr("fingerprint").detachTo(names.appendedRaw());
2641 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2642 break;
2643 case 'k': // --keyfile
2644 Bstr("key_file").detachTo(names.appendedRaw());
2645 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2646 break;
2647 case 'P': // --passphrase
2648 Bstr("pass_phrase").detachTo(names.appendedRaw());
2649 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2650 break;
2651 case 't': // --tenancy
2652 Bstr("tenancy").detachTo(names.appendedRaw());
2653 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2654 break;
2655 case 'c': // --compartment
2656 Bstr("compartment").detachTo(names.appendedRaw());
2657 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2658 break;
2659 case 'r': // --region
2660 Bstr("region").detachTo(names.appendedRaw());
2661 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2662 break;
2663 default:
2664 return errorGetOpt(c, &ValueUnion);
2665 }
2666 }
2667
2668 /* check for required options */
2669 if (bstrProvider.isEmpty())
2670 return errorSyntax(Misc::tr("Parameter --provider is required"));
2671 if (bstrProfile.isEmpty())
2672 return errorSyntax(Misc::tr("Parameter --profile is required"));
2673
2674 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
2675
2676 ComPtr<ICloudProviderManager> pCloudProviderManager;
2677 CHECK_ERROR2_RET(hrc, pVirtualBox,
2678 COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()),
2679 RTEXITCODE_FAILURE);
2680
2681 ComPtr<ICloudProvider> pCloudProvider;
2682
2683 CHECK_ERROR2_RET(hrc, pCloudProviderManager,
2684 GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()),
2685 RTEXITCODE_FAILURE);
2686
2687 ComPtr<ICloudProfile> pCloudProfile;
2688
2689 if (pCloudProvider)
2690 {
2691 CHECK_ERROR2_RET(hrc, pCloudProvider,
2692 GetProfileByName(bstrProfile.raw(), pCloudProfile.asOutParam()),
2693 RTEXITCODE_FAILURE);
2694 CHECK_ERROR2_RET(hrc, pCloudProfile,
2695 SetProperties(ComSafeArrayAsInParam(names), ComSafeArrayAsInParam(values)),
2696 RTEXITCODE_FAILURE);
2697 }
2698
2699 CHECK_ERROR2(hrc, pCloudProvider, SaveProfiles());
2700
2701 RTPrintf(Misc::tr("Provider %ls: profile '%ls' was updated.\n"),bstrProvider.raw(), bstrProfile.raw());
2702
2703 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2704}
2705
2706/**
2707 * Gets the properties of cloud profile
2708 *
2709 * @returns 0 on success, 1 on failure
2710 */
2711static RTEXITCODE showCloudProfileProperties(HandlerArg *a, PCLOUDPROFILECOMMONOPT pCommonOpts)
2712{
2713 HRESULT hrc = S_OK;
2714
2715 Bstr bstrProvider(pCommonOpts->pszProviderName);
2716 Bstr bstrProfile(pCommonOpts->pszProfileName);
2717
2718 /* check for required options */
2719 if (bstrProvider.isEmpty())
2720 return errorSyntax(Misc::tr("Parameter --provider is required"));
2721 if (bstrProfile.isEmpty())
2722 return errorSyntax(Misc::tr("Parameter --profile is required"));
2723
2724 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
2725 ComPtr<ICloudProviderManager> pCloudProviderManager;
2726 CHECK_ERROR2_RET(hrc, pVirtualBox,
2727 COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()),
2728 RTEXITCODE_FAILURE);
2729 ComPtr<ICloudProvider> pCloudProvider;
2730 CHECK_ERROR2_RET(hrc, pCloudProviderManager,
2731 GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()),
2732 RTEXITCODE_FAILURE);
2733
2734 ComPtr<ICloudProfile> pCloudProfile;
2735 if (pCloudProvider)
2736 {
2737 CHECK_ERROR2_RET(hrc, pCloudProvider,
2738 GetProfileByName(bstrProfile.raw(), pCloudProfile.asOutParam()),
2739 RTEXITCODE_FAILURE);
2740
2741 Bstr bstrProviderID;
2742 pCloudProfile->COMGETTER(ProviderId)(bstrProviderID.asOutParam());
2743 RTPrintf(Misc::tr("Provider GUID: %ls\n"), bstrProviderID.raw());
2744
2745 com::SafeArray<BSTR> names;
2746 com::SafeArray<BSTR> values;
2747 CHECK_ERROR2_RET(hrc, pCloudProfile,
2748 GetProperties(Bstr().raw(), ComSafeArrayAsOutParam(names), ComSafeArrayAsOutParam(values)),
2749 RTEXITCODE_FAILURE);
2750 size_t cNames = names.size();
2751 size_t cValues = values.size();
2752 bool fFirst = true;
2753 for (size_t k = 0; k < cNames; k++)
2754 {
2755 Bstr value;
2756 if (k < cValues)
2757 value = values[k];
2758 RTPrintf("%s%ls=%ls\n",
2759 fFirst ? Misc::tr("Property: ") : " ",
2760 names[k], value.raw());
2761 fFirst = false;
2762 }
2763
2764 RTPrintf("\n");
2765 }
2766
2767 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2768}
2769
2770static RTEXITCODE addCloudProfile(HandlerArg *a, int iFirst, PCLOUDPROFILECOMMONOPT pCommonOpts)
2771{
2772 HRESULT hrc = S_OK;
2773
2774 Bstr bstrProvider(pCommonOpts->pszProviderName);
2775 Bstr bstrProfile(pCommonOpts->pszProfileName);
2776
2777
2778 /* check for required options */
2779 if (bstrProvider.isEmpty())
2780 return errorSyntax(Misc::tr("Parameter --provider is required"));
2781 if (bstrProfile.isEmpty())
2782 return errorSyntax(Misc::tr("Parameter --profile is required"));
2783
2784 /*
2785 * Parse options.
2786 */
2787 static const RTGETOPTDEF s_aOptions[] =
2788 {
2789 { "--clouduser", 'u', RTGETOPT_REQ_STRING },
2790 { "--fingerprint", 'p', RTGETOPT_REQ_STRING },
2791 { "--keyfile", 'k', RTGETOPT_REQ_STRING },
2792 { "--passphrase", 'P', RTGETOPT_REQ_STRING },
2793 { "--tenancy", 't', RTGETOPT_REQ_STRING },
2794 { "--compartment", 'c', RTGETOPT_REQ_STRING },
2795 { "--region", 'r', RTGETOPT_REQ_STRING }
2796 };
2797
2798 RTGETOPTSTATE GetState;
2799 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0);
2800 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
2801
2802 com::SafeArray<BSTR> names;
2803 com::SafeArray<BSTR> values;
2804
2805 int c;
2806 RTGETOPTUNION ValueUnion;
2807 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2808 {
2809 switch (c)
2810 {
2811 case 'u': // --clouduser
2812 Bstr("user").detachTo(names.appendedRaw());
2813 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2814 break;
2815 case 'p': // --fingerprint
2816 Bstr("fingerprint").detachTo(names.appendedRaw());
2817 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2818 break;
2819 case 'k': // --keyfile
2820 Bstr("key_file").detachTo(names.appendedRaw());
2821 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2822 break;
2823 case 'P': // --passphrase
2824 Bstr("pass_phrase").detachTo(names.appendedRaw());
2825 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2826 break;
2827 case 't': // --tenancy
2828 Bstr("tenancy").detachTo(names.appendedRaw());
2829 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2830 break;
2831 case 'c': // --compartment
2832 Bstr("compartment").detachTo(names.appendedRaw());
2833 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2834 break;
2835 case 'r': // --region
2836 Bstr("region").detachTo(names.appendedRaw());
2837 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2838 break;
2839 default:
2840 return errorGetOpt(c, &ValueUnion);
2841 }
2842 }
2843
2844 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
2845
2846 ComPtr<ICloudProviderManager> pCloudProviderManager;
2847 CHECK_ERROR2_RET(hrc, pVirtualBox,
2848 COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()),
2849 RTEXITCODE_FAILURE);
2850
2851 ComPtr<ICloudProvider> pCloudProvider;
2852 CHECK_ERROR2_RET(hrc, pCloudProviderManager,
2853 GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()),
2854 RTEXITCODE_FAILURE);
2855
2856 CHECK_ERROR2_RET(hrc, pCloudProvider,
2857 CreateProfile(bstrProfile.raw(),
2858 ComSafeArrayAsInParam(names),
2859 ComSafeArrayAsInParam(values)),
2860 RTEXITCODE_FAILURE);
2861
2862 CHECK_ERROR2(hrc, pCloudProvider, SaveProfiles());
2863
2864 RTPrintf(Misc::tr("Provider %ls: profile '%ls' was added.\n"),bstrProvider.raw(), bstrProfile.raw());
2865
2866 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2867}
2868
2869static RTEXITCODE deleteCloudProfile(HandlerArg *a, PCLOUDPROFILECOMMONOPT pCommonOpts)
2870{
2871 HRESULT hrc = S_OK;
2872
2873 Bstr bstrProvider(pCommonOpts->pszProviderName);
2874 Bstr bstrProfile(pCommonOpts->pszProfileName);
2875
2876 /* check for required options */
2877 if (bstrProvider.isEmpty())
2878 return errorSyntax(Misc::tr("Parameter --provider is required"));
2879 if (bstrProfile.isEmpty())
2880 return errorSyntax(Misc::tr("Parameter --profile is required"));
2881
2882 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
2883 ComPtr<ICloudProviderManager> pCloudProviderManager;
2884 CHECK_ERROR2_RET(hrc, pVirtualBox,
2885 COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()),
2886 RTEXITCODE_FAILURE);
2887 ComPtr<ICloudProvider> pCloudProvider;
2888 CHECK_ERROR2_RET(hrc, pCloudProviderManager,
2889 GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()),
2890 RTEXITCODE_FAILURE);
2891
2892 ComPtr<ICloudProfile> pCloudProfile;
2893 if (pCloudProvider)
2894 {
2895 CHECK_ERROR2_RET(hrc, pCloudProvider,
2896 GetProfileByName(bstrProfile.raw(), pCloudProfile.asOutParam()),
2897 RTEXITCODE_FAILURE);
2898
2899 CHECK_ERROR2_RET(hrc, pCloudProfile,
2900 Remove(),
2901 RTEXITCODE_FAILURE);
2902
2903 CHECK_ERROR2_RET(hrc, pCloudProvider,
2904 SaveProfiles(),
2905 RTEXITCODE_FAILURE);
2906
2907 RTPrintf(Misc::tr("Provider %ls: profile '%ls' was deleted.\n"), bstrProvider.raw(), bstrProfile.raw());
2908 }
2909
2910 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2911}
2912
2913RTEXITCODE handleCloudProfile(HandlerArg *a)
2914{
2915 if (a->argc < 1)
2916 return errorNoSubcommand();
2917
2918 static const RTGETOPTDEF s_aOptions[] =
2919 {
2920 /* common options */
2921 { "--provider", 'v', RTGETOPT_REQ_STRING },
2922 { "--profile", 'f', RTGETOPT_REQ_STRING },
2923 /* subcommands */
2924 { "add", 1000, RTGETOPT_REQ_NOTHING },
2925 { "show", 1001, RTGETOPT_REQ_NOTHING },
2926 { "update", 1002, RTGETOPT_REQ_NOTHING },
2927 { "delete", 1003, RTGETOPT_REQ_NOTHING },
2928 };
2929
2930 RTGETOPTSTATE GetState;
2931 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
2932 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
2933
2934 CLOUDPROFILECOMMONOPT CommonOpts = { NULL, NULL };
2935 int c;
2936 RTGETOPTUNION ValueUnion;
2937 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2938 {
2939 switch (c)
2940 {
2941 case 'v': // --provider
2942 CommonOpts.pszProviderName = ValueUnion.psz;
2943 break;
2944 case 'f': // --profile
2945 CommonOpts.pszProfileName = ValueUnion.psz;
2946 break;
2947 /* Sub-commands: */
2948 case 1000:
2949 setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_ADD);
2950 return addCloudProfile(a, GetState.iNext, &CommonOpts);
2951 case 1001:
2952 setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_SHOW);
2953 return showCloudProfileProperties(a, &CommonOpts);
2954 case 1002:
2955 setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_UPDATE);
2956 return setCloudProfileProperties(a, GetState.iNext, &CommonOpts);
2957 case 1003:
2958 setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_DELETE);
2959 return deleteCloudProfile(a, &CommonOpts);
2960 case VINF_GETOPT_NOT_OPTION:
2961 return errorUnknownSubcommand(ValueUnion.psz);
2962
2963 default:
2964 return errorGetOpt(c, &ValueUnion);
2965 }
2966 }
2967
2968 return errorNoSubcommand();
2969}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use