VirtualBox

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

Last change on this file since 70582 was 70582, checked in by vboxsync, 7 years ago

bugref:8345. First draft of move VM feature. There are some issues but base logic should stay the same. This code hasn't been built on Windows OSes yet.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 71.1 KB
Line 
1/* $Id: VBoxManageMisc.cpp 70582 2018-01-15 10:13:41Z vboxsync $ */
2/** @file
3 * VBoxManage - VirtualBox's command-line interface.
4 */
5
6/*
7 * Copyright (C) 2006-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#ifndef VBOX_ONLY_DOCS
23# include <VBox/com/com.h>
24# include <VBox/com/string.h>
25# include <VBox/com/Guid.h>
26# include <VBox/com/array.h>
27# include <VBox/com/ErrorInfo.h>
28# include <VBox/com/errorprint.h>
29# include <VBox/com/VirtualBox.h>
30#endif /* !VBOX_ONLY_DOCS */
31
32#include <iprt/asm.h>
33#include <iprt/buildconfig.h>
34#include <iprt/cidr.h>
35#include <iprt/ctype.h>
36#include <iprt/dir.h>
37#include <iprt/env.h>
38#include <VBox/err.h>
39#include <iprt/file.h>
40#include <iprt/sha.h>
41#include <iprt/initterm.h>
42#include <iprt/param.h>
43#include <iprt/path.h>
44#include <iprt/cpp/path.h>
45#include <iprt/stream.h>
46#include <iprt/string.h>
47#include <iprt/stdarg.h>
48#include <iprt/thread.h>
49#include <iprt/uuid.h>
50#include <iprt/getopt.h>
51#include <iprt/ctype.h>
52#include <VBox/version.h>
53#include <VBox/log.h>
54
55#include "VBoxManage.h"
56
57#include <list>
58
59using namespace com;
60
61
62
63RTEXITCODE handleRegisterVM(HandlerArg *a)
64{
65 HRESULT rc;
66
67 if (a->argc != 1)
68 return errorSyntax(USAGE_REGISTERVM, "Incorrect number of parameters");
69
70 ComPtr<IMachine> machine;
71 /** @todo Ugly hack to get both the API interpretation of relative paths
72 * and the client's interpretation of relative paths. Remove after the API
73 * has been redesigned. */
74 rc = a->virtualBox->OpenMachine(Bstr(a->argv[0]).raw(),
75 machine.asOutParam());
76 if (rc == VBOX_E_FILE_ERROR)
77 {
78 char szVMFileAbs[RTPATH_MAX] = "";
79 int vrc = RTPathAbs(a->argv[0], szVMFileAbs, sizeof(szVMFileAbs));
80 if (RT_FAILURE(vrc))
81 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot convert filename \"%s\" to absolute path: %Rrc", a->argv[0], vrc);
82 CHECK_ERROR(a->virtualBox, OpenMachine(Bstr(szVMFileAbs).raw(),
83 machine.asOutParam()));
84 }
85 else if (FAILED(rc))
86 CHECK_ERROR(a->virtualBox, OpenMachine(Bstr(a->argv[0]).raw(),
87 machine.asOutParam()));
88 if (SUCCEEDED(rc))
89 {
90 ASSERT(machine);
91 CHECK_ERROR(a->virtualBox, RegisterMachine(machine));
92 }
93 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
94}
95
96static const RTGETOPTDEF g_aUnregisterVMOptions[] =
97{
98 { "--delete", 'd', RTGETOPT_REQ_NOTHING },
99 { "-delete", 'd', RTGETOPT_REQ_NOTHING }, // deprecated
100};
101
102RTEXITCODE handleUnregisterVM(HandlerArg *a)
103{
104 HRESULT rc;
105 const char *VMName = NULL;
106 bool fDelete = false;
107
108 int c;
109 RTGETOPTUNION ValueUnion;
110 RTGETOPTSTATE GetState;
111 // start at 0 because main() has hacked both the argc and argv given to us
112 RTGetOptInit(&GetState, a->argc, a->argv, g_aUnregisterVMOptions, RT_ELEMENTS(g_aUnregisterVMOptions),
113 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
114 while ((c = RTGetOpt(&GetState, &ValueUnion)))
115 {
116 switch (c)
117 {
118 case 'd': // --delete
119 fDelete = true;
120 break;
121
122 case VINF_GETOPT_NOT_OPTION:
123 if (!VMName)
124 VMName = ValueUnion.psz;
125 else
126 return errorSyntax(USAGE_UNREGISTERVM, "Invalid parameter '%s'", ValueUnion.psz);
127 break;
128
129 default:
130 if (c > 0)
131 {
132 if (RT_C_IS_PRINT(c))
133 return errorSyntax(USAGE_UNREGISTERVM, "Invalid option -%c", c);
134 else
135 return errorSyntax(USAGE_UNREGISTERVM, "Invalid option case %i", c);
136 }
137 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
138 return errorSyntax(USAGE_UNREGISTERVM, "unknown option: %s\n", ValueUnion.psz);
139 else if (ValueUnion.pDef)
140 return errorSyntax(USAGE_UNREGISTERVM, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
141 else
142 return errorSyntax(USAGE_UNREGISTERVM, "error: %Rrs", c);
143 }
144 }
145
146 /* check for required options */
147 if (!VMName)
148 return errorSyntax(USAGE_UNREGISTERVM, "VM name required");
149
150 ComPtr<IMachine> machine;
151 CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(VMName).raw(),
152 machine.asOutParam()),
153 RTEXITCODE_FAILURE);
154 SafeIfaceArray<IMedium> aMedia;
155 CHECK_ERROR_RET(machine, Unregister(CleanupMode_DetachAllReturnHardDisksOnly,
156 ComSafeArrayAsOutParam(aMedia)),
157 RTEXITCODE_FAILURE);
158 if (fDelete)
159 {
160 ComPtr<IProgress> pProgress;
161 CHECK_ERROR_RET(machine, DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress.asOutParam()),
162 RTEXITCODE_FAILURE);
163
164 rc = showProgress(pProgress);
165 CHECK_PROGRESS_ERROR_RET(pProgress, ("Machine delete failed"), RTEXITCODE_FAILURE);
166 }
167 else
168 {
169 /* Note that the IMachine::Unregister method will return the medium
170 * reference in a sane order, which means that closing will normally
171 * succeed, unless there is still another machine which uses the
172 * medium. No harm done if we ignore the error. */
173 for (size_t i = 0; i < aMedia.size(); i++)
174 {
175 IMedium *pMedium = aMedia[i];
176 if (pMedium)
177 rc = pMedium->Close();
178 }
179 rc = S_OK;
180 }
181 return RTEXITCODE_SUCCESS;
182}
183
184static const RTGETOPTDEF g_aCreateVMOptions[] =
185{
186 { "--name", 'n', RTGETOPT_REQ_STRING },
187 { "-name", 'n', RTGETOPT_REQ_STRING },
188 { "--groups", 'g', RTGETOPT_REQ_STRING },
189 { "--basefolder", 'p', RTGETOPT_REQ_STRING },
190 { "-basefolder", 'p', RTGETOPT_REQ_STRING },
191 { "--ostype", 'o', RTGETOPT_REQ_STRING },
192 { "-ostype", 'o', RTGETOPT_REQ_STRING },
193 { "--uuid", 'u', RTGETOPT_REQ_UUID },
194 { "-uuid", 'u', RTGETOPT_REQ_UUID },
195 { "--register", 'r', RTGETOPT_REQ_NOTHING },
196 { "-register", 'r', RTGETOPT_REQ_NOTHING },
197};
198
199RTEXITCODE handleCreateVM(HandlerArg *a)
200{
201 HRESULT rc;
202 Bstr bstrBaseFolder;
203 Bstr bstrName;
204 Bstr bstrOsTypeId;
205 Bstr bstrUuid;
206 bool fRegister = false;
207 com::SafeArray<BSTR> groups;
208
209 int c;
210 RTGETOPTUNION ValueUnion;
211 RTGETOPTSTATE GetState;
212 // start at 0 because main() has hacked both the argc and argv given to us
213 RTGetOptInit(&GetState, a->argc, a->argv, g_aCreateVMOptions, RT_ELEMENTS(g_aCreateVMOptions),
214 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
215 while ((c = RTGetOpt(&GetState, &ValueUnion)))
216 {
217 switch (c)
218 {
219 case 'n': // --name
220 bstrName = ValueUnion.psz;
221 break;
222
223 case 'g': // --groups
224 parseGroups(ValueUnion.psz, &groups);
225 break;
226
227 case 'p': // --basefolder
228 bstrBaseFolder = ValueUnion.psz;
229 break;
230
231 case 'o': // --ostype
232 bstrOsTypeId = ValueUnion.psz;
233 break;
234
235 case 'u': // --uuid
236 bstrUuid = Guid(ValueUnion.Uuid).toUtf16().raw();
237 break;
238
239 case 'r': // --register
240 fRegister = true;
241 break;
242
243 default:
244 return errorGetOpt(USAGE_CREATEVM, c, &ValueUnion);
245 }
246 }
247
248 /* check for required options */
249 if (bstrName.isEmpty())
250 return errorSyntax(USAGE_CREATEVM, "Parameter --name is required");
251
252 do
253 {
254 Bstr createFlags;
255 if (!bstrUuid.isEmpty())
256 createFlags = BstrFmt("UUID=%ls", bstrUuid.raw());
257 Bstr bstrPrimaryGroup;
258 if (groups.size())
259 bstrPrimaryGroup = groups[0];
260 Bstr bstrSettingsFile;
261 CHECK_ERROR_BREAK(a->virtualBox,
262 ComposeMachineFilename(bstrName.raw(),
263 bstrPrimaryGroup.raw(),
264 createFlags.raw(),
265 bstrBaseFolder.raw(),
266 bstrSettingsFile.asOutParam()));
267 ComPtr<IMachine> machine;
268 CHECK_ERROR_BREAK(a->virtualBox,
269 CreateMachine(bstrSettingsFile.raw(),
270 bstrName.raw(),
271 ComSafeArrayAsInParam(groups),
272 bstrOsTypeId.raw(),
273 createFlags.raw(),
274 machine.asOutParam()));
275
276 CHECK_ERROR_BREAK(machine, SaveSettings());
277 if (fRegister)
278 {
279 CHECK_ERROR_BREAK(a->virtualBox, RegisterMachine(machine));
280 }
281 Bstr uuid;
282 CHECK_ERROR_BREAK(machine, COMGETTER(Id)(uuid.asOutParam()));
283 Bstr settingsFile;
284 CHECK_ERROR_BREAK(machine, COMGETTER(SettingsFilePath)(settingsFile.asOutParam()));
285 RTPrintf("Virtual machine '%ls' is created%s.\n"
286 "UUID: %s\n"
287 "Settings file: '%ls'\n",
288 bstrName.raw(), fRegister ? " and registered" : "",
289 Utf8Str(uuid).c_str(), settingsFile.raw());
290 }
291 while (0);
292
293 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
294}
295
296static const RTGETOPTDEF g_aMoveVMOptions[] =
297{
298 { "--type", 't', RTGETOPT_REQ_STRING },
299 { "--folder", 'f', RTGETOPT_REQ_STRING },
300};
301
302RTEXITCODE handleMoveVM(HandlerArg *a)
303{
304 HRESULT rc;
305 const char *pszSrcName = NULL;
306 const char *pszTargetFolder = NULL;
307 const char *pszType = NULL;
308
309 int c;
310 int vrc = VINF_SUCCESS;
311 RTGETOPTUNION ValueUnion;
312 RTGETOPTSTATE GetState;
313
314 // start at 0 because main() has hacked both the argc and argv given to us
315 RTGetOptInit(&GetState, a->argc, a->argv, g_aMoveVMOptions, RT_ELEMENTS(g_aMoveVMOptions),
316 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
317 while ((c = RTGetOpt(&GetState, &ValueUnion)))
318 {
319 switch (c)
320 {
321 case 't': // --type
322 pszType = ValueUnion.psz;
323 break;
324
325 case 'f': // --target folder
326
327 char szPath[RTPATH_MAX];
328 pszTargetFolder = ValueUnion.psz;
329
330 vrc = RTPathAbs(pszTargetFolder, szPath, sizeof(szPath));
331 if (RT_FAILURE(vrc))
332 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs(%s,,) failed with rc=%Rrc", pszTargetFolder, vrc);
333 break;
334
335 case VINF_GETOPT_NOT_OPTION:
336 if (!pszSrcName)
337 pszSrcName = ValueUnion.psz;
338 else
339 return errorSyntax(USAGE_MOVEVM, "Invalid parameter '%s'", ValueUnion.psz);
340 break;
341
342 default:
343 return errorGetOpt(USAGE_MOVEVM, c, &ValueUnion);
344 }
345 }
346
347
348 /* Check for required options */
349 if (!pszSrcName)
350 return errorSyntax(USAGE_MOVEVM, "VM name required");
351
352 /* Get the machine object */
353 ComPtr<IMachine> srcMachine;
354 CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(pszSrcName).raw(),
355 srcMachine.asOutParam()),
356 RTEXITCODE_FAILURE);
357
358 if (srcMachine)
359 {
360 /* Start the moving */
361 ComPtr<IProgress> progress;
362 do
363 {
364 /* we have to open a session for this task */
365 CHECK_ERROR_BREAK(srcMachine, LockMachine(a->session, LockType_Write));
366 ComPtr<IMachine> sessionMachine;
367 do
368 {
369 CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
370 CHECK_ERROR_BREAK(sessionMachine, MoveTo(Bstr(pszTargetFolder).raw(),
371 Bstr(pszType).raw(),
372 progress.asOutParam()));
373 rc = showProgress(progress);
374 CHECK_PROGRESS_ERROR_RET(progress, ("Move VM failed"), RTEXITCODE_FAILURE);
375// CHECK_ERROR_BREAK(sessionMachine, SaveSettings());
376 } while (0);
377
378 sessionMachine.setNull();
379 CHECK_ERROR_BREAK(a->session, UnlockMachine());
380 } while (0);
381 }
382
383 RTPrintf("Machine has been successfully moved into %s\n", pszTargetFolder);
384
385 return RTEXITCODE_SUCCESS;
386}
387
388static const RTGETOPTDEF g_aCloneVMOptions[] =
389{
390 { "--snapshot", 's', RTGETOPT_REQ_STRING },
391 { "--name", 'n', RTGETOPT_REQ_STRING },
392 { "--groups", 'g', RTGETOPT_REQ_STRING },
393 { "--mode", 'm', RTGETOPT_REQ_STRING },
394 { "--options", 'o', RTGETOPT_REQ_STRING },
395 { "--register", 'r', RTGETOPT_REQ_NOTHING },
396 { "--basefolder", 'p', RTGETOPT_REQ_STRING },
397 { "--uuid", 'u', RTGETOPT_REQ_UUID },
398};
399
400static int parseCloneMode(const char *psz, CloneMode_T *pMode)
401{
402 if (!RTStrICmp(psz, "machine"))
403 *pMode = CloneMode_MachineState;
404 else if (!RTStrICmp(psz, "machineandchildren"))
405 *pMode = CloneMode_MachineAndChildStates;
406 else if (!RTStrICmp(psz, "all"))
407 *pMode = CloneMode_AllStates;
408 else
409 return VERR_PARSE_ERROR;
410
411 return VINF_SUCCESS;
412}
413
414static int parseCloneOptions(const char *psz, com::SafeArray<CloneOptions_T> *options)
415{
416 int rc = VINF_SUCCESS;
417 while (psz && *psz && RT_SUCCESS(rc))
418 {
419 size_t len;
420 const char *pszComma = strchr(psz, ',');
421 if (pszComma)
422 len = pszComma - psz;
423 else
424 len = strlen(psz);
425 if (len > 0)
426 {
427 if (!RTStrNICmp(psz, "KeepAllMACs", len))
428 options->push_back(CloneOptions_KeepAllMACs);
429 else if (!RTStrNICmp(psz, "KeepNATMACs", len))
430 options->push_back(CloneOptions_KeepNATMACs);
431 else if (!RTStrNICmp(psz, "KeepDiskNames", len))
432 options->push_back(CloneOptions_KeepDiskNames);
433 else if ( !RTStrNICmp(psz, "Link", len)
434 || !RTStrNICmp(psz, "Linked", len))
435 options->push_back(CloneOptions_Link);
436 else
437 rc = VERR_PARSE_ERROR;
438 }
439 if (pszComma)
440 psz += len + 1;
441 else
442 psz += len;
443 }
444
445 return rc;
446}
447
448RTEXITCODE handleCloneVM(HandlerArg *a)
449{
450 HRESULT rc;
451 const char *pszSrcName = NULL;
452 const char *pszSnapshotName = NULL;
453 CloneMode_T mode = CloneMode_MachineState;
454 com::SafeArray<CloneOptions_T> options;
455 const char *pszTrgName = NULL;
456 const char *pszTrgBaseFolder = NULL;
457 bool fRegister = false;
458 Bstr bstrUuid;
459 com::SafeArray<BSTR> groups;
460
461 int c;
462 RTGETOPTUNION ValueUnion;
463 RTGETOPTSTATE GetState;
464 // start at 0 because main() has hacked both the argc and argv given to us
465 RTGetOptInit(&GetState, a->argc, a->argv, g_aCloneVMOptions, RT_ELEMENTS(g_aCloneVMOptions),
466 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
467 while ((c = RTGetOpt(&GetState, &ValueUnion)))
468 {
469 switch (c)
470 {
471 case 's': // --snapshot
472 pszSnapshotName = ValueUnion.psz;
473 break;
474
475 case 'n': // --name
476 pszTrgName = ValueUnion.psz;
477 break;
478
479 case 'g': // --groups
480 parseGroups(ValueUnion.psz, &groups);
481 break;
482
483 case 'p': // --basefolder
484 pszTrgBaseFolder = ValueUnion.psz;
485 break;
486
487 case 'm': // --mode
488 if (RT_FAILURE(parseCloneMode(ValueUnion.psz, &mode)))
489 return errorArgument("Invalid clone mode '%s'\n", ValueUnion.psz);
490 break;
491
492 case 'o': // --options
493 if (RT_FAILURE(parseCloneOptions(ValueUnion.psz, &options)))
494 return errorArgument("Invalid clone options '%s'\n", ValueUnion.psz);
495 break;
496
497 case 'u': // --uuid
498 bstrUuid = Guid(ValueUnion.Uuid).toUtf16().raw();
499 break;
500
501 case 'r': // --register
502 fRegister = true;
503 break;
504
505 case VINF_GETOPT_NOT_OPTION:
506 if (!pszSrcName)
507 pszSrcName = ValueUnion.psz;
508 else
509 return errorSyntax(USAGE_CLONEVM, "Invalid parameter '%s'", ValueUnion.psz);
510 break;
511
512 default:
513 return errorGetOpt(USAGE_CLONEVM, c, &ValueUnion);
514 }
515 }
516
517 /* Check for required options */
518 if (!pszSrcName)
519 return errorSyntax(USAGE_CLONEVM, "VM name required");
520
521 /* Get the machine object */
522 ComPtr<IMachine> srcMachine;
523 CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(pszSrcName).raw(),
524 srcMachine.asOutParam()),
525 RTEXITCODE_FAILURE);
526
527 /* If a snapshot name/uuid was given, get the particular machine of this
528 * snapshot. */
529 if (pszSnapshotName)
530 {
531 ComPtr<ISnapshot> srcSnapshot;
532 CHECK_ERROR_RET(srcMachine, FindSnapshot(Bstr(pszSnapshotName).raw(),
533 srcSnapshot.asOutParam()),
534 RTEXITCODE_FAILURE);
535 CHECK_ERROR_RET(srcSnapshot, COMGETTER(Machine)(srcMachine.asOutParam()),
536 RTEXITCODE_FAILURE);
537 }
538
539 /* Default name necessary? */
540 if (!pszTrgName)
541 pszTrgName = RTStrAPrintf2("%s Clone", pszSrcName);
542
543 Bstr createFlags;
544 if (!bstrUuid.isEmpty())
545 createFlags = BstrFmt("UUID=%ls", bstrUuid.raw());
546 Bstr bstrPrimaryGroup;
547 if (groups.size())
548 bstrPrimaryGroup = groups[0];
549 Bstr bstrSettingsFile;
550 CHECK_ERROR_RET(a->virtualBox,
551 ComposeMachineFilename(Bstr(pszTrgName).raw(),
552 bstrPrimaryGroup.raw(),
553 createFlags.raw(),
554 Bstr(pszTrgBaseFolder).raw(),
555 bstrSettingsFile.asOutParam()),
556 RTEXITCODE_FAILURE);
557
558 ComPtr<IMachine> trgMachine;
559 CHECK_ERROR_RET(a->virtualBox, CreateMachine(bstrSettingsFile.raw(),
560 Bstr(pszTrgName).raw(),
561 ComSafeArrayAsInParam(groups),
562 NULL,
563 createFlags.raw(),
564 trgMachine.asOutParam()),
565 RTEXITCODE_FAILURE);
566
567 /* Start the cloning */
568 ComPtr<IProgress> progress;
569 CHECK_ERROR_RET(srcMachine, CloneTo(trgMachine,
570 mode,
571 ComSafeArrayAsInParam(options),
572 progress.asOutParam()),
573 RTEXITCODE_FAILURE);
574 rc = showProgress(progress);
575 CHECK_PROGRESS_ERROR_RET(progress, ("Clone VM failed"), RTEXITCODE_FAILURE);
576
577 if (fRegister)
578 CHECK_ERROR_RET(a->virtualBox, RegisterMachine(trgMachine), RTEXITCODE_FAILURE);
579
580 Bstr bstrNewName;
581 CHECK_ERROR_RET(trgMachine, COMGETTER(Name)(bstrNewName.asOutParam()), RTEXITCODE_FAILURE);
582 RTPrintf("Machine has been successfully cloned as \"%ls\"\n", bstrNewName.raw());
583
584 return RTEXITCODE_SUCCESS;
585}
586
587RTEXITCODE handleStartVM(HandlerArg *a)
588{
589 HRESULT rc = S_OK;
590 std::list<const char *> VMs;
591 Bstr sessionType;
592 Utf8Str strEnv;
593
594#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
595 /* make sure the VM process will by default start on the same display as VBoxManage */
596 {
597 const char *pszDisplay = RTEnvGet("DISPLAY");
598 if (pszDisplay)
599 strEnv = Utf8StrFmt("DISPLAY=%s\n", pszDisplay);
600 const char *pszXAuth = RTEnvGet("XAUTHORITY");
601 if (pszXAuth)
602 strEnv.append(Utf8StrFmt("XAUTHORITY=%s\n", pszXAuth));
603 }
604#endif
605
606 static const RTGETOPTDEF s_aStartVMOptions[] =
607 {
608 { "--type", 't', RTGETOPT_REQ_STRING },
609 { "-type", 't', RTGETOPT_REQ_STRING }, // deprecated
610 { "--putenv", 'E', RTGETOPT_REQ_STRING },
611 };
612 int c;
613 RTGETOPTUNION ValueUnion;
614 RTGETOPTSTATE GetState;
615 // start at 0 because main() has hacked both the argc and argv given to us
616 RTGetOptInit(&GetState, a->argc, a->argv, s_aStartVMOptions, RT_ELEMENTS(s_aStartVMOptions),
617 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
618 while ((c = RTGetOpt(&GetState, &ValueUnion)))
619 {
620 switch (c)
621 {
622 case 't': // --type
623 if (!RTStrICmp(ValueUnion.psz, "gui"))
624 {
625 sessionType = "gui";
626 }
627#ifdef VBOX_WITH_VBOXSDL
628 else if (!RTStrICmp(ValueUnion.psz, "sdl"))
629 {
630 sessionType = "sdl";
631 }
632#endif
633#ifdef VBOX_WITH_HEADLESS
634 else if (!RTStrICmp(ValueUnion.psz, "capture"))
635 {
636 sessionType = "capture";
637 }
638 else if (!RTStrICmp(ValueUnion.psz, "headless"))
639 {
640 sessionType = "headless";
641 }
642#endif
643 else
644 sessionType = ValueUnion.psz;
645 break;
646
647 case 'E': // --putenv
648 if (!RTStrStr(ValueUnion.psz, "\n"))
649 strEnv.append(Utf8StrFmt("%s\n", ValueUnion.psz));
650 else
651 return errorSyntax(USAGE_STARTVM, "Parameter to option --putenv must not contain any newline character");
652 break;
653
654 case VINF_GETOPT_NOT_OPTION:
655 VMs.push_back(ValueUnion.psz);
656 break;
657
658 default:
659 if (c > 0)
660 {
661 if (RT_C_IS_PRINT(c))
662 return errorSyntax(USAGE_STARTVM, "Invalid option -%c", c);
663 else
664 return errorSyntax(USAGE_STARTVM, "Invalid option case %i", c);
665 }
666 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
667 return errorSyntax(USAGE_STARTVM, "unknown option: %s\n", ValueUnion.psz);
668 else if (ValueUnion.pDef)
669 return errorSyntax(USAGE_STARTVM, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
670 else
671 return errorSyntax(USAGE_STARTVM, "error: %Rrs", c);
672 }
673 }
674
675 /* check for required options */
676 if (VMs.empty())
677 return errorSyntax(USAGE_STARTVM, "at least one VM name or uuid required");
678
679 for (std::list<const char *>::const_iterator it = VMs.begin();
680 it != VMs.end();
681 ++it)
682 {
683 HRESULT rc2 = rc;
684 const char *pszVM = *it;
685 ComPtr<IMachine> machine;
686 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszVM).raw(),
687 machine.asOutParam()));
688 if (machine)
689 {
690 ComPtr<IProgress> progress;
691 CHECK_ERROR(machine, LaunchVMProcess(a->session, sessionType.raw(),
692 Bstr(strEnv).raw(), progress.asOutParam()));
693 if (SUCCEEDED(rc) && !progress.isNull())
694 {
695 RTPrintf("Waiting for VM \"%s\" to power on...\n", pszVM);
696 CHECK_ERROR(progress, WaitForCompletion(-1));
697 if (SUCCEEDED(rc))
698 {
699 BOOL completed = true;
700 CHECK_ERROR(progress, COMGETTER(Completed)(&completed));
701 if (SUCCEEDED(rc))
702 {
703 ASSERT(completed);
704
705 LONG iRc;
706 CHECK_ERROR(progress, COMGETTER(ResultCode)(&iRc));
707 if (SUCCEEDED(rc))
708 {
709 if (SUCCEEDED(iRc))
710 RTPrintf("VM \"%s\" has been successfully started.\n", pszVM);
711 else
712 {
713 ProgressErrorInfo info(progress);
714 com::GluePrintErrorInfo(info);
715 }
716 rc = iRc;
717 }
718 }
719 }
720 }
721 }
722
723 /* it's important to always close sessions */
724 a->session->UnlockMachine();
725
726 /* make sure that we remember the failed state */
727 if (FAILED(rc2))
728 rc = rc2;
729 }
730
731 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
732}
733
734RTEXITCODE handleDiscardState(HandlerArg *a)
735{
736 HRESULT rc;
737
738 if (a->argc != 1)
739 return errorSyntax(USAGE_DISCARDSTATE, "Incorrect number of parameters");
740
741 ComPtr<IMachine> machine;
742 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
743 machine.asOutParam()));
744 if (machine)
745 {
746 do
747 {
748 /* we have to open a session for this task */
749 CHECK_ERROR_BREAK(machine, LockMachine(a->session, LockType_Write));
750 do
751 {
752 ComPtr<IMachine> sessionMachine;
753 CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
754 CHECK_ERROR_BREAK(sessionMachine, DiscardSavedState(true /* fDeleteFile */));
755 } while (0);
756 CHECK_ERROR_BREAK(a->session, UnlockMachine());
757 } while (0);
758 }
759
760 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
761}
762
763RTEXITCODE handleAdoptState(HandlerArg *a)
764{
765 HRESULT rc;
766
767 if (a->argc != 2)
768 return errorSyntax(USAGE_ADOPTSTATE, "Incorrect number of parameters");
769
770 ComPtr<IMachine> machine;
771 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
772 machine.asOutParam()));
773 if (machine)
774 {
775 char szStateFileAbs[RTPATH_MAX] = "";
776 int vrc = RTPathAbs(a->argv[1], szStateFileAbs, sizeof(szStateFileAbs));
777 if (RT_FAILURE(vrc))
778 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot convert filename \"%s\" to absolute path: %Rrc", a->argv[0], vrc);
779
780 do
781 {
782 /* we have to open a session for this task */
783 CHECK_ERROR_BREAK(machine, LockMachine(a->session, LockType_Write));
784 do
785 {
786 ComPtr<IMachine> sessionMachine;
787 CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
788 CHECK_ERROR_BREAK(sessionMachine, AdoptSavedState(Bstr(szStateFileAbs).raw()));
789 } while (0);
790 CHECK_ERROR_BREAK(a->session, UnlockMachine());
791 } while (0);
792 }
793
794 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
795}
796
797RTEXITCODE handleGetExtraData(HandlerArg *a)
798{
799 HRESULT rc = S_OK;
800
801 if (a->argc != 2)
802 return errorSyntax(USAGE_GETEXTRADATA, "Incorrect number of parameters");
803
804 /* global data? */
805 if (!strcmp(a->argv[0], "global"))
806 {
807 /* enumeration? */
808 if (!strcmp(a->argv[1], "enumerate"))
809 {
810 SafeArray<BSTR> aKeys;
811 CHECK_ERROR(a->virtualBox, GetExtraDataKeys(ComSafeArrayAsOutParam(aKeys)));
812
813 for (size_t i = 0;
814 i < aKeys.size();
815 ++i)
816 {
817 Bstr bstrKey(aKeys[i]);
818 Bstr bstrValue;
819 CHECK_ERROR(a->virtualBox, GetExtraData(bstrKey.raw(),
820 bstrValue.asOutParam()));
821
822 RTPrintf("Key: %ls, Value: %ls\n", bstrKey.raw(), bstrValue.raw());
823 }
824 }
825 else
826 {
827 Bstr value;
828 CHECK_ERROR(a->virtualBox, GetExtraData(Bstr(a->argv[1]).raw(),
829 value.asOutParam()));
830 if (!value.isEmpty())
831 RTPrintf("Value: %ls\n", value.raw());
832 else
833 RTPrintf("No value set!\n");
834 }
835 }
836 else
837 {
838 ComPtr<IMachine> machine;
839 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
840 machine.asOutParam()));
841 if (machine)
842 {
843 /* enumeration? */
844 if (!strcmp(a->argv[1], "enumerate"))
845 {
846 SafeArray<BSTR> aKeys;
847 CHECK_ERROR(machine, GetExtraDataKeys(ComSafeArrayAsOutParam(aKeys)));
848
849 for (size_t i = 0;
850 i < aKeys.size();
851 ++i)
852 {
853 Bstr bstrKey(aKeys[i]);
854 Bstr bstrValue;
855 CHECK_ERROR(machine, GetExtraData(bstrKey.raw(),
856 bstrValue.asOutParam()));
857
858 RTPrintf("Key: %ls, Value: %ls\n", bstrKey.raw(), bstrValue.raw());
859 }
860 }
861 else
862 {
863 Bstr value;
864 CHECK_ERROR(machine, GetExtraData(Bstr(a->argv[1]).raw(),
865 value.asOutParam()));
866 if (!value.isEmpty())
867 RTPrintf("Value: %ls\n", value.raw());
868 else
869 RTPrintf("No value set!\n");
870 }
871 }
872 }
873 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
874}
875
876RTEXITCODE handleSetExtraData(HandlerArg *a)
877{
878 HRESULT rc = S_OK;
879
880 if (a->argc < 2)
881 return errorSyntax(USAGE_SETEXTRADATA, "Not enough parameters");
882
883 /* global data? */
884 if (!strcmp(a->argv[0], "global"))
885 {
886 /** @todo passing NULL is deprecated */
887 if (a->argc < 3)
888 CHECK_ERROR(a->virtualBox, SetExtraData(Bstr(a->argv[1]).raw(),
889 NULL));
890 else if (a->argc == 3)
891 CHECK_ERROR(a->virtualBox, SetExtraData(Bstr(a->argv[1]).raw(),
892 Bstr(a->argv[2]).raw()));
893 else
894 return errorSyntax(USAGE_SETEXTRADATA, "Too many parameters");
895 }
896 else
897 {
898 ComPtr<IMachine> machine;
899 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
900 machine.asOutParam()));
901 if (machine)
902 {
903 /* open an existing session for the VM */
904 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
905 /* get the session machine */
906 ComPtr<IMachine> sessionMachine;
907 CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE);
908 /** @todo passing NULL is deprecated */
909 if (a->argc < 3)
910 CHECK_ERROR(sessionMachine, SetExtraData(Bstr(a->argv[1]).raw(),
911 NULL));
912 else if (a->argc == 3)
913 CHECK_ERROR(sessionMachine, SetExtraData(Bstr(a->argv[1]).raw(),
914 Bstr(a->argv[2]).raw()));
915 else
916 return errorSyntax(USAGE_SETEXTRADATA, "Too many parameters");
917 }
918 }
919 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
920}
921
922RTEXITCODE handleSetProperty(HandlerArg *a)
923{
924 HRESULT rc;
925
926 /* there must be two arguments: property name and value */
927 if (a->argc != 2)
928 return errorSyntax(USAGE_SETPROPERTY, "Incorrect number of parameters");
929
930 ComPtr<ISystemProperties> systemProperties;
931 a->virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam());
932
933 if (!strcmp(a->argv[0], "machinefolder"))
934 {
935 /* reset to default? */
936 if (!strcmp(a->argv[1], "default"))
937 CHECK_ERROR(systemProperties, COMSETTER(DefaultMachineFolder)(NULL));
938 else
939 CHECK_ERROR(systemProperties, COMSETTER(DefaultMachineFolder)(Bstr(a->argv[1]).raw()));
940 }
941 else if (!strcmp(a->argv[0], "hwvirtexclusive"))
942 {
943 bool fHwVirtExclusive;
944
945 if (!strcmp(a->argv[1], "on"))
946 fHwVirtExclusive = true;
947 else if (!strcmp(a->argv[1], "off"))
948 fHwVirtExclusive = false;
949 else
950 return errorArgument("Invalid hwvirtexclusive argument '%s'", a->argv[1]);
951 CHECK_ERROR(systemProperties, COMSETTER(ExclusiveHwVirt)(fHwVirtExclusive));
952 }
953 else if ( !strcmp(a->argv[0], "vrdeauthlibrary")
954 || !strcmp(a->argv[0], "vrdpauthlibrary"))
955 {
956 if (!strcmp(a->argv[0], "vrdpauthlibrary"))
957 RTStrmPrintf(g_pStdErr, "Warning: 'vrdpauthlibrary' is deprecated. Use 'vrdeauthlibrary'.\n");
958
959 /* reset to default? */
960 if (!strcmp(a->argv[1], "default"))
961 CHECK_ERROR(systemProperties, COMSETTER(VRDEAuthLibrary)(NULL));
962 else
963 CHECK_ERROR(systemProperties, COMSETTER(VRDEAuthLibrary)(Bstr(a->argv[1]).raw()));
964 }
965 else if (!strcmp(a->argv[0], "websrvauthlibrary"))
966 {
967 /* reset to default? */
968 if (!strcmp(a->argv[1], "default"))
969 CHECK_ERROR(systemProperties, COMSETTER(WebServiceAuthLibrary)(NULL));
970 else
971 CHECK_ERROR(systemProperties, COMSETTER(WebServiceAuthLibrary)(Bstr(a->argv[1]).raw()));
972 }
973 else if (!strcmp(a->argv[0], "vrdeextpack"))
974 {
975 /* disable? */
976 if (!strcmp(a->argv[1], "null"))
977 CHECK_ERROR(systemProperties, COMSETTER(DefaultVRDEExtPack)(NULL));
978 else
979 CHECK_ERROR(systemProperties, COMSETTER(DefaultVRDEExtPack)(Bstr(a->argv[1]).raw()));
980 }
981 else if (!strcmp(a->argv[0], "loghistorycount"))
982 {
983 uint32_t uVal;
984 int vrc;
985 vrc = RTStrToUInt32Ex(a->argv[1], NULL, 0, &uVal);
986 if (vrc != VINF_SUCCESS)
987 return errorArgument("Error parsing Log history count '%s'", a->argv[1]);
988 CHECK_ERROR(systemProperties, COMSETTER(LogHistoryCount)(uVal));
989 }
990 else if (!strcmp(a->argv[0], "autostartdbpath"))
991 {
992 /* disable? */
993 if (!strcmp(a->argv[1], "null"))
994 CHECK_ERROR(systemProperties, COMSETTER(AutostartDatabasePath)(NULL));
995 else
996 CHECK_ERROR(systemProperties, COMSETTER(AutostartDatabasePath)(Bstr(a->argv[1]).raw()));
997 }
998 else if (!strcmp(a->argv[0], "defaultfrontend"))
999 {
1000 Bstr bstrDefaultFrontend(a->argv[1]);
1001 if (!strcmp(a->argv[1], "default"))
1002 bstrDefaultFrontend.setNull();
1003 CHECK_ERROR(systemProperties, COMSETTER(DefaultFrontend)(bstrDefaultFrontend.raw()));
1004 }
1005 else if (!strcmp(a->argv[0], "logginglevel"))
1006 {
1007 Bstr bstrLoggingLevel(a->argv[1]);
1008 if (!strcmp(a->argv[1], "default"))
1009 bstrLoggingLevel.setNull();
1010 CHECK_ERROR(systemProperties, COMSETTER(LoggingLevel)(bstrLoggingLevel.raw()));
1011 }
1012 else
1013 return errorSyntax(USAGE_SETPROPERTY, "Invalid parameter '%s'", a->argv[0]);
1014
1015 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1016}
1017
1018RTEXITCODE handleSharedFolder(HandlerArg *a)
1019{
1020 HRESULT rc;
1021
1022 /* we need at least a command and target */
1023 if (a->argc < 2)
1024 return errorSyntax(USAGE_SHAREDFOLDER, "Not enough parameters");
1025
1026 const char *pszMachineName = a->argv[1];
1027 ComPtr<IMachine> machine;
1028 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszMachineName).raw(), machine.asOutParam()));
1029 if (!machine)
1030 return RTEXITCODE_FAILURE;
1031
1032 if (!strcmp(a->argv[0], "add"))
1033 {
1034 /* we need at least four more parameters */
1035 if (a->argc < 5)
1036 return errorSyntax(USAGE_SHAREDFOLDER_ADD, "Not enough parameters");
1037
1038 char *name = NULL;
1039 char *hostpath = NULL;
1040 bool fTransient = false;
1041 bool fWritable = true;
1042 bool fAutoMount = false;
1043
1044 for (int i = 2; i < a->argc; i++)
1045 {
1046 if ( !strcmp(a->argv[i], "--name")
1047 || !strcmp(a->argv[i], "-name"))
1048 {
1049 if (a->argc <= i + 1 || !*a->argv[i+1])
1050 return errorArgument("Missing argument to '%s'", a->argv[i]);
1051 i++;
1052 name = a->argv[i];
1053 }
1054 else if ( !strcmp(a->argv[i], "--hostpath")
1055 || !strcmp(a->argv[i], "-hostpath"))
1056 {
1057 if (a->argc <= i + 1 || !*a->argv[i+1])
1058 return errorArgument("Missing argument to '%s'", a->argv[i]);
1059 i++;
1060 hostpath = a->argv[i];
1061 }
1062 else if ( !strcmp(a->argv[i], "--readonly")
1063 || !strcmp(a->argv[i], "-readonly"))
1064 {
1065 fWritable = false;
1066 }
1067 else if ( !strcmp(a->argv[i], "--transient")
1068 || !strcmp(a->argv[i], "-transient"))
1069 {
1070 fTransient = true;
1071 }
1072 else if ( !strcmp(a->argv[i], "--automount")
1073 || !strcmp(a->argv[i], "-automount"))
1074 {
1075 fAutoMount = true;
1076 }
1077 else
1078 return errorSyntax(USAGE_SHAREDFOLDER_ADD, "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str());
1079 }
1080
1081 if (NULL != strstr(name, " "))
1082 return errorSyntax(USAGE_SHAREDFOLDER_ADD, "No spaces allowed in parameter '-name'!");
1083
1084 /* required arguments */
1085 if (!name || !hostpath)
1086 {
1087 return errorSyntax(USAGE_SHAREDFOLDER_ADD, "Parameters --name and --hostpath are required");
1088 }
1089
1090 if (fTransient)
1091 {
1092 ComPtr<IConsole> console;
1093
1094 /* open an existing session for the VM */
1095 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
1096
1097 /* get the session machine */
1098 ComPtr<IMachine> sessionMachine;
1099 CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE);
1100
1101 /* get the session console */
1102 CHECK_ERROR_RET(a->session, COMGETTER(Console)(console.asOutParam()), RTEXITCODE_FAILURE);
1103 if (console.isNull())
1104 return RTMsgErrorExit(RTEXITCODE_FAILURE,
1105 "Machine '%s' is not currently running.\n", pszMachineName);
1106
1107 CHECK_ERROR(console, CreateSharedFolder(Bstr(name).raw(),
1108 Bstr(hostpath).raw(),
1109 fWritable, fAutoMount));
1110 a->session->UnlockMachine();
1111 }
1112 else
1113 {
1114 /* open a session for the VM */
1115 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE);
1116
1117 /* get the mutable session machine */
1118 ComPtr<IMachine> sessionMachine;
1119 a->session->COMGETTER(Machine)(sessionMachine.asOutParam());
1120
1121 CHECK_ERROR(sessionMachine, CreateSharedFolder(Bstr(name).raw(),
1122 Bstr(hostpath).raw(),
1123 fWritable, fAutoMount));
1124 if (SUCCEEDED(rc))
1125 CHECK_ERROR(sessionMachine, SaveSettings());
1126
1127 a->session->UnlockMachine();
1128 }
1129 }
1130 else if (!strcmp(a->argv[0], "remove"))
1131 {
1132 /* we need at least two more parameters */
1133 if (a->argc < 3)
1134 return errorSyntax(USAGE_SHAREDFOLDER_REMOVE, "Not enough parameters");
1135
1136 char *name = NULL;
1137 bool fTransient = false;
1138
1139 for (int i = 2; i < a->argc; i++)
1140 {
1141 if ( !strcmp(a->argv[i], "--name")
1142 || !strcmp(a->argv[i], "-name"))
1143 {
1144 if (a->argc <= i + 1 || !*a->argv[i+1])
1145 return errorArgument("Missing argument to '%s'", a->argv[i]);
1146 i++;
1147 name = a->argv[i];
1148 }
1149 else if ( !strcmp(a->argv[i], "--transient")
1150 || !strcmp(a->argv[i], "-transient"))
1151 {
1152 fTransient = true;
1153 }
1154 else
1155 return errorSyntax(USAGE_SHAREDFOLDER_REMOVE, "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str());
1156 }
1157
1158 /* required arguments */
1159 if (!name)
1160 return errorSyntax(USAGE_SHAREDFOLDER_REMOVE, "Parameter --name is required");
1161
1162 if (fTransient)
1163 {
1164 /* open an existing session for the VM */
1165 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
1166 /* get the session machine */
1167 ComPtr<IMachine> sessionMachine;
1168 CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE);
1169 /* get the session console */
1170 ComPtr<IConsole> console;
1171 CHECK_ERROR_RET(a->session, COMGETTER(Console)(console.asOutParam()), RTEXITCODE_FAILURE);
1172 if (console.isNull())
1173 return RTMsgErrorExit(RTEXITCODE_FAILURE,
1174 "Machine '%s' is not currently running.\n", pszMachineName);
1175
1176 CHECK_ERROR(console, RemoveSharedFolder(Bstr(name).raw()));
1177
1178 a->session->UnlockMachine();
1179 }
1180 else
1181 {
1182 /* open a session for the VM */
1183 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE);
1184
1185 /* get the mutable session machine */
1186 ComPtr<IMachine> sessionMachine;
1187 a->session->COMGETTER(Machine)(sessionMachine.asOutParam());
1188
1189 CHECK_ERROR(sessionMachine, RemoveSharedFolder(Bstr(name).raw()));
1190
1191 /* commit and close the session */
1192 CHECK_ERROR(sessionMachine, SaveSettings());
1193 a->session->UnlockMachine();
1194 }
1195 }
1196 else
1197 return errorSyntax(USAGE_SHAREDFOLDER, "Invalid parameter '%s'", Utf8Str(a->argv[0]).c_str());
1198
1199 return RTEXITCODE_SUCCESS;
1200}
1201
1202RTEXITCODE handleExtPack(HandlerArg *a)
1203{
1204 if (a->argc < 1)
1205 return errorNoSubcommand();
1206
1207 ComObjPtr<IExtPackManager> ptrExtPackMgr;
1208 CHECK_ERROR2I_RET(a->virtualBox, COMGETTER(ExtensionPackManager)(ptrExtPackMgr.asOutParam()), RTEXITCODE_FAILURE);
1209
1210 RTGETOPTSTATE GetState;
1211 RTGETOPTUNION ValueUnion;
1212 int ch;
1213 HRESULT hrc = S_OK;
1214
1215 if (!strcmp(a->argv[0], "install"))
1216 {
1217 setCurrentSubcommand(HELP_SCOPE_EXTPACK_INSTALL);
1218 const char *pszName = NULL;
1219 bool fReplace = false;
1220
1221 static const RTGETOPTDEF s_aInstallOptions[] =
1222 {
1223 { "--replace", 'r', RTGETOPT_REQ_NOTHING },
1224 { "--accept-license", 'a', RTGETOPT_REQ_STRING },
1225 };
1226
1227 RTCList<RTCString> lstLicenseHashes;
1228 RTGetOptInit(&GetState, a->argc, a->argv, s_aInstallOptions, RT_ELEMENTS(s_aInstallOptions), 1, 0 /*fFlags*/);
1229 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1230 {
1231 switch (ch)
1232 {
1233 case 'r':
1234 fReplace = true;
1235 break;
1236
1237 case 'a':
1238 lstLicenseHashes.append(ValueUnion.psz);
1239 lstLicenseHashes[lstLicenseHashes.size() - 1].toLower();
1240 break;
1241
1242 case VINF_GETOPT_NOT_OPTION:
1243 if (pszName)
1244 return errorSyntax("Too many extension pack names given to \"extpack uninstall\"");
1245 pszName = ValueUnion.psz;
1246 break;
1247
1248 default:
1249 return errorGetOpt(ch, &ValueUnion);
1250 }
1251 }
1252 if (!pszName)
1253 return errorSyntax("No extension pack name was given to \"extpack install\"");
1254
1255 char szPath[RTPATH_MAX];
1256 int vrc = RTPathAbs(pszName, szPath, sizeof(szPath));
1257 if (RT_FAILURE(vrc))
1258 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs(%s,,) failed with rc=%Rrc", pszName, vrc);
1259
1260 Bstr bstrTarball(szPath);
1261 Bstr bstrName;
1262 ComPtr<IExtPackFile> ptrExtPackFile;
1263 CHECK_ERROR2I_RET(ptrExtPackMgr, OpenExtPackFile(bstrTarball.raw(), ptrExtPackFile.asOutParam()), RTEXITCODE_FAILURE);
1264 CHECK_ERROR2I_RET(ptrExtPackFile, COMGETTER(Name)(bstrName.asOutParam()), RTEXITCODE_FAILURE);
1265 BOOL fShowLicense = true;
1266 CHECK_ERROR2I_RET(ptrExtPackFile, COMGETTER(ShowLicense)(&fShowLicense), RTEXITCODE_FAILURE);
1267 if (fShowLicense)
1268 {
1269 Bstr bstrLicense;
1270 CHECK_ERROR2I_RET(ptrExtPackFile,
1271 QueryLicense(Bstr("").raw() /* PreferredLocale */,
1272 Bstr("").raw() /* PreferredLanguage */,
1273 Bstr("txt").raw() /* Format */,
1274 bstrLicense.asOutParam()), RTEXITCODE_FAILURE);
1275 Utf8Str strLicense(bstrLicense);
1276 uint8_t abHash[RTSHA256_HASH_SIZE];
1277 char szDigest[RTSHA256_DIGEST_LEN + 1];
1278 RTSha256(strLicense.c_str(), strLicense.length(), abHash);
1279 vrc = RTSha256ToString(abHash, szDigest, sizeof(szDigest));
1280 AssertRCStmt(vrc, szDigest[0] = '\0');
1281 if (lstLicenseHashes.contains(szDigest))
1282 RTPrintf("License accepted.\n");
1283 else
1284 {
1285 RTPrintf("%s\n", strLicense.c_str());
1286 RTPrintf("Do you agree to these license terms and conditions (y/n)? " );
1287 ch = RTStrmGetCh(g_pStdIn);
1288 RTPrintf("\n");
1289 if (ch != 'y' && ch != 'Y')
1290 {
1291 RTPrintf("Installation of \"%ls\" aborted.\n", bstrName.raw());
1292 return RTEXITCODE_FAILURE;
1293 }
1294 if (szDigest[0])
1295 RTPrintf("License accepted. For batch installaltion add\n"
1296 "--accept-license=%s\n"
1297 "to the VBoxManage command line.\n\n", szDigest);
1298 }
1299 }
1300 ComPtr<IProgress> ptrProgress;
1301 CHECK_ERROR2I_RET(ptrExtPackFile, Install(fReplace, NULL, ptrProgress.asOutParam()), RTEXITCODE_FAILURE);
1302 hrc = showProgress(ptrProgress);
1303 CHECK_PROGRESS_ERROR_RET(ptrProgress, ("Failed to install \"%s\"", szPath), RTEXITCODE_FAILURE);
1304
1305 RTPrintf("Successfully installed \"%ls\".\n", bstrName.raw());
1306 }
1307 else if (!strcmp(a->argv[0], "uninstall"))
1308 {
1309 setCurrentSubcommand(HELP_SCOPE_EXTPACK_UNINSTALL);
1310 const char *pszName = NULL;
1311 bool fForced = false;
1312
1313 static const RTGETOPTDEF s_aUninstallOptions[] =
1314 {
1315 { "--force", 'f', RTGETOPT_REQ_NOTHING },
1316 };
1317
1318 RTGetOptInit(&GetState, a->argc, a->argv, s_aUninstallOptions, RT_ELEMENTS(s_aUninstallOptions), 1, 0);
1319 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1320 {
1321 switch (ch)
1322 {
1323 case 'f':
1324 fForced = true;
1325 break;
1326
1327 case VINF_GETOPT_NOT_OPTION:
1328 if (pszName)
1329 return errorSyntax("Too many extension pack names given to \"extpack uninstall\"");
1330 pszName = ValueUnion.psz;
1331 break;
1332
1333 default:
1334 return errorGetOpt(ch, &ValueUnion);
1335 }
1336 }
1337 if (!pszName)
1338 return errorSyntax("No extension pack name was given to \"extpack uninstall\"");
1339
1340 Bstr bstrName(pszName);
1341 ComPtr<IProgress> ptrProgress;
1342 CHECK_ERROR2I_RET(ptrExtPackMgr, Uninstall(bstrName.raw(), fForced, NULL, ptrProgress.asOutParam()), RTEXITCODE_FAILURE);
1343 hrc = showProgress(ptrProgress);
1344 CHECK_PROGRESS_ERROR_RET(ptrProgress, ("Failed to uninstall \"%s\"", pszName), RTEXITCODE_FAILURE);
1345
1346 RTPrintf("Successfully uninstalled \"%s\".\n", pszName);
1347 }
1348 else if (!strcmp(a->argv[0], "cleanup"))
1349 {
1350 setCurrentSubcommand(HELP_SCOPE_EXTPACK_CLEANUP);
1351 if (a->argc > 1)
1352 return errorTooManyParameters(&a->argv[1]);
1353 CHECK_ERROR2I_RET(ptrExtPackMgr, Cleanup(), RTEXITCODE_FAILURE);
1354 RTPrintf("Successfully performed extension pack cleanup\n");
1355 }
1356 else
1357 return errorUnknownSubcommand(a->argv[0]);
1358
1359 return RTEXITCODE_SUCCESS;
1360}
1361
1362RTEXITCODE handleUnattendedDetect(HandlerArg *a)
1363{
1364 HRESULT hrc;
1365
1366 /*
1367 * Options. We work directly on an IUnattended instace while parsing
1368 * the options. This saves a lot of extra clutter.
1369 */
1370 bool fMachineReadable = false;
1371 char szIsoPath[RTPATH_MAX];
1372 szIsoPath[0] = '\0';
1373
1374 /*
1375 * Parse options.
1376 */
1377 static const RTGETOPTDEF s_aOptions[] =
1378 {
1379 { "--iso", 'i', RTGETOPT_REQ_STRING },
1380 { "--machine-readable", 'M', RTGETOPT_REQ_NOTHING },
1381 };
1382
1383 RTGETOPTSTATE GetState;
1384 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1385 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
1386
1387 int c;
1388 RTGETOPTUNION ValueUnion;
1389 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
1390 {
1391 switch (c)
1392 {
1393 case 'i': // --iso
1394 vrc = RTPathAbs(ValueUnion.psz, szIsoPath, sizeof(szIsoPath));
1395 if (RT_FAILURE(vrc))
1396 return errorSyntax("RTPathAbs failed on '%s': %Rrc", ValueUnion.psz, vrc);
1397 break;
1398
1399 case 'M': // --machine-readable.
1400 fMachineReadable = true;
1401 break;
1402
1403 default:
1404 return errorGetOpt(c, &ValueUnion);
1405 }
1406 }
1407
1408 /*
1409 * Check for required stuff.
1410 */
1411 if (szIsoPath[0] == '\0')
1412 return errorSyntax("No ISO specified");
1413
1414 /*
1415 * Do the job.
1416 */
1417 ComPtr<IUnattended> ptrUnattended;
1418 CHECK_ERROR2_RET(hrc, a->virtualBox, CreateUnattendedInstaller(ptrUnattended.asOutParam()), RTEXITCODE_FAILURE);
1419 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(IsoPath)(Bstr(szIsoPath).raw()), RTEXITCODE_FAILURE);
1420 CHECK_ERROR2(hrc, ptrUnattended, DetectIsoOS());
1421 RTEXITCODE rcExit = SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1422
1423 /*
1424 * Retrieve the results.
1425 */
1426 Bstr bstrDetectedOSTypeId;
1427 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSTypeId)(bstrDetectedOSTypeId.asOutParam()), RTEXITCODE_FAILURE);
1428 Bstr bstrDetectedVersion;
1429 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSVersion)(bstrDetectedVersion.asOutParam()), RTEXITCODE_FAILURE);
1430 Bstr bstrDetectedFlavor;
1431 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSFlavor)(bstrDetectedFlavor.asOutParam()), RTEXITCODE_FAILURE);
1432 Bstr bstrDetectedLanguages;
1433 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSLanguages)(bstrDetectedLanguages.asOutParam()), RTEXITCODE_FAILURE);
1434 Bstr bstrDetectedHints;
1435 CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSHints)(bstrDetectedHints.asOutParam()), RTEXITCODE_FAILURE);
1436 if (fMachineReadable)
1437 RTPrintf("OSTypeId=\"%ls\"\n"
1438 "OSVersion=\"%ls\"\n"
1439 "OSFlavor=\"%ls\"\n"
1440 "OSLanguages=\"%ls\"\n"
1441 "OSHints=\"%ls\"\n",
1442 bstrDetectedOSTypeId.raw(),
1443 bstrDetectedVersion.raw(),
1444 bstrDetectedFlavor.raw(),
1445 bstrDetectedLanguages.raw(),
1446 bstrDetectedHints.raw());
1447 else
1448 {
1449 RTMsgInfo("Detected '%s' to be:\n", szIsoPath);
1450 RTPrintf(" OS TypeId = %ls\n"
1451 " OS Version = %ls\n"
1452 " OS Flavor = %ls\n"
1453 " OS Languages = %ls\n"
1454 " OS Hints = %ls\n",
1455 bstrDetectedOSTypeId.raw(),
1456 bstrDetectedVersion.raw(),
1457 bstrDetectedFlavor.raw(),
1458 bstrDetectedLanguages.raw(),
1459 bstrDetectedHints.raw());
1460 }
1461
1462 return rcExit;
1463}
1464
1465RTEXITCODE handleUnattendedInstall(HandlerArg *a)
1466{
1467 HRESULT hrc;
1468 char szAbsPath[RTPATH_MAX];
1469
1470 /*
1471 * Options. We work directly on an IUnattended instace while parsing
1472 * the options. This saves a lot of extra clutter.
1473 */
1474 ComPtr<IUnattended> ptrUnattended;
1475 CHECK_ERROR2_RET(hrc, a->virtualBox, CreateUnattendedInstaller(ptrUnattended.asOutParam()), RTEXITCODE_FAILURE);
1476 RTCList<RTCString> arrPackageSelectionAdjustments;
1477 ComPtr<IMachine> ptrMachine;
1478 bool fDryRun = false;
1479 const char *pszSessionType = "none";
1480
1481 /*
1482 * Parse options.
1483 */
1484 static const RTGETOPTDEF s_aOptions[] =
1485 {
1486 { "--iso", 'i', RTGETOPT_REQ_STRING },
1487 { "--user", 'u', RTGETOPT_REQ_STRING },
1488 { "--password", 'p', RTGETOPT_REQ_STRING },
1489 { "--password-file", 'X', RTGETOPT_REQ_STRING },
1490 { "--full-user-name", 'U', RTGETOPT_REQ_STRING },
1491 { "--key", 'k', RTGETOPT_REQ_STRING },
1492 { "--install-additions", 'A', RTGETOPT_REQ_NOTHING },
1493 { "--no-install-additions", 'N', RTGETOPT_REQ_NOTHING },
1494 { "--additions-iso", 'a', RTGETOPT_REQ_STRING },
1495 { "--install-txs", 't', RTGETOPT_REQ_NOTHING },
1496 { "--no-install-txs", 'T', RTGETOPT_REQ_NOTHING },
1497 { "--validation-kit-iso", 'K', RTGETOPT_REQ_STRING },
1498 { "--locale", 'l', RTGETOPT_REQ_STRING },
1499 { "--country", 'Y', RTGETOPT_REQ_STRING },
1500 { "--time-zone", 'z', RTGETOPT_REQ_STRING },
1501 { "--proxy", 'y', RTGETOPT_REQ_STRING },
1502 { "--hostname", 'H', RTGETOPT_REQ_STRING },
1503 { "--package-selection-adjustment", 's', RTGETOPT_REQ_STRING },
1504 { "--dry-run", 'D', RTGETOPT_REQ_NOTHING },
1505 // advance options:
1506 { "--auxiliary-base-path", 'x', RTGETOPT_REQ_STRING },
1507 { "--image-index", 'm', RTGETOPT_REQ_UINT32 },
1508 { "--script-template", 'c', RTGETOPT_REQ_STRING },
1509 { "--post-install-template", 'C', RTGETOPT_REQ_STRING },
1510 { "--post-install-command", 'P', RTGETOPT_REQ_STRING },
1511 { "--extra-install-kernel-parameters", 'I', RTGETOPT_REQ_STRING },
1512 { "--language", 'L', RTGETOPT_REQ_STRING },
1513 // start vm related options:
1514 { "--start-vm", 'S', RTGETOPT_REQ_STRING },
1515 /** @todo Add a --wait option too for waiting for the VM to shut down or
1516 * something like that...? */
1517 };
1518
1519 RTGETOPTSTATE GetState;
1520 int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1521 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
1522
1523 int c;
1524 RTGETOPTUNION ValueUnion;
1525 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
1526 {
1527 switch (c)
1528 {
1529 case VINF_GETOPT_NOT_OPTION:
1530 if (ptrMachine.isNotNull())
1531 return errorSyntax("VM name/UUID given more than once!");
1532 CHECK_ERROR2_RET(hrc, a->virtualBox, FindMachine(Bstr(ValueUnion.psz).raw(), ptrMachine.asOutParam()), RTEXITCODE_FAILURE);
1533 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Machine)(ptrMachine), RTEXITCODE_FAILURE);
1534 break;
1535
1536 case 'i': // --iso
1537 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
1538 if (RT_FAILURE(vrc))
1539 return errorSyntax("RTPathAbs failed on '%s': %Rrc", ValueUnion.psz, vrc);
1540 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(IsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
1541 break;
1542
1543 case 'u': // --user
1544 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(User)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1545 break;
1546
1547 case 'p': // --password
1548 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Password)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1549 break;
1550
1551 case 'X': // --password-file
1552 {
1553 Utf8Str strPassword;
1554 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword);
1555 if (rcExit != RTEXITCODE_SUCCESS)
1556 return rcExit;
1557 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Password)(Bstr(strPassword).raw()), RTEXITCODE_FAILURE);
1558 break;
1559 }
1560
1561 case 'U': // --full-user-name
1562 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(FullUserName)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1563 break;
1564
1565 case 'k': // --key
1566 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ProductKey)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1567 break;
1568
1569 case 'A': // --install-additions
1570 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallGuestAdditions)(TRUE), RTEXITCODE_FAILURE);
1571 break;
1572 case 'N': // --no-install-additions
1573 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallGuestAdditions)(FALSE), RTEXITCODE_FAILURE);
1574 break;
1575 case 'a': // --additions-iso
1576 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
1577 if (RT_FAILURE(vrc))
1578 return errorSyntax("RTPathAbs failed on '%s': %Rrc", ValueUnion.psz, vrc);
1579 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AdditionsIsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
1580 break;
1581
1582 case 't': // --install-txs
1583 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallTestExecService)(TRUE), RTEXITCODE_FAILURE);
1584 break;
1585 case 'T': // --no-install-txs
1586 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallTestExecService)(FALSE), RTEXITCODE_FAILURE);
1587 break;
1588 case 'K': // --valiation-kit-iso
1589 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
1590 if (RT_FAILURE(vrc))
1591 return errorSyntax("RTPathAbs failed on '%s': %Rrc", ValueUnion.psz, vrc);
1592 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ValidationKitIsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
1593 break;
1594
1595 case 'l': // --locale
1596 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Locale)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1597 break;
1598
1599 case 'Y': // --country
1600 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Country)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1601 break;
1602
1603 case 'z': // --time-zone;
1604 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(TimeZone)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1605 break;
1606
1607 case 'y': // --proxy
1608 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Proxy)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1609 break;
1610
1611 case 'H': // --hostname
1612 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Hostname)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1613 break;
1614
1615 case 's': // --package-selection-adjustment
1616 arrPackageSelectionAdjustments.append(ValueUnion.psz);
1617 break;
1618
1619 case 'D':
1620 fDryRun = true;
1621 break;
1622
1623 case 'x': // --auxiliary-base-path
1624 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
1625 if (RT_FAILURE(vrc))
1626 return errorSyntax("RTPathAbs failed on '%s': %Rrc", ValueUnion.psz, vrc);
1627 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AuxiliaryBasePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
1628 break;
1629
1630 case 'm': // --image-index
1631 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ImageIndex)(ValueUnion.u32), RTEXITCODE_FAILURE);
1632 break;
1633
1634 case 'c': // --script-template
1635 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
1636 if (RT_FAILURE(vrc))
1637 return errorSyntax("RTPathAbs failed on '%s': %Rrc", ValueUnion.psz, vrc);
1638 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ScriptTemplatePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
1639 break;
1640
1641 case 'C': // --post-install-script-template
1642 vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath));
1643 if (RT_FAILURE(vrc))
1644 return errorSyntax("RTPathAbs failed on '%s': %Rrc", ValueUnion.psz, vrc);
1645 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PostInstallScriptTemplatePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE);
1646 break;
1647
1648 case 'P': // --post-install-command.
1649 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PostInstallCommand)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1650 break;
1651
1652 case 'I': // --extra-install-kernel-parameters
1653 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ExtraInstallKernelParameters)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1654 break;
1655
1656 case 'L': // --language
1657 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Language)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE);
1658 break;
1659
1660 case 'S': // --start-vm
1661 pszSessionType = ValueUnion.psz;
1662 break;
1663
1664 default:
1665 return errorGetOpt(c, &ValueUnion);
1666 }
1667 }
1668
1669 /*
1670 * Check for required stuff.
1671 */
1672 if (ptrMachine.isNull())
1673 return errorSyntax("Missing VM name/UUID");
1674
1675 /*
1676 * Set accumulative attributes.
1677 */
1678 if (arrPackageSelectionAdjustments.size() == 1)
1679 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PackageSelectionAdjustments)(Bstr(arrPackageSelectionAdjustments[0]).raw()),
1680 RTEXITCODE_FAILURE);
1681 else if (arrPackageSelectionAdjustments.size() > 1)
1682 {
1683 RTCString strAdjustments;
1684 strAdjustments.join(arrPackageSelectionAdjustments, ";");
1685 CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PackageSelectionAdjustments)(Bstr(strAdjustments).raw()), RTEXITCODE_FAILURE);
1686 }
1687
1688 /*
1689 * Get details about the machine so we can display them below.
1690 */
1691 Bstr bstrMachineName;
1692 CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(Name)(bstrMachineName.asOutParam()), RTEXITCODE_FAILURE);
1693 Bstr bstrUuid;
1694 CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(Id)(bstrUuid.asOutParam()), RTEXITCODE_FAILURE);
1695 BSTR bstrInstalledOS;
1696 CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(OSTypeId)(&bstrInstalledOS), RTEXITCODE_FAILURE);
1697 Utf8Str strInstalledOS(bstrInstalledOS);
1698
1699 /*
1700 * Temporarily lock the machine to check whether it's running or not.
1701 * We take this opportunity to disable the first run wizard.
1702 */
1703 CHECK_ERROR2_RET(hrc, ptrMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
1704 {
1705 ComPtr<IConsole> ptrConsole;
1706 CHECK_ERROR2(hrc, a->session, COMGETTER(Console)(ptrConsole.asOutParam()));
1707
1708 if ( ptrConsole.isNull()
1709 && SUCCEEDED(hrc)
1710 && ( RTStrICmp(pszSessionType, "gui") == 0
1711 || RTStrICmp(pszSessionType, "none") == 0))
1712 {
1713 ComPtr<IMachine> ptrSessonMachine;
1714 CHECK_ERROR2(hrc, a->session, COMGETTER(Machine)(ptrSessonMachine.asOutParam()));
1715 if (ptrSessonMachine.isNotNull())
1716 {
1717 CHECK_ERROR2(hrc, ptrSessonMachine, SetExtraData(Bstr("GUI/FirstRun").raw(), Bstr("0").raw()));
1718 }
1719 }
1720
1721 a->session->UnlockMachine();
1722 if (FAILED(hrc))
1723 return RTEXITCODE_FAILURE;
1724 if (ptrConsole.isNotNull())
1725 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Machine '%ls' is currently running", bstrMachineName.raw());
1726 }
1727
1728 /*
1729 * Do the work.
1730 */
1731 RTMsgInfo("%s unattended installation of %s in machine '%ls' (%ls).\n",
1732 RTStrICmp(pszSessionType, "none") == 0 ? "Preparing" : "Starting",
1733 strInstalledOS.c_str(), bstrMachineName.raw(), bstrUuid.raw());
1734
1735 CHECK_ERROR2_RET(hrc, ptrUnattended,Prepare(), RTEXITCODE_FAILURE);
1736 if (!fDryRun)
1737 {
1738 CHECK_ERROR2_RET(hrc, ptrUnattended, ConstructMedia(), RTEXITCODE_FAILURE);
1739 CHECK_ERROR2_RET(hrc, ptrUnattended,ReconfigureVM(), RTEXITCODE_FAILURE);
1740 }
1741
1742 /*
1743 * Retrieve and display the parameters actually used.
1744 */
1745 RTMsgInfo("Using values:\n");
1746#define SHOW_ATTR(a_Attr, a_szText, a_Type, a_szFmt) do { \
1747 a_Type Value; \
1748 HRESULT hrc2 = ptrUnattended->COMGETTER(a_Attr)(&Value); \
1749 if (SUCCEEDED(hrc2)) \
1750 RTPrintf(" %32s = " a_szFmt "\n", a_szText, Value); \
1751 else \
1752 RTPrintf(" %32s = failed: %Rhrc\n", a_szText, hrc2); \
1753 } while (0)
1754#define SHOW_STR_ATTR(a_Attr, a_szText) do { \
1755 Bstr bstrString; \
1756 HRESULT hrc2 = ptrUnattended->COMGETTER(a_Attr)(bstrString.asOutParam()); \
1757 if (SUCCEEDED(hrc2)) \
1758 RTPrintf(" %32s = %ls\n", a_szText, bstrString.raw()); \
1759 else \
1760 RTPrintf(" %32s = failed: %Rhrc\n", a_szText, hrc2); \
1761 } while (0)
1762
1763 SHOW_STR_ATTR(IsoPath, "isoPath");
1764 SHOW_STR_ATTR(User, "user");
1765 SHOW_STR_ATTR(Password, "password");
1766 SHOW_STR_ATTR(FullUserName, "fullUserName");
1767 SHOW_STR_ATTR(ProductKey, "productKey");
1768 SHOW_STR_ATTR(AdditionsIsoPath, "additionsIsoPath");
1769 SHOW_ATTR( InstallGuestAdditions, "installGuestAdditions", BOOL, "%RTbool");
1770 SHOW_STR_ATTR(ValidationKitIsoPath, "validationKitIsoPath");
1771 SHOW_ATTR( InstallTestExecService, "installTestExecService", BOOL, "%RTbool");
1772 SHOW_STR_ATTR(Locale, "locale");
1773 SHOW_STR_ATTR(Country, "country");
1774 SHOW_STR_ATTR(TimeZone, "timeZone");
1775 SHOW_STR_ATTR(Proxy, "proxy");
1776 SHOW_STR_ATTR(Hostname, "hostname");
1777 SHOW_STR_ATTR(PackageSelectionAdjustments, "packageSelectionAdjustments");
1778 SHOW_STR_ATTR(AuxiliaryBasePath, "auxiliaryBasePath");
1779 SHOW_ATTR( ImageIndex, "imageIndex", ULONG, "%u");
1780 SHOW_STR_ATTR(ScriptTemplatePath, "scriptTemplatePath");
1781 SHOW_STR_ATTR(PostInstallScriptTemplatePath, "postInstallScriptTemplatePath");
1782 SHOW_STR_ATTR(PostInstallCommand, "postInstallCommand");
1783 SHOW_STR_ATTR(ExtraInstallKernelParameters, "extraInstallKernelParameters");
1784 SHOW_STR_ATTR(Language, "language");
1785 SHOW_STR_ATTR(DetectedOSTypeId, "detectedOSTypeId");
1786 SHOW_STR_ATTR(DetectedOSVersion, "detectedOSVersion");
1787 SHOW_STR_ATTR(DetectedOSFlavor, "detectedOSFlavor");
1788 SHOW_STR_ATTR(DetectedOSLanguages, "detectedOSLanguages");
1789 SHOW_STR_ATTR(DetectedOSHints, "detectedOSHints");
1790
1791#undef SHOW_STR_ATTR
1792#undef SHOW_ATTR
1793
1794 /* We can drop the IUnatteded object now. */
1795 ptrUnattended.setNull();
1796
1797 /*
1798 * Start the VM if requested.
1799 */
1800 if ( fDryRun
1801 || RTStrICmp(pszSessionType, "none") == 0)
1802 {
1803 if (!fDryRun)
1804 RTMsgInfo("VM '%ls' (%ls) is ready to be started (e.g. VBoxManage startvm).\n", bstrMachineName.raw(), bstrUuid.raw());
1805 hrc = S_OK;
1806 }
1807 else
1808 {
1809 Bstr env;
1810#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
1811 /* make sure the VM process will start on the same display as VBoxManage */
1812 Utf8Str str;
1813 const char *pszDisplay = RTEnvGet("DISPLAY");
1814 if (pszDisplay)
1815 str = Utf8StrFmt("DISPLAY=%s\n", pszDisplay);
1816 const char *pszXAuth = RTEnvGet("XAUTHORITY");
1817 if (pszXAuth)
1818 str.append(Utf8StrFmt("XAUTHORITY=%s\n", pszXAuth));
1819 env = str;
1820#endif
1821 ComPtr<IProgress> ptrProgress;
1822 CHECK_ERROR2(hrc, ptrMachine, LaunchVMProcess(a->session, Bstr(pszSessionType).raw(), env.raw(), ptrProgress.asOutParam()));
1823 if (SUCCEEDED(hrc) && !ptrProgress.isNull())
1824 {
1825 RTMsgInfo("Waiting for VM '%ls' to power on...\n", bstrMachineName.raw());
1826 CHECK_ERROR2(hrc, ptrProgress, WaitForCompletion(-1));
1827 if (SUCCEEDED(hrc))
1828 {
1829 BOOL fCompleted = true;
1830 CHECK_ERROR2(hrc, ptrProgress, COMGETTER(Completed)(&fCompleted));
1831 if (SUCCEEDED(hrc))
1832 {
1833 ASSERT(fCompleted);
1834
1835 LONG iRc;
1836 CHECK_ERROR2(hrc, ptrProgress, COMGETTER(ResultCode)(&iRc));
1837 if (SUCCEEDED(hrc))
1838 {
1839 if (SUCCEEDED(iRc))
1840 RTMsgInfo("VM '%ls' (%ls) has been successfully started.\n", bstrMachineName.raw(), bstrUuid.raw());
1841 else
1842 {
1843 ProgressErrorInfo info(ptrProgress);
1844 com::GluePrintErrorInfo(info);
1845 }
1846 hrc = iRc;
1847 }
1848 }
1849 }
1850 }
1851
1852 /*
1853 * Do we wait for the VM to power down?
1854 */
1855 }
1856
1857 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1858}
1859
1860
1861RTEXITCODE handleUnattended(HandlerArg *a)
1862{
1863 /*
1864 * Sub-command switch.
1865 */
1866 if (a->argc < 1)
1867 return errorNoSubcommand();
1868
1869 if (!strcmp(a->argv[0], "detect"))
1870 {
1871 setCurrentSubcommand(HELP_SCOPE_UNATTENDED_DETECT);
1872 return handleUnattendedDetect(a);
1873 }
1874
1875 if (!strcmp(a->argv[0], "install"))
1876 {
1877 setCurrentSubcommand(HELP_SCOPE_UNATTENDED_INSTALL);
1878 return handleUnattendedInstall(a);
1879 }
1880
1881 /* Consider some kind of create-vm-and-install-guest-os command. */
1882 return errorUnknownSubcommand(a->argv[0]);
1883}
Note: See TracBrowser for help on using the repository browser.

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