VirtualBox

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

Last change on this file since 102539 was 102539, checked in by vboxsync, 6 months ago

VBoxManage/Unattended: Renamed the option "--password" to "--user-password" (kept "--password" for backwards compatibility though) and added option "--admin-password". Updated docs [accidentally removed "--password-file"]. bugref:10554

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 111.8 KB
Line 
1/* $Id: VBoxManageMisc.cpp 102539 2023-12-08 12:36:21Z 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 };
2137 static const RTGETOPTDEF s_aOptions[] =
2138 {
2139 { "--iso", 'i', RTGETOPT_REQ_STRING },
2140 { "--user", 'u', RTGETOPT_REQ_STRING },
2141 { "--password", 'p', RTGETOPT_REQ_STRING }, /* Keep for backwards compatibility! */
2142 { "--password-file", 'X', RTGETOPT_REQ_STRING }, /* Keep for backwards compatibility! */
2143 { "--user-password", 'p', RTGETOPT_REQ_STRING },
2144 { "--admin-password", kUnattendedInstallOpt_AdminPassword, RTGETOPT_REQ_STRING },
2145 { "--full-user-name", 'U', RTGETOPT_REQ_STRING },
2146 { "--key", 'k', RTGETOPT_REQ_STRING },
2147 { "--install-additions", 'A', RTGETOPT_REQ_NOTHING },
2148 { "--no-install-additions", 'N', RTGETOPT_REQ_NOTHING },
2149 { "--additions-iso", 'a', RTGETOPT_REQ_STRING },
2150 { "--install-txs", 't', RTGETOPT_REQ_NOTHING },
2151 { "--no-install-txs", 'T', RTGETOPT_REQ_NOTHING },
2152 { "--validation-kit-iso", 'K', RTGETOPT_REQ_STRING },
2153 { "--locale", 'l', RTGETOPT_REQ_STRING },
2154 { "--country", 'Y', RTGETOPT_REQ_STRING },
2155 { "--time-zone", 'z', RTGETOPT_REQ_STRING },
2156 { "--proxy", 'y', RTGETOPT_REQ_STRING },
2157 { "--hostname", 'H', RTGETOPT_REQ_STRING },
2158 { "--package-selection-adjustment", 's', RTGETOPT_REQ_STRING },
2159 { "--dry-run", 'D', RTGETOPT_REQ_NOTHING },
2160 // advance options:
2161 { "--auxiliary-base-path", 'x', RTGETOPT_REQ_STRING },
2162 { "--image-index", 'm', RTGETOPT_REQ_UINT32 },
2163 { "--script-template", 'c', RTGETOPT_REQ_STRING },
2164 { "--post-install-template", 'C', RTGETOPT_REQ_STRING },
2165 { "--post-install-command", 'P', RTGETOPT_REQ_STRING },
2166 { "--extra-install-kernel-parameters", 'I', RTGETOPT_REQ_STRING },
2167 { "--language", 'L', RTGETOPT_REQ_STRING },
2168 // start vm related options:
2169 { "--start-vm", 'S', RTGETOPT_REQ_STRING },
2170 /** @todo Add a --wait option too for waiting for the VM to shut down or
2171 * something like that...? */
2172 };
2173
2174 RTGETOPTSTATE GetState;
2175 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2176 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
2177
2178 int c;
2179 RTGETOPTUNION ValueUnion;
2180 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2181 {
2182 switch (c)
2183 {
2184 case VINF_GETOPT_NOT_OPTION:
2185 if (ptrMachine.isNotNull())
2186 return errorSyntax(Misc::tr("VM name/UUID given more than once!"));
2187 CHECK_ERROR2_RET(hrc, a->virtualBox, FindMachine(Bstr(ValueUnion.psz).raw(), ptrMachine.asOutParam()), RTEXITCODE_FAILURE);
2188 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Machine)(ptrMachine), RTEXITCODE_FAILURE);
2189 break;
2190
2191 case 'i': // --iso
2192 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2193 if (RT_FAILURE(vrc))
2194 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2195 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(IsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2196 break;
2197
2198 case 'u': // --user
2199 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(User)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2200 break;
2201
2202 case 'p': // --[user-]password
2203 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(UserPassword)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2204 break;
2205
2206 case kUnattendedInstallOpt_AdminPassword: // --admin-password
2207 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AdminPassword)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2208 break;
2209
2210 case 'X': // --password-file
2211 {
2212 Utf8Str strPassword;
2213 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
2214 if (rcExit != RTEXITCODE_SUCCESS)
2215 return rcExit;
2216 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(UserPassword)(Bstr(strPassword).raw()), RTEXITCODE_FAILURE);
2217 break;
2218 }
2219
2220 case 'U': // --full-user-name
2221 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(FullUserName)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2222 break;
2223
2224 case 'k': // --key
2225 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ProductKey)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2226 break;
2227
2228 case 'A': // --install-additions
2229 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallGuestAdditions)(TRUE), RTEXITCODE_FAILURE);
2230 break;
2231 case 'N': // --no-install-additions
2232 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallGuestAdditions)(FALSE), RTEXITCODE_FAILURE);
2233 break;
2234 case 'a': // --additions-iso
2235 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2236 if (RT_FAILURE(vrc))
2237 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2238 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AdditionsIsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2239 break;
2240
2241 case 't': // --install-txs
2242 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallTestExecService)(TRUE), RTEXITCODE_FAILURE);
2243 break;
2244 case 'T': // --no-install-txs
2245 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallTestExecService)(FALSE), RTEXITCODE_FAILURE);
2246 break;
2247 case 'K': // --valiation-kit-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(ValidationKitIsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2252 break;
2253
2254 case 'l': // --locale
2255 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Locale)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2256 break;
2257
2258 case 'Y': // --country
2259 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Country)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2260 break;
2261
2262 case 'z': // --time-zone;
2263 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(TimeZone)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2264 break;
2265
2266 case 'y': // --proxy
2267 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Proxy)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2268 break;
2269
2270 case 'H': // --hostname
2271 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Hostname)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2272 break;
2273
2274 case 's': // --package-selection-adjustment
2275 arrPackageSelectionAdjustments.append(ValueUnion.psz);
2276 break;
2277
2278 case 'D':
2279 fDryRun = true;
2280 break;
2281
2282 case 'x': // --auxiliary-base-path
2283 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2284 if (RT_FAILURE(vrc))
2285 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2286 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AuxiliaryBasePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2287 break;
2288
2289 case 'm': // --image-index
2290 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ImageIndex)(ValueUnion.u32), RTEXITCODE_FAILURE);
2291 break;
2292
2293 case 'c': // --script-template
2294 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2295 if (RT_FAILURE(vrc))
2296 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2297 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ScriptTemplatePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2298 break;
2299
2300 case 'C': // --post-install-script-template
2301 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
2302 if (RT_FAILURE(vrc))
2303 return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc);
2304 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PostInstallScriptTemplatePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
2305 break;
2306
2307 case 'P': // --post-install-command.
2308 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PostInstallCommand)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2309 break;
2310
2311 case 'I': // --extra-install-kernel-parameters
2312 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ExtraInstallKernelParameters)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2313 break;
2314
2315 case 'L': // --language
2316 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Language)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
2317 break;
2318
2319 case 'S': // --start-vm
2320 pszSessionType = ValueUnion.psz;
2321 break;
2322
2323 default:
2324 return errorGetOpt(c, &ValueUnion);
2325 }
2326 }
2327
2328 /*
2329 * Check for required stuff.
2330 */
2331 if (ptrMachine.isNull())
2332 return errorSyntax(Misc::tr("Missing VM name/UUID"));
2333
2334 /*
2335 * Set accumulative attributes.
2336 */
2337 if (arrPackageSelectionAdjustments.size() == 1)
2338 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PackageSelectionAdjustments)(Bstr(arrPackageSelectionAdjustments[0]).raw()),
2339 RTEXITCODE_FAILURE);
2340 else if (arrPackageSelectionAdjustments.size() > 1)
2341 {
2342 RTCString strAdjustments;
2343 strAdjustments.join(arrPackageSelectionAdjustments, ";");
2344 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PackageSelectionAdjustments)(Bstr(strAdjustments).raw()), RTEXITCODE_FAILURE);
2345 }
2346
2347 /*
2348 * Get details about the machine so we can display them below.
2349 */
2350 Bstr bstrMachineName;
2351 CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(Name)(bstrMachineName.asOutParam()), RTEXITCODE_FAILURE);
2352 Bstr bstrUuid;
2353 CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(Id)(bstrUuid.asOutParam()), RTEXITCODE_FAILURE);
2354 BSTR bstrInstalledOS;
2355 CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(OSTypeId)(&bstrInstalledOS), RTEXITCODE_FAILURE);
2356 Utf8Str strInstalledOS(bstrInstalledOS);
2357
2358 /*
2359 * Temporarily lock the machine to check whether it's running or not.
2360 * We take this opportunity to disable the first run wizard.
2361 */
2362 CHECK_ERROR2_RET(hrc, ptrMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
2363 {
2364 ComPtr<IConsole> ptrConsole;
2365 CHECK_ERROR2(hrc, a->session, COMGETTER(Console)(ptrConsole.asOutParam()));
2366
2367 if ( ptrConsole.isNull()
2368 && SUCCEEDED(hrc)
2369 && ( RTStrICmp(pszSessionType, "gui") == 0
2370 || RTStrICmp(pszSessionType, "none") == 0))
2371 {
2372 ComPtr<IMachine> ptrSessonMachine;
2373 CHECK_ERROR2(hrc, a->session, COMGETTER(Machine)(ptrSessonMachine.asOutParam()));
2374 if (ptrSessonMachine.isNotNull())
2375 {
2376 CHECK_ERROR2(hrc, ptrSessonMachine, SetExtraData(Bstr("GUI/FirstRun").raw(), Bstr("0").raw()));
2377 }
2378 }
2379
2380 a->session->UnlockMachine();
2381 if (FAILED(hrc))
2382 return RTEXITCODE_FAILURE;
2383 if (ptrConsole.isNotNull())
2384 return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("Machine '%ls' is currently running"), bstrMachineName.raw());
2385 }
2386
2387 /*
2388 * Do the work.
2389 */
2390 RTMsgInfo(Misc::tr("%s unattended installation of %s in machine '%ls' (%ls).\n"),
2391 RTStrICmp(pszSessionType, "none") == 0 ? Misc::tr("Preparing") : Misc::tr("Starting"),
2392 strInstalledOS.c_str(), bstrMachineName.raw(), bstrUuid.raw());
2393
2394 CHECK_ERROR2_RET(hrc, ptrUnattended,Prepare(), RTEXITCODE_FAILURE);
2395 if (!fDryRun)
2396 {
2397 CHECK_ERROR2_RET(hrc, ptrUnattended, ConstructMedia(), RTEXITCODE_FAILURE);
2398 CHECK_ERROR2_RET(hrc, ptrUnattended, ReconfigureVM(), RTEXITCODE_FAILURE);
2399 }
2400
2401 /*
2402 * Retrieve and display the parameters actually used.
2403 */
2404 RTMsgInfo(Misc::tr("Using values:\n"));
2405#define SHOW_ATTR(a_Attr, a_szText, a_Type, a_szFmt) do { \
2406 a_Type Value; \
2407 HRESULT hrc2 = ptrUnattended->COMGETTER(a_Attr)(&Value); \
2408 if (SUCCEEDED(hrc2)) \
2409 RTPrintf(" %32s = " a_szFmt "\n", a_szText, Value); \
2410 else \
2411 RTPrintf(Misc::tr(" %32s = failed: %Rhrc\n"), a_szText, hrc2); \
2412 } while (0)
2413#define SHOW_STR_ATTR(a_Attr, a_szText) do { \
2414 Bstr bstrString; \
2415 HRESULT hrc2 = ptrUnattended->COMGETTER(a_Attr)(bstrString.asOutParam()); \
2416 if (SUCCEEDED(hrc2)) \
2417 RTPrintf(" %32s = %ls\n", a_szText, bstrString.raw()); \
2418 else \
2419 RTPrintf(Misc::tr(" %32s = failed: %Rhrc\n"), a_szText, hrc2); \
2420 } while (0)
2421
2422 SHOW_STR_ATTR(IsoPath, "isoPath");
2423 SHOW_STR_ATTR(User, "user");
2424 SHOW_STR_ATTR(UserPassword, "password"); /* Keep for backwards compatibility! */
2425 SHOW_STR_ATTR(UserPassword, "user-password");
2426 SHOW_STR_ATTR(AdminPassword, "admin-password");
2427 SHOW_STR_ATTR(FullUserName, "fullUserName");
2428 SHOW_STR_ATTR(ProductKey, "productKey");
2429 SHOW_STR_ATTR(AdditionsIsoPath, "additionsIsoPath");
2430 SHOW_ATTR( InstallGuestAdditions, "installGuestAdditions", BOOL, "%RTbool");
2431 SHOW_STR_ATTR(ValidationKitIsoPath, "validationKitIsoPath");
2432 SHOW_ATTR( InstallTestExecService, "installTestExecService", BOOL, "%RTbool");
2433 SHOW_STR_ATTR(Locale, "locale");
2434 SHOW_STR_ATTR(Country, "country");
2435 SHOW_STR_ATTR(TimeZone, "timeZone");
2436 SHOW_STR_ATTR(Proxy, "proxy");
2437 SHOW_STR_ATTR(Hostname, "hostname");
2438 SHOW_STR_ATTR(PackageSelectionAdjustments, "packageSelectionAdjustments");
2439 SHOW_STR_ATTR(AuxiliaryBasePath, "auxiliaryBasePath");
2440 SHOW_ATTR( ImageIndex, "imageIndex", ULONG, "%u");
2441 SHOW_STR_ATTR(ScriptTemplatePath, "scriptTemplatePath");
2442 SHOW_STR_ATTR(PostInstallScriptTemplatePath, "postInstallScriptTemplatePath");
2443 SHOW_STR_ATTR(PostInstallCommand, "postInstallCommand");
2444 SHOW_STR_ATTR(ExtraInstallKernelParameters, "extraInstallKernelParameters");
2445 SHOW_STR_ATTR(Language, "language");
2446 SHOW_STR_ATTR(DetectedOSTypeId, "detectedOSTypeId");
2447 SHOW_STR_ATTR(DetectedOSVersion, "detectedOSVersion");
2448 SHOW_STR_ATTR(DetectedOSFlavor, "detectedOSFlavor");
2449 SHOW_STR_ATTR(DetectedOSLanguages, "detectedOSLanguages");
2450 SHOW_STR_ATTR(DetectedOSHints, "detectedOSHints");
2451 {
2452 ULONG idxImage = 0;
2453 HRESULT hrc2 = ptrUnattended->COMGETTER(ImageIndex)(&idxImage);
2454 if (FAILED(hrc2))
2455 idxImage = 0;
2456 SafeArray<BSTR> aImageNames;
2457 hrc2 = ptrUnattended->COMGETTER(DetectedImageNames)(ComSafeArrayAsOutParam(aImageNames));
2458 if (SUCCEEDED(hrc2))
2459 {
2460 SafeArray<ULONG> aImageIndices;
2461 hrc2 = ptrUnattended->COMGETTER(DetectedImageIndices)(ComSafeArrayAsOutParam(aImageIndices));
2462 if (SUCCEEDED(hrc2))
2463 {
2464 Assert(aImageNames.size() == aImageIndices.size());
2465 for (size_t i = 0; i < aImageNames.size(); i++)
2466 {
2467 char szTmp[64];
2468 RTStrPrintf(szTmp, sizeof(szTmp), "detectedImage[%u]%s", i, idxImage != aImageIndices[i] ? "" : "*");
2469 RTPrintf(" %32s = #%u: %ls\n", szTmp, aImageIndices[i], aImageNames[i]);
2470 }
2471 }
2472 else
2473 RTPrintf(Misc::tr(" %32s = failed: %Rhrc\n"), "detectedImageIndices", hrc2);
2474 }
2475 else
2476 RTPrintf(Misc::tr(" %32 = failed: %Rhrc\n"), "detectedImageNames", hrc2);
2477 }
2478
2479#undef SHOW_STR_ATTR
2480#undef SHOW_ATTR
2481
2482 /* We can drop the IUnatteded object now. */
2483 ptrUnattended.setNull();
2484
2485 /*
2486 * Start the VM if requested.
2487 */
2488 if ( fDryRun
2489 || RTStrICmp(pszSessionType, "none") == 0)
2490 {
2491 if (!fDryRun)
2492 RTMsgInfo(Misc::tr("VM '%ls' (%ls) is ready to be started (e.g. VBoxManage startvm).\n"), bstrMachineName.raw(), bstrUuid.raw());
2493 hrc = S_OK;
2494 }
2495 else
2496 {
2497 com::SafeArray<IN_BSTR> aBstrEnv;
2498#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
2499 /* make sure the VM process will start on the same display as VBoxManage */
2500 const char *pszDisplay = RTEnvGet("DISPLAY");
2501 if (pszDisplay)
2502 aBstrEnv.push_back(BstrFmt("DISPLAY=%s", pszDisplay).raw());
2503 const char *pszXAuth = RTEnvGet("XAUTHORITY");
2504 if (pszXAuth)
2505 aBstrEnv.push_back(BstrFmt("XAUTHORITY=%s", pszXAuth).raw());
2506#endif
2507 ComPtr<IProgress> ptrProgress;
2508 CHECK_ERROR2(hrc, ptrMachine, LaunchVMProcess(a->session, Bstr(pszSessionType).raw(), ComSafeArrayAsInParam(aBstrEnv), ptrProgress.asOutParam()));
2509 if (SUCCEEDED(hrc) && !ptrProgress.isNull())
2510 {
2511 RTMsgInfo(Misc::tr("Waiting for VM '%ls' to power on...\n"), bstrMachineName.raw());
2512 CHECK_ERROR2(hrc, ptrProgress, WaitForCompletion(-1));
2513 if (SUCCEEDED(hrc))
2514 {
2515 BOOL fCompleted = true;
2516 CHECK_ERROR2(hrc, ptrProgress, COMGETTER(Completed)(&fCompleted));
2517 if (SUCCEEDED(hrc))
2518 {
2519 ASSERT(fCompleted);
2520
2521 LONG iRc;
2522 CHECK_ERROR2(hrc, ptrProgress, COMGETTER(ResultCode)(&iRc));
2523 if (SUCCEEDED(hrc))
2524 {
2525 if (SUCCEEDED(iRc))
2526 RTMsgInfo(Misc::tr("VM '%ls' (%ls) has been successfully started.\n"),
2527 bstrMachineName.raw(), bstrUuid.raw());
2528 else
2529 {
2530 ProgressErrorInfo info(ptrProgress);
2531 com::GluePrintErrorInfo(info);
2532 }
2533 hrc = iRc;
2534 }
2535 }
2536 }
2537 }
2538
2539 /*
2540 * Do we wait for the VM to power down?
2541 */
2542 }
2543
2544 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2545}
2546
2547
2548RTEXITCODE handleUnattended(HandlerArg *a)
2549{
2550 /*
2551 * Sub-command switch.
2552 */
2553 if (a->argc < 1)
2554 return errorNoSubcommand();
2555
2556 if (!strcmp(a->argv[0], "detect"))
2557 {
2558 setCurrentSubcommand(HELP_SCOPE_UNATTENDED_DETECT);
2559 return handleUnattendedDetect(a);
2560 }
2561
2562 if (!strcmp(a->argv[0], "install"))
2563 {
2564 setCurrentSubcommand(HELP_SCOPE_UNATTENDED_INSTALL);
2565 return handleUnattendedInstall(a);
2566 }
2567
2568 /* Consider some kind of create-vm-and-install-guest-os command. */
2569 return errorUnknownSubcommand(a->argv[0]);
2570}
2571
2572/**
2573 * Common Cloud profile options.
2574 */
2575typedef struct
2576{
2577 const char *pszProviderName;
2578 const char *pszProfileName;
2579} CLOUDPROFILECOMMONOPT;
2580typedef CLOUDPROFILECOMMONOPT *PCLOUDPROFILECOMMONOPT;
2581
2582/**
2583 * Sets the properties of cloud profile
2584 *
2585 * @returns 0 on success, 1 on failure
2586 */
2587
2588static RTEXITCODE setCloudProfileProperties(HandlerArg *a, int iFirst, PCLOUDPROFILECOMMONOPT pCommonOpts)
2589{
2590
2591 HRESULT hrc = S_OK;
2592
2593 Bstr bstrProvider(pCommonOpts->pszProviderName);
2594 Bstr bstrProfile(pCommonOpts->pszProfileName);
2595
2596 /*
2597 * Parse options.
2598 */
2599 static const RTGETOPTDEF s_aOptions[] =
2600 {
2601 { "--clouduser", 'u', RTGETOPT_REQ_STRING },
2602 { "--fingerprint", 'p', RTGETOPT_REQ_STRING },
2603 { "--keyfile", 'k', RTGETOPT_REQ_STRING },
2604 { "--passphrase", 'P', RTGETOPT_REQ_STRING },
2605 { "--tenancy", 't', RTGETOPT_REQ_STRING },
2606 { "--compartment", 'c', RTGETOPT_REQ_STRING },
2607 { "--region", 'r', RTGETOPT_REQ_STRING }
2608 };
2609
2610 RTGETOPTSTATE GetState;
2611 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0);
2612 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
2613
2614 com::SafeArray<BSTR> names;
2615 com::SafeArray<BSTR> values;
2616
2617 int c;
2618 RTGETOPTUNION ValueUnion;
2619 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2620 {
2621 switch (c)
2622 {
2623 case 'u': // --clouduser
2624 Bstr("user").detachTo(names.appendedRaw());
2625 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2626 break;
2627 case 'p': // --fingerprint
2628 Bstr("fingerprint").detachTo(names.appendedRaw());
2629 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2630 break;
2631 case 'k': // --keyfile
2632 Bstr("key_file").detachTo(names.appendedRaw());
2633 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2634 break;
2635 case 'P': // --passphrase
2636 Bstr("pass_phrase").detachTo(names.appendedRaw());
2637 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2638 break;
2639 case 't': // --tenancy
2640 Bstr("tenancy").detachTo(names.appendedRaw());
2641 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2642 break;
2643 case 'c': // --compartment
2644 Bstr("compartment").detachTo(names.appendedRaw());
2645 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2646 break;
2647 case 'r': // --region
2648 Bstr("region").detachTo(names.appendedRaw());
2649 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2650 break;
2651 default:
2652 return errorGetOpt(c, &ValueUnion);
2653 }
2654 }
2655
2656 /* check for required options */
2657 if (bstrProvider.isEmpty())
2658 return errorSyntax(Misc::tr("Parameter --provider is required"));
2659 if (bstrProfile.isEmpty())
2660 return errorSyntax(Misc::tr("Parameter --profile is required"));
2661
2662 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
2663
2664 ComPtr<ICloudProviderManager> pCloudProviderManager;
2665 CHECK_ERROR2_RET(hrc, pVirtualBox,
2666 COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()),
2667 RTEXITCODE_FAILURE);
2668
2669 ComPtr<ICloudProvider> pCloudProvider;
2670
2671 CHECK_ERROR2_RET(hrc, pCloudProviderManager,
2672 GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()),
2673 RTEXITCODE_FAILURE);
2674
2675 ComPtr<ICloudProfile> pCloudProfile;
2676
2677 if (pCloudProvider)
2678 {
2679 CHECK_ERROR2_RET(hrc, pCloudProvider,
2680 GetProfileByName(bstrProfile.raw(), pCloudProfile.asOutParam()),
2681 RTEXITCODE_FAILURE);
2682 CHECK_ERROR2_RET(hrc, pCloudProfile,
2683 SetProperties(ComSafeArrayAsInParam(names), ComSafeArrayAsInParam(values)),
2684 RTEXITCODE_FAILURE);
2685 }
2686
2687 CHECK_ERROR2(hrc, pCloudProvider, SaveProfiles());
2688
2689 RTPrintf(Misc::tr("Provider %ls: profile '%ls' was updated.\n"),bstrProvider.raw(), bstrProfile.raw());
2690
2691 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2692}
2693
2694/**
2695 * Gets the properties of cloud profile
2696 *
2697 * @returns 0 on success, 1 on failure
2698 */
2699static RTEXITCODE showCloudProfileProperties(HandlerArg *a, PCLOUDPROFILECOMMONOPT pCommonOpts)
2700{
2701 HRESULT hrc = S_OK;
2702
2703 Bstr bstrProvider(pCommonOpts->pszProviderName);
2704 Bstr bstrProfile(pCommonOpts->pszProfileName);
2705
2706 /* check for required options */
2707 if (bstrProvider.isEmpty())
2708 return errorSyntax(Misc::tr("Parameter --provider is required"));
2709 if (bstrProfile.isEmpty())
2710 return errorSyntax(Misc::tr("Parameter --profile is required"));
2711
2712 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
2713 ComPtr<ICloudProviderManager> pCloudProviderManager;
2714 CHECK_ERROR2_RET(hrc, pVirtualBox,
2715 COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()),
2716 RTEXITCODE_FAILURE);
2717 ComPtr<ICloudProvider> pCloudProvider;
2718 CHECK_ERROR2_RET(hrc, pCloudProviderManager,
2719 GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()),
2720 RTEXITCODE_FAILURE);
2721
2722 ComPtr<ICloudProfile> pCloudProfile;
2723 if (pCloudProvider)
2724 {
2725 CHECK_ERROR2_RET(hrc, pCloudProvider,
2726 GetProfileByName(bstrProfile.raw(), pCloudProfile.asOutParam()),
2727 RTEXITCODE_FAILURE);
2728
2729 Bstr bstrProviderID;
2730 pCloudProfile->COMGETTER(ProviderId)(bstrProviderID.asOutParam());
2731 RTPrintf(Misc::tr("Provider GUID: %ls\n"), bstrProviderID.raw());
2732
2733 com::SafeArray<BSTR> names;
2734 com::SafeArray<BSTR> values;
2735 CHECK_ERROR2_RET(hrc, pCloudProfile,
2736 GetProperties(Bstr().raw(), ComSafeArrayAsOutParam(names), ComSafeArrayAsOutParam(values)),
2737 RTEXITCODE_FAILURE);
2738 size_t cNames = names.size();
2739 size_t cValues = values.size();
2740 bool fFirst = true;
2741 for (size_t k = 0; k < cNames; k++)
2742 {
2743 Bstr value;
2744 if (k < cValues)
2745 value = values[k];
2746 RTPrintf("%s%ls=%ls\n",
2747 fFirst ? Misc::tr("Property: ") : " ",
2748 names[k], value.raw());
2749 fFirst = false;
2750 }
2751
2752 RTPrintf("\n");
2753 }
2754
2755 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2756}
2757
2758static RTEXITCODE addCloudProfile(HandlerArg *a, int iFirst, PCLOUDPROFILECOMMONOPT pCommonOpts)
2759{
2760 HRESULT hrc = S_OK;
2761
2762 Bstr bstrProvider(pCommonOpts->pszProviderName);
2763 Bstr bstrProfile(pCommonOpts->pszProfileName);
2764
2765
2766 /* check for required options */
2767 if (bstrProvider.isEmpty())
2768 return errorSyntax(Misc::tr("Parameter --provider is required"));
2769 if (bstrProfile.isEmpty())
2770 return errorSyntax(Misc::tr("Parameter --profile is required"));
2771
2772 /*
2773 * Parse options.
2774 */
2775 static const RTGETOPTDEF s_aOptions[] =
2776 {
2777 { "--clouduser", 'u', RTGETOPT_REQ_STRING },
2778 { "--fingerprint", 'p', RTGETOPT_REQ_STRING },
2779 { "--keyfile", 'k', RTGETOPT_REQ_STRING },
2780 { "--passphrase", 'P', RTGETOPT_REQ_STRING },
2781 { "--tenancy", 't', RTGETOPT_REQ_STRING },
2782 { "--compartment", 'c', RTGETOPT_REQ_STRING },
2783 { "--region", 'r', RTGETOPT_REQ_STRING }
2784 };
2785
2786 RTGETOPTSTATE GetState;
2787 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0);
2788 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
2789
2790 com::SafeArray<BSTR> names;
2791 com::SafeArray<BSTR> values;
2792
2793 int c;
2794 RTGETOPTUNION ValueUnion;
2795 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2796 {
2797 switch (c)
2798 {
2799 case 'u': // --clouduser
2800 Bstr("user").detachTo(names.appendedRaw());
2801 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2802 break;
2803 case 'p': // --fingerprint
2804 Bstr("fingerprint").detachTo(names.appendedRaw());
2805 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2806 break;
2807 case 'k': // --keyfile
2808 Bstr("key_file").detachTo(names.appendedRaw());
2809 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2810 break;
2811 case 'P': // --passphrase
2812 Bstr("pass_phrase").detachTo(names.appendedRaw());
2813 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2814 break;
2815 case 't': // --tenancy
2816 Bstr("tenancy").detachTo(names.appendedRaw());
2817 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2818 break;
2819 case 'c': // --compartment
2820 Bstr("compartment").detachTo(names.appendedRaw());
2821 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2822 break;
2823 case 'r': // --region
2824 Bstr("region").detachTo(names.appendedRaw());
2825 Bstr(ValueUnion.psz).detachTo(values.appendedRaw());
2826 break;
2827 default:
2828 return errorGetOpt(c, &ValueUnion);
2829 }
2830 }
2831
2832 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
2833
2834 ComPtr<ICloudProviderManager> pCloudProviderManager;
2835 CHECK_ERROR2_RET(hrc, pVirtualBox,
2836 COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()),
2837 RTEXITCODE_FAILURE);
2838
2839 ComPtr<ICloudProvider> pCloudProvider;
2840 CHECK_ERROR2_RET(hrc, pCloudProviderManager,
2841 GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()),
2842 RTEXITCODE_FAILURE);
2843
2844 CHECK_ERROR2_RET(hrc, pCloudProvider,
2845 CreateProfile(bstrProfile.raw(),
2846 ComSafeArrayAsInParam(names),
2847 ComSafeArrayAsInParam(values)),
2848 RTEXITCODE_FAILURE);
2849
2850 CHECK_ERROR2(hrc, pCloudProvider, SaveProfiles());
2851
2852 RTPrintf(Misc::tr("Provider %ls: profile '%ls' was added.\n"),bstrProvider.raw(), bstrProfile.raw());
2853
2854 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2855}
2856
2857static RTEXITCODE deleteCloudProfile(HandlerArg *a, PCLOUDPROFILECOMMONOPT pCommonOpts)
2858{
2859 HRESULT hrc = S_OK;
2860
2861 Bstr bstrProvider(pCommonOpts->pszProviderName);
2862 Bstr bstrProfile(pCommonOpts->pszProfileName);
2863
2864 /* check for required options */
2865 if (bstrProvider.isEmpty())
2866 return errorSyntax(Misc::tr("Parameter --provider is required"));
2867 if (bstrProfile.isEmpty())
2868 return errorSyntax(Misc::tr("Parameter --profile is required"));
2869
2870 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
2871 ComPtr<ICloudProviderManager> pCloudProviderManager;
2872 CHECK_ERROR2_RET(hrc, pVirtualBox,
2873 COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()),
2874 RTEXITCODE_FAILURE);
2875 ComPtr<ICloudProvider> pCloudProvider;
2876 CHECK_ERROR2_RET(hrc, pCloudProviderManager,
2877 GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()),
2878 RTEXITCODE_FAILURE);
2879
2880 ComPtr<ICloudProfile> pCloudProfile;
2881 if (pCloudProvider)
2882 {
2883 CHECK_ERROR2_RET(hrc, pCloudProvider,
2884 GetProfileByName(bstrProfile.raw(), pCloudProfile.asOutParam()),
2885 RTEXITCODE_FAILURE);
2886
2887 CHECK_ERROR2_RET(hrc, pCloudProfile,
2888 Remove(),
2889 RTEXITCODE_FAILURE);
2890
2891 CHECK_ERROR2_RET(hrc, pCloudProvider,
2892 SaveProfiles(),
2893 RTEXITCODE_FAILURE);
2894
2895 RTPrintf(Misc::tr("Provider %ls: profile '%ls' was deleted.\n"), bstrProvider.raw(), bstrProfile.raw());
2896 }
2897
2898 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2899}
2900
2901RTEXITCODE handleCloudProfile(HandlerArg *a)
2902{
2903 if (a->argc < 1)
2904 return errorNoSubcommand();
2905
2906 static const RTGETOPTDEF s_aOptions[] =
2907 {
2908 /* common options */
2909 { "--provider", 'v', RTGETOPT_REQ_STRING },
2910 { "--profile", 'f', RTGETOPT_REQ_STRING },
2911 /* subcommands */
2912 { "add", 1000, RTGETOPT_REQ_NOTHING },
2913 { "show", 1001, RTGETOPT_REQ_NOTHING },
2914 { "update", 1002, RTGETOPT_REQ_NOTHING },
2915 { "delete", 1003, RTGETOPT_REQ_NOTHING },
2916 };
2917
2918 RTGETOPTSTATE GetState;
2919 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
2920 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
2921
2922 CLOUDPROFILECOMMONOPT CommonOpts = { NULL, NULL };
2923 int c;
2924 RTGETOPTUNION ValueUnion;
2925 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2926 {
2927 switch (c)
2928 {
2929 case 'v': // --provider
2930 CommonOpts.pszProviderName = ValueUnion.psz;
2931 break;
2932 case 'f': // --profile
2933 CommonOpts.pszProfileName = ValueUnion.psz;
2934 break;
2935 /* Sub-commands: */
2936 case 1000:
2937 setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_ADD);
2938 return addCloudProfile(a, GetState.iNext, &CommonOpts);
2939 case 1001:
2940 setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_SHOW);
2941 return showCloudProfileProperties(a, &CommonOpts);
2942 case 1002:
2943 setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_UPDATE);
2944 return setCloudProfileProperties(a, GetState.iNext, &CommonOpts);
2945 case 1003:
2946 setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_DELETE);
2947 return deleteCloudProfile(a, &CommonOpts);
2948 case VINF_GETOPT_NOT_OPTION:
2949 return errorUnknownSubcommand(ValueUnion.psz);
2950
2951 default:
2952 return errorGetOpt(c, &ValueUnion);
2953 }
2954 }
2955
2956 return errorNoSubcommand();
2957}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use