VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 4 weeks ago

Copyright year updates by scm.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette