VirtualBox

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

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

doc/manual,include/VBox,Frontends/{VBoxManage,VirtualBox/src},Main/{include,SharedFolder,Console,Machine,VirtualBox,VirtualBox.xidl}: Added global shared folders and adjusted fetching and handling of folders between shared folder types bugref:3544

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

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