summaryrefslogtreecommitdiffstats
path: root/src/VBox/Frontends/VBoxManage/VBoxManageCloudMachine.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/Frontends/VBoxManage/VBoxManageCloudMachine.cpp1482
1 files changed, 1482 insertions, 0 deletions
diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageCloudMachine.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageCloudMachine.cpp
new file mode 100644
index 00000000..78223337
--- /dev/null
+++ b/src/VBox/Frontends/VBoxManage/VBoxManageCloudMachine.cpp
@@ -0,0 +1,1482 @@
+/* $Id: VBoxManageCloudMachine.cpp $ */
+/** @file
+ * VBoxManageCloudMachine - The cloud machine related commands.
+ */
+
+/*
+ * 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "VBoxManage.h"
+
+#include <VBox/log.h>
+
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/errorprint.h>
+
+#include <algorithm>
+#include <vector>
+
+DECLARE_TRANSLATION_CONTEXT(CloudMachine);
+
+
+struct CMachineHandlerArg
+ : public HandlerArg
+{
+ ComPtr<ICloudClient> pClient;
+
+ const char *pcszSpec; /* RTGETOPTUNION::psz, points inside argv */
+ enum { GUESS, ID, NAME } enmSpecKind;
+ ComPtr<ICloudMachine> pMachine;
+
+ explicit CMachineHandlerArg(const HandlerArg &a)
+ : HandlerArg(a), pcszSpec(NULL), enmSpecKind(GUESS) {}
+};
+
+
+static int selectCloudProvider(ComPtr<ICloudProvider> &pProvider,
+ const ComPtr<IVirtualBox> &pVirtualBox,
+ const char *pszProviderName);
+static int selectCloudProfile(ComPtr<ICloudProfile> &pProfile,
+ const ComPtr<ICloudProvider> &pProvider,
+ const char *pszProviderName);
+static int getCloudClient(CMachineHandlerArg &a,
+ const char *pcszProviderName,
+ const char *pcszProfileName);
+
+static HRESULT getMachineList(com::SafeIfaceArray<ICloudMachine> &aMachines,
+ const ComPtr<ICloudClient> &pClient);
+
+static HRESULT getMachineBySpec(CMachineHandlerArg *a);
+static HRESULT getMachineById(CMachineHandlerArg *a);
+static HRESULT getMachineByName(CMachineHandlerArg *a);
+static HRESULT getMachineByGuess(CMachineHandlerArg *a);
+
+static int checkMachineSpecArgument(CMachineHandlerArg *a,
+ int ch, const RTGETOPTUNION &Val);
+
+
+static RTEXITCODE handleCloudMachineImpl(CMachineHandlerArg *a, int iFirst);
+
+static RTEXITCODE handleCloudMachineStart(CMachineHandlerArg *a, int iFirst);
+static RTEXITCODE handleCloudMachineReboot(CMachineHandlerArg *a, int iFirst);
+static RTEXITCODE handleCloudMachineReset(CMachineHandlerArg *a, int iFirst);
+static RTEXITCODE handleCloudMachineShutdown(CMachineHandlerArg *a, int iFirst);
+static RTEXITCODE handleCloudMachinePowerdown(CMachineHandlerArg *a, int iFirst);
+static RTEXITCODE handleCloudMachineTerminate(CMachineHandlerArg *a, int iFirst);
+
+static RTEXITCODE handleCloudMachineConsoleHistory(CMachineHandlerArg *a, int iFirst);
+
+static RTEXITCODE listCloudMachinesImpl(CMachineHandlerArg *a, int iFirst);
+static RTEXITCODE handleCloudMachineInfo(CMachineHandlerArg *a, int iFirst);
+
+static HRESULT printMachineInfo(const ComPtr<ICloudMachine> &pMachine);
+static HRESULT printFormValue(const ComPtr<IFormValue> &pValue);
+
+
+
+/*
+ * This is a temporary hack as I don't want to refactor "cloud"
+ * handling right now, as it's not yet clear to me what is the
+ * direction that we want to take with it.
+ *
+ * The problem with the way "cloud" command handling is currently
+ * written is that it's a bit schizophrenic about whether we have
+ * multiple cloud providers or not. OTOH it insists on --provider
+ * being mandatory, on the other it hardcodes the list of available
+ * subcommands, though in principle those can vary from provider to
+ * provider. If we do want to support multiple providers we might
+ * need to come up with a way to allow an extpack provider to supply
+ * its own VBoxManage command handler for "cloud" based on --provider
+ * as the selector.
+ *
+ * Processing of --provider and --profile should not be postponed
+ * until the leaf command handler, but rather happen immediately, so
+ * do this here at our earliest opportunity (without actually doing it
+ * in handleCloud).
+ */
+RTEXITCODE
+handleCloudMachine(HandlerArg *a, int iFirst,
+ const char *pcszProviderName,
+ const char *pcszProfileName)
+{
+ CMachineHandlerArg handlerArg(*a);
+ int vrc = getCloudClient(handlerArg, pcszProviderName, pcszProfileName);
+ if (RT_FAILURE(vrc))
+ return RTEXITCODE_FAILURE;
+
+ return handleCloudMachineImpl(&handlerArg, iFirst);
+}
+
+
+/*
+ * Select cloud provider to use based on the --provider option to the
+ * "cloud" command. The option is not mandatory if only a single
+ * provider is available.
+ */
+static int
+selectCloudProvider(ComPtr<ICloudProvider> &pProvider,
+ const ComPtr<IVirtualBox> &pVirtualBox,
+ const char *pcszProviderName)
+{
+ HRESULT hrc;
+
+ ComPtr<ICloudProviderManager> pCloudProviderManager;
+ CHECK_ERROR2_RET(hrc, pVirtualBox,
+ COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()),
+ VERR_GENERAL_FAILURE);
+
+
+ /*
+ * If the provider is explicitly specified, just look it up and
+ * return.
+ */
+ if (pcszProviderName != NULL)
+ {
+ /*
+ * Should we also provide a way to specify the provider also
+ * by its id? Is it even useful? If so, should we use a
+ * different option or check if the provider name looks like
+ * an id and used a different getter?
+ */
+ CHECK_ERROR2_RET(hrc, pCloudProviderManager,
+ GetProviderByShortName(com::Bstr(pcszProviderName).raw(),
+ pProvider.asOutParam()),
+ VERR_NOT_FOUND);
+
+ return VINF_SUCCESS;
+ }
+
+
+ /*
+ * We have only one provider and it's not clear if we will ever
+ * have more than one. Forcing the user to explicitly specify the
+ * only provider available is not very nice. So try to be
+ * friendly.
+ */
+ com::SafeIfaceArray<ICloudProvider> aProviders;
+ CHECK_ERROR2_RET(hrc, pCloudProviderManager,
+ COMGETTER(Providers)(ComSafeArrayAsOutParam(aProviders)),
+ VERR_GENERAL_FAILURE);
+
+ if (aProviders.size() == 0)
+ {
+ RTMsgError(CloudMachine::tr("cloud: no providers available"));
+ return VERR_NOT_FOUND;
+ }
+
+ if (aProviders.size() > 1)
+ {
+ RTMsgError(CloudMachine::tr("cloud: multiple providers available,"
+ " '--provider' option is required"));
+ return VERR_MISSING;
+ }
+
+ /* Do RTMsgInfo telling the user which one was selected? */
+ pProvider = aProviders[0];
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Select cloud profile to use based on the --profile option to the
+ * "cloud" command. The option is not mandatory if only a single
+ * profile exists.
+ */
+static int
+selectCloudProfile(ComPtr<ICloudProfile> &pProfile,
+ const ComPtr<ICloudProvider> &pProvider,
+ const char *pcszProfileName)
+{
+ HRESULT hrc;
+
+ /*
+ * If the profile is explicitly specified, just look it up and
+ * return.
+ */
+ if (pcszProfileName != NULL)
+ {
+ CHECK_ERROR2_RET(hrc, pProvider,
+ GetProfileByName(com::Bstr(pcszProfileName).raw(),
+ pProfile.asOutParam()),
+ VERR_NOT_FOUND);
+
+ return VINF_SUCCESS;
+ }
+
+
+ /*
+ * If the user has just one profile for this provider, don't force
+ * them to specify it. I'm not entirely sure about this one,
+ * actually. It's nice for interactive use, but it might be not
+ * forward compatible if used in a script and then when another
+ * profile is created the script starts failing. I'd say, give
+ * them enough rope...
+ */
+ com::SafeIfaceArray<ICloudProfile> aProfiles;
+ CHECK_ERROR2_RET(hrc, pProvider,
+ COMGETTER(Profiles)(ComSafeArrayAsOutParam(aProfiles)),
+ VERR_GENERAL_FAILURE);
+
+ if (aProfiles.size() == 0)
+ {
+ RTMsgError(CloudMachine::tr("cloud: no profiles exist"));
+ return VERR_NOT_FOUND;
+ }
+
+ if (aProfiles.size() > 1)
+ {
+ RTMsgError(CloudMachine::tr("cloud: multiple profiles exist, '--profile' option is required"));
+ return VERR_MISSING;
+ }
+
+ /* Do RTMsgInfo telling the user which one was selected? */
+ pProfile = aProfiles[0];
+ return VINF_SUCCESS;
+}
+
+
+static int
+getCloudClient(CMachineHandlerArg &a,
+ const char *pcszProviderName,
+ const char *pcszProfileName)
+{
+ ComPtr<ICloudProvider> pProvider;
+ int vrc = selectCloudProvider(pProvider, a.virtualBox, pcszProviderName);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ ComPtr<ICloudProfile> pProfile;
+ vrc = selectCloudProfile(pProfile, pProvider, pcszProfileName);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ ComPtr<ICloudClient> pCloudClient;
+ CHECK_ERROR2I_RET(pProfile, CreateCloudClient(pCloudClient.asOutParam()), VERR_GENERAL_FAILURE);
+
+ a.pClient = pCloudClient;
+ return VINF_SUCCESS;
+}
+
+
+static HRESULT
+getMachineList(com::SafeIfaceArray<ICloudMachine> &aMachines,
+ const ComPtr<ICloudClient> &pClient)
+{
+ HRESULT hrc;
+
+ ComPtr<IProgress> pListProgress;
+ CHECK_ERROR2_RET(hrc, pClient,
+ ReadCloudMachineList(pListProgress.asOutParam()),
+ hrc);
+
+ hrc = showProgress(pListProgress, SHOW_PROGRESS_NONE);
+ if (FAILED(hrc))
+ return hrc;
+
+ CHECK_ERROR2_RET(hrc, pClient,
+ COMGETTER(CloudMachineList)(ComSafeArrayAsOutParam(aMachines)),
+ hrc);
+
+ return S_OK;
+}
+
+
+static HRESULT
+getMachineById(CMachineHandlerArg *a)
+{
+ HRESULT hrc;
+
+ ComPtr<ICloudMachine> pMachine;
+ CHECK_ERROR2_RET(hrc, a->pClient,
+ GetCloudMachine(com::Bstr(a->pcszSpec).raw(),
+ pMachine.asOutParam()), hrc);
+
+ ComPtr<IProgress> pRefreshProgress;
+ CHECK_ERROR2_RET(hrc, pMachine,
+ Refresh(pRefreshProgress.asOutParam()), hrc);
+
+ hrc = showProgress(pRefreshProgress, SHOW_PROGRESS_NONE);
+ if (FAILED(hrc))
+ return hrc;
+
+ a->pMachine = pMachine;
+ return S_OK;
+}
+
+
+static HRESULT
+getMachineByName(CMachineHandlerArg *a)
+{
+ HRESULT hrc;
+
+ com::SafeIfaceArray<ICloudMachine> aMachines;
+ hrc = getMachineList(aMachines, a->pClient);
+ if (FAILED(hrc))
+ return hrc;
+
+ const size_t cMachines = aMachines.size();
+ if (cMachines == 0)
+ return VBOX_E_OBJECT_NOT_FOUND;
+
+ ComPtr<ICloudMachine> pMachineFound;
+ for (size_t i = 0; i < cMachines; ++i)
+ {
+ const ComPtr<ICloudMachine> pMachine = aMachines[i];
+
+ com::Bstr bstrName;
+ CHECK_ERROR2_RET(hrc, pMachine,
+ COMGETTER(Name)(bstrName.asOutParam()),
+ hrc);
+
+ if (!bstrName.equals(a->pcszSpec))
+ continue;
+
+ if (pMachineFound.isNull())
+ pMachineFound = pMachine;
+ else
+ {
+ com::Bstr bstrId1, bstrId2;
+ CHECK_ERROR2_RET(hrc, pMachineFound,
+ COMGETTER(Id)(bstrId1.asOutParam()),
+ hrc);
+ CHECK_ERROR2_RET(hrc, pMachine,
+ COMGETTER(Id)(bstrId2.asOutParam()),
+ hrc);
+
+ RTMsgError(CloudMachine::tr("ambiguous name: %ls and %ls"), bstrId1.raw(), bstrId2.raw());
+ return VBOX_E_OBJECT_NOT_FOUND;
+ }
+ }
+
+ if (pMachineFound.isNull())
+ return VBOX_E_OBJECT_NOT_FOUND;
+
+ a->pMachine = pMachineFound;
+ return S_OK;
+}
+
+
+/*
+ * Try to find the machine refered by pcszWhatever. If the look up by
+ * id fails we might want to fallback to look up by name, b/c someone
+ * might want to use a uuid as a display name of a machine. But cloud
+ * lookups are not fast, so that would be incurring performance
+ * penalty for typos or for machines that are gone. Should provide
+ * explicit --id/--name options instead.
+ */
+static HRESULT
+getMachineByGuess(CMachineHandlerArg *a)
+{
+ HRESULT hrc;
+
+ RTUUID Uuid;
+ int vrc = RTUuidFromStr(&Uuid, a->pcszSpec);
+ if (RT_SUCCESS(vrc))
+ hrc = getMachineById(a);
+ else
+ hrc = getMachineByName(a);
+
+ if (FAILED(hrc))
+ return hrc;
+
+ return S_OK;
+}
+
+
+
+/*
+ * RTGETOPTINIT_FLAGS_NO_STD_OPTS recognizes both --help and --version
+ * and we don't want the latter. It's easier to add one line of this
+ * macro to the s_aOptions initializers than to filter out --version.
+ */
+#define CLOUD_MACHINE_RTGETOPTDEF_HELP \
+ { "--help", 'h', RTGETOPT_REQ_NOTHING }, \
+ { "-help", 'h', RTGETOPT_REQ_NOTHING }, \
+ { "help", 'h', RTGETOPT_REQ_NOTHING }, \
+ { "-?", 'h', RTGETOPT_REQ_NOTHING }
+
+static RTEXITCODE
+errThereCanBeOnlyOne()
+{
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ CloudMachine::tr("only one machine can be specified"));
+}
+
+
+#define CLOUD_MACHINE_RTGETOPTDEF_MACHINE \
+ { "--id", 'i', RTGETOPT_REQ_STRING }, \
+ { "--name", 'n', RTGETOPT_REQ_STRING }
+
+
+/*
+ * Almost all the cloud machine commands take a machine argument, so
+ * factor out the code to fish it out from the command line.
+ *
+ * ch - option should be processed by the caller.
+ * VINF_SUCCESS - option was processed.
+ * VERR_PARSE_ERROR - RTEXITCODE_SYNTAX
+ * Other IPRT errors - RTEXITCODE_FAILURE
+ */
+static int
+checkMachineSpecArgument(CMachineHandlerArg *a,
+ int ch, const RTGETOPTUNION &Val)
+{
+ int vrc;
+
+ switch (ch)
+ {
+ /*
+ * Note that we don't used RTGETOPT_REQ_UUID here as it would
+ * be too limiting. First, we need the original string for
+ * the API call, not the UUID, and second, if the UUID has bad
+ * forward RTGetOptPrintError doesn't have access to the
+ * option argument for the error message. So do the format
+ * check ourselves.
+ */
+ case 'i': /* --id */
+ {
+ const char *pcszId = Val.psz;
+
+ if (a->pcszSpec != NULL)
+ {
+ errThereCanBeOnlyOne();
+ return VERR_PARSE_ERROR;
+ }
+
+ RTUUID Uuid;
+ vrc = RTUuidFromStr(&Uuid, pcszId);
+ if (RT_FAILURE(vrc))
+ {
+ RTMsgError(CloudMachine::tr("not a valid uuid: %s"), pcszId);
+ return VERR_PARSE_ERROR;
+ }
+
+ a->pcszSpec = pcszId;
+ a->enmSpecKind = CMachineHandlerArg::ID;
+ return VINF_SUCCESS;
+ }
+
+ case 'n': /* --name */
+ {
+ const char *pcszName = Val.psz;
+
+ if (a->pcszSpec != NULL)
+ {
+ errThereCanBeOnlyOne();
+ return VERR_PARSE_ERROR;
+ }
+
+ a->pcszSpec = pcszName;
+ a->enmSpecKind = CMachineHandlerArg::NAME;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Plain word (no dash/es). This must name a machine, though
+ * we have to guess whether it's an id or a name.
+ */
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ const char *pcszNameOrId = Val.psz;
+
+ if (a->pcszSpec != NULL)
+ {
+ errThereCanBeOnlyOne();
+ return VERR_PARSE_ERROR;
+ }
+
+ a->pcszSpec = pcszNameOrId;
+ a->enmSpecKind = CMachineHandlerArg::GUESS;
+ return VINF_SUCCESS;
+ }
+
+ /* might as well do it here */
+ case 'h': /* --help */
+ {
+ printHelp(g_pStdOut);
+ return VINF_CALLBACK_RETURN;
+ }
+ }
+
+ /* let the caller deal with it */
+ return VINF_NOT_SUPPORTED;
+}
+
+
+static HRESULT
+getMachineBySpec(CMachineHandlerArg *a)
+{
+ HRESULT hrc = E_FAIL;
+
+ if (a->pcszSpec == NULL)
+ {
+ RTMsgErrorExit(RTEXITCODE_SYNTAX, CloudMachine::tr("machine not specified"));
+ return E_FAIL;
+ }
+
+ if (a->pcszSpec[0] == '\0')
+ {
+ RTMsgError(CloudMachine::tr("machine name is empty"));
+ return E_FAIL;
+ }
+
+ switch (a->enmSpecKind)
+ {
+ case CMachineHandlerArg::ID:
+ hrc = getMachineById(a);
+ if (FAILED(hrc))
+ {
+ if (hrc == VBOX_E_OBJECT_NOT_FOUND)
+ RTMsgError(CloudMachine::tr("unable to find machine with id %s"), a->pcszSpec);
+ return hrc;
+ }
+ break;
+
+ case CMachineHandlerArg::NAME:
+ hrc = getMachineByName(a);
+ if (FAILED(hrc))
+ {
+ if (hrc == VBOX_E_OBJECT_NOT_FOUND)
+ RTMsgError(CloudMachine::tr("unable to find machine with name %s"), a->pcszSpec);
+ return hrc;
+ }
+ break;
+
+ case CMachineHandlerArg::GUESS:
+ hrc = getMachineByGuess(a);
+ if (FAILED(hrc))
+ {
+ if (hrc == VBOX_E_OBJECT_NOT_FOUND)
+ RTMsgError(CloudMachine::tr("unable to find machine %s"), a->pcszSpec);
+ return hrc;
+ }
+ break;
+ }
+
+ /* switch was exhaustive (and successful) */
+ AssertReturn(SUCCEEDED(hrc), E_FAIL);
+ return S_OK;
+}
+
+
+
+
+/*
+ * cloud machine [--id id | --name name] command ...
+ *
+ * We allow machine to be specified after "machine" but only with an
+ * explicit option for the obvious reason. We will also check for
+ * these options and machine spec as a plain words argument after the
+ * command word, so user can use either of:
+ *
+ * cloud machine --name foo start
+ * cloud machine start --name foo
+ * cloud machine start foo
+ *
+ * This will accept e.g. cloud machine --name foo list ... b/c we
+ * don't yet know that it's "list" that is coming, so commands that
+ * don't take machine argument check that separately when called. One
+ * side effect of this is that specifying several machines or using a
+ * syntactically invalid id will be reported as such, not as an
+ * unknown option, but that's a relatively minor nit.
+ */
+static RTEXITCODE
+handleCloudMachineImpl(CMachineHandlerArg *a, int iFirst)
+{
+ enum
+ {
+ kMachineIota = 1000,
+ kMachine_ConsoleHistory,
+ kMachine_Info,
+ kMachine_List,
+ kMachine_Powerdown,
+ kMachine_Reboot,
+ kMachine_Reset,
+ kMachine_Shutdown,
+ kMachine_Start,
+ kMachine_Terminate,
+ };
+
+ // setCurrentSubcommand(HELP_SCOPE_CLOUD_MACHINE);
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "console-history", kMachine_ConsoleHistory, RTGETOPT_REQ_NOTHING },
+ { "consolehistory", kMachine_ConsoleHistory, RTGETOPT_REQ_NOTHING },
+ { "info", kMachine_Info, RTGETOPT_REQ_NOTHING },
+ { "list", kMachine_List, RTGETOPT_REQ_NOTHING },
+ { "powerdown", kMachine_Powerdown, RTGETOPT_REQ_NOTHING },
+ { "reboot", kMachine_Reboot, RTGETOPT_REQ_NOTHING },
+ { "reset", kMachine_Reset, RTGETOPT_REQ_NOTHING },
+ { "shutdown", kMachine_Shutdown, RTGETOPT_REQ_NOTHING },
+ { "start", kMachine_Start, RTGETOPT_REQ_NOTHING },
+ { "terminate", kMachine_Terminate, RTGETOPT_REQ_NOTHING },
+ CLOUD_MACHINE_RTGETOPTDEF_MACHINE,
+ CLOUD_MACHINE_RTGETOPTDEF_HELP
+ };
+
+ RTGETOPTSTATE OptState;
+ int vrc = RTGetOptInit(&OptState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions),
+ iFirst, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
+ AssertRCReturn(vrc, RTMsgErrorExit(RTEXITCODE_INIT, CloudMachine::tr("cloud machine: RTGetOptInit: %Rra"), vrc));
+
+ int ch;
+ RTGETOPTUNION Val;
+ while ((ch = RTGetOpt(&OptState, &Val)) != 0)
+ {
+ if (RT_FAILURE(ch))
+ return RTGetOptPrintError(ch, &Val);
+
+ /*
+ * Check for an unknown word first: checkMachineSpecArgument()
+ * would try to interpret that as a machine id/name.
+ */
+ if (ch == VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ CloudMachine::tr("Invalid sub-command: %s"), Val.psz);
+
+ /*
+ * Allow --id/--name after "machine", before the command.
+ * Also handles --help.
+ */
+ vrc = checkMachineSpecArgument(a, ch, Val);
+ if (vrc == VINF_SUCCESS)
+ continue;
+ if (vrc == VINF_CALLBACK_RETURN)
+ return RTEXITCODE_SUCCESS;
+ if (vrc == VERR_PARSE_ERROR)
+ return RTEXITCODE_SYNTAX;
+
+ /*
+ * Dispatch to command implementation ([ab]use getopt to do
+ * string comparisons for us).
+ */
+ switch (ch)
+ {
+ case kMachine_ConsoleHistory:
+ return handleCloudMachineConsoleHistory(a, OptState.iNext);
+
+ case kMachine_Info:
+ return handleCloudMachineInfo(a, OptState.iNext);
+
+ case kMachine_List:
+ return listCloudMachinesImpl(a, OptState.iNext);
+
+ case kMachine_Powerdown:
+ return handleCloudMachinePowerdown(a, OptState.iNext);
+
+ case kMachine_Reboot:
+ return handleCloudMachineReboot(a, OptState.iNext);
+
+ case kMachine_Reset:
+ return handleCloudMachineReset(a, OptState.iNext);
+
+ case kMachine_Shutdown:
+ return handleCloudMachineShutdown(a, OptState.iNext);
+
+ case kMachine_Start:
+ return handleCloudMachineStart(a, OptState.iNext);
+
+ case kMachine_Terminate:
+ return handleCloudMachineTerminate(a, OptState.iNext);
+
+ default: /* should never happen */
+ return RTMsgErrorExit(RTEXITCODE_INIT,
+ CloudMachine::tr("cloud machine: internal error: %d"), ch);
+ }
+ }
+
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ CloudMachine::tr("cloud machine: command required\n"
+ "Try '--help' for more information."));
+}
+
+
+/*
+ * cloud list machines
+ *
+ * The "cloud list" prefix handling is in VBoxManageCloud.cpp, so this
+ * function is not static. See handleCloudMachine() for the
+ * explanation early provider/profile lookup.
+ */
+RTEXITCODE
+listCloudMachines(HandlerArg *a, int iFirst,
+ const char *pcszProviderName,
+ const char *pcszProfileName)
+{
+ CMachineHandlerArg handlerArg(*a);
+ int vrc = getCloudClient(handlerArg, pcszProviderName, pcszProfileName);
+ if (RT_FAILURE(vrc))
+ return RTEXITCODE_FAILURE;
+
+ return listCloudMachinesImpl(&handlerArg, iFirst);
+}
+
+
+/*
+ * cloud machine list # convenience alias
+ * cloud list machines # see above
+ */
+static RTEXITCODE
+listCloudMachinesImpl(CMachineHandlerArg *a, int iFirst)
+{
+ // setCurrentSubcommand(HELP_SCOPE_CLOUD_MACHINE_LIST);
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--long", 'l', RTGETOPT_REQ_NOTHING },
+ { "--sort", 's', RTGETOPT_REQ_NOTHING },
+ CLOUD_MACHINE_RTGETOPTDEF_HELP
+ };
+
+ enum kFormatEnum { kFormat_Short, kFormat_Long };
+ kFormatEnum enmFormat = kFormat_Short;
+
+ enum kSortOrderEnum { kSortOrder_None, kSortOrder_Name, kSortOrder_Id };
+ kSortOrderEnum enmSortOrder = kSortOrder_None;
+
+ if (a->pcszSpec != NULL)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ CloudMachine::tr("cloud machine list: unexpected machine argument"));
+
+
+ RTGETOPTSTATE OptState;
+ int vrc = RTGetOptInit(&OptState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions),
+ iFirst, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
+ AssertRCReturn(vrc, RTMsgErrorExit(RTEXITCODE_INIT, CloudMachine::tr("cloud machine list: RTGetOptInit: %Rra"), vrc));
+
+ int ch;
+ RTGETOPTUNION Val;
+ while ((ch = RTGetOpt(&OptState, &Val)) != 0)
+ {
+ switch (ch)
+ {
+ case 'l':
+ enmFormat = kFormat_Long;
+ break;
+
+ case 's':
+ /** @todo optional argument to select the sort key? */
+ enmSortOrder = kSortOrder_Name;
+ break;
+
+ case 'h': /* --help */
+ printHelp(g_pStdOut);
+ return RTEXITCODE_SUCCESS;
+
+
+ case VINF_GETOPT_NOT_OPTION:
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ CloudMachine::tr("Invalid sub-command: %s"), Val.psz);
+
+ default:
+ return RTGetOptPrintError(ch, &Val);
+ }
+ }
+
+ com::SafeIfaceArray<ICloudMachine> aMachines;
+ HRESULT hrc = getMachineList(aMachines, a->pClient);
+ if (FAILED(hrc))
+ return RTEXITCODE_FAILURE;
+
+ const size_t cMachines = aMachines.size();
+ if (cMachines == 0)
+ return RTEXITCODE_SUCCESS;
+
+
+ /*
+ * Get names/ids that we need for the short output and to sort the
+ * list.
+ */
+ std::vector<ComPtr<ICloudMachine> > vMachines(cMachines);
+ std::vector<com::Bstr> vBstrNames(cMachines);
+ std::vector<com::Bstr> vBstrIds(cMachines);
+ for (size_t i = 0; i < cMachines; ++i)
+ {
+ vMachines[i] = aMachines[i];
+
+ CHECK_ERROR2_RET(hrc, vMachines[i],
+ COMGETTER(Name)(vBstrNames[i].asOutParam()),
+ RTEXITCODE_FAILURE);
+
+ CHECK_ERROR2_RET(hrc, vMachines[i],
+ COMGETTER(Id)(vBstrIds[i].asOutParam()),
+ RTEXITCODE_FAILURE);
+ }
+
+
+ /*
+ * Sort the list if necessary. The sort is indirect via an
+ * intermediate array of indexes.
+ */
+ std::vector<size_t> vIndexes(cMachines);
+ for (size_t i = 0; i < cMachines; ++i)
+ vIndexes[i] = i;
+
+ if (enmSortOrder != kSortOrder_None)
+ {
+ struct SortBy {
+ const std::vector<com::Bstr> &ks;
+ SortBy(const std::vector<com::Bstr> &aKeys) : ks(aKeys) {}
+ bool operator() (size_t l, size_t r) { return ks[l] < ks[r]; }
+ };
+
+ std::sort(vIndexes.begin(), vIndexes.end(),
+ SortBy(enmSortOrder == kSortOrder_Name
+ ? vBstrNames : vBstrIds));
+ }
+
+
+ if (enmFormat == kFormat_Short)
+ {
+ for (size_t i = 0; i < cMachines; ++i)
+ {
+ const size_t idx = vIndexes[i];
+ const com::Bstr &bstrId = vBstrIds[idx];
+ const com::Bstr &bstrName = vBstrNames[idx];
+
+ RTPrintf("%ls %ls\n", bstrId.raw(), bstrName.raw());
+ }
+ }
+ else // kFormat_Long
+ {
+ for (size_t i = 0; i < cMachines; ++i)
+ {
+ const size_t idx = vIndexes[i];
+ const ComPtr<ICloudMachine> &pMachine = vMachines[idx];
+
+ if (i != 0)
+ RTPrintf("\n");
+ printMachineInfo(pMachine);
+ }
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/*
+ * cloud showvminfo "id"
+ *
+ * Alias for "cloud machine info" that tries to match the local vm
+ * counterpart.
+ */
+RTEXITCODE
+handleCloudShowVMInfo(HandlerArg *a, int iFirst,
+ const char *pcszProviderName,
+ const char *pcszProfileName)
+{
+ CMachineHandlerArg handlerArg(*a);
+ int vrc = getCloudClient(handlerArg, pcszProviderName, pcszProfileName);
+ if (RT_FAILURE(vrc))
+ return RTEXITCODE_FAILURE;
+
+ return handleCloudMachineInfo(&handlerArg, iFirst);
+}
+
+
+/*
+ * cloud machine info "id" ...
+ */
+static RTEXITCODE
+handleCloudMachineInfo(CMachineHandlerArg *a, int iFirst)
+{
+ enum
+ {
+ kMachineInfoIota = 1000,
+ kMachineInfo_Details,
+ };
+
+ // setCurrentSubcommand(HELP_SCOPE_CLOUD_MACHINE_INFO);
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--details", kMachineInfo_Details, RTGETOPT_REQ_NOTHING },
+ CLOUD_MACHINE_RTGETOPTDEF_MACHINE,
+ CLOUD_MACHINE_RTGETOPTDEF_HELP
+ };
+
+ RTGETOPTSTATE OptState;
+ int vrc = RTGetOptInit(&OptState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions),
+ iFirst, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
+ AssertRCReturn(vrc, RTMsgErrorExit(RTEXITCODE_INIT, "RTGetOptInit: %Rra", vrc));
+
+ int ch;
+ RTGETOPTUNION Val;
+ while ((ch = RTGetOpt(&OptState, &Val)) != 0)
+ {
+ vrc = checkMachineSpecArgument(a, ch, Val);
+ if (vrc == VINF_SUCCESS)
+ continue;
+ if (vrc == VINF_CALLBACK_RETURN)
+ return RTEXITCODE_SUCCESS;
+ if (vrc == VERR_PARSE_ERROR)
+ return RTEXITCODE_SYNTAX;
+
+ switch (ch)
+ {
+ case kMachineInfo_Details:
+ /* currently no-op */
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &Val);
+ }
+ }
+
+ HRESULT hrc = getMachineBySpec(a);
+ if (FAILED(hrc))
+ return RTEXITCODE_FAILURE;
+
+ /* end of boilerplate */
+
+
+ hrc = printMachineInfo(a->pMachine);
+ if (FAILED(hrc))
+ return RTEXITCODE_FAILURE;
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static HRESULT
+printMachineInfo(const ComPtr<ICloudMachine> &pMachine)
+{
+ HRESULT hrc;
+
+ com::Bstr bstrId;
+ CHECK_ERROR2_RET(hrc, pMachine,
+ COMGETTER(Id)(bstrId.asOutParam()),
+ hrc);
+ RTPrintf("UUID: %ls\n", bstrId.raw());
+
+
+ /*
+ * Check if the machine is accessible and print the error
+ * message if not.
+ */
+ BOOL fAccessible = FALSE;
+ CHECK_ERROR2_RET(hrc, pMachine,
+ COMGETTER(Accessible)(&fAccessible), hrc);
+
+ if (!fAccessible)
+ {
+ RTMsgError(CloudMachine::tr("machine is not accessible")); // XXX: Id?
+
+ ComPtr<IVirtualBoxErrorInfo> pErrorInfo;
+ CHECK_ERROR2_RET(hrc, pMachine,
+ COMGETTER(AccessError)(pErrorInfo.asOutParam()), hrc);
+
+ while (!pErrorInfo.isNull())
+ {
+ com::Bstr bstrText;
+ CHECK_ERROR2_RET(hrc, pErrorInfo,
+ COMGETTER(Text)(bstrText.asOutParam()), hrc);
+ RTStrmPrintf(g_pStdErr, "%ls\n", bstrText.raw());
+
+ CHECK_ERROR2_RET(hrc, pErrorInfo,
+ COMGETTER(Next)(pErrorInfo.asOutParam()), hrc);
+ }
+
+ return E_FAIL;
+ }
+
+
+ /*
+ * The machine seems to be ok, print its details.
+ */
+ CloudMachineState_T enmState;
+ CHECK_ERROR2_RET(hrc, pMachine,
+ COMGETTER(State)(&enmState),
+ hrc);
+ switch (enmState) {
+ case CloudMachineState_Invalid:
+ RTPrintf(CloudMachine::tr("State: Invalid (%RU32)\n"), CloudMachineState_Invalid);
+ break;
+
+ case CloudMachineState_Provisioning:
+ RTPrintf(CloudMachine::tr("State: Provisioning (%RU32)\n"), CloudMachineState_Provisioning);
+ break;
+
+ case CloudMachineState_Running:
+ RTPrintf(CloudMachine::tr("State: Running (%RU32)\n"), CloudMachineState_Running);
+ break;
+
+ case CloudMachineState_Starting:
+ RTPrintf(CloudMachine::tr("State: Starting (%RU32)\n"), CloudMachineState_Starting);
+ break;
+
+ case CloudMachineState_Stopping:
+ RTPrintf(CloudMachine::tr("State: Stopping (%RU32)\n"), CloudMachineState_Stopping);
+ break;
+
+ case CloudMachineState_Stopped:
+ RTPrintf(CloudMachine::tr("State: Stopped (%RU32)\n"), CloudMachineState_Stopped);
+ break;
+
+ case CloudMachineState_CreatingImage:
+ RTPrintf(CloudMachine::tr("State: CreatingImage (%RU32)\n"), CloudMachineState_CreatingImage);
+ break;
+
+ case CloudMachineState_Terminating:
+ RTPrintf(CloudMachine::tr("State: Terminating (%RU32)\n"), CloudMachineState_Terminating);
+ break;
+
+ case CloudMachineState_Terminated:
+ RTPrintf(CloudMachine::tr("State: Terminated (%RU32)\n"), CloudMachineState_Terminated);
+ break;
+
+ default:
+ RTPrintf(CloudMachine::tr("State: Unknown state (%RU32)\n"), enmState);
+ }
+
+ ComPtr<IForm> pDetails;
+ CHECK_ERROR2_RET(hrc, pMachine,
+ GetDetailsForm(pDetails.asOutParam()), hrc);
+
+ if (RT_UNLIKELY(pDetails.isNull()))
+ {
+ RTMsgError(CloudMachine::tr("null details")); /* better error message? */
+ return E_FAIL;
+ }
+
+ com::SafeIfaceArray<IFormValue> aValues;
+ CHECK_ERROR2_RET(hrc, pDetails,
+ COMGETTER(Values)(ComSafeArrayAsOutParam(aValues)), hrc);
+ for (size_t i = 0; i < aValues.size(); ++i)
+ {
+ hrc = printFormValue(aValues[i]);
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ return S_OK;
+}
+
+
+static HRESULT
+printFormValue(const ComPtr<IFormValue> &pValue)
+{
+ HRESULT hrc;
+
+ BOOL fVisible = FALSE;
+ CHECK_ERROR2_RET(hrc, pValue,
+ COMGETTER(Visible)(&fVisible), hrc);
+ if (!fVisible)
+ return S_OK;
+
+
+ com::Bstr bstrLabel;
+ CHECK_ERROR2_RET(hrc, pValue,
+ COMGETTER(Label)(bstrLabel.asOutParam()), hrc);
+
+ FormValueType_T enmType;
+ CHECK_ERROR2_RET(hrc, pValue,
+ COMGETTER(Type)(&enmType), hrc);
+
+ switch (enmType)
+ {
+ case FormValueType_Boolean:
+ {
+ ComPtr<IBooleanFormValue> pBoolValue;
+ hrc = pValue.queryInterfaceTo(pBoolValue.asOutParam());
+ if (FAILED(hrc))
+ {
+ RTStrmPrintf(g_pStdErr,
+ CloudMachine::tr("%ls: unable to convert to boolean value\n"),
+ bstrLabel.raw());
+ break;
+ }
+
+ BOOL fSelected;
+ hrc = pBoolValue->GetSelected(&fSelected);
+ if (FAILED(hrc))
+ {
+ RTStrmPrintf(g_pStdOut,
+ "%ls: %Rhra", bstrLabel.raw(), hrc);
+ break;
+ }
+
+ RTPrintf("%ls: %RTbool\n",
+ bstrLabel.raw(), RT_BOOL(fSelected));
+ break;
+ }
+
+ case FormValueType_String:
+ {
+ ComPtr<IStringFormValue> pStrValue;
+ hrc = pValue.queryInterfaceTo(pStrValue.asOutParam());
+ if (FAILED(hrc))
+ {
+ RTStrmPrintf(g_pStdErr,
+ CloudMachine::tr("%ls: unable to convert to string value\n"),
+ bstrLabel.raw());
+ break;
+ }
+
+ /*
+ * GUI hack: if clipboard string is set, it contains
+ * untruncated long value, usually full OCID, so check it
+ * first. Make this selectable with an option?
+ */
+ com::Bstr bstrValue;
+ hrc = pStrValue->COMGETTER(ClipboardString)(bstrValue.asOutParam());
+ if (FAILED(hrc))
+ {
+ RTStrmPrintf(g_pStdOut,
+ "%ls: %Rhra", bstrLabel.raw(), hrc);
+ break;
+ }
+
+ if (bstrValue.isEmpty())
+ {
+ hrc = pStrValue->GetString(bstrValue.asOutParam());
+ if (FAILED(hrc))
+ {
+ RTStrmPrintf(g_pStdOut,
+ "%ls: %Rhra", bstrLabel.raw(), hrc);
+ break;
+ }
+ }
+
+ RTPrintf("%ls: %ls\n",
+ bstrLabel.raw(), bstrValue.raw());
+ break;
+ }
+
+ case FormValueType_RangedInteger:
+ {
+ ComPtr<IRangedIntegerFormValue> pIntValue;
+ hrc = pValue.queryInterfaceTo(pIntValue.asOutParam());
+ if (FAILED(hrc))
+ {
+ RTStrmPrintf(g_pStdErr,
+ CloudMachine::tr("%ls: unable to convert to integer value\n"),
+ bstrLabel.raw());
+ break;
+ }
+
+ LONG lValue;
+ hrc = pIntValue->GetInteger(&lValue);
+ if (FAILED(hrc))
+ {
+ RTStrmPrintf(g_pStdOut,
+ "%ls: %Rhra", bstrLabel.raw(), hrc);
+ break;
+ }
+
+ RTPrintf("%ls: %RI64\n",
+ bstrLabel.raw(), (int64_t)lValue);
+ break;
+ }
+
+ case FormValueType_Choice:
+ {
+ ComPtr<IChoiceFormValue> pChoiceValue;
+ hrc = pValue.queryInterfaceTo(pChoiceValue.asOutParam());
+ if (FAILED(hrc))
+ {
+ RTStrmPrintf(g_pStdErr,
+ CloudMachine::tr("%ls: unable to convert to choice value\n"),
+ bstrLabel.raw());
+ break;
+ }
+
+ com::SafeArray<BSTR> aValues;
+ hrc = pChoiceValue->COMGETTER(Values)(ComSafeArrayAsOutParam(aValues));
+ if (FAILED(hrc))
+ {
+ RTStrmPrintf(g_pStdOut,
+ CloudMachine::tr("%ls: values: %Rhra"),
+ bstrLabel.raw(), hrc);
+ break;
+ }
+
+ LONG idxSelected = -1;
+ hrc = pChoiceValue->GetSelectedIndex(&idxSelected);
+ if (FAILED(hrc))
+ {
+ RTStrmPrintf(g_pStdOut,
+ CloudMachine::tr("%ls: selectedIndex: %Rhra"),
+ bstrLabel.raw(), hrc);
+ break;
+ }
+
+ if (idxSelected < 0 || (size_t)idxSelected > aValues.size())
+ {
+ RTStrmPrintf(g_pStdOut,
+ CloudMachine::tr("%ls: selected index %RI64 out of range [0, %zu)\n"),
+ bstrLabel.raw(), (int64_t)idxSelected, aValues.size());
+ break;
+ }
+
+ RTPrintf("%ls: %ls\n",
+ bstrLabel.raw(), aValues[idxSelected]);
+ break;
+ }
+
+ default:
+ {
+ RTStrmPrintf(g_pStdOut, CloudMachine::tr("unknown value type %RU32\n"), enmType);
+ break;
+ }
+ }
+
+ return S_OK;
+}
+
+
+/*
+ * Boilerplate code to get machine by name/id from the arguments.
+ * Shared by action subcommands b/c they currently don't have any
+ * extra options (but we can't use this for e.g. "info" that has
+ * --details).
+ */
+static RTEXITCODE
+getMachineFromArgs(CMachineHandlerArg *a, int iFirst)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ CLOUD_MACHINE_RTGETOPTDEF_MACHINE,
+ CLOUD_MACHINE_RTGETOPTDEF_HELP
+ };
+
+ RTGETOPTSTATE OptState;
+ int vrc = RTGetOptInit(&OptState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions),
+ iFirst, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
+ AssertRCReturn(vrc, RTMsgErrorExit(RTEXITCODE_INIT, /* internal error */ "RTGetOptInit: %Rra", vrc));
+
+ int ch;
+ RTGETOPTUNION Val;
+ while ((ch = RTGetOpt(&OptState, &Val)) != 0)
+ {
+ vrc = checkMachineSpecArgument(a, ch, Val);
+ if (vrc == VINF_SUCCESS)
+ continue;
+ if (vrc == VINF_CALLBACK_RETURN)
+ return RTEXITCODE_SUCCESS;
+ if (vrc == VERR_PARSE_ERROR)
+ return RTEXITCODE_SYNTAX;
+
+ switch (ch)
+ {
+ /* no other options currently */
+ default:
+ return RTGetOptPrintError(ch, &Val);
+ }
+ }
+
+ HRESULT hrc = getMachineBySpec(a);
+ if (FAILED(hrc))
+ return RTEXITCODE_FAILURE;
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/*
+ * cloud machine start "id"
+ */
+static RTEXITCODE
+handleCloudMachineStart(CMachineHandlerArg *a, int iFirst)
+{
+ HRESULT hrc;
+
+ // setCurrentSubcommand(HELP_SCOPE_CLOUD_MACHINE_START);
+ RTEXITCODE status = getMachineFromArgs(a, iFirst);
+ if (status != RTEXITCODE_SUCCESS)
+ return status;
+
+
+ ComPtr<IProgress> pProgress;
+ CHECK_ERROR2_RET(hrc, a->pMachine,
+ PowerUp(pProgress.asOutParam()),
+ RTEXITCODE_FAILURE);
+
+ hrc = showProgress(pProgress, SHOW_PROGRESS_NONE);
+ return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/*
+ * cloud machine reboot "id"
+ * "Press" ACPI power button, then power the instance back up.
+ */
+static RTEXITCODE
+handleCloudMachineReboot(CMachineHandlerArg *a, int iFirst)
+{
+ HRESULT hrc;
+
+ // setCurrentSubcommand(HELP_SCOPE_CLOUD_MACHINE_REBOOT);
+ RTEXITCODE status = getMachineFromArgs(a, iFirst);
+ if (status != RTEXITCODE_SUCCESS)
+ return status;
+
+
+ ComPtr<IProgress> pProgress;
+ CHECK_ERROR2_RET(hrc, a->pMachine,
+ Reboot(pProgress.asOutParam()),
+ RTEXITCODE_FAILURE);
+
+ hrc = showProgress(pProgress, SHOW_PROGRESS_NONE);
+ return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/*
+ * cloud machine reset "id"
+ * Force power down machine, then power the instance back up.
+ */
+static RTEXITCODE
+handleCloudMachineReset(CMachineHandlerArg *a, int iFirst)
+{
+ HRESULT hrc;
+
+ // setCurrentSubcommand(HELP_SCOPE_CLOUD_MACHINE_RESET);
+ RTEXITCODE status = getMachineFromArgs(a, iFirst);
+ if (status != RTEXITCODE_SUCCESS)
+ return status;
+
+
+ ComPtr<IProgress> pProgress;
+ CHECK_ERROR2_RET(hrc, a->pMachine,
+ Reset(pProgress.asOutParam()),
+ RTEXITCODE_FAILURE);
+
+ hrc = showProgress(pProgress, SHOW_PROGRESS_NONE);
+ return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/*
+ * cloud machine shutdown "id"
+ * "Press" ACPI power button.
+ */
+static RTEXITCODE
+handleCloudMachineShutdown(CMachineHandlerArg *a, int iFirst)
+{
+ HRESULT hrc;
+
+ // setCurrentSubcommand(HELP_SCOPE_CLOUD_MACHINE_SHUTDOWN);
+ RTEXITCODE status = getMachineFromArgs(a, iFirst);
+ if (status != RTEXITCODE_SUCCESS)
+ return status;
+
+
+ ComPtr<IProgress> pProgress;
+ CHECK_ERROR2_RET(hrc, a->pMachine,
+ Shutdown(pProgress.asOutParam()),
+ RTEXITCODE_FAILURE);
+
+ hrc = showProgress(pProgress, SHOW_PROGRESS_NONE);
+ return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/*
+ * cloud machine powerdown "id"
+ * Yank the power cord.
+ */
+static RTEXITCODE
+handleCloudMachinePowerdown(CMachineHandlerArg *a, int iFirst)
+{
+ HRESULT hrc;
+
+ // setCurrentSubcommand(HELP_SCOPE_CLOUD_MACHINE_POWERDOWN);
+ RTEXITCODE status = getMachineFromArgs(a, iFirst);
+ if (status != RTEXITCODE_SUCCESS)
+ return status;
+
+
+ ComPtr<IProgress> pProgress;
+ CHECK_ERROR2_RET(hrc, a->pMachine,
+ PowerDown(pProgress.asOutParam()),
+ RTEXITCODE_FAILURE);
+
+ hrc = showProgress(pProgress, SHOW_PROGRESS_NONE);
+ return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/*
+ * cloud machine terminate "id"
+ * Discard the instance running this machine.
+ */
+static RTEXITCODE
+handleCloudMachineTerminate(CMachineHandlerArg *a, int iFirst)
+{
+ HRESULT hrc;
+
+ // setCurrentSubcommand(HELP_SCOPE_CLOUD_MACHINE_TERMINATE);
+ RTEXITCODE status = getMachineFromArgs(a, iFirst);
+ if (status != RTEXITCODE_SUCCESS)
+ return status;
+
+
+ ComPtr<IProgress> pProgress;
+ CHECK_ERROR2_RET(hrc, a->pMachine,
+ Terminate(pProgress.asOutParam()),
+ RTEXITCODE_FAILURE);
+
+ hrc = showProgress(pProgress, SHOW_PROGRESS_NONE);
+ return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/*
+ * cloud machine console-history "id"
+ */
+static RTEXITCODE
+handleCloudMachineConsoleHistory(CMachineHandlerArg *a, int iFirst)
+{
+ HRESULT hrc;
+
+ // setCurrentSubcommand(HELP_SCOPE_CLOUD_MACHINE_CONSOLEHISTORY);
+ RTEXITCODE status = getMachineFromArgs(a, iFirst);
+ if (status != RTEXITCODE_SUCCESS)
+ return status;
+
+
+ ComPtr<IDataStream> pHistoryStream;
+ ComPtr<IProgress> pHistoryProgress;
+ CHECK_ERROR2_RET(hrc, a->pMachine,
+ GetConsoleHistory(pHistoryStream.asOutParam(),
+ pHistoryProgress.asOutParam()),
+ RTEXITCODE_FAILURE);
+
+ hrc = showProgress(pHistoryProgress, SHOW_PROGRESS_NONE);
+ if (FAILED(hrc))
+ return RTEXITCODE_FAILURE;
+
+ bool fEOF = false;
+ while (!fEOF)
+ {
+ com::SafeArray<BYTE> aChunk;
+ CHECK_ERROR2_RET(hrc, pHistoryStream,
+ Read(64 *_1K, 0, ComSafeArrayAsOutParam(aChunk)),
+ RTEXITCODE_FAILURE);
+ if (aChunk.size() == 0)
+ break;
+
+ RTStrmWrite(g_pStdOut, aChunk.raw(), aChunk.size());
+ }
+
+ return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}