VirtualBox

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

Last change on this file since 99204 was 98298, checked in by vboxsync, 16 months ago

VBoxManage: rc -> vrc/hrc. Make scm check. bugref:10223

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

© 2023 Oracle
ContactPrivacy policyTerms of Use