VirtualBox

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

Last change on this file was 105087, checked in by vboxsync, 6 days ago

doc/manual,include/VBox,Frontends/VBoxManage,HostServices/SharedFolders,
Main/{include,SharedFolder,Console,Machine,VirtualBox.xidl}: Add a
new attribute to ISharedFolder for specifying a symbolic link creation
policy to restrict the source pathname when creating symbolic links
within a guest. The symbolic link policies are represented by a new
enumeration of type SymlinkPolicy_T which includes values for no
restrictions ('any'), symlink sources only within the subtree of the
share ('subtree'), symlink sources as any relative path ('relative'),
and no symlinks allowed ('forbidden'). The symlink policy can only be
applied to permanent shared folders at this stage. bugref:10619

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

© 2023 Oracle
ContactPrivacy policyTerms of Use