VirtualBox

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

Last change on this file since 102540 was 102540, checked in by vboxsync, 5 months ago

VBoxManage/Unattended: Added password file support for the admin password. Renamed "--password-file" (kept for backwards compatibility though) to "--user-password-file". Updated docs. bugref:10554

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

© 2023 Oracle
ContactPrivacy policyTerms of Use