From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp | 2920 ++++++++++++++++++++++ 1 file changed, 2920 insertions(+) create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp (limited to 'src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp') diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp new file mode 100644 index 00000000..fca7a98c --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp @@ -0,0 +1,2920 @@ +/* $Id: VBoxManageMisc.cpp $ */ +/** @file + * VBoxManage - VirtualBox's command-line interface. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VBoxManage.h" + +#include + +using namespace com; + +DECLARE_TRANSLATION_CONTEXT(Misc); + +static const RTGETOPTDEF g_aRegisterVMOptions[] = +{ + { "--password", 'p', RTGETOPT_REQ_STRING }, +}; + +RTEXITCODE handleRegisterVM(HandlerArg *a) +{ + HRESULT hrc; + const char *VMName = NULL; + + Bstr bstrVMName; + Bstr bstrPasswordFile; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + // start at 0 because main() has hacked both the argc and argv given to us + RTGetOptInit(&GetState, a->argc, a->argv, g_aRegisterVMOptions, RT_ELEMENTS(g_aRegisterVMOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'p': // --password + bstrPasswordFile = ValueUnion.psz; + break; + + case VINF_GETOPT_NOT_OPTION: + if (bstrVMName.isEmpty()) + VMName = ValueUnion.psz; + else + return errorSyntax(Misc::tr("Invalid parameter '%s'"), ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(Misc::tr("Invalid option -%c"), c); + return errorSyntax(Misc::tr("Invalid option case %i"), c); + } + if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Misc::tr("unknown option: %s\n"), ValueUnion.psz); + if (ValueUnion.pDef) + return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c); + return errorSyntax(Misc::tr("error: %Rrs"), c); + } + } + + Utf8Str strPassword; + + if (bstrPasswordFile.isNotEmpty()) + { + if (bstrPasswordFile == "-") + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, Misc::tr("Enter password:")); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(a->argv[3], &strPassword); + if (rcExit == RTEXITCODE_FAILURE) + return RTMsgErrorExitFailure(Misc::tr("Failed to read password from file")); + } + } + + ComPtr machine; + /** @todo Ugly hack to get both the API interpretation of relative paths + * and the client's interpretation of relative paths. Remove after the API + * has been redesigned. */ + hrc = a->virtualBox->OpenMachine(Bstr(a->argv[0]).raw(), + Bstr(strPassword).raw(), + machine.asOutParam()); + if (FAILED(hrc) && !RTPathStartsWithRoot(a->argv[0])) + { + char szVMFileAbs[RTPATH_MAX] = ""; + int vrc = RTPathAbs(a->argv[0], szVMFileAbs, sizeof(szVMFileAbs)); + if (RT_FAILURE(vrc)) + return RTMsgErrorExitFailure(Misc::tr("Failed to convert \"%s\" to an absolute path: %Rrc"), + a->argv[0], vrc); + CHECK_ERROR(a->virtualBox, OpenMachine(Bstr(szVMFileAbs).raw(), + Bstr(strPassword).raw(), + machine.asOutParam())); + } + else if (FAILED(hrc)) + com::GlueHandleComError(a->virtualBox, + "OpenMachine(Bstr(a->argv[0]).raw(), Bstr(strPassword).raw(), machine.asOutParam()))", + hrc, __FILE__, __LINE__); + if (SUCCEEDED(hrc)) + { + ASSERT(machine); + CHECK_ERROR(a->virtualBox, RegisterMachine(machine)); + } + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static const RTGETOPTDEF g_aUnregisterVMOptions[] = +{ + { "--delete", 'd', RTGETOPT_REQ_NOTHING }, + { "-delete", 'd', RTGETOPT_REQ_NOTHING }, // deprecated + { "--delete-all", 'a', RTGETOPT_REQ_NOTHING }, + { "-delete-all", 'a', RTGETOPT_REQ_NOTHING }, // deprecated +}; + +RTEXITCODE handleUnregisterVM(HandlerArg *a) +{ + HRESULT hrc; + const char *VMName = NULL; + bool fDelete = false; + bool fDeleteAll = false; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + // start at 0 because main() has hacked both the argc and argv given to us + RTGetOptInit(&GetState, a->argc, a->argv, g_aUnregisterVMOptions, RT_ELEMENTS(g_aUnregisterVMOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'd': // --delete + fDelete = true; + break; + + case 'a': // --delete-all + fDeleteAll = true; + break; + + case VINF_GETOPT_NOT_OPTION: + if (!VMName) + VMName = ValueUnion.psz; + else + return errorSyntax(Misc::tr("Invalid parameter '%s'"), ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(Misc::tr("Invalid option -%c"), c); + return errorSyntax(Misc::tr("Invalid option case %i"), c); + } + if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Misc::tr("unknown option: %s\n"), ValueUnion.psz); + if (ValueUnion.pDef) + return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c); + return errorSyntax(Misc::tr("error: %Rrs"), c); + } + } + + /* check for required options */ + if (!VMName) + return errorSyntax(Misc::tr("VM name required")); + + ComPtr machine; + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(VMName).raw(), + machine.asOutParam()), + RTEXITCODE_FAILURE); + SafeIfaceArray aMedia; + CHECK_ERROR_RET(machine, Unregister(fDeleteAll ? CleanupMode_DetachAllReturnHardDisksAndVMRemovable + :CleanupMode_DetachAllReturnHardDisksOnly, + ComSafeArrayAsOutParam(aMedia)), + RTEXITCODE_FAILURE); + if (fDelete || fDeleteAll) + { + ComPtr pProgress; + CHECK_ERROR_RET(machine, DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress.asOutParam()), + RTEXITCODE_FAILURE); + + hrc = showProgress(pProgress); + CHECK_PROGRESS_ERROR_RET(pProgress, (Misc::tr("Machine delete failed")), RTEXITCODE_FAILURE); + } + else + { + /* Note that the IMachine::Unregister method will return the medium + * reference in a sane order, which means that closing will normally + * succeed, unless there is still another machine which uses the + * medium. No harm done if we ignore the error. */ + for (size_t i = 0; i < aMedia.size(); i++) + { + IMedium *pMedium = aMedia[i]; + if (pMedium) + hrc = pMedium->Close(); + } + hrc = S_OK; /** @todo r=andy Why overwriting the result from closing the medium above? */ + } + return RTEXITCODE_SUCCESS; +} + +static const RTGETOPTDEF g_aCreateVMOptions[] = +{ + { "--name", 'n', RTGETOPT_REQ_STRING }, + { "-name", 'n', RTGETOPT_REQ_STRING }, + { "--groups", 'g', RTGETOPT_REQ_STRING }, + { "--basefolder", 'p', RTGETOPT_REQ_STRING }, + { "-basefolder", 'p', RTGETOPT_REQ_STRING }, + { "--ostype", 'o', RTGETOPT_REQ_STRING }, + { "-ostype", 'o', RTGETOPT_REQ_STRING }, + { "--uuid", 'u', RTGETOPT_REQ_UUID }, + { "-uuid", 'u', RTGETOPT_REQ_UUID }, + { "--register", 'r', RTGETOPT_REQ_NOTHING }, + { "-register", 'r', RTGETOPT_REQ_NOTHING }, + { "--default", 'd', RTGETOPT_REQ_NOTHING }, + { "-default", 'd', RTGETOPT_REQ_NOTHING }, + { "--cipher", 'c', RTGETOPT_REQ_STRING }, + { "-cipher", 'c', RTGETOPT_REQ_STRING }, + { "--password-id", 'i', RTGETOPT_REQ_STRING }, + { "-password-id", 'i', RTGETOPT_REQ_STRING }, + { "--password", 'w', RTGETOPT_REQ_STRING }, + { "-password", 'w', RTGETOPT_REQ_STRING }, +}; + +RTEXITCODE handleCreateVM(HandlerArg *a) +{ + HRESULT hrc; + Bstr bstrBaseFolder; + Bstr bstrName; + Bstr bstrOsTypeId; + Bstr bstrUuid; + bool fRegister = false; + bool fDefault = false; + /* TBD. Now not used */ + Bstr bstrDefaultFlags; + com::SafeArray groups; + Bstr bstrCipher; + Bstr bstrPasswordId; + const char *pszPassword = NULL; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + // start at 0 because main() has hacked both the argc and argv given to us + RTGetOptInit(&GetState, a->argc, a->argv, g_aCreateVMOptions, RT_ELEMENTS(g_aCreateVMOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'n': // --name + bstrName = ValueUnion.psz; + break; + + case 'g': // --groups + parseGroups(ValueUnion.psz, &groups); + break; + + case 'p': // --basefolder + bstrBaseFolder = ValueUnion.psz; + break; + + case 'o': // --ostype + bstrOsTypeId = ValueUnion.psz; + break; + + case 'u': // --uuid + bstrUuid = Guid(ValueUnion.Uuid).toUtf16().raw(); + break; + + case 'r': // --register + fRegister = true; + break; + + case 'd': // --default + fDefault = true; + break; + + case 'c': // --cipher + bstrCipher = ValueUnion.psz; + break; + + case 'i': // --password-id + bstrPasswordId = ValueUnion.psz; + break; + + case 'w': // --password + pszPassword = ValueUnion.psz; + break; + + default: + return errorGetOpt(c, &ValueUnion); + } + } + + /* check for required options */ + if (bstrName.isEmpty()) + return errorSyntax(Misc::tr("Parameter --name is required")); + + do + { + Bstr createFlags; + if (!bstrUuid.isEmpty()) + createFlags = BstrFmt("UUID=%ls", bstrUuid.raw()); + Bstr bstrPrimaryGroup; + if (groups.size()) + bstrPrimaryGroup = groups[0]; + Bstr bstrSettingsFile; + CHECK_ERROR_BREAK(a->virtualBox, + ComposeMachineFilename(bstrName.raw(), + bstrPrimaryGroup.raw(), + createFlags.raw(), + bstrBaseFolder.raw(), + bstrSettingsFile.asOutParam())); + Utf8Str strPassword; + if (pszPassword) + { + if (!RTStrCmp(pszPassword, "-")) + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter the password:"); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(pszPassword, &strPassword); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError("Failed to read new password from file"); + return rcExit; + } + } + } + ComPtr machine; + CHECK_ERROR_BREAK(a->virtualBox, + CreateMachine(bstrSettingsFile.raw(), + bstrName.raw(), + ComSafeArrayAsInParam(groups), + bstrOsTypeId.raw(), + createFlags.raw(), + bstrCipher.raw(), + bstrPasswordId.raw(), + Bstr(strPassword).raw(), + machine.asOutParam())); + + CHECK_ERROR_BREAK(machine, SaveSettings()); + if (fDefault) + { + /* ApplyDefaults assumes the machine is already registered */ + CHECK_ERROR_BREAK(machine, ApplyDefaults(bstrDefaultFlags.raw())); + CHECK_ERROR_BREAK(machine, SaveSettings()); + } + if (fRegister) + { + CHECK_ERROR_BREAK(a->virtualBox, RegisterMachine(machine)); + } + + Bstr uuid; + CHECK_ERROR_BREAK(machine, COMGETTER(Id)(uuid.asOutParam())); + Bstr settingsFile; + CHECK_ERROR_BREAK(machine, COMGETTER(SettingsFilePath)(settingsFile.asOutParam())); + RTPrintf(Misc::tr("Virtual machine '%ls' is created%s.\n" + "UUID: %s\n" + "Settings file: '%ls'\n"), + bstrName.raw(), fRegister ? Misc::tr(" and registered") : "", + Utf8Str(uuid).c_str(), settingsFile.raw()); + } + while (0); + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static const RTGETOPTDEF g_aMoveVMOptions[] = +{ + { "--type", 't', RTGETOPT_REQ_STRING }, + { "--folder", 'f', RTGETOPT_REQ_STRING }, +}; + +RTEXITCODE handleMoveVM(HandlerArg *a) +{ + HRESULT hrc; + const char *pszSrcName = NULL; + const char *pszType = NULL; + char szTargetFolder[RTPATH_MAX]; + + int c; + int vrc = VINF_SUCCESS; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + + // start at 0 because main() has hacked both the argc and argv given to us + RTGetOptInit(&GetState, a->argc, a->argv, g_aMoveVMOptions, RT_ELEMENTS(g_aMoveVMOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 't': // --type + pszType = ValueUnion.psz; + break; + + case 'f': // --target folder + if (ValueUnion.psz && ValueUnion.psz[0] != '\0') + { + vrc = RTPathAbs(ValueUnion.psz, szTargetFolder, sizeof(szTargetFolder)); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("RTPathAbs(%s,,) failed with vrc=%Rrc"), + ValueUnion.psz, vrc); + } else { + szTargetFolder[0] = '\0'; + } + break; + + case VINF_GETOPT_NOT_OPTION: + if (!pszSrcName) + pszSrcName = ValueUnion.psz; + else + return errorSyntax(Misc::tr("Invalid parameter '%s'"), ValueUnion.psz); + break; + + default: + return errorGetOpt(c, &ValueUnion); + } + } + + + if (!pszType) + pszType = "basic"; + + /* Check for required options */ + if (!pszSrcName) + return errorSyntax(Misc::tr("VM name required")); + + /* Get the machine object */ + ComPtr srcMachine; + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(pszSrcName).raw(), + srcMachine.asOutParam()), + RTEXITCODE_FAILURE); + + if (srcMachine) + { + /* Start the moving */ + ComPtr progress; + + /* we have to open a session for this task */ + CHECK_ERROR_RET(srcMachine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE); + ComPtr sessionMachine; + + CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(sessionMachine, + MoveTo(Bstr(szTargetFolder).raw(), + Bstr(pszType).raw(), + progress.asOutParam()), + RTEXITCODE_FAILURE); + hrc = showProgress(progress); + CHECK_PROGRESS_ERROR_RET(progress, (Misc::tr("Move VM failed")), RTEXITCODE_FAILURE); + + sessionMachine.setNull(); + CHECK_ERROR_RET(a->session, UnlockMachine(), RTEXITCODE_FAILURE); + + RTPrintf(Misc::tr("Machine has been successfully moved into %s\n"), + szTargetFolder[0] != '\0' ? szTargetFolder : Misc::tr("the same location")); + } + + return RTEXITCODE_SUCCESS; +} + +static const RTGETOPTDEF g_aCloneVMOptions[] = +{ + { "--snapshot", 's', RTGETOPT_REQ_STRING }, + { "--name", 'n', RTGETOPT_REQ_STRING }, + { "--groups", 'g', RTGETOPT_REQ_STRING }, + { "--mode", 'm', RTGETOPT_REQ_STRING }, + { "--options", 'o', RTGETOPT_REQ_STRING }, + { "--register", 'r', RTGETOPT_REQ_NOTHING }, + { "--basefolder", 'p', RTGETOPT_REQ_STRING }, + { "--uuid", 'u', RTGETOPT_REQ_UUID }, +}; + +static int parseCloneMode(const char *psz, CloneMode_T *pMode) +{ + if (!RTStrICmp(psz, "machine")) + *pMode = CloneMode_MachineState; + else if (!RTStrICmp(psz, "machineandchildren")) + *pMode = CloneMode_MachineAndChildStates; + else if (!RTStrICmp(psz, "all")) + *pMode = CloneMode_AllStates; + else + return VERR_PARSE_ERROR; + + return VINF_SUCCESS; +} + +static int parseCloneOptions(const char *psz, com::SafeArray *options) +{ + int vrc = VINF_SUCCESS; + while (psz && *psz && RT_SUCCESS(vrc)) + { + size_t len; + const char *pszComma = strchr(psz, ','); + if (pszComma) + len = pszComma - psz; + else + len = strlen(psz); + if (len > 0) + { + if (!RTStrNICmp(psz, "KeepAllMACs", len)) + options->push_back(CloneOptions_KeepAllMACs); + else if (!RTStrNICmp(psz, "KeepNATMACs", len)) + options->push_back(CloneOptions_KeepNATMACs); + else if (!RTStrNICmp(psz, "KeepDiskNames", len)) + options->push_back(CloneOptions_KeepDiskNames); + else if ( !RTStrNICmp(psz, "Link", len) + || !RTStrNICmp(psz, "Linked", len)) + options->push_back(CloneOptions_Link); + else if ( !RTStrNICmp(psz, "KeepHwUUIDs", len) + || !RTStrNICmp(psz, "KeepHwUUID", len)) + options->push_back(CloneOptions_KeepHwUUIDs); + else + vrc = VERR_PARSE_ERROR; + } + if (pszComma) + psz += len + 1; + else + psz += len; + } + + return vrc; +} + +RTEXITCODE handleCloneVM(HandlerArg *a) +{ + HRESULT hrc; + const char *pszSrcName = NULL; + const char *pszSnapshotName = NULL; + CloneMode_T mode = CloneMode_MachineState; + com::SafeArray options; + const char *pszTrgName = NULL; + const char *pszTrgBaseFolder = NULL; + bool fRegister = false; + Bstr bstrUuid; + com::SafeArray groups; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + // start at 0 because main() has hacked both the argc and argv given to us + RTGetOptInit(&GetState, a->argc, a->argv, g_aCloneVMOptions, RT_ELEMENTS(g_aCloneVMOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 's': // --snapshot + pszSnapshotName = ValueUnion.psz; + break; + + case 'n': // --name + pszTrgName = ValueUnion.psz; + break; + + case 'g': // --groups + parseGroups(ValueUnion.psz, &groups); + break; + + case 'p': // --basefolder + pszTrgBaseFolder = ValueUnion.psz; + break; + + case 'm': // --mode + if (RT_FAILURE(parseCloneMode(ValueUnion.psz, &mode))) + return errorArgument(Misc::tr("Invalid clone mode '%s'\n"), ValueUnion.psz); + break; + + case 'o': // --options + if (RT_FAILURE(parseCloneOptions(ValueUnion.psz, &options))) + return errorArgument(Misc::tr("Invalid clone options '%s'\n"), ValueUnion.psz); + break; + + case 'u': // --uuid + bstrUuid = Guid(ValueUnion.Uuid).toUtf16().raw(); + break; + + case 'r': // --register + fRegister = true; + break; + + case VINF_GETOPT_NOT_OPTION: + if (!pszSrcName) + pszSrcName = ValueUnion.psz; + else + return errorSyntax(Misc::tr("Invalid parameter '%s'"), ValueUnion.psz); + break; + + default: + return errorGetOpt(c, &ValueUnion); + } + } + + /* Check for required options */ + if (!pszSrcName) + return errorSyntax(Misc::tr("VM name required")); + + /* Get the machine object */ + ComPtr srcMachine; + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(pszSrcName).raw(), + srcMachine.asOutParam()), + RTEXITCODE_FAILURE); + + /* If a snapshot name/uuid was given, get the particular machine of this + * snapshot. */ + if (pszSnapshotName) + { + ComPtr srcSnapshot; + CHECK_ERROR_RET(srcMachine, FindSnapshot(Bstr(pszSnapshotName).raw(), + srcSnapshot.asOutParam()), + RTEXITCODE_FAILURE); + CHECK_ERROR_RET(srcSnapshot, COMGETTER(Machine)(srcMachine.asOutParam()), + RTEXITCODE_FAILURE); + } + + /* Default name necessary? */ + if (!pszTrgName) + pszTrgName = RTStrAPrintf2(Misc::tr("%s Clone"), pszSrcName); + + Bstr createFlags; + if (!bstrUuid.isEmpty()) + createFlags = BstrFmt("UUID=%ls", bstrUuid.raw()); + Bstr bstrPrimaryGroup; + if (groups.size()) + bstrPrimaryGroup = groups[0]; + Bstr bstrSettingsFile; + CHECK_ERROR_RET(a->virtualBox, + ComposeMachineFilename(Bstr(pszTrgName).raw(), + bstrPrimaryGroup.raw(), + createFlags.raw(), + Bstr(pszTrgBaseFolder).raw(), + bstrSettingsFile.asOutParam()), + RTEXITCODE_FAILURE); + + ComPtr trgMachine; + CHECK_ERROR_RET(a->virtualBox, CreateMachine(bstrSettingsFile.raw(), + Bstr(pszTrgName).raw(), + ComSafeArrayAsInParam(groups), + NULL, + createFlags.raw(), + NULL, + NULL, + NULL, + trgMachine.asOutParam()), + RTEXITCODE_FAILURE); + + /* Start the cloning */ + ComPtr progress; + CHECK_ERROR_RET(srcMachine, CloneTo(trgMachine, + mode, + ComSafeArrayAsInParam(options), + progress.asOutParam()), + RTEXITCODE_FAILURE); + hrc = showProgress(progress); + CHECK_PROGRESS_ERROR_RET(progress, (Misc::tr("Clone VM failed")), RTEXITCODE_FAILURE); + + if (fRegister) + CHECK_ERROR_RET(a->virtualBox, RegisterMachine(trgMachine), RTEXITCODE_FAILURE); + + Bstr bstrNewName; + CHECK_ERROR_RET(trgMachine, COMGETTER(Name)(bstrNewName.asOutParam()), RTEXITCODE_FAILURE); + RTPrintf(Misc::tr("Machine has been successfully cloned as \"%ls\"\n"), bstrNewName.raw()); + + return RTEXITCODE_SUCCESS; +} + +RTEXITCODE handleStartVM(HandlerArg *a) +{ + HRESULT hrc = S_OK; + std::list VMs; + Bstr sessionType; + com::SafeArray aBstrEnv; + const char *pszPassword = NULL; + const char *pszPasswordId = NULL; + Utf8Str strPassword; + +#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) + /* make sure the VM process will by default start on the same display as VBoxManage */ + { + const char *pszDisplay = RTEnvGet("DISPLAY"); + if (pszDisplay) + aBstrEnv.push_back(BstrFmt("DISPLAY=%s", pszDisplay).raw()); + const char *pszXAuth = RTEnvGet("XAUTHORITY"); + if (pszXAuth) + aBstrEnv.push_back(BstrFmt("XAUTHORITY=%s", pszXAuth).raw()); + } +#endif + + static const RTGETOPTDEF s_aStartVMOptions[] = + { + { "--type", 't', RTGETOPT_REQ_STRING }, + { "-type", 't', RTGETOPT_REQ_STRING }, // deprecated + { "--putenv", 'E', RTGETOPT_REQ_STRING }, + { "--password", 'p', RTGETOPT_REQ_STRING }, + { "--password-id", 'i', RTGETOPT_REQ_STRING } + }; + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + // start at 0 because main() has hacked both the argc and argv given to us + RTGetOptInit(&GetState, a->argc, a->argv, s_aStartVMOptions, RT_ELEMENTS(s_aStartVMOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 't': // --type + if (!RTStrICmp(ValueUnion.psz, "gui")) + { + sessionType = "gui"; + } +#ifdef VBOX_WITH_VBOXSDL + else if (!RTStrICmp(ValueUnion.psz, "sdl")) + { + sessionType = "sdl"; + } +#endif +#ifdef VBOX_WITH_HEADLESS + else if (!RTStrICmp(ValueUnion.psz, "capture")) + { + sessionType = "capture"; + } + else if (!RTStrICmp(ValueUnion.psz, "headless")) + { + sessionType = "headless"; + } +#endif + else + sessionType = ValueUnion.psz; + break; + + case 'E': // --putenv + if (!RTStrStr(ValueUnion.psz, "\n")) + aBstrEnv.push_back(Bstr(ValueUnion.psz).raw()); + else + return errorSyntax(Misc::tr("Parameter to option --putenv must not contain any newline character")); + break; + + case 'p': // --password + pszPassword = ValueUnion.psz; + break; + + case 'i': // --password-id + pszPasswordId = ValueUnion.psz; + break; + + case VINF_GETOPT_NOT_OPTION: + VMs.push_back(ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(Misc::tr("Invalid option -%c"), c); + else + return errorSyntax(Misc::tr("Invalid option case %i"), c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Misc::tr("unknown option: %s\n"), ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(Misc::tr("error: %Rrs"), c); + } + } + + /* check for required options */ + if (VMs.empty()) + return errorSyntax(Misc::tr("at least one VM name or uuid required")); + + if (pszPassword) + { + if (!RTStrCmp(pszPassword, "-")) + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter the password:"); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(pszPassword, &strPassword); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError("Failed to read new password from file"); + return rcExit; + } + } + } + + for (std::list::const_iterator it = VMs.begin(); + it != VMs.end(); + ++it) + { + HRESULT hrc2 = hrc; + const char *pszVM = *it; + ComPtr machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszVM).raw(), + machine.asOutParam())); + if (machine) + { + if (pszPasswordId && strPassword.isNotEmpty()) + { + CHECK_ERROR(machine, AddEncryptionPassword(Bstr(pszPasswordId).raw(), Bstr(strPassword).raw())); + if (hrc == VBOX_E_PASSWORD_INCORRECT) + RTMsgError("Password incorrect!"); + } + if (SUCCEEDED(hrc)) + { + ComPtr progress; + CHECK_ERROR(machine, LaunchVMProcess(a->session, sessionType.raw(), + ComSafeArrayAsInParam(aBstrEnv), progress.asOutParam())); + if (SUCCEEDED(hrc) && !progress.isNull()) + { + RTPrintf("Waiting for VM \"%s\" to power on...\n", pszVM); + CHECK_ERROR(progress, WaitForCompletion(-1)); + if (SUCCEEDED(hrc)) + { + BOOL completed = true; + CHECK_ERROR(progress, COMGETTER(Completed)(&completed)); + if (SUCCEEDED(hrc)) + { + ASSERT(completed); + + LONG iRc; + CHECK_ERROR(progress, COMGETTER(ResultCode)(&iRc)); + if (SUCCEEDED(hrc)) + { + if (SUCCEEDED(iRc)) + RTPrintf("VM \"%s\" has been successfully started.\n", pszVM); + else + { + ProgressErrorInfo info(progress); + com::GluePrintErrorInfo(info); + } + hrc = iRc; + } + } + } + } + } + } + + /* it's important to always close sessions */ + a->session->UnlockMachine(); + + /* make sure that we remember the failed state */ + if (FAILED(hrc2)) + hrc = hrc2; + } + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION +static const RTGETOPTDEF g_aSetVMEncryptionOptions[] = +{ + { "--new-password", 'n', RTGETOPT_REQ_STRING }, + { "--old-password", 'o', RTGETOPT_REQ_STRING }, + { "--cipher", 'c', RTGETOPT_REQ_STRING }, + { "--new-password-id", 'i', RTGETOPT_REQ_STRING }, + { "--force", 'f', RTGETOPT_REQ_NOTHING}, +}; + +RTEXITCODE handleSetVMEncryption(HandlerArg *a, const char *pszFilenameOrUuid) +{ + HRESULT hrc; + ComPtr machine; + const char *pszPasswordNew = NULL; + const char *pszPasswordOld = NULL; + const char *pszCipher = NULL; + const char *pszNewPasswordId = NULL; + BOOL fForce = FALSE; + Utf8Str strPasswordNew; + Utf8Str strPasswordOld; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + // start at 0 because main() has hacked both the argc and argv given to us + RTGetOptInit(&GetState, a->argc, a->argv, g_aSetVMEncryptionOptions, RT_ELEMENTS(g_aSetVMEncryptionOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'n': // --new-password + pszPasswordNew = ValueUnion.psz; + break; + + case 'o': // --old-password + pszPasswordOld = ValueUnion.psz; + break; + + case 'c': // --cipher + pszCipher = ValueUnion.psz; + break; + + case 'i': // --new-password-id + pszNewPasswordId = ValueUnion.psz; + break; + + case 'f': // --force + fForce = TRUE; + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(Misc::tr("Invalid option -%c"), c); + else + return errorSyntax(Misc::tr("Invalid option case %i"), c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Misc::tr("unknown option: %s\n"), ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(Misc::tr("%s: %Rrs"), ValueUnion.pDef->pszLong, c); + else + return errorSyntax(Misc::tr("error: %Rrs"), c); + } + } + + if (!pszFilenameOrUuid) + return errorSyntax(Misc::tr("VM name or UUID required")); + + if (!pszPasswordNew && !pszPasswordOld) + return errorSyntax(Misc::tr("No password specified")); + + if ( (pszPasswordNew && !pszNewPasswordId) + || (!pszPasswordNew && pszNewPasswordId)) + return errorSyntax(Misc::tr("A new password must always have a valid identifier set at the same time")); + + if (pszPasswordOld) + { + if (!RTStrCmp(pszPasswordOld, "-")) + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPasswordOld, "Enter old password:"); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(pszPasswordOld, &strPasswordOld); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError("Failed to read old password from file"); + return rcExit; + } + } + } + if (pszPasswordNew) + { + if (!RTStrCmp(pszPasswordNew, "-")) + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPasswordNew, "Enter new password:"); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(pszPasswordNew, &strPasswordNew); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError("Failed to read new password from file"); + return rcExit; + } + } + } + + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszFilenameOrUuid).raw(), + machine.asOutParam())); + if (machine) + { + ComPtr progress; + CHECK_ERROR(machine, ChangeEncryption(Bstr(strPasswordOld).raw(), Bstr(pszCipher).raw(), + Bstr(strPasswordNew).raw(), Bstr(pszNewPasswordId).raw(), + fForce, progress.asOutParam())); + if (SUCCEEDED(hrc)) + hrc = showProgress(progress); + if (FAILED(hrc)) + { + if (hrc == E_NOTIMPL) + RTMsgError("Encrypt VM operation is not implemented!"); + else if (hrc == VBOX_E_NOT_SUPPORTED) + RTMsgError("Encrypt VM operation for this cipher is not implemented yet!"); + else if (!progress.isNull()) + CHECK_PROGRESS_ERROR(progress, ("Failed to encrypt the VM")); + else + RTMsgError("Failed to encrypt the VM!"); + } + } + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleCheckVMPassword(HandlerArg *a, const char *pszFilenameOrUuid) +{ + HRESULT hrc; + ComPtr machine; + Utf8Str strPassword; + + if (a->argc != 1) + return errorSyntax(Misc::tr("Invalid number of arguments: %d"), a->argc); + + if (!RTStrCmp(a->argv[0], "-")) + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter the password:"); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(a->argv[0], &strPassword); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError("Failed to read password from file"); + return rcExit; + } + } + + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszFilenameOrUuid).raw(), + machine.asOutParam())); + if (machine) + { + CHECK_ERROR(machine, CheckEncryptionPassword(Bstr(strPassword).raw())); + if (SUCCEEDED(hrc)) + RTPrintf("The given password is correct\n"); + } + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static const RTGETOPTDEF g_aAddVMOptions[] = +{ + { "--password", 'p', RTGETOPT_REQ_STRING }, + { "--password-id", 'i', RTGETOPT_REQ_STRING } +}; + +RTEXITCODE handleAddVMPassword(HandlerArg *a, const char *pszFilenameOrUuid) +{ + HRESULT hrc; + ComPtr machine; + const char *pszPassword = NULL; + const char *pszPasswordId = NULL; + Utf8Str strPassword; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + // start at 0 because main() has hacked both the argc and argv given to us + RTGetOptInit(&GetState, a->argc, a->argv, g_aAddVMOptions, RT_ELEMENTS(g_aAddVMOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'p': // --password + pszPassword = ValueUnion.psz; + break; + + case 'i': // --password-id + pszPasswordId = ValueUnion.psz; + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(Misc::tr("Invalid option -%c"), c); + else + return errorSyntax(Misc::tr("Invalid option case %i"), c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Misc::tr("unknown option: %s\n"), ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(Misc::tr("%s: %Rrs"), ValueUnion.pDef->pszLong, c); + else + return errorSyntax(Misc::tr("error: %Rrs"), c); + } + } + + if (!pszFilenameOrUuid) + return errorSyntax(Misc::tr("VM name or UUID required")); + + if (!pszPassword) + return errorSyntax(Misc::tr("No password specified")); + + if (!pszPasswordId) + return errorSyntax(Misc::tr("No password identifier specified")); + + if (!RTStrCmp(pszPassword, "-")) + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter the password:"); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(pszPassword, &strPassword); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError("Failed to read new password from file"); + return rcExit; + } + } + + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszFilenameOrUuid).raw(), + machine.asOutParam())); + if (machine) + { + ComPtr progress; + CHECK_ERROR(machine, AddEncryptionPassword(Bstr(pszPasswordId).raw(), Bstr(strPassword).raw())); + if (hrc == VBOX_E_PASSWORD_INCORRECT) + RTMsgError("Password incorrect!"); + } + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleRemoveVMPassword(HandlerArg *a, const char *pszFilenameOrUuid) +{ + HRESULT hrc; + ComPtr machine; + + if (a->argc != 1) + return errorSyntax(Misc::tr("Invalid number of arguments: %d"), a->argc); + + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszFilenameOrUuid).raw(), + machine.asOutParam())); + if (machine) + { + CHECK_ERROR(machine, RemoveEncryptionPassword(Bstr(a->argv[0]).raw())); + if (hrc == VBOX_E_INVALID_VM_STATE) + RTMsgError("The machine is in online or transient state\n"); + } + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleEncryptVM(HandlerArg *a) +{ + if (a->argc < 2) + return errorSyntax(Misc::tr("subcommand required")); + + HandlerArg handlerArg; + handlerArg.argc = a->argc - 2; + handlerArg.argv = &a->argv[2]; + handlerArg.virtualBox = a->virtualBox; + handlerArg.session = a->session; + if (!strcmp(a->argv[1], "setencryption")) + return handleSetVMEncryption(&handlerArg, a->argv[0]); + if (!strcmp(a->argv[1], "checkpassword")) + return handleCheckVMPassword(&handlerArg, a->argv[0]); + if (!strcmp(a->argv[1], "addpassword")) + return handleAddVMPassword(&handlerArg, a->argv[0]); + if (!strcmp(a->argv[1], "removepassword")) + return handleRemoveVMPassword(&handlerArg, a->argv[0]); + return errorSyntax(Misc::tr("unknown subcommand")); +} +#endif /* !VBOX_WITH_FULL_VM_ENCRYPTION */ + +RTEXITCODE handleDiscardState(HandlerArg *a) +{ + HRESULT hrc; + + if (a->argc != 1) + return errorSyntax(Misc::tr("Incorrect number of parameters")); + + ComPtr machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (machine) + { + do + { + /* we have to open a session for this task */ + CHECK_ERROR_BREAK(machine, LockMachine(a->session, LockType_Write)); + do + { + ComPtr sessionMachine; + CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam())); + CHECK_ERROR_BREAK(sessionMachine, DiscardSavedState(true /* fDeleteFile */)); + } while (0); + CHECK_ERROR_BREAK(a->session, UnlockMachine()); + } while (0); + } + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleAdoptState(HandlerArg *a) +{ + HRESULT hrc; + + if (a->argc != 2) + return errorSyntax(Misc::tr("Incorrect number of parameters")); + + ComPtr machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (machine) + { + char szStateFileAbs[RTPATH_MAX] = ""; + int vrc = RTPathAbs(a->argv[1], szStateFileAbs, sizeof(szStateFileAbs)); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("Cannot convert filename \"%s\" to absolute path: %Rrc"), + a->argv[0], vrc); + + do + { + /* we have to open a session for this task */ + CHECK_ERROR_BREAK(machine, LockMachine(a->session, LockType_Write)); + do + { + ComPtr sessionMachine; + CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam())); + CHECK_ERROR_BREAK(sessionMachine, AdoptSavedState(Bstr(szStateFileAbs).raw())); + } while (0); + CHECK_ERROR_BREAK(a->session, UnlockMachine()); + } while (0); + } + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleGetExtraData(HandlerArg *a) +{ + HRESULT hrc = S_OK; + + if (a->argc > 2 || a->argc < 1) + return errorSyntax(Misc::tr("Incorrect number of parameters")); + + /* global data? */ + if (!strcmp(a->argv[0], "global")) + { + /* enumeration? */ + if (a->argc < 2 || !strcmp(a->argv[1], "enumerate")) + { + SafeArray aKeys; + CHECK_ERROR(a->virtualBox, GetExtraDataKeys(ComSafeArrayAsOutParam(aKeys))); + + for (size_t i = 0; + i < aKeys.size(); + ++i) + { + Bstr bstrKey(aKeys[i]); + Bstr bstrValue; + CHECK_ERROR(a->virtualBox, GetExtraData(bstrKey.raw(), + bstrValue.asOutParam())); + + RTPrintf(Misc::tr("Key: %ls, Value: %ls\n"), bstrKey.raw(), bstrValue.raw()); + } + } + else + { + Bstr value; + CHECK_ERROR(a->virtualBox, GetExtraData(Bstr(a->argv[1]).raw(), + value.asOutParam())); + if (!value.isEmpty()) + RTPrintf(Misc::tr("Value: %ls\n"), value.raw()); + else + RTPrintf(Misc::tr("No value set!\n")); + } + } + else + { + ComPtr machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (machine) + { + /* enumeration? */ + if (a->argc < 2 || !strcmp(a->argv[1], "enumerate")) + { + SafeArray aKeys; + CHECK_ERROR(machine, GetExtraDataKeys(ComSafeArrayAsOutParam(aKeys))); + + for (size_t i = 0; + i < aKeys.size(); + ++i) + { + Bstr bstrKey(aKeys[i]); + Bstr bstrValue; + CHECK_ERROR(machine, GetExtraData(bstrKey.raw(), + bstrValue.asOutParam())); + + RTPrintf(Misc::tr("Key: %ls, Value: %ls\n"), bstrKey.raw(), bstrValue.raw()); + } + } + else + { + Bstr value; + CHECK_ERROR(machine, GetExtraData(Bstr(a->argv[1]).raw(), + value.asOutParam())); + if (!value.isEmpty()) + RTPrintf(Misc::tr("Value: %ls\n"), value.raw()); + else + RTPrintf(Misc::tr("No value set!\n")); + } + } + } + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleSetExtraData(HandlerArg *a) +{ + HRESULT hrc = S_OK; + + if (a->argc < 2) + return errorSyntax(Misc::tr("Not enough parameters")); + + /* global data? */ + if (!strcmp(a->argv[0], "global")) + { + /** @todo passing NULL is deprecated */ + if (a->argc < 3) + CHECK_ERROR(a->virtualBox, SetExtraData(Bstr(a->argv[1]).raw(), + NULL)); + else if (a->argc == 3) + CHECK_ERROR(a->virtualBox, SetExtraData(Bstr(a->argv[1]).raw(), + Bstr(a->argv[2]).raw())); + else + return errorSyntax(Misc::tr("Too many parameters")); + } + else + { + ComPtr machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (machine) + { + /* open an existing session for the VM */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + /* get the session machine */ + ComPtr sessionMachine; + CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE); + /** @todo passing NULL is deprecated */ + if (a->argc < 3) + CHECK_ERROR(sessionMachine, SetExtraData(Bstr(a->argv[1]).raw(), + NULL)); + else if (a->argc == 3) + CHECK_ERROR(sessionMachine, SetExtraData(Bstr(a->argv[1]).raw(), + Bstr(a->argv[2]).raw())); + else + return errorSyntax(Misc::tr("Too many parameters")); + } + } + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleSetProperty(HandlerArg *a) +{ + HRESULT hrc; + + /* there must be two arguments: property name and value */ + if (a->argc != 2) + return errorSyntax(Misc::tr("Incorrect number of parameters")); + + ComPtr systemProperties; + a->virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam()); + + if (!strcmp(a->argv[0], "machinefolder")) + { + /* reset to default? */ + if (!strcmp(a->argv[1], "default")) + CHECK_ERROR(systemProperties, COMSETTER(DefaultMachineFolder)(NULL)); + else + CHECK_ERROR(systemProperties, COMSETTER(DefaultMachineFolder)(Bstr(a->argv[1]).raw())); + } + else if (!strcmp(a->argv[0], "hwvirtexclusive")) + { + bool fHwVirtExclusive; + + if (!strcmp(a->argv[1], "on")) + fHwVirtExclusive = true; + else if (!strcmp(a->argv[1], "off")) + fHwVirtExclusive = false; + else + return errorArgument(Misc::tr("Invalid hwvirtexclusive argument '%s'"), a->argv[1]); + CHECK_ERROR(systemProperties, COMSETTER(ExclusiveHwVirt)(fHwVirtExclusive)); + } + else if ( !strcmp(a->argv[0], "vrdeauthlibrary") + || !strcmp(a->argv[0], "vrdpauthlibrary")) + { + if (!strcmp(a->argv[0], "vrdpauthlibrary")) + RTStrmPrintf(g_pStdErr, Misc::tr("Warning: 'vrdpauthlibrary' is deprecated. Use 'vrdeauthlibrary'.\n")); + + /* reset to default? */ + if (!strcmp(a->argv[1], "default")) + CHECK_ERROR(systemProperties, COMSETTER(VRDEAuthLibrary)(NULL)); + else + CHECK_ERROR(systemProperties, COMSETTER(VRDEAuthLibrary)(Bstr(a->argv[1]).raw())); + } + else if (!strcmp(a->argv[0], "websrvauthlibrary")) + { + /* reset to default? */ + if (!strcmp(a->argv[1], "default")) + CHECK_ERROR(systemProperties, COMSETTER(WebServiceAuthLibrary)(NULL)); + else + CHECK_ERROR(systemProperties, COMSETTER(WebServiceAuthLibrary)(Bstr(a->argv[1]).raw())); + } + else if (!strcmp(a->argv[0], "vrdeextpack")) + { + /* disable? */ + if (!strcmp(a->argv[1], "null")) + CHECK_ERROR(systemProperties, COMSETTER(DefaultVRDEExtPack)(NULL)); + else + CHECK_ERROR(systemProperties, COMSETTER(DefaultVRDEExtPack)(Bstr(a->argv[1]).raw())); + } + else if (!strcmp(a->argv[0], "loghistorycount")) + { + uint32_t uVal; + int vrc; + vrc = RTStrToUInt32Ex(a->argv[1], NULL, 0, &uVal); + if (vrc != VINF_SUCCESS) + return errorArgument(Misc::tr("Error parsing Log history count '%s'"), a->argv[1]); + CHECK_ERROR(systemProperties, COMSETTER(LogHistoryCount)(uVal)); + } + else if (!strcmp(a->argv[0], "autostartdbpath")) + { + /* disable? */ + if (!strcmp(a->argv[1], "null")) + CHECK_ERROR(systemProperties, COMSETTER(AutostartDatabasePath)(NULL)); + else + CHECK_ERROR(systemProperties, COMSETTER(AutostartDatabasePath)(Bstr(a->argv[1]).raw())); + } + else if (!strcmp(a->argv[0], "defaultfrontend")) + { + Bstr bstrDefaultFrontend(a->argv[1]); + if (!strcmp(a->argv[1], "default")) + bstrDefaultFrontend.setNull(); + CHECK_ERROR(systemProperties, COMSETTER(DefaultFrontend)(bstrDefaultFrontend.raw())); + } + else if (!strcmp(a->argv[0], "logginglevel")) + { + Bstr bstrLoggingLevel(a->argv[1]); + if (!strcmp(a->argv[1], "default")) + bstrLoggingLevel.setNull(); + CHECK_ERROR(systemProperties, COMSETTER(LoggingLevel)(bstrLoggingLevel.raw())); + } + else if (!strcmp(a->argv[0], "proxymode")) + { + ProxyMode_T enmProxyMode; + if (!RTStrICmpAscii(a->argv[1], "system")) + enmProxyMode = ProxyMode_System; + else if (!RTStrICmpAscii(a->argv[1], "noproxy")) + enmProxyMode = ProxyMode_NoProxy; + else if (!RTStrICmpAscii(a->argv[1], "manual")) + enmProxyMode = ProxyMode_Manual; + else + return errorArgument(Misc::tr("Unknown proxy mode: '%s'"), a->argv[1]); + CHECK_ERROR(systemProperties, COMSETTER(ProxyMode)(enmProxyMode)); + } + else if (!strcmp(a->argv[0], "proxyurl")) + { + Bstr bstrProxyUrl(a->argv[1]); + CHECK_ERROR(systemProperties, COMSETTER(ProxyURL)(bstrProxyUrl.raw())); + } +#ifdef VBOX_WITH_MAIN_NLS + else if (!strcmp(a->argv[0], "language")) + { + Bstr bstrLanguage(a->argv[1]); + CHECK_ERROR(systemProperties, COMSETTER(LanguageId)(bstrLanguage.raw())); + + /* Kudge alert! Make sure the language change notification is processed, + otherwise it may arrive as (XP)COM shuts down and cause + trouble in debug builds. */ +# ifdef DEBUG + uint64_t const tsStart = RTTimeNanoTS(); +# endif + unsigned cMsgs = 0; + int vrc; + while ( RT_SUCCESS(vrc = NativeEventQueue::getMainEventQueue()->processEventQueue(32 /*ms*/)) + || vrc == VERR_INTERRUPTED) + cMsgs++; +# ifdef DEBUG + RTPrintf("vrc=%Rrc cMsgs=%u nsElapsed=%'RU64\n", vrc, cMsgs, RTTimeNanoTS() - tsStart); +# endif + } +#endif + else + return errorSyntax(Misc::tr("Invalid parameter '%s'"), a->argv[0]); + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +/** + * sharedfolder add + */ +static RTEXITCODE handleSharedFolderAdd(HandlerArg *a) +{ + /* + * Parse arguments (argv[0] == subcommand). + */ + static const RTGETOPTDEF s_aAddOptions[] = + { + { "--name", 'n', RTGETOPT_REQ_STRING }, + { "-name", 'n', RTGETOPT_REQ_STRING }, // deprecated + { "--hostpath", 'p', RTGETOPT_REQ_STRING }, + { "-hostpath", 'p', RTGETOPT_REQ_STRING }, // deprecated + { "--readonly", 'r', RTGETOPT_REQ_NOTHING }, + { "-readonly", 'r', RTGETOPT_REQ_NOTHING }, // deprecated + { "--transient", 't', RTGETOPT_REQ_NOTHING }, + { "-transient", 't', RTGETOPT_REQ_NOTHING }, // deprecated + { "--automount", 'a', RTGETOPT_REQ_NOTHING }, + { "-automount", 'a', RTGETOPT_REQ_NOTHING }, // deprecated + { "--auto-mount-point", 'm', RTGETOPT_REQ_STRING }, + }; + const char *pszMachineName = NULL; + const char *pszName = NULL; + const char *pszHostPath = NULL; + bool fTransient = false; + bool fWritable = true; + bool fAutoMount = false; + const char *pszAutoMountPoint = ""; + + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, a->argc, a->argv, s_aAddOptions, RT_ELEMENTS(s_aAddOptions), 1 /*iFirst*/, 0 /*fFlags*/); + int c; + RTGETOPTUNION ValueUnion; + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'n': + pszName = ValueUnion.psz; + break; + case 'p': + pszHostPath = ValueUnion.psz; + break; + case 'r': + fWritable = false; + break; + case 't': + fTransient = true; + break; + case 'a': + fAutoMount = true; + break; + case 'm': + pszAutoMountPoint = ValueUnion.psz; + break; + case VINF_GETOPT_NOT_OPTION: + if (pszMachineName) + return errorArgument(Misc::tr("Machine name is given more than once: first '%s', then '%s'"), + pszMachineName, ValueUnion.psz); + pszMachineName = ValueUnion.psz; + break; + default: + return errorGetOpt(c, &ValueUnion); + } + } + + if (!pszMachineName) + return errorSyntax(Misc::tr("No machine was specified")); + + if (!pszName) + return errorSyntax(Misc::tr("No shared folder name (--name) was given")); + if (strchr(pszName, ' ')) + return errorSyntax(Misc::tr("Invalid shared folder name '%s': contains space"), pszName); + if (strchr(pszName, '\t')) + return errorSyntax(Misc::tr("Invalid shared folder name '%s': contains tabs"), pszName); + if (strchr(pszName, '\n') || strchr(pszName, '\r')) + return errorSyntax(Misc::tr("Invalid shared folder name '%s': contains newline"), pszName); + + if (!pszHostPath) + return errorSyntax(Misc::tr("No host path (--hostpath) was given")); + char szAbsHostPath[RTPATH_MAX]; + int vrc = RTPathAbs(pszHostPath, szAbsHostPath, sizeof(szAbsHostPath)); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("RTAbsPath failed on '%s': %Rrc"), pszHostPath, vrc); + + /* + * Done parsing, do some work. + */ + ComPtr ptrMachine; + CHECK_ERROR2I_RET(a->virtualBox, FindMachine(Bstr(pszMachineName).raw(), ptrMachine.asOutParam()), RTEXITCODE_FAILURE); + AssertReturn(ptrMachine.isNotNull(), RTEXITCODE_FAILURE); + + HRESULT hrc; + if (fTransient) + { + /* open an existing session for the VM */ + CHECK_ERROR2I_RET(ptrMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + + /* get the session machine */ + ComPtr ptrSessionMachine; + CHECK_ERROR2I_RET(a->session, COMGETTER(Machine)(ptrSessionMachine.asOutParam()), RTEXITCODE_FAILURE); + + /* get the session console */ + ComPtr ptrConsole; + CHECK_ERROR2I_RET(a->session, COMGETTER(Console)(ptrConsole.asOutParam()), RTEXITCODE_FAILURE); + if (ptrConsole.isNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("Machine '%s' is not currently running."), pszMachineName); + + CHECK_ERROR2(hrc, ptrConsole, CreateSharedFolder(Bstr(pszName).raw(), Bstr(szAbsHostPath).raw(), + fWritable, fAutoMount, Bstr(pszAutoMountPoint).raw())); + a->session->UnlockMachine(); + } + else + { + /* open a session for the VM */ + CHECK_ERROR2I_RET(ptrMachine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE); + + /* get the mutable session machine */ + ComPtr ptrSessionMachine; + CHECK_ERROR2I_RET(a->session, COMGETTER(Machine)(ptrSessionMachine.asOutParam()), RTEXITCODE_FAILURE); + + CHECK_ERROR2(hrc, ptrSessionMachine, CreateSharedFolder(Bstr(pszName).raw(), Bstr(szAbsHostPath).raw(), + fWritable, fAutoMount, Bstr(pszAutoMountPoint).raw())); + if (SUCCEEDED(hrc)) + { + CHECK_ERROR2(hrc, ptrSessionMachine, SaveSettings()); + } + + a->session->UnlockMachine(); + } + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +/** + * sharedfolder remove + */ +static RTEXITCODE handleSharedFolderRemove(HandlerArg *a) +{ + /* + * Parse arguments (argv[0] == subcommand). + */ + static const RTGETOPTDEF s_aRemoveOptions[] = + { + { "--name", 'n', RTGETOPT_REQ_STRING }, + { "-name", 'n', RTGETOPT_REQ_STRING }, // deprecated + { "--transient", 't', RTGETOPT_REQ_NOTHING }, + { "-transient", 't', RTGETOPT_REQ_NOTHING }, // deprecated + }; + const char *pszMachineName = NULL; + const char *pszName = NULL; + bool fTransient = false; + + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, a->argc, a->argv, s_aRemoveOptions, RT_ELEMENTS(s_aRemoveOptions), 1 /*iFirst*/, 0 /*fFlags*/); + int c; + RTGETOPTUNION ValueUnion; + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'n': + pszName = ValueUnion.psz; + break; + case 't': + fTransient = true; + break; + case VINF_GETOPT_NOT_OPTION: + if (pszMachineName) + return errorArgument(Misc::tr("Machine name is given more than once: first '%s', then '%s'"), + pszMachineName, ValueUnion.psz); + pszMachineName = ValueUnion.psz; + break; + default: + return errorGetOpt(c, &ValueUnion); + } + } + + if (!pszMachineName) + return errorSyntax(Misc::tr("No machine was specified")); + if (!pszName) + return errorSyntax(Misc::tr("No shared folder name (--name) was given")); + + /* + * Done parsing, do some real work. + */ + ComPtr ptrMachine; + CHECK_ERROR2I_RET(a->virtualBox, FindMachine(Bstr(pszMachineName).raw(), ptrMachine.asOutParam()), RTEXITCODE_FAILURE); + AssertReturn(ptrMachine.isNotNull(), RTEXITCODE_FAILURE); + + HRESULT hrc; + if (fTransient) + { + /* open an existing session for the VM */ + CHECK_ERROR2I_RET(ptrMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + /* get the session machine */ + ComPtr ptrSessionMachine; + CHECK_ERROR2I_RET(a->session, COMGETTER(Machine)(ptrSessionMachine.asOutParam()), RTEXITCODE_FAILURE); + /* get the session console */ + ComPtr ptrConsole; + CHECK_ERROR2I_RET(a->session, COMGETTER(Console)(ptrConsole.asOutParam()), RTEXITCODE_FAILURE); + if (ptrConsole.isNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("Machine '%s' is not currently running.\n"), pszMachineName); + + CHECK_ERROR2(hrc, ptrConsole, RemoveSharedFolder(Bstr(pszName).raw())); + + a->session->UnlockMachine(); + } + else + { + /* open a session for the VM */ + CHECK_ERROR2I_RET(ptrMachine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE); + + /* get the mutable session machine */ + ComPtr ptrSessionMachine; + CHECK_ERROR2I_RET(a->session, COMGETTER(Machine)(ptrSessionMachine.asOutParam()), RTEXITCODE_FAILURE); + + CHECK_ERROR2(hrc, ptrSessionMachine, RemoveSharedFolder(Bstr(pszName).raw())); + + /* commit and close the session */ + if (SUCCEEDED(hrc)) + { + CHECK_ERROR2(hrc, ptrSessionMachine, SaveSettings()); + } + a->session->UnlockMachine(); + } + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +RTEXITCODE handleSharedFolder(HandlerArg *a) +{ + if (a->argc < 1) + return errorSyntax(Misc::tr("Not enough parameters")); + + if (!strcmp(a->argv[0], "add")) + { + setCurrentSubcommand(HELP_SCOPE_SHAREDFOLDER_ADD); + return handleSharedFolderAdd(a); + } + + if (!strcmp(a->argv[0], "remove")) + { + setCurrentSubcommand(HELP_SCOPE_SHAREDFOLDER_REMOVE); + return handleSharedFolderRemove(a); + } + + return errorUnknownSubcommand(a->argv[0]); +} + +RTEXITCODE handleExtPack(HandlerArg *a) +{ + if (a->argc < 1) + return errorNoSubcommand(); + + ComObjPtr ptrExtPackMgr; + CHECK_ERROR2I_RET(a->virtualBox, COMGETTER(ExtensionPackManager)(ptrExtPackMgr.asOutParam()), RTEXITCODE_FAILURE); + + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + int ch; + HRESULT hrc = S_OK; + + if (!strcmp(a->argv[0], "install")) + { + setCurrentSubcommand(HELP_SCOPE_EXTPACK_INSTALL); + const char *pszName = NULL; + bool fReplace = false; + + static const RTGETOPTDEF s_aInstallOptions[] = + { + { "--replace", 'r', RTGETOPT_REQ_NOTHING }, + { "--accept-license", 'a', RTGETOPT_REQ_STRING }, + }; + + RTCList lstLicenseHashes; + RTGetOptInit(&GetState, a->argc, a->argv, s_aInstallOptions, RT_ELEMENTS(s_aInstallOptions), 1, 0 /*fFlags*/); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'r': + fReplace = true; + break; + + case 'a': + lstLicenseHashes.append(ValueUnion.psz); + lstLicenseHashes[lstLicenseHashes.size() - 1].toLower(); + break; + + case VINF_GETOPT_NOT_OPTION: + if (pszName) + return errorSyntax(Misc::tr("Too many extension pack names given to \"extpack uninstall\"")); + pszName = ValueUnion.psz; + break; + + default: + return errorGetOpt(ch, &ValueUnion); + } + } + if (!pszName) + return errorSyntax(Misc::tr("No extension pack name was given to \"extpack install\"")); + + char szPath[RTPATH_MAX]; + int vrc = RTPathAbs(pszName, szPath, sizeof(szPath)); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("RTPathAbs(%s,,) failed with vrc=%Rrc"), pszName, vrc); + + Bstr bstrTarball(szPath); + Bstr bstrName; + ComPtr ptrExtPackFile; + CHECK_ERROR2I_RET(ptrExtPackMgr, OpenExtPackFile(bstrTarball.raw(), ptrExtPackFile.asOutParam()), RTEXITCODE_FAILURE); + CHECK_ERROR2I_RET(ptrExtPackFile, COMGETTER(Name)(bstrName.asOutParam()), RTEXITCODE_FAILURE); + BOOL fShowLicense = true; + CHECK_ERROR2I_RET(ptrExtPackFile, COMGETTER(ShowLicense)(&fShowLicense), RTEXITCODE_FAILURE); + if (fShowLicense) + { + Bstr bstrLicense; + CHECK_ERROR2I_RET(ptrExtPackFile, + QueryLicense(Bstr("").raw() /* PreferredLocale */, + Bstr("").raw() /* PreferredLanguage */, + Bstr("txt").raw() /* Format */, + bstrLicense.asOutParam()), RTEXITCODE_FAILURE); + Utf8Str strLicense(bstrLicense); + uint8_t abHash[RTSHA256_HASH_SIZE]; + char szDigest[RTSHA256_DIGEST_LEN + 1]; + RTSha256(strLicense.c_str(), strLicense.length(), abHash); + vrc = RTSha256ToString(abHash, szDigest, sizeof(szDigest)); + AssertRCStmt(vrc, szDigest[0] = '\0'); + if (lstLicenseHashes.contains(szDigest)) + RTPrintf(Misc::tr("License accepted.\n")); + else + { + RTPrintf("%s\n", strLicense.c_str()); + RTPrintf(Misc::tr("Do you agree to these license terms and conditions (y/n)? ")); + ch = RTStrmGetCh(g_pStdIn); + RTPrintf("\n"); + if (ch != 'y' && ch != 'Y') + { + RTPrintf(Misc::tr("Installation of \"%ls\" aborted.\n"), bstrName.raw()); + return RTEXITCODE_FAILURE; + } + if (szDigest[0]) + RTPrintf(Misc::tr("License accepted. For batch installation add\n" + "--accept-license=%s\n" + "to the VBoxManage command line.\n\n"), szDigest); + } + } + ComPtr ptrProgress; + CHECK_ERROR2I_RET(ptrExtPackFile, Install(fReplace, NULL, ptrProgress.asOutParam()), RTEXITCODE_FAILURE); + hrc = showProgress(ptrProgress); + CHECK_PROGRESS_ERROR_RET(ptrProgress, (Misc::tr("Failed to install \"%s\""), szPath), RTEXITCODE_FAILURE); + + RTPrintf(Misc::tr("Successfully installed \"%ls\".\n"), bstrName.raw()); + } + else if (!strcmp(a->argv[0], "uninstall")) + { + setCurrentSubcommand(HELP_SCOPE_EXTPACK_UNINSTALL); + const char *pszName = NULL; + bool fForced = false; + + static const RTGETOPTDEF s_aUninstallOptions[] = + { + { "--force", 'f', RTGETOPT_REQ_NOTHING }, + }; + + RTGetOptInit(&GetState, a->argc, a->argv, s_aUninstallOptions, RT_ELEMENTS(s_aUninstallOptions), 1, 0); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'f': + fForced = true; + break; + + case VINF_GETOPT_NOT_OPTION: + if (pszName) + return errorSyntax(Misc::tr("Too many extension pack names given to \"extpack uninstall\"")); + pszName = ValueUnion.psz; + break; + + default: + return errorGetOpt(ch, &ValueUnion); + } + } + if (!pszName) + return errorSyntax(Misc::tr("No extension pack name was given to \"extpack uninstall\"")); + + Bstr bstrName(pszName); + ComPtr ptrProgress; + CHECK_ERROR2I_RET(ptrExtPackMgr, Uninstall(bstrName.raw(), fForced, NULL, ptrProgress.asOutParam()), RTEXITCODE_FAILURE); + hrc = showProgress(ptrProgress); + CHECK_PROGRESS_ERROR_RET(ptrProgress, (Misc::tr("Failed to uninstall \"%s\""), pszName), RTEXITCODE_FAILURE); + + RTPrintf(Misc::tr("Successfully uninstalled \"%s\".\n"), pszName); + } + else if (!strcmp(a->argv[0], "cleanup")) + { + setCurrentSubcommand(HELP_SCOPE_EXTPACK_CLEANUP); + if (a->argc > 1) + return errorTooManyParameters(&a->argv[1]); + CHECK_ERROR2I_RET(ptrExtPackMgr, Cleanup(), RTEXITCODE_FAILURE); + RTPrintf(Misc::tr("Successfully performed extension pack cleanup\n")); + } + else + return errorUnknownSubcommand(a->argv[0]); + + return RTEXITCODE_SUCCESS; +} + +RTEXITCODE handleUnattendedDetect(HandlerArg *a) +{ + HRESULT hrc; + + /* + * Options. We work directly on an IUnattended instace while parsing + * the options. This saves a lot of extra clutter. + */ + bool fMachineReadable = false; + char szIsoPath[RTPATH_MAX]; + szIsoPath[0] = '\0'; + + /* + * Parse options. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--iso", 'i', RTGETOPT_REQ_STRING }, + { "--machine-readable", 'M', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTSTATE GetState; + int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(vrc, RTEXITCODE_FAILURE); + + int c; + RTGETOPTUNION ValueUnion; + while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (c) + { + case 'i': // --iso + vrc = RTPathAbs(ValueUnion.psz, szIsoPath, sizeof(szIsoPath)); + if (RT_FAILURE(vrc)) + return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc); + break; + + case 'M': // --machine-readable. + fMachineReadable = true; + break; + + default: + return errorGetOpt(c, &ValueUnion); + } + } + + /* + * Check for required stuff. + */ + if (szIsoPath[0] == '\0') + return errorSyntax(Misc::tr("No ISO specified")); + + /* + * Do the job. + */ + ComPtr ptrUnattended; + CHECK_ERROR2_RET(hrc, a->virtualBox, CreateUnattendedInstaller(ptrUnattended.asOutParam()), RTEXITCODE_FAILURE); + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(IsoPath)(Bstr(szIsoPath).raw()), RTEXITCODE_FAILURE); + CHECK_ERROR2(hrc, ptrUnattended, DetectIsoOS()); + RTEXITCODE rcExit = SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + + /* + * Retrieve the results. + */ + Bstr bstrDetectedOSTypeId; + CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSTypeId)(bstrDetectedOSTypeId.asOutParam()), RTEXITCODE_FAILURE); + Bstr bstrDetectedVersion; + CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSVersion)(bstrDetectedVersion.asOutParam()), RTEXITCODE_FAILURE); + Bstr bstrDetectedFlavor; + CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSFlavor)(bstrDetectedFlavor.asOutParam()), RTEXITCODE_FAILURE); + Bstr bstrDetectedLanguages; + CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSLanguages)(bstrDetectedLanguages.asOutParam()), RTEXITCODE_FAILURE); + Bstr bstrDetectedHints; + CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedOSHints)(bstrDetectedHints.asOutParam()), RTEXITCODE_FAILURE); + SafeArray aImageNames; + CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedImageNames)(ComSafeArrayAsOutParam(aImageNames)), RTEXITCODE_FAILURE); + SafeArray aImageIndices; + CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(DetectedImageIndices)(ComSafeArrayAsOutParam(aImageIndices)), RTEXITCODE_FAILURE); + Assert(aImageNames.size() == aImageIndices.size()); + BOOL fInstallSupported = FALSE; + CHECK_ERROR2_RET(hrc, ptrUnattended, COMGETTER(IsUnattendedInstallSupported)(&fInstallSupported), RTEXITCODE_FAILURE); + + if (fMachineReadable) + { + outputMachineReadableString("OSTypeId", &bstrDetectedOSTypeId); + outputMachineReadableString("OSVersion", &bstrDetectedVersion); + outputMachineReadableString("OSFlavor", &bstrDetectedFlavor); + outputMachineReadableString("OSLanguages", &bstrDetectedLanguages); + outputMachineReadableString("OSHints", &bstrDetectedHints); + for (size_t i = 0; i < aImageNames.size(); i++) + { + Bstr const bstrName = aImageNames[i]; + outputMachineReadableStringWithFmtName(&bstrName, false, "ImageIndex%u", aImageIndices[i]); + } + outputMachineReadableBool("IsInstallSupported", &fInstallSupported); + } + else + { + RTMsgInfo(Misc::tr("Detected '%s' to be:\n"), szIsoPath); + RTPrintf(Misc::tr(" OS TypeId = %ls\n" + " OS Version = %ls\n" + " OS Flavor = %ls\n" + " OS Languages = %ls\n" + " OS Hints = %ls\n"), + bstrDetectedOSTypeId.raw(), + bstrDetectedVersion.raw(), + bstrDetectedFlavor.raw(), + bstrDetectedLanguages.raw(), + bstrDetectedHints.raw()); + for (size_t i = 0; i < aImageNames.size(); i++) + RTPrintf(" Image #%-3u = %ls\n", aImageIndices[i], aImageNames[i]); + if (fInstallSupported) + RTPrintf(Misc::tr(" Unattended installation supported = yes\n")); + else + RTPrintf(Misc::tr(" Unattended installation supported = no\n")); + } + + return rcExit; +} + +RTEXITCODE handleUnattendedInstall(HandlerArg *a) +{ + HRESULT hrc; + char szAbsPath[RTPATH_MAX]; + + /* + * Options. We work directly on an IUnattended instance while parsing + * the options. This saves a lot of extra clutter. + */ + ComPtr ptrUnattended; + CHECK_ERROR2_RET(hrc, a->virtualBox, CreateUnattendedInstaller(ptrUnattended.asOutParam()), RTEXITCODE_FAILURE); + RTCList arrPackageSelectionAdjustments; + ComPtr ptrMachine; + bool fDryRun = false; + const char *pszSessionType = "none"; + + /* + * Parse options. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--iso", 'i', RTGETOPT_REQ_STRING }, + { "--user", 'u', RTGETOPT_REQ_STRING }, + { "--password", 'p', RTGETOPT_REQ_STRING }, + { "--password-file", 'X', RTGETOPT_REQ_STRING }, + { "--full-user-name", 'U', RTGETOPT_REQ_STRING }, + { "--key", 'k', RTGETOPT_REQ_STRING }, + { "--install-additions", 'A', RTGETOPT_REQ_NOTHING }, + { "--no-install-additions", 'N', RTGETOPT_REQ_NOTHING }, + { "--additions-iso", 'a', RTGETOPT_REQ_STRING }, + { "--install-txs", 't', RTGETOPT_REQ_NOTHING }, + { "--no-install-txs", 'T', RTGETOPT_REQ_NOTHING }, + { "--validation-kit-iso", 'K', RTGETOPT_REQ_STRING }, + { "--locale", 'l', RTGETOPT_REQ_STRING }, + { "--country", 'Y', RTGETOPT_REQ_STRING }, + { "--time-zone", 'z', RTGETOPT_REQ_STRING }, + { "--proxy", 'y', RTGETOPT_REQ_STRING }, + { "--hostname", 'H', RTGETOPT_REQ_STRING }, + { "--package-selection-adjustment", 's', RTGETOPT_REQ_STRING }, + { "--dry-run", 'D', RTGETOPT_REQ_NOTHING }, + // advance options: + { "--auxiliary-base-path", 'x', RTGETOPT_REQ_STRING }, + { "--image-index", 'm', RTGETOPT_REQ_UINT32 }, + { "--script-template", 'c', RTGETOPT_REQ_STRING }, + { "--post-install-template", 'C', RTGETOPT_REQ_STRING }, + { "--post-install-command", 'P', RTGETOPT_REQ_STRING }, + { "--extra-install-kernel-parameters", 'I', RTGETOPT_REQ_STRING }, + { "--language", 'L', RTGETOPT_REQ_STRING }, + // start vm related options: + { "--start-vm", 'S', RTGETOPT_REQ_STRING }, + /** @todo Add a --wait option too for waiting for the VM to shut down or + * something like that...? */ + }; + + RTGETOPTSTATE GetState; + int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(vrc, RTEXITCODE_FAILURE); + + int c; + RTGETOPTUNION ValueUnion; + while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (c) + { + case VINF_GETOPT_NOT_OPTION: + if (ptrMachine.isNotNull()) + return errorSyntax(Misc::tr("VM name/UUID given more than once!")); + CHECK_ERROR2_RET(hrc, a->virtualBox, FindMachine(Bstr(ValueUnion.psz).raw(), ptrMachine.asOutParam()), RTEXITCODE_FAILURE); + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Machine)(ptrMachine), RTEXITCODE_FAILURE); + break; + + case 'i': // --iso + vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath)); + if (RT_FAILURE(vrc)) + return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc); + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(IsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE); + break; + + case 'u': // --user + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(User)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'p': // --password + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Password)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'X': // --password-file + { + Utf8Str strPassword; + RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPassword); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Password)(Bstr(strPassword).raw()), RTEXITCODE_FAILURE); + break; + } + + case 'U': // --full-user-name + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(FullUserName)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'k': // --key + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ProductKey)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'A': // --install-additions + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallGuestAdditions)(TRUE), RTEXITCODE_FAILURE); + break; + case 'N': // --no-install-additions + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallGuestAdditions)(FALSE), RTEXITCODE_FAILURE); + break; + case 'a': // --additions-iso + vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath)); + if (RT_FAILURE(vrc)) + return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc); + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AdditionsIsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE); + break; + + case 't': // --install-txs + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallTestExecService)(TRUE), RTEXITCODE_FAILURE); + break; + case 'T': // --no-install-txs + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(InstallTestExecService)(FALSE), RTEXITCODE_FAILURE); + break; + case 'K': // --valiation-kit-iso + vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath)); + if (RT_FAILURE(vrc)) + return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc); + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ValidationKitIsoPath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE); + break; + + case 'l': // --locale + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Locale)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'Y': // --country + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Country)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'z': // --time-zone; + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(TimeZone)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'y': // --proxy + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Proxy)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'H': // --hostname + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Hostname)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 's': // --package-selection-adjustment + arrPackageSelectionAdjustments.append(ValueUnion.psz); + break; + + case 'D': + fDryRun = true; + break; + + case 'x': // --auxiliary-base-path + vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath)); + if (RT_FAILURE(vrc)) + return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc); + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(AuxiliaryBasePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE); + break; + + case 'm': // --image-index + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ImageIndex)(ValueUnion.u32), RTEXITCODE_FAILURE); + break; + + case 'c': // --script-template + vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath)); + if (RT_FAILURE(vrc)) + return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc); + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ScriptTemplatePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE); + break; + + case 'C': // --post-install-script-template + vrc = RTPathAbs(ValueUnion.psz, szAbsPath, sizeof(szAbsPath)); + if (RT_FAILURE(vrc)) + return errorSyntax(Misc::tr("RTPathAbs failed on '%s': %Rrc"), ValueUnion.psz, vrc); + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PostInstallScriptTemplatePath)(Bstr(szAbsPath).raw()), RTEXITCODE_FAILURE); + break; + + case 'P': // --post-install-command. + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PostInstallCommand)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'I': // --extra-install-kernel-parameters + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(ExtraInstallKernelParameters)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'L': // --language + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(Language)(Bstr(ValueUnion.psz).raw()), RTEXITCODE_FAILURE); + break; + + case 'S': // --start-vm + pszSessionType = ValueUnion.psz; + break; + + default: + return errorGetOpt(c, &ValueUnion); + } + } + + /* + * Check for required stuff. + */ + if (ptrMachine.isNull()) + return errorSyntax(Misc::tr("Missing VM name/UUID")); + + /* + * Set accumulative attributes. + */ + if (arrPackageSelectionAdjustments.size() == 1) + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PackageSelectionAdjustments)(Bstr(arrPackageSelectionAdjustments[0]).raw()), + RTEXITCODE_FAILURE); + else if (arrPackageSelectionAdjustments.size() > 1) + { + RTCString strAdjustments; + strAdjustments.join(arrPackageSelectionAdjustments, ";"); + CHECK_ERROR2_RET(hrc, ptrUnattended, COMSETTER(PackageSelectionAdjustments)(Bstr(strAdjustments).raw()), RTEXITCODE_FAILURE); + } + + /* + * Get details about the machine so we can display them below. + */ + Bstr bstrMachineName; + CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(Name)(bstrMachineName.asOutParam()), RTEXITCODE_FAILURE); + Bstr bstrUuid; + CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(Id)(bstrUuid.asOutParam()), RTEXITCODE_FAILURE); + BSTR bstrInstalledOS; + CHECK_ERROR2_RET(hrc, ptrMachine, COMGETTER(OSTypeId)(&bstrInstalledOS), RTEXITCODE_FAILURE); + Utf8Str strInstalledOS(bstrInstalledOS); + + /* + * Temporarily lock the machine to check whether it's running or not. + * We take this opportunity to disable the first run wizard. + */ + CHECK_ERROR2_RET(hrc, ptrMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + { + ComPtr ptrConsole; + CHECK_ERROR2(hrc, a->session, COMGETTER(Console)(ptrConsole.asOutParam())); + + if ( ptrConsole.isNull() + && SUCCEEDED(hrc) + && ( RTStrICmp(pszSessionType, "gui") == 0 + || RTStrICmp(pszSessionType, "none") == 0)) + { + ComPtr ptrSessonMachine; + CHECK_ERROR2(hrc, a->session, COMGETTER(Machine)(ptrSessonMachine.asOutParam())); + if (ptrSessonMachine.isNotNull()) + { + CHECK_ERROR2(hrc, ptrSessonMachine, SetExtraData(Bstr("GUI/FirstRun").raw(), Bstr("0").raw())); + } + } + + a->session->UnlockMachine(); + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + if (ptrConsole.isNotNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Misc::tr("Machine '%ls' is currently running"), bstrMachineName.raw()); + } + + /* + * Do the work. + */ + RTMsgInfo(Misc::tr("%s unattended installation of %s in machine '%ls' (%ls).\n"), + RTStrICmp(pszSessionType, "none") == 0 ? Misc::tr("Preparing") : Misc::tr("Starting"), + strInstalledOS.c_str(), bstrMachineName.raw(), bstrUuid.raw()); + + CHECK_ERROR2_RET(hrc, ptrUnattended,Prepare(), RTEXITCODE_FAILURE); + if (!fDryRun) + { + CHECK_ERROR2_RET(hrc, ptrUnattended, ConstructMedia(), RTEXITCODE_FAILURE); + CHECK_ERROR2_RET(hrc, ptrUnattended, ReconfigureVM(), RTEXITCODE_FAILURE); + } + + /* + * Retrieve and display the parameters actually used. + */ + RTMsgInfo(Misc::tr("Using values:\n")); +#define SHOW_ATTR(a_Attr, a_szText, a_Type, a_szFmt) do { \ + a_Type Value; \ + HRESULT hrc2 = ptrUnattended->COMGETTER(a_Attr)(&Value); \ + if (SUCCEEDED(hrc2)) \ + RTPrintf(" %32s = " a_szFmt "\n", a_szText, Value); \ + else \ + RTPrintf(Misc::tr(" %32s = failed: %Rhrc\n"), a_szText, hrc2); \ + } while (0) +#define SHOW_STR_ATTR(a_Attr, a_szText) do { \ + Bstr bstrString; \ + HRESULT hrc2 = ptrUnattended->COMGETTER(a_Attr)(bstrString.asOutParam()); \ + if (SUCCEEDED(hrc2)) \ + RTPrintf(" %32s = %ls\n", a_szText, bstrString.raw()); \ + else \ + RTPrintf(Misc::tr(" %32s = failed: %Rhrc\n"), a_szText, hrc2); \ + } while (0) + + SHOW_STR_ATTR(IsoPath, "isoPath"); + SHOW_STR_ATTR(User, "user"); + SHOW_STR_ATTR(Password, "password"); + SHOW_STR_ATTR(FullUserName, "fullUserName"); + SHOW_STR_ATTR(ProductKey, "productKey"); + SHOW_STR_ATTR(AdditionsIsoPath, "additionsIsoPath"); + SHOW_ATTR( InstallGuestAdditions, "installGuestAdditions", BOOL, "%RTbool"); + SHOW_STR_ATTR(ValidationKitIsoPath, "validationKitIsoPath"); + SHOW_ATTR( InstallTestExecService, "installTestExecService", BOOL, "%RTbool"); + SHOW_STR_ATTR(Locale, "locale"); + SHOW_STR_ATTR(Country, "country"); + SHOW_STR_ATTR(TimeZone, "timeZone"); + SHOW_STR_ATTR(Proxy, "proxy"); + SHOW_STR_ATTR(Hostname, "hostname"); + SHOW_STR_ATTR(PackageSelectionAdjustments, "packageSelectionAdjustments"); + SHOW_STR_ATTR(AuxiliaryBasePath, "auxiliaryBasePath"); + SHOW_ATTR( ImageIndex, "imageIndex", ULONG, "%u"); + SHOW_STR_ATTR(ScriptTemplatePath, "scriptTemplatePath"); + SHOW_STR_ATTR(PostInstallScriptTemplatePath, "postInstallScriptTemplatePath"); + SHOW_STR_ATTR(PostInstallCommand, "postInstallCommand"); + SHOW_STR_ATTR(ExtraInstallKernelParameters, "extraInstallKernelParameters"); + SHOW_STR_ATTR(Language, "language"); + SHOW_STR_ATTR(DetectedOSTypeId, "detectedOSTypeId"); + SHOW_STR_ATTR(DetectedOSVersion, "detectedOSVersion"); + SHOW_STR_ATTR(DetectedOSFlavor, "detectedOSFlavor"); + SHOW_STR_ATTR(DetectedOSLanguages, "detectedOSLanguages"); + SHOW_STR_ATTR(DetectedOSHints, "detectedOSHints"); + { + ULONG idxImage = 0; + HRESULT hrc2 = ptrUnattended->COMGETTER(ImageIndex)(&idxImage); + if (FAILED(hrc2)) + idxImage = 0; + SafeArray aImageNames; + hrc2 = ptrUnattended->COMGETTER(DetectedImageNames)(ComSafeArrayAsOutParam(aImageNames)); + if (SUCCEEDED(hrc2)) + { + SafeArray aImageIndices; + hrc2 = ptrUnattended->COMGETTER(DetectedImageIndices)(ComSafeArrayAsOutParam(aImageIndices)); + if (SUCCEEDED(hrc2)) + { + Assert(aImageNames.size() == aImageIndices.size()); + for (size_t i = 0; i < aImageNames.size(); i++) + { + char szTmp[64]; + RTStrPrintf(szTmp, sizeof(szTmp), "detectedImage[%u]%s", i, idxImage != aImageIndices[i] ? "" : "*"); + RTPrintf(" %32s = #%u: %ls\n", szTmp, aImageIndices[i], aImageNames[i]); + } + } + else + RTPrintf(Misc::tr(" %32s = failed: %Rhrc\n"), "detectedImageIndices", hrc2); + } + else + RTPrintf(Misc::tr(" %32 = failed: %Rhrc\n"), "detectedImageNames", hrc2); + } + +#undef SHOW_STR_ATTR +#undef SHOW_ATTR + + /* We can drop the IUnatteded object now. */ + ptrUnattended.setNull(); + + /* + * Start the VM if requested. + */ + if ( fDryRun + || RTStrICmp(pszSessionType, "none") == 0) + { + if (!fDryRun) + RTMsgInfo(Misc::tr("VM '%ls' (%ls) is ready to be started (e.g. VBoxManage startvm).\n"), bstrMachineName.raw(), bstrUuid.raw()); + hrc = S_OK; + } + else + { + com::SafeArray aBstrEnv; +#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) + /* make sure the VM process will start on the same display as VBoxManage */ + const char *pszDisplay = RTEnvGet("DISPLAY"); + if (pszDisplay) + aBstrEnv.push_back(BstrFmt("DISPLAY=%s", pszDisplay).raw()); + const char *pszXAuth = RTEnvGet("XAUTHORITY"); + if (pszXAuth) + aBstrEnv.push_back(BstrFmt("XAUTHORITY=%s", pszXAuth).raw()); +#endif + ComPtr ptrProgress; + CHECK_ERROR2(hrc, ptrMachine, LaunchVMProcess(a->session, Bstr(pszSessionType).raw(), ComSafeArrayAsInParam(aBstrEnv), ptrProgress.asOutParam())); + if (SUCCEEDED(hrc) && !ptrProgress.isNull()) + { + RTMsgInfo(Misc::tr("Waiting for VM '%ls' to power on...\n"), bstrMachineName.raw()); + CHECK_ERROR2(hrc, ptrProgress, WaitForCompletion(-1)); + if (SUCCEEDED(hrc)) + { + BOOL fCompleted = true; + CHECK_ERROR2(hrc, ptrProgress, COMGETTER(Completed)(&fCompleted)); + if (SUCCEEDED(hrc)) + { + ASSERT(fCompleted); + + LONG iRc; + CHECK_ERROR2(hrc, ptrProgress, COMGETTER(ResultCode)(&iRc)); + if (SUCCEEDED(hrc)) + { + if (SUCCEEDED(iRc)) + RTMsgInfo(Misc::tr("VM '%ls' (%ls) has been successfully started.\n"), + bstrMachineName.raw(), bstrUuid.raw()); + else + { + ProgressErrorInfo info(ptrProgress); + com::GluePrintErrorInfo(info); + } + hrc = iRc; + } + } + } + } + + /* + * Do we wait for the VM to power down? + */ + } + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +RTEXITCODE handleUnattended(HandlerArg *a) +{ + /* + * Sub-command switch. + */ + if (a->argc < 1) + return errorNoSubcommand(); + + if (!strcmp(a->argv[0], "detect")) + { + setCurrentSubcommand(HELP_SCOPE_UNATTENDED_DETECT); + return handleUnattendedDetect(a); + } + + if (!strcmp(a->argv[0], "install")) + { + setCurrentSubcommand(HELP_SCOPE_UNATTENDED_INSTALL); + return handleUnattendedInstall(a); + } + + /* Consider some kind of create-vm-and-install-guest-os command. */ + return errorUnknownSubcommand(a->argv[0]); +} + +/** + * Common Cloud profile options. + */ +typedef struct +{ + const char *pszProviderName; + const char *pszProfileName; +} CLOUDPROFILECOMMONOPT; +typedef CLOUDPROFILECOMMONOPT *PCLOUDPROFILECOMMONOPT; + +/** + * Sets the properties of cloud profile + * + * @returns 0 on success, 1 on failure + */ + +static RTEXITCODE setCloudProfileProperties(HandlerArg *a, int iFirst, PCLOUDPROFILECOMMONOPT pCommonOpts) +{ + + HRESULT hrc = S_OK; + + Bstr bstrProvider(pCommonOpts->pszProviderName); + Bstr bstrProfile(pCommonOpts->pszProfileName); + + /* + * Parse options. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--clouduser", 'u', RTGETOPT_REQ_STRING }, + { "--fingerprint", 'p', RTGETOPT_REQ_STRING }, + { "--keyfile", 'k', RTGETOPT_REQ_STRING }, + { "--passphrase", 'P', RTGETOPT_REQ_STRING }, + { "--tenancy", 't', RTGETOPT_REQ_STRING }, + { "--compartment", 'c', RTGETOPT_REQ_STRING }, + { "--region", 'r', RTGETOPT_REQ_STRING } + }; + + RTGETOPTSTATE GetState; + int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0); + AssertRCReturn(vrc, RTEXITCODE_FAILURE); + + com::SafeArray names; + com::SafeArray values; + + int c; + RTGETOPTUNION ValueUnion; + while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (c) + { + case 'u': // --clouduser + Bstr("user").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 'p': // --fingerprint + Bstr("fingerprint").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 'k': // --keyfile + Bstr("key_file").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 'P': // --passphrase + Bstr("pass_phrase").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 't': // --tenancy + Bstr("tenancy").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 'c': // --compartment + Bstr("compartment").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 'r': // --region + Bstr("region").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + default: + return errorGetOpt(c, &ValueUnion); + } + } + + /* check for required options */ + if (bstrProvider.isEmpty()) + return errorSyntax(Misc::tr("Parameter --provider is required")); + if (bstrProfile.isEmpty()) + return errorSyntax(Misc::tr("Parameter --profile is required")); + + ComPtr pVirtualBox = a->virtualBox; + + ComPtr pCloudProviderManager; + CHECK_ERROR2_RET(hrc, pVirtualBox, + COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()), + RTEXITCODE_FAILURE); + + ComPtr pCloudProvider; + + CHECK_ERROR2_RET(hrc, pCloudProviderManager, + GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()), + RTEXITCODE_FAILURE); + + ComPtr pCloudProfile; + + if (pCloudProvider) + { + CHECK_ERROR2_RET(hrc, pCloudProvider, + GetProfileByName(bstrProfile.raw(), pCloudProfile.asOutParam()), + RTEXITCODE_FAILURE); + CHECK_ERROR2_RET(hrc, pCloudProfile, + SetProperties(ComSafeArrayAsInParam(names), ComSafeArrayAsInParam(values)), + RTEXITCODE_FAILURE); + } + + CHECK_ERROR2(hrc, pCloudProvider, SaveProfiles()); + + RTPrintf(Misc::tr("Provider %ls: profile '%ls' was updated.\n"),bstrProvider.raw(), bstrProfile.raw()); + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +/** + * Gets the properties of cloud profile + * + * @returns 0 on success, 1 on failure + */ +static RTEXITCODE showCloudProfileProperties(HandlerArg *a, PCLOUDPROFILECOMMONOPT pCommonOpts) +{ + HRESULT hrc = S_OK; + + Bstr bstrProvider(pCommonOpts->pszProviderName); + Bstr bstrProfile(pCommonOpts->pszProfileName); + + /* check for required options */ + if (bstrProvider.isEmpty()) + return errorSyntax(Misc::tr("Parameter --provider is required")); + if (bstrProfile.isEmpty()) + return errorSyntax(Misc::tr("Parameter --profile is required")); + + ComPtr pVirtualBox = a->virtualBox; + ComPtr pCloudProviderManager; + CHECK_ERROR2_RET(hrc, pVirtualBox, + COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()), + RTEXITCODE_FAILURE); + ComPtr pCloudProvider; + CHECK_ERROR2_RET(hrc, pCloudProviderManager, + GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()), + RTEXITCODE_FAILURE); + + ComPtr pCloudProfile; + if (pCloudProvider) + { + CHECK_ERROR2_RET(hrc, pCloudProvider, + GetProfileByName(bstrProfile.raw(), pCloudProfile.asOutParam()), + RTEXITCODE_FAILURE); + + Bstr bstrProviderID; + pCloudProfile->COMGETTER(ProviderId)(bstrProviderID.asOutParam()); + RTPrintf(Misc::tr("Provider GUID: %ls\n"), bstrProviderID.raw()); + + com::SafeArray names; + com::SafeArray values; + CHECK_ERROR2_RET(hrc, pCloudProfile, + GetProperties(Bstr().raw(), ComSafeArrayAsOutParam(names), ComSafeArrayAsOutParam(values)), + RTEXITCODE_FAILURE); + size_t cNames = names.size(); + size_t cValues = values.size(); + bool fFirst = true; + for (size_t k = 0; k < cNames; k++) + { + Bstr value; + if (k < cValues) + value = values[k]; + RTPrintf("%s%ls=%ls\n", + fFirst ? Misc::tr("Property: ") : " ", + names[k], value.raw()); + fFirst = false; + } + + RTPrintf("\n"); + } + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static RTEXITCODE addCloudProfile(HandlerArg *a, int iFirst, PCLOUDPROFILECOMMONOPT pCommonOpts) +{ + HRESULT hrc = S_OK; + + Bstr bstrProvider(pCommonOpts->pszProviderName); + Bstr bstrProfile(pCommonOpts->pszProfileName); + + + /* check for required options */ + if (bstrProvider.isEmpty()) + return errorSyntax(Misc::tr("Parameter --provider is required")); + if (bstrProfile.isEmpty()) + return errorSyntax(Misc::tr("Parameter --profile is required")); + + /* + * Parse options. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--clouduser", 'u', RTGETOPT_REQ_STRING }, + { "--fingerprint", 'p', RTGETOPT_REQ_STRING }, + { "--keyfile", 'k', RTGETOPT_REQ_STRING }, + { "--passphrase", 'P', RTGETOPT_REQ_STRING }, + { "--tenancy", 't', RTGETOPT_REQ_STRING }, + { "--compartment", 'c', RTGETOPT_REQ_STRING }, + { "--region", 'r', RTGETOPT_REQ_STRING } + }; + + RTGETOPTSTATE GetState; + int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0); + AssertRCReturn(vrc, RTEXITCODE_FAILURE); + + com::SafeArray names; + com::SafeArray values; + + int c; + RTGETOPTUNION ValueUnion; + while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (c) + { + case 'u': // --clouduser + Bstr("user").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 'p': // --fingerprint + Bstr("fingerprint").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 'k': // --keyfile + Bstr("key_file").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 'P': // --passphrase + Bstr("pass_phrase").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 't': // --tenancy + Bstr("tenancy").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 'c': // --compartment + Bstr("compartment").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + case 'r': // --region + Bstr("region").detachTo(names.appendedRaw()); + Bstr(ValueUnion.psz).detachTo(values.appendedRaw()); + break; + default: + return errorGetOpt(c, &ValueUnion); + } + } + + ComPtr pVirtualBox = a->virtualBox; + + ComPtr pCloudProviderManager; + CHECK_ERROR2_RET(hrc, pVirtualBox, + COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()), + RTEXITCODE_FAILURE); + + ComPtr pCloudProvider; + CHECK_ERROR2_RET(hrc, pCloudProviderManager, + GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()), + RTEXITCODE_FAILURE); + + CHECK_ERROR2_RET(hrc, pCloudProvider, + CreateProfile(bstrProfile.raw(), + ComSafeArrayAsInParam(names), + ComSafeArrayAsInParam(values)), + RTEXITCODE_FAILURE); + + CHECK_ERROR2(hrc, pCloudProvider, SaveProfiles()); + + RTPrintf(Misc::tr("Provider %ls: profile '%ls' was added.\n"),bstrProvider.raw(), bstrProfile.raw()); + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static RTEXITCODE deleteCloudProfile(HandlerArg *a, PCLOUDPROFILECOMMONOPT pCommonOpts) +{ + HRESULT hrc = S_OK; + + Bstr bstrProvider(pCommonOpts->pszProviderName); + Bstr bstrProfile(pCommonOpts->pszProfileName); + + /* check for required options */ + if (bstrProvider.isEmpty()) + return errorSyntax(Misc::tr("Parameter --provider is required")); + if (bstrProfile.isEmpty()) + return errorSyntax(Misc::tr("Parameter --profile is required")); + + ComPtr pVirtualBox = a->virtualBox; + ComPtr pCloudProviderManager; + CHECK_ERROR2_RET(hrc, pVirtualBox, + COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()), + RTEXITCODE_FAILURE); + ComPtr pCloudProvider; + CHECK_ERROR2_RET(hrc, pCloudProviderManager, + GetProviderByShortName(bstrProvider.raw(), pCloudProvider.asOutParam()), + RTEXITCODE_FAILURE); + + ComPtr pCloudProfile; + if (pCloudProvider) + { + CHECK_ERROR2_RET(hrc, pCloudProvider, + GetProfileByName(bstrProfile.raw(), pCloudProfile.asOutParam()), + RTEXITCODE_FAILURE); + + CHECK_ERROR2_RET(hrc, pCloudProfile, + Remove(), + RTEXITCODE_FAILURE); + + CHECK_ERROR2_RET(hrc, pCloudProvider, + SaveProfiles(), + RTEXITCODE_FAILURE); + + RTPrintf(Misc::tr("Provider %ls: profile '%ls' was deleted.\n"), bstrProvider.raw(), bstrProfile.raw()); + } + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleCloudProfile(HandlerArg *a) +{ + if (a->argc < 1) + return errorNoSubcommand(); + + static const RTGETOPTDEF s_aOptions[] = + { + /* common options */ + { "--provider", 'v', RTGETOPT_REQ_STRING }, + { "--profile", 'f', RTGETOPT_REQ_STRING }, + /* subcommands */ + { "add", 1000, RTGETOPT_REQ_NOTHING }, + { "show", 1001, RTGETOPT_REQ_NOTHING }, + { "update", 1002, RTGETOPT_REQ_NOTHING }, + { "delete", 1003, RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTSTATE GetState; + int vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0); + AssertRCReturn(vrc, RTEXITCODE_FAILURE); + + CLOUDPROFILECOMMONOPT CommonOpts = { NULL, NULL }; + int c; + RTGETOPTUNION ValueUnion; + while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (c) + { + case 'v': // --provider + CommonOpts.pszProviderName = ValueUnion.psz; + break; + case 'f': // --profile + CommonOpts.pszProfileName = ValueUnion.psz; + break; + /* Sub-commands: */ + case 1000: + setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_ADD); + return addCloudProfile(a, GetState.iNext, &CommonOpts); + case 1001: + setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_SHOW); + return showCloudProfileProperties(a, &CommonOpts); + case 1002: + setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_UPDATE); + return setCloudProfileProperties(a, GetState.iNext, &CommonOpts); + case 1003: + setCurrentSubcommand(HELP_SCOPE_CLOUDPROFILE_DELETE); + return deleteCloudProfile(a, &CommonOpts); + case VINF_GETOPT_NOT_OPTION: + return errorUnknownSubcommand(ValueUnion.psz); + + default: + return errorGetOpt(c, &ValueUnion); + } + } + + return errorNoSubcommand(); +} -- cgit v1.2.3