VirtualBox

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

Last change on this file since 97698 was 97602, checked in by vboxsync, 2 years ago

Frontends/VBoxManage: If a user runs 'VBoxManage registervm foo.vbox'
the call to IVirtualBox::openMachine() in handleRegisterVM() will fail
since IVirtualBox::openMachine() requires an absolute path to the
settings file. However, handleRegisterVM() will retry the call with the
absolute path to the settings file in such cases. But the check that the
openMachine() failure was due to a missing settings file is insufficient,
only handling a failure in VirtualBox::i_calculateFullPath(), not a
failure to locate the relative pathname in the VirtualBox home directory
(where the global settings file is located as returned by
GetVBoxUserHomeDirectory()) so in practice the retry never happens. Fix
the check so that the absolute pathname fallback occurs as intended.

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