diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Frontends/VBoxManage | |
parent | Initial commit. (diff) | |
download | virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Frontends/VBoxManage')
26 files changed, 31577 insertions, 0 deletions
diff --git a/src/VBox/Frontends/VBoxManage/Makefile.kmk b/src/VBox/Frontends/VBoxManage/Makefile.kmk new file mode 100644 index 00000000..02b93dc4 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/Makefile.kmk @@ -0,0 +1,208 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for VBoxManage (the cli frontend). +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +include $(PATH_ROOT)/doc/manual/Config.kmk + + +VBOX_COMMON_VBOXMANAGE_DEFS = \ + $(if $(VBOX_WITH_AHCI), VBOX_WITH_AHCI) \ + $(if $(VBOX_WITH_COPYTOGUEST),VBOX_WITH_COPYTOGUEST) \ + $(if $(VBOX_WITH_E1000),VBOX_WITH_E1000) \ + $(if $(VBOX_WITH_GUEST_CONTROL),VBOX_WITH_GUEST_CONTROL) \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS) \ + $(if $(VBOX_WITH_HEADLESS), VBOX_WITH_HEADLESS) \ + $(if $(VBOX_WITH_HGCM), VBOX_WITH_HGCM) \ + $(if $(VBOX_WITH_HOSTNETIF_API), VBOX_WITH_HOSTNETIF_API) \ + $(if $(VBOX_WITH_NETFLT), VBOX_WITH_NETFLT) \ + $(if $(VBOX_WITH_AUDIO_OSS), VBOX_WITH_AUDIO_OSS) \ + $(if $(VBOX_WITH_AUDIO_ALSA), VBOX_WITH_AUDIO_ALSA) \ + $(if $(VBOX_WITH_AUDIO_PULSE),VBOX_WITH_AUDIO_PULSE) \ + $(if $(VBOX_WITH_SCSI), VBOX_WITH_SCSI) \ + $(if $(VBOX_WITH_VBOXSDL), VBOX_WITH_VBOXSDL) \ + $(if $(VBOX_WITH_VIDEOHWACCEL), VBOX_WITH_VIDEOHWACCEL) \ + $(if $(VBOX_WITH_VIRTIO),VBOX_WITH_VIRTIO) \ + $(if $(VBOX_WITH_USB_CARDREADER),VBOX_WITH_USB_CARDREADER) \ + $(if $(VBOX_WITH_PCI_PASSTHROUGH),VBOX_WITH_PCI_PASSTHROUGH) \ + $(if $(VBOX_WITH_RECORDING),VBOX_WITH_RECORDING) \ + $(if $(VBOX_WITH_AUDIO_RECORDING),VBOX_WITH_AUDIO_RECORDING) \ + $(if $(VBOX_WITH_NAT_SERVICE),VBOX_WITH_NAT_SERVICE) \ + $(if $(VBOX_WITH_VMSVGA),VBOX_WITH_VMSVGA) + + +ifdef VBOX_WITH_DOCS + PROGRAMS += VBoxManageHelp +endif +VBoxManageHelp_TEMPLATE = VBoxAdvBldProg +VBoxManageHelp_DEFS += \ + VBOX_ONLY_DOCS \ + $(VBOX_COMMON_VBOXMANAGE_DEFS) +VBoxManageHelp_SOURCES = \ + VBoxManage.cpp \ + VBoxManageHelp.cpp \ + $(if $(VBOX_WITH_GUEST_PROPS),VBoxManageGuestProp.cpp) \ + $(if $(VBOX_WITH_GUEST_CONTROL),VBoxManageGuestCtrl.cpp) + +ifndef VBOX_ONLY_DOCS + PROGRAMS += VBoxManage + VBoxManage_TEMPLATE = VBOXMAINCLIENTEXE + VBoxManage_DEFS += $(VBOX_COMMON_VBOXMANAGE_DEFS) + VBoxManage_DEFS.win = _WIN32_WINNT=0x0500 + VBoxManage_INCS = \ + $(VBoxManage_0_OUTDIR) + VBoxManage_INTERMEDIATES = \ + $(VBoxManage_0_OUTDIR)/VBoxManageBuiltInHelp.h + VBoxManage_SOURCES = \ + VBoxManage.cpp \ + VBoxInternalManage.cpp \ + VBoxManageAppliance.cpp \ + VBoxManageBandwidthControl.cpp \ + VBoxManageControlVM.cpp \ + VBoxManageDebugVM.cpp \ + VBoxManageDHCPServer.cpp \ + VBoxManageDisk.cpp \ + $(if $(VBOX_WITH_GUEST_CONTROL),VBoxManageGuestCtrl.cpp) \ + $(if $(VBOX_WITH_GUEST_CONTROL),VBoxManageGuestCtrlListener.cpp) \ + $(if $(VBOX_WITH_GUEST_PROPS),VBoxManageGuestProp.cpp) \ + VBoxManageHelp.cpp \ + $(VBoxManage_0_OUTDIR)/VBoxManageBuiltInHelp.cpp \ + VBoxManageHostonly.cpp \ + VBoxManageInfo.cpp \ + VBoxManageList.cpp \ + VBoxManageMetrics.cpp \ + VBoxManageMisc.cpp \ + VBoxManageModifyVM.cpp \ + VBoxManageSnapshot.cpp \ + VBoxManageStorageController.cpp \ + VBoxManageUSB.cpp \ + $(if $(VBOX_WITH_NAT_SERVICE),VBoxManageNATNetwork.cpp,) \ + $(if $(VBOX_WITH_NAT_SERVICE),../../NetworkServices/NetLib/VBoxNetPortForwardString.cpp,) + VBoxManage_SOURCES.win = \ + VBoxManage.rc + VBoxManage_LIBS += $(LIB_DDU) + + # VBoxNetPortForwardString.h + VBoxManageNATNetwork.cpp_INCS += ../../NetworkServices/NetLib/ + +endif # VBOX_ONLY_DOCS + +ifneq ($(KBUILD_TARGET),win) + # Workaround for buggy gcc-4.3 compilers, see + # + # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36474 + # + # Some later revisions of version 4.3.1 are known to work so we assume + # that version 4.3.2 or later has this bug definitely fixed. + VBoxManage_CXXFLAGS.release += \ + $(if $(VBOX_GCC_VERSION_CXX),$(if-expr $(VBOX_GCC_VERSION_CXX) < 40300 || $(VBOX_GCC_VERSION_CXX) > 40301,,--param max-fields-for-field-sensitive=0),) + VBoxManageHelp_CXXFLAGS.release = $(VBoxManage_CXXFLAGS.release) +endif + + +# +# VBoxManage built-in help from XML refentry in doc/manual/en_US/. +# +$(call KB_FN_DO_PASS0_ON_TARGET,VBoxManage) + +VBoxManage_CLEAN += \ + $(VBoxManage_0_OUTDIR)/VBoxManageBuiltInHelp.cpp \ + $(VBoxManage_0_OUTDIR)/VBoxManageBuiltInHelp.cpp.ts \ + $(VBoxManage_0_OUTDIR)/VBoxManageBuiltInHelp.h \ + $(VBoxManage_0_OUTDIR)/VBoxManageBuiltInHelp.h.ts \ + $(addprefix $(VBoxManage_0_OUTDIR)/,$(filter man_VBoxManage-%,$(VBOX_MANUAL_XML_REFENTRY_FILES))) + + + +# Preprocess the xml files, applying remarks. +$(foreach file,$(filter man_VBoxManage-%,$(VBOX_MANUAL_XML_REFENTRY_FILES)) \ +, $(evalcall2 def_vbox_refentry_preprocess_for_manpage,$(VBoxManage_0_OUTDIR),$(file),$(VBOX_PATH_MANUAL_SRC)/en_US/$(file))) + + +# Generate the .cpp file. +$(VBoxManage_0_OUTDIR)/VBoxManageBuiltInHelp.cpp.ts \ ++| $(VBoxManage_0_OUTDIR)/VBoxManageBuiltInHelp.cpp: \ + $(VBOX_DOCBOOK_REFENTRY_TO_C_HELP) \ + $(addprefix $(VBoxManage_0_OUTDIR)/,$(filter man_VBoxManage-%,$(VBOX_MANUAL_XML_REFENTRY_FILES))) \ + $(VBOX_XML_CATALOG) $(VBOX_XML_CATALOG_DOCBOOK) $(MAKEFILE) | $$(dir $$@) + $(call MSG_TOOL,xsltproc $(notdir $(firstword $(filter %.xsl,$^))),,$(filter %.xml,$^),$(patsubst %.ts,%,$@)) + $(QUIET)$(APPEND) -tn "$@" \ + '/* Autogenerated by $<, do not edit! */' \ + '' \ + '#include "VBoxManageBuiltInHelp.h"' \ + '' + $(foreach refentry,$(filter %.xml,$^) \ + ,$(NLTAB)$(QUIET)$(call VBOX_XSLTPROC_WITH_CAT, -a+to "$@") $< $(refentry)) + $(QUIET)$(APPEND) -n "$@" \ + '' \ + 'PCRTMSGREFENTRY g_apHelpEntries[] = ' \ + '{' + $(foreach refentry,$(filter %.xml,$^) \ + ,$(NLTAB)$(QUIET)$(APPEND) -n "$@" \ + ' &g_$(subst -,_,$(tolower $(patsubst man_%,%,$(notdir $(basename $(refentry)))))), ') + $(QUIET)$(APPEND) -n "$@" \ + '};' \ + 'const uint32_t g_cHelpEntries = RT_ELEMENTS(g_apHelpEntries);' \ + '' + $(QUIET)$(CP) --changed -- "$@" "$(patsubst %.ts,%,$@)" +# The above APPEND stuff trigger some kind of problem on some boxes when not split up... +# update: Fixed in SVN (strcpy -> memmove in new_job(), job.c - r2591). Just need to rebuild all platforms. + + +$(VBoxManage_0_OUTDIR)/VBoxManageBuiltInHelp.h.ts \ ++| $(VBoxManage_0_OUTDIR)/VBoxManageBuiltInHelp.h: \ + $(VBOX_DOCBOOK_REFENTRY_TO_H_HELP) \ + $(addprefix $(VBoxManage_0_OUTDIR)/,$(filter man_VBoxManage-%,$(VBOX_MANUAL_XML_REFENTRY_FILES))) \ + $(VBOX_XML_CATALOG) $(VBOX_XML_CATALOG_DOCBOOK) $(MAKEFILE) | $$(dir $$@) + $(call MSG_TOOL,xsltproc $(notdir $(firstword $(filter %.xsl,$^))),,$(filter %.xml,$^),$(patsubst %.ts,%,$@)) + $(QUIET)$(APPEND) -tn "$@" \ + '/* Autogenerated by $<, do not edit! */' \ + '' \ + '#ifndef ___VBoxManageBuiltInHelp_h___' \ + '#define ___VBoxManageBuiltInHelp_h___' \ + '' \ + '#include <iprt/message.h>' \ + '' \ + 'RT_C_DECLS_BEGIN' \ + '' \ + 'typedef enum HELP_CMD_VBOXMANAGE' \ + '{' \ + ' HELP_CMD_VBOXMANAGE_INVALID = 0,' + $(foreach refentry,$(filter %.xml,$^) \ + ,$(NLTAB)$(QUIET)$(call VBOX_XSLTPROC_WITH_CAT, -a+to "$@") \ + --stringparam 'g_sMode' 'cmd' $< $(refentry)) + $(QUIET)$(APPEND) -n "$@" \ + ' HELP_CMD_VBOXMANAGE_END' \ + '} HELP_CMD_VBOXMANAGE;' + $(foreach refentry,$(filter %.xml,$^) \ + ,$(NLTAB)$(QUIET)$(call VBOX_XSLTPROC_WITH_CAT, -a+to "$@") \ + --stringparam 'g_sMode' 'subcmd' $< $(refentry)) + $(QUIET)$(APPEND) -n "$@" \ + '' \ + 'extern PCRTMSGREFENTRY g_apHelpEntries[];' \ + 'extern const uint32_t g_cHelpEntries;' \ + '' \ + 'RT_C_DECLS_END' \ + '' \ + '#endif' \ + '' + $(QUIET)$(CP) --changed -- "$@" "$(patsubst %.ts,%,$@)" + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Frontends/VBoxManage/VBoxInternalManage.cpp b/src/VBox/Frontends/VBoxManage/VBoxInternalManage.cpp new file mode 100644 index 00000000..9beaf9bc --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxInternalManage.cpp @@ -0,0 +1,2597 @@ +/* $Id: VBoxInternalManage.cpp $ */ +/** @file + * VBoxManage - The 'internalcommands' command. + * + * VBoxInternalManage used to be a second CLI for doing special tricks, + * not intended for general usage, only for assisting VBox developers. + * It is now integrated into VBoxManage. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> + +#include <VBox/com/VirtualBox.h> + +#include <VBox/vd.h> +#include <VBox/sup.h> +#include <VBox/log.h> + +#include <iprt/ctype.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/uuid.h> +#include <iprt/sha.h> + +#include "VBoxManage.h" + +/* Includes for the raw disk stuff. */ +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +# include <winioctl.h> +#elif defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) \ + || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) +# include <errno.h> +# include <sys/ioctl.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <fcntl.h> +# include <unistd.h> +#endif +#ifdef RT_OS_LINUX +# include <sys/utsname.h> +# include <linux/hdreg.h> +# include <linux/fs.h> +# include <stdlib.h> /* atoi() */ +#endif /* RT_OS_LINUX */ +#ifdef RT_OS_DARWIN +# include <sys/disk.h> +#endif /* RT_OS_DARWIN */ +#ifdef RT_OS_SOLARIS +# include <stropts.h> +# include <sys/dkio.h> +# include <sys/vtoc.h> +#endif /* RT_OS_SOLARIS */ +#ifdef RT_OS_FREEBSD +# include <sys/disk.h> +#endif /* RT_OS_FREEBSD */ + +using namespace com; + + +/** Macro for checking whether a partition is of extended type or not. */ +#define PARTTYPE_IS_EXTENDED(x) ((x) == 0x05 || (x) == 0x0f || (x) == 0x85) + +/** Maximum number of partitions we can deal with. + * Ridiculously large number, but the memory consumption is rather low so who + * cares about never using most entries. */ +#define HOSTPARTITION_MAX 100 + + +typedef struct HOSTPARTITION +{ + /** partition number */ + unsigned uIndex; + /** partition number (internal only, windows specific numbering) */ + unsigned uIndexWin; + /** partition type */ + unsigned uType; + /** CHS/cylinder of the first sector */ + unsigned uStartCylinder; + /** CHS/head of the first sector */ + unsigned uStartHead; + /** CHS/head of the first sector */ + unsigned uStartSector; + /** CHS/cylinder of the last sector */ + unsigned uEndCylinder; + /** CHS/head of the last sector */ + unsigned uEndHead; + /** CHS/sector of the last sector */ + unsigned uEndSector; + /** start sector of this partition relative to the beginning of the hard + * disk or relative to the beginning of the extended partition table */ + uint64_t uStart; + /** numer of sectors of the partition */ + uint64_t uSize; + /** start sector of this partition _table_ */ + uint64_t uPartDataStart; + /** numer of sectors of this partition _table_ */ + uint64_t cPartDataSectors; +} HOSTPARTITION, *PHOSTPARTITION; + +typedef struct HOSTPARTITIONS +{ + /** partitioning type - MBR or GPT */ + VDISKPARTTYPE uPartitioningType; + unsigned cPartitions; + HOSTPARTITION aPartitions[HOSTPARTITION_MAX]; +} HOSTPARTITIONS, *PHOSTPARTITIONS; + +/** flag whether we're in internal mode */ +bool g_fInternalMode; + +/** + * Print the usage info. + */ +void printUsageInternal(USAGECATEGORY u64Cmd, PRTSTREAM pStrm) +{ + RTStrmPrintf(pStrm, + "Usage: VBoxManage internalcommands <command> [command arguments]\n" + "\n" + "Commands:\n" + "\n" + "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s" + "WARNING: This is a development tool and shall only be used to analyse\n" + " problems. It is completely unsupported and will change in\n" + " incompatible ways without warning.\n", + + (u64Cmd & USAGE_LOADMAP) + ? " loadmap <vmname|uuid> <symfile> <address> [module] [subtrahend] [segment]\n" + " This will instruct DBGF to load the given map file\n" + " during initialization. (See also loadmap in the debugger.)\n" + "\n" + : "", + (u64Cmd & USAGE_LOADSYMS) + ? " loadsyms <vmname|uuid> <symfile> [delta] [module] [module address]\n" + " This will instruct DBGF to load the given symbol file\n" + " during initialization.\n" + "\n" + : "", + (u64Cmd & USAGE_SETHDUUID) + ? " sethduuid <filepath> [<uuid>]\n" + " Assigns a new UUID to the given image file. This way, multiple copies\n" + " of a container can be registered.\n" + "\n" + : "", + (u64Cmd & USAGE_SETHDPARENTUUID) + ? " sethdparentuuid <filepath> <uuid>\n" + " Assigns a new parent UUID to the given image file.\n" + "\n" + : "", + (u64Cmd & USAGE_DUMPHDINFO) + ? " dumphdinfo <filepath>\n" + " Prints information about the image at the given location.\n" + "\n" + : "", + (u64Cmd & USAGE_LISTPARTITIONS) + ? " listpartitions -rawdisk <diskname>\n" + " Lists all partitions on <diskname>.\n" + "\n" + : "", + (u64Cmd & USAGE_CREATERAWVMDK) + ? " createrawvmdk -filename <filename> -rawdisk <diskname>\n" + " [-partitions <list of partition numbers> [-mbr <filename>] ]\n" + " [-relative]\n" + " Creates a new VMDK image which gives access to an entire host disk (if\n" + " the parameter -partitions is not specified) or some partitions of a\n" + " host disk. If access to individual partitions is granted, then the\n" + " parameter -mbr can be used to specify an alternative MBR to be used\n" + " (the partitioning information in the MBR file is ignored).\n" + " The diskname is on Linux e.g. /dev/sda, and on Windows e.g.\n" + " \\\\.\\PhysicalDrive0).\n" + " On Linux or FreeBSD host the parameter -relative causes a VMDK file to\n" + " be created which refers to individual partitions instead to the entire\n" + " disk.\n" + " The necessary partition numbers can be queried with\n" + " VBoxManage internalcommands listpartitions\n" + "\n" + : "", + (u64Cmd & USAGE_RENAMEVMDK) + ? " renamevmdk -from <filename> -to <filename>\n" + " Renames an existing VMDK image, including the base file and all its extents.\n" + "\n" + : "", + (u64Cmd & USAGE_CONVERTTORAW) + ? " converttoraw [-format <fileformat>] <filename> <outputfile>" +#ifdef ENABLE_CONVERT_RAW_TO_STDOUT + "|stdout" +#endif /* ENABLE_CONVERT_RAW_TO_STDOUT */ + "\n" + " Convert image to raw, writing to file" +#ifdef ENABLE_CONVERT_RAW_TO_STDOUT + " or stdout" +#endif /* ENABLE_CONVERT_RAW_TO_STDOUT */ + ".\n" + "\n" + : "", + (u64Cmd & USAGE_CONVERTHD) + ? " converthd [-srcformat VDI|VMDK|VHD|RAW]\n" + " [-dstformat VDI|VMDK|VHD|RAW]\n" + " <inputfile> <outputfile>\n" + " converts hard disk images between formats\n" + "\n" + : "", + (u64Cmd & USAGE_REPAIRHD) + ? " repairhd [-dry-run]\n" + " [-format VDI|VMDK|VHD|...]\n" + " <filename>\n" + " Tries to repair corrupted disk images\n" + "\n" + : "", +#ifdef RT_OS_WINDOWS + (u64Cmd & USAGE_MODINSTALL) + ? " modinstall\n" + " Installs the necessary driver for the host OS\n" + "\n" + : "", + (u64Cmd & USAGE_MODUNINSTALL) + ? " moduninstall\n" + " Deinstalls the driver\n" + "\n" + : "", +#else + "", + "", +#endif + (u64Cmd & USAGE_DEBUGLOG) + ? " debuglog <vmname|uuid> [--enable|--disable] [--flags todo]\n" + " [--groups todo] [--destinations todo]\n" + " Controls debug logging.\n" + "\n" + : "", + (u64Cmd & USAGE_PASSWORDHASH) + ? " passwordhash <passsword>\n" + " Generates a password hash.\n" + "\n" + : "", + (u64Cmd & USAGE_GUESTSTATS) + ? " gueststats <vmname|uuid> [--interval <seconds>]\n" + " Obtains and prints internal guest statistics.\n" + " Sets the update interval if specified.\n" + "\n" + : "" + ); +} + +/** @todo this is no longer necessary, we can enumerate extra data */ +/** + * Finds a new unique key name. + * + * I don't think this is 100% race condition proof, but we assumes + * the user is not trying to push this point. + * + * @returns Result from the insert. + * @param pMachine The Machine object. + * @param pszKeyBase The base key. + * @param rKey Reference to the string object in which we will return the key. + */ +static HRESULT NewUniqueKey(ComPtr<IMachine> pMachine, const char *pszKeyBase, Utf8Str &rKey) +{ + Bstr KeyBase(pszKeyBase); + Bstr Keys; + HRESULT hrc = pMachine->GetExtraData(KeyBase.raw(), Keys.asOutParam()); + if (FAILED(hrc)) + return hrc; + + /* if there are no keys, it's simple. */ + if (Keys.isEmpty()) + { + rKey = "1"; + return pMachine->SetExtraData(KeyBase.raw(), Bstr(rKey).raw()); + } + + /* find a unique number - brute force rulez. */ + Utf8Str KeysUtf8(Keys); + const char *pszKeys = RTStrStripL(KeysUtf8.c_str()); + for (unsigned i = 1; i < 1000000; i++) + { + char szKey[32]; + size_t cchKey = RTStrPrintf(szKey, sizeof(szKey), "%#x", i); + const char *psz = strstr(pszKeys, szKey); + while (psz) + { + if ( ( psz == pszKeys + || psz[-1] == ' ') + && ( psz[cchKey] == ' ' + || !psz[cchKey]) + ) + break; + psz = strstr(psz + cchKey, szKey); + } + if (!psz) + { + rKey = szKey; + Utf8StrFmt NewKeysUtf8("%s %s", pszKeys, szKey); + return pMachine->SetExtraData(KeyBase.raw(), + Bstr(NewKeysUtf8).raw()); + } + } + RTMsgError("Cannot find unique key for '%s'!", pszKeyBase); + return E_FAIL; +} + + +#if 0 +/** + * Remove a key. + * + * I don't think this isn't 100% race condition proof, but we assumes + * the user is not trying to push this point. + * + * @returns Result from the insert. + * @param pMachine The machine object. + * @param pszKeyBase The base key. + * @param pszKey The key to remove. + */ +static HRESULT RemoveKey(ComPtr<IMachine> pMachine, const char *pszKeyBase, const char *pszKey) +{ + Bstr Keys; + HRESULT hrc = pMachine->GetExtraData(Bstr(pszKeyBase), Keys.asOutParam()); + if (FAILED(hrc)) + return hrc; + + /* if there are no keys, it's simple. */ + if (Keys.isEmpty()) + return S_OK; + + char *pszKeys; + int rc = RTUtf16ToUtf8(Keys.raw(), &pszKeys); + if (RT_SUCCESS(rc)) + { + /* locate it */ + size_t cchKey = strlen(pszKey); + char *psz = strstr(pszKeys, pszKey); + while (psz) + { + if ( ( psz == pszKeys + || psz[-1] == ' ') + && ( psz[cchKey] == ' ' + || !psz[cchKey]) + ) + break; + psz = strstr(psz + cchKey, pszKey); + } + if (psz) + { + /* remove it */ + char *pszNext = RTStrStripL(psz + cchKey); + if (*pszNext) + memmove(psz, pszNext, strlen(pszNext) + 1); + else + *psz = '\0'; + psz = RTStrStrip(pszKeys); + + /* update */ + hrc = pMachine->SetExtraData(Bstr(pszKeyBase), Bstr(psz)); + } + + RTStrFree(pszKeys); + return hrc; + } + else + RTMsgError("Failed to delete key '%s' from '%s', string conversion error %Rrc!", + pszKey, pszKeyBase, rc); + + return E_FAIL; +} +#endif + + +/** + * Sets a key value, does necessary error bitching. + * + * @returns COM status code. + * @param pMachine The Machine object. + * @param pszKeyBase The key base. + * @param pszKey The key. + * @param pszAttribute The attribute name. + * @param pszValue The string value. + */ +static HRESULT SetString(ComPtr<IMachine> pMachine, const char *pszKeyBase, const char *pszKey, const char *pszAttribute, const char *pszValue) +{ + HRESULT hrc = pMachine->SetExtraData(BstrFmt("%s/%s/%s", pszKeyBase, + pszKey, pszAttribute).raw(), + Bstr(pszValue).raw()); + if (FAILED(hrc)) + RTMsgError("Failed to set '%s/%s/%s' to '%s'! hrc=%#x", + pszKeyBase, pszKey, pszAttribute, pszValue, hrc); + return hrc; +} + + +/** + * Sets a key value, does necessary error bitching. + * + * @returns COM status code. + * @param pMachine The Machine object. + * @param pszKeyBase The key base. + * @param pszKey The key. + * @param pszAttribute The attribute name. + * @param u64Value The value. + */ +static HRESULT SetUInt64(ComPtr<IMachine> pMachine, const char *pszKeyBase, const char *pszKey, const char *pszAttribute, uint64_t u64Value) +{ + char szValue[64]; + RTStrPrintf(szValue, sizeof(szValue), "%#RX64", u64Value); + return SetString(pMachine, pszKeyBase, pszKey, pszAttribute, szValue); +} + + +/** + * Sets a key value, does necessary error bitching. + * + * @returns COM status code. + * @param pMachine The Machine object. + * @param pszKeyBase The key base. + * @param pszKey The key. + * @param pszAttribute The attribute name. + * @param i64Value The value. + */ +static HRESULT SetInt64(ComPtr<IMachine> pMachine, const char *pszKeyBase, const char *pszKey, const char *pszAttribute, int64_t i64Value) +{ + char szValue[64]; + RTStrPrintf(szValue, sizeof(szValue), "%RI64", i64Value); + return SetString(pMachine, pszKeyBase, pszKey, pszAttribute, szValue); +} + + +/** + * Identical to the 'loadsyms' command. + */ +static RTEXITCODE CmdLoadSyms(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aSession); + HRESULT rc; + + /* + * Get the VM + */ + ComPtr<IMachine> machine; + CHECK_ERROR_RET(aVirtualBox, FindMachine(Bstr(argv[0]).raw(), + machine.asOutParam()), RTEXITCODE_FAILURE); + + /* + * Parse the command. + */ + const char *pszFilename; + int64_t offDelta = 0; + const char *pszModule = NULL; + uint64_t ModuleAddress = UINT64_MAX; + uint64_t ModuleSize = 0; + + /* filename */ + if (argc < 2) + return errorArgument("Missing the filename argument!\n"); + pszFilename = argv[1]; + + /* offDelta */ + if (argc >= 3) + { + int irc = RTStrToInt64Ex(argv[2], NULL, 0, &offDelta); + if (RT_FAILURE(irc)) + return errorArgument(argv[0], "Failed to read delta '%s', rc=%Rrc\n", argv[2], rc); + } + + /* pszModule */ + if (argc >= 4) + pszModule = argv[3]; + + /* ModuleAddress */ + if (argc >= 5) + { + int irc = RTStrToUInt64Ex(argv[4], NULL, 0, &ModuleAddress); + if (RT_FAILURE(irc)) + return errorArgument(argv[0], "Failed to read module address '%s', rc=%Rrc\n", argv[4], rc); + } + + /* ModuleSize */ + if (argc >= 6) + { + int irc = RTStrToUInt64Ex(argv[5], NULL, 0, &ModuleSize); + if (RT_FAILURE(irc)) + return errorArgument(argv[0], "Failed to read module size '%s', rc=%Rrc\n", argv[5], rc); + } + + /* + * Add extra data. + */ + Utf8Str KeyStr; + HRESULT hrc = NewUniqueKey(machine, "VBoxInternal/DBGF/loadsyms", KeyStr); + if (SUCCEEDED(hrc)) + hrc = SetString(machine, "VBoxInternal/DBGF/loadsyms", KeyStr.c_str(), "Filename", pszFilename); + if (SUCCEEDED(hrc) && argc >= 3) + hrc = SetInt64(machine, "VBoxInternal/DBGF/loadsyms", KeyStr.c_str(), "Delta", offDelta); + if (SUCCEEDED(hrc) && argc >= 4) + hrc = SetString(machine, "VBoxInternal/DBGF/loadsyms", KeyStr.c_str(), "Module", pszModule); + if (SUCCEEDED(hrc) && argc >= 5) + hrc = SetUInt64(machine, "VBoxInternal/DBGF/loadsyms", KeyStr.c_str(), "ModuleAddress", ModuleAddress); + if (SUCCEEDED(hrc) && argc >= 6) + hrc = SetUInt64(machine, "VBoxInternal/DBGF/loadsyms", KeyStr.c_str(), "ModuleSize", ModuleSize); + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Identical to the 'loadmap' command. + */ +static RTEXITCODE CmdLoadMap(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aSession); + HRESULT rc; + + /* + * Get the VM + */ + ComPtr<IMachine> machine; + CHECK_ERROR_RET(aVirtualBox, FindMachine(Bstr(argv[0]).raw(), + machine.asOutParam()), RTEXITCODE_FAILURE); + + /* + * Parse the command. + */ + const char *pszFilename; + uint64_t ModuleAddress = UINT64_MAX; + const char *pszModule = NULL; + uint64_t offSubtrahend = 0; + uint32_t iSeg = UINT32_MAX; + + /* filename */ + if (argc < 2) + return errorArgument("Missing the filename argument!\n"); + pszFilename = argv[1]; + + /* address */ + if (argc < 3) + return errorArgument("Missing the module address argument!\n"); + int irc = RTStrToUInt64Ex(argv[2], NULL, 0, &ModuleAddress); + if (RT_FAILURE(irc)) + return errorArgument(argv[0], "Failed to read module address '%s', rc=%Rrc\n", argv[2], rc); + + /* name (optional) */ + if (argc > 3) + pszModule = argv[3]; + + /* subtrahend (optional) */ + if (argc > 4) + { + irc = RTStrToUInt64Ex(argv[4], NULL, 0, &offSubtrahend); + if (RT_FAILURE(irc)) + return errorArgument(argv[0], "Failed to read subtrahend '%s', rc=%Rrc\n", argv[4], rc); + } + + /* segment (optional) */ + if (argc > 5) + { + irc = RTStrToUInt32Ex(argv[5], NULL, 0, &iSeg); + if (RT_FAILURE(irc)) + return errorArgument(argv[0], "Failed to read segment number '%s', rc=%Rrc\n", argv[5], rc); + } + + /* + * Add extra data. + */ + Utf8Str KeyStr; + HRESULT hrc = NewUniqueKey(machine, "VBoxInternal/DBGF/loadmap", KeyStr); + if (SUCCEEDED(hrc)) + hrc = SetString(machine, "VBoxInternal/DBGF/loadmap", KeyStr.c_str(), "Filename", pszFilename); + if (SUCCEEDED(hrc)) + hrc = SetUInt64(machine, "VBoxInternal/DBGF/loadmap", KeyStr.c_str(), "Address", ModuleAddress); + if (SUCCEEDED(hrc) && pszModule != NULL) + hrc = SetString(machine, "VBoxInternal/DBGF/loadmap", KeyStr.c_str(), "Name", pszModule); + if (SUCCEEDED(hrc) && offSubtrahend != 0) + hrc = SetUInt64(machine, "VBoxInternal/DBGF/loadmap", KeyStr.c_str(), "Subtrahend", offSubtrahend); + if (SUCCEEDED(hrc) && iSeg != UINT32_MAX) + hrc = SetUInt64(machine, "VBoxInternal/DBGF/loadmap", KeyStr.c_str(), "Segment", iSeg); + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +static DECLCALLBACK(void) handleVDError(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va) +{ + RT_NOREF(pvUser); + RTMsgErrorV(pszFormat, va); + RTMsgError("Error code %Rrc at %s(%u) in function %s", rc, RT_SRC_POS_ARGS); +} + +static DECLCALLBACK(int) handleVDMessage(void *pvUser, const char *pszFormat, va_list va) +{ + NOREF(pvUser); + return RTPrintfV(pszFormat, va); +} + +static RTEXITCODE CmdSetHDUUID(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aVirtualBox, aSession); + Guid uuid; + RTUUID rtuuid; + enum eUuidType { + HDUUID, + HDPARENTUUID + } uuidType; + + if (!strcmp(argv[0], "sethduuid")) + { + uuidType = HDUUID; + if (argc != 3 && argc != 2) + return errorSyntax(USAGE_SETHDUUID, "Not enough parameters"); + /* if specified, take UUID, otherwise generate a new one */ + if (argc == 3) + { + if (RT_FAILURE(RTUuidFromStr(&rtuuid, argv[2]))) + return errorSyntax(USAGE_SETHDUUID, "Invalid UUID parameter"); + uuid = argv[2]; + } else + uuid.create(); + } + else if (!strcmp(argv[0], "sethdparentuuid")) + { + uuidType = HDPARENTUUID; + if (argc != 3) + return errorSyntax(USAGE_SETHDPARENTUUID, "Not enough parameters"); + if (RT_FAILURE(RTUuidFromStr(&rtuuid, argv[2]))) + return errorSyntax(USAGE_SETHDPARENTUUID, "Invalid UUID parameter"); + uuid = argv[2]; + } + else + return errorSyntax(USAGE_SETHDUUID, "Invalid invocation"); + + /* just try it */ + char *pszFormat = NULL; + VDTYPE enmType = VDTYPE_INVALID; + int rc = VDGetFormat(NULL /* pVDIfsDisk */, NULL /* pVDIfsImage */, + argv[1], &pszFormat, &enmType); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Format autodetect failed: %Rrc", rc); + + PVDISK pDisk = NULL; + + PVDINTERFACE pVDIfs = NULL; + VDINTERFACEERROR vdInterfaceError; + vdInterfaceError.pfnError = handleVDError; + vdInterfaceError.pfnMessage = handleVDMessage; + + rc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR, + NULL, sizeof(VDINTERFACEERROR), &pVDIfs); + AssertRC(rc); + + rc = VDCreate(pVDIfs, enmType, &pDisk); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot create the virtual disk container: %Rrc", rc); + + /* Open the image */ + rc = VDOpen(pDisk, pszFormat, argv[1], VD_OPEN_FLAGS_NORMAL | VD_OPEN_FLAGS_INFO, NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot open the image: %Rrc", rc); + + if (uuidType == HDUUID) + rc = VDSetUuid(pDisk, VD_LAST_IMAGE, uuid.raw()); + else + rc = VDSetParentUuid(pDisk, VD_LAST_IMAGE, uuid.raw()); + if (RT_FAILURE(rc)) + RTMsgError("Cannot set a new UUID: %Rrc", rc); + else + RTPrintf("UUID changed to: %s\n", uuid.toString().c_str()); + + VDCloseAll(pDisk); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +static RTEXITCODE CmdDumpHDInfo(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aVirtualBox, aSession); + + /* we need exactly one parameter: the image file */ + if (argc != 1) + { + return errorSyntax(USAGE_DUMPHDINFO, "Not enough parameters"); + } + + /* just try it */ + char *pszFormat = NULL; + VDTYPE enmType = VDTYPE_INVALID; + int rc = VDGetFormat(NULL /* pVDIfsDisk */, NULL /* pVDIfsImage */, + argv[0], &pszFormat, &enmType); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Format autodetect failed: %Rrc", rc); + + PVDISK pDisk = NULL; + + PVDINTERFACE pVDIfs = NULL; + VDINTERFACEERROR vdInterfaceError; + vdInterfaceError.pfnError = handleVDError; + vdInterfaceError.pfnMessage = handleVDMessage; + + rc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR, + NULL, sizeof(VDINTERFACEERROR), &pVDIfs); + AssertRC(rc); + + rc = VDCreate(pVDIfs, enmType, &pDisk); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot create the virtual disk container: %Rrc", rc); + + /* Open the image */ + rc = VDOpen(pDisk, pszFormat, argv[0], VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO, NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot open the image: %Rrc", rc); + + VDDumpImages(pDisk); + + VDCloseAll(pDisk); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static int partRead(RTFILE File, PHOSTPARTITIONS pPart) +{ + uint8_t aBuffer[512]; + uint8_t partitionTableHeader[512]; + uint32_t sector_size = 512; + uint64_t lastUsableLBA = 0; + int rc; + + VDISKPARTTYPE partitioningType; + + pPart->cPartitions = 0; + memset(pPart->aPartitions, '\0', sizeof(pPart->aPartitions)); + + rc = RTFileReadAt(File, 0, &aBuffer, sizeof(aBuffer), NULL); + if (RT_FAILURE(rc)) + return rc; + + if (aBuffer[450] == 0xEE)/* check the sign of the GPT disk*/ + { + partitioningType = GPT; + pPart->uPartitioningType = GPT;//partitioningType; + + if (aBuffer[510] != 0x55 || aBuffer[511] != 0xaa) + return VERR_INVALID_PARAMETER; + + rc = RTFileReadAt(File, sector_size, &partitionTableHeader, sector_size, NULL); + if (RT_SUCCESS(rc)) + { + /** @todo r=bird: This is a 64-bit magic value, right... */ + const char *l_ppth = (char *)partitionTableHeader; + if (strncmp(l_ppth, "EFI PART", 8)) + return VERR_INVALID_PARAMETER; + + /** @todo check GPT Version */ + + /** @todo r=bird: C have this handy concept called structures which + * greatly simplify data access... (Someone is really lazy here!) */ +#if 0 /* unused */ + uint64_t firstUsableLBA = RT_MAKE_U64_FROM_U8(partitionTableHeader[40], + partitionTableHeader[41], + partitionTableHeader[42], + partitionTableHeader[43], + partitionTableHeader[44], + partitionTableHeader[45], + partitionTableHeader[46], + partitionTableHeader[47] + ); +#endif + lastUsableLBA = RT_MAKE_U64_FROM_U8(partitionTableHeader[48], + partitionTableHeader[49], + partitionTableHeader[50], + partitionTableHeader[51], + partitionTableHeader[52], + partitionTableHeader[53], + partitionTableHeader[54], + partitionTableHeader[55] + ); + uint32_t partitionsNumber = RT_MAKE_U32_FROM_U8(partitionTableHeader[80], + partitionTableHeader[81], + partitionTableHeader[82], + partitionTableHeader[83] + ); + uint32_t partitionEntrySize = RT_MAKE_U32_FROM_U8(partitionTableHeader[84], + partitionTableHeader[85], + partitionTableHeader[86], + partitionTableHeader[87] + ); + + uint32_t currentEntry = 0; + + if (partitionEntrySize * partitionsNumber > 4 * _1M) + { + RTMsgError("The GPT header seems corrupt because it contains too many entries"); + return VERR_INVALID_PARAMETER; + } + + uint8_t *pbPartTable = (uint8_t *)RTMemAllocZ(RT_ALIGN_Z(partitionEntrySize * partitionsNumber, 512)); + if (!pbPartTable) + { + RTMsgError("Allocating memory for the GPT partitions entries failed"); + return VERR_NO_MEMORY; + } + + /* partition entries begin from LBA2 */ + /** @todo r=aeichner: Reading from LBA 2 is not always correct, the header will contain the starting LBA. */ + rc = RTFileReadAt(File, 1024, pbPartTable, RT_ALIGN_Z(partitionEntrySize * partitionsNumber, 512), NULL); + if (RT_FAILURE(rc)) + { + RTMsgError("Reading the partition table failed"); + RTMemFree(pbPartTable); + return rc; + } + + while (currentEntry < partitionsNumber) + { + uint8_t *partitionEntry = pbPartTable + currentEntry * partitionEntrySize; + + uint64_t start = RT_MAKE_U64_FROM_U8(partitionEntry[32], partitionEntry[33], partitionEntry[34], partitionEntry[35], + partitionEntry[36], partitionEntry[37], partitionEntry[38], partitionEntry[39]); + uint64_t end = RT_MAKE_U64_FROM_U8(partitionEntry[40], partitionEntry[41], partitionEntry[42], partitionEntry[43], + partitionEntry[44], partitionEntry[45], partitionEntry[46], partitionEntry[47]); + + PHOSTPARTITION pCP = &pPart->aPartitions[pPart->cPartitions++]; + pCP->uIndex = currentEntry + 1; + pCP->uIndexWin = currentEntry + 1; + pCP->uType = 0; + pCP->uStartCylinder = 0; + pCP->uStartHead = 0; + pCP->uStartSector = 0; + pCP->uEndCylinder = 0; + pCP->uEndHead = 0; + pCP->uEndSector = 0; + pCP->uPartDataStart = 0; /* will be filled out later properly. */ + pCP->cPartDataSectors = 0; + if (start==0 || end==0) + { + pCP->uIndex = 0; + pCP->uIndexWin = 0; + --pPart->cPartitions; + break; + } + else + { + pCP->uStart = start; + pCP->uSize = (end +1) - start;/*+1 LBA because the last address is included*/ + } + + ++currentEntry; + } + + RTMemFree(pbPartTable); + } + } + else + { + partitioningType = MBR; + pPart->uPartitioningType = MBR;//partitioningType; + + if (aBuffer[510] != 0x55 || aBuffer[511] != 0xaa) + return VERR_INVALID_PARAMETER; + + unsigned uExtended = (unsigned)-1; + unsigned uIndexWin = 1; + + for (unsigned i = 0; i < 4; i++) + { + uint8_t *p = &aBuffer[0x1be + i * 16]; + if (p[4] == 0) + continue; + PHOSTPARTITION pCP = &pPart->aPartitions[pPart->cPartitions++]; + pCP->uIndex = i + 1; + pCP->uType = p[4]; + pCP->uStartCylinder = (uint32_t)p[3] + ((uint32_t)(p[2] & 0xc0) << 2); + pCP->uStartHead = p[1]; + pCP->uStartSector = p[2] & 0x3f; + pCP->uEndCylinder = (uint32_t)p[7] + ((uint32_t)(p[6] & 0xc0) << 2); + pCP->uEndHead = p[5]; + pCP->uEndSector = p[6] & 0x3f; + pCP->uStart = RT_MAKE_U32_FROM_U8(p[8], p[9], p[10], p[11]); + pCP->uSize = RT_MAKE_U32_FROM_U8(p[12], p[13], p[14], p[15]); + pCP->uPartDataStart = 0; /* will be filled out later properly. */ + pCP->cPartDataSectors = 0; + + if (PARTTYPE_IS_EXTENDED(p[4])) + { + if (uExtended == (unsigned)-1) + { + uExtended = (unsigned)(pCP - pPart->aPartitions); + pCP->uIndexWin = 0; + } + else + { + RTMsgError("More than one extended partition"); + return VERR_INVALID_PARAMETER; + } + } + else + { + pCP->uIndexWin = uIndexWin; + uIndexWin++; + } + } + + if (uExtended != (unsigned)-1) + { + unsigned uIndex = 5; + uint64_t uStart = pPart->aPartitions[uExtended].uStart; + uint64_t uOffset = 0; + if (!uStart) + { + RTMsgError("Inconsistency for logical partition start"); + return VERR_INVALID_PARAMETER; + } + + do + { + rc = RTFileReadAt(File, (uStart + uOffset) * 512, &aBuffer, sizeof(aBuffer), NULL); + if (RT_FAILURE(rc)) + return rc; + + if (aBuffer[510] != 0x55 || aBuffer[511] != 0xaa) + { + RTMsgError("Logical partition without magic"); + return VERR_INVALID_PARAMETER; + } + uint8_t *p = &aBuffer[0x1be]; + + if (p[4] == 0) + { + RTMsgError("Logical partition with type 0 encountered"); + return VERR_INVALID_PARAMETER; + } + + PHOSTPARTITION pCP = &pPart->aPartitions[pPart->cPartitions++]; + pCP->uIndex = uIndex; + pCP->uIndexWin = uIndexWin; + pCP->uType = p[4]; + pCP->uStartCylinder = (uint32_t)p[3] + ((uint32_t)(p[2] & 0xc0) << 2); + pCP->uStartHead = p[1]; + pCP->uStartSector = p[2] & 0x3f; + pCP->uEndCylinder = (uint32_t)p[7] + ((uint32_t)(p[6] & 0xc0) << 2); + pCP->uEndHead = p[5]; + pCP->uEndSector = p[6] & 0x3f; + uint32_t uStartOffset = RT_MAKE_U32_FROM_U8(p[8], p[9], p[10], p[11]); + if (!uStartOffset) + { + RTMsgError("Invalid partition start offset"); + return VERR_INVALID_PARAMETER; + } + pCP->uStart = uStart + uOffset + uStartOffset; + pCP->uSize = RT_MAKE_U32_FROM_U8(p[12], p[13], p[14], p[15]); + /* Fill out partitioning location info for EBR. */ + pCP->uPartDataStart = uStart + uOffset; + pCP->cPartDataSectors = uStartOffset; + p += 16; + if (p[4] == 0) + uExtended = (unsigned)-1; + else if (PARTTYPE_IS_EXTENDED(p[4])) + { + uExtended = uIndex; + uIndex++; + uIndexWin++; + uOffset = RT_MAKE_U32_FROM_U8(p[8], p[9], p[10], p[11]); + } + else + { + RTMsgError("Logical partition chain broken"); + return VERR_INVALID_PARAMETER; + } + } while (uExtended != (unsigned)-1); + } + } + + + /* Sort partitions in ascending order of start sector, plus a trivial + * bit of consistency checking. */ + for (unsigned i = 0; i < pPart->cPartitions-1; i++) + { + unsigned uMinIdx = i; + uint64_t uMinVal = pPart->aPartitions[i].uStart; + for (unsigned j = i + 1; j < pPart->cPartitions; j++) + { + if (pPart->aPartitions[j].uStart < uMinVal) + { + uMinIdx = j; + uMinVal = pPart->aPartitions[j].uStart; + } + else if (pPart->aPartitions[j].uStart == uMinVal) + { + RTMsgError("Two partitions start at the same place"); + return VERR_INVALID_PARAMETER; + } + else if (pPart->aPartitions[j].uStart == 0) + { + RTMsgError("Partition starts at sector 0"); + return VERR_INVALID_PARAMETER; + } + } + if (uMinIdx != i) + { + /* Swap entries at index i and uMinIdx. */ + memcpy(&pPart->aPartitions[pPart->cPartitions], + &pPart->aPartitions[i], sizeof(HOSTPARTITION)); + memcpy(&pPart->aPartitions[i], + &pPart->aPartitions[uMinIdx], sizeof(HOSTPARTITION)); + memcpy(&pPart->aPartitions[uMinIdx], + &pPart->aPartitions[pPart->cPartitions], sizeof(HOSTPARTITION)); + } + } + + /* Fill out partitioning location info for MBR or GPT. */ + pPart->aPartitions[0].uPartDataStart = 0; + pPart->aPartitions[0].cPartDataSectors = pPart->aPartitions[0].uStart; + + /* Fill out partitioning location info for backup GPT. */ + if (partitioningType == GPT) + { + pPart->aPartitions[pPart->cPartitions-1].uPartDataStart = lastUsableLBA+1; + pPart->aPartitions[pPart->cPartitions-1].cPartDataSectors = 33; + + /* Now do a some partition table consistency checking, to reject the most + * obvious garbage which can lead to trouble later. */ + uint64_t uPrevEnd = 0; + for (unsigned i = 0; i < pPart->cPartitions; i++) + { + if (pPart->aPartitions[i].cPartDataSectors) + uPrevEnd = pPart->aPartitions[i].uPartDataStart + pPart->aPartitions[i].cPartDataSectors; + if (pPart->aPartitions[i].uStart < uPrevEnd && + pPart->cPartitions-1 != i) + { + RTMsgError("Overlapping GPT partitions"); + return VERR_INVALID_PARAMETER; + } + } + } + else + { + /* Now do a some partition table consistency checking, to reject the most + * obvious garbage which can lead to trouble later. */ + uint64_t uPrevEnd = 0; + for (unsigned i = 0; i < pPart->cPartitions; i++) + { + if (pPart->aPartitions[i].cPartDataSectors) + uPrevEnd = pPart->aPartitions[i].uPartDataStart + pPart->aPartitions[i].cPartDataSectors; + if (pPart->aPartitions[i].uStart < uPrevEnd) + { + RTMsgError("Overlapping MBR partitions"); + return VERR_INVALID_PARAMETER; + } + if (!PARTTYPE_IS_EXTENDED(pPart->aPartitions[i].uType)) + uPrevEnd = pPart->aPartitions[i].uStart + pPart->aPartitions[i].uSize; + } + } + + return VINF_SUCCESS; +} + +static RTEXITCODE CmdListPartitions(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aVirtualBox, aSession); + Utf8Str rawdisk; + + /* let's have a closer look at the arguments */ + for (int i = 0; i < argc; i++) + { + if (strcmp(argv[i], "-rawdisk") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + rawdisk = argv[i]; + } + else + { + return errorSyntax(USAGE_LISTPARTITIONS, "Invalid parameter '%s'", argv[i]); + } + } + + if (rawdisk.isEmpty()) + return errorSyntax(USAGE_LISTPARTITIONS, "Mandatory parameter -rawdisk missing"); + + RTFILE hRawFile; + int vrc = RTFileOpen(&hRawFile, rawdisk.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot open the raw disk: %Rrc", vrc); + + HOSTPARTITIONS partitions; + vrc = partRead(hRawFile, &partitions); + /* Don't bail out on errors, print the table and return the result code. */ + + RTPrintf("Number Type StartCHS EndCHS Size (MiB) Start (Sect)\n"); + for (unsigned i = 0; i < partitions.cPartitions; i++) + { + /* Don't show the extended partition, otherwise users might think they + * can add it to the list of partitions for raw partition access. */ + if (PARTTYPE_IS_EXTENDED(partitions.aPartitions[i].uType)) + continue; + + RTPrintf("%-7u %#04x %-4u/%-3u/%-2u %-4u/%-3u/%-2u %10llu %10llu\n", + partitions.aPartitions[i].uIndex, + partitions.aPartitions[i].uType, + partitions.aPartitions[i].uStartCylinder, + partitions.aPartitions[i].uStartHead, + partitions.aPartitions[i].uStartSector, + partitions.aPartitions[i].uEndCylinder, + partitions.aPartitions[i].uEndHead, + partitions.aPartitions[i].uEndSector, + partitions.aPartitions[i].uSize / 2048, + partitions.aPartitions[i].uStart); + } + + return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static PVDISKRAWPARTDESC appendPartDesc(uint32_t *pcPartDescs, PVDISKRAWPARTDESC *ppPartDescs) +{ + (*pcPartDescs)++; + PVDISKRAWPARTDESC p; + p = (PVDISKRAWPARTDESC)RTMemRealloc(*ppPartDescs, + *pcPartDescs * sizeof(VDISKRAWPARTDESC)); + *ppPartDescs = p; + if (p) + { + p = p + *pcPartDescs - 1; + memset(p, '\0', sizeof(VDISKRAWPARTDESC)); + } + + return p; +} + +static RTEXITCODE CmdCreateRawVMDK(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aVirtualBox, aSession); + HRESULT rc = S_OK; + Utf8Str filename; + const char *pszMBRFilename = NULL; + Utf8Str rawdisk; + const char *pszPartitions = NULL; + bool fRelative = false; + + uint64_t cbSize = 0; + PVDISK pDisk = NULL; + VDISKRAW RawDescriptor; + PVDINTERFACE pVDIfs = NULL; + + /* let's have a closer look at the arguments */ + for (int i = 0; i < argc; i++) + { + if (strcmp(argv[i], "-filename") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + filename = argv[i]; + } + else if (strcmp(argv[i], "-mbr") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + pszMBRFilename = argv[i]; + } + else if (strcmp(argv[i], "-rawdisk") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + rawdisk = argv[i]; + } + else if (strcmp(argv[i], "-partitions") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + pszPartitions = argv[i]; + } +#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_WINDOWS) + else if (strcmp(argv[i], "-relative") == 0) + { + fRelative = true; + } +#endif /* RT_OS_LINUX || RT_OS_FREEBSD */ + else + return errorSyntax(USAGE_CREATERAWVMDK, "Invalid parameter '%s'", argv[i]); + } + + if (filename.isEmpty()) + return errorSyntax(USAGE_CREATERAWVMDK, "Mandatory parameter -filename missing"); + if (rawdisk.isEmpty()) + return errorSyntax(USAGE_CREATERAWVMDK, "Mandatory parameter -rawdisk missing"); + if (!pszPartitions && pszMBRFilename) + return errorSyntax(USAGE_CREATERAWVMDK, "The parameter -mbr is only valid when the parameter -partitions is also present"); + +#ifdef RT_OS_DARWIN + fRelative = true; +#endif /* RT_OS_DARWIN */ + RTFILE hRawFile; + int vrc = RTFileOpen(&hRawFile, rawdisk.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot open the raw disk '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } + +#ifdef RT_OS_WINDOWS + /* Windows NT has no IOCTL_DISK_GET_LENGTH_INFORMATION ioctl. This was + * added to Windows XP, so we have to use the available info from DriveGeo. + * Note that we cannot simply use IOCTL_DISK_GET_DRIVE_GEOMETRY as it + * yields a slightly different result than IOCTL_DISK_GET_LENGTH_INFO. + * We call IOCTL_DISK_GET_DRIVE_GEOMETRY first as we need to check the media + * type anyway, and if IOCTL_DISK_GET_LENGTH_INFORMATION is supported + * we will later override cbSize. + */ + DISK_GEOMETRY DriveGeo; + DWORD cbDriveGeo; + if (DeviceIoControl((HANDLE)RTFileToNative(hRawFile), + IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, + &DriveGeo, sizeof(DriveGeo), &cbDriveGeo, NULL)) + { + if ( DriveGeo.MediaType == FixedMedia + || DriveGeo.MediaType == RemovableMedia) + { + cbSize = DriveGeo.Cylinders.QuadPart + * DriveGeo.TracksPerCylinder + * DriveGeo.SectorsPerTrack + * DriveGeo.BytesPerSector; + } + else + { + RTMsgError("File '%s' is no fixed/removable medium device", rawdisk.c_str()); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + + GET_LENGTH_INFORMATION DiskLenInfo; + DWORD junk; + if (DeviceIoControl((HANDLE)RTFileToNative(hRawFile), + IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, + &DiskLenInfo, sizeof(DiskLenInfo), &junk, (LPOVERLAPPED)NULL)) + { + /* IOCTL_DISK_GET_LENGTH_INFO is supported -- override cbSize. */ + cbSize = DiskLenInfo.Length.QuadPart; + } + if ( fRelative + && !rawdisk.startsWith("\\\\.\\PhysicalDrive", Utf8Str::CaseInsensitive)) + { + RTMsgError("The -relative parameter is invalid for raw disk %s", rawdisk.c_str()); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + } + else + { + /* + * Could be raw image, remember error code and try to get the size first + * before failing. + */ + vrc = RTErrConvertFromWin32(GetLastError()); + if (RT_FAILURE(RTFileGetSize(hRawFile, &cbSize))) + { + RTMsgError("Cannot get the geometry of the raw disk '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } + else + { + if (fRelative) + { + RTMsgError("The -relative parameter is invalid for raw images"); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + vrc = VINF_SUCCESS; + } + } +#elif defined(RT_OS_LINUX) + struct stat DevStat; + if (!fstat(RTFileToNative(hRawFile), &DevStat)) + { + if (S_ISBLK(DevStat.st_mode)) + { +#ifdef BLKGETSIZE64 + /* BLKGETSIZE64 is broken up to 2.4.17 and in many 2.5.x. In 2.6.0 + * it works without problems. */ + struct utsname utsname; + if ( uname(&utsname) == 0 + && ( (strncmp(utsname.release, "2.5.", 4) == 0 && atoi(&utsname.release[4]) >= 18) + || (strncmp(utsname.release, "2.", 2) == 0 && atoi(&utsname.release[2]) >= 6))) + { + uint64_t cbBlk; + if (!ioctl(RTFileToNative(hRawFile), BLKGETSIZE64, &cbBlk)) + cbSize = cbBlk; + } +#endif /* BLKGETSIZE64 */ + if (!cbSize) + { + long cBlocks; + if (!ioctl(RTFileToNative(hRawFile), BLKGETSIZE, &cBlocks)) + cbSize = (uint64_t)cBlocks << 9; + else + { + vrc = RTErrConvertFromErrno(errno); + RTMsgError("Cannot get the size of the raw disk '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } + } + } + else if (S_ISREG(DevStat.st_mode)) + { + vrc = RTFileGetSize(hRawFile, &cbSize); + if (RT_FAILURE(vrc)) + { + RTMsgError("Failed to get size of file '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } + else if (fRelative) + { + RTMsgError("The -relative parameter is invalid for raw images"); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + } + else + { + RTMsgError("File '%s' is no block device", rawdisk.c_str()); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + } + else + { + vrc = RTErrConvertFromErrno(errno); + RTMsgError("Failed to get file informtation for raw disk '%s': %Rrc", + rawdisk.c_str(), vrc); + } +#elif defined(RT_OS_DARWIN) + struct stat DevStat; + if (!fstat(RTFileToNative(hRawFile), &DevStat)) + { + if (S_ISBLK(DevStat.st_mode)) + { + uint64_t cBlocks; + uint32_t cbBlock; + if (!ioctl(RTFileToNative(hRawFile), DKIOCGETBLOCKCOUNT, &cBlocks)) + { + if (!ioctl(RTFileToNative(hRawFile), DKIOCGETBLOCKSIZE, &cbBlock)) + cbSize = cBlocks * cbBlock; + else + { + RTMsgError("Cannot get the block size for file '%s': %Rrc", rawdisk.c_str(), vrc); + vrc = RTErrConvertFromErrno(errno); + goto out; + } + } + else + { + vrc = RTErrConvertFromErrno(errno); + RTMsgError("Cannot get the block count for file '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } + } + else if (S_ISREG(DevStat.st_mode)) + { + fRelative = false; /* Must be false for raw image files. */ + vrc = RTFileGetSize(hRawFile, &cbSize); + if (RT_FAILURE(vrc)) + { + RTMsgError("Failed to get size of file '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } + } + else + { + RTMsgError("File '%s' is neither block device nor regular file", rawdisk.c_str()); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + } + else + { + vrc = RTErrConvertFromErrno(errno); + RTMsgError("Failed to get file informtation for raw disk '%s': %Rrc", + rawdisk.c_str(), vrc); + } +#elif defined(RT_OS_SOLARIS) + struct stat DevStat; + if (!fstat(RTFileToNative(hRawFile), &DevStat)) + { + if (S_ISBLK(DevStat.st_mode) || S_ISCHR(DevStat.st_mode)) + { + struct dk_minfo mediainfo; + if (!ioctl(RTFileToNative(hRawFile), DKIOCGMEDIAINFO, &mediainfo)) + cbSize = mediainfo.dki_capacity * mediainfo.dki_lbsize; + else + { + vrc = RTErrConvertFromErrno(errno); + RTMsgError("Cannot get the size of the raw disk '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } + } + else if (S_ISREG(DevStat.st_mode)) + { + vrc = RTFileGetSize(hRawFile, &cbSize); + if (RT_FAILURE(vrc)) + { + RTMsgError("Failed to get size of file '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } + } + else + { + RTMsgError("File '%s' is no block or char device", rawdisk.c_str()); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + } + else + { + vrc = RTErrConvertFromErrno(errno); + RTMsgError("Failed to get file informtation for raw disk '%s': %Rrc", + rawdisk.c_str(), vrc); + } +#elif defined(RT_OS_FREEBSD) + struct stat DevStat; + if (!fstat(RTFileToNative(hRawFile), &DevStat)) + { + if (S_ISCHR(DevStat.st_mode)) + { + off_t cbMedia = 0; + if (!ioctl(RTFileToNative(hRawFile), DIOCGMEDIASIZE, &cbMedia)) + cbSize = cbMedia; + else + { + vrc = RTErrConvertFromErrno(errno); + RTMsgError("Cannot get the block count for file '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } + } + else if (S_ISREG(DevStat.st_mode)) + { + if (fRelative) + { + RTMsgError("The -relative parameter is invalid for raw images"); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + cbSize = DevStat.st_size; + } + else + { + RTMsgError("File '%s' is neither character device nor regular file", rawdisk.c_str()); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + } + else + { + vrc = RTErrConvertFromErrno(errno); + RTMsgError("Failed to get file informtation for raw disk '%s': %Rrc", + rawdisk.c_str(), vrc); + } +#else /* all unrecognized OSes */ + /* Hopefully this works on all other hosts. If it doesn't, it'll just fail + * creating the VMDK, so no real harm done. */ + vrc = RTFileGetSize(hRawFile, &cbSize); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot get the size of the raw disk '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } +#endif + + /* Check whether cbSize is actually sensible. */ + if (!cbSize || cbSize % 512) + { + RTMsgError("Detected size of raw disk '%s' is %RU64, an invalid value", rawdisk.c_str(), cbSize); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + + RawDescriptor.szSignature[0] = 'R'; + RawDescriptor.szSignature[1] = 'A'; + RawDescriptor.szSignature[2] = 'W'; + RawDescriptor.szSignature[3] = '\0'; + if (!pszPartitions) + { + RawDescriptor.uFlags = VDISKRAW_DISK; + RawDescriptor.pszRawDisk = rawdisk.c_str(); + } + else + { + RawDescriptor.uFlags = VDISKRAW_NORMAL; + RawDescriptor.pszRawDisk = NULL; + RawDescriptor.cPartDescs = 0; + RawDescriptor.pPartDescs = NULL; + + uint32_t uPartitions = 0; + uint32_t uPartitionsRO = 0; + + const char *p = pszPartitions; + char *pszNext; + uint32_t u32; + while (*p != '\0') + { + vrc = RTStrToUInt32Ex(p, &pszNext, 0, &u32); + if (RT_FAILURE(vrc)) + { + RTMsgError("Incorrect value in partitions parameter"); + goto out; + } + uPartitions |= RT_BIT(u32); + p = pszNext; + if (*p == 'r') + { + uPartitionsRO |= RT_BIT(u32); + p++; + } + if (*p == ',') + p++; + else if (*p != '\0') + { + RTMsgError("Incorrect separator in partitions parameter"); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + } + + HOSTPARTITIONS partitions; + vrc = partRead(hRawFile, &partitions); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot read the partition information from '%s'", rawdisk.c_str()); + goto out; + } + + RawDescriptor.uPartitioningType = partitions.uPartitioningType; + + for (unsigned i = 0; i < partitions.cPartitions; i++) + { + if ( uPartitions & RT_BIT(partitions.aPartitions[i].uIndex) + && PARTTYPE_IS_EXTENDED(partitions.aPartitions[i].uType)) + { + /* Some ignorant user specified an extended partition. + * Bad idea, as this would trigger an overlapping + * partitions error later during VMDK creation. So warn + * here and ignore what the user requested. */ + RTMsgWarning("It is not possible (and necessary) to explicitly give access to the " + "extended partition %u. If required, enable access to all logical " + "partitions inside this extended partition.", + partitions.aPartitions[i].uIndex); + uPartitions &= ~RT_BIT(partitions.aPartitions[i].uIndex); + } + } + + for (unsigned i = 0; i < partitions.cPartitions; i++) + { + PVDISKRAWPARTDESC pPartDesc = NULL; + + /* first dump the MBR/EPT data area */ + if (partitions.aPartitions[i].cPartDataSectors) + { + pPartDesc = appendPartDesc(&RawDescriptor.cPartDescs, + &RawDescriptor.pPartDescs); + if (!pPartDesc) + { + RTMsgError("Out of memory allocating the partition list for '%s'", rawdisk.c_str()); + vrc = VERR_NO_MEMORY; + goto out; + } + + /** @todo the clipping below isn't 100% accurate, as it should + * actually clip to the track size. However, that's easier said + * than done as figuring out the track size is heuristics. In + * any case the clipping is adjusted later after sorting, to + * prevent overlapping data areas on the resulting image. */ + pPartDesc->cbData = RT_MIN(partitions.aPartitions[i].cPartDataSectors, 63) * 512; + pPartDesc->uStart = partitions.aPartitions[i].uPartDataStart * 512; + Assert(pPartDesc->cbData - (size_t)pPartDesc->cbData == 0); + void *pPartData = RTMemAlloc((size_t)pPartDesc->cbData); + if (!pPartData) + { + RTMsgError("Out of memory allocating the partition descriptor for '%s'", rawdisk.c_str()); + vrc = VERR_NO_MEMORY; + goto out; + } + vrc = RTFileReadAt(hRawFile, partitions.aPartitions[i].uPartDataStart * 512, + pPartData, (size_t)pPartDesc->cbData, NULL); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot read partition data from raw device '%s': %Rrc", rawdisk.c_str(), vrc); + goto out; + } + /* Splice in the replacement MBR code if specified. */ + if ( partitions.aPartitions[i].uPartDataStart == 0 + && pszMBRFilename) + { + RTFILE MBRFile; + vrc = RTFileOpen(&MBRFile, pszMBRFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot open replacement MBR file '%s' specified with -mbr: %Rrc", pszMBRFilename, vrc); + goto out; + } + vrc = RTFileReadAt(MBRFile, 0, pPartData, 0x1be, NULL); + RTFileClose(MBRFile); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot read replacement MBR file '%s': %Rrc", pszMBRFilename, vrc); + goto out; + } + } + pPartDesc->pvPartitionData = pPartData; + } + + if (PARTTYPE_IS_EXTENDED(partitions.aPartitions[i].uType)) + { + /* Suppress exporting the actual extended partition. Only + * logical partitions should be processed. However completely + * ignoring it leads to leaving out the EBR data. */ + continue; + } + + /* set up values for non-relative device names */ + const char *pszRawName = rawdisk.c_str(); + uint64_t uStartOffset = partitions.aPartitions[i].uStart * 512; + + pPartDesc = appendPartDesc(&RawDescriptor.cPartDescs, + &RawDescriptor.pPartDescs); + if (!pPartDesc) + { + RTMsgError("Out of memory allocating the partition list for '%s'", rawdisk.c_str()); + vrc = VERR_NO_MEMORY; + goto out; + } + + if (uPartitions & RT_BIT(partitions.aPartitions[i].uIndex)) + { + if (uPartitionsRO & RT_BIT(partitions.aPartitions[i].uIndex)) + pPartDesc->uFlags |= VDISKRAW_READONLY; + + if (fRelative) + { +#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) + /* Refer to the correct partition and use offset 0. */ + char *psz; +#if defined(RT_OS_LINUX) + /* + * Check whether raw disk ends with a digit. In that case + * insert a p before adding the partition number. + * This is used for nvme devices only currently which look like + * /dev/nvme0n1p1 but might be extended to other devices in the + * future. + */ + size_t cchRawDisk = rawdisk.length(); + if (RT_C_IS_DIGIT(pszRawName[cchRawDisk - 1])) + RTStrAPrintf(&psz, + "%sp%u", + rawdisk.c_str(), + partitions.aPartitions[i].uIndex); + else + RTStrAPrintf(&psz, + "%s%u", + rawdisk.c_str(), + partitions.aPartitions[i].uIndex); +#elif defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) + RTStrAPrintf(&psz, + "%ss%u", + rawdisk.c_str(), + partitions.aPartitions[i].uIndex); +#endif + if (!psz) + { + vrc = VERR_NO_STR_MEMORY; + RTMsgError("Cannot create reference to individual partition %u, rc=%Rrc", + partitions.aPartitions[i].uIndex, vrc); + goto out; + } + pszRawName = psz; + uStartOffset = 0; +#elif defined(RT_OS_WINDOWS) + /* Refer to the correct partition and use offset 0. */ + char *psz; + RTStrAPrintf(&psz, "\\\\.\\Harddisk%sPartition%u", + rawdisk.c_str() + 17, + partitions.aPartitions[i].uIndexWin); + if (!psz) + { + vrc = VERR_NO_STR_MEMORY; + RTMsgError("Cannot create reference to individual partition %u (numbered %u), rc=%Rrc", + partitions.aPartitions[i].uIndex, partitions.aPartitions[i].uIndexWin, vrc); + goto out; + } + pszRawName = psz; + uStartOffset = 0; +#else + /** @todo not implemented for other hosts. Treat just like + * not specified (this code is actually never reached). */ +#endif + } + + pPartDesc->pszRawDevice = pszRawName; + pPartDesc->uStartOffset = uStartOffset; + } + else + { + pPartDesc->pszRawDevice = NULL; + pPartDesc->uStartOffset = 0; + } + + pPartDesc->uStart = partitions.aPartitions[i].uStart * 512; + pPartDesc->cbData = partitions.aPartitions[i].uSize * 512; + } + + /* Sort data areas in ascending order of start. */ + for (unsigned i = 0; i < RawDescriptor.cPartDescs-1; i++) + { + unsigned uMinIdx = i; + uint64_t uMinVal = RawDescriptor.pPartDescs[i].uStart; + for (unsigned j = i + 1; j < RawDescriptor.cPartDescs; j++) + { + if (RawDescriptor.pPartDescs[j].uStart < uMinVal) + { + uMinIdx = j; + uMinVal = RawDescriptor.pPartDescs[j].uStart; + } + } + if (uMinIdx != i) + { + /* Swap entries at index i and uMinIdx. */ + VDISKRAWPARTDESC tmp; + memcpy(&tmp, &RawDescriptor.pPartDescs[i], sizeof(tmp)); + memcpy(&RawDescriptor.pPartDescs[i], &RawDescriptor.pPartDescs[uMinIdx], sizeof(tmp)); + memcpy(&RawDescriptor.pPartDescs[uMinIdx], &tmp, sizeof(tmp)); + } + } + + /* Have a second go at MBR/EPT, GPT area clipping. Now that the data areas + * are sorted this is much easier to get 100% right. */ + //for (unsigned i = 0; i < RawDescriptor.cPartDescs-1; i++) + for (unsigned i = 0; i < RawDescriptor.cPartDescs; i++) + { + if (RawDescriptor.pPartDescs[i].pvPartitionData) + { + RawDescriptor.pPartDescs[i].cbData = RT_MIN(RawDescriptor.pPartDescs[i+1].uStart - RawDescriptor.pPartDescs[i].uStart, RawDescriptor.pPartDescs[i].cbData); + if (!RawDescriptor.pPartDescs[i].cbData) + { + if (RawDescriptor.uPartitioningType == MBR) + { + RTMsgError("MBR/EPT overlaps with data area"); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + else + { + if (RawDescriptor.cPartDescs != i+1) + { + RTMsgError("GPT overlaps with data area"); + vrc = VERR_INVALID_PARAMETER; + goto out; + } + } + } + } + } + } + + RTFileClose(hRawFile); + +#ifdef DEBUG_klaus + if (!(RawDescriptor.uFlags & VDISKRAW_DISK)) + { + RTPrintf("# start length startoffset partdataptr device\n"); + for (unsigned i = 0; i < RawDescriptor.cPartDescs; i++) + { + RTPrintf("%2u %14RU64 %14RU64 %14RU64 %#18p %s\n", i, + RawDescriptor.pPartDescs[i].uStart, + RawDescriptor.pPartDescs[i].cbData, + RawDescriptor.pPartDescs[i].uStartOffset, + RawDescriptor.pPartDescs[i].pvPartitionData, + RawDescriptor.pPartDescs[i].pszRawDevice); + } + } +#endif + + VDINTERFACEERROR vdInterfaceError; + vdInterfaceError.pfnError = handleVDError; + vdInterfaceError.pfnMessage = handleVDMessage; + + rc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR, + NULL, sizeof(VDINTERFACEERROR), &pVDIfs); + AssertRC(vrc); + + vrc = VDCreate(pVDIfs, VDTYPE_HDD, &pDisk); /* Raw VMDK's are harddisk only. */ + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot create the virtual disk container: %Rrc", vrc); + goto out; + } + + Assert(RT_MIN(cbSize / 512 / 16 / 63, 16383) - + (unsigned int)RT_MIN(cbSize / 512 / 16 / 63, 16383) == 0); + VDGEOMETRY PCHS, LCHS; + PCHS.cCylinders = (unsigned int)RT_MIN(cbSize / 512 / 16 / 63, 16383); + PCHS.cHeads = 16; + PCHS.cSectors = 63; + LCHS.cCylinders = 0; + LCHS.cHeads = 0; + LCHS.cSectors = 0; + vrc = VDCreateBase(pDisk, "VMDK", filename.c_str(), cbSize, + VD_IMAGE_FLAGS_FIXED | VD_VMDK_IMAGE_FLAGS_RAWDISK, + (char *)&RawDescriptor, &PCHS, &LCHS, NULL, + VD_OPEN_FLAGS_NORMAL, NULL, NULL); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot create the raw disk VMDK: %Rrc", vrc); + goto out; + } + RTPrintf("RAW host disk access VMDK file %s created successfully.\n", filename.c_str()); + + VDCloseAll(pDisk); + + /* Clean up allocated memory etc. */ + if (pszPartitions) + { + for (unsigned i = 0; i < RawDescriptor.cPartDescs; i++) + { + /* Free memory allocated for relative device name. */ + if (fRelative && RawDescriptor.pPartDescs[i].pszRawDevice) + RTStrFree((char *)(void *)RawDescriptor.pPartDescs[i].pszRawDevice); + if (RawDescriptor.pPartDescs[i].pvPartitionData) + RTMemFree((void *)RawDescriptor.pPartDescs[i].pvPartitionData); + } + if (RawDescriptor.pPartDescs) + RTMemFree(RawDescriptor.pPartDescs); + } + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + +out: + RTMsgError("The raw disk vmdk file was not created"); + return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static RTEXITCODE CmdRenameVMDK(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aVirtualBox, aSession); + Utf8Str src; + Utf8Str dst; + /* Parse the arguments. */ + for (int i = 0; i < argc; i++) + { + if (strcmp(argv[i], "-from") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + src = argv[i]; + } + else if (strcmp(argv[i], "-to") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + dst = argv[i]; + } + else + { + return errorSyntax(USAGE_RENAMEVMDK, "Invalid parameter '%s'", argv[i]); + } + } + + if (src.isEmpty()) + return errorSyntax(USAGE_RENAMEVMDK, "Mandatory parameter -from missing"); + if (dst.isEmpty()) + return errorSyntax(USAGE_RENAMEVMDK, "Mandatory parameter -to missing"); + + PVDISK pDisk = NULL; + + PVDINTERFACE pVDIfs = NULL; + VDINTERFACEERROR vdInterfaceError; + vdInterfaceError.pfnError = handleVDError; + vdInterfaceError.pfnMessage = handleVDMessage; + + int vrc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR, + NULL, sizeof(VDINTERFACEERROR), &pVDIfs); + AssertRC(vrc); + + vrc = VDCreate(pVDIfs, VDTYPE_HDD, &pDisk); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot create the virtual disk container: %Rrc", vrc); + + vrc = VDOpen(pDisk, "VMDK", src.c_str(), VD_OPEN_FLAGS_NORMAL, NULL); + if (RT_SUCCESS(vrc)) + { + vrc = VDCopy(pDisk, 0, pDisk, "VMDK", dst.c_str(), true, 0, + VD_IMAGE_FLAGS_NONE, NULL, VD_OPEN_FLAGS_NORMAL, + NULL, NULL, NULL); + if (RT_FAILURE(vrc)) + RTMsgError("Cannot rename the image: %Rrc", vrc); + } + else + RTMsgError("Cannot create the source image: %Rrc", vrc); + VDCloseAll(pDisk); + return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static RTEXITCODE CmdConvertToRaw(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aVirtualBox, aSession); + Utf8Str srcformat; + Utf8Str src; + Utf8Str dst; + bool fWriteToStdOut = false; + + /* Parse the arguments. */ + for (int i = 0; i < argc; i++) + { + if (strcmp(argv[i], "-format") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + srcformat = argv[i]; + } + else if (src.isEmpty()) + { + src = argv[i]; + } + else if (dst.isEmpty()) + { + dst = argv[i]; +#ifdef ENABLE_CONVERT_RAW_TO_STDOUT + if (!strcmp(argv[i], "stdout")) + fWriteToStdOut = true; +#endif /* ENABLE_CONVERT_RAW_TO_STDOUT */ + } + else + { + return errorSyntax(USAGE_CONVERTTORAW, "Invalid parameter '%s'", argv[i]); + } + } + + if (src.isEmpty()) + return errorSyntax(USAGE_CONVERTTORAW, "Mandatory filename parameter missing"); + if (dst.isEmpty()) + return errorSyntax(USAGE_CONVERTTORAW, "Mandatory outputfile parameter missing"); + + PVDISK pDisk = NULL; + + PVDINTERFACE pVDIfs = NULL; + VDINTERFACEERROR vdInterfaceError; + vdInterfaceError.pfnError = handleVDError; + vdInterfaceError.pfnMessage = handleVDMessage; + + int vrc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR, + NULL, sizeof(VDINTERFACEERROR), &pVDIfs); + AssertRC(vrc); + + /** @todo Support convert to raw for floppy and DVD images too. */ + vrc = VDCreate(pVDIfs, VDTYPE_HDD, &pDisk); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot create the virtual disk container: %Rrc", vrc); + + /* Open raw output file. */ + RTFILE outFile; + vrc = VINF_SUCCESS; + if (fWriteToStdOut) + vrc = RTFileFromNative(&outFile, 1); + else + vrc = RTFileOpen(&outFile, dst.c_str(), RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_ALL); + if (RT_FAILURE(vrc)) + { + VDCloseAll(pDisk); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot create destination file \"%s\": %Rrc", dst.c_str(), vrc); + } + + if (srcformat.isEmpty()) + { + char *pszFormat = NULL; + VDTYPE enmType = VDTYPE_INVALID; + vrc = VDGetFormat(NULL /* pVDIfsDisk */, NULL /* pVDIfsImage */, + src.c_str(), &pszFormat, &enmType); + if (RT_FAILURE(vrc) || enmType != VDTYPE_HDD) + { + VDCloseAll(pDisk); + if (!fWriteToStdOut) + { + RTFileClose(outFile); + RTFileDelete(dst.c_str()); + } + if (RT_FAILURE(vrc)) + RTMsgError("No file format specified and autodetect failed - please specify format: %Rrc", vrc); + else + RTMsgError("Only converting harddisk images is supported"); + return RTEXITCODE_FAILURE; + } + srcformat = pszFormat; + RTStrFree(pszFormat); + } + vrc = VDOpen(pDisk, srcformat.c_str(), src.c_str(), VD_OPEN_FLAGS_READONLY, NULL); + if (RT_FAILURE(vrc)) + { + VDCloseAll(pDisk); + if (!fWriteToStdOut) + { + RTFileClose(outFile); + RTFileDelete(dst.c_str()); + } + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot open the source image: %Rrc", vrc); + } + + uint64_t cbSize = VDGetSize(pDisk, VD_LAST_IMAGE); + uint64_t offFile = 0; +#define RAW_BUFFER_SIZE _128K + size_t cbBuf = RAW_BUFFER_SIZE; + void *pvBuf = RTMemAlloc(cbBuf); + if (pvBuf) + { + RTStrmPrintf(g_pStdErr, "Converting image \"%s\" with size %RU64 bytes (%RU64MB) to raw...\n", src.c_str(), cbSize, (cbSize + _1M - 1) / _1M); + while (offFile < cbSize) + { + size_t cb = (size_t)RT_MIN(cbSize - offFile, cbBuf); + vrc = VDRead(pDisk, offFile, pvBuf, cb); + if (RT_FAILURE(vrc)) + break; + vrc = RTFileWrite(outFile, pvBuf, cb, NULL); + if (RT_FAILURE(vrc)) + break; + offFile += cb; + } + RTMemFree(pvBuf); + if (RT_FAILURE(vrc)) + { + VDCloseAll(pDisk); + if (!fWriteToStdOut) + { + RTFileClose(outFile); + RTFileDelete(dst.c_str()); + } + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot copy image data: %Rrc", vrc); + } + } + else + { + vrc = VERR_NO_MEMORY; + VDCloseAll(pDisk); + if (!fWriteToStdOut) + { + RTFileClose(outFile); + RTFileDelete(dst.c_str()); + } + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory allocating read buffer"); + } + + if (!fWriteToStdOut) + RTFileClose(outFile); + VDCloseAll(pDisk); + return RTEXITCODE_SUCCESS; +} + +static RTEXITCODE CmdConvertHardDisk(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aVirtualBox, aSession); + Utf8Str srcformat; + Utf8Str dstformat; + Utf8Str src; + Utf8Str dst; + int vrc; + PVDISK pSrcDisk = NULL; + PVDISK pDstDisk = NULL; + VDTYPE enmSrcType = VDTYPE_INVALID; + + /* Parse the arguments. */ + for (int i = 0; i < argc; i++) + { + if (strcmp(argv[i], "-srcformat") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + srcformat = argv[i]; + } + else if (strcmp(argv[i], "-dstformat") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + dstformat = argv[i]; + } + else if (src.isEmpty()) + { + src = argv[i]; + } + else if (dst.isEmpty()) + { + dst = argv[i]; + } + else + { + return errorSyntax(USAGE_CONVERTHD, "Invalid parameter '%s'", argv[i]); + } + } + + if (src.isEmpty()) + return errorSyntax(USAGE_CONVERTHD, "Mandatory input image parameter missing"); + if (dst.isEmpty()) + return errorSyntax(USAGE_CONVERTHD, "Mandatory output image parameter missing"); + + + PVDINTERFACE pVDIfs = NULL; + VDINTERFACEERROR vdInterfaceError; + vdInterfaceError.pfnError = handleVDError; + vdInterfaceError.pfnMessage = handleVDMessage; + + vrc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR, + NULL, sizeof(VDINTERFACEERROR), &pVDIfs); + AssertRC(vrc); + + do + { + /* Try to determine input image format */ + if (srcformat.isEmpty()) + { + char *pszFormat = NULL; + vrc = VDGetFormat(NULL /* pVDIfsDisk */, NULL /* pVDIfsImage */, + src.c_str(), &pszFormat, &enmSrcType); + if (RT_FAILURE(vrc)) + { + RTMsgError("No file format specified and autodetect failed - please specify format: %Rrc", vrc); + break; + } + srcformat = pszFormat; + RTStrFree(pszFormat); + } + + vrc = VDCreate(pVDIfs, enmSrcType, &pSrcDisk); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot create the source virtual disk container: %Rrc", vrc); + break; + } + + /* Open the input image */ + vrc = VDOpen(pSrcDisk, srcformat.c_str(), src.c_str(), VD_OPEN_FLAGS_READONLY, NULL); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot open the source image: %Rrc", vrc); + break; + } + + /* Output format defaults to VDI */ + if (dstformat.isEmpty()) + dstformat = "VDI"; + + vrc = VDCreate(pVDIfs, enmSrcType, &pDstDisk); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot create the destination virtual disk container: %Rrc", vrc); + break; + } + + uint64_t cbSize = VDGetSize(pSrcDisk, VD_LAST_IMAGE); + RTStrmPrintf(g_pStdErr, "Converting image \"%s\" with size %RU64 bytes (%RU64MB)...\n", src.c_str(), cbSize, (cbSize + _1M - 1) / _1M); + + /* Create the output image */ + vrc = VDCopy(pSrcDisk, VD_LAST_IMAGE, pDstDisk, dstformat.c_str(), + dst.c_str(), false, 0, VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED, + NULL, VD_OPEN_FLAGS_NORMAL, NULL, NULL, NULL); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot copy the image: %Rrc", vrc); + break; + } + } + while (0); + if (pDstDisk) + VDCloseAll(pDstDisk); + if (pSrcDisk) + VDCloseAll(pSrcDisk); + + return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +/** + * Tries to repair a corrupted hard disk image. + * + * @returns VBox status code + */ +static RTEXITCODE CmdRepairHardDisk(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aVirtualBox, aSession); + Utf8Str image; + Utf8Str format; + int vrc; + bool fDryRun = false; + + /* Parse the arguments. */ + for (int i = 0; i < argc; i++) + { + if (strcmp(argv[i], "-dry-run") == 0) + { + fDryRun = true; + } + else if (strcmp(argv[i], "-format") == 0) + { + if (argc <= i + 1) + { + return errorArgument("Missing argument to '%s'", argv[i]); + } + i++; + format = argv[i]; + } + else if (image.isEmpty()) + { + image = argv[i]; + } + else + { + return errorSyntax(USAGE_REPAIRHD, "Invalid parameter '%s'", argv[i]); + } + } + + if (image.isEmpty()) + return errorSyntax(USAGE_REPAIRHD, "Mandatory input image parameter missing"); + + PVDINTERFACE pVDIfs = NULL; + VDINTERFACEERROR vdInterfaceError; + vdInterfaceError.pfnError = handleVDError; + vdInterfaceError.pfnMessage = handleVDMessage; + + vrc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR, + NULL, sizeof(VDINTERFACEERROR), &pVDIfs); + AssertRC(vrc); + + do + { + /* Try to determine input image format */ + if (format.isEmpty()) + { + char *pszFormat = NULL; + VDTYPE enmSrcType = VDTYPE_INVALID; + + vrc = VDGetFormat(NULL /* pVDIfsDisk */, NULL /* pVDIfsImage */, + image.c_str(), &pszFormat, &enmSrcType); + if (RT_FAILURE(vrc) && (vrc != VERR_VD_IMAGE_CORRUPTED)) + { + RTMsgError("No file format specified and autodetect failed - please specify format: %Rrc", vrc); + break; + } + format = pszFormat; + RTStrFree(pszFormat); + } + + uint32_t fFlags = 0; + if (fDryRun) + fFlags |= VD_REPAIR_DRY_RUN; + + vrc = VDRepair(pVDIfs, NULL, image.c_str(), format.c_str(), fFlags); + } + while (0); + + return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +/** + * Unloads the necessary driver. + * + * @returns VBox status code + */ +static RTEXITCODE CmdModUninstall(void) +{ + int rc = SUPR3Uninstall(); + if (RT_SUCCESS(rc) || rc == VERR_NOT_IMPLEMENTED) + return RTEXITCODE_SUCCESS; + return RTEXITCODE_FAILURE; +} + +/** + * Loads the necessary driver. + * + * @returns VBox status code + */ +static RTEXITCODE CmdModInstall(void) +{ + int rc = SUPR3Install(); + if (RT_SUCCESS(rc) || rc == VERR_NOT_IMPLEMENTED) + return RTEXITCODE_SUCCESS; + return RTEXITCODE_FAILURE; +} + +static RTEXITCODE CmdDebugLog(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + /* + * The first parameter is the name or UUID of a VM with a direct session + * that we wish to open. + */ + if (argc < 1) + return errorSyntax(USAGE_DEBUGLOG, "Missing VM name/UUID"); + + ComPtr<IMachine> ptrMachine; + HRESULT rc; + CHECK_ERROR_RET(aVirtualBox, FindMachine(Bstr(argv[0]).raw(), + ptrMachine.asOutParam()), RTEXITCODE_FAILURE); + + CHECK_ERROR_RET(ptrMachine, LockMachine(aSession, LockType_Shared), RTEXITCODE_FAILURE); + + /* + * Get the debugger interface. + */ + ComPtr<IConsole> ptrConsole; + CHECK_ERROR_RET(aSession, COMGETTER(Console)(ptrConsole.asOutParam()), RTEXITCODE_FAILURE); + + ComPtr<IMachineDebugger> ptrDebugger; + CHECK_ERROR_RET(ptrConsole, COMGETTER(Debugger)(ptrDebugger.asOutParam()), RTEXITCODE_FAILURE); + + /* + * Parse the command. + */ + bool fEnablePresent = false; + bool fEnable = false; + bool fFlagsPresent = false; + RTCString strFlags; + bool fGroupsPresent = false; + RTCString strGroups; + bool fDestsPresent = false; + RTCString strDests; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--disable", 'E', RTGETOPT_REQ_NOTHING }, + { "--enable", 'e', RTGETOPT_REQ_NOTHING }, + { "--flags", 'f', RTGETOPT_REQ_STRING }, + { "--groups", 'g', RTGETOPT_REQ_STRING }, + { "--destinations", 'd', RTGETOPT_REQ_STRING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'e': + fEnablePresent = true; + fEnable = true; + break; + + case 'E': + fEnablePresent = true; + fEnable = false; + break; + + case 'f': + fFlagsPresent = true; + if (*ValueUnion.psz) + { + if (strFlags.isNotEmpty()) + strFlags.append(' '); + strFlags.append(ValueUnion.psz); + } + break; + + case 'g': + fGroupsPresent = true; + if (*ValueUnion.psz) + { + if (strGroups.isNotEmpty()) + strGroups.append(' '); + strGroups.append(ValueUnion.psz); + } + break; + + case 'd': + fDestsPresent = true; + if (*ValueUnion.psz) + { + if (strDests.isNotEmpty()) + strDests.append(' '); + strDests.append(ValueUnion.psz); + } + break; + + default: + return errorGetOpt(USAGE_DEBUGLOG, ch, &ValueUnion); + } + } + + /* + * Do the job. + */ + if (fEnablePresent && !fEnable) + CHECK_ERROR_RET(ptrDebugger, COMSETTER(LogEnabled)(FALSE), RTEXITCODE_FAILURE); + + /** @todo flags, groups destination. */ + if (fFlagsPresent || fGroupsPresent || fDestsPresent) + RTMsgWarning("One or more of the requested features are not implemented! Feel free to do this."); + + if (fEnablePresent && fEnable) + CHECK_ERROR_RET(ptrDebugger, COMSETTER(LogEnabled)(TRUE), RTEXITCODE_FAILURE); + return RTEXITCODE_SUCCESS; +} + +/** + * Generate a SHA-256 password hash + */ +static RTEXITCODE CmdGeneratePasswordHash(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + RT_NOREF(aVirtualBox, aSession); + + /* one parameter, the password to hash */ + if (argc != 1) + return errorSyntax(USAGE_PASSWORDHASH, "password to hash required"); + + uint8_t abDigest[RTSHA256_HASH_SIZE]; + RTSha256(argv[0], strlen(argv[0]), abDigest); + char pszDigest[RTSHA256_DIGEST_LEN + 1]; + RTSha256ToString(abDigest, pszDigest, sizeof(pszDigest)); + RTPrintf("Password hash: %s\n", pszDigest); + + return RTEXITCODE_SUCCESS; +} + +/** + * Print internal guest statistics or + * set internal guest statistics update interval if specified + */ +static RTEXITCODE CmdGuestStats(int argc, char **argv, ComPtr<IVirtualBox> aVirtualBox, ComPtr<ISession> aSession) +{ + /* one parameter, guest name */ + if (argc < 1) + return errorSyntax(USAGE_GUESTSTATS, "Missing VM name/UUID"); + + /* + * Parse the command. + */ + ULONG aUpdateInterval = 0; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--interval", 'i', RTGETOPT_REQ_UINT32 } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'i': + aUpdateInterval = ValueUnion.u32; + break; + + default: + return errorGetOpt(USAGE_GUESTSTATS, ch, &ValueUnion); + } + } + + if (argc > 1 && aUpdateInterval == 0) + return errorSyntax(USAGE_GUESTSTATS, "Invalid update interval specified"); + + RTPrintf("argc=%d interval=%u\n", argc, aUpdateInterval); + + ComPtr<IMachine> ptrMachine; + HRESULT rc; + CHECK_ERROR_RET(aVirtualBox, FindMachine(Bstr(argv[0]).raw(), + ptrMachine.asOutParam()), RTEXITCODE_FAILURE); + + CHECK_ERROR_RET(ptrMachine, LockMachine(aSession, LockType_Shared), RTEXITCODE_FAILURE); + + /* + * Get the guest interface. + */ + ComPtr<IConsole> ptrConsole; + CHECK_ERROR_RET(aSession, COMGETTER(Console)(ptrConsole.asOutParam()), RTEXITCODE_FAILURE); + + ComPtr<IGuest> ptrGuest; + CHECK_ERROR_RET(ptrConsole, COMGETTER(Guest)(ptrGuest.asOutParam()), RTEXITCODE_FAILURE); + + if (aUpdateInterval) + CHECK_ERROR_RET(ptrGuest, COMSETTER(StatisticsUpdateInterval)(aUpdateInterval), RTEXITCODE_FAILURE); + else + { + ULONG mCpuUser, mCpuKernel, mCpuIdle; + ULONG mMemTotal, mMemFree, mMemBalloon, mMemShared, mMemCache, mPageTotal; + ULONG ulMemAllocTotal, ulMemFreeTotal, ulMemBalloonTotal, ulMemSharedTotal; + + CHECK_ERROR_RET(ptrGuest, InternalGetStatistics(&mCpuUser, &mCpuKernel, &mCpuIdle, + &mMemTotal, &mMemFree, &mMemBalloon, &mMemShared, &mMemCache, + &mPageTotal, &ulMemAllocTotal, &ulMemFreeTotal, + &ulMemBalloonTotal, &ulMemSharedTotal), + RTEXITCODE_FAILURE); + RTPrintf("mCpuUser=%u mCpuKernel=%u mCpuIdle=%u\n" + "mMemTotal=%u mMemFree=%u mMemBalloon=%u mMemShared=%u mMemCache=%u\n" + "mPageTotal=%u ulMemAllocTotal=%u ulMemFreeTotal=%u ulMemBalloonTotal=%u ulMemSharedTotal=%u\n", + mCpuUser, mCpuKernel, mCpuIdle, + mMemTotal, mMemFree, mMemBalloon, mMemShared, mMemCache, + mPageTotal, ulMemAllocTotal, ulMemFreeTotal, ulMemBalloonTotal, ulMemSharedTotal); + + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Wrapper for handling internal commands + */ +RTEXITCODE handleInternalCommands(HandlerArg *a) +{ + g_fInternalMode = true; + + /* at least a command is required */ + if (a->argc < 1) + return errorSyntax(USAGE_ALL, "Command missing"); + + /* + * The 'string switch' on command name. + */ + const char *pszCmd = a->argv[0]; + if (!strcmp(pszCmd, "loadmap")) + return CmdLoadMap(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + if (!strcmp(pszCmd, "loadsyms")) + return CmdLoadSyms(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + //if (!strcmp(pszCmd, "unloadsyms")) + // return CmdUnloadSyms(argc - 1, &a->argv[1]); + if (!strcmp(pszCmd, "sethduuid") || !strcmp(pszCmd, "sethdparentuuid")) + return CmdSetHDUUID(a->argc, &a->argv[0], a->virtualBox, a->session); + if (!strcmp(pszCmd, "dumphdinfo")) + return CmdDumpHDInfo(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + if (!strcmp(pszCmd, "listpartitions")) + return CmdListPartitions(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + if (!strcmp(pszCmd, "createrawvmdk")) + return CmdCreateRawVMDK(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + if (!strcmp(pszCmd, "renamevmdk")) + return CmdRenameVMDK(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + if (!strcmp(pszCmd, "converttoraw")) + return CmdConvertToRaw(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + if (!strcmp(pszCmd, "converthd")) + return CmdConvertHardDisk(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + if (!strcmp(pszCmd, "modinstall")) + return CmdModInstall(); + if (!strcmp(pszCmd, "moduninstall")) + return CmdModUninstall(); + if (!strcmp(pszCmd, "debuglog")) + return CmdDebugLog(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + if (!strcmp(pszCmd, "passwordhash")) + return CmdGeneratePasswordHash(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + if (!strcmp(pszCmd, "gueststats")) + return CmdGuestStats(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + if (!strcmp(pszCmd, "repairhd")) + return CmdRepairHardDisk(a->argc - 1, &a->argv[1], a->virtualBox, a->session); + + /* default: */ + return errorSyntax(USAGE_ALL, "Invalid command '%s'", a->argv[0]); +} + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManage.cpp b/src/VBox/Frontends/VBoxManage/VBoxManage.cpp new file mode 100644 index 00000000..fe3fcffe --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManage.cpp @@ -0,0 +1,760 @@ +/* $Id: VBoxManage.cpp $ */ +/** @file + * VBoxManage - VirtualBox's command-line interface. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef VBOX_ONLY_DOCS +# include <VBox/com/com.h> +# include <VBox/com/string.h> +# include <VBox/com/Guid.h> +# include <VBox/com/array.h> +# include <VBox/com/ErrorInfo.h> +# include <VBox/com/errorprint.h> +# include <VBox/com/NativeEventQueue.h> + +# include <VBox/com/VirtualBox.h> +#endif /* !VBOX_ONLY_DOCS */ + +#include <VBox/version.h> + +#include <iprt/asm.h> +#include <iprt/buildconfig.h> +#include <iprt/ctype.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include <signal.h> + +#include "VBoxManage.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The command doesn't need the COM stuff. */ +#define VBMG_CMD_F_NO_COM RT_BIT_32(0) + +#define VBMG_CMD_TODO HELP_CMD_VBOXMANAGE_INVALID + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifndef VBOX_ONLY_DOCS +/** + * VBoxManage command descriptor. + */ +typedef struct VBMGCMD +{ + /** The command. */ + const char *pszCommand; + /** The help category. */ + USAGECATEGORY enmHelpCat; + /** The new help command. */ + enum HELP_CMD_VBOXMANAGE enmCmdHelp; + /** The handler. */ + RTEXITCODE (*pfnHandler)(HandlerArg *pArg); + /** VBMG_CMD_F_XXX, */ + uint32_t fFlags; +} VBMGCMD; +/** Pointer to a const VBoxManage command descriptor. */ +typedef VBMGCMD const *PCVBMGCMD; +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/*extern*/ bool g_fDetailedProgress = false; + +#ifndef VBOX_ONLY_DOCS +/** Set by the signal handler. */ +static volatile bool g_fCanceled = false; + + +/** + * All registered command handlers + */ +static const VBMGCMD g_aCommands[] = +{ + { "internalcommands", 0, VBMG_CMD_TODO, handleInternalCommands, 0 }, + { "list", USAGE_LIST, VBMG_CMD_TODO, handleList, 0 }, + { "showvminfo", USAGE_SHOWVMINFO, VBMG_CMD_TODO, handleShowVMInfo, 0 }, + { "registervm", USAGE_REGISTERVM, VBMG_CMD_TODO, handleRegisterVM, 0 }, + { "unregistervm", USAGE_UNREGISTERVM, VBMG_CMD_TODO, handleUnregisterVM, 0 }, + { "clonevm", USAGE_CLONEVM, VBMG_CMD_TODO, handleCloneVM, 0 }, + { "movevm", USAGE_MOVEVM, VBMG_CMD_TODO, handleMoveVM, 0 }, + { "mediumproperty", USAGE_MEDIUMPROPERTY, VBMG_CMD_TODO, handleMediumProperty, 0 }, + { "hdproperty", USAGE_MEDIUMPROPERTY, VBMG_CMD_TODO, handleMediumProperty, 0 }, /* backward compatibility */ + { "createmedium", USAGE_CREATEMEDIUM, VBMG_CMD_TODO, handleCreateMedium, 0 }, + { "createhd", USAGE_CREATEMEDIUM, VBMG_CMD_TODO, handleCreateMedium, 0 }, /* backward compatibility */ + { "createvdi", USAGE_CREATEMEDIUM, VBMG_CMD_TODO, handleCreateMedium, 0 }, /* backward compatibility */ + { "modifymedium", USAGE_MODIFYMEDIUM, VBMG_CMD_TODO, handleModifyMedium, 0 }, + { "modifyhd", USAGE_MODIFYMEDIUM, VBMG_CMD_TODO, handleModifyMedium, 0 }, /* backward compatibility */ + { "modifyvdi", USAGE_MODIFYMEDIUM, VBMG_CMD_TODO, handleModifyMedium, 0 }, /* backward compatibility */ + { "clonemedium", USAGE_CLONEMEDIUM, VBMG_CMD_TODO, handleCloneMedium, 0 }, + { "clonehd", USAGE_CLONEMEDIUM, VBMG_CMD_TODO, handleCloneMedium, 0 }, /* backward compatibility */ + { "clonevdi", USAGE_CLONEMEDIUM, VBMG_CMD_TODO, handleCloneMedium, 0 }, /* backward compatibility */ + { "encryptmedium", USAGE_ENCRYPTMEDIUM, VBMG_CMD_TODO, handleEncryptMedium, 0 }, + { "checkmediumpwd", USAGE_MEDIUMENCCHKPWD, VBMG_CMD_TODO, handleCheckMediumPassword, 0 }, + { "createvm", USAGE_CREATEVM, VBMG_CMD_TODO, handleCreateVM, 0 }, + { "modifyvm", USAGE_MODIFYVM, VBMG_CMD_TODO, handleModifyVM, 0 }, + { "startvm", USAGE_STARTVM, VBMG_CMD_TODO, handleStartVM, 0 }, + { "controlvm", USAGE_CONTROLVM, VBMG_CMD_TODO, handleControlVM, 0 }, + { "unattended", USAGE_UNATTENDED, HELP_CMD_UNATTENDED, handleUnattended, 0 }, + { "discardstate", USAGE_DISCARDSTATE, VBMG_CMD_TODO, handleDiscardState, 0 }, + { "adoptstate", USAGE_ADOPTSTATE, VBMG_CMD_TODO, handleAdoptState, 0 }, + { "snapshot", USAGE_SNAPSHOT, VBMG_CMD_TODO, handleSnapshot, 0 }, + { "closemedium", USAGE_CLOSEMEDIUM, VBMG_CMD_TODO, handleCloseMedium, 0 }, + { "storageattach", USAGE_STORAGEATTACH, VBMG_CMD_TODO, handleStorageAttach, 0 }, + { "storagectl", USAGE_STORAGECONTROLLER,VBMG_CMD_TODO, handleStorageController, 0 }, + { "showmediuminfo", USAGE_SHOWMEDIUMINFO, VBMG_CMD_TODO, handleShowMediumInfo, 0 }, + { "showhdinfo", USAGE_SHOWMEDIUMINFO, VBMG_CMD_TODO, handleShowMediumInfo, 0 }, /* backward compatibility */ + { "showvdiinfo", USAGE_SHOWMEDIUMINFO, VBMG_CMD_TODO, handleShowMediumInfo, 0 }, /* backward compatibility */ + { "mediumio", USAGE_MEDIUMIO, HELP_CMD_MEDIUMIO, handleMediumIO, 0 }, + { "getextradata", USAGE_GETEXTRADATA, VBMG_CMD_TODO, handleGetExtraData, 0 }, + { "setextradata", USAGE_SETEXTRADATA, VBMG_CMD_TODO, handleSetExtraData, 0 }, + { "setproperty", USAGE_SETPROPERTY, VBMG_CMD_TODO, handleSetProperty, 0 }, + { "usbfilter", USAGE_USBFILTER, VBMG_CMD_TODO, handleUSBFilter, 0 }, + { "sharedfolder", USAGE_SHAREDFOLDER, VBMG_CMD_TODO, handleSharedFolder, 0 }, +#ifdef VBOX_WITH_GUEST_PROPS + { "guestproperty", USAGE_GUESTPROPERTY, VBMG_CMD_TODO, handleGuestProperty, 0 }, +#endif +#ifdef VBOX_WITH_GUEST_CONTROL + { "guestcontrol", USAGE_GUESTCONTROL, VBMG_CMD_TODO, handleGuestControl, 0 }, +#endif + { "metrics", USAGE_METRICS, VBMG_CMD_TODO, handleMetrics, 0 }, + { "import", USAGE_IMPORTAPPLIANCE, VBMG_CMD_TODO, handleImportAppliance, 0 }, + { "export", USAGE_EXPORTAPPLIANCE, VBMG_CMD_TODO, handleExportAppliance, 0 }, +#ifdef VBOX_WITH_NETFLT + { "hostonlyif", USAGE_HOSTONLYIFS, VBMG_CMD_TODO, handleHostonlyIf, 0 }, +#endif + { "dhcpserver", USAGE_DHCPSERVER, VBMG_CMD_TODO, handleDHCPServer, 0 }, +#ifdef VBOX_WITH_NAT_SERVICE + { "natnetwork", USAGE_NATNETWORK, VBMG_CMD_TODO, handleNATNetwork, 0 }, +#endif + { "extpack", USAGE_EXTPACK, HELP_CMD_EXTPACK, handleExtPack, 0 }, + { "bandwidthctl", USAGE_BANDWIDTHCONTROL, VBMG_CMD_TODO, handleBandwidthControl, 0 }, + { "debugvm", USAGE_DEBUGVM, HELP_CMD_DEBUGVM, handleDebugVM, 0 }, + { "convertfromraw", USAGE_CONVERTFROMRAW, VBMG_CMD_TODO, handleConvertFromRaw, VBMG_CMD_F_NO_COM }, + { "convertdd", USAGE_CONVERTFROMRAW, VBMG_CMD_TODO, handleConvertFromRaw, VBMG_CMD_F_NO_COM }, + { "usbdevsource", USAGE_USBDEVSOURCE, VBMG_CMD_TODO, handleUSBDevSource, 0 } +}; + + +/** + * Looks up a command by name. + * + * @returns Pointer to the command structure. + * @param pszCommand Name of the command. + */ +static PCVBMGCMD lookupCommand(const char *pszCommand) +{ + if (pszCommand) + for (uint32_t i = 0; i < RT_ELEMENTS(g_aCommands); i++) + if (!strcmp(g_aCommands[i].pszCommand, pszCommand)) + return &g_aCommands[i]; + return NULL; +} + + +/** + * Signal handler that sets g_fCanceled. + * + * This can be executed on any thread in the process, on Windows it may even be + * a thread dedicated to delivering this signal. Do not doing anything + * unnecessary here. + */ +static void showProgressSignalHandler(int iSignal) +{ + NOREF(iSignal); + ASMAtomicWriteBool(&g_fCanceled, true); +} + +/** + * Print out progress on the console. + * + * This runs the main event queue every now and then to prevent piling up + * unhandled things (which doesn't cause real problems, just makes things + * react a little slower than in the ideal case). + */ +HRESULT showProgress(ComPtr<IProgress> progress) +{ + using namespace com; + + BOOL fCompleted = FALSE; + ULONG ulCurrentPercent = 0; + ULONG ulLastPercent = 0; + + ULONG ulLastOperationPercent = (ULONG)-1; + + ULONG ulLastOperation = (ULONG)-1; + Bstr bstrOperationDescription; + + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + + ULONG cOperations = 1; + HRESULT hrc = progress->COMGETTER(OperationCount)(&cOperations); + if (FAILED(hrc)) + { + RTStrmPrintf(g_pStdErr, "Progress object failure: %Rhrc\n", hrc); + RTStrmFlush(g_pStdErr); + return hrc; + } + + /* + * Note: Outputting the progress info to stderr (g_pStdErr) is intentional + * to not get intermixed with other (raw) stdout data which might get + * written in the meanwhile. + */ + + if (!g_fDetailedProgress) + { + RTStrmPrintf(g_pStdErr, "0%%..."); + RTStrmFlush(g_pStdErr); + } + + /* setup signal handling if cancelable */ + bool fCanceledAlready = false; + BOOL fCancelable; + hrc = progress->COMGETTER(Cancelable)(&fCancelable); + if (FAILED(hrc)) + fCancelable = FALSE; + if (fCancelable) + { + signal(SIGINT, showProgressSignalHandler); + signal(SIGTERM, showProgressSignalHandler); +#ifdef SIGBREAK + signal(SIGBREAK, showProgressSignalHandler); +#endif + } + + hrc = progress->COMGETTER(Completed(&fCompleted)); + while (SUCCEEDED(hrc)) + { + progress->COMGETTER(Percent(&ulCurrentPercent)); + + if (g_fDetailedProgress) + { + ULONG ulOperation = 1; + hrc = progress->COMGETTER(Operation)(&ulOperation); + if (FAILED(hrc)) + break; + ULONG ulCurrentOperationPercent = 0; + hrc = progress->COMGETTER(OperationPercent(&ulCurrentOperationPercent)); + if (FAILED(hrc)) + break; + + if (ulLastOperation != ulOperation) + { + hrc = progress->COMGETTER(OperationDescription(bstrOperationDescription.asOutParam())); + if (FAILED(hrc)) + break; + ulLastPercent = (ULONG)-1; // force print + ulLastOperation = ulOperation; + } + + if ( ulCurrentPercent != ulLastPercent + || ulCurrentOperationPercent != ulLastOperationPercent + ) + { + LONG lSecsRem = 0; + progress->COMGETTER(TimeRemaining)(&lSecsRem); + + RTStrmPrintf(g_pStdErr, "(%u/%u) %ls %02u%% => %02u%% (%d s remaining)\n", ulOperation + 1, cOperations, + bstrOperationDescription.raw(), ulCurrentOperationPercent, ulCurrentPercent, lSecsRem); + ulLastPercent = ulCurrentPercent; + ulLastOperationPercent = ulCurrentOperationPercent; + } + } + else + { + /* did we cross a 10% mark? */ + if (ulCurrentPercent / 10 > ulLastPercent / 10) + { + /* make sure to also print out missed steps */ + for (ULONG curVal = (ulLastPercent / 10) * 10 + 10; curVal <= (ulCurrentPercent / 10) * 10; curVal += 10) + { + if (curVal < 100) + { + RTStrmPrintf(g_pStdErr, "%u%%...", curVal); + RTStrmFlush(g_pStdErr); + } + } + ulLastPercent = (ulCurrentPercent / 10) * 10; + } + } + if (fCompleted) + break; + + /* process async cancelation */ + if (g_fCanceled && !fCanceledAlready) + { + hrc = progress->Cancel(); + if (SUCCEEDED(hrc)) + fCanceledAlready = true; + else + g_fCanceled = false; + } + + /* make sure the loop is not too tight */ + progress->WaitForCompletion(100); + + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + hrc = progress->COMGETTER(Completed(&fCompleted)); + } + + /* undo signal handling */ + if (fCancelable) + { + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); +# ifdef SIGBREAK + signal(SIGBREAK, SIG_DFL); +# endif + } + + /* complete the line. */ + LONG iRc = E_FAIL; + hrc = progress->COMGETTER(ResultCode)(&iRc); + if (SUCCEEDED(hrc)) + { + if (SUCCEEDED(iRc)) + RTStrmPrintf(g_pStdErr, "100%%\n"); + else if (g_fCanceled) + RTStrmPrintf(g_pStdErr, "CANCELED\n"); + else + { + if (!g_fDetailedProgress) + RTStrmPrintf(g_pStdErr, "\n"); + RTStrmPrintf(g_pStdErr, "Progress state: %Rhrc\n", iRc); + } + hrc = iRc; + } + else + { + if (!g_fDetailedProgress) + RTStrmPrintf(g_pStdErr, "\n"); + RTStrmPrintf(g_pStdErr, "Progress object failure: %Rhrc\n", hrc); + } + RTStrmFlush(g_pStdErr); + return hrc; +} + +RTEXITCODE readPasswordFile(const char *pszFilename, com::Utf8Str *pPasswd) +{ + size_t cbFile; + char szPasswd[512]; + int vrc = VINF_SUCCESS; + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + bool fStdIn = !strcmp(pszFilename, "stdin"); + PRTSTREAM pStrm; + if (!fStdIn) + vrc = RTStrmOpen(pszFilename, "r", &pStrm); + else + pStrm = g_pStdIn; + if (RT_SUCCESS(vrc)) + { + vrc = RTStrmReadEx(pStrm, szPasswd, sizeof(szPasswd)-1, &cbFile); + if (RT_SUCCESS(vrc)) + { + if (cbFile >= sizeof(szPasswd)-1) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Provided password in file '%s' is too long", pszFilename); + else + { + unsigned i; + for (i = 0; i < cbFile && !RT_C_IS_CNTRL(szPasswd[i]); i++) + ; + szPasswd[i] = '\0'; + *pPasswd = szPasswd; + } + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot read password from file '%s': %Rrc", pszFilename, vrc); + if (!fStdIn) + RTStrmClose(pStrm); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot open password file '%s' (%Rrc)", pszFilename, vrc); + + return rcExit; +} + +static RTEXITCODE settingsPasswordFile(ComPtr<IVirtualBox> virtualBox, const char *pszFilename) +{ + com::Utf8Str passwd; + RTEXITCODE rcExit = readPasswordFile(pszFilename, &passwd); + if (rcExit == RTEXITCODE_SUCCESS) + { + CHECK_ERROR2I_STMT(virtualBox, SetSettingsSecret(com::Bstr(passwd).raw()), rcExit = RTEXITCODE_FAILURE); + } + + return rcExit; +} + +RTEXITCODE readPasswordFromConsole(com::Utf8Str *pPassword, const char *pszPrompt, ...) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + char aszPwdInput[_1K] = { 0 }; + va_list vaArgs; + + va_start(vaArgs, pszPrompt); + int vrc = RTStrmPrintfV(g_pStdOut, pszPrompt, vaArgs); + if (RT_SUCCESS(vrc)) + { + bool fEchoOld = false; + vrc = RTStrmInputGetEchoChars(g_pStdIn, &fEchoOld); + if (RT_SUCCESS(vrc)) + { + vrc = RTStrmInputSetEchoChars(g_pStdIn, false); + if (RT_SUCCESS(vrc)) + { + vrc = RTStrmGetLine(g_pStdIn, &aszPwdInput[0], sizeof(aszPwdInput)); + if (RT_SUCCESS(vrc)) + *pPassword = aszPwdInput; + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed read password from command line (%Rrc)", vrc); + + int vrc2 = RTStrmInputSetEchoChars(g_pStdIn, fEchoOld); + AssertRC(vrc2); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to disable echoing typed characters (%Rrc)", vrc); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to retrieve echo setting (%Rrc)", vrc); + + RTStrmPutStr(g_pStdOut, "\n"); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to print prompt (%Rrc)", vrc); + va_end(vaArgs); + + return rcExit; +} + +#endif /* !VBOX_ONLY_DOCS */ + + +int main(int argc, char *argv[]) +{ + /* + * Before we do anything, init the runtime without loading + * the support driver. + */ + int vrc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(vrc)) + return RTMsgInitFailure(vrc); +#if defined(RT_OS_WINDOWS) && !defined(VBOX_ONLY_DOCS) + ATL::CComModule _Module; /* Required internally by ATL (constructor records instance in global variable). */ +#endif + + /* + * Parse the global options + */ + bool fShowLogo = false; + bool fShowHelp = false; + int iCmd = 1; + int iCmdArg; + const char *pszSettingsPw = NULL; + const char *pszSettingsPwFile = NULL; +#ifndef VBOX_ONLY_DOCS + int cResponseFileArgs = 0; + char **papszResponseFileArgs = NULL; + char **papszNewArgv = NULL; +#endif + + for (int i = 1; i < argc || argc <= iCmd; i++) + { + if ( argc <= iCmd + || !strcmp(argv[i], "help") + || !strcmp(argv[i], "--help") + || !strcmp(argv[i], "-?") + || !strcmp(argv[i], "-h") + || !strcmp(argv[i], "-help")) + { + if (i >= argc - 1) + { + showLogo(g_pStdOut); + printUsage(USAGE_ALL, ~0U, g_pStdOut); + return 0; + } + fShowLogo = true; + fShowHelp = true; + iCmd++; + continue; + } + +#ifndef VBOX_ONLY_DOCS + if ( !strcmp(argv[i], "-V") + || !strcmp(argv[i], "--version") + || !strcmp(argv[i], "-v") /* deprecated */ + || !strcmp(argv[i], "-version") /* deprecated */ + || !strcmp(argv[i], "-Version") /* deprecated */) + { + /* Print version number, and do nothing else. */ + RTPrintf("%sr%u\n", VBOX_VERSION_STRING, RTBldCfgRevision()); + return 0; + } +#endif + + if ( !strcmp(argv[i], "--dumpopts") + || !strcmp(argv[i], "-dumpopts") /* deprecated */) + { + /* Special option to dump really all commands, + * even the ones not understood on this platform. */ + printUsage(USAGE_DUMPOPTS, ~0U, g_pStdOut); + return 0; + } + + if ( !strcmp(argv[i], "--nologo") + || !strcmp(argv[i], "-q") + || !strcmp(argv[i], "-nologo") /* deprecated */) + { + /* suppress the logo */ + fShowLogo = false; + iCmd++; + } + else if ( !strcmp(argv[i], "--detailed-progress") + || !strcmp(argv[i], "-d")) + { + /* detailed progress report */ + g_fDetailedProgress = true; + iCmd++; + } + else if (!strcmp(argv[i], "--settingspw")) + { + if (i >= argc - 1) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Password expected"); + /* password for certain settings */ + pszSettingsPw = argv[i + 1]; + iCmd += 2; + } + else if (!strcmp(argv[i], "--settingspwfile")) + { + if (i >= argc-1) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "No password file specified"); + pszSettingsPwFile = argv[i+1]; + iCmd += 2; + } +#ifndef VBOX_ONLY_DOCS + else if (argv[i][0] == '@') + { + if (papszResponseFileArgs) + return RTMsgErrorExitFailure("Only one response file allowed"); + + /* Load response file, making sure it's valid UTF-8. */ + char *pszResponseFile; + size_t cbResponseFile; + vrc = RTFileReadAllEx(&argv[i][1], 0, RTFOFF_MAX, RTFILE_RDALL_O_DENY_NONE | RTFILE_RDALL_F_TRAILING_ZERO_BYTE, + (void **)&pszResponseFile, &cbResponseFile); + if (RT_FAILURE(vrc)) + return RTMsgErrorExitFailure("Error reading response file '%s': %Rrc", &argv[i][1], vrc); + vrc = RTStrValidateEncoding(pszResponseFile); + if (RT_FAILURE(vrc)) + { + RTFileReadAllFree(pszResponseFile, cbResponseFile); + return RTMsgErrorExitFailure("Invalid response file ('%s') encoding: %Rrc", &argv[i][1], vrc); + } + + /* Parse it. */ + vrc = RTGetOptArgvFromString(&papszResponseFileArgs, &cResponseFileArgs, pszResponseFile, + RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL); + RTFileReadAllFree(pszResponseFile, cbResponseFile); + if (RT_FAILURE(vrc)) + return RTMsgErrorExitFailure("Failed to parse response file '%s' (bourne shell style): %Rrc", &argv[i][1], vrc); + + /* Construct new argv+argc with the response file arguments inserted. */ + int cNewArgs = argc + cResponseFileArgs; + papszNewArgv = (char **)RTMemAllocZ((cNewArgs + 2) * sizeof(papszNewArgv[0])); + if (!papszNewArgv) + return RTMsgErrorExitFailure("out of memory"); + memcpy(&papszNewArgv[0], &argv[0], sizeof(argv[0]) * (i + 1)); + memcpy(&papszNewArgv[i + 1], papszResponseFileArgs, sizeof(argv[0]) * cResponseFileArgs); + memcpy(&papszNewArgv[i + 1 + cResponseFileArgs], &argv[i + 1], sizeof(argv[0]) * (argc - i - 1 + 1)); + argv = papszNewArgv; + argc = argc + cResponseFileArgs; + + iCmd++; + } +#endif + else + break; + } + + iCmdArg = iCmd + 1; + + /* + * Show the logo and lookup the command and deal with fShowHelp = true. + */ + if (fShowLogo) + showLogo(g_pStdOut); + +#ifndef VBOX_ONLY_DOCS + PCVBMGCMD pCmd = lookupCommand(argv[iCmd]); + if (pCmd && pCmd->enmCmdHelp != VBMG_CMD_TODO) + setCurrentCommand(pCmd->enmCmdHelp); + + if ( pCmd + && ( fShowHelp + || ( argc - iCmdArg == 0 + && pCmd->enmHelpCat != 0))) + { + if (pCmd->enmCmdHelp == VBMG_CMD_TODO) + printUsage(pCmd->enmHelpCat, ~0U, g_pStdOut); + else if (fShowHelp) + printHelp(g_pStdOut); + else + printUsage(g_pStdOut); + return RTEXITCODE_FAILURE; /* error */ + } + if (!pCmd) + { + if (!strcmp(argv[iCmd], "commands")) + { + RTPrintf("commands:\n"); + for (unsigned i = 0; i < RT_ELEMENTS(g_aCommands); i++) + if ( i == 0 /* skip backwards compatibility entries */ + || g_aCommands[i].enmHelpCat != g_aCommands[i - 1].enmHelpCat) + RTPrintf(" %s\n", g_aCommands[i].pszCommand); + return RTEXITCODE_SUCCESS; + } + return errorSyntax(USAGE_ALL, "Invalid command '%s'", argv[iCmd]); + } + + RTEXITCODE rcExit; + if (!(pCmd->fFlags & VBMG_CMD_F_NO_COM)) + { + /* + * Initialize COM. + */ + using namespace com; + HRESULT hrc = com::Initialize(); + if (FAILED(hrc)) + { +# ifdef VBOX_WITH_XPCOM + if (hrc == NS_ERROR_FILE_ACCESS_DENIED) + { + char szHome[RTPATH_MAX] = ""; + com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome)); + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome); + } +# endif + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize COM! (hrc=%Rhrc)", hrc); + } + + + /* + * Get the remote VirtualBox object and create a local session object. + */ + rcExit = RTEXITCODE_FAILURE; + ComPtr<IVirtualBoxClient> virtualBoxClient; + ComPtr<IVirtualBox> virtualBox; + hrc = virtualBoxClient.createInprocObject(CLSID_VirtualBoxClient); + if (SUCCEEDED(hrc)) + hrc = virtualBoxClient->COMGETTER(VirtualBox)(virtualBox.asOutParam()); + if (SUCCEEDED(hrc)) + { + ComPtr<ISession> session; + hrc = session.createInprocObject(CLSID_Session); + if (SUCCEEDED(hrc)) + { + /* Session secret. */ + if (pszSettingsPw) + CHECK_ERROR2I_STMT(virtualBox, SetSettingsSecret(Bstr(pszSettingsPw).raw()), rcExit = RTEXITCODE_FAILURE); + else if (pszSettingsPwFile) + rcExit = settingsPasswordFile(virtualBox, pszSettingsPwFile); + else + rcExit = RTEXITCODE_SUCCESS; + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Call the handler. + */ + HandlerArg handlerArg = { argc - iCmdArg, &argv[iCmdArg], virtualBox, session }; + rcExit = pCmd->pfnHandler(&handlerArg); + + /* Although all handlers should always close the session if they open it, + * we do it here just in case if some of the handlers contains a bug -- + * leaving the direct session not closed will turn the machine state to + * Aborted which may have unwanted side effects like killing the saved + * state file (if the machine was in the Saved state before). */ + session->UnlockMachine(); + } + + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + } + else + { + com::ErrorInfo info; + RTMsgError("Failed to create a session object!"); + if (!info.isFullAvailable() && !info.isBasicAvailable()) + com::GluePrintRCMessage(hrc); + else + com::GluePrintErrorInfo(info); + } + } + else + { + com::ErrorInfo info; + RTMsgError("Failed to create the VirtualBox object!"); + if (!info.isFullAvailable() && !info.isBasicAvailable()) + { + com::GluePrintRCMessage(hrc); + RTMsgError("Most likely, the VirtualBox COM server is not running or failed to start."); + } + else + com::GluePrintErrorInfo(info); + } + + /* + * Terminate COM, make sure the virtualBox object has been released. + */ + virtualBox.setNull(); + virtualBoxClient.setNull(); + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + com::Shutdown(); + } + else + { + /* + * The command needs no COM. + */ + HandlerArg handlerArg; + handlerArg.argc = argc - iCmdArg; + handlerArg.argv = &argv[iCmdArg]; + rcExit = pCmd->pfnHandler(&handlerArg); + } + + if (papszResponseFileArgs) + { + RTGetOptArgvFree(papszResponseFileArgs); + RTMemFree(papszNewArgv); + } + + return rcExit; +#else /* VBOX_ONLY_DOCS */ + return RTEXITCODE_SUCCESS; +#endif /* VBOX_ONLY_DOCS */ +} diff --git a/src/VBox/Frontends/VBoxManage/VBoxManage.h b/src/VBox/Frontends/VBoxManage/VBoxManage.h new file mode 100644 index 00000000..e1d619ae --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManage.h @@ -0,0 +1,338 @@ +/* $Id: VBoxManage.h $ */ +/** @file + * VBoxManage - VirtualBox command-line interface, internal header file. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxManage_VBoxManage_h +#define VBOX_INCLUDED_SRC_VBoxManage_VBoxManage_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifndef VBOX_ONLY_DOCS +#include <VBox/com/com.h> +#include <VBox/com/ptr.h> +#include <VBox/com/VirtualBox.h> +#include <VBox/com/string.h> +#include <VBox/com/array.h> +#endif /* !VBOX_ONLY_DOCS */ + +#include <iprt/types.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/getopt.h> + +#ifndef VBOX_ONLY_DOCS +# include "VBoxManageBuiltInHelp.h" +#endif + + +//////////////////////////////////////////////////////////////////////////////// +// +// definitions +// +//////////////////////////////////////////////////////////////////////////////// + +/** @name Syntax diagram category. + * @{ */ +#define USAGE_DUMPOPTS 0 +#define USAGE_LIST RT_BIT_64(0) +#define USAGE_SHOWVMINFO RT_BIT_64(1) +#define USAGE_REGISTERVM RT_BIT_64(2) +#define USAGE_UNREGISTERVM RT_BIT_64(3) +#define USAGE_CREATEVM RT_BIT_64(4) +#define USAGE_MODIFYVM RT_BIT_64(5) +#define USAGE_CLONEVM RT_BIT_64(6) +#define USAGE_STARTVM RT_BIT_64(7) +#define USAGE_CONTROLVM RT_BIT_64(8) +#define USAGE_DISCARDSTATE RT_BIT_64(9) +#define USAGE_SNAPSHOT RT_BIT_64(10) +#define USAGE_CLOSEMEDIUM RT_BIT_64(11) +#define USAGE_SHOWMEDIUMINFO RT_BIT_64(12) +#define USAGE_CREATEMEDIUM RT_BIT_64(13) +#define USAGE_MODIFYMEDIUM RT_BIT_64(14) +#define USAGE_CLONEMEDIUM RT_BIT_64(15) +#define USAGE_MOVEVM RT_BIT_64(16) +#define USAGE_CREATEHOSTIF RT_BIT_64(17) +#define USAGE_REMOVEHOSTIF RT_BIT_64(18) +#define USAGE_GETEXTRADATA RT_BIT_64(19) +#define USAGE_SETEXTRADATA RT_BIT_64(20) +#define USAGE_SETPROPERTY RT_BIT_64(21) +#define USAGE_USBFILTER (RT_BIT_64(22) | RT_BIT_64(23) | RT_BIT_64(24)) +#define USAGE_USBFILTER_ADD RT_BIT_64(22) +#define USAGE_USBFILTER_MODIFY RT_BIT_64(23) +#define USAGE_USBFILTER_REMOVE RT_BIT_64(24) +#define USAGE_SHAREDFOLDER (RT_BIT_64(25) | RT_BIT_64(26)) +#define USAGE_SHAREDFOLDER_ADD RT_BIT_64(25) +#define USAGE_SHAREDFOLDER_REMOVE RT_BIT_64(26) +#define USAGE_UNATTENDED RT_BIT_64(27) +#define USAGE_MEDIUMIO RT_BIT_64(28) +#define USAGE_LOADSYMS RT_BIT_64(29) +#define USAGE_LOADMAP RT_BIT_64(30) +#define USAGE_SETHDUUID RT_BIT_64(31) +#define USAGE_CONVERTFROMRAW RT_BIT_64(32) +#define USAGE_LISTPARTITIONS RT_BIT_64(33) +#define USAGE_CREATERAWVMDK RT_BIT_64(34) +#define USAGE_DEBUGVM RT_BIT_64(35) +#define USAGE_ADOPTSTATE RT_BIT_64(36) +#define USAGE_MODINSTALL RT_BIT_64(37) +#define USAGE_MODUNINSTALL RT_BIT_64(38) +#define USAGE_RENAMEVMDK RT_BIT_64(39) +#ifdef VBOX_WITH_GUEST_PROPS +# define USAGE_GUESTPROPERTY RT_BIT_64(40) +#endif /* VBOX_WITH_GUEST_PROPS defined */ +#define USAGE_CONVERTTORAW RT_BIT_64(41) +#define USAGE_METRICS RT_BIT_64(42) +#define USAGE_CONVERTHD RT_BIT_64(43) +#define USAGE_IMPORTAPPLIANCE RT_BIT_64(44) +#define USAGE_EXPORTAPPLIANCE RT_BIT_64(45) +#define USAGE_HOSTONLYIFS RT_BIT_64(46) +#define USAGE_DHCPSERVER RT_BIT_64(47) +#define USAGE_DUMPHDINFO RT_BIT_64(48) +#define USAGE_STORAGEATTACH RT_BIT_64(49) +#define USAGE_STORAGECONTROLLER RT_BIT_64(50) +#ifdef VBOX_WITH_GUEST_CONTROL +# define USAGE_GUESTCONTROL RT_BIT_64(51) +#endif /* VBOX_WITH_GUEST_CONTROL defined */ +#define USAGE_DEBUGLOG RT_BIT_64(52) +#define USAGE_SETHDPARENTUUID RT_BIT_64(53) +#define USAGE_PASSWORDHASH RT_BIT_64(54) +#define USAGE_EXTPACK RT_BIT_64(55) +#define USAGE_BANDWIDTHCONTROL RT_BIT_64(56) +#define USAGE_GUESTSTATS RT_BIT_64(57) +#define USAGE_REPAIRHD RT_BIT_64(58) +#define USAGE_NATNETWORK RT_BIT_64(59) +#define USAGE_MEDIUMPROPERTY RT_BIT_64(60) +#define USAGE_ENCRYPTMEDIUM RT_BIT_64(61) +#define USAGE_MEDIUMENCCHKPWD RT_BIT_64(62) +#define USAGE_USBDEVSOURCE RT_BIT_64(63) +#define USAGE_ALL (~(uint64_t)0) +/** @} */ + +#ifdef VBOX_WITH_GUEST_CONTROL +# define USAGE_GSTCTRL_RUN RT_BIT(0) +# define USAGE_GSTCTRL_START RT_BIT(1) +# define USAGE_GSTCTRL_COPYFROM RT_BIT(2) +# define USAGE_GSTCTRL_COPYTO RT_BIT(3) +# define USAGE_GSTCTRL_MKDIR RT_BIT(4) +# define USAGE_GSTCTRL_RMDIR RT_BIT(5) +# define USAGE_GSTCTRL_RM RT_BIT(6) +# define USAGE_GSTCTRL_MV RT_BIT(7) +# define USAGE_GSTCTRL_MKTEMP RT_BIT(8) +# define USAGE_GSTCTRL_LIST RT_BIT(9) +# define USAGE_GSTCTRL_CLOSEPROCESS RT_BIT(10) +# define USAGE_GSTCTRL_CLOSESESSION RT_BIT(11) +# define USAGE_GSTCTRL_STAT RT_BIT(12) +# define USAGE_GSTCTRL_UPDATEGA RT_BIT(13) +# define USAGE_GSTCTRL_WATCH RT_BIT(14) +#endif + + +typedef uint64_t USAGECATEGORY; + +/** command handler argument */ +struct HandlerArg +{ + int argc; + char **argv; + +#ifndef VBOX_ONLY_DOCS + ComPtr<IVirtualBox> virtualBox; + ComPtr<ISession> session; +#endif +}; + +/** flag whether we're in internal mode */ +extern bool g_fInternalMode; + +/** showVMInfo details */ +typedef enum +{ + VMINFO_NONE = 0, + VMINFO_STANDARD = 1, /**< standard details */ + VMINFO_FULL = 2, /**< both */ + VMINFO_MACHINEREADABLE = 3, /**< both, and make it machine readable */ + VMINFO_COMPACT = 4 +} VMINFO_DETAILS; + + +//////////////////////////////////////////////////////////////////////////////// +// +// global variables +// +//////////////////////////////////////////////////////////////////////////////// + +extern bool g_fDetailedProgress; // in VBoxManage.cpp + + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// +//////////////////////////////////////////////////////////////////////////////// + +/* VBoxManageHelp.cpp */ +void printUsage(USAGECATEGORY fCategory, uint32_t fSubCategory, PRTSTREAM pStrm); +RTEXITCODE errorSyntax(USAGECATEGORY fCategory, const char *pszFormat, ...); +RTEXITCODE errorSyntaxEx(USAGECATEGORY fCategory, uint32_t fSubCategory, const char *pszFormat, ...); +RTEXITCODE errorGetOpt(USAGECATEGORY fCategory, int rc, union RTGETOPTUNION const *pValueUnion); +RTEXITCODE errorGetOptEx(USAGECATEGORY fCategory, uint32_t fSubCategory, int rc, union RTGETOPTUNION const *pValueUnion); +RTEXITCODE errorArgument(const char *pszFormat, ...); + +void printUsageInternal(USAGECATEGORY fCategory, PRTSTREAM pStrm); + +#ifndef VBOX_ONLY_DOCS +void setCurrentCommand(enum HELP_CMD_VBOXMANAGE enmCommand); +void setCurrentSubcommand(uint64_t fCurSubcommandScope); + +void printUsage(PRTSTREAM pStrm); +void printHelp(PRTSTREAM pStrm); +RTEXITCODE errorNoSubcommand(void); +RTEXITCODE errorUnknownSubcommand(const char *pszSubCmd); +RTEXITCODE errorTooManyParameters(char **papszArgs); +RTEXITCODE errorGetOpt(int rcGetOpt, union RTGETOPTUNION const *pValueUnion); +RTEXITCODE errorSyntax(const char *pszFormat, ...); + +HRESULT showProgress(ComPtr<IProgress> progress); +#endif + +/* VBoxManage.cpp */ +void showLogo(PRTSTREAM pStrm); + +#ifndef VBOX_ONLY_DOCS +RTEXITCODE readPasswordFile(const char *pszFilename, com::Utf8Str *pPasswd); +RTEXITCODE readPasswordFromConsole(com::Utf8Str *pPassword, const char *pszPrompt, ...); + +RTEXITCODE handleInternalCommands(HandlerArg *a); +#endif /* !VBOX_ONLY_DOCS */ + +/* VBoxManageControlVM.cpp */ +RTEXITCODE handleControlVM(HandlerArg *a); +#ifndef VBOX_ONLY_DOCS +unsigned int getMaxNics(IVirtualBox* vbox, IMachine* mach); +#endif + +/* VBoxManageModifyVM.cpp */ +#ifndef VBOX_ONLY_DOCS +void parseGroups(const char *pcszGroups, com::SafeArray<BSTR> *pGroups); +#endif +RTEXITCODE handleModifyVM(HandlerArg *a); + +/* VBoxManageDebugVM.cpp */ +RTEXITCODE handleDebugVM(HandlerArg *a); + +/* VBoxManageGuestProp.cpp */ +extern void usageGuestProperty(PRTSTREAM pStrm, const char *pcszSep1, const char *pcszSep2); + +/* VBoxManageGuestCtrl.cpp */ +extern void usageGuestControl(PRTSTREAM pStrm, const char *pcszSep1, const char *pcszSep2, uint32_t fSubCategory); + +#ifndef VBOX_ONLY_DOCS +/* VBoxManageGuestProp.cpp */ +RTEXITCODE handleGuestProperty(HandlerArg *a); + +/* VBoxManageGuestCtrl.cpp */ +RTEXITCODE handleGuestControl(HandlerArg *a); + +/* VBoxManageVMInfo.cpp */ +HRESULT showSnapshots(ComPtr<ISnapshot> &rootSnapshot, + ComPtr<ISnapshot> ¤tSnapshot, + VMINFO_DETAILS details, + const com::Utf8Str &prefix = "", + int level = 0); +RTEXITCODE handleShowVMInfo(HandlerArg *a); +HRESULT showVMInfo(ComPtr<IVirtualBox> pVirtualBox, + ComPtr<IMachine> pMachine, + ComPtr<ISession> pSession, + VMINFO_DETAILS details = VMINFO_NONE); +const char *machineStateToName(MachineState_T machineState, bool fShort); +HRESULT showBandwidthGroups(ComPtr<IBandwidthControl> &bwCtrl, + VMINFO_DETAILS details); + +/* VBoxManageList.cpp */ +RTEXITCODE handleList(HandlerArg *a); + +/* VBoxManageMetrics.cpp */ +RTEXITCODE handleMetrics(HandlerArg *a); + +/* VBoxManageMisc.cpp */ +RTEXITCODE handleRegisterVM(HandlerArg *a); +RTEXITCODE handleUnregisterVM(HandlerArg *a); +RTEXITCODE handleCreateVM(HandlerArg *a); +RTEXITCODE handleCloneVM(HandlerArg *a); +RTEXITCODE handleStartVM(HandlerArg *a); +RTEXITCODE handleDiscardState(HandlerArg *a); +RTEXITCODE handleAdoptState(HandlerArg *a); +RTEXITCODE handleGetExtraData(HandlerArg *a); +RTEXITCODE handleSetExtraData(HandlerArg *a); +RTEXITCODE handleSetProperty(HandlerArg *a); +RTEXITCODE handleSharedFolder(HandlerArg *a); +RTEXITCODE handleExtPack(HandlerArg *a); +RTEXITCODE handleUnattended(HandlerArg *a); +RTEXITCODE handleMoveVM(HandlerArg *a); + +/* VBoxManageDisk.cpp */ +HRESULT openMedium(HandlerArg *a, const char *pszFilenameOrUuid, + DeviceType_T enmDevType, AccessMode_T enmAccessMode, + ComPtr<IMedium> &pMedium, bool fForceNewUuidOnOpen, + bool fSilent); +RTEXITCODE handleCreateMedium(HandlerArg *a); +RTEXITCODE handleModifyMedium(HandlerArg *a); +RTEXITCODE handleCloneMedium(HandlerArg *a); +RTEXITCODE handleMediumProperty(HandlerArg *a); +RTEXITCODE handleEncryptMedium(HandlerArg *a); +RTEXITCODE handleCheckMediumPassword(HandlerArg *a); +RTEXITCODE handleConvertFromRaw(HandlerArg *a); +HRESULT showMediumInfo(const ComPtr<IVirtualBox> &pVirtualBox, + const ComPtr<IMedium> &pMedium, + const char *pszParentUUID, + bool fOptLong); +RTEXITCODE handleShowMediumInfo(HandlerArg *a); +RTEXITCODE handleCloseMedium(HandlerArg *a); +RTEXITCODE handleMediumIO(HandlerArg *a); +int parseMediumType(const char *psz, MediumType_T *penmMediumType); +int parseBool(const char *psz, bool *pb); + +/* VBoxManageStorageController.cpp */ +RTEXITCODE handleStorageAttach(HandlerArg *a); +RTEXITCODE handleStorageController(HandlerArg *a); + +// VBoxManageImport.cpp +RTEXITCODE handleImportAppliance(HandlerArg *a); +RTEXITCODE handleExportAppliance(HandlerArg *a); + +// VBoxManageSnapshot.cpp +RTEXITCODE handleSnapshot(HandlerArg *a); + +/* VBoxManageUSB.cpp */ +RTEXITCODE handleUSBFilter(HandlerArg *a); +RTEXITCODE handleUSBDevSource(HandlerArg *a); + +/* VBoxManageHostonly.cpp */ +RTEXITCODE handleHostonlyIf(HandlerArg *a); + +/* VBoxManageDHCPServer.cpp */ +RTEXITCODE handleDHCPServer(HandlerArg *a); + +/* VBoxManageNATNetwork.cpp */ +RTEXITCODE handleNATNetwork(HandlerArg *a); + + +/* VBoxManageBandwidthControl.cpp */ +RTEXITCODE handleBandwidthControl(HandlerArg *a); + +#endif /* !VBOX_ONLY_DOCS */ + +#endif /* !VBOX_INCLUDED_SRC_VBoxManage_VBoxManage_h */ diff --git a/src/VBox/Frontends/VBoxManage/VBoxManage.rc b/src/VBox/Frontends/VBoxManage/VBoxManage.rc new file mode 100644 index 00000000..eaa3b632 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManage.rc @@ -0,0 +1,51 @@ +/* $Id: VBoxManage.rc $ */ +/** @file + * VBoxManage - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_APP + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Command Line Tool\0" + VALUE "InternalName", "VBoxManage\0" + VALUE "OriginalFilename", "VBoxManage.exe\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageAppliance.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageAppliance.cpp new file mode 100644 index 00000000..af80898c --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageAppliance.cpp @@ -0,0 +1,1516 @@ +/* $Id: VBoxManageAppliance.cpp $ */ +/** @file + * VBoxManage - The appliance-related commands. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_ONLY_DOCS + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef VBOX_ONLY_DOCS +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> + +#include <list> +#include <map> +#endif /* !VBOX_ONLY_DOCS */ + +#include <iprt/stream.h> +#include <iprt/getopt.h> +#include <iprt/ctype.h> +#include <iprt/path.h> +#include <iprt/file.h> + +#include <VBox/log.h> +#include <VBox/param.h> + +#include "VBoxManage.h" +using namespace com; + + +// funcs +/////////////////////////////////////////////////////////////////////////////// + +typedef std::map<Utf8Str, Utf8Str> ArgsMap; // pairs of strings like "vmname" => "newvmname" +typedef std::map<uint32_t, ArgsMap> ArgsMapsMap; // map of maps, one for each virtual system, sorted by index + +typedef std::map<uint32_t, bool> IgnoresMap; // pairs of numeric description entry indices +typedef std::map<uint32_t, IgnoresMap> IgnoresMapsMap; // map of maps, one for each virtual system, sorted by index + +static bool findArgValue(Utf8Str &strOut, + ArgsMap *pmapArgs, + const Utf8Str &strKey) +{ + if (pmapArgs) + { + ArgsMap::iterator it; + it = pmapArgs->find(strKey); + if (it != pmapArgs->end()) + { + strOut = it->second; + pmapArgs->erase(it); + return true; + } + } + + return false; +} + +static int parseImportOptions(const char *psz, com::SafeArray<ImportOptions_T> *options) +{ + int rc = VINF_SUCCESS; + while (psz && *psz && RT_SUCCESS(rc)) + { + 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(ImportOptions_KeepAllMACs); + else if (!RTStrNICmp(psz, "KeepNATMACs", len)) + options->push_back(ImportOptions_KeepNATMACs); + else if (!RTStrNICmp(psz, "ImportToVDI", len)) + options->push_back(ImportOptions_ImportToVDI); + else + rc = VERR_PARSE_ERROR; + } + if (pszComma) + psz += len + 1; + else + psz += len; + } + + return rc; +} + +static const RTGETOPTDEF g_aImportApplianceOptions[] = +{ + { "--dry-run", 'n', RTGETOPT_REQ_NOTHING }, + { "-dry-run", 'n', RTGETOPT_REQ_NOTHING }, // deprecated + { "--dryrun", 'n', RTGETOPT_REQ_NOTHING }, + { "-dryrun", 'n', RTGETOPT_REQ_NOTHING }, // deprecated + { "--detailed-progress", 'P', RTGETOPT_REQ_NOTHING }, + { "-detailed-progress", 'P', RTGETOPT_REQ_NOTHING }, // deprecated + { "--vsys", 's', RTGETOPT_REQ_UINT32 }, + { "-vsys", 's', RTGETOPT_REQ_UINT32 }, // deprecated + { "--ostype", 'o', RTGETOPT_REQ_STRING }, + { "-ostype", 'o', RTGETOPT_REQ_STRING }, // deprecated + { "--vmname", 'V', RTGETOPT_REQ_STRING }, + { "-vmname", 'V', RTGETOPT_REQ_STRING }, // deprecated + { "--settingsfile", 'S', RTGETOPT_REQ_STRING }, + { "--basefolder", 'p', RTGETOPT_REQ_STRING }, + { "--group", 'g', RTGETOPT_REQ_STRING }, + { "--memory", 'm', RTGETOPT_REQ_STRING }, + { "-memory", 'm', RTGETOPT_REQ_STRING }, // deprecated + { "--cpus", 'c', RTGETOPT_REQ_STRING }, + { "--description", 'd', RTGETOPT_REQ_STRING }, + { "--eula", 'L', RTGETOPT_REQ_STRING }, + { "-eula", 'L', RTGETOPT_REQ_STRING }, // deprecated + { "--unit", 'u', RTGETOPT_REQ_UINT32 }, + { "-unit", 'u', RTGETOPT_REQ_UINT32 }, // deprecated + { "--ignore", 'x', RTGETOPT_REQ_NOTHING }, + { "-ignore", 'x', RTGETOPT_REQ_NOTHING }, // deprecated + { "--scsitype", 'T', RTGETOPT_REQ_UINT32 }, + { "-scsitype", 'T', RTGETOPT_REQ_UINT32 }, // deprecated + { "--type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated + { "-type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated +#if 0 /* Changing the controller is fully valid, but the current design on how + the params are evaluated here doesn't allow two parameter for one + unit. The target disk path is more important. I leave it for future + improvments. */ + { "--controller", 'C', RTGETOPT_REQ_STRING }, +#endif + { "--disk", 'D', RTGETOPT_REQ_STRING }, + { "--options", 'O', RTGETOPT_REQ_STRING }, +}; + +RTEXITCODE handleImportAppliance(HandlerArg *arg) +{ + HRESULT rc = S_OK; + + Utf8Str strOvfFilename; + bool fExecute = true; // if true, then we actually do the import + com::SafeArray<ImportOptions_T> options; + uint32_t ulCurVsys = (uint32_t)-1; + uint32_t ulCurUnit = (uint32_t)-1; + // for each --vsys X command, maintain a map of command line items + // (we'll parse them later after interpreting the OVF, when we can + // actually check whether they make sense semantically) + ArgsMapsMap mapArgsMapsPerVsys; + IgnoresMapsMap mapIgnoresMapsPerVsys; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + // start at 0 because main() has hacked both the argc and argv given to us + RTGetOptInit(&GetState, arg->argc, arg->argv, g_aImportApplianceOptions, RT_ELEMENTS(g_aImportApplianceOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'n': // --dry-run + fExecute = false; + break; + + case 'P': // --detailed-progress + g_fDetailedProgress = true; + break; + + case 's': // --vsys + ulCurVsys = ValueUnion.u32; + ulCurUnit = (uint32_t)-1; + break; + + case 'o': // --ostype + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["ostype"] = ValueUnion.psz; + break; + + case 'V': // --vmname + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz; + break; + + case 'S': // --settingsfile + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["settingsfile"] = ValueUnion.psz; + break; + + case 'p': // --basefolder + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["basefolder"] = ValueUnion.psz; + break; + + case 'g': // --group + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["group"] = ValueUnion.psz; + break; + + case 'd': // --description + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz; + break; + + case 'L': // --eula + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz; + break; + + case 'm': // --memory + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["memory"] = ValueUnion.psz; + break; + + case 'c': // --cpus + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["cpus"] = ValueUnion.psz; + break; + + case 'u': // --unit + ulCurUnit = ValueUnion.u32; + break; + + case 'x': // --ignore + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + if (ulCurUnit == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong); + mapIgnoresMapsPerVsys[ulCurVsys][ulCurUnit] = true; + break; + + case 'T': // --scsitype + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + if (ulCurUnit == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("scsitype%u", ulCurUnit)] = ValueUnion.psz; + break; + + case 'C': // --controller + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + if (ulCurUnit == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("controller%u", ulCurUnit)] = ValueUnion.psz; + break; + + case 'D': // --disk + if (ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + if (ulCurUnit == (uint32_t)-1) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Option \"%s\" requires preceding --unit argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("disk%u", ulCurUnit)] = ValueUnion.psz; + break; + + case 'O': // --options + if (RT_FAILURE(parseImportOptions(ValueUnion.psz, &options))) + return errorArgument("Invalid import options '%s'\n", ValueUnion.psz); + break; + + case VINF_GETOPT_NOT_OPTION: + if (strOvfFilename.isEmpty()) + strOvfFilename = ValueUnion.psz; + else + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option -%c", c); + else + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Invalid option case %i", c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "unknown option: %s\n", ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(USAGE_IMPORTAPPLIANCE, "error: %Rrs", c); + } + } + + if (strOvfFilename.isEmpty()) + return errorSyntax(USAGE_IMPORTAPPLIANCE, "Not enough arguments for \"import\" command."); + + do + { + ComPtr<IAppliance> pAppliance; + CHECK_ERROR_BREAK(arg->virtualBox, CreateAppliance(pAppliance.asOutParam())); + + char *pszAbsFilePath; + if (strOvfFilename.startsWith("S3://", RTCString::CaseInsensitive) || + strOvfFilename.startsWith("SunCloud://", RTCString::CaseInsensitive) || + strOvfFilename.startsWith("webdav://", RTCString::CaseInsensitive)) + pszAbsFilePath = RTStrDup(strOvfFilename.c_str()); + else + pszAbsFilePath = RTPathAbsDup(strOvfFilename.c_str()); + ComPtr<IProgress> progressRead; + CHECK_ERROR_BREAK(pAppliance, Read(Bstr(pszAbsFilePath).raw(), + progressRead.asOutParam())); + RTStrFree(pszAbsFilePath); + + rc = showProgress(progressRead); + CHECK_PROGRESS_ERROR_RET(progressRead, ("Appliance read failed"), RTEXITCODE_FAILURE); + + Bstr path; /* fetch the path, there is stuff like username/password removed if any */ + CHECK_ERROR_BREAK(pAppliance, COMGETTER(Path)(path.asOutParam())); + // call interpret(); this can yield both warnings and errors, so we need + // to tinker with the error info a bit + RTStrmPrintf(g_pStdErr, "Interpreting %ls...\n", path.raw()); + rc = pAppliance->Interpret(); + com::ErrorInfo info0(pAppliance, COM_IIDOF(IAppliance)); + + com::SafeArray<BSTR> aWarnings; + if (SUCCEEDED(pAppliance->GetWarnings(ComSafeArrayAsOutParam(aWarnings)))) + { + size_t cWarnings = aWarnings.size(); + for (unsigned i = 0; i < cWarnings; ++i) + { + Bstr bstrWarning(aWarnings[i]); + RTMsgWarning("%ls.", bstrWarning.raw()); + } + } + + if (FAILED(rc)) // during interpret, after printing warnings + { + com::GluePrintErrorInfo(info0); + com::GluePrintErrorContext("Interpret", __FILE__, __LINE__); + break; + } + + RTStrmPrintf(g_pStdErr, "OK.\n"); + + // fetch all disks + com::SafeArray<BSTR> retDisks; + CHECK_ERROR_BREAK(pAppliance, + COMGETTER(Disks)(ComSafeArrayAsOutParam(retDisks))); + if (retDisks.size() > 0) + { + RTPrintf("Disks:\n"); + for (unsigned i = 0; i < retDisks.size(); i++) + RTPrintf(" %ls\n", retDisks[i]); + RTPrintf("\n"); + } + + // fetch virtual system descriptions + com::SafeIfaceArray<IVirtualSystemDescription> aVirtualSystemDescriptions; + CHECK_ERROR_BREAK(pAppliance, + COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions))); + + size_t cVirtualSystemDescriptions = aVirtualSystemDescriptions.size(); + + // match command line arguments with virtual system descriptions; + // this is only to sort out invalid indices at this time + ArgsMapsMap::const_iterator it; + for (it = mapArgsMapsPerVsys.begin(); + it != mapArgsMapsPerVsys.end(); + ++it) + { + uint32_t ulVsys = it->first; + if (ulVsys >= cVirtualSystemDescriptions) + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Invalid index %RI32 with -vsys option; the OVF contains only %zu virtual system(s).", + ulVsys, cVirtualSystemDescriptions); + } + + uint32_t cLicensesInTheWay = 0; + + // dump virtual system descriptions and match command-line arguments + if (cVirtualSystemDescriptions > 0) + { + for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i) + { + com::SafeArray<VirtualSystemDescriptionType_T> retTypes; + com::SafeArray<BSTR> aRefs; + com::SafeArray<BSTR> aOvfValues; + com::SafeArray<BSTR> aVBoxValues; + com::SafeArray<BSTR> aExtraConfigValues; + CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i], + GetDescription(ComSafeArrayAsOutParam(retTypes), + ComSafeArrayAsOutParam(aRefs), + ComSafeArrayAsOutParam(aOvfValues), + ComSafeArrayAsOutParam(aVBoxValues), + ComSafeArrayAsOutParam(aExtraConfigValues))); + + RTPrintf("Virtual system %u:\n", i); + + // look up the corresponding command line options, if any + ArgsMap *pmapArgs = NULL; + ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i); + if (itm != mapArgsMapsPerVsys.end()) + pmapArgs = &itm->second; + + // this collects the final values for setFinalValues() + com::SafeArray<BOOL> aEnabled(retTypes.size()); + com::SafeArray<BSTR> aFinalValues(retTypes.size()); + + for (unsigned a = 0; a < retTypes.size(); ++a) + { + VirtualSystemDescriptionType_T t = retTypes[a]; + + Utf8Str strOverride; + + Bstr bstrFinalValue = aVBoxValues[a]; + + bool fIgnoreThis = mapIgnoresMapsPerVsys[i][a]; + + aEnabled[a] = true; + + switch (t) + { + case VirtualSystemDescriptionType_OS: + if (findArgValue(strOverride, pmapArgs, "ostype")) + { + bstrFinalValue = strOverride; + RTPrintf("%2u: OS type specified with --ostype: \"%ls\"\n", + a, bstrFinalValue.raw()); + } + else + RTPrintf("%2u: Suggested OS type: \"%ls\"" + "\n (change with \"--vsys %u --ostype <type>\"; use \"list ostypes\" to list all possible values)\n", + a, bstrFinalValue.raw(), i); + break; + + case VirtualSystemDescriptionType_Name: + if (findArgValue(strOverride, pmapArgs, "vmname")) + { + bstrFinalValue = strOverride; + RTPrintf("%2u: VM name specified with --vmname: \"%ls\"\n", + a, bstrFinalValue.raw()); + } + else + RTPrintf("%2u: Suggested VM name \"%ls\"" + "\n (change with \"--vsys %u --vmname <name>\")\n", + a, bstrFinalValue.raw(), i); + break; + + case VirtualSystemDescriptionType_Product: + RTPrintf("%2u: Product (ignored): %ls\n", + a, aVBoxValues[a]); + break; + + case VirtualSystemDescriptionType_ProductUrl: + RTPrintf("%2u: ProductUrl (ignored): %ls\n", + a, aVBoxValues[a]); + break; + + case VirtualSystemDescriptionType_Vendor: + RTPrintf("%2u: Vendor (ignored): %ls\n", + a, aVBoxValues[a]); + break; + + case VirtualSystemDescriptionType_VendorUrl: + RTPrintf("%2u: VendorUrl (ignored): %ls\n", + a, aVBoxValues[a]); + break; + + case VirtualSystemDescriptionType_Version: + RTPrintf("%2u: Version (ignored): %ls\n", + a, aVBoxValues[a]); + break; + + case VirtualSystemDescriptionType_Description: + if (findArgValue(strOverride, pmapArgs, "description")) + { + bstrFinalValue = strOverride; + RTPrintf("%2u: Description specified with --description: \"%ls\"\n", + a, bstrFinalValue.raw()); + } + else + RTPrintf("%2u: Description \"%ls\"" + "\n (change with \"--vsys %u --description <desc>\")\n", + a, bstrFinalValue.raw(), i); + break; + + case VirtualSystemDescriptionType_License: + ++cLicensesInTheWay; + if (findArgValue(strOverride, pmapArgs, "eula")) + { + if (strOverride == "show") + { + RTPrintf("%2u: End-user license agreement" + "\n (accept with \"--vsys %u --eula accept\"):" + "\n\n%ls\n\n", + a, i, bstrFinalValue.raw()); + } + else if (strOverride == "accept") + { + RTPrintf("%2u: End-user license agreement (accepted)\n", + a); + --cLicensesInTheWay; + } + else + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Argument to --eula must be either \"show\" or \"accept\"."); + } + else + RTPrintf("%2u: End-user license agreement" + "\n (display with \"--vsys %u --eula show\";" + "\n accept with \"--vsys %u --eula accept\")\n", + a, i, i); + break; + + case VirtualSystemDescriptionType_CPU: + if (findArgValue(strOverride, pmapArgs, "cpus")) + { + uint32_t cCPUs; + if ( strOverride.toInt(cCPUs) == VINF_SUCCESS + && cCPUs >= VMM_MIN_CPU_COUNT + && cCPUs <= VMM_MAX_CPU_COUNT + ) + { + bstrFinalValue = strOverride; + RTPrintf("%2u: No. of CPUs specified with --cpus: %ls\n", + a, bstrFinalValue.raw()); + } + else + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Argument to --cpus option must be a number greater than %d and less than %d.", + VMM_MIN_CPU_COUNT - 1, VMM_MAX_CPU_COUNT + 1); + } + else + RTPrintf("%2u: Number of CPUs: %ls\n (change with \"--vsys %u --cpus <n>\")\n", + a, bstrFinalValue.raw(), i); + break; + + case VirtualSystemDescriptionType_Memory: + { + if (findArgValue(strOverride, pmapArgs, "memory")) + { + uint32_t ulMemMB; + if (VINF_SUCCESS == strOverride.toInt(ulMemMB)) + { + bstrFinalValue = strOverride; + RTPrintf("%2u: Guest memory specified with --memory: %ls MB\n", + a, bstrFinalValue.raw()); + } + else + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Argument to --memory option must be a non-negative number."); + } + else + RTPrintf("%2u: Guest memory: %ls MB\n (change with \"--vsys %u --memory <MB>\")\n", + a, bstrFinalValue.raw(), i); + break; + } + + case VirtualSystemDescriptionType_HardDiskControllerIDE: + if (fIgnoreThis) + { + RTPrintf("%2u: IDE controller, type %ls -- disabled\n", + a, + aVBoxValues[a]); + aEnabled[a] = false; + } + else + RTPrintf("%2u: IDE controller, type %ls" + "\n (disable with \"--vsys %u --unit %u --ignore\")\n", + a, + aVBoxValues[a], + i, a); + break; + + case VirtualSystemDescriptionType_HardDiskControllerSATA: + if (fIgnoreThis) + { + RTPrintf("%2u: SATA controller, type %ls -- disabled\n", + a, + aVBoxValues[a]); + aEnabled[a] = false; + } + else + RTPrintf("%2u: SATA controller, type %ls" + "\n (disable with \"--vsys %u --unit %u --ignore\")\n", + a, + aVBoxValues[a], + i, a); + break; + + case VirtualSystemDescriptionType_HardDiskControllerSAS: + if (fIgnoreThis) + { + RTPrintf("%2u: SAS controller, type %ls -- disabled\n", + a, + aVBoxValues[a]); + aEnabled[a] = false; + } + else + RTPrintf("%2u: SAS controller, type %ls" + "\n (disable with \"--vsys %u --unit %u --ignore\")\n", + a, + aVBoxValues[a], + i, a); + break; + + case VirtualSystemDescriptionType_HardDiskControllerSCSI: + if (fIgnoreThis) + { + RTPrintf("%2u: SCSI controller, type %ls -- disabled\n", + a, + aVBoxValues[a]); + aEnabled[a] = false; + } + else + { + Utf8StrFmt strTypeArg("scsitype%u", a); + if (findArgValue(strOverride, pmapArgs, strTypeArg)) + { + bstrFinalValue = strOverride; + RTPrintf("%2u: SCSI controller, type set with --unit %u --scsitype: \"%ls\"\n", + a, + a, + bstrFinalValue.raw()); + } + else + RTPrintf("%2u: SCSI controller, type %ls" + "\n (change with \"--vsys %u --unit %u --scsitype {BusLogic|LsiLogic}\";" + "\n disable with \"--vsys %u --unit %u --ignore\")\n", + a, + aVBoxValues[a], + i, a, i, a); + } + break; + + case VirtualSystemDescriptionType_HardDiskImage: + if (fIgnoreThis) + { + RTPrintf("%2u: Hard disk image: source image=%ls -- disabled\n", + a, + aOvfValues[a]); + aEnabled[a] = false; + } + else + { + Utf8StrFmt strTypeArg("disk%u", a); + RTCList<ImportOptions_T> optionsList = options.toList(); + + bstrFinalValue = aVBoxValues[a]; + + if (findArgValue(strOverride, pmapArgs, strTypeArg)) + { + if (!optionsList.contains(ImportOptions_ImportToVDI)) + { + RTUUID uuid; + /* Check if this is a uuid. If so, don't touch. */ + int vrc = RTUuidFromStr(&uuid, strOverride.c_str()); + if (vrc != VINF_SUCCESS) + { + /* Make the path absolute. */ + if (!RTPathStartsWithRoot(strOverride.c_str())) + { + char pszPwd[RTPATH_MAX]; + vrc = RTPathGetCurrent(pszPwd, RTPATH_MAX); + if (RT_SUCCESS(vrc)) + strOverride = Utf8Str(pszPwd).append(RTPATH_SLASH).append(strOverride); + } + } + bstrFinalValue = strOverride; + } + else + { + //print some error about incompatible command-line arguments + return errorSyntax(USAGE_IMPORTAPPLIANCE, + "Option --ImportToVDI shall not be used together with " + "manually set target path."); + + } + + RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n", + a, + aOvfValues[a], + bstrFinalValue.raw(), + aExtraConfigValues[a]); + } +#if 0 /* Changing the controller is fully valid, but the current design on how + the params are evaluated here doesn't allow two parameter for one + unit. The target disk path is more important I leave it for future + improvments. */ + Utf8StrFmt strTypeArg("controller%u", a); + if (findArgValue(strOverride, pmapArgs, strTypeArg)) + { + // strOverride now has the controller index as a number, but we + // need a "controller=X" format string + strOverride = Utf8StrFmt("controller=%s", strOverride.c_str()); + Bstr bstrExtraConfigValue = strOverride; + bstrExtraConfigValue.detachTo(&aExtraConfigValues[a]); + RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls\n", + a, + aOvfValues[a], + aVBoxValues[a], + aExtraConfigValues[a]); + } +#endif + else + { + strOverride = aVBoxValues[a]; + + /* + * Current solution isn't optimal. + * Better way is to provide API call for function + * Appliance::i_findMediumFormatFromDiskImage() + * and creating one new function which returns + * struct ovf::DiskImage for currently processed disk. + */ + + /* + * if user wants to convert all imported disks to VDI format + * we need to replace files extensions to "vdi" + * except CD/DVD disks + */ + if (optionsList.contains(ImportOptions_ImportToVDI)) + { + ComPtr<IVirtualBox> pVirtualBox = arg->virtualBox; + ComPtr<ISystemProperties> systemProperties; + com::SafeIfaceArray<IMediumFormat> mediumFormats; + Bstr bstrFormatName; + + CHECK_ERROR(pVirtualBox, + COMGETTER(SystemProperties)(systemProperties.asOutParam())); + + CHECK_ERROR(systemProperties, + COMGETTER(MediumFormats)(ComSafeArrayAsOutParam(mediumFormats))); + + /* go through all supported media formats and store files extensions only for RAW */ + com::SafeArray<BSTR> extensions; + + for (unsigned j = 0; j < mediumFormats.size(); ++j) + { + com::SafeArray<DeviceType_T> deviceType; + ComPtr<IMediumFormat> mediumFormat = mediumFormats[j]; + CHECK_ERROR(mediumFormat, COMGETTER(Name)(bstrFormatName.asOutParam())); + Utf8Str strFormatName = Utf8Str(bstrFormatName); + + if (strFormatName.compare("RAW", Utf8Str::CaseInsensitive) == 0) + { + /* getting files extensions for "RAW" format */ + CHECK_ERROR(mediumFormat, + DescribeFileExtensions(ComSafeArrayAsOutParam(extensions), + ComSafeArrayAsOutParam(deviceType))); + break; + } + } + + /* go through files extensions for RAW format and compare them with + * extension of current file + */ + bool fReplace = true; + + const char *pszExtension = RTPathSuffix(strOverride.c_str()); + if (pszExtension) + pszExtension++; + + for (unsigned j = 0; j < extensions.size(); ++j) + { + Bstr bstrExt(extensions[j]); + Utf8Str strExtension(bstrExt); + if(strExtension.compare(pszExtension, Utf8Str::CaseInsensitive) == 0) + { + fReplace = false; + break; + } + } + + if (fReplace) + { + strOverride = strOverride.stripSuffix(); + strOverride = strOverride.append(".").append("vdi"); + } + } + + bstrFinalValue = strOverride; + + RTPrintf("%2u: Hard disk image: source image=%ls, target path=%ls, %ls" + "\n (change target path with \"--vsys %u --unit %u --disk path\";" + "\n disable with \"--vsys %u --unit %u --ignore\")\n", + a, + aOvfValues[a], + bstrFinalValue.raw(), + aExtraConfigValues[a], + i, a, i, a); + } + } + break; + + case VirtualSystemDescriptionType_CDROM: + if (fIgnoreThis) + { + RTPrintf("%2u: CD-ROM -- disabled\n", + a); + aEnabled[a] = false; + } + else + RTPrintf("%2u: CD-ROM" + "\n (disable with \"--vsys %u --unit %u --ignore\")\n", + a, i, a); + break; + + case VirtualSystemDescriptionType_Floppy: + if (fIgnoreThis) + { + RTPrintf("%2u: Floppy -- disabled\n", + a); + aEnabled[a] = false; + } + else + RTPrintf("%2u: Floppy" + "\n (disable with \"--vsys %u --unit %u --ignore\")\n", + a, i, a); + break; + + case VirtualSystemDescriptionType_NetworkAdapter: + RTPrintf("%2u: Network adapter: orig %ls, config %ls, extra %ls\n", /// @todo implement once we have a plan for the back-end + a, + aOvfValues[a], + aVBoxValues[a], + aExtraConfigValues[a]); + break; + + case VirtualSystemDescriptionType_USBController: + if (fIgnoreThis) + { + RTPrintf("%2u: USB controller -- disabled\n", + a); + aEnabled[a] = false; + } + else + RTPrintf("%2u: USB controller" + "\n (disable with \"--vsys %u --unit %u --ignore\")\n", + a, i, a); + break; + + case VirtualSystemDescriptionType_SoundCard: + if (fIgnoreThis) + { + RTPrintf("%2u: Sound card \"%ls\" -- disabled\n", + a, + aOvfValues[a]); + aEnabled[a] = false; + } + else + RTPrintf("%2u: Sound card (appliance expects \"%ls\", can change on import)" + "\n (disable with \"--vsys %u --unit %u --ignore\")\n", + a, + aOvfValues[a], + i, + a); + break; + + case VirtualSystemDescriptionType_SettingsFile: + if (findArgValue(strOverride, pmapArgs, "settingsfile")) + { + bstrFinalValue = strOverride; + RTPrintf("%2u: VM settings file name specified with --settingsfile: \"%ls\"\n", + a, bstrFinalValue.raw()); + } + else + RTPrintf("%2u: Suggested VM settings file name \"%ls\"" + "\n (change with \"--vsys %u --settingsfile <filename>\")\n", + a, bstrFinalValue.raw(), i); + break; + + case VirtualSystemDescriptionType_BaseFolder: + if (findArgValue(strOverride, pmapArgs, "basefolder")) + { + bstrFinalValue = strOverride; + RTPrintf("%2u: VM base folder specified with --basefolder: \"%ls\"\n", + a, bstrFinalValue.raw()); + } + else + RTPrintf("%2u: Suggested VM base folder \"%ls\"" + "\n (change with \"--vsys %u --basefolder <path>\")\n", + a, bstrFinalValue.raw(), i); + break; + + case VirtualSystemDescriptionType_PrimaryGroup: + if (findArgValue(strOverride, pmapArgs, "group")) + { + bstrFinalValue = strOverride; + RTPrintf("%2u: VM group specified with --group: \"%ls\"\n", + a, bstrFinalValue.raw()); + } + else + RTPrintf("%2u: Suggested VM group \"%ls\"" + "\n (change with \"--vsys %u --group <group>\")\n", + a, bstrFinalValue.raw(), i); + break; + + case VirtualSystemDescriptionType_CloudInstanceShape: + case VirtualSystemDescriptionType_CloudDomain: + case VirtualSystemDescriptionType_CloudBootDiskSize: + case VirtualSystemDescriptionType_CloudBucket: + case VirtualSystemDescriptionType_CloudOCIVCN: + case VirtualSystemDescriptionType_CloudPublicIP: + case VirtualSystemDescriptionType_CloudProfileName: + case VirtualSystemDescriptionType_CloudOCISubnet: + case VirtualSystemDescriptionType_CloudKeepObject: + case VirtualSystemDescriptionType_CloudLaunchInstance: + case VirtualSystemDescriptionType_Miscellaneous: + /** @todo VirtualSystemDescriptionType_Miscellaneous? */ + break; + + case VirtualSystemDescriptionType_Ignore: +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case VirtualSystemDescriptionType_32BitHack: +#endif + break; + } + + bstrFinalValue.detachTo(&aFinalValues[a]); + } + + if (fExecute) + CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i], + SetFinalValues(ComSafeArrayAsInParam(aEnabled), + ComSafeArrayAsInParam(aFinalValues), + ComSafeArrayAsInParam(aExtraConfigValues))); + + } // for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i) + + if (cLicensesInTheWay == 1) + RTMsgError("Cannot import until the license agreement listed above is accepted."); + else if (cLicensesInTheWay > 1) + RTMsgError("Cannot import until the %c license agreements listed above are accepted.", cLicensesInTheWay); + + if (!cLicensesInTheWay && fExecute) + { + // go! + ComPtr<IProgress> progress; + CHECK_ERROR_BREAK(pAppliance, + ImportMachines(ComSafeArrayAsInParam(options), progress.asOutParam())); + + rc = showProgress(progress); + CHECK_PROGRESS_ERROR_RET(progress, ("Appliance import failed"), RTEXITCODE_FAILURE); + + if (SUCCEEDED(rc)) + RTPrintf("Successfully imported the appliance.\n"); + } + } // end if (aVirtualSystemDescriptions.size() > 0) + } while (0); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static int parseExportOptions(const char *psz, com::SafeArray<ExportOptions_T> *options) +{ + int rc = VINF_SUCCESS; + while (psz && *psz && RT_SUCCESS(rc)) + { + size_t len; + const char *pszComma = strchr(psz, ','); + if (pszComma) + len = pszComma - psz; + else + len = strlen(psz); + if (len > 0) + { + if (!RTStrNICmp(psz, "CreateManifest", len)) + options->push_back(ExportOptions_CreateManifest); + else if (!RTStrNICmp(psz, "manifest", len)) + options->push_back(ExportOptions_CreateManifest); + else if (!RTStrNICmp(psz, "ExportDVDImages", len)) + options->push_back(ExportOptions_ExportDVDImages); + else if (!RTStrNICmp(psz, "iso", len)) + options->push_back(ExportOptions_ExportDVDImages); + else if (!RTStrNICmp(psz, "StripAllMACs", len)) + options->push_back(ExportOptions_StripAllMACs); + else if (!RTStrNICmp(psz, "nomacs", len)) + options->push_back(ExportOptions_StripAllMACs); + else if (!RTStrNICmp(psz, "StripAllNonNATMACs", len)) + options->push_back(ExportOptions_StripAllNonNATMACs); + else if (!RTStrNICmp(psz, "nomacsbutnat", len)) + options->push_back(ExportOptions_StripAllNonNATMACs); + else + rc = VERR_PARSE_ERROR; + } + if (pszComma) + psz += len + 1; + else + psz += len; + } + + return rc; +} + +static const RTGETOPTDEF g_aExportOptions[] = +{ + { "--output", 'o', RTGETOPT_REQ_STRING }, + { "--legacy09", 'l', RTGETOPT_REQ_NOTHING }, + { "--ovf09", 'l', RTGETOPT_REQ_NOTHING }, + { "--ovf10", '1', RTGETOPT_REQ_NOTHING }, + { "--ovf20", '2', RTGETOPT_REQ_NOTHING }, + { "--opc10", 'c', RTGETOPT_REQ_NOTHING }, + { "--manifest", 'm', RTGETOPT_REQ_NOTHING }, // obsoleted by --options + { "--iso", 'I', RTGETOPT_REQ_NOTHING }, // obsoleted by --options + { "--vsys", 's', RTGETOPT_REQ_UINT32 }, + { "--vmname", 'V', RTGETOPT_REQ_STRING }, + { "--product", 'p', RTGETOPT_REQ_STRING }, + { "--producturl", 'P', RTGETOPT_REQ_STRING }, + { "--vendor", 'n', RTGETOPT_REQ_STRING }, + { "--vendorurl", 'N', RTGETOPT_REQ_STRING }, + { "--version", 'v', RTGETOPT_REQ_STRING }, + { "--description", 'd', RTGETOPT_REQ_STRING }, + { "--eula", 'e', RTGETOPT_REQ_STRING }, + { "--eulafile", 'E', RTGETOPT_REQ_STRING }, + { "--options", 'O', RTGETOPT_REQ_STRING }, + { "--cloud", 'C', RTGETOPT_REQ_UINT32 }, + { "--cloudshape", 'S', RTGETOPT_REQ_STRING }, + { "--clouddomain", 'D', RTGETOPT_REQ_STRING }, + { "--clouddisksize", 'R', RTGETOPT_REQ_STRING }, + { "--cloudbucket", 'B', RTGETOPT_REQ_STRING }, + { "--cloudocivcn", 'Q', RTGETOPT_REQ_STRING }, + { "--cloudpublicip", 'A', RTGETOPT_REQ_STRING }, + { "--cloudprofile", 'F', RTGETOPT_REQ_STRING }, + { "--cloudocisubnet", 'T', RTGETOPT_REQ_STRING }, + { "--cloudkeepobject", 'K', RTGETOPT_REQ_STRING }, + { "--cloudlaunchinstance", 'L', RTGETOPT_REQ_STRING }, +}; + +enum +{ + NOT_SET, LOCAL, CLOUD +} exportType; + +RTEXITCODE handleExportAppliance(HandlerArg *a) +{ + HRESULT rc = S_OK; + + Utf8Str strOutputFile; + Utf8Str strOvfFormat("ovf-1.0"); // the default export version + bool fManifest = false; // the default + bool fCloud = false; // the default + exportType = NOT_SET; + bool fExportISOImages = false; // the default + com::SafeArray<ExportOptions_T> options; + std::list< ComPtr<IMachine> > llMachines; + + uint32_t ulCurVsys = (uint32_t)-1; + // for each --vsys X command, maintain a map of command line items + ArgsMapsMap mapArgsMapsPerVsys; + do + { + 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_aExportOptions, + RT_ELEMENTS(g_aExportOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + + Utf8Str strProductUrl; + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'o': // --output + if (strOutputFile.length()) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "You can only specify --output once."); + else + strOutputFile = ValueUnion.psz; + break; + + case 'l': // --legacy09/--ovf09 + strOvfFormat = "ovf-0.9"; + break; + + case '1': // --ovf10 + strOvfFormat = "ovf-1.0"; + break; + + case '2': // --ovf20 + strOvfFormat = "ovf-2.0"; + break; + + case 'c': // --opc + strOvfFormat = "opc-1.0"; + break; + + case 'I': // --iso + fExportISOImages = true; + break; + + case 'm': // --manifest + fManifest = true; + break; + + case 's': // --vsys + if (fCloud == false && exportType == NOT_SET) + exportType = LOCAL; + + if (exportType != LOCAL) + return errorSyntax(USAGE_EXPORTAPPLIANCE, + "Option \"%s\" can't be used together with \"--cloud\" argument.", + GetState.pDef->pszLong); + + ulCurVsys = ValueUnion.u32; + break; + + case 'V': // --vmname + if (exportType == NOT_SET || ulCurVsys == (uint32_t)-1) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys or --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz; + break; + + case 'p': // --product + if (exportType != LOCAL) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["product"] = ValueUnion.psz; + break; + + case 'P': // --producturl + if (exportType != LOCAL) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["producturl"] = ValueUnion.psz; + break; + + case 'n': // --vendor + if (exportType != LOCAL) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["vendor"] = ValueUnion.psz; + break; + + case 'N': // --vendorurl + if (exportType != LOCAL) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["vendorurl"] = ValueUnion.psz; + break; + + case 'v': // --version + if (exportType != LOCAL) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["version"] = ValueUnion.psz; + break; + + case 'd': // --description + if (exportType != LOCAL) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz; + break; + + case 'e': // --eula + if (exportType != LOCAL) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz; + break; + + case 'E': // --eulafile + if (exportType != LOCAL) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --vsys argument.", GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["eulafile"] = ValueUnion.psz; + break; + + case 'O': // --options + if (RT_FAILURE(parseExportOptions(ValueUnion.psz, &options))) + return errorArgument("Invalid export options '%s'\n", ValueUnion.psz); + break; + + /*--cloud and --vsys are orthogonal, only one must be presented*/ + case 'C': // --cloud + if (fCloud == false && exportType == NOT_SET) + { + fCloud = true; + exportType = CLOUD; + } + + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, + "Option \"%s\" can't be used together with \"--vsys\" argument.", + GetState.pDef->pszLong); + + ulCurVsys = ValueUnion.u32; + break; + + /* Cloud export settings */ + case 'S': // --cloudshape + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["cloudshape"] = ValueUnion.psz; + break; + + case 'D': // --clouddomain + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["clouddomain"] = ValueUnion.psz; + break; + + case 'R': // --clouddisksize + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["clouddisksize"] = ValueUnion.psz; + break; + + case 'B': // --cloudbucket + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz; + break; + + case 'Q': // --cloudocivcn + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["cloudocivcn"] = ValueUnion.psz; + break; + + case 'A': // --cloudpublicip + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["cloudpublicip"] = ValueUnion.psz; + break; + + case 'F': // --cloudprofile + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz; + break; + + case 'T': // --cloudocisubnet + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["cloudocisubnet"] = ValueUnion.psz; + break; + + case 'K': // --cloudkeepobject + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["cloudkeepobject"] = ValueUnion.psz; + break; + + case 'L': // --cloudlaunchinstance + if (exportType != CLOUD) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Option \"%s\" requires preceding --cloud argument.", + GetState.pDef->pszLong); + mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchinstance"] = ValueUnion.psz; + break; + + case VINF_GETOPT_NOT_OPTION: + { + Utf8Str strMachine(ValueUnion.psz); + // must be machine: try UUID or name + ComPtr<IMachine> machine; + CHECK_ERROR_BREAK(a->virtualBox, FindMachine(Bstr(strMachine).raw(), + machine.asOutParam())); + if (machine) + llMachines.push_back(machine); + break; + } + + default: + if (c > 0) + { + if (RT_C_IS_GRAPH(c)) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: -%c", c); + else + return errorSyntax(USAGE_EXPORTAPPLIANCE, "unhandled option: %i", c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "unknown option: %s", ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(USAGE_EXPORTAPPLIANCE, "%Rrs", c); + } + + if (FAILED(rc)) + break; + } + + if (FAILED(rc)) + break; + + if (llMachines.empty()) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "At least one machine must be specified with the export command."); + if (!strOutputFile.length()) + return errorSyntax(USAGE_EXPORTAPPLIANCE, "Missing --output argument with export command."); + + // match command line arguments with the machines count + // this is only to sort out invalid indices at this time + ArgsMapsMap::const_iterator it; + for (it = mapArgsMapsPerVsys.begin(); + it != mapArgsMapsPerVsys.end(); + ++it) + { + uint32_t ulVsys = it->first; + if (ulVsys >= llMachines.size()) + return errorSyntax(USAGE_EXPORTAPPLIANCE, + "Invalid index %RI32 with -vsys option; you specified only %zu virtual system(s).", + ulVsys, llMachines.size()); + } + + ComPtr<IAppliance> pAppliance; + CHECK_ERROR_BREAK(a->virtualBox, CreateAppliance(pAppliance.asOutParam())); + + char *pszAbsFilePath = 0; + if (strOutputFile.startsWith("S3://", RTCString::CaseInsensitive) || + strOutputFile.startsWith("SunCloud://", RTCString::CaseInsensitive) || + strOutputFile.startsWith("webdav://", RTCString::CaseInsensitive) || + strOutputFile.startsWith("OCI://", RTCString::CaseInsensitive)) + pszAbsFilePath = RTStrDup(strOutputFile.c_str()); + else + pszAbsFilePath = RTPathAbsDup(strOutputFile.c_str()); + + std::list< ComPtr<IMachine> >::iterator itM; + uint32_t i=0; + for (itM = llMachines.begin(); + itM != llMachines.end(); + ++itM, ++i) + { + ComPtr<IMachine> pMachine = *itM; + ComPtr<IVirtualSystemDescription> pVSD; + CHECK_ERROR_BREAK(pMachine, ExportTo(pAppliance, Bstr(pszAbsFilePath).raw(), pVSD.asOutParam())); + + // Add additional info to the virtual system description if the user wants so + ArgsMap *pmapArgs = NULL; + ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i); + if (itm != mapArgsMapsPerVsys.end()) + pmapArgs = &itm->second; + if (pmapArgs) + { + ArgsMap::iterator itD; + for (itD = pmapArgs->begin(); + itD != pmapArgs->end(); + ++itD) + { + if (itD->first == "vmname") + { + //remove default value if user has specified new name (default value is set in the ExportTo()) + pVSD->RemoveDescriptionByType(VirtualSystemDescriptionType_Name); + pVSD->AddDescription(VirtualSystemDescriptionType_Name, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + } + else if (itD->first == "product") + pVSD->AddDescription(VirtualSystemDescriptionType_Product, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "producturl") + pVSD->AddDescription(VirtualSystemDescriptionType_ProductUrl, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "vendor") + pVSD->AddDescription(VirtualSystemDescriptionType_Vendor, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "vendorurl") + pVSD->AddDescription(VirtualSystemDescriptionType_VendorUrl, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "version") + pVSD->AddDescription(VirtualSystemDescriptionType_Version, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "description") + pVSD->AddDescription(VirtualSystemDescriptionType_Description, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "eula") + pVSD->AddDescription(VirtualSystemDescriptionType_License, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "eulafile") + { + Utf8Str strContent; + void *pvFile; + size_t cbFile; + int irc = RTFileReadAll(itD->second.c_str(), &pvFile, &cbFile); + if (RT_SUCCESS(irc)) + { + Bstr bstrContent((char*)pvFile, cbFile); + pVSD->AddDescription(VirtualSystemDescriptionType_License, + bstrContent.raw(), + bstrContent.raw()); + RTFileReadAllFree(pvFile, cbFile); + } + else + { + RTMsgError("Cannot read license file \"%s\" which should be included in the virtual system %u.", + itD->second.c_str(), i); + return RTEXITCODE_FAILURE; + } + } + /* add cloud export settings */ + else if (itD->first == "cloudshape") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudInstanceShape, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "clouddomain") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudDomain, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "clouddisksize") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudBootDiskSize, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "cloudbucket") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudBucket, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "cloudocivcn") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCIVCN, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "cloudpublicip") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudPublicIP, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "cloudprofile") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudProfileName, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "cloudocisubnet") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCISubnet, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "cloudkeepobject") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudKeepObject, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + else if (itD->first == "cloudlaunchinstance") + pVSD->AddDescription(VirtualSystemDescriptionType_CloudLaunchInstance, + Bstr(itD->second).raw(), + Bstr(itD->second).raw()); + } + } + } + + if (FAILED(rc)) + break; + + /* Query required passwords and supply them to the appliance. */ + com::SafeArray<BSTR> aIdentifiers; + + CHECK_ERROR_BREAK(pAppliance, GetPasswordIds(ComSafeArrayAsOutParam(aIdentifiers))); + + if (aIdentifiers.size() > 0) + { + com::SafeArray<BSTR> aPasswords(aIdentifiers.size()); + RTPrintf("Enter the passwords for the following identifiers to export the apppliance:\n"); + for (unsigned idxId = 0; idxId < aIdentifiers.size(); idxId++) + { + com::Utf8Str strPassword; + Bstr bstrPassword; + Bstr bstrId = aIdentifiers[idxId]; + + RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Password ID %s:", Utf8Str(bstrId).c_str()); + if (rcExit == RTEXITCODE_FAILURE) + { + RTStrFree(pszAbsFilePath); + return rcExit; + } + + bstrPassword = strPassword; + bstrPassword.detachTo(&aPasswords[idxId]); + } + + CHECK_ERROR_BREAK(pAppliance, AddPasswords(ComSafeArrayAsInParam(aIdentifiers), + ComSafeArrayAsInParam(aPasswords))); + } + + if (fManifest) + options.push_back(ExportOptions_CreateManifest); + + if (fExportISOImages) + options.push_back(ExportOptions_ExportDVDImages); + + ComPtr<IProgress> progress; + CHECK_ERROR_BREAK(pAppliance, Write(Bstr(strOvfFormat).raw(), + ComSafeArrayAsInParam(options), + Bstr(pszAbsFilePath).raw(), + progress.asOutParam())); + RTStrFree(pszAbsFilePath); + + rc = showProgress(progress); + CHECK_PROGRESS_ERROR_RET(progress, ("Appliance write failed"), RTEXITCODE_FAILURE); + + if (SUCCEEDED(rc)) + RTPrintf("Successfully exported %d machine(s).\n", llMachines.size()); + + } while (0); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +#endif /* !VBOX_ONLY_DOCS */ diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageBandwidthControl.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageBandwidthControl.cpp new file mode 100644 index 00000000..8b736c58 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageBandwidthControl.cpp @@ -0,0 +1,373 @@ +/* $Id: VBoxManageBandwidthControl.cpp $ */ +/** @file + * VBoxManage - The bandwidth control related commands. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_ONLY_DOCS + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> + +#include <iprt/err.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/ctype.h> +#include <iprt/stream.h> +#include <iprt/getopt.h> +#include <VBox/log.h> + +#include "VBoxManage.h" +using namespace com; + + +// funcs +/////////////////////////////////////////////////////////////////////////////// + + +/** + * Parses a string in the following format "n[k|m|g|K|M|G]". Stores the value + * of n expressed in bytes to *pLimit. k meas kilobit, while K means kilobyte. + * + * @returns Error message or NULL if successful. + * @param pcszLimit The string to parse. + * @param pLimit Where to store the result. + */ +static const char *parseLimit(const char *pcszLimit, int64_t *pLimit) +{ + int iMultiplier = _1M; + char *pszNext = NULL; + int rc = RTStrToInt64Ex(pcszLimit, &pszNext, 10, pLimit); + + switch (rc) + { + case VINF_SUCCESS: + break; + case VWRN_NUMBER_TOO_BIG: + return "Limit is too big\n"; + case VWRN_TRAILING_CHARS: + switch (*pszNext) + { + case 'G': iMultiplier = _1G; break; + case 'M': iMultiplier = _1M; break; + case 'K': iMultiplier = _1K; break; + case 'g': iMultiplier = 125000000; break; + case 'm': iMultiplier = 125000; break; + case 'k': iMultiplier = 125; break; + default: return "Invalid unit suffix. Valid suffixes are: k, m, g, K, M, G\n"; + } + break; + case VWRN_TRAILING_SPACES: + return "Trailing spaces in limit!\n"; + case VERR_NO_DIGITS: + return "No digits in limit specifier\n"; + default: + return "Invalid limit specifier\n"; + } + if (*pLimit < 0) + return "Limit cannot be negative\n"; + if (*pLimit > INT64_MAX / iMultiplier) + return "Limit is too big\n"; + *pLimit *= iMultiplier; + + return NULL; +} + +/** + * Handles the 'bandwidthctl myvm add' sub-command. + * @returns Exit code. + * @param a The handler argument package. + * @param bwCtrl Reference to the bandwidth control interface. + */ +static RTEXITCODE handleBandwidthControlAdd(HandlerArg *a, ComPtr<IBandwidthControl> &bwCtrl) +{ + HRESULT rc = S_OK; + static const RTGETOPTDEF g_aBWCtlAddOptions[] = + { + { "--type", 't', RTGETOPT_REQ_STRING }, + { "--limit", 'l', RTGETOPT_REQ_STRING } + }; + + + Bstr name(a->argv[2]); + if (name.isEmpty()) + { + errorArgument("Bandwidth group name must not be empty!\n"); + return RTEXITCODE_FAILURE; + } + + const char *pszType = NULL; + int64_t cMaxBytesPerSec = INT64_MAX; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, a->argc, a->argv, g_aBWCtlAddOptions, + RT_ELEMENTS(g_aBWCtlAddOptions), 3, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + + while ( SUCCEEDED(rc) + && (c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 't': // bandwidth group type + { + if (ValueUnion.psz) + pszType = ValueUnion.psz; + else + rc = E_FAIL; + break; + } + + case 'l': // limit + { + if (ValueUnion.psz) + { + const char *pcszError = parseLimit(ValueUnion.psz, &cMaxBytesPerSec); + if (pcszError) + { + errorArgument(pcszError); + return RTEXITCODE_FAILURE; + } + } + else + rc = E_FAIL; + break; + } + + default: + { + errorGetOpt(USAGE_BANDWIDTHCONTROL, c, &ValueUnion); + rc = E_FAIL; + break; + } + } + } + + BandwidthGroupType_T enmType; + + if (!RTStrICmp(pszType, "disk")) + enmType = BandwidthGroupType_Disk; + else if (!RTStrICmp(pszType, "network")) + enmType = BandwidthGroupType_Network; + else + { + errorArgument("Invalid bandwidth group type\n"); + return RTEXITCODE_FAILURE; + } + + CHECK_ERROR2I_RET(bwCtrl, CreateBandwidthGroup(name.raw(), enmType, (LONG64)cMaxBytesPerSec), RTEXITCODE_FAILURE); + + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the 'bandwidthctl myvm set' sub-command. + * @returns Exit code. + * @param a The handler argument package. + * @param bwCtrl Reference to the bandwidth control interface. + */ +static RTEXITCODE handleBandwidthControlSet(HandlerArg *a, ComPtr<IBandwidthControl> &bwCtrl) +{ + HRESULT rc = S_OK; + static const RTGETOPTDEF g_aBWCtlAddOptions[] = + { + { "--limit", 'l', RTGETOPT_REQ_STRING } + }; + + + Bstr name(a->argv[2]); + int64_t cMaxBytesPerSec = INT64_MAX; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, a->argc, a->argv, g_aBWCtlAddOptions, + RT_ELEMENTS(g_aBWCtlAddOptions), 3, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + + while ( SUCCEEDED(rc) + && (c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'l': // limit + { + if (ValueUnion.psz) + { + const char *pcszError = parseLimit(ValueUnion.psz, &cMaxBytesPerSec); + if (pcszError) + { + errorArgument(pcszError); + return RTEXITCODE_FAILURE; + } + } + else + rc = E_FAIL; + break; + } + + default: + { + errorGetOpt(USAGE_BANDWIDTHCONTROL, c, &ValueUnion); + rc = E_FAIL; + break; + } + } + } + + + if (cMaxBytesPerSec != INT64_MAX) + { + ComPtr<IBandwidthGroup> bwGroup; + CHECK_ERROR2I_RET(bwCtrl, GetBandwidthGroup(name.raw(), bwGroup.asOutParam()), RTEXITCODE_FAILURE); + if (SUCCEEDED(rc)) + { + CHECK_ERROR2I_RET(bwGroup, COMSETTER(MaxBytesPerSec)((LONG64)cMaxBytesPerSec), RTEXITCODE_FAILURE); + } + } + + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the 'bandwidthctl myvm remove' sub-command. + * @returns Exit code. + * @param a The handler argument package. + * @param bwCtrl Reference to the bandwidth control interface. + */ +static RTEXITCODE handleBandwidthControlRemove(HandlerArg *a, ComPtr<IBandwidthControl> &bwCtrl) +{ + Bstr name(a->argv[2]); + CHECK_ERROR2I_RET(bwCtrl, DeleteBandwidthGroup(name.raw()), RTEXITCODE_FAILURE); + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the 'bandwidthctl myvm list' sub-command. + * @returns Exit code. + * @param a The handler argument package. + * @param bwCtrl Reference to the bandwidth control interface. + */ +static RTEXITCODE handleBandwidthControlList(HandlerArg *pArgs, ComPtr<IBandwidthControl> &rptrBWControl) +{ + static const RTGETOPTDEF g_aOptions[] = + { + { "--machinereadable", 'M', RTGETOPT_REQ_NOTHING }, + }; + + VMINFO_DETAILS enmDetails = VMINFO_STANDARD; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, g_aOptions, RT_ELEMENTS(g_aOptions), 2 /*iArg*/, 0 /*fFlags*/); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'M': + enmDetails = VMINFO_MACHINEREADABLE; + break; + default: + return errorGetOpt(USAGE_BANDWIDTHCONTROL, c, &ValueUnion); + } + } + + if (FAILED(showBandwidthGroups(rptrBWControl, enmDetails))) + return RTEXITCODE_FAILURE; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Handles the 'bandwidthctl' command. + * @returns Exit code. + * @param a The handler argument package. + */ +RTEXITCODE handleBandwidthControl(HandlerArg *a) +{ + HRESULT rc = S_OK; + ComPtr<IMachine> machine; + ComPtr<IBandwidthControl> bwCtrl; + + if (a->argc < 2) + return errorSyntax(USAGE_BANDWIDTHCONTROL, "Too few parameters"); + else if (a->argc > 7) + return errorSyntax(USAGE_BANDWIDTHCONTROL, "Too many parameters"); + + /* try to find the given machine */ + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam()), RTEXITCODE_FAILURE); + + /* open a session for the VM (new or shared) */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + SessionType_T st; + CHECK_ERROR_RET(a->session, COMGETTER(Type)(&st), RTEXITCODE_FAILURE); + bool fRunTime = (st == SessionType_Shared); + + /* get the mutable session machine */ + a->session->COMGETTER(Machine)(machine.asOutParam()); + rc = machine->COMGETTER(BandwidthControl)(bwCtrl.asOutParam()); + if (FAILED(rc)) goto leave; + + if (!strcmp(a->argv[1], "add")) + { + if (fRunTime) + { + errorArgument("Bandwidth groups cannot be created while the VM is running\n"); + goto leave; + } + rc = handleBandwidthControlAdd(a, bwCtrl) == RTEXITCODE_SUCCESS ? S_OK : E_FAIL; + } + else if (!strcmp(a->argv[1], "remove")) + { + if (fRunTime) + { + errorArgument("Bandwidth groups cannot be deleted while the VM is running\n"); + goto leave; + } + rc = handleBandwidthControlRemove(a, bwCtrl) == RTEXITCODE_SUCCESS ? S_OK : E_FAIL; + } + else if (!strcmp(a->argv[1], "set")) + rc = handleBandwidthControlSet(a, bwCtrl) == RTEXITCODE_SUCCESS ? S_OK : E_FAIL; + else if (!strcmp(a->argv[1], "list")) + rc = handleBandwidthControlList(a, bwCtrl) == RTEXITCODE_SUCCESS ? S_OK : E_FAIL; + else + { + errorSyntax(USAGE_BANDWIDTHCONTROL, "Invalid parameter '%s'", Utf8Str(a->argv[1]).c_str()); + rc = E_FAIL; + } + + /* commit changes */ + if (SUCCEEDED(rc)) + CHECK_ERROR(machine, SaveSettings()); + +leave: + /* it's important to always close sessions */ + a->session->UnlockMachine(); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +#endif /* !VBOX_ONLY_DOCS */ + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageControlVM.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageControlVM.cpp new file mode 100644 index 00000000..8cb0abe5 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageControlVM.cpp @@ -0,0 +1,2290 @@ +/* $Id: VBoxManageControlVM.cpp $ */ +/** @file + * VBoxManage - Implementation of the controlvm command. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> + +#include <iprt/ctype.h> +#include <iprt/getopt.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/uuid.h> +#include <iprt/file.h> +#include <VBox/log.h> + +#include "VBoxManage.h" + +#include <list> + + +/** + * Parses a number. + * + * @returns Valid number on success. + * @returns 0 if invalid number. All necessary bitching has been done. + * @param psz Pointer to the nic number. + */ +static unsigned parseNum(const char *psz, unsigned cMaxNum, const char *name) +{ + uint32_t u32; + char *pszNext; + int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u32); + if ( RT_SUCCESS(rc) + && *pszNext == '\0' + && u32 >= 1 + && u32 <= cMaxNum) + return (unsigned)u32; + errorArgument("Invalid %s number '%s'", name, psz); + return 0; +} + +unsigned int getMaxNics(IVirtualBox* vbox, IMachine* mach) +{ + ComPtr<ISystemProperties> info; + ChipsetType_T aChipset; + ULONG NetworkAdapterCount = 0; + HRESULT rc; + + do { + CHECK_ERROR_BREAK(vbox, COMGETTER(SystemProperties)(info.asOutParam())); + CHECK_ERROR_BREAK(mach, COMGETTER(ChipsetType)(&aChipset)); + CHECK_ERROR_BREAK(info, GetMaxNetworkAdapters(aChipset, &NetworkAdapterCount)); + + return (unsigned int)NetworkAdapterCount; + } while (0); + + return 0; +} + +#define KBDCHARDEF_MOD_NONE 0x00 +#define KBDCHARDEF_MOD_SHIFT 0x01 + +typedef struct KBDCHARDEF +{ + uint8_t u8Scancode; + uint8_t u8Modifiers; +} KBDCHARDEF; + +static const KBDCHARDEF g_aASCIIChars[0x80] = +{ + /* 0x00 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x01 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x02 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x03 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x04 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x05 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x06 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x07 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x08 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x09 ' ' */ {0x0f, KBDCHARDEF_MOD_NONE}, + /* 0x0A ' ' */ {0x1c, KBDCHARDEF_MOD_NONE}, + /* 0x0B ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x0C ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x0D ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x0E ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x0F ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x10 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x11 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x12 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x13 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x14 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x15 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x16 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x17 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x18 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x19 ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x1A ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x1B ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x1C ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x1D ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x1E ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x1F ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, + /* 0x20 ' ' */ {0x39, KBDCHARDEF_MOD_NONE}, + /* 0x21 '!' */ {0x02, KBDCHARDEF_MOD_SHIFT}, + /* 0x22 '"' */ {0x28, KBDCHARDEF_MOD_SHIFT}, + /* 0x23 '#' */ {0x04, KBDCHARDEF_MOD_SHIFT}, + /* 0x24 '$' */ {0x05, KBDCHARDEF_MOD_SHIFT}, + /* 0x25 '%' */ {0x06, KBDCHARDEF_MOD_SHIFT}, + /* 0x26 '&' */ {0x08, KBDCHARDEF_MOD_SHIFT}, + /* 0x27 ''' */ {0x28, KBDCHARDEF_MOD_NONE}, + /* 0x28 '(' */ {0x0a, KBDCHARDEF_MOD_SHIFT}, + /* 0x29 ')' */ {0x0b, KBDCHARDEF_MOD_SHIFT}, + /* 0x2A '*' */ {0x09, KBDCHARDEF_MOD_SHIFT}, + /* 0x2B '+' */ {0x0d, KBDCHARDEF_MOD_SHIFT}, + /* 0x2C ',' */ {0x33, KBDCHARDEF_MOD_NONE}, + /* 0x2D '-' */ {0x0c, KBDCHARDEF_MOD_NONE}, + /* 0x2E '.' */ {0x34, KBDCHARDEF_MOD_NONE}, + /* 0x2F '/' */ {0x35, KBDCHARDEF_MOD_NONE}, + /* 0x30 '0' */ {0x0b, KBDCHARDEF_MOD_NONE}, + /* 0x31 '1' */ {0x02, KBDCHARDEF_MOD_NONE}, + /* 0x32 '2' */ {0x03, KBDCHARDEF_MOD_NONE}, + /* 0x33 '3' */ {0x04, KBDCHARDEF_MOD_NONE}, + /* 0x34 '4' */ {0x05, KBDCHARDEF_MOD_NONE}, + /* 0x35 '5' */ {0x06, KBDCHARDEF_MOD_NONE}, + /* 0x36 '6' */ {0x07, KBDCHARDEF_MOD_NONE}, + /* 0x37 '7' */ {0x08, KBDCHARDEF_MOD_NONE}, + /* 0x38 '8' */ {0x09, KBDCHARDEF_MOD_NONE}, + /* 0x39 '9' */ {0x0a, KBDCHARDEF_MOD_NONE}, + /* 0x3A ':' */ {0x27, KBDCHARDEF_MOD_SHIFT}, + /* 0x3B ';' */ {0x27, KBDCHARDEF_MOD_NONE}, + /* 0x3C '<' */ {0x33, KBDCHARDEF_MOD_SHIFT}, + /* 0x3D '=' */ {0x0d, KBDCHARDEF_MOD_NONE}, + /* 0x3E '>' */ {0x34, KBDCHARDEF_MOD_SHIFT}, + /* 0x3F '?' */ {0x35, KBDCHARDEF_MOD_SHIFT}, + /* 0x40 '@' */ {0x03, KBDCHARDEF_MOD_SHIFT}, + /* 0x41 'A' */ {0x1e, KBDCHARDEF_MOD_SHIFT}, + /* 0x42 'B' */ {0x30, KBDCHARDEF_MOD_SHIFT}, + /* 0x43 'C' */ {0x2e, KBDCHARDEF_MOD_SHIFT}, + /* 0x44 'D' */ {0x20, KBDCHARDEF_MOD_SHIFT}, + /* 0x45 'E' */ {0x12, KBDCHARDEF_MOD_SHIFT}, + /* 0x46 'F' */ {0x21, KBDCHARDEF_MOD_SHIFT}, + /* 0x47 'G' */ {0x22, KBDCHARDEF_MOD_SHIFT}, + /* 0x48 'H' */ {0x23, KBDCHARDEF_MOD_SHIFT}, + /* 0x49 'I' */ {0x17, KBDCHARDEF_MOD_SHIFT}, + /* 0x4A 'J' */ {0x24, KBDCHARDEF_MOD_SHIFT}, + /* 0x4B 'K' */ {0x25, KBDCHARDEF_MOD_SHIFT}, + /* 0x4C 'L' */ {0x26, KBDCHARDEF_MOD_SHIFT}, + /* 0x4D 'M' */ {0x32, KBDCHARDEF_MOD_SHIFT}, + /* 0x4E 'N' */ {0x31, KBDCHARDEF_MOD_SHIFT}, + /* 0x4F 'O' */ {0x18, KBDCHARDEF_MOD_SHIFT}, + /* 0x50 'P' */ {0x19, KBDCHARDEF_MOD_SHIFT}, + /* 0x51 'Q' */ {0x10, KBDCHARDEF_MOD_SHIFT}, + /* 0x52 'R' */ {0x13, KBDCHARDEF_MOD_SHIFT}, + /* 0x53 'S' */ {0x1f, KBDCHARDEF_MOD_SHIFT}, + /* 0x54 'T' */ {0x14, KBDCHARDEF_MOD_SHIFT}, + /* 0x55 'U' */ {0x16, KBDCHARDEF_MOD_SHIFT}, + /* 0x56 'V' */ {0x2f, KBDCHARDEF_MOD_SHIFT}, + /* 0x57 'W' */ {0x11, KBDCHARDEF_MOD_SHIFT}, + /* 0x58 'X' */ {0x2d, KBDCHARDEF_MOD_SHIFT}, + /* 0x59 'Y' */ {0x15, KBDCHARDEF_MOD_SHIFT}, + /* 0x5A 'Z' */ {0x2c, KBDCHARDEF_MOD_SHIFT}, + /* 0x5B '[' */ {0x1a, KBDCHARDEF_MOD_NONE}, + /* 0x5C '\' */ {0x2b, KBDCHARDEF_MOD_NONE}, + /* 0x5D ']' */ {0x1b, KBDCHARDEF_MOD_NONE}, + /* 0x5E '^' */ {0x07, KBDCHARDEF_MOD_SHIFT}, + /* 0x5F '_' */ {0x0c, KBDCHARDEF_MOD_SHIFT}, + /* 0x60 '`' */ {0x28, KBDCHARDEF_MOD_NONE}, + /* 0x61 'a' */ {0x1e, KBDCHARDEF_MOD_NONE}, + /* 0x62 'b' */ {0x30, KBDCHARDEF_MOD_NONE}, + /* 0x63 'c' */ {0x2e, KBDCHARDEF_MOD_NONE}, + /* 0x64 'd' */ {0x20, KBDCHARDEF_MOD_NONE}, + /* 0x65 'e' */ {0x12, KBDCHARDEF_MOD_NONE}, + /* 0x66 'f' */ {0x21, KBDCHARDEF_MOD_NONE}, + /* 0x67 'g' */ {0x22, KBDCHARDEF_MOD_NONE}, + /* 0x68 'h' */ {0x23, KBDCHARDEF_MOD_NONE}, + /* 0x69 'i' */ {0x17, KBDCHARDEF_MOD_NONE}, + /* 0x6A 'j' */ {0x24, KBDCHARDEF_MOD_NONE}, + /* 0x6B 'k' */ {0x25, KBDCHARDEF_MOD_NONE}, + /* 0x6C 'l' */ {0x26, KBDCHARDEF_MOD_NONE}, + /* 0x6D 'm' */ {0x32, KBDCHARDEF_MOD_NONE}, + /* 0x6E 'n' */ {0x31, KBDCHARDEF_MOD_NONE}, + /* 0x6F 'o' */ {0x18, KBDCHARDEF_MOD_NONE}, + /* 0x70 'p' */ {0x19, KBDCHARDEF_MOD_NONE}, + /* 0x71 'q' */ {0x10, KBDCHARDEF_MOD_NONE}, + /* 0x72 'r' */ {0x13, KBDCHARDEF_MOD_NONE}, + /* 0x73 's' */ {0x1f, KBDCHARDEF_MOD_NONE}, + /* 0x74 't' */ {0x14, KBDCHARDEF_MOD_NONE}, + /* 0x75 'u' */ {0x16, KBDCHARDEF_MOD_NONE}, + /* 0x76 'v' */ {0x2f, KBDCHARDEF_MOD_NONE}, + /* 0x77 'w' */ {0x11, KBDCHARDEF_MOD_NONE}, + /* 0x78 'x' */ {0x2d, KBDCHARDEF_MOD_NONE}, + /* 0x79 'y' */ {0x15, KBDCHARDEF_MOD_NONE}, + /* 0x7A 'z' */ {0x2c, KBDCHARDEF_MOD_NONE}, + /* 0x7B '{' */ {0x1a, KBDCHARDEF_MOD_SHIFT}, + /* 0x7C '|' */ {0x2b, KBDCHARDEF_MOD_SHIFT}, + /* 0x7D '}' */ {0x1b, KBDCHARDEF_MOD_SHIFT}, + /* 0x7E '~' */ {0x29, KBDCHARDEF_MOD_SHIFT}, + /* 0x7F ' ' */ {0x00, KBDCHARDEF_MOD_NONE}, +}; + +static HRESULT keyboardPutScancodes(IKeyboard *pKeyboard, const std::list<LONG> &llScancodes) +{ + /* Send scancodes to the VM. */ + com::SafeArray<LONG> saScancodes(llScancodes); + +#if 1 + HRESULT rc = S_OK; + size_t i; + for (i = 0; i < saScancodes.size(); ++i) + { + rc = pKeyboard->PutScancode(saScancodes[i]); + if (FAILED(rc)) + { + RTMsgError("Failed to send a scancode"); + break; + } + + RTThreadSleep(10); /* "Typing" too fast causes lost characters. */ + } +#else + /** @todo PutScancodes does not deliver more than 20 scancodes. */ + ULONG codesStored = 0; + HRESULT rc = pKeyboard->PutScancodes(ComSafeArrayAsInParam(saScancodes), + &codesStored); + if (SUCCEEDED(rc) && codesStored < saScancodes.size()) + { + RTMsgError("Only %d scancodes were stored", codesStored); + rc = E_FAIL; + } +#endif + + return rc; +} + +static void keyboardCharsToScancodes(const char *pch, size_t cchMax, std::list<LONG> &llScancodes, bool *pfShift) +{ + size_t cchProcessed = 0; + const char *p = pch; + while (cchProcessed < cchMax) + { + ++cchProcessed; + const uint8_t c = (uint8_t)*p++; + if (c < RT_ELEMENTS(g_aASCIIChars)) + { + const KBDCHARDEF *d = &g_aASCIIChars[c]; + if (d->u8Scancode) + { + const bool fNeedShift = RT_BOOL(d->u8Modifiers & KBDCHARDEF_MOD_SHIFT); + if (*pfShift != fNeedShift) + { + *pfShift = fNeedShift; + /* Press or release the SHIFT key. */ + llScancodes.push_back(0x2a | (fNeedShift? 0x00: 0x80)); + } + + llScancodes.push_back(d->u8Scancode); + llScancodes.push_back(d->u8Scancode | 0x80); + } + } + } +} + +static HRESULT keyboardPutString(IKeyboard *pKeyboard, int argc, char **argv) +{ + std::list<LONG> llScancodes; + bool fShift = false; + + /* Convert command line string(s) to the en-us keyboard scancodes. */ + int i; + for (i = 1 + 1; i < argc; ++i) + { + if (!llScancodes.empty()) + { + /* Insert a SPACE before the next string. */ + llScancodes.push_back(0x39); + llScancodes.push_back(0x39 | 0x80); + } + + keyboardCharsToScancodes(argv[i], strlen(argv[i]), llScancodes, &fShift); + } + + /* Release SHIFT if pressed. */ + if (fShift) + llScancodes.push_back(0x2a | 0x80); + + return keyboardPutScancodes(pKeyboard, llScancodes); +} + +static HRESULT keyboardPutFile(IKeyboard *pKeyboard, const char *pszFilename) +{ + std::list<LONG> llScancodes; + bool fShift = false; + + RTFILE File = NIL_RTFILE; + int vrc = RTFileOpen(&File, pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(vrc)) + { + uint64_t cbFile = 0; + vrc = RTFileGetSize(File, &cbFile); + if (RT_SUCCESS(vrc)) + { + const uint64_t cbFileMax = _64K; + if (cbFile <= cbFileMax) + { + const size_t cbBuffer = _4K; + char *pchBuf = (char *)RTMemAlloc(cbBuffer); + if (pchBuf) + { + size_t cbRemaining = (size_t)cbFile; + while (cbRemaining > 0) + { + const size_t cbToRead = cbRemaining > cbBuffer ? cbBuffer : cbRemaining; + + size_t cbRead = 0; + vrc = RTFileRead(File, pchBuf, cbToRead, &cbRead); + if (RT_FAILURE(vrc) || cbRead == 0) + break; + + keyboardCharsToScancodes(pchBuf, cbRead, llScancodes, &fShift); + cbRemaining -= cbRead; + } + + RTMemFree(pchBuf); + } + else + RTMsgError("Out of memory allocating %d bytes", cbBuffer); + } + else + RTMsgError("File size %RI64 is greater than %RI64: '%s'", cbFile, cbFileMax, pszFilename); + } + else + RTMsgError("Cannot get size of file '%s': %Rrc", pszFilename, vrc); + + RTFileClose(File); + } + else + RTMsgError("Cannot open file '%s': %Rrc", pszFilename, vrc); + + /* Release SHIFT if pressed. */ + if (fShift) + llScancodes.push_back(0x2a | 0x80); + + return keyboardPutScancodes(pKeyboard, llScancodes); +} + + +RTEXITCODE handleControlVM(HandlerArg *a) +{ + using namespace com; + bool fNeedsSaving = false; + HRESULT rc; + + if (a->argc < 2) + return errorSyntax(USAGE_CONTROLVM, "Not enough parameters"); + + /* try to find the given machine */ + ComPtr<IMachine> machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + /* open a session for the VM */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + + ComPtr<IConsole> console; + ComPtr<IMachine> sessionMachine; + + do + { + /* get the associated console */ + CHECK_ERROR_BREAK(a->session, COMGETTER(Console)(console.asOutParam())); + if (!console) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Machine '%s' is not currently running", a->argv[0]); + + /* ... and session machine */ + CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam())); + + /* which command? */ + if (!strcmp(a->argv[1], "pause")) + { + CHECK_ERROR_BREAK(console, Pause()); + } + else if (!strcmp(a->argv[1], "resume")) + { + CHECK_ERROR_BREAK(console, Resume()); + } + else if (!strcmp(a->argv[1], "reset")) + { + CHECK_ERROR_BREAK(console, Reset()); + } + else if (!strcmp(a->argv[1], "unplugcpu")) + { + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'. Expected CPU number.", a->argv[1]); + rc = E_FAIL; + break; + } + + unsigned n = parseNum(a->argv[2], 32, "CPU"); + + CHECK_ERROR_BREAK(sessionMachine, HotUnplugCPU(n)); + } + else if (!strcmp(a->argv[1], "plugcpu")) + { + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'. Expected CPU number.", a->argv[1]); + rc = E_FAIL; + break; + } + + unsigned n = parseNum(a->argv[2], 32, "CPU"); + + CHECK_ERROR_BREAK(sessionMachine, HotPlugCPU(n)); + } + else if (!strcmp(a->argv[1], "cpuexecutioncap")) + { + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'. Expected execution cap number.", a->argv[1]); + rc = E_FAIL; + break; + } + + unsigned n = parseNum(a->argv[2], 100, "ExecutionCap"); + + CHECK_ERROR_BREAK(sessionMachine, COMSETTER(CPUExecutionCap)(n)); + } + else if (!strcmp(a->argv[1], "audioin")) + { + ComPtr<IAudioAdapter> adapter; + CHECK_ERROR_BREAK(sessionMachine, COMGETTER(AudioAdapter)(adapter.asOutParam())); + if (adapter) + { + if (!strcmp(a->argv[2], "on")) + { + CHECK_ERROR_RET(adapter, COMSETTER(EnabledIn)(TRUE), RTEXITCODE_FAILURE); + } + else if (!strcmp(a->argv[2], "off")) + { + CHECK_ERROR_RET(adapter, COMSETTER(EnabledIn)(FALSE), RTEXITCODE_FAILURE); + } + else + { + errorArgument("Invalid value '%s'", Utf8Str(a->argv[2]).c_str()); + rc = E_FAIL; + break; + } + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + else + { + errorArgument("audio adapter not enabled in VM configuration"); + rc = E_FAIL; + break; + } + } + else if (!strcmp(a->argv[1], "audioout")) + { + ComPtr<IAudioAdapter> adapter; + CHECK_ERROR_BREAK(sessionMachine, COMGETTER(AudioAdapter)(adapter.asOutParam())); + if (adapter) + { + if (!strcmp(a->argv[2], "on")) + { + CHECK_ERROR_RET(adapter, COMSETTER(EnabledOut)(TRUE), RTEXITCODE_FAILURE); + } + else if (!strcmp(a->argv[2], "off")) + { + CHECK_ERROR_RET(adapter, COMSETTER(EnabledOut)(FALSE), RTEXITCODE_FAILURE); + } + else + { + errorArgument("Invalid value '%s'", Utf8Str(a->argv[2]).c_str()); + rc = E_FAIL; + break; + } + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + else + { + errorArgument("audio adapter not enabled in VM configuration"); + rc = E_FAIL; + break; + } + } + else if (!strcmp(a->argv[1], "clipboard")) + { + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'. Expected clipboard mode.", a->argv[1]); + rc = E_FAIL; + break; + } + + ClipboardMode_T mode = ClipboardMode_Disabled; /* Shut up MSC */ + if (!strcmp(a->argv[2], "disabled")) + mode = ClipboardMode_Disabled; + else if (!strcmp(a->argv[2], "hosttoguest")) + mode = ClipboardMode_HostToGuest; + else if (!strcmp(a->argv[2], "guesttohost")) + mode = ClipboardMode_GuestToHost; + else if (!strcmp(a->argv[2], "bidirectional")) + mode = ClipboardMode_Bidirectional; + else + { + errorArgument("Invalid '%s' argument '%s'.", a->argv[1], a->argv[2]); + rc = E_FAIL; + } + if (SUCCEEDED(rc)) + { + CHECK_ERROR_BREAK(sessionMachine, COMSETTER(ClipboardMode)(mode)); + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + } + else if (!strcmp(a->argv[1], "draganddrop")) + { + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'. Expected drag and drop mode.", a->argv[1]); + rc = E_FAIL; + break; + } + + DnDMode_T mode = DnDMode_Disabled; /* Shup up MSC. */ + if (!strcmp(a->argv[2], "disabled")) + mode = DnDMode_Disabled; + else if (!strcmp(a->argv[2], "hosttoguest")) + mode = DnDMode_HostToGuest; + else if (!strcmp(a->argv[2], "guesttohost")) + mode = DnDMode_GuestToHost; + else if (!strcmp(a->argv[2], "bidirectional")) + mode = DnDMode_Bidirectional; + else + { + errorArgument("Invalid '%s' argument '%s'.", a->argv[1], a->argv[2]); + rc = E_FAIL; + } + if (SUCCEEDED(rc)) + { + CHECK_ERROR_BREAK(sessionMachine, COMSETTER(DnDMode)(mode)); + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + } + else if (!strcmp(a->argv[1], "poweroff")) + { + ComPtr<IProgress> progress; + CHECK_ERROR_BREAK(console, PowerDown(progress.asOutParam())); + + rc = showProgress(progress); + CHECK_PROGRESS_ERROR(progress, ("Failed to power off machine")); + } + else if (!strcmp(a->argv[1], "savestate")) + { + /* first pause so we don't trigger a live save which needs more time/resources */ + bool fPaused = false; + rc = console->Pause(); + if (FAILED(rc)) + { + bool fError = true; + if (rc == VBOX_E_INVALID_VM_STATE) + { + /* check if we are already paused */ + MachineState_T machineState; + CHECK_ERROR_BREAK(console, COMGETTER(State)(&machineState)); + /* the error code was lost by the previous instruction */ + rc = VBOX_E_INVALID_VM_STATE; + if (machineState != MachineState_Paused) + { + RTMsgError("Machine in invalid state %d -- %s\n", + machineState, machineStateToName(machineState, false)); + } + else + { + fError = false; + fPaused = true; + } + } + if (fError) + break; + } + + ComPtr<IProgress> progress; + CHECK_ERROR(sessionMachine, SaveState(progress.asOutParam())); + if (FAILED(rc)) + { + if (!fPaused) + console->Resume(); + break; + } + + rc = showProgress(progress); + CHECK_PROGRESS_ERROR(progress, ("Failed to save machine state")); + if (FAILED(rc)) + { + if (!fPaused) + console->Resume(); + } + } + else if (!strcmp(a->argv[1], "acpipowerbutton")) + { + CHECK_ERROR_BREAK(console, PowerButton()); + } + else if (!strcmp(a->argv[1], "acpisleepbutton")) + { + CHECK_ERROR_BREAK(console, SleepButton()); + } + else if (!strcmp(a->argv[1], "keyboardputscancode")) + { + ComPtr<IKeyboard> pKeyboard; + CHECK_ERROR_BREAK(console, COMGETTER(Keyboard)(pKeyboard.asOutParam())); + if (!pKeyboard) + { + RTMsgError("Guest not running"); + rc = E_FAIL; + break; + } + + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'. Expected IBM PC AT set 2 keyboard scancode(s) as hex byte(s).", a->argv[1]); + rc = E_FAIL; + break; + } + + std::list<LONG> llScancodes; + + /* Process the command line. */ + int i; + for (i = 1 + 1; i < a->argc; i++) + { + if ( RT_C_IS_XDIGIT (a->argv[i][0]) + && RT_C_IS_XDIGIT (a->argv[i][1]) + && a->argv[i][2] == 0) + { + uint8_t u8Scancode; + int irc = RTStrToUInt8Ex(a->argv[i], NULL, 16, &u8Scancode); + if (RT_FAILURE (irc)) + { + RTMsgError("Converting '%s' returned %Rrc!", a->argv[i], rc); + rc = E_FAIL; + break; + } + + llScancodes.push_back(u8Scancode); + } + else + { + RTMsgError("Error: '%s' is not a hex byte!", a->argv[i]); + rc = E_FAIL; + break; + } + } + + if (FAILED(rc)) + break; + + rc = keyboardPutScancodes(pKeyboard, llScancodes); + } + else if (!strcmp(a->argv[1], "keyboardputstring")) + { + ComPtr<IKeyboard> pKeyboard; + CHECK_ERROR_BREAK(console, COMGETTER(Keyboard)(pKeyboard.asOutParam())); + if (!pKeyboard) + { + RTMsgError("Guest not running"); + rc = E_FAIL; + break; + } + + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'. Expected ASCII string(s).", a->argv[1]); + rc = E_FAIL; + break; + } + + rc = keyboardPutString(pKeyboard, a->argc, a->argv); + } + else if (!strcmp(a->argv[1], "keyboardputfile")) + { + ComPtr<IKeyboard> pKeyboard; + CHECK_ERROR_BREAK(console, COMGETTER(Keyboard)(pKeyboard.asOutParam())); + if (!pKeyboard) + { + RTMsgError("Guest not running"); + rc = E_FAIL; + break; + } + + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'. Expected file name.", a->argv[1]); + rc = E_FAIL; + break; + } + + rc = keyboardPutFile(pKeyboard, a->argv[2]); + } + else if (!strncmp(a->argv[1], "setlinkstate", 12)) + { + /* Get the number of network adapters */ + ULONG NetworkAdapterCount = getMaxNics(a->virtualBox, sessionMachine); + unsigned n = parseNum(&a->argv[1][12], NetworkAdapterCount, "NIC"); + if (!n) + { + rc = E_FAIL; + break; + } + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + /* get the corresponding network adapter */ + ComPtr<INetworkAdapter> adapter; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(n - 1, adapter.asOutParam())); + if (adapter) + { + if (!strcmp(a->argv[2], "on")) + { + CHECK_ERROR_BREAK(adapter, COMSETTER(CableConnected)(TRUE)); + } + else if (!strcmp(a->argv[2], "off")) + { + CHECK_ERROR_BREAK(adapter, COMSETTER(CableConnected)(FALSE)); + } + else + { + errorArgument("Invalid link state '%s'", Utf8Str(a->argv[2]).c_str()); + rc = E_FAIL; + break; + } + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + } + /* here the order in which strncmp is called is important + * cause nictracefile can be very well compared with + * nictrace and nic and thus everything will always fail + * if the order is changed + */ + else if (!strncmp(a->argv[1], "nictracefile", 12)) + { + /* Get the number of network adapters */ + ULONG NetworkAdapterCount = getMaxNics(a->virtualBox, sessionMachine); + unsigned n = parseNum(&a->argv[1][12], NetworkAdapterCount, "NIC"); + if (!n) + { + rc = E_FAIL; + break; + } + if (a->argc <= 2) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + + /* get the corresponding network adapter */ + ComPtr<INetworkAdapter> adapter; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(n - 1, adapter.asOutParam())); + if (adapter) + { + BOOL fEnabled; + adapter->COMGETTER(Enabled)(&fEnabled); + if (fEnabled) + { + if (a->argv[2]) + { + CHECK_ERROR_RET(adapter, COMSETTER(TraceFile)(Bstr(a->argv[2]).raw()), RTEXITCODE_FAILURE); + } + else + { + errorArgument("Invalid filename or filename not specified for NIC %lu", n); + rc = E_FAIL; + break; + } + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + else + RTMsgError("The NIC %d is currently disabled and thus its tracefile can't be changed", n); + } + } + else if (!strncmp(a->argv[1], "nictrace", 8)) + { + /* Get the number of network adapters */ + ULONG NetworkAdapterCount = getMaxNics(a->virtualBox, sessionMachine); + unsigned n = parseNum(&a->argv[1][8], NetworkAdapterCount, "NIC"); + if (!n) + { + rc = E_FAIL; + break; + } + if (a->argc <= 2) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + + /* get the corresponding network adapter */ + ComPtr<INetworkAdapter> adapter; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(n - 1, adapter.asOutParam())); + if (adapter) + { + BOOL fEnabled; + adapter->COMGETTER(Enabled)(&fEnabled); + if (fEnabled) + { + if (!strcmp(a->argv[2], "on")) + { + CHECK_ERROR_RET(adapter, COMSETTER(TraceEnabled)(TRUE), RTEXITCODE_FAILURE); + } + else if (!strcmp(a->argv[2], "off")) + { + CHECK_ERROR_RET(adapter, COMSETTER(TraceEnabled)(FALSE), RTEXITCODE_FAILURE); + } + else + { + errorArgument("Invalid nictrace%lu argument '%s'", n, Utf8Str(a->argv[2]).c_str()); + rc = E_FAIL; + break; + } + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + else + RTMsgError("The NIC %d is currently disabled and thus its trace flag can't be changed", n); + } + } + else if( a->argc > 2 + && !strncmp(a->argv[1], "natpf", 5)) + { + /* Get the number of network adapters */ + ULONG NetworkAdapterCount = getMaxNics(a->virtualBox, sessionMachine); + unsigned n = parseNum(&a->argv[1][5], NetworkAdapterCount, "NIC"); + if (!n) + { + rc = E_FAIL; + break; + } + if (a->argc <= 2) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + + /* get the corresponding network adapter */ + ComPtr<INetworkAdapter> adapter; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(n - 1, adapter.asOutParam())); + if (!adapter) + { + rc = E_FAIL; + break; + } + ComPtr<INATEngine> engine; + CHECK_ERROR(adapter, COMGETTER(NATEngine)(engine.asOutParam())); + if (!engine) + { + rc = E_FAIL; + break; + } + + if (!strcmp(a->argv[2], "delete")) + { + if (a->argc >= 3) + CHECK_ERROR(engine, RemoveRedirect(Bstr(a->argv[3]).raw())); + } + else + { +#define ITERATE_TO_NEXT_TERM(ch) \ + do { \ + while (*ch != ',') \ + { \ + if (*ch == 0) \ + { \ + return errorSyntax(USAGE_CONTROLVM, \ + "Missing or invalid argument to '%s'", \ + a->argv[1]); \ + } \ + ch++; \ + } \ + *ch = '\0'; \ + ch++; \ + } while(0) + + char *strName; + char *strProto; + char *strHostIp; + char *strHostPort; + char *strGuestIp; + char *strGuestPort; + char *strRaw = RTStrDup(a->argv[2]); + char *ch = strRaw; + strName = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strProto = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strHostIp = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strHostPort = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strGuestIp = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strGuestPort = RTStrStrip(ch); + NATProtocol_T proto; + if (RTStrICmp(strProto, "udp") == 0) + proto = NATProtocol_UDP; + else if (RTStrICmp(strProto, "tcp") == 0) + proto = NATProtocol_TCP; + else + { + return errorSyntax(USAGE_CONTROLVM, + "Wrong rule proto '%s' specified -- only 'udp' and 'tcp' are allowed.", + strProto); + } + CHECK_ERROR(engine, AddRedirect(Bstr(strName).raw(), proto, Bstr(strHostIp).raw(), + RTStrToUInt16(strHostPort), Bstr(strGuestIp).raw(), RTStrToUInt16(strGuestPort))); +#undef ITERATE_TO_NEXT_TERM + } + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + else if (!strncmp(a->argv[1], "nicproperty", 11)) + { + /* Get the number of network adapters */ + ULONG NetworkAdapterCount = getMaxNics(a->virtualBox, sessionMachine); + unsigned n = parseNum(&a->argv[1][11], NetworkAdapterCount, "NIC"); + if (!n) + { + rc = E_FAIL; + break; + } + if (a->argc <= 2) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + + /* get the corresponding network adapter */ + ComPtr<INetworkAdapter> adapter; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(n - 1, adapter.asOutParam())); + if (adapter) + { + BOOL fEnabled; + adapter->COMGETTER(Enabled)(&fEnabled); + if (fEnabled) + { + /* Parse 'name=value' */ + char *pszProperty = RTStrDup(a->argv[2]); + if (pszProperty) + { + char *pDelimiter = strchr(pszProperty, '='); + if (pDelimiter) + { + *pDelimiter = '\0'; + + Bstr bstrName = pszProperty; + Bstr bstrValue = &pDelimiter[1]; + CHECK_ERROR(adapter, SetProperty(bstrName.raw(), bstrValue.raw())); + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + else + { + errorArgument("Invalid nicproperty%d argument '%s'", n, a->argv[2]); + rc = E_FAIL; + } + RTStrFree(pszProperty); + } + else + { + RTStrmPrintf(g_pStdErr, "Error: Failed to allocate memory for nicproperty%d '%s'\n", n, a->argv[2]); + rc = E_FAIL; + } + if (FAILED(rc)) + break; + } + else + RTMsgError("The NIC %d is currently disabled and thus its properties can't be changed", n); + } + } + else if (!strncmp(a->argv[1], "nicpromisc", 10)) + { + /* Get the number of network adapters */ + ULONG NetworkAdapterCount = getMaxNics(a->virtualBox, sessionMachine); + unsigned n = parseNum(&a->argv[1][10], NetworkAdapterCount, "NIC"); + if (!n) + { + rc = E_FAIL; + break; + } + if (a->argc <= 2) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + + /* get the corresponding network adapter */ + ComPtr<INetworkAdapter> adapter; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(n - 1, adapter.asOutParam())); + if (adapter) + { + BOOL fEnabled; + adapter->COMGETTER(Enabled)(&fEnabled); + if (fEnabled) + { + NetworkAdapterPromiscModePolicy_T enmPromiscModePolicy; + if (!strcmp(a->argv[2], "deny")) + enmPromiscModePolicy = NetworkAdapterPromiscModePolicy_Deny; + else if ( !strcmp(a->argv[2], "allow-vms") + || !strcmp(a->argv[2], "allow-network")) + enmPromiscModePolicy = NetworkAdapterPromiscModePolicy_AllowNetwork; + else if (!strcmp(a->argv[2], "allow-all")) + enmPromiscModePolicy = NetworkAdapterPromiscModePolicy_AllowAll; + else + { + errorArgument("Unknown promiscuous mode policy '%s'", a->argv[2]); + rc = E_INVALIDARG; + break; + } + + CHECK_ERROR(adapter, COMSETTER(PromiscModePolicy)(enmPromiscModePolicy)); + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + else + RTMsgError("The NIC %d is currently disabled and thus its promiscuous mode can't be changed", n); + } + } + else if (!strncmp(a->argv[1], "nic", 3)) + { + /* Get the number of network adapters */ + ULONG NetworkAdapterCount = getMaxNics(a->virtualBox, sessionMachine); + unsigned n = parseNum(&a->argv[1][3], NetworkAdapterCount, "NIC"); + if (!n) + { + rc = E_FAIL; + break; + } + if (a->argc <= 2) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + + /* get the corresponding network adapter */ + ComPtr<INetworkAdapter> adapter; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(n - 1, adapter.asOutParam())); + if (adapter) + { + BOOL fEnabled; + adapter->COMGETTER(Enabled)(&fEnabled); + if (fEnabled) + { + if (!strcmp(a->argv[2], "null")) + { + CHECK_ERROR_RET(adapter, COMSETTER(AttachmentType)(NetworkAttachmentType_Null), RTEXITCODE_FAILURE); + } + else if (!strcmp(a->argv[2], "nat")) + { + if (a->argc == 4) + CHECK_ERROR_RET(adapter, COMSETTER(NATNetwork)(Bstr(a->argv[3]).raw()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(adapter, COMSETTER(AttachmentType)(NetworkAttachmentType_NAT), RTEXITCODE_FAILURE); + } + else if ( !strcmp(a->argv[2], "bridged") + || !strcmp(a->argv[2], "hostif")) /* backward compatibility */ + { + if (a->argc <= 3) + { + errorArgument("Missing argument to '%s'", a->argv[2]); + rc = E_FAIL; + break; + } + CHECK_ERROR_RET(adapter, COMSETTER(BridgedInterface)(Bstr(a->argv[3]).raw()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(adapter, COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged), RTEXITCODE_FAILURE); + } + else if (!strcmp(a->argv[2], "intnet")) + { + if (a->argc <= 3) + { + errorArgument("Missing argument to '%s'", a->argv[2]); + rc = E_FAIL; + break; + } + CHECK_ERROR_RET(adapter, COMSETTER(InternalNetwork)(Bstr(a->argv[3]).raw()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(adapter, COMSETTER(AttachmentType)(NetworkAttachmentType_Internal), RTEXITCODE_FAILURE); + } +#if defined(VBOX_WITH_NETFLT) + else if (!strcmp(a->argv[2], "hostonly")) + { + if (a->argc <= 3) + { + errorArgument("Missing argument to '%s'", a->argv[2]); + rc = E_FAIL; + break; + } + CHECK_ERROR_RET(adapter, COMSETTER(HostOnlyInterface)(Bstr(a->argv[3]).raw()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(adapter, COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly), RTEXITCODE_FAILURE); + } +#endif + else if (!strcmp(a->argv[2], "generic")) + { + if (a->argc <= 3) + { + errorArgument("Missing argument to '%s'", a->argv[2]); + rc = E_FAIL; + break; + } + CHECK_ERROR_RET(adapter, COMSETTER(GenericDriver)(Bstr(a->argv[3]).raw()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(adapter, COMSETTER(AttachmentType)(NetworkAttachmentType_Generic), RTEXITCODE_FAILURE); + } + else if (!strcmp(a->argv[2], "natnetwork")) + { + if (a->argc <= 3) + { + errorArgument("Missing argument to '%s'", a->argv[2]); + rc = E_FAIL; + break; + } + CHECK_ERROR_RET(adapter, COMSETTER(NATNetwork)(Bstr(a->argv[3]).raw()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(adapter, COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork), RTEXITCODE_FAILURE); + } + /** @todo obsolete, remove eventually */ + else if (!strcmp(a->argv[2], "vde")) + { + if (a->argc <= 3) + { + errorArgument("Missing argument to '%s'", a->argv[2]); + rc = E_FAIL; + break; + } + CHECK_ERROR_RET(adapter, COMSETTER(AttachmentType)(NetworkAttachmentType_Generic), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(adapter, SetProperty(Bstr("name").raw(), Bstr(a->argv[3]).raw()), RTEXITCODE_FAILURE); + } + else + { + errorArgument("Invalid type '%s' specfied for NIC %lu", Utf8Str(a->argv[2]).c_str(), n); + rc = E_FAIL; + break; + } + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + else + RTMsgError("The NIC %d is currently disabled and thus its attachment type can't be changed", n); + } + } + else if ( !strcmp(a->argv[1], "vrde") + || !strcmp(a->argv[1], "vrdp")) + { + if (!strcmp(a->argv[1], "vrdp")) + RTStrmPrintf(g_pStdErr, "Warning: 'vrdp' is deprecated. Use 'vrde'.\n"); + + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + if (vrdeServer) + { + if (!strcmp(a->argv[2], "on")) + { + CHECK_ERROR_BREAK(vrdeServer, COMSETTER(Enabled)(TRUE)); + } + else if (!strcmp(a->argv[2], "off")) + { + CHECK_ERROR_BREAK(vrdeServer, COMSETTER(Enabled)(FALSE)); + } + else + { + errorArgument("Invalid remote desktop server state '%s'", Utf8Str(a->argv[2]).c_str()); + rc = E_FAIL; + break; + } + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + } + else if ( !strcmp(a->argv[1], "vrdeport") + || !strcmp(a->argv[1], "vrdpport")) + { + if (!strcmp(a->argv[1], "vrdpport")) + RTStrmPrintf(g_pStdErr, "Warning: 'vrdpport' is deprecated. Use 'vrdeport'.\n"); + + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + if (vrdeServer) + { + Bstr ports; + + if (!strcmp(a->argv[2], "default")) + ports = "0"; + else + ports = a->argv[2]; + + CHECK_ERROR_BREAK(vrdeServer, SetVRDEProperty(Bstr("TCP/Ports").raw(), ports.raw())); + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + } + else if ( !strcmp(a->argv[1], "vrdevideochannelquality") + || !strcmp(a->argv[1], "vrdpvideochannelquality")) + { + if (!strcmp(a->argv[1], "vrdpvideochannelquality")) + RTStrmPrintf(g_pStdErr, "Warning: 'vrdpvideochannelquality' is deprecated. Use 'vrdevideochannelquality'.\n"); + + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + if (vrdeServer) + { + Bstr value = a->argv[2]; + + CHECK_ERROR(vrdeServer, SetVRDEProperty(Bstr("VideoChannel/Quality").raw(), value.raw())); + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + } + else if (!strcmp(a->argv[1], "vrdeproperty")) + { + if (a->argc <= 1 + 1) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + if (vrdeServer) + { + /* Parse 'name=value' */ + char *pszProperty = RTStrDup(a->argv[2]); + if (pszProperty) + { + char *pDelimiter = strchr(pszProperty, '='); + if (pDelimiter) + { + *pDelimiter = '\0'; + + Bstr bstrName = pszProperty; + Bstr bstrValue = &pDelimiter[1]; + CHECK_ERROR(vrdeServer, SetVRDEProperty(bstrName.raw(), bstrValue.raw())); + if (SUCCEEDED(rc)) + fNeedsSaving = true; + } + else + { + errorArgument("Invalid vrdeproperty argument '%s'", a->argv[2]); + rc = E_FAIL; + } + RTStrFree(pszProperty); + } + else + { + RTStrmPrintf(g_pStdErr, "Error: Failed to allocate memory for VRDE property '%s'\n", a->argv[2]); + rc = E_FAIL; + } + } + if (FAILED(rc)) + { + break; + } + } + else if ( !strcmp(a->argv[1], "usbattach") + || !strcmp(a->argv[1], "usbdetach")) + { + if (a->argc < 3) + { + errorSyntax(USAGE_CONTROLVM, "Not enough parameters"); + rc = E_FAIL; + break; + } + else if (a->argc == 4 || a->argc > 5) + { + errorSyntax(USAGE_CONTROLVM, "Wrong number of arguments"); + rc = E_FAIL; + break; + } + + bool attach = !strcmp(a->argv[1], "usbattach"); + + Bstr usbId = a->argv[2]; + Bstr captureFilename; + + if (a->argc == 5) + { + if (!strcmp(a->argv[3], "--capturefile")) + captureFilename = a->argv[4]; + else + { + errorArgument("Invalid parameter '%s'", a->argv[3]); + rc = E_FAIL; + break; + } + } + + Guid guid(usbId); + if (!guid.isValid()) + { + // assume address + if (attach) + { + ComPtr<IHost> host; + CHECK_ERROR_BREAK(a->virtualBox, COMGETTER(Host)(host.asOutParam())); + SafeIfaceArray <IHostUSBDevice> coll; + CHECK_ERROR_BREAK(host, COMGETTER(USBDevices)(ComSafeArrayAsOutParam(coll))); + ComPtr<IHostUSBDevice> dev; + CHECK_ERROR_BREAK(host, FindUSBDeviceByAddress(Bstr(a->argv[2]).raw(), + dev.asOutParam())); + CHECK_ERROR_BREAK(dev, COMGETTER(Id)(usbId.asOutParam())); + } + else + { + SafeIfaceArray <IUSBDevice> coll; + CHECK_ERROR_BREAK(console, COMGETTER(USBDevices)(ComSafeArrayAsOutParam(coll))); + ComPtr<IUSBDevice> dev; + CHECK_ERROR_BREAK(console, FindUSBDeviceByAddress(Bstr(a->argv[2]).raw(), + dev.asOutParam())); + CHECK_ERROR_BREAK(dev, COMGETTER(Id)(usbId.asOutParam())); + } + } + else if (guid.isZero()) + { + errorArgument("Zero UUID argument '%s'", a->argv[2]); + rc = E_FAIL; + break; + } + + if (attach) + CHECK_ERROR_BREAK(console, AttachUSBDevice(usbId.raw(), captureFilename.raw())); + else + { + ComPtr<IUSBDevice> dev; + CHECK_ERROR_BREAK(console, DetachUSBDevice(usbId.raw(), + dev.asOutParam())); + } + } + else if (!strcmp(a->argv[1], "setvideomodehint")) + { + if (a->argc != 5 && a->argc != 6 && a->argc != 7 && a->argc != 9) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + bool fEnabled = true; + uint32_t uXRes = RTStrToUInt32(a->argv[2]); + uint32_t uYRes = RTStrToUInt32(a->argv[3]); + uint32_t uBpp = RTStrToUInt32(a->argv[4]); + uint32_t uDisplayIdx = 0; + bool fChangeOrigin = false; + int32_t iOriginX = 0; + int32_t iOriginY = 0; + if (a->argc >= 6) + uDisplayIdx = RTStrToUInt32(a->argv[5]); + if (a->argc >= 7) + { + int vrc = parseBool(a->argv[6], &fEnabled); + if (RT_FAILURE(vrc)) + { + errorSyntax(USAGE_CONTROLVM, "Either \"yes\" or \"no\" is expected"); + rc = E_FAIL; + break; + } + fEnabled = !RTStrICmp(a->argv[6], "yes"); + } + if (a->argc == 9) + { + iOriginX = RTStrToInt32(a->argv[7]); + iOriginY = RTStrToInt32(a->argv[8]); + fChangeOrigin = true; + } + + ComPtr<IDisplay> pDisplay; + CHECK_ERROR_BREAK(console, COMGETTER(Display)(pDisplay.asOutParam())); + if (!pDisplay) + { + RTMsgError("Guest not running"); + rc = E_FAIL; + break; + } + CHECK_ERROR_BREAK(pDisplay, SetVideoModeHint(uDisplayIdx, fEnabled, + fChangeOrigin, iOriginX, iOriginY, + uXRes, uYRes, uBpp)); + } + else if (!strcmp(a->argv[1], "setscreenlayout")) + { + if (a->argc < 4) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + ComPtr<IDisplay> pDisplay; + CHECK_ERROR_BREAK(console, COMGETTER(Display)(pDisplay.asOutParam())); + if (!pDisplay) + { + RTMsgError("Guest not running"); + rc = E_FAIL; + break; + } + + com::SafeIfaceArray<IGuestScreenInfo> aGuestScreenInfos; + + /* Parse "<display> on|primary <xorigin> <yorigin> <xres> <yres> <bpp> | off" sequences. */ + int argc = a->argc - 2; + char **argv = &a->argv[2]; + while (argc >= 2) + { + ULONG aDisplay = RTStrToUInt32(argv[0]); + BOOL aPrimary = FALSE; + + GuestMonitorStatus_T aStatus; + if (RTStrICmp(argv[1], "primary") == 0) + { + aStatus = GuestMonitorStatus_Enabled; + aPrimary = TRUE; + } + else if (RTStrICmp(argv[1], "on") == 0) + aStatus = GuestMonitorStatus_Enabled; + else if (RTStrICmp(argv[1], "off") == 0) + aStatus = GuestMonitorStatus_Disabled; + else + { + errorSyntax(USAGE_CONTROLVM, "Display status must be <on> or <off>"); + rc = E_FAIL; + break; + } + + BOOL aChangeOrigin = FALSE; + LONG aOriginX = 0; + LONG aOriginY = 0; + ULONG aWidth = 0; + ULONG aHeight = 0; + ULONG aBitsPerPixel = 0; + if (aStatus == GuestMonitorStatus_Enabled) + { + if (argc < 7) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + aChangeOrigin = TRUE; + aOriginX = RTStrToUInt32(argv[2]); + aOriginY = RTStrToUInt32(argv[3]); + aWidth = RTStrToUInt32(argv[4]); + aHeight = RTStrToUInt32(argv[5]); + aBitsPerPixel = RTStrToUInt32(argv[6]); + + argc -= 7; + argv += 7; + } + else + { + argc -= 2; + argv += 2; + } + + ComPtr<IGuestScreenInfo> pInfo; + CHECK_ERROR_BREAK(pDisplay, CreateGuestScreenInfo(aDisplay, aStatus, aPrimary, aChangeOrigin, + aOriginX, aOriginY, aWidth, aHeight, aBitsPerPixel, + pInfo.asOutParam())); + aGuestScreenInfos.push_back(pInfo); + } + + if (FAILED(rc)) + break; + + CHECK_ERROR_BREAK(pDisplay, SetScreenLayout(ScreenLayoutMode_Apply, ComSafeArrayAsInParam(aGuestScreenInfos))); + } + else if (!strcmp(a->argv[1], "setcredentials")) + { + bool fAllowLocalLogon = true; + if ( a->argc == 7 + || ( a->argc == 8 + && ( !strcmp(a->argv[3], "-p") + || !strcmp(a->argv[3], "--passwordfile")))) + { + if ( strcmp(a->argv[5 + (a->argc - 7)], "--allowlocallogon") + && strcmp(a->argv[5 + (a->argc - 7)], "-allowlocallogon")) + { + errorArgument("Invalid parameter '%s'", a->argv[5]); + rc = E_FAIL; + break; + } + if (!strcmp(a->argv[6 + (a->argc - 7)], "no")) + fAllowLocalLogon = false; + } + else if ( a->argc != 5 + && ( a->argc != 6 + || ( strcmp(a->argv[3], "-p") + && strcmp(a->argv[3], "--passwordfile")))) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + Utf8Str passwd, domain; + if (a->argc == 5 || a->argc == 7) + { + passwd = a->argv[3]; + domain = a->argv[4]; + } + else + { + RTEXITCODE rcExit = readPasswordFile(a->argv[4], &passwd); + if (rcExit != RTEXITCODE_SUCCESS) + { + rc = E_FAIL; + break; + } + domain = a->argv[5]; + } + + ComPtr<IGuest> pGuest; + CHECK_ERROR_BREAK(console, COMGETTER(Guest)(pGuest.asOutParam())); + if (!pGuest) + { + RTMsgError("Guest not running"); + rc = E_FAIL; + break; + } + CHECK_ERROR_BREAK(pGuest, SetCredentials(Bstr(a->argv[2]).raw(), + Bstr(passwd).raw(), + Bstr(domain).raw(), + fAllowLocalLogon)); + } +#if 0 /** @todo review & remove */ + else if (!strcmp(a->argv[1], "dvdattach")) + { + Bstr uuid; + if (a->argc != 3) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + ComPtr<IMedium> dvdMedium; + + /* unmount? */ + if (!strcmp(a->argv[2], "none")) + { + /* nothing to do, NULL object will cause unmount */ + } + /* host drive? */ + else if (!strncmp(a->argv[2], "host:", 5)) + { + ComPtr<IHost> host; + CHECK_ERROR(a->virtualBox, COMGETTER(Host)(host.asOutParam())); + + rc = host->FindHostDVDDrive(Bstr(a->argv[2] + 5), dvdMedium.asOutParam()); + if (!dvdMedium) + { + errorArgument("Invalid host DVD drive name \"%s\"", + a->argv[2] + 5); + rc = E_FAIL; + break; + } + } + else + { + /* first assume it's a UUID */ + uuid = a->argv[2]; + rc = a->virtualBox->GetDVDImage(uuid, dvdMedium.asOutParam()); + if (FAILED(rc) || !dvdMedium) + { + /* must be a filename, check if it's in the collection */ + rc = a->virtualBox->FindDVDImage(Bstr(a->argv[2]), dvdMedium.asOutParam()); + /* not registered, do that on the fly */ + if (!dvdMedium) + { + Bstr emptyUUID; + CHECK_ERROR(a->virtualBox, OpenDVDImage(Bstr(a->argv[2]), emptyUUID, dvdMedium.asOutParam())); + } + } + if (!dvdMedium) + { + rc = E_FAIL; + break; + } + } + + /** @todo generalize this, allow arbitrary number of DVD drives + * and as a consequence multiple attachments and different + * storage controllers. */ + if (dvdMedium) + dvdMedium->COMGETTER(Id)(uuid.asOutParam()); + else + uuid = Guid().toString(); + CHECK_ERROR(machine, MountMedium(Bstr("IDE Controller"), 1, 0, uuid, FALSE /* aForce */)); + } + else if (!strcmp(a->argv[1], "floppyattach")) + { + Bstr uuid; + if (a->argc != 3) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + ComPtr<IMedium> floppyMedium; + + /* unmount? */ + if (!strcmp(a->argv[2], "none")) + { + /* nothing to do, NULL object will cause unmount */ + } + /* host drive? */ + else if (!strncmp(a->argv[2], "host:", 5)) + { + ComPtr<IHost> host; + CHECK_ERROR(a->virtualBox, COMGETTER(Host)(host.asOutParam())); + host->FindHostFloppyDrive(Bstr(a->argv[2] + 5), floppyMedium.asOutParam()); + if (!floppyMedium) + { + errorArgument("Invalid host floppy drive name \"%s\"", + a->argv[2] + 5); + rc = E_FAIL; + break; + } + } + else + { + /* first assume it's a UUID */ + uuid = a->argv[2]; + rc = a->virtualBox->GetFloppyImage(uuid, floppyMedium.asOutParam()); + if (FAILED(rc) || !floppyMedium) + { + /* must be a filename, check if it's in the collection */ + rc = a->virtualBox->FindFloppyImage(Bstr(a->argv[2]), floppyMedium.asOutParam()); + /* not registered, do that on the fly */ + if (!floppyMedium) + { + Bstr emptyUUID; + CHECK_ERROR(a->virtualBox, OpenFloppyImage(Bstr(a->argv[2]), emptyUUID, floppyMedium.asOutParam())); + } + } + if (!floppyMedium) + { + rc = E_FAIL; + break; + } + } + floppyMedium->COMGETTER(Id)(uuid.asOutParam()); + CHECK_ERROR(machine, MountMedium(Bstr("Floppy Controller"), 0, 0, uuid, FALSE /* aForce */)); + } +#endif /* obsolete dvdattach/floppyattach */ + else if (!strcmp(a->argv[1], "guestmemoryballoon")) + { + if (a->argc != 3) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + uint32_t uVal; + int vrc; + vrc = RTStrToUInt32Ex(a->argv[2], NULL, 0, &uVal); + if (vrc != VINF_SUCCESS) + { + errorArgument("Error parsing guest memory balloon size '%s'", a->argv[2]); + rc = E_FAIL; + break; + } + /* guest is running; update IGuest */ + ComPtr<IGuest> pGuest; + rc = console->COMGETTER(Guest)(pGuest.asOutParam()); + if (SUCCEEDED(rc)) + { + if (!pGuest) + { + RTMsgError("Guest not running"); + rc = E_FAIL; + break; + } + CHECK_ERROR(pGuest, COMSETTER(MemoryBalloonSize)(uVal)); + } + } + else if (!strcmp(a->argv[1], "teleport")) + { + Bstr bstrHostname; + uint32_t uMaxDowntime = 250 /*ms*/; + uint32_t uPort = UINT32_MAX; + uint32_t cMsTimeout = 0; + Utf8Str strPassword; + static const RTGETOPTDEF s_aTeleportOptions[] = + { + { "--host", 'h', RTGETOPT_REQ_STRING }, /** @todo RTGETOPT_FLAG_MANDATORY */ + { "--hostname", 'h', RTGETOPT_REQ_STRING }, /** @todo remove this */ + { "--maxdowntime", 'd', RTGETOPT_REQ_UINT32 }, + { "--port", 'P', RTGETOPT_REQ_UINT32 }, /** @todo RTGETOPT_FLAG_MANDATORY */ + { "--passwordfile", 'p', RTGETOPT_REQ_STRING }, + { "--password", 'W', RTGETOPT_REQ_STRING }, + { "--timeout", 't', RTGETOPT_REQ_UINT32 }, + { "--detailed-progress", 'D', RTGETOPT_REQ_NOTHING } + }; + RTGETOPTSTATE GetOptState; + RTGetOptInit(&GetOptState, a->argc, a->argv, s_aTeleportOptions, RT_ELEMENTS(s_aTeleportOptions), 2, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + int ch; + RTGETOPTUNION Value; + while ( SUCCEEDED(rc) + && (ch = RTGetOpt(&GetOptState, &Value))) + { + switch (ch) + { + case 'h': bstrHostname = Value.psz; break; + case 'd': uMaxDowntime = Value.u32; break; + case 'D': g_fDetailedProgress = true; break; + case 'P': uPort = Value.u32; break; + case 'p': + { + RTEXITCODE rcExit = readPasswordFile(Value.psz, &strPassword); + if (rcExit != RTEXITCODE_SUCCESS) + rc = E_FAIL; + break; + } + case 'W': strPassword = Value.psz; break; + case 't': cMsTimeout = Value.u32; break; + default: + errorGetOpt(USAGE_CONTROLVM, ch, &Value); + rc = E_FAIL; + break; + } + } + if (FAILED(rc)) + break; + + ComPtr<IProgress> progress; + CHECK_ERROR_BREAK(console, Teleport(bstrHostname.raw(), uPort, + Bstr(strPassword).raw(), + uMaxDowntime, + progress.asOutParam())); + + if (cMsTimeout) + { + rc = progress->COMSETTER(Timeout)(cMsTimeout); + if (FAILED(rc) && rc != VBOX_E_INVALID_OBJECT_STATE) + CHECK_ERROR_BREAK(progress, COMSETTER(Timeout)(cMsTimeout)); /* lazyness */ + } + + rc = showProgress(progress); + CHECK_PROGRESS_ERROR(progress, ("Teleportation failed")); + } + else if (!strcmp(a->argv[1], "screenshotpng")) + { + if (a->argc <= 2 || a->argc > 4) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + int vrc; + uint32_t iScreen = 0; + if (a->argc == 4) + { + vrc = RTStrToUInt32Ex(a->argv[3], NULL, 0, &iScreen); + if (vrc != VINF_SUCCESS) + { + errorArgument("Error parsing display number '%s'", a->argv[3]); + rc = E_FAIL; + break; + } + } + ComPtr<IDisplay> pDisplay; + CHECK_ERROR_BREAK(console, COMGETTER(Display)(pDisplay.asOutParam())); + if (!pDisplay) + { + RTMsgError("Guest not running"); + rc = E_FAIL; + break; + } + ULONG width, height, bpp; + LONG xOrigin, yOrigin; + GuestMonitorStatus_T monitorStatus; + CHECK_ERROR_BREAK(pDisplay, GetScreenResolution(iScreen, &width, &height, &bpp, &xOrigin, &yOrigin, &monitorStatus)); + com::SafeArray<BYTE> saScreenshot; + CHECK_ERROR_BREAK(pDisplay, TakeScreenShotToArray(iScreen, width, height, BitmapFormat_PNG, ComSafeArrayAsOutParam(saScreenshot))); + RTFILE pngFile = NIL_RTFILE; + vrc = RTFileOpen(&pngFile, a->argv[2], RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_TRUNCATE | RTFILE_O_DENY_ALL); + if (RT_FAILURE(vrc)) + { + RTMsgError("Failed to create file '%s' (%Rrc)", a->argv[2], vrc); + rc = E_FAIL; + break; + } + vrc = RTFileWrite(pngFile, saScreenshot.raw(), saScreenshot.size(), NULL); + if (RT_FAILURE(vrc)) + { + RTMsgError("Failed to write screenshot to file '%s' (%Rrc)", a->argv[2], vrc); + rc = E_FAIL; + } + RTFileClose(pngFile); + } +#ifdef VBOX_WITH_RECORDING + else if (!strcmp(a->argv[1], "recording")) + { + if (a->argc < 3) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + ComPtr<IRecordingSettings> recordingSettings; + CHECK_ERROR_BREAK(machine, COMGETTER(RecordingSettings)(recordingSettings.asOutParam())); + + SafeIfaceArray <IRecordingScreenSettings> saRecordingScreenScreens; + CHECK_ERROR_BREAK(recordingSettings, COMGETTER(Screens)(ComSafeArrayAsOutParam(saRecordingScreenScreens))); + + /* Note: For now all screens have the same configuration. */ + + /* + * Note: Commands starting with "vcp" are the deprecated versions and are + * kept to ensure backwards compatibility. + */ + if (!strcmp(a->argv[2], "on")) + { + CHECK_ERROR_RET(recordingSettings, COMSETTER(Enabled)(TRUE), RTEXITCODE_FAILURE); + } + else if (!strcmp(a->argv[2], "off")) + { + CHECK_ERROR_RET(recordingSettings, COMSETTER(Enabled)(FALSE), RTEXITCODE_FAILURE); + } + else if (!strcmp(a->argv[2], "screens")) + { + ULONG cMonitors = 64; + CHECK_ERROR_BREAK(machine, COMGETTER(MonitorCount)(&cMonitors)); + com::SafeArray<BOOL> saScreens(cMonitors); + if ( a->argc == 4 + && !strcmp(a->argv[3], "all")) + { + /* enable all screens */ + for (unsigned i = 0; i < cMonitors; i++) + saScreens[i] = true; + } + else if ( a->argc == 4 + && !strcmp(a->argv[3], "none")) + { + /* disable all screens */ + for (unsigned i = 0; i < cMonitors; i++) + saScreens[i] = false; + + /** @todo r=andy What if this is specified? */ + } + else + { + /* enable selected screens */ + for (unsigned i = 0; i < cMonitors; i++) + saScreens[i] = false; + for (int i = 3; SUCCEEDED(rc) && i < a->argc; i++) + { + uint32_t iScreen; + int vrc = RTStrToUInt32Ex(a->argv[i], NULL, 0, &iScreen); + if (vrc != VINF_SUCCESS) + { + errorArgument("Error parsing display number '%s'", a->argv[i]); + rc = E_FAIL; + break; + } + if (iScreen >= cMonitors) + { + errorArgument("Invalid screen ID specified '%u'", iScreen); + rc = E_FAIL; + break; + } + saScreens[iScreen] = true; + } + } + + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR_BREAK(saRecordingScreenScreens[i], COMSETTER(Enabled)(saScreens[i])); + } + else if (!strcmp(a->argv[2], "filename")) + { + if (a->argc != 4) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR_BREAK(saRecordingScreenScreens[i], COMSETTER(Filename)(Bstr(a->argv[3]).raw())); + } + else if ( !strcmp(a->argv[2], "videores") + || !strcmp(a->argv[2], "videoresolution")) + { + if (a->argc != 5) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + uint32_t uWidth; + int vrc = RTStrToUInt32Ex(a->argv[3], NULL, 0, &uWidth); + if (RT_FAILURE(vrc)) + { + errorArgument("Error parsing video width '%s'", a->argv[3]); + rc = E_FAIL; + break; + } + + uint32_t uHeight; + vrc = RTStrToUInt32Ex(a->argv[4], NULL, 0, &uHeight); + if (RT_FAILURE(vrc)) + { + errorArgument("Error parsing video height '%s'", a->argv[4]); + rc = E_FAIL; + break; + } + + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + { + CHECK_ERROR_BREAK(saRecordingScreenScreens[i], COMSETTER(VideoWidth)(uWidth)); + CHECK_ERROR_BREAK(saRecordingScreenScreens[i], COMSETTER(VideoHeight)(uHeight)); + } + } + else if (!strcmp(a->argv[2], "videorate")) + { + if (a->argc != 4) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + uint32_t uRate; + int vrc = RTStrToUInt32Ex(a->argv[3], NULL, 0, &uRate); + if (RT_FAILURE(vrc)) + { + errorArgument("Error parsing video rate '%s'", a->argv[3]); + rc = E_FAIL; + break; + } + + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR_BREAK(saRecordingScreenScreens[i], COMSETTER(VideoRate)(uRate)); + } + else if (!strcmp(a->argv[2], "videofps")) + { + if (a->argc != 4) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + uint32_t uFPS; + int vrc = RTStrToUInt32Ex(a->argv[3], NULL, 0, &uFPS); + if (RT_FAILURE(vrc)) + { + errorArgument("Error parsing video FPS '%s'", a->argv[3]); + rc = E_FAIL; + break; + } + + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR_BREAK(saRecordingScreenScreens[i], COMSETTER(VideoFPS)(uFPS)); + } + else if (!strcmp(a->argv[2], "maxtime")) + { + if (a->argc != 4) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + uint32_t uMaxTime; + int vrc = RTStrToUInt32Ex(a->argv[3], NULL, 0, &uMaxTime); + if (RT_FAILURE(vrc)) + { + errorArgument("Error parsing maximum time '%s'", a->argv[3]); + rc = E_FAIL; + break; + } + + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR_BREAK(saRecordingScreenScreens[i], COMSETTER(MaxTime)(uMaxTime)); + } + else if (!strcmp(a->argv[2], "maxfilesize")) + { + if (a->argc != 4) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + uint32_t uMaxFileSize; + int vrc = RTStrToUInt32Ex(a->argv[3], NULL, 0, &uMaxFileSize); + if (RT_FAILURE(vrc)) + { + errorArgument("Error parsing maximum file size '%s'", a->argv[3]); + rc = E_FAIL; + break; + } + + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR_BREAK(saRecordingScreenScreens[i], COMSETTER(MaxFileSize)(uMaxFileSize)); + } + else if (!strcmp(a->argv[2], "opts")) + { + if (a->argc != 4) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + rc = E_FAIL; + break; + } + + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR_BREAK(saRecordingScreenScreens[i], COMSETTER(Options)(Bstr(a->argv[3]).raw())); + } + } +#endif /* VBOX_WITH_RECORDING */ + else if (!strcmp(a->argv[1], "webcam")) + { + if (a->argc < 3) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + + ComPtr<IEmulatedUSB> pEmulatedUSB; + CHECK_ERROR_BREAK(console, COMGETTER(EmulatedUSB)(pEmulatedUSB.asOutParam())); + if (!pEmulatedUSB) + { + RTMsgError("Guest not running"); + rc = E_FAIL; + break; + } + + if (!strcmp(a->argv[2], "attach")) + { + Bstr path(""); + if (a->argc >= 4) + path = a->argv[3]; + Bstr settings(""); + if (a->argc >= 5) + settings = a->argv[4]; + CHECK_ERROR_BREAK(pEmulatedUSB, WebcamAttach(path.raw(), settings.raw())); + } + else if (!strcmp(a->argv[2], "detach")) + { + Bstr path(""); + if (a->argc >= 4) + path = a->argv[3]; + CHECK_ERROR_BREAK(pEmulatedUSB, WebcamDetach(path.raw())); + } + else if (!strcmp(a->argv[2], "list")) + { + com::SafeArray <BSTR> webcams; + CHECK_ERROR_BREAK(pEmulatedUSB, COMGETTER(Webcams)(ComSafeArrayAsOutParam(webcams))); + for (size_t i = 0; i < webcams.size(); ++i) + { + RTPrintf("%ls\n", webcams[i][0]? webcams[i]: Bstr("default").raw()); + } + } + else + { + errorArgument("Invalid argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + } + else if (!strcmp(a->argv[1], "addencpassword")) + { + if ( a->argc != 4 + && a->argc != 6) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + break; + } + + BOOL fRemoveOnSuspend = FALSE; + if (a->argc == 6) + { + if ( strcmp(a->argv[4], "--removeonsuspend") + || ( strcmp(a->argv[5], "yes") + && strcmp(a->argv[5], "no"))) + { + errorSyntax(USAGE_CONTROLVM, "Invalid parameters"); + break; + } + if (!strcmp(a->argv[5], "yes")) + fRemoveOnSuspend = TRUE; + } + + Bstr bstrPwId(a->argv[2]); + Utf8Str strPassword; + + if (!RTStrCmp(a->argv[3], "-")) + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter password:"); + if (rcExit == RTEXITCODE_FAILURE) + break; + } + else + { + RTEXITCODE rcExit = readPasswordFile(a->argv[3], &strPassword); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError("Failed to read new password from file"); + break; + } + } + + CHECK_ERROR_BREAK(console, AddDiskEncryptionPassword(bstrPwId.raw(), Bstr(strPassword).raw(), fRemoveOnSuspend)); + } + else if (!strcmp(a->argv[1], "removeencpassword")) + { + if (a->argc != 3) + { + errorSyntax(USAGE_CONTROLVM, "Incorrect number of parameters"); + break; + } + Bstr bstrPwId(a->argv[2]); + CHECK_ERROR_BREAK(console, RemoveDiskEncryptionPassword(bstrPwId.raw())); + } + else if (!strcmp(a->argv[1], "removeallencpasswords")) + { + CHECK_ERROR_BREAK(console, ClearAllDiskEncryptionPasswords()); + } + else if (!strncmp(a->argv[1], "changeuartmode", 14)) + { + unsigned n = parseNum(&a->argv[1][14], 4, "UART"); + if (!n) + { + rc = E_FAIL; + break; + } + if (a->argc < 3) + { + errorArgument("Missing argument to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + + ComPtr<ISerialPort> uart; + + CHECK_ERROR_BREAK(sessionMachine, GetSerialPort(n - 1, uart.asOutParam())); + ASSERT(uart); + + if (!RTStrICmp(a->argv[2], "disconnected")) + { + if (a->argc != 3) + { + errorArgument("Incorrect arguments to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_Disconnected)); + } + else if ( !RTStrICmp(a->argv[2], "server") + || !RTStrICmp(a->argv[2], "client") + || !RTStrICmp(a->argv[2], "tcpserver") + || !RTStrICmp(a->argv[2], "tcpclient") + || !RTStrICmp(a->argv[2], "file")) + { + const char *pszMode = a->argv[2]; + if (a->argc != 4) + { + errorArgument("Incorrect arguments to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + + CHECK_ERROR(uart, COMSETTER(Path)(Bstr(a->argv[3]).raw())); + + /* + * Change to disconnected first to get changes in just a parameter causing + * the correct changes later on. + */ + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_Disconnected)); + if (!RTStrICmp(pszMode, "server")) + { + CHECK_ERROR(uart, COMSETTER(Server)(TRUE)); + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_HostPipe)); + } + else if (!RTStrICmp(pszMode, "client")) + { + CHECK_ERROR(uart, COMSETTER(Server)(FALSE)); + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_HostPipe)); + } + else if (!RTStrICmp(pszMode, "tcpserver")) + { + CHECK_ERROR(uart, COMSETTER(Server)(TRUE)); + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_TCP)); + } + else if (!RTStrICmp(pszMode, "tcpclient")) + { + CHECK_ERROR(uart, COMSETTER(Server)(FALSE)); + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_TCP)); + } + else if (!RTStrICmp(pszMode, "file")) + { + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_RawFile)); + } + } + else + { + if (a->argc != 3) + { + errorArgument("Incorrect arguments to '%s'", a->argv[1]); + rc = E_FAIL; + break; + } + CHECK_ERROR(uart, COMSETTER(Path)(Bstr(a->argv[2]).raw())); + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_HostDevice)); + } + } + else + { + errorSyntax(USAGE_CONTROLVM, "Invalid parameter '%s'", a->argv[1]); + rc = E_FAIL; + } + } while (0); + + /* The client has to trigger saving the state explicitely. */ + if (fNeedsSaving) + CHECK_ERROR(sessionMachine, SaveSettings()); + + a->session->UnlockMachine(); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageDHCPServer.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageDHCPServer.cpp new file mode 100644 index 00000000..499c60a3 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageDHCPServer.cpp @@ -0,0 +1,525 @@ +/* $Id: VBoxManageDHCPServer.cpp $ */ +/** @file + * VBoxManage - Implementation of dhcpserver command. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef VBOX_ONLY_DOCS +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> +#endif /* !VBOX_ONLY_DOCS */ + +#include <iprt/cidr.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/net.h> +#include <iprt/getopt.h> +#include <iprt/ctype.h> + +#include <VBox/log.h> + +#include "VBoxManage.h" + +#include <string> +#include <vector> +#include <map> + +#ifndef VBOX_ONLY_DOCS +using namespace com; + +typedef enum enMainOpCodes +{ + OP_ADD = 1000, + OP_REMOVE, + OP_MODIFY, + OP_RESTART +} OPCODE; + +typedef std::pair<DhcpOpt_T, std::string> DhcpOptSpec; +typedef std::vector<DhcpOptSpec> DhcpOpts; +typedef DhcpOpts::iterator DhcpOptIterator; + +typedef std::vector<DhcpOpt_T> DhcpOptIds; +typedef DhcpOptIds::iterator DhcpOptIdIterator; + +struct VmNameSlotKey +{ + const std::string VmName; + uint8_t u8Slot; + + VmNameSlotKey(const std::string &aVmName, uint8_t aSlot) + : VmName(aVmName) + , u8Slot(aSlot) + {} + + bool operator< (const VmNameSlotKey& that) const + { + if (VmName == that.VmName) + return u8Slot < that.u8Slot; + else + return VmName < that.VmName; + } +}; + +typedef std::map<VmNameSlotKey, DhcpOpts> VmSlot2OptionsM; +typedef VmSlot2OptionsM::iterator VmSlot2OptionsIterator; +typedef VmSlot2OptionsM::value_type VmSlot2OptionsPair; + +typedef std::map<VmNameSlotKey, DhcpOptIds> VmSlot2OptionIdsM; +typedef VmSlot2OptionIdsM::iterator VmSlot2OptionIdsIterator; + +typedef std::vector<VmNameSlotKey> VmConfigs; + +static const RTGETOPTDEF g_aDHCPIPOptions[] = +{ + { "--netname", 't', RTGETOPT_REQ_STRING }, /* we use 't' instead of 'n' to avoid + * 1. the misspelled "-enable" long option to be treated as 'e' (for -enable) + 'n' (for -netname) + "<the_rest_opt>" (for net name) + * 2. the misspelled "-netmask" to be treated as 'n' (for -netname) + "<the_rest_opt>" (for net name) + */ + { "-netname", 't', RTGETOPT_REQ_STRING }, // deprecated (if removed check below) + { "--ifname", 'f', RTGETOPT_REQ_STRING }, /* we use 'f' instead of 'i' to avoid + * 1. the misspelled "-disable" long option to be treated as 'd' (for -disable) + 'i' (for -ifname) + "<the_rest_opt>" (for if name) + */ + { "-ifname", 'f', RTGETOPT_REQ_STRING }, // deprecated + { "--ip", 'a', RTGETOPT_REQ_STRING }, + { "-ip", 'a', RTGETOPT_REQ_STRING }, // deprecated + { "--netmask", 'm', RTGETOPT_REQ_STRING }, + { "-netmask", 'm', RTGETOPT_REQ_STRING }, // deprecated + { "--lowerip", 'l', RTGETOPT_REQ_STRING }, + { "-lowerip", 'l', RTGETOPT_REQ_STRING }, // deprecated + { "--upperip", 'u', RTGETOPT_REQ_STRING }, + { "-upperip", 'u', RTGETOPT_REQ_STRING }, // deprecated + { "--enable", 'e', RTGETOPT_REQ_NOTHING }, + { "-enable", 'e', RTGETOPT_REQ_NOTHING }, // deprecated + { "--disable", 'd', RTGETOPT_REQ_NOTHING }, + { "-disable", 'd', RTGETOPT_REQ_NOTHING }, // deprecated + { "--options", 'o', RTGETOPT_REQ_NOTHING }, + { "--vm", 'M', RTGETOPT_REQ_STRING}, /* only with -o */ + { "--nic", 'n', RTGETOPT_REQ_UINT8}, /* only with -o and -M */ + { "--id", 'i', RTGETOPT_REQ_UINT8}, /* only with -o */ + { "--value", 'p', RTGETOPT_REQ_STRING}, /* only with -i */ + { "--remove", 'r', RTGETOPT_REQ_NOTHING} /* only with -i */ +}; + +static RTEXITCODE handleOp(HandlerArg *a, OPCODE enmCode, int iStart) +{ + if (a->argc - iStart < 2) + return errorSyntax(USAGE_DHCPSERVER, "Not enough parameters"); + + int index = iStart; + HRESULT rc; + bool fOptionsRead = false; + bool fVmOptionRead = false; + + const char *pszVmName = NULL; + const char *pNetName = NULL; + const char *pIfName = NULL; + const char * pIp = NULL; + const char * pNetmask = NULL; + const char * pLowerIp = NULL; + const char * pUpperIp = NULL; + + uint8_t u8OptId = (uint8_t)~0; + uint8_t u8Slot = (uint8_t)~0; + + int enable = -1; + + DhcpOpts GlobalDhcpOptions; + DhcpOptIds GlobalDhcpOptions2Delete; + VmSlot2OptionsM VmSlot2Options; + VmSlot2OptionIdsM VmSlot2Options2Delete; + VmConfigs VmConfigs2Delete; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, + a->argc, + a->argv, + g_aDHCPIPOptions, + enmCode != OP_REMOVE ? RT_ELEMENTS(g_aDHCPIPOptions) : 4, /* we use only --netname and --ifname for remove*/ + index, + RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 't': // --netname + if(pNetName) + return errorSyntax(USAGE_DHCPSERVER, "You can only specify --netname once."); + else if (pIfName) + return errorSyntax(USAGE_DHCPSERVER, "You can either use a --netname or --ifname for identifying the DHCP server."); + else + { + pNetName = ValueUnion.psz; + } + break; + case 'f': // --ifname + if(pIfName) + return errorSyntax(USAGE_DHCPSERVER, "You can only specify --ifname once."); + else if (pNetName) + return errorSyntax(USAGE_DHCPSERVER, "You can either use a --netname or --ipname for identifying the DHCP server."); + else + { + pIfName = ValueUnion.psz; + } + break; + case 'a': // -ip + if(pIp) + return errorSyntax(USAGE_DHCPSERVER, "You can only specify --ip once."); + else + { + pIp = ValueUnion.psz; + } + break; + case 'm': // --netmask + if(pNetmask) + return errorSyntax(USAGE_DHCPSERVER, "You can only specify --netmask once."); + else + { + pNetmask = ValueUnion.psz; + } + break; + case 'l': // --lowerip + if(pLowerIp) + return errorSyntax(USAGE_DHCPSERVER, "You can only specify --lowerip once."); + else + { + pLowerIp = ValueUnion.psz; + } + break; + case 'u': // --upperip + if(pUpperIp) + return errorSyntax(USAGE_DHCPSERVER, "You can only specify --upperip once."); + else + { + pUpperIp = ValueUnion.psz; + } + break; + case 'e': // --enable + if(enable >= 0) + return errorSyntax(USAGE_DHCPSERVER, "You can specify either --enable or --disable once."); + else + { + enable = 1; + } + break; + case 'd': // --disable + if(enable >= 0) + return errorSyntax(USAGE_DHCPSERVER, "You can specify either --enable or --disable once."); + else + { + enable = 0; + } + break; + case VINF_GETOPT_NOT_OPTION: + return errorSyntax(USAGE_DHCPSERVER, "unhandled parameter: %s", ValueUnion.psz); + break; + + case 'o': // --options + { + // {"--vm", 'n', RTGETOPT_REQ_STRING}, /* only with -o */ + // {"--nic", 'c', RTGETOPT_REQ_UINT8}, /* only with -o and -n*/ + // {"--id", 'i', RTGETOPT_REQ_UINT8}, /* only with -o */ + // {"--value", 'p', RTGETOPT_REQ_STRING} /* only with -i */ + if (fOptionsRead) + return errorSyntax(USAGE_DHCPSERVER, + "previos option edition wasn't finished"); + fOptionsRead = true; + fVmOptionRead = false; /* we want specify new global or vm option*/ + u8Slot = (uint8_t)~0; + u8OptId = (uint8_t)~0; + pszVmName = NULL; + } /* end of --options */ + break; + + case 'M': // --vm + { + if (fVmOptionRead) + return errorSyntax(USAGE_DHCPSERVER, + "previous vm option edition wasn't finished"); + else + fVmOptionRead = true; + u8Slot = (uint8_t)~0; /* clear slor */ + pszVmName = RTStrDup(ValueUnion.psz); + } + break; /* end of --vm */ + + case 'n': // --nic + { + if (!fVmOptionRead) + return errorSyntax(USAGE_DHCPSERVER, + "vm name wasn't specified"); + + u8Slot = ValueUnion.u8; + + if (u8Slot < 1) + return errorSyntax(USAGE_DHCPSERVER, + "invalid NIC number: %u", u8Slot); + --u8Slot; + } + break; /* end of --nic */ + + case 'i': // --id + { + if (!fOptionsRead) + return errorSyntax(USAGE_DHCPSERVER, + "-o wasn't found"); + + u8OptId = ValueUnion.u8; + } + break; /* end of --id */ + + case 'p': // --value + { + if (!fOptionsRead) + return errorSyntax(USAGE_DHCPSERVER, + "-o wasn't found"); + + if (u8OptId == (uint8_t)~0) + return errorSyntax(USAGE_DHCPSERVER, + "--id wasn't found"); + if ( fVmOptionRead + && u8Slot == (uint8_t)~0) + return errorSyntax(USAGE_DHCPSERVER, + "--nic wasn't found"); + + DhcpOpts &opts = fVmOptionRead ? VmSlot2Options[VmNameSlotKey(pszVmName, u8Slot)] + : GlobalDhcpOptions; + std::string strVal = ValueUnion.psz; + opts.push_back(DhcpOptSpec((DhcpOpt_T)u8OptId, strVal)); + + } + break; // --end of value + + case 'r': /* --remove */ + { + if (!fOptionsRead) + return errorSyntax(USAGE_DHCPSERVER, + "-o wasn't found"); + + if (u8OptId == (uint8_t)~0) + return errorSyntax(USAGE_DHCPSERVER, + "--id wasn't found"); + if ( fVmOptionRead + && u8Slot == (uint8_t)~0) + return errorSyntax(USAGE_DHCPSERVER, + "--nic wasn't found"); + + DhcpOptIds &optIds = fVmOptionRead ? VmSlot2Options2Delete[VmNameSlotKey(pszVmName, u8Slot)] + : GlobalDhcpOptions2Delete; + optIds.push_back((DhcpOpt_T)u8OptId); + } + break; /* --end of remove */ + + default: + if (c > 0) + { + if (RT_C_IS_GRAPH(c)) + return errorSyntax(USAGE_DHCPSERVER, "unhandled option: -%c", c); + return errorSyntax(USAGE_DHCPSERVER, "unhandled option: %i", c); + } + if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_DHCPSERVER, "unknown option: %s", ValueUnion.psz); + if (ValueUnion.pDef) + return errorSyntax(USAGE_DHCPSERVER, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + return errorSyntax(USAGE_DHCPSERVER, "%Rrs", c); + } + } + + if(! pNetName && !pIfName) + return errorSyntax(USAGE_DHCPSERVER, "You need to specify either --netname or --ifname to identify the DHCP server"); + + if( enmCode != OP_REMOVE + && enmCode != OP_RESTART + && GlobalDhcpOptions.empty() + && VmSlot2Options.empty() + && GlobalDhcpOptions2Delete.empty() + && VmSlot2Options2Delete.empty()) + { + if(enable < 0 || pIp || pNetmask || pLowerIp || pUpperIp) + { + if(!pIp) + return errorSyntax(USAGE_DHCPSERVER, "You need to specify --ip option"); + + if(!pNetmask) + return errorSyntax(USAGE_DHCPSERVER, "You need to specify --netmask option"); + + if(!pLowerIp) + return errorSyntax(USAGE_DHCPSERVER, "You need to specify --lowerip option"); + + if(!pUpperIp) + return errorSyntax(USAGE_DHCPSERVER, "You need to specify --upperip option"); + } + } + + Bstr NetName; + if(!pNetName) + { + ComPtr<IHost> host; + CHECK_ERROR(a->virtualBox, COMGETTER(Host)(host.asOutParam())); + + ComPtr<IHostNetworkInterface> hif; + CHECK_ERROR(host, FindHostNetworkInterfaceByName(Bstr(pIfName).mutableRaw(), hif.asOutParam())); + if (FAILED(rc)) + return errorArgument("Could not find interface '%s'", pIfName); + + CHECK_ERROR(hif, COMGETTER(NetworkName) (NetName.asOutParam())); + if (FAILED(rc)) + return errorArgument("Could not get network name for the interface '%s'", pIfName); + } + else + { + NetName = Bstr(pNetName); + } + + ComPtr<IDHCPServer> svr; + rc = a->virtualBox->FindDHCPServerByNetworkName(NetName.mutableRaw(), svr.asOutParam()); + if(enmCode == OP_ADD) + { + if (SUCCEEDED(rc)) + return errorArgument("DHCP server already exists"); + + CHECK_ERROR(a->virtualBox, CreateDHCPServer(NetName.mutableRaw(), svr.asOutParam())); + if (FAILED(rc)) + return errorArgument("Failed to create the DHCP server"); + } + else if (FAILED(rc)) + { + return errorArgument("DHCP server does not exist"); + } + + if (enmCode == OP_RESTART) + { + CHECK_ERROR(svr, Restart()); + if(FAILED(rc)) + return errorArgument("Failed to restart server"); + } + else if (enmCode == OP_REMOVE) + { + CHECK_ERROR(a->virtualBox, RemoveDHCPServer(svr)); + if(FAILED(rc)) + return errorArgument("Failed to remove server"); + } + else + { + if (pIp || pNetmask || pLowerIp || pUpperIp) + { + CHECK_ERROR(svr, SetConfiguration ( + Bstr(pIp).mutableRaw(), + Bstr(pNetmask).mutableRaw(), + Bstr(pLowerIp).mutableRaw(), + Bstr(pUpperIp).mutableRaw())); + if(FAILED(rc)) + return errorArgument("Failed to set configuration"); + } + + if(enable >= 0) + { + CHECK_ERROR(svr, COMSETTER(Enabled) ((BOOL)enable)); + } + + /* remove specified options */ + DhcpOptIdIterator itOptId; + for (itOptId = GlobalDhcpOptions2Delete.begin(); + itOptId != GlobalDhcpOptions2Delete.end(); + ++itOptId) + { + CHECK_ERROR(svr, RemoveGlobalOption(*itOptId)); + } + VmSlot2OptionIdsIterator itIdVector; + for (itIdVector = VmSlot2Options2Delete.begin(); + itIdVector != VmSlot2Options2Delete.end(); + ++itIdVector) + { + for(itOptId = itIdVector->second.begin(); + itOptId != itIdVector->second.end(); + ++itOptId) + { + CHECK_ERROR(svr, + RemoveVmSlotOption(Bstr(itIdVector->first.VmName.c_str()).raw(), + itIdVector->first.u8Slot, + *itOptId)); + } + } + + /* option processing */ + DhcpOptIterator itOpt; + VmSlot2OptionsIterator it; + + /* Global Options */ + for(itOpt = GlobalDhcpOptions.begin(); + itOpt != GlobalDhcpOptions.end(); + ++itOpt) + { + CHECK_ERROR(svr, + AddGlobalOption( + itOpt->first, + com::Bstr(itOpt->second.c_str()).raw())); + } + + /* heh, vm slot options. */ + + for (it = VmSlot2Options.begin(); + it != VmSlot2Options.end(); + ++it) + { + for(itOpt = it->second.begin(); + itOpt != it->second.end(); + ++itOpt) + { + CHECK_ERROR(svr, + AddVmSlotOption(Bstr(it->first.VmName.c_str()).raw(), + it->first.u8Slot, + itOpt->first, + com::Bstr(itOpt->second.c_str()).raw())); + } + } + } + + return RTEXITCODE_SUCCESS; +} + + +RTEXITCODE handleDHCPServer(HandlerArg *a) +{ + if (a->argc < 1) + return errorSyntax(USAGE_DHCPSERVER, "Not enough parameters"); + + RTEXITCODE rcExit; + if (strcmp(a->argv[0], "modify") == 0) + rcExit = handleOp(a, OP_MODIFY, 1); + else if (strcmp(a->argv[0], "add") == 0) + rcExit = handleOp(a, OP_ADD, 1); + else if (strcmp(a->argv[0], "remove") == 0) + rcExit = handleOp(a, OP_REMOVE, 1); + else if (strcmp(a->argv[0], "restart") == 0) + rcExit = handleOp(a, OP_RESTART, 1); + else + rcExit = errorSyntax(USAGE_DHCPSERVER, "Invalid parameter '%s'", Utf8Str(a->argv[0]).c_str()); + + return rcExit; +} + +#endif /* !VBOX_ONLY_DOCS */ + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageDebugVM.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageDebugVM.cpp new file mode 100644 index 00000000..5efaa0ad --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageDebugVM.cpp @@ -0,0 +1,899 @@ +/* $Id: VBoxManageDebugVM.cpp $ */ +/** @file + * VBoxManage - Implementation of the debugvm command. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> + +#include <VBox/types.h> +#include <iprt/ctype.h> +#include <iprt/getopt.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/uuid.h> +#include <VBox/log.h> + +#include "VBoxManage.h" + + +/** + * Handles the getregisters sub-command. + * + * @returns Suitable exit code. + * @param pArgs The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_GetRegisters(HandlerArg *pArgs, IMachineDebugger *pDebugger) +{ + /* + * We take a list of register names (case insensitive). If 'all' is + * encountered we'll dump all registers. + */ + ULONG idCpu = 0; + unsigned cRegisters = 0; + + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + static const RTGETOPTDEF s_aOptions[] = + { + { "--cpu", 'c', RTGETOPT_REQ_UINT32 }, + }; + int rc = RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 2, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_FAILURE); + + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'c': + idCpu = ValueUnion.u32; + break; + + case VINF_GETOPT_NOT_OPTION: + if (!RTStrICmp(ValueUnion.psz, "all")) + { + com::SafeArray<BSTR> aBstrNames; + com::SafeArray<BSTR> aBstrValues; + CHECK_ERROR2I_RET(pDebugger, GetRegisters(idCpu, ComSafeArrayAsOutParam(aBstrNames), + ComSafeArrayAsOutParam(aBstrValues)), + RTEXITCODE_FAILURE); + Assert(aBstrNames.size() == aBstrValues.size()); + + size_t cchMaxName = 8; + for (size_t i = 0; i < aBstrNames.size(); i++) + { + size_t cchName = RTUtf16Len(aBstrNames[i]); + if (cchName > cchMaxName) + cchMaxName = cchName; + } + + for (size_t i = 0; i < aBstrNames.size(); i++) + RTPrintf("%-*ls = %ls\n", cchMaxName, aBstrNames[i], aBstrValues[i]); + } + else + { + com::Bstr bstrName = ValueUnion.psz; + com::Bstr bstrValue; + CHECK_ERROR2I_RET(pDebugger, GetRegister(idCpu, bstrName.raw(), bstrValue.asOutParam()), RTEXITCODE_FAILURE); + RTPrintf("%s = %ls\n", ValueUnion.psz, bstrValue.raw()); + } + cRegisters++; + break; + + default: + return errorGetOpt(rc, &ValueUnion); + } + } + + if (!cRegisters) + return errorSyntax("The getregisters sub-command takes at least one register name"); + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the info sub-command. + * + * @returns Suitable exit code. + * @param pArgs The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_Info(HandlerArg *pArgs, IMachineDebugger *pDebugger) +{ + /* + * Parse arguments. + */ + const char *pszInfo = NULL; + const char *pszArgs = NULL; + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + int rc = RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, NULL, 0, 2, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_FAILURE); + + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case VINF_GETOPT_NOT_OPTION: + if (!pszInfo) + pszInfo = ValueUnion.psz; + else if (!pszArgs) + pszArgs = ValueUnion.psz; + else + return errorTooManyParameters(&pArgs->argv[GetState.iNext - 1]); + break; + default: + return errorGetOpt(rc, &ValueUnion); + } + } + + if (!pszInfo) + return errorSyntax("Must specify info item to display"); + + /* + * Do the work. + */ + com::Bstr bstrName(pszInfo); + com::Bstr bstrArgs(pszArgs); + com::Bstr bstrInfo; + CHECK_ERROR2I_RET(pDebugger, Info(bstrName.raw(), bstrArgs.raw(), bstrInfo.asOutParam()), RTEXITCODE_FAILURE); + RTPrintf("%ls", bstrInfo.raw()); + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the inject sub-command. + * + * @returns Suitable exit code. + * @param a The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_InjectNMI(HandlerArg *a, IMachineDebugger *pDebugger) +{ + if (a->argc != 2) + return errorTooManyParameters(&a->argv[1]); + CHECK_ERROR2I_RET(pDebugger, InjectNMI(), RTEXITCODE_FAILURE); + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the log sub-command. + * + * @returns Suitable exit code. + * @param pArgs The handler arguments. + * @param pDebugger Pointer to the debugger interface. + * @param pszSubCmd The sub command. + */ +static RTEXITCODE handleDebugVM_LogXXXX(HandlerArg *pArgs, IMachineDebugger *pDebugger, const char *pszSubCmd) +{ + /* + * Parse arguments. + */ + bool fRelease = false; + com::Utf8Str strSettings; + + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + + /* + * NB: don't use short options to prevent log specifications like + * "-drv_foo" from being interpreted as options. + */ +# define DEBUGVM_LOG_DEBUG (VINF_GETOPT_NOT_OPTION + 'd') +# define DEBUGVM_LOG_RELEASE (VINF_GETOPT_NOT_OPTION + 'r') + + static const RTGETOPTDEF s_aOptions[] = + { + { "--debug", DEBUGVM_LOG_DEBUG, RTGETOPT_REQ_NOTHING }, + { "--release", DEBUGVM_LOG_RELEASE, RTGETOPT_REQ_NOTHING } + }; + int rc = RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 2, + /* + * Note: RTGETOPTINIT_FLAGS_NO_STD_OPTS is needed to not get into an infinite hang in the following + * while-loop when processing log groups starting with "h", + * e.g. "VBoxManage debugvm <VM Name> log --debug -hex". + */ + RTGETOPTINIT_FLAGS_OPTS_FIRST | RTGETOPTINIT_FLAGS_NO_STD_OPTS); + AssertRCReturn(rc, RTEXITCODE_FAILURE); + + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case DEBUGVM_LOG_RELEASE: + fRelease = true; + break; + + case DEBUGVM_LOG_DEBUG: + fRelease = false; + break; + + /* Because log strings can start with "-" (like "-all+dev_foo") + * we have to take everything we got as a setting and apply it. + * IPRT will take care of the validation afterwards. */ + default: + if (strSettings.length() == 0) + strSettings = ValueUnion.psz; + else + { + strSettings.append(' '); + strSettings.append(ValueUnion.psz); + } + break; + } + } + + if (fRelease) + { + com::Utf8Str strTmp(strSettings); + strSettings = "release:"; + strSettings.append(strTmp); + } + + com::Bstr bstrSettings(strSettings); + if (!strcmp(pszSubCmd, "log")) + CHECK_ERROR2I_RET(pDebugger, ModifyLogGroups(bstrSettings.raw()), RTEXITCODE_FAILURE); + else if (!strcmp(pszSubCmd, "logdest")) + CHECK_ERROR2I_RET(pDebugger, ModifyLogDestinations(bstrSettings.raw()), RTEXITCODE_FAILURE); + else if (!strcmp(pszSubCmd, "logflags")) + CHECK_ERROR2I_RET(pDebugger, ModifyLogFlags(bstrSettings.raw()), RTEXITCODE_FAILURE); + else + AssertFailedReturn(RTEXITCODE_FAILURE); + + return RTEXITCODE_SUCCESS; +} + + +/** + * Handles the inject sub-command. + * + * @returns Suitable exit code. + * @param pArgs The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_DumpVMCore(HandlerArg *pArgs, IMachineDebugger *pDebugger) +{ + /* + * Parse arguments. + */ + const char *pszFilename = NULL; + const char *pszCompression = NULL; + + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + static const RTGETOPTDEF s_aOptions[] = + { + { "--filename", 'f', RTGETOPT_REQ_STRING }, + { "--compression", 'c', RTGETOPT_REQ_STRING } + }; + int rc = RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 2, 0 /*fFlags*/); + AssertRCReturn(rc, RTEXITCODE_FAILURE); + + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'c': + if (pszCompression) + return errorSyntax("The --compression option has already been given"); + pszCompression = ValueUnion.psz; + break; + case 'f': + if (pszFilename) + return errorSyntax("The --filename option has already been given"); + pszFilename = ValueUnion.psz; + break; + default: + return errorGetOpt(rc, &ValueUnion); + } + } + + if (!pszFilename) + return errorSyntax("The --filename option is required"); + + /* + * Make the filename absolute before handing it on to the API. + */ + char szAbsFilename[RTPATH_MAX]; + rc = RTPathAbs(pszFilename, szAbsFilename, sizeof(szAbsFilename)); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs failed on '%s': %Rrc", pszFilename, rc); + + com::Bstr bstrFilename(szAbsFilename); + com::Bstr bstrCompression(pszCompression); + CHECK_ERROR2I_RET(pDebugger, DumpGuestCore(bstrFilename.raw(), bstrCompression.raw()), RTEXITCODE_FAILURE); + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the osdetect sub-command. + * + * @returns Suitable exit code. + * @param a The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_OSDetect(HandlerArg *a, IMachineDebugger *pDebugger) +{ + if (a->argc != 2) + return errorTooManyParameters(&a->argv[1]); + + com::Bstr bstrIgnore; + com::Bstr bstrAll("all"); + CHECK_ERROR2I_RET(pDebugger, LoadPlugIn(bstrAll.raw(), bstrIgnore.asOutParam()), RTEXITCODE_FAILURE); + + com::Bstr bstrName; + CHECK_ERROR2I_RET(pDebugger, DetectOS(bstrName.asOutParam()), RTEXITCODE_FAILURE); + RTPrintf("Detected: %ls\n", bstrName.raw()); + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the osinfo sub-command. + * + * @returns Suitable exit code. + * @param a The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_OSInfo(HandlerArg *a, IMachineDebugger *pDebugger) +{ + if (a->argc != 2) + return errorTooManyParameters(&a->argv[1]); + + com::Bstr bstrName; + CHECK_ERROR2I_RET(pDebugger, COMGETTER(OSName)(bstrName.asOutParam()), RTEXITCODE_FAILURE); + com::Bstr bstrVersion; + CHECK_ERROR2I_RET(pDebugger, COMGETTER(OSVersion)(bstrVersion.asOutParam()), RTEXITCODE_FAILURE); + RTPrintf("Name: %ls\n", bstrName.raw()); + RTPrintf("Version: %ls\n", bstrVersion.raw()); + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the osdmsg sub-command. + * + * @returns Suitable exit code. + * @param pArgs The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_OSDmesg(HandlerArg *pArgs, IMachineDebugger *pDebugger) +{ + /* + * Parse argument. + */ + uint32_t uMaxMessages = 0; + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + static const RTGETOPTDEF s_aOptions[] = + { + { "--lines", 'n', RTGETOPT_REQ_UINT32 }, + }; + int rc = RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 2, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_FAILURE); + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + switch (rc) + { + case 'n': uMaxMessages = ValueUnion.u32; break; + default: return errorGetOpt(rc, &ValueUnion); + } + + /* + * Do it. + */ + com::Bstr bstrDmesg; + CHECK_ERROR2I_RET(pDebugger, QueryOSKernelLog(uMaxMessages, bstrDmesg.asOutParam()), RTEXITCODE_FAILURE); + RTPrintf("%ls\n", bstrDmesg.raw()); + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the setregisters sub-command. + * + * @returns Suitable exit code. + * @param pArgs The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_SetRegisters(HandlerArg *pArgs, IMachineDebugger *pDebugger) +{ + /* + * We take a list of register assignments, that is register=value. + */ + ULONG idCpu = 0; + com::SafeArray<IN_BSTR> aBstrNames; + com::SafeArray<IN_BSTR> aBstrValues; + + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + static const RTGETOPTDEF s_aOptions[] = + { + { "--cpu", 'c', RTGETOPT_REQ_UINT32 }, + }; + int rc = RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 2, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_FAILURE); + + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'c': + idCpu = ValueUnion.u32; + break; + + case VINF_GETOPT_NOT_OPTION: + { + const char *pszEqual = strchr(ValueUnion.psz, '='); + if (!pszEqual) + return errorSyntax("setregisters expects input on the form 'register=value' got '%s'", ValueUnion.psz); + try + { + com::Bstr bstrName(ValueUnion.psz, pszEqual - ValueUnion.psz); + com::Bstr bstrValue(pszEqual + 1); + if ( !aBstrNames.push_back(bstrName.raw()) + || !aBstrValues.push_back(bstrValue.raw())) + throw std::bad_alloc(); + } + catch (std::bad_alloc &) + { + RTMsgError("Out of memory\n"); + return RTEXITCODE_FAILURE; + } + break; + } + + default: + return errorGetOpt(rc, &ValueUnion); + } + } + + if (!aBstrNames.size()) + return errorSyntax("The setregisters sub-command takes at least one register name"); + + /* + * If it is only one register, use the single register method just so + * we expose it and can test it from the command line. + */ + if (aBstrNames.size() == 1) + { + CHECK_ERROR2I_RET(pDebugger, SetRegister(idCpu, aBstrNames[0], aBstrValues[0]), RTEXITCODE_FAILURE); + RTPrintf("Successfully set %ls\n", aBstrNames[0]); + } + else + { + CHECK_ERROR2I_RET(pDebugger, SetRegisters(idCpu, ComSafeArrayAsInParam(aBstrNames), ComSafeArrayAsInParam(aBstrValues)), + RTEXITCODE_FAILURE); + RTPrintf("Successfully set %u registers\n", aBstrNames.size()); + } + + return RTEXITCODE_SUCCESS; +} + +/** @name debugvm show flags + * @{ */ +#define DEBUGVM_SHOW_FLAGS_HUMAN_READABLE UINT32_C(0x00000000) +#define DEBUGVM_SHOW_FLAGS_SH_EXPORT UINT32_C(0x00000001) +#define DEBUGVM_SHOW_FLAGS_SH_EVAL UINT32_C(0x00000002) +#define DEBUGVM_SHOW_FLAGS_CMD_SET UINT32_C(0x00000003) +#define DEBUGVM_SHOW_FLAGS_FMT_MASK UINT32_C(0x00000003) +/** @} */ + +/** + * Prints a variable according to the @a fFlags. + * + * @param pszVar The variable name. + * @param pbstrValue The variable value. + * @param fFlags The debugvm show flags. + */ +static void handleDebugVM_Show_PrintVar(const char *pszVar, com::Bstr const *pbstrValue, uint32_t fFlags) +{ + switch (fFlags & DEBUGVM_SHOW_FLAGS_FMT_MASK) + { + case DEBUGVM_SHOW_FLAGS_HUMAN_READABLE: RTPrintf(" %27s=%ls\n", pszVar, pbstrValue->raw()); break; + case DEBUGVM_SHOW_FLAGS_SH_EXPORT: RTPrintf("export %s='%ls'\n", pszVar, pbstrValue->raw()); break; + case DEBUGVM_SHOW_FLAGS_SH_EVAL: RTPrintf("%s='%ls'\n", pszVar, pbstrValue->raw()); break; + case DEBUGVM_SHOW_FLAGS_CMD_SET: RTPrintf("set %s=%ls\n", pszVar, pbstrValue->raw()); break; + default: AssertFailed(); + } +} + +/** + * Handles logdbg-settings. + * + * @returns Exit code. + * @param pDebugger The debugger interface. + * @param fFlags The debugvm show flags. + */ +static RTEXITCODE handleDebugVM_Show_LogDbgSettings(IMachineDebugger *pDebugger, uint32_t fFlags) +{ + if ((fFlags & DEBUGVM_SHOW_FLAGS_FMT_MASK) == DEBUGVM_SHOW_FLAGS_HUMAN_READABLE) + RTPrintf("Debug logger settings:\n"); + + com::Bstr bstr; + CHECK_ERROR2I_RET(pDebugger, COMGETTER(LogDbgGroups)(bstr.asOutParam()), RTEXITCODE_FAILURE); + handleDebugVM_Show_PrintVar("VBOX_LOG", &bstr, fFlags); + + CHECK_ERROR2I_RET(pDebugger, COMGETTER(LogDbgFlags)(bstr.asOutParam()), RTEXITCODE_FAILURE); + handleDebugVM_Show_PrintVar("VBOX_LOG_FLAGS", &bstr, fFlags); + + CHECK_ERROR2I_RET(pDebugger, COMGETTER(LogDbgDestinations)(bstr.asOutParam()), RTEXITCODE_FAILURE); + handleDebugVM_Show_PrintVar("VBOX_LOG_DEST", &bstr, fFlags); + return RTEXITCODE_SUCCESS; +} + +/** + * Handles logrel-settings. + * + * @returns Exit code. + * @param pDebugger The debugger interface. + * @param fFlags The debugvm show flags. + */ +static RTEXITCODE handleDebugVM_Show_LogRelSettings(IMachineDebugger *pDebugger, uint32_t fFlags) +{ + if ((fFlags & DEBUGVM_SHOW_FLAGS_FMT_MASK) == DEBUGVM_SHOW_FLAGS_HUMAN_READABLE) + RTPrintf("Release logger settings:\n"); + + com::Bstr bstr; + CHECK_ERROR2I_RET(pDebugger, COMGETTER(LogRelGroups)(bstr.asOutParam()), RTEXITCODE_FAILURE); + handleDebugVM_Show_PrintVar("VBOX_RELEASE_LOG", &bstr, fFlags); + + CHECK_ERROR2I_RET(pDebugger, COMGETTER(LogRelFlags)(bstr.asOutParam()), RTEXITCODE_FAILURE); + handleDebugVM_Show_PrintVar("VBOX_RELEASE_LOG_FLAGS", &bstr, fFlags); + + CHECK_ERROR2I_RET(pDebugger, COMGETTER(LogRelDestinations)(bstr.asOutParam()), RTEXITCODE_FAILURE); + handleDebugVM_Show_PrintVar("VBOX_RELEASE_LOG_DEST", &bstr, fFlags); + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the show sub-command. + * + * @returns Suitable exit code. + * @param pArgs The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_Show(HandlerArg *pArgs, IMachineDebugger *pDebugger) +{ + /* + * Parse arguments and what to show. Order dependent. + */ + uint32_t fFlags = DEBUGVM_SHOW_FLAGS_HUMAN_READABLE; + + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + static const RTGETOPTDEF s_aOptions[] = + { + { "--human-readable", 'H', RTGETOPT_REQ_NOTHING }, + { "--sh-export", 'e', RTGETOPT_REQ_NOTHING }, + { "--sh-eval", 'E', RTGETOPT_REQ_NOTHING }, + { "--cmd-set", 's', RTGETOPT_REQ_NOTHING }, + }; + int rc = RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 2, 0 /*fFlags*/); + AssertRCReturn(rc, RTEXITCODE_FAILURE); + + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'H': + fFlags = (fFlags & ~DEBUGVM_SHOW_FLAGS_FMT_MASK) | DEBUGVM_SHOW_FLAGS_HUMAN_READABLE; + break; + + case 'e': + fFlags = (fFlags & ~DEBUGVM_SHOW_FLAGS_FMT_MASK) | DEBUGVM_SHOW_FLAGS_SH_EXPORT; + break; + + case 'E': + fFlags = (fFlags & ~DEBUGVM_SHOW_FLAGS_FMT_MASK) | DEBUGVM_SHOW_FLAGS_SH_EVAL; + break; + + case 's': + fFlags = (fFlags & ~DEBUGVM_SHOW_FLAGS_FMT_MASK) | DEBUGVM_SHOW_FLAGS_CMD_SET; + break; + + case VINF_GETOPT_NOT_OPTION: + { + RTEXITCODE rcExit; + if (!strcmp(ValueUnion.psz, "log-settings")) + { + rcExit = handleDebugVM_Show_LogDbgSettings(pDebugger, fFlags); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = handleDebugVM_Show_LogRelSettings(pDebugger, fFlags); + } + else if (!strcmp(ValueUnion.psz, "logdbg-settings")) + rcExit = handleDebugVM_Show_LogDbgSettings(pDebugger, fFlags); + else if (!strcmp(ValueUnion.psz, "logrel-settings")) + rcExit = handleDebugVM_Show_LogRelSettings(pDebugger, fFlags); + else + rcExit = errorSyntax("The show sub-command has no idea what '%s' might be", ValueUnion.psz); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + break; + } + + default: + return errorGetOpt(rc, &ValueUnion); + } + } + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the stack sub-command. + * + * @returns Suitable exit code. + * @param pArgs The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_Stack(HandlerArg *pArgs, IMachineDebugger *pDebugger) +{ + /* + * Parse arguments. + */ + VMCPUID idCpu = VMCPUID_ALL; + + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + static const RTGETOPTDEF s_aOptions[] = + { + { "--cpu", 'c', RTGETOPT_REQ_UINT32 }, + }; + int rc = RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 2, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_FAILURE); + + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'c': + idCpu = ValueUnion.u32; + break; + + default: + return errorGetOpt(rc, &ValueUnion); + } + } + + /* + * Dump stack. + */ + com::Bstr bstrGuestStack; + if (idCpu != VMCPUID_ALL) + { + /* Single CPU */ + CHECK_ERROR2I_RET(pDebugger, DumpGuestStack(idCpu, bstrGuestStack.asOutParam()), RTEXITCODE_FAILURE); + RTPrintf("%ls\n", bstrGuestStack.raw()); + } + else + { + /* All CPUs. */ + ComPtr<IMachine> ptrMachine; + CHECK_ERROR2I_RET(pArgs->session, COMGETTER(Machine)(ptrMachine.asOutParam()), RTEXITCODE_FAILURE); + ULONG cCpus; + CHECK_ERROR2I_RET(ptrMachine, COMGETTER(CPUCount)(&cCpus), RTEXITCODE_FAILURE); + + for (idCpu = 0; idCpu < (VMCPUID)cCpus; idCpu++) + { + CHECK_ERROR2I_RET(pDebugger, DumpGuestStack(idCpu, bstrGuestStack.asOutParam()), RTEXITCODE_FAILURE); + if (cCpus > 1) + { + if (idCpu > 0) + RTPrintf("\n"); + RTPrintf("====================== CPU #%u ======================\n", idCpu); + } + RTPrintf("%ls\n", bstrGuestStack.raw()); + } + } + + + return RTEXITCODE_SUCCESS; +} + +/** + * Handles the statistics sub-command. + * + * @returns Suitable exit code. + * @param pArgs The handler arguments. + * @param pDebugger Pointer to the debugger interface. + */ +static RTEXITCODE handleDebugVM_Statistics(HandlerArg *pArgs, IMachineDebugger *pDebugger) +{ + /* + * Parse arguments. + */ + bool fWithDescriptions = false; + const char *pszPattern = NULL; /* all */ + bool fReset = false; + + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + static const RTGETOPTDEF s_aOptions[] = + { + { "--descriptions", 'd', RTGETOPT_REQ_NOTHING }, + { "--pattern", 'p', RTGETOPT_REQ_STRING }, + { "--reset", 'r', RTGETOPT_REQ_NOTHING }, + }; + int rc = RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 2, 0 /*fFlags*/); + AssertRCReturn(rc, RTEXITCODE_FAILURE); + + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'd': + fWithDescriptions = true; + break; + + case 'p': + if (pszPattern) + return errorSyntax("Multiple --pattern options are not permitted"); + pszPattern = ValueUnion.psz; + break; + + case 'r': + fReset = true; + break; + + default: + return errorGetOpt(rc, &ValueUnion); + } + } + + if (fReset && fWithDescriptions) + return errorSyntax("The --reset and --descriptions options does not mix"); + + /* + * Execute the order. + */ + com::Bstr bstrPattern(pszPattern); + if (fReset) + CHECK_ERROR2I_RET(pDebugger, ResetStats(bstrPattern.raw()), RTEXITCODE_FAILURE); + else + { + com::Bstr bstrStats; + CHECK_ERROR2I_RET(pDebugger, GetStats(bstrPattern.raw(), fWithDescriptions, bstrStats.asOutParam()), + RTEXITCODE_FAILURE); + /* if (fFormatted) + { big mess } + else + */ + RTPrintf("%ls\n", bstrStats.raw()); + } + + return RTEXITCODE_SUCCESS; +} + +RTEXITCODE handleDebugVM(HandlerArg *pArgs) +{ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + + /* + * The first argument is the VM name or UUID. Open a session to it. + */ + if (pArgs->argc < 2) + return errorNoSubcommand(); + ComPtr<IMachine> ptrMachine; + CHECK_ERROR2I_RET(pArgs->virtualBox, FindMachine(com::Bstr(pArgs->argv[0]).raw(), ptrMachine.asOutParam()), RTEXITCODE_FAILURE); + CHECK_ERROR2I_RET(ptrMachine, LockMachine(pArgs->session, LockType_Shared), RTEXITCODE_FAILURE); + + /* + * Get the associated console and machine debugger. + */ + HRESULT hrc; + ComPtr<IConsole> ptrConsole; + CHECK_ERROR2(hrc, pArgs->session, COMGETTER(Console)(ptrConsole.asOutParam())); + if (SUCCEEDED(hrc)) + { + if (ptrConsole.isNotNull()) + { + ComPtr<IMachineDebugger> ptrDebugger; + CHECK_ERROR2(hrc, ptrConsole, COMGETTER(Debugger)(ptrDebugger.asOutParam())); + if (SUCCEEDED(hrc)) + { + /* + * String switch on the sub-command. + */ + const char *pszSubCmd = pArgs->argv[1]; + if (!strcmp(pszSubCmd, "dumpvmcore")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_DUMPVMCORE); + rcExit = handleDebugVM_DumpVMCore(pArgs, ptrDebugger); + } + else if (!strcmp(pszSubCmd, "getregisters")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_GETREGISTERS); + rcExit = handleDebugVM_GetRegisters(pArgs, ptrDebugger); + } + else if (!strcmp(pszSubCmd, "info")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_INFO); + rcExit = handleDebugVM_Info(pArgs, ptrDebugger); + } + else if (!strcmp(pszSubCmd, "injectnmi")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_INJECTNMI); + rcExit = handleDebugVM_InjectNMI(pArgs, ptrDebugger); + } + else if (!strcmp(pszSubCmd, "log")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_LOG); + rcExit = handleDebugVM_LogXXXX(pArgs, ptrDebugger, pszSubCmd); + } + else if (!strcmp(pszSubCmd, "logdest")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_LOGDEST); + rcExit = handleDebugVM_LogXXXX(pArgs, ptrDebugger, pszSubCmd); + } + else if (!strcmp(pszSubCmd, "logflags")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_LOGFLAGS); + rcExit = handleDebugVM_LogXXXX(pArgs, ptrDebugger, pszSubCmd); + } + else if (!strcmp(pszSubCmd, "osdetect")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_OSDETECT); + rcExit = handleDebugVM_OSDetect(pArgs, ptrDebugger); + } + else if (!strcmp(pszSubCmd, "osinfo")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_OSINFO); + rcExit = handleDebugVM_OSInfo(pArgs, ptrDebugger); + } + else if (!strcmp(pszSubCmd, "osdmesg")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_OSDMESG); + rcExit = handleDebugVM_OSDmesg(pArgs, ptrDebugger); + } + else if (!strcmp(pszSubCmd, "setregisters")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_SETREGISTERS); + rcExit = handleDebugVM_SetRegisters(pArgs, ptrDebugger); + } + else if (!strcmp(pszSubCmd, "show")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_SHOW); + rcExit = handleDebugVM_Show(pArgs, ptrDebugger); + } + else if (!strcmp(pszSubCmd, "stack")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_STACK); + rcExit = handleDebugVM_Stack(pArgs, ptrDebugger); + } + else if (!strcmp(pszSubCmd, "statistics")) + { + setCurrentSubcommand(HELP_SCOPE_DEBUGVM_STATISTICS); + rcExit = handleDebugVM_Statistics(pArgs, ptrDebugger); + } + else + errorUnknownSubcommand(pszSubCmd); + } + } + else + RTMsgError("Machine '%s' is not currently running.\n", pArgs->argv[0]); + } + + pArgs->session->UnlockMachine(); + + return rcExit; +} + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp new file mode 100644 index 00000000..ef4db168 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp @@ -0,0 +1,2560 @@ +/* $Id: VBoxManageDisk.cpp $ */ +/** @file + * VBoxManage - The disk/medium related commands. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_ONLY_DOCS + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> + +#include <iprt/asm.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/ctype.h> +#include <iprt/getopt.h> +#include <VBox/log.h> +#include <VBox/vd.h> + +#include "VBoxManage.h" +using namespace com; + +/** Medium category. */ +typedef enum MEDIUMCATEGORY +{ + MEDIUMCATEGORY_NONE = 0, + MEDIUMCATEGORY_DISK, + MEDIUMCATEGORY_DVD, + MEDIUMCATEGORY_FLOPPY +} MEDIUMCATEGORY; + + + +// funcs +/////////////////////////////////////////////////////////////////////////////// + + +static DECLCALLBACK(void) handleVDError(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va) +{ + RT_NOREF(pvUser); + RTMsgErrorV(pszFormat, va); + RTMsgError("Error code %Rrc at %s(%u) in function %s", rc, RT_SRC_POS_ARGS); +} + +static int parseMediumVariant(const char *psz, MediumVariant_T *pMediumVariant) +{ + int rc = VINF_SUCCESS; + unsigned uMediumVariant = (unsigned)(*pMediumVariant); + while (psz && *psz && RT_SUCCESS(rc)) + { + size_t len; + const char *pszComma = strchr(psz, ','); + if (pszComma) + len = pszComma - psz; + else + len = strlen(psz); + if (len > 0) + { + // Parsing is intentionally inconsistent: "standard" resets the + // variant, whereas the other flags are cumulative. + if (!RTStrNICmp(psz, "standard", len)) + uMediumVariant = MediumVariant_Standard; + else if ( !RTStrNICmp(psz, "fixed", len) + || !RTStrNICmp(psz, "static", len)) + uMediumVariant |= MediumVariant_Fixed; + else if (!RTStrNICmp(psz, "Diff", len)) + uMediumVariant |= MediumVariant_Diff; + else if (!RTStrNICmp(psz, "split2g", len)) + uMediumVariant |= MediumVariant_VmdkSplit2G; + else if ( !RTStrNICmp(psz, "stream", len) + || !RTStrNICmp(psz, "streamoptimized", len)) + uMediumVariant |= MediumVariant_VmdkStreamOptimized; + else if (!RTStrNICmp(psz, "esx", len)) + uMediumVariant |= MediumVariant_VmdkESX; + else if (!RTStrNICmp(psz, "formatted", len)) + uMediumVariant |= MediumVariant_Formatted; + else + rc = VERR_PARSE_ERROR; + } + if (pszComma) + psz += len + 1; + else + psz += len; + } + + if (RT_SUCCESS(rc)) + *pMediumVariant = (MediumVariant_T)uMediumVariant; + return rc; +} + +int parseMediumType(const char *psz, MediumType_T *penmMediumType) +{ + int rc = VINF_SUCCESS; + MediumType_T enmMediumType = MediumType_Normal; + if (!RTStrICmp(psz, "normal")) + enmMediumType = MediumType_Normal; + else if (!RTStrICmp(psz, "immutable")) + enmMediumType = MediumType_Immutable; + else if (!RTStrICmp(psz, "writethrough")) + enmMediumType = MediumType_Writethrough; + else if (!RTStrICmp(psz, "shareable")) + enmMediumType = MediumType_Shareable; + else if (!RTStrICmp(psz, "readonly")) + enmMediumType = MediumType_Readonly; + else if (!RTStrICmp(psz, "multiattach")) + enmMediumType = MediumType_MultiAttach; + else + rc = VERR_PARSE_ERROR; + + if (RT_SUCCESS(rc)) + *penmMediumType = enmMediumType; + return rc; +} + +/** @todo move this into getopt, as getting bool values is generic */ +int parseBool(const char *psz, bool *pb) +{ + int rc = VINF_SUCCESS; + if ( !RTStrICmp(psz, "on") + || !RTStrICmp(psz, "yes") + || !RTStrICmp(psz, "true") + || !RTStrICmp(psz, "1") + || !RTStrICmp(psz, "enable") + || !RTStrICmp(psz, "enabled")) + { + *pb = true; + } + else if ( !RTStrICmp(psz, "off") + || !RTStrICmp(psz, "no") + || !RTStrICmp(psz, "false") + || !RTStrICmp(psz, "0") + || !RTStrICmp(psz, "disable") + || !RTStrICmp(psz, "disabled")) + { + *pb = false; + } + else + rc = VERR_PARSE_ERROR; + + return rc; +} + +HRESULT openMedium(HandlerArg *a, const char *pszFilenameOrUuid, + DeviceType_T enmDevType, AccessMode_T enmAccessMode, + ComPtr<IMedium> &pMedium, bool fForceNewUuidOnOpen, + bool fSilent) +{ + HRESULT rc; + Guid id(pszFilenameOrUuid); + char szFilenameAbs[RTPATH_MAX] = ""; + + /* If it is no UUID, convert the filename to an absolute one. */ + if (!id.isValid()) + { + int irc = RTPathAbs(pszFilenameOrUuid, szFilenameAbs, sizeof(szFilenameAbs)); + if (RT_FAILURE(irc)) + { + if (!fSilent) + RTMsgError("Cannot convert filename \"%s\" to absolute path", pszFilenameOrUuid); + return E_FAIL; + } + pszFilenameOrUuid = szFilenameAbs; + } + + if (!fSilent) + CHECK_ERROR(a->virtualBox, OpenMedium(Bstr(pszFilenameOrUuid).raw(), + enmDevType, + enmAccessMode, + fForceNewUuidOnOpen, + pMedium.asOutParam())); + else + rc = a->virtualBox->OpenMedium(Bstr(pszFilenameOrUuid).raw(), + enmDevType, + enmAccessMode, + fForceNewUuidOnOpen, + pMedium.asOutParam()); + + return rc; +} + +static HRESULT createMedium(HandlerArg *a, const char *pszFormat, + const char *pszFilename, DeviceType_T enmDevType, + AccessMode_T enmAccessMode, ComPtr<IMedium> &pMedium) +{ + HRESULT rc; + char szFilenameAbs[RTPATH_MAX] = ""; + + /** @todo laziness shortcut. should really check the MediumFormatCapabilities */ + if (RTStrICmp(pszFormat, "iSCSI")) + { + int irc = RTPathAbs(pszFilename, szFilenameAbs, sizeof(szFilenameAbs)); + if (RT_FAILURE(irc)) + { + RTMsgError("Cannot convert filename \"%s\" to absolute path", pszFilename); + return E_FAIL; + } + pszFilename = szFilenameAbs; + } + + CHECK_ERROR(a->virtualBox, CreateMedium(Bstr(pszFormat).raw(), + Bstr(pszFilename).raw(), + enmAccessMode, + enmDevType, + pMedium.asOutParam())); + return rc; +} + +static const RTGETOPTDEF g_aCreateMediumOptions[] = +{ + { "disk", 'H', RTGETOPT_REQ_NOTHING }, + { "dvd", 'D', RTGETOPT_REQ_NOTHING }, + { "floppy", 'L', RTGETOPT_REQ_NOTHING }, + { "--filename", 'f', RTGETOPT_REQ_STRING }, + { "-filename", 'f', RTGETOPT_REQ_STRING }, // deprecated + { "--diffparent", 'd', RTGETOPT_REQ_STRING }, + { "--size", 's', RTGETOPT_REQ_UINT64 }, + { "-size", 's', RTGETOPT_REQ_UINT64 }, // deprecated + { "--sizebyte", 'S', RTGETOPT_REQ_UINT64 }, + { "--format", 'o', RTGETOPT_REQ_STRING }, + { "-format", 'o', RTGETOPT_REQ_STRING }, // deprecated + { "--static", 'F', RTGETOPT_REQ_NOTHING }, + { "-static", 'F', RTGETOPT_REQ_NOTHING }, // deprecated + { "--variant", 'm', RTGETOPT_REQ_STRING }, + { "-variant", 'm', RTGETOPT_REQ_STRING }, // deprecated +}; + +RTEXITCODE handleCreateMedium(HandlerArg *a) +{ + HRESULT rc; + int vrc; + const char *filename = NULL; + const char *diffparent = NULL; + uint64_t size = 0; + enum { + CMD_NONE, + CMD_DISK, + CMD_DVD, + CMD_FLOPPY + } cmd = CMD_NONE; + const char *format = NULL; + bool fBase = true; + MediumVariant_T enmMediumVariant = MediumVariant_Standard; + + 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_aCreateMediumOptions, RT_ELEMENTS(g_aCreateMediumOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'H': // disk + if (cmd != CMD_NONE) + return errorSyntax(USAGE_CREATEMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_DISK; + break; + + case 'D': // DVD + if (cmd != CMD_NONE) + return errorSyntax(USAGE_CREATEMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_DVD; + break; + + case 'L': // floppy + if (cmd != CMD_NONE) + return errorSyntax(USAGE_CREATEMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_FLOPPY; + break; + + case 'f': // --filename + filename = ValueUnion.psz; + break; + + case 'd': // --diffparent + diffparent = ValueUnion.psz; + fBase = false; + break; + + case 's': // --size + size = ValueUnion.u64 * _1M; + break; + + case 'S': // --sizebyte + size = ValueUnion.u64; + break; + + case 'o': // --format + format = ValueUnion.psz; + break; + + case 'F': // --static ("fixed"/"flat") + { + unsigned uMediumVariant = (unsigned)enmMediumVariant; + uMediumVariant |= MediumVariant_Fixed; + enmMediumVariant = (MediumVariant_T)uMediumVariant; + break; + } + + case 'm': // --variant + vrc = parseMediumVariant(ValueUnion.psz, &enmMediumVariant); + if (RT_FAILURE(vrc)) + return errorArgument("Invalid medium variant '%s'", ValueUnion.psz); + break; + + case VINF_GETOPT_NOT_OPTION: + return errorSyntax(USAGE_CREATEMEDIUM, "Invalid parameter '%s'", ValueUnion.psz); + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(USAGE_CREATEMEDIUM, "Invalid option -%c", c); + else + return errorSyntax(USAGE_CREATEMEDIUM, "Invalid option case %i", c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_CREATEMEDIUM, "unknown option: %s\n", ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(USAGE_CREATEMEDIUM, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(USAGE_CREATEMEDIUM, "error: %Rrs", c); + } + } + + /* check the outcome */ + if (cmd == CMD_NONE) + cmd = CMD_DISK; + ComPtr<IMedium> pParentMedium; + if (fBase) + { + if ( !filename + || !*filename + || size == 0) + return errorSyntax(USAGE_CREATEMEDIUM, "Parameters --filename and --size are required"); + if (!format || !*format) + { + if (cmd == CMD_DISK) + format = "VDI"; + else if (cmd == CMD_DVD || cmd == CMD_FLOPPY) + { + format = "RAW"; + unsigned uMediumVariant = (unsigned)enmMediumVariant; + uMediumVariant |= MediumVariant_Fixed; + enmMediumVariant = (MediumVariant_T)uMediumVariant; + } + } + } + else + { + if ( !filename + || !*filename) + return errorSyntax(USAGE_CREATEMEDIUM, "Parameters --filename is required"); + size = 0; + if (cmd != CMD_DISK) + return errorSyntax(USAGE_CREATEMEDIUM, "Creating a differencing medium is only supported for hard disks"); + enmMediumVariant = MediumVariant_Diff; + if (!format || !*format) + { + const char *pszExt = RTPathSuffix(filename); + /* Skip over . if there is an extension. */ + if (pszExt) + pszExt++; + if (!pszExt || !*pszExt) + format = "VDI"; + else + format = pszExt; + } + rc = openMedium(a, diffparent, DeviceType_HardDisk, + AccessMode_ReadWrite, pParentMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + if (pParentMedium.isNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid parent hard disk reference, avoiding crash"); + MediumState_T state; + CHECK_ERROR(pParentMedium, COMGETTER(State)(&state)); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + if (state == MediumState_Inaccessible) + { + CHECK_ERROR(pParentMedium, RefreshState(&state)); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + } + } + /* check for filename extension */ + /** @todo use IMediumFormat to cover all extensions generically */ + Utf8Str strName(filename); + if (!RTPathHasSuffix(strName.c_str())) + { + Utf8Str strFormat(format); + if (cmd == CMD_DISK) + { + if (strFormat.compare("vmdk", RTCString::CaseInsensitive) == 0) + strName.append(".vmdk"); + else if (strFormat.compare("vhd", RTCString::CaseInsensitive) == 0) + strName.append(".vhd"); + else + strName.append(".vdi"); + } else if (cmd == CMD_DVD) + strName.append(".iso"); + else if (cmd == CMD_FLOPPY) + strName.append(".img"); + filename = strName.c_str(); + } + + ComPtr<IMedium> pMedium; + if (cmd == CMD_DISK) + rc = createMedium(a, format, filename, DeviceType_HardDisk, + AccessMode_ReadWrite, pMedium); + else if (cmd == CMD_DVD) + rc = createMedium(a, format, filename, DeviceType_DVD, + AccessMode_ReadOnly, pMedium); + else if (cmd == CMD_FLOPPY) + rc = createMedium(a, format, filename, DeviceType_Floppy, + AccessMode_ReadWrite, pMedium); + else + rc = E_INVALIDARG; /* cannot happen but make gcc happy */ + + if (SUCCEEDED(rc) && pMedium) + { + ComPtr<IProgress> pProgress; + com::SafeArray<MediumVariant_T> l_variants(sizeof(MediumVariant_T)*8); + + for (ULONG i = 0; i < l_variants.size(); ++i) + { + ULONG temp = enmMediumVariant; + temp &= 1<<i; + l_variants [i] = (MediumVariant_T)temp; + } + + if (fBase) + CHECK_ERROR(pMedium, CreateBaseStorage(size, ComSafeArrayAsInParam(l_variants), pProgress.asOutParam())); + else + CHECK_ERROR(pParentMedium, CreateDiffStorage(pMedium, ComSafeArrayAsInParam(l_variants), pProgress.asOutParam())); + if (SUCCEEDED(rc) && pProgress) + { + rc = showProgress(pProgress); + CHECK_PROGRESS_ERROR(pProgress, ("Failed to create medium")); + } + } + + if (SUCCEEDED(rc) && pMedium) + { + Bstr uuid; + CHECK_ERROR(pMedium, COMGETTER(Id)(uuid.asOutParam())); + RTPrintf("Medium created. UUID: %s\n", Utf8Str(uuid).c_str()); + + //CHECK_ERROR(pMedium, Close()); + } + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static const RTGETOPTDEF g_aModifyMediumOptions[] = +{ + { "disk", 'H', RTGETOPT_REQ_NOTHING }, + { "dvd", 'D', RTGETOPT_REQ_NOTHING }, + { "floppy", 'L', RTGETOPT_REQ_NOTHING }, + { "--type", 't', RTGETOPT_REQ_STRING }, + { "-type", 't', RTGETOPT_REQ_STRING }, // deprecated + { "settype", 't', RTGETOPT_REQ_STRING }, // deprecated + { "--autoreset", 'z', RTGETOPT_REQ_STRING }, + { "-autoreset", 'z', RTGETOPT_REQ_STRING }, // deprecated + { "autoreset", 'z', RTGETOPT_REQ_STRING }, // deprecated + { "--property", 'p', RTGETOPT_REQ_STRING }, + { "--compact", 'c', RTGETOPT_REQ_NOTHING }, + { "-compact", 'c', RTGETOPT_REQ_NOTHING }, // deprecated + { "compact", 'c', RTGETOPT_REQ_NOTHING }, // deprecated + { "--resize", 'r', RTGETOPT_REQ_UINT64 }, + { "--resizebyte", 'R', RTGETOPT_REQ_UINT64 }, + { "--move", 'm', RTGETOPT_REQ_STRING }, + { "--setlocation", 'l', RTGETOPT_REQ_STRING }, + { "--description", 'd', RTGETOPT_REQ_STRING } +}; + +RTEXITCODE handleModifyMedium(HandlerArg *a) +{ + HRESULT rc; + int vrc; + enum { + CMD_NONE, + CMD_DISK, + CMD_DVD, + CMD_FLOPPY + } cmd = CMD_NONE; + ComPtr<IMedium> pMedium; + MediumType_T enmMediumType = MediumType_Normal; /* Shut up MSC */ + bool AutoReset = false; + SafeArray<BSTR> mediumPropNames; + SafeArray<BSTR> mediumPropValues; + bool fModifyMediumType = false; + bool fModifyAutoReset = false; + bool fModifyProperties = false; + bool fModifyCompact = false; + bool fModifyResize = false; + bool fModifyResizeMB = false; + bool fMoveMedium = false; + bool fModifyDescription = false; + bool fSetNewLocation = false; + uint64_t cbResize = 0; + const char *pszFilenameOrUuid = NULL; + char *pszNewLocation = 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_aModifyMediumOptions, RT_ELEMENTS(g_aModifyMediumOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'H': // disk + if (cmd != CMD_NONE) + return errorSyntax(USAGE_MODIFYMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_DISK; + break; + + case 'D': // DVD + if (cmd != CMD_NONE) + return errorSyntax(USAGE_MODIFYMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_DVD; + break; + + case 'L': // floppy + if (cmd != CMD_NONE) + return errorSyntax(USAGE_MODIFYMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_FLOPPY; + break; + + case 't': // --type + vrc = parseMediumType(ValueUnion.psz, &enmMediumType); + if (RT_FAILURE(vrc)) + return errorArgument("Invalid medium type '%s'", ValueUnion.psz); + fModifyMediumType = true; + break; + + case 'z': // --autoreset + vrc = parseBool(ValueUnion.psz, &AutoReset); + if (RT_FAILURE(vrc)) + return errorArgument("Invalid autoreset parameter '%s'", ValueUnion.psz); + fModifyAutoReset = true; + break; + + case 'p': // --property + { + /* Parse 'name=value' */ + char *pszProperty = RTStrDup(ValueUnion.psz); + if (pszProperty) + { + char *pDelimiter = strchr(pszProperty, '='); + if (pDelimiter) + { + *pDelimiter = '\0'; + + Bstr bstrName(pszProperty); + Bstr bstrValue(&pDelimiter[1]); + bstrName.detachTo(mediumPropNames.appendedRaw()); + bstrValue.detachTo(mediumPropValues.appendedRaw()); + fModifyProperties = true; + } + else + { + errorArgument("Invalid --property argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + RTStrFree(pszProperty); + } + else + { + RTStrmPrintf(g_pStdErr, "Error: Failed to allocate memory for medium property '%s'\n", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case 'c': // --compact + fModifyCompact = true; + break; + + case 'r': // --resize + cbResize = ValueUnion.u64 * _1M; + fModifyResize = true; + fModifyResizeMB = true; // do sanity check! + break; + + case 'R': // --resizebyte + cbResize = ValueUnion.u64; + fModifyResize = true; + break; + + case 'm': // --move + /* Get a new location */ + pszNewLocation = RTPathAbsDup(ValueUnion.psz); + fMoveMedium = true; + break; + + case 'l': // --setlocation + /* Get a new location */ + pszNewLocation = RTPathAbsDup(ValueUnion.psz); + fSetNewLocation = true; + break; + + case 'd': // --description + /* Get a new description */ + pszNewLocation = RTStrDup(ValueUnion.psz); + fModifyDescription = true; + break; + + case VINF_GETOPT_NOT_OPTION: + if (!pszFilenameOrUuid) + pszFilenameOrUuid = ValueUnion.psz; + else + return errorSyntax(USAGE_MODIFYMEDIUM, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(USAGE_MODIFYMEDIUM, "Invalid option -%c", c); + else + return errorSyntax(USAGE_MODIFYMEDIUM, "Invalid option case %i", c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_MODIFYMEDIUM, "unknown option: %s\n", ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(USAGE_MODIFYMEDIUM, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(USAGE_MODIFYMEDIUM, "error: %Rrs", c); + } + } + + if (cmd == CMD_NONE) + cmd = CMD_DISK; + + if (!pszFilenameOrUuid) + return errorSyntax(USAGE_MODIFYMEDIUM, "Medium name or UUID required"); + + if (!fModifyMediumType + && !fModifyAutoReset + && !fModifyProperties + && !fModifyCompact + && !fModifyResize + && !fMoveMedium + && !fSetNewLocation + && !fModifyDescription + ) + return errorSyntax(USAGE_MODIFYMEDIUM, "No operation specified"); + + /* Always open the medium if necessary, there is no other way. */ + if (cmd == CMD_DISK) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_DVD, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_Floppy, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else + rc = E_INVALIDARG; /* cannot happen but make gcc happy */ + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + if (pMedium.isNull()) + { + RTMsgError("Invalid medium reference, avoiding crash"); + return RTEXITCODE_FAILURE; + } + + if ( fModifyResize + && fModifyResizeMB) + { + // Sanity check + // + // In general users should know what they do but in this case users have no + // alternative to VBoxManage. If happens that one wants to resize the disk + // and uses --resize and does not consider that this parameter expects the + // new medium size in MB not Byte. If the operation is started and then + // aborted by the user, the result is most likely a medium which doesn't + // work anymore. + MediumState_T state; + pMedium->RefreshState(&state); + LONG64 logicalSize; + pMedium->COMGETTER(LogicalSize)(&logicalSize); + if (cbResize > (uint64_t)logicalSize * 1000) + { + RTMsgError("Error: Attempt to resize the medium from %RU64.%RU64 MB to %RU64.%RU64 MB. Use --resizebyte if this is intended!\n", + logicalSize / _1M, (logicalSize % _1M) / (_1M / 10), cbResize / _1M, (cbResize % _1M) / (_1M / 10)); + return RTEXITCODE_FAILURE; + } + } + + if (fModifyMediumType) + { + MediumType_T enmCurrMediumType; + CHECK_ERROR(pMedium, COMGETTER(Type)(&enmCurrMediumType)); + + if (enmCurrMediumType != enmMediumType) + CHECK_ERROR(pMedium, COMSETTER(Type)(enmMediumType)); + } + + if (fModifyAutoReset) + { + CHECK_ERROR(pMedium, COMSETTER(AutoReset)(AutoReset)); + } + + if (fModifyProperties) + { + CHECK_ERROR(pMedium, SetProperties(ComSafeArrayAsInParam(mediumPropNames), ComSafeArrayAsInParam(mediumPropValues))); + } + + if (fModifyCompact) + { + ComPtr<IProgress> pProgress; + CHECK_ERROR(pMedium, Compact(pProgress.asOutParam())); + if (SUCCEEDED(rc)) + rc = showProgress(pProgress); + if (FAILED(rc)) + { + if (rc == E_NOTIMPL) + RTMsgError("Compact medium operation is not implemented!"); + else if (rc == VBOX_E_NOT_SUPPORTED) + RTMsgError("Compact medium operation for this format is not implemented yet!"); + else if (!pProgress.isNull()) + CHECK_PROGRESS_ERROR(pProgress, ("Failed to compact medium")); + else + RTMsgError("Failed to compact medium!"); + } + } + + if (fModifyResize) + { + ComPtr<IProgress> pProgress; + CHECK_ERROR(pMedium, Resize(cbResize, pProgress.asOutParam())); + if (SUCCEEDED(rc)) + rc = showProgress(pProgress); + if (FAILED(rc)) + { + if (!pProgress.isNull()) + CHECK_PROGRESS_ERROR(pProgress, ("Failed to resize medium")); + else if (rc == E_NOTIMPL) + RTMsgError("Resize medium operation is not implemented!"); + else if (rc == VBOX_E_NOT_SUPPORTED) + RTMsgError("Resize medium operation for this format is not implemented yet!"); + else + RTMsgError("Failed to resize medium!"); + } + } + + if (fMoveMedium) + { + do + { + ComPtr<IProgress> pProgress; + Utf8Str strLocation(pszNewLocation); + RTStrFree(pszNewLocation); + CHECK_ERROR(pMedium, MoveTo(Bstr(strLocation).raw(), pProgress.asOutParam())); + + if (SUCCEEDED(rc) && !pProgress.isNull()) + { + rc = showProgress(pProgress); + CHECK_PROGRESS_ERROR(pProgress, ("Failed to move medium")); + } + + Bstr uuid; + CHECK_ERROR_BREAK(pMedium, COMGETTER(Id)(uuid.asOutParam())); + + RTPrintf("Move medium with UUID %s finished\n", Utf8Str(uuid).c_str()); + } + while (0); + } + + if (fSetNewLocation) + { + Utf8Str strLocation(pszNewLocation); + RTStrFree(pszNewLocation); + CHECK_ERROR(pMedium, COMSETTER(Location)(Bstr(strLocation).raw())); + + Bstr uuid; + CHECK_ERROR(pMedium, COMGETTER(Id)(uuid.asOutParam())); + RTPrintf("Set new location of medium with UUID %s finished\n", Utf8Str(uuid).c_str()); + } + + if (fModifyDescription) + { + CHECK_ERROR(pMedium, COMSETTER(Description)(Bstr(pszNewLocation).raw())); + + RTPrintf("Medium description has been changed.\n"); + } + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static const RTGETOPTDEF g_aCloneMediumOptions[] = +{ + { "disk", 'd', RTGETOPT_REQ_NOTHING }, + { "dvd", 'D', RTGETOPT_REQ_NOTHING }, + { "floppy", 'f', RTGETOPT_REQ_NOTHING }, + { "--format", 'o', RTGETOPT_REQ_STRING }, + { "-format", 'o', RTGETOPT_REQ_STRING }, + { "--static", 'F', RTGETOPT_REQ_NOTHING }, + { "-static", 'F', RTGETOPT_REQ_NOTHING }, + { "--existing", 'E', RTGETOPT_REQ_NOTHING }, + { "--variant", 'm', RTGETOPT_REQ_STRING }, + { "-variant", 'm', RTGETOPT_REQ_STRING }, +}; + +RTEXITCODE handleCloneMedium(HandlerArg *a) +{ + HRESULT rc; + int vrc; + enum { + CMD_NONE, + CMD_DISK, + CMD_DVD, + CMD_FLOPPY + } cmd = CMD_NONE; + const char *pszSrc = NULL; + const char *pszDst = NULL; + Bstr format; + MediumVariant_T enmMediumVariant = MediumVariant_Standard; + bool fExisting = 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_aCloneMediumOptions, RT_ELEMENTS(g_aCloneMediumOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'd': // disk + if (cmd != CMD_NONE) + return errorSyntax(USAGE_CLONEMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_DISK; + break; + + case 'D': // DVD + if (cmd != CMD_NONE) + return errorSyntax(USAGE_CLONEMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_DVD; + break; + + case 'f': // floppy + if (cmd != CMD_NONE) + return errorSyntax(USAGE_CLONEMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_FLOPPY; + break; + + case 'o': // --format + format = ValueUnion.psz; + break; + + case 'F': // --static + { + unsigned uMediumVariant = (unsigned)enmMediumVariant; + uMediumVariant |= MediumVariant_Fixed; + enmMediumVariant = (MediumVariant_T)uMediumVariant; + break; + } + + case 'E': // --existing + fExisting = true; + break; + + case 'm': // --variant + vrc = parseMediumVariant(ValueUnion.psz, &enmMediumVariant); + if (RT_FAILURE(vrc)) + return errorArgument("Invalid medium variant '%s'", ValueUnion.psz); + break; + + case VINF_GETOPT_NOT_OPTION: + if (!pszSrc) + pszSrc = ValueUnion.psz; + else if (!pszDst) + pszDst = ValueUnion.psz; + else + return errorSyntax(USAGE_CLONEMEDIUM, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_GRAPH(c)) + return errorSyntax(USAGE_CLONEMEDIUM, "unhandled option: -%c", c); + else + return errorSyntax(USAGE_CLONEMEDIUM, "unhandled option: %i", c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_CLONEMEDIUM, "unknown option: %s", ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(USAGE_CLONEMEDIUM, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(USAGE_CLONEMEDIUM, "error: %Rrs", c); + } + } + + if (cmd == CMD_NONE) + cmd = CMD_DISK; + if (!pszSrc) + return errorSyntax(USAGE_CLONEMEDIUM, "Mandatory UUID or input file parameter missing"); + if (!pszDst) + return errorSyntax(USAGE_CLONEMEDIUM, "Mandatory output file parameter missing"); + if (fExisting && (!format.isEmpty() || enmMediumVariant != MediumVariant_Standard)) + return errorSyntax(USAGE_CLONEMEDIUM, "Specified options which cannot be used with --existing"); + + ComPtr<IMedium> pSrcMedium; + ComPtr<IMedium> pDstMedium; + + if (cmd == CMD_DISK) + rc = openMedium(a, pszSrc, DeviceType_HardDisk, AccessMode_ReadOnly, pSrcMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + rc = openMedium(a, pszSrc, DeviceType_DVD, AccessMode_ReadOnly, pSrcMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + rc = openMedium(a, pszSrc, DeviceType_Floppy, AccessMode_ReadOnly, pSrcMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else + rc = E_INVALIDARG; /* cannot happen but make gcc happy */ + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + do + { + /* open/create destination medium */ + if (fExisting) + { + if (cmd == CMD_DISK) + rc = openMedium(a, pszDst, DeviceType_HardDisk, AccessMode_ReadWrite, pDstMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + rc = openMedium(a, pszDst, DeviceType_DVD, AccessMode_ReadOnly, pDstMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + rc = openMedium(a, pszDst, DeviceType_Floppy, AccessMode_ReadWrite, pDstMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (FAILED(rc)) + break; + + /* Perform accessibility check now. */ + MediumState_T state; + CHECK_ERROR_BREAK(pDstMedium, RefreshState(&state)); + CHECK_ERROR_BREAK(pDstMedium, COMGETTER(Format)(format.asOutParam())); + } + else + { + /* + * In case the format is unspecified check that the source medium supports + * image creation and use the same format for the destination image. + * Use the default image format if it is not supported. + */ + if (format.isEmpty()) + { + ComPtr<IMediumFormat> pMediumFmt; + com::SafeArray<MediumFormatCapabilities_T> l_caps; + CHECK_ERROR_BREAK(pSrcMedium, COMGETTER(MediumFormat)(pMediumFmt.asOutParam())); + CHECK_ERROR_BREAK(pMediumFmt, COMGETTER(Capabilities)(ComSafeArrayAsOutParam(l_caps))); + ULONG caps=0; + for (size_t i = 0; i < l_caps.size(); i++) + caps |= l_caps[i]; + if (caps & ( MediumFormatCapabilities_CreateDynamic + | MediumFormatCapabilities_CreateFixed)) + CHECK_ERROR_BREAK(pMediumFmt, COMGETTER(Id)(format.asOutParam())); + } + Utf8Str strFormat(format); + if (cmd == CMD_DISK) + rc = createMedium(a, strFormat.c_str(), pszDst, DeviceType_HardDisk, + AccessMode_ReadWrite, pDstMedium); + else if (cmd == CMD_DVD) + rc = createMedium(a, strFormat.c_str(), pszDst, DeviceType_DVD, + AccessMode_ReadOnly, pDstMedium); + else if (cmd == CMD_FLOPPY) + rc = createMedium(a, strFormat.c_str(), pszDst, DeviceType_Floppy, + AccessMode_ReadWrite, pDstMedium); + if (FAILED(rc)) + break; + } + + ComPtr<IProgress> pProgress; + com::SafeArray<MediumVariant_T> l_variants(sizeof(MediumVariant_T)*8); + + for (ULONG i = 0; i < l_variants.size(); ++i) + { + ULONG temp = enmMediumVariant; + temp &= 1<<i; + l_variants [i] = (MediumVariant_T)temp; + } + + CHECK_ERROR_BREAK(pSrcMedium, CloneTo(pDstMedium, ComSafeArrayAsInParam(l_variants), NULL, pProgress.asOutParam())); + + rc = showProgress(pProgress); + CHECK_PROGRESS_ERROR_BREAK(pProgress, ("Failed to clone medium")); + + Bstr uuid; + CHECK_ERROR_BREAK(pDstMedium, COMGETTER(Id)(uuid.asOutParam())); + + RTPrintf("Clone medium created in format '%ls'. UUID: %s\n", + format.raw(), Utf8Str(uuid).c_str()); + } + while (0); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static const RTGETOPTDEF g_aConvertFromRawHardDiskOptions[] = +{ + { "--format", 'o', RTGETOPT_REQ_STRING }, + { "-format", 'o', RTGETOPT_REQ_STRING }, + { "--static", 'F', RTGETOPT_REQ_NOTHING }, + { "-static", 'F', RTGETOPT_REQ_NOTHING }, + { "--variant", 'm', RTGETOPT_REQ_STRING }, + { "-variant", 'm', RTGETOPT_REQ_STRING }, + { "--uuid", 'u', RTGETOPT_REQ_STRING }, +}; + +RTEXITCODE handleConvertFromRaw(HandlerArg *a) +{ + int rc = VINF_SUCCESS; + bool fReadFromStdIn = false; + const char *format = "VDI"; + const char *srcfilename = NULL; + const char *dstfilename = NULL; + const char *filesize = NULL; + unsigned uImageFlags = VD_IMAGE_FLAGS_NONE; + void *pvBuf = NULL; + RTUUID uuid; + PCRTUUID pUuid = 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_aConvertFromRawHardDiskOptions, RT_ELEMENTS(g_aConvertFromRawHardDiskOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'u': // --uuid + if (RT_FAILURE(RTUuidFromStr(&uuid, ValueUnion.psz))) + return errorSyntax(USAGE_CONVERTFROMRAW, "Invalid UUID '%s'", ValueUnion.psz); + pUuid = &uuid; + break; + case 'o': // --format + format = ValueUnion.psz; + break; + + case 'm': // --variant + { + MediumVariant_T enmMediumVariant = MediumVariant_Standard; + rc = parseMediumVariant(ValueUnion.psz, &enmMediumVariant); + if (RT_FAILURE(rc)) + return errorArgument("Invalid medium variant '%s'", ValueUnion.psz); + /// @todo cleaner solution than assuming 1:1 mapping? + uImageFlags = (unsigned)enmMediumVariant; + break; + } + case VINF_GETOPT_NOT_OPTION: + if (!srcfilename) + { + srcfilename = ValueUnion.psz; + fReadFromStdIn = !strcmp(srcfilename, "stdin"); + } + else if (!dstfilename) + dstfilename = ValueUnion.psz; + else if (fReadFromStdIn && !filesize) + filesize = ValueUnion.psz; + else + return errorSyntax(USAGE_CONVERTFROMRAW, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + return errorGetOpt(USAGE_CONVERTFROMRAW, c, &ValueUnion); + } + } + + if (!srcfilename || !dstfilename || (fReadFromStdIn && !filesize)) + return errorSyntax(USAGE_CONVERTFROMRAW, "Incorrect number of parameters"); + RTStrmPrintf(g_pStdErr, "Converting from raw image file=\"%s\" to file=\"%s\"...\n", + srcfilename, dstfilename); + + PVDISK pDisk = NULL; + + PVDINTERFACE pVDIfs = NULL; + VDINTERFACEERROR vdInterfaceError; + vdInterfaceError.pfnError = handleVDError; + vdInterfaceError.pfnMessage = NULL; + + rc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR, + NULL, sizeof(VDINTERFACEERROR), &pVDIfs); + AssertRC(rc); + + /* open raw image file. */ + RTFILE File; + if (fReadFromStdIn) + rc = RTFileFromNative(&File, RTFILE_NATIVE_STDIN); + else + rc = RTFileOpen(&File, srcfilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_FAILURE(rc)) + { + RTMsgError("Cannot open file \"%s\": %Rrc", srcfilename, rc); + goto out; + } + + uint64_t cbFile; + /* get image size. */ + if (fReadFromStdIn) + cbFile = RTStrToUInt64(filesize); + else + rc = RTFileGetSize(File, &cbFile); + if (RT_FAILURE(rc)) + { + RTMsgError("Cannot get image size for file \"%s\": %Rrc", srcfilename, rc); + goto out; + } + + RTStrmPrintf(g_pStdErr, "Creating %s image with size %RU64 bytes (%RU64MB)...\n", + (uImageFlags & VD_IMAGE_FLAGS_FIXED) ? "fixed" : "dynamic", cbFile, (cbFile + _1M - 1) / _1M); + char pszComment[256]; + RTStrPrintf(pszComment, sizeof(pszComment), "Converted image from %s", srcfilename); + rc = VDCreate(pVDIfs, VDTYPE_HDD, &pDisk); + if (RT_FAILURE(rc)) + { + RTMsgError("Cannot create the virtual disk container: %Rrc", rc); + goto out; + } + + Assert(RT_MIN(cbFile / 512 / 16 / 63, 16383) - + (unsigned int)RT_MIN(cbFile / 512 / 16 / 63, 16383) == 0); + VDGEOMETRY PCHS, LCHS; + PCHS.cCylinders = (unsigned int)RT_MIN(cbFile / 512 / 16 / 63, 16383); + PCHS.cHeads = 16; + PCHS.cSectors = 63; + LCHS.cCylinders = 0; + LCHS.cHeads = 0; + LCHS.cSectors = 0; + rc = VDCreateBase(pDisk, format, dstfilename, cbFile, + uImageFlags, pszComment, &PCHS, &LCHS, pUuid, + VD_OPEN_FLAGS_NORMAL, NULL, NULL); + if (RT_FAILURE(rc)) + { + RTMsgError("Cannot create the disk image \"%s\": %Rrc", dstfilename, rc); + goto out; + } + + size_t cbBuffer; + cbBuffer = _1M; + pvBuf = RTMemAlloc(cbBuffer); + if (!pvBuf) + { + rc = VERR_NO_MEMORY; + RTMsgError("Out of memory allocating buffers for image \"%s\": %Rrc", dstfilename, rc); + goto out; + } + + uint64_t offFile; + offFile = 0; + while (offFile < cbFile) + { + size_t cbRead; + size_t cbToRead; + cbRead = 0; + cbToRead = cbFile - offFile >= (uint64_t)cbBuffer ? + cbBuffer : (size_t)(cbFile - offFile); + rc = RTFileRead(File, pvBuf, cbToRead, &cbRead); + if (RT_FAILURE(rc) || !cbRead) + break; + rc = VDWrite(pDisk, offFile, pvBuf, cbRead); + if (RT_FAILURE(rc)) + { + RTMsgError("Failed to write to disk image \"%s\": %Rrc", dstfilename, rc); + goto out; + } + offFile += cbRead; + } + +out: + if (pvBuf) + RTMemFree(pvBuf); + if (pDisk) + VDClose(pDisk, RT_FAILURE(rc)); + if (File != NIL_RTFILE) + RTFileClose(File); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +HRESULT showMediumInfo(const ComPtr<IVirtualBox> &pVirtualBox, + const ComPtr<IMedium> &pMedium, + const char *pszParentUUID, + bool fOptLong) +{ + HRESULT rc = S_OK; + do + { + Bstr uuid; + pMedium->COMGETTER(Id)(uuid.asOutParam()); + RTPrintf("UUID: %ls\n", uuid.raw()); + if (pszParentUUID) + RTPrintf("Parent UUID: %s\n", pszParentUUID); + + /* check for accessibility */ + MediumState_T enmState; + CHECK_ERROR_BREAK(pMedium, RefreshState(&enmState)); + const char *pszState = "unknown"; + switch (enmState) + { + case MediumState_NotCreated: + pszState = "not created"; + break; + case MediumState_Created: + pszState = "created"; + break; + case MediumState_LockedRead: + pszState = "locked read"; + break; + case MediumState_LockedWrite: + pszState = "locked write"; + break; + case MediumState_Inaccessible: + pszState = "inaccessible"; + break; + case MediumState_Creating: + pszState = "creating"; + break; + case MediumState_Deleting: + pszState = "deleting"; + break; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case MediumState_32BitHack: break; /* Shut up compiler warnings. */ +#endif + } + RTPrintf("State: %s\n", pszState); + + if (fOptLong && enmState == MediumState_Inaccessible) + { + Bstr err; + CHECK_ERROR_BREAK(pMedium, COMGETTER(LastAccessError)(err.asOutParam())); + RTPrintf("Access Error: %ls\n", err.raw()); + } + + if (fOptLong) + { + Bstr description; + pMedium->COMGETTER(Description)(description.asOutParam()); + if (!description.isEmpty()) + RTPrintf("Description: %ls\n", description.raw()); + } + + MediumType_T type; + pMedium->COMGETTER(Type)(&type); + const char *typeStr = "unknown"; + switch (type) + { + case MediumType_Normal: + if (pszParentUUID && Guid(pszParentUUID).isValid()) + typeStr = "normal (differencing)"; + else + typeStr = "normal (base)"; + break; + case MediumType_Immutable: + typeStr = "immutable"; + break; + case MediumType_Writethrough: + typeStr = "writethrough"; + break; + case MediumType_Shareable: + typeStr = "shareable"; + break; + case MediumType_Readonly: + typeStr = "readonly"; + break; + case MediumType_MultiAttach: + typeStr = "multiattach"; + break; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case MediumType_32BitHack: break; /* Shut up compiler warnings. */ +#endif + } + RTPrintf("Type: %s\n", typeStr); + + /* print out information specific for differencing media */ + if (fOptLong && pszParentUUID && Guid(pszParentUUID).isValid()) + { + BOOL autoReset = FALSE; + pMedium->COMGETTER(AutoReset)(&autoReset); + RTPrintf("Auto-Reset: %s\n", autoReset ? "on" : "off"); + } + + Bstr loc; + pMedium->COMGETTER(Location)(loc.asOutParam()); + RTPrintf("Location: %ls\n", loc.raw()); + + Bstr format; + pMedium->COMGETTER(Format)(format.asOutParam()); + RTPrintf("Storage format: %ls\n", format.raw()); + + if (fOptLong) + { + com::SafeArray<MediumVariant_T> safeArray_variant; + + pMedium->COMGETTER(Variant)(ComSafeArrayAsOutParam(safeArray_variant)); + ULONG variant=0; + for (size_t i = 0; i < safeArray_variant.size(); i++) + variant |= safeArray_variant[i]; + + const char *variantStr = "unknown"; + switch (variant & ~(MediumVariant_Fixed | MediumVariant_Diff)) + { + case MediumVariant_VmdkSplit2G: + variantStr = "split2G"; + break; + case MediumVariant_VmdkStreamOptimized: + variantStr = "streamOptimized"; + break; + case MediumVariant_VmdkESX: + variantStr = "ESX"; + break; + case MediumVariant_Standard: + variantStr = "default"; + break; + } + const char *variantTypeStr = "dynamic"; + if (variant & MediumVariant_Fixed) + variantTypeStr = "fixed"; + else if (variant & MediumVariant_Diff) + variantTypeStr = "differencing"; + RTPrintf("Format variant: %s %s\n", variantTypeStr, variantStr); + } + + LONG64 logicalSize; + pMedium->COMGETTER(LogicalSize)(&logicalSize); + RTPrintf("Capacity: %lld MBytes\n", logicalSize >> 20); + if (fOptLong) + { + LONG64 actualSize; + pMedium->COMGETTER(Size)(&actualSize); + RTPrintf("Size on disk: %lld MBytes\n", actualSize >> 20); + } + + Bstr strCipher; + Bstr strPasswordId; + HRESULT rc2 = pMedium->GetEncryptionSettings(strCipher.asOutParam(), strPasswordId.asOutParam()); + if (SUCCEEDED(rc2)) + { + RTPrintf("Encryption: enabled\n"); + if (fOptLong) + { + RTPrintf("Cipher: %ls\n", strCipher.raw()); + RTPrintf("Password ID: %ls\n", strPasswordId.raw()); + } + } + else + RTPrintf("Encryption: disabled\n"); + + if (fOptLong) + { + com::SafeArray<BSTR> names; + com::SafeArray<BSTR> values; + pMedium->GetProperties(Bstr().raw(), ComSafeArrayAsOutParam(names), ComSafeArrayAsOutParam(values)); + size_t cNames = names.size(); + size_t cValues = values.size(); + bool fFirst = true; + for (size_t i = 0; i < cNames; i++) + { + Bstr value; + if (i < cValues) + value = values[i]; + RTPrintf("%s%ls=%ls\n", + fFirst ? "Property: " : " ", + names[i], value.raw()); + fFirst = false; + } + } + + if (fOptLong) + { + bool fFirst = true; + com::SafeArray<BSTR> machineIds; + pMedium->COMGETTER(MachineIds)(ComSafeArrayAsOutParam(machineIds)); + for (size_t i = 0; i < machineIds.size(); i++) + { + ComPtr<IMachine> pMachine; + CHECK_ERROR(pVirtualBox, FindMachine(machineIds[i], pMachine.asOutParam())); + if (pMachine) + { + Bstr name; + pMachine->COMGETTER(Name)(name.asOutParam()); + pMachine->COMGETTER(Id)(uuid.asOutParam()); + RTPrintf("%s%ls (UUID: %ls)", + fFirst ? "In use by VMs: " : " ", + name.raw(), machineIds[i]); + fFirst = false; + com::SafeArray<BSTR> snapshotIds; + pMedium->GetSnapshotIds(machineIds[i], + ComSafeArrayAsOutParam(snapshotIds)); + for (size_t j = 0; j < snapshotIds.size(); j++) + { + ComPtr<ISnapshot> pSnapshot; + pMachine->FindSnapshot(snapshotIds[j], pSnapshot.asOutParam()); + if (pSnapshot) + { + Bstr snapshotName; + pSnapshot->COMGETTER(Name)(snapshotName.asOutParam()); + RTPrintf(" [%ls (UUID: %ls)]", snapshotName.raw(), snapshotIds[j]); + } + } + RTPrintf("\n"); + } + } + } + + if (fOptLong) + { + com::SafeIfaceArray<IMedium> children; + pMedium->COMGETTER(Children)(ComSafeArrayAsOutParam(children)); + bool fFirst = true; + for (size_t i = 0; i < children.size(); i++) + { + ComPtr<IMedium> pChild(children[i]); + if (pChild) + { + Bstr childUUID; + pChild->COMGETTER(Id)(childUUID.asOutParam()); + RTPrintf("%s%ls\n", + fFirst ? "Child UUIDs: " : " ", + childUUID.raw()); + fFirst = false; + } + } + } + } + while (0); + + return rc; +} + +static const RTGETOPTDEF g_aShowMediumInfoOptions[] = +{ + { "disk", 'd', RTGETOPT_REQ_NOTHING }, + { "dvd", 'D', RTGETOPT_REQ_NOTHING }, + { "floppy", 'f', RTGETOPT_REQ_NOTHING }, +}; + +RTEXITCODE handleShowMediumInfo(HandlerArg *a) +{ + enum { + CMD_NONE, + CMD_DISK, + CMD_DVD, + CMD_FLOPPY + } cmd = CMD_NONE; + const char *pszFilenameOrUuid = 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_aShowMediumInfoOptions, RT_ELEMENTS(g_aShowMediumInfoOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'd': // disk + if (cmd != CMD_NONE) + return errorSyntax(USAGE_SHOWMEDIUMINFO, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_DISK; + break; + + case 'D': // DVD + if (cmd != CMD_NONE) + return errorSyntax(USAGE_SHOWMEDIUMINFO, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_DVD; + break; + + case 'f': // floppy + if (cmd != CMD_NONE) + return errorSyntax(USAGE_SHOWMEDIUMINFO, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_FLOPPY; + break; + + case VINF_GETOPT_NOT_OPTION: + if (!pszFilenameOrUuid) + pszFilenameOrUuid = ValueUnion.psz; + else + return errorSyntax(USAGE_SHOWMEDIUMINFO, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(USAGE_SHOWMEDIUMINFO, "Invalid option -%c", c); + else + return errorSyntax(USAGE_SHOWMEDIUMINFO, "Invalid option case %i", c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_SHOWMEDIUMINFO, "unknown option: %s\n", ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(USAGE_SHOWMEDIUMINFO, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(USAGE_SHOWMEDIUMINFO, "error: %Rrs", c); + } + } + + if (cmd == CMD_NONE) + cmd = CMD_DISK; + + /* check for required options */ + if (!pszFilenameOrUuid) + return errorSyntax(USAGE_SHOWMEDIUMINFO, "Medium name or UUID required"); + + HRESULT rc = S_OK; /* Prevents warning. */ + + ComPtr<IMedium> pMedium; + if (cmd == CMD_DISK) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_DVD, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_Floppy, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + Utf8Str strParentUUID("base"); + ComPtr<IMedium> pParent; + pMedium->COMGETTER(Parent)(pParent.asOutParam()); + if (!pParent.isNull()) + { + Bstr bstrParentUUID; + pParent->COMGETTER(Id)(bstrParentUUID.asOutParam()); + strParentUUID = bstrParentUUID; + } + + rc = showMediumInfo(a->virtualBox, pMedium, strParentUUID.c_str(), true); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static const RTGETOPTDEF g_aCloseMediumOptions[] = +{ + { "disk", 'd', RTGETOPT_REQ_NOTHING }, + { "dvd", 'D', RTGETOPT_REQ_NOTHING }, + { "floppy", 'f', RTGETOPT_REQ_NOTHING }, + { "--delete", 'r', RTGETOPT_REQ_NOTHING }, +}; + +RTEXITCODE handleCloseMedium(HandlerArg *a) +{ + HRESULT rc = S_OK; + enum { + CMD_NONE, + CMD_DISK, + CMD_DVD, + CMD_FLOPPY + } cmd = CMD_NONE; + const char *pszFilenameOrUuid = NULL; + bool fDelete = 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_aCloseMediumOptions, RT_ELEMENTS(g_aCloseMediumOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'd': // disk + if (cmd != CMD_NONE) + return errorSyntax(USAGE_CLOSEMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_DISK; + break; + + case 'D': // DVD + if (cmd != CMD_NONE) + return errorSyntax(USAGE_CLOSEMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_DVD; + break; + + case 'f': // floppy + if (cmd != CMD_NONE) + return errorSyntax(USAGE_CLOSEMEDIUM, "Only one command can be specified: '%s'", ValueUnion.psz); + cmd = CMD_FLOPPY; + break; + + case 'r': // --delete + fDelete = true; + break; + + case VINF_GETOPT_NOT_OPTION: + if (!pszFilenameOrUuid) + pszFilenameOrUuid = ValueUnion.psz; + else + return errorSyntax(USAGE_CLOSEMEDIUM, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(USAGE_CLOSEMEDIUM, "Invalid option -%c", c); + else + return errorSyntax(USAGE_CLOSEMEDIUM, "Invalid option case %i", c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_CLOSEMEDIUM, "unknown option: %s\n", ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(USAGE_CLOSEMEDIUM, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(USAGE_CLOSEMEDIUM, "error: %Rrs", c); + } + } + + /* check for required options */ + if (cmd == CMD_NONE) + cmd = CMD_DISK; + if (!pszFilenameOrUuid) + return errorSyntax(USAGE_CLOSEMEDIUM, "Medium name or UUID required"); + + ComPtr<IMedium> pMedium; + if (cmd == CMD_DISK) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_DVD, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_Floppy, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + + if (SUCCEEDED(rc) && pMedium) + { + if (fDelete) + { + ComPtr<IProgress> pProgress; + CHECK_ERROR(pMedium, DeleteStorage(pProgress.asOutParam())); + if (SUCCEEDED(rc)) + { + rc = showProgress(pProgress); + CHECK_PROGRESS_ERROR(pProgress, ("Failed to delete medium")); + } + else + RTMsgError("Failed to delete medium. Error code %Rrc", rc); + } + CHECK_ERROR(pMedium, Close()); + } + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleMediumProperty(HandlerArg *a) +{ + HRESULT rc = S_OK; + const char *pszCmd = NULL; + enum { + CMD_NONE, + CMD_DISK, + CMD_DVD, + CMD_FLOPPY + } cmd = CMD_NONE; + const char *pszAction = NULL; + const char *pszFilenameOrUuid = NULL; + const char *pszProperty = NULL; + ComPtr<IMedium> pMedium; + + pszCmd = (a->argc > 0) ? a->argv[0] : ""; + if ( !RTStrICmp(pszCmd, "disk") + || !RTStrICmp(pszCmd, "dvd") + || !RTStrICmp(pszCmd, "floppy")) + { + if (!RTStrICmp(pszCmd, "disk")) + cmd = CMD_DISK; + else if (!RTStrICmp(pszCmd, "dvd")) + cmd = CMD_DVD; + else if (!RTStrICmp(pszCmd, "floppy")) + cmd = CMD_FLOPPY; + else + { + AssertMsgFailed(("unexpected parameter %s\n", pszCmd)); + cmd = CMD_DISK; + } + a->argv++; + a->argc--; + } + else + { + pszCmd = NULL; + cmd = CMD_DISK; + } + + if (a->argc == 0) + return errorSyntax(USAGE_MEDIUMPROPERTY, "Missing action"); + + pszAction = a->argv[0]; + if ( RTStrICmp(pszAction, "set") + && RTStrICmp(pszAction, "get") + && RTStrICmp(pszAction, "delete")) + return errorSyntax(USAGE_MEDIUMPROPERTY, "Invalid action given: %s", pszAction); + + if ( ( !RTStrICmp(pszAction, "set") + && a->argc != 4) + || ( RTStrICmp(pszAction, "set") + && a->argc != 3)) + return errorSyntax(USAGE_MEDIUMPROPERTY, "Invalid number of arguments given for action: %s", pszAction); + + pszFilenameOrUuid = a->argv[1]; + pszProperty = a->argv[2]; + + if (cmd == CMD_DISK) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_DVD, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + rc = openMedium(a, pszFilenameOrUuid, DeviceType_Floppy, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (SUCCEEDED(rc) && !pMedium.isNull()) + { + if (!RTStrICmp(pszAction, "set")) + { + const char *pszValue = a->argv[3]; + CHECK_ERROR(pMedium, SetProperty(Bstr(pszProperty).raw(), Bstr(pszValue).raw())); + } + else if (!RTStrICmp(pszAction, "get")) + { + Bstr strVal; + CHECK_ERROR(pMedium, GetProperty(Bstr(pszProperty).raw(), strVal.asOutParam())); + if (SUCCEEDED(rc)) + RTPrintf("%s=%ls\n", pszProperty, strVal.raw()); + } + else if (!RTStrICmp(pszAction, "delete")) + { + CHECK_ERROR(pMedium, SetProperty(Bstr(pszProperty).raw(), Bstr().raw())); + /** @todo */ + } + } + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static const RTGETOPTDEF g_aEncryptMediumOptions[] = +{ + { "--newpassword", 'n', RTGETOPT_REQ_STRING }, + { "--oldpassword", 'o', RTGETOPT_REQ_STRING }, + { "--cipher", 'c', RTGETOPT_REQ_STRING }, + { "--newpasswordid", 'i', RTGETOPT_REQ_STRING } +}; + +RTEXITCODE handleEncryptMedium(HandlerArg *a) +{ + HRESULT rc; + ComPtr<IMedium> hardDisk; + const char *pszPasswordNew = NULL; + const char *pszPasswordOld = NULL; + const char *pszCipher = NULL; + const char *pszFilenameOrUuid = NULL; + const char *pszNewPasswordId = NULL; + 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_aEncryptMediumOptions, RT_ELEMENTS(g_aEncryptMediumOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'n': // --newpassword + pszPasswordNew = ValueUnion.psz; + break; + + case 'o': // --oldpassword + pszPasswordOld = ValueUnion.psz; + break; + + case 'c': // --cipher + pszCipher = ValueUnion.psz; + break; + + case 'i': // --newpasswordid + pszNewPasswordId = ValueUnion.psz; + break; + + case VINF_GETOPT_NOT_OPTION: + if (!pszFilenameOrUuid) + pszFilenameOrUuid = ValueUnion.psz; + else + return errorSyntax(USAGE_ENCRYPTMEDIUM, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(USAGE_ENCRYPTMEDIUM, "Invalid option -%c", c); + else + return errorSyntax(USAGE_ENCRYPTMEDIUM, "Invalid option case %i", c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_ENCRYPTMEDIUM, "unknown option: %s\n", ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(USAGE_ENCRYPTMEDIUM, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(USAGE_ENCRYPTMEDIUM, "error: %Rrs", c); + } + } + + if (!pszFilenameOrUuid) + return errorSyntax(USAGE_ENCRYPTMEDIUM, "Disk name or UUID required"); + + if (!pszPasswordNew && !pszPasswordOld) + return errorSyntax(USAGE_ENCRYPTMEDIUM, "No password specified"); + + if ( (pszPasswordNew && !pszNewPasswordId) + || (!pszPasswordNew && pszNewPasswordId)) + return errorSyntax(USAGE_ENCRYPTMEDIUM, "A new password must always have a valid identifier set at the same time"); + + 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; + } + } + } + + 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; + } + } + } + + /* Always open the medium if necessary, there is no other way. */ + rc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadWrite, hardDisk, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + if (hardDisk.isNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid hard disk reference, avoiding crash"); + + ComPtr<IProgress> progress; + CHECK_ERROR(hardDisk, ChangeEncryption(Bstr(strPasswordOld).raw(), Bstr(pszCipher).raw(), + Bstr(strPasswordNew).raw(), Bstr(pszNewPasswordId).raw(), + progress.asOutParam())); + if (SUCCEEDED(rc)) + rc = showProgress(progress); + if (FAILED(rc)) + { + if (rc == E_NOTIMPL) + RTMsgError("Encrypt hard disk operation is not implemented!"); + else if (rc == VBOX_E_NOT_SUPPORTED) + RTMsgError("Encrypt hard disk operation for this cipher is not implemented yet!"); + else if (!progress.isNull()) + CHECK_PROGRESS_ERROR(progress, ("Failed to encrypt hard disk")); + else + RTMsgError("Failed to encrypt hard disk!"); + } + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleCheckMediumPassword(HandlerArg *a) +{ + HRESULT rc; + ComPtr<IMedium> hardDisk; + const char *pszFilenameOrUuid = NULL; + Utf8Str strPassword; + + if (a->argc != 2) + return errorSyntax(USAGE_MEDIUMENCCHKPWD, "Invalid number of arguments: %d", a->argc); + + pszFilenameOrUuid = a->argv[0]; + + if (!RTStrCmp(a->argv[1], "-")) + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, "Enter password:"); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(a->argv[1], &strPassword); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError("Failed to read password from file"); + return rcExit; + } + } + + /* Always open the medium if necessary, there is no other way. */ + rc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadWrite, hardDisk, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + if (hardDisk.isNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid hard disk reference, avoiding crash"); + + CHECK_ERROR(hardDisk, CheckEncryptionPassword(Bstr(strPassword).raw())); + if (SUCCEEDED(rc)) + RTPrintf("The given password is correct\n"); + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/********************************************************************************************************************************* +* The mediumio command * +*********************************************************************************************************************************/ + +/** + * Common MediumIO options. + */ +typedef struct MEDIUMIOCOMMONOPT +{ + const char *pszFilenameOrUuid; + DeviceType_T enmDeviceType; + const char *pszPasswordFile; +} MEDIUMIOCOMMONOPT; +typedef MEDIUMIOCOMMONOPT *PMEDIUMIOCOMMONOPT; +typedef MEDIUMIOCOMMONOPT const *PCMEDIUMIOCOMMONOPT; + +/* For RTGETOPTDEF array initializer. */ +#define MEDIUMIOCOMMONOPT_DEFS() \ + { "--disk", 'd', RTGETOPT_REQ_STRING }, \ + { "--harddisk", 'd', RTGETOPT_REQ_STRING }, \ + { "disk", 'd', RTGETOPT_REQ_STRING }, \ + { "harddisk", 'd', RTGETOPT_REQ_STRING }, \ + { "--dvd", 'D', RTGETOPT_REQ_STRING }, \ + { "--iso", 'D', RTGETOPT_REQ_STRING }, \ + { "dvd", 'D', RTGETOPT_REQ_STRING }, \ + { "iso", 'D', RTGETOPT_REQ_STRING }, \ + { "--floppy", 'f', RTGETOPT_REQ_STRING }, \ + { "floppy", 'f', RTGETOPT_REQ_STRING }, \ + { "--password-file", 'P', RTGETOPT_REQ_STRING } + +/* For option switch. */ +#define MEDIUMIOCOMMONOPT_CASES(a_pCommonOpts) \ + case 'd': \ + (a_pCommonOpts)->enmDeviceType = DeviceType_HardDisk; \ + (a_pCommonOpts)->pszFilenameOrUuid = ValueUnion.psz; \ + break; \ + case 'D': \ + (a_pCommonOpts)->enmDeviceType = DeviceType_DVD; \ + (a_pCommonOpts)->pszFilenameOrUuid = ValueUnion.psz; \ + break; \ + case 'f': \ + (a_pCommonOpts)->enmDeviceType = DeviceType_Floppy; \ + (a_pCommonOpts)->pszFilenameOrUuid = ValueUnion.psz; \ + break; \ + case 'P': \ + (a_pCommonOpts)->pszPasswordFile = ValueUnion.psz; \ + break + + +/** + * Worker for mediumio operations that returns a IMediumIO for the specified + * medium. + * + * @returns Exit code. + * @param pHandler The handler state structure (for IVirtualBox). + * @param pCommonOpts Common mediumio options. + * @param fWritable Whether to open writable (true) or read only + * (false). + * @param rPtrMediumIO Where to return the IMediumIO pointer. + * @param pcbMedium Where to return the meidum size. Optional. + */ +static RTEXITCODE mediumIOOpenMediumForIO(HandlerArg *pHandler, PCMEDIUMIOCOMMONOPT pCommonOpts, bool fWritable, + ComPtr<IMediumIO> &rPtrMediumIO, uint64_t *pcbMedium = NULL) +{ + /* Clear returns. */ + if (pcbMedium) + *pcbMedium = 0; + rPtrMediumIO.setNull(); + + /* + * Make sure a medium was specified already. + */ + if (pCommonOpts->enmDeviceType == DeviceType_Null) + return errorSyntax("No medium specified!"); + + /* + * Read the password. + */ + Bstr bstrPassword; + if (pCommonOpts->pszPasswordFile) + { + Utf8Str strPassword; + RTEXITCODE rcExit; + if (pCommonOpts->pszPasswordFile[0] == '-' && pCommonOpts->pszPasswordFile[1] == '\0') + rcExit = readPasswordFromConsole(&strPassword, "Enter encryption password:"); + else + rcExit = readPasswordFile(pCommonOpts->pszPasswordFile, &strPassword); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + bstrPassword = strPassword; + strPassword.assign(strPassword.length(), '*'); + } + + /* + * Open the medium and then get I/O access to it. + */ + ComPtr<IMedium> ptrMedium; + HRESULT hrc = openMedium(pHandler, pCommonOpts->pszFilenameOrUuid, pCommonOpts->enmDeviceType, + fWritable ? AccessMode_ReadWrite : AccessMode_ReadOnly, + ptrMedium, false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (SUCCEEDED(hrc)) + { + CHECK_ERROR2I_STMT(ptrMedium, OpenForIO(fWritable, bstrPassword.raw(), rPtrMediumIO.asOutParam()), hrc = hrcCheck); + + /* + * If the size is requested get it after we've opened it. + */ + if (pcbMedium && SUCCEEDED(hrc)) + { + LONG64 cbLogical = 0; + CHECK_ERROR2I_STMT(ptrMedium, COMGETTER(LogicalSize)(&cbLogical), hrc = hrcCheck); + *pcbMedium = cbLogical; + if (!SUCCEEDED(hrc)) + rPtrMediumIO.setNull(); + } + } + + if (bstrPassword.isNotEmpty()) + memset(bstrPassword.mutableRaw(), '*', bstrPassword.length() * sizeof(RTUTF16)); + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * mediumio formatfat + */ +static RTEXITCODE handleMediumIOFormatFat(HandlerArg *a, int iFirst, PMEDIUMIOCOMMONOPT pCommonOpts) +{ + /* + * Parse the options. + */ + bool fQuick = false; + static const RTGETOPTDEF s_aOptions[] = + { + MEDIUMIOCOMMONOPT_DEFS(), + { "--quick", 'q', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0); + AssertRC(rc); + RTGETOPTUNION ValueUnion; + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + MEDIUMIOCOMMONOPT_CASES(pCommonOpts); + + case 'q': + fQuick = true; + break; + + default: + return errorGetOpt(rc, &ValueUnion); + } + } + + /* + * Open the medium for I/O and format it. + */ + ComPtr<IMediumIO> ptrMediumIO; + RTEXITCODE rcExit = mediumIOOpenMediumForIO(a, pCommonOpts, true /*fWritable*/, ptrMediumIO); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + CHECK_ERROR2I_RET(ptrMediumIO, FormatFAT(fQuick), RTEXITCODE_FAILURE); + return RTEXITCODE_SUCCESS; +} + +/** + * mediumio cat + */ +static RTEXITCODE handleMediumIOCat(HandlerArg *a, int iFirst, PMEDIUMIOCOMMONOPT pCommonOpts) +{ + /* + * Parse the options. + */ + static const RTGETOPTDEF s_aOptions[] = + { + MEDIUMIOCOMMONOPT_DEFS(), + { "--hex", 'H', RTGETOPT_REQ_NOTHING }, + { "--offset", 'o', RTGETOPT_REQ_UINT64 }, + { "--output", 'O', RTGETOPT_REQ_STRING }, + { "--size", 's', RTGETOPT_REQ_UINT64 }, + }; + bool fHex = false; + uint64_t off = 0; + const char *pszOutput = NULL; + uint64_t cb = UINT64_MAX; + + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0); + AssertRC(rc); + RTGETOPTUNION ValueUnion; + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + MEDIUMIOCOMMONOPT_CASES(pCommonOpts); + + case 'H': + fHex = true; + break; + + case 'o': + off = ValueUnion.u64; + break; + + case 'O': + pszOutput = ValueUnion.psz; + break; + + case 's': + cb = ValueUnion.u64; + break; + + default: + return errorGetOpt(rc, &ValueUnion); + } + } + + /* + * Open the medium for I/O. + */ + ComPtr<IMediumIO> ptrMediumIO; + uint64_t cbMedium; + RTEXITCODE rcExit = mediumIOOpenMediumForIO(a, pCommonOpts, false /*fWritable*/, ptrMediumIO, &cbMedium); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Do we have an output file or do we write to stdout? + */ + PRTSTREAM pOut = NULL; + if (pszOutput && (pszOutput[0] != '-' || pszOutput[1] != '\0')) + { + int vrc = RTStrmOpen(pszOutput, fHex ? "wt" : "wb", &pOut); + if (RT_FAILURE(vrc)) + rcExit = RTMsgErrorExitFailure("Error opening '%s' for writing: %Rrc", pszOutput, vrc); + } + else + { + pOut = g_pStdOut; + if (!fHex) + RTStrmSetMode(pOut, true, -1); + } + + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Adjust 'cb' now that we've got the medium size. + */ + if (off >= cbMedium) + { + RTMsgWarning("Specified offset (%#RX64) is beyond the end of the medium (%#RX64)", off, cbMedium); + cb = 0; + } + else if ( cb > cbMedium + || cb + off > cbMedium) + cb = cbMedium - off; + + /* + * Hex dump preps. (The duplication detection is making ASSUMPTIONS about + * all the reads being a multiple of cchWidth, except for the final one.) + */ + char abHexBuf[16] = { 0 }; + size_t cbHexBuf = 0; + unsigned const cchWidth = RT_ELEMENTS(abHexBuf); + uint64_t const offEndDupCheck = cb - cchWidth; + uint64_t cDuplicates = 0; + + /* + * Do the reading. + */ + while (cb > 0) + { + char szLine[32 + cchWidth * 4 + 32]; + + /* Do the reading. */ + uint32_t const cbToRead = (uint32_t)RT_MIN(cb, _128K); + SafeArray<BYTE> SafeArrayBuf; + HRESULT hrc = ptrMediumIO->Read(off, cbToRead, ComSafeArrayAsOutParam(SafeArrayBuf)); + if (FAILED(hrc)) + { + RTStrPrintf(szLine, sizeof(szLine), "Read(%zu bytes at %#RX64)", cbToRead, off); + com::GlueHandleComError(ptrMediumIO, szLine, hrc, __FILE__, __LINE__); + break; + } + + /* Output the data. */ + size_t const cbReturned = SafeArrayBuf.size(); + if (cbReturned) + { + BYTE const *pbBuf = SafeArrayBuf.raw(); + int vrc = VINF_SUCCESS; + if (!fHex) + vrc = RTStrmWrite(pOut, pbBuf, cbReturned); + else + { + /* hexdump -C */ + uint64_t offHex = off; + uint64_t const offHexEnd = off + cbReturned; + while (offHex < offHexEnd) + { + if ( offHex >= offEndDupCheck + || cbHexBuf == 0 + || memcmp(pbBuf, abHexBuf, cchWidth) != 0 + || ( cDuplicates == 0 + && ( offHex + cchWidth >= offEndDupCheck + || memcmp(pbBuf + cchWidth, pbBuf, cchWidth) != 0)) ) + { + if (cDuplicates > 0) + { + RTStrmPrintf(pOut, "********** <ditto x %RU64>\n", cDuplicates); + cDuplicates = 0; + } + + size_t cch = RTStrPrintf(szLine, sizeof(szLine), "%012RX64:", offHex); + unsigned i; + for (i = 0; i < cchWidth && offHex + i < offHexEnd; i++) + { + static const char s_szHexDigits[17] = "0123456789abcdef"; + szLine[cch++] = (i & 7) || i == 0 ? ' ' : '-'; + uint8_t const u8 = pbBuf[i]; + szLine[cch++] = s_szHexDigits[u8 >> 4]; + szLine[cch++] = s_szHexDigits[u8 & 0xf]; + } + while (i++ < cchWidth) + { + szLine[cch++] = ' '; + szLine[cch++] = ' '; + szLine[cch++] = ' '; + } + szLine[cch++] = ' '; + + for (i = 0; i < cchWidth && offHex + i < offHexEnd; i++) + { + uint8_t const u8 = pbBuf[i]; + szLine[cch++] = u8 < 127 && u8 >= 32 ? u8 : '.'; + } + szLine[cch++] = '\n'; + szLine[cch] = '\0'; + + vrc = RTStrmWrite(pOut, szLine, cch); + if (RT_FAILURE(vrc)) + break; + + + /* copy bytes over to the duplication detection buffer. */ + cbHexBuf = (size_t)RT_MIN(cchWidth, offHexEnd - offHex); + memcpy(abHexBuf, pbBuf, cbHexBuf); + } + else + cDuplicates++; + + /* Advance to next line. */ + pbBuf += cchWidth; + offHex += cchWidth; + } + } + if (RT_FAILURE(vrc)) + { + rcExit = RTMsgErrorExitFailure("Error writing to '%s': %Rrc", pszOutput, vrc); + break; + } + } + + /* Advance. */ + if (cbReturned != cbToRead) + { + rcExit = RTMsgErrorExitFailure("Expected read() at offset %RU64 (%#RX64) to return %#zx bytes, only got %#zx!\n", + off, off, cbReturned, cbToRead); + break; + } + off += cbReturned; + cb -= cbReturned; + } + + /* + * Close output. + */ + if (pOut != g_pStdOut) + { + int vrc = RTStrmClose(pOut); + if (RT_FAILURE(vrc)) + rcExit = RTMsgErrorExitFailure("Error closing '%s': %Rrc", pszOutput, vrc); + } + else if (!fHex) + RTStrmSetMode(pOut, false, -1); + } + } + return rcExit; +} + +/** + * mediumio stream + */ +static RTEXITCODE handleMediumIOStream(HandlerArg *a, int iFirst, PMEDIUMIOCOMMONOPT pCommonOpts) +{ + /* + * Parse the options. + */ + static const RTGETOPTDEF s_aOptions[] = + { + MEDIUMIOCOMMONOPT_DEFS(), + { "--output", 'O', RTGETOPT_REQ_STRING }, + { "--format", 'F', RTGETOPT_REQ_STRING }, + { "--variant", 'v', RTGETOPT_REQ_STRING } + }; + const char *pszOutput = NULL; + MediumVariant_T enmMediumVariant = MediumVariant_Standard; + Bstr strFormat; + + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0); + AssertRC(rc); + RTGETOPTUNION ValueUnion; + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + MEDIUMIOCOMMONOPT_CASES(pCommonOpts); + + case 'O': + pszOutput = ValueUnion.psz; + break; + case 'F': + strFormat = ValueUnion.psz; + break; + case 'v': // --variant + { + int vrc = parseMediumVariant(ValueUnion.psz, &enmMediumVariant); + if (RT_FAILURE(vrc)) + return errorArgument("Invalid medium variant '%s'", ValueUnion.psz); + break; + } + + default: + return errorGetOpt(rc, &ValueUnion); + } + } + + /* + * Open the medium for I/O. + */ + ComPtr<IMediumIO> ptrMediumIO; + uint64_t cbMedium; + RTEXITCODE rcExit = mediumIOOpenMediumForIO(a, pCommonOpts, false /*fWritable*/, ptrMediumIO, &cbMedium); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Do we have an output file or do we write to stdout? + */ + PRTSTREAM pOut = NULL; + if (pszOutput && (pszOutput[0] != '-' || pszOutput[1] != '\0')) + { + int vrc = RTStrmOpen(pszOutput, "wb", &pOut); + if (RT_FAILURE(vrc)) + rcExit = RTMsgErrorExitFailure("Error opening '%s' for writing: %Rrc", pszOutput, vrc); + } + else + { + pOut = g_pStdOut; + RTStrmSetMode(pOut, true, -1); + } + + if (rcExit == RTEXITCODE_SUCCESS) + { + ComPtr<IDataStream> ptrDataStream; + ComPtr<IProgress> ptrProgress; + + com::SafeArray<MediumVariant_T> l_variants(sizeof(MediumVariant_T)*8); + + for (ULONG i = 0; i < l_variants.size(); ++i) + { + ULONG temp = enmMediumVariant; + temp &= 1<<i; + l_variants [i] = (MediumVariant_T)temp; + } + + HRESULT hrc = ptrMediumIO->ConvertToStream(strFormat.raw(), ComSafeArrayAsInParam(l_variants), 10 * _1M, ptrDataStream.asOutParam(), ptrProgress.asOutParam()); + if (hrc == S_OK) + { + /* Read until we reached the end of the stream. */ + for (;;) + { + SafeArray<BYTE> SafeArrayBuf; + + hrc = ptrDataStream->Read(_64K, 0 /*Infinite wait*/, ComSafeArrayAsOutParam(SafeArrayBuf)); + if ( FAILED(hrc) + || SafeArrayBuf.size() == 0) + break; + + /* Output the data. */ + size_t const cbReturned = SafeArrayBuf.size(); + if (cbReturned) + { + BYTE const *pbBuf = SafeArrayBuf.raw(); + int vrc = VINF_SUCCESS; + vrc = RTStrmWrite(pOut, pbBuf, cbReturned); + if (RT_FAILURE(vrc)) + { + rcExit = RTMsgErrorExitFailure("Error writing to '%s': %Rrc", pszOutput, vrc); + break; + } + } + + /** @todo Check progress. */ + } + } + else + { + com::GlueHandleComError(ptrMediumIO, "ConvertToStream()", hrc, __FILE__, __LINE__); + rcExit = RTEXITCODE_FAILURE; + } + + /* + * Close output. + */ + if (pOut != g_pStdOut) + { + int vrc = RTStrmClose(pOut); + if (RT_FAILURE(vrc)) + rcExit = RTMsgErrorExitFailure("Error closing '%s': %Rrc", pszOutput, vrc); + } + else + RTStrmSetMode(pOut, false, -1); + } + } + return rcExit; +} + + +RTEXITCODE handleMediumIO(HandlerArg *a) +{ + /* + * Parse image-option and sub-command. + */ + static const RTGETOPTDEF s_aOptions[] = + { + MEDIUMIOCOMMONOPT_DEFS(), + /* sub-commands */ + { "formatfat", 1000, RTGETOPT_REQ_NOTHING }, + { "cat", 1001, RTGETOPT_REQ_NOTHING }, + { "stream", 1002, RTGETOPT_REQ_NOTHING }, + }; + MEDIUMIOCOMMONOPT CommonOpts = { NULL, DeviceType_Null, NULL }; + + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0); + AssertRC(rc); + RTGETOPTUNION ValueUnion; + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + MEDIUMIOCOMMONOPT_CASES(&CommonOpts); + + /* Sub-commands: */ + case 1000: + setCurrentSubcommand(HELP_SCOPE_MEDIUMIO_FORMATFAT); + return handleMediumIOFormatFat(a, GetState.iNext, &CommonOpts); + case 1001: + setCurrentSubcommand(HELP_SCOPE_MEDIUMIO_CAT); + return handleMediumIOCat(a, GetState.iNext, &CommonOpts); + case 1002: + setCurrentSubcommand(HELP_SCOPE_MEDIUMIO_STREAM); + return handleMediumIOStream(a, GetState.iNext, &CommonOpts); + + case VINF_GETOPT_NOT_OPTION: + return errorUnknownSubcommand(ValueUnion.psz); + + default: + return errorGetOpt(rc, &ValueUnion); + } + } + return errorNoSubcommand(); +} + +#endif /* !VBOX_ONLY_DOCS */ diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp new file mode 100644 index 00000000..8d0b3372 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp @@ -0,0 +1,3435 @@ +/* $Id: VBoxManageGuestCtrl.cpp $ */ +/** @file + * VBoxManage - Implementation of guestcontrol command. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxManage.h" +#include "VBoxManageGuestCtrl.h" + +#ifndef VBOX_ONLY_DOCS + +#include <VBox/com/array.h> +#include <VBox/com/com.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/listeners.h> +#include <VBox/com/NativeEventQueue.h> +#include <VBox/com/string.h> +#include <VBox/com/VirtualBox.h> + +#include <VBox/err.h> +#include <VBox/log.h> + +#include <iprt/asm.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/list.h> +#include <iprt/path.h> +#include <iprt/process.h> /* For RTProcSelf(). */ +#include <iprt/thread.h> +#include <iprt/vfs.h> + +#include <map> +#include <vector> + +#ifdef USE_XPCOM_QUEUE +# include <sys/select.h> +# include <errno.h> +#endif + +#include <signal.h> + +#ifdef RT_OS_DARWIN +# include <CoreFoundation/CFRunLoop.h> +#endif + +using namespace com; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define GCTLCMD_COMMON_OPT_USER 999 /**< The --username option number. */ +#define GCTLCMD_COMMON_OPT_PASSWORD 998 /**< The --password option number. */ +#define GCTLCMD_COMMON_OPT_PASSWORD_FILE 997 /**< The --password-file option number. */ +#define GCTLCMD_COMMON_OPT_DOMAIN 996 /**< The --domain option number. */ +/** Common option definitions. */ +#define GCTLCMD_COMMON_OPTION_DEFS() \ + { "--username", GCTLCMD_COMMON_OPT_USER, RTGETOPT_REQ_STRING }, \ + { "--passwordfile", GCTLCMD_COMMON_OPT_PASSWORD_FILE, RTGETOPT_REQ_STRING }, \ + { "--password", GCTLCMD_COMMON_OPT_PASSWORD, RTGETOPT_REQ_STRING }, \ + { "--domain", GCTLCMD_COMMON_OPT_DOMAIN, RTGETOPT_REQ_STRING }, \ + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, \ + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + +/** Handles common options in the typical option parsing switch. */ +#define GCTLCMD_COMMON_OPTION_CASES(a_pCtx, a_ch, a_pValueUnion) \ + case 'v': \ + case 'q': \ + case GCTLCMD_COMMON_OPT_USER: \ + case GCTLCMD_COMMON_OPT_DOMAIN: \ + case GCTLCMD_COMMON_OPT_PASSWORD: \ + case GCTLCMD_COMMON_OPT_PASSWORD_FILE: \ + { \ + RTEXITCODE rcExitCommon = gctlCtxSetOption(a_pCtx, a_ch, a_pValueUnion); \ + if (RT_UNLIKELY(rcExitCommon != RTEXITCODE_SUCCESS)) \ + return rcExitCommon; \ + } break + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Set by the signal handler when current guest control + * action shall be aborted. */ +static volatile bool g_fGuestCtrlCanceled = false; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Listener declarations. + */ +VBOX_LISTENER_DECLARE(GuestFileEventListenerImpl) +VBOX_LISTENER_DECLARE(GuestProcessEventListenerImpl) +VBOX_LISTENER_DECLARE(GuestSessionEventListenerImpl) +VBOX_LISTENER_DECLARE(GuestEventListenerImpl) + + +/** + * Definition of a guestcontrol command, with handler and various flags. + */ +typedef struct GCTLCMDDEF +{ + /** The command name. */ + const char *pszName; + + /** + * Actual command handler callback. + * + * @param pCtx Pointer to command context to use. + */ + DECLR3CALLBACKMEMBER(RTEXITCODE, pfnHandler, (struct GCTLCMDCTX *pCtx, int argc, char **argv)); + + /** The command usage flags. */ + uint32_t fCmdUsage; + /** Command context flags (GCTLCMDCTX_F_XXX). */ + uint32_t fCmdCtx; +} GCTLCMD; +/** Pointer to a const guest control command definition. */ +typedef GCTLCMDDEF const *PCGCTLCMDDEF; + +/** @name GCTLCMDCTX_F_XXX - Command context flags. + * @{ + */ +/** No flags set. */ +#define GCTLCMDCTX_F_NONE 0 +/** Don't install a signal handler (CTRL+C trap). */ +#define GCTLCMDCTX_F_NO_SIGNAL_HANDLER RT_BIT(0) +/** No guest session needed. */ +#define GCTLCMDCTX_F_SESSION_ANONYMOUS RT_BIT(1) +/** @} */ + +/** + * Context for handling a specific command. + */ +typedef struct GCTLCMDCTX +{ + HandlerArg *pArg; + + /** Pointer to the command definition. */ + PCGCTLCMDDEF pCmdDef; + /** The VM name or UUID. */ + const char *pszVmNameOrUuid; + + /** Whether we've done the post option parsing init already. */ + bool fPostOptionParsingInited; + /** Whether we've locked the VM session. */ + bool fLockedVmSession; + /** Whether to detach (@c true) or close the session. */ + bool fDetachGuestSession; + /** Set if we've installed the signal handler. */ + bool fInstalledSignalHandler; + /** The verbosity level. */ + uint32_t cVerbose; + /** User name. */ + Utf8Str strUsername; + /** Password. */ + Utf8Str strPassword; + /** Domain. */ + Utf8Str strDomain; + /** Pointer to the IGuest interface. */ + ComPtr<IGuest> pGuest; + /** Pointer to the to be used guest session. */ + ComPtr<IGuestSession> pGuestSession; + /** The guest session ID. */ + ULONG uSessionID; + +} GCTLCMDCTX, *PGCTLCMDCTX; + + +/** + * An entry for an element which needs to be copied/created to/on the guest. + */ +typedef struct DESTFILEENTRY +{ + DESTFILEENTRY(Utf8Str strFilename) : mFilename(strFilename) {} + Utf8Str mFilename; +} DESTFILEENTRY, *PDESTFILEENTRY; +/* + * Map for holding destination entries, whereas the key is the destination + * directory and the mapped value is a vector holding all elements for this directory. + */ +typedef std::map< Utf8Str, std::vector<DESTFILEENTRY> > DESTDIRMAP, *PDESTDIRMAP; +typedef std::map< Utf8Str, std::vector<DESTFILEENTRY> >::iterator DESTDIRMAPITER, *PDESTDIRMAPITER; + + +/** + * RTGetOpt-IDs for the guest execution control command line. + */ +enum GETOPTDEF_EXEC +{ + GETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES = 1000, + GETOPTDEF_EXEC_NO_PROFILE, + GETOPTDEF_EXEC_OUTPUTFORMAT, + GETOPTDEF_EXEC_DOS2UNIX, + GETOPTDEF_EXEC_UNIX2DOS, + GETOPTDEF_EXEC_WAITFOREXIT, + GETOPTDEF_EXEC_WAITFORSTDOUT, + GETOPTDEF_EXEC_WAITFORSTDERR +}; + +enum kStreamTransform +{ + kStreamTransform_None = 0, + kStreamTransform_Dos2Unix, + kStreamTransform_Unix2Dos +}; +#endif /* VBOX_ONLY_DOCS */ + + +void usageGuestControl(PRTSTREAM pStrm, const char *pcszSep1, const char *pcszSep2, uint32_t uSubCmd) +{ + const uint32_t fAnonSubCmds = USAGE_GSTCTRL_CLOSESESSION + | USAGE_GSTCTRL_LIST + | USAGE_GSTCTRL_CLOSEPROCESS + | USAGE_GSTCTRL_CLOSESESSION + | USAGE_GSTCTRL_UPDATEGA + | USAGE_GSTCTRL_WATCH; + + /* 0 1 2 3 4 5 6 7 8XXXXXXXXXX */ + /* 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 */ + if (~fAnonSubCmds & uSubCmd) + RTStrmPrintf(pStrm, + "%s guestcontrol %s <uuid|vmname> [--verbose|-v] [--quiet|-q]\n" + " [--username <name>] [--domain <domain>]\n" + " [--passwordfile <file> | --password <password>]\n%s", + pcszSep1, pcszSep2, uSubCmd == ~0U ? "\n" : ""); + if (uSubCmd & USAGE_GSTCTRL_RUN) + RTStrmPrintf(pStrm, + " run [common-options]\n" + " [--exe <path to executable>] [--timeout <msec>]\n" + " [-E|--putenv <NAME>[=<VALUE>]] [--unquoted-args]\n" + " [--ignore-operhaned-processes] [--profile]\n" + " [--no-wait-stdout|--wait-stdout]\n" + " [--no-wait-stderr|--wait-stderr]\n" + " [--dos2unix] [--unix2dos]\n" + " -- <program/arg0> [argument1] ... [argumentN]]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_START) + RTStrmPrintf(pStrm, + " start [common-options]\n" + " [--exe <path to executable>] [--timeout <msec>]\n" + " [-E|--putenv <NAME>[=<VALUE>]] [--unquoted-args]\n" + " [--ignore-operhaned-processes] [--profile]\n" + " -- <program/arg0> [argument1] ... [argumentN]]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_COPYFROM) + RTStrmPrintf(pStrm, + " copyfrom [common-options]\n" + " [--follow] [-R|--recursive]\n" + " <guest-src0> [guest-src1 [...]] <host-dst>\n" + "\n" + " copyfrom [common-options]\n" + " [--follow] [-R|--recursive]\n" + " [--target-directory <host-dst-dir>]\n" + " <guest-src0> [guest-src1 [...]]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_COPYTO) + RTStrmPrintf(pStrm, + " copyto [common-options]\n" + " [--follow] [-R|--recursive]\n" + " <host-src0> [host-src1 [...]] <guest-dst>\n" + "\n" + " copyto [common-options]\n" + " [--follow] [-R|--recursive]\n" + " [--target-directory <guest-dst>]\n" + " <host-src0> [host-src1 [...]]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_MKDIR) + RTStrmPrintf(pStrm, + " mkdir|createdir[ectory] [common-options]\n" + " [--parents] [--mode <mode>]\n" + " <guest directory> [...]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_RMDIR) + RTStrmPrintf(pStrm, + " rmdir|removedir[ectory] [common-options]\n" + " [-R|--recursive]\n" + " <guest directory> [...]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_RM) + RTStrmPrintf(pStrm, + " removefile|rm [common-options] [-f|--force]\n" + " <guest file> [...]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_MV) + RTStrmPrintf(pStrm, + " mv|move|ren[ame] [common-options]\n" + " <source> [source1 [...]] <dest>\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_MKTEMP) + RTStrmPrintf(pStrm, + " mktemp|createtemp[orary] [common-options]\n" + " [--secure] [--mode <mode>] [--tmpdir <directory>]\n" + " <template>\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_STAT) + RTStrmPrintf(pStrm, + " stat [common-options]\n" + " <file> [...]\n" + "\n"); + + /* + * Command not requiring authentication. + */ + if (fAnonSubCmds & uSubCmd) + { + /* 0 1 2 3 4 5 6 7 8XXXXXXXXXX */ + /* 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 */ + RTStrmPrintf(pStrm, + "%s guestcontrol %s <uuid|vmname> [--verbose|-v] [--quiet|-q]\n%s", + pcszSep1, pcszSep2, uSubCmd == ~0U ? "\n" : ""); + if (uSubCmd & USAGE_GSTCTRL_LIST) + RTStrmPrintf(pStrm, + " list <all|sessions|processes|files> [common-opts]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_CLOSEPROCESS) + RTStrmPrintf(pStrm, + " closeprocess [common-options]\n" + " < --session-id <ID>\n" + " | --session-name <name or pattern>\n" + " <PID1> [PID1 [...]]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_CLOSESESSION) + RTStrmPrintf(pStrm, + " closesession [common-options]\n" + " < --all | --session-id <ID>\n" + " | --session-name <name or pattern> >\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_UPDATEGA) + RTStrmPrintf(pStrm, + " updatega|updateguestadditions|updateadditions\n" + " [--source <guest additions .ISO>]\n" + " [--wait-start] [common-options]\n" + " [-- [<argument1>] ... [<argumentN>]]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_WATCH) + RTStrmPrintf(pStrm, + " watch [common-options]\n" + "\n"); + } +} + +#ifndef VBOX_ONLY_DOCS + + +#ifdef RT_OS_WINDOWS +static BOOL WINAPI gctlSignalHandler(DWORD dwCtrlType) +{ + bool fEventHandled = FALSE; + switch (dwCtrlType) + { + /* User pressed CTRL+C or CTRL+BREAK or an external event was sent + * via GenerateConsoleCtrlEvent(). */ + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_C_EVENT: + ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true); + fEventHandled = TRUE; + break; + default: + break; + /** @todo Add other events here. */ + } + + return fEventHandled; +} +#else /* !RT_OS_WINDOWS */ +/** + * Signal handler that sets g_fGuestCtrlCanceled. + * + * This can be executed on any thread in the process, on Windows it may even be + * a thread dedicated to delivering this signal. Don't do anything + * unnecessary here. + */ +static void gctlSignalHandler(int iSignal) +{ + NOREF(iSignal); + ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true); +} +#endif + + +/** + * Installs a custom signal handler to get notified + * whenever the user wants to intercept the program. + * + * @todo Make this handler available for all VBoxManage modules? + */ +static int gctlSignalHandlerInstall(void) +{ + g_fGuestCtrlCanceled = false; + + int rc = VINF_SUCCESS; +#ifdef RT_OS_WINDOWS + if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)gctlSignalHandler, TRUE /* Add handler */)) + { + rc = RTErrConvertFromWin32(GetLastError()); + RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc); + } +#else + signal(SIGINT, gctlSignalHandler); + signal(SIGTERM, gctlSignalHandler); +# ifdef SIGBREAK + signal(SIGBREAK, gctlSignalHandler); +# endif +#endif + return rc; +} + + +/** + * Uninstalls a previously installed signal handler. + */ +static int gctlSignalHandlerUninstall(void) +{ + int rc = VINF_SUCCESS; +#ifdef RT_OS_WINDOWS + if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */)) + { + rc = RTErrConvertFromWin32(GetLastError()); + RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc); + } +#else + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); +# ifdef SIGBREAK + signal(SIGBREAK, SIG_DFL); +# endif +#endif + return rc; +} + + +/** + * Translates a process status to a human readable string. + */ +const char *gctlProcessStatusToText(ProcessStatus_T enmStatus) +{ + switch (enmStatus) + { + case ProcessStatus_Starting: + return "starting"; + case ProcessStatus_Started: + return "started"; + case ProcessStatus_Paused: + return "paused"; + case ProcessStatus_Terminating: + return "terminating"; + case ProcessStatus_TerminatedNormally: + return "successfully terminated"; + case ProcessStatus_TerminatedSignal: + return "terminated by signal"; + case ProcessStatus_TerminatedAbnormally: + return "abnormally aborted"; + case ProcessStatus_TimedOutKilled: + return "timed out"; + case ProcessStatus_TimedOutAbnormally: + return "timed out, hanging"; + case ProcessStatus_Down: + return "killed"; + case ProcessStatus_Error: + return "error"; + default: + break; + } + return "unknown"; +} + +/** + * Translates a guest process wait result to a human readable string. + */ +const char *gctlProcessWaitResultToText(ProcessWaitResult_T enmWaitResult) +{ + switch (enmWaitResult) + { + case ProcessWaitResult_Start: + return "started"; + case ProcessWaitResult_Terminate: + return "terminated"; + case ProcessWaitResult_Status: + return "status changed"; + case ProcessWaitResult_Error: + return "error"; + case ProcessWaitResult_Timeout: + return "timed out"; + case ProcessWaitResult_StdIn: + return "stdin ready"; + case ProcessWaitResult_StdOut: + return "data on stdout"; + case ProcessWaitResult_StdErr: + return "data on stderr"; + case ProcessWaitResult_WaitFlagNotSupported: + return "waiting flag not supported"; + default: + break; + } + return "unknown"; +} + +/** + * Translates a guest session status to a human readable string. + */ +const char *gctlGuestSessionStatusToText(GuestSessionStatus_T enmStatus) +{ + switch (enmStatus) + { + case GuestSessionStatus_Starting: + return "starting"; + case GuestSessionStatus_Started: + return "started"; + case GuestSessionStatus_Terminating: + return "terminating"; + case GuestSessionStatus_Terminated: + return "terminated"; + case GuestSessionStatus_TimedOutKilled: + return "timed out"; + case GuestSessionStatus_TimedOutAbnormally: + return "timed out, hanging"; + case GuestSessionStatus_Down: + return "killed"; + case GuestSessionStatus_Error: + return "error"; + default: + break; + } + return "unknown"; +} + +/** + * Translates a guest file status to a human readable string. + */ +const char *gctlFileStatusToText(FileStatus_T enmStatus) +{ + switch (enmStatus) + { + case FileStatus_Opening: + return "opening"; + case FileStatus_Open: + return "open"; + case FileStatus_Closing: + return "closing"; + case FileStatus_Closed: + return "closed"; + case FileStatus_Down: + return "killed"; + case FileStatus_Error: + return "error"; + default: + break; + } + return "unknown"; +} + +/** + * Translates a file system objec type to a string. + */ +const char *gctlFsObjTypeToName(FsObjType_T enmType) +{ + switch (enmType) + { + case FsObjType_Unknown: return "unknown"; + case FsObjType_Fifo: return "fifo"; + case FsObjType_DevChar: return "char-device"; + case FsObjType_Directory: return "directory"; + case FsObjType_DevBlock: return "block-device"; + case FsObjType_File: return "file"; + case FsObjType_Symlink: return "symlink"; + case FsObjType_Socket: return "socket"; + case FsObjType_WhiteOut: return "white-out"; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case FsObjType_32BitHack: break; +#endif + } + return "unknown"; +} + +static int gctlPrintError(com::ErrorInfo &errorInfo) +{ + if ( errorInfo.isFullAvailable() + || errorInfo.isBasicAvailable()) + { + /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way + * because it contains more accurate info about what went wrong. */ + if (errorInfo.getResultCode() == VBOX_E_IPRT_ERROR) + RTMsgError("%ls.", errorInfo.getText().raw()); + else + { + RTMsgError("Error details:"); + GluePrintErrorInfo(errorInfo); + } + return VERR_GENERAL_FAILURE; /** @todo */ + } + AssertMsgFailedReturn(("Object has indicated no error (%Rhrc)!?\n", errorInfo.getResultCode()), + VERR_INVALID_PARAMETER); +} + +static int gctlPrintError(IUnknown *pObj, const GUID &aIID) +{ + com::ErrorInfo ErrInfo(pObj, aIID); + return gctlPrintError(ErrInfo); +} + +static int gctlPrintProgressError(ComPtr<IProgress> pProgress) +{ + int vrc = VINF_SUCCESS; + HRESULT rc; + + do + { + BOOL fCanceled; + CHECK_ERROR_BREAK(pProgress, COMGETTER(Canceled)(&fCanceled)); + if (!fCanceled) + { + LONG rcProc; + CHECK_ERROR_BREAK(pProgress, COMGETTER(ResultCode)(&rcProc)); + if (FAILED(rcProc)) + { + com::ProgressErrorInfo ErrInfo(pProgress); + vrc = gctlPrintError(ErrInfo); + } + } + + } while(0); + + AssertMsgStmt(SUCCEEDED(rc), ("Could not lookup progress information\n"), vrc = VERR_COM_UNEXPECTED); + + return vrc; +} + + + +/* + * + * + * Guest Control Command Context + * Guest Control Command Context + * Guest Control Command Context + * Guest Control Command Context + * + * + * + */ + + +/** + * Initializes a guest control command context structure. + * + * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE on failure (after + * informing the user of course). + * @param pCtx The command context to init. + * @param pArg The handle argument package. + */ +static RTEXITCODE gctrCmdCtxInit(PGCTLCMDCTX pCtx, HandlerArg *pArg) +{ + RT_ZERO(*pCtx); + pCtx->pArg = pArg; + + /* + * The user name defaults to the host one, if we can get at it. + */ + char szUser[1024]; + int rc = RTProcQueryUsername(RTProcSelf(), szUser, sizeof(szUser), NULL); + if ( RT_SUCCESS(rc) + && RTStrIsValidEncoding(szUser)) /* paranoia required on posix */ + { + try + { + pCtx->strUsername = szUser; + } + catch (std::bad_alloc &) + { + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory"); + } + } + /* else: ignore this failure. */ + + return RTEXITCODE_SUCCESS; +} + + +/** + * Worker for GCTLCMD_COMMON_OPTION_CASES. + * + * @returns RTEXITCODE_SUCCESS if the option was handled successfully. If not, + * an error message is printed and an appropriate failure exit code is + * returned. + * @param pCtx The guest control command context. + * @param ch The option char or ordinal. + * @param pValueUnion The option value union. + */ +static RTEXITCODE gctlCtxSetOption(PGCTLCMDCTX pCtx, int ch, PRTGETOPTUNION pValueUnion) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + switch (ch) + { + case GCTLCMD_COMMON_OPT_USER: /* User name */ + if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS)) + pCtx->strUsername = pValueUnion->psz; + else + RTMsgWarning("The --username|-u option is ignored by '%s'", pCtx->pCmdDef->pszName); + break; + + case GCTLCMD_COMMON_OPT_PASSWORD: /* Password */ + if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS)) + { + if (pCtx->strPassword.isNotEmpty()) + RTMsgWarning("Password is given more than once."); + pCtx->strPassword = pValueUnion->psz; + } + else + RTMsgWarning("The --password option is ignored by '%s'", pCtx->pCmdDef->pszName); + break; + + case GCTLCMD_COMMON_OPT_PASSWORD_FILE: /* Password file */ + if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS)) + rcExit = readPasswordFile(pValueUnion->psz, &pCtx->strPassword); + else + RTMsgWarning("The --password-file|-p option is ignored by '%s'", pCtx->pCmdDef->pszName); + break; + + case GCTLCMD_COMMON_OPT_DOMAIN: /* domain */ + if (!pCtx->pCmdDef || !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS)) + pCtx->strDomain = pValueUnion->psz; + else + RTMsgWarning("The --domain option is ignored by '%s'", pCtx->pCmdDef->pszName); + break; + + case 'v': /* --verbose */ + pCtx->cVerbose++; + break; + + case 'q': /* --quiet */ + if (pCtx->cVerbose) + pCtx->cVerbose--; + break; + + default: + AssertFatalMsgFailed(("ch=%d (%c)\n", ch, ch)); + } + return rcExit; +} + + +/** + * Initializes the VM for IGuest operation. + * + * This opens a shared session to a running VM and gets hold of IGuest. + * + * @returns RTEXITCODE_SUCCESS on success. RTEXITCODE_FAILURE and user message + * on failure. + * @param pCtx The guest control command context. + * GCTLCMDCTX::pGuest will be set on success. + */ +static RTEXITCODE gctlCtxInitVmSession(PGCTLCMDCTX pCtx) +{ + HRESULT rc; + AssertPtr(pCtx); + AssertPtr(pCtx->pArg); + + /* + * Find the VM and check if it's running. + */ + ComPtr<IMachine> machine; + CHECK_ERROR(pCtx->pArg->virtualBox, FindMachine(Bstr(pCtx->pszVmNameOrUuid).raw(), machine.asOutParam())); + if (SUCCEEDED(rc)) + { + MachineState_T enmMachineState; + CHECK_ERROR(machine, COMGETTER(State)(&enmMachineState)); + if ( SUCCEEDED(rc) + && enmMachineState == MachineState_Running) + { + /* + * It's running. So, open a session to it and get the IGuest interface. + */ + CHECK_ERROR(machine, LockMachine(pCtx->pArg->session, LockType_Shared)); + if (SUCCEEDED(rc)) + { + pCtx->fLockedVmSession = true; + ComPtr<IConsole> ptrConsole; + CHECK_ERROR(pCtx->pArg->session, COMGETTER(Console)(ptrConsole.asOutParam())); + if (SUCCEEDED(rc)) + { + if (ptrConsole.isNotNull()) + { + CHECK_ERROR(ptrConsole, COMGETTER(Guest)(pCtx->pGuest.asOutParam())); + if (SUCCEEDED(rc)) + return RTEXITCODE_SUCCESS; + } + else + RTMsgError("Failed to get a IConsole pointer for the machine. Is it still running?\n"); + } + } + } + else if (SUCCEEDED(rc)) + RTMsgError("Machine \"%s\" is not running (currently %s)!\n", + pCtx->pszVmNameOrUuid, machineStateToName(enmMachineState, false)); + } + return RTEXITCODE_FAILURE; +} + + +/** + * Creates a guest session with the VM. + * + * @retval RTEXITCODE_SUCCESS on success. + * @retval RTEXITCODE_FAILURE and user message on failure. + * @param pCtx The guest control command context. + * GCTCMDCTX::pGuestSession and GCTLCMDCTX::uSessionID + * will be set. + */ +static RTEXITCODE gctlCtxInitGuestSession(PGCTLCMDCTX pCtx) +{ + HRESULT rc; + AssertPtr(pCtx); + Assert(!(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS)); + Assert(pCtx->pGuest.isNotNull()); + + /* + * Build up a reasonable guest session name. Useful for identifying + * a specific session when listing / searching for them. + */ + char *pszSessionName; + if (RTStrAPrintf(&pszSessionName, + "[%RU32] VBoxManage Guest Control [%s] - %s", + RTProcSelf(), pCtx->pszVmNameOrUuid, pCtx->pCmdDef->pszName) < 0) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "No enough memory for session name"); + + /* + * Create a guest session. + */ + if (pCtx->cVerbose) + RTPrintf("Creating guest session as user '%s'...\n", pCtx->strUsername.c_str()); + try + { + CHECK_ERROR(pCtx->pGuest, CreateSession(Bstr(pCtx->strUsername).raw(), + Bstr(pCtx->strPassword).raw(), + Bstr(pCtx->strDomain).raw(), + Bstr(pszSessionName).raw(), + pCtx->pGuestSession.asOutParam())); + } + catch (std::bad_alloc &) + { + RTMsgError("Out of memory setting up IGuest::CreateSession call"); + rc = E_OUTOFMEMORY; + } + if (SUCCEEDED(rc)) + { + /* + * Wait for guest session to start. + */ + if (pCtx->cVerbose) + RTPrintf("Waiting for guest session to start...\n"); + GuestSessionWaitResult_T enmWaitResult = GuestSessionWaitResult_None; /* Shut up MSC */ + try + { + com::SafeArray<GuestSessionWaitForFlag_T> aSessionWaitFlags; + aSessionWaitFlags.push_back(GuestSessionWaitForFlag_Start); + CHECK_ERROR(pCtx->pGuestSession, WaitForArray(ComSafeArrayAsInParam(aSessionWaitFlags), + /** @todo Make session handling timeouts configurable. */ + 30 * 1000, &enmWaitResult)); + } + catch (std::bad_alloc &) + { + RTMsgError("Out of memory setting up IGuestSession::WaitForArray call"); + rc = E_OUTOFMEMORY; + } + if (SUCCEEDED(rc)) + { + /* The WaitFlagNotSupported result may happen with GAs older than 4.3. */ + if ( enmWaitResult == GuestSessionWaitResult_Start + || enmWaitResult == GuestSessionWaitResult_WaitFlagNotSupported) + { + /* + * Get the session ID and we're ready to rumble. + */ + CHECK_ERROR(pCtx->pGuestSession, COMGETTER(Id)(&pCtx->uSessionID)); + if (SUCCEEDED(rc)) + { + if (pCtx->cVerbose) + RTPrintf("Successfully started guest session (ID %RU32)\n", pCtx->uSessionID); + RTStrFree(pszSessionName); + return RTEXITCODE_SUCCESS; + } + } + else + { + GuestSessionStatus_T enmSessionStatus; + CHECK_ERROR(pCtx->pGuestSession, COMGETTER(Status)(&enmSessionStatus)); + RTMsgError("Error starting guest session (current status is: %s)\n", + SUCCEEDED(rc) ? gctlGuestSessionStatusToText(enmSessionStatus) : "<unknown>"); + } + } + } + + RTStrFree(pszSessionName); + return RTEXITCODE_FAILURE; +} + + +/** + * Completes the guest control context initialization after parsing arguments. + * + * Will validate common arguments, open a VM session, and if requested open a + * guest session and install the CTRL-C signal handler. + * + * It is good to validate all the options and arguments you can before making + * this call. However, the VM session, IGuest and IGuestSession interfaces are + * not availabe till after this call, so take care. + * + * @retval RTEXITCODE_SUCCESS on success. + * @retval RTEXITCODE_FAILURE and user message on failure. + * @param pCtx The guest control command context. + * GCTCMDCTX::pGuestSession and GCTLCMDCTX::uSessionID + * will be set. + * @remarks Can safely be called multiple times, will only do work once. + */ +static RTEXITCODE gctlCtxPostOptionParsingInit(PGCTLCMDCTX pCtx) +{ + if (pCtx->fPostOptionParsingInited) + return RTEXITCODE_SUCCESS; + + /* + * Check that the user name isn't empty when we need it. + */ + RTEXITCODE rcExit; + if ( (pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS) + || pCtx->strUsername.isNotEmpty()) + { + /* + * Open the VM session and if required, a guest session. + */ + rcExit = gctlCtxInitVmSession(pCtx); + if ( rcExit == RTEXITCODE_SUCCESS + && !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS)) + rcExit = gctlCtxInitGuestSession(pCtx); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Install signal handler if requested (errors are ignored). + */ + if (!(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_NO_SIGNAL_HANDLER)) + { + int rc = gctlSignalHandlerInstall(); + pCtx->fInstalledSignalHandler = RT_SUCCESS(rc); + } + } + } + else + rcExit = errorSyntaxEx(USAGE_GUESTCONTROL, pCtx->pCmdDef->fCmdUsage, "No user name specified!"); + + pCtx->fPostOptionParsingInited = rcExit == RTEXITCODE_SUCCESS; + return rcExit; +} + + +/** + * Cleans up the context when the command returns. + * + * This will close any open guest session, unless the DETACH flag is set. + * It will also close any VM session that may be been established. Any signal + * handlers we've installed will also be removed. + * + * Un-initializes the VM after guest control usage. + * @param pCmdCtx Pointer to command context. + */ +static void gctlCtxTerm(PGCTLCMDCTX pCtx) +{ + HRESULT rc; + AssertPtr(pCtx); + + /* + * Uninstall signal handler. + */ + if (pCtx->fInstalledSignalHandler) + { + gctlSignalHandlerUninstall(); + pCtx->fInstalledSignalHandler = false; + } + + /* + * Close, or at least release, the guest session. + */ + if (pCtx->pGuestSession.isNotNull()) + { + if ( !(pCtx->pCmdDef->fCmdCtx & GCTLCMDCTX_F_SESSION_ANONYMOUS) + && !pCtx->fDetachGuestSession) + { + if (pCtx->cVerbose) + RTPrintf("Closing guest session ...\n"); + + CHECK_ERROR(pCtx->pGuestSession, Close()); + } + else if ( pCtx->fDetachGuestSession + && pCtx->cVerbose) + RTPrintf("Guest session detached\n"); + + pCtx->pGuestSession.setNull(); + } + + /* + * Close the VM session. + */ + if (pCtx->fLockedVmSession) + { + Assert(pCtx->pArg->session.isNotNull()); + CHECK_ERROR(pCtx->pArg->session, UnlockMachine()); + pCtx->fLockedVmSession = false; + } +} + + + + + +/* + * + * + * Guest Control Command Handling. + * Guest Control Command Handling. + * Guest Control Command Handling. + * Guest Control Command Handling. + * Guest Control Command Handling. + * + * + */ + + +/** @name EXITCODEEXEC_XXX - Special run exit codes. + * + * Special exit codes for returning errors/information of a started guest + * process to the command line VBoxManage was started from. Useful for e.g. + * scripting. + * + * ASSUMING that all platforms have at least 7-bits for the exit code we can do + * the following mapping: + * - Guest exit code 0 is mapped to 0 on the host. + * - Guest exit codes 1 thru 93 (0x5d) are displaced by 32, so that 1 + * becomes 33 (0x21) on the host and 93 becomes 125 (0x7d) on the host. + * - Guest exit codes 94 (0x5e) and above are mapped to 126 (0x5e). + * + * We ASSUME that all VBoxManage status codes are in the range 0 thru 32. + * + * @note These are frozen as of 4.1.0. + * @note The guest exit code mappings was introduced with 5.0 and the 'run' + * command, they are/was not supported by 'exec'. + * @sa gctlRunCalculateExitCode + */ +/** Process exited normally but with an exit code <> 0. */ +#define EXITCODEEXEC_CODE ((RTEXITCODE)16) +#define EXITCODEEXEC_FAILED ((RTEXITCODE)17) +#define EXITCODEEXEC_TERM_SIGNAL ((RTEXITCODE)18) +#define EXITCODEEXEC_TERM_ABEND ((RTEXITCODE)19) +#define EXITCODEEXEC_TIMEOUT ((RTEXITCODE)20) +#define EXITCODEEXEC_DOWN ((RTEXITCODE)21) +/** Execution was interrupt by user (ctrl-c). */ +#define EXITCODEEXEC_CANCELED ((RTEXITCODE)22) +/** The first mapped guest (non-zero) exit code. */ +#define EXITCODEEXEC_MAPPED_FIRST 33 +/** The last mapped guest (non-zero) exit code value (inclusive). */ +#define EXITCODEEXEC_MAPPED_LAST 125 +/** The number of exit codes from EXITCODEEXEC_MAPPED_FIRST to + * EXITCODEEXEC_MAPPED_LAST. This is also the highest guest exit code number + * we're able to map. */ +#define EXITCODEEXEC_MAPPED_RANGE (93) +/** The guest exit code displacement value. */ +#define EXITCODEEXEC_MAPPED_DISPLACEMENT 32 +/** The guest exit code was too big to be mapped. */ +#define EXITCODEEXEC_MAPPED_BIG ((RTEXITCODE)126) +/** @} */ + +/** + * Calculates the exit code of VBoxManage. + * + * @returns The exit code to return. + * @param enmStatus The guest process status. + * @param uExitCode The associated guest process exit code (where + * applicable). + * @param fReturnExitCodes Set if we're to use the 32-126 range for guest + * exit codes. + */ +static RTEXITCODE gctlRunCalculateExitCode(ProcessStatus_T enmStatus, ULONG uExitCode, bool fReturnExitCodes) +{ + switch (enmStatus) + { + case ProcessStatus_TerminatedNormally: + if (uExitCode == 0) + return RTEXITCODE_SUCCESS; + if (!fReturnExitCodes) + return EXITCODEEXEC_CODE; + if (uExitCode <= EXITCODEEXEC_MAPPED_RANGE) + return (RTEXITCODE) (uExitCode + EXITCODEEXEC_MAPPED_DISPLACEMENT); + return EXITCODEEXEC_MAPPED_BIG; + + case ProcessStatus_TerminatedAbnormally: + return EXITCODEEXEC_TERM_ABEND; + case ProcessStatus_TerminatedSignal: + return EXITCODEEXEC_TERM_SIGNAL; + +#if 0 /* see caller! */ + case ProcessStatus_TimedOutKilled: + return EXITCODEEXEC_TIMEOUT; + case ProcessStatus_Down: + return EXITCODEEXEC_DOWN; /* Service/OS is stopping, process was killed. */ + case ProcessStatus_Error: + return EXITCODEEXEC_FAILED; + + /* The following is probably for detached? */ + case ProcessStatus_Starting: + return RTEXITCODE_SUCCESS; + case ProcessStatus_Started: + return RTEXITCODE_SUCCESS; + case ProcessStatus_Paused: + return RTEXITCODE_SUCCESS; + case ProcessStatus_Terminating: + return RTEXITCODE_SUCCESS; /** @todo ???? */ +#endif + + default: + AssertMsgFailed(("Unknown exit status (%u/%u) from guest process returned!\n", enmStatus, uExitCode)); + return RTEXITCODE_FAILURE; + } +} + + +/** + * Pumps guest output to the host. + * + * @return IPRT status code. + * @param pProcess Pointer to appropriate process object. + * @param hVfsIosDst Where to write the data. + * @param uHandle Handle where to read the data from. + * @param cMsTimeout Timeout (in ms) to wait for the operation to + * complete. + */ +static int gctlRunPumpOutput(IProcess *pProcess, RTVFSIOSTREAM hVfsIosDst, ULONG uHandle, RTMSINTERVAL cMsTimeout) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + Assert(hVfsIosDst != NIL_RTVFSIOSTREAM); + + int vrc; + + SafeArray<BYTE> aOutputData; + HRESULT hrc = pProcess->Read(uHandle, _64K, RT_MAX(cMsTimeout, 1), ComSafeArrayAsOutParam(aOutputData)); + if (SUCCEEDED(hrc)) + { + size_t cbOutputData = aOutputData.size(); + if (cbOutputData == 0) + vrc = VINF_SUCCESS; + else + { + BYTE const *pbBuf = aOutputData.raw(); + AssertPtr(pbBuf); + + vrc = RTVfsIoStrmWrite(hVfsIosDst, pbBuf, cbOutputData, true /*fBlocking*/, NULL); + if (RT_FAILURE(vrc)) + RTMsgError("Unable to write output, rc=%Rrc\n", vrc); + } + } + else + vrc = gctlPrintError(pProcess, COM_IIDOF(IProcess)); + return vrc; +} + + +/** + * Configures a host handle for pumping guest bits. + * + * @returns true if enabled and we successfully configured it. + * @param fEnabled Whether pumping this pipe is configured. + * @param enmHandle The IPRT standard handle designation. + * @param pszName The name for user messages. + * @param enmTransformation The transformation to apply. + * @param phVfsIos Where to return the resulting I/O stream handle. + */ +static bool gctlRunSetupHandle(bool fEnabled, RTHANDLESTD enmHandle, const char *pszName, + kStreamTransform enmTransformation, PRTVFSIOSTREAM phVfsIos) +{ + if (fEnabled) + { + int vrc = RTVfsIoStrmFromStdHandle(enmHandle, 0, true /*fLeaveOpen*/, phVfsIos); + if (RT_SUCCESS(vrc)) + { + if (enmTransformation != kStreamTransform_None) + { + RTMsgWarning("Unsupported %s line ending conversion", pszName); + /** @todo Implement dos2unix and unix2dos stream filters. */ + } + return true; + } + RTMsgWarning("Error getting %s handle: %Rrc", pszName, vrc); + } + return false; +} + + +/** + * Returns the remaining time (in ms) based on the start time and a set + * timeout value. Returns RT_INDEFINITE_WAIT if no timeout was specified. + * + * @return RTMSINTERVAL Time left (in ms). + * @param u64StartMs Start time (in ms). + * @param cMsTimeout Timeout value (in ms). + */ +static RTMSINTERVAL gctlRunGetRemainingTime(uint64_t u64StartMs, RTMSINTERVAL cMsTimeout) +{ + if (!cMsTimeout || cMsTimeout == RT_INDEFINITE_WAIT) /* If no timeout specified, wait forever. */ + return RT_INDEFINITE_WAIT; + + uint64_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs; + if (u64ElapsedMs >= cMsTimeout) + return 0; + + return cMsTimeout - (RTMSINTERVAL)u64ElapsedMs; +} + +/** + * Common handler for the 'run' and 'start' commands. + * + * @returns Command exit code. + * @param pCtx Guest session context. + * @param argc The argument count. + * @param argv The argument vector for this command. + * @param fRunCmd Set if it's 'run' clear if 'start'. + * @param fHelp The help flag for the command. + */ +static RTEXITCODE gctlHandleRunCommon(PGCTLCMDCTX pCtx, int argc, char **argv, bool fRunCmd, uint32_t fHelp) +{ + RT_NOREF(fHelp); + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + /* + * Parse arguments. + */ + enum kGstCtrlRunOpt + { + kGstCtrlRunOpt_IgnoreOrphanedProcesses = 1000, + kGstCtrlRunOpt_NoProfile, /** @todo Deprecated and will be removed soon; use kGstCtrlRunOpt_Profile instead, if needed. */ + kGstCtrlRunOpt_Profile, + kGstCtrlRunOpt_Dos2Unix, + kGstCtrlRunOpt_Unix2Dos, + kGstCtrlRunOpt_WaitForStdOut, + kGstCtrlRunOpt_NoWaitForStdOut, + kGstCtrlRunOpt_WaitForStdErr, + kGstCtrlRunOpt_NoWaitForStdErr + }; + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + { "--putenv", 'E', RTGETOPT_REQ_STRING }, + { "--exe", 'e', RTGETOPT_REQ_STRING }, + { "--timeout", 't', RTGETOPT_REQ_UINT32 }, + { "--unquoted-args", 'u', RTGETOPT_REQ_NOTHING }, + { "--ignore-operhaned-processes", kGstCtrlRunOpt_IgnoreOrphanedProcesses, RTGETOPT_REQ_NOTHING }, + { "--no-profile", kGstCtrlRunOpt_NoProfile, RTGETOPT_REQ_NOTHING }, /** @todo Deprecated. */ + { "--profile", kGstCtrlRunOpt_Profile, RTGETOPT_REQ_NOTHING }, + /* run only: 6 - options */ + { "--dos2unix", kGstCtrlRunOpt_Dos2Unix, RTGETOPT_REQ_NOTHING }, + { "--unix2dos", kGstCtrlRunOpt_Unix2Dos, RTGETOPT_REQ_NOTHING }, + { "--no-wait-stdout", kGstCtrlRunOpt_NoWaitForStdOut, RTGETOPT_REQ_NOTHING }, + { "--wait-stdout", kGstCtrlRunOpt_WaitForStdOut, RTGETOPT_REQ_NOTHING }, + { "--no-wait-stderr", kGstCtrlRunOpt_NoWaitForStdErr, RTGETOPT_REQ_NOTHING }, + { "--wait-stderr", kGstCtrlRunOpt_WaitForStdErr, RTGETOPT_REQ_NOTHING }, + }; + + /** @todo stdin handling. */ + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + int vrc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions) - (fRunCmd ? 0 : 6), + 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRC(vrc); + + com::SafeArray<ProcessCreateFlag_T> aCreateFlags; + com::SafeArray<ProcessWaitForFlag_T> aWaitFlags; + com::SafeArray<IN_BSTR> aArgs; + com::SafeArray<IN_BSTR> aEnv; + const char * pszImage = NULL; + bool fWaitForStdOut = fRunCmd; + bool fWaitForStdErr = fRunCmd; + RTVFSIOSTREAM hVfsStdOut = NIL_RTVFSIOSTREAM; + RTVFSIOSTREAM hVfsStdErr = NIL_RTVFSIOSTREAM; + enum kStreamTransform enmStdOutTransform = kStreamTransform_None; + enum kStreamTransform enmStdErrTransform = kStreamTransform_None; + RTMSINTERVAL cMsTimeout = 0; + + try + { + /* Wait for process start in any case. This is useful for scripting VBoxManage + * when relying on its overall exit code. */ + aWaitFlags.push_back(ProcessWaitForFlag_Start); + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case 'E': + if ( ValueUnion.psz[0] == '\0' + || ValueUnion.psz[0] == '=') + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RUN, + "Invalid argument variable[=value]: '%s'", ValueUnion.psz); + aEnv.push_back(Bstr(ValueUnion.psz).raw()); + break; + + case kGstCtrlRunOpt_IgnoreOrphanedProcesses: + aCreateFlags.push_back(ProcessCreateFlag_IgnoreOrphanedProcesses); + break; + + case kGstCtrlRunOpt_NoProfile: + /** @todo Deprecated, will be removed. */ + RTPrintf("Warning: Deprecated option \"--no-profile\" specified\n"); + break; + + case kGstCtrlRunOpt_Profile: + aCreateFlags.push_back(ProcessCreateFlag_Profile); + break; + + case 'e': + pszImage = ValueUnion.psz; + break; + + case 'u': + aCreateFlags.push_back(ProcessCreateFlag_UnquotedArguments); + break; + + /** @todo Add a hidden flag. */ + + case 't': /* Timeout */ + cMsTimeout = ValueUnion.u32; + break; + + /* run only options: */ + case kGstCtrlRunOpt_Dos2Unix: + Assert(fRunCmd); + enmStdErrTransform = enmStdOutTransform = kStreamTransform_Dos2Unix; + break; + case kGstCtrlRunOpt_Unix2Dos: + Assert(fRunCmd); + enmStdErrTransform = enmStdOutTransform = kStreamTransform_Unix2Dos; + break; + + case kGstCtrlRunOpt_WaitForStdOut: + Assert(fRunCmd); + fWaitForStdOut = true; + break; + case kGstCtrlRunOpt_NoWaitForStdOut: + Assert(fRunCmd); + fWaitForStdOut = false; + break; + + case kGstCtrlRunOpt_WaitForStdErr: + Assert(fRunCmd); + fWaitForStdErr = true; + break; + case kGstCtrlRunOpt_NoWaitForStdErr: + Assert(fRunCmd); + fWaitForStdErr = false; + break; + + case VINF_GETOPT_NOT_OPTION: + aArgs.push_back(Bstr(ValueUnion.psz).raw()); + if (!pszImage) + { + Assert(aArgs.size() == 1); + pszImage = ValueUnion.psz; + } + break; + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RUN, ch, &ValueUnion); + + } /* switch */ + } /* while RTGetOpt */ + + /* Must have something to execute. */ + if (!pszImage || !*pszImage) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RUN, "No executable specified!"); + + /* + * Finalize process creation and wait flags and input/output streams. + */ + if (!fRunCmd) + { + aCreateFlags.push_back(ProcessCreateFlag_WaitForProcessStartOnly); + Assert(!fWaitForStdOut); + Assert(!fWaitForStdErr); + } + else + { + aWaitFlags.push_back(ProcessWaitForFlag_Terminate); + fWaitForStdOut = gctlRunSetupHandle(fWaitForStdOut, RTHANDLESTD_OUTPUT, "stdout", enmStdOutTransform, &hVfsStdOut); + if (fWaitForStdOut) + { + aCreateFlags.push_back(ProcessCreateFlag_WaitForStdOut); + aWaitFlags.push_back(ProcessWaitForFlag_StdOut); + } + fWaitForStdErr = gctlRunSetupHandle(fWaitForStdErr, RTHANDLESTD_ERROR, "stderr", enmStdErrTransform, &hVfsStdErr); + if (fWaitForStdErr) + { + aCreateFlags.push_back(ProcessCreateFlag_WaitForStdErr); + aWaitFlags.push_back(ProcessWaitForFlag_StdErr); + } + } + } + catch (std::bad_alloc &) + { + return RTMsgErrorExit(RTEXITCODE_FAILURE, "VERR_NO_MEMORY\n"); + } + + RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + HRESULT rc; + + try + { + do + { + /* Get current time stamp to later calculate rest of timeout left. */ + uint64_t msStart = RTTimeMilliTS(); + + /* + * Create the process. + */ + if (pCtx->cVerbose) + { + if (cMsTimeout == 0) + RTPrintf("Starting guest process ...\n"); + else + RTPrintf("Starting guest process (within %ums)\n", cMsTimeout); + } + ComPtr<IGuestProcess> pProcess; + CHECK_ERROR_BREAK(pCtx->pGuestSession, ProcessCreate(Bstr(pszImage).raw(), + ComSafeArrayAsInParam(aArgs), + ComSafeArrayAsInParam(aEnv), + ComSafeArrayAsInParam(aCreateFlags), + gctlRunGetRemainingTime(msStart, cMsTimeout), + pProcess.asOutParam())); + + /* + * Explicitly wait for the guest process to be in a started state. + */ + com::SafeArray<ProcessWaitForFlag_T> aWaitStartFlags; + aWaitStartFlags.push_back(ProcessWaitForFlag_Start); + ProcessWaitResult_T waitResult; + CHECK_ERROR_BREAK(pProcess, WaitForArray(ComSafeArrayAsInParam(aWaitStartFlags), + gctlRunGetRemainingTime(msStart, cMsTimeout), &waitResult)); + + ULONG uPID = 0; + CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID)); + if (fRunCmd && pCtx->cVerbose) + RTPrintf("Process '%s' (PID %RU32) started\n", pszImage, uPID); + else if (!fRunCmd && pCtx->cVerbose) + { + /* Just print plain PID to make it easier for scripts + * invoking VBoxManage. */ + RTPrintf("[%RU32 - Session %RU32]\n", uPID, pCtx->uSessionID); + } + + /* + * Wait for process to exit/start... + */ + RTMSINTERVAL cMsTimeLeft = 1; /* Will be calculated. */ + bool fReadStdOut = false; + bool fReadStdErr = false; + bool fCompleted = false; + bool fCompletedStartCmd = false; + + vrc = VINF_SUCCESS; + while ( !fCompleted + && cMsTimeLeft > 0) + { + cMsTimeLeft = gctlRunGetRemainingTime(msStart, cMsTimeout); + CHECK_ERROR_BREAK(pProcess, WaitForArray(ComSafeArrayAsInParam(aWaitFlags), + RT_MIN(500 /*ms*/, RT_MAX(cMsTimeLeft, 1 /*ms*/)), + &waitResult)); + switch (waitResult) + { + case ProcessWaitResult_Start: + fCompletedStartCmd = fCompleted = !fRunCmd; /* Only wait for startup if the 'start' command. */ + break; + case ProcessWaitResult_StdOut: + fReadStdOut = true; + break; + case ProcessWaitResult_StdErr: + fReadStdErr = true; + break; + case ProcessWaitResult_Terminate: + if (pCtx->cVerbose) + RTPrintf("Process terminated\n"); + /* Process terminated, we're done. */ + fCompleted = true; + break; + case ProcessWaitResult_WaitFlagNotSupported: + /* The guest does not support waiting for stdout/err, so + * yield to reduce the CPU load due to busy waiting. */ + RTThreadYield(); + fReadStdOut = fReadStdErr = true; + break; + case ProcessWaitResult_Timeout: + { + /** @todo It is really unclear whether we will get stuck with the timeout + * result here if the guest side times out the process and fails to + * kill the process... To be on the save side, double the IPC and + * check the process status every time we time out. */ + ProcessStatus_T enmProcStatus; + CHECK_ERROR_BREAK(pProcess, COMGETTER(Status)(&enmProcStatus)); + if ( enmProcStatus == ProcessStatus_TimedOutKilled + || enmProcStatus == ProcessStatus_TimedOutAbnormally) + fCompleted = true; + fReadStdOut = fReadStdErr = true; + break; + } + case ProcessWaitResult_Status: + /* ignore. */ + break; + case ProcessWaitResult_Error: + /* waitFor is dead in the water, I think, so better leave the loop. */ + vrc = VERR_CALLBACK_RETURN; + break; + + case ProcessWaitResult_StdIn: AssertFailed(); /* did ask for this! */ break; + case ProcessWaitResult_None: AssertFailed(); /* used. */ break; + default: AssertFailed(); /* huh? */ break; + } + + if (g_fGuestCtrlCanceled) + break; + + /* + * Pump output as needed. + */ + if (fReadStdOut) + { + cMsTimeLeft = gctlRunGetRemainingTime(msStart, cMsTimeout); + int vrc2 = gctlRunPumpOutput(pProcess, hVfsStdOut, 1 /* StdOut */, cMsTimeLeft); + if (RT_FAILURE(vrc2) && RT_SUCCESS(vrc)) + vrc = vrc2; + fReadStdOut = false; + } + if (fReadStdErr) + { + cMsTimeLeft = gctlRunGetRemainingTime(msStart, cMsTimeout); + int vrc2 = gctlRunPumpOutput(pProcess, hVfsStdErr, 2 /* StdErr */, cMsTimeLeft); + if (RT_FAILURE(vrc2) && RT_SUCCESS(vrc)) + vrc = vrc2; + fReadStdErr = false; + } + if ( RT_FAILURE(vrc) + || g_fGuestCtrlCanceled) + break; + + /* + * Process events before looping. + */ + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + } /* while */ + + /* + * Report status back to the user. + */ + if (g_fGuestCtrlCanceled) + { + if (pCtx->cVerbose) + RTPrintf("Process execution aborted!\n"); + rcExit = EXITCODEEXEC_CANCELED; + } + else if (fCompletedStartCmd) + { + if (pCtx->cVerbose) + RTPrintf("Process successfully started!\n"); + rcExit = RTEXITCODE_SUCCESS; + } + else if (fCompleted) + { + ProcessStatus_T procStatus; + CHECK_ERROR_BREAK(pProcess, COMGETTER(Status)(&procStatus)); + if ( procStatus == ProcessStatus_TerminatedNormally + || procStatus == ProcessStatus_TerminatedAbnormally + || procStatus == ProcessStatus_TerminatedSignal) + { + LONG lExitCode; + CHECK_ERROR_BREAK(pProcess, COMGETTER(ExitCode)(&lExitCode)); + if (pCtx->cVerbose) + RTPrintf("Exit code=%u (Status=%u [%s])\n", + lExitCode, procStatus, gctlProcessStatusToText(procStatus)); + + rcExit = gctlRunCalculateExitCode(procStatus, lExitCode, true /*fReturnExitCodes*/); + } + else if ( procStatus == ProcessStatus_TimedOutKilled + || procStatus == ProcessStatus_TimedOutAbnormally) + { + if (pCtx->cVerbose) + RTPrintf("Process timed out (guest side) and %s\n", + procStatus == ProcessStatus_TimedOutAbnormally + ? "failed to terminate so far" : "was terminated"); + rcExit = EXITCODEEXEC_TIMEOUT; + } + else + { + if (pCtx->cVerbose) + RTPrintf("Process now is in status [%s] (unexpected)\n", gctlProcessStatusToText(procStatus)); + rcExit = RTEXITCODE_FAILURE; + } + } + else if (RT_FAILURE_NP(vrc)) + { + if (pCtx->cVerbose) + RTPrintf("Process monitor loop quit with vrc=%Rrc\n", vrc); + rcExit = RTEXITCODE_FAILURE; + } + else + { + if (pCtx->cVerbose) + RTPrintf("Process monitor loop timed out\n"); + rcExit = EXITCODEEXEC_TIMEOUT; + } + + } while (0); + } + catch (std::bad_alloc &) + { + rc = E_OUTOFMEMORY; + } + + /* + * Decide what to do with the guest session. + * + * If it's the 'start' command where detach the guest process after + * starting, don't close the guest session it is part of, except on + * failure or ctrl-c. + * + * For the 'run' command the guest process quits with us. + */ + if (!fRunCmd && SUCCEEDED(rc) && !g_fGuestCtrlCanceled) + pCtx->fDetachGuestSession = true; + + /* Make sure we return failure on failure. */ + if (FAILED(rc) && rcExit == RTEXITCODE_SUCCESS) + rcExit = RTEXITCODE_FAILURE; + return rcExit; +} + + +static DECLCALLBACK(RTEXITCODE) gctlHandleRun(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + return gctlHandleRunCommon(pCtx, argc, argv, true /*fRunCmd*/, USAGE_GSTCTRL_RUN); +} + + +static DECLCALLBACK(RTEXITCODE) gctlHandleStart(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + return gctlHandleRunCommon(pCtx, argc, argv, false /*fRunCmd*/, USAGE_GSTCTRL_START); +} + + +static RTEXITCODE gctlHandleCopy(PGCTLCMDCTX pCtx, int argc, char **argv, bool fHostToGuest) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + /** @todo r=bird: This command isn't very unix friendly in general. mkdir + * is much better (partly because it is much simpler of course). The main + * arguments against this is that (1) all but two options conflicts with + * what 'man cp' tells me on a GNU/Linux system, (2) wildchar matching is + * done windows CMD style (though not in a 100% compatible way), and (3) + * that only one source is allowed - efficiently sabotaging default + * wildcard expansion by a unix shell. The best solution here would be + * two different variant, one windowsy (xcopy) and one unixy (gnu cp). */ + + /* + * IGuest::CopyToGuest is kept as simple as possible to let the developer choose + * what and how to implement the file enumeration/recursive lookup, like VBoxManage + * does in here. + */ + enum GETOPTDEF_COPY + { + GETOPTDEF_COPY_FOLLOW = 1000, + GETOPTDEF_COPY_TARGETDIR + }; + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + { "--follow", GETOPTDEF_COPY_FOLLOW, RTGETOPT_REQ_NOTHING }, + { "--recursive", 'R', RTGETOPT_REQ_NOTHING }, + { "--target-directory", GETOPTDEF_COPY_TARGETDIR, RTGETOPT_REQ_STRING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + bool fDstMustBeDir = false; + const char *pszDst = NULL; + bool fFollow = false; + bool fRecursive = false; + uint32_t uUsage = fHostToGuest ? USAGE_GSTCTRL_COPYTO : USAGE_GSTCTRL_COPYFROM; + + int vrc = VINF_SUCCESS; + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) != 0 + && ch != VINF_GETOPT_NOT_OPTION) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case GETOPTDEF_COPY_FOLLOW: + fFollow = true; + break; + + case 'R': /* Recursive processing */ + fRecursive = true; + break; + + case GETOPTDEF_COPY_TARGETDIR: + pszDst = ValueUnion.psz; + fDstMustBeDir = true; + break; + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, uUsage, ch, &ValueUnion); + } + } + + char **papszSources = RTGetOptNonOptionArrayPtr(&GetState); + size_t cSources = &argv[argc] - papszSources; + + if (!cSources) + return errorSyntaxEx(USAGE_GUESTCONTROL, uUsage, "No sources specified!"); + + /* Unless a --target-directory is given, the last argument is the destination, so + bump it from the source list. */ + if (pszDst == NULL && cSources >= 2) + pszDst = papszSources[--cSources]; + + if (pszDst == NULL) + return errorSyntaxEx(USAGE_GUESTCONTROL, uUsage, "No destination specified!"); + + char szAbsDst[RTPATH_MAX]; + if (!fHostToGuest) + { + vrc = RTPathAbs(pszDst, szAbsDst, sizeof(szAbsDst)); + if (RT_SUCCESS(vrc)) + pszDst = szAbsDst; + else + return RTMsgErrorExitFailure("RTPathAbs failed on '%s': %Rrc", pszDst, vrc); + } + + RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* + * Done parsing arguments, do some more preparations. + */ + if (pCtx->cVerbose) + { + if (fHostToGuest) + RTPrintf("Copying from host to guest ...\n"); + else + RTPrintf("Copying from guest to host ...\n"); + } + + HRESULT rc = S_OK; + ComPtr<IProgress> pProgress; +/** @todo r=bird: This codes does nothing to handle the case where there are + * multiple sources. You need to do serveral thing before thats handled + * correctly. For starters the progress object handling needs to be moved + * inside the loop. Next you need to check what the destination is, because you + * can only copy multiple source files/directories to another directory. You + * actually need to check whether the target exists and is a directory + * regardless of you have 1 or 10 source files/dirs. + * + * Btw. the original approach to error handling here was APPALING. If some file + * couldn't be stat'ed or if it was a file/directory, you only spat out messages + * in verbose mode and never set the status code. + * + * The handling of the wildcard filtering expressions in sources was also just + * skipped. I've corrected this, but you still need to make up your mind wrt + * wildcards or not. + * + * Update: I've kicked out the whole SourceFileEntry/vecSources stuff as we can + * use argv directly without any unnecessary copying. You just have to + * look for the wildcards inside this loop instead. + */ + NOREF(fDstMustBeDir); + if (cSources != 1) + return RTMsgErrorExitFailure("Only one source file or directory at the moment."); + for (size_t iSrc = 0; iSrc < cSources; iSrc++) + { + const char *pszSource = papszSources[iSrc]; + + /* Check if the source contains any wilecards in the last component, if so we + don't know how to deal with it yet and refuse. */ + const char *pszSrcFinalComp = RTPathFilename(pszSource); + if (pszSrcFinalComp && strpbrk(pszSrcFinalComp, "*?")) + rcExit = RTMsgErrorExitFailure("Skipping '%s' because wildcard expansion isn't implemented yet\n", pszSource); + else if (fHostToGuest) + { + /* + * Source is host, destiation guest. + */ + char szAbsSrc[RTPATH_MAX]; + vrc = RTPathAbs(pszSource, szAbsSrc, sizeof(szAbsSrc)); + if (RT_SUCCESS(vrc)) + { + RTFSOBJINFO ObjInfo; + vrc = RTPathQueryInfo(szAbsSrc, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_FILE(ObjInfo.Attr.fMode)) + { + if (pCtx->cVerbose) + RTPrintf("File '%s' -> '%s'\n", szAbsSrc, pszDst); + + SafeArray<FileCopyFlag_T> copyFlags; + rc = pCtx->pGuestSession->FileCopyToGuest(Bstr(szAbsSrc).raw(), Bstr(pszDst).raw(), + ComSafeArrayAsInParam(copyFlags), pProgress.asOutParam()); + } + else if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + { + if (pCtx->cVerbose) + RTPrintf("Directory '%s' -> '%s'\n", szAbsSrc, pszDst); + + SafeArray<DirectoryCopyFlag_T> copyFlags; + copyFlags.push_back(DirectoryCopyFlag_CopyIntoExisting); + rc = pCtx->pGuestSession->DirectoryCopyToGuest(Bstr(szAbsSrc).raw(), Bstr(pszDst).raw(), + ComSafeArrayAsInParam(copyFlags), pProgress.asOutParam()); + } + else + rcExit = RTMsgErrorExitFailure("Not a file or directory: %s\n", szAbsSrc); + } + else + rcExit = RTMsgErrorExitFailure("RTPathQueryInfo failed on '%s': %Rrc", szAbsSrc, vrc); + } + else + rcExit = RTMsgErrorExitFailure("RTPathAbs failed on '%s': %Rrc", pszSource, vrc); + } + else + { + /* + * Source guest, destination host. + */ + /* We need to query the source type on the guest first in order to know which copy flavor we need. */ + ComPtr<IGuestFsObjInfo> pFsObjInfo; + rc = pCtx->pGuestSession->FsObjQueryInfo(Bstr(pszSource).raw(), TRUE /* fFollowSymlinks */, pFsObjInfo.asOutParam()); + if (SUCCEEDED(rc)) + { + FsObjType_T enmObjType; + CHECK_ERROR(pFsObjInfo,COMGETTER(Type)(&enmObjType)); + if (SUCCEEDED(rc)) + { + /* Take action according to source file. */ + if (enmObjType == FsObjType_Directory) + { + if (pCtx->cVerbose) + RTPrintf("Directory '%s' -> '%s'\n", pszSource, pszDst); + + SafeArray<DirectoryCopyFlag_T> aCopyFlags; + aCopyFlags.push_back(DirectoryCopyFlag_CopyIntoExisting); + rc = pCtx->pGuestSession->DirectoryCopyFromGuest(Bstr(pszSource).raw(), Bstr(pszDst).raw(), + ComSafeArrayAsInParam(aCopyFlags), pProgress.asOutParam()); + } + else if (enmObjType == FsObjType_File) + { + if (pCtx->cVerbose) + RTPrintf("File '%s' -> '%s'\n", pszSource, pszDst); + + SafeArray<FileCopyFlag_T> aCopyFlags; + rc = pCtx->pGuestSession->FileCopyFromGuest(Bstr(pszSource).raw(), Bstr(pszDst).raw(), + ComSafeArrayAsInParam(aCopyFlags), pProgress.asOutParam()); + } + else + rcExit = RTMsgErrorExitFailure("Not a file or directory: %s\n", pszSource); + } + else + rcExit = RTEXITCODE_FAILURE; + } + else + rcExit = RTMsgErrorExitFailure("FsObjQueryInfo failed on '%s': %Rhrc", pszSource, rc); + } + } + + if (FAILED(rc)) + { + vrc = gctlPrintError(pCtx->pGuestSession, COM_IIDOF(IGuestSession)); + } + else if (pProgress.isNotNull()) + { + if (pCtx->cVerbose) + rc = showProgress(pProgress); + else + rc = pProgress->WaitForCompletion(-1 /* No timeout */); + if (SUCCEEDED(rc)) + CHECK_PROGRESS_ERROR(pProgress, ("File copy failed")); + vrc = gctlPrintProgressError(pProgress); + } + if (RT_FAILURE(vrc)) + rcExit = RTEXITCODE_FAILURE; + + return rcExit; +} + +static DECLCALLBACK(RTEXITCODE) gctlHandleCopyFrom(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + return gctlHandleCopy(pCtx, argc, argv, false /* Guest to host */); +} + +static DECLCALLBACK(RTEXITCODE) gctlHandleCopyTo(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + return gctlHandleCopy(pCtx, argc, argv, true /* Host to guest */); +} + +static DECLCALLBACK(RTEXITCODE) gctrlHandleMkDir(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + { "--mode", 'm', RTGETOPT_REQ_UINT32 }, + { "--parents", 'P', RTGETOPT_REQ_NOTHING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + SafeArray<DirectoryCreateFlag_T> aDirCreateFlags; + uint32_t fDirMode = 0; /* Default mode. */ + uint32_t cDirsCreated = 0; + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case 'm': /* Mode */ + fDirMode = ValueUnion.u32; + break; + + case 'P': /* Create parents */ + aDirCreateFlags.push_back(DirectoryCreateFlag_Parents); + break; + + case VINF_GETOPT_NOT_OPTION: + if (cDirsCreated == 0) + { + /* + * First non-option - no more options now. + */ + rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (pCtx->cVerbose) + RTPrintf("Creating %RU32 directories...\n", argc - GetState.iNext + 1); + } + if (g_fGuestCtrlCanceled) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "mkdir was interrupted by Ctrl-C (%u left)\n", + argc - GetState.iNext + 1); + + /* + * Create the specified directory. + * + * On failure we'll change the exit status to failure and + * continue with the next directory that needs creating. We do + * this because we only create new things, and because this is + * how /bin/mkdir works on unix. + */ + cDirsCreated++; + if (pCtx->cVerbose) + RTPrintf("Creating directory \"%s\" ...\n", ValueUnion.psz); + try + { + HRESULT rc; + CHECK_ERROR(pCtx->pGuestSession, DirectoryCreate(Bstr(ValueUnion.psz).raw(), + fDirMode, ComSafeArrayAsInParam(aDirCreateFlags))); + if (FAILED(rc)) + rcExit = RTEXITCODE_FAILURE; + } + catch (std::bad_alloc &) + { + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory\n"); + } + break; + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKDIR, ch, &ValueUnion); + } + } + + if (!cDirsCreated) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKDIR, "No directory to create specified!"); + return rcExit; +} + + +static DECLCALLBACK(RTEXITCODE) gctlHandleRmDir(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + { "--recursive", 'R', RTGETOPT_REQ_NOTHING }, + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + bool fRecursive = false; + uint32_t cDirRemoved = 0; + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case 'R': + fRecursive = true; + break; + + case VINF_GETOPT_NOT_OPTION: + { + if (cDirRemoved == 0) + { + /* + * First non-option - no more options now. + */ + rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (pCtx->cVerbose) + RTPrintf("Removing %RU32 directorie%s(s)...\n", argc - GetState.iNext + 1, fRecursive ? "tree" : ""); + } + if (g_fGuestCtrlCanceled) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "rmdir was interrupted by Ctrl-C (%u left)\n", + argc - GetState.iNext + 1); + + cDirRemoved++; + HRESULT rc; + if (!fRecursive) + { + /* + * Remove exactly one directory. + */ + if (pCtx->cVerbose) + RTPrintf("Removing directory \"%s\" ...\n", ValueUnion.psz); + try + { + CHECK_ERROR(pCtx->pGuestSession, DirectoryRemove(Bstr(ValueUnion.psz).raw())); + } + catch (std::bad_alloc &) + { + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory\n"); + } + } + else + { + /* + * Remove the directory and anything under it, that means files + * and everything. This is in the tradition of the Windows NT + * CMD.EXE "rmdir /s" operation, a tradition which jpsoft's TCC + * strongly warns against (and half-ways questions the sense of). + */ + if (pCtx->cVerbose) + RTPrintf("Recursively removing directory \"%s\" ...\n", ValueUnion.psz); + try + { + /** @todo Make flags configurable. */ + com::SafeArray<DirectoryRemoveRecFlag_T> aRemRecFlags; + aRemRecFlags.push_back(DirectoryRemoveRecFlag_ContentAndDir); + + ComPtr<IProgress> ptrProgress; + CHECK_ERROR(pCtx->pGuestSession, DirectoryRemoveRecursive(Bstr(ValueUnion.psz).raw(), + ComSafeArrayAsInParam(aRemRecFlags), + ptrProgress.asOutParam())); + if (SUCCEEDED(rc)) + { + if (pCtx->cVerbose) + rc = showProgress(ptrProgress); + else + rc = ptrProgress->WaitForCompletion(-1 /* indefinitely */); + if (SUCCEEDED(rc)) + CHECK_PROGRESS_ERROR(ptrProgress, ("Directory deletion failed")); + ptrProgress.setNull(); + } + } + catch (std::bad_alloc &) + { + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory during recursive rmdir\n"); + } + } + + /* + * This command returns immediately on failure since it's destructive in nature. + */ + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + break; + } + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RMDIR, ch, &ValueUnion); + } + } + + if (!cDirRemoved) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RMDIR, "No directory to remove specified!"); + return rcExit; +} + +static DECLCALLBACK(RTEXITCODE) gctlHandleRm(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + { "--force", 'f', RTGETOPT_REQ_NOTHING, }, + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + uint32_t cFilesDeleted = 0; + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + bool fForce = true; + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case VINF_GETOPT_NOT_OPTION: + if (cFilesDeleted == 0) + { + /* + * First non-option - no more options now. + */ + rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (pCtx->cVerbose) + RTPrintf("Removing %RU32 file(s)...\n", argc - GetState.iNext + 1); + } + if (g_fGuestCtrlCanceled) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "rm was interrupted by Ctrl-C (%u left)\n", + argc - GetState.iNext + 1); + + /* + * Remove the specified file. + * + * On failure we will by default stop, however, the force option will + * by unix traditions force us to ignore errors and continue. + */ + cFilesDeleted++; + if (pCtx->cVerbose) + RTPrintf("Removing file \"%s\" ...\n", ValueUnion.psz); + try + { + /** @todo How does IGuestSession::FsObjRemove work with read-only files? Do we + * need to do some chmod or whatever to better emulate the --force flag? */ + HRESULT rc; + CHECK_ERROR(pCtx->pGuestSession, FsObjRemove(Bstr(ValueUnion.psz).raw())); + if (FAILED(rc) && !fForce) + return RTEXITCODE_FAILURE; + } + catch (std::bad_alloc &) + { + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory\n"); + } + break; + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RM, ch, &ValueUnion); + } + } + + if (!cFilesDeleted && !fForce) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_RM, "No file to remove specified!"); + return rcExit; +} + +static DECLCALLBACK(RTEXITCODE) gctlHandleMv(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() +/** @todo Missing --force/-f flag. */ + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + int vrc = VINF_SUCCESS; + + bool fDryrun = false; + std::vector< Utf8Str > vecSources; + const char *pszDst = NULL; + com::SafeArray<FsObjRenameFlag_T> aRenameFlags; + + try + { + /** @todo Make flags configurable. */ + aRenameFlags.push_back(FsObjRenameFlag_NoReplace); + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(vrc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + /** @todo Implement a --dryrun command. */ + /** @todo Implement rename flags. */ + + case VINF_GETOPT_NOT_OPTION: + vecSources.push_back(Utf8Str(ValueUnion.psz)); + pszDst = ValueUnion.psz; + break; + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MV, ch, &ValueUnion); + } + } + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize, rc=%Rrc\n", vrc); + + size_t cSources = vecSources.size(); + if (!cSources) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MV, + "No source(s) to move specified!"); + if (cSources < 2) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MV, + "No destination specified!"); + + RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* Delete last element, which now is the destination. */ + vecSources.pop_back(); + cSources = vecSources.size(); + + HRESULT rc = S_OK; + + if (cSources > 1) + { + BOOL fExists = FALSE; + rc = pCtx->pGuestSession->DirectoryExists(Bstr(pszDst).raw(), FALSE /*followSymlinks*/, &fExists); + if (FAILED(rc) || !fExists) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Destination must be a directory when specifying multiple sources\n"); + } + + /* + * Rename (move) the entries. + */ + if (pCtx->cVerbose) + RTPrintf("Renaming %RU32 %s ...\n", cSources, cSources > 1 ? "entries" : "entry"); + + std::vector< Utf8Str >::iterator it = vecSources.begin(); + while ( it != vecSources.end() + && !g_fGuestCtrlCanceled) + { + Utf8Str strCurSource = (*it); + + ComPtr<IGuestFsObjInfo> pFsObjInfo; + FsObjType_T enmObjType = FsObjType_Unknown; /* Shut up MSC */ + rc = pCtx->pGuestSession->FsObjQueryInfo(Bstr(strCurSource).raw(), FALSE /*followSymlinks*/, pFsObjInfo.asOutParam()); + if (SUCCEEDED(rc)) + rc = pFsObjInfo->COMGETTER(Type)(&enmObjType); + if (FAILED(rc)) + { + if (pCtx->cVerbose) + RTPrintf("Warning: Cannot stat for element \"%s\": No such file or directory\n", strCurSource.c_str()); + ++it; + continue; /* Skip. */ + } + + if (pCtx->cVerbose) + RTPrintf("Renaming %s \"%s\" to \"%s\" ...\n", + enmObjType == FsObjType_Directory ? "directory" : "file", + strCurSource.c_str(), pszDst); + + if (!fDryrun) + { + if (enmObjType == FsObjType_Directory) + { + CHECK_ERROR_BREAK(pCtx->pGuestSession, FsObjRename(Bstr(strCurSource).raw(), + Bstr(pszDst).raw(), + ComSafeArrayAsInParam(aRenameFlags))); + + /* Break here, since it makes no sense to rename mroe than one source to + * the same directory. */ +/** @todo r=bird: You are being kind of windowsy (or just DOSish) about the 'sense' part here, + * while being totaly buggy about the behavior. 'VBoxManage guestcontrol ren dir1 dir2 dstdir' will + * stop after 'dir1' and SILENTLY ignore dir2. If you tried this on Windows, you'd see an error + * being displayed. If you 'man mv' on a nearby unixy system, you'd see that they've made perfect + * sense out of any situation with more than one source. */ + it = vecSources.end(); + break; + } + else + CHECK_ERROR_BREAK(pCtx->pGuestSession, FsObjRename(Bstr(strCurSource).raw(), + Bstr(pszDst).raw(), + ComSafeArrayAsInParam(aRenameFlags))); + } + + ++it; + } + + if ( (it != vecSources.end()) + && pCtx->cVerbose) + { + RTPrintf("Warning: Not all sources were renamed\n"); + } + + return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS; +} + +static DECLCALLBACK(RTEXITCODE) gctlHandleMkTemp(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + { "--mode", 'm', RTGETOPT_REQ_UINT32 }, + { "--directory", 'D', RTGETOPT_REQ_NOTHING }, + { "--secure", 's', RTGETOPT_REQ_NOTHING }, + { "--tmpdir", 't', RTGETOPT_REQ_STRING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + Utf8Str strTemplate; + uint32_t fMode = 0; /* Default mode. */ + bool fDirectory = false; + bool fSecure = false; + Utf8Str strTempDir; + + DESTDIRMAP mapDirs; + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case 'm': /* Mode */ + fMode = ValueUnion.u32; + break; + + case 'D': /* Create directory */ + fDirectory = true; + break; + + case 's': /* Secure */ + fSecure = true; + break; + + case 't': /* Temp directory */ + strTempDir = ValueUnion.psz; + break; + + case VINF_GETOPT_NOT_OPTION: + if (strTemplate.isEmpty()) + strTemplate = ValueUnion.psz; + else + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKTEMP, + "More than one template specified!\n"); + break; + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKTEMP, ch, &ValueUnion); + } + } + + if (strTemplate.isEmpty()) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKTEMP, + "No template specified!"); + + if (!fDirectory) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_MKTEMP, + "Creating temporary files is currently not supported!"); + + RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* + * Create the directories. + */ + if (pCtx->cVerbose) + { + if (fDirectory && !strTempDir.isEmpty()) + RTPrintf("Creating temporary directory from template '%s' in directory '%s' ...\n", + strTemplate.c_str(), strTempDir.c_str()); + else if (fDirectory) + RTPrintf("Creating temporary directory from template '%s' in default temporary directory ...\n", + strTemplate.c_str()); + else if (!fDirectory && !strTempDir.isEmpty()) + RTPrintf("Creating temporary file from template '%s' in directory '%s' ...\n", + strTemplate.c_str(), strTempDir.c_str()); + else if (!fDirectory) + RTPrintf("Creating temporary file from template '%s' in default temporary directory ...\n", + strTemplate.c_str()); + } + + HRESULT rc = S_OK; + if (fDirectory) + { + Bstr bstrDirectory; + CHECK_ERROR(pCtx->pGuestSession, DirectoryCreateTemp(Bstr(strTemplate).raw(), + fMode, Bstr(strTempDir).raw(), + fSecure, + bstrDirectory.asOutParam())); + if (SUCCEEDED(rc)) + RTPrintf("Directory name: %ls\n", bstrDirectory.raw()); + } + else + { + // else - temporary file not yet implemented + /** @todo implement temporary file creation (we fend it off above, no + * worries). */ + rc = E_FAIL; + } + + return FAILED(rc) ? RTEXITCODE_FAILURE : RTEXITCODE_SUCCESS; +} + +static DECLCALLBACK(RTEXITCODE) gctlHandleStat(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + { "--dereference", 'L', RTGETOPT_REQ_NOTHING }, + { "--file-system", 'f', RTGETOPT_REQ_NOTHING }, + { "--format", 'c', RTGETOPT_REQ_STRING }, + { "--terse", 't', RTGETOPT_REQ_NOTHING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) != 0 + && ch != VINF_GETOPT_NOT_OPTION) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case 'L': /* Dereference */ + case 'f': /* File-system */ + case 'c': /* Format */ + case 't': /* Terse */ + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_STAT, + "Command \"%s\" not implemented yet!", ValueUnion.psz); + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_STAT, ch, &ValueUnion); + } + } + + if (ch != VINF_GETOPT_NOT_OPTION) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_STAT, "Nothing to stat!"); + + RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + + /* + * Do the file stat'ing. + */ + while (ch == VINF_GETOPT_NOT_OPTION) + { + if (pCtx->cVerbose) + RTPrintf("Checking for element \"%s\" ...\n", ValueUnion.psz); + + ComPtr<IGuestFsObjInfo> pFsObjInfo; + HRESULT hrc = pCtx->pGuestSession->FsObjQueryInfo(Bstr(ValueUnion.psz).raw(), FALSE /*followSymlinks*/, + pFsObjInfo.asOutParam()); + if (FAILED(hrc)) + { + /** @todo r=bird: There might be other reasons why we end up here than + * non-existing "element" (object or file, please, nobody calls it elements). */ + if (pCtx->cVerbose) + RTPrintf("Failed to stat '%s': No such file\n", ValueUnion.psz); + rcExit = RTEXITCODE_FAILURE; + } + else + { + RTPrintf(" File: '%s'\n", ValueUnion.psz); /** @todo escape this name. */ + + FsObjType_T enmType = FsObjType_Unknown; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(Type)(&enmType)); + LONG64 cbObject = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(ObjectSize)(&cbObject)); + LONG64 cbAllocated = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(AllocatedSize)(&cbAllocated)); + LONG uid = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(UID)(&uid)); + LONG gid = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(GID)(&gid)); + Bstr bstrUsername; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(UserName)(bstrUsername.asOutParam())); + Bstr bstrGroupName; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(GroupName)(bstrGroupName.asOutParam())); + Bstr bstrAttribs; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(FileAttributes)(bstrAttribs.asOutParam())); + LONG64 idNode = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(NodeId)(&idNode)); + ULONG uDevNode = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(NodeIdDevice)(&uDevNode)); + ULONG uDeviceNo = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(DeviceNumber)(&uDeviceNo)); + ULONG cHardLinks = 1; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(HardLinks)(&cHardLinks)); + LONG64 nsBirthTime = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(BirthTime)(&nsBirthTime)); + LONG64 nsChangeTime = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(ChangeTime)(&nsChangeTime)); + LONG64 nsModificationTime = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(ModificationTime)(&nsModificationTime)); + LONG64 nsAccessTime = 0; + CHECK_ERROR2I(pFsObjInfo, COMGETTER(AccessTime)(&nsAccessTime)); + + RTPrintf(" Size: %-17RU64 Alloc: %-19RU64 Type: %s\n", cbObject, cbAllocated, gctlFsObjTypeToName(enmType)); + RTPrintf("Device: %#-17RX32 INode: %-18RU64 Links: %u\n", uDevNode, idNode, cHardLinks); + + Utf8Str strAttrib(bstrAttribs); + char *pszMode = strAttrib.mutableRaw(); + char *pszAttribs = strchr(pszMode, ' '); + if (pszAttribs) + do *pszAttribs++ = '\0'; + while (*pszAttribs == ' '); + else + pszAttribs = strchr(pszMode, '\0'); + if (uDeviceNo != 0) + RTPrintf(" Mode: %-16s Attrib: %-17s Dev ID: %#RX32\n", pszMode, pszAttribs, uDeviceNo); + else + RTPrintf(" Mode: %-16s Attrib: %s\n", pszMode, pszAttribs); + + RTPrintf(" Owner: %4d/%-12ls Group: %4d/%ls\n", uid, bstrUsername.raw(), gid, bstrGroupName.raw()); + + RTTIMESPEC TimeSpec; + char szTmp[RTTIME_STR_LEN]; + RTPrintf(" Birth: %s\n", RTTimeSpecToString(RTTimeSpecSetNano(&TimeSpec, nsBirthTime), szTmp, sizeof(szTmp))); + RTPrintf("Change: %s\n", RTTimeSpecToString(RTTimeSpecSetNano(&TimeSpec, nsChangeTime), szTmp, sizeof(szTmp))); + RTPrintf("Modify: %s\n", RTTimeSpecToString(RTTimeSpecSetNano(&TimeSpec, nsModificationTime), szTmp, sizeof(szTmp))); + RTPrintf("Access: %s\n", RTTimeSpecToString(RTTimeSpecSetNano(&TimeSpec, nsAccessTime), szTmp, sizeof(szTmp))); + + /* Skiping: Generation ID - only the ISO9660 VFS sets this. FreeBSD user flags. */ + } + + /* Next file. */ + ch = RTGetOpt(&GetState, &ValueUnion); + } + + return rcExit; +} + +static DECLCALLBACK(RTEXITCODE) gctlHandleUpdateAdditions(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + /* + * Check the syntax. We can deduce the correct syntax from the number of + * arguments. + */ + Utf8Str strSource; + com::SafeArray<IN_BSTR> aArgs; + bool fWaitStartOnly = false; + + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + { "--source", 's', RTGETOPT_REQ_STRING }, + { "--wait-start", 'w', RTGETOPT_REQ_NOTHING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + int vrc = VINF_SUCCESS; + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(vrc)) + { + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case 's': + strSource = ValueUnion.psz; + break; + + case 'w': + fWaitStartOnly = true; + break; + + case VINF_GETOPT_NOT_OPTION: + if (aArgs.size() == 0 && strSource.isEmpty()) + strSource = ValueUnion.psz; + else + aArgs.push_back(Bstr(ValueUnion.psz).raw()); + break; + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_UPDATEGA, ch, &ValueUnion); + } + } + + if (pCtx->cVerbose) + RTPrintf("Updating Guest Additions ...\n"); + + HRESULT rc = S_OK; + while (strSource.isEmpty()) + { + ComPtr<ISystemProperties> pProperties; + CHECK_ERROR_BREAK(pCtx->pArg->virtualBox, COMGETTER(SystemProperties)(pProperties.asOutParam())); + Bstr strISO; + CHECK_ERROR_BREAK(pProperties, COMGETTER(DefaultAdditionsISO)(strISO.asOutParam())); + strSource = strISO; + break; + } + + /* Determine source if not set yet. */ + if (strSource.isEmpty()) + { + RTMsgError("No Guest Additions source found or specified, aborting\n"); + vrc = VERR_FILE_NOT_FOUND; + } + else if (!RTFileExists(strSource.c_str())) + { + RTMsgError("Source \"%s\" does not exist!\n", strSource.c_str()); + vrc = VERR_FILE_NOT_FOUND; + } + + if (RT_SUCCESS(vrc)) + { + if (pCtx->cVerbose) + RTPrintf("Using source: %s\n", strSource.c_str()); + + + RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + + com::SafeArray<AdditionsUpdateFlag_T> aUpdateFlags; + if (fWaitStartOnly) + { + aUpdateFlags.push_back(AdditionsUpdateFlag_WaitForUpdateStartOnly); + if (pCtx->cVerbose) + RTPrintf("Preparing and waiting for Guest Additions installer to start ...\n"); + } + + ComPtr<IProgress> pProgress; + CHECK_ERROR(pCtx->pGuest, UpdateGuestAdditions(Bstr(strSource).raw(), + ComSafeArrayAsInParam(aArgs), + /* Wait for whole update process to complete. */ + ComSafeArrayAsInParam(aUpdateFlags), + pProgress.asOutParam())); + if (FAILED(rc)) + vrc = gctlPrintError(pCtx->pGuest, COM_IIDOF(IGuest)); + else + { + if (pCtx->cVerbose) + rc = showProgress(pProgress); + else + rc = pProgress->WaitForCompletion(-1 /* No timeout */); + + if (SUCCEEDED(rc)) + CHECK_PROGRESS_ERROR(pProgress, ("Guest additions update failed")); + vrc = gctlPrintProgressError(pProgress); + if ( RT_SUCCESS(vrc) + && pCtx->cVerbose) + { + RTPrintf("Guest Additions update successful\n"); + } + } + } + + return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static DECLCALLBACK(RTEXITCODE) gctlHandleList(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + bool fSeenListArg = false; + bool fListAll = false; + bool fListSessions = false; + bool fListProcesses = false; + bool fListFiles = false; + + int vrc = VINF_SUCCESS; + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(vrc)) + { + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case VINF_GETOPT_NOT_OPTION: + if ( !RTStrICmp(ValueUnion.psz, "sessions") + || !RTStrICmp(ValueUnion.psz, "sess")) + fListSessions = true; + else if ( !RTStrICmp(ValueUnion.psz, "processes") + || !RTStrICmp(ValueUnion.psz, "procs")) + fListSessions = fListProcesses = true; /* Showing processes implies showing sessions. */ + else if (!RTStrICmp(ValueUnion.psz, "files")) + fListSessions = fListFiles = true; /* Showing files implies showing sessions. */ + else if (!RTStrICmp(ValueUnion.psz, "all")) + fListAll = true; + else + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_LIST, + "Unknown list: '%s'", ValueUnion.psz); + fSeenListArg = true; + break; + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_UPDATEGA, ch, &ValueUnion); + } + } + + if (!fSeenListArg) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_LIST, "Missing list name"); + Assert(fListAll || fListSessions); + + RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + + /** @todo Do we need a machine-readable output here as well? */ + + HRESULT rc; + size_t cTotalProcs = 0; + size_t cTotalFiles = 0; + + SafeIfaceArray <IGuestSession> collSessions; + CHECK_ERROR(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions))); + if (SUCCEEDED(rc)) + { + size_t const cSessions = collSessions.size(); + if (cSessions) + { + RTPrintf("Active guest sessions:\n"); + + /** @todo Make this output a bit prettier. No time now. */ + + for (size_t i = 0; i < cSessions; i++) + { + ComPtr<IGuestSession> pCurSession = collSessions[i]; + if (!pCurSession.isNull()) + { + do + { + ULONG uID; + CHECK_ERROR_BREAK(pCurSession, COMGETTER(Id)(&uID)); + Bstr strName; + CHECK_ERROR_BREAK(pCurSession, COMGETTER(Name)(strName.asOutParam())); + Bstr strUser; + CHECK_ERROR_BREAK(pCurSession, COMGETTER(User)(strUser.asOutParam())); + GuestSessionStatus_T sessionStatus; + CHECK_ERROR_BREAK(pCurSession, COMGETTER(Status)(&sessionStatus)); + RTPrintf("\n\tSession #%-3zu ID=%-3RU32 User=%-16ls Status=[%s] Name=%ls", + i, uID, strUser.raw(), gctlGuestSessionStatusToText(sessionStatus), strName.raw()); + } while (0); + + if ( fListAll + || fListProcesses) + { + SafeIfaceArray <IGuestProcess> collProcesses; + CHECK_ERROR_BREAK(pCurSession, COMGETTER(Processes)(ComSafeArrayAsOutParam(collProcesses))); + for (size_t a = 0; a < collProcesses.size(); a++) + { + ComPtr<IGuestProcess> pCurProcess = collProcesses[a]; + if (!pCurProcess.isNull()) + { + do + { + ULONG uPID; + CHECK_ERROR_BREAK(pCurProcess, COMGETTER(PID)(&uPID)); + Bstr strExecPath; + CHECK_ERROR_BREAK(pCurProcess, COMGETTER(ExecutablePath)(strExecPath.asOutParam())); + ProcessStatus_T procStatus; + CHECK_ERROR_BREAK(pCurProcess, COMGETTER(Status)(&procStatus)); + + RTPrintf("\n\t\tProcess #%-03zu PID=%-6RU32 Status=[%s] Command=%ls", + a, uPID, gctlProcessStatusToText(procStatus), strExecPath.raw()); + } while (0); + } + } + + cTotalProcs += collProcesses.size(); + } + + if ( fListAll + || fListFiles) + { + SafeIfaceArray <IGuestFile> collFiles; + CHECK_ERROR_BREAK(pCurSession, COMGETTER(Files)(ComSafeArrayAsOutParam(collFiles))); + for (size_t a = 0; a < collFiles.size(); a++) + { + ComPtr<IGuestFile> pCurFile = collFiles[a]; + if (!pCurFile.isNull()) + { + do + { + ULONG idFile; + CHECK_ERROR_BREAK(pCurFile, COMGETTER(Id)(&idFile)); + Bstr strName; + CHECK_ERROR_BREAK(pCurFile, COMGETTER(Filename)(strName.asOutParam())); + FileStatus_T fileStatus; + CHECK_ERROR_BREAK(pCurFile, COMGETTER(Status)(&fileStatus)); + + RTPrintf("\n\t\tFile #%-03zu ID=%-6RU32 Status=[%s] Name=%ls", + a, idFile, gctlFileStatusToText(fileStatus), strName.raw()); + } while (0); + } + } + + cTotalFiles += collFiles.size(); + } + } + } + + RTPrintf("\n\nTotal guest sessions: %zu\n", collSessions.size()); + if (fListAll || fListProcesses) + RTPrintf("Total guest processes: %zu\n", cTotalProcs); + if (fListAll || fListFiles) + RTPrintf("Total guest files: %zu\n", cTotalFiles); + } + else + RTPrintf("No active guest sessions found\n"); + } + + if (FAILED(rc)) /** @todo yeah, right... Only the last error? */ + rcExit = RTEXITCODE_FAILURE; + + return rcExit; +} + +static DECLCALLBACK(RTEXITCODE) gctlHandleCloseProcess(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + { "--session-id", 'i', RTGETOPT_REQ_UINT32 }, + { "--session-name", 'n', RTGETOPT_REQ_STRING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + std::vector < uint32_t > vecPID; + ULONG idSession = UINT32_MAX; + Utf8Str strSessionName; + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case 'n': /* Session name (or pattern) */ + strSessionName = ValueUnion.psz; + break; + + case 'i': /* Session ID */ + idSession = ValueUnion.u32; + break; + + case VINF_GETOPT_NOT_OPTION: + { + /* Treat every else specified as a PID to kill. */ + uint32_t uPid; + int rc = RTStrToUInt32Ex(ValueUnion.psz, NULL, 0, &uPid); + if ( RT_SUCCESS(rc) + && rc != VWRN_TRAILING_CHARS + && rc != VWRN_NUMBER_TOO_BIG + && rc != VWRN_NEGATIVE_UNSIGNED) + { + if (uPid != 0) + { + try + { + vecPID.push_back(uPid); + } + catch (std::bad_alloc &) + { + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory"); + } + } + else + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS, "Invalid PID value: 0"); + } + else + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS, "Error parsing PID value: %Rrc", rc); + break; + } + + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS, ch, &ValueUnion); + } + } + + if (vecPID.empty()) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS, + "At least one PID must be specified to kill!"); + + if ( strSessionName.isEmpty() + && idSession == UINT32_MAX) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS, "No session ID specified!"); + + if ( strSessionName.isNotEmpty() + && idSession != UINT32_MAX) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSEPROCESS, + "Either session ID or name (pattern) must be specified"); + + RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + HRESULT rc = S_OK; + + ComPtr<IGuestSession> pSession; + ComPtr<IGuestProcess> pProcess; + do + { + uint32_t uProcsTerminated = 0; + + SafeIfaceArray <IGuestSession> collSessions; + CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions))); + size_t cSessions = collSessions.size(); + + uint32_t cSessionsHandled = 0; + for (size_t i = 0; i < cSessions; i++) + { + pSession = collSessions[i]; + Assert(!pSession.isNull()); + + ULONG uID; /* Session ID */ + CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID)); + Bstr strName; + CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam())); + Utf8Str strNameUtf8(strName); /* Session name */ + + bool fSessionFound; + if (strSessionName.isEmpty()) /* Search by ID. Slow lookup. */ + fSessionFound = uID == idSession; + else /* ... or by naming pattern. */ + fSessionFound = RTStrSimplePatternMatch(strSessionName.c_str(), strNameUtf8.c_str()); + if (fSessionFound) + { + AssertStmt(!pSession.isNull(), break); + cSessionsHandled++; + + SafeIfaceArray <IGuestProcess> collProcs; + CHECK_ERROR_BREAK(pSession, COMGETTER(Processes)(ComSafeArrayAsOutParam(collProcs))); + + size_t cProcs = collProcs.size(); + for (size_t p = 0; p < cProcs; p++) + { + pProcess = collProcs[p]; + Assert(!pProcess.isNull()); + + ULONG uPID; /* Process ID */ + CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID)); + + bool fProcFound = false; + for (size_t a = 0; a < vecPID.size(); a++) /* Slow, but works. */ + { + fProcFound = vecPID[a] == uPID; + if (fProcFound) + break; + } + + if (fProcFound) + { + if (pCtx->cVerbose) + RTPrintf("Terminating process (PID %RU32) (session ID %RU32) ...\n", + uPID, uID); + CHECK_ERROR_BREAK(pProcess, Terminate()); + uProcsTerminated++; + } + else + { + if (idSession != UINT32_MAX) + RTPrintf("No matching process(es) for session ID %RU32 found\n", + idSession); + } + + pProcess.setNull(); + } + + pSession.setNull(); + } + } + + if (!cSessionsHandled) + RTPrintf("No matching session(s) found\n"); + + if (uProcsTerminated) + RTPrintf("%RU32 %s terminated\n", + uProcsTerminated, uProcsTerminated == 1 ? "process" : "processes"); + + } while (0); + + pProcess.setNull(); + pSession.setNull(); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +static DECLCALLBACK(RTEXITCODE) gctlHandleCloseSession(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + enum GETOPTDEF_SESSIONCLOSE + { + GETOPTDEF_SESSIONCLOSE_ALL = 2000 + }; + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + { "--all", GETOPTDEF_SESSIONCLOSE_ALL, RTGETOPT_REQ_NOTHING }, + { "--session-id", 'i', RTGETOPT_REQ_UINT32 }, + { "--session-name", 'n', RTGETOPT_REQ_STRING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + ULONG idSession = UINT32_MAX; + Utf8Str strSessionName; + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case 'n': /* Session name pattern */ + strSessionName = ValueUnion.psz; + break; + + case 'i': /* Session ID */ + idSession = ValueUnion.u32; + break; + + case GETOPTDEF_SESSIONCLOSE_ALL: + strSessionName = "*"; + break; + + case VINF_GETOPT_NOT_OPTION: + /** @todo Supply a CSV list of IDs or patterns to close? + * break; */ + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSESESSION, ch, &ValueUnion); + } + } + + if ( strSessionName.isEmpty() + && idSession == UINT32_MAX) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSESESSION, + "No session ID specified!"); + + if ( !strSessionName.isEmpty() + && idSession != UINT32_MAX) + return errorSyntaxEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_CLOSESESSION, + "Either session ID or name (pattern) must be specified"); + + RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + HRESULT rc = S_OK; + + do + { + size_t cSessionsHandled = 0; + + SafeIfaceArray <IGuestSession> collSessions; + CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(Sessions)(ComSafeArrayAsOutParam(collSessions))); + size_t cSessions = collSessions.size(); + + for (size_t i = 0; i < cSessions; i++) + { + ComPtr<IGuestSession> pSession = collSessions[i]; + Assert(!pSession.isNull()); + + ULONG uID; /* Session ID */ + CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID)); + Bstr strName; + CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam())); + Utf8Str strNameUtf8(strName); /* Session name */ + + bool fSessionFound; + if (strSessionName.isEmpty()) /* Search by ID. Slow lookup. */ + fSessionFound = uID == idSession; + else /* ... or by naming pattern. */ + fSessionFound = RTStrSimplePatternMatch(strSessionName.c_str(), strNameUtf8.c_str()); + if (fSessionFound) + { + cSessionsHandled++; + + Assert(!pSession.isNull()); + if (pCtx->cVerbose) + RTPrintf("Closing guest session ID=#%RU32 \"%s\" ...\n", + uID, strNameUtf8.c_str()); + CHECK_ERROR_BREAK(pSession, Close()); + if (pCtx->cVerbose) + RTPrintf("Guest session successfully closed\n"); + + pSession.setNull(); + } + } + + if (!cSessionsHandled) + { + RTPrintf("No guest session(s) found\n"); + rc = E_ABORT; /* To set exit code accordingly. */ + } + + } while (0); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +static DECLCALLBACK(RTEXITCODE) gctlHandleWatch(PGCTLCMDCTX pCtx, int argc, char **argv) +{ + AssertPtrReturn(pCtx, RTEXITCODE_FAILURE); + + /* + * Parse arguments. + */ + static const RTGETOPTDEF s_aOptions[] = + { + GCTLCMD_COMMON_OPTION_DEFS() + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(pCtx, ch, &ValueUnion); + + case VINF_GETOPT_NOT_OPTION: + default: + return errorGetOptEx(USAGE_GUESTCONTROL, USAGE_GSTCTRL_WATCH, ch, &ValueUnion); + } + } + + /** @todo Specify categories to watch for. */ + /** @todo Specify a --timeout for waiting only for a certain amount of time? */ + + RTEXITCODE rcExit = gctlCtxPostOptionParsingInit(pCtx); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + HRESULT rc; + + try + { + ComObjPtr<GuestEventListenerImpl> pGuestListener; + do + { + /* Listener creation. */ + pGuestListener.createObject(); + pGuestListener->init(new GuestEventListener()); + + /* Register for IGuest events. */ + ComPtr<IEventSource> es; + CHECK_ERROR_BREAK(pCtx->pGuest, COMGETTER(EventSource)(es.asOutParam())); + com::SafeArray<VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnGuestSessionRegistered); + /** @todo Also register for VBoxEventType_OnGuestUserStateChanged on demand? */ + CHECK_ERROR_BREAK(es, RegisterListener(pGuestListener, ComSafeArrayAsInParam(eventTypes), + true /* Active listener */)); + /* Note: All other guest control events have to be registered + * as their corresponding objects appear. */ + + } while (0); + + if (pCtx->cVerbose) + RTPrintf("Waiting for events ...\n"); + +/** @todo r=bird: This are-we-there-yet approach to things could easily be + * replaced by a global event semaphore that gets signalled from the + * signal handler and the callback event. Please fix! */ + while (!g_fGuestCtrlCanceled) + { + /** @todo Timeout handling (see above)? */ + RTThreadSleep(10); + } + + if (pCtx->cVerbose) + RTPrintf("Signal caught, exiting ...\n"); + + if (!pGuestListener.isNull()) + { + /* Guest callback unregistration. */ + ComPtr<IEventSource> pES; + CHECK_ERROR(pCtx->pGuest, COMGETTER(EventSource)(pES.asOutParam())); + if (!pES.isNull()) + CHECK_ERROR(pES, UnregisterListener(pGuestListener)); + pGuestListener.setNull(); + } + } + catch (std::bad_alloc &) + { + rc = E_OUTOFMEMORY; + } + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +/** + * Access the guest control store. + * + * @returns program exit code. + * @note see the command line API description for parameters + */ +RTEXITCODE handleGuestControl(HandlerArg *pArg) +{ + AssertPtr(pArg); + +#ifdef DEBUG_andy_disabled + if (RT_FAILURE(tstTranslatePath())) + return RTEXITCODE_FAILURE; +#endif + + /* + * Command definitions. + */ + static const GCTLCMDDEF s_aCmdDefs[] = + { + { "run", gctlHandleRun, USAGE_GSTCTRL_RUN, 0, }, + { "start", gctlHandleStart, USAGE_GSTCTRL_START, 0, }, + { "copyfrom", gctlHandleCopyFrom, USAGE_GSTCTRL_COPYFROM, 0, }, + { "copyto", gctlHandleCopyTo, USAGE_GSTCTRL_COPYTO, 0, }, + + { "mkdir", gctrlHandleMkDir, USAGE_GSTCTRL_MKDIR, 0, }, + { "md", gctrlHandleMkDir, USAGE_GSTCTRL_MKDIR, 0, }, + { "createdirectory", gctrlHandleMkDir, USAGE_GSTCTRL_MKDIR, 0, }, + { "createdir", gctrlHandleMkDir, USAGE_GSTCTRL_MKDIR, 0, }, + + { "rmdir", gctlHandleRmDir, USAGE_GSTCTRL_RMDIR, 0, }, + { "removedir", gctlHandleRmDir, USAGE_GSTCTRL_RMDIR, 0, }, + { "removedirectory", gctlHandleRmDir, USAGE_GSTCTRL_RMDIR, 0, }, + + { "rm", gctlHandleRm, USAGE_GSTCTRL_RM, 0, }, + { "removefile", gctlHandleRm, USAGE_GSTCTRL_RM, 0, }, + { "erase", gctlHandleRm, USAGE_GSTCTRL_RM, 0, }, + { "del", gctlHandleRm, USAGE_GSTCTRL_RM, 0, }, + { "delete", gctlHandleRm, USAGE_GSTCTRL_RM, 0, }, + + { "mv", gctlHandleMv, USAGE_GSTCTRL_MV, 0, }, + { "move", gctlHandleMv, USAGE_GSTCTRL_MV, 0, }, + { "ren", gctlHandleMv, USAGE_GSTCTRL_MV, 0, }, + { "rename", gctlHandleMv, USAGE_GSTCTRL_MV, 0, }, + + { "mktemp", gctlHandleMkTemp, USAGE_GSTCTRL_MKTEMP, 0, }, + { "createtemp", gctlHandleMkTemp, USAGE_GSTCTRL_MKTEMP, 0, }, + { "createtemporary", gctlHandleMkTemp, USAGE_GSTCTRL_MKTEMP, 0, }, + + { "stat", gctlHandleStat, USAGE_GSTCTRL_STAT, 0, }, + + { "closeprocess", gctlHandleCloseProcess, USAGE_GSTCTRL_CLOSEPROCESS, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, }, + { "closesession", gctlHandleCloseSession, USAGE_GSTCTRL_CLOSESESSION, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, }, + { "list", gctlHandleList, USAGE_GSTCTRL_LIST, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, }, + { "watch", gctlHandleWatch, USAGE_GSTCTRL_WATCH, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, }, + + {"updateguestadditions",gctlHandleUpdateAdditions, USAGE_GSTCTRL_UPDATEGA, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, }, + { "updateadditions", gctlHandleUpdateAdditions, USAGE_GSTCTRL_UPDATEGA, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, }, + { "updatega", gctlHandleUpdateAdditions, USAGE_GSTCTRL_UPDATEGA, GCTLCMDCTX_F_SESSION_ANONYMOUS | GCTLCMDCTX_F_NO_SIGNAL_HANDLER, }, + }; + + /* + * VBoxManage guestcontrol [common-options] <VM> [common-options] <sub-command> ... + * + * Parse common options and VM name until we find a sub-command. Allowing + * the user to put the user and password related options before the + * sub-command makes it easier to edit the command line when doing several + * operations with the same guest user account. (Accidentally, it also + * makes the syntax diagram shorter and easier to read.) + */ + GCTLCMDCTX CmdCtx; + RTEXITCODE rcExit = gctrCmdCtxInit(&CmdCtx, pArg); + if (rcExit == RTEXITCODE_SUCCESS) + { + static const RTGETOPTDEF s_CommonOptions[] = { GCTLCMD_COMMON_OPTION_DEFS() }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_CommonOptions, RT_ELEMENTS(s_CommonOptions), 0, 0 /* No sorting! */); + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (ch) + { + GCTLCMD_COMMON_OPTION_CASES(&CmdCtx, ch, &ValueUnion); + + case VINF_GETOPT_NOT_OPTION: + /* First comes the VM name or UUID. */ + if (!CmdCtx.pszVmNameOrUuid) + CmdCtx.pszVmNameOrUuid = ValueUnion.psz; + /* + * The sub-command is next. Look it up and invoke it. + * Note! Currently no warnings about user/password options (like we'll do later on) + * for GCTLCMDCTX_F_SESSION_ANONYMOUS commands. No reason to be too pedantic. + */ + else + { + const char *pszCmd = ValueUnion.psz; + uint32_t iCmd; + for (iCmd = 0; iCmd < RT_ELEMENTS(s_aCmdDefs); iCmd++) + if (strcmp(s_aCmdDefs[iCmd].pszName, pszCmd) == 0) + { + CmdCtx.pCmdDef = &s_aCmdDefs[iCmd]; + + rcExit = s_aCmdDefs[iCmd].pfnHandler(&CmdCtx, pArg->argc - GetState.iNext + 1, + &pArg->argv[GetState.iNext - 1]); + + gctlCtxTerm(&CmdCtx); + return rcExit; + } + return errorSyntax(USAGE_GUESTCONTROL, "Unknown sub-command: '%s'", pszCmd); + } + break; + + default: + return errorGetOpt(USAGE_GUESTCONTROL, ch, &ValueUnion); + } + } + if (CmdCtx.pszVmNameOrUuid) + rcExit = errorSyntax(USAGE_GUESTCONTROL, "Missing sub-command"); + else + rcExit = errorSyntax(USAGE_GUESTCONTROL, "Missing VM name and sub-command"); + } + return rcExit; +} +#endif /* !VBOX_ONLY_DOCS */ + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.h b/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.h new file mode 100644 index 00000000..016fa548 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.h @@ -0,0 +1,236 @@ +/* $Id: VBoxManageGuestCtrl.h $ */ +/** @file + * VBoxManageGuestCtrl.h - Definitions for guest control. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxManage_VBoxManageGuestCtrl_h +#define VBOX_INCLUDED_SRC_VBoxManage_VBoxManageGuestCtrl_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifndef VBOX_ONLY_DOCS + +#include <VBox/com/com.h> +#include <VBox/com/listeners.h> +#include <VBox/com/VirtualBox.h> + +#include <iprt/time.h> + +#include <map> + +const char *gctlFileStatusToText(FileStatus_T enmStatus); +const char *gctlProcessStatusToText(ProcessStatus_T enmStatus); +const char *gctlGuestSessionStatusToText(GuestSessionStatus_T enmStatus); + +using namespace com; + +class GuestFileEventListener; +typedef ListenerImpl<GuestFileEventListener> GuestFileEventListenerImpl; + +class GuestProcessEventListener; +typedef ListenerImpl<GuestProcessEventListener> GuestProcessEventListenerImpl; + +class GuestSessionEventListener; +typedef ListenerImpl<GuestSessionEventListener> GuestSessionEventListenerImpl; + +class GuestEventListener; +typedef ListenerImpl<GuestEventListener> GuestEventListenerImpl; + +/** Simple statistics class for binding locally + * held data to a specific guest object. */ +class GuestEventStats +{ + +public: + + GuestEventStats(void) + : uLastUpdatedMS(RTTimeMilliTS()) + { + } + + /** @todo Make this more a class than a structure. */ +public: + + uint64_t uLastUpdatedMS; +}; + +class GuestFileStats : public GuestEventStats +{ + +public: + + GuestFileStats(void) { } + + GuestFileStats(ComObjPtr<GuestFileEventListenerImpl> pListenerImpl) + : mListener(pListenerImpl) + { + } + +public: /** @todo */ + + ComObjPtr<GuestFileEventListenerImpl> mListener; +}; + +class GuestProcStats : public GuestEventStats +{ + +public: + + GuestProcStats(void) { } + + GuestProcStats(ComObjPtr<GuestProcessEventListenerImpl> pListenerImpl) + : mListener(pListenerImpl) + { + } + +public: /** @todo */ + + ComObjPtr<GuestProcessEventListenerImpl> mListener; +}; + +class GuestSessionStats : public GuestEventStats +{ + +public: + + GuestSessionStats(void) { } + + GuestSessionStats(ComObjPtr<GuestSessionEventListenerImpl> pListenerImpl) + : mListener(pListenerImpl) + { + } + +public: /** @todo */ + + ComObjPtr<GuestSessionEventListenerImpl> mListener; +}; + +/** Map containing all watched guest files. */ +typedef std::map< ComPtr<IGuestFile>, GuestFileStats > GuestEventFiles; +/** Map containing all watched guest processes. */ +typedef std::map< ComPtr<IGuestProcess>, GuestProcStats > GuestEventProcs; +/** Map containing all watched guest sessions. */ +typedef std::map< ComPtr<IGuestSession>, GuestSessionStats > GuestEventSessions; + +class GuestListenerBase +{ +public: + + GuestListenerBase(void); + + virtual ~GuestListenerBase(void); + +public: + + HRESULT init(bool fVerbose = false); + +protected: + + /** Verbose flag. */ + bool mfVerbose; +}; + +/** + * Handler for guest process events. + */ +class GuestFileEventListener : public GuestListenerBase +{ +public: + + GuestFileEventListener(void); + + virtual ~GuestFileEventListener(void); + +public: + + void uninit(void); + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent); + +protected: + +}; + +/** + * Handler for guest process events. + */ +class GuestProcessEventListener : public GuestListenerBase +{ +public: + + GuestProcessEventListener(void); + + virtual ~GuestProcessEventListener(void); + +public: + + void uninit(void); + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent); + +protected: + +}; + +/** + * Handler for guest session events. + */ +class GuestSessionEventListener : public GuestListenerBase +{ +public: + + GuestSessionEventListener(void); + + virtual ~GuestSessionEventListener(void); + +public: + + void uninit(void); + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent); + +protected: + + GuestEventFiles mFiles; + GuestEventProcs mProcs; +}; + +/** + * Handler for guest events. + */ +class GuestEventListener : public GuestListenerBase +{ + +public: + + GuestEventListener(void); + + virtual ~GuestEventListener(void); + +public: + + void uninit(void); + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent); + +protected: + + GuestEventSessions mSessions; +}; +#endif /* !VBOX_ONLY_DOCS */ + +#endif /* !VBOX_INCLUDED_SRC_VBoxManage_VBoxManageGuestCtrl_h */ + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrlListener.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrlListener.cpp new file mode 100644 index 00000000..20603474 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrlListener.cpp @@ -0,0 +1,518 @@ +/* $Id: VBoxManageGuestCtrlListener.cpp $ */ +/** @file + * VBoxManage - Guest control listener implementations. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxManage.h" +#include "VBoxManageGuestCtrl.h" + +#ifndef VBOX_ONLY_DOCS + +#include <VBox/com/com.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> + +#include <iprt/time.h> + +#include <map> +#include <vector> + + + +/* + * GuestListenerBase + * GuestListenerBase + * GuestListenerBase + */ + +GuestListenerBase::GuestListenerBase(void) + : mfVerbose(false) +{ +} + +GuestListenerBase::~GuestListenerBase(void) +{ +} + +HRESULT GuestListenerBase::init(bool fVerbose) +{ + mfVerbose = fVerbose; + return S_OK; +} + + + +/* + * GuestFileEventListener + * GuestFileEventListener + * GuestFileEventListener + */ + +GuestFileEventListener::GuestFileEventListener(void) +{ +} + +GuestFileEventListener::~GuestFileEventListener(void) +{ +} + +void GuestFileEventListener::uninit(void) +{ + +} + +STDMETHODIMP GuestFileEventListener::HandleEvent(VBoxEventType_T aType, IEvent *aEvent) +{ + switch (aType) + { + case VBoxEventType_OnGuestFileStateChanged: + { + HRESULT rc; + do + { + ComPtr<IGuestFileStateChangedEvent> pEvent = aEvent; + Assert(!pEvent.isNull()); + + ComPtr<IGuestFile> pProcess; + CHECK_ERROR_BREAK(pEvent, COMGETTER(File)(pProcess.asOutParam())); + AssertBreak(!pProcess.isNull()); + FileStatus_T fileSts; + CHECK_ERROR_BREAK(pEvent, COMGETTER(Status)(&fileSts)); + Bstr strPath; + CHECK_ERROR_BREAK(pProcess, COMGETTER(Filename)(strPath.asOutParam())); + ULONG uID; + CHECK_ERROR_BREAK(pProcess, COMGETTER(Id)(&uID)); + + RTPrintf("File ID=%RU32 \"%s\" changed status to [%s]\n", + uID, Utf8Str(strPath).c_str(), gctlFileStatusToText(fileSts)); + + } while (0); + break; + } + + default: + AssertFailed(); + } + + return S_OK; +} + + +/* + * GuestProcessEventListener + * GuestProcessEventListener + * GuestProcessEventListener + */ + +GuestProcessEventListener::GuestProcessEventListener(void) +{ +} + +GuestProcessEventListener::~GuestProcessEventListener(void) +{ +} + +void GuestProcessEventListener::uninit(void) +{ + +} + +STDMETHODIMP GuestProcessEventListener::HandleEvent(VBoxEventType_T aType, IEvent *aEvent) +{ + switch (aType) + { + case VBoxEventType_OnGuestProcessStateChanged: + { + HRESULT rc; + do + { + ComPtr<IGuestProcessStateChangedEvent> pEvent = aEvent; + Assert(!pEvent.isNull()); + + ComPtr<IGuestProcess> pProcess; + CHECK_ERROR_BREAK(pEvent, COMGETTER(Process)(pProcess.asOutParam())); + AssertBreak(!pProcess.isNull()); + ProcessStatus_T procSts; + CHECK_ERROR_BREAK(pEvent, COMGETTER(Status)(&procSts)); + Bstr strPath; + CHECK_ERROR_BREAK(pProcess, COMGETTER(ExecutablePath)(strPath.asOutParam())); + ULONG uPID; + CHECK_ERROR_BREAK(pProcess, COMGETTER(PID)(&uPID)); + + RTPrintf("Process PID=%RU32 \"%s\" changed status to [%s]\n", + uPID, Utf8Str(strPath).c_str(), gctlProcessStatusToText(procSts)); + + } while (0); + break; + } + + default: + AssertFailed(); + } + + return S_OK; +} + + +/* + * GuestSessionEventListener + * GuestSessionEventListener + * GuestSessionEventListener + */ + +GuestSessionEventListener::GuestSessionEventListener(void) +{ +} + +GuestSessionEventListener::~GuestSessionEventListener(void) +{ +} + +void GuestSessionEventListener::uninit(void) +{ + GuestEventProcs::iterator itProc = mProcs.begin(); + while (itProc != mProcs.end()) + { + if (!itProc->first.isNull()) + { + HRESULT rc; + do + { + /* Listener unregistration. */ + ComPtr<IEventSource> pES; + CHECK_ERROR_BREAK(itProc->first, COMGETTER(EventSource)(pES.asOutParam())); + if (!pES.isNull()) + CHECK_ERROR_BREAK(pES, UnregisterListener(itProc->second.mListener)); + } while (0); + itProc->first->Release(); + } + + ++itProc; + } + mProcs.clear(); + + GuestEventFiles::iterator itFile = mFiles.begin(); + while (itFile != mFiles.end()) + { + if (!itFile->first.isNull()) + { + HRESULT rc; + do + { + /* Listener unregistration. */ + ComPtr<IEventSource> pES; + CHECK_ERROR_BREAK(itFile->first, COMGETTER(EventSource)(pES.asOutParam())); + if (!pES.isNull()) + CHECK_ERROR_BREAK(pES, UnregisterListener(itFile->second.mListener)); + } while (0); + itFile->first->Release(); + } + + ++itFile; + } + mFiles.clear(); +} + +STDMETHODIMP GuestSessionEventListener::HandleEvent(VBoxEventType_T aType, IEvent *aEvent) +{ + switch (aType) + { + case VBoxEventType_OnGuestFileRegistered: + { + HRESULT rc; + do + { + ComPtr<IGuestFileRegisteredEvent> pEvent = aEvent; + Assert(!pEvent.isNull()); + + ComPtr<IGuestFile> pFile; + CHECK_ERROR_BREAK(pEvent, COMGETTER(File)(pFile.asOutParam())); + AssertBreak(!pFile.isNull()); + BOOL fRegistered; + CHECK_ERROR_BREAK(pEvent, COMGETTER(Registered)(&fRegistered)); + Bstr strPath; + CHECK_ERROR_BREAK(pFile, COMGETTER(Filename)(strPath.asOutParam())); + + RTPrintf("File \"%s\" %s\n", + Utf8Str(strPath).c_str(), + fRegistered ? "registered" : "unregistered"); + if (fRegistered) + { + if (mfVerbose) + RTPrintf("Registering ...\n"); + + /* Register for IGuestFile events. */ + ComObjPtr<GuestFileEventListenerImpl> pListener; + pListener.createObject(); + CHECK_ERROR_BREAK(pListener, init(new GuestFileEventListener())); + + ComPtr<IEventSource> es; + CHECK_ERROR_BREAK(pFile, COMGETTER(EventSource)(es.asOutParam())); + com::SafeArray<VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); + CHECK_ERROR_BREAK(es, RegisterListener(pListener, ComSafeArrayAsInParam(eventTypes), + true /* Active listener */)); + + GuestFileStats fileStats(pListener); + mFiles[pFile] = fileStats; + } + else + { + GuestEventFiles::iterator itFile = mFiles.find(pFile); + if (itFile != mFiles.end()) + { + if (mfVerbose) + RTPrintf("Unregistering file ...\n"); + + if (!itFile->first.isNull()) + { + /* Listener unregistration. */ + ComPtr<IEventSource> pES; + CHECK_ERROR(itFile->first, COMGETTER(EventSource)(pES.asOutParam())); + if (!pES.isNull()) + CHECK_ERROR(pES, UnregisterListener(itFile->second.mListener)); + itFile->first->Release(); + } + + mFiles.erase(itFile); + } + } + + } while (0); + break; + } + + case VBoxEventType_OnGuestProcessRegistered: + { + HRESULT rc; + do + { + ComPtr<IGuestProcessRegisteredEvent> pEvent = aEvent; + Assert(!pEvent.isNull()); + + ComPtr<IGuestProcess> pProcess; + CHECK_ERROR_BREAK(pEvent, COMGETTER(Process)(pProcess.asOutParam())); + AssertBreak(!pProcess.isNull()); + BOOL fRegistered; + CHECK_ERROR_BREAK(pEvent, COMGETTER(Registered)(&fRegistered)); + Bstr strPath; + CHECK_ERROR_BREAK(pProcess, COMGETTER(ExecutablePath)(strPath.asOutParam())); + + RTPrintf("Process \"%s\" %s\n", + Utf8Str(strPath).c_str(), + fRegistered ? "registered" : "unregistered"); + if (fRegistered) + { + if (mfVerbose) + RTPrintf("Registering ...\n"); + + /* Register for IGuestProcess events. */ + ComObjPtr<GuestProcessEventListenerImpl> pListener; + pListener.createObject(); + CHECK_ERROR_BREAK(pListener, init(new GuestProcessEventListener())); + + ComPtr<IEventSource> es; + CHECK_ERROR_BREAK(pProcess, COMGETTER(EventSource)(es.asOutParam())); + com::SafeArray<VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged); + CHECK_ERROR_BREAK(es, RegisterListener(pListener, ComSafeArrayAsInParam(eventTypes), + true /* Active listener */)); + + GuestProcStats procStats(pListener); + mProcs[pProcess] = procStats; + } + else + { + GuestEventProcs::iterator itProc = mProcs.find(pProcess); + if (itProc != mProcs.end()) + { + if (mfVerbose) + RTPrintf("Unregistering process ...\n"); + + if (!itProc->first.isNull()) + { + /* Listener unregistration. */ + ComPtr<IEventSource> pES; + CHECK_ERROR(itProc->first, COMGETTER(EventSource)(pES.asOutParam())); + if (!pES.isNull()) + CHECK_ERROR(pES, UnregisterListener(itProc->second.mListener)); + itProc->first->Release(); + } + + mProcs.erase(itProc); + } + } + + } while (0); + break; + } + + case VBoxEventType_OnGuestSessionStateChanged: + { + HRESULT rc; + do + { + ComPtr<IGuestSessionStateChangedEvent> pEvent = aEvent; + Assert(!pEvent.isNull()); + ComPtr<IGuestSession> pSession; + CHECK_ERROR_BREAK(pEvent, COMGETTER(Session)(pSession.asOutParam())); + AssertBreak(!pSession.isNull()); + + GuestSessionStatus_T sessSts; + CHECK_ERROR_BREAK(pSession, COMGETTER(Status)(&sessSts)); + ULONG uID; + CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID)); + Bstr strName; + CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam())); + + RTPrintf("Session ID=%RU32 \"%s\" changed status to [%s]\n", + uID, Utf8Str(strName).c_str(), gctlGuestSessionStatusToText(sessSts)); + + } while (0); + break; + } + + default: + AssertFailed(); + } + + return S_OK; +} + + +/* + * GuestEventListener + * GuestEventListener + * GuestEventListener + */ + +GuestEventListener::GuestEventListener(void) +{ +} + +GuestEventListener::~GuestEventListener(void) +{ +} + +void GuestEventListener::uninit(void) +{ + GuestEventSessions::iterator itSession = mSessions.begin(); + while (itSession != mSessions.end()) + { + if (!itSession->first.isNull()) + { + HRESULT rc; + do + { + /* Listener unregistration. */ + ComPtr<IEventSource> pES; + CHECK_ERROR_BREAK(itSession->first, COMGETTER(EventSource)(pES.asOutParam())); + if (!pES.isNull()) + CHECK_ERROR_BREAK(pES, UnregisterListener(itSession->second.mListener)); + + } while (0); + itSession->first->Release(); + } + + ++itSession; + } + mSessions.clear(); +} + +STDMETHODIMP GuestEventListener::HandleEvent(VBoxEventType_T aType, IEvent *aEvent) +{ + switch (aType) + { + case VBoxEventType_OnGuestSessionRegistered: + { + HRESULT rc; + do + { + ComPtr<IGuestSessionRegisteredEvent> pEvent = aEvent; + Assert(!pEvent.isNull()); + + ComPtr<IGuestSession> pSession; + CHECK_ERROR_BREAK(pEvent, COMGETTER(Session)(pSession.asOutParam())); + AssertBreak(!pSession.isNull()); + BOOL fRegistered; + CHECK_ERROR_BREAK(pEvent, COMGETTER(Registered)(&fRegistered)); + Bstr strName; + CHECK_ERROR_BREAK(pSession, COMGETTER(Name)(strName.asOutParam())); + ULONG uID; + CHECK_ERROR_BREAK(pSession, COMGETTER(Id)(&uID)); + + RTPrintf("Session ID=%RU32 \"%s\" %s\n", + uID, Utf8Str(strName).c_str(), + fRegistered ? "registered" : "unregistered"); + if (fRegistered) + { + if (mfVerbose) + RTPrintf("Registering ...\n"); + + /* Register for IGuestSession events. */ + ComObjPtr<GuestSessionEventListenerImpl> pListener; + pListener.createObject(); + CHECK_ERROR_BREAK(pListener, init(new GuestSessionEventListener())); + + ComPtr<IEventSource> es; + CHECK_ERROR_BREAK(pSession, COMGETTER(EventSource)(es.asOutParam())); + com::SafeArray<VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnGuestFileRegistered); + eventTypes.push_back(VBoxEventType_OnGuestProcessRegistered); + CHECK_ERROR_BREAK(es, RegisterListener(pListener, ComSafeArrayAsInParam(eventTypes), + true /* Active listener */)); + + GuestSessionStats sessionStats(pListener); + mSessions[pSession] = sessionStats; + } + else + { + GuestEventSessions::iterator itSession = mSessions.find(pSession); + if (itSession != mSessions.end()) + { + if (mfVerbose) + RTPrintf("Unregistering ...\n"); + + if (!itSession->first.isNull()) + { + /* Listener unregistration. */ + ComPtr<IEventSource> pES; + CHECK_ERROR_BREAK(itSession->first, COMGETTER(EventSource)(pES.asOutParam())); + if (!pES.isNull()) + CHECK_ERROR_BREAK(pES, UnregisterListener(itSession->second.mListener)); + itSession->first->Release(); + } + + mSessions.erase(itSession); + } + } + + } while (0); + break; + } + + default: + AssertFailed(); + } + + return S_OK; +} + +#endif /* !VBOX_ONLY_DOCS */ + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageGuestProp.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageGuestProp.cpp new file mode 100644 index 00000000..36ccc472 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageGuestProp.cpp @@ -0,0 +1,437 @@ +/* $Id: VBoxManageGuestProp.cpp $ */ +/** @file + * VBoxManage - Implementation of guestproperty command. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxManage.h" + +#ifndef VBOX_ONLY_DOCS + +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> + +#include <VBox/log.h> +#include <iprt/asm.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include <iprt/thread.h> + +#ifdef USE_XPCOM_QUEUE +# include <sys/select.h> +# include <errno.h> +#endif + +#ifdef RT_OS_DARWIN +# include <CoreFoundation/CFRunLoop.h> +#endif + +using namespace com; + +#endif /* !VBOX_ONLY_DOCS */ + +void usageGuestProperty(PRTSTREAM pStrm, const char *pcszSep1, const char *pcszSep2) +{ + RTStrmPrintf(pStrm, + "%s guestproperty %s get <uuid|vmname>\n" + " <property> [--verbose]\n" + "\n", pcszSep1, pcszSep2); + RTStrmPrintf(pStrm, + "%s guestproperty %s set <uuid|vmname>\n" + " <property> [<value> [--flags <flags>]]\n" + "\n", pcszSep1, pcszSep2); + RTStrmPrintf(pStrm, + "%s guestproperty %s delete|unset <uuid|vmname>\n" + " <property>\n" + "\n", pcszSep1, pcszSep2); + RTStrmPrintf(pStrm, + "%s guestproperty %s enumerate <uuid|vmname>\n" + " [--patterns <patterns>]\n" + "\n", pcszSep1, pcszSep2); + RTStrmPrintf(pStrm, + "%s guestproperty %s wait <uuid|vmname> <patterns>\n" + " [--timeout <msec>] [--fail-on-timeout]\n" + "\n", pcszSep1, pcszSep2); +} + +#ifndef VBOX_ONLY_DOCS + +static RTEXITCODE handleGetGuestProperty(HandlerArg *a) +{ + HRESULT rc = S_OK; + + bool verbose = false; + if ( a->argc == 3 + && ( !strcmp(a->argv[2], "--verbose") + || !strcmp(a->argv[2], "-verbose"))) + verbose = true; + else if (a->argc != 2) + return errorSyntax(USAGE_GUESTPROPERTY, "Incorrect parameters"); + + ComPtr<IMachine> machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (machine) + { + /* open a session for the VM - new or existing */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + + /* get the mutable session machine */ + a->session->COMGETTER(Machine)(machine.asOutParam()); + + Bstr value; + LONG64 i64Timestamp; + Bstr flags; + CHECK_ERROR(machine, GetGuestProperty(Bstr(a->argv[1]).raw(), + value.asOutParam(), + &i64Timestamp, flags.asOutParam())); + if (value.isEmpty()) + RTPrintf("No value set!\n"); + else + RTPrintf("Value: %ls\n", value.raw()); + if (!value.isEmpty() && verbose) + { + RTPrintf("Timestamp: %lld\n", i64Timestamp); + RTPrintf("Flags: %ls\n", flags.raw()); + } + } + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static RTEXITCODE handleSetGuestProperty(HandlerArg *a) +{ + HRESULT rc = S_OK; + + /* + * Check the syntax. We can deduce the correct syntax from the number of + * arguments. + */ + bool usageOK = true; + const char *pszName = NULL; + const char *pszValue = NULL; + const char *pszFlags = NULL; + if (a->argc == 3) + pszValue = a->argv[2]; + else if (a->argc == 4) + usageOK = false; + else if (a->argc == 5) + { + pszValue = a->argv[2]; + if ( strcmp(a->argv[3], "--flags") + && strcmp(a->argv[3], "-flags")) + usageOK = false; + pszFlags = a->argv[4]; + } + else if (a->argc != 2) + usageOK = false; + if (!usageOK) + return errorSyntax(USAGE_GUESTPROPERTY, "Incorrect parameters"); + /* This is always needed. */ + pszName = a->argv[1]; + + ComPtr<IMachine> machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (machine) + { + /* open a session for the VM - new or existing */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + + /* get the mutable session machine */ + a->session->COMGETTER(Machine)(machine.asOutParam()); + + if (!pszFlags) + CHECK_ERROR(machine, SetGuestPropertyValue(Bstr(pszName).raw(), + Bstr(pszValue).raw())); + else + CHECK_ERROR(machine, SetGuestProperty(Bstr(pszName).raw(), + Bstr(pszValue).raw(), + Bstr(pszFlags).raw())); + + if (SUCCEEDED(rc)) + CHECK_ERROR(machine, SaveSettings()); + + a->session->UnlockMachine(); + } + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static RTEXITCODE handleDeleteGuestProperty(HandlerArg *a) +{ + HRESULT rc = S_OK; + + /* + * Check the syntax. We can deduce the correct syntax from the number of + * arguments. + */ + bool usageOK = true; + const char *pszName = NULL; + if (a->argc != 2) + usageOK = false; + if (!usageOK) + return errorSyntax(USAGE_GUESTPROPERTY, "Incorrect parameters"); + /* This is always needed. */ + pszName = a->argv[1]; + + ComPtr<IMachine> machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (machine) + { + /* open a session for the VM - new or existing */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + + /* get the mutable session machine */ + a->session->COMGETTER(Machine)(machine.asOutParam()); + + CHECK_ERROR(machine, DeleteGuestProperty(Bstr(pszName).raw())); + + if (SUCCEEDED(rc)) + CHECK_ERROR(machine, SaveSettings()); + + a->session->UnlockMachine(); + } + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +/** + * Enumerates the properties in the guest property store. + * + * @returns 0 on success, 1 on failure + * @note see the command line API description for parameters + */ +static RTEXITCODE handleEnumGuestProperty(HandlerArg *a) +{ + /* + * Check the syntax. We can deduce the correct syntax from the number of + * arguments. + */ + if ( a->argc < 1 + || a->argc == 2 + || ( a->argc > 3 + && strcmp(a->argv[1], "--patterns") + && strcmp(a->argv[1], "-patterns"))) + return errorSyntax(USAGE_GUESTPROPERTY, "Incorrect parameters"); + + /* + * Pack the patterns + */ + Utf8Str Utf8Patterns(a->argc > 2 ? a->argv[2] : ""); + for (int i = 3; i < a->argc; ++i) + Utf8Patterns = Utf8StrFmt ("%s,%s", Utf8Patterns.c_str(), a->argv[i]); + + /* + * Make the actual call to Main. + */ + ComPtr<IMachine> machine; + HRESULT rc; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (machine) + { + /* open a session for the VM - new or existing */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + + /* get the mutable session machine */ + a->session->COMGETTER(Machine)(machine.asOutParam()); + + com::SafeArray<BSTR> names; + com::SafeArray<BSTR> values; + com::SafeArray<LONG64> timestamps; + com::SafeArray<BSTR> flags; + CHECK_ERROR(machine, EnumerateGuestProperties(Bstr(Utf8Patterns).raw(), + ComSafeArrayAsOutParam(names), + ComSafeArrayAsOutParam(values), + ComSafeArrayAsOutParam(timestamps), + ComSafeArrayAsOutParam(flags))); + if (SUCCEEDED(rc)) + { + if (names.size() == 0) + RTPrintf("No properties found.\n"); + for (unsigned i = 0; i < names.size(); ++i) + RTPrintf("Name: %ls, value: %ls, timestamp: %lld, flags: %ls\n", + names[i], values[i], timestamps[i], flags[i]); + } + } + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +/** + * Enumerates the properties in the guest property store. + * + * @returns 0 on success, 1 on failure + * @note see the command line API description for parameters + */ +static RTEXITCODE handleWaitGuestProperty(HandlerArg *a) +{ + /* + * Handle arguments + */ + bool fFailOnTimeout = false; + const char *pszPatterns = NULL; + uint32_t cMsTimeout = RT_INDEFINITE_WAIT; + bool usageOK = true; + if (a->argc < 2) + usageOK = false; + else + pszPatterns = a->argv[1]; + ComPtr<IMachine> machine; + HRESULT rc; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (!machine) + usageOK = false; + for (int i = 2; usageOK && i < a->argc; ++i) + { + if ( !strcmp(a->argv[i], "--timeout") + || !strcmp(a->argv[i], "-timeout")) + { + if ( i + 1 >= a->argc + || RTStrToUInt32Full(a->argv[i + 1], 10, &cMsTimeout) != VINF_SUCCESS) + usageOK = false; + else + ++i; + } + else if (!strcmp(a->argv[i], "--fail-on-timeout")) + fFailOnTimeout = true; + else + usageOK = false; + } + if (!usageOK) + return errorSyntax(USAGE_GUESTPROPERTY, "Incorrect parameters"); + + /* + * Set up the event listener and wait until found match or timeout. + */ + Bstr aMachStrGuid; + machine->COMGETTER(Id)(aMachStrGuid.asOutParam()); + Guid aMachGuid(aMachStrGuid); + ComPtr<IEventSource> es; + CHECK_ERROR(a->virtualBox, COMGETTER(EventSource)(es.asOutParam())); + ComPtr<IEventListener> listener; + CHECK_ERROR(es, CreateListener(listener.asOutParam())); + com::SafeArray <VBoxEventType_T> eventTypes(1); + eventTypes.push_back(VBoxEventType_OnGuestPropertyChanged); + CHECK_ERROR(es, RegisterListener(listener, ComSafeArrayAsInParam(eventTypes), false)); + + uint64_t u64Started = RTTimeMilliTS(); + bool fSignalled = false; + do + { + unsigned cMsWait; + if (cMsTimeout == RT_INDEFINITE_WAIT) + cMsWait = 1000; + else + { + uint64_t cMsElapsed = RTTimeMilliTS() - u64Started; + if (cMsElapsed >= cMsTimeout) + break; /* timed out */ + cMsWait = RT_MIN(1000, cMsTimeout - (uint32_t)cMsElapsed); + } + + ComPtr<IEvent> ev; + rc = es->GetEvent(listener, cMsWait, ev.asOutParam()); + if (ev) + { + VBoxEventType_T aType; + rc = ev->COMGETTER(Type)(&aType); + switch (aType) + { + case VBoxEventType_OnGuestPropertyChanged: + { + ComPtr<IGuestPropertyChangedEvent> gpcev = ev; + Assert(gpcev); + Bstr aNextStrGuid; + gpcev->COMGETTER(MachineId)(aNextStrGuid.asOutParam()); + if (aMachGuid != Guid(aNextStrGuid)) + continue; + Bstr aNextName; + gpcev->COMGETTER(Name)(aNextName.asOutParam()); + if (RTStrSimplePatternMultiMatch(pszPatterns, RTSTR_MAX, + Utf8Str(aNextName).c_str(), RTSTR_MAX, NULL)) + { + Bstr aNextValue, aNextFlags; + gpcev->COMGETTER(Value)(aNextValue.asOutParam()); + gpcev->COMGETTER(Flags)(aNextFlags.asOutParam()); + RTPrintf("Name: %ls, value: %ls, flags: %ls\n", + aNextName.raw(), aNextValue.raw(), aNextFlags.raw()); + fSignalled = true; + } + break; + } + default: + AssertFailed(); + } + } + } while (!fSignalled); + + es->UnregisterListener(listener); + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + if (!fSignalled) + { + RTMsgError("Time out or interruption while waiting for a notification."); + if (fFailOnTimeout) + /* Hysterical rasins: We always returned 2 here, which now translates to syntax error... Which is bad. */ + rcExit = RTEXITCODE_SYNTAX; + } + return rcExit; +} + +/** + * Access the guest property store. + * + * @returns 0 on success, 1 on failure + * @note see the command line API description for parameters + */ +RTEXITCODE handleGuestProperty(HandlerArg *a) +{ + HandlerArg arg = *a; + arg.argc = a->argc - 1; + arg.argv = a->argv + 1; + + /** @todo This command does not follow the syntax where the <uuid|vmname> + * comes between the command and subcommand. The commands controlvm, + * snapshot and debugvm puts it between. + */ + + if (a->argc == 0) + return errorSyntax(USAGE_GUESTPROPERTY, "Incorrect parameters"); + + /* switch (cmd) */ + if (strcmp(a->argv[0], "get") == 0) + return handleGetGuestProperty(&arg); + if (strcmp(a->argv[0], "set") == 0) + return handleSetGuestProperty(&arg); + if (strcmp(a->argv[0], "delete") == 0 || strcmp(a->argv[0], "unset") == 0) + return handleDeleteGuestProperty(&arg); + if (strcmp(a->argv[0], "enumerate") == 0) + return handleEnumGuestProperty(&arg); + if (strcmp(a->argv[0], "wait") == 0) + return handleWaitGuestProperty(&arg); + + /* default: */ + return errorSyntax(USAGE_GUESTPROPERTY, "Incorrect parameters"); +} + +#endif /* !VBOX_ONLY_DOCS */ diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageHelp.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageHelp.cpp new file mode 100644 index 00000000..4b8c7390 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageHelp.cpp @@ -0,0 +1,1404 @@ +/* $Id: VBoxManageHelp.cpp $ */ +/** @file + * VBoxManage - help and other message output. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/version.h> + +#include <iprt/buildconfig.h> +#include <iprt/ctype.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/stream.h> +#include <iprt/message.h> + +#include "VBoxManage.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** If the usage is the given number of length long or longer, the error is + * repeated so the user can actually see it. */ +#define ERROR_REPEAT_AFTER_USAGE_LENGTH 16 + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifndef VBOX_ONLY_DOCS +enum HELP_CMD_VBOXMANAGE g_enmCurCommand = HELP_CMD_VBOXMANAGE_INVALID; +/** The scope maskt for the current subcommand. */ +uint64_t g_fCurSubcommandScope = RTMSGREFENTRYSTR_SCOPE_GLOBAL; + +/** + * Sets the current command. + * + * This affects future calls to error and help functions. + * + * @param enmCommand The command. + */ +void setCurrentCommand(enum HELP_CMD_VBOXMANAGE enmCommand) +{ + Assert(g_enmCurCommand == HELP_CMD_VBOXMANAGE_INVALID); + g_enmCurCommand = enmCommand; + g_fCurSubcommandScope = RTMSGREFENTRYSTR_SCOPE_GLOBAL; +} + + +/** + * Sets the current subcommand. + * + * This affects future calls to error and help functions. + * + * @param fSubcommandScope The subcommand scope. + */ +void setCurrentSubcommand(uint64_t fSubcommandScope) +{ + g_fCurSubcommandScope = fSubcommandScope; +} + + + + +/** + * Prints brief help for a command or subcommand. + * + * @returns Number of lines written. + * @param enmCommand The command. + * @param fSubcommandScope The subcommand scope, REFENTRYSTR_SCOPE_GLOBAL + * for all. + * @param pStrm The output stream. + */ +static uint32_t printBriefCommandOrSubcommandHelp(enum HELP_CMD_VBOXMANAGE enmCommand, uint64_t fSubcommandScope, PRTSTREAM pStrm) +{ + uint32_t cLinesWritten = 0; + uint32_t cPendingBlankLines = 0; + uint32_t cFound = 0; + for (uint32_t i = 0; i < g_cHelpEntries; i++) + { + PCRTMSGREFENTRY pHelp = g_apHelpEntries[i]; + if (pHelp->idInternal == (int64_t)enmCommand) + { + cFound++; + if (cFound == 1) + { + if (fSubcommandScope == RTMSGREFENTRYSTR_SCOPE_GLOBAL) + RTStrmPrintf(pStrm, "Usage - %c%s:\n", RT_C_TO_UPPER(pHelp->pszBrief[0]), pHelp->pszBrief + 1); + else + RTStrmPrintf(pStrm, "Usage:\n"); + } + RTMsgRefEntryPrintStringTable(pStrm, &pHelp->Synopsis, fSubcommandScope, &cPendingBlankLines, &cLinesWritten); + if (!cPendingBlankLines) + cPendingBlankLines = 1; + } + } + Assert(cFound > 0); + return cLinesWritten; +} + + +/** + * Prints the brief usage information for the current (sub)command. + * + * @param pStrm The output stream. + */ +void printUsage(PRTSTREAM pStrm) +{ + printBriefCommandOrSubcommandHelp(g_enmCurCommand, g_fCurSubcommandScope, pStrm); +} + + +/** + * Prints full help for a command or subcommand. + * + * @param enmCommand The command. + * @param fSubcommandScope The subcommand scope, REFENTRYSTR_SCOPE_GLOBAL + * for all. + * @param pStrm The output stream. + */ +static void printFullCommandOrSubcommandHelp(enum HELP_CMD_VBOXMANAGE enmCommand, uint64_t fSubcommandScope, PRTSTREAM pStrm) +{ + uint32_t cPendingBlankLines = 0; + uint32_t cFound = 0; + for (uint32_t i = 0; i < g_cHelpEntries; i++) + { + PCRTMSGREFENTRY pHelp = g_apHelpEntries[i]; + if ( pHelp->idInternal == (int64_t)enmCommand + || enmCommand == HELP_CMD_VBOXMANAGE_INVALID) + { + cFound++; + RTMsgRefEntryPrintStringTable(pStrm, &pHelp->Help, fSubcommandScope, &cPendingBlankLines, NULL /*pcLinesWritten*/); + if (cPendingBlankLines < 2) + cPendingBlankLines = 2; + } + } + Assert(cFound > 0); +} + + +/** + * Prints the full help for the current (sub)command. + * + * @param pStrm The output stream. + */ +void printHelp(PRTSTREAM pStrm) +{ + printFullCommandOrSubcommandHelp(g_enmCurCommand, g_fCurSubcommandScope, pStrm); +} + + +/** + * Display no subcommand error message and current command usage. + * + * @returns RTEXITCODE_SYNTAX. + */ +RTEXITCODE errorNoSubcommand(void) +{ + Assert(g_enmCurCommand != HELP_CMD_VBOXMANAGE_INVALID); + Assert(g_fCurSubcommandScope == RTMSGREFENTRYSTR_SCOPE_GLOBAL); + + return errorSyntax("No subcommand specified"); +} + + +/** + * Display unknown subcommand error message and current command usage. + * + * May show full command help instead if the subcommand is a common help option. + * + * @returns RTEXITCODE_SYNTAX, or RTEXITCODE_SUCCESS if common help option. + * @param pszSubcommand The name of the alleged subcommand. + */ +RTEXITCODE errorUnknownSubcommand(const char *pszSubcommand) +{ + Assert(g_enmCurCommand != HELP_CMD_VBOXMANAGE_INVALID); + Assert(g_fCurSubcommandScope == RTMSGREFENTRYSTR_SCOPE_GLOBAL); + + /* check if help was requested. */ + if ( strcmp(pszSubcommand, "--help") == 0 + || strcmp(pszSubcommand, "-h") == 0 + || strcmp(pszSubcommand, "-?") == 0) + { + printFullCommandOrSubcommandHelp(g_enmCurCommand, g_fCurSubcommandScope, g_pStdOut); + return RTEXITCODE_SUCCESS; + } + + return errorSyntax("Unknown subcommand: %s", pszSubcommand); +} + + +/** + * Display too many parameters error message and current command usage. + * + * May show full command help instead if the subcommand is a common help option. + * + * @returns RTEXITCODE_SYNTAX, or RTEXITCODE_SUCCESS if common help option. + * @param papszArgs The first unwanted parameter. Terminated by + * NULL entry. + */ +RTEXITCODE errorTooManyParameters(char **papszArgs) +{ + Assert(g_enmCurCommand != HELP_CMD_VBOXMANAGE_INVALID); + Assert(g_fCurSubcommandScope != RTMSGREFENTRYSTR_SCOPE_GLOBAL); + + /* check if help was requested. */ + if (papszArgs) + { + for (uint32_t i = 0; papszArgs[i]; i++) + if ( strcmp(papszArgs[i], "--help") == 0 + || strcmp(papszArgs[i], "-h") == 0 + || strcmp(papszArgs[i], "-?") == 0) + { + printFullCommandOrSubcommandHelp(g_enmCurCommand, g_fCurSubcommandScope, g_pStdOut); + return RTEXITCODE_SUCCESS; + } + else if (!strcmp(papszArgs[i], "--")) + break; + } + + return errorSyntax("Too many parameters"); +} + + +/** + * Display current (sub)command usage and the custom error message. + * + * @returns RTEXITCODE_SYNTAX. + * @param pszFormat Custom error message format string. + * @param ... Format arguments. + */ +RTEXITCODE errorSyntax(const char *pszFormat, ...) +{ + Assert(g_enmCurCommand != HELP_CMD_VBOXMANAGE_INVALID); + + showLogo(g_pStdErr); + + va_list va; + va_start(va, pszFormat); + RTMsgErrorV(pszFormat, va); + va_end(va); + + RTStrmPutCh(g_pStdErr, '\n'); + if ( printBriefCommandOrSubcommandHelp(g_enmCurCommand, g_fCurSubcommandScope, g_pStdErr) + >= ERROR_REPEAT_AFTER_USAGE_LENGTH) + { + /* Usage was very long, repeat the error message. */ + RTStrmPutCh(g_pStdErr, '\n'); + va_start(va, pszFormat); + RTMsgErrorV(pszFormat, va); + va_end(va); + } + return RTEXITCODE_SYNTAX; +} + + +/** + * Worker for errorGetOpt. + * + * @param rcGetOpt The RTGetOpt return value. + * @param pValueUnion The value union returned by RTGetOpt. + */ +static void errorGetOptWorker(int rcGetOpt, union RTGETOPTUNION const *pValueUnion) +{ + if (rcGetOpt == VINF_GETOPT_NOT_OPTION) + RTMsgError("Invalid parameter '%s'", pValueUnion->psz); + else if (rcGetOpt > 0) + { + if (RT_C_IS_PRINT(rcGetOpt)) + RTMsgError("Invalid option -%c", rcGetOpt); + else + RTMsgError("Invalid option case %i", rcGetOpt); + } + else if (rcGetOpt == VERR_GETOPT_UNKNOWN_OPTION) + RTMsgError("Unknown option: %s", pValueUnion->psz); + else if (rcGetOpt == VERR_GETOPT_INVALID_ARGUMENT_FORMAT) + RTMsgError("Invalid argument format: %s", pValueUnion->psz); + else if (pValueUnion->pDef) + RTMsgError("%s: %Rrs", pValueUnion->pDef->pszLong, rcGetOpt); + else + RTMsgError("%Rrs", rcGetOpt); +} + + +/** + * Handled an RTGetOpt error or common option. + * + * This implements the 'V' and 'h' cases. It reports appropriate syntax errors + * for other @a rcGetOpt values. + * + * @retval RTEXITCODE_SUCCESS if help or version request. + * @retval RTEXITCODE_SYNTAX if not help or version request. + * @param rcGetOpt The RTGetOpt return value. + * @param pValueUnion The value union returned by RTGetOpt. + */ +RTEXITCODE errorGetOpt(int rcGetOpt, union RTGETOPTUNION const *pValueUnion) +{ + Assert(g_enmCurCommand != HELP_CMD_VBOXMANAGE_INVALID); + + /* + * Check if it is an unhandled standard option. + */ + if (rcGetOpt == 'V') + { + RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision()); + return RTEXITCODE_SUCCESS; + } + + if (rcGetOpt == 'h') + { + printFullCommandOrSubcommandHelp(g_enmCurCommand, g_fCurSubcommandScope, g_pStdOut); + return RTEXITCODE_SUCCESS; + } + + /* + * We failed. + */ + showLogo(g_pStdErr); + errorGetOptWorker(rcGetOpt, pValueUnion); + if ( printBriefCommandOrSubcommandHelp(g_enmCurCommand, g_fCurSubcommandScope, g_pStdErr) + >= ERROR_REPEAT_AFTER_USAGE_LENGTH) + { + /* Usage was very long, repeat the error message. */ + RTStrmPutCh(g_pStdErr, '\n'); + errorGetOptWorker(rcGetOpt, pValueUnion); + } + return RTEXITCODE_SYNTAX; +} + +#endif /* !VBOX_ONLY_DOCS */ + + + +void showLogo(PRTSTREAM pStrm) +{ + static bool s_fShown; /* show only once */ + + if (!s_fShown) + { + RTStrmPrintf(pStrm, VBOX_PRODUCT " Command Line Management Interface Version " + VBOX_VERSION_STRING "\n" + "(C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n" + "All rights reserved.\n" + "\n"); + s_fShown = true; + } +} + + + + +void printUsage(USAGECATEGORY fCategory, uint32_t fSubCategory, PRTSTREAM pStrm) +{ + bool fDumpOpts = false; +#ifdef RT_OS_LINUX + bool fLinux = true; +#else + bool fLinux = false; +#endif +#ifdef RT_OS_WINDOWS + bool fWin = true; +#else + bool fWin = false; +#endif +#ifdef RT_OS_SOLARIS + bool fSolaris = true; +#else + bool fSolaris = false; +#endif +#ifdef RT_OS_FREEBSD + bool fFreeBSD = true; +#else + bool fFreeBSD = false; +#endif +#ifdef RT_OS_DARWIN + bool fDarwin = true; +#else + bool fDarwin = false; +#endif +#ifdef VBOX_WITH_VBOXSDL + bool fVBoxSDL = true; +#else + bool fVBoxSDL = false; +#endif + + if (fCategory == USAGE_DUMPOPTS) + { + fDumpOpts = true; + fLinux = true; + fWin = true; + fSolaris = true; + fFreeBSD = true; + fDarwin = true; + fVBoxSDL = true; + fCategory = USAGE_ALL; + } + + RTStrmPrintf(pStrm, + "Usage:\n" + "\n"); + + if (fCategory == USAGE_ALL) + RTStrmPrintf(pStrm, + " VBoxManage [<general option>] <command>\n" + " \n \n" + "General Options:\n \n" + " [-v|--version] print version number and exit\n" + " [-q|--nologo] suppress the logo\n" + " [--settingspw <pw>] provide the settings password\n" + " [--settingspwfile <file>] provide a file containing the settings password\n" + " [@<response-file>] load arguments from the given response file (bourne style)\n" + " \n \n" + "Commands:\n \n"); + + const char *pcszSep1 = " "; + const char *pcszSep2 = " "; + if (fCategory != USAGE_ALL) + { + pcszSep1 = "VBoxManage"; + pcszSep2 = ""; + } + +#define SEP pcszSep1, pcszSep2 + + if (fCategory & USAGE_LIST) + RTStrmPrintf(pStrm, + "%s list [--long|-l] [--sorted|-s]%s vms|runningvms|ostypes|hostdvds|hostfloppies|\n" +#if defined(VBOX_WITH_NETFLT) + " intnets|bridgedifs|hostonlyifs|natnets|dhcpservers|\n" +#else + " intnets|bridgedifs|natnets|dhcpservers|hostinfo|\n" +#endif + " hostinfo|hostcpuids|hddbackends|hdds|dvds|floppies|\n" + " usbhost|usbfilters|systemproperties|extpacks|\n" + " groups|webcams|screenshotformats|cloudproviders|\n" + " cloudprofiles\n" + "\n", SEP); + + if (fCategory & USAGE_SHOWVMINFO) + RTStrmPrintf(pStrm, + "%s showvminfo %s <uuid|vmname> [--details]\n" + " [--machinereadable]\n" + "%s showvminfo %s <uuid|vmname> --log <idx>\n" + "\n", SEP, SEP); + + if (fCategory & USAGE_REGISTERVM) + RTStrmPrintf(pStrm, + "%s registervm %s <filename>\n" + "\n", SEP); + + if (fCategory & USAGE_UNREGISTERVM) + RTStrmPrintf(pStrm, + "%s unregistervm %s <uuid|vmname> [--delete]\n" + "\n", SEP); + + if (fCategory & USAGE_CREATEVM) + RTStrmPrintf(pStrm, + "%s createvm %s --name <name>\n" + " [--groups <group>, ...]\n" + " [--ostype <ostype>]\n" + " [--register]\n" + " [--basefolder <path>]\n" + " [--uuid <uuid>]\n" + " [--default]\n" + "\n", SEP); + + if (fCategory & USAGE_MODIFYVM) + { + RTStrmPrintf(pStrm, + "%s modifyvm %s <uuid|vmname>\n" + " [--name <name>]\n" + " [--groups <group>, ...]\n" + " [--description <desc>]\n" + " [--ostype <ostype>]\n" + " [--iconfile <filename>]\n" + " [--memory <memorysize in MB>]\n" + " [--pagefusion on|off]\n" + " [--vram <vramsize in MB>]\n" + " [--acpi on|off]\n" +#ifdef VBOX_WITH_PCI_PASSTHROUGH + " [--pciattach 03:04.0]\n" + " [--pciattach 03:04.0@02:01.0]\n" + " [--pcidetach 03:04.0]\n" +#endif + " [--ioapic on|off]\n" + " [--hpet on|off]\n" + " [--triplefaultreset on|off]\n" + " [--apic on|off]\n" + " [--x2apic on|off]\n" + " [--paravirtprovider none|default|legacy|minimal|\n" + " hyperv|kvm]\n" + " [--paravirtdebug <key=value> [,<key=value> ...]]\n" + " [--hwvirtex on|off]\n" + " [--nestedpaging on|off]\n" + " [--largepages on|off]\n" + " [--vtxvpid on|off]\n" + " [--vtxux on|off]\n" + " [--pae on|off]\n" + " [--longmode on|off]\n" + " [--ibpb-on-vm-exit on|off]\n" + " [--ibpb-on-vm-entry on|off]\n" + " [--spec-ctrl on|off]\n" + " [--l1d-flush-on-sched on|off]\n" + " [--l1d-flush-on-vm-entry on|off]\n" + " [--nested-hw-virt on|off]\n" + " [--cpu-profile \"host|Intel 80[86|286|386]\"]\n" + " [--cpuid-portability-level <0..3>\n" + " [--cpuid-set <leaf[:subleaf]> <eax> <ebx> <ecx> <edx>]\n" + " [--cpuid-remove <leaf[:subleaf]>]\n" + " [--cpuidremoveall]\n" + " [--hardwareuuid <uuid>]\n" + " [--cpus <number>]\n" + " [--cpuhotplug on|off]\n" + " [--plugcpu <id>]\n" + " [--unplugcpu <id>]\n" + " [--cpuexecutioncap <1-100>]\n" + " [--rtcuseutc on|off]\n" +#ifdef VBOX_WITH_VMSVGA + " [--graphicscontroller none|vboxvga|vmsvga|vboxsvga]\n" +#else + " [--graphicscontroller none|vboxvga]\n" +#endif + " [--monitorcount <number>]\n" + " [--accelerate3d on|off]\n" +#ifdef VBOX_WITH_VIDEOHWACCEL + " [--accelerate2dvideo on|off]\n" +#endif + " [--firmware bios|efi|efi32|efi64]\n" + " [--chipset ich9|piix3]\n" + " [--bioslogofadein on|off]\n" + " [--bioslogofadeout on|off]\n" + " [--bioslogodisplaytime <msec>]\n" + " [--bioslogoimagepath <imagepath>]\n" + " [--biosbootmenu disabled|menuonly|messageandmenu]\n" + " [--biosapic disabled|apic|x2apic]\n" + " [--biossystemtimeoffset <msec>]\n" + " [--biospxedebug on|off]\n" + " [--boot<1-4> none|floppy|dvd|disk|net>]\n" + " [--nic<1-N> none|null|nat|bridged|intnet" +#if defined(VBOX_WITH_NETFLT) + "|hostonly" +#endif + "|\n" + " generic|natnetwork" + "]\n" + " [--nictype<1-N> Am79C970A|Am79C973" +#ifdef VBOX_WITH_E1000 + "|\n 82540EM|82543GC|82545EM" +#endif +#ifdef VBOX_WITH_VIRTIO + "|\n virtio" +#endif /* VBOX_WITH_VIRTIO */ + "]\n" + " [--cableconnected<1-N> on|off]\n" + " [--nictrace<1-N> on|off]\n" + " [--nictracefile<1-N> <filename>]\n" + " [--nicproperty<1-N> name=[value]]\n" + " [--nicspeed<1-N> <kbps>]\n" + " [--nicbootprio<1-N> <priority>]\n" + " [--nicpromisc<1-N> deny|allow-vms|allow-all]\n" + " [--nicbandwidthgroup<1-N> none|<name>]\n" + " [--bridgeadapter<1-N> none|<devicename>]\n" +#if defined(VBOX_WITH_NETFLT) + " [--hostonlyadapter<1-N> none|<devicename>]\n" +#endif + " [--intnet<1-N> <network name>]\n" + " [--nat-network<1-N> <network name>]\n" + " [--nicgenericdrv<1-N> <driver>\n" + " [--natnet<1-N> <network>|default]\n" + " [--natsettings<1-N> [<mtu>],[<socksnd>],\n" + " [<sockrcv>],[<tcpsnd>],\n" + " [<tcprcv>]]\n" + " [--natpf<1-N> [<rulename>],tcp|udp,[<hostip>],\n" + " <hostport>,[<guestip>],<guestport>]\n" + " [--natpf<1-N> delete <rulename>]\n" + " [--nattftpprefix<1-N> <prefix>]\n" + " [--nattftpfile<1-N> <file>]\n" + " [--nattftpserver<1-N> <ip>]\n" + " [--natbindip<1-N> <ip>\n" + " [--natdnspassdomain<1-N> on|off]\n" + " [--natdnsproxy<1-N> on|off]\n" + " [--natdnshostresolver<1-N> on|off]\n" + " [--nataliasmode<1-N> default|[log],[proxyonly],\n" + " [sameports]]\n" + " [--macaddress<1-N> auto|<mac>]\n" + " [--mouse ps2|usb|usbtablet|usbmultitouch]\n" + " [--keyboard ps2|usb\n" + " [--uart<1-N> off|<I/O base> <IRQ>]\n" + " [--uartmode<1-N> disconnected|\n" + " server <pipe>|\n" + " client <pipe>|\n" + " tcpserver <port>|\n" + " tcpclient <hostname:port>|\n" + " file <file>|\n" + " <devicename>]\n" + " [--uarttype<1-N> 16450|16550A|16750\n" +#if defined(RT_OS_LINUX) || defined(RT_OS_WINDOWS) + " [--lpt<1-N> off|<I/O base> <IRQ>]\n" + " [--lptmode<1-N> <devicename>]\n" +#endif + " [--guestmemoryballoon <balloonsize in MB>]\n" + " [--audio none|null", SEP); + if (fWin) + { +#ifdef VBOX_WITH_WINMM + RTStrmPrintf(pStrm, "|winmm|dsound"); +#else + RTStrmPrintf(pStrm, "|dsound"); +#endif + } + if (fLinux || fSolaris) + { + RTStrmPrintf(pStrm, "" +#ifdef VBOX_WITH_AUDIO_OSS + "|oss" +#endif +#ifdef VBOX_WITH_AUDIO_ALSA + "|alsa" +#endif +#ifdef VBOX_WITH_AUDIO_PULSE + "|pulse" +#endif + ); + } + if (fFreeBSD) + { +#ifdef VBOX_WITH_AUDIO_OSS + /* Get the line break sorted when dumping all option variants. */ + if (fDumpOpts) + { + RTStrmPrintf(pStrm, "|\n" + " oss"); + } + else + RTStrmPrintf(pStrm, "|oss"); +#endif +#ifdef VBOX_WITH_AUDIO_PULSE + RTStrmPrintf(pStrm, "|pulse"); +#endif + } + if (fDarwin) + { + RTStrmPrintf(pStrm, "|coreaudio"); + } + RTStrmPrintf(pStrm, "]\n"); + RTStrmPrintf(pStrm, + " [--audioin on|off]\n" + " [--audioout on|off]\n" + " [--audiocontroller ac97|hda|sb16]\n" + " [--audiocodec stac9700|ad1980|stac9221|sb16]\n" + " [--clipboard disabled|hosttoguest|guesttohost|\n" + " bidirectional]\n" + " [--draganddrop disabled|hosttoguest|guesttohost|\n" + " bidirectional]\n"); + RTStrmPrintf(pStrm, + " [--vrde on|off]\n" + " [--vrdeextpack default|<name>\n" + " [--vrdeproperty <name=[value]>]\n" + " [--vrdeport <hostport>]\n" + " [--vrdeaddress <hostip>]\n" + " [--vrdeauthtype null|external|guest]\n" + " [--vrdeauthlibrary default|<name>\n" + " [--vrdemulticon on|off]\n" + " [--vrdereusecon on|off]\n" + " [--vrdevideochannel on|off]\n" + " [--vrdevideochannelquality <percent>]\n"); + RTStrmPrintf(pStrm, + " [--usbohci on|off]\n" + " [--usbehci on|off]\n" + " [--usbxhci on|off]\n" + " [--usbrename <oldname> <newname>]\n" + " [--snapshotfolder default|<path>]\n" + " [--teleporter on|off]\n" + " [--teleporterport <port>]\n" + " [--teleporteraddress <address|empty>\n" + " [--teleporterpassword <password>]\n" + " [--teleporterpasswordfile <file>|stdin]\n" + " [--tracing-enabled on|off]\n" + " [--tracing-config <config-string>]\n" + " [--tracing-allow-vm-access on|off]\n" +#if 0 + " [--iocache on|off]\n" + " [--iocachesize <I/O cache size in MB>]\n" +#endif +#if 0 + " [--faulttolerance master|standby]\n" + " [--faulttoleranceaddress <name>]\n" + " [--faulttoleranceport <port>]\n" + " [--faulttolerancesyncinterval <msec>]\n" + " [--faulttolerancepassword <password>]\n" +#endif +#ifdef VBOX_WITH_USB_CARDREADER + " [--usbcardreader on|off]\n" +#endif + " [--autostart-enabled on|off]\n" + " [--autostart-delay <seconds>]\n" +#if 0 + " [--autostop-type disabled|savestate|poweroff|\n" + " acpishutdown]\n" +#endif +#ifdef VBOX_WITH_RECORDING + " [--recording on|off]\n" + " [--recording screens all|<screen ID> [<screen ID> ...]]\n" + " [--recording filename <filename>]\n" + " [--recording videores <width> <height>]\n" + " [--recording videorate <rate>]\n" + " [--recording videofps <fps>]\n" + " [--recording maxtime <s>]\n" + " [--recording maxfilesize <MB>]\n" + " [--recording opts <key=value> [,<key=value> ...]]\n" +#endif + " [--defaultfrontend default|<name>]\n" + "\n"); + } + + if (fCategory & USAGE_CLONEVM) + RTStrmPrintf(pStrm, + "%s clonevm %s <uuid|vmname>\n" + " [--snapshot <uuid>|<name>]\n" + " [--mode machine|machineandchildren|all]\n" + " [--options link|keepallmacs|keepnatmacs|\n" + " keepdisknames|keephwuuids]\n" + " [--name <name>]\n" + " [--groups <group>, ...]\n" + " [--basefolder <basefolder>]\n" + " [--uuid <uuid>]\n" + " [--register]\n" + "\n", SEP); + + if (fCategory & USAGE_MOVEVM) + RTStrmPrintf(pStrm, + "%s movevm %s <uuid|vmname>\n" + " --type basic\n" + " [--folder <path>]\n" + "\n", SEP); + + if (fCategory & USAGE_IMPORTAPPLIANCE) + RTStrmPrintf(pStrm, + "%s import %s <ovfname/ovaname>\n" + " [--dry-run|-n]\n" + " [--options keepallmacs|keepnatmacs|importtovdi]\n" + " [more options]\n" + " (run with -n to have options displayed\n" + " for a particular OVF)\n\n", SEP); + + if (fCategory & USAGE_EXPORTAPPLIANCE) + RTStrmPrintf(pStrm, + "%s export %s <machines> --output|-o <name>.<ovf/ova/tar.gz>\n" + " [--legacy09|--ovf09|--ovf10|--ovf20|--opc10]\n" + " [--manifest]\n" + " [--iso]\n" + " [--options manifest|iso|nomacs|nomacsbutnat]\n" + " [--vsys <number of virtual system>]\n" + " [--vmname <name>]\n" + " [--product <product name>]\n" + " [--producturl <product url>]\n" + " [--vendor <vendor name>]\n" + " [--vendorurl <vendor url>]\n" + " [--version <version info>]\n" + " [--description <description info>]\n" + " [--eula <license text>]\n" + " [--eulafile <filename>]\n" + " [--cloud <number of virtual system>]\n" + " [--vmname <name>]\n" + " [--cloudprofile <cloud profile name>]\n" + " [--cloudshape <shape>]\n" + " [--clouddomain <domain>]\n" + " [--clouddisksize <disk size in GB>]\n" + " [--cloudbucket <bucket name>]\n" + " [--cloudocivcn <OCI vcn id>]\n" + " [--cloudocisubnet <OCI subnet id>]\n" + " [--cloudkeepobject <true/false>]\n" + " [--cloudlaunchinstance <true/false>]\n" + " [--cloudpublicip <true/false>]\n" + "\n", SEP); + + if (fCategory & USAGE_STARTVM) + { + RTStrmPrintf(pStrm, + "%s startvm %s <uuid|vmname>...\n" + " [--type gui", SEP); + if (fVBoxSDL) + RTStrmPrintf(pStrm, "|sdl"); + RTStrmPrintf(pStrm, "|headless|separate]\n"); + RTStrmPrintf(pStrm, + " [-E|--putenv <NAME>[=<VALUE>]]\n" + "\n"); + } + + if (fCategory & USAGE_CONTROLVM) + { + RTStrmPrintf(pStrm, + "%s controlvm %s <uuid|vmname>\n" + " pause|resume|reset|poweroff|savestate|\n" + " acpipowerbutton|acpisleepbutton|\n" + " keyboardputscancode <hex> [<hex> ...]|\n" + " keyboardputstring <string1> [<string2> ...]|\n" + " keyboardputfile <filename>|\n" + " setlinkstate<1-N> on|off |\n" +#if defined(VBOX_WITH_NETFLT) + " nic<1-N> null|nat|bridged|intnet|hostonly|generic|\n" + " natnetwork [<devicename>] |\n" +#else /* !VBOX_WITH_NETFLT */ + " nic<1-N> null|nat|bridged|intnet|generic|natnetwork\n" + " [<devicename>] |\n" +#endif /* !VBOX_WITH_NETFLT */ + " nictrace<1-N> on|off |\n" + " nictracefile<1-N> <filename> |\n" + " nicproperty<1-N> name=[value] |\n" + " nicpromisc<1-N> deny|allow-vms|allow-all |\n" + " natpf<1-N> [<rulename>],tcp|udp,[<hostip>],\n" + " <hostport>,[<guestip>],<guestport> |\n" + " natpf<1-N> delete <rulename> |\n" + " guestmemoryballoon <balloonsize in MB> |\n" + " usbattach <uuid>|<address>\n" + " [--capturefile <filename>] |\n" + " usbdetach <uuid>|<address> |\n" + " audioin on|off |\n" + " audioout on|off |\n" + " clipboard disabled|hosttoguest|guesttohost|\n" + " bidirectional |\n" + " draganddrop disabled|hosttoguest|guesttohost|\n" + " bidirectional |\n" + " vrde on|off |\n" + " vrdeport <port> |\n" + " vrdeproperty <name=[value]> |\n" + " vrdevideochannelquality <percent> |\n" + " setvideomodehint <xres> <yres> <bpp>\n" + " [[<display>] [<enabled:yes|no> |\n" + " [<xorigin> <yorigin>]]] |\n" + " setscreenlayout <display> on|primary <xorigin> <yorigin> <xres> <yres> <bpp> | off\n" + " screenshotpng <file> [display] |\n" +#ifdef VBOX_WITH_RECORDING + " recording on|off |\n" + " recording screens all|none|<screen>,[<screen>...] |\n" + " recording filename <file> |\n" + " recording videores <width>x<height> |\n" + " recording videorate <rate> |\n" + " recording videofps <fps> |\n" + " recording maxtime <s> |\n" + " recording maxfilesize <MB> |\n" +#endif /* VBOX_WITH_RECORDING */ + " setcredentials <username>\n" + " --passwordfile <file> | <password>\n" + " <domain>\n" + " [--allowlocallogon <yes|no>] |\n" + " teleport --host <name> --port <port>\n" + " [--maxdowntime <msec>]\n" + " [--passwordfile <file> |\n" + " --password <password>] |\n" + " plugcpu <id> |\n" + " unplugcpu <id> |\n" + " cpuexecutioncap <1-100>\n" + " webcam <attach [path [settings]]> | <detach [path]> | <list>\n" + " addencpassword <id>\n" + " <password file>|-\n" + " [--removeonsuspend <yes|no>]\n" + " removeencpassword <id>\n" + " removeallencpasswords\n" + " changeuartmode<1-N> disconnected|\n" + " server <pipe>|\n" + " client <pipe>|\n" + " tcpserver <port>|\n" + " tcpclient <hostname:port>|\n" + " file <file>|\n" + " <devicename>]\n" + "\n", SEP); + } + + if (fCategory & USAGE_DISCARDSTATE) + RTStrmPrintf(pStrm, + "%s discardstate %s <uuid|vmname>\n" + "\n", SEP); + + if (fCategory & USAGE_ADOPTSTATE) + RTStrmPrintf(pStrm, + "%s adoptstate %s <uuid|vmname> <state_file>\n" + "\n", SEP); + + if (fCategory & USAGE_SNAPSHOT) + RTStrmPrintf(pStrm, + "%s snapshot %s <uuid|vmname>\n" + " take <name> [--description <desc>] [--live]\n" + " [--uniquename Number,Timestamp,Space,Force] |\n" + " delete <uuid|snapname> |\n" + " restore <uuid|snapname> |\n" + " restorecurrent |\n" + " edit <uuid|snapname>|--current\n" + " [--name <name>]\n" + " [--description <desc>] |\n" + " list [--details|--machinereadable] |\n" + " showvminfo <uuid|snapname>\n" + "\n", SEP); + + if (fCategory & USAGE_CLOSEMEDIUM) + RTStrmPrintf(pStrm, + "%s closemedium %s [disk|dvd|floppy] <uuid|filename>\n" + " [--delete]\n" + "\n", SEP); + + if (fCategory & USAGE_STORAGEATTACH) + RTStrmPrintf(pStrm, + "%s storageattach %s <uuid|vmname>\n" + " --storagectl <name>\n" + " [--port <number>]\n" + " [--device <number>]\n" + " [--type dvddrive|hdd|fdd]\n" + " [--medium none|emptydrive|additions|\n" + " <uuid|filename>|host:<drive>|iscsi]\n" + " [--mtype normal|writethrough|immutable|shareable|\n" + " readonly|multiattach]\n" + " [--comment <text>]\n" + " [--setuuid <uuid>]\n" + " [--setparentuuid <uuid>]\n" + " [--passthrough on|off]\n" + " [--tempeject on|off]\n" + " [--nonrotational on|off]\n" + " [--discard on|off]\n" + " [--hotpluggable on|off]\n" + " [--bandwidthgroup <name>]\n" + " [--forceunmount]\n" + " [--server <name>|<ip>]\n" + " [--target <target>]\n" + " [--tport <port>]\n" + " [--lun <lun>]\n" + " [--encodedlun <lun>]\n" + " [--username <username>]\n" + " [--password <password>]\n" + " [--passwordfile <file>]\n" + " [--initiator <initiator>]\n" + " [--intnet]\n" + "\n", SEP); + + if (fCategory & USAGE_STORAGECONTROLLER) + RTStrmPrintf(pStrm, + "%s storagectl %s <uuid|vmname>\n" + " --name <name>\n" + " [--add ide|sata|scsi|floppy|sas|usb|pcie]\n" + " [--controller LSILogic|LSILogicSAS|BusLogic|\n" + " IntelAHCI|PIIX3|PIIX4|ICH6|I82078|\n" + " [ USB|NVMe]\n" + " [--portcount <1-n>]\n" + " [--hostiocache on|off]\n" + " [--bootable on|off]\n" + " [--rename <name>]\n" + " [--remove]\n" + "\n", SEP); + + if (fCategory & USAGE_BANDWIDTHCONTROL) + RTStrmPrintf(pStrm, + "%s bandwidthctl %s <uuid|vmname>\n" + " add <name> --type disk|network\n" + " --limit <megabytes per second>[k|m|g|K|M|G] |\n" + " set <name>\n" + " --limit <megabytes per second>[k|m|g|K|M|G] |\n" + " remove <name> |\n" + " list [--machinereadable]\n" + " (limit units: k=kilobit, m=megabit, g=gigabit,\n" + " K=kilobyte, M=megabyte, G=gigabyte)\n" + "\n", SEP); + + if (fCategory & USAGE_SHOWMEDIUMINFO) + RTStrmPrintf(pStrm, + "%s showmediuminfo %s [disk|dvd|floppy] <uuid|filename>\n" + "\n", SEP); + + if (fCategory & USAGE_CREATEMEDIUM) + RTStrmPrintf(pStrm, + "%s createmedium %s [disk|dvd|floppy] --filename <filename>\n" + " [--size <megabytes>|--sizebyte <bytes>]\n" + " [--diffparent <uuid>|<filename>\n" + " [--format VDI|VMDK|VHD] (default: VDI)\n" + " [--variant Standard,Fixed,Split2G,Stream,ESX,\n" + " Formatted]\n" + "\n", SEP); + + if (fCategory & USAGE_MODIFYMEDIUM) + RTStrmPrintf(pStrm, + "%s modifymedium %s [disk|dvd|floppy] <uuid|filename>\n" + " [--type normal|writethrough|immutable|shareable|\n" + " readonly|multiattach]\n" + " [--autoreset on|off]\n" + " [--property <name=[value]>]\n" + " [--compact]\n" + " [--resize <megabytes>|--resizebyte <bytes>]\n" + " [--move <path>]\n" + " [--setlocation <path>]\n" + " [--description <description string>]" + "\n", SEP); + + if (fCategory & USAGE_CLONEMEDIUM) + RTStrmPrintf(pStrm, + "%s clonemedium %s [disk|dvd|floppy] <uuid|inputfile> <uuid|outputfile>\n" + " [--format VDI|VMDK|VHD|RAW|<other>]\n" + " [--variant Standard,Fixed,Split2G,Stream,ESX]\n" + " [--existing]\n" + "\n", SEP); + + if (fCategory & USAGE_MEDIUMPROPERTY) + RTStrmPrintf(pStrm, + "%s mediumproperty %s [disk|dvd|floppy] set <uuid|filename>\n" + " <property> <value>\n" + "\n" + " [disk|dvd|floppy] get <uuid|filename>\n" + " <property>\n" + "\n" + " [disk|dvd|floppy] delete <uuid|filename>\n" + " <property>\n" + "\n", SEP); + + if (fCategory & USAGE_ENCRYPTMEDIUM) + RTStrmPrintf(pStrm, + "%s encryptmedium %s <uuid|filename>\n" + " [--newpassword <file>|-]\n" + " [--oldpassword <file>|-]\n" + " [--cipher <cipher identifier>]\n" + " [--newpasswordid <password identifier>]\n" + "\n", SEP); + + if (fCategory & USAGE_MEDIUMENCCHKPWD) + RTStrmPrintf(pStrm, + "%s checkmediumpwd %s <uuid|filename>\n" + " <pwd file>|-\n" + "\n", SEP); + + if (fCategory & USAGE_CONVERTFROMRAW) + RTStrmPrintf(pStrm, + "%s convertfromraw %s <filename> <outputfile>\n" + " [--format VDI|VMDK|VHD]\n" + " [--variant Standard,Fixed,Split2G,Stream,ESX]\n" + " [--uuid <uuid>]\n" + "%s convertfromraw %s stdin <outputfile> <bytes>\n" + " [--format VDI|VMDK|VHD]\n" + " [--variant Standard,Fixed,Split2G,Stream,ESX]\n" + " [--uuid <uuid>]\n" + "\n", SEP, SEP); + + if (fCategory & USAGE_GETEXTRADATA) + RTStrmPrintf(pStrm, + "%s getextradata %s global|<uuid|vmname>\n" + " <key>|[enumerate]\n" + "\n", SEP); + + if (fCategory & USAGE_SETEXTRADATA) + RTStrmPrintf(pStrm, + "%s setextradata %s global|<uuid|vmname>\n" + " <key>\n" + " [<value>] (no value deletes key)\n" + "\n", SEP); + + if (fCategory & USAGE_SETPROPERTY) + RTStrmPrintf(pStrm, + "%s setproperty %s machinefolder default|<folder> |\n" + " hwvirtexclusive on|off |\n" + " vrdeauthlibrary default|<library> |\n" + " websrvauthlibrary default|null|<library> |\n" + " vrdeextpack null|<library> |\n" + " autostartdbpath null|<folder> |\n" + " loghistorycount <value>\n" + " defaultfrontend default|<name>\n" + " logginglevel <log setting>\n" + " proxymode system|noproxy|manual\n" + " proxyurl <url>\n" + "\n", SEP); + + if (fCategory & USAGE_USBFILTER_ADD) + RTStrmPrintf(pStrm, + "%s usbfilter %s add <index,0-N>\n" + " --target <uuid|vmname>|global\n" + " --name <string>\n" + " --action ignore|hold (global filters only)\n" + " [--active yes|no] (yes)\n" + " [--vendorid <XXXX>] (null)\n" + " [--productid <XXXX>] (null)\n" + " [--revision <IIFF>] (null)\n" + " [--manufacturer <string>] (null)\n" + " [--product <string>] (null)\n" + " [--remote yes|no] (null, VM filters only)\n" + " [--serialnumber <string>] (null)\n" + " [--maskedinterfaces <XXXXXXXX>]\n" + "\n", SEP); + + if (fCategory & USAGE_USBFILTER_MODIFY) + RTStrmPrintf(pStrm, + "%s usbfilter %s modify <index,0-N>\n" + " --target <uuid|vmname>|global\n" + " [--name <string>]\n" + " [--action ignore|hold] (global filters only)\n" + " [--active yes|no]\n" + " [--vendorid <XXXX>|\"\"]\n" + " [--productid <XXXX>|\"\"]\n" + " [--revision <IIFF>|\"\"]\n" + " [--manufacturer <string>|\"\"]\n" + " [--product <string>|\"\"]\n" + " [--remote yes|no] (null, VM filters only)\n" + " [--serialnumber <string>|\"\"]\n" + " [--maskedinterfaces <XXXXXXXX>]\n" + "\n", SEP); + + if (fCategory & USAGE_USBFILTER_REMOVE) + RTStrmPrintf(pStrm, + "%s usbfilter %s remove <index,0-N>\n" + " --target <uuid|vmname>|global\n" + "\n", SEP); + + if (fCategory & USAGE_SHAREDFOLDER_ADD) + RTStrmPrintf(pStrm, + "%s sharedfolder %s add <uuid|vmname>\n" + " --name <name> --hostpath <hostpath>\n" + " [--transient] [--readonly] [--automount]\n" + "\n", SEP); + + if (fCategory & USAGE_SHAREDFOLDER_REMOVE) + RTStrmPrintf(pStrm, + "%s sharedfolder %s remove <uuid|vmname>\n" + " --name <name> [--transient]\n" + "\n", SEP); + +#ifdef VBOX_WITH_GUEST_PROPS + if (fCategory & USAGE_GUESTPROPERTY) + usageGuestProperty(pStrm, SEP); +#endif /* VBOX_WITH_GUEST_PROPS defined */ + +#ifdef VBOX_WITH_GUEST_CONTROL + if (fCategory & USAGE_GUESTCONTROL) + usageGuestControl(pStrm, SEP, fSubCategory); +#endif /* VBOX_WITH_GUEST_CONTROL defined */ + + if (fCategory & USAGE_METRICS) + RTStrmPrintf(pStrm, + "%s metrics %s list [*|host|<vmname> [<metric_list>]]\n" + " (comma-separated)\n\n" + "%s metrics %s setup\n" + " [--period <seconds>] (default: 1)\n" + " [--samples <count>] (default: 1)\n" + " [--list]\n" + " [*|host|<vmname> [<metric_list>]]\n\n" + "%s metrics %s query [*|host|<vmname> [<metric_list>]]\n\n" + "%s metrics %s enable\n" + " [--list]\n" + " [*|host|<vmname> [<metric_list>]]\n\n" + "%s metrics %s disable\n" + " [--list]\n" + " [*|host|<vmname> [<metric_list>]]\n\n" + "%s metrics %s collect\n" + " [--period <seconds>] (default: 1)\n" + " [--samples <count>] (default: 1)\n" + " [--list]\n" + " [--detach]\n" + " [*|host|<vmname> [<metric_list>]]\n" + "\n", SEP, SEP, SEP, SEP, SEP, SEP); + +#if defined(VBOX_WITH_NAT_SERVICE) + if (fCategory & USAGE_NATNETWORK) + { + RTStrmPrintf(pStrm, + "%s natnetwork %s add --netname <name>\n" + " --network <network>\n" + " [--enable|--disable]\n" + " [--dhcp on|off]\n" + " [--port-forward-4 <rule>]\n" + " [--loopback-4 <rule>]\n" + " [--ipv6 on|off]\n" + " [--port-forward-6 <rule>]\n" + " [--loopback-6 <rule>]\n\n" + "%s natnetwork %s remove --netname <name>\n\n" + "%s natnetwork %s modify --netname <name>\n" + " [--network <network>]\n" + " [--enable|--disable]\n" + " [--dhcp on|off]\n" + " [--port-forward-4 <rule>]\n" + " [--loopback-4 <rule>]\n" + " [--ipv6 on|off]\n" + " [--port-forward-6 <rule>]\n" + " [--loopback-6 <rule>]\n\n" + "%s natnetwork %s start --netname <name>\n\n" + "%s natnetwork %s stop --netname <name>\n\n" + "%s natnetwork %s list [<pattern>]\n" + "\n", SEP, SEP, SEP, SEP, SEP, SEP); + + + } +#endif + +#if defined(VBOX_WITH_NETFLT) + if (fCategory & USAGE_HOSTONLYIFS) + { + RTStrmPrintf(pStrm, + "%s hostonlyif %s ipconfig <name>\n" + " [--dhcp |\n" + " --ip<ipv4> [--netmask<ipv4> (def: 255.255.255.0)] |\n" + " --ipv6<ipv6> [--netmasklengthv6<length> (def: 64)]]\n" +# if !defined(RT_OS_SOLARIS) || defined(VBOX_ONLY_DOCS) + " create |\n" + " remove <name>\n" +# endif + "\n", SEP); + } +#endif + + if (fCategory & USAGE_DHCPSERVER) + { + RTStrmPrintf(pStrm, + "%s dhcpserver %s add|modify --netname <network_name> |\n" +#if defined(VBOX_WITH_NETFLT) + " --ifname <hostonly_if_name>\n" +#endif + " [--ip <ip_address>\n" + " --netmask <network_mask>\n" + " --lowerip <lower_ip>\n" + " --upperip <upper_ip>]\n" + " [--enable | --disable]\n" + " [--options [--vm <name> --nic <1-N>]\n" + " --id <number> [--value <string> | --remove]]\n" + " (multiple options allowed after --options)\n\n" + "%s dhcpserver %s remove --netname <network_name> |\n" +#if defined(VBOX_WITH_NETFLT) + " --ifname <hostonly_if_name>\n" +#endif + "\n", SEP, SEP); + } + + if (fCategory & USAGE_USBDEVSOURCE) + { + RTStrmPrintf(pStrm, + "%s usbdevsource %s add <source name>\n" + " --backend <backend>\n" + " --address <address>\n" + "%s usbdevsource %s remove <source name>\n" + "\n", SEP, SEP); + } + +#ifndef VBOX_ONLY_DOCS /* Converted to man page, not needed. */ + if (fCategory == USAGE_ALL) + { + uint32_t cPendingBlankLines = 0; + for (uint32_t i = 0; i < g_cHelpEntries; i++) + { + PCRTMSGREFENTRY pHelp = g_apHelpEntries[i]; + while (cPendingBlankLines-- > 0) + RTStrmPutCh(pStrm, '\n'); + RTStrmPrintf(pStrm, " %c%s:\n", RT_C_TO_UPPER(pHelp->pszBrief[0]), pHelp->pszBrief + 1); + cPendingBlankLines = 0; + RTMsgRefEntryPrintStringTable(pStrm, &pHelp->Synopsis, RTMSGREFENTRYSTR_SCOPE_GLOBAL, + &cPendingBlankLines, NULL /*pcLinesWritten*/); + cPendingBlankLines = RT_MAX(cPendingBlankLines, 1); + } + } +#endif +} + +/** + * Print a usage synopsis and the syntax error message. + * @returns RTEXITCODE_SYNTAX. + */ +RTEXITCODE errorSyntax(USAGECATEGORY fCategory, const char *pszFormat, ...) +{ + va_list args; + showLogo(g_pStdErr); // show logo even if suppressed +#ifndef VBOX_ONLY_DOCS + if (g_fInternalMode) + printUsageInternal(fCategory, g_pStdErr); + else + printUsage(fCategory, ~0U, g_pStdErr); +#else + RT_NOREF_PV(fCategory); +#endif + va_start(args, pszFormat); + RTStrmPrintf(g_pStdErr, "\nSyntax error: %N\n", pszFormat, &args); + va_end(args); + return RTEXITCODE_SYNTAX; +} + +/** + * Print a usage synopsis and the syntax error message. + * @returns RTEXITCODE_SYNTAX. + */ +RTEXITCODE errorSyntaxEx(USAGECATEGORY fCategory, uint32_t fSubCategory, const char *pszFormat, ...) +{ + va_list args; + showLogo(g_pStdErr); // show logo even if suppressed +#ifndef VBOX_ONLY_DOCS + if (g_fInternalMode) + printUsageInternal(fCategory, g_pStdErr); + else + printUsage(fCategory, fSubCategory, g_pStdErr); +#else + RT_NOREF2(fCategory, fSubCategory); +#endif + va_start(args, pszFormat); + RTStrmPrintf(g_pStdErr, "\nSyntax error: %N\n", pszFormat, &args); + va_end(args); + return RTEXITCODE_SYNTAX; +} + +/** + * errorSyntax for RTGetOpt users. + * + * @returns RTEXITCODE_SYNTAX. + * + * @param fCategory The usage category of the command. + * @param fSubCategory The usage sub-category of the command. + * @param rc The RTGetOpt return code. + * @param pValueUnion The value union. + */ +RTEXITCODE errorGetOptEx(USAGECATEGORY fCategory, uint32_t fSubCategory, int rc, union RTGETOPTUNION const *pValueUnion) +{ + /* + * Check if it is an unhandled standard option. + */ +#ifndef VBOX_ONLY_DOCS + if (rc == 'V') + { + RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision()); + return RTEXITCODE_SUCCESS; + } +#endif + + if (rc == 'h') + { + showLogo(g_pStdErr); +#ifndef VBOX_ONLY_DOCS + if (g_fInternalMode) + printUsageInternal(fCategory, g_pStdOut); + else + printUsage(fCategory, fSubCategory, g_pStdOut); +#endif + return RTEXITCODE_SUCCESS; + } + + /* + * General failure. + */ + showLogo(g_pStdErr); // show logo even if suppressed +#ifndef VBOX_ONLY_DOCS + if (g_fInternalMode) + printUsageInternal(fCategory, g_pStdErr); + else + printUsage(fCategory, fSubCategory, g_pStdErr); +#else + RT_NOREF2(fCategory, fSubCategory); +#endif + + if (rc == VINF_GETOPT_NOT_OPTION) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid parameter '%s'", pValueUnion->psz); + if (rc > 0) + { + if (RT_C_IS_PRINT(rc)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid option -%c", rc); + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid option case %i", rc); + } + if (rc == VERR_GETOPT_UNKNOWN_OPTION) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option: %s", pValueUnion->psz); + if (rc == VERR_GETOPT_INVALID_ARGUMENT_FORMAT) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid argument format: %s", pValueUnion->psz); + if (pValueUnion->pDef) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "%s: %Rrs", pValueUnion->pDef->pszLong, rc); + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "%Rrs", rc); +} + +/** + * errorSyntax for RTGetOpt users. + * + * @returns RTEXITCODE_SYNTAX. + * + * @param fUsageCategory The usage category of the command. + * @param rc The RTGetOpt return code. + * @param pValueUnion The value union. + */ +RTEXITCODE errorGetOpt(USAGECATEGORY fCategory, int rc, union RTGETOPTUNION const *pValueUnion) +{ + return errorGetOptEx(fCategory, ~0U, rc, pValueUnion); +} + +/** + * Print an error message without the syntax stuff. + * + * @returns RTEXITCODE_SYNTAX. + */ +RTEXITCODE errorArgument(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + RTMsgErrorV(pszFormat, args); + va_end(args); + return RTEXITCODE_SYNTAX; +} + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageHostonly.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageHostonly.cpp new file mode 100644 index 00000000..401a80dc --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageHostonly.cpp @@ -0,0 +1,283 @@ +/* $Id: VBoxManageHostonly.cpp $ */ +/** @file + * VBoxManage - Implementation of hostonlyif command. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef VBOX_ONLY_DOCS +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> +#endif /* !VBOX_ONLY_DOCS */ + +#include <iprt/cidr.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/net.h> +#include <iprt/getopt.h> +#include <iprt/ctype.h> + +#include <VBox/log.h> + +#include "VBoxManage.h" + +#ifndef VBOX_ONLY_DOCS +using namespace com; + +static const RTGETOPTDEF g_aHostOnlyCreateOptions[] = +{ + { "--machinereadable", 'M', RTGETOPT_REQ_NOTHING }, +}; + +#if defined(VBOX_WITH_NETFLT) && !defined(RT_OS_SOLARIS) +static RTEXITCODE handleCreate(HandlerArg *a) +{ + /* + * Parse input. + */ + bool fMachineReadable = false; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, a->argc, a->argv, g_aHostOnlyCreateOptions, + RT_ELEMENTS(g_aHostOnlyCreateOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + int c; + while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (c) + { + case 'M': // --machinereadable + fMachineReadable = true; + break; + + default: + return errorGetOpt(USAGE_HOSTONLYIFS, c, &ValueUnion); + } + } + + /* + * Do the work. + */ + ComPtr<IHost> host; + CHECK_ERROR2I_RET(a->virtualBox, COMGETTER(Host)(host.asOutParam()), RTEXITCODE_FAILURE); + + ComPtr<IHostNetworkInterface> hif; + ComPtr<IProgress> progress; + + CHECK_ERROR2I_RET(host, CreateHostOnlyNetworkInterface(hif.asOutParam(), progress.asOutParam()), RTEXITCODE_FAILURE); + + if (fMachineReadable) + { + CHECK_PROGRESS_ERROR_RET(progress, (""), RTEXITCODE_FAILURE); + } + else + { + /*HRESULT hrc =*/ showProgress(progress); + CHECK_PROGRESS_ERROR_RET(progress, ("Failed to create the host-only adapter"), RTEXITCODE_FAILURE); + } + + Bstr bstrName; + CHECK_ERROR2I(hif, COMGETTER(Name)(bstrName.asOutParam())); + + if (fMachineReadable) + RTPrintf("%ls", bstrName.raw()); + else + RTPrintf("Interface '%ls' was successfully created\n", bstrName.raw()); + return RTEXITCODE_SUCCESS; +} + +static RTEXITCODE handleRemove(HandlerArg *a) +{ + /* + * Parse input. + */ + const char *pszName = NULL; + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, a->argc, a->argv, NULL, 0, 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + switch (ch) + { + case VINF_GETOPT_NOT_OPTION: + if (pszName) + return errorSyntax(USAGE_HOSTONLYIFS, "Only one interface name can be specified"); + pszName = ValueUnion.psz; + break; + + default: + return errorGetOpt(USAGE_HOSTONLYIFS, ch, &ValueUnion); + } + if (!pszName) + return errorSyntax(USAGE_HOSTONLYIFS, "No interface name was specified"); + + /* + * Do the work. + */ + ComPtr<IHost> host; + CHECK_ERROR2I_RET(a->virtualBox, COMGETTER(Host)(host.asOutParam()), RTEXITCODE_FAILURE); + + ComPtr<IHostNetworkInterface> hif; + CHECK_ERROR2I_RET(host, FindHostNetworkInterfaceByName(Bstr(pszName).raw(), hif.asOutParam()), RTEXITCODE_FAILURE); + + Bstr guid; + CHECK_ERROR2I_RET(hif, COMGETTER(Id)(guid.asOutParam()), RTEXITCODE_FAILURE); + + ComPtr<IProgress> progress; + CHECK_ERROR2I_RET(host, RemoveHostOnlyNetworkInterface(guid.raw(), progress.asOutParam()), RTEXITCODE_FAILURE); + + /*HRESULT hrc =*/ showProgress(progress); + CHECK_PROGRESS_ERROR_RET(progress, ("Failed to remove the host-only adapter"), RTEXITCODE_FAILURE); + + return RTEXITCODE_SUCCESS; +} +#endif + +static const RTGETOPTDEF g_aHostOnlyIPOptions[] + = { + { "--dhcp", 'd', RTGETOPT_REQ_NOTHING }, + { "-dhcp", 'd', RTGETOPT_REQ_NOTHING }, // deprecated + { "--ip", 'a', RTGETOPT_REQ_STRING }, + { "-ip", 'a', RTGETOPT_REQ_STRING }, // deprecated + { "--netmask", 'm', RTGETOPT_REQ_STRING }, + { "-netmask", 'm', RTGETOPT_REQ_STRING }, // deprecated + { "--ipv6", 'b', RTGETOPT_REQ_STRING }, + { "-ipv6", 'b', RTGETOPT_REQ_STRING }, // deprecated + { "--netmasklengthv6", 'l', RTGETOPT_REQ_UINT8 }, + { "-netmasklengthv6", 'l', RTGETOPT_REQ_UINT8 } // deprecated + }; + +static RTEXITCODE handleIpConfig(HandlerArg *a) +{ + bool fDhcp = false; + bool fNetmasklengthv6 = false; + uint32_t uNetmasklengthv6 = UINT32_MAX; + const char *pszIpv6 = NULL; + const char *pszIp = NULL; + const char *pszNetmask = NULL; + const char *pszName = NULL; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, a->argc, a->argv, g_aHostOnlyIPOptions, RT_ELEMENTS(g_aHostOnlyIPOptions), + 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (c) + { + case 'd': // --dhcp + fDhcp = true; + break; + case 'a': // --ip + if (pszIp) + RTMsgWarning("The --ip option is specified more than once"); + pszIp = ValueUnion.psz; + break; + case 'm': // --netmask + if (pszNetmask) + RTMsgWarning("The --netmask option is specified more than once"); + pszNetmask = ValueUnion.psz; + break; + case 'b': // --ipv6 + if (pszIpv6) + RTMsgWarning("The --ipv6 option is specified more than once"); + pszIpv6 = ValueUnion.psz; + break; + case 'l': // --netmasklengthv6 + if (fNetmasklengthv6) + RTMsgWarning("The --netmasklengthv6 option is specified more than once"); + fNetmasklengthv6 = true; + uNetmasklengthv6 = ValueUnion.u8; + break; + case VINF_GETOPT_NOT_OPTION: + if (pszName) + return errorSyntax(USAGE_HOSTONLYIFS, "Only one interface name can be specified"); + pszName = ValueUnion.psz; + break; + default: + return errorGetOpt(USAGE_HOSTONLYIFS, c, &ValueUnion); + } + } + + /* parameter sanity check */ + if (fDhcp && (fNetmasklengthv6 || pszIpv6 || pszIp || pszNetmask)) + return errorSyntax(USAGE_HOSTONLYIFS, "You can not use --dhcp with static ip configuration parameters: --ip, --netmask, --ipv6 and --netmasklengthv6."); + if ((pszIp || pszNetmask) && (fNetmasklengthv6 || pszIpv6)) + return errorSyntax(USAGE_HOSTONLYIFS, "You can not use ipv4 configuration (--ip and --netmask) with ipv6 (--ipv6 and --netmasklengthv6) simultaneously."); + + ComPtr<IHost> host; + CHECK_ERROR2I_RET(a->virtualBox, COMGETTER(Host)(host.asOutParam()), RTEXITCODE_FAILURE); + + ComPtr<IHostNetworkInterface> hif; + CHECK_ERROR2I_RET(host, FindHostNetworkInterfaceByName(Bstr(pszName).raw(), hif.asOutParam()), RTEXITCODE_FAILURE); + if (hif.isNull()) + return errorArgument("Could not find interface '%s'", pszName); + + if (fDhcp) + CHECK_ERROR2I_RET(hif, EnableDynamicIPConfig(), RTEXITCODE_FAILURE); + else if (pszIp) + { + if (!pszNetmask) + pszNetmask = "255.255.255.0"; /* ?? */ + CHECK_ERROR2I_RET(hif, EnableStaticIPConfig(Bstr(pszIp).raw(), Bstr(pszNetmask).raw()), RTEXITCODE_FAILURE); + } + else if (pszIpv6) + { + BOOL fIpV6Supported; + CHECK_ERROR2I_RET(hif, COMGETTER(IPV6Supported)(&fIpV6Supported), RTEXITCODE_FAILURE); + if (!fIpV6Supported) + { + RTMsgError("IPv6 setting is not supported for this adapter"); + return RTEXITCODE_FAILURE; + } + + if (uNetmasklengthv6 == UINT32_MAX) + uNetmasklengthv6 = 64; /* ?? */ + CHECK_ERROR2I_RET(hif, EnableStaticIPConfigV6(Bstr(pszIpv6).raw(), (ULONG)uNetmasklengthv6), RTEXITCODE_FAILURE); + } + else + return errorSyntax(USAGE_HOSTONLYIFS, "Neither -dhcp nor -ip nor -ipv6 was specfified"); + + return RTEXITCODE_SUCCESS; +} + + +RTEXITCODE handleHostonlyIf(HandlerArg *a) +{ + if (a->argc < 1) + return errorSyntax(USAGE_HOSTONLYIFS, "No sub-command specified"); + + RTEXITCODE rcExit; + if (!strcmp(a->argv[0], "ipconfig")) + rcExit = handleIpConfig(a); +#if defined(VBOX_WITH_NETFLT) && !defined(RT_OS_SOLARIS) + else if (!strcmp(a->argv[0], "create")) + rcExit = handleCreate(a); + else if (!strcmp(a->argv[0], "remove")) + rcExit = handleRemove(a); +#endif + else + rcExit = errorSyntax(USAGE_HOSTONLYIFS, "Unknown sub-command '%s'", a->argv[0]); + return rcExit; +} + +#endif /* !VBOX_ONLY_DOCS */ diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageInfo.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageInfo.cpp new file mode 100644 index 00000000..afc489a3 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageInfo.cpp @@ -0,0 +1,2691 @@ +/* $Id: VBoxManageInfo.cpp $ */ +/** @file + * VBoxManage - The 'showvminfo' command and helper routines. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_ONLY_DOCS + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> + +#include <VBox/com/VirtualBox.h> + +#ifdef VBOX_WITH_PCI_PASSTHROUGH +#include <VBox/pci.h> +#endif + +#include <VBox/log.h> +#include <VBox/version.h> +#include <iprt/stream.h> +#include <iprt/time.h> +#include <iprt/string.h> +#include <iprt/getopt.h> +#include <iprt/ctype.h> + +#include "VBoxManage.h" +using namespace com; + + +// funcs +/////////////////////////////////////////////////////////////////////////////// + +/** + * Helper for formatting an indexed name or some such thing. + */ +static const char *FmtNm(char psz[80], const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + RTStrPrintfV(psz, 80, pszFormat, va); + va_end(va); + return psz; +} + +HRESULT showSnapshots(ComPtr<ISnapshot> &rootSnapshot, + ComPtr<ISnapshot> ¤tSnapshot, + VMINFO_DETAILS details, + const Utf8Str &prefix /* = ""*/, + int level /*= 0*/) +{ + /* start with the root */ + Bstr name; + Bstr uuid; + Bstr description; + CHECK_ERROR2I_RET(rootSnapshot, COMGETTER(Name)(name.asOutParam()), hrcCheck); + CHECK_ERROR2I_RET(rootSnapshot, COMGETTER(Id)(uuid.asOutParam()), hrcCheck); + CHECK_ERROR2I_RET(rootSnapshot, COMGETTER(Description)(description.asOutParam()), hrcCheck); + bool fCurrent = (rootSnapshot == currentSnapshot); + if (details == VMINFO_MACHINEREADABLE) + { + /* print with hierarchical numbering */ + RTPrintf("SnapshotName%s=\"%ls\"\n", prefix.c_str(), name.raw()); + RTPrintf("SnapshotUUID%s=\"%s\"\n", prefix.c_str(), Utf8Str(uuid).c_str()); + if (!description.isEmpty()) + RTPrintf("SnapshotDescription%s=\"%ls\"\n", prefix.c_str(), description.raw()); + if (fCurrent) + { + RTPrintf("CurrentSnapshotName=\"%ls\"\n", name.raw()); + RTPrintf("CurrentSnapshotUUID=\"%s\"\n", Utf8Str(uuid).c_str()); + RTPrintf("CurrentSnapshotNode=\"SnapshotName%s\"\n", prefix.c_str()); + } + } + else + { + /* print with indentation */ + RTPrintf(" %sName: %ls (UUID: %s)%s\n", + prefix.c_str(), + name.raw(), + Utf8Str(uuid).c_str(), + (fCurrent) ? " *" : ""); + if (!description.isEmpty()) + RTPrintf(" %sDescription:\n%ls\n", prefix.c_str(), description.raw()); + } + + /* get the children */ + HRESULT hrc = S_OK; + SafeIfaceArray <ISnapshot> coll; + CHECK_ERROR2I_RET(rootSnapshot,COMGETTER(Children)(ComSafeArrayAsOutParam(coll)), hrcCheck); + if (!coll.isNull()) + { + for (size_t index = 0; index < coll.size(); ++index) + { + ComPtr<ISnapshot> snapshot = coll[index]; + if (snapshot) + { + Utf8Str newPrefix; + if (details == VMINFO_MACHINEREADABLE) + newPrefix = Utf8StrFmt("%s-%d", prefix.c_str(), index + 1); + else + { + newPrefix = Utf8StrFmt("%s ", prefix.c_str()); + } + + /* recursive call */ + HRESULT hrc2 = showSnapshots(snapshot, currentSnapshot, details, newPrefix, level + 1); + if (FAILED(hrc2)) + hrc = hrc2; + } + } + } + return hrc; +} + +static void makeTimeStr(char *s, int cb, int64_t millies) +{ + RTTIME t; + RTTIMESPEC ts; + + RTTimeSpecSetMilli(&ts, millies); + + RTTimeExplode(&t, &ts); + + RTStrPrintf(s, cb, "%04d/%02d/%02d %02d:%02d:%02d UTC", + t.i32Year, t.u8Month, t.u8MonthDay, + t.u8Hour, t.u8Minute, t.u8Second); +} + +const char *machineStateToName(MachineState_T machineState, bool fShort) +{ + switch (machineState) + { + case MachineState_PoweredOff: + return fShort ? "poweroff" : "powered off"; + case MachineState_Saved: + return "saved"; + case MachineState_Teleported: + return "teleported"; + case MachineState_Aborted: + return "aborted"; + case MachineState_Running: + return "running"; + case MachineState_Paused: + return "paused"; + case MachineState_Stuck: + return fShort ? "gurumeditation" : "guru meditation"; + case MachineState_Teleporting: + return "teleporting"; + case MachineState_LiveSnapshotting: + return fShort ? "livesnapshotting" : "live snapshotting"; + case MachineState_Starting: + return "starting"; + case MachineState_Stopping: + return "stopping"; + case MachineState_Saving: + return "saving"; + case MachineState_Restoring: + return "restoring"; + case MachineState_TeleportingPausedVM: + return fShort ? "teleportingpausedvm" : "teleporting paused vm"; + case MachineState_TeleportingIn: + return fShort ? "teleportingin" : "teleporting (incoming)"; + case MachineState_FaultTolerantSyncing: + return fShort ? "faulttolerantsyncing" : "fault tolerant syncing"; + case MachineState_DeletingSnapshotOnline: + return fShort ? "deletingsnapshotlive" : "deleting snapshot live"; + case MachineState_DeletingSnapshotPaused: + return fShort ? "deletingsnapshotlivepaused" : "deleting snapshot live paused"; + case MachineState_OnlineSnapshotting: + return fShort ? "onlinesnapshotting" : "online snapshotting"; + case MachineState_RestoringSnapshot: + return fShort ? "restoringsnapshot" : "restoring snapshot"; + case MachineState_DeletingSnapshot: + return fShort ? "deletingsnapshot" : "deleting snapshot"; + case MachineState_SettingUp: + return fShort ? "settingup" : "setting up"; + case MachineState_Snapshotting: + return fShort ? "snapshotting" : "offline snapshotting"; + default: + break; + } + return "unknown"; +} + +const char *facilityStateToName(AdditionsFacilityStatus_T faStatus, bool fShort) +{ + switch (faStatus) + { + case AdditionsFacilityStatus_Inactive: + return fShort ? "inactive" : "not active"; + case AdditionsFacilityStatus_Paused: + return "paused"; + case AdditionsFacilityStatus_PreInit: + return fShort ? "preinit" : "pre-initializing"; + case AdditionsFacilityStatus_Init: + return fShort ? "init" : "initializing"; + case AdditionsFacilityStatus_Active: + return fShort ? "active" : "active/running"; + case AdditionsFacilityStatus_Terminating: + return "terminating"; + case AdditionsFacilityStatus_Terminated: + return "terminated"; + case AdditionsFacilityStatus_Failed: + return "failed"; + case AdditionsFacilityStatus_Unknown: + default: + break; + } + return "unknown"; +} + +/** + * This takes care of escaping double quotes and slashes that the string might + * contain. + * + * @param pszName The variable name. + * @param pszValue The value. + */ +static void outputMachineReadableString(const char *pszName, const char *pszValue) +{ + Assert(strpbrk(pszName, "\"\\") == NULL); + + if ( !pszValue + || !*pszValue + || ( strchr(pszValue, '"') == NULL + && strchr(pszValue, '\\') == NULL) ) + RTPrintf("%s=\"%s\"\n", pszName, pszValue); + else + { + /* The value needs escaping. */ + RTPrintf("%s=\"", pszName); + const char *psz = pszValue; + for (;;) + { + const char *pszNext = strpbrk(psz, "\"\\"); + if (!pszNext) + { + RTPrintf("%s", psz); + break; + } + RTPrintf("%.*s\\%c", pszNext - psz, psz, *pszNext); + psz = pszNext + 1; + } + RTPrintf("\"\n"); + } +} + + +/** + * This takes care of escaping double quotes and slashes that the string might + * contain. + * + * @param pszName The variable name. + * @param pbstrValue The value. + */ +static void outputMachineReadableString(const char *pszName, Bstr const *pbstrValue) +{ + com::Utf8Str strValue(*pbstrValue); + outputMachineReadableString(pszName, strValue.c_str()); +} + +/** + * Converts bandwidth group type to a string. + * @returns String representation. + * @param enmType Bandwidth control group type. + */ +static const char * bwGroupTypeToString(BandwidthGroupType_T enmType) +{ + switch (enmType) + { + case BandwidthGroupType_Null: return "Null"; + case BandwidthGroupType_Disk: return "Disk"; + case BandwidthGroupType_Network: return "Network"; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case BandwidthGroupType_32BitHack: break; /* Shut up compiler warnings. */ +#endif + } + return "unknown"; +} + +HRESULT showBandwidthGroups(ComPtr<IBandwidthControl> &bwCtrl, + VMINFO_DETAILS details) +{ + int rc = S_OK; + SafeIfaceArray<IBandwidthGroup> bwGroups; + + CHECK_ERROR_RET(bwCtrl, GetAllBandwidthGroups(ComSafeArrayAsOutParam(bwGroups)), rc); + + if (bwGroups.size() && details != VMINFO_MACHINEREADABLE) + RTPrintf("\n\n"); + for (size_t i = 0; i < bwGroups.size(); i++) + { + Bstr strName; + LONG64 cMaxBytesPerSec; + BandwidthGroupType_T enmType; + + CHECK_ERROR_RET(bwGroups[i], COMGETTER(Name)(strName.asOutParam()), rc); + CHECK_ERROR_RET(bwGroups[i], COMGETTER(Type)(&enmType), rc); + CHECK_ERROR_RET(bwGroups[i], COMGETTER(MaxBytesPerSec)(&cMaxBytesPerSec), rc); + + const char *pszType = bwGroupTypeToString(enmType); + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("BandwidthGroup%zu=%ls,%s,%lld\n", i, strName.raw(), pszType, cMaxBytesPerSec); + else + { + const char *pszUnits = ""; + LONG64 cBytes = cMaxBytesPerSec; + if (cBytes == 0) + { + RTPrintf("Name: '%ls', Type: %s, Limit: none (disabled)\n", strName.raw(), pszType); + continue; + } + else if (!(cBytes % _1G)) + { + pszUnits = "G"; + cBytes /= _1G; + } + else if (!(cBytes % _1M)) + { + pszUnits = "M"; + cBytes /= _1M; + } + else if (!(cBytes % _1K)) + { + pszUnits = "K"; + cBytes /= _1K; + } + const char *pszNetUnits = NULL; + if (enmType == BandwidthGroupType_Network) + { + /* + * We want to report network rate limit in bits/s, not bytes. + * Only if it cannot be express it in kilobits we will fall + * back to reporting it in bytes. + */ + LONG64 cBits = cMaxBytesPerSec; + if (!(cBits % 125)) + { + cBits /= 125; + pszNetUnits = "k"; + if (!(cBits % 1000000)) + { + cBits /= 1000000; + pszNetUnits = "g"; + } + else if (!(cBits % 1000)) + { + cBits /= 1000; + pszNetUnits = "m"; + } + RTPrintf("Name: '%ls', Type: %s, Limit: %lld %sbits/sec (%lld %sbytes/sec)\n", strName.raw(), pszType, cBits, pszNetUnits, cBytes, pszUnits); + } + } + if (!pszNetUnits) + RTPrintf("Name: '%ls', Type: %s, Limit: %lld %sbytes/sec\n", strName.raw(), pszType, cBytes, pszUnits); + } + } + if (details != VMINFO_MACHINEREADABLE) + RTPrintf(bwGroups.size() != 0 ? "\n" : "<none>\n\n"); + + return rc; +} + +/** Shows a shared folder. */ +static HRESULT showSharedFolder(ComPtr<ISharedFolder> &sf, VMINFO_DETAILS details, const char *pszDesc, + const char *pszMrInfix, size_t idxMr, bool fFirst) +{ + Bstr name, hostPath, bstrAutoMountPoint; + BOOL writable = FALSE, fAutoMount = FALSE; + CHECK_ERROR2I_RET(sf, COMGETTER(Name)(name.asOutParam()), hrcCheck); + CHECK_ERROR2I_RET(sf, COMGETTER(HostPath)(hostPath.asOutParam()), hrcCheck); + CHECK_ERROR2I_RET(sf, COMGETTER(Writable)(&writable), hrcCheck); + CHECK_ERROR2I_RET(sf, COMGETTER(AutoMount)(&fAutoMount), hrcCheck); + CHECK_ERROR2I_RET(sf, COMGETTER(AutoMountPoint)(bstrAutoMountPoint.asOutParam()), hrcCheck); + + if (fFirst && details != VMINFO_MACHINEREADABLE) + RTPrintf("\n\n"); + if (details == VMINFO_MACHINEREADABLE) + { + char szNm[80]; + outputMachineReadableString(FmtNm(szNm, "SharedFolderName%s%zu", pszMrInfix, idxMr), &name); + outputMachineReadableString(FmtNm(szNm, "SharedFolderPath%s%zu", pszMrInfix, idxMr), &hostPath); + } + else + { + RTPrintf("Name: '%ls', Host path: '%ls' (%s), %s%s", + name.raw(), hostPath.raw(), pszDesc, writable ? "writable" : "readonly", fAutoMount ? ", auto-mount" : ""); + if (bstrAutoMountPoint.isNotEmpty()) + RTPrintf(", mount-point: '%ls'\n", bstrAutoMountPoint.raw()); + else + RTPrintf("\n"); + } + return S_OK; +} + + +static const char *paravirtProviderToString(ParavirtProvider_T provider, VMINFO_DETAILS details) +{ + switch (provider) + { + case ParavirtProvider_None: + if (details == VMINFO_MACHINEREADABLE) + return "none"; + return "None"; + + case ParavirtProvider_Default: + if (details == VMINFO_MACHINEREADABLE) + return "default"; + return "Default"; + + case ParavirtProvider_Legacy: + if (details == VMINFO_MACHINEREADABLE) + return "legacy"; + return "Legacy"; + + case ParavirtProvider_Minimal: + if (details == VMINFO_MACHINEREADABLE) + return "minimal"; + return "Minimal"; + + case ParavirtProvider_HyperV: + if (details == VMINFO_MACHINEREADABLE) + return "hyperv"; + return "HyperV"; + + case ParavirtProvider_KVM: + if (details == VMINFO_MACHINEREADABLE) + return "kvm"; + return "KVM"; + + default: + if (details == VMINFO_MACHINEREADABLE) + return "unknown"; + return "Unknown"; + } +} + + +/* Disable global optimizations for MSC 8.0/64 to make it compile in reasonable + time. MSC 7.1/32 doesn't have quite as much trouble with it, but still + sufficient to qualify for this hack as well since this code isn't performance + critical and probably won't gain much from the extra optimizing in real life. */ +#if defined(_MSC_VER) +# pragma optimize("g", off) +# pragma warning(push) +# if _MSC_VER < RT_MSC_VER_VC120 +# pragma warning(disable: 4748) +# endif +#endif + +HRESULT showVMInfo(ComPtr<IVirtualBox> pVirtualBox, + ComPtr<IMachine> machine, + ComPtr<ISession> pSession, + VMINFO_DETAILS details /*= VMINFO_NONE*/) +{ + HRESULT rc; + ComPtr<IConsole> pConsole; + if (pSession) + pSession->COMGETTER(Console)(pConsole.asOutParam()); + + char szNm[80]; + char szValue[256]; + +#define SHOW_UTF8_STRING(a_pszMachine, a_pszHuman, a_szValue) \ + do \ + { \ + Assert(a_pszHuman[strlen(a_pszHuman) - 1] == ':'); \ + if (details == VMINFO_MACHINEREADABLE) \ + outputMachineReadableString(a_pszMachine, a_szValue); \ + else \ + RTPrintf("%-28s %s\n", a_pszHuman, a_szValue); \ + } while (0) + +#define SHOW_BSTR_STRING(a_pszMachine, a_pszHuman, a_bstrValue) \ + do \ + { \ + Assert(a_pszHuman[strlen(a_pszHuman) - 1] == ':'); \ + if (details == VMINFO_MACHINEREADABLE) \ + outputMachineReadableString(a_pszMachine, &a_bstrValue); \ + else \ + RTPrintf("%-28s %ls\n", a_pszHuman, a_bstrValue.raw()); \ + } while (0) + +#define SHOW_BOOL_VALUE_EX(a_pszMachine, a_pszHuman, a_fValue, a_szTrue, a_szFalse) \ + do \ + { \ + if (details == VMINFO_MACHINEREADABLE) \ + outputMachineReadableString(a_pszMachine, a_fValue ? "on" : "off"); \ + else \ + RTPrintf("%-28s %s\n", a_pszHuman, a_fValue ? a_szTrue: a_szFalse); \ + } while (0) + +#define SHOW_BOOL_VALUE(a_pszMachine, a_pszHuman, a_fValue) \ + SHOW_BOOL_VALUE_EX(a_pszMachine, a_pszHuman, a_fValue, "enabled", "disabled") + +#define SHOW_ULONG_VALUE(a_pszMachine, a_pszHuman, a_uValue, a_pszUnit) \ + do \ + { \ + if (details == VMINFO_MACHINEREADABLE) \ + RTPrintf("%s=%u\n", a_pszMachine, a_uValue); \ + else \ + RTPrintf("%-28s %u%s\n", a_pszHuman, a_uValue, a_pszUnit); \ + } while (0) + +#define SHOW_LONG64_VALUE(a_pszMachine, a_pszHuman, a_llValue, a_pszUnit) \ + do \ + { \ + if (details == VMINFO_MACHINEREADABLE) \ + RTPrintf("%s=%lld\n", a_pszMachine, a_llValue); \ + else \ + RTPrintf("%-28s %lld%s\n", a_pszHuman, a_llValue, a_pszUnit); \ + } while (0) + +#define SHOW_BOOLEAN_PROP(a_pObj, a_Prop, a_pszMachine, a_pszHuman) \ + SHOW_BOOLEAN_PROP_EX(a_pObj, a_Prop, a_pszMachine, a_pszHuman, "enabled", "disabled") + +#define SHOW_BOOLEAN_PROP_EX(a_pObj, a_Prop, a_pszMachine, a_pszHuman, a_szTrue, a_szFalse) \ + do \ + { \ + BOOL f; \ + CHECK_ERROR2I_RET(a_pObj, COMGETTER(a_Prop)(&f), hrcCheck); \ + if (details == VMINFO_MACHINEREADABLE) \ + outputMachineReadableString(a_pszMachine, f ? "on" : "off"); \ + else \ + RTPrintf("%-28s %s\n", a_pszHuman, f ? a_szTrue : a_szFalse); \ + } while (0) + +#define SHOW_BOOLEAN_METHOD(a_pObj, a_Invocation, a_pszMachine, a_pszHuman) \ + do \ + { \ + BOOL f; \ + CHECK_ERROR2I_RET(a_pObj, a_Invocation, hrcCheck); \ + if (details == VMINFO_MACHINEREADABLE) \ + outputMachineReadableString(a_pszMachine, f ? "on" : "off"); \ + else \ + RTPrintf("%-28s %s\n", a_pszHuman, f ? "enabled" : "disabled"); \ + } while (0) + +#define SHOW_STRING_PROP(a_pObj, a_Prop, a_pszMachine, a_pszHuman) \ + do \ + { \ + Bstr bstr; \ + CHECK_ERROR2I_RET(a_pObj, COMGETTER(a_Prop)(bstr.asOutParam()), hrcCheck); \ + if (details == VMINFO_MACHINEREADABLE) \ + outputMachineReadableString(a_pszMachine, &bstr); \ + else \ + RTPrintf("%-28s %ls\n", a_pszHuman, bstr.raw()); \ + } while (0) + +#define SHOW_STRING_PROP_NOT_EMPTY(a_pObj, a_Prop, a_pszMachine, a_pszHuman) \ + do \ + { \ + Bstr bstr; \ + CHECK_ERROR2I_RET(a_pObj, COMGETTER(a_Prop)(bstr.asOutParam()), hrcCheck); \ + if (bstr.isNotEmpty()) \ + { \ + if (details == VMINFO_MACHINEREADABLE) \ + outputMachineReadableString(a_pszMachine, &bstr); \ + else \ + RTPrintf("%-28s %ls\n", a_pszHuman, bstr.raw()); \ + } \ + } while (0) + + /** @def SHOW_STRING_PROP_MAJ + * For not breaking the output in a dot release we don't show default values. */ +#define SHOW_STRING_PROP_MAJ(a_pObj, a_Prop, a_pszMachine, a_pszHuman, a_pszUnless, a_uMajorVer) \ + do \ + { \ + Bstr bstr; \ + CHECK_ERROR2I_RET(a_pObj, COMGETTER(a_Prop)(bstr.asOutParam()), hrcCheck); \ + if ((a_uMajorVer) <= VBOX_VERSION_MAJOR || !bstr.equals(a_pszUnless)) \ + { \ + if (details == VMINFO_MACHINEREADABLE)\ + outputMachineReadableString(a_pszMachine, &bstr); \ + else \ + RTPrintf("%-28s %ls\n", a_pszHuman, bstr.raw()); \ + } \ + } while (0) + +#define SHOW_STRINGARRAY_PROP(a_pObj, a_Prop, a_pszMachine, a_pszHuman) \ + do \ + { \ + SafeArray<BSTR> array; \ + CHECK_ERROR2I_RET(a_pObj, COMGETTER(a_Prop)(ComSafeArrayAsOutParam(array)), hrcCheck); \ + Utf8Str str; \ + for (size_t i = 0; i < array.size(); i++) \ + { \ + if (i != 0) \ + str.append(","); \ + str.append(Utf8Str(array[i]).c_str()); \ + } \ + Bstr bstr(str); \ + if (details == VMINFO_MACHINEREADABLE) \ + outputMachineReadableString(a_pszMachine, &bstr); \ + else \ + RTPrintf("%-28s %ls\n", a_pszHuman, bstr.raw()); \ + } while (0) + +#define SHOW_UUID_PROP(a_pObj, a_Prop, a_pszMachine, a_pszHuman) \ + SHOW_STRING_PROP(a_pObj, a_Prop, a_pszMachine, a_pszHuman) + +#define SHOW_USHORT_PROP_EX2(a_pObj, a_Prop, a_pszMachine, a_pszHuman, a_pszUnit, a_szFmtMachine, a_szFmtHuman) \ + do \ + { \ + USHORT u16 = 0; \ + CHECK_ERROR2I_RET(a_pObj, COMGETTER(a_Prop)(&u16), hrcCheck); \ + if (details == VMINFO_MACHINEREADABLE) \ + RTPrintf("%s=" a_szFmtMachine "\n", a_pszMachine, u16); \ + else \ + RTPrintf("%-28s " a_szFmtHuman "%s\n", a_pszHuman, u16, u16, a_pszUnit); \ + } while (0) + +#define SHOW_ULONG_PROP(a_pObj, a_Prop, a_pszMachine, a_pszHuman, a_pszUnit) \ + do \ + { \ + ULONG u32 = 0; \ + CHECK_ERROR2I_RET(a_pObj, COMGETTER(a_Prop)(&u32), hrcCheck); \ + if (details == VMINFO_MACHINEREADABLE) \ + RTPrintf("%s=%u\n", a_pszMachine, u32); \ + else \ + RTPrintf("%-28s %u%s\n", a_pszHuman, u32, a_pszUnit); \ + } while (0) + +#define SHOW_LONG64_PROP(a_pObj, a_Prop, a_pszMachine, a_pszHuman, a_pszUnit) \ + do \ + { \ + LONG64 i64 = 0; \ + CHECK_ERROR2I_RET(a_pObj, COMGETTER(a_Prop)(&i64), hrcCheck); \ + if (details == VMINFO_MACHINEREADABLE) \ + RTPrintf("%s=%lld\n", a_pszMachine, i64); \ + else \ + RTPrintf("%-28s %'lld%s\n", a_pszHuman, i64, a_pszUnit); \ + } while (0) + + /* + * The rules for output in -argdump format: + * 1) the key part (the [0-9a-zA-Z_\-]+ string before the '=' delimiter) + * is all lowercase for "VBoxManage modifyvm" parameters. Any + * other values printed are in CamelCase. + * 2) strings (anything non-decimal) are printed surrounded by + * double quotes '"'. If the strings themselves contain double + * quotes, these characters are escaped by '\'. Any '\' character + * in the original string is also escaped by '\'. + * 3) numbers (containing just [0-9\-]) are written out unchanged. + */ + + BOOL fAccessible; + CHECK_ERROR2I_RET(machine, COMGETTER(Accessible)(&fAccessible), hrcCheck); + if (!fAccessible) + { + Bstr uuid; + machine->COMGETTER(Id)(uuid.asOutParam()); + if (details == VMINFO_COMPACT) + RTPrintf("\"<inaccessible>\" {%s}\n", Utf8Str(uuid).c_str()); + else + { + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("name=\"<inaccessible>\"\n"); + else + RTPrintf("Name: <inaccessible!>\n"); + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("UUID=\"%s\"\n", Utf8Str(uuid).c_str()); + else + RTPrintf("UUID: %s\n", Utf8Str(uuid).c_str()); + if (details != VMINFO_MACHINEREADABLE) + { + Bstr settingsFilePath; + rc = machine->COMGETTER(SettingsFilePath)(settingsFilePath.asOutParam()); + RTPrintf("Config file: %ls\n", settingsFilePath.raw()); + ComPtr<IVirtualBoxErrorInfo> accessError; + rc = machine->COMGETTER(AccessError)(accessError.asOutParam()); + RTPrintf("Access error details:\n"); + ErrorInfo ei(accessError); + GluePrintErrorInfo(ei); + RTPrintf("\n"); + } + } + return S_OK; + } + + if (details == VMINFO_COMPACT) + { + Bstr machineName; + machine->COMGETTER(Name)(machineName.asOutParam()); + Bstr uuid; + machine->COMGETTER(Id)(uuid.asOutParam()); + + RTPrintf("\"%ls\" {%s}\n", machineName.raw(), Utf8Str(uuid).c_str()); + return S_OK; + } + + SHOW_STRING_PROP( machine, Name, "name", "Name:"); + SHOW_STRINGARRAY_PROP( machine, Groups, "groups", "Groups:"); + Bstr osTypeId; + CHECK_ERROR2I_RET(machine, COMGETTER(OSTypeId)(osTypeId.asOutParam()), hrcCheck); + ComPtr<IGuestOSType> osType; + pVirtualBox->GetGuestOSType(osTypeId.raw(), osType.asOutParam()); + if (!osType.isNull()) + SHOW_STRING_PROP( osType, Description, "ostype", "Guest OS:"); + else + SHOW_STRING_PROP( machine, OSTypeId, "ostype", "Guest OS:"); + SHOW_UUID_PROP( machine, Id, "UUID", "UUID:"); + SHOW_STRING_PROP( machine, SettingsFilePath, "CfgFile", "Config file:"); + SHOW_STRING_PROP( machine, SnapshotFolder, "SnapFldr", "Snapshot folder:"); + SHOW_STRING_PROP( machine, LogFolder, "LogFldr", "Log folder:"); + SHOW_UUID_PROP( machine, HardwareUUID, "hardwareuuid", "Hardware UUID:"); + SHOW_ULONG_PROP( machine, MemorySize, "memory", "Memory size", "MB"); + SHOW_BOOLEAN_PROP( machine, PageFusionEnabled, "pagefusion", "Page Fusion:"); + SHOW_ULONG_PROP( machine, VRAMSize, "vram", "VRAM size:", "MB"); + SHOW_ULONG_PROP( machine, CPUExecutionCap, "cpuexecutioncap", "CPU exec cap:", "%"); + SHOW_BOOLEAN_PROP( machine, HPETEnabled, "hpet", "HPET:"); + SHOW_STRING_PROP_MAJ( machine, CPUProfile, "cpu-profile", "CPUProfile:", "host", 6); + + ChipsetType_T chipsetType; + CHECK_ERROR2I_RET(machine, COMGETTER(ChipsetType)(&chipsetType), hrcCheck); + const char *pszChipsetType; + switch (chipsetType) + { + case ChipsetType_Null: pszChipsetType = "invalid"; break; + case ChipsetType_PIIX3: pszChipsetType = "piix3"; break; + case ChipsetType_ICH9: pszChipsetType = "ich9"; break; + default: AssertFailed(); pszChipsetType = "unknown"; break; + } + SHOW_UTF8_STRING("chipset", "Chipset:", pszChipsetType); + + FirmwareType_T firmwareType; + CHECK_ERROR2I_RET(machine, COMGETTER(FirmwareType)(&firmwareType), hrcCheck); + const char *pszFirmwareType; + switch (firmwareType) + { + case FirmwareType_BIOS: pszFirmwareType = "BIOS"; break; + case FirmwareType_EFI: pszFirmwareType = "EFI"; break; + case FirmwareType_EFI32: pszFirmwareType = "EFI32"; break; + case FirmwareType_EFI64: pszFirmwareType = "EFI64"; break; + case FirmwareType_EFIDUAL: pszFirmwareType = "EFIDUAL"; break; + default: AssertFailed(); pszFirmwareType = "unknown"; break; + } + SHOW_UTF8_STRING("firmware", "Firmware:", pszFirmwareType); + + SHOW_ULONG_PROP( machine, CPUCount, "cpus", "Number of CPUs:", ""); + SHOW_BOOLEAN_METHOD( machine, GetCPUProperty(CPUPropertyType_PAE, &f), "pae", "PAE:"); + SHOW_BOOLEAN_METHOD( machine, GetCPUProperty(CPUPropertyType_LongMode, &f), "longmode", "Long Mode:"); + SHOW_BOOLEAN_METHOD( machine, GetCPUProperty(CPUPropertyType_TripleFaultReset, &f), "triplefaultreset", "Triple Fault Reset:"); + SHOW_BOOLEAN_METHOD( machine, GetCPUProperty(CPUPropertyType_APIC, &f), "apic", "APIC:"); + SHOW_BOOLEAN_METHOD( machine, GetCPUProperty(CPUPropertyType_X2APIC, &f), "x2apic", "X2APIC:"); + SHOW_BOOLEAN_METHOD( machine, GetCPUProperty(CPUPropertyType_HWVirt, &f), "nested-hw-virt", "Nested VT-x/AMD-V:"); + SHOW_ULONG_PROP( machine, CPUIDPortabilityLevel, "cpuid-portability-level", "CPUID Portability Level:", ""); + + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("%-28s ", "CPUID overrides:"); + ULONG uOrdinal = 0; + for (uOrdinal = 0; uOrdinal < _4K; uOrdinal++) + { + ULONG uLeaf, uSubLeaf, uEAX, uEBX, uECX, uEDX; + rc = machine->GetCPUIDLeafByOrdinal(uOrdinal, &uLeaf, &uSubLeaf, &uEAX, &uEBX, &uECX, &uEDX); + if (SUCCEEDED(rc)) + { + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("cpuid=%08x,%08x,%08x,%08x,%08x,%08x", uLeaf, uSubLeaf, uEAX, uEBX, uECX, uEDX); + else + { + if (!uOrdinal) + RTPrintf("Leaf no. EAX EBX ECX EDX\n"); + RTPrintf("%-28s %08x/%03x %08x %08x %08x %08x\n", "", uLeaf, uSubLeaf, uEAX, uEBX, uECX, uEDX); + } + } + else + { + if (rc != E_INVALIDARG) + com::GlueHandleComError(machine, "GetCPUIDLeaf", rc, __FILE__, __LINE__); + break; + } + } + if (!uOrdinal && details != VMINFO_MACHINEREADABLE) + RTPrintf("None\n"); + + ComPtr<IBIOSSettings> biosSettings; + CHECK_ERROR2I_RET(machine, COMGETTER(BIOSSettings)(biosSettings.asOutParam()), hrcCheck); + + BIOSBootMenuMode_T bootMenuMode; + CHECK_ERROR2I_RET(biosSettings, COMGETTER(BootMenuMode)(&bootMenuMode), hrcCheck); + const char *pszBootMenu; + switch (bootMenuMode) + { + case BIOSBootMenuMode_Disabled: + pszBootMenu = "disabled"; + break; + case BIOSBootMenuMode_MenuOnly: + if (details == VMINFO_MACHINEREADABLE) + pszBootMenu = "menuonly"; + else + pszBootMenu = "menu only"; + break; + default: + if (details == VMINFO_MACHINEREADABLE) + pszBootMenu = "messageandmenu"; + else + pszBootMenu = "message and menu"; + } + SHOW_UTF8_STRING("bootmenu", "Boot menu mode:", pszBootMenu); + + ComPtr<ISystemProperties> systemProperties; + CHECK_ERROR2I_RET(pVirtualBox, COMGETTER(SystemProperties)(systemProperties.asOutParam()), hrcCheck); + ULONG maxBootPosition = 0; + CHECK_ERROR2I_RET(systemProperties, COMGETTER(MaxBootPosition)(&maxBootPosition), hrcCheck); + for (ULONG i = 1; i <= maxBootPosition; i++) + { + DeviceType_T bootOrder; + CHECK_ERROR2I_RET(machine, GetBootOrder(i, &bootOrder), hrcCheck); + const char *pszDevice; + if (bootOrder == DeviceType_Floppy) + pszDevice = details == VMINFO_MACHINEREADABLE ? "floppy" : "Floppy"; + else if (bootOrder == DeviceType_DVD) + pszDevice = details == VMINFO_MACHINEREADABLE ? "dvd" : "DVD"; + else if (bootOrder == DeviceType_HardDisk) + pszDevice = details == VMINFO_MACHINEREADABLE ? "disk" : "HardDisk"; + else if (bootOrder == DeviceType_Network) + pszDevice = details == VMINFO_MACHINEREADABLE ? "net" : "Network"; + else if (bootOrder == DeviceType_USB) + pszDevice = details == VMINFO_MACHINEREADABLE ? "usb" : "USB"; + else if (bootOrder == DeviceType_SharedFolder) + pszDevice = details == VMINFO_MACHINEREADABLE ? "sharedfolder" : "Shared Folder"; + else + pszDevice = details == VMINFO_MACHINEREADABLE ? "none" : "Not Assigned"; + SHOW_UTF8_STRING(FmtNm(szNm, "boot%u", i), FmtNm(szNm, "Boot Device %u:", i), pszDevice); + } + + SHOW_BOOLEAN_PROP(biosSettings, ACPIEnabled, "acpi", "ACPI:"); + SHOW_BOOLEAN_PROP(biosSettings, IOAPICEnabled, "ioapic", "IOAPIC:"); + + APICMode_T apicMode; + CHECK_ERROR2I_RET(biosSettings, COMGETTER(APICMode)(&apicMode), hrcCheck); + const char *pszAPIC; + switch (apicMode) + { + case APICMode_Disabled: + pszAPIC = "disabled"; + break; + case APICMode_APIC: + default: + if (details == VMINFO_MACHINEREADABLE) + pszAPIC = "apic"; + else + pszAPIC = "APIC"; + break; + case APICMode_X2APIC: + if (details == VMINFO_MACHINEREADABLE) + pszAPIC = "x2apic"; + else + pszAPIC = "x2APIC"; + break; + } + SHOW_UTF8_STRING("biosapic", "BIOS APIC mode:", pszAPIC); + + SHOW_LONG64_PROP(biosSettings, TimeOffset, "biossystemtimeoffset", "Time offset:", "ms"); + SHOW_BOOLEAN_PROP_EX(machine, RTCUseUTC, "rtcuseutc", "RTC:", "UTC", "local time"); + SHOW_BOOLEAN_METHOD(machine, GetHWVirtExProperty(HWVirtExPropertyType_Enabled, &f), "hwvirtex", "Hardw. virt.ext:"); + SHOW_BOOLEAN_METHOD(machine, GetHWVirtExProperty(HWVirtExPropertyType_NestedPaging, &f),"nestedpaging", "Nested Paging:"); + SHOW_BOOLEAN_METHOD(machine, GetHWVirtExProperty(HWVirtExPropertyType_LargePages, &f), "largepages", "Large Pages:"); + SHOW_BOOLEAN_METHOD(machine, GetHWVirtExProperty(HWVirtExPropertyType_VPID, &f), "vtxvpid", "VT-x VPID:"); + SHOW_BOOLEAN_METHOD(machine, GetHWVirtExProperty(HWVirtExPropertyType_UnrestrictedExecution, &f), "vtxux", "VT-x unr. exec.:"); + + ParavirtProvider_T paravirtProvider; + CHECK_ERROR2I_RET(machine, COMGETTER(ParavirtProvider)(¶virtProvider), hrcCheck); + const char *pszParavirtProvider = paravirtProviderToString(paravirtProvider, details); + SHOW_UTF8_STRING("paravirtprovider", "Paravirt. Provider:", pszParavirtProvider); + + ParavirtProvider_T effParavirtProvider; + CHECK_ERROR2I_RET(machine, GetEffectiveParavirtProvider(&effParavirtProvider), hrcCheck); + const char *pszEffParavirtProvider = paravirtProviderToString(effParavirtProvider, details); + SHOW_UTF8_STRING("effparavirtprovider", "Effective Paravirt. Prov.:", pszEffParavirtProvider); + + Bstr paravirtDebug; + CHECK_ERROR2I_RET(machine, COMGETTER(ParavirtDebug)(paravirtDebug.asOutParam()), hrcCheck); + if (paravirtDebug.isNotEmpty()) + SHOW_BSTR_STRING("paravirtdebug", "Paravirt. Debug:", paravirtDebug); + + MachineState_T machineState; + CHECK_ERROR2I_RET(machine, COMGETTER(State)(&machineState), hrcCheck); + const char *pszState = machineStateToName(machineState, details == VMINFO_MACHINEREADABLE /*=fShort*/); + + LONG64 stateSince; + machine->COMGETTER(LastStateChange)(&stateSince); + RTTIMESPEC timeSpec; + RTTimeSpecSetMilli(&timeSpec, stateSince); + char pszTime[30] = {0}; + RTTimeSpecToString(&timeSpec, pszTime, sizeof(pszTime)); + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("VMState=\"%s\"\n", pszState); + RTPrintf("VMStateChangeTime=\"%s\"\n", pszTime); + + Bstr stateFile; + machine->COMGETTER(StateFilePath)(stateFile.asOutParam()); + if (!stateFile.isEmpty()) + RTPrintf("VMStateFile=\"%ls\"\n", stateFile.raw()); + } + else + RTPrintf("%-28s %s (since %s)\n", "State:", pszState, pszTime); + + SHOW_ULONG_PROP( machine, MonitorCount, "monitorcount", "Monitor count:", ""); + SHOW_BOOLEAN_PROP( machine, Accelerate3DEnabled, "accelerate3d", "3D Acceleration:"); +#ifdef VBOX_WITH_VIDEOHWACCEL + SHOW_BOOLEAN_PROP( machine, Accelerate2DVideoEnabled, "accelerate2dvideo", "2D Video Acceleration:"); +#endif + SHOW_BOOLEAN_PROP( machine, TeleporterEnabled, "teleporterenabled", "Teleporter Enabled:"); + SHOW_ULONG_PROP( machine, TeleporterPort, "teleporterport", "Teleporter Port:", ""); + SHOW_STRING_PROP( machine, TeleporterAddress, "teleporteraddress", "Teleporter Address:"); + SHOW_STRING_PROP( machine, TeleporterPassword, "teleporterpassword", "Teleporter Password:"); + SHOW_BOOLEAN_PROP( machine, TracingEnabled, "tracing-enabled", "Tracing Enabled:"); + SHOW_BOOLEAN_PROP( machine, AllowTracingToAccessVM, "tracing-allow-vm-access", "Allow Tracing to Access VM:"); + SHOW_STRING_PROP( machine, TracingConfig, "tracing-config", "Tracing Configuration:"); + SHOW_BOOLEAN_PROP( machine, AutostartEnabled, "autostart-enabled", "Autostart Enabled:"); + SHOW_ULONG_PROP( machine, AutostartDelay, "autostart-delay", "Autostart Delay:", ""); + SHOW_STRING_PROP( machine, DefaultFrontend, "defaultfrontend", "Default Frontend:"); + +/** @todo Convert the remainder of the function to SHOW_XXX macros and add error + * checking where missing. */ + /* + * Storage Controllers and their attached Mediums. + */ + com::SafeIfaceArray<IStorageController> storageCtls; + CHECK_ERROR(machine, COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(storageCtls))); + for (size_t i = 0; i < storageCtls.size(); ++ i) + { + ComPtr<IStorageController> storageCtl = storageCtls[i]; + StorageControllerType_T enmCtlType = StorageControllerType_Null; + const char *pszCtl = NULL; + ULONG ulValue = 0; + BOOL fBootable = FALSE; + Bstr storageCtlName; + + storageCtl->COMGETTER(Name)(storageCtlName.asOutParam()); + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("storagecontrollername%u=\"%ls\"\n", i, storageCtlName.raw()); + else + RTPrintf("Storage Controller Name (%u): %ls\n", i, storageCtlName.raw()); + + storageCtl->COMGETTER(ControllerType)(&enmCtlType); + switch (enmCtlType) + { + case StorageControllerType_LsiLogic: + pszCtl = "LsiLogic"; + break; + case StorageControllerType_LsiLogicSas: + pszCtl = "LsiLogicSas"; + break; + case StorageControllerType_BusLogic: + pszCtl = "BusLogic"; + break; + case StorageControllerType_IntelAhci: + pszCtl = "IntelAhci"; + break; + case StorageControllerType_PIIX3: + pszCtl = "PIIX3"; + break; + case StorageControllerType_PIIX4: + pszCtl = "PIIX4"; + break; + case StorageControllerType_ICH6: + pszCtl = "ICH6"; + break; + case StorageControllerType_I82078: + pszCtl = "I82078"; + break; + case StorageControllerType_USB: + pszCtl = "USB"; + break; + + default: + pszCtl = "unknown"; + } + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("storagecontrollertype%u=\"%s\"\n", i, pszCtl); + else + RTPrintf("Storage Controller Type (%u): %s\n", i, pszCtl); + + storageCtl->COMGETTER(Instance)(&ulValue); + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("storagecontrollerinstance%u=\"%lu\"\n", i, ulValue); + else + RTPrintf("Storage Controller Instance Number (%u): %lu\n", i, ulValue); + + storageCtl->COMGETTER(MaxPortCount)(&ulValue); + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("storagecontrollermaxportcount%u=\"%lu\"\n", i, ulValue); + else + RTPrintf("Storage Controller Max Port Count (%u): %lu\n", i, ulValue); + + storageCtl->COMGETTER(PortCount)(&ulValue); + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("storagecontrollerportcount%u=\"%lu\"\n", i, ulValue); + else + RTPrintf("Storage Controller Port Count (%u): %lu\n", i, ulValue); + + storageCtl->COMGETTER(Bootable)(&fBootable); + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("storagecontrollerbootable%u=\"%s\"\n", i, fBootable ? "on" : "off"); + else + RTPrintf("Storage Controller Bootable (%u): %s\n", i, fBootable ? "on" : "off"); + } + + for (size_t j = 0; j < storageCtls.size(); ++ j) + { + ComPtr<IStorageController> storageCtl = storageCtls[j]; + ComPtr<IMedium> medium; + Bstr storageCtlName; + Bstr filePath; + ULONG cDevices; + ULONG cPorts; + + storageCtl->COMGETTER(Name)(storageCtlName.asOutParam()); + storageCtl->COMGETTER(MaxDevicesPerPortCount)(&cDevices); + storageCtl->COMGETTER(PortCount)(&cPorts); + + for (ULONG i = 0; i < cPorts; ++ i) + { + for (ULONG k = 0; k < cDevices; ++ k) + { + ComPtr<IMediumAttachment> mediumAttach; + machine->GetMediumAttachment(storageCtlName.raw(), + i, k, + mediumAttach.asOutParam()); + BOOL fIsEjected = FALSE; + BOOL fTempEject = FALSE; + DeviceType_T devType = DeviceType_Null; + if (mediumAttach) + { + mediumAttach->COMGETTER(TemporaryEject)(&fTempEject); + mediumAttach->COMGETTER(IsEjected)(&fIsEjected); + mediumAttach->COMGETTER(Type)(&devType); + } + rc = machine->GetMedium(storageCtlName.raw(), i, k, + medium.asOutParam()); + if (SUCCEEDED(rc) && medium) + { + BOOL fPassthrough = FALSE; + + if (mediumAttach) + mediumAttach->COMGETTER(Passthrough)(&fPassthrough); + + medium->COMGETTER(Location)(filePath.asOutParam()); + Bstr uuid; + medium->COMGETTER(Id)(uuid.asOutParam()); + + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("\"%ls-%d-%d\"=\"%ls\"\n", storageCtlName.raw(), + i, k, filePath.raw()); + RTPrintf("\"%ls-ImageUUID-%d-%d\"=\"%s\"\n", + storageCtlName.raw(), i, k, Utf8Str(uuid).c_str()); + if (fPassthrough) + RTPrintf("\"%ls-dvdpassthrough\"=\"%s\"\n", storageCtlName.raw(), + fPassthrough ? "on" : "off"); + if (devType == DeviceType_DVD) + { + RTPrintf("\"%ls-tempeject\"=\"%s\"\n", storageCtlName.raw(), + fTempEject ? "on" : "off"); + RTPrintf("\"%ls-IsEjected\"=\"%s\"\n", storageCtlName.raw(), + fIsEjected ? "on" : "off"); + } + } + else + { + RTPrintf("%ls (%d, %d): %ls (UUID: %s)", + storageCtlName.raw(), i, k, filePath.raw(), + Utf8Str(uuid).c_str()); + if (fPassthrough) + RTPrintf(" (passthrough enabled)"); + if (fTempEject) + RTPrintf(" (temp eject)"); + if (fIsEjected) + RTPrintf(" (ejected)"); + RTPrintf("\n"); + } + } + else if (SUCCEEDED(rc)) + { + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("\"%ls-%d-%d\"=\"emptydrive\"\n", storageCtlName.raw(), i, k); + if (devType == DeviceType_DVD) + RTPrintf("\"%ls-IsEjected\"=\"%s\"\n", storageCtlName.raw(), + fIsEjected ? "on" : "off"); + } + else + { + RTPrintf("%ls (%d, %d): Empty", storageCtlName.raw(), i, k); + if (fTempEject) + RTPrintf(" (temp eject)"); + if (fIsEjected) + RTPrintf(" (ejected)"); + RTPrintf("\n"); + } + } + else + { + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("\"%ls-%d-%d\"=\"none\"\n", storageCtlName.raw(), i, k); + } + } + } + } + + /* get the maximum amount of NICS */ + ULONG maxNICs = getMaxNics(pVirtualBox, machine); + + for (ULONG currentNIC = 0; currentNIC < maxNICs; currentNIC++) + { + ComPtr<INetworkAdapter> nic; + rc = machine->GetNetworkAdapter(currentNIC, nic.asOutParam()); + if (SUCCEEDED(rc) && nic) + { + FmtNm(szNm, details == VMINFO_MACHINEREADABLE ? "nic%u" : "NIC %u:", currentNIC + 1); + + BOOL fEnabled; + nic->COMGETTER(Enabled)(&fEnabled); + if (!fEnabled) + { + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("%s=\"none\"\n", szNm); + else + RTPrintf("%-28s disabled\n", szNm); + } + else + { + Bstr strMACAddress; + nic->COMGETTER(MACAddress)(strMACAddress.asOutParam()); + Utf8Str strAttachment; + Utf8Str strNatSettings = ""; + Utf8Str strNatForwardings = ""; + NetworkAttachmentType_T attachment; + nic->COMGETTER(AttachmentType)(&attachment); + switch (attachment) + { + case NetworkAttachmentType_Null: + if (details == VMINFO_MACHINEREADABLE) + strAttachment = "null"; + else + strAttachment = "none"; + break; + + case NetworkAttachmentType_NAT: + { + Bstr strNetwork; + ComPtr<INATEngine> engine; + nic->COMGETTER(NATEngine)(engine.asOutParam()); + engine->COMGETTER(Network)(strNetwork.asOutParam()); + com::SafeArray<BSTR> forwardings; + engine->COMGETTER(Redirects)(ComSafeArrayAsOutParam(forwardings)); + strNatForwardings = ""; + for (size_t i = 0; i < forwardings.size(); ++i) + { + bool fSkip = false; + BSTR r = forwardings[i]; + Utf8Str utf = Utf8Str(r); + Utf8Str strName; + Utf8Str strProto; + Utf8Str strHostPort; + Utf8Str strHostIP; + Utf8Str strGuestPort; + Utf8Str strGuestIP; + size_t pos, ppos; + pos = ppos = 0; + #define ITERATE_TO_NEXT_TERM(res, str, pos, ppos) \ + do { \ + pos = str.find(",", ppos); \ + if (pos == Utf8Str::npos) \ + { \ + Log(( #res " extracting from %s is failed\n", str.c_str())); \ + fSkip = true; \ + } \ + res = str.substr(ppos, pos - ppos); \ + Log2((#res " %s pos:%d, ppos:%d\n", res.c_str(), pos, ppos)); \ + ppos = pos + 1; \ + } while (0) + ITERATE_TO_NEXT_TERM(strName, utf, pos, ppos); + if (fSkip) continue; + ITERATE_TO_NEXT_TERM(strProto, utf, pos, ppos); + if (fSkip) continue; + ITERATE_TO_NEXT_TERM(strHostIP, utf, pos, ppos); + if (fSkip) continue; + ITERATE_TO_NEXT_TERM(strHostPort, utf, pos, ppos); + if (fSkip) continue; + ITERATE_TO_NEXT_TERM(strGuestIP, utf, pos, ppos); + if (fSkip) continue; + strGuestPort = utf.substr(ppos, utf.length() - ppos); + #undef ITERATE_TO_NEXT_TERM + switch (strProto.toUInt32()) + { + case NATProtocol_TCP: + strProto = "tcp"; + break; + case NATProtocol_UDP: + strProto = "udp"; + break; + default: + strProto = "unk"; + break; + } + if (details == VMINFO_MACHINEREADABLE) + { + strNatForwardings = Utf8StrFmt("%sForwarding(%d)=\"%s,%s,%s,%s,%s,%s\"\n", + strNatForwardings.c_str(), i, strName.c_str(), strProto.c_str(), + strHostIP.c_str(), strHostPort.c_str(), + strGuestIP.c_str(), strGuestPort.c_str()); + } + else + { + strNatForwardings = Utf8StrFmt("%sNIC %d Rule(%d): name = %s, protocol = %s," + " host ip = %s, host port = %s, guest ip = %s, guest port = %s\n", + strNatForwardings.c_str(), currentNIC + 1, i, strName.c_str(), strProto.c_str(), + strHostIP.c_str(), strHostPort.c_str(), + strGuestIP.c_str(), strGuestPort.c_str()); + } + } + ULONG mtu = 0; + ULONG sockSnd = 0; + ULONG sockRcv = 0; + ULONG tcpSnd = 0; + ULONG tcpRcv = 0; + engine->GetNetworkSettings(&mtu, &sockSnd, &sockRcv, &tcpSnd, &tcpRcv); + +/** @todo r=klaus dnsproxy etc needs to be dumped, too */ + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("natnet%d=\"%ls\"\n", currentNIC + 1, strNetwork.length() ? strNetwork.raw(): Bstr("nat").raw()); + strAttachment = "nat"; + strNatSettings = Utf8StrFmt("mtu=\"%d\"\nsockSnd=\"%d\"\nsockRcv=\"%d\"\ntcpWndSnd=\"%d\"\ntcpWndRcv=\"%d\"\n", + mtu, sockSnd ? sockSnd : 64, sockRcv ? sockRcv : 64, tcpSnd ? tcpSnd : 64, tcpRcv ? tcpRcv : 64); + } + else + { + strAttachment = "NAT"; + strNatSettings = Utf8StrFmt("NIC %d Settings: MTU: %d, Socket (send: %d, receive: %d), TCP Window (send:%d, receive: %d)\n", + currentNIC + 1, mtu, sockSnd ? sockSnd : 64, sockRcv ? sockRcv : 64, tcpSnd ? tcpSnd : 64, tcpRcv ? tcpRcv : 64); + } + break; + } + + case NetworkAttachmentType_Bridged: + { + Bstr strBridgeAdp; + nic->COMGETTER(BridgedInterface)(strBridgeAdp.asOutParam()); + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("bridgeadapter%d=\"%ls\"\n", currentNIC + 1, strBridgeAdp.raw()); + strAttachment = "bridged"; + } + else + strAttachment = Utf8StrFmt("Bridged Interface '%ls'", strBridgeAdp.raw()); + break; + } + + case NetworkAttachmentType_Internal: + { + Bstr strNetwork; + nic->COMGETTER(InternalNetwork)(strNetwork.asOutParam()); + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("intnet%d=\"%ls\"\n", currentNIC + 1, strNetwork.raw()); + strAttachment = "intnet"; + } + else + strAttachment = Utf8StrFmt("Internal Network '%s'", Utf8Str(strNetwork).c_str()); + break; + } + + case NetworkAttachmentType_HostOnly: + { + Bstr strHostonlyAdp; + nic->COMGETTER(HostOnlyInterface)(strHostonlyAdp.asOutParam()); + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("hostonlyadapter%d=\"%ls\"\n", currentNIC + 1, strHostonlyAdp.raw()); + strAttachment = "hostonly"; + } + else + strAttachment = Utf8StrFmt("Host-only Interface '%ls'", strHostonlyAdp.raw()); + break; + } + + case NetworkAttachmentType_Generic: + { + Bstr strGenericDriver; + nic->COMGETTER(GenericDriver)(strGenericDriver.asOutParam()); + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("generic%d=\"%ls\"\n", currentNIC + 1, strGenericDriver.raw()); + strAttachment = "Generic"; + } + else + { + strAttachment = Utf8StrFmt("Generic '%ls'", strGenericDriver.raw()); + + // show the generic properties + com::SafeArray<BSTR> aProperties; + com::SafeArray<BSTR> aValues; + rc = nic->GetProperties(NULL, + ComSafeArrayAsOutParam(aProperties), + ComSafeArrayAsOutParam(aValues)); + if (SUCCEEDED(rc)) + { + strAttachment += " { "; + for (unsigned i = 0; i < aProperties.size(); ++i) + strAttachment += Utf8StrFmt(!i ? "%ls='%ls'" : ", %ls='%ls'", + aProperties[i], aValues[i]); + strAttachment += " }"; + } + } + break; + } + + case NetworkAttachmentType_NATNetwork: + { + Bstr strNetwork; + nic->COMGETTER(NATNetwork)(strNetwork.asOutParam()); + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("nat-network%d=\"%ls\"\n", currentNIC + 1, strNetwork.raw()); + strAttachment = "natnetwork"; + } + else + strAttachment = Utf8StrFmt("NAT Network '%s'", Utf8Str(strNetwork).c_str()); + break; + } + + default: + strAttachment = "unknown"; + break; + } + + /* cable connected */ + BOOL fConnected; + nic->COMGETTER(CableConnected)(&fConnected); + + /* promisc policy */ + NetworkAdapterPromiscModePolicy_T enmPromiscModePolicy; + CHECK_ERROR2I_RET(nic, COMGETTER(PromiscModePolicy)(&enmPromiscModePolicy), hrcCheck); + const char *pszPromiscuousGuestPolicy; + switch (enmPromiscModePolicy) + { + case NetworkAdapterPromiscModePolicy_Deny: pszPromiscuousGuestPolicy = "deny"; break; + case NetworkAdapterPromiscModePolicy_AllowNetwork: pszPromiscuousGuestPolicy = "allow-vms"; break; + case NetworkAdapterPromiscModePolicy_AllowAll: pszPromiscuousGuestPolicy = "allow-all"; break; + default: AssertFailedReturn(E_INVALIDARG); + } + + /* trace stuff */ + BOOL fTraceEnabled; + nic->COMGETTER(TraceEnabled)(&fTraceEnabled); + Bstr traceFile; + nic->COMGETTER(TraceFile)(traceFile.asOutParam()); + + /* NIC type */ + NetworkAdapterType_T NICType; + nic->COMGETTER(AdapterType)(&NICType); + const char *pszNICType; + switch (NICType) + { + case NetworkAdapterType_Am79C970A: pszNICType = "Am79C970A"; break; + case NetworkAdapterType_Am79C973: pszNICType = "Am79C973"; break; +#ifdef VBOX_WITH_E1000 + case NetworkAdapterType_I82540EM: pszNICType = "82540EM"; break; + case NetworkAdapterType_I82543GC: pszNICType = "82543GC"; break; + case NetworkAdapterType_I82545EM: pszNICType = "82545EM"; break; +#endif +#ifdef VBOX_WITH_VIRTIO + case NetworkAdapterType_Virtio: pszNICType = "virtio"; break; +#endif + default: AssertFailed(); pszNICType = "unknown"; break; + } + + /* reported line speed */ + ULONG ulLineSpeed; + nic->COMGETTER(LineSpeed)(&ulLineSpeed); + + /* boot priority of the adapter */ + ULONG ulBootPriority; + nic->COMGETTER(BootPriority)(&ulBootPriority); + + /* bandwidth group */ + ComObjPtr<IBandwidthGroup> pBwGroup; + Bstr strBwGroup; + nic->COMGETTER(BandwidthGroup)(pBwGroup.asOutParam()); + if (!pBwGroup.isNull()) + pBwGroup->COMGETTER(Name)(strBwGroup.asOutParam()); + + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("macaddress%d=\"%ls\"\n", currentNIC + 1, strMACAddress.raw()); + RTPrintf("cableconnected%d=\"%s\"\n", currentNIC + 1, fConnected ? "on" : "off"); + RTPrintf("nic%d=\"%s\"\n", currentNIC + 1, strAttachment.c_str()); + RTPrintf("nictype%d=\"%s\"\n", currentNIC + 1, pszNICType); + RTPrintf("nicspeed%d=\"%d\"\n", currentNIC + 1, ulLineSpeed); + } + else + RTPrintf("%-28s MAC: %ls, Attachment: %s, Cable connected: %s, Trace: %s (file: %ls), Type: %s, Reported speed: %d Mbps, Boot priority: %d, Promisc Policy: %s, Bandwidth group: %ls\n", + szNm, strMACAddress.raw(), strAttachment.c_str(), + fConnected ? "on" : "off", + fTraceEnabled ? "on" : "off", + traceFile.isEmpty() ? Bstr("none").raw() : traceFile.raw(), + pszNICType, + ulLineSpeed / 1000, + (int)ulBootPriority, + pszPromiscuousGuestPolicy, + strBwGroup.isEmpty() ? Bstr("none").raw() : strBwGroup.raw()); + if (strNatSettings.length()) + RTPrintf(strNatSettings.c_str()); + if (strNatForwardings.length()) + RTPrintf(strNatForwardings.c_str()); + } + } + } + + /* Pointing device information */ + PointingHIDType_T aPointingHID; + const char *pszHID = "Unknown"; + const char *pszMrHID = "unknown"; + machine->COMGETTER(PointingHIDType)(&aPointingHID); + switch (aPointingHID) + { + case PointingHIDType_None: + pszHID = "None"; + pszMrHID = "none"; + break; + case PointingHIDType_PS2Mouse: + pszHID = "PS/2 Mouse"; + pszMrHID = "ps2mouse"; + break; + case PointingHIDType_USBMouse: + pszHID = "USB Mouse"; + pszMrHID = "usbmouse"; + break; + case PointingHIDType_USBTablet: + pszHID = "USB Tablet"; + pszMrHID = "usbtablet"; + break; + case PointingHIDType_ComboMouse: + pszHID = "USB Tablet and PS/2 Mouse"; + pszMrHID = "combomouse"; + break; + case PointingHIDType_USBMultiTouch: + pszHID = "USB Multi-Touch"; + pszMrHID = "usbmultitouch"; + break; + default: + break; + } + SHOW_UTF8_STRING("hidpointing", "Pointing Device:", details == VMINFO_MACHINEREADABLE ? pszMrHID : pszHID); + + /* Keyboard device information */ + KeyboardHIDType_T aKeyboardHID; + machine->COMGETTER(KeyboardHIDType)(&aKeyboardHID); + pszHID = "Unknown"; + pszMrHID = "unknown"; + switch (aKeyboardHID) + { + case KeyboardHIDType_None: + pszHID = "None"; + pszMrHID = "none"; + break; + case KeyboardHIDType_PS2Keyboard: + pszHID = "PS/2 Keyboard"; + pszMrHID = "ps2kbd"; + break; + case KeyboardHIDType_USBKeyboard: + pszHID = "USB Keyboard"; + pszMrHID = "usbkbd"; + break; + case KeyboardHIDType_ComboKeyboard: + pszHID = "USB and PS/2 Keyboard"; + pszMrHID = "combokbd"; + break; + default: + break; + } + SHOW_UTF8_STRING("hidkeyboard", "Keyboard Device:", details == VMINFO_MACHINEREADABLE ? pszMrHID : pszHID); + + ComPtr<ISystemProperties> sysProps; + pVirtualBox->COMGETTER(SystemProperties)(sysProps.asOutParam()); + + /* get the maximum amount of UARTs */ + ULONG maxUARTs = 0; + sysProps->COMGETTER(SerialPortCount)(&maxUARTs); + for (ULONG currentUART = 0; currentUART < maxUARTs; currentUART++) + { + ComPtr<ISerialPort> uart; + rc = machine->GetSerialPort(currentUART, uart.asOutParam()); + if (SUCCEEDED(rc) && uart) + { + FmtNm(szNm, details == VMINFO_MACHINEREADABLE ? "uart%u" : "UART %u:", currentUART + 1); + + /* show the config of this UART */ + BOOL fEnabled; + uart->COMGETTER(Enabled)(&fEnabled); + if (!fEnabled) + { + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("%s=\"off\"\n", szNm); + else + RTPrintf("%-28s disabled\n", szNm); + } + else + { + ULONG ulIRQ, ulIOBase; + PortMode_T HostMode; + Bstr path; + BOOL fServer; + UartType_T UartType; + uart->COMGETTER(IRQ)(&ulIRQ); + uart->COMGETTER(IOBase)(&ulIOBase); + uart->COMGETTER(Path)(path.asOutParam()); + uart->COMGETTER(Server)(&fServer); + uart->COMGETTER(HostMode)(&HostMode); + uart->COMGETTER(UartType)(&UartType); + + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("%s=\"%#06x,%d\"\n", szNm, ulIOBase, ulIRQ); + else + RTPrintf("%-28s I/O base: %#06x, IRQ: %d", szNm, ulIOBase, ulIRQ); + switch (HostMode) + { + default: + case PortMode_Disconnected: + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("uartmode%d=\"disconnected\"\n", currentUART + 1); + else + RTPrintf(", disconnected"); + break; + case PortMode_RawFile: + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("uartmode%d=\"file,%ls\"\n", currentUART + 1, + path.raw()); + else + RTPrintf(", attached to raw file '%ls'\n", + path.raw()); + break; + case PortMode_TCP: + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("uartmode%d=\"%s,%ls\"\n", currentUART + 1, + fServer ? "tcpserver" : "tcpclient", path.raw()); + else + RTPrintf(", attached to tcp (%s) '%ls'", + fServer ? "server" : "client", path.raw()); + break; + case PortMode_HostPipe: + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("uartmode%d=\"%s,%ls\"\n", currentUART + 1, + fServer ? "server" : "client", path.raw()); + else + RTPrintf(", attached to pipe (%s) '%ls'", + fServer ? "server" : "client", path.raw()); + break; + case PortMode_HostDevice: + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("uartmode%d=\"%ls\"\n", currentUART + 1, + path.raw()); + else + RTPrintf(", attached to device '%ls'", path.raw()); + break; + } + switch (UartType) + { + default: + case UartType_U16450: + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("uarttype%d=\"16450\"\n", currentUART + 1); + else + RTPrintf(", 16450\n"); + break; + case UartType_U16550A: + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("uarttype%d=\"16550A\"\n", currentUART + 1); + else + RTPrintf(", 16550A\n"); + break; + case UartType_U16750: + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("uarttype%d=\"16750\"\n", currentUART + 1); + else + RTPrintf(", 16750\n"); + break; + } + } + } + } + + /* get the maximum amount of LPTs */ + ULONG maxLPTs = 0; + sysProps->COMGETTER(ParallelPortCount)(&maxLPTs); + for (ULONG currentLPT = 0; currentLPT < maxLPTs; currentLPT++) + { + ComPtr<IParallelPort> lpt; + rc = machine->GetParallelPort(currentLPT, lpt.asOutParam()); + if (SUCCEEDED(rc) && lpt) + { + FmtNm(szNm, details == VMINFO_MACHINEREADABLE ? "lpt%u" : "LPT %u:", currentLPT + 1); + + /* show the config of this LPT */ + BOOL fEnabled; + lpt->COMGETTER(Enabled)(&fEnabled); + if (!fEnabled) + { + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("%s=\"off\"\n", szNm); + else + RTPrintf("%-28s disabled\n", szNm); + } + else + { + ULONG ulIRQ, ulIOBase; + Bstr path; + lpt->COMGETTER(IRQ)(&ulIRQ); + lpt->COMGETTER(IOBase)(&ulIOBase); + lpt->COMGETTER(Path)(path.asOutParam()); + + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("%s=\"%#06x,%d\"\n", szNm, ulIOBase, ulIRQ); + else + RTPrintf("%-28s I/O base: %#06x, IRQ: %d", szNm, ulIOBase, ulIRQ); + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("lptmode%d=\"%ls\"\n", currentLPT + 1, path.raw()); + else + RTPrintf(", attached to device '%ls'\n", path.raw()); + } + } + } + + ComPtr<IAudioAdapter> AudioAdapter; + rc = machine->COMGETTER(AudioAdapter)(AudioAdapter.asOutParam()); + if (SUCCEEDED(rc)) + { + const char *pszDrv = "Unknown"; + const char *pszCtrl = "Unknown"; + const char *pszCodec = "Unknown"; + BOOL fEnabled; + rc = AudioAdapter->COMGETTER(Enabled)(&fEnabled); + if (SUCCEEDED(rc) && fEnabled) + { + AudioDriverType_T enmDrvType; + rc = AudioAdapter->COMGETTER(AudioDriver)(&enmDrvType); + switch (enmDrvType) + { + case AudioDriverType_Null: + if (details == VMINFO_MACHINEREADABLE) + pszDrv = "null"; + else + pszDrv = "Null"; + break; + case AudioDriverType_WinMM: + if (details == VMINFO_MACHINEREADABLE) + pszDrv = "winmm"; + else + pszDrv = "WINMM"; + break; + case AudioDriverType_DirectSound: + if (details == VMINFO_MACHINEREADABLE) + pszDrv = "dsound"; + else + pszDrv = "DSOUND"; + break; + case AudioDriverType_OSS: + if (details == VMINFO_MACHINEREADABLE) + pszDrv = "oss"; + else + pszDrv = "OSS"; + break; + case AudioDriverType_ALSA: + if (details == VMINFO_MACHINEREADABLE) + pszDrv = "alsa"; + else + pszDrv = "ALSA"; + break; + case AudioDriverType_Pulse: + if (details == VMINFO_MACHINEREADABLE) + pszDrv = "pulse"; + else + pszDrv = "PulseAudio"; + break; + case AudioDriverType_CoreAudio: + if (details == VMINFO_MACHINEREADABLE) + pszDrv = "coreaudio"; + else + pszDrv = "CoreAudio"; + break; + case AudioDriverType_SolAudio: + if (details == VMINFO_MACHINEREADABLE) + pszDrv = "solaudio"; + else + pszDrv = "SolAudio"; + break; + default: + if (details == VMINFO_MACHINEREADABLE) + pszDrv = "unknown"; + break; + } + AudioControllerType_T enmCtrlType; + rc = AudioAdapter->COMGETTER(AudioController)(&enmCtrlType); + switch (enmCtrlType) + { + case AudioControllerType_AC97: + if (details == VMINFO_MACHINEREADABLE) + pszCtrl = "ac97"; + else + pszCtrl = "AC97"; + break; + case AudioControllerType_SB16: + if (details == VMINFO_MACHINEREADABLE) + pszCtrl = "sb16"; + else + pszCtrl = "SB16"; + break; + case AudioControllerType_HDA: + if (details == VMINFO_MACHINEREADABLE) + pszCtrl = "hda"; + else + pszCtrl = "HDA"; + break; + default: + break; + } + AudioCodecType_T enmCodecType; + rc = AudioAdapter->COMGETTER(AudioCodec)(&enmCodecType); + switch (enmCodecType) + { + case AudioCodecType_SB16: + pszCodec = "SB16"; + break; + case AudioCodecType_STAC9700: + pszCodec = "STAC9700"; + break; + case AudioCodecType_AD1980: + pszCodec = "AD1980"; + break; + case AudioCodecType_STAC9221: + pszCodec = "STAC9221"; + break; + case AudioCodecType_Null: break; /* Shut up MSC. */ + default: break; + } + } + else + fEnabled = FALSE; + + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("audio=\"%s\"\n", fEnabled ? pszDrv : "none"); + else + { + RTPrintf("%-28s %s", "Audio:", fEnabled ? "enabled" : "disabled"); + if (fEnabled) + RTPrintf(" (Driver: %s, Controller: %s, Codec: %s)", pszDrv, pszCtrl, pszCodec); + RTPrintf("\n"); + } + SHOW_BOOLEAN_PROP(AudioAdapter, EnabledIn, "audio_in", "Audio playback:"); + SHOW_BOOLEAN_PROP(AudioAdapter, EnabledOut, "audio_out", "Audio capture:"); + } + + /* Shared clipboard */ + { + const char *psz; + ClipboardMode_T enmMode = (ClipboardMode_T)0; + rc = machine->COMGETTER(ClipboardMode)(&enmMode); + switch (enmMode) + { + case ClipboardMode_Disabled: + psz = "disabled"; + break; + case ClipboardMode_HostToGuest: + psz = details == VMINFO_MACHINEREADABLE ? "hosttoguest" : "HostToGuest"; + break; + case ClipboardMode_GuestToHost: + psz = details == VMINFO_MACHINEREADABLE ? "guesttohost" : "GuestToHost"; + break; + case ClipboardMode_Bidirectional: + psz = details == VMINFO_MACHINEREADABLE ? "bidirectional" : "Bidirectional"; + break; + default: + psz = details == VMINFO_MACHINEREADABLE ? "unknown" : "Unknown"; + break; + } + SHOW_UTF8_STRING("clipboard", "Clipboard Mode:", psz); + } + + /* Drag and drop */ + { + const char *psz; + DnDMode_T enmMode; + rc = machine->COMGETTER(DnDMode)(&enmMode); + switch (enmMode) + { + case DnDMode_Disabled: + psz = "disabled"; + break; + case DnDMode_HostToGuest: + psz = details == VMINFO_MACHINEREADABLE ? "hosttoguest" : "HostToGuest"; + break; + case DnDMode_GuestToHost: + psz = details == VMINFO_MACHINEREADABLE ? "guesttohost" : "GuestToHost"; + break; + case DnDMode_Bidirectional: + psz = details == VMINFO_MACHINEREADABLE ? "bidirectional" : "Bidirectional"; + break; + default: + psz = details == VMINFO_MACHINEREADABLE ? "unknown" : "Unknown"; + break; + } + SHOW_UTF8_STRING("draganddrop", "Drag and drop Mode:", psz); + } + + { + SessionState_T sessState; + rc = machine->COMGETTER(SessionState)(&sessState); + if (SUCCEEDED(rc) && sessState != SessionState_Unlocked) + { + Bstr sessName; + rc = machine->COMGETTER(SessionName)(sessName.asOutParam()); + if (SUCCEEDED(rc) && !sessName.isEmpty()) + SHOW_BSTR_STRING("SessionName", "Session name:", sessName); + } + } + + if (pConsole) + { + do + { + ComPtr<IDisplay> display; + rc = pConsole->COMGETTER(Display)(display.asOutParam()); + if (rc == E_ACCESSDENIED || display.isNull()) + break; /* VM not powered up */ + if (FAILED(rc)) + { + com::GlueHandleComError(pConsole, "COMGETTER(Display)(display.asOutParam())", rc, __FILE__, __LINE__); + return rc; + } + ULONG xRes, yRes, bpp; + LONG xOrigin, yOrigin; + GuestMonitorStatus_T monitorStatus; + rc = display->GetScreenResolution(0, &xRes, &yRes, &bpp, &xOrigin, &yOrigin, &monitorStatus); + if (rc == E_ACCESSDENIED) + break; /* VM not powered up */ + if (FAILED(rc)) + { + com::ErrorInfo info(display, COM_IIDOF(IDisplay)); + GluePrintErrorInfo(info); + return rc; + } + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("VideoMode=\"%d,%d,%d\"@%d,%d %d\n", xRes, yRes, bpp, xOrigin, yOrigin, monitorStatus); + else + { + const char *pszMonitorStatus = "unknown status"; + switch (monitorStatus) + { + case GuestMonitorStatus_Blank: pszMonitorStatus = "blank"; break; + case GuestMonitorStatus_Enabled: pszMonitorStatus = "enabled"; break; + case GuestMonitorStatus_Disabled: pszMonitorStatus = "disabled"; break; + default: break; + } + RTPrintf("%-28s %dx%dx%d at %d,%d %s\n", "Video mode:", xRes, yRes, bpp, xOrigin, yOrigin, pszMonitorStatus); + } + } + while (0); + } + + /* + * Remote Desktop + */ + ComPtr<IVRDEServer> vrdeServer; + rc = machine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + if (SUCCEEDED(rc) && vrdeServer) + { + BOOL fEnabled = false; + vrdeServer->COMGETTER(Enabled)(&fEnabled); + if (fEnabled) + { + LONG currentPort = -1; + Bstr ports; + vrdeServer->GetVRDEProperty(Bstr("TCP/Ports").raw(), ports.asOutParam()); + Bstr address; + vrdeServer->GetVRDEProperty(Bstr("TCP/Address").raw(), address.asOutParam()); + BOOL fMultiCon; + vrdeServer->COMGETTER(AllowMultiConnection)(&fMultiCon); + BOOL fReuseCon; + vrdeServer->COMGETTER(ReuseSingleConnection)(&fReuseCon); + Bstr videoChannel; + vrdeServer->GetVRDEProperty(Bstr("VideoChannel/Enabled").raw(), videoChannel.asOutParam()); + BOOL fVideoChannel = (videoChannel.compare(Bstr("true"), Bstr::CaseInsensitive)== 0) + || (videoChannel == "1"); + Bstr videoChannelQuality; + vrdeServer->GetVRDEProperty(Bstr("VideoChannel/Quality").raw(), videoChannelQuality.asOutParam()); + AuthType_T authType = (AuthType_T)0; + const char *strAuthType; + vrdeServer->COMGETTER(AuthType)(&authType); + switch (authType) + { + case AuthType_Null: + strAuthType = "null"; + break; + case AuthType_External: + strAuthType = "external"; + break; + case AuthType_Guest: + strAuthType = "guest"; + break; + default: + strAuthType = "unknown"; + break; + } + if (pConsole) + { + ComPtr<IVRDEServerInfo> vrdeServerInfo; + CHECK_ERROR_RET(pConsole, COMGETTER(VRDEServerInfo)(vrdeServerInfo.asOutParam()), rc); + if (!vrdeServerInfo.isNull()) + { + rc = vrdeServerInfo->COMGETTER(Port)(¤tPort); + if (rc == E_ACCESSDENIED) + { + currentPort = -1; /* VM not powered up */ + } + else if (FAILED(rc)) + { + com::ErrorInfo info(vrdeServerInfo, COM_IIDOF(IVRDEServerInfo)); + GluePrintErrorInfo(info); + return rc; + } + } + } + if (details == VMINFO_MACHINEREADABLE) + { + RTPrintf("vrde=\"on\"\n"); + RTPrintf("vrdeport=%d\n", currentPort); + RTPrintf("vrdeports=\"%ls\"\n", ports.raw()); + RTPrintf("vrdeaddress=\"%ls\"\n", address.raw()); + RTPrintf("vrdeauthtype=\"%s\"\n", strAuthType); + RTPrintf("vrdemulticon=\"%s\"\n", fMultiCon ? "on" : "off"); + RTPrintf("vrdereusecon=\"%s\"\n", fReuseCon ? "on" : "off"); + RTPrintf("vrdevideochannel=\"%s\"\n", fVideoChannel ? "on" : "off"); + if (fVideoChannel) + RTPrintf("vrdevideochannelquality=\"%ls\"\n", videoChannelQuality.raw()); + } + else + { + if (address.isEmpty()) + address = "0.0.0.0"; + RTPrintf("%-28s enabled (Address %ls, Ports %ls, MultiConn: %s, ReuseSingleConn: %s, Authentication type: %s)\n", + "VRDE:", address.raw(), ports.raw(), fMultiCon ? "on" : "off", fReuseCon ? "on" : "off", strAuthType); + if (pConsole && currentPort != -1 && currentPort != 0) + RTPrintf("%-28s %d\n", "VRDE port:", currentPort); + if (fVideoChannel) + RTPrintf("%-28s enabled (Quality %ls)\n", "Video redirection:", videoChannelQuality.raw()); + else + RTPrintf("%-28s disabled\n", "Video redirection:"); + } + com::SafeArray<BSTR> aProperties; + if (SUCCEEDED(vrdeServer->COMGETTER(VRDEProperties)(ComSafeArrayAsOutParam(aProperties)))) + { + unsigned i; + for (i = 0; i < aProperties.size(); ++i) + { + Bstr value; + vrdeServer->GetVRDEProperty(aProperties[i], value.asOutParam()); + if (details == VMINFO_MACHINEREADABLE) + { + if (value.isEmpty()) + RTPrintf("vrdeproperty[%ls]=<not set>\n", aProperties[i]); + else + RTPrintf("vrdeproperty[%ls]=\"%ls\"\n", aProperties[i], value.raw()); + } + else + { + if (value.isEmpty()) + RTPrintf("%-28s: %-10lS = <not set>\n", "VRDE property", aProperties[i]); + else + RTPrintf("%-28s: %-10lS = \"%ls\"\n", "VRDE property", aProperties[i], value.raw()); + } + } + } + } + else + { + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("vrde=\"off\"\n"); + else + RTPrintf("%-28s disabled\n", "VRDE:"); + } + } + + /* + * USB. + */ + SafeIfaceArray<IUSBController> USBCtlColl; + rc = machine->COMGETTER(USBControllers)(ComSafeArrayAsOutParam(USBCtlColl)); + if (SUCCEEDED(rc)) + { + bool fOhciEnabled = false; + bool fEhciEnabled = false; + bool fXhciEnabled = false; + + for (unsigned i = 0; i < USBCtlColl.size(); i++) + { + USBControllerType_T enmType; + + rc = USBCtlColl[i]->COMGETTER(Type)(&enmType); + if (SUCCEEDED(rc)) + { + switch (enmType) + { + case USBControllerType_OHCI: + fOhciEnabled = true; + break; + case USBControllerType_EHCI: + fEhciEnabled = true; + break; + case USBControllerType_XHCI: + fXhciEnabled = true; + break; + default: + break; + } + } + } + + SHOW_BOOL_VALUE("usb", "OHCI USB:", fOhciEnabled); + SHOW_BOOL_VALUE("ehci", "EHCI USB:", fEhciEnabled); + SHOW_BOOL_VALUE("xhci", "xHCI USB:", fXhciEnabled); + } + + ComPtr<IUSBDeviceFilters> USBFlts; + rc = machine->COMGETTER(USBDeviceFilters)(USBFlts.asOutParam()); + if (SUCCEEDED(rc)) + { + SafeIfaceArray <IUSBDeviceFilter> Coll; + rc = USBFlts->COMGETTER(DeviceFilters)(ComSafeArrayAsOutParam(Coll)); + if (SUCCEEDED(rc)) + { + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("\nUSB Device Filters:\n\n"); + + if (Coll.size() == 0) + { + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("<none>\n\n"); + } + else + { + for (size_t index = 0; index < Coll.size(); ++index) + { + ComPtr<IUSBDeviceFilter> DevPtr = Coll[index]; + + if (details != VMINFO_MACHINEREADABLE) + SHOW_UTF8_STRING("index", "Index:", FmtNm(szNm, "%zu", index)); + SHOW_BOOLEAN_PROP_EX(DevPtr, Active, FmtNm(szNm, "USBFilterActive%zu", index + 1), "Active:", "yes", "no"); + SHOW_STRING_PROP(DevPtr, Name, FmtNm(szNm, "USBFilterName%zu", index + 1), "Name:"); + SHOW_STRING_PROP(DevPtr, VendorId, FmtNm(szNm, "USBFilterVendorId%zu", index + 1), "VendorId:"); + SHOW_STRING_PROP(DevPtr, ProductId, FmtNm(szNm, "USBFilterProductId%zu", index + 1), "ProductId:"); + SHOW_STRING_PROP(DevPtr, Revision, FmtNm(szNm, "USBFilterRevision%zu", index + 1), "Revision:"); + SHOW_STRING_PROP(DevPtr, Manufacturer, FmtNm(szNm, "USBFilterManufacturer%zu", index + 1), "Manufacturer:"); + SHOW_STRING_PROP(DevPtr, Product, FmtNm(szNm, "USBFilterProduct%zu", index + 1), "Product:"); + SHOW_STRING_PROP(DevPtr, Remote, FmtNm(szNm, "USBFilterRemote%zu", index + 1), "Remote:"); + SHOW_STRING_PROP(DevPtr, SerialNumber, FmtNm(szNm, "USBFilterSerialNumber%zu", index + 1), "Serial Number:"); + if (details != VMINFO_MACHINEREADABLE) + { + ULONG fMaskedIfs; + CHECK_ERROR_RET(DevPtr, COMGETTER(MaskedInterfaces)(&fMaskedIfs), rc); + if (fMaskedIfs) + RTPrintf("%-28s %#010x\n", "Masked Interfaces:", fMaskedIfs); + RTPrintf("\n"); + } + } + } + } + + if (pConsole) + { + /* scope */ + { + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("Available remote USB devices:\n\n"); + + SafeIfaceArray <IHostUSBDevice> coll; + CHECK_ERROR_RET(pConsole, COMGETTER(RemoteUSBDevices)(ComSafeArrayAsOutParam(coll)), rc); + + if (coll.size() == 0) + { + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("<none>\n\n"); + } + else + { + /* This code is duplicated below, with USBAttach as prefix. */ + const char *pszPfx = "USBRemote"; + for (size_t i = 0; i < coll.size(); ++i) + { + ComPtr<IHostUSBDevice> dev = coll[i]; + + SHOW_STRING_PROP(dev, Id, FmtNm(szNm, "%sActive%zu", pszPfx, i + 1), "UUID:"); + SHOW_USHORT_PROP_EX2(dev, VendorId, FmtNm(szNm, "%sVendorId%zu", pszPfx, i + 1), "VendorId:", "", "%#06x", "%#06x (%04X)"); + SHOW_USHORT_PROP_EX2(dev, ProductId, FmtNm(szNm, "%sProductId%zu", pszPfx, i + 1), "ProductId:", "", "%#06x", "%#06x (%04X)"); + + USHORT bcdRevision; + CHECK_ERROR_RET(dev, COMGETTER(Revision)(&bcdRevision), rc); + if (details == VMINFO_MACHINEREADABLE) + RTStrPrintf(szValue, sizeof(szValue), "%#04x%02x", bcdRevision >> 8, bcdRevision & 0xff); + else + RTStrPrintf(szValue, sizeof(szValue), "%u.%u (%02u%02u)\n", + bcdRevision >> 8, bcdRevision & 0xff, bcdRevision >> 8, bcdRevision & 0xff); + SHOW_UTF8_STRING(FmtNm(szNm, "%sRevision%zu", pszPfx, i + 1), "Revision:", szValue); + + SHOW_STRING_PROP_NOT_EMPTY(dev, Manufacturer, FmtNm(szNm, "%sManufacturer%zu", pszPfx, i + 1), "Manufacturer:"); + SHOW_STRING_PROP_NOT_EMPTY(dev, Product, FmtNm(szNm, "%sProduct%zu", pszPfx, i + 1), "Product:"); + SHOW_STRING_PROP_NOT_EMPTY(dev, SerialNumber, FmtNm(szNm, "%sSerialNumber%zu", pszPfx, i + 1), "SerialNumber:"); + SHOW_STRING_PROP_NOT_EMPTY(dev, Address, FmtNm(szNm, "%sAddress%zu", pszPfx, i + 1), "Address:"); + + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("\n"); + } + } + } + + /* scope */ + { + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("Currently Attached USB Devices:\n\n"); + + SafeIfaceArray <IUSBDevice> coll; + CHECK_ERROR_RET(pConsole, COMGETTER(USBDevices)(ComSafeArrayAsOutParam(coll)), rc); + + if (coll.size() == 0) + { + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("<none>\n\n"); + } + else + { + /* This code is duplicated below, with USBAttach as prefix. */ + const char *pszPfx = "USBAttach"; + for (size_t i = 0; i < coll.size(); ++i) + { + ComPtr<IHostUSBDevice> dev = coll[i]; + + SHOW_STRING_PROP(dev, Id, FmtNm(szNm, "%sActive%zu", pszPfx, i + 1), "UUID:"); + SHOW_USHORT_PROP_EX2(dev, VendorId, FmtNm(szNm, "%sVendorId%zu", pszPfx, i + 1), "VendorId:", "", "%#06x", "%#06x (%04X)"); + SHOW_USHORT_PROP_EX2(dev, ProductId, FmtNm(szNm, "%sProductId%zu", pszPfx, i + 1), "ProductId:", "", "%#06x", "%#06x (%04X)"); + + USHORT bcdRevision; + CHECK_ERROR_RET(dev, COMGETTER(Revision)(&bcdRevision), rc); + if (details == VMINFO_MACHINEREADABLE) + RTStrPrintf(szValue, sizeof(szValue), "%#04x%02x", bcdRevision >> 8, bcdRevision & 0xff); + else + RTStrPrintf(szValue, sizeof(szValue), "%u.%u (%02u%02u)\n", + bcdRevision >> 8, bcdRevision & 0xff, bcdRevision >> 8, bcdRevision & 0xff); + SHOW_UTF8_STRING(FmtNm(szNm, "%sRevision%zu", pszPfx, i + 1), "Revision:", szValue); + + SHOW_STRING_PROP_NOT_EMPTY(dev, Manufacturer, FmtNm(szNm, "%sManufacturer%zu", pszPfx, i + 1), "Manufacturer:"); + SHOW_STRING_PROP_NOT_EMPTY(dev, Product, FmtNm(szNm, "%sProduct%zu", pszPfx, i + 1), "Product:"); + SHOW_STRING_PROP_NOT_EMPTY(dev, SerialNumber, FmtNm(szNm, "%sSerialNumber%zu", pszPfx, i + 1), "SerialNumber:"); + SHOW_STRING_PROP_NOT_EMPTY(dev, Address, FmtNm(szNm, "%sAddress%zu", pszPfx, i + 1), "Address:"); + + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("\n"); + } + } + } + } + } /* USB */ + +#ifdef VBOX_WITH_PCI_PASSTHROUGH + /* Host PCI passthrough devices */ + { + SafeIfaceArray <IPCIDeviceAttachment> assignments; + rc = machine->COMGETTER(PCIDeviceAssignments)(ComSafeArrayAsOutParam(assignments)); + if (SUCCEEDED(rc)) + { + if (assignments.size() > 0 && (details != VMINFO_MACHINEREADABLE)) + { + RTPrintf("\nAttached physical PCI devices:\n\n"); + } + + for (size_t index = 0; index < assignments.size(); ++index) + { + ComPtr<IPCIDeviceAttachment> Assignment = assignments[index]; + char szHostPCIAddress[32], szGuestPCIAddress[32]; + LONG iHostPCIAddress = -1, iGuestPCIAddress = -1; + Bstr DevName; + + Assignment->COMGETTER(Name)(DevName.asOutParam()); + Assignment->COMGETTER(HostAddress)(&iHostPCIAddress); + Assignment->COMGETTER(GuestAddress)(&iGuestPCIAddress); + PCIBusAddress().fromLong(iHostPCIAddress).format(szHostPCIAddress, sizeof(szHostPCIAddress)); + PCIBusAddress().fromLong(iGuestPCIAddress).format(szGuestPCIAddress, sizeof(szGuestPCIAddress)); + + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("AttachedHostPCI=%s,%s\n", szHostPCIAddress, szGuestPCIAddress); + else + RTPrintf(" Host device %ls at %s attached as %s\n", DevName.raw(), szHostPCIAddress, szGuestPCIAddress); + } + + if (assignments.size() > 0 && (details != VMINFO_MACHINEREADABLE)) + { + RTPrintf("\n"); + } + } + } + /* Host PCI passthrough devices */ +#endif + + /* + * Bandwidth groups + */ + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("Bandwidth groups: "); + { + ComPtr<IBandwidthControl> bwCtrl; + CHECK_ERROR_RET(machine, COMGETTER(BandwidthControl)(bwCtrl.asOutParam()), rc); + + rc = showBandwidthGroups(bwCtrl, details); + } + + + /* + * Shared folders + */ + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("Shared folders:"); + uint32_t numSharedFolders = 0; +#if 0 // not yet implemented + /* globally shared folders first */ + { + SafeIfaceArray <ISharedFolder> sfColl; + CHECK_ERROR_RET(pVirtualBox, COMGETTER(SharedFolders)(ComSafeArrayAsOutParam(sfColl)), rc); + for (size_t i = 0; i < sfColl.size(); ++i) + { + ComPtr<ISharedFolder> sf = sfColl[i]; + showSharedFolder(sf, details, "global mapping", "GlobalMapping", i + 1, numSharedFolders == 0); + ++numSharedFolders; + } + } +#endif + /* now VM mappings */ + { + com::SafeIfaceArray <ISharedFolder> folders; + CHECK_ERROR_RET(machine, COMGETTER(SharedFolders)(ComSafeArrayAsOutParam(folders)), rc); + for (size_t i = 0; i < folders.size(); ++i) + { + ComPtr<ISharedFolder> sf = folders[i]; + showSharedFolder(sf, details, "machine mapping", "MachineMapping", i + 1, numSharedFolders == 0); + ++numSharedFolders; + } + } + /* transient mappings */ + if (pConsole) + { + com::SafeIfaceArray <ISharedFolder> folders; + CHECK_ERROR_RET(pConsole, COMGETTER(SharedFolders)(ComSafeArrayAsOutParam(folders)), rc); + for (size_t i = 0; i < folders.size(); ++i) + { + ComPtr<ISharedFolder> sf = folders[i]; + showSharedFolder(sf, details, "transient mapping", "TransientMapping", i + 1, numSharedFolders == 0); + ++numSharedFolders; + } + } + if (!numSharedFolders && details != VMINFO_MACHINEREADABLE) + RTPrintf("<none>\n"); + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("\n"); + + if (pConsole) + { + /* + * Live VRDE info. + */ + ComPtr<IVRDEServerInfo> vrdeServerInfo; + CHECK_ERROR_RET(pConsole, COMGETTER(VRDEServerInfo)(vrdeServerInfo.asOutParam()), rc); + BOOL fActive = FALSE; + ULONG cNumberOfClients = 0; + LONG64 BeginTime = 0; + LONG64 EndTime = 0; + LONG64 BytesSent = 0; + LONG64 BytesSentTotal = 0; + LONG64 BytesReceived = 0; + LONG64 BytesReceivedTotal = 0; + Bstr User; + Bstr Domain; + Bstr ClientName; + Bstr ClientIP; + ULONG ClientVersion = 0; + ULONG EncryptionStyle = 0; + + if (!vrdeServerInfo.isNull()) + { + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(Active)(&fActive), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(NumberOfClients)(&cNumberOfClients), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(BeginTime)(&BeginTime), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(EndTime)(&EndTime), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(BytesSent)(&BytesSent), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(BytesSentTotal)(&BytesSentTotal), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(BytesReceived)(&BytesReceived), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(BytesReceivedTotal)(&BytesReceivedTotal), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(User)(User.asOutParam()), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(Domain)(Domain.asOutParam()), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(ClientName)(ClientName.asOutParam()), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(ClientIP)(ClientIP.asOutParam()), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(ClientVersion)(&ClientVersion), rc); + CHECK_ERROR_RET(vrdeServerInfo, COMGETTER(EncryptionStyle)(&EncryptionStyle), rc); + } + + SHOW_BOOL_VALUE_EX("VRDEActiveConnection", "VRDE Connection:", fActive, "active", "not active"); + SHOW_ULONG_VALUE("VRDEClients=", "Clients so far:", cNumberOfClients, ""); + + if (cNumberOfClients > 0) + { + char szTimeValue[128]; + makeTimeStr(szTimeValue, sizeof(szTimeValue), BeginTime); + if (fActive) + SHOW_UTF8_STRING("VRDEStartTime", "Start time:", szTimeValue); + else + { + SHOW_UTF8_STRING("VRDELastStartTime", "Last started:", szTimeValue); + makeTimeStr(szTimeValue, sizeof(szTimeValue), EndTime); + SHOW_UTF8_STRING("VRDELastEndTime", "Last ended:", szTimeValue); + } + + int64_t ThroughputSend = 0; + int64_t ThroughputReceive = 0; + if (EndTime != BeginTime) + { + ThroughputSend = (BytesSent * 1000) / (EndTime - BeginTime); + ThroughputReceive = (BytesReceived * 1000) / (EndTime - BeginTime); + } + SHOW_LONG64_VALUE("VRDEBytesSent", "Sent:", BytesSent, "Bytes"); + SHOW_LONG64_VALUE("VRDEThroughputSend", "Average speed:", ThroughputSend, "B/s"); + SHOW_LONG64_VALUE("VRDEBytesSentTotal", "Sent total:", BytesSentTotal, "Bytes"); + + SHOW_LONG64_VALUE("VRDEBytesReceived", "Received:", BytesReceived, "Bytes"); + SHOW_LONG64_VALUE("VRDEThroughputReceive", "Speed:", ThroughputReceive, "B/s"); + SHOW_LONG64_VALUE("VRDEBytesReceivedTotal", "Received total:", BytesReceivedTotal, "Bytes"); + + if (fActive) + { + SHOW_BSTR_STRING("VRDEUserName", "User name:", User); + SHOW_BSTR_STRING("VRDEDomain", "Domain:", Domain); + SHOW_BSTR_STRING("VRDEClientName", "Client name:", ClientName); + SHOW_BSTR_STRING("VRDEClientIP", "Client IP:", ClientIP); + SHOW_ULONG_VALUE("VRDEClientVersion", "Client version:", ClientVersion, ""); + SHOW_UTF8_STRING("VRDEEncryption", "Encryption:", EncryptionStyle == 0 ? "RDP4" : "RDP5 (X.509)"); + } + } + + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("\n"); + } + +#ifdef VBOX_WITH_RECORDING + { + /* Video capture */ + BOOL fCaptureVideo = FALSE; +# ifdef VBOX_WITH_AUDIO_RECORDING + BOOL fCaptureAudio = FALSE; +# endif + + ComPtr<IRecordingSettings> recordingSettings; + CHECK_ERROR_RET(machine, COMGETTER(RecordingSettings)(recordingSettings.asOutParam()), rc); + + SafeIfaceArray <IRecordingScreenSettings> saRecordingScreenScreens; + CHECK_ERROR_RET(recordingSettings, COMGETTER(Screens)(ComSafeArrayAsOutParam(saRecordingScreenScreens)), rc); + + /* For now all screens have the same configuration; so take screen 0 and work with that. */ + ULONG fFeatures; + CHECK_ERROR_RET(saRecordingScreenScreens[0], COMGETTER(Features)(&fFeatures), rc); + ULONG Width; + CHECK_ERROR_RET(saRecordingScreenScreens[0], COMGETTER(VideoWidth)(&Width), rc); + ULONG Height; + CHECK_ERROR_RET(saRecordingScreenScreens[0], COMGETTER(VideoHeight)(&Height), rc); + ULONG Rate; + CHECK_ERROR_RET(saRecordingScreenScreens[0], COMGETTER(VideoRate)(&Rate), rc); + ULONG Fps; + CHECK_ERROR_RET(saRecordingScreenScreens[0], COMGETTER(VideoFPS)(&Fps), rc); + Bstr bstrFile; + CHECK_ERROR_RET(saRecordingScreenScreens[0], COMGETTER(Filename)(bstrFile.asOutParam()), rc); + Bstr bstrOptions; + CHECK_ERROR_RET(saRecordingScreenScreens[0], COMGETTER(Options)(bstrOptions.asOutParam()), rc); + + Utf8Str strOptions(bstrOptions); + size_t pos = 0; + com::Utf8Str key, value; + while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos) + { + if (key.compare("vc_enabled", Utf8Str::CaseInsensitive) == 0) + { + fCaptureVideo = value.compare("true", Utf8Str::CaseInsensitive) == 0; + } + else if (key.compare("ac_enabled", Utf8Str::CaseInsensitive) == 0) + { +# ifdef VBOX_WITH_AUDIO_RECORDING + fCaptureAudio = value.compare("true", Utf8Str::CaseInsensitive) == 0; +# endif + } + } + + SHOW_BOOL_VALUE_EX("videocap", "Capturing:", fCaptureVideo, "active", "not active"); +# ifdef VBOX_WITH_AUDIO_RECORDING + SHOW_BOOL_VALUE_EX("videocapaudio", "Capture audio:", fCaptureAudio, "active", "not active"); +# endif + szValue[0] = '\0'; + for (size_t i = 0, off = 0; i < saRecordingScreenScreens.size(); i++) + { + BOOL fEnabled; + CHECK_ERROR_RET(saRecordingScreenScreens[i], COMGETTER(Enabled)(&fEnabled), rc); + if (fEnabled && off < sizeof(szValue) - 3) + off += RTStrPrintf(&szValue[off], sizeof(szValue) - off, off ? ",%zu" : "%zu", i); + } + SHOW_UTF8_STRING("capturescreens", "Capture screens:", szValue); + SHOW_BSTR_STRING("capturefilename", "Capture file:", bstrFile); + RTStrPrintf(szValue, sizeof(szValue), "%ux%u", Width, Height); + SHOW_UTF8_STRING("captureres", "Capture dimensions:", szValue); + SHOW_ULONG_VALUE("capturevideorate", "Capture rate:", Rate, "kbps"); + SHOW_ULONG_VALUE("capturevideofps", "Capture FPS:", Fps, "kbps"); + SHOW_BSTR_STRING("captureopts", "Capture options:", bstrOptions); + + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("\n"); + /** @todo Add more audio capturing profile / information here. */ + } +#endif /* VBOX_WITH_RECORDING */ + + if ( details == VMINFO_STANDARD + || details == VMINFO_FULL + || details == VMINFO_MACHINEREADABLE) + { + Bstr description; + machine->COMGETTER(Description)(description.asOutParam()); + if (!description.isEmpty()) + { + if (details == VMINFO_MACHINEREADABLE) + outputMachineReadableString("description", &description); + else + RTPrintf("Description:\n%ls\n", description.raw()); + } + } + + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("Guest:\n\n"); + + SHOW_ULONG_PROP(machine, MemoryBalloonSize, "GuestMemoryBalloon", "Configured memory balloon size:", "MB"); + + if (pConsole) + { + ComPtr<IGuest> guest; + rc = pConsole->COMGETTER(Guest)(guest.asOutParam()); + if (SUCCEEDED(rc) && !guest.isNull()) + { + SHOW_STRING_PROP_NOT_EMPTY(guest, OSTypeId, "GuestOSType", "OS type:"); + + AdditionsRunLevelType_T guestRunLevel; /** @todo Add a runlevel-to-string (e.g. 0 = "None") method? */ + rc = guest->COMGETTER(AdditionsRunLevel)(&guestRunLevel); + if (SUCCEEDED(rc)) + SHOW_ULONG_VALUE("GuestAdditionsRunLevel", "Additions run level:", (ULONG)guestRunLevel, ""); + + Bstr guestString; + rc = guest->COMGETTER(AdditionsVersion)(guestString.asOutParam()); + if ( SUCCEEDED(rc) + && !guestString.isEmpty()) + { + ULONG uRevision; + rc = guest->COMGETTER(AdditionsRevision)(&uRevision); + if (FAILED(rc)) + uRevision = 0; + RTStrPrintf(szValue, sizeof(szValue), "%ls r%u", guestString.raw(), uRevision); + SHOW_UTF8_STRING("GuestAdditionsVersion", "Additions version", szValue); + } + + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("\nGuest Facilities:\n\n"); + + /* Print information about known Guest Additions facilities: */ + SafeIfaceArray <IAdditionsFacility> collFac; + CHECK_ERROR_RET(guest, COMGETTER(Facilities)(ComSafeArrayAsOutParam(collFac)), rc); + LONG64 lLastUpdatedMS; + char szLastUpdated[32]; + AdditionsFacilityStatus_T curStatus; + for (size_t index = 0; index < collFac.size(); ++index) + { + ComPtr<IAdditionsFacility> fac = collFac[index]; + if (fac) + { + CHECK_ERROR_RET(fac, COMGETTER(Name)(guestString.asOutParam()), rc); + if (!guestString.isEmpty()) + { + CHECK_ERROR_RET(fac, COMGETTER(Status)(&curStatus), rc); + CHECK_ERROR_RET(fac, COMGETTER(LastUpdated)(&lLastUpdatedMS), rc); + if (details == VMINFO_MACHINEREADABLE) + RTPrintf("GuestAdditionsFacility_%ls=%u,%lld\n", + guestString.raw(), curStatus, lLastUpdatedMS); + else + { + makeTimeStr(szLastUpdated, sizeof(szLastUpdated), lLastUpdatedMS); + RTPrintf("Facility \"%ls\": %s (last update: %s)\n", + guestString.raw(), facilityStateToName(curStatus, false /* No short naming */), szLastUpdated); + } + } + else + AssertMsgFailed(("Facility with undefined name retrieved!\n")); + } + else + AssertMsgFailed(("Invalid facility returned!\n")); + } + if (!collFac.size() && details != VMINFO_MACHINEREADABLE) + RTPrintf("No active facilities.\n"); + } + } + + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("\n"); + + /* + * snapshots + */ + ComPtr<ISnapshot> snapshot; + rc = machine->FindSnapshot(Bstr().raw(), snapshot.asOutParam()); + if (SUCCEEDED(rc) && snapshot) + { + ComPtr<ISnapshot> currentSnapshot; + rc = machine->COMGETTER(CurrentSnapshot)(currentSnapshot.asOutParam()); + if (SUCCEEDED(rc)) + { + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("Snapshots:\n\n"); + showSnapshots(snapshot, currentSnapshot, details); + } + } + + if (details != VMINFO_MACHINEREADABLE) + RTPrintf("\n"); + return S_OK; +} + +#if defined(_MSC_VER) +# pragma optimize("", on) +# pragma warning(pop) +#endif + +static const RTGETOPTDEF g_aShowVMInfoOptions[] = +{ + { "--details", 'D', RTGETOPT_REQ_NOTHING }, + { "-details", 'D', RTGETOPT_REQ_NOTHING }, // deprecated + { "--machinereadable", 'M', RTGETOPT_REQ_NOTHING }, + { "-machinereadable", 'M', RTGETOPT_REQ_NOTHING }, // deprecated + { "--log", 'l', RTGETOPT_REQ_UINT32 }, +}; + +RTEXITCODE handleShowVMInfo(HandlerArg *a) +{ + HRESULT rc; + const char *VMNameOrUuid = NULL; + bool fLog = false; + uint32_t uLogIdx = 0; + bool fDetails = false; + bool fMachinereadable = 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_aShowVMInfoOptions, RT_ELEMENTS(g_aShowVMInfoOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'D': // --details + fDetails = true; + break; + + case 'M': // --machinereadable + fMachinereadable = true; + break; + + case 'l': // --log + fLog = true; + uLogIdx = ValueUnion.u32; + break; + + case VINF_GETOPT_NOT_OPTION: + if (!VMNameOrUuid) + VMNameOrUuid = ValueUnion.psz; + else + return errorSyntax(USAGE_SHOWVMINFO, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + return errorGetOpt(USAGE_SHOWVMINFO, c, &ValueUnion); + } + } + + /* check for required options */ + if (!VMNameOrUuid) + return errorSyntax(USAGE_SHOWVMINFO, "VM name or UUID required"); + + /* try to find the given machine */ + ComPtr<IMachine> machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(VMNameOrUuid).raw(), + machine.asOutParam())); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + /* Printing the log is exclusive. */ + if (fLog && (fMachinereadable || fDetails)) + return errorSyntax(USAGE_SHOWVMINFO, "Option --log is exclusive"); + + if (fLog) + { + ULONG64 uOffset = 0; + SafeArray<BYTE> aLogData; + size_t cbLogData; + while (true) + { + /* Reset the array */ + aLogData.setNull(); + /* Fetch a chunk of the log file */ + CHECK_ERROR_BREAK(machine, ReadLog(uLogIdx, uOffset, _1M, + ComSafeArrayAsOutParam(aLogData))); + cbLogData = aLogData.size(); + if (cbLogData == 0) + break; + /* aLogData has a platform dependent line ending, standardize on + * Unix style, as RTStrmWrite does the LF -> CR/LF replacement on + * Windows. Otherwise we end up with CR/CR/LF on Windows. */ + size_t cbLogDataPrint = cbLogData; + for (BYTE *s = aLogData.raw(), *d = s; + s - aLogData.raw() < (ssize_t)cbLogData; + s++, d++) + { + if (*s == '\r') + { + /* skip over CR, adjust destination */ + d--; + cbLogDataPrint--; + } + else if (s != d) + *d = *s; + } + RTStrmWrite(g_pStdOut, aLogData.raw(), cbLogDataPrint); + uOffset += cbLogData; + } + } + else + { + /* 2nd option can be -details or -argdump */ + VMINFO_DETAILS details = VMINFO_NONE; + if (fMachinereadable) + details = VMINFO_MACHINEREADABLE; + else if (fDetails) + details = VMINFO_FULL; + else + details = VMINFO_STANDARD; + + /* open an existing session for the VM */ + rc = machine->LockMachine(a->session, LockType_Shared); + if (SUCCEEDED(rc)) + /* get the session machine */ + rc = a->session->COMGETTER(Machine)(machine.asOutParam()); + + rc = showVMInfo(a->virtualBox, machine, a->session, details); + + a->session->UnlockMachine(); + } + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +#endif /* !VBOX_ONLY_DOCS */ +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageList.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageList.cpp new file mode 100644 index 00000000..d5fb8ee2 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageList.cpp @@ -0,0 +1,1608 @@ +/* $Id: VBoxManageList.cpp $ */ +/** @file + * VBoxManage - The 'list' command. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_ONLY_DOCS + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> + +#include <VBox/com/VirtualBox.h> + +#include <VBox/log.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include <iprt/getopt.h> +#include <iprt/ctype.h> + +#include <vector> +#include <algorithm> + +#include "VBoxManage.h" +using namespace com; + +#ifdef VBOX_WITH_HOSTNETIF_API +static const char *getHostIfMediumTypeText(HostNetworkInterfaceMediumType_T enmType) +{ + switch (enmType) + { + case HostNetworkInterfaceMediumType_Ethernet: return "Ethernet"; + case HostNetworkInterfaceMediumType_PPP: return "PPP"; + case HostNetworkInterfaceMediumType_SLIP: return "SLIP"; + case HostNetworkInterfaceMediumType_Unknown: return "Unknown"; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case HostNetworkInterfaceMediumType_32BitHack: break; /* Shut up compiler warnings. */ +#endif + } + return "unknown"; +} + +static const char *getHostIfStatusText(HostNetworkInterfaceStatus_T enmStatus) +{ + switch (enmStatus) + { + case HostNetworkInterfaceStatus_Up: return "Up"; + case HostNetworkInterfaceStatus_Down: return "Down"; + case HostNetworkInterfaceStatus_Unknown: return "Unknown"; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case HostNetworkInterfaceStatus_32BitHack: break; /* Shut up compiler warnings. */ +#endif + } + return "unknown"; +} +#endif /* VBOX_WITH_HOSTNETIF_API */ + +static const char*getDeviceTypeText(DeviceType_T enmType) +{ + switch (enmType) + { + case DeviceType_HardDisk: return "HardDisk"; + case DeviceType_DVD: return "DVD"; + case DeviceType_Floppy: return "Floppy"; + /* Make MSC happy */ + case DeviceType_Null: return "Null"; + case DeviceType_Network: return "Network"; + case DeviceType_USB: return "USB"; + case DeviceType_SharedFolder: return "SharedFolder"; + case DeviceType_Graphics3D: return "Graphics3D"; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case DeviceType_32BitHack: break; /* Shut up compiler warnings. */ +#endif + } + return "Unknown"; +} + + +/** + * List internal networks. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + */ +static HRESULT listInternalNetworks(const ComPtr<IVirtualBox> pVirtualBox) +{ + HRESULT rc; + com::SafeArray<BSTR> internalNetworks; + CHECK_ERROR(pVirtualBox, COMGETTER(InternalNetworks)(ComSafeArrayAsOutParam(internalNetworks))); + for (size_t i = 0; i < internalNetworks.size(); ++i) + { + RTPrintf("Name: %ls\n", internalNetworks[i]); + } + return rc; +} + + +/** + * List network interfaces information (bridged/host only). + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + * @param fIsBridged Selects between listing host interfaces (for + * use with bridging) or host only interfaces. + */ +static HRESULT listNetworkInterfaces(const ComPtr<IVirtualBox> pVirtualBox, + bool fIsBridged) +{ + HRESULT rc; + ComPtr<IHost> host; + CHECK_ERROR(pVirtualBox, COMGETTER(Host)(host.asOutParam())); + com::SafeIfaceArray<IHostNetworkInterface> hostNetworkInterfaces; +#if defined(VBOX_WITH_NETFLT) + if (fIsBridged) + CHECK_ERROR(host, FindHostNetworkInterfacesOfType(HostNetworkInterfaceType_Bridged, + ComSafeArrayAsOutParam(hostNetworkInterfaces))); + else + CHECK_ERROR(host, FindHostNetworkInterfacesOfType(HostNetworkInterfaceType_HostOnly, + ComSafeArrayAsOutParam(hostNetworkInterfaces))); +#else + RT_NOREF(fIsBridged); + CHECK_ERROR(host, COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(hostNetworkInterfaces))); +#endif + for (size_t i = 0; i < hostNetworkInterfaces.size(); ++i) + { + ComPtr<IHostNetworkInterface> networkInterface = hostNetworkInterfaces[i]; +#ifndef VBOX_WITH_HOSTNETIF_API + Bstr interfaceName; + networkInterface->COMGETTER(Name)(interfaceName.asOutParam()); + RTPrintf("Name: %ls\n", interfaceName.raw()); + Guid interfaceGuid; + networkInterface->COMGETTER(Id)(interfaceGuid.asOutParam()); + RTPrintf("GUID: %ls\n\n", Bstr(interfaceGuid.toString()).raw()); +#else /* VBOX_WITH_HOSTNETIF_API */ + Bstr interfaceName; + networkInterface->COMGETTER(Name)(interfaceName.asOutParam()); + RTPrintf("Name: %ls\n", interfaceName.raw()); + Bstr interfaceGuid; + networkInterface->COMGETTER(Id)(interfaceGuid.asOutParam()); + RTPrintf("GUID: %ls\n", interfaceGuid.raw()); + BOOL bDHCPEnabled; + networkInterface->COMGETTER(DHCPEnabled)(&bDHCPEnabled); + RTPrintf("DHCP: %s\n", bDHCPEnabled ? "Enabled" : "Disabled"); + + Bstr IPAddress; + networkInterface->COMGETTER(IPAddress)(IPAddress.asOutParam()); + RTPrintf("IPAddress: %ls\n", IPAddress.raw()); + Bstr NetworkMask; + networkInterface->COMGETTER(NetworkMask)(NetworkMask.asOutParam()); + RTPrintf("NetworkMask: %ls\n", NetworkMask.raw()); + Bstr IPV6Address; + networkInterface->COMGETTER(IPV6Address)(IPV6Address.asOutParam()); + RTPrintf("IPV6Address: %ls\n", IPV6Address.raw()); + ULONG IPV6NetworkMaskPrefixLength; + networkInterface->COMGETTER(IPV6NetworkMaskPrefixLength)(&IPV6NetworkMaskPrefixLength); + RTPrintf("IPV6NetworkMaskPrefixLength: %d\n", IPV6NetworkMaskPrefixLength); + Bstr HardwareAddress; + networkInterface->COMGETTER(HardwareAddress)(HardwareAddress.asOutParam()); + RTPrintf("HardwareAddress: %ls\n", HardwareAddress.raw()); + HostNetworkInterfaceMediumType_T Type; + networkInterface->COMGETTER(MediumType)(&Type); + RTPrintf("MediumType: %s\n", getHostIfMediumTypeText(Type)); + BOOL fWireless; + networkInterface->COMGETTER(Wireless)(&fWireless); + RTPrintf("Wireless: %s\n", fWireless ? "Yes" : "No"); + HostNetworkInterfaceStatus_T Status; + networkInterface->COMGETTER(Status)(&Status); + RTPrintf("Status: %s\n", getHostIfStatusText(Status)); + Bstr netName; + networkInterface->COMGETTER(NetworkName)(netName.asOutParam()); + RTPrintf("VBoxNetworkName: %ls\n\n", netName.raw()); +#endif + } + return rc; +} + + +/** + * List host information. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + */ +static HRESULT listHostInfo(const ComPtr<IVirtualBox> pVirtualBox) +{ + static struct + { + ProcessorFeature_T feature; + const char *pszName; + } features[] + = + { + { ProcessorFeature_HWVirtEx, "HW virtualization" }, + { ProcessorFeature_PAE, "PAE" }, + { ProcessorFeature_LongMode, "long mode" }, + { ProcessorFeature_NestedPaging, "nested paging" }, + }; + HRESULT rc; + ComPtr<IHost> Host; + CHECK_ERROR(pVirtualBox, COMGETTER(Host)(Host.asOutParam())); + + RTPrintf("Host Information:\n\n"); + + LONG64 u64UtcTime = 0; + CHECK_ERROR(Host, COMGETTER(UTCTime)(&u64UtcTime)); + RTTIMESPEC timeSpec; + char szTime[32]; + RTPrintf("Host time: %s\n", RTTimeSpecToString(RTTimeSpecSetMilli(&timeSpec, u64UtcTime), szTime, sizeof(szTime))); + + ULONG processorOnlineCount = 0; + CHECK_ERROR(Host, COMGETTER(ProcessorOnlineCount)(&processorOnlineCount)); + RTPrintf("Processor online count: %lu\n", processorOnlineCount); + ULONG processorCount = 0; + CHECK_ERROR(Host, COMGETTER(ProcessorCount)(&processorCount)); + RTPrintf("Processor count: %lu\n", processorCount); + ULONG processorOnlineCoreCount = 0; + CHECK_ERROR(Host, COMGETTER(ProcessorOnlineCoreCount)(&processorOnlineCoreCount)); + RTPrintf("Processor online core count: %lu\n", processorOnlineCoreCount); + ULONG processorCoreCount = 0; + CHECK_ERROR(Host, COMGETTER(ProcessorCoreCount)(&processorCoreCount)); + RTPrintf("Processor core count: %lu\n", processorCoreCount); + for (unsigned i = 0; i < RT_ELEMENTS(features); i++) + { + BOOL supported; + CHECK_ERROR(Host, GetProcessorFeature(features[i].feature, &supported)); + RTPrintf("Processor supports %s: %s\n", features[i].pszName, supported ? "yes" : "no"); + } + for (ULONG i = 0; i < processorCount; i++) + { + ULONG processorSpeed = 0; + CHECK_ERROR(Host, GetProcessorSpeed(i, &processorSpeed)); + if (processorSpeed) + RTPrintf("Processor#%u speed: %lu MHz\n", i, processorSpeed); + else + RTPrintf("Processor#%u speed: unknown\n", i); + Bstr processorDescription; + CHECK_ERROR(Host, GetProcessorDescription(i, processorDescription.asOutParam())); + RTPrintf("Processor#%u description: %ls\n", i, processorDescription.raw()); + } + + ULONG memorySize = 0; + CHECK_ERROR(Host, COMGETTER(MemorySize)(&memorySize)); + RTPrintf("Memory size: %lu MByte\n", memorySize); + + ULONG memoryAvailable = 0; + CHECK_ERROR(Host, COMGETTER(MemoryAvailable)(&memoryAvailable)); + RTPrintf("Memory available: %lu MByte\n", memoryAvailable); + + Bstr operatingSystem; + CHECK_ERROR(Host, COMGETTER(OperatingSystem)(operatingSystem.asOutParam())); + RTPrintf("Operating system: %ls\n", operatingSystem.raw()); + + Bstr oSVersion; + CHECK_ERROR(Host, COMGETTER(OSVersion)(oSVersion.asOutParam())); + RTPrintf("Operating system version: %ls\n", oSVersion.raw()); + return rc; +} + + +/** + * List media information. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + * @param aMedia Medium objects to list information for. + * @param pszParentUUIDStr String with the parent UUID string (or "base"). + * @param fOptLong Long (@c true) or short list format. + */ +static HRESULT listMedia(const ComPtr<IVirtualBox> pVirtualBox, + const com::SafeIfaceArray<IMedium> &aMedia, + const char *pszParentUUIDStr, + bool fOptLong) +{ + HRESULT rc = S_OK; + for (size_t i = 0; i < aMedia.size(); ++i) + { + ComPtr<IMedium> pMedium = aMedia[i]; + + rc = showMediumInfo(pVirtualBox, pMedium, pszParentUUIDStr, fOptLong); + + RTPrintf("\n"); + + com::SafeIfaceArray<IMedium> children; + CHECK_ERROR(pMedium, COMGETTER(Children)(ComSafeArrayAsOutParam(children))); + if (children.size() > 0) + { + Bstr uuid; + pMedium->COMGETTER(Id)(uuid.asOutParam()); + + // depth first listing of child media + rc = listMedia(pVirtualBox, children, Utf8Str(uuid).c_str(), fOptLong); + } + } + + return rc; +} + + +/** + * List virtual image backends. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + */ +static HRESULT listHddBackends(const ComPtr<IVirtualBox> pVirtualBox) +{ + HRESULT rc; + ComPtr<ISystemProperties> systemProperties; + CHECK_ERROR(pVirtualBox, COMGETTER(SystemProperties)(systemProperties.asOutParam())); + com::SafeIfaceArray<IMediumFormat> mediumFormats; + CHECK_ERROR(systemProperties, COMGETTER(MediumFormats)(ComSafeArrayAsOutParam(mediumFormats))); + + RTPrintf("Supported hard disk backends:\n\n"); + for (size_t i = 0; i < mediumFormats.size(); ++i) + { + /* General information */ + Bstr id; + CHECK_ERROR(mediumFormats[i], COMGETTER(Id)(id.asOutParam())); + + Bstr description; + CHECK_ERROR(mediumFormats[i], + COMGETTER(Name)(description.asOutParam())); + + ULONG caps = 0; + com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap; + CHECK_ERROR(mediumFormats[i], + COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap))); + for (ULONG j = 0; j < mediumFormatCap.size(); j++) + caps |= mediumFormatCap[j]; + + + RTPrintf("Backend %u: id='%ls' description='%ls' capabilities=%#06x extensions='", + i, id.raw(), description.raw(), caps); + + /* File extensions */ + com::SafeArray<BSTR> fileExtensions; + com::SafeArray<DeviceType_T> deviceTypes; + CHECK_ERROR(mediumFormats[i], + DescribeFileExtensions(ComSafeArrayAsOutParam(fileExtensions), ComSafeArrayAsOutParam(deviceTypes))); + for (size_t j = 0; j < fileExtensions.size(); ++j) + { + RTPrintf("%ls (%s)", Bstr(fileExtensions[j]).raw(), getDeviceTypeText(deviceTypes[j])); + if (j != fileExtensions.size()-1) + RTPrintf(","); + } + RTPrintf("'"); + + /* Configuration keys */ + com::SafeArray<BSTR> propertyNames; + com::SafeArray<BSTR> propertyDescriptions; + com::SafeArray<DataType_T> propertyTypes; + com::SafeArray<ULONG> propertyFlags; + com::SafeArray<BSTR> propertyDefaults; + CHECK_ERROR(mediumFormats[i], + DescribeProperties(ComSafeArrayAsOutParam(propertyNames), + ComSafeArrayAsOutParam(propertyDescriptions), + ComSafeArrayAsOutParam(propertyTypes), + ComSafeArrayAsOutParam(propertyFlags), + ComSafeArrayAsOutParam(propertyDefaults))); + + RTPrintf(" properties=("); + if (propertyNames.size() > 0) + { + for (size_t j = 0; j < propertyNames.size(); ++j) + { + RTPrintf("\n name='%ls' desc='%ls' type=", + Bstr(propertyNames[j]).raw(), Bstr(propertyDescriptions[j]).raw()); + switch (propertyTypes[j]) + { + case DataType_Int32: RTPrintf("int"); break; + case DataType_Int8: RTPrintf("byte"); break; + case DataType_String: RTPrintf("string"); break; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case DataType_32BitHack: break; /* Shut up compiler warnings. */ +#endif + } + RTPrintf(" flags=%#04x", propertyFlags[j]); + RTPrintf(" default='%ls'", Bstr(propertyDefaults[j]).raw()); + if (j != propertyNames.size()-1) + RTPrintf(", "); + } + } + RTPrintf(")\n"); + } + return rc; +} + + +/** + * List USB devices attached to the host. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + */ +static HRESULT listUsbHost(const ComPtr<IVirtualBox> &pVirtualBox) +{ + HRESULT rc; + ComPtr<IHost> Host; + CHECK_ERROR_RET(pVirtualBox, COMGETTER(Host)(Host.asOutParam()), 1); + + SafeIfaceArray<IHostUSBDevice> CollPtr; + CHECK_ERROR_RET(Host, COMGETTER(USBDevices)(ComSafeArrayAsOutParam(CollPtr)), 1); + + RTPrintf("Host USB Devices:\n\n"); + + if (CollPtr.size() == 0) + { + RTPrintf("<none>\n\n"); + } + else + { + for (size_t i = 0; i < CollPtr.size(); ++i) + { + ComPtr<IHostUSBDevice> dev = CollPtr[i]; + + /* Query info. */ + Bstr id; + CHECK_ERROR_RET(dev, COMGETTER(Id)(id.asOutParam()), 1); + USHORT usVendorId; + CHECK_ERROR_RET(dev, COMGETTER(VendorId)(&usVendorId), 1); + USHORT usProductId; + CHECK_ERROR_RET(dev, COMGETTER(ProductId)(&usProductId), 1); + USHORT bcdRevision; + CHECK_ERROR_RET(dev, COMGETTER(Revision)(&bcdRevision), 1); + USHORT usPort; + CHECK_ERROR_RET(dev, COMGETTER(Port)(&usPort), 1); + USHORT usVersion; + CHECK_ERROR_RET(dev, COMGETTER(Version)(&usVersion), 1); + USHORT usPortVersion; + CHECK_ERROR_RET(dev, COMGETTER(PortVersion)(&usPortVersion), 1); + USBConnectionSpeed_T enmSpeed; + CHECK_ERROR_RET(dev, COMGETTER(Speed)(&enmSpeed), 1); + + RTPrintf("UUID: %s\n" + "VendorId: %#06x (%04X)\n" + "ProductId: %#06x (%04X)\n" + "Revision: %u.%u (%02u%02u)\n" + "Port: %u\n", + Utf8Str(id).c_str(), + usVendorId, usVendorId, usProductId, usProductId, + bcdRevision >> 8, bcdRevision & 0xff, + bcdRevision >> 8, bcdRevision & 0xff, + usPort); + + const char *pszSpeed = "?"; + switch (enmSpeed) + { + case USBConnectionSpeed_Low: + pszSpeed = "Low"; + break; + case USBConnectionSpeed_Full: + pszSpeed = "Full"; + break; + case USBConnectionSpeed_High: + pszSpeed = "High"; + break; + case USBConnectionSpeed_Super: + pszSpeed = "Super"; + break; + case USBConnectionSpeed_SuperPlus: + pszSpeed = "SuperPlus"; + break; + default: + ASSERT(false); + break; + } + + RTPrintf("USB version/speed: %u/%s\n", usVersion, pszSpeed); + + /* optional stuff. */ + SafeArray<BSTR> CollDevInfo; + Bstr bstr; + CHECK_ERROR_RET(dev, COMGETTER(DeviceInfo)(ComSafeArrayAsOutParam(CollDevInfo)), 1); + if (CollDevInfo.size() >= 1) + bstr = Bstr(CollDevInfo[0]); + if (!bstr.isEmpty()) + RTPrintf("Manufacturer: %ls\n", bstr.raw()); + if (CollDevInfo.size() >= 2) + bstr = Bstr(CollDevInfo[1]); + if (!bstr.isEmpty()) + RTPrintf("Product: %ls\n", bstr.raw()); + CHECK_ERROR_RET(dev, COMGETTER(SerialNumber)(bstr.asOutParam()), 1); + if (!bstr.isEmpty()) + RTPrintf("SerialNumber: %ls\n", bstr.raw()); + CHECK_ERROR_RET(dev, COMGETTER(Address)(bstr.asOutParam()), 1); + if (!bstr.isEmpty()) + RTPrintf("Address: %ls\n", bstr.raw()); + + /* current state */ + USBDeviceState_T state; + CHECK_ERROR_RET(dev, COMGETTER(State)(&state), 1); + const char *pszState = "?"; + switch (state) + { + case USBDeviceState_NotSupported: + pszState = "Not supported"; + break; + case USBDeviceState_Unavailable: + pszState = "Unavailable"; + break; + case USBDeviceState_Busy: + pszState = "Busy"; + break; + case USBDeviceState_Available: + pszState = "Available"; + break; + case USBDeviceState_Held: + pszState = "Held"; + break; + case USBDeviceState_Captured: + pszState = "Captured"; + break; + default: + ASSERT(false); + break; + } + RTPrintf("Current State: %s\n\n", pszState); + } + } + return rc; +} + + +/** + * List USB filters. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + */ +static HRESULT listUsbFilters(const ComPtr<IVirtualBox> &pVirtualBox) +{ + HRESULT rc; + + RTPrintf("Global USB Device Filters:\n\n"); + + ComPtr<IHost> host; + CHECK_ERROR_RET(pVirtualBox, COMGETTER(Host)(host.asOutParam()), 1); + + SafeIfaceArray<IHostUSBDeviceFilter> coll; + CHECK_ERROR_RET(host, COMGETTER(USBDeviceFilters)(ComSafeArrayAsOutParam(coll)), 1); + + if (coll.size() == 0) + { + RTPrintf("<none>\n\n"); + } + else + { + for (size_t index = 0; index < coll.size(); ++index) + { + ComPtr<IHostUSBDeviceFilter> flt = coll[index]; + + /* Query info. */ + + RTPrintf("Index: %zu\n", index); + + BOOL active = FALSE; + CHECK_ERROR_RET(flt, COMGETTER(Active)(&active), 1); + RTPrintf("Active: %s\n", active ? "yes" : "no"); + + USBDeviceFilterAction_T action; + CHECK_ERROR_RET(flt, COMGETTER(Action)(&action), 1); + const char *pszAction = "<invalid>"; + switch (action) + { + case USBDeviceFilterAction_Ignore: + pszAction = "Ignore"; + break; + case USBDeviceFilterAction_Hold: + pszAction = "Hold"; + break; + default: + break; + } + RTPrintf("Action: %s\n", pszAction); + + Bstr bstr; + CHECK_ERROR_RET(flt, COMGETTER(Name)(bstr.asOutParam()), 1); + RTPrintf("Name: %ls\n", bstr.raw()); + CHECK_ERROR_RET(flt, COMGETTER(VendorId)(bstr.asOutParam()), 1); + RTPrintf("VendorId: %ls\n", bstr.raw()); + CHECK_ERROR_RET(flt, COMGETTER(ProductId)(bstr.asOutParam()), 1); + RTPrintf("ProductId: %ls\n", bstr.raw()); + CHECK_ERROR_RET(flt, COMGETTER(Revision)(bstr.asOutParam()), 1); + RTPrintf("Revision: %ls\n", bstr.raw()); + CHECK_ERROR_RET(flt, COMGETTER(Manufacturer)(bstr.asOutParam()), 1); + RTPrintf("Manufacturer: %ls\n", bstr.raw()); + CHECK_ERROR_RET(flt, COMGETTER(Product)(bstr.asOutParam()), 1); + RTPrintf("Product: %ls\n", bstr.raw()); + CHECK_ERROR_RET(flt, COMGETTER(SerialNumber)(bstr.asOutParam()), 1); + RTPrintf("Serial Number: %ls\n\n", bstr.raw()); + } + } + return rc; +} + + +/** + * List system properties. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + */ +static HRESULT listSystemProperties(const ComPtr<IVirtualBox> &pVirtualBox) +{ + ComPtr<ISystemProperties> systemProperties; + CHECK_ERROR2I_RET(pVirtualBox, COMGETTER(SystemProperties)(systemProperties.asOutParam()), hrcCheck); + + Bstr str; + ULONG ulValue; + LONG64 i64Value; + BOOL fValue; + const char *psz; + + pVirtualBox->COMGETTER(APIVersion)(str.asOutParam()); + RTPrintf("API version: %ls\n", str.raw()); + + systemProperties->COMGETTER(MinGuestRAM)(&ulValue); + RTPrintf("Minimum guest RAM size: %u Megabytes\n", ulValue); + systemProperties->COMGETTER(MaxGuestRAM)(&ulValue); + RTPrintf("Maximum guest RAM size: %u Megabytes\n", ulValue); + systemProperties->COMGETTER(MinGuestVRAM)(&ulValue); + RTPrintf("Minimum video RAM size: %u Megabytes\n", ulValue); + systemProperties->COMGETTER(MaxGuestVRAM)(&ulValue); + RTPrintf("Maximum video RAM size: %u Megabytes\n", ulValue); + systemProperties->COMGETTER(MaxGuestMonitors)(&ulValue); + RTPrintf("Maximum guest monitor count: %u\n", ulValue); + systemProperties->COMGETTER(MinGuestCPUCount)(&ulValue); + RTPrintf("Minimum guest CPU count: %u\n", ulValue); + systemProperties->COMGETTER(MaxGuestCPUCount)(&ulValue); + RTPrintf("Maximum guest CPU count: %u\n", ulValue); + systemProperties->COMGETTER(InfoVDSize)(&i64Value); + RTPrintf("Virtual disk limit (info): %lld Bytes\n", i64Value); + systemProperties->COMGETTER(SerialPortCount)(&ulValue); + RTPrintf("Maximum Serial Port count: %u\n", ulValue); + systemProperties->COMGETTER(ParallelPortCount)(&ulValue); + RTPrintf("Maximum Parallel Port count: %u\n", ulValue); + systemProperties->COMGETTER(MaxBootPosition)(&ulValue); + RTPrintf("Maximum Boot Position: %u\n", ulValue); + systemProperties->GetMaxNetworkAdapters(ChipsetType_PIIX3, &ulValue); + RTPrintf("Maximum PIIX3 Network Adapter count: %u\n", ulValue); + systemProperties->GetMaxNetworkAdapters(ChipsetType_ICH9, &ulValue); + RTPrintf("Maximum ICH9 Network Adapter count: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_PIIX3, StorageBus_IDE, &ulValue); + RTPrintf("Maximum PIIX3 IDE Controllers: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_ICH9, StorageBus_IDE, &ulValue); + RTPrintf("Maximum ICH9 IDE Controllers: %u\n", ulValue); + systemProperties->GetMaxPortCountForStorageBus(StorageBus_IDE, &ulValue); + RTPrintf("Maximum IDE Port count: %u\n", ulValue); + systemProperties->GetMaxDevicesPerPortForStorageBus(StorageBus_IDE, &ulValue); + RTPrintf("Maximum Devices per IDE Port: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_PIIX3, StorageBus_SATA, &ulValue); + RTPrintf("Maximum PIIX3 SATA Controllers: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_ICH9, StorageBus_SATA, &ulValue); + RTPrintf("Maximum ICH9 SATA Controllers: %u\n", ulValue); + systemProperties->GetMaxPortCountForStorageBus(StorageBus_SATA, &ulValue); + RTPrintf("Maximum SATA Port count: %u\n", ulValue); + systemProperties->GetMaxDevicesPerPortForStorageBus(StorageBus_SATA, &ulValue); + RTPrintf("Maximum Devices per SATA Port: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_PIIX3, StorageBus_SCSI, &ulValue); + RTPrintf("Maximum PIIX3 SCSI Controllers: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_ICH9, StorageBus_SCSI, &ulValue); + RTPrintf("Maximum ICH9 SCSI Controllers: %u\n", ulValue); + systemProperties->GetMaxPortCountForStorageBus(StorageBus_SCSI, &ulValue); + RTPrintf("Maximum SCSI Port count: %u\n", ulValue); + systemProperties->GetMaxDevicesPerPortForStorageBus(StorageBus_SCSI, &ulValue); + RTPrintf("Maximum Devices per SCSI Port: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_PIIX3, StorageBus_SAS, &ulValue); + RTPrintf("Maximum SAS PIIX3 Controllers: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_ICH9, StorageBus_SAS, &ulValue); + RTPrintf("Maximum SAS ICH9 Controllers: %u\n", ulValue); + systemProperties->GetMaxPortCountForStorageBus(StorageBus_SAS, &ulValue); + RTPrintf("Maximum SAS Port count: %u\n", ulValue); + systemProperties->GetMaxDevicesPerPortForStorageBus(StorageBus_SAS, &ulValue); + RTPrintf("Maximum Devices per SAS Port: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_PIIX3, StorageBus_PCIe, &ulValue); + RTPrintf("Maximum NVMe PIIX3 Controllers: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_ICH9, StorageBus_PCIe, &ulValue); + RTPrintf("Maximum NVMe ICH9 Controllers: %u\n", ulValue); + systemProperties->GetMaxPortCountForStorageBus(StorageBus_PCIe, &ulValue); + RTPrintf("Maximum NVMe Port count: %u\n", ulValue); + systemProperties->GetMaxDevicesPerPortForStorageBus(StorageBus_PCIe, &ulValue); + RTPrintf("Maximum Devices per NVMe Port: %u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_PIIX3, StorageBus_Floppy, &ulValue); + RTPrintf("Maximum PIIX3 Floppy Controllers:%u\n", ulValue); + systemProperties->GetMaxInstancesOfStorageBus(ChipsetType_ICH9, StorageBus_Floppy, &ulValue); + RTPrintf("Maximum ICH9 Floppy Controllers: %u\n", ulValue); + systemProperties->GetMaxPortCountForStorageBus(StorageBus_Floppy, &ulValue); + RTPrintf("Maximum Floppy Port count: %u\n", ulValue); + systemProperties->GetMaxDevicesPerPortForStorageBus(StorageBus_Floppy, &ulValue); + RTPrintf("Maximum Devices per Floppy Port: %u\n", ulValue); +#if 0 + systemProperties->GetFreeDiskSpaceWarning(&i64Value); + RTPrintf("Free disk space warning at: %u Bytes\n", i64Value); + systemProperties->GetFreeDiskSpacePercentWarning(&ulValue); + RTPrintf("Free disk space warning at: %u %%\n", ulValue); + systemProperties->GetFreeDiskSpaceError(&i64Value); + RTPrintf("Free disk space error at: %u Bytes\n", i64Value); + systemProperties->GetFreeDiskSpacePercentError(&ulValue); + RTPrintf("Free disk space error at: %u %%\n", ulValue); +#endif + systemProperties->COMGETTER(DefaultMachineFolder)(str.asOutParam()); + RTPrintf("Default machine folder: %ls\n", str.raw()); + systemProperties->COMGETTER(RawModeSupported)(&fValue); + RTPrintf("Raw-mode Supported: %s\n", fValue ? "yes" : "no"); + systemProperties->COMGETTER(ExclusiveHwVirt)(&fValue); + RTPrintf("Exclusive HW virtualization use: %s\n", fValue ? "on" : "off"); + systemProperties->COMGETTER(DefaultHardDiskFormat)(str.asOutParam()); + RTPrintf("Default hard disk format: %ls\n", str.raw()); + systemProperties->COMGETTER(VRDEAuthLibrary)(str.asOutParam()); + RTPrintf("VRDE auth library: %ls\n", str.raw()); + systemProperties->COMGETTER(WebServiceAuthLibrary)(str.asOutParam()); + RTPrintf("Webservice auth. library: %ls\n", str.raw()); + systemProperties->COMGETTER(DefaultVRDEExtPack)(str.asOutParam()); + RTPrintf("Remote desktop ExtPack: %ls\n", str.raw()); + systemProperties->COMGETTER(LogHistoryCount)(&ulValue); + RTPrintf("Log history count: %u\n", ulValue); + systemProperties->COMGETTER(DefaultFrontend)(str.asOutParam()); + RTPrintf("Default frontend: %ls\n", str.raw()); + AudioDriverType_T enmAudio; + systemProperties->COMGETTER(DefaultAudioDriver)(&enmAudio); + switch (enmAudio) + { + case AudioDriverType_Null: psz = "Null"; break; + case AudioDriverType_WinMM: psz = "WinMM"; break; + case AudioDriverType_OSS: psz = "OSS"; break; + case AudioDriverType_ALSA: psz = "ALSA"; break; + case AudioDriverType_DirectSound: psz = "DirectSound"; break; + case AudioDriverType_CoreAudio: psz = "CoreAudio"; break; + case AudioDriverType_MMPM: psz = "MMPM"; break; + case AudioDriverType_Pulse: psz = "Pulse"; break; + case AudioDriverType_SolAudio: psz = "SolAudio"; break; + default: psz = "Unknown"; + } + RTPrintf("Default audio driver: %s\n", psz); + systemProperties->COMGETTER(AutostartDatabasePath)(str.asOutParam()); + RTPrintf("Autostart database path: %ls\n", str.raw()); + systemProperties->COMGETTER(DefaultAdditionsISO)(str.asOutParam()); + RTPrintf("Default Guest Additions ISO: %ls\n", str.raw()); + systemProperties->COMGETTER(LoggingLevel)(str.asOutParam()); + RTPrintf("Logging Level: %ls\n", str.raw()); + ProxyMode_T enmProxyMode = (ProxyMode_T)42; + systemProperties->COMGETTER(ProxyMode)(&enmProxyMode); + psz = "Unknown"; + switch (enmProxyMode) + { + case ProxyMode_System: psz = "System"; break; + case ProxyMode_NoProxy: psz = "NoProxy"; break; + case ProxyMode_Manual: psz = "Manual"; break; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case ProxyMode_32BitHack: break; /* Shut up compiler warnings. */ +#endif + } + RTPrintf("Proxy Mode: %s\n", psz); + systemProperties->COMGETTER(ProxyURL)(str.asOutParam()); + RTPrintf("Proxy URL: %ls\n", str.raw()); + return S_OK; +} + + +/** + * Helper function for querying and displaying DHCP option for an adapter. + * + * @returns See produceList. + * @param pSrv Smart pointer to IDHCPServer. + * @param vmSlot String identifying the adapter, like '[vmname]:slot' + */ +static HRESULT listVmSlotDhcpOptions(const ComPtr<IDHCPServer> pSrv, const Utf8Str& vmSlot) +{ + RTCList<RTCString> lstParts = vmSlot.split(":"); + if (lstParts.size() < 2) + return E_INVALIDARG; + if (lstParts[0].length() < 2 || !lstParts[0].startsWith("[") || !lstParts[0].endsWith("]")) + return E_INVALIDARG; + Bstr vmName(lstParts[0].substr(1, lstParts[0].length()-2)); + ULONG uSlot = lstParts[1].toUInt32(); + com::SafeArray<BSTR> options; + CHECK_ERROR2I_RET(pSrv, GetVmSlotOptions(vmName.raw(), uSlot, ComSafeArrayAsOutParam(options)), hrcCheck); + if (options.size()) + RTPrintf("Options for NIC %d of '%ls':\n", uSlot + 1, vmName.raw()); + for (size_t i = 0; i < options.size(); ++i) + RTPrintf(" %ls\n", options[i]); + + return S_OK; +} + + +/** + * List DHCP servers. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + */ +static HRESULT listDhcpServers(const ComPtr<IVirtualBox> &pVirtualBox) +{ + HRESULT rc = S_OK; + com::SafeIfaceArray<IDHCPServer> svrs; + CHECK_ERROR_RET(pVirtualBox, COMGETTER(DHCPServers)(ComSafeArrayAsOutParam(svrs)), rc); + for (size_t i = 0; i < svrs.size(); ++i) + { + ComPtr<IDHCPServer> svr = svrs[i]; + Bstr netName; + svr->COMGETTER(NetworkName)(netName.asOutParam()); + RTPrintf("NetworkName: %ls\n", netName.raw()); + Bstr ip; + svr->COMGETTER(IPAddress)(ip.asOutParam()); + RTPrintf("IP: %ls\n", ip.raw()); + Bstr netmask; + svr->COMGETTER(NetworkMask)(netmask.asOutParam()); + RTPrintf("NetworkMask: %ls\n", netmask.raw()); + Bstr lowerIp; + svr->COMGETTER(LowerIP)(lowerIp.asOutParam()); + RTPrintf("lowerIPAddress: %ls\n", lowerIp.raw()); + Bstr upperIp; + svr->COMGETTER(UpperIP)(upperIp.asOutParam()); + RTPrintf("upperIPAddress: %ls\n", upperIp.raw()); + BOOL fEnabled; + svr->COMGETTER(Enabled)(&fEnabled); + RTPrintf("Enabled: %s\n", fEnabled ? "Yes" : "No"); + com::SafeArray<BSTR> globalOptions; + CHECK_ERROR_BREAK(svr, COMGETTER(GlobalOptions)(ComSafeArrayAsOutParam(globalOptions))); + if (globalOptions.size()) + { + RTPrintf("Global options:\n"); + for (size_t j = 0; j < globalOptions.size(); ++j) + RTPrintf(" %ls\n", globalOptions[j]); + } + com::SafeArray<BSTR> vmConfigs; + CHECK_ERROR_BREAK(svr, COMGETTER(VmConfigs)(ComSafeArrayAsOutParam(vmConfigs))); + for (size_t j = 0; j < vmConfigs.size(); ++j) + { + rc = listVmSlotDhcpOptions(svr, vmConfigs[j]); + if (FAILED(rc)) + break; + } + RTPrintf("\n"); + } + + return rc; +} + +/** + * List extension packs. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + */ +static HRESULT listExtensionPacks(const ComPtr<IVirtualBox> &pVirtualBox) +{ + ComObjPtr<IExtPackManager> ptrExtPackMgr; + CHECK_ERROR2I_RET(pVirtualBox, COMGETTER(ExtensionPackManager)(ptrExtPackMgr.asOutParam()), hrcCheck); + + SafeIfaceArray<IExtPack> extPacks; + CHECK_ERROR2I_RET(ptrExtPackMgr, COMGETTER(InstalledExtPacks)(ComSafeArrayAsOutParam(extPacks)), hrcCheck); + RTPrintf("Extension Packs: %u\n", extPacks.size()); + + HRESULT hrc = S_OK; + for (size_t i = 0; i < extPacks.size(); i++) + { + /* Read all the properties. */ + Bstr bstrName; + CHECK_ERROR2I_STMT(extPacks[i], COMGETTER(Name)(bstrName.asOutParam()), hrc = hrcCheck; bstrName.setNull()); + Bstr bstrDesc; + CHECK_ERROR2I_STMT(extPacks[i], COMGETTER(Description)(bstrDesc.asOutParam()), hrc = hrcCheck; bstrDesc.setNull()); + Bstr bstrVersion; + CHECK_ERROR2I_STMT(extPacks[i], COMGETTER(Version)(bstrVersion.asOutParam()), hrc = hrcCheck; bstrVersion.setNull()); + ULONG uRevision; + CHECK_ERROR2I_STMT(extPacks[i], COMGETTER(Revision)(&uRevision), hrc = hrcCheck; uRevision = 0); + Bstr bstrEdition; + CHECK_ERROR2I_STMT(extPacks[i], COMGETTER(Edition)(bstrEdition.asOutParam()), hrc = hrcCheck; bstrEdition.setNull()); + Bstr bstrVrdeModule; + CHECK_ERROR2I_STMT(extPacks[i], COMGETTER(VRDEModule)(bstrVrdeModule.asOutParam()),hrc=hrcCheck; bstrVrdeModule.setNull()); + BOOL fUsable; + CHECK_ERROR2I_STMT(extPacks[i], COMGETTER(Usable)(&fUsable), hrc = hrcCheck; fUsable = FALSE); + Bstr bstrWhy; + CHECK_ERROR2I_STMT(extPacks[i], COMGETTER(WhyUnusable)(bstrWhy.asOutParam()), hrc = hrcCheck; bstrWhy.setNull()); + + /* Display them. */ + if (i) + RTPrintf("\n"); + RTPrintf("Pack no.%2zu: %ls\n" + "Version: %ls\n" + "Revision: %u\n" + "Edition: %ls\n" + "Description: %ls\n" + "VRDE Module: %ls\n" + "Usable: %RTbool\n" + "Why unusable: %ls\n", + i, bstrName.raw(), + bstrVersion.raw(), + uRevision, + bstrEdition.raw(), + bstrDesc.raw(), + bstrVrdeModule.raw(), + fUsable != FALSE, + bstrWhy.raw()); + + /* Query plugins and display them. */ + } + return hrc; +} + + +/** + * List machine groups. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + */ +static HRESULT listGroups(const ComPtr<IVirtualBox> &pVirtualBox) +{ + SafeArray<BSTR> groups; + CHECK_ERROR2I_RET(pVirtualBox, COMGETTER(MachineGroups)(ComSafeArrayAsOutParam(groups)), hrcCheck); + + for (size_t i = 0; i < groups.size(); i++) + { + RTPrintf("\"%ls\"\n", groups[i]); + } + return S_OK; +} + + +/** + * List video capture devices. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox pointer. + */ +static HRESULT listVideoInputDevices(const ComPtr<IVirtualBox> pVirtualBox) +{ + HRESULT rc; + ComPtr<IHost> host; + CHECK_ERROR(pVirtualBox, COMGETTER(Host)(host.asOutParam())); + com::SafeIfaceArray<IHostVideoInputDevice> hostVideoInputDevices; + CHECK_ERROR(host, COMGETTER(VideoInputDevices)(ComSafeArrayAsOutParam(hostVideoInputDevices))); + RTPrintf("Video Input Devices: %u\n", hostVideoInputDevices.size()); + for (size_t i = 0; i < hostVideoInputDevices.size(); ++i) + { + ComPtr<IHostVideoInputDevice> p = hostVideoInputDevices[i]; + Bstr name; + p->COMGETTER(Name)(name.asOutParam()); + Bstr path; + p->COMGETTER(Path)(path.asOutParam()); + Bstr alias; + p->COMGETTER(Alias)(alias.asOutParam()); + RTPrintf("%ls \"%ls\"\n%ls\n", alias.raw(), name.raw(), path.raw()); + } + return rc; +} + +/** + * List supported screen shot formats. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox pointer. + */ +static HRESULT listScreenShotFormats(const ComPtr<IVirtualBox> pVirtualBox) +{ + HRESULT rc = S_OK; + ComPtr<ISystemProperties> systemProperties; + CHECK_ERROR(pVirtualBox, COMGETTER(SystemProperties)(systemProperties.asOutParam())); + com::SafeArray<BitmapFormat_T> formats; + CHECK_ERROR(systemProperties, COMGETTER(ScreenShotFormats)(ComSafeArrayAsOutParam(formats))); + + RTPrintf("Supported %d screen shot formats:\n", formats.size()); + for (size_t i = 0; i < formats.size(); ++i) + { + uint32_t u32Format = (uint32_t)formats[i]; + char szFormat[5]; + szFormat[0] = RT_BYTE1(u32Format); + szFormat[1] = RT_BYTE2(u32Format); + szFormat[2] = RT_BYTE3(u32Format); + szFormat[3] = RT_BYTE4(u32Format); + szFormat[4] = 0; + RTPrintf(" BitmapFormat_%s (0x%08X)\n", szFormat, u32Format); + } + return rc; +} + +/** + * List available cloud providers. + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox pointer. + */ +static HRESULT listCloudProviders(const ComPtr<IVirtualBox> pVirtualBox) +{ + HRESULT rc = S_OK; + ComPtr<ICloudProviderManager> pCloudProviderManager; + CHECK_ERROR(pVirtualBox, COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam())); + com::SafeIfaceArray<ICloudProvider> apCloudProviders; + CHECK_ERROR(pCloudProviderManager, COMGETTER(Providers)(ComSafeArrayAsOutParam(apCloudProviders))); + + RTPrintf("Supported %d cloud providers:\n", apCloudProviders.size()); + for (size_t i = 0; i < apCloudProviders.size(); ++i) + { + ComPtr<ICloudProvider> pCloudProvider = apCloudProviders[i]; + Bstr bstrProviderName; + pCloudProvider->COMGETTER(Name)(bstrProviderName.asOutParam()); + RTPrintf("Name: %ls\n", bstrProviderName.raw()); + pCloudProvider->COMGETTER(ShortName)(bstrProviderName.asOutParam()); + RTPrintf("Short Name: %ls\n", bstrProviderName.raw()); + Bstr bstrProviderID; + pCloudProvider->COMGETTER(Id)(bstrProviderID.asOutParam()); + RTPrintf("GUID: %ls\n", bstrProviderID.raw()); + + RTPrintf("\n"); + } + return rc; +} + + +/** + * List all available cloud profiles (by iterating over the cloud providers). + * + * @returns See produceList. + * @param pVirtualBox Reference to the IVirtualBox pointer. + * @param fOptLong If true, list all profile properties. + */ +static HRESULT listCloudProfiles(const ComPtr<IVirtualBox> pVirtualBox, bool fOptLong) +{ + HRESULT rc = S_OK; + ComPtr<ICloudProviderManager> pCloudProviderManager; + CHECK_ERROR(pVirtualBox, COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam())); + com::SafeIfaceArray<ICloudProvider> apCloudProviders; + CHECK_ERROR(pCloudProviderManager, COMGETTER(Providers)(ComSafeArrayAsOutParam(apCloudProviders))); + + for (size_t i = 0; i < apCloudProviders.size(); ++i) + { + ComPtr<ICloudProvider> pCloudProvider = apCloudProviders[i]; + com::SafeIfaceArray<ICloudProfile> apCloudProfiles; + CHECK_ERROR(pCloudProvider, COMGETTER(Profiles)(ComSafeArrayAsOutParam(apCloudProfiles))); + for (size_t j = 0; j < apCloudProfiles.size(); ++j) + { + ComPtr<ICloudProfile> pCloudProfile = apCloudProfiles[j]; + Bstr bstrProfileName; + pCloudProfile->COMGETTER(Name)(bstrProfileName.asOutParam()); + RTPrintf("Name: %ls\n", bstrProfileName.raw()); + Bstr bstrProviderID; + pCloudProfile->COMGETTER(ProviderId)(bstrProviderID.asOutParam()); + RTPrintf("Provider GUID: %ls\n", bstrProviderID.raw()); + + if (fOptLong) + { + com::SafeArray<BSTR> names; + com::SafeArray<BSTR> values; + pCloudProfile->GetProperties(Bstr().raw(), ComSafeArrayAsOutParam(names), ComSafeArrayAsOutParam(values)); + 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 ? "Property: " : " ", + names[k], value.raw()); + fFirst = false; + } + } + + RTPrintf("\n"); + } + } + return rc; +} + + +/** + * The type of lists we can produce. + */ +enum enmListType +{ + kListNotSpecified = 1000, + kListVMs, + kListRunningVMs, + kListOsTypes, + kListHostDvds, + kListHostFloppies, + kListInternalNetworks, + kListBridgedInterfaces, +#if defined(VBOX_WITH_NETFLT) + kListHostOnlyInterfaces, +#endif + kListHostCpuIDs, + kListHostInfo, + kListHddBackends, + kListHdds, + kListDvds, + kListFloppies, + kListUsbHost, + kListUsbFilters, + kListSystemProperties, + kListDhcpServers, + kListExtPacks, + kListGroups, + kListNatNetworks, + kListVideoInputDevices, + kListScreenShotFormats, + kListCloudProviders, + kListCloudProfiles, +}; + + +/** + * Produces the specified listing. + * + * @returns S_OK or some COM error code that has been reported in full. + * @param enmList The list to produce. + * @param fOptLong Long (@c true) or short list format. + * @param pVirtualBox Reference to the IVirtualBox smart pointer. + */ +static HRESULT produceList(enum enmListType enmCommand, bool fOptLong, bool fOptSorted, const ComPtr<IVirtualBox> &pVirtualBox) +{ + HRESULT rc = S_OK; + switch (enmCommand) + { + case kListNotSpecified: + AssertFailed(); + return E_FAIL; + + case kListVMs: + { + /* + * Get the list of all registered VMs + */ + com::SafeIfaceArray<IMachine> machines; + rc = pVirtualBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(machines)); + if (SUCCEEDED(rc)) + { + /* + * Display it. + */ + if (!fOptSorted) + { + for (size_t i = 0; i < machines.size(); ++i) + if (machines[i]) + rc = showVMInfo(pVirtualBox, machines[i], NULL, fOptLong ? VMINFO_STANDARD : VMINFO_COMPACT); + } + else + { + /* + * Sort the list by name before displaying it. + */ + std::vector<std::pair<com::Bstr, IMachine *> > sortedMachines; + for (size_t i = 0; i < machines.size(); ++i) + { + IMachine *pMachine = machines[i]; + if (pMachine) /* no idea why we need to do this... */ + { + Bstr bstrName; + pMachine->COMGETTER(Name)(bstrName.asOutParam()); + sortedMachines.push_back(std::pair<com::Bstr, IMachine *>(bstrName, pMachine)); + } + } + + std::sort(sortedMachines.begin(), sortedMachines.end()); + + for (size_t i = 0; i < sortedMachines.size(); ++i) + rc = showVMInfo(pVirtualBox, sortedMachines[i].second, NULL, fOptLong ? VMINFO_STANDARD : VMINFO_COMPACT); + } + } + break; + } + + case kListRunningVMs: + { + /* + * Get the list of all _running_ VMs + */ + com::SafeIfaceArray<IMachine> machines; + rc = pVirtualBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(machines)); + com::SafeArray<MachineState_T> states; + if (SUCCEEDED(rc)) + rc = pVirtualBox->GetMachineStates(ComSafeArrayAsInParam(machines), ComSafeArrayAsOutParam(states)); + if (SUCCEEDED(rc)) + { + /* + * Iterate through the collection + */ + for (size_t i = 0; i < machines.size(); ++i) + { + if (machines[i]) + { + MachineState_T machineState = states[i]; + switch (machineState) + { + case MachineState_Running: + case MachineState_Teleporting: + case MachineState_LiveSnapshotting: + case MachineState_Paused: + case MachineState_TeleportingPausedVM: + rc = showVMInfo(pVirtualBox, machines[i], NULL, fOptLong ? VMINFO_STANDARD : VMINFO_COMPACT); + break; + default: break; /* Shut up MSC */ + } + } + } + } + break; + } + + case kListOsTypes: + { + com::SafeIfaceArray<IGuestOSType> coll; + rc = pVirtualBox->COMGETTER(GuestOSTypes)(ComSafeArrayAsOutParam(coll)); + if (SUCCEEDED(rc)) + { + /* + * Iterate through the collection. + */ + for (size_t i = 0; i < coll.size(); ++i) + { + ComPtr<IGuestOSType> guestOS; + guestOS = coll[i]; + Bstr guestId; + guestOS->COMGETTER(Id)(guestId.asOutParam()); + RTPrintf("ID: %ls\n", guestId.raw()); + Bstr guestDescription; + guestOS->COMGETTER(Description)(guestDescription.asOutParam()); + RTPrintf("Description: %ls\n", guestDescription.raw()); + Bstr familyId; + guestOS->COMGETTER(FamilyId)(familyId.asOutParam()); + RTPrintf("Family ID: %ls\n", familyId.raw()); + Bstr familyDescription; + guestOS->COMGETTER(FamilyDescription)(familyDescription.asOutParam()); + RTPrintf("Family Desc: %ls\n", familyDescription.raw()); + BOOL is64Bit; + guestOS->COMGETTER(Is64Bit)(&is64Bit); + RTPrintf("64 bit: %RTbool\n", is64Bit); + RTPrintf("\n"); + } + } + break; + } + + case kListHostDvds: + { + ComPtr<IHost> host; + CHECK_ERROR(pVirtualBox, COMGETTER(Host)(host.asOutParam())); + com::SafeIfaceArray<IMedium> coll; + CHECK_ERROR(host, COMGETTER(DVDDrives)(ComSafeArrayAsOutParam(coll))); + if (SUCCEEDED(rc)) + { + for (size_t i = 0; i < coll.size(); ++i) + { + ComPtr<IMedium> dvdDrive = coll[i]; + Bstr uuid; + dvdDrive->COMGETTER(Id)(uuid.asOutParam()); + RTPrintf("UUID: %s\n", Utf8Str(uuid).c_str()); + Bstr location; + dvdDrive->COMGETTER(Location)(location.asOutParam()); + RTPrintf("Name: %ls\n\n", location.raw()); + } + } + break; + } + + case kListHostFloppies: + { + ComPtr<IHost> host; + CHECK_ERROR(pVirtualBox, COMGETTER(Host)(host.asOutParam())); + com::SafeIfaceArray<IMedium> coll; + CHECK_ERROR(host, COMGETTER(FloppyDrives)(ComSafeArrayAsOutParam(coll))); + if (SUCCEEDED(rc)) + { + for (size_t i = 0; i < coll.size(); ++i) + { + ComPtr<IMedium> floppyDrive = coll[i]; + Bstr uuid; + floppyDrive->COMGETTER(Id)(uuid.asOutParam()); + RTPrintf("UUID: %s\n", Utf8Str(uuid).c_str()); + Bstr location; + floppyDrive->COMGETTER(Location)(location.asOutParam()); + RTPrintf("Name: %ls\n\n", location.raw()); + } + } + break; + } + + case kListInternalNetworks: + rc = listInternalNetworks(pVirtualBox); + break; + + case kListBridgedInterfaces: +#if defined(VBOX_WITH_NETFLT) + case kListHostOnlyInterfaces: +#endif + rc = listNetworkInterfaces(pVirtualBox, enmCommand == kListBridgedInterfaces); + break; + + case kListHostInfo: + rc = listHostInfo(pVirtualBox); + break; + + case kListHostCpuIDs: + { + ComPtr<IHost> Host; + CHECK_ERROR(pVirtualBox, COMGETTER(Host)(Host.asOutParam())); + + RTPrintf("Host CPUIDs:\n\nLeaf no. EAX EBX ECX EDX\n"); + ULONG uCpuNo = 0; /* ASSUMES that CPU#0 is online. */ + static uint32_t const s_auCpuIdRanges[] = + { + UINT32_C(0x00000000), UINT32_C(0x0000007f), + UINT32_C(0x80000000), UINT32_C(0x8000007f), + UINT32_C(0xc0000000), UINT32_C(0xc000007f) + }; + for (unsigned i = 0; i < RT_ELEMENTS(s_auCpuIdRanges); i += 2) + { + ULONG uEAX, uEBX, uECX, uEDX, cLeafs; + CHECK_ERROR(Host, GetProcessorCPUIDLeaf(uCpuNo, s_auCpuIdRanges[i], 0, &cLeafs, &uEBX, &uECX, &uEDX)); + if (cLeafs < s_auCpuIdRanges[i] || cLeafs > s_auCpuIdRanges[i+1]) + continue; + cLeafs++; + for (ULONG iLeaf = s_auCpuIdRanges[i]; iLeaf <= cLeafs; iLeaf++) + { + CHECK_ERROR(Host, GetProcessorCPUIDLeaf(uCpuNo, iLeaf, 0, &uEAX, &uEBX, &uECX, &uEDX)); + RTPrintf("%08x %08x %08x %08x %08x\n", iLeaf, uEAX, uEBX, uECX, uEDX); + } + } + break; + } + + case kListHddBackends: + rc = listHddBackends(pVirtualBox); + break; + + case kListHdds: + { + com::SafeIfaceArray<IMedium> hdds; + CHECK_ERROR(pVirtualBox, COMGETTER(HardDisks)(ComSafeArrayAsOutParam(hdds))); + rc = listMedia(pVirtualBox, hdds, "base", fOptLong); + break; + } + + case kListDvds: + { + com::SafeIfaceArray<IMedium> dvds; + CHECK_ERROR(pVirtualBox, COMGETTER(DVDImages)(ComSafeArrayAsOutParam(dvds))); + rc = listMedia(pVirtualBox, dvds, NULL, fOptLong); + break; + } + + case kListFloppies: + { + com::SafeIfaceArray<IMedium> floppies; + CHECK_ERROR(pVirtualBox, COMGETTER(FloppyImages)(ComSafeArrayAsOutParam(floppies))); + rc = listMedia(pVirtualBox, floppies, NULL, fOptLong); + break; + } + + case kListUsbHost: + rc = listUsbHost(pVirtualBox); + break; + + case kListUsbFilters: + rc = listUsbFilters(pVirtualBox); + break; + + case kListSystemProperties: + rc = listSystemProperties(pVirtualBox); + break; + + case kListDhcpServers: + rc = listDhcpServers(pVirtualBox); + break; + + case kListExtPacks: + rc = listExtensionPacks(pVirtualBox); + break; + + case kListGroups: + rc = listGroups(pVirtualBox); + break; + + case kListNatNetworks: + { + com::SafeIfaceArray<INATNetwork> nets; + CHECK_ERROR(pVirtualBox, COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(nets))); + for (size_t i = 0; i < nets.size(); ++i) + { + ComPtr<INATNetwork> net = nets[i]; + Bstr netName; + net->COMGETTER(NetworkName)(netName.asOutParam()); + RTPrintf("NetworkName: %ls\n", netName.raw()); + Bstr gateway; + net->COMGETTER(Gateway)(gateway.asOutParam()); + RTPrintf("IP: %ls\n", gateway.raw()); + Bstr network; + net->COMGETTER(Network)(network.asOutParam()); + RTPrintf("Network: %ls\n", network.raw()); + BOOL fEnabled; + net->COMGETTER(IPv6Enabled)(&fEnabled); + RTPrintf("IPv6 Enabled: %s\n", fEnabled ? "Yes" : "No"); + Bstr ipv6prefix; + net->COMGETTER(IPv6Prefix)(ipv6prefix.asOutParam()); + RTPrintf("IPv6 Prefix: %ls\n", ipv6prefix.raw()); + net->COMGETTER(NeedDhcpServer)(&fEnabled); + RTPrintf("DHCP Enabled: %s\n", fEnabled ? "Yes" : "No"); + net->COMGETTER(Enabled)(&fEnabled); + RTPrintf("Enabled: %s\n", fEnabled ? "Yes" : "No"); + +#define PRINT_STRING_ARRAY(title) \ + if (strs.size() > 0) \ + { \ + RTPrintf(title); \ + size_t j = 0; \ + for (;j < strs.size(); ++j) \ + RTPrintf(" %s\n", Utf8Str(strs[j]).c_str()); \ + } + + com::SafeArray<BSTR> strs; + + CHECK_ERROR(nets[i], COMGETTER(PortForwardRules4)(ComSafeArrayAsOutParam(strs))); + PRINT_STRING_ARRAY("Port-forwarding (ipv4)\n"); + strs.setNull(); + + CHECK_ERROR(nets[i], COMGETTER(PortForwardRules6)(ComSafeArrayAsOutParam(strs))); + PRINT_STRING_ARRAY("Port-forwarding (ipv6)\n"); + strs.setNull(); + + CHECK_ERROR(nets[i], COMGETTER(LocalMappings)(ComSafeArrayAsOutParam(strs))); + PRINT_STRING_ARRAY("loopback mappings (ipv4)\n"); + strs.setNull(); + +#undef PRINT_STRING_ARRAY + RTPrintf("\n"); + } + break; + } + + case kListVideoInputDevices: + rc = listVideoInputDevices(pVirtualBox); + break; + + case kListScreenShotFormats: + rc = listScreenShotFormats(pVirtualBox); + break; + + case kListCloudProviders: + rc = listCloudProviders(pVirtualBox); + break; + + case kListCloudProfiles: + rc = listCloudProfiles(pVirtualBox, fOptLong); + break; + + /* No default here, want gcc warnings. */ + + } /* end switch */ + + return rc; +} + +/** + * Handles the 'list' command. + * + * @returns Appropriate exit code. + * @param a Handler argument. + */ +RTEXITCODE handleList(HandlerArg *a) +{ + bool fOptLong = false; + bool fOptMultiple = false; + bool fOptSorted = false; + enum enmListType enmOptCommand = kListNotSpecified; + + static const RTGETOPTDEF s_aListOptions[] = + { + { "--long", 'l', RTGETOPT_REQ_NOTHING }, + { "--multiple", 'm', RTGETOPT_REQ_NOTHING }, /* not offical yet */ + { "--sorted", 's', RTGETOPT_REQ_NOTHING }, + { "vms", kListVMs, RTGETOPT_REQ_NOTHING }, + { "runningvms", kListRunningVMs, RTGETOPT_REQ_NOTHING }, + { "ostypes", kListOsTypes, RTGETOPT_REQ_NOTHING }, + { "hostdvds", kListHostDvds, RTGETOPT_REQ_NOTHING }, + { "hostfloppies", kListHostFloppies, RTGETOPT_REQ_NOTHING }, + { "intnets", kListInternalNetworks, RTGETOPT_REQ_NOTHING }, + { "hostifs", kListBridgedInterfaces, RTGETOPT_REQ_NOTHING }, /* backward compatibility */ + { "bridgedifs", kListBridgedInterfaces, RTGETOPT_REQ_NOTHING }, +#if defined(VBOX_WITH_NETFLT) + { "hostonlyifs", kListHostOnlyInterfaces, RTGETOPT_REQ_NOTHING }, +#endif + { "natnetworks", kListNatNetworks, RTGETOPT_REQ_NOTHING }, + { "natnets", kListNatNetworks, RTGETOPT_REQ_NOTHING }, + { "hostinfo", kListHostInfo, RTGETOPT_REQ_NOTHING }, + { "hostcpuids", kListHostCpuIDs, RTGETOPT_REQ_NOTHING }, + { "hddbackends", kListHddBackends, RTGETOPT_REQ_NOTHING }, + { "hdds", kListHdds, RTGETOPT_REQ_NOTHING }, + { "dvds", kListDvds, RTGETOPT_REQ_NOTHING }, + { "floppies", kListFloppies, RTGETOPT_REQ_NOTHING }, + { "usbhost", kListUsbHost, RTGETOPT_REQ_NOTHING }, + { "usbfilters", kListUsbFilters, RTGETOPT_REQ_NOTHING }, + { "systemproperties", kListSystemProperties, RTGETOPT_REQ_NOTHING }, + { "dhcpservers", kListDhcpServers, RTGETOPT_REQ_NOTHING }, + { "extpacks", kListExtPacks, RTGETOPT_REQ_NOTHING }, + { "groups", kListGroups, RTGETOPT_REQ_NOTHING }, + { "webcams", kListVideoInputDevices, RTGETOPT_REQ_NOTHING }, + { "screenshotformats", kListScreenShotFormats, RTGETOPT_REQ_NOTHING }, + { "cloudproviders", kListCloudProviders, RTGETOPT_REQ_NOTHING }, + { "cloudprofiles", kListCloudProfiles, RTGETOPT_REQ_NOTHING }, + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, a->argc, a->argv, s_aListOptions, RT_ELEMENTS(s_aListOptions), + 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'l': /* --long */ + fOptLong = true; + break; + + case 's': + fOptSorted = true; + break; + + case 'm': + fOptMultiple = true; + if (enmOptCommand == kListNotSpecified) + break; + ch = enmOptCommand; + RT_FALL_THRU(); + + case kListVMs: + case kListRunningVMs: + case kListOsTypes: + case kListHostDvds: + case kListHostFloppies: + case kListInternalNetworks: + case kListBridgedInterfaces: +#if defined(VBOX_WITH_NETFLT) + case kListHostOnlyInterfaces: +#endif + case kListHostInfo: + case kListHostCpuIDs: + case kListHddBackends: + case kListHdds: + case kListDvds: + case kListFloppies: + case kListUsbHost: + case kListUsbFilters: + case kListSystemProperties: + case kListDhcpServers: + case kListExtPacks: + case kListGroups: + case kListNatNetworks: + case kListVideoInputDevices: + case kListScreenShotFormats: + case kListCloudProviders: + case kListCloudProfiles: + enmOptCommand = (enum enmListType)ch; + if (fOptMultiple) + { + HRESULT hrc = produceList((enum enmListType)ch, fOptLong, fOptSorted, a->virtualBox); + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + } + break; + + case VINF_GETOPT_NOT_OPTION: + return errorSyntax(USAGE_LIST, "Unknown subcommand \"%s\".", ValueUnion.psz); + + default: + return errorGetOpt(USAGE_LIST, ch, &ValueUnion); + } + } + + /* + * If not in multiple list mode, we have to produce the list now. + */ + if (enmOptCommand == kListNotSpecified) + return errorSyntax(USAGE_LIST, "Missing subcommand for \"list\" command.\n"); + if (!fOptMultiple) + { + HRESULT hrc = produceList(enmOptCommand, fOptLong, fOptSorted, a->virtualBox); + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + } + + return RTEXITCODE_SUCCESS; +} + +#endif /* !VBOX_ONLY_DOCS */ +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageMetrics.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageMetrics.cpp new file mode 100644 index 00000000..3f655c47 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageMetrics.cpp @@ -0,0 +1,651 @@ +/* $Id: VBoxManageMetrics.cpp $ */ +/** @file + * VBoxManage - The 'metrics' command. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_ONLY_DOCS + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> + +#include <iprt/asm.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <VBox/log.h> + +#include <set> +#include <utility> + +#include "VBoxManage.h" +using namespace com; + + +// funcs +/////////////////////////////////////////////////////////////////////////////// + + +static HRESULT parseFilterParameters(int argc, char *argv[], + ComPtr<IVirtualBox> aVirtualBox, + ComSafeArrayOut(BSTR, outMetrics), + ComSafeArrayOut(IUnknown *, outObjects)) +{ + HRESULT rc = S_OK; + com::SafeArray<BSTR> retMetrics(1); + com::SafeIfaceArray <IUnknown> retObjects; + + Bstr metricNames, baseNames; + + /* Metric list */ + if (argc > 1) + metricNames = argv[1]; + else + { + metricNames = L"*"; + baseNames = L"*"; + } + metricNames.cloneTo(&retMetrics[0]); + + /* Object name */ + if (argc > 0 && strcmp(argv[0], "*")) + { + if (!strcmp(argv[0], "host")) + { + ComPtr<IHost> host; + CHECK_ERROR(aVirtualBox, COMGETTER(Host)(host.asOutParam())); + retObjects.reset(1); + host.queryInterfaceTo(&retObjects[0]); + } + else + { + ComPtr<IMachine> machine; + rc = aVirtualBox->FindMachine(Bstr(argv[0]).raw(), + machine.asOutParam()); + if (SUCCEEDED(rc)) + { + retObjects.reset(1); + machine.queryInterfaceTo(&retObjects[0]); + } + else + { + errorArgument("Invalid machine name: '%s'", argv[0]); + return rc; + } + } + + } + + retMetrics.detachTo(ComSafeArrayOutArg(outMetrics)); + retObjects.detachTo(ComSafeArrayOutArg(outObjects)); + + return rc; +} + +static Bstr toBaseName(Utf8Str& aFullName) +{ + char *pszRaw = aFullName.mutableRaw(); + /* + * Currently there are two metrics which base name is the same as the + * sub-metric name: CPU/MHz and Net/<iface>/LinkSpeed. + */ + if (pszRaw && strcmp(pszRaw, "CPU/MHz") && !RTStrSimplePatternMatch("Net/*/LinkSpeed", pszRaw)) + { + char *pszSlash = strrchr(pszRaw, '/'); + if (pszSlash) + { + *pszSlash = 0; + aFullName.jolt(); + } + } + return Bstr(aFullName); +} + +static Bstr getObjectName(ComPtr<IUnknown> aObject) +{ + HRESULT rc; + + ComPtr<IHost> host = aObject; + if (!host.isNull()) + return Bstr("host"); + + ComPtr<IMachine> machine = aObject; + if (!machine.isNull()) + { + Bstr name; + CHECK_ERROR(machine, COMGETTER(Name)(name.asOutParam())); + if (SUCCEEDED(rc)) + return name; + } + return Bstr("unknown"); +} + +static void listAffectedMetrics(ComSafeArrayIn(IPerformanceMetric*, aMetrics)) +{ + HRESULT rc; + com::SafeIfaceArray<IPerformanceMetric> metrics(ComSafeArrayInArg(aMetrics)); + if (metrics.size()) + { + ComPtr<IUnknown> object; + Bstr metricName; + RTPrintf("The following metrics were modified:\n\n" + "Object Metric\n" + "---------- --------------------\n"); + for (size_t i = 0; i < metrics.size(); i++) + { + CHECK_ERROR(metrics[i], COMGETTER(Object)(object.asOutParam())); + CHECK_ERROR(metrics[i], COMGETTER(MetricName)(metricName.asOutParam())); + RTPrintf("%-10ls %-20ls\n", + getObjectName(object).raw(), metricName.raw()); + } + RTPrintf("\n"); + } + else + { + RTMsgError("No metrics match the specified filter!"); + } +} + +/** + * list + */ +static RTEXITCODE handleMetricsList(int argc, char *argv[], + ComPtr<IVirtualBox> aVirtualBox, + ComPtr<IPerformanceCollector> performanceCollector) +{ + HRESULT rc; + com::SafeArray<BSTR> metrics; + com::SafeIfaceArray<IUnknown> objects; + + rc = parseFilterParameters(argc - 1, &argv[1], aVirtualBox, + ComSafeArrayAsOutParam(metrics), + ComSafeArrayAsOutParam(objects)); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + com::SafeIfaceArray<IPerformanceMetric> metricInfo; + + CHECK_ERROR(performanceCollector, + GetMetrics(ComSafeArrayAsInParam(metrics), + ComSafeArrayAsInParam(objects), + ComSafeArrayAsOutParam(metricInfo))); + + ComPtr<IUnknown> object; + Bstr metricName, unit, description; + ULONG period, count; + LONG minimum, maximum; + RTPrintf( +"Object Metric Unit Minimum Maximum Period Count Description\n" +"--------------- ---------------------------------------- ---- ---------- ---------- ---------- ---------- -----------\n"); + for (size_t i = 0; i < metricInfo.size(); i++) + { + CHECK_ERROR(metricInfo[i], COMGETTER(Object)(object.asOutParam())); + CHECK_ERROR(metricInfo[i], COMGETTER(MetricName)(metricName.asOutParam())); + CHECK_ERROR(metricInfo[i], COMGETTER(Period)(&period)); + CHECK_ERROR(metricInfo[i], COMGETTER(Count)(&count)); + CHECK_ERROR(metricInfo[i], COMGETTER(MinimumValue)(&minimum)); + CHECK_ERROR(metricInfo[i], COMGETTER(MaximumValue)(&maximum)); + CHECK_ERROR(metricInfo[i], COMGETTER(Unit)(unit.asOutParam())); + CHECK_ERROR(metricInfo[i], COMGETTER(Description)(description.asOutParam())); + RTPrintf("%-15ls %-40ls %-4ls %10d %10d %10u %10u %ls\n", + getObjectName(object).raw(), metricName.raw(), unit.raw(), + minimum, maximum, period, count, description.raw()); + } + + return RTEXITCODE_SUCCESS; +} + +/** + * Metrics setup + */ +static RTEXITCODE handleMetricsSetup(int argc, char *argv[], + ComPtr<IVirtualBox> aVirtualBox, + ComPtr<IPerformanceCollector> performanceCollector) +{ + HRESULT rc; + com::SafeArray<BSTR> metrics; + com::SafeIfaceArray<IUnknown> objects; + uint32_t period = 1, samples = 1; + bool listMatches = false; + int i; + + for (i = 1; i < argc; i++) + { + if ( !strcmp(argv[i], "--period") + || !strcmp(argv[i], "-period")) + { + if (argc <= i + 1) + return errorArgument("Missing argument to '%s'", argv[i]); + if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &period) + || !period) + return errorArgument("Invalid value for 'period' parameter: '%s'", argv[i]); + } + else if ( !strcmp(argv[i], "--samples") + || !strcmp(argv[i], "-samples")) + { + if (argc <= i + 1) + return errorArgument("Missing argument to '%s'", argv[i]); + if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &samples) + || !samples) + return errorArgument("Invalid value for 'samples' parameter: '%s'", argv[i]); + } + else if ( !strcmp(argv[i], "--list") + || !strcmp(argv[i], "-list")) + listMatches = true; + else + break; /* The rest of params should define the filter */ + } + + rc = parseFilterParameters(argc - i, &argv[i], aVirtualBox, + ComSafeArrayAsOutParam(metrics), + ComSafeArrayAsOutParam(objects)); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + com::SafeIfaceArray<IPerformanceMetric> affectedMetrics; + CHECK_ERROR(performanceCollector, + SetupMetrics(ComSafeArrayAsInParam(metrics), + ComSafeArrayAsInParam(objects), period, samples, + ComSafeArrayAsOutParam(affectedMetrics))); + if (FAILED(rc)) + return RTEXITCODE_SYNTAX; /** @todo figure out why we must return 2 here. */ + + if (listMatches) + listAffectedMetrics(ComSafeArrayAsInParam(affectedMetrics)); + + return RTEXITCODE_SUCCESS; +} + +/** + * metrics query + */ +static RTEXITCODE handleMetricsQuery(int argc, char *argv[], + ComPtr<IVirtualBox> aVirtualBox, + ComPtr<IPerformanceCollector> performanceCollector) +{ + HRESULT rc; + com::SafeArray<BSTR> metrics; + com::SafeIfaceArray<IUnknown> objects; + + rc = parseFilterParameters(argc - 1, &argv[1], aVirtualBox, + ComSafeArrayAsOutParam(metrics), + ComSafeArrayAsOutParam(objects)); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + com::SafeArray<BSTR> retNames; + com::SafeIfaceArray<IUnknown> retObjects; + com::SafeArray<BSTR> retUnits; + com::SafeArray<ULONG> retScales; + com::SafeArray<ULONG> retSequenceNumbers; + com::SafeArray<ULONG> retIndices; + com::SafeArray<ULONG> retLengths; + com::SafeArray<LONG> retData; + CHECK_ERROR(performanceCollector, QueryMetricsData(ComSafeArrayAsInParam(metrics), + ComSafeArrayAsInParam(objects), + ComSafeArrayAsOutParam(retNames), + ComSafeArrayAsOutParam(retObjects), + ComSafeArrayAsOutParam(retUnits), + ComSafeArrayAsOutParam(retScales), + ComSafeArrayAsOutParam(retSequenceNumbers), + ComSafeArrayAsOutParam(retIndices), + ComSafeArrayAsOutParam(retLengths), + ComSafeArrayAsOutParam(retData)) ); + + RTPrintf("Object Metric Values\n" + "--------------- ---------------------------------------- --------------------------------------------\n"); + for (unsigned i = 0; i < retNames.size(); i++) + { + Bstr metricUnit(retUnits[i]); + Bstr metricName(retNames[i]); + RTPrintf("%-15ls %-40ls ", getObjectName(retObjects[i]).raw(), metricName.raw()); + const char *separator = ""; + for (unsigned j = 0; j < retLengths[i]; j++) + { + if (retScales[i] == 1) + RTPrintf("%s%d %ls", separator, retData[retIndices[i] + j], metricUnit.raw()); + else + RTPrintf("%s%d.%02d%ls", separator, retData[retIndices[i] + j] / retScales[i], + (retData[retIndices[i] + j] * 100 / retScales[i]) % 100, metricUnit.raw()); + separator = ", "; + } + RTPrintf("\n"); + } + + return RTEXITCODE_SUCCESS; +} + +static void getTimestamp(char *pts, size_t tsSize) +{ + *pts = 0; + AssertReturnVoid(tsSize >= 13); /* 3+3+3+3+1 */ + RTTIMESPEC TimeSpec; + RTTIME Time; + RTTimeExplode(&Time, RTTimeNow(&TimeSpec)); + pts += RTStrFormatNumber(pts, Time.u8Hour, 10, 2, 0, RTSTR_F_ZEROPAD); + *pts++ = ':'; + pts += RTStrFormatNumber(pts, Time.u8Minute, 10, 2, 0, RTSTR_F_ZEROPAD); + *pts++ = ':'; + pts += RTStrFormatNumber(pts, Time.u8Second, 10, 2, 0, RTSTR_F_ZEROPAD); + *pts++ = '.'; + pts += RTStrFormatNumber(pts, Time.u32Nanosecond / 1000000, 10, 3, 0, RTSTR_F_ZEROPAD); + *pts = 0; +} + +/** Used by the handleMetricsCollect loop. */ +static bool volatile g_fKeepGoing = true; + +#ifdef RT_OS_WINDOWS +/** + * Handler routine for catching Ctrl-C, Ctrl-Break and closing of + * the console. + * + * @returns true if handled, false if not handled. + * @param dwCtrlType The type of control signal. + * + * @remarks This is called on a new thread. + */ +static BOOL WINAPI ctrlHandler(DWORD dwCtrlType) +{ + switch (dwCtrlType) + { + /* Ctrl-C or Ctrl-Break or Close */ + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + /* Let's shut down gracefully. */ + ASMAtomicWriteBool(&g_fKeepGoing, false); + return TRUE; + } + /* Don't care about the rest -- let it die a horrible death. */ + return FALSE; +} +#endif /* RT_OS_WINDOWS */ + +/** + * collect + */ +static RTEXITCODE handleMetricsCollect(int argc, char *argv[], + ComPtr<IVirtualBox> aVirtualBox, + ComPtr<IPerformanceCollector> performanceCollector) +{ + HRESULT rc; + com::SafeArray<BSTR> metrics; + com::SafeIfaceArray<IUnknown> objects; + uint32_t period = 1, samples = 1; + bool isDetached = false, listMatches = false; + int i; + for (i = 1; i < argc; i++) + { + if ( !strcmp(argv[i], "--period") + || !strcmp(argv[i], "-period")) + { + if (argc <= i + 1) + return errorArgument("Missing argument to '%s'", argv[i]); + if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &period) + || !period) + return errorArgument("Invalid value for 'period' parameter: '%s'", argv[i]); + } + else if ( !strcmp(argv[i], "--samples") + || !strcmp(argv[i], "-samples")) + { + if (argc <= i + 1) + return errorArgument("Missing argument to '%s'", argv[i]); + if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &samples) + || !samples) + return errorArgument("Invalid value for 'samples' parameter: '%s'", argv[i]); + } + else if ( !strcmp(argv[i], "--list") + || !strcmp(argv[i], "-list")) + listMatches = true; + else if ( !strcmp(argv[i], "--detach") + || !strcmp(argv[i], "-detach")) + isDetached = true; + else + break; /* The rest of params should define the filter */ + } + + rc = parseFilterParameters(argc - i, &argv[i], aVirtualBox, + ComSafeArrayAsOutParam(metrics), + ComSafeArrayAsOutParam(objects)); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + com::SafeIfaceArray<IPerformanceMetric> metricInfo; + + CHECK_ERROR(performanceCollector, + GetMetrics(ComSafeArrayAsInParam(metrics), + ComSafeArrayAsInParam(objects), + ComSafeArrayAsOutParam(metricInfo))); + + std::set<std::pair<ComPtr<IUnknown>,Bstr> > baseMetrics; + ComPtr<IUnknown> objectFiltered; + Bstr metricNameFiltered; + for (i = 0; i < (int)metricInfo.size(); i++) + { + CHECK_ERROR(metricInfo[i], COMGETTER(Object)(objectFiltered.asOutParam())); + CHECK_ERROR(metricInfo[i], COMGETTER(MetricName)(metricNameFiltered.asOutParam())); + Utf8Str baseMetricName(metricNameFiltered); + baseMetrics.insert(std::make_pair(objectFiltered, toBaseName(baseMetricName))); + } + com::SafeArray<BSTR> baseMetricsFiltered(baseMetrics.size()); + com::SafeIfaceArray<IUnknown> objectsFiltered(baseMetrics.size()); + std::set<std::pair<ComPtr<IUnknown>,Bstr> >::iterator it; + i = 0; + for (it = baseMetrics.begin(); it != baseMetrics.end(); ++it) + { + it->first.queryInterfaceTo(&objectsFiltered[i]); + Bstr(it->second).detachTo(&baseMetricsFiltered[i++]); + } + com::SafeIfaceArray<IPerformanceMetric> affectedMetrics; + CHECK_ERROR(performanceCollector, + SetupMetrics(ComSafeArrayAsInParam(baseMetricsFiltered), + ComSafeArrayAsInParam(objectsFiltered), period, samples, + ComSafeArrayAsOutParam(affectedMetrics))); + if (FAILED(rc)) + return RTEXITCODE_SYNTAX; /** @todo figure out why we must return 2 here. */ + + if (listMatches) + listAffectedMetrics(ComSafeArrayAsInParam(affectedMetrics)); + if (!affectedMetrics.size()) + return RTEXITCODE_FAILURE; + + if (isDetached) + { + RTMsgWarning("The background process holding collected metrics will shutdown\n" + "in few seconds, discarding all collected data and parameters."); + return RTEXITCODE_SUCCESS; + } + +#ifdef RT_OS_WINDOWS + SetConsoleCtrlHandler(ctrlHandler, true); +#endif /* RT_OS_WINDOWS */ + + RTPrintf("Time stamp Object Metric Value\n"); + + while (g_fKeepGoing) + { + RTPrintf("------------ ---------- -------------------- --------------------\n"); + RTThreadSleep(period * 1000); // Sleep for 'period' seconds + char ts[15]; + + getTimestamp(ts, sizeof(ts)); + com::SafeArray<BSTR> retNames; + com::SafeIfaceArray<IUnknown> retObjects; + com::SafeArray<BSTR> retUnits; + com::SafeArray<ULONG> retScales; + com::SafeArray<ULONG> retSequenceNumbers; + com::SafeArray<ULONG> retIndices; + com::SafeArray<ULONG> retLengths; + com::SafeArray<LONG> retData; + CHECK_ERROR(performanceCollector, QueryMetricsData(ComSafeArrayAsInParam(metrics), + ComSafeArrayAsInParam(objects), + ComSafeArrayAsOutParam(retNames), + ComSafeArrayAsOutParam(retObjects), + ComSafeArrayAsOutParam(retUnits), + ComSafeArrayAsOutParam(retScales), + ComSafeArrayAsOutParam(retSequenceNumbers), + ComSafeArrayAsOutParam(retIndices), + ComSafeArrayAsOutParam(retLengths), + ComSafeArrayAsOutParam(retData)) ); + for (unsigned j = 0; j < retNames.size(); j++) + { + Bstr metricUnit(retUnits[j]); + Bstr metricName(retNames[j]); + RTPrintf("%-12s %-10ls %-20ls ", ts, getObjectName(retObjects[j]).raw(), metricName.raw()); + const char *separator = ""; + for (unsigned k = 0; k < retLengths[j]; k++) + { + if (retScales[j] == 1) + RTPrintf("%s%d %ls", separator, retData[retIndices[j] + k], metricUnit.raw()); + else + RTPrintf("%s%d.%02d%ls", separator, retData[retIndices[j] + k] / retScales[j], + (retData[retIndices[j] + k] * 100 / retScales[j]) % 100, metricUnit.raw()); + separator = ", "; + } + RTPrintf("\n"); + } + RTStrmFlush(g_pStdOut); + } + +#ifdef RT_OS_WINDOWS + SetConsoleCtrlHandler(ctrlHandler, false); +#endif /* RT_OS_WINDOWS */ + + return RTEXITCODE_SUCCESS; +} + +/** + * Enable metrics + */ +static RTEXITCODE handleMetricsEnable(int argc, char *argv[], + ComPtr<IVirtualBox> aVirtualBox, + ComPtr<IPerformanceCollector> performanceCollector) +{ + HRESULT rc; + com::SafeArray<BSTR> metrics; + com::SafeIfaceArray<IUnknown> objects; + bool listMatches = false; + int i; + + for (i = 1; i < argc; i++) + { + if ( !strcmp(argv[i], "--list") + || !strcmp(argv[i], "-list")) + listMatches = true; + else + break; /* The rest of params should define the filter */ + } + + rc = parseFilterParameters(argc - i, &argv[i], aVirtualBox, + ComSafeArrayAsOutParam(metrics), + ComSafeArrayAsOutParam(objects)); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + com::SafeIfaceArray<IPerformanceMetric> affectedMetrics; + CHECK_ERROR(performanceCollector, + EnableMetrics(ComSafeArrayAsInParam(metrics), + ComSafeArrayAsInParam(objects), + ComSafeArrayAsOutParam(affectedMetrics))); + if (FAILED(rc)) + return RTEXITCODE_SYNTAX; /** @todo figure out why we must return 2 here. */ + + if (listMatches) + listAffectedMetrics(ComSafeArrayAsInParam(affectedMetrics)); + + return RTEXITCODE_SUCCESS; +} + +/** + * Disable metrics + */ +static RTEXITCODE handleMetricsDisable(int argc, char *argv[], + ComPtr<IVirtualBox> aVirtualBox, + ComPtr<IPerformanceCollector> performanceCollector) +{ + HRESULT rc; + com::SafeArray<BSTR> metrics; + com::SafeIfaceArray<IUnknown> objects; + bool listMatches = false; + int i; + + for (i = 1; i < argc; i++) + { + if ( !strcmp(argv[i], "--list") + || !strcmp(argv[i], "-list")) + listMatches = true; + else + break; /* The rest of params should define the filter */ + } + + rc = parseFilterParameters(argc - i, &argv[i], aVirtualBox, + ComSafeArrayAsOutParam(metrics), + ComSafeArrayAsOutParam(objects)); + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + com::SafeIfaceArray<IPerformanceMetric> affectedMetrics; + CHECK_ERROR(performanceCollector, + DisableMetrics(ComSafeArrayAsInParam(metrics), + ComSafeArrayAsInParam(objects), + ComSafeArrayAsOutParam(affectedMetrics))); + if (FAILED(rc)) + return RTEXITCODE_SYNTAX; /** @todo figure out why we must return 2 here. */ + + if (listMatches) + listAffectedMetrics(ComSafeArrayAsInParam(affectedMetrics)); + + return RTEXITCODE_SUCCESS; +} + + +RTEXITCODE handleMetrics(HandlerArg *a) +{ + /* at least one option: subcommand name */ + if (a->argc < 1) + return errorSyntax(USAGE_METRICS, "Subcommand missing"); + + ComPtr<IPerformanceCollector> performanceCollector; + CHECK_ERROR2I_RET(a->virtualBox, COMGETTER(PerformanceCollector)(performanceCollector.asOutParam()), RTEXITCODE_FAILURE); + + RTEXITCODE rcExit; + if (!strcmp(a->argv[0], "list")) + rcExit = handleMetricsList(a->argc, a->argv, a->virtualBox, performanceCollector); + else if (!strcmp(a->argv[0], "setup")) + rcExit = handleMetricsSetup(a->argc, a->argv, a->virtualBox, performanceCollector); + else if (!strcmp(a->argv[0], "query")) + rcExit = handleMetricsQuery(a->argc, a->argv, a->virtualBox, performanceCollector); + else if (!strcmp(a->argv[0], "collect")) + rcExit = handleMetricsCollect(a->argc, a->argv, a->virtualBox, performanceCollector); + else if (!strcmp(a->argv[0], "enable")) + rcExit = handleMetricsEnable(a->argc, a->argv, a->virtualBox, performanceCollector); + else if (!strcmp(a->argv[0], "disable")) + rcExit = handleMetricsDisable(a->argc, a->argv, a->virtualBox, performanceCollector); + else + return errorSyntax(USAGE_METRICS, "Invalid subcommand '%s'", a->argv[0]); + + return rcExit; +} + +#endif /* !VBOX_ONLY_DOCS */ + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp new file mode 100644 index 00000000..4d93833d --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp @@ -0,0 +1,1941 @@ +/* $Id: VBoxManageMisc.cpp $ */ +/** @file + * VBoxManage - VirtualBox's command-line interface. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef VBOX_ONLY_DOCS +# include <VBox/com/com.h> +# include <VBox/com/string.h> +# include <VBox/com/Guid.h> +# include <VBox/com/array.h> +# include <VBox/com/ErrorInfo.h> +# include <VBox/com/errorprint.h> +# include <VBox/com/VirtualBox.h> +#endif /* !VBOX_ONLY_DOCS */ + +#include <iprt/asm.h> +#include <iprt/buildconfig.h> +#include <iprt/cidr.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/sha.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/cpp/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/stdarg.h> +#include <iprt/thread.h> +#include <iprt/uuid.h> +#include <iprt/getopt.h> +#include <iprt/ctype.h> +#include <VBox/version.h> +#include <VBox/log.h> + +#include "VBoxManage.h" + +#include <list> + +using namespace com; + + + +RTEXITCODE handleRegisterVM(HandlerArg *a) +{ + HRESULT rc; + + if (a->argc != 1) + return errorSyntax(USAGE_REGISTERVM, "Incorrect number of parameters"); + + ComPtr<IMachine> 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. */ + rc = a->virtualBox->OpenMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam()); + if (rc == VBOX_E_FILE_ERROR) + { + char szVMFileAbs[RTPATH_MAX] = ""; + int vrc = RTPathAbs(a->argv[0], szVMFileAbs, sizeof(szVMFileAbs)); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot convert filename \"%s\" to absolute path: %Rrc", a->argv[0], vrc); + CHECK_ERROR(a->virtualBox, OpenMachine(Bstr(szVMFileAbs).raw(), + machine.asOutParam())); + } + else if (FAILED(rc)) + CHECK_ERROR(a->virtualBox, OpenMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam())); + if (SUCCEEDED(rc)) + { + ASSERT(machine); + CHECK_ERROR(a->virtualBox, RegisterMachine(machine)); + } + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static const RTGETOPTDEF g_aUnregisterVMOptions[] = +{ + { "--delete", 'd', RTGETOPT_REQ_NOTHING }, + { "-delete", 'd', RTGETOPT_REQ_NOTHING }, // deprecated +}; + +RTEXITCODE handleUnregisterVM(HandlerArg *a) +{ + HRESULT rc; + const char *VMName = NULL; + bool fDelete = 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 VINF_GETOPT_NOT_OPTION: + if (!VMName) + VMName = ValueUnion.psz; + else + return errorSyntax(USAGE_UNREGISTERVM, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(USAGE_UNREGISTERVM, "Invalid option -%c", c); + return errorSyntax(USAGE_UNREGISTERVM, "Invalid option case %i", c); + } + if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_UNREGISTERVM, "unknown option: %s\n", ValueUnion.psz); + if (ValueUnion.pDef) + return errorSyntax(USAGE_UNREGISTERVM, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + return errorSyntax(USAGE_UNREGISTERVM, "error: %Rrs", c); + } + } + + /* check for required options */ + if (!VMName) + return errorSyntax(USAGE_UNREGISTERVM, "VM name required"); + + ComPtr<IMachine> machine; + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(VMName).raw(), + machine.asOutParam()), + RTEXITCODE_FAILURE); + SafeIfaceArray<IMedium> aMedia; + CHECK_ERROR_RET(machine, Unregister(CleanupMode_DetachAllReturnHardDisksOnly, + ComSafeArrayAsOutParam(aMedia)), + RTEXITCODE_FAILURE); + if (fDelete) + { + ComPtr<IProgress> pProgress; + CHECK_ERROR_RET(machine, DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress.asOutParam()), + RTEXITCODE_FAILURE); + + rc = showProgress(pProgress); + CHECK_PROGRESS_ERROR_RET(pProgress, ("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) + rc = pMedium->Close(); + } + rc = S_OK; + } + 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 }, +}; + +RTEXITCODE handleCreateVM(HandlerArg *a) +{ + HRESULT rc; + Bstr bstrBaseFolder; + Bstr bstrName; + Bstr bstrOsTypeId; + Bstr bstrUuid; + bool fRegister = false; + bool fDefault = false; + /* TBD. Now not used */ + Bstr bstrDefaultFlags; + com::SafeArray<BSTR> 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_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; + + default: + return errorGetOpt(USAGE_CREATEVM, c, &ValueUnion); + } + } + + /* check for required options */ + if (bstrName.isEmpty()) + return errorSyntax(USAGE_CREATEVM, "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())); + ComPtr<IMachine> machine; + CHECK_ERROR_BREAK(a->virtualBox, + CreateMachine(bstrSettingsFile.raw(), + bstrName.raw(), + ComSafeArrayAsInParam(groups), + bstrOsTypeId.raw(), + createFlags.raw(), + machine.asOutParam())); + + CHECK_ERROR_BREAK(machine, SaveSettings()); + if (fRegister) + { + CHECK_ERROR_BREAK(a->virtualBox, RegisterMachine(machine)); + } + if (fDefault) + { + /* ApplyDefaults assumes the machine is already registered */ + CHECK_ERROR_BREAK(machine, ApplyDefaults(bstrDefaultFlags.raw())); + CHECK_ERROR_BREAK(machine, SaveSettings()); + } + Bstr uuid; + CHECK_ERROR_BREAK(machine, COMGETTER(Id)(uuid.asOutParam())); + Bstr settingsFile; + CHECK_ERROR_BREAK(machine, COMGETTER(SettingsFilePath)(settingsFile.asOutParam())); + RTPrintf("Virtual machine '%ls' is created%s.\n" + "UUID: %s\n" + "Settings file: '%ls'\n", + bstrName.raw(), fRegister ? " and registered" : "", + Utf8Str(uuid).c_str(), settingsFile.raw()); + } + while (0); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static const RTGETOPTDEF g_aMoveVMOptions[] = +{ + { "--type", 't', RTGETOPT_REQ_STRING }, + { "--folder", 'f', RTGETOPT_REQ_STRING }, +}; + +RTEXITCODE handleMoveVM(HandlerArg *a) +{ + HRESULT rc; + const char *pszSrcName = NULL; + const char *pszTargetFolder = NULL; + const char *pszType = NULL; + + 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 + + char szPath[RTPATH_MAX]; + pszTargetFolder = ValueUnion.psz; + + vrc = RTPathAbs(pszTargetFolder, szPath, sizeof(szPath)); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs(%s,,) failed with rc=%Rrc", pszTargetFolder, vrc); + break; + + case VINF_GETOPT_NOT_OPTION: + if (!pszSrcName) + pszSrcName = ValueUnion.psz; + else + return errorSyntax(USAGE_MOVEVM, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + return errorGetOpt(USAGE_MOVEVM, c, &ValueUnion); + } + } + + + if (!pszType) + { + pszType = "basic"; + } + + /* Check for required options */ + if (!pszSrcName) + return errorSyntax(USAGE_MOVEVM, "VM name required"); + + /* Get the machine object */ + ComPtr<IMachine> srcMachine; + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(pszSrcName).raw(), + srcMachine.asOutParam()), + RTEXITCODE_FAILURE); + + if (srcMachine) + { + /* Start the moving */ + ComPtr<IProgress> progress; + + /* we have to open a session for this task */ + CHECK_ERROR_RET(srcMachine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE); + ComPtr<IMachine> sessionMachine; + + CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(sessionMachine, MoveTo(Bstr(pszTargetFolder).raw(), + Bstr(pszType).raw(), + progress.asOutParam()), RTEXITCODE_FAILURE); + rc = showProgress(progress); + CHECK_PROGRESS_ERROR_RET(progress, ("Move VM failed"), RTEXITCODE_FAILURE); + + sessionMachine.setNull(); + CHECK_ERROR_RET(a->session, UnlockMachine(), RTEXITCODE_FAILURE); + +// do +// { +// /* we have to open a session for this task */ +// CHECK_ERROR_BREAK(srcMachine, LockMachine(a->session, LockType_Write)); +// ComPtr<IMachine> sessionMachine; +// do +// { +// CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam())); +// CHECK_ERROR_BREAK(sessionMachine, MoveTo(Bstr(pszTargetFolder).raw(), +// Bstr(pszType).raw(), +// progress.asOutParam())); +// rc = showProgress(progress); +// CHECK_PROGRESS_ERROR_RET(progress, ("Move VM failed"), RTEXITCODE_FAILURE); +//// CHECK_ERROR_BREAK(sessionMachine, SaveSettings()); +// } while (0); +// +// sessionMachine.setNull(); +// CHECK_ERROR_BREAK(a->session, UnlockMachine()); +// } while (0); + RTPrintf("Machine has been successfully moved into %s\n", pszTargetFolder); + } + + 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<CloneOptions_T> *options) +{ + int rc = VINF_SUCCESS; + while (psz && *psz && RT_SUCCESS(rc)) + { + 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 + rc = VERR_PARSE_ERROR; + } + if (pszComma) + psz += len + 1; + else + psz += len; + } + + return rc; +} + +RTEXITCODE handleCloneVM(HandlerArg *a) +{ + HRESULT rc; + const char *pszSrcName = NULL; + const char *pszSnapshotName = NULL; + CloneMode_T mode = CloneMode_MachineState; + com::SafeArray<CloneOptions_T> options; + const char *pszTrgName = NULL; + const char *pszTrgBaseFolder = NULL; + bool fRegister = false; + Bstr bstrUuid; + com::SafeArray<BSTR> 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("Invalid clone mode '%s'\n", ValueUnion.psz); + break; + + case 'o': // --options + if (RT_FAILURE(parseCloneOptions(ValueUnion.psz, &options))) + return errorArgument("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(USAGE_CLONEVM, "Invalid parameter '%s'", ValueUnion.psz); + break; + + default: + return errorGetOpt(USAGE_CLONEVM, c, &ValueUnion); + } + } + + /* Check for required options */ + if (!pszSrcName) + return errorSyntax(USAGE_CLONEVM, "VM name required"); + + /* Get the machine object */ + ComPtr<IMachine> 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<ISnapshot> 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("%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<IMachine> trgMachine; + CHECK_ERROR_RET(a->virtualBox, CreateMachine(bstrSettingsFile.raw(), + Bstr(pszTrgName).raw(), + ComSafeArrayAsInParam(groups), + NULL, + createFlags.raw(), + trgMachine.asOutParam()), + RTEXITCODE_FAILURE); + + /* Start the cloning */ + ComPtr<IProgress> progress; + CHECK_ERROR_RET(srcMachine, CloneTo(trgMachine, + mode, + ComSafeArrayAsInParam(options), + progress.asOutParam()), + RTEXITCODE_FAILURE); + rc = showProgress(progress); + CHECK_PROGRESS_ERROR_RET(progress, ("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("Machine has been successfully cloned as \"%ls\"\n", bstrNewName.raw()); + + return RTEXITCODE_SUCCESS; +} + +RTEXITCODE handleStartVM(HandlerArg *a) +{ + HRESULT rc = S_OK; + std::list<const char *> VMs; + Bstr sessionType; + Utf8Str strEnv; + +#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) + strEnv = Utf8StrFmt("DISPLAY=%s\n", pszDisplay); + const char *pszXAuth = RTEnvGet("XAUTHORITY"); + if (pszXAuth) + strEnv.append(Utf8StrFmt("XAUTHORITY=%s\n", pszXAuth)); + } +#endif + + static const RTGETOPTDEF s_aStartVMOptions[] = + { + { "--type", 't', RTGETOPT_REQ_STRING }, + { "-type", 't', RTGETOPT_REQ_STRING }, // deprecated + { "--putenv", 'E', 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")) + strEnv.append(Utf8StrFmt("%s\n", ValueUnion.psz)); + else + return errorSyntax(USAGE_STARTVM, "Parameter to option --putenv must not contain any newline character"); + break; + + case VINF_GETOPT_NOT_OPTION: + VMs.push_back(ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(USAGE_STARTVM, "Invalid option -%c", c); + else + return errorSyntax(USAGE_STARTVM, "Invalid option case %i", c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(USAGE_STARTVM, "unknown option: %s\n", ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax(USAGE_STARTVM, "%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(USAGE_STARTVM, "error: %Rrs", c); + } + } + + /* check for required options */ + if (VMs.empty()) + return errorSyntax(USAGE_STARTVM, "at least one VM name or uuid required"); + + for (std::list<const char *>::const_iterator it = VMs.begin(); + it != VMs.end(); + ++it) + { + HRESULT rc2 = rc; + const char *pszVM = *it; + ComPtr<IMachine> machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszVM).raw(), + machine.asOutParam())); + if (machine) + { + ComPtr<IProgress> progress; + CHECK_ERROR(machine, LaunchVMProcess(a->session, sessionType.raw(), + Bstr(strEnv).raw(), progress.asOutParam())); + if (SUCCEEDED(rc) && !progress.isNull()) + { + RTPrintf("Waiting for VM \"%s\" to power on...\n", pszVM); + CHECK_ERROR(progress, WaitForCompletion(-1)); + if (SUCCEEDED(rc)) + { + BOOL completed = true; + CHECK_ERROR(progress, COMGETTER(Completed)(&completed)); + if (SUCCEEDED(rc)) + { + ASSERT(completed); + + LONG iRc; + CHECK_ERROR(progress, COMGETTER(ResultCode)(&iRc)); + if (SUCCEEDED(rc)) + { + if (SUCCEEDED(iRc)) + RTPrintf("VM \"%s\" has been successfully started.\n", pszVM); + else + { + ProgressErrorInfo info(progress); + com::GluePrintErrorInfo(info); + } + rc = iRc; + } + } + } + } + } + + /* it's important to always close sessions */ + a->session->UnlockMachine(); + + /* make sure that we remember the failed state */ + if (FAILED(rc2)) + rc = rc2; + } + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleDiscardState(HandlerArg *a) +{ + HRESULT rc; + + if (a->argc != 1) + return errorSyntax(USAGE_DISCARDSTATE, "Incorrect number of parameters"); + + ComPtr<IMachine> 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<IMachine> 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(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleAdoptState(HandlerArg *a) +{ + HRESULT rc; + + if (a->argc != 2) + return errorSyntax(USAGE_ADOPTSTATE, "Incorrect number of parameters"); + + ComPtr<IMachine> 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, "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<IMachine> 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(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleGetExtraData(HandlerArg *a) +{ + HRESULT rc = S_OK; + + if (a->argc > 2 || a->argc < 1) + return errorSyntax(USAGE_GETEXTRADATA, "Incorrect number of parameters"); + + /* global data? */ + if (!strcmp(a->argv[0], "global")) + { + /* enumeration? */ + if (a->argc < 2 || !strcmp(a->argv[1], "enumerate")) + { + SafeArray<BSTR> 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("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("Value: %ls\n", value.raw()); + else + RTPrintf("No value set!\n"); + } + } + else + { + ComPtr<IMachine> 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<BSTR> 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("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("Value: %ls\n", value.raw()); + else + RTPrintf("No value set!\n"); + } + } + } + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleSetExtraData(HandlerArg *a) +{ + HRESULT rc = S_OK; + + if (a->argc < 2) + return errorSyntax(USAGE_SETEXTRADATA, "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(USAGE_SETEXTRADATA, "Too many parameters"); + } + else + { + ComPtr<IMachine> 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<IMachine> 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(USAGE_SETEXTRADATA, "Too many parameters"); + } + } + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleSetProperty(HandlerArg *a) +{ + HRESULT rc; + + /* there must be two arguments: property name and value */ + if (a->argc != 2) + return errorSyntax(USAGE_SETPROPERTY, "Incorrect number of parameters"); + + ComPtr<ISystemProperties> 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("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, "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("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("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())); + } + else + return errorSyntax(USAGE_SETPROPERTY, "Invalid parameter '%s'", a->argv[0]); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleSharedFolder(HandlerArg *a) +{ + HRESULT rc; + + /* we need at least a command and target */ + if (a->argc < 2) + return errorSyntax(USAGE_SHAREDFOLDER, "Not enough parameters"); + + const char *pszMachineName = a->argv[1]; + ComPtr<IMachine> machine; + CHECK_ERROR(a->virtualBox, FindMachine(Bstr(pszMachineName).raw(), machine.asOutParam())); + if (!machine) + return RTEXITCODE_FAILURE; + + if (!strcmp(a->argv[0], "add")) + { + /* we need at least four more parameters */ + if (a->argc < 5) + return errorSyntax(USAGE_SHAREDFOLDER_ADD, "Not enough parameters"); + + char *name = NULL; + char *hostpath = NULL; + bool fTransient = false; + bool fWritable = true; + bool fAutoMount = false; + const char *pszAutoMountPoint = ""; + + for (int i = 2; i < a->argc; i++) + { + if ( !strcmp(a->argv[i], "--name") + || !strcmp(a->argv[i], "-name")) + { + if (a->argc <= i + 1 || !*a->argv[i+1]) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + name = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--hostpath") + || !strcmp(a->argv[i], "-hostpath")) + { + if (a->argc <= i + 1 || !*a->argv[i+1]) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + hostpath = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--readonly") + || !strcmp(a->argv[i], "-readonly")) + { + fWritable = false; + } + else if ( !strcmp(a->argv[i], "--transient") + || !strcmp(a->argv[i], "-transient")) + { + fTransient = true; + } + else if ( !strcmp(a->argv[i], "--automount") + || !strcmp(a->argv[i], "-automount")) + { + fAutoMount = true; + } + else if (!strcmp(a->argv[i], "--auto-mount-point")) + { + if (a->argc <= i + 1 || !*a->argv[i+1]) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + pszAutoMountPoint = a->argv[i]; + } + else + return errorSyntax(USAGE_SHAREDFOLDER_ADD, "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str()); + } + + if (NULL != strstr(name, " ")) + return errorSyntax(USAGE_SHAREDFOLDER_ADD, "No spaces allowed in parameter '-name'!"); + + /* required arguments */ + if (!name || !hostpath) + { + return errorSyntax(USAGE_SHAREDFOLDER_ADD, "Parameters --name and --hostpath are required"); + } + + if (fTransient) + { + ComPtr<IConsole> console; + + /* open an existing session for the VM */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + + /* get the session machine */ + ComPtr<IMachine> sessionMachine; + CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE); + + /* get the session console */ + CHECK_ERROR_RET(a->session, COMGETTER(Console)(console.asOutParam()), RTEXITCODE_FAILURE); + if (console.isNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Machine '%s' is not currently running.\n", pszMachineName); + + CHECK_ERROR(console, CreateSharedFolder(Bstr(name).raw(), Bstr(hostpath).raw(), + fWritable, fAutoMount, Bstr(pszAutoMountPoint).raw())); + a->session->UnlockMachine(); + } + else + { + /* open a session for the VM */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE); + + /* get the mutable session machine */ + ComPtr<IMachine> sessionMachine; + a->session->COMGETTER(Machine)(sessionMachine.asOutParam()); + + CHECK_ERROR(sessionMachine, CreateSharedFolder(Bstr(name).raw(), Bstr(hostpath).raw(), + fWritable, fAutoMount, Bstr(pszAutoMountPoint).raw())); + if (SUCCEEDED(rc)) + CHECK_ERROR(sessionMachine, SaveSettings()); + + a->session->UnlockMachine(); + } + } + else if (!strcmp(a->argv[0], "remove")) + { + /* we need at least two more parameters */ + if (a->argc < 3) + return errorSyntax(USAGE_SHAREDFOLDER_REMOVE, "Not enough parameters"); + + char *name = NULL; + bool fTransient = false; + + for (int i = 2; i < a->argc; i++) + { + if ( !strcmp(a->argv[i], "--name") + || !strcmp(a->argv[i], "-name")) + { + if (a->argc <= i + 1 || !*a->argv[i+1]) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + name = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--transient") + || !strcmp(a->argv[i], "-transient")) + { + fTransient = true; + } + else + return errorSyntax(USAGE_SHAREDFOLDER_REMOVE, "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str()); + } + + /* required arguments */ + if (!name) + return errorSyntax(USAGE_SHAREDFOLDER_REMOVE, "Parameter --name is required"); + + if (fTransient) + { + /* open an existing session for the VM */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + /* get the session machine */ + ComPtr<IMachine> sessionMachine; + CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE); + /* get the session console */ + ComPtr<IConsole> console; + CHECK_ERROR_RET(a->session, COMGETTER(Console)(console.asOutParam()), RTEXITCODE_FAILURE); + if (console.isNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Machine '%s' is not currently running.\n", pszMachineName); + + CHECK_ERROR(console, RemoveSharedFolder(Bstr(name).raw())); + + a->session->UnlockMachine(); + } + else + { + /* open a session for the VM */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE); + + /* get the mutable session machine */ + ComPtr<IMachine> sessionMachine; + a->session->COMGETTER(Machine)(sessionMachine.asOutParam()); + + CHECK_ERROR(sessionMachine, RemoveSharedFolder(Bstr(name).raw())); + + /* commit and close the session */ + CHECK_ERROR(sessionMachine, SaveSettings()); + a->session->UnlockMachine(); + } + } + else + return errorSyntax(USAGE_SHAREDFOLDER, "Invalid parameter '%s'", Utf8Str(a->argv[0]).c_str()); + + return RTEXITCODE_SUCCESS; +} + +RTEXITCODE handleExtPack(HandlerArg *a) +{ + if (a->argc < 1) + return errorNoSubcommand(); + + ComObjPtr<IExtPackManager> 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<RTCString> 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("Too many extension pack names given to \"extpack uninstall\""); + pszName = ValueUnion.psz; + break; + + default: + return errorGetOpt(ch, &ValueUnion); + } + } + if (!pszName) + return errorSyntax("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, "RTPathAbs(%s,,) failed with rc=%Rrc", pszName, vrc); + + Bstr bstrTarball(szPath); + Bstr bstrName; + ComPtr<IExtPackFile> 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("License accepted.\n"); + else + { + RTPrintf("%s\n", strLicense.c_str()); + RTPrintf("Do you agree to these license terms and conditions (y/n)? " ); + ch = RTStrmGetCh(g_pStdIn); + RTPrintf("\n"); + if (ch != 'y' && ch != 'Y') + { + RTPrintf("Installation of \"%ls\" aborted.\n", bstrName.raw()); + return RTEXITCODE_FAILURE; + } + if (szDigest[0]) + RTPrintf("License accepted. For batch installaltion add\n" + "--accept-license=%s\n" + "to the VBoxManage command line.\n\n", szDigest); + } + } + ComPtr<IProgress> ptrProgress; + CHECK_ERROR2I_RET(ptrExtPackFile, Install(fReplace, NULL, ptrProgress.asOutParam()), RTEXITCODE_FAILURE); + hrc = showProgress(ptrProgress); + CHECK_PROGRESS_ERROR_RET(ptrProgress, ("Failed to install \"%s\"", szPath), RTEXITCODE_FAILURE); + + RTPrintf("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("Too many extension pack names given to \"extpack uninstall\""); + pszName = ValueUnion.psz; + break; + + default: + return errorGetOpt(ch, &ValueUnion); + } + } + if (!pszName) + return errorSyntax("No extension pack name was given to \"extpack uninstall\""); + + Bstr bstrName(pszName); + ComPtr<IProgress> ptrProgress; + CHECK_ERROR2I_RET(ptrExtPackMgr, Uninstall(bstrName.raw(), fForced, NULL, ptrProgress.asOutParam()), RTEXITCODE_FAILURE); + hrc = showProgress(ptrProgress); + CHECK_PROGRESS_ERROR_RET(ptrProgress, ("Failed to uninstall \"%s\"", pszName), RTEXITCODE_FAILURE); + + RTPrintf("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("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("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("No ISO specified"); + + /* + * Do the job. + */ + ComPtr<IUnattended> 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); + if (fMachineReadable) + RTPrintf("OSTypeId=\"%ls\"\n" + "OSVersion=\"%ls\"\n" + "OSFlavor=\"%ls\"\n" + "OSLanguages=\"%ls\"\n" + "OSHints=\"%ls\"\n", + bstrDetectedOSTypeId.raw(), + bstrDetectedVersion.raw(), + bstrDetectedFlavor.raw(), + bstrDetectedLanguages.raw(), + bstrDetectedHints.raw()); + else + { + RTMsgInfo("Detected '%s' to be:\n", szIsoPath); + RTPrintf(" 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()); + } + + return rcExit; +} + +RTEXITCODE handleUnattendedInstall(HandlerArg *a) +{ + HRESULT hrc; + char szAbsPath[RTPATH_MAX]; + + /* + * Options. We work directly on an IUnattended instace while parsing + * the options. This saves a lot of extra clutter. + */ + ComPtr<IUnattended> ptrUnattended; + CHECK_ERROR2_RET(hrc, a->virtualBox, CreateUnattendedInstaller(ptrUnattended.asOutParam()), RTEXITCODE_FAILURE); + RTCList<RTCString> arrPackageSelectionAdjustments; + ComPtr<IMachine> 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("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("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("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("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("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("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("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("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<IConsole> ptrConsole; + CHECK_ERROR2(hrc, a->session, COMGETTER(Console)(ptrConsole.asOutParam())); + + if ( ptrConsole.isNull() + && SUCCEEDED(hrc) + && ( RTStrICmp(pszSessionType, "gui") == 0 + || RTStrICmp(pszSessionType, "none") == 0)) + { + ComPtr<IMachine> 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, "Machine '%ls' is currently running", bstrMachineName.raw()); + } + + /* + * Do the work. + */ + RTMsgInfo("%s unattended installation of %s in machine '%ls' (%ls).\n", + RTStrICmp(pszSessionType, "none") == 0 ? "Preparing" : "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("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(" %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(" %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"); + +#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("VM '%ls' (%ls) is ready to be started (e.g. VBoxManage startvm).\n", bstrMachineName.raw(), bstrUuid.raw()); + hrc = S_OK; + } + else + { + Bstr env; +#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) + /* make sure the VM process will start on the same display as VBoxManage */ + Utf8Str str; + const char *pszDisplay = RTEnvGet("DISPLAY"); + if (pszDisplay) + str = Utf8StrFmt("DISPLAY=%s\n", pszDisplay); + const char *pszXAuth = RTEnvGet("XAUTHORITY"); + if (pszXAuth) + str.append(Utf8StrFmt("XAUTHORITY=%s\n", pszXAuth)); + env = str; +#endif + ComPtr<IProgress> ptrProgress; + CHECK_ERROR2(hrc, ptrMachine, LaunchVMProcess(a->session, Bstr(pszSessionType).raw(), env.raw(), ptrProgress.asOutParam())); + if (SUCCEEDED(hrc) && !ptrProgress.isNull()) + { + RTMsgInfo("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("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]); +} diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp new file mode 100644 index 00000000..6ecda59e --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp @@ -0,0 +1,3202 @@ +/* $Id: VBoxManageModifyVM.cpp $ */ +/** @file + * VBoxManage - Implementation of modifyvm command. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef VBOX_ONLY_DOCS +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> +#endif /* !VBOX_ONLY_DOCS */ + +#include <iprt/cidr.h> +#include <iprt/ctype.h> +#include <iprt/file.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/getopt.h> +#include <VBox/log.h> +#include "VBoxManage.h" + +#ifndef VBOX_ONLY_DOCS +using namespace com; +/** @todo refine this after HDD changes; MSC 8.0/64 has trouble with handleModifyVM. */ +#if defined(_MSC_VER) +# pragma optimize("g", off) +# if _MSC_VER < RT_MSC_VER_VC120 +# pragma warning(disable:4748) +# endif +#endif + +enum +{ + MODIFYVM_NAME = 1000, + MODIFYVM_GROUPS, + MODIFYVM_DESCRIPTION, + MODIFYVM_OSTYPE, + MODIFYVM_ICONFILE, + MODIFYVM_MEMORY, + MODIFYVM_PAGEFUSION, + MODIFYVM_VRAM, + MODIFYVM_FIRMWARE, + MODIFYVM_ACPI, + MODIFYVM_IOAPIC, + MODIFYVM_PAE, + MODIFYVM_LONGMODE, + MODIFYVM_CPUID_PORTABILITY, + MODIFYVM_TFRESET, + MODIFYVM_APIC, + MODIFYVM_X2APIC, + MODIFYVM_PARAVIRTPROVIDER, + MODIFYVM_PARAVIRTDEBUG, + MODIFYVM_HWVIRTEX, + MODIFYVM_NESTEDPAGING, + MODIFYVM_LARGEPAGES, + MODIFYVM_VTXVPID, + MODIFYVM_VTXUX, + MODIFYVM_IBPB_ON_VM_EXIT, + MODIFYVM_IBPB_ON_VM_ENTRY, + MODIFYVM_SPEC_CTRL, + MODIFYVM_L1D_FLUSH_ON_SCHED, + MODIFYVM_L1D_FLUSH_ON_VM_ENTRY, + MODIFYVM_NESTED_HW_VIRT, + MODIFYVM_CPUS, + MODIFYVM_CPUHOTPLUG, + MODIFYVM_CPU_PROFILE, + MODIFYVM_PLUGCPU, + MODIFYVM_UNPLUGCPU, + MODIFYVM_SETCPUID, + MODIFYVM_SETCPUID_OLD, + MODIFYVM_DELCPUID, + MODIFYVM_DELCPUID_OLD, + MODIFYVM_DELALLCPUID, + MODIFYVM_GRAPHICSCONTROLLER, + MODIFYVM_MONITORCOUNT, + MODIFYVM_ACCELERATE3D, +#ifdef VBOX_WITH_VIDEOHWACCEL + MODIFYVM_ACCELERATE2DVIDEO, +#endif + MODIFYVM_BIOSLOGOFADEIN, + MODIFYVM_BIOSLOGOFADEOUT, + MODIFYVM_BIOSLOGODISPLAYTIME, + MODIFYVM_BIOSLOGOIMAGEPATH, + MODIFYVM_BIOSBOOTMENU, + MODIFYVM_BIOSAPIC, + MODIFYVM_BIOSSYSTEMTIMEOFFSET, + MODIFYVM_BIOSPXEDEBUG, + MODIFYVM_BOOT, + MODIFYVM_HDA, // deprecated + MODIFYVM_HDB, // deprecated + MODIFYVM_HDD, // deprecated + MODIFYVM_IDECONTROLLER, // deprecated + MODIFYVM_SATAPORTCOUNT, // deprecated + MODIFYVM_SATAPORT, // deprecated + MODIFYVM_SATA, // deprecated + MODIFYVM_SCSIPORT, // deprecated + MODIFYVM_SCSITYPE, // deprecated + MODIFYVM_SCSI, // deprecated + MODIFYVM_DVDPASSTHROUGH, // deprecated + MODIFYVM_DVD, // deprecated + MODIFYVM_FLOPPY, // deprecated + MODIFYVM_NICTRACEFILE, + MODIFYVM_NICTRACE, + MODIFYVM_NICPROPERTY, + MODIFYVM_NICTYPE, + MODIFYVM_NICSPEED, + MODIFYVM_NICBOOTPRIO, + MODIFYVM_NICPROMISC, + MODIFYVM_NICBWGROUP, + MODIFYVM_NIC, + MODIFYVM_CABLECONNECTED, + MODIFYVM_BRIDGEADAPTER, + MODIFYVM_HOSTONLYADAPTER, + MODIFYVM_INTNET, + MODIFYVM_GENERICDRV, + MODIFYVM_NATNETWORKNAME, + MODIFYVM_NATNET, + MODIFYVM_NATBINDIP, + MODIFYVM_NATSETTINGS, + MODIFYVM_NATPF, + MODIFYVM_NATALIASMODE, + MODIFYVM_NATTFTPPREFIX, + MODIFYVM_NATTFTPFILE, + MODIFYVM_NATTFTPSERVER, + MODIFYVM_NATDNSPASSDOMAIN, + MODIFYVM_NATDNSPROXY, + MODIFYVM_NATDNSHOSTRESOLVER, + MODIFYVM_MACADDRESS, + MODIFYVM_HIDPTR, + MODIFYVM_HIDKBD, + MODIFYVM_UARTMODE, + MODIFYVM_UARTTYPE, + MODIFYVM_UART, +#if defined(RT_OS_LINUX) || defined(RT_OS_WINDOWS) + MODIFYVM_LPTMODE, + MODIFYVM_LPT, +#endif + MODIFYVM_GUESTMEMORYBALLOON, + MODIFYVM_AUDIOCONTROLLER, + MODIFYVM_AUDIOCODEC, + MODIFYVM_AUDIO, + MODIFYVM_AUDIOIN, + MODIFYVM_AUDIOOUT, + MODIFYVM_CLIPBOARD, + MODIFYVM_DRAGANDDROP, + MODIFYVM_VRDPPORT, /* VRDE: deprecated */ + MODIFYVM_VRDPADDRESS, /* VRDE: deprecated */ + MODIFYVM_VRDPAUTHTYPE, /* VRDE: deprecated */ + MODIFYVM_VRDPMULTICON, /* VRDE: deprecated */ + MODIFYVM_VRDPREUSECON, /* VRDE: deprecated */ + MODIFYVM_VRDPVIDEOCHANNEL, /* VRDE: deprecated */ + MODIFYVM_VRDPVIDEOCHANNELQUALITY, /* VRDE: deprecated */ + MODIFYVM_VRDP, /* VRDE: deprecated */ + MODIFYVM_VRDEPROPERTY, + MODIFYVM_VRDEPORT, + MODIFYVM_VRDEADDRESS, + MODIFYVM_VRDEAUTHTYPE, + MODIFYVM_VRDEAUTHLIBRARY, + MODIFYVM_VRDEMULTICON, + MODIFYVM_VRDEREUSECON, + MODIFYVM_VRDEVIDEOCHANNEL, + MODIFYVM_VRDEVIDEOCHANNELQUALITY, + MODIFYVM_VRDE_EXTPACK, + MODIFYVM_VRDE, + MODIFYVM_RTCUSEUTC, + MODIFYVM_USBRENAME, + MODIFYVM_USBXHCI, + MODIFYVM_USBEHCI, + MODIFYVM_USBOHCI, + MODIFYVM_SNAPSHOTFOLDER, + MODIFYVM_TELEPORTER_ENABLED, + MODIFYVM_TELEPORTER_PORT, + MODIFYVM_TELEPORTER_ADDRESS, + MODIFYVM_TELEPORTER_PASSWORD, + MODIFYVM_TELEPORTER_PASSWORD_FILE, + MODIFYVM_TRACING_ENABLED, + MODIFYVM_TRACING_CONFIG, + MODIFYVM_TRACING_ALLOW_VM_ACCESS, + MODIFYVM_HARDWARE_UUID, + MODIFYVM_HPET, + MODIFYVM_IOCACHE, + MODIFYVM_IOCACHESIZE, + MODIFYVM_FAULT_TOLERANCE, + MODIFYVM_FAULT_TOLERANCE_ADDRESS, + MODIFYVM_FAULT_TOLERANCE_PORT, + MODIFYVM_FAULT_TOLERANCE_PASSWORD, + MODIFYVM_FAULT_TOLERANCE_SYNC_INTERVAL, + MODIFYVM_CPU_EXECTUION_CAP, + MODIFYVM_AUTOSTART_ENABLED, + MODIFYVM_AUTOSTART_DELAY, + MODIFYVM_AUTOSTOP_TYPE, +#ifdef VBOX_WITH_PCI_PASSTHROUGH + MODIFYVM_ATTACH_PCI, + MODIFYVM_DETACH_PCI, +#endif +#ifdef VBOX_WITH_USB_CARDREADER + MODIFYVM_USBCARDREADER, +#endif +#ifdef VBOX_WITH_RECORDING + MODIFYVM_RECORDING, + MODIFYVM_RECORDING_FEATURES, + MODIFYVM_RECORDING_SCREENS, + MODIFYVM_RECORDING_FILENAME, + MODIFYVM_RECORDING_VIDEO_WIDTH, + MODIFYVM_RECORDING_VIDEO_HEIGHT, + MODIFYVM_RECORDING_VIDEO_RES, + MODIFYVM_RECORDING_VIDEO_RATE, + MODIFYVM_RECORDING_VIDEO_FPS, + MODIFYVM_RECORDING_MAXTIME, + MODIFYVM_RECORDING_MAXSIZE, + MODIFYVM_RECORDING_OPTIONS, +#endif + MODIFYVM_CHIPSET, + MODIFYVM_DEFAULTFRONTEND +}; + +static const RTGETOPTDEF g_aModifyVMOptions[] = +{ +/** @todo Convert to dash separated names like --triple-fault-reset! Please + * do that for all new options as we don't need more character soups + * around VirtualBox - typedefs more than covers that demand! */ + { "--name", MODIFYVM_NAME, RTGETOPT_REQ_STRING }, + { "--groups", MODIFYVM_GROUPS, RTGETOPT_REQ_STRING }, + { "--description", MODIFYVM_DESCRIPTION, RTGETOPT_REQ_STRING }, + { "--ostype", MODIFYVM_OSTYPE, RTGETOPT_REQ_STRING }, + { "--iconfile", MODIFYVM_ICONFILE, RTGETOPT_REQ_STRING }, + { "--memory", MODIFYVM_MEMORY, RTGETOPT_REQ_UINT32 }, + { "--pagefusion", MODIFYVM_PAGEFUSION, RTGETOPT_REQ_BOOL_ONOFF }, + { "--vram", MODIFYVM_VRAM, RTGETOPT_REQ_UINT32 }, + { "--firmware", MODIFYVM_FIRMWARE, RTGETOPT_REQ_STRING }, + { "--acpi", MODIFYVM_ACPI, RTGETOPT_REQ_BOOL_ONOFF }, + { "--ioapic", MODIFYVM_IOAPIC, RTGETOPT_REQ_BOOL_ONOFF }, + { "--pae", MODIFYVM_PAE, RTGETOPT_REQ_BOOL_ONOFF }, + { "--longmode", MODIFYVM_LONGMODE, RTGETOPT_REQ_BOOL_ONOFF }, + { "--cpuid-portability-level", MODIFYVM_CPUID_PORTABILITY, RTGETOPT_REQ_UINT32 }, + { "--triplefaultreset", MODIFYVM_TFRESET, RTGETOPT_REQ_BOOL_ONOFF }, + { "--apic", MODIFYVM_APIC, RTGETOPT_REQ_BOOL_ONOFF }, + { "--x2apic", MODIFYVM_X2APIC, RTGETOPT_REQ_BOOL_ONOFF }, + { "--paravirtprovider", MODIFYVM_PARAVIRTPROVIDER, RTGETOPT_REQ_STRING }, + { "--paravirtdebug", MODIFYVM_PARAVIRTDEBUG, RTGETOPT_REQ_STRING }, + { "--hwvirtex", MODIFYVM_HWVIRTEX, RTGETOPT_REQ_BOOL_ONOFF }, + { "--nestedpaging", MODIFYVM_NESTEDPAGING, RTGETOPT_REQ_BOOL_ONOFF }, + { "--largepages", MODIFYVM_LARGEPAGES, RTGETOPT_REQ_BOOL_ONOFF }, + { "--vtxvpid", MODIFYVM_VTXVPID, RTGETOPT_REQ_BOOL_ONOFF }, + { "--vtxux", MODIFYVM_VTXUX, RTGETOPT_REQ_BOOL_ONOFF }, + { "--ibpb-on-vm-exit", MODIFYVM_IBPB_ON_VM_EXIT, RTGETOPT_REQ_BOOL_ONOFF }, + { "--ibpb-on-vm-entry", MODIFYVM_IBPB_ON_VM_ENTRY, RTGETOPT_REQ_BOOL_ONOFF }, + { "--spec-ctrl", MODIFYVM_SPEC_CTRL, RTGETOPT_REQ_BOOL_ONOFF }, + { "--l1d-flush-on-sched", MODIFYVM_L1D_FLUSH_ON_SCHED, RTGETOPT_REQ_BOOL_ONOFF }, + { "--l1d-flush-on-vm-entry", MODIFYVM_L1D_FLUSH_ON_VM_ENTRY, RTGETOPT_REQ_BOOL_ONOFF }, + { "--nested-hw-virt", MODIFYVM_NESTED_HW_VIRT, RTGETOPT_REQ_BOOL_ONOFF }, + { "--cpuid-set", MODIFYVM_SETCPUID, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX }, + { "--cpuid-remove", MODIFYVM_DELCPUID, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX }, + { "--cpuidset", MODIFYVM_SETCPUID_OLD, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX }, + { "--cpuidremove", MODIFYVM_DELCPUID_OLD, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX }, + { "--cpuidremoveall", MODIFYVM_DELALLCPUID, RTGETOPT_REQ_NOTHING}, + { "--cpus", MODIFYVM_CPUS, RTGETOPT_REQ_UINT32 }, + { "--cpuhotplug", MODIFYVM_CPUHOTPLUG, RTGETOPT_REQ_BOOL_ONOFF }, + { "--cpu-profile", MODIFYVM_CPU_PROFILE, RTGETOPT_REQ_STRING }, + { "--plugcpu", MODIFYVM_PLUGCPU, RTGETOPT_REQ_UINT32 }, + { "--unplugcpu", MODIFYVM_UNPLUGCPU, RTGETOPT_REQ_UINT32 }, + { "--cpuexecutioncap", MODIFYVM_CPU_EXECTUION_CAP, RTGETOPT_REQ_UINT32 }, + { "--rtcuseutc", MODIFYVM_RTCUSEUTC, RTGETOPT_REQ_BOOL_ONOFF }, + { "--graphicscontroller", MODIFYVM_GRAPHICSCONTROLLER, RTGETOPT_REQ_STRING }, + { "--monitorcount", MODIFYVM_MONITORCOUNT, RTGETOPT_REQ_UINT32 }, + { "--accelerate3d", MODIFYVM_ACCELERATE3D, RTGETOPT_REQ_BOOL_ONOFF }, +#ifdef VBOX_WITH_VIDEOHWACCEL + { "--accelerate2dvideo", MODIFYVM_ACCELERATE2DVIDEO, RTGETOPT_REQ_BOOL_ONOFF }, +#endif + { "--bioslogofadein", MODIFYVM_BIOSLOGOFADEIN, RTGETOPT_REQ_BOOL_ONOFF }, + { "--bioslogofadeout", MODIFYVM_BIOSLOGOFADEOUT, RTGETOPT_REQ_BOOL_ONOFF }, + { "--bioslogodisplaytime", MODIFYVM_BIOSLOGODISPLAYTIME, RTGETOPT_REQ_UINT32 }, + { "--bioslogoimagepath", MODIFYVM_BIOSLOGOIMAGEPATH, RTGETOPT_REQ_STRING }, + { "--biosbootmenu", MODIFYVM_BIOSBOOTMENU, RTGETOPT_REQ_STRING }, + { "--biossystemtimeoffset", MODIFYVM_BIOSSYSTEMTIMEOFFSET, RTGETOPT_REQ_INT64 }, + { "--biosapic", MODIFYVM_BIOSAPIC, RTGETOPT_REQ_STRING }, + { "--biospxedebug", MODIFYVM_BIOSPXEDEBUG, RTGETOPT_REQ_BOOL_ONOFF }, + { "--boot", MODIFYVM_BOOT, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--hda", MODIFYVM_HDA, RTGETOPT_REQ_STRING }, + { "--hdb", MODIFYVM_HDB, RTGETOPT_REQ_STRING }, + { "--hdd", MODIFYVM_HDD, RTGETOPT_REQ_STRING }, + { "--idecontroller", MODIFYVM_IDECONTROLLER, RTGETOPT_REQ_STRING }, + { "--sataportcount", MODIFYVM_SATAPORTCOUNT, RTGETOPT_REQ_UINT32 }, + { "--sataport", MODIFYVM_SATAPORT, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--sata", MODIFYVM_SATA, RTGETOPT_REQ_STRING }, + { "--scsiport", MODIFYVM_SCSIPORT, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--scsitype", MODIFYVM_SCSITYPE, RTGETOPT_REQ_STRING }, + { "--scsi", MODIFYVM_SCSI, RTGETOPT_REQ_STRING }, + { "--dvdpassthrough", MODIFYVM_DVDPASSTHROUGH, RTGETOPT_REQ_STRING }, + { "--dvd", MODIFYVM_DVD, RTGETOPT_REQ_STRING }, + { "--floppy", MODIFYVM_FLOPPY, RTGETOPT_REQ_STRING }, + { "--nictracefile", MODIFYVM_NICTRACEFILE, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nictrace", MODIFYVM_NICTRACE, RTGETOPT_REQ_BOOL_ONOFF | RTGETOPT_FLAG_INDEX }, + { "--nicproperty", MODIFYVM_NICPROPERTY, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nictype", MODIFYVM_NICTYPE, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nicspeed", MODIFYVM_NICSPEED, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_INDEX }, + { "--nicbootprio", MODIFYVM_NICBOOTPRIO, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_INDEX }, + { "--nicpromisc", MODIFYVM_NICPROMISC, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nicbandwidthgroup", MODIFYVM_NICBWGROUP, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nic", MODIFYVM_NIC, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--cableconnected", MODIFYVM_CABLECONNECTED, RTGETOPT_REQ_BOOL_ONOFF | RTGETOPT_FLAG_INDEX }, + { "--bridgeadapter", MODIFYVM_BRIDGEADAPTER, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--hostonlyadapter", MODIFYVM_HOSTONLYADAPTER, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--intnet", MODIFYVM_INTNET, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nicgenericdrv", MODIFYVM_GENERICDRV, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nat-network", MODIFYVM_NATNETWORKNAME, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--natnetwork", MODIFYVM_NATNETWORKNAME, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--natnet", MODIFYVM_NATNET, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--natbindip", MODIFYVM_NATBINDIP, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--natsettings", MODIFYVM_NATSETTINGS, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--natpf", MODIFYVM_NATPF, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nataliasmode", MODIFYVM_NATALIASMODE, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nattftpprefix", MODIFYVM_NATTFTPPREFIX, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nattftpfile", MODIFYVM_NATTFTPFILE, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--nattftpserver", MODIFYVM_NATTFTPSERVER, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--natdnspassdomain", MODIFYVM_NATDNSPASSDOMAIN, RTGETOPT_REQ_BOOL_ONOFF | RTGETOPT_FLAG_INDEX }, + { "--natdnsproxy", MODIFYVM_NATDNSPROXY, RTGETOPT_REQ_BOOL_ONOFF | RTGETOPT_FLAG_INDEX }, + { "--natdnshostresolver", MODIFYVM_NATDNSHOSTRESOLVER, RTGETOPT_REQ_BOOL_ONOFF | RTGETOPT_FLAG_INDEX }, + { "--macaddress", MODIFYVM_MACADDRESS, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--mouse", MODIFYVM_HIDPTR, RTGETOPT_REQ_STRING }, + { "--keyboard", MODIFYVM_HIDKBD, RTGETOPT_REQ_STRING }, + { "--uartmode", MODIFYVM_UARTMODE, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--uarttype", MODIFYVM_UARTTYPE, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--uart", MODIFYVM_UART, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, +#if defined(RT_OS_LINUX) || defined(RT_OS_WINDOWS) + { "--lptmode", MODIFYVM_LPTMODE, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, + { "--lpt", MODIFYVM_LPT, RTGETOPT_REQ_STRING | RTGETOPT_FLAG_INDEX }, +#endif + { "--guestmemoryballoon", MODIFYVM_GUESTMEMORYBALLOON, RTGETOPT_REQ_UINT32 }, + { "--audiocontroller", MODIFYVM_AUDIOCONTROLLER, RTGETOPT_REQ_STRING }, + { "--audiocodec", MODIFYVM_AUDIOCODEC, RTGETOPT_REQ_STRING }, + { "--audio", MODIFYVM_AUDIO, RTGETOPT_REQ_STRING }, + { "--audioin", MODIFYVM_AUDIOIN, RTGETOPT_REQ_BOOL_ONOFF }, + { "--audioout", MODIFYVM_AUDIOOUT, RTGETOPT_REQ_BOOL_ONOFF }, + { "--clipboard", MODIFYVM_CLIPBOARD, RTGETOPT_REQ_STRING }, + { "--draganddrop", MODIFYVM_DRAGANDDROP, RTGETOPT_REQ_STRING }, + { "--vrdpport", MODIFYVM_VRDPPORT, RTGETOPT_REQ_STRING }, /* deprecated */ + { "--vrdpaddress", MODIFYVM_VRDPADDRESS, RTGETOPT_REQ_STRING }, /* deprecated */ + { "--vrdpauthtype", MODIFYVM_VRDPAUTHTYPE, RTGETOPT_REQ_STRING }, /* deprecated */ + { "--vrdpmulticon", MODIFYVM_VRDPMULTICON, RTGETOPT_REQ_BOOL_ONOFF }, /* deprecated */ + { "--vrdpreusecon", MODIFYVM_VRDPREUSECON, RTGETOPT_REQ_BOOL_ONOFF }, /* deprecated */ + { "--vrdpvideochannel", MODIFYVM_VRDPVIDEOCHANNEL, RTGETOPT_REQ_BOOL_ONOFF }, /* deprecated */ + { "--vrdpvideochannelquality", MODIFYVM_VRDPVIDEOCHANNELQUALITY, RTGETOPT_REQ_STRING }, /* deprecated */ + { "--vrdp", MODIFYVM_VRDP, RTGETOPT_REQ_BOOL_ONOFF }, /* deprecated */ + { "--vrdeproperty", MODIFYVM_VRDEPROPERTY, RTGETOPT_REQ_STRING }, + { "--vrdeport", MODIFYVM_VRDEPORT, RTGETOPT_REQ_STRING }, + { "--vrdeaddress", MODIFYVM_VRDEADDRESS, RTGETOPT_REQ_STRING }, + { "--vrdeauthtype", MODIFYVM_VRDEAUTHTYPE, RTGETOPT_REQ_STRING }, + { "--vrdeauthlibrary", MODIFYVM_VRDEAUTHLIBRARY, RTGETOPT_REQ_STRING }, + { "--vrdemulticon", MODIFYVM_VRDEMULTICON, RTGETOPT_REQ_BOOL_ONOFF }, + { "--vrdereusecon", MODIFYVM_VRDEREUSECON, RTGETOPT_REQ_BOOL_ONOFF }, + { "--vrdevideochannel", MODIFYVM_VRDEVIDEOCHANNEL, RTGETOPT_REQ_BOOL_ONOFF }, + { "--vrdevideochannelquality", MODIFYVM_VRDEVIDEOCHANNELQUALITY, RTGETOPT_REQ_STRING }, + { "--vrdeextpack", MODIFYVM_VRDE_EXTPACK, RTGETOPT_REQ_STRING }, + { "--vrde", MODIFYVM_VRDE, RTGETOPT_REQ_BOOL_ONOFF }, + { "--usbrename", MODIFYVM_USBRENAME, RTGETOPT_REQ_STRING }, + { "--usbxhci", MODIFYVM_USBXHCI, RTGETOPT_REQ_BOOL_ONOFF }, + { "--usbehci", MODIFYVM_USBEHCI, RTGETOPT_REQ_BOOL_ONOFF }, + { "--usbohci", MODIFYVM_USBOHCI, RTGETOPT_REQ_BOOL_ONOFF }, + { "--usb", MODIFYVM_USBOHCI, RTGETOPT_REQ_BOOL_ONOFF }, /* deprecated */ + { "--snapshotfolder", MODIFYVM_SNAPSHOTFOLDER, RTGETOPT_REQ_STRING }, + { "--teleporter", MODIFYVM_TELEPORTER_ENABLED, RTGETOPT_REQ_BOOL_ONOFF }, + { "--teleporterenabled", MODIFYVM_TELEPORTER_ENABLED, RTGETOPT_REQ_BOOL_ONOFF }, /* deprecated */ + { "--teleporterport", MODIFYVM_TELEPORTER_PORT, RTGETOPT_REQ_UINT32 }, + { "--teleporteraddress", MODIFYVM_TELEPORTER_ADDRESS, RTGETOPT_REQ_STRING }, + { "--teleporterpassword", MODIFYVM_TELEPORTER_PASSWORD, RTGETOPT_REQ_STRING }, + { "--teleporterpasswordfile", MODIFYVM_TELEPORTER_PASSWORD_FILE, RTGETOPT_REQ_STRING }, + { "--tracing-enabled", MODIFYVM_TRACING_ENABLED, RTGETOPT_REQ_BOOL_ONOFF }, + { "--tracing-config", MODIFYVM_TRACING_CONFIG, RTGETOPT_REQ_STRING }, + { "--tracing-allow-vm-access", MODIFYVM_TRACING_ALLOW_VM_ACCESS, RTGETOPT_REQ_BOOL_ONOFF }, + { "--hardwareuuid", MODIFYVM_HARDWARE_UUID, RTGETOPT_REQ_STRING }, + { "--hpet", MODIFYVM_HPET, RTGETOPT_REQ_BOOL_ONOFF }, + { "--iocache", MODIFYVM_IOCACHE, RTGETOPT_REQ_BOOL_ONOFF }, + { "--iocachesize", MODIFYVM_IOCACHESIZE, RTGETOPT_REQ_UINT32 }, + { "--faulttolerance", MODIFYVM_FAULT_TOLERANCE, RTGETOPT_REQ_STRING }, + { "--faulttoleranceaddress", MODIFYVM_FAULT_TOLERANCE_ADDRESS, RTGETOPT_REQ_STRING }, + { "--faulttoleranceport", MODIFYVM_FAULT_TOLERANCE_PORT, RTGETOPT_REQ_UINT32 }, + { "--faulttolerancepassword", MODIFYVM_FAULT_TOLERANCE_PASSWORD, RTGETOPT_REQ_STRING }, + { "--faulttolerancesyncinterval", MODIFYVM_FAULT_TOLERANCE_SYNC_INTERVAL, RTGETOPT_REQ_UINT32 }, + { "--chipset", MODIFYVM_CHIPSET, RTGETOPT_REQ_STRING }, +#ifdef VBOX_WITH_RECORDING + { "--recording", MODIFYVM_RECORDING, RTGETOPT_REQ_BOOL_ONOFF }, + { "--recordingscreens", MODIFYVM_RECORDING_SCREENS, RTGETOPT_REQ_STRING }, + { "--recordingfile", MODIFYVM_RECORDING_FILENAME, RTGETOPT_REQ_STRING }, + { "--recordingmaxtime", MODIFYVM_RECORDING_MAXTIME, RTGETOPT_REQ_INT32 }, + { "--recordingmaxsize", MODIFYVM_RECORDING_MAXSIZE, RTGETOPT_REQ_INT32 }, + { "--recordingopts", MODIFYVM_RECORDING_OPTIONS, RTGETOPT_REQ_STRING }, + { "--recordingoptions", MODIFYVM_RECORDING_OPTIONS, RTGETOPT_REQ_STRING }, + { "--recordingvideores", MODIFYVM_RECORDING_VIDEO_RES, RTGETOPT_REQ_STRING }, + { "--recordingvideoresolution", MODIFYVM_RECORDING_VIDEO_RES, RTGETOPT_REQ_STRING }, + { "--recordingvideorate", MODIFYVM_RECORDING_VIDEO_RATE, RTGETOPT_REQ_UINT32 }, + { "--recordingvideofps", MODIFYVM_RECORDING_VIDEO_FPS, RTGETOPT_REQ_UINT32 }, +#endif + { "--autostart-enabled", MODIFYVM_AUTOSTART_ENABLED, RTGETOPT_REQ_BOOL_ONOFF }, + { "--autostart-delay", MODIFYVM_AUTOSTART_DELAY, RTGETOPT_REQ_UINT32 }, + { "--autostop-type", MODIFYVM_AUTOSTOP_TYPE, RTGETOPT_REQ_STRING }, +#ifdef VBOX_WITH_PCI_PASSTHROUGH + { "--pciattach", MODIFYVM_ATTACH_PCI, RTGETOPT_REQ_STRING }, + { "--pcidetach", MODIFYVM_DETACH_PCI, RTGETOPT_REQ_STRING }, +#endif +#ifdef VBOX_WITH_USB_CARDREADER + { "--usbcardreader", MODIFYVM_USBCARDREADER, RTGETOPT_REQ_BOOL_ONOFF }, +#endif + { "--defaultfrontend", MODIFYVM_DEFAULTFRONTEND, RTGETOPT_REQ_STRING }, +}; + +static void vrdeWarningDeprecatedOption(const char *pszOption) +{ + RTStrmPrintf(g_pStdErr, "Warning: '--vrdp%s' is deprecated. Use '--vrde%s'.\n", pszOption, pszOption); +} + +/** Parse PCI address in format 01:02.03 and convert it to the numeric representation. */ +static int32_t parsePci(const char* szPciAddr) +{ + char* pszNext = (char*)szPciAddr; + int rc; + uint8_t aVals[3] = {0, 0, 0}; + + rc = RTStrToUInt8Ex(pszNext, &pszNext, 16, &aVals[0]); + if (RT_FAILURE(rc) || pszNext == NULL || *pszNext != ':') + return -1; + + rc = RTStrToUInt8Ex(pszNext+1, &pszNext, 16, &aVals[1]); + if (RT_FAILURE(rc) || pszNext == NULL || *pszNext != '.') + return -1; + + rc = RTStrToUInt8Ex(pszNext+1, &pszNext, 16, &aVals[2]); + if (RT_FAILURE(rc) || pszNext == NULL) + return -1; + + return (aVals[0] << 8) | (aVals[1] << 3) | (aVals[2] << 0); +} + +void parseGroups(const char *pcszGroups, com::SafeArray<BSTR> *pGroups) +{ + while (pcszGroups) + { + char *pComma = RTStrStr(pcszGroups, ","); + if (pComma) + { + Bstr(pcszGroups, pComma - pcszGroups).detachTo(pGroups->appendedRaw()); + pcszGroups = pComma + 1; + } + else + { + Bstr(pcszGroups).detachTo(pGroups->appendedRaw()); + pcszGroups = NULL; + } + } +} + +#ifdef VBOX_WITH_RECORDING +static int parseScreens(const char *pcszScreens, com::SafeArray<BOOL> *pScreens) +{ + while (pcszScreens && *pcszScreens) + { + char *pszNext; + uint32_t iScreen; + int rc = RTStrToUInt32Ex(pcszScreens, &pszNext, 0, &iScreen); + if (RT_FAILURE(rc)) + return 1; + if (iScreen >= pScreens->size()) + return 1; + if (pszNext && *pszNext) + { + pszNext = RTStrStripL(pszNext); + if (*pszNext != ',') + return 1; + pszNext++; + } + (*pScreens)[iScreen] = true; + pcszScreens = pszNext; + } + return 0; +} +#endif + +static int parseNum(uint32_t uIndex, unsigned cMaxIndex, const char *pszName) +{ + if ( uIndex >= 1 + && uIndex <= cMaxIndex) + return uIndex; + errorArgument("Invalid %s number %u", pszName, uIndex); + return 0; +} + +RTEXITCODE handleModifyVM(HandlerArg *a) +{ + int c; + HRESULT rc; + Bstr name; + + /* VM ID + at least one parameter. Parameter arguments are checked + * individually. */ + if (a->argc < 2) + return errorSyntax(USAGE_MODIFYVM, "Not enough parameters"); + + /* try to find the given sessionMachine */ + ComPtr<IMachine> machine; + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam()), RTEXITCODE_FAILURE); + + + /* Get the number of network adapters */ + ULONG NetworkAdapterCount = getMaxNics(a->virtualBox, machine); + + /* open a session for the VM */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE); + + /* get the mutable session sessionMachine */ + ComPtr<IMachine> sessionMachine; + CHECK_ERROR_RET(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()), RTEXITCODE_FAILURE); + + ComPtr<IBIOSSettings> biosSettings; + sessionMachine->COMGETTER(BIOSSettings)(biosSettings.asOutParam()); + + RTGETOPTSTATE GetOptState; + RTGetOptInit(&GetOptState, a->argc, a->argv, g_aModifyVMOptions, + RT_ELEMENTS(g_aModifyVMOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + + RTGETOPTUNION ValueUnion; + while ( SUCCEEDED (rc) + && (c = RTGetOpt(&GetOptState, &ValueUnion))) + { + switch (c) + { + case MODIFYVM_NAME: + { + CHECK_ERROR(sessionMachine, COMSETTER(Name)(Bstr(ValueUnion.psz).raw())); + break; + } + case MODIFYVM_GROUPS: + { + com::SafeArray<BSTR> groups; + parseGroups(ValueUnion.psz, &groups); + CHECK_ERROR(sessionMachine, COMSETTER(Groups)(ComSafeArrayAsInParam(groups))); + break; + } + case MODIFYVM_DESCRIPTION: + { + CHECK_ERROR(sessionMachine, COMSETTER(Description)(Bstr(ValueUnion.psz).raw())); + break; + } + case MODIFYVM_OSTYPE: + { + CHECK_ERROR(sessionMachine, COMSETTER(OSTypeId)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_ICONFILE: + { + RTFILE iconFile; + int vrc = RTFileOpen(&iconFile, ValueUnion.psz, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot open file \"%s\": %Rrc", ValueUnion.psz, vrc); + rc = E_FAIL; + break; + } + uint64_t cbSize; + vrc = RTFileGetSize(iconFile, &cbSize); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot get size of file \"%s\": %Rrc", ValueUnion.psz, vrc); + rc = E_FAIL; + break; + } + if (cbSize > _256K) + { + RTMsgError("File \"%s\" is bigger than 256KByte", ValueUnion.psz); + rc = E_FAIL; + break; + } + SafeArray<BYTE> icon((size_t)cbSize); + rc = RTFileRead(iconFile, icon.raw(), (size_t)cbSize, NULL); + if (RT_FAILURE(vrc)) + { + RTMsgError("Cannot read contents of file \"%s\": %Rrc", ValueUnion.psz, vrc); + rc = E_FAIL; + break; + } + RTFileClose(iconFile); + CHECK_ERROR(sessionMachine, COMSETTER(Icon)(ComSafeArrayAsInParam(icon))); + break; + } + + case MODIFYVM_MEMORY: + { + CHECK_ERROR(sessionMachine, COMSETTER(MemorySize)(ValueUnion.u32)); + break; + } + + case MODIFYVM_PAGEFUSION: + { + CHECK_ERROR(sessionMachine, COMSETTER(PageFusionEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_VRAM: + { + CHECK_ERROR(sessionMachine, COMSETTER(VRAMSize)(ValueUnion.u32)); + break; + } + + case MODIFYVM_FIRMWARE: + { + if (!RTStrICmp(ValueUnion.psz, "efi")) + { + CHECK_ERROR(sessionMachine, COMSETTER(FirmwareType)(FirmwareType_EFI)); + } + else if (!RTStrICmp(ValueUnion.psz, "efi32")) + { + CHECK_ERROR(sessionMachine, COMSETTER(FirmwareType)(FirmwareType_EFI32)); + } + else if (!RTStrICmp(ValueUnion.psz, "efi64")) + { + CHECK_ERROR(sessionMachine, COMSETTER(FirmwareType)(FirmwareType_EFI64)); + } + else if (!RTStrICmp(ValueUnion.psz, "efidual")) + { + CHECK_ERROR(sessionMachine, COMSETTER(FirmwareType)(FirmwareType_EFIDUAL)); + } + else if (!RTStrICmp(ValueUnion.psz, "bios")) + { + CHECK_ERROR(sessionMachine, COMSETTER(FirmwareType)(FirmwareType_BIOS)); + } + else + { + errorArgument("Invalid --firmware argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_ACPI: + { + CHECK_ERROR(biosSettings, COMSETTER(ACPIEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_IOAPIC: + { + CHECK_ERROR(biosSettings, COMSETTER(IOAPICEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_PAE: + { + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_PAE, ValueUnion.f)); + break; + } + + case MODIFYVM_LONGMODE: + { + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_LongMode, ValueUnion.f)); + break; + } + + case MODIFYVM_CPUID_PORTABILITY: + { + CHECK_ERROR(sessionMachine, COMSETTER(CPUIDPortabilityLevel)(ValueUnion.u32)); + break; + } + + case MODIFYVM_TFRESET: + { + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_TripleFaultReset, ValueUnion.f)); + break; + } + + case MODIFYVM_APIC: + { + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_APIC, ValueUnion.f)); + break; + } + + case MODIFYVM_X2APIC: + { + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_X2APIC, ValueUnion.f)); + break; + } + + case MODIFYVM_PARAVIRTPROVIDER: + { + if ( !RTStrICmp(ValueUnion.psz, "none") + || !RTStrICmp(ValueUnion.psz, "disabled")) + CHECK_ERROR(sessionMachine, COMSETTER(ParavirtProvider)(ParavirtProvider_None)); + else if (!RTStrICmp(ValueUnion.psz, "default")) + CHECK_ERROR(sessionMachine, COMSETTER(ParavirtProvider)(ParavirtProvider_Default)); + else if (!RTStrICmp(ValueUnion.psz, "legacy")) + CHECK_ERROR(sessionMachine, COMSETTER(ParavirtProvider)(ParavirtProvider_Legacy)); + else if (!RTStrICmp(ValueUnion.psz, "minimal")) + CHECK_ERROR(sessionMachine, COMSETTER(ParavirtProvider)(ParavirtProvider_Minimal)); + else if (!RTStrICmp(ValueUnion.psz, "hyperv")) + CHECK_ERROR(sessionMachine, COMSETTER(ParavirtProvider)(ParavirtProvider_HyperV)); + else if (!RTStrICmp(ValueUnion.psz, "kvm")) + CHECK_ERROR(sessionMachine, COMSETTER(ParavirtProvider)(ParavirtProvider_KVM)); + else + { + errorArgument("Invalid --paravirtprovider argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_PARAVIRTDEBUG: + { + CHECK_ERROR(sessionMachine, COMSETTER(ParavirtDebug)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_HWVIRTEX: + { + CHECK_ERROR(sessionMachine, SetHWVirtExProperty(HWVirtExPropertyType_Enabled, ValueUnion.f)); + break; + } + + case MODIFYVM_SETCPUID: + case MODIFYVM_SETCPUID_OLD: + { + uint32_t const idx = c == MODIFYVM_SETCPUID ? ValueUnion.PairU32.uFirst : ValueUnion.u32; + uint32_t const idxSub = c == MODIFYVM_SETCPUID ? ValueUnion.PairU32.uSecond : UINT32_MAX; + uint32_t aValue[4]; + for (unsigned i = 0; i < 4; i++) + { + int vrc = RTGetOptFetchValue(&GetOptState, &ValueUnion, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX); + if (RT_FAILURE(vrc)) + return errorSyntax(USAGE_MODIFYVM, "Missing or Invalid argument to '%s'", GetOptState.pDef->pszLong); + aValue[i] = ValueUnion.u32; + } + CHECK_ERROR(sessionMachine, SetCPUIDLeaf(idx, idxSub, aValue[0], aValue[1], aValue[2], aValue[3])); + break; + } + + case MODIFYVM_DELCPUID: + CHECK_ERROR(sessionMachine, RemoveCPUIDLeaf(ValueUnion.PairU32.uFirst, ValueUnion.PairU32.uSecond)); + break; + + case MODIFYVM_DELCPUID_OLD: + CHECK_ERROR(sessionMachine, RemoveCPUIDLeaf(ValueUnion.u32, UINT32_MAX)); + break; + + case MODIFYVM_DELALLCPUID: + { + CHECK_ERROR(sessionMachine, RemoveAllCPUIDLeaves()); + break; + } + + case MODIFYVM_NESTEDPAGING: + { + CHECK_ERROR(sessionMachine, SetHWVirtExProperty(HWVirtExPropertyType_NestedPaging, ValueUnion.f)); + break; + } + + case MODIFYVM_LARGEPAGES: + { + CHECK_ERROR(sessionMachine, SetHWVirtExProperty(HWVirtExPropertyType_LargePages, ValueUnion.f)); + break; + } + + case MODIFYVM_VTXVPID: + { + CHECK_ERROR(sessionMachine, SetHWVirtExProperty(HWVirtExPropertyType_VPID, ValueUnion.f)); + break; + } + + case MODIFYVM_VTXUX: + { + CHECK_ERROR(sessionMachine, SetHWVirtExProperty(HWVirtExPropertyType_UnrestrictedExecution, ValueUnion.f)); + break; + } + + case MODIFYVM_IBPB_ON_VM_EXIT: + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_IBPBOnVMExit, ValueUnion.f)); + break; + + case MODIFYVM_IBPB_ON_VM_ENTRY: + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_IBPBOnVMEntry, ValueUnion.f)); + break; + + case MODIFYVM_SPEC_CTRL: + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_SpecCtrl, ValueUnion.f)); + break; + + case MODIFYVM_L1D_FLUSH_ON_SCHED: + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_L1DFlushOnEMTScheduling, ValueUnion.f)); + break; + + case MODIFYVM_L1D_FLUSH_ON_VM_ENTRY: + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_L1DFlushOnVMEntry, ValueUnion.f)); + break; + + case MODIFYVM_NESTED_HW_VIRT: + CHECK_ERROR(sessionMachine, SetCPUProperty(CPUPropertyType_HWVirt, ValueUnion.f)); + break; + + case MODIFYVM_CPUS: + { + CHECK_ERROR(sessionMachine, COMSETTER(CPUCount)(ValueUnion.u32)); + break; + } + + case MODIFYVM_RTCUSEUTC: + { + CHECK_ERROR(sessionMachine, COMSETTER(RTCUseUTC)(ValueUnion.f)); + break; + } + + case MODIFYVM_CPUHOTPLUG: + { + CHECK_ERROR(sessionMachine, COMSETTER(CPUHotPlugEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_CPU_PROFILE: + { + CHECK_ERROR(sessionMachine, COMSETTER(CPUProfile)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_PLUGCPU: + { + CHECK_ERROR(sessionMachine, HotPlugCPU(ValueUnion.u32)); + break; + } + + case MODIFYVM_UNPLUGCPU: + { + CHECK_ERROR(sessionMachine, HotUnplugCPU(ValueUnion.u32)); + break; + } + + case MODIFYVM_CPU_EXECTUION_CAP: + { + CHECK_ERROR(sessionMachine, COMSETTER(CPUExecutionCap)(ValueUnion.u32)); + break; + } + + case MODIFYVM_GRAPHICSCONTROLLER: + { + if ( !RTStrICmp(ValueUnion.psz, "none") + || !RTStrICmp(ValueUnion.psz, "disabled")) + CHECK_ERROR(sessionMachine, COMSETTER(GraphicsControllerType)(GraphicsControllerType_Null)); + else if ( !RTStrICmp(ValueUnion.psz, "vboxvga") + || !RTStrICmp(ValueUnion.psz, "vbox") + || !RTStrICmp(ValueUnion.psz, "vga") + || !RTStrICmp(ValueUnion.psz, "vesa")) + CHECK_ERROR(sessionMachine, COMSETTER(GraphicsControllerType)(GraphicsControllerType_VBoxVGA)); +#ifdef VBOX_WITH_VMSVGA + else if ( !RTStrICmp(ValueUnion.psz, "vmsvga") + || !RTStrICmp(ValueUnion.psz, "vmware")) + CHECK_ERROR(sessionMachine, COMSETTER(GraphicsControllerType)(GraphicsControllerType_VMSVGA)); + else if ( !RTStrICmp(ValueUnion.psz, "vboxsvga") + || !RTStrICmp(ValueUnion.psz, "svga")) + CHECK_ERROR(sessionMachine, COMSETTER(GraphicsControllerType)(GraphicsControllerType_VBoxSVGA)); +#endif + else + { + errorArgument("Invalid --graphicscontroller argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_MONITORCOUNT: + { + CHECK_ERROR(sessionMachine, COMSETTER(MonitorCount)(ValueUnion.u32)); + break; + } + + case MODIFYVM_ACCELERATE3D: + { + CHECK_ERROR(sessionMachine, COMSETTER(Accelerate3DEnabled)(ValueUnion.f)); + break; + } + +#ifdef VBOX_WITH_VIDEOHWACCEL + case MODIFYVM_ACCELERATE2DVIDEO: + { + CHECK_ERROR(sessionMachine, COMSETTER(Accelerate2DVideoEnabled)(ValueUnion.f)); + break; + } +#endif + + case MODIFYVM_BIOSLOGOFADEIN: + { + CHECK_ERROR(biosSettings, COMSETTER(LogoFadeIn)(ValueUnion.f)); + break; + } + + case MODIFYVM_BIOSLOGOFADEOUT: + { + CHECK_ERROR(biosSettings, COMSETTER(LogoFadeOut)(ValueUnion.f)); + break; + } + + case MODIFYVM_BIOSLOGODISPLAYTIME: + { + CHECK_ERROR(biosSettings, COMSETTER(LogoDisplayTime)(ValueUnion.u32)); + break; + } + + case MODIFYVM_BIOSLOGOIMAGEPATH: + { + CHECK_ERROR(biosSettings, COMSETTER(LogoImagePath)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_BIOSBOOTMENU: + { + if (!RTStrICmp(ValueUnion.psz, "disabled")) + { + CHECK_ERROR(biosSettings, COMSETTER(BootMenuMode)(BIOSBootMenuMode_Disabled)); + } + else if (!RTStrICmp(ValueUnion.psz, "menuonly")) + { + CHECK_ERROR(biosSettings, COMSETTER(BootMenuMode)(BIOSBootMenuMode_MenuOnly)); + } + else if (!RTStrICmp(ValueUnion.psz, "messageandmenu")) + { + CHECK_ERROR(biosSettings, COMSETTER(BootMenuMode)(BIOSBootMenuMode_MessageAndMenu)); + } + else + { + errorArgument("Invalid --biosbootmenu argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_BIOSAPIC: + { + if (!RTStrICmp(ValueUnion.psz, "disabled")) + { + CHECK_ERROR(biosSettings, COMSETTER(APICMode)(APICMode_Disabled)); + } + else if ( !RTStrICmp(ValueUnion.psz, "apic") + || !RTStrICmp(ValueUnion.psz, "lapic") + || !RTStrICmp(ValueUnion.psz, "xapic")) + { + CHECK_ERROR(biosSettings, COMSETTER(APICMode)(APICMode_APIC)); + } + else if (!RTStrICmp(ValueUnion.psz, "x2apic")) + { + CHECK_ERROR(biosSettings, COMSETTER(APICMode)(APICMode_X2APIC)); + } + else + { + errorArgument("Invalid --biosapic argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_BIOSSYSTEMTIMEOFFSET: + { + CHECK_ERROR(biosSettings, COMSETTER(TimeOffset)(ValueUnion.i64)); + break; + } + + case MODIFYVM_BIOSPXEDEBUG: + { + CHECK_ERROR(biosSettings, COMSETTER(PXEDebugEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_BOOT: + { + if (!RTStrICmp(ValueUnion.psz, "none")) + { + CHECK_ERROR(sessionMachine, SetBootOrder(GetOptState.uIndex, DeviceType_Null)); + } + else if (!RTStrICmp(ValueUnion.psz, "floppy")) + { + CHECK_ERROR(sessionMachine, SetBootOrder(GetOptState.uIndex, DeviceType_Floppy)); + } + else if (!RTStrICmp(ValueUnion.psz, "dvd")) + { + CHECK_ERROR(sessionMachine, SetBootOrder(GetOptState.uIndex, DeviceType_DVD)); + } + else if (!RTStrICmp(ValueUnion.psz, "disk")) + { + CHECK_ERROR(sessionMachine, SetBootOrder(GetOptState.uIndex, DeviceType_HardDisk)); + } + else if (!RTStrICmp(ValueUnion.psz, "net")) + { + CHECK_ERROR(sessionMachine, SetBootOrder(GetOptState.uIndex, DeviceType_Network)); + } + else + return errorArgument("Invalid boot device '%s'", ValueUnion.psz); + break; + } + + case MODIFYVM_HDA: // deprecated + case MODIFYVM_HDB: // deprecated + case MODIFYVM_HDD: // deprecated + case MODIFYVM_SATAPORT: // deprecated + { + uint32_t u1 = 0, u2 = 0; + Bstr bstrController = L"IDE Controller"; + + switch (c) + { + case MODIFYVM_HDA: // deprecated + u1 = 0; + break; + + case MODIFYVM_HDB: // deprecated + u1 = 0; + u2 = 1; + break; + + case MODIFYVM_HDD: // deprecated + u1 = 1; + u2 = 1; + break; + + case MODIFYVM_SATAPORT: // deprecated + u1 = GetOptState.uIndex; + bstrController = L"SATA"; + break; + } + + if (!RTStrICmp(ValueUnion.psz, "none")) + { + sessionMachine->DetachDevice(bstrController.raw(), u1, u2); + } + else + { + ComPtr<IMedium> hardDisk; + rc = openMedium(a, ValueUnion.psz, DeviceType_HardDisk, + AccessMode_ReadWrite, hardDisk, + false /* fForceNewUuidOnOpen */, + false /* fSilent */); + if (FAILED(rc)) + break; + if (hardDisk) + { + CHECK_ERROR(sessionMachine, AttachDevice(bstrController.raw(), + u1, u2, + DeviceType_HardDisk, + hardDisk)); + } + else + rc = E_FAIL; + } + break; + } + + case MODIFYVM_IDECONTROLLER: // deprecated + { + ComPtr<IStorageController> storageController; + CHECK_ERROR(sessionMachine, GetStorageControllerByName(Bstr("IDE Controller").raw(), + storageController.asOutParam())); + + if (!RTStrICmp(ValueUnion.psz, "PIIX3")) + { + CHECK_ERROR(storageController, COMSETTER(ControllerType)(StorageControllerType_PIIX3)); + } + else if (!RTStrICmp(ValueUnion.psz, "PIIX4")) + { + CHECK_ERROR(storageController, COMSETTER(ControllerType)(StorageControllerType_PIIX4)); + } + else if (!RTStrICmp(ValueUnion.psz, "ICH6")) + { + CHECK_ERROR(storageController, COMSETTER(ControllerType)(StorageControllerType_ICH6)); + } + else + { + errorArgument("Invalid --idecontroller argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_SATAPORTCOUNT: // deprecated + { + ComPtr<IStorageController> SataCtl; + CHECK_ERROR(sessionMachine, GetStorageControllerByName(Bstr("SATA").raw(), + SataCtl.asOutParam())); + + if (SUCCEEDED(rc) && ValueUnion.u32 > 0) + CHECK_ERROR(SataCtl, COMSETTER(PortCount)(ValueUnion.u32)); + break; + } + + case MODIFYVM_SATA: // deprecated + { + if (!RTStrICmp(ValueUnion.psz, "on") || !RTStrICmp(ValueUnion.psz, "enable")) + { + ComPtr<IStorageController> ctl; + CHECK_ERROR(sessionMachine, AddStorageController(Bstr("SATA").raw(), + StorageBus_SATA, + ctl.asOutParam())); + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_IntelAhci)); + } + else if (!RTStrICmp(ValueUnion.psz, "off") || !RTStrICmp(ValueUnion.psz, "disable")) + CHECK_ERROR(sessionMachine, RemoveStorageController(Bstr("SATA").raw())); + else + return errorArgument("Invalid --usb argument '%s'", ValueUnion.psz); + break; + } + + case MODIFYVM_SCSIPORT: // deprecated + { + if (!RTStrICmp(ValueUnion.psz, "none")) + { + rc = sessionMachine->DetachDevice(Bstr("LsiLogic").raw(), + GetOptState.uIndex, 0); + if (FAILED(rc)) + CHECK_ERROR(sessionMachine, DetachDevice(Bstr("BusLogic").raw(), + GetOptState.uIndex, 0)); + } + else + { + ComPtr<IMedium> hardDisk; + rc = openMedium(a, ValueUnion.psz, DeviceType_HardDisk, + AccessMode_ReadWrite, hardDisk, + false /* fForceNewUuidOnOpen */, + false /* fSilent */); + if (FAILED(rc)) + break; + if (hardDisk) + { + rc = sessionMachine->AttachDevice(Bstr("LsiLogic").raw(), + GetOptState.uIndex, 0, + DeviceType_HardDisk, + hardDisk); + if (FAILED(rc)) + CHECK_ERROR(sessionMachine, + AttachDevice(Bstr("BusLogic").raw(), + GetOptState.uIndex, 0, + DeviceType_HardDisk, + hardDisk)); + } + else + rc = E_FAIL; + } + break; + } + + case MODIFYVM_SCSITYPE: // deprecated + { + ComPtr<IStorageController> ctl; + + if (!RTStrICmp(ValueUnion.psz, "LsiLogic")) + { + rc = sessionMachine->RemoveStorageController(Bstr("BusLogic").raw()); + if (FAILED(rc)) + CHECK_ERROR(sessionMachine, RemoveStorageController(Bstr("LsiLogic").raw())); + + CHECK_ERROR(sessionMachine, + AddStorageController(Bstr("LsiLogic").raw(), + StorageBus_SCSI, + ctl.asOutParam())); + + if (SUCCEEDED(rc)) + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_LsiLogic)); + } + else if (!RTStrICmp(ValueUnion.psz, "BusLogic")) + { + rc = sessionMachine->RemoveStorageController(Bstr("LsiLogic").raw()); + if (FAILED(rc)) + CHECK_ERROR(sessionMachine, RemoveStorageController(Bstr("BusLogic").raw())); + + CHECK_ERROR(sessionMachine, + AddStorageController(Bstr("BusLogic").raw(), + StorageBus_SCSI, + ctl.asOutParam())); + + if (SUCCEEDED(rc)) + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_BusLogic)); + } + else + return errorArgument("Invalid --scsitype argument '%s'", ValueUnion.psz); + break; + } + + case MODIFYVM_SCSI: // deprecated + { + if (!RTStrICmp(ValueUnion.psz, "on") || !RTStrICmp(ValueUnion.psz, "enable")) + { + ComPtr<IStorageController> ctl; + + CHECK_ERROR(sessionMachine, AddStorageController(Bstr("BusLogic").raw(), + StorageBus_SCSI, + ctl.asOutParam())); + if (SUCCEEDED(rc)) + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_BusLogic)); + } + else if (!RTStrICmp(ValueUnion.psz, "off") || !RTStrICmp(ValueUnion.psz, "disable")) + { + rc = sessionMachine->RemoveStorageController(Bstr("BusLogic").raw()); + if (FAILED(rc)) + CHECK_ERROR(sessionMachine, RemoveStorageController(Bstr("LsiLogic").raw())); + } + break; + } + + case MODIFYVM_DVDPASSTHROUGH: // deprecated + { + CHECK_ERROR(sessionMachine, PassthroughDevice(Bstr("IDE Controller").raw(), + 1, 0, + !RTStrICmp(ValueUnion.psz, "on"))); + break; + } + + case MODIFYVM_DVD: // deprecated + { + ComPtr<IMedium> dvdMedium; + + /* unmount? */ + if (!RTStrICmp(ValueUnion.psz, "none")) + { + /* nothing to do, NULL object will cause unmount */ + } + /* host drive? */ + else if (!RTStrNICmp(ValueUnion.psz, RT_STR_TUPLE("host:"))) + { + ComPtr<IHost> host; + CHECK_ERROR(a->virtualBox, COMGETTER(Host)(host.asOutParam())); + rc = host->FindHostDVDDrive(Bstr(ValueUnion.psz + 5).raw(), + dvdMedium.asOutParam()); + if (!dvdMedium) + { + /* 2nd try: try with the real name, important on Linux+libhal */ + char szPathReal[RTPATH_MAX]; + if (RT_FAILURE(RTPathReal(ValueUnion.psz + 5, szPathReal, sizeof(szPathReal)))) + { + errorArgument("Invalid host DVD drive name \"%s\"", ValueUnion.psz + 5); + rc = E_FAIL; + break; + } + rc = host->FindHostDVDDrive(Bstr(szPathReal).raw(), + dvdMedium.asOutParam()); + if (!dvdMedium) + { + errorArgument("Invalid host DVD drive name \"%s\"", ValueUnion.psz + 5); + rc = E_FAIL; + break; + } + } + } + else + { + rc = openMedium(a, ValueUnion.psz, DeviceType_DVD, + AccessMode_ReadOnly, dvdMedium, + false /* fForceNewUuidOnOpen */, + false /* fSilent */); + if (FAILED(rc)) + break; + if (!dvdMedium) + { + rc = E_FAIL; + break; + } + } + + CHECK_ERROR(sessionMachine, MountMedium(Bstr("IDE Controller").raw(), + 1, 0, + dvdMedium, + FALSE /* aForce */)); + break; + } + + case MODIFYVM_FLOPPY: // deprecated + { + ComPtr<IMedium> floppyMedium; + ComPtr<IMediumAttachment> floppyAttachment; + sessionMachine->GetMediumAttachment(Bstr("Floppy Controller").raw(), + 0, 0, floppyAttachment.asOutParam()); + + /* disable? */ + if (!RTStrICmp(ValueUnion.psz, "disabled")) + { + /* disable the controller */ + if (floppyAttachment) + CHECK_ERROR(sessionMachine, DetachDevice(Bstr("Floppy Controller").raw(), + 0, 0)); + } + else + { + /* enable the controller */ + if (!floppyAttachment) + CHECK_ERROR(sessionMachine, AttachDeviceWithoutMedium(Bstr("Floppy Controller").raw(), + 0, 0, + DeviceType_Floppy)); + + /* unmount? */ + if ( !RTStrICmp(ValueUnion.psz, "none") + || !RTStrICmp(ValueUnion.psz, "empty")) // deprecated + { + /* nothing to do, NULL object will cause unmount */ + } + /* host drive? */ + else if (!RTStrNICmp(ValueUnion.psz, RT_STR_TUPLE("host:"))) + { + ComPtr<IHost> host; + CHECK_ERROR(a->virtualBox, COMGETTER(Host)(host.asOutParam())); + rc = host->FindHostFloppyDrive(Bstr(ValueUnion.psz + 5).raw(), + floppyMedium.asOutParam()); + if (!floppyMedium) + { + errorArgument("Invalid host floppy drive name \"%s\"", ValueUnion.psz + 5); + rc = E_FAIL; + break; + } + } + else + { + rc = openMedium(a, ValueUnion.psz, DeviceType_Floppy, + AccessMode_ReadWrite, floppyMedium, + false /* fForceNewUuidOnOpen */, + false /* fSilent */); + if (FAILED(rc)) + break; + if (!floppyMedium) + { + rc = E_FAIL; + break; + } + } + CHECK_ERROR(sessionMachine, MountMedium(Bstr("Floppy Controller").raw(), + 0, 0, + floppyMedium, + FALSE /* aForce */)); + } + break; + } + + case MODIFYVM_NICTRACEFILE: + { + + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + CHECK_ERROR(nic, COMSETTER(TraceFile)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_NICTRACE: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + CHECK_ERROR(nic, COMSETTER(TraceEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_NICPROPERTY: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + if (nic) + { + /* Parse 'name=value' */ + char *pszProperty = RTStrDup(ValueUnion.psz); + if (pszProperty) + { + char *pDelimiter = strchr(pszProperty, '='); + if (pDelimiter) + { + *pDelimiter = '\0'; + + Bstr bstrName = pszProperty; + Bstr bstrValue = &pDelimiter[1]; + CHECK_ERROR(nic, SetProperty(bstrName.raw(), bstrValue.raw())); + } + else + { + errorArgument("Invalid --nicproperty%d argument '%s'", GetOptState.uIndex, ValueUnion.psz); + rc = E_FAIL; + } + RTStrFree(pszProperty); + } + else + { + RTStrmPrintf(g_pStdErr, "Error: Failed to allocate memory for --nicproperty%d '%s'\n", GetOptState.uIndex, ValueUnion.psz); + rc = E_FAIL; + } + } + break; + } + case MODIFYVM_NICTYPE: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + if (!RTStrICmp(ValueUnion.psz, "Am79C970A")) + { + CHECK_ERROR(nic, COMSETTER(AdapterType)(NetworkAdapterType_Am79C970A)); + } + else if (!RTStrICmp(ValueUnion.psz, "Am79C973")) + { + CHECK_ERROR(nic, COMSETTER(AdapterType)(NetworkAdapterType_Am79C973)); + } +#ifdef VBOX_WITH_E1000 + else if (!RTStrICmp(ValueUnion.psz, "82540EM")) + { + CHECK_ERROR(nic, COMSETTER(AdapterType)(NetworkAdapterType_I82540EM)); + } + else if (!RTStrICmp(ValueUnion.psz, "82543GC")) + { + CHECK_ERROR(nic, COMSETTER(AdapterType)(NetworkAdapterType_I82543GC)); + } + else if (!RTStrICmp(ValueUnion.psz, "82545EM")) + { + CHECK_ERROR(nic, COMSETTER(AdapterType)(NetworkAdapterType_I82545EM)); + } +#endif +#ifdef VBOX_WITH_VIRTIO + else if (!RTStrICmp(ValueUnion.psz, "virtio")) + { + CHECK_ERROR(nic, COMSETTER(AdapterType)(NetworkAdapterType_Virtio)); + } +#endif /* VBOX_WITH_VIRTIO */ + else + { + errorArgument("Invalid NIC type '%s' specified for NIC %u", ValueUnion.psz, GetOptState.uIndex); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_NICSPEED: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + CHECK_ERROR(nic, COMSETTER(LineSpeed)(ValueUnion.u32)); + break; + } + + case MODIFYVM_NICBOOTPRIO: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + /* Somewhat arbitrary limitation - we can pass a list of up to 4 PCI devices + * to the PXE ROM, hence only boot priorities 1-4 are allowed (in addition to + * 0 for the default lowest priority). + */ + if (ValueUnion.u32 > 4) + { + errorArgument("Invalid boot priority '%u' specfied for NIC %u", ValueUnion.u32, GetOptState.uIndex); + rc = E_FAIL; + } + else + { + CHECK_ERROR(nic, COMSETTER(BootPriority)(ValueUnion.u32)); + } + break; + } + + case MODIFYVM_NICPROMISC: + { + NetworkAdapterPromiscModePolicy_T enmPromiscModePolicy; + if (!RTStrICmp(ValueUnion.psz, "deny")) + enmPromiscModePolicy = NetworkAdapterPromiscModePolicy_Deny; + else if ( !RTStrICmp(ValueUnion.psz, "allow-vms") + || !RTStrICmp(ValueUnion.psz, "allow-network")) + enmPromiscModePolicy = NetworkAdapterPromiscModePolicy_AllowNetwork; + else if (!RTStrICmp(ValueUnion.psz, "allow-all")) + enmPromiscModePolicy = NetworkAdapterPromiscModePolicy_AllowAll; + else + { + errorArgument("Unknown promiscuous mode policy '%s'", ValueUnion.psz); + rc = E_INVALIDARG; + break; + } + + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + CHECK_ERROR(nic, COMSETTER(PromiscModePolicy)(enmPromiscModePolicy)); + break; + } + + case MODIFYVM_NICBWGROUP: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + if (!RTStrICmp(ValueUnion.psz, "none")) + { + /* Just remove the bandwidth group. */ + CHECK_ERROR(nic, COMSETTER(BandwidthGroup)(NULL)); + } + else + { + ComPtr<IBandwidthControl> bwCtrl; + ComPtr<IBandwidthGroup> bwGroup; + + CHECK_ERROR(sessionMachine, COMGETTER(BandwidthControl)(bwCtrl.asOutParam())); + + if (SUCCEEDED(rc)) + { + CHECK_ERROR(bwCtrl, GetBandwidthGroup(Bstr(ValueUnion.psz).raw(), bwGroup.asOutParam())); + if (SUCCEEDED(rc)) + { + CHECK_ERROR(nic, COMSETTER(BandwidthGroup)(bwGroup)); + } + } + } + break; + } + + case MODIFYVM_NIC: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + if (!RTStrICmp(ValueUnion.psz, "none")) + { + CHECK_ERROR(nic, COMSETTER(Enabled)(FALSE)); + } + else if (!RTStrICmp(ValueUnion.psz, "null")) + { + CHECK_ERROR(nic, COMSETTER(Enabled)(TRUE)); + CHECK_ERROR(nic, COMSETTER(AttachmentType)(NetworkAttachmentType_Null)); + } + else if (!RTStrICmp(ValueUnion.psz, "nat")) + { + CHECK_ERROR(nic, COMSETTER(Enabled)(TRUE)); + CHECK_ERROR(nic, COMSETTER(AttachmentType)(NetworkAttachmentType_NAT)); + } + else if ( !RTStrICmp(ValueUnion.psz, "bridged") + || !RTStrICmp(ValueUnion.psz, "hostif")) /* backward compatibility */ + { + CHECK_ERROR(nic, COMSETTER(Enabled)(TRUE)); + CHECK_ERROR(nic, COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged)); + } + else if (!RTStrICmp(ValueUnion.psz, "intnet")) + { + CHECK_ERROR(nic, COMSETTER(Enabled)(TRUE)); + CHECK_ERROR(nic, COMSETTER(AttachmentType)(NetworkAttachmentType_Internal)); + } + else if (!RTStrICmp(ValueUnion.psz, "hostonly")) + { + + CHECK_ERROR(nic, COMSETTER(Enabled)(TRUE)); + CHECK_ERROR(nic, COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly)); + } + else if (!RTStrICmp(ValueUnion.psz, "generic")) + { + + CHECK_ERROR(nic, COMSETTER(Enabled)(TRUE)); + CHECK_ERROR(nic, COMSETTER(AttachmentType)(NetworkAttachmentType_Generic)); + } + else if (!RTStrICmp(ValueUnion.psz, "natnetwork")) + { + + CHECK_ERROR(nic, COMSETTER(Enabled)(TRUE)); + CHECK_ERROR(nic, COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork)); + } + else + { + errorArgument("Invalid type '%s' specfied for NIC %u", ValueUnion.psz, GetOptState.uIndex); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_CABLECONNECTED: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + CHECK_ERROR(nic, COMSETTER(CableConnected)(ValueUnion.f)); + break; + } + + case MODIFYVM_BRIDGEADAPTER: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + /* remove it? */ + if (!RTStrICmp(ValueUnion.psz, "none")) + { + CHECK_ERROR(nic, COMSETTER(BridgedInterface)(Bstr().raw())); + } + else + { + CHECK_ERROR(nic, COMSETTER(BridgedInterface)(Bstr(ValueUnion.psz).raw())); + } + break; + } + + case MODIFYVM_HOSTONLYADAPTER: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + /* remove it? */ + if (!RTStrICmp(ValueUnion.psz, "none")) + { + CHECK_ERROR(nic, COMSETTER(HostOnlyInterface)(Bstr().raw())); + } + else + { + CHECK_ERROR(nic, COMSETTER(HostOnlyInterface)(Bstr(ValueUnion.psz).raw())); + } + break; + } + + case MODIFYVM_INTNET: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + /* remove it? */ + if (!RTStrICmp(ValueUnion.psz, "none")) + { + CHECK_ERROR(nic, COMSETTER(InternalNetwork)(Bstr().raw())); + } + else + { + CHECK_ERROR(nic, COMSETTER(InternalNetwork)(Bstr(ValueUnion.psz).raw())); + } + break; + } + + case MODIFYVM_GENERICDRV: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + CHECK_ERROR(nic, COMSETTER(GenericDriver)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_NATNETWORKNAME: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + CHECK_ERROR(nic, COMSETTER(NATNetwork)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_NATNET: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + ComPtr<INATEngine> engine; + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + + const char *psz = ValueUnion.psz; + if (!RTStrICmp("default", psz)) + psz = ""; + + CHECK_ERROR(engine, COMSETTER(Network)(Bstr(psz).raw())); + break; + } + + case MODIFYVM_NATBINDIP: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + ComPtr<INATEngine> engine; + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + + CHECK_ERROR(engine, COMSETTER(HostIP)(Bstr(ValueUnion.psz).raw())); + break; + } + +#define ITERATE_TO_NEXT_TERM(ch) \ + do { \ + while (*ch != ',') \ + { \ + if (*ch == 0) \ + { \ + return errorSyntax(USAGE_MODIFYVM, \ + "Missing or Invalid argument to '%s'", \ + GetOptState.pDef->pszLong); \ + } \ + ch++; \ + } \ + *ch = '\0'; \ + ch++; \ + } while(0) + + case MODIFYVM_NATSETTINGS: + { + ComPtr<INetworkAdapter> nic; + ComPtr<INATEngine> engine; + char *strMtu; + char *strSockSnd; + char *strSockRcv; + char *strTcpSnd; + char *strTcpRcv; + char *strRaw = RTStrDup(ValueUnion.psz); + char *ch = strRaw; + strMtu = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strSockSnd = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strSockRcv = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strTcpSnd = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strTcpRcv = RTStrStrip(ch); + + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + CHECK_ERROR(engine, SetNetworkSettings(RTStrToUInt32(strMtu), RTStrToUInt32(strSockSnd), RTStrToUInt32(strSockRcv), + RTStrToUInt32(strTcpSnd), RTStrToUInt32(strTcpRcv))); + break; + } + + + case MODIFYVM_NATPF: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + ComPtr<INATEngine> engine; + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + + /* format name:proto:hostip:hostport:guestip:guestport*/ + if (RTStrCmp(ValueUnion.psz, "delete") != 0) + { + char *strName; + char *strProto; + char *strHostIp; + char *strHostPort; + char *strGuestIp; + char *strGuestPort; + char *strRaw = RTStrDup(ValueUnion.psz); + char *ch = strRaw; + strName = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strProto = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strHostIp = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strHostPort = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strGuestIp = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch); + strGuestPort = RTStrStrip(ch); + NATProtocol_T proto; + if (RTStrICmp(strProto, "udp") == 0) + proto = NATProtocol_UDP; + else if (RTStrICmp(strProto, "tcp") == 0) + proto = NATProtocol_TCP; + else + { + errorArgument("Invalid proto '%s' specfied for NIC %u", ValueUnion.psz, GetOptState.uIndex); + rc = E_FAIL; + break; + } + CHECK_ERROR(engine, AddRedirect(Bstr(strName).raw(), proto, + Bstr(strHostIp).raw(), + RTStrToUInt16(strHostPort), + Bstr(strGuestIp).raw(), + RTStrToUInt16(strGuestPort))); + } + else + { + /* delete NAT Rule operation */ + int vrc; + vrc = RTGetOptFetchValue(&GetOptState, &ValueUnion, RTGETOPT_REQ_STRING); + if (RT_FAILURE(vrc)) + return errorSyntax(USAGE_MODIFYVM, "Not enough parameters"); + CHECK_ERROR(engine, RemoveRedirect(Bstr(ValueUnion.psz).raw())); + } + break; + } + #undef ITERATE_TO_NEXT_TERM + case MODIFYVM_NATALIASMODE: + { + ComPtr<INetworkAdapter> nic; + ComPtr<INATEngine> engine; + uint32_t aliasMode = 0; + + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + if (RTStrCmp(ValueUnion.psz, "default") == 0) + aliasMode = 0; + else + { + char *token = (char *)ValueUnion.psz; + while (token) + { + if (RTStrNCmp(token, RT_STR_TUPLE("log")) == 0) + aliasMode |= NATAliasMode_AliasLog; + else if (RTStrNCmp(token, RT_STR_TUPLE("proxyonly")) == 0) + aliasMode |= NATAliasMode_AliasProxyOnly; + else if (RTStrNCmp(token, RT_STR_TUPLE("sameports")) == 0) + aliasMode |= NATAliasMode_AliasUseSamePorts; + token = RTStrStr(token, ","); + if (token == NULL) + break; + token++; + } + } + CHECK_ERROR(engine, COMSETTER(AliasMode)(aliasMode)); + break; + } + + case MODIFYVM_NATTFTPPREFIX: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + ComPtr<INATEngine> engine; + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + + CHECK_ERROR(engine, COMSETTER(TFTPPrefix)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_NATTFTPFILE: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + ComPtr<INATEngine> engine; + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + + CHECK_ERROR(engine, COMSETTER(TFTPBootFile)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_NATTFTPSERVER: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + ComPtr<INATEngine> engine; + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + + CHECK_ERROR(engine, COMSETTER(TFTPNextServer)(Bstr(ValueUnion.psz).raw())); + break; + } + case MODIFYVM_NATDNSPASSDOMAIN: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + ComPtr<INATEngine> engine; + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + + CHECK_ERROR(engine, COMSETTER(DNSPassDomain)(ValueUnion.f)); + break; + } + + case MODIFYVM_NATDNSPROXY: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + ComPtr<INATEngine> engine; + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + + CHECK_ERROR(engine, COMSETTER(DNSProxy)(ValueUnion.f)); + break; + } + + case MODIFYVM_NATDNSHOSTRESOLVER: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + ComPtr<INATEngine> engine; + CHECK_ERROR(nic, COMGETTER(NATEngine)(engine.asOutParam())); + + CHECK_ERROR(engine, COMSETTER(DNSUseHostResolver)(ValueUnion.f)); + break; + } + case MODIFYVM_MACADDRESS: + { + if (!parseNum(GetOptState.uIndex, NetworkAdapterCount, "NIC")) + break; + + ComPtr<INetworkAdapter> nic; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(GetOptState.uIndex - 1, nic.asOutParam())); + ASSERT(nic); + + /* generate one? */ + if (!RTStrICmp(ValueUnion.psz, "auto")) + { + CHECK_ERROR(nic, COMSETTER(MACAddress)(Bstr().raw())); + } + else + { + CHECK_ERROR(nic, COMSETTER(MACAddress)(Bstr(ValueUnion.psz).raw())); + } + break; + } + + case MODIFYVM_HIDPTR: + { + bool fEnableUsb = false; + if (!RTStrICmp(ValueUnion.psz, "ps2")) + { + CHECK_ERROR(sessionMachine, COMSETTER(PointingHIDType)(PointingHIDType_PS2Mouse)); + } + else if (!RTStrICmp(ValueUnion.psz, "usb")) + { + CHECK_ERROR(sessionMachine, COMSETTER(PointingHIDType)(PointingHIDType_USBMouse)); + if (SUCCEEDED(rc)) + fEnableUsb = true; + } + else if (!RTStrICmp(ValueUnion.psz, "usbtablet")) + { + CHECK_ERROR(sessionMachine, COMSETTER(PointingHIDType)(PointingHIDType_USBTablet)); + if (SUCCEEDED(rc)) + fEnableUsb = true; + } + else if (!RTStrICmp(ValueUnion.psz, "usbmultitouch")) + { + CHECK_ERROR(sessionMachine, COMSETTER(PointingHIDType)(PointingHIDType_USBMultiTouch)); + if (SUCCEEDED(rc)) + fEnableUsb = true; + } + else + { + errorArgument("Invalid type '%s' specfied for pointing device", ValueUnion.psz); + rc = E_FAIL; + } + if (fEnableUsb) + { + /* Make sure either the OHCI or xHCI controller is enabled. */ + ULONG cOhciCtrls = 0; + ULONG cXhciCtrls = 0; + rc = sessionMachine->GetUSBControllerCountByType(USBControllerType_OHCI, &cOhciCtrls); + if (SUCCEEDED(rc)) { + rc = sessionMachine->GetUSBControllerCountByType(USBControllerType_XHCI, &cXhciCtrls); + if ( SUCCEEDED(rc) + && cOhciCtrls + cXhciCtrls == 0) + { + /* If there's nothing, enable OHCI (always available). */ + ComPtr<IUSBController> UsbCtl; + CHECK_ERROR(sessionMachine, AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, + UsbCtl.asOutParam())); + } + } + } + break; + } + + case MODIFYVM_HIDKBD: + { + bool fEnableUsb = false; + if (!RTStrICmp(ValueUnion.psz, "ps2")) + { + CHECK_ERROR(sessionMachine, COMSETTER(KeyboardHIDType)(KeyboardHIDType_PS2Keyboard)); + } + else if (!RTStrICmp(ValueUnion.psz, "usb")) + { + CHECK_ERROR(sessionMachine, COMSETTER(KeyboardHIDType)(KeyboardHIDType_USBKeyboard)); + if (SUCCEEDED(rc)) + fEnableUsb = true; + } + else + { + errorArgument("Invalid type '%s' specfied for keyboard", ValueUnion.psz); + rc = E_FAIL; + } + if (fEnableUsb) + { + /* Make sure either the OHCI or xHCI controller is enabled. */ + ULONG cOhciCtrls = 0; + ULONG cXhciCtrls = 0; + rc = sessionMachine->GetUSBControllerCountByType(USBControllerType_OHCI, &cOhciCtrls); + if (SUCCEEDED(rc)) { + rc = sessionMachine->GetUSBControllerCountByType(USBControllerType_XHCI, &cXhciCtrls); + if ( SUCCEEDED(rc) + && cOhciCtrls + cXhciCtrls == 0) + { + /* If there's nothing, enable OHCI (always available). */ + ComPtr<IUSBController> UsbCtl; + CHECK_ERROR(sessionMachine, AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, + UsbCtl.asOutParam())); + } + } + } + break; + } + + case MODIFYVM_UARTMODE: + { + ComPtr<ISerialPort> uart; + + CHECK_ERROR_BREAK(sessionMachine, GetSerialPort(GetOptState.uIndex - 1, uart.asOutParam())); + ASSERT(uart); + + if (!RTStrICmp(ValueUnion.psz, "disconnected")) + { + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_Disconnected)); + } + else if ( !RTStrICmp(ValueUnion.psz, "server") + || !RTStrICmp(ValueUnion.psz, "client") + || !RTStrICmp(ValueUnion.psz, "tcpserver") + || !RTStrICmp(ValueUnion.psz, "tcpclient") + || !RTStrICmp(ValueUnion.psz, "file")) + { + const char *pszMode = ValueUnion.psz; + + int vrc = RTGetOptFetchValue(&GetOptState, &ValueUnion, RTGETOPT_REQ_STRING); + if (RT_FAILURE(vrc)) + return errorSyntax(USAGE_MODIFYVM, + "Missing or Invalid argument to '%s'", + GetOptState.pDef->pszLong); + + CHECK_ERROR(uart, COMSETTER(Path)(Bstr(ValueUnion.psz).raw())); + + if (!RTStrICmp(pszMode, "server")) + { + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_HostPipe)); + CHECK_ERROR(uart, COMSETTER(Server)(TRUE)); + } + else if (!RTStrICmp(pszMode, "client")) + { + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_HostPipe)); + CHECK_ERROR(uart, COMSETTER(Server)(FALSE)); + } + else if (!RTStrICmp(pszMode, "tcpserver")) + { + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_TCP)); + CHECK_ERROR(uart, COMSETTER(Server)(TRUE)); + } + else if (!RTStrICmp(pszMode, "tcpclient")) + { + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_TCP)); + CHECK_ERROR(uart, COMSETTER(Server)(FALSE)); + } + else if (!RTStrICmp(pszMode, "file")) + { + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_RawFile)); + } + } + else + { + CHECK_ERROR(uart, COMSETTER(Path)(Bstr(ValueUnion.psz).raw())); + CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_HostDevice)); + } + break; + } + + case MODIFYVM_UARTTYPE: + { + ComPtr<ISerialPort> uart; + + CHECK_ERROR_BREAK(sessionMachine, GetSerialPort(GetOptState.uIndex - 1, uart.asOutParam())); + ASSERT(uart); + + if (!RTStrICmp(ValueUnion.psz, "16450")) + { + CHECK_ERROR(uart, COMSETTER(UartType)(UartType_U16450)); + } + else if (!RTStrICmp(ValueUnion.psz, "16550A")) + { + CHECK_ERROR(uart, COMSETTER(UartType)(UartType_U16550A)); + } + else if (!RTStrICmp(ValueUnion.psz, "16750")) + { + CHECK_ERROR(uart, COMSETTER(UartType)(UartType_U16750)); + } + else + return errorSyntax(USAGE_MODIFYVM, + "Invalid argument to '%s'", + GetOptState.pDef->pszLong); + break; + } + + case MODIFYVM_UART: + { + ComPtr<ISerialPort> uart; + + CHECK_ERROR_BREAK(sessionMachine, GetSerialPort(GetOptState.uIndex - 1, uart.asOutParam())); + ASSERT(uart); + + if (!RTStrICmp(ValueUnion.psz, "off") || !RTStrICmp(ValueUnion.psz, "disable")) + CHECK_ERROR(uart, COMSETTER(Enabled)(FALSE)); + else + { + const char *pszIOBase = ValueUnion.psz; + uint32_t uVal = 0; + + int vrc = RTGetOptFetchValue(&GetOptState, &ValueUnion, RTGETOPT_REQ_UINT32) != MODIFYVM_UART; + if (RT_FAILURE(vrc)) + return errorSyntax(USAGE_MODIFYVM, + "Missing or Invalid argument to '%s'", + GetOptState.pDef->pszLong); + + CHECK_ERROR(uart, COMSETTER(IRQ)(ValueUnion.u32)); + + vrc = RTStrToUInt32Ex(pszIOBase, NULL, 0, &uVal); + if (vrc != VINF_SUCCESS || uVal == 0) + return errorArgument("Error parsing UART I/O base '%s'", pszIOBase); + CHECK_ERROR(uart, COMSETTER(IOBase)(uVal)); + + CHECK_ERROR(uart, COMSETTER(Enabled)(TRUE)); + } + break; + } + +#if defined(RT_OS_LINUX) || defined(RT_OS_WINDOWS) + case MODIFYVM_LPTMODE: + { + ComPtr<IParallelPort> lpt; + + CHECK_ERROR_BREAK(sessionMachine, GetParallelPort(GetOptState.uIndex - 1, lpt.asOutParam())); + ASSERT(lpt); + + CHECK_ERROR(lpt, COMSETTER(Path)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_LPT: + { + ComPtr<IParallelPort> lpt; + + CHECK_ERROR_BREAK(sessionMachine, GetParallelPort(GetOptState.uIndex - 1, lpt.asOutParam())); + ASSERT(lpt); + + if (!RTStrICmp(ValueUnion.psz, "off") || !RTStrICmp(ValueUnion.psz, "disable")) + CHECK_ERROR(lpt, COMSETTER(Enabled)(FALSE)); + else + { + const char *pszIOBase = ValueUnion.psz; + uint32_t uVal = 0; + + int vrc = RTGetOptFetchValue(&GetOptState, &ValueUnion, RTGETOPT_REQ_UINT32) != MODIFYVM_LPT; + if (RT_FAILURE(vrc)) + return errorSyntax(USAGE_MODIFYVM, + "Missing or Invalid argument to '%s'", + GetOptState.pDef->pszLong); + + CHECK_ERROR(lpt, COMSETTER(IRQ)(ValueUnion.u32)); + + vrc = RTStrToUInt32Ex(pszIOBase, NULL, 0, &uVal); + if (vrc != VINF_SUCCESS || uVal == 0) + return errorArgument("Error parsing LPT I/O base '%s'", pszIOBase); + CHECK_ERROR(lpt, COMSETTER(IOBase)(uVal)); + + CHECK_ERROR(lpt, COMSETTER(Enabled)(TRUE)); + } + break; + } +#endif + + case MODIFYVM_GUESTMEMORYBALLOON: + { + CHECK_ERROR(sessionMachine, COMSETTER(MemoryBalloonSize)(ValueUnion.u32)); + break; + } + + case MODIFYVM_AUDIOCONTROLLER: + { + ComPtr<IAudioAdapter> audioAdapter; + sessionMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam()); + ASSERT(audioAdapter); + + if (!RTStrICmp(ValueUnion.psz, "sb16")) + CHECK_ERROR(audioAdapter, COMSETTER(AudioController)(AudioControllerType_SB16)); + else if (!RTStrICmp(ValueUnion.psz, "ac97")) + CHECK_ERROR(audioAdapter, COMSETTER(AudioController)(AudioControllerType_AC97)); + else if (!RTStrICmp(ValueUnion.psz, "hda")) + CHECK_ERROR(audioAdapter, COMSETTER(AudioController)(AudioControllerType_HDA)); + else + { + errorArgument("Invalid --audiocontroller argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_AUDIOCODEC: + { + ComPtr<IAudioAdapter> audioAdapter; + sessionMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam()); + ASSERT(audioAdapter); + + if (!RTStrICmp(ValueUnion.psz, "sb16")) + CHECK_ERROR(audioAdapter, COMSETTER(AudioCodec)(AudioCodecType_SB16)); + else if (!RTStrICmp(ValueUnion.psz, "stac9700")) + CHECK_ERROR(audioAdapter, COMSETTER(AudioCodec)(AudioCodecType_STAC9700)); + else if (!RTStrICmp(ValueUnion.psz, "ad1980")) + CHECK_ERROR(audioAdapter, COMSETTER(AudioCodec)(AudioCodecType_AD1980)); + else if (!RTStrICmp(ValueUnion.psz, "stac9221")) + CHECK_ERROR(audioAdapter, COMSETTER(AudioCodec)(AudioCodecType_STAC9221)); + else + { + errorArgument("Invalid --audiocodec argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_AUDIO: + { + ComPtr<IAudioAdapter> audioAdapter; + sessionMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam()); + ASSERT(audioAdapter); + + /* disable? */ + if (!RTStrICmp(ValueUnion.psz, "none")) + { + CHECK_ERROR(audioAdapter, COMSETTER(Enabled)(false)); + } + else if (!RTStrICmp(ValueUnion.psz, "null")) + { + CHECK_ERROR(audioAdapter, COMSETTER(AudioDriver)(AudioDriverType_Null)); + CHECK_ERROR(audioAdapter, COMSETTER(Enabled)(true)); + } +#ifdef RT_OS_WINDOWS +#ifdef VBOX_WITH_WINMM + else if (!RTStrICmp(ValueUnion.psz, "winmm")) + { + CHECK_ERROR(audioAdapter, COMSETTER(AudioDriver)(AudioDriverType_WinMM)); + CHECK_ERROR(audioAdapter, COMSETTER(Enabled)(true)); + } +#endif + else if (!RTStrICmp(ValueUnion.psz, "dsound")) + { + CHECK_ERROR(audioAdapter, COMSETTER(AudioDriver)(AudioDriverType_DirectSound)); + CHECK_ERROR(audioAdapter, COMSETTER(Enabled)(true)); + } +#endif /* RT_OS_WINDOWS */ +#ifdef VBOX_WITH_AUDIO_OSS + else if (!RTStrICmp(ValueUnion.psz, "oss")) + { + CHECK_ERROR(audioAdapter, COMSETTER(AudioDriver)(AudioDriverType_OSS)); + CHECK_ERROR(audioAdapter, COMSETTER(Enabled)(true)); + } +#endif +#ifdef VBOX_WITH_AUDIO_ALSA + else if (!RTStrICmp(ValueUnion.psz, "alsa")) + { + CHECK_ERROR(audioAdapter, COMSETTER(AudioDriver)(AudioDriverType_ALSA)); + CHECK_ERROR(audioAdapter, COMSETTER(Enabled)(true)); + } +#endif +#ifdef VBOX_WITH_AUDIO_PULSE + else if (!RTStrICmp(ValueUnion.psz, "pulse")) + { + CHECK_ERROR(audioAdapter, COMSETTER(AudioDriver)(AudioDriverType_Pulse)); + CHECK_ERROR(audioAdapter, COMSETTER(Enabled)(true)); + } +#endif +#ifdef RT_OS_DARWIN + else if (!RTStrICmp(ValueUnion.psz, "coreaudio")) + { + CHECK_ERROR(audioAdapter, COMSETTER(AudioDriver)(AudioDriverType_CoreAudio)); + CHECK_ERROR(audioAdapter, COMSETTER(Enabled)(true)); + } +#endif /* !RT_OS_DARWIN */ + else + { + errorArgument("Invalid --audio argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_AUDIOIN: + { + ComPtr<IAudioAdapter> audioAdapter; + sessionMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam()); + ASSERT(audioAdapter); + + CHECK_ERROR(audioAdapter, COMSETTER(EnabledIn)(ValueUnion.f)); + break; + } + + case MODIFYVM_AUDIOOUT: + { + ComPtr<IAudioAdapter> audioAdapter; + sessionMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam()); + ASSERT(audioAdapter); + + CHECK_ERROR(audioAdapter, COMSETTER(EnabledOut)(ValueUnion.f)); + break; + } + + case MODIFYVM_CLIPBOARD: + { + ClipboardMode_T mode = ClipboardMode_Disabled; /* Shut up MSC */ + if (!RTStrICmp(ValueUnion.psz, "disabled")) + mode = ClipboardMode_Disabled; + else if (!RTStrICmp(ValueUnion.psz, "hosttoguest")) + mode = ClipboardMode_HostToGuest; + else if (!RTStrICmp(ValueUnion.psz, "guesttohost")) + mode = ClipboardMode_GuestToHost; + else if (!RTStrICmp(ValueUnion.psz, "bidirectional")) + mode = ClipboardMode_Bidirectional; + else + { + errorArgument("Invalid --clipboard argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + if (SUCCEEDED(rc)) + { + CHECK_ERROR(sessionMachine, COMSETTER(ClipboardMode)(mode)); + } + break; + } + + case MODIFYVM_DRAGANDDROP: + { + DnDMode_T mode = DnDMode_Disabled; /* Shut up MSC */ + if (!RTStrICmp(ValueUnion.psz, "disabled")) + mode = DnDMode_Disabled; + else if (!RTStrICmp(ValueUnion.psz, "hosttoguest")) + mode = DnDMode_HostToGuest; + else if (!RTStrICmp(ValueUnion.psz, "guesttohost")) + mode = DnDMode_GuestToHost; + else if (!RTStrICmp(ValueUnion.psz, "bidirectional")) + mode = DnDMode_Bidirectional; + else + { + errorArgument("Invalid --draganddrop argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + if (SUCCEEDED(rc)) + { + CHECK_ERROR(sessionMachine, COMSETTER(DnDMode)(mode)); + } + break; + } + + case MODIFYVM_VRDE_EXTPACK: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + if (vrdeServer) + { + if (RTStrICmp(ValueUnion.psz, "default") != 0) + { + Bstr bstr(ValueUnion.psz); + CHECK_ERROR(vrdeServer, COMSETTER(VRDEExtPack)(bstr.raw())); + } + else + CHECK_ERROR(vrdeServer, COMSETTER(VRDEExtPack)(Bstr().raw())); + } + break; + } + + case MODIFYVM_VRDEPROPERTY: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + if (vrdeServer) + { + /* Parse 'name=value' */ + char *pszProperty = RTStrDup(ValueUnion.psz); + if (pszProperty) + { + char *pDelimiter = strchr(pszProperty, '='); + if (pDelimiter) + { + *pDelimiter = '\0'; + + Bstr bstrName = pszProperty; + Bstr bstrValue = &pDelimiter[1]; + CHECK_ERROR(vrdeServer, SetVRDEProperty(bstrName.raw(), bstrValue.raw())); + } + else + { + RTStrFree(pszProperty); + + errorArgument("Invalid --vrdeproperty argument '%s'", ValueUnion.psz); + rc = E_FAIL; + break; + } + RTStrFree(pszProperty); + } + else + { + RTStrmPrintf(g_pStdErr, "Error: Failed to allocate memory for VRDE property '%s'\n", ValueUnion.psz); + rc = E_FAIL; + } + } + break; + } + + case MODIFYVM_VRDPPORT: + vrdeWarningDeprecatedOption("port"); + RT_FALL_THRU(); + + case MODIFYVM_VRDEPORT: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + if (!RTStrICmp(ValueUnion.psz, "default")) + CHECK_ERROR(vrdeServer, SetVRDEProperty(Bstr("TCP/Ports").raw(), Bstr("0").raw())); + else + CHECK_ERROR(vrdeServer, SetVRDEProperty(Bstr("TCP/Ports").raw(), Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_VRDPADDRESS: + vrdeWarningDeprecatedOption("address"); + RT_FALL_THRU(); + + case MODIFYVM_VRDEADDRESS: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + CHECK_ERROR(vrdeServer, SetVRDEProperty(Bstr("TCP/Address").raw(), Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_VRDPAUTHTYPE: + vrdeWarningDeprecatedOption("authtype"); + RT_FALL_THRU(); + case MODIFYVM_VRDEAUTHTYPE: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + if (!RTStrICmp(ValueUnion.psz, "null")) + { + CHECK_ERROR(vrdeServer, COMSETTER(AuthType)(AuthType_Null)); + } + else if (!RTStrICmp(ValueUnion.psz, "external")) + { + CHECK_ERROR(vrdeServer, COMSETTER(AuthType)(AuthType_External)); + } + else if (!RTStrICmp(ValueUnion.psz, "guest")) + { + CHECK_ERROR(vrdeServer, COMSETTER(AuthType)(AuthType_Guest)); + } + else + { + errorArgument("Invalid --vrdeauthtype argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_VRDEAUTHLIBRARY: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + if (vrdeServer) + { + if (RTStrICmp(ValueUnion.psz, "default") != 0) + { + Bstr bstr(ValueUnion.psz); + CHECK_ERROR(vrdeServer, COMSETTER(AuthLibrary)(bstr.raw())); + } + else + CHECK_ERROR(vrdeServer, COMSETTER(AuthLibrary)(Bstr().raw())); + } + break; + } + + case MODIFYVM_VRDPMULTICON: + vrdeWarningDeprecatedOption("multicon"); + RT_FALL_THRU(); + case MODIFYVM_VRDEMULTICON: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + CHECK_ERROR(vrdeServer, COMSETTER(AllowMultiConnection)(ValueUnion.f)); + break; + } + + case MODIFYVM_VRDPREUSECON: + vrdeWarningDeprecatedOption("reusecon"); + RT_FALL_THRU(); + case MODIFYVM_VRDEREUSECON: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + CHECK_ERROR(vrdeServer, COMSETTER(ReuseSingleConnection)(ValueUnion.f)); + break; + } + + case MODIFYVM_VRDPVIDEOCHANNEL: + vrdeWarningDeprecatedOption("videochannel"); + RT_FALL_THRU(); + case MODIFYVM_VRDEVIDEOCHANNEL: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + CHECK_ERROR(vrdeServer, SetVRDEProperty(Bstr("VideoChannel/Enabled").raw(), + ValueUnion.f? Bstr("true").raw(): Bstr("false").raw())); + break; + } + + case MODIFYVM_VRDPVIDEOCHANNELQUALITY: + vrdeWarningDeprecatedOption("videochannelquality"); + RT_FALL_THRU(); + case MODIFYVM_VRDEVIDEOCHANNELQUALITY: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + CHECK_ERROR(vrdeServer, SetVRDEProperty(Bstr("VideoChannel/Quality").raw(), + Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_VRDP: + vrdeWarningDeprecatedOption(""); + RT_FALL_THRU(); + case MODIFYVM_VRDE: + { + ComPtr<IVRDEServer> vrdeServer; + sessionMachine->COMGETTER(VRDEServer)(vrdeServer.asOutParam()); + ASSERT(vrdeServer); + + CHECK_ERROR(vrdeServer, COMSETTER(Enabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_USBRENAME: + { + const char *pszName = ValueUnion.psz; + int vrc = RTGetOptFetchValue(&GetOptState, &ValueUnion, RTGETOPT_REQ_STRING); + if (RT_FAILURE(vrc)) + return errorSyntax(USAGE_MODIFYVM, + "Missing or Invalid argument to '%s'", + GetOptState.pDef->pszLong); + const char *pszNewName = ValueUnion.psz; + + SafeIfaceArray<IUSBController> ctrls; + CHECK_ERROR(sessionMachine, COMGETTER(USBControllers)(ComSafeArrayAsOutParam(ctrls))); + bool fRenamed = false; + for (size_t i = 0; i < ctrls.size(); i++) + { + ComPtr<IUSBController> pCtrl = ctrls[i]; + Bstr bstrName; + CHECK_ERROR(pCtrl, COMGETTER(Name)(bstrName.asOutParam())); + if (bstrName == pszName) + { + bstrName = pszNewName; + CHECK_ERROR(pCtrl, COMSETTER(Name)(bstrName.raw())); + fRenamed = true; + } + } + if (!fRenamed) + { + errorArgument("Invalid --usbrename parameters, nothing renamed"); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_USBXHCI: + { + ULONG cXhciCtrls = 0; + rc = sessionMachine->GetUSBControllerCountByType(USBControllerType_XHCI, &cXhciCtrls); + if (SUCCEEDED(rc)) + { + if (!cXhciCtrls && ValueUnion.f) + { + ComPtr<IUSBController> UsbCtl; + CHECK_ERROR(sessionMachine, AddUSBController(Bstr("xHCI").raw(), USBControllerType_XHCI, + UsbCtl.asOutParam())); + } + else if (cXhciCtrls && !ValueUnion.f) + { + SafeIfaceArray<IUSBController> ctrls; + CHECK_ERROR(sessionMachine, COMGETTER(USBControllers)(ComSafeArrayAsOutParam(ctrls))); + for (size_t i = 0; i < ctrls.size(); i++) + { + ComPtr<IUSBController> pCtrl = ctrls[i]; + USBControllerType_T enmType; + CHECK_ERROR(pCtrl, COMGETTER(Type)(&enmType)); + if (enmType == USBControllerType_XHCI) + { + Bstr ctrlName; + CHECK_ERROR(pCtrl, COMGETTER(Name)(ctrlName.asOutParam())); + CHECK_ERROR(sessionMachine, RemoveUSBController(ctrlName.raw())); + } + } + } + } + break; + } + + case MODIFYVM_USBEHCI: + { + ULONG cEhciCtrls = 0; + rc = sessionMachine->GetUSBControllerCountByType(USBControllerType_EHCI, &cEhciCtrls); + if (SUCCEEDED(rc)) + { + if (!cEhciCtrls && ValueUnion.f) + { + ComPtr<IUSBController> UsbCtl; + CHECK_ERROR(sessionMachine, AddUSBController(Bstr("EHCI").raw(), USBControllerType_EHCI, + UsbCtl.asOutParam())); + } + else if (cEhciCtrls && !ValueUnion.f) + { + SafeIfaceArray<IUSBController> ctrls; + CHECK_ERROR(sessionMachine, COMGETTER(USBControllers)(ComSafeArrayAsOutParam(ctrls))); + for (size_t i = 0; i < ctrls.size(); i++) + { + ComPtr<IUSBController> pCtrl = ctrls[i]; + USBControllerType_T enmType; + CHECK_ERROR(pCtrl, COMGETTER(Type)(&enmType)); + if (enmType == USBControllerType_EHCI) + { + Bstr ctrlName; + CHECK_ERROR(pCtrl, COMGETTER(Name)(ctrlName.asOutParam())); + CHECK_ERROR(sessionMachine, RemoveUSBController(ctrlName.raw())); + } + } + } + } + break; + } + + case MODIFYVM_USBOHCI: + { + ULONG cOhciCtrls = 0; + rc = sessionMachine->GetUSBControllerCountByType(USBControllerType_OHCI, &cOhciCtrls); + if (SUCCEEDED(rc)) + { + if (!cOhciCtrls && ValueUnion.f) + { + ComPtr<IUSBController> UsbCtl; + CHECK_ERROR(sessionMachine, AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, + UsbCtl.asOutParam())); + } + else if (cOhciCtrls && !ValueUnion.f) + { + SafeIfaceArray<IUSBController> ctrls; + CHECK_ERROR(sessionMachine, COMGETTER(USBControllers)(ComSafeArrayAsOutParam(ctrls))); + for (size_t i = 0; i < ctrls.size(); i++) + { + ComPtr<IUSBController> pCtrl = ctrls[i]; + USBControllerType_T enmType; + CHECK_ERROR(pCtrl, COMGETTER(Type)(&enmType)); + if (enmType == USBControllerType_OHCI) + { + Bstr ctrlName; + CHECK_ERROR(pCtrl, COMGETTER(Name)(ctrlName.asOutParam())); + CHECK_ERROR(sessionMachine, RemoveUSBController(ctrlName.raw())); + } + } + } + } + break; + } + + case MODIFYVM_SNAPSHOTFOLDER: + { + if (!RTStrICmp(ValueUnion.psz, "default")) + CHECK_ERROR(sessionMachine, COMSETTER(SnapshotFolder)(Bstr().raw())); + else + CHECK_ERROR(sessionMachine, COMSETTER(SnapshotFolder)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_TELEPORTER_ENABLED: + { + CHECK_ERROR(sessionMachine, COMSETTER(TeleporterEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_TELEPORTER_PORT: + { + CHECK_ERROR(sessionMachine, COMSETTER(TeleporterPort)(ValueUnion.u32)); + break; + } + + case MODIFYVM_TELEPORTER_ADDRESS: + { + CHECK_ERROR(sessionMachine, COMSETTER(TeleporterAddress)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_TELEPORTER_PASSWORD: + { + CHECK_ERROR(sessionMachine, COMSETTER(TeleporterPassword)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_TELEPORTER_PASSWORD_FILE: + { + Utf8Str password; + RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &password); + if (rcExit != RTEXITCODE_SUCCESS) + rc = E_FAIL; + else + CHECK_ERROR(sessionMachine, COMSETTER(TeleporterPassword)(Bstr(password).raw())); + break; + } + + case MODIFYVM_TRACING_ENABLED: + { + CHECK_ERROR(sessionMachine, COMSETTER(TracingEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_TRACING_CONFIG: + { + CHECK_ERROR(sessionMachine, COMSETTER(TracingConfig)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_TRACING_ALLOW_VM_ACCESS: + { + CHECK_ERROR(sessionMachine, COMSETTER(AllowTracingToAccessVM)(ValueUnion.f)); + break; + } + + case MODIFYVM_FAULT_TOLERANCE: + { + if (!RTStrICmp(ValueUnion.psz, "master")) + { + CHECK_ERROR(sessionMachine, COMSETTER(FaultToleranceState(FaultToleranceState_Master))); + } + else + if (!RTStrICmp(ValueUnion.psz, "standby")) + { + CHECK_ERROR(sessionMachine, COMSETTER(FaultToleranceState(FaultToleranceState_Standby))); + } + else + { + errorArgument("Invalid --faulttolerance argument '%s'", ValueUnion.psz); + rc = E_FAIL; + } + break; + } + + case MODIFYVM_FAULT_TOLERANCE_ADDRESS: + { + CHECK_ERROR(sessionMachine, COMSETTER(FaultToleranceAddress)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_FAULT_TOLERANCE_PORT: + { + CHECK_ERROR(sessionMachine, COMSETTER(FaultTolerancePort)(ValueUnion.u32)); + break; + } + + case MODIFYVM_FAULT_TOLERANCE_PASSWORD: + { + CHECK_ERROR(sessionMachine, COMSETTER(FaultTolerancePassword)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_FAULT_TOLERANCE_SYNC_INTERVAL: + { + CHECK_ERROR(sessionMachine, COMSETTER(FaultToleranceSyncInterval)(ValueUnion.u32)); + break; + } + + case MODIFYVM_HARDWARE_UUID: + { + CHECK_ERROR(sessionMachine, COMSETTER(HardwareUUID)(Bstr(ValueUnion.psz).raw())); + break; + } + + case MODIFYVM_HPET: + { + CHECK_ERROR(sessionMachine, COMSETTER(HPETEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_IOCACHE: + { + CHECK_ERROR(sessionMachine, COMSETTER(IOCacheEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_IOCACHESIZE: + { + CHECK_ERROR(sessionMachine, COMSETTER(IOCacheSize)(ValueUnion.u32)); + break; + } + + case MODIFYVM_CHIPSET: + { + if (!RTStrICmp(ValueUnion.psz, "piix3")) + { + CHECK_ERROR(sessionMachine, COMSETTER(ChipsetType)(ChipsetType_PIIX3)); + } + else if (!RTStrICmp(ValueUnion.psz, "ich9")) + { + CHECK_ERROR(sessionMachine, COMSETTER(ChipsetType)(ChipsetType_ICH9)); + BOOL fIoApic = FALSE; + CHECK_ERROR(biosSettings, COMGETTER(IOAPICEnabled)(&fIoApic)); + if (!fIoApic) + { + RTStrmPrintf(g_pStdErr, "*** I/O APIC must be enabled for ICH9, enabling. ***\n"); + CHECK_ERROR(biosSettings, COMSETTER(IOAPICEnabled)(TRUE)); + } + } + else + { + errorArgument("Invalid --chipset argument '%s' (valid: piix3,ich9)", ValueUnion.psz); + rc = E_FAIL; + } + break; + } +#ifdef VBOX_WITH_RECORDING + case MODIFYVM_RECORDING: + RT_FALL_THROUGH(); + case MODIFYVM_RECORDING_SCREENS: + RT_FALL_THROUGH(); + case MODIFYVM_RECORDING_FILENAME: + RT_FALL_THROUGH(); + case MODIFYVM_RECORDING_VIDEO_WIDTH: + RT_FALL_THROUGH(); + case MODIFYVM_RECORDING_VIDEO_HEIGHT: + RT_FALL_THROUGH(); + case MODIFYVM_RECORDING_VIDEO_RES: + RT_FALL_THROUGH(); + case MODIFYVM_RECORDING_VIDEO_RATE: + RT_FALL_THROUGH(); + case MODIFYVM_RECORDING_VIDEO_FPS: + RT_FALL_THROUGH(); + case MODIFYVM_RECORDING_MAXTIME: + RT_FALL_THROUGH(); + case MODIFYVM_RECORDING_MAXSIZE: + RT_FALL_THROUGH(); + case MODIFYVM_RECORDING_OPTIONS: + { + ComPtr<IRecordingSettings> recordingSettings; + CHECK_ERROR_BREAK(machine, COMGETTER(RecordingSettings)(recordingSettings.asOutParam())); + SafeIfaceArray <IRecordingScreenSettings> saRecordingScreenScreens; + CHECK_ERROR_BREAK(recordingSettings, COMGETTER(Screens)(ComSafeArrayAsOutParam(saRecordingScreenScreens))); + + switch (c) + { + case MODIFYVM_RECORDING: + { + CHECK_ERROR(recordingSettings, COMSETTER(Enabled)(ValueUnion.f)); + break; + } + case MODIFYVM_RECORDING_SCREENS: + { + ULONG cMonitors = 64; + CHECK_ERROR(sessionMachine, COMGETTER(MonitorCount)(&cMonitors)); + com::SafeArray<BOOL> screens(cMonitors); + if (parseScreens(ValueUnion.psz, &screens)) + { + errorArgument("Invalid list of screens specified\n"); + rc = E_FAIL; + break; + } + + if (cMonitors > saRecordingScreenScreens.size()) /* Paranoia. */ + cMonitors = (ULONG)saRecordingScreenScreens.size(); + + for (size_t i = 0; i < cMonitors; ++i) + CHECK_ERROR_BREAK(saRecordingScreenScreens[i], COMSETTER(Enabled)(screens[i])); + break; + } + case MODIFYVM_RECORDING_FILENAME: + { + Bstr bstr; + /* empty string will fall through, leaving bstr empty */ + if (*ValueUnion.psz) + { + char szVCFileAbs[RTPATH_MAX] = ""; + int vrc = RTPathAbs(ValueUnion.psz, szVCFileAbs, sizeof(szVCFileAbs)); + if (RT_FAILURE(vrc)) + { + errorArgument("Cannot convert filename \"%s\" to absolute path\n", ValueUnion.psz); + rc = E_FAIL; + break; + } + bstr = szVCFileAbs; + } + + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR(saRecordingScreenScreens[i], COMSETTER(Filename)(bstr.raw())); + break; + } + case MODIFYVM_RECORDING_VIDEO_WIDTH: + { + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR(saRecordingScreenScreens[i], COMSETTER(VideoWidth)(ValueUnion.u32)); + break; + } + case MODIFYVM_RECORDING_VIDEO_HEIGHT: + { + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR(saRecordingScreenScreens[i], COMSETTER(VideoHeight)(ValueUnion.u32)); + break; + } + case MODIFYVM_RECORDING_VIDEO_RES: + { + uint32_t uWidth = 0; + char *pszNext; + int vrc = RTStrToUInt32Ex(ValueUnion.psz, &pszNext, 0, &uWidth); + if (RT_FAILURE(vrc) || vrc != VWRN_TRAILING_CHARS || !pszNext || *pszNext != 'x') + { + errorArgument("Error parsing video resolution '%s' (expected <width>x<height>)", ValueUnion.psz); + rc = E_FAIL; + break; + } + uint32_t uHeight = 0; + vrc = RTStrToUInt32Ex(pszNext+1, NULL, 0, &uHeight); + if (vrc != VINF_SUCCESS) + { + errorArgument("Error parsing video resolution '%s' (expected <width>x<height>)", ValueUnion.psz); + rc = E_FAIL; + break; + } + + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + { + CHECK_ERROR(saRecordingScreenScreens[i], COMSETTER(VideoWidth)(uWidth)); + CHECK_ERROR(saRecordingScreenScreens[i], COMSETTER(VideoHeight)(uHeight)); + } + break; + } + case MODIFYVM_RECORDING_VIDEO_RATE: + { + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR(saRecordingScreenScreens[i], COMSETTER(VideoRate)(ValueUnion.u32)); + break; + } + case MODIFYVM_RECORDING_VIDEO_FPS: + { + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR(saRecordingScreenScreens[i], COMSETTER(VideoFPS)(ValueUnion.u32)); + break; + } + case MODIFYVM_RECORDING_MAXTIME: + { + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR(saRecordingScreenScreens[i], COMSETTER(MaxTime)(ValueUnion.u32)); + break; + } + case MODIFYVM_RECORDING_MAXSIZE: + { + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR(saRecordingScreenScreens[i], COMSETTER(MaxFileSize)(ValueUnion.u32)); + break; + } + case MODIFYVM_RECORDING_OPTIONS: + { + Bstr bstr(ValueUnion.psz); + for (size_t i = 0; i < saRecordingScreenScreens.size(); ++i) + CHECK_ERROR(saRecordingScreenScreens[i], COMSETTER(Options)(bstr.raw())); + break; + } + } + + break; + } +#endif + case MODIFYVM_AUTOSTART_ENABLED: + { + CHECK_ERROR(sessionMachine, COMSETTER(AutostartEnabled)(ValueUnion.f)); + break; + } + + case MODIFYVM_AUTOSTART_DELAY: + { + CHECK_ERROR(sessionMachine, COMSETTER(AutostartDelay)(ValueUnion.u32)); + break; + } + + case MODIFYVM_AUTOSTOP_TYPE: + { + AutostopType_T enmAutostopType = AutostopType_Disabled; + + if (!RTStrICmp(ValueUnion.psz, "disabled")) + enmAutostopType = AutostopType_Disabled; + else if (!RTStrICmp(ValueUnion.psz, "savestate")) + enmAutostopType = AutostopType_SaveState; + else if (!RTStrICmp(ValueUnion.psz, "poweroff")) + enmAutostopType = AutostopType_PowerOff; + else if (!RTStrICmp(ValueUnion.psz, "acpishutdown")) + enmAutostopType = AutostopType_AcpiShutdown; + else + { + errorArgument("Invalid --autostop-type argument '%s' (valid: disabled, savestate, poweroff, acpishutdown)", ValueUnion.psz); + rc = E_FAIL; + } + + if (SUCCEEDED(rc)) + CHECK_ERROR(sessionMachine, COMSETTER(AutostopType)(enmAutostopType)); + break; + } +#ifdef VBOX_WITH_PCI_PASSTHROUGH + case MODIFYVM_ATTACH_PCI: + { + const char* pAt = strchr(ValueUnion.psz, '@'); + int32_t iHostAddr, iGuestAddr; + + iHostAddr = parsePci(ValueUnion.psz); + iGuestAddr = pAt != NULL ? parsePci(pAt + 1) : iHostAddr; + + if (iHostAddr == -1 || iGuestAddr == -1) + { + errorArgument("Invalid --pciattach argument '%s' (valid: 'HB:HD.HF@GB:GD.GF' or just 'HB:HD.HF')", ValueUnion.psz); + rc = E_FAIL; + } + else + { + CHECK_ERROR(sessionMachine, AttachHostPCIDevice(iHostAddr, iGuestAddr, TRUE)); + } + + break; + } + case MODIFYVM_DETACH_PCI: + { + int32_t iHostAddr; + + iHostAddr = parsePci(ValueUnion.psz); + if (iHostAddr == -1) + { + errorArgument("Invalid --pcidetach argument '%s' (valid: 'HB:HD.HF')", ValueUnion.psz); + rc = E_FAIL; + } + else + { + CHECK_ERROR(sessionMachine, DetachHostPCIDevice(iHostAddr)); + } + + break; + } +#endif + +#ifdef VBOX_WITH_USB_CARDREADER + case MODIFYVM_USBCARDREADER: + { + CHECK_ERROR(sessionMachine, COMSETTER(EmulatedUSBCardReaderEnabled)(ValueUnion.f)); + break; + } +#endif /* VBOX_WITH_USB_CARDREADER */ + + case MODIFYVM_DEFAULTFRONTEND: + { + Bstr bstr(ValueUnion.psz); + if (bstr == "default") + bstr = Bstr::Empty; + CHECK_ERROR(sessionMachine, COMSETTER(DefaultFrontend)(bstr.raw())); + break; + } + + default: + { + errorGetOpt(USAGE_MODIFYVM, c, &ValueUnion); + rc = E_FAIL; + break; + } + } + } + + /* commit changes */ + if (SUCCEEDED(rc)) + CHECK_ERROR(sessionMachine, SaveSettings()); + + /* it's important to always close sessions */ + a->session->UnlockMachine(); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +#endif /* !VBOX_ONLY_DOCS */ diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageNATNetwork.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageNATNetwork.cpp new file mode 100644 index 00000000..6312bb03 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageNATNetwork.cpp @@ -0,0 +1,523 @@ +/* $Id: VBoxManageNATNetwork.cpp $ */ +/** @file + * VBoxManage - Implementation of NAT Network command command. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef VBOX_ONLY_DOCS + +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> +#endif /* !VBOX_ONLY_DOCS */ + +#ifndef RT_OS_WINDOWS +# include <netinet/in.h> +#else +/* from <ws2ipdef.h> */ +# define INET6_ADDRSTRLEN 65 +#endif + +#define IPv6 + +#include <iprt/cdefs.h> +#include <iprt/cidr.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/net.h> +#include <iprt/getopt.h> +#include <iprt/ctype.h> + +#include <VBox/log.h> + +#include <vector> +#include <string> + +#include "VBoxManage.h" +#include "VBoxPortForwardString.h" + +#ifndef VBOX_ONLY_DOCS + +using namespace com; + +typedef enum +{ + OP_ADD = 1000, + OP_REMOVE, + OP_MODIFY, + OP_START, + OP_STOP +} OPCODE; + +typedef struct PFNAME2DELETE +{ + char szName[PF_NAMELEN]; + bool fIPv6; +} PFNAME2DELETE, *PPFNAME2DELETE; + +typedef std::vector<PFNAME2DELETE> VPF2DELETE; +typedef VPF2DELETE::const_iterator VPF2DELETEITERATOR; + +typedef std::vector<PORTFORWARDRULE> VPF2ADD; +typedef VPF2ADD::const_iterator VPF2ADDITERATOR; + +typedef std::vector<std::string> LOOPBACK2DELETEADD; +typedef LOOPBACK2DELETEADD::iterator LOOPBACK2DELETEADDITERATOR; + +static HRESULT printNATNetwork(const ComPtr<INATNetwork> &pNATNet) +{ + HRESULT rc; + + do + { + Bstr strVal; + CHECK_ERROR_BREAK(pNATNet, COMGETTER(NetworkName)(strVal.asOutParam())); + RTPrintf("Name: %ls\n", strVal.raw()); + CHECK_ERROR_BREAK(pNATNet, COMGETTER(Network)(strVal.asOutParam())); + RTPrintf("Network: %ls\n", strVal.raw()); + CHECK_ERROR_BREAK(pNATNet, COMGETTER(Gateway)(strVal.asOutParam())); + RTPrintf("Gateway: %ls\n", strVal.raw()); + BOOL fVal; + CHECK_ERROR_BREAK(pNATNet, COMGETTER(IPv6Enabled)(&fVal)); + RTPrintf("IPv6: %s\n", fVal ? "Yes" : "No"); + if (fVal) + { + CHECK_ERROR_BREAK(pNATNet, COMGETTER(IPv6Prefix)(strVal.asOutParam())); + RTPrintf("IPv6 Prefix: %s\n", strVal.raw()); + } + CHECK_ERROR_BREAK(pNATNet, COMGETTER(Enabled)(&fVal)); + RTPrintf("Enabled: %s\n", fVal ? "Yes" : "No"); + /** @todo Add more information here. */ + RTPrintf("\n"); + + } while (0); + + return rc; +} + +static RTEXITCODE handleNATList(HandlerArg *a) +{ + HRESULT rc; + + RTPrintf("NAT Networks:\n\n"); + + const char *pszFilter = NULL; + if (a->argc > 1) + pszFilter = a->argv[1]; + + size_t cFound = 0; + + com::SafeIfaceArray<INATNetwork> arrNetNets; + CHECK_ERROR(a->virtualBox, COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(arrNetNets))); + for (size_t i = 0; i < arrNetNets.size(); ++i) + { + ComPtr<INATNetwork> pNATNet = arrNetNets[i]; + + if (pszFilter) + { + Bstr strVal; + CHECK_ERROR_BREAK(pNATNet, COMGETTER(NetworkName)(strVal.asOutParam())); + + Utf8Str strValUTF8 = Utf8Str(strVal); + if (!RTStrSimplePatternMatch(pszFilter, strValUTF8.c_str())) + continue; + } + + if (i > 0) + RTPrintf("\n"); + rc = printNATNetwork(pNATNet); + if (FAILED(rc)) + break; + + cFound++; + } + + if (SUCCEEDED(rc)) + RTPrintf("%zu %s found\n", cFound, cFound == 1 ? "network" : "networks"); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static RTEXITCODE handleOp(HandlerArg *a, OPCODE enmCode) +{ + if (a->argc - 1 <= 1) + return errorSyntax(USAGE_NATNETWORK, "Not enough parameters"); + + const char *pNetName = NULL; + const char *pNetworkCidr = NULL; + int enable = -1; + int dhcp = -1; + int ipv6 = -1; + + VPF2DELETE vPfName2Delete; + VPF2ADD vPf2Add; + + LOOPBACK2DELETEADD vLoopback2Delete; + LOOPBACK2DELETEADD vLoopback2Add; + + LONG loopback6Offset = 0; /* ignore me */ + + static const RTGETOPTDEF g_aNATNetworkIPOptions[] = + { + { "--netname", 't', RTGETOPT_REQ_STRING }, + { "--network", 'n', RTGETOPT_REQ_STRING }, + { "--dhcp", 'h', RTGETOPT_REQ_BOOL }, + { "--ipv6", '6', RTGETOPT_REQ_BOOL }, + { "--enable", 'e', RTGETOPT_REQ_NOTHING }, + { "--disable", 'd', RTGETOPT_REQ_NOTHING }, + { "--port-forward-4", 'p', RTGETOPT_REQ_STRING }, + { "--port-forward-6", 'P', RTGETOPT_REQ_STRING }, + { "--loopback-4", 'l', RTGETOPT_REQ_STRING }, + { "--loopback-6", 'L', RTGETOPT_REQ_STRING }, + }; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, a->argc, a->argv, g_aNATNetworkIPOptions, + enmCode != OP_REMOVE ? RT_ELEMENTS(g_aNATNetworkIPOptions) : 4, /* we use only --netname and --ifname for remove*/ + 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (c) + { + case 't': // --netname + if (pNetName) + return errorSyntax(USAGE_NATNETWORK, "You can only specify --netname only once."); + pNetName = ValueUnion.psz; + break; + + case 'n': // --network + if (pNetworkCidr) + return errorSyntax(USAGE_NATNETWORK, "You can only specify --network only once."); + pNetworkCidr = ValueUnion.psz; + break; + + case 'e': // --enable + if (enable >= 0) + return errorSyntax(USAGE_NATNETWORK, "You can specify either --enable or --disable once."); + enable = 1; + break; + + case 'd': // --disable + if (enable >= 0) + return errorSyntax(USAGE_NATNETWORK, "You can specify either --enable or --disable once."); + enable = 0; + break; + + case 'h': + if (dhcp != -1) + return errorSyntax(USAGE_NATNETWORK, "You can specify --dhcp only once."); + dhcp = ValueUnion.f; + break; + + case '6': + if (ipv6 != -1) + return errorSyntax(USAGE_NATNETWORK, "You can specify --ipv6 only once."); + ipv6 = ValueUnion.f; + break; + + case 'L': /* ipv6 loopback */ + case 'l': /* ipv4 loopback */ + if (RTStrCmp(ValueUnion.psz, "delete") == 0) + { + /* deletion */ + if (enmCode != OP_MODIFY) + errorSyntax(USAGE_NATNETWORK, + "loopback couldn't be deleted on modified\n"); + if (c == 'L') + loopback6Offset = -1; + else + { + int vrc; + RTGETOPTUNION Addr2Delete; + vrc = RTGetOptFetchValue(&GetState, + &Addr2Delete, + RTGETOPT_REQ_STRING); + if (RT_FAILURE(vrc)) + return errorSyntax(USAGE_NATNETWORK, + "Not enough parmaters\n"); + + vLoopback2Delete.push_back(std::string(Addr2Delete.psz)); + } + } + else + { + /* addition */ + if (c == 'L') + loopback6Offset = ValueUnion.u32; + else + vLoopback2Add.push_back(std::string(ValueUnion.psz)); + } + break; + + case 'P': /* ipv6 portforwarding*/ + case 'p': /* ipv4 portforwarding */ + { + if (RTStrCmp(ValueUnion.psz, "delete") != 0) + { + /* addition */ + /* netPfStrToPf will clean up the Pfr */ + PORTFORWARDRULE Pfr; + int irc = netPfStrToPf(ValueUnion.psz, (c == 'P'), &Pfr); + if (RT_FAILURE(irc)) + return errorSyntax(USAGE_NATNETWORK, "Invalid port-forward rule %s\n", ValueUnion.psz); + + vPf2Add.push_back(Pfr); + } + else + { + /* deletion */ + if (enmCode != OP_MODIFY) + return errorSyntax(USAGE_NATNETWORK, + "Port-forward could be deleted on modify \n"); + + RTGETOPTUNION NamePf2DeleteUnion; + int vrc = RTGetOptFetchValue(&GetState, &NamePf2DeleteUnion, RTGETOPT_REQ_STRING); + if (RT_FAILURE(vrc)) + return errorSyntax(USAGE_NATNETWORK, "Not enough parmaters\n"); + + if (strlen(NamePf2DeleteUnion.psz) > PF_NAMELEN) + return errorSyntax(USAGE_NATNETWORK, "Port-forward rule name is too long\n"); + + PFNAME2DELETE Name2Delete; + RT_ZERO(Name2Delete); + RTStrCopy(Name2Delete.szName, PF_NAMELEN, NamePf2DeleteUnion.psz); + Name2Delete.fIPv6 = (c == 'P'); + vPfName2Delete.push_back(Name2Delete); + } + break; + } + + default: + return errorGetOpt(USAGE_NATNETWORK, c, &ValueUnion); + } + } + + if (!pNetName) + return errorSyntax(USAGE_NATNETWORK, "You need to specify the --netname option"); + /* verification */ + switch (enmCode) + { + case OP_ADD: + if (!pNetworkCidr) + return errorSyntax(USAGE_NATNETWORK, "You need to specify the --network option"); + break; + case OP_MODIFY: + case OP_REMOVE: + case OP_START: + case OP_STOP: + break; + default: + AssertMsgFailedReturn(("Unknown operation (:%d)", enmCode), RTEXITCODE_FAILURE); + } + + HRESULT rc; + Bstr NetName; + NetName = Bstr(pNetName); + + ComPtr<INATNetwork> net; + rc = a->virtualBox->FindNATNetworkByName(NetName.mutableRaw(), net.asOutParam()); + if (enmCode == OP_ADD) + { + if (SUCCEEDED(rc)) + return errorArgument("NATNetwork server already exists"); + + CHECK_ERROR(a->virtualBox, CreateNATNetwork(NetName.raw(), net.asOutParam())); + if (FAILED(rc)) + return errorArgument("Failed to create the NAT network service"); + } + else if (FAILED(rc)) + return errorArgument("NATNetwork server does not exist"); + + switch (enmCode) + { + case OP_ADD: + case OP_MODIFY: + { + if (pNetworkCidr) + { + CHECK_ERROR(net, COMSETTER(Network)(Bstr(pNetworkCidr).raw())); + if (FAILED(rc)) + return errorArgument("Failed to set configuration"); + } + if (dhcp >= 0) + { + CHECK_ERROR(net, COMSETTER(NeedDhcpServer) ((BOOL)dhcp)); + if (FAILED(rc)) + return errorArgument("Failed to set configuration"); + } + + if (ipv6 >= 0) + { + CHECK_ERROR(net, COMSETTER(IPv6Enabled) ((BOOL)ipv6)); + if (FAILED(rc)) + return errorArgument("Failed to set configuration"); + } + + if (!vPfName2Delete.empty()) + { + VPF2DELETEITERATOR it; + for (it = vPfName2Delete.begin(); it != vPfName2Delete.end(); ++it) + { + CHECK_ERROR(net, RemovePortForwardRule((BOOL)(*it).fIPv6, + Bstr((*it).szName).raw())); + if (FAILED(rc)) + return errorArgument("Failed to delete pf"); + } + } + + if (!vPf2Add.empty()) + { + VPF2ADDITERATOR it; + for (it = vPf2Add.begin(); it != vPf2Add.end(); ++it) + { + NATProtocol_T proto = NATProtocol_TCP; + if ((*it).iPfrProto == IPPROTO_TCP) + proto = NATProtocol_TCP; + else if ((*it).iPfrProto == IPPROTO_UDP) + proto = NATProtocol_UDP; + else + continue; /* XXX: warning here. */ + + CHECK_ERROR(net, AddPortForwardRule((BOOL)(*it).fPfrIPv6, + Bstr((*it).szPfrName).raw(), + proto, + Bstr((*it).szPfrHostAddr).raw(), + (*it).u16PfrHostPort, + Bstr((*it).szPfrGuestAddr).raw(), + (*it).u16PfrGuestPort)); + if (FAILED(rc)) + return errorArgument("Failed to add pf"); + } + } + + if (loopback6Offset) + { + if (loopback6Offset == -1) + loopback6Offset = 0; /* deletion */ + + CHECK_ERROR_RET(net, COMSETTER(LoopbackIp6)(loopback6Offset), RTEXITCODE_FAILURE); + } + + /* addLocalMapping (hostid, offset) */ + if (!vLoopback2Add.empty()) + { + /* we're expecting stings 127.0.0.1=5 */ + LOOPBACK2DELETEADDITERATOR it; + for (it = vLoopback2Add.begin(); + it != vLoopback2Add.end(); + ++it) + { + std::string address, strOffset; + size_t pos = it->find('='); + LONG lOffset = 0; + Bstr bstrAddress; + + AssertReturn(pos != std::string::npos, errorArgument("invalid loopback string")); + + address = it->substr(0, pos); + strOffset = it->substr(pos + 1); + + lOffset = RTStrToUInt32(strOffset.c_str()); + AssertReturn(lOffset > 0, errorArgument("invalid loopback string")); + + bstrAddress = Bstr(address.c_str()); + + CHECK_ERROR_RET(net, AddLocalMapping(bstrAddress.raw(), lOffset), RTEXITCODE_FAILURE); + } + } + + if (!vLoopback2Delete.empty()) + { + /* we're expecting stings 127.0.0.1 */ + LOOPBACK2DELETEADDITERATOR it; + for (it = vLoopback2Add.begin(); + it != vLoopback2Add.end(); + ++it) + { + Bstr bstrAddress; + bstrAddress = Bstr(it->c_str()); + + CHECK_ERROR_RET(net, AddLocalMapping(bstrAddress.raw(), 0), RTEXITCODE_FAILURE); + } + } + + if (enable >= 0) + { + CHECK_ERROR(net, COMSETTER(Enabled) ((BOOL)enable)); + if (FAILED(rc)) + return errorArgument("Failed to set configuration"); + } + break; + } + case OP_REMOVE: + { + CHECK_ERROR(a->virtualBox, RemoveNATNetwork(net)); + if (FAILED(rc)) + return errorArgument("Failed to remove nat network"); + break; + } + case OP_START: + { + CHECK_ERROR(net, Start(Bstr("whatever").raw())); + if (FAILED(rc)) + return errorArgument("Failed to start network"); + break; + } + case OP_STOP: + { + CHECK_ERROR(net, Stop()); + if (FAILED(rc)) + return errorArgument("Failed to start network"); + break; + } + default:; + } + return RTEXITCODE_SUCCESS; +} + + +RTEXITCODE handleNATNetwork(HandlerArg *a) +{ + if (a->argc < 1) + return errorSyntax(USAGE_NATNETWORK, "Not enough parameters"); + + RTEXITCODE rcExit; + if (strcmp(a->argv[0], "modify") == 0) + rcExit = handleOp(a, OP_MODIFY); + else if (strcmp(a->argv[0], "add") == 0) + rcExit = handleOp(a, OP_ADD); + else if (strcmp(a->argv[0], "remove") == 0) + rcExit = handleOp(a, OP_REMOVE); + else if (strcmp(a->argv[0], "start") == 0) + rcExit = handleOp(a, OP_START); + else if (strcmp(a->argv[0], "stop") == 0) + rcExit = handleOp(a, OP_STOP); + else if (strcmp(a->argv[0], "list") == 0) + rcExit = handleNATList(a); + else + rcExit = errorSyntax(USAGE_NATNETWORK, "Invalid parameter '%s'", Utf8Str(a->argv[0]).c_str()); + return rcExit; +} + +#endif /* !VBOX_ONLY_DOCS */ diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageSnapshot.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageSnapshot.cpp new file mode 100644 index 00000000..61156bf2 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageSnapshot.cpp @@ -0,0 +1,641 @@ +/* $Id: VBoxManageSnapshot.cpp $ */ +/** @file + * VBoxManage - The 'snapshot' command. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> + +#include <VBox/com/VirtualBox.h> + +#include <iprt/getopt.h> +#include <iprt/stream.h> +#include <iprt/time.h> + +#include "VBoxManage.h" +using namespace com; + +/** + * Helper function used with "VBoxManage snapshot ... dump". Gets called to find the + * snapshot in the machine's snapshot tree that uses a particular diff image child of + * a medium. + * Horribly inefficient since we keep re-querying the snapshots tree for each image, + * but this is for quick debugging only. + * @param pMedium + * @param pThisSnapshot + * @param pCurrentSnapshot + * @param uMediumLevel + * @param uSnapshotLevel + * @return + */ +bool FindAndPrintSnapshotUsingMedium(ComPtr<IMedium> &pMedium, + ComPtr<ISnapshot> &pThisSnapshot, + ComPtr<ISnapshot> &pCurrentSnapshot, + uint32_t uMediumLevel, + uint32_t uSnapshotLevel) +{ + HRESULT rc; + + do + { + // get snapshot machine so we can figure out which diff image this created + ComPtr<IMachine> pSnapshotMachine; + CHECK_ERROR_BREAK(pThisSnapshot, COMGETTER(Machine)(pSnapshotMachine.asOutParam())); + + // get media attachments + SafeIfaceArray<IMediumAttachment> aAttachments; + CHECK_ERROR_BREAK(pSnapshotMachine, COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(aAttachments))); + + for (uint32_t i = 0; + i < aAttachments.size(); + ++i) + { + ComPtr<IMediumAttachment> pAttach(aAttachments[i]); + DeviceType_T type; + CHECK_ERROR_BREAK(pAttach, COMGETTER(Type)(&type)); + if (type == DeviceType_HardDisk) + { + ComPtr<IMedium> pMediumInSnapshot; + CHECK_ERROR_BREAK(pAttach, COMGETTER(Medium)(pMediumInSnapshot.asOutParam())); + + if (pMediumInSnapshot == pMedium) + { + // get snapshot name + Bstr bstrSnapshotName; + CHECK_ERROR_BREAK(pThisSnapshot, COMGETTER(Name)(bstrSnapshotName.asOutParam())); + + RTPrintf("%*s \"%ls\"%s\n", + 50 + uSnapshotLevel * 2, "", // indent + bstrSnapshotName.raw(), + (pThisSnapshot == pCurrentSnapshot) ? " (CURSNAP)" : ""); + return true; // found + } + } + } + + // not found: then recurse into child snapshots + SafeIfaceArray<ISnapshot> aSnapshots; + CHECK_ERROR_BREAK(pThisSnapshot, COMGETTER(Children)(ComSafeArrayAsOutParam(aSnapshots))); + + for (uint32_t i = 0; + i < aSnapshots.size(); + ++i) + { + ComPtr<ISnapshot> pChild(aSnapshots[i]); + if (FindAndPrintSnapshotUsingMedium(pMedium, + pChild, + pCurrentSnapshot, + uMediumLevel, + uSnapshotLevel + 1)) + // found: + break; + } + } while (0); + + return false; +} + +/** + * Helper function used with "VBoxManage snapshot ... dump". Called from DumpSnapshot() + * for each hard disk attachment found in a virtual machine. This then writes out the + * root (base) medium for that hard disk attachment and recurses into the children + * tree of that medium, correlating it with the snapshots of the machine. + * @param pCurrentStateMedium constant, the medium listed in the current machine data (latest diff image). + * @param pMedium variant, initially the base medium, then a child of the base medium when recursing. + * @param pRootSnapshot constant, the root snapshot of the machine, if any; this then looks into the child snapshots. + * @param pCurrentSnapshot constant, the machine's current snapshot (so we can mark it in the output). + * @param uLevel variant, the recursion level for output indentation. + */ +void DumpMediumWithChildren(ComPtr<IMedium> &pCurrentStateMedium, + ComPtr<IMedium> &pMedium, + ComPtr<ISnapshot> &pRootSnapshot, + ComPtr<ISnapshot> &pCurrentSnapshot, + uint32_t uLevel) +{ + HRESULT rc; + do + { + // print this medium + Bstr bstrMediumName; + CHECK_ERROR_BREAK(pMedium, COMGETTER(Name)(bstrMediumName.asOutParam())); + RTPrintf("%*s \"%ls\"%s\n", + uLevel * 2, "", // indent + bstrMediumName.raw(), + (pCurrentStateMedium == pMedium) ? " (CURSTATE)" : ""); + + // find and print the snapshot that uses this particular medium (diff image) + FindAndPrintSnapshotUsingMedium(pMedium, pRootSnapshot, pCurrentSnapshot, uLevel, 0); + + // recurse into children + SafeIfaceArray<IMedium> aChildren; + CHECK_ERROR_BREAK(pMedium, COMGETTER(Children)(ComSafeArrayAsOutParam(aChildren))); + for (uint32_t i = 0; + i < aChildren.size(); + ++i) + { + ComPtr<IMedium> pChild(aChildren[i]); + DumpMediumWithChildren(pCurrentStateMedium, pChild, pRootSnapshot, pCurrentSnapshot, uLevel + 1); + } + } while (0); +} + + +/** + * Handles the 'snapshot myvm list' sub-command. + * @returns Exit code. + * @param pArgs The handler argument package. + * @param pMachine Reference to the VM (locked) we're operating on. + */ +static RTEXITCODE handleSnapshotList(HandlerArg *pArgs, ComPtr<IMachine> &pMachine) +{ + static const RTGETOPTDEF g_aOptions[] = + { + { "--details", 'D', RTGETOPT_REQ_NOTHING }, + { "--machinereadable", 'M', RTGETOPT_REQ_NOTHING }, + }; + + VMINFO_DETAILS enmDetails = VMINFO_STANDARD; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, g_aOptions, RT_ELEMENTS(g_aOptions), 2 /*iArg*/, 0 /*fFlags*/); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'D': enmDetails = VMINFO_FULL; break; + case 'M': enmDetails = VMINFO_MACHINEREADABLE; break; + default: return errorGetOpt(USAGE_SNAPSHOT, c, &ValueUnion); + } + } + + ComPtr<ISnapshot> pSnapshot; + HRESULT hrc = pMachine->FindSnapshot(Bstr().raw(), pSnapshot.asOutParam()); + if (FAILED(hrc)) + { + RTPrintf("This machine does not have any snapshots\n"); + return RTEXITCODE_FAILURE; + } + if (pSnapshot) + { + ComPtr<ISnapshot> pCurrentSnapshot; + CHECK_ERROR2I_RET(pMachine, COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam()), RTEXITCODE_FAILURE); + hrc = showSnapshots(pSnapshot, pCurrentSnapshot, enmDetails); + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + } + return RTEXITCODE_SUCCESS; +} + +/** + * Implementation for "VBoxManage snapshot ... dump". This goes thru the machine's + * medium attachments and calls DumpMediumWithChildren() for each hard disk medium found, + * which then dumps the parent/child tree of that medium together with the corresponding + * snapshots. + * @param pMachine Machine to dump snapshots for. + */ +void DumpSnapshot(ComPtr<IMachine> &pMachine) +{ + HRESULT rc; + + do + { + // get root snapshot + ComPtr<ISnapshot> pSnapshot; + CHECK_ERROR_BREAK(pMachine, FindSnapshot(Bstr("").raw(), pSnapshot.asOutParam())); + + // get current snapshot + ComPtr<ISnapshot> pCurrentSnapshot; + CHECK_ERROR_BREAK(pMachine, COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam())); + + // get media attachments + SafeIfaceArray<IMediumAttachment> aAttachments; + CHECK_ERROR_BREAK(pMachine, COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(aAttachments))); + for (uint32_t i = 0; + i < aAttachments.size(); + ++i) + { + ComPtr<IMediumAttachment> pAttach(aAttachments[i]); + DeviceType_T type; + CHECK_ERROR_BREAK(pAttach, COMGETTER(Type)(&type)); + if (type == DeviceType_HardDisk) + { + ComPtr<IMedium> pCurrentStateMedium; + CHECK_ERROR_BREAK(pAttach, COMGETTER(Medium)(pCurrentStateMedium.asOutParam())); + + ComPtr<IMedium> pBaseMedium; + CHECK_ERROR_BREAK(pCurrentStateMedium, COMGETTER(Base)(pBaseMedium.asOutParam())); + + Bstr bstrBaseMediumName; + CHECK_ERROR_BREAK(pBaseMedium, COMGETTER(Name)(bstrBaseMediumName.asOutParam())); + + RTPrintf("[%RI32] Images and snapshots for medium \"%ls\"\n", i, bstrBaseMediumName.raw()); + + DumpMediumWithChildren(pCurrentStateMedium, + pBaseMedium, + pSnapshot, + pCurrentSnapshot, + 0); + } + } + } while (0); +} + +typedef enum SnapshotUniqueFlags +{ + SnapshotUniqueFlags_Null = 0, + SnapshotUniqueFlags_Number = RT_BIT(1), + SnapshotUniqueFlags_Timestamp = RT_BIT(2), + SnapshotUniqueFlags_Space = RT_BIT(16), + SnapshotUniqueFlags_Force = RT_BIT(30) +} SnapshotUniqueFlags; + +static int parseSnapshotUniqueFlags(const char *psz, SnapshotUniqueFlags *pUnique) +{ + int rc = VINF_SUCCESS; + unsigned uUnique = 0; + while (psz && *psz && RT_SUCCESS(rc)) + { + size_t len; + const char *pszComma = strchr(psz, ','); + if (pszComma) + len = pszComma - psz; + else + len = strlen(psz); + if (len > 0) + { + if (!RTStrNICmp(psz, "number", len)) + uUnique |= SnapshotUniqueFlags_Number; + else if (!RTStrNICmp(psz, "timestamp", len)) + uUnique |= SnapshotUniqueFlags_Timestamp; + else if (!RTStrNICmp(psz, "space", len)) + uUnique |= SnapshotUniqueFlags_Space; + else if (!RTStrNICmp(psz, "force", len)) + uUnique |= SnapshotUniqueFlags_Force; + else + rc = VERR_PARSE_ERROR; + } + if (pszComma) + psz += len + 1; + else + psz += len; + } + + if (RT_SUCCESS(rc)) + *pUnique = (SnapshotUniqueFlags)uUnique; + return rc; +} + +/** + * Implementation for all VBoxManage snapshot ... subcommands. + * @param a + * @return + */ +RTEXITCODE handleSnapshot(HandlerArg *a) +{ + HRESULT rc; + + /* we need at least a VM and a command */ + if (a->argc < 2) + return errorSyntax(USAGE_SNAPSHOT, "Not enough parameters"); + + /* the first argument must be the VM */ + Bstr bstrMachine(a->argv[0]); + ComPtr<IMachine> pMachine; + CHECK_ERROR(a->virtualBox, FindMachine(bstrMachine.raw(), + pMachine.asOutParam())); + if (!pMachine) + return RTEXITCODE_FAILURE; + + /* we have to open a session for this task (new or shared) */ + CHECK_ERROR_RET(pMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + do + { + /* replace the (read-only) IMachine object by a writable one */ + ComPtr<IMachine> sessionMachine; + CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam())); + + /* switch based on the command */ + bool fDelete = false, + fRestore = false, + fRestoreCurrent = false; + + if (!strcmp(a->argv[1], "take")) + { + /* there must be a name */ + if (a->argc < 3) + { + errorSyntax(USAGE_SNAPSHOT, "Missing snapshot name"); + rc = E_FAIL; + break; + } + Bstr name(a->argv[2]); + + /* parse the optional arguments */ + Bstr desc; + bool fPause = true; /* default is NO live snapshot */ + SnapshotUniqueFlags enmUnique = SnapshotUniqueFlags_Null; + static const RTGETOPTDEF s_aTakeOptions[] = + { + { "--description", 'd', RTGETOPT_REQ_STRING }, + { "-description", 'd', RTGETOPT_REQ_STRING }, + { "-desc", 'd', RTGETOPT_REQ_STRING }, + { "--pause", 'p', RTGETOPT_REQ_NOTHING }, + { "--live", 'l', RTGETOPT_REQ_NOTHING }, + { "--uniquename", 'u', RTGETOPT_REQ_STRING } + }; + RTGETOPTSTATE GetOptState; + RTGetOptInit(&GetOptState, a->argc, a->argv, s_aTakeOptions, RT_ELEMENTS(s_aTakeOptions), + 3, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + int ch; + RTGETOPTUNION Value; + int vrc; + while ( SUCCEEDED(rc) + && (ch = RTGetOpt(&GetOptState, &Value))) + { + switch (ch) + { + case 'p': + fPause = true; + break; + + case 'l': + fPause = false; + break; + + case 'd': + desc = Value.psz; + break; + + case 'u': + vrc = parseSnapshotUniqueFlags(Value.psz, &enmUnique); + if (RT_FAILURE(vrc)) + return errorArgument("Invalid unique name description '%s'", Value.psz); + break; + + default: + errorGetOpt(USAGE_SNAPSHOT, ch, &Value); + rc = E_FAIL; + break; + } + } + if (FAILED(rc)) + break; + + if (enmUnique & (SnapshotUniqueFlags_Number | SnapshotUniqueFlags_Timestamp)) + { + ComPtr<ISnapshot> pSnapshot; + rc = sessionMachine->FindSnapshot(name.raw(), + pSnapshot.asOutParam()); + if (SUCCEEDED(rc) || (enmUnique & SnapshotUniqueFlags_Force)) + { + /* there is a duplicate, need to create a unique name */ + uint32_t count = 0; + RTTIMESPEC now; + + if (enmUnique & SnapshotUniqueFlags_Number) + { + if (enmUnique & SnapshotUniqueFlags_Force) + count = 1; + else + count = 2; + RTTimeSpecSetNano(&now, 0); /* Shut up MSC */ + } + else + RTTimeNow(&now); + + while (count < 500) + { + Utf8Str suffix; + if (enmUnique & SnapshotUniqueFlags_Number) + suffix = Utf8StrFmt("%u", count); + else + { + RTTIMESPEC nowplus = now; + RTTimeSpecAddSeconds(&nowplus, count); + RTTIME stamp; + RTTimeExplode(&stamp, &nowplus); + suffix = Utf8StrFmt("%04u-%02u-%02uT%02u:%02u:%02uZ", stamp.i32Year, stamp.u8Month, stamp.u8MonthDay, stamp.u8Hour, stamp.u8Minute, stamp.u8Second); + } + Bstr tryName = name; + if (enmUnique & SnapshotUniqueFlags_Space) + tryName = BstrFmt("%ls %s", name.raw(), suffix.c_str()); + else + tryName = BstrFmt("%ls%s", name.raw(), suffix.c_str()); + count++; + rc = sessionMachine->FindSnapshot(tryName.raw(), + pSnapshot.asOutParam()); + if (FAILED(rc)) + { + name = tryName; + break; + } + } + if (SUCCEEDED(rc)) + { + errorArgument("Failed to generate a unique snapshot name"); + rc = E_FAIL; + break; + } + } + rc = S_OK; + } + + ComPtr<IProgress> progress; + Bstr snapId; + CHECK_ERROR_BREAK(sessionMachine, TakeSnapshot(name.raw(), desc.raw(), + fPause, snapId.asOutParam(), + progress.asOutParam())); + + rc = showProgress(progress); + if (SUCCEEDED(rc)) + RTPrintf("Snapshot taken. UUID: %ls\n", snapId.raw()); + else + CHECK_PROGRESS_ERROR(progress, ("Failed to take snapshot")); + } + else if ( (fDelete = !strcmp(a->argv[1], "delete")) + || (fRestore = !strcmp(a->argv[1], "restore")) + || (fRestoreCurrent = !strcmp(a->argv[1], "restorecurrent")) + ) + { + if (fRestoreCurrent) + { + if (a->argc > 2) + { + errorSyntax(USAGE_SNAPSHOT, "Too many arguments"); + rc = E_FAIL; + break; + } + } + /* exactly one parameter: snapshot name */ + else if (a->argc != 3) + { + errorSyntax(USAGE_SNAPSHOT, "Expecting snapshot name only"); + rc = E_FAIL; + break; + } + + ComPtr<ISnapshot> pSnapshot; + + if (fRestoreCurrent) + { + CHECK_ERROR_BREAK(sessionMachine, COMGETTER(CurrentSnapshot)(pSnapshot.asOutParam())); + if (pSnapshot.isNull()) + { + RTPrintf("This machine does not have any snapshots\n"); + return RTEXITCODE_FAILURE; + } + } + else + { + // restore or delete snapshot: then resolve cmd line argument to snapshot instance + CHECK_ERROR_BREAK(sessionMachine, FindSnapshot(Bstr(a->argv[2]).raw(), + pSnapshot.asOutParam())); + } + + Bstr bstrSnapGuid; + CHECK_ERROR_BREAK(pSnapshot, COMGETTER(Id)(bstrSnapGuid.asOutParam())); + + Bstr bstrSnapName; + CHECK_ERROR_BREAK(pSnapshot, COMGETTER(Name)(bstrSnapName.asOutParam())); + + ComPtr<IProgress> pProgress; + + RTPrintf("%s snapshot '%ls' (%ls)\n", + fDelete ? "Deleting" : "Restoring", bstrSnapName.raw(), bstrSnapGuid.raw()); + + if (fDelete) + { + CHECK_ERROR_BREAK(sessionMachine, DeleteSnapshot(bstrSnapGuid.raw(), + pProgress.asOutParam())); + } + else + { + // restore or restore current + CHECK_ERROR_BREAK(sessionMachine, RestoreSnapshot(pSnapshot, pProgress.asOutParam())); + } + + rc = showProgress(pProgress); + CHECK_PROGRESS_ERROR(pProgress, ("Snapshot operation failed")); + } + else if (!strcmp(a->argv[1], "edit")) + { + if (a->argc < 3) + { + errorSyntax(USAGE_SNAPSHOT, "Missing snapshot name"); + rc = E_FAIL; + break; + } + + ComPtr<ISnapshot> pSnapshot; + + if ( !strcmp(a->argv[2], "--current") + || !strcmp(a->argv[2], "-current")) + { + CHECK_ERROR_BREAK(sessionMachine, COMGETTER(CurrentSnapshot)(pSnapshot.asOutParam())); + if (pSnapshot.isNull()) + { + RTPrintf("This machine does not have any snapshots\n"); + return RTEXITCODE_FAILURE; + } + } + else + { + CHECK_ERROR_BREAK(sessionMachine, FindSnapshot(Bstr(a->argv[2]).raw(), + pSnapshot.asOutParam())); + } + + /* parse options */ + for (int i = 3; i < a->argc; i++) + { + if ( !strcmp(a->argv[i], "--name") + || !strcmp(a->argv[i], "-name") + || !strcmp(a->argv[i], "-newname")) + { + if (a->argc <= i + 1) + { + errorArgument("Missing argument to '%s'", a->argv[i]); + rc = E_FAIL; + break; + } + i++; + pSnapshot->COMSETTER(Name)(Bstr(a->argv[i]).raw()); + } + else if ( !strcmp(a->argv[i], "--description") + || !strcmp(a->argv[i], "-description") + || !strcmp(a->argv[i], "-newdesc")) + { + if (a->argc <= i + 1) + { + errorArgument("Missing argument to '%s'", a->argv[i]); + rc = E_FAIL; + break; + } + i++; + pSnapshot->COMSETTER(Description)(Bstr(a->argv[i]).raw()); + } + else + { + errorSyntax(USAGE_SNAPSHOT, "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str()); + rc = E_FAIL; + break; + } + } + + } + else if (!strcmp(a->argv[1], "showvminfo")) + { + /* exactly one parameter: snapshot name */ + if (a->argc != 3) + { + errorSyntax(USAGE_SNAPSHOT, "Expecting snapshot name only"); + rc = E_FAIL; + break; + } + + ComPtr<ISnapshot> pSnapshot; + + CHECK_ERROR_BREAK(sessionMachine, FindSnapshot(Bstr(a->argv[2]).raw(), + pSnapshot.asOutParam())); + + /* get the machine of the given snapshot */ + ComPtr<IMachine> pMachine2; + pSnapshot->COMGETTER(Machine)(pMachine2.asOutParam()); + showVMInfo(a->virtualBox, pMachine2, NULL, VMINFO_NONE); + } + else if (!strcmp(a->argv[1], "list")) + rc = handleSnapshotList(a, sessionMachine) == RTEXITCODE_SUCCESS ? S_OK : E_FAIL; + else if (!strcmp(a->argv[1], "dump")) // undocumented parameter to debug snapshot info + DumpSnapshot(sessionMachine); + else + { + errorSyntax(USAGE_SNAPSHOT, "Invalid parameter '%s'", Utf8Str(a->argv[1]).c_str()); + rc = E_FAIL; + } + } while (0); + + a->session->UnlockMachine(); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageStorageController.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageStorageController.cpp new file mode 100644 index 00000000..0e1b7a28 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageStorageController.cpp @@ -0,0 +1,1290 @@ +/* $Id: VBoxManageStorageController.cpp $ */ +/** @file + * VBoxManage - The storage controller related commands. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_ONLY_DOCS + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> + +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/ctype.h> +#include <iprt/stream.h> +#include <iprt/getopt.h> +#include <VBox/log.h> + +#include "VBoxManage.h" +using namespace com; + + +// funcs +/////////////////////////////////////////////////////////////////////////////// + + +static const RTGETOPTDEF g_aStorageAttachOptions[] = +{ + { "--storagectl", 's', RTGETOPT_REQ_STRING }, + { "--port", 'p', RTGETOPT_REQ_UINT32 }, + { "--device", 'd', RTGETOPT_REQ_UINT32 }, + { "--type", 't', RTGETOPT_REQ_STRING }, + { "--medium", 'm', RTGETOPT_REQ_STRING }, + { "--mtype", 'M', RTGETOPT_REQ_STRING }, + { "--passthrough", 'h', RTGETOPT_REQ_STRING }, + { "--tempeject", 'e', RTGETOPT_REQ_STRING }, + { "--nonrotational", 'n', RTGETOPT_REQ_STRING }, + { "--discard", 'u', RTGETOPT_REQ_STRING }, + { "--hotpluggable", 'o', RTGETOPT_REQ_STRING }, + { "--bandwidthgroup", 'b', RTGETOPT_REQ_STRING }, + { "--forceunmount", 'f', RTGETOPT_REQ_NOTHING }, + { "--comment", 'C', RTGETOPT_REQ_STRING }, + { "--setuuid", 'q', RTGETOPT_REQ_STRING }, + { "--setparentuuid", 'Q', RTGETOPT_REQ_STRING }, + // iSCSI options + { "--server", 'S', RTGETOPT_REQ_STRING }, + { "--target", 'T', RTGETOPT_REQ_STRING }, + { "--tport", 'P', RTGETOPT_REQ_STRING }, + { "--lun", 'L', RTGETOPT_REQ_STRING }, + { "--encodedlun", 'E', RTGETOPT_REQ_STRING }, + { "--username", 'U', RTGETOPT_REQ_STRING }, + { "--password", 'W', RTGETOPT_REQ_STRING }, + { "--passwordfile", 'w', RTGETOPT_REQ_STRING }, + { "--initiator", 'N', RTGETOPT_REQ_STRING }, + { "--intnet", 'I', RTGETOPT_REQ_NOTHING }, +}; + +RTEXITCODE handleStorageAttach(HandlerArg *a) +{ + int c = VERR_INTERNAL_ERROR; /* initialized to shut up gcc */ + HRESULT rc = S_OK; + ULONG port = ~0U; + ULONG device = ~0U; + bool fForceUnmount = false; + bool fSetMediumType = false; + bool fSetNewUuid = false; + bool fSetNewParentUuid = false; + MediumType_T enmMediumType = MediumType_Normal; + Bstr bstrComment; + const char *pszCtl = NULL; + DeviceType_T devTypeRequested = DeviceType_Null; + const char *pszMedium = NULL; + const char *pszPassThrough = NULL; + const char *pszTempEject = NULL; + const char *pszNonRotational = NULL; + const char *pszDiscard = NULL; + const char *pszHotPluggable = NULL; + const char *pszBandwidthGroup = NULL; + Bstr bstrNewUuid; + Bstr bstrNewParentUuid; + // iSCSI options + Bstr bstrServer; + Bstr bstrTarget; + Bstr bstrPort; + Bstr bstrLun; + Bstr bstrUsername; + Bstr bstrPassword; + Bstr bstrInitiator; + Bstr bstrIso; + Utf8Str strIso; + bool fIntNet = false; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + ComPtr<IMachine> machine; + ComPtr<IStorageController> storageCtl; + ComPtr<ISystemProperties> systemProperties; + + RTGetOptInit(&GetState, a->argc, a->argv, g_aStorageAttachOptions, + RT_ELEMENTS(g_aStorageAttachOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + + while ( SUCCEEDED(rc) + && (c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 's': // storage controller name + { + if (ValueUnion.psz) + pszCtl = ValueUnion.psz; + else + rc = E_FAIL; + break; + } + + case 'p': // port + { + port = ValueUnion.u32; + break; + } + + case 'd': // device + { + device = ValueUnion.u32; + break; + } + + case 'm': // medium <none|emptydrive|additions|uuid|filename|host:<drive>|iSCSI> + { + if (ValueUnion.psz) + pszMedium = ValueUnion.psz; + else + rc = E_FAIL; + break; + } + + case 't': // type <dvddrive|hdd|fdd> + { + if (ValueUnion.psz) + { + if (!RTStrICmp(ValueUnion.psz, "hdd")) + devTypeRequested = DeviceType_HardDisk; + else if (!RTStrICmp(ValueUnion.psz, "fdd")) + devTypeRequested = DeviceType_Floppy; + else if (!RTStrICmp(ValueUnion.psz, "dvddrive")) + devTypeRequested = DeviceType_DVD; + else + return errorArgument("Invalid --type argument '%s'", ValueUnion.psz); + } + else + rc = E_FAIL; + break; + } + + case 'h': // passthrough <on|off> + { + if (ValueUnion.psz) + pszPassThrough = ValueUnion.psz; + else + rc = E_FAIL; + break; + } + + case 'e': // tempeject <on|off> + { + if (ValueUnion.psz) + pszTempEject = ValueUnion.psz; + else + rc = E_FAIL; + break; + } + + case 'n': // nonrotational <on|off> + { + if (ValueUnion.psz) + pszNonRotational = ValueUnion.psz; + else + rc = E_FAIL; + break; + } + + case 'u': // discard <on|off> + { + if (ValueUnion.psz) + pszDiscard = ValueUnion.psz; + else + rc = E_FAIL; + break; + } + + case 'o': // hotpluggable <on|off> + { + if (ValueUnion.psz) + pszHotPluggable = ValueUnion.psz; + else + rc = E_FAIL; + break; + } + + case 'b': // bandwidthgroup <name> + { + if (ValueUnion.psz) + pszBandwidthGroup = ValueUnion.psz; + else + rc = E_FAIL; + break; + } + + case 'f': // force unmount medium during runtime + { + fForceUnmount = true; + break; + } + + case 'C': + if (ValueUnion.psz) + bstrComment = ValueUnion.psz; + else + rc = E_FAIL; + break; + + case 'q': + if (ValueUnion.psz) + { + bstrNewUuid = ValueUnion.psz; + fSetNewUuid = true; + } + else + rc = E_FAIL; + break; + + case 'Q': + if (ValueUnion.psz) + { + bstrNewParentUuid = ValueUnion.psz; + fSetNewParentUuid = true; + } + else + rc = E_FAIL; + break; + + case 'S': // --server + bstrServer = ValueUnion.psz; + break; + + case 'T': // --target + bstrTarget = ValueUnion.psz; + break; + + case 'P': // --tport + bstrPort = ValueUnion.psz; + break; + + case 'L': // --lun + bstrLun = ValueUnion.psz; + break; + + case 'E': // --encodedlun + bstrLun = BstrFmt("enc%s", ValueUnion.psz); + break; + + case 'U': // --username + bstrUsername = ValueUnion.psz; + break; + + case 'W': // --password + bstrPassword = ValueUnion.psz; + break; + + case 'w': // --passwordFile + { + Utf8Str utf8Password; + RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &utf8Password); + if (rcExit != RTEXITCODE_SUCCESS) + rc = E_FAIL; + bstrPassword = utf8Password; + break; + } + case 'N': // --initiator + bstrInitiator = ValueUnion.psz; + break; + + case 'M': // --type + { + int vrc = parseMediumType(ValueUnion.psz, &enmMediumType); + if (RT_FAILURE(vrc)) + return errorArgument("Invalid medium type '%s'", ValueUnion.psz); + fSetMediumType = true; + break; + } + + case 'I': // --intnet + fIntNet = true; + break; + + default: + { + errorGetOpt(USAGE_STORAGEATTACH, c, &ValueUnion); + rc = E_FAIL; + break; + } + } + } + + if (FAILED(rc)) + return RTEXITCODE_FAILURE; + + if (!pszCtl) + return errorSyntax(USAGE_STORAGEATTACH, "Storage controller name not specified"); + + /* get the virtualbox system properties */ + CHECK_ERROR_RET(a->virtualBox, COMGETTER(SystemProperties)(systemProperties.asOutParam()), RTEXITCODE_FAILURE); + + // find the machine, lock it, get the mutable session machine + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + SessionType_T st; + CHECK_ERROR_RET(a->session, COMGETTER(Type)(&st), RTEXITCODE_FAILURE); + a->session->COMGETTER(Machine)(machine.asOutParam()); + + try + { + bool fRunTime = (st == SessionType_Shared); + + if (fRunTime) + { + if (pszPassThrough) + throw Utf8Str("Drive passthrough state cannot be changed while the VM is running\n"); + else if (pszBandwidthGroup) + throw Utf8Str("Bandwidth group cannot be changed while the VM is running\n"); + } + + /* check if the storage controller is present */ + rc = machine->GetStorageControllerByName(Bstr(pszCtl).raw(), + storageCtl.asOutParam()); + if (FAILED(rc)) + throw Utf8StrFmt("Could not find a controller named '%s'\n", pszCtl); + + StorageBus_T storageBus = StorageBus_Null; + CHECK_ERROR_RET(storageCtl, COMGETTER(Bus)(&storageBus), RTEXITCODE_FAILURE); + ULONG maxPorts = 0; + CHECK_ERROR_RET(systemProperties, GetMaxPortCountForStorageBus(storageBus, &maxPorts), RTEXITCODE_FAILURE); + ULONG maxDevices = 0; + CHECK_ERROR_RET(systemProperties, GetMaxDevicesPerPortForStorageBus(storageBus, &maxDevices), RTEXITCODE_FAILURE); + + if (port == ~0U) + { + if (maxPorts == 1) + port = 0; + else + return errorSyntax(USAGE_STORAGEATTACH, "Port not specified"); + } + if (device == ~0U) + { + if (maxDevices == 1) + device = 0; + else + return errorSyntax(USAGE_STORAGEATTACH, "Device not specified"); + } + + /* for sata controller check if the port count is big enough + * to accommodate the current port which is being assigned + * else just increase the port count + */ + { + ULONG ulPortCount = 0; + ULONG ulMaxPortCount = 0; + + CHECK_ERROR(storageCtl, COMGETTER(MaxPortCount)(&ulMaxPortCount)); + CHECK_ERROR(storageCtl, COMGETTER(PortCount)(&ulPortCount)); + + if ( (ulPortCount != ulMaxPortCount) + && (port >= ulPortCount) + && (port < ulMaxPortCount)) + CHECK_ERROR(storageCtl, COMSETTER(PortCount)(port + 1)); + } + + StorageControllerType_T ctlType = StorageControllerType_Null; + CHECK_ERROR(storageCtl, COMGETTER(ControllerType)(&ctlType)); + + if (!RTStrICmp(pszMedium, "none")) + { + CHECK_ERROR(machine, DetachDevice(Bstr(pszCtl).raw(), port, device)); + } + else if (!RTStrICmp(pszMedium, "emptydrive")) + { + if (fRunTime) + { + ComPtr<IMediumAttachment> mediumAttachment; + DeviceType_T deviceType = DeviceType_Null; + rc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), port, device, + mediumAttachment.asOutParam()); + if (SUCCEEDED(rc)) + { + mediumAttachment->COMGETTER(Type)(&deviceType); + + if ( (deviceType == DeviceType_DVD) + || (deviceType == DeviceType_Floppy)) + { + /* just unmount the floppy/dvd */ + CHECK_ERROR(machine, UnmountMedium(Bstr(pszCtl).raw(), + port, + device, + fForceUnmount)); + } + } + else if (devTypeRequested == DeviceType_DVD) + { + /* + * Try to attach an empty DVD drive as a hotplug operation. + * Main will complain if the controller doesn't support hotplugging. + */ + CHECK_ERROR(machine, AttachDeviceWithoutMedium(Bstr(pszCtl).raw(), port, device, + devTypeRequested)); + deviceType = DeviceType_DVD; /* To avoid the error message below. */ + } + + if ( FAILED(rc) + || !( deviceType == DeviceType_DVD + || deviceType == DeviceType_Floppy) + ) + throw Utf8StrFmt("No DVD/Floppy Drive attached to the controller '%s'" + "at the port: %u, device: %u", pszCtl, port, device); + + } + else + { + DeviceType_T deviceType = DeviceType_Null; + com::SafeArray <DeviceType_T> saDeviceTypes; + ULONG driveCheck = 0; + + /* check if the device type is supported by the controller */ + CHECK_ERROR(systemProperties, GetDeviceTypesForStorageBus(storageBus, ComSafeArrayAsOutParam(saDeviceTypes))); + for (size_t i = 0; i < saDeviceTypes.size(); ++ i) + { + if ( (saDeviceTypes[i] == DeviceType_DVD) + || (saDeviceTypes[i] == DeviceType_Floppy)) + driveCheck++; + } + + if (!driveCheck) + throw Utf8StrFmt("The attachment is not supported by the storage controller '%s'", pszCtl); + + if (storageBus == StorageBus_Floppy) + deviceType = DeviceType_Floppy; + else + deviceType = DeviceType_DVD; + + /* attach a empty floppy/dvd drive after removing previous attachment */ + machine->DetachDevice(Bstr(pszCtl).raw(), port, device); + CHECK_ERROR(machine, AttachDeviceWithoutMedium(Bstr(pszCtl).raw(), port, device, + deviceType)); + } + } // end if (!RTStrICmp(pszMedium, "emptydrive")) + else + { + ComPtr<IMedium> pMedium2Mount; + + // not "none", not "emptydrive": then it must be a UUID or filename or hostdrive or iSCSI; + // for all these we first need to know the type of drive we're attaching to + { + /* + * try to determine the type of the drive from the + * storage controller chipset, the attachment and + * the medium being attached + */ + if (ctlType == StorageControllerType_I82078) // floppy controller + devTypeRequested = DeviceType_Floppy; + else + { + /* + * for SATA/SCSI/IDE it is hard to tell if it is a harddisk or + * a dvd being attached so lets check if the medium attachment + * and the medium, both are of same type. if yes then we are + * sure of its type and don't need the user to enter it manually + * else ask the user for the type. + */ + ComPtr<IMediumAttachment> mediumAttachment; + rc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, + mediumAttachment.asOutParam()); + if (SUCCEEDED(rc)) + { + DeviceType_T deviceType; + mediumAttachment->COMGETTER(Type)(&deviceType); + + if (pszMedium) + { + if (!RTStrICmp(pszMedium, "additions")) + { + ComPtr<ISystemProperties> pProperties; + CHECK_ERROR(a->virtualBox, + COMGETTER(SystemProperties)(pProperties.asOutParam())); + CHECK_ERROR(pProperties, COMGETTER(DefaultAdditionsISO)(bstrIso.asOutParam())); + strIso = Utf8Str(bstrIso); + if (strIso.isEmpty()) + throw Utf8Str("Cannot find the Guest Additions ISO image\n"); + pszMedium = strIso.c_str(); + if (devTypeRequested == DeviceType_Null) + devTypeRequested = DeviceType_DVD; + } + ComPtr<IMedium> pExistingMedium; + rc = openMedium(a, pszMedium, deviceType, + AccessMode_ReadWrite, + pExistingMedium, + false /* fForceNewUuidOnOpen */, + true /* fSilent */); + if (SUCCEEDED(rc) && pExistingMedium) + { + if ( (deviceType == DeviceType_DVD) + || (deviceType == DeviceType_HardDisk) + ) + devTypeRequested = deviceType; + } + } + else + devTypeRequested = deviceType; + } + } + } + + if (devTypeRequested == DeviceType_Null) // still the initializer value? + throw Utf8Str("Argument --type must be specified\n"); + + /* check if the device type is supported by the controller */ + { + com::SafeArray <DeviceType_T> saDeviceTypes; + + CHECK_ERROR(systemProperties, GetDeviceTypesForStorageBus(storageBus, ComSafeArrayAsOutParam(saDeviceTypes))); + if (SUCCEEDED(rc)) + { + ULONG driveCheck = 0; + for (size_t i = 0; i < saDeviceTypes.size(); ++ i) + if (saDeviceTypes[i] == devTypeRequested) + driveCheck++; + if (!driveCheck) + throw Utf8StrFmt("The given attachment is not supported by the storage controller '%s'", pszCtl); + } + else + goto leave; + } + + // find the medium given + /* host drive? */ + if (!RTStrNICmp(pszMedium, RT_STR_TUPLE("host:"))) + { + ComPtr<IHost> host; + CHECK_ERROR(a->virtualBox, COMGETTER(Host)(host.asOutParam())); + + if (devTypeRequested == DeviceType_DVD) + { + rc = host->FindHostDVDDrive(Bstr(pszMedium + 5).raw(), + pMedium2Mount.asOutParam()); + if (!pMedium2Mount) + { + /* 2nd try: try with the real name, important on Linux+libhal */ + char szPathReal[RTPATH_MAX]; + if (RT_FAILURE(RTPathReal(pszMedium + 5, szPathReal, sizeof(szPathReal)))) + throw Utf8StrFmt("Invalid host DVD drive name \"%s\"", pszMedium + 5); + rc = host->FindHostDVDDrive(Bstr(szPathReal).raw(), + pMedium2Mount.asOutParam()); + if (!pMedium2Mount) + throw Utf8StrFmt("Invalid host DVD drive name \"%s\"", pszMedium + 5); + } + } + else + { + // floppy + rc = host->FindHostFloppyDrive(Bstr(pszMedium + 5).raw(), + pMedium2Mount.asOutParam()); + if (!pMedium2Mount) + throw Utf8StrFmt("Invalid host floppy drive name \"%s\"", pszMedium + 5); + } + } + else if (!RTStrICmp(pszMedium, "iSCSI")) + { + /* check for required options */ + if (bstrServer.isEmpty() || bstrTarget.isEmpty()) + throw Utf8StrFmt("Parameters --server and --target are required for iSCSI media"); + + /** @todo move the location stuff to Main, which can use pfnComposeName + * from the disk backends to construct the location properly. Also do + * not use slashes to separate the parts, as otherwise only the last + * element containing information will be shown. */ + Bstr bstrISCSIMedium; + if ( bstrLun.isEmpty() + || (bstrLun == "0") + || (bstrLun == "enc0") + ) + bstrISCSIMedium = BstrFmt("%ls|%ls", bstrServer.raw(), bstrTarget.raw()); + else + bstrISCSIMedium = BstrFmt("%ls|%ls|%ls", bstrServer.raw(), bstrTarget.raw(), bstrLun.raw()); + + CHECK_ERROR(a->virtualBox, CreateMedium(Bstr("iSCSI").raw(), + bstrISCSIMedium.raw(), + AccessMode_ReadWrite, + DeviceType_HardDisk, + pMedium2Mount.asOutParam())); + if (FAILED(rc)) goto leave; + if (!bstrPort.isEmpty()) + bstrServer = BstrFmt("%ls:%ls", bstrServer.raw(), bstrPort.raw()); + + // set the other iSCSI parameters as properties + com::SafeArray <BSTR> names; + com::SafeArray <BSTR> values; + Bstr("TargetAddress").detachTo(names.appendedRaw()); + bstrServer.detachTo(values.appendedRaw()); + Bstr("TargetName").detachTo(names.appendedRaw()); + bstrTarget.detachTo(values.appendedRaw()); + + if (!bstrLun.isEmpty()) + { + Bstr("LUN").detachTo(names.appendedRaw()); + bstrLun.detachTo(values.appendedRaw()); + } + if (!bstrUsername.isEmpty()) + { + Bstr("InitiatorUsername").detachTo(names.appendedRaw()); + bstrUsername.detachTo(values.appendedRaw()); + } + if (!bstrPassword.isEmpty()) + { + Bstr("InitiatorSecret").detachTo(names.appendedRaw()); + bstrPassword.detachTo(values.appendedRaw()); + } + if (!bstrInitiator.isEmpty()) + { + Bstr("InitiatorName").detachTo(names.appendedRaw()); + bstrInitiator.detachTo(values.appendedRaw()); + } + + /// @todo add --targetName and --targetPassword options + + if (fIntNet) + { + Bstr("HostIPStack").detachTo(names.appendedRaw()); + Bstr("0").detachTo(values.appendedRaw()); + } + + CHECK_ERROR(pMedium2Mount, SetProperties(ComSafeArrayAsInParam(names), + ComSafeArrayAsInParam(values))); + if (FAILED(rc)) goto leave; + Bstr guid; + CHECK_ERROR(pMedium2Mount, COMGETTER(Id)(guid.asOutParam())); + if (FAILED(rc)) goto leave; + RTPrintf("iSCSI disk created. UUID: %s\n", Utf8Str(guid).c_str()); + } + else + { + if (!pszMedium) + { + ComPtr<IMediumAttachment> mediumAttachment; + rc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, + mediumAttachment.asOutParam()); + if (FAILED(rc)) + throw Utf8Str("Missing --medium argument"); + } + else + { + Bstr bstrMedium(pszMedium); + rc = openMedium(a, pszMedium, devTypeRequested, + AccessMode_ReadWrite, pMedium2Mount, + fSetNewUuid, false /* fSilent */); + if (FAILED(rc) || !pMedium2Mount) + throw Utf8StrFmt("Invalid UUID or filename \"%s\"", pszMedium); + } + } + + // set medium/parent medium UUID, if so desired + if (pMedium2Mount && (fSetNewUuid || fSetNewParentUuid)) + { + CHECK_ERROR(pMedium2Mount, SetIds(fSetNewUuid, bstrNewUuid.raw(), + fSetNewParentUuid, bstrNewParentUuid.raw())); + if (FAILED(rc)) + throw Utf8Str("Failed to set the medium/parent medium UUID"); + } + + // set medium type, if so desired + if (pMedium2Mount && fSetMediumType) + { + MediumType_T enmMediumTypeOld; + CHECK_ERROR(pMedium2Mount, COMGETTER(Type)(&enmMediumTypeOld)); + if (SUCCEEDED(rc)) + { + if (enmMediumTypeOld != enmMediumType) + { + CHECK_ERROR(pMedium2Mount, COMSETTER(Type)(enmMediumType)); + if (FAILED(rc)) + throw Utf8Str("Failed to set the medium type"); + } + } + } + + if (pMedium2Mount && !bstrComment.isEmpty()) + { + CHECK_ERROR(pMedium2Mount, COMSETTER(Description)(bstrComment.raw())); + } + + if (pszMedium) + { + switch (devTypeRequested) + { + case DeviceType_DVD: + case DeviceType_Floppy: + { + if (!fRunTime) + { + ComPtr<IMediumAttachment> mediumAttachment; + // check if there is a dvd/floppy drive at the given location, if not attach one first + rc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), + port, + device, + mediumAttachment.asOutParam()); + if (SUCCEEDED(rc)) + { + DeviceType_T deviceType; + mediumAttachment->COMGETTER(Type)(&deviceType); + if (deviceType != devTypeRequested) + { + machine->DetachDevice(Bstr(pszCtl).raw(), port, device); + rc = machine->AttachDeviceWithoutMedium(Bstr(pszCtl).raw(), + port, + device, + devTypeRequested); // DeviceType_DVD or DeviceType_Floppy + } + } + else + { + rc = machine->AttachDeviceWithoutMedium(Bstr(pszCtl).raw(), + port, + device, + devTypeRequested); // DeviceType_DVD or DeviceType_Floppy + } + } + + if (pMedium2Mount) + { + CHECK_ERROR(machine, MountMedium(Bstr(pszCtl).raw(), + port, + device, + pMedium2Mount, + fForceUnmount)); + } + break; + } // end DeviceType_DVD or DeviceType_Floppy: + + case DeviceType_HardDisk: + { + // if there is anything attached at the given location, remove it + machine->DetachDevice(Bstr(pszCtl).raw(), port, device); + CHECK_ERROR(machine, AttachDevice(Bstr(pszCtl).raw(), + port, + device, + DeviceType_HardDisk, + pMedium2Mount)); + break; + } + + default: break; /* Shut up MSC */ + } + } + } + + if ( pszPassThrough + && (SUCCEEDED(rc))) + { + ComPtr<IMediumAttachment> mattach; + CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, mattach.asOutParam())); + + if (SUCCEEDED(rc)) + { + if (!RTStrICmp(pszPassThrough, "on")) + { + CHECK_ERROR(machine, PassthroughDevice(Bstr(pszCtl).raw(), + port, device, TRUE)); + } + else if (!RTStrICmp(pszPassThrough, "off")) + { + CHECK_ERROR(machine, PassthroughDevice(Bstr(pszCtl).raw(), + port, device, FALSE)); + } + else + throw Utf8StrFmt("Invalid --passthrough argument '%s'", pszPassThrough); + } + else + throw Utf8StrFmt("Couldn't find the controller attachment for the controller '%s'\n", pszCtl); + } + + if ( pszTempEject + && (SUCCEEDED(rc))) + { + ComPtr<IMediumAttachment> mattach; + CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, mattach.asOutParam())); + + if (SUCCEEDED(rc)) + { + if (!RTStrICmp(pszTempEject, "on")) + { + CHECK_ERROR(machine, TemporaryEjectDevice(Bstr(pszCtl).raw(), + port, device, TRUE)); + } + else if (!RTStrICmp(pszTempEject, "off")) + { + CHECK_ERROR(machine, TemporaryEjectDevice(Bstr(pszCtl).raw(), + port, device, FALSE)); + } + else + throw Utf8StrFmt("Invalid --tempeject argument '%s'", pszTempEject); + } + else + throw Utf8StrFmt("Couldn't find the controller attachment for the controller '%s'\n", pszCtl); + } + + if ( pszNonRotational + && (SUCCEEDED(rc))) + { + ComPtr<IMediumAttachment> mattach; + CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, mattach.asOutParam())); + + if (SUCCEEDED(rc)) + { + if (!RTStrICmp(pszNonRotational, "on")) + { + CHECK_ERROR(machine, NonRotationalDevice(Bstr(pszCtl).raw(), + port, device, TRUE)); + } + else if (!RTStrICmp(pszNonRotational, "off")) + { + CHECK_ERROR(machine, NonRotationalDevice(Bstr(pszCtl).raw(), + port, device, FALSE)); + } + else + throw Utf8StrFmt("Invalid --nonrotational argument '%s'", pszNonRotational); + } + else + throw Utf8StrFmt("Couldn't find the controller attachment for the controller '%s'\n", pszCtl); + } + + if ( pszDiscard + && (SUCCEEDED(rc))) + { + ComPtr<IMediumAttachment> mattach; + CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, mattach.asOutParam())); + + if (SUCCEEDED(rc)) + { + if (!RTStrICmp(pszDiscard, "on")) + { + CHECK_ERROR(machine, SetAutoDiscardForDevice(Bstr(pszCtl).raw(), + port, device, TRUE)); + } + else if (!RTStrICmp(pszDiscard, "off")) + { + CHECK_ERROR(machine, SetAutoDiscardForDevice(Bstr(pszCtl).raw(), + port, device, FALSE)); + } + else + throw Utf8StrFmt("Invalid --discard argument '%s'", pszDiscard); + } + else + throw Utf8StrFmt("Couldn't find the controller attachment for the controller '%s'\n", pszCtl); + } + + if ( pszHotPluggable + && (SUCCEEDED(rc))) + { + ComPtr<IMediumAttachment> mattach; + CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, mattach.asOutParam())); + + if (SUCCEEDED(rc)) + { + if (!RTStrICmp(pszHotPluggable, "on")) + { + CHECK_ERROR(machine, SetHotPluggableForDevice(Bstr(pszCtl).raw(), + port, device, TRUE)); + } + else if (!RTStrICmp(pszHotPluggable, "off")) + { + CHECK_ERROR(machine, SetHotPluggableForDevice(Bstr(pszCtl).raw(), + port, device, FALSE)); + } + else + throw Utf8StrFmt("Invalid --hotpluggable argument '%s'", pszHotPluggable); + } + else + throw Utf8StrFmt("Couldn't find the controller attachment for the controller '%s'\n", pszCtl); + } + + if ( pszBandwidthGroup + && !fRunTime + && SUCCEEDED(rc)) + { + + if (!RTStrICmp(pszBandwidthGroup, "none")) + { + /* Just remove the bandwidth gorup. */ + CHECK_ERROR(machine, SetNoBandwidthGroupForDevice(Bstr(pszCtl).raw(), + port, device)); + } + else + { + ComPtr<IBandwidthControl> bwCtrl; + ComPtr<IBandwidthGroup> bwGroup; + + CHECK_ERROR(machine, COMGETTER(BandwidthControl)(bwCtrl.asOutParam())); + + if (SUCCEEDED(rc)) + { + CHECK_ERROR(bwCtrl, GetBandwidthGroup(Bstr(pszBandwidthGroup).raw(), bwGroup.asOutParam())); + if (SUCCEEDED(rc)) + { + CHECK_ERROR(machine, SetBandwidthGroupForDevice(Bstr(pszCtl).raw(), + port, device, bwGroup)); + } + } + } + } + + /* commit changes */ + if (SUCCEEDED(rc)) + CHECK_ERROR(machine, SaveSettings()); + } + catch (const Utf8Str &strError) + { + errorArgument("%s", strError.c_str()); + rc = E_FAIL; + } + + // machine must always be unlocked, even on errors +leave: + a->session->UnlockMachine(); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +static const RTGETOPTDEF g_aStorageControllerOptions[] = +{ + { "--name", 'n', RTGETOPT_REQ_STRING }, + { "--add", 'a', RTGETOPT_REQ_STRING }, + { "--controller", 'c', RTGETOPT_REQ_STRING }, + { "--portcount", 'p', RTGETOPT_REQ_UINT32 }, + { "--remove", 'r', RTGETOPT_REQ_NOTHING }, + { "--rename", 'R', RTGETOPT_REQ_STRING }, + { "--hostiocache", 'i', RTGETOPT_REQ_STRING }, + { "--bootable", 'b', RTGETOPT_REQ_STRING }, +}; + +RTEXITCODE handleStorageController(HandlerArg *a) +{ + int c; + const char *pszCtl = NULL; + const char *pszBusType = NULL; + const char *pszCtlType = NULL; + const char *pszHostIOCache = NULL; + const char *pszBootable = NULL; + const char *pszCtlNewName = NULL; + ULONG portcount = ~0U; + bool fRemoveCtl = false; + ComPtr<IMachine> machine; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + + if (a->argc < 4) + return errorSyntax(USAGE_STORAGECONTROLLER, "Too few parameters"); + + RTGetOptInit (&GetState, a->argc, a->argv, g_aStorageControllerOptions, + RT_ELEMENTS(g_aStorageControllerOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + + while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (c) + { + case 'n': // controller name + Assert(ValueUnion.psz); + pszCtl = ValueUnion.psz; + break; + + case 'a': // controller bus type <ide/sata/scsi/floppy> + Assert(ValueUnion.psz); + pszBusType = ValueUnion.psz; + break; + + case 'c': // controller <lsilogic/buslogic/intelahci/piix3/piix4/ich6/i82078> + Assert(ValueUnion.psz); + pszCtlType = ValueUnion.psz; + break; + + case 'p': // portcount + portcount = ValueUnion.u32; + break; + + case 'r': // remove controller + fRemoveCtl = true; + break; + + case 'R': // rename controller + Assert(ValueUnion.psz); + pszCtlNewName = ValueUnion.psz; + break; + + case 'i': + pszHostIOCache = ValueUnion.psz; + break; + + case 'b': + pszBootable = ValueUnion.psz; + break; + + default: + return errorGetOpt(USAGE_STORAGECONTROLLER, c, &ValueUnion); + } + } + + HRESULT rc; + + /* try to find the given machine */ + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam()), RTEXITCODE_FAILURE); + + /* open a session for the VM */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE); + + /* get the mutable session machine */ + a->session->COMGETTER(Machine)(machine.asOutParam()); + + if (!pszCtl) + { + /* it's important to always close sessions */ + a->session->UnlockMachine(); + return errorSyntax(USAGE_STORAGECONTROLLER, "Storage controller name not specified\n"); + } + + if (fRemoveCtl) + { + CHECK_ERROR(machine, RemoveStorageController(Bstr(pszCtl).raw())); + } + else + { + if (pszBusType) + { + ComPtr<IStorageController> ctl; + + if (!RTStrICmp(pszBusType, "ide")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_IDE, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "sata")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_SATA, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "scsi")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_SCSI, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "floppy")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_Floppy, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "sas")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_SAS, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "usb")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_USB, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "pcie")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_PCIe, + ctl.asOutParam())); + } + else + { + errorArgument("Invalid --add argument '%s'", pszBusType); + rc = E_FAIL; + } + } + + if ( pszCtlType + && SUCCEEDED(rc)) + { + ComPtr<IStorageController> ctl; + + CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(), + ctl.asOutParam())); + + if (SUCCEEDED(rc)) + { + if (!RTStrICmp(pszCtlType, "lsilogic")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_LsiLogic)); + } + else if (!RTStrICmp(pszCtlType, "buslogic")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_BusLogic)); + } + else if (!RTStrICmp(pszCtlType, "intelahci")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_IntelAhci)); + } + else if (!RTStrICmp(pszCtlType, "piix3")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_PIIX3)); + } + else if (!RTStrICmp(pszCtlType, "piix4")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_PIIX4)); + } + else if (!RTStrICmp(pszCtlType, "ich6")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_ICH6)); + } + else if (!RTStrICmp(pszCtlType, "i82078")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_I82078)); + } + else if (!RTStrICmp(pszCtlType, "lsilogicsas")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas)); + } + else if (!RTStrICmp(pszCtlType, "usb")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_USB)); + } + else if (!RTStrICmp(pszCtlType, "nvme")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_NVMe)); + } + else + { + errorArgument("Invalid --type argument '%s'", pszCtlType); + rc = E_FAIL; + } + } + else + { + errorArgument("Couldn't find the controller with the name: '%s'\n", pszCtl); + rc = E_FAIL; + } + } + + if ( (portcount != ~0U) + && SUCCEEDED(rc)) + { + ComPtr<IStorageController> ctl; + + CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(), + ctl.asOutParam())); + + if (SUCCEEDED(rc)) + { + CHECK_ERROR(ctl, COMSETTER(PortCount)(portcount)); + } + else + { + errorArgument("Couldn't find the controller with the name: '%s'\n", pszCtl); + rc = E_FAIL; + } + } + + if ( pszHostIOCache + && SUCCEEDED(rc)) + { + ComPtr<IStorageController> ctl; + + CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(), + ctl.asOutParam())); + + if (SUCCEEDED(rc)) + { + if (!RTStrICmp(pszHostIOCache, "on")) + { + CHECK_ERROR(ctl, COMSETTER(UseHostIOCache)(TRUE)); + } + else if (!RTStrICmp(pszHostIOCache, "off")) + { + CHECK_ERROR(ctl, COMSETTER(UseHostIOCache)(FALSE)); + } + else + { + errorArgument("Invalid --hostiocache argument '%s'", pszHostIOCache); + rc = E_FAIL; + } + } + else + { + errorArgument("Couldn't find the controller with the name: '%s'\n", pszCtl); + rc = E_FAIL; + } + } + + if ( pszBootable + && SUCCEEDED(rc)) + { + if (SUCCEEDED(rc)) + { + if (!RTStrICmp(pszBootable, "on")) + { + CHECK_ERROR(machine, SetStorageControllerBootable(Bstr(pszCtl).raw(), TRUE)); + } + else if (!RTStrICmp(pszBootable, "off")) + { + CHECK_ERROR(machine, SetStorageControllerBootable(Bstr(pszCtl).raw(), FALSE)); + } + else + { + errorArgument("Invalid --bootable argument '%s'", pszBootable); + rc = E_FAIL; + } + } + else + { + errorArgument("Couldn't find the controller with the name: '%s'\n", pszCtl); + rc = E_FAIL; + } + } + + if ( pszCtlNewName + && SUCCEEDED(rc)) + { + ComPtr<IStorageController> ctl; + + CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(), + ctl.asOutParam())); + + if (SUCCEEDED(rc)) + { + CHECK_ERROR(ctl, COMSETTER(Name)(Bstr(pszCtlNewName).raw())); + } + else + { + errorArgument("Couldn't find the controller with the name: '%s'\n", pszCtl); + rc = E_FAIL; + } + } + + } + + /* commit changes */ + if (SUCCEEDED(rc)) + CHECK_ERROR(machine, SaveSettings()); + + /* it's important to always close sessions */ + a->session->UnlockMachine(); + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +#endif /* !VBOX_ONLY_DOCS */ + diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageUSB.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageUSB.cpp new file mode 100644 index 00000000..237c85d0 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageUSB.cpp @@ -0,0 +1,600 @@ +/* $Id: VBoxManageUSB.cpp $ */ +/** @file + * VBoxManage - VirtualBox's command-line interface. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/VirtualBox.h> + +#include "VBoxManage.h" + +#include <iprt/asm.h> + +using namespace com; + +/** + * Quick IUSBDevice implementation for detaching / attaching + * devices to the USB Controller. + */ +class MyUSBDevice : public IUSBDevice +{ +public: + // public initializer/uninitializer for internal purposes only + MyUSBDevice(uint16_t a_u16VendorId, uint16_t a_u16ProductId, uint16_t a_bcdRevision, uint64_t a_u64SerialHash, const char *a_pszComment) + : m_usVendorId(a_u16VendorId), m_usProductId(a_u16ProductId), + m_bcdRevision(a_bcdRevision), m_u64SerialHash(a_u64SerialHash), + m_bstrComment(a_pszComment), + m_cRefs(0) + { + } + virtual ~MyUSBDevice() {} + + STDMETHOD_(ULONG, AddRef)(void) + { + return ASMAtomicIncU32(&m_cRefs); + } + STDMETHOD_(ULONG, Release)(void) + { + ULONG cRefs = ASMAtomicDecU32(&m_cRefs); + if (!cRefs) + delete this; + return cRefs; + } + STDMETHOD(QueryInterface)(const IID &iid, void **ppvObject) + { + Guid guid(iid); + if (guid == Guid(COM_IIDOF(IUnknown))) + *ppvObject = (IUnknown *)this; +#ifdef RT_OS_WINDOWS + else if (guid == Guid(COM_IIDOF(IDispatch))) + *ppvObject = (IDispatch *)this; +#endif + else if (guid == Guid(COM_IIDOF(IUSBDevice))) + *ppvObject = (IUSBDevice *)this; + else + return E_NOINTERFACE; + AddRef(); + return S_OK; + } + + STDMETHOD(COMGETTER(Id))(OUT_GUID a_pId) { NOREF(a_pId); return E_NOTIMPL; } + STDMETHOD(COMGETTER(VendorId))(USHORT *a_pusVendorId) { *a_pusVendorId = m_usVendorId; return S_OK; } + STDMETHOD(COMGETTER(ProductId))(USHORT *a_pusProductId) { *a_pusProductId = m_usProductId; return S_OK; } + STDMETHOD(COMGETTER(Revision))(USHORT *a_pusRevision) { *a_pusRevision = m_bcdRevision; return S_OK; } + STDMETHOD(COMGETTER(SerialHash))(ULONG64 *a_pullSerialHash) { *a_pullSerialHash = m_u64SerialHash; return S_OK; } + STDMETHOD(COMGETTER(Manufacturer))(BSTR *a_pManufacturer) { NOREF(a_pManufacturer); return E_NOTIMPL; } + STDMETHOD(COMGETTER(Product))(BSTR *a_pProduct) { NOREF(a_pProduct); return E_NOTIMPL; } + STDMETHOD(COMGETTER(SerialNumber))(BSTR *a_pSerialNumber) { NOREF(a_pSerialNumber); return E_NOTIMPL; } + STDMETHOD(COMGETTER(Address))(BSTR *a_pAddress) { NOREF(a_pAddress); return E_NOTIMPL; } + +private: + /** The vendor id of this USB device. */ + USHORT m_usVendorId; + /** The product id of this USB device. */ + USHORT m_usProductId; + /** The product revision number of this USB device. + * (high byte = integer; low byte = decimal) */ + USHORT m_bcdRevision; + /** The USB serial hash of the device. */ + uint64_t m_u64SerialHash; + /** The user comment string. */ + Bstr m_bstrComment; + /** Reference counter. */ + uint32_t volatile m_cRefs; +}; + + +// types +/////////////////////////////////////////////////////////////////////////////// + +template <typename T> +class Nullable +{ +public: + + Nullable() : mIsNull(true) {} + Nullable(const T &aValue, bool aIsNull = false) + : mIsNull(aIsNull), mValue(aValue) {} + + bool isNull() const { return mIsNull; }; + void setNull(bool aIsNull = true) { mIsNull = aIsNull; } + + operator const T&() const { return mValue; } + + Nullable &operator= (const T &aValue) + { + mValue = aValue; + mIsNull = false; + return *this; + } + +private: + + bool mIsNull; + T mValue; +}; + +/** helper structure to encapsulate USB filter manipulation commands */ +struct USBFilterCmd +{ + struct USBFilter + { + USBFilter() + : mAction(USBDeviceFilterAction_Null) + {} + + Bstr mName; + Nullable <bool> mActive; + Bstr mVendorId; + Bstr mProductId; + Bstr mRevision; + Bstr mManufacturer; + Bstr mProduct; + Bstr mRemote; + Bstr mSerialNumber; + Nullable <ULONG> mMaskedInterfaces; + USBDeviceFilterAction_T mAction; + }; + + enum Action { Invalid, Add, Modify, Remove }; + + USBFilterCmd() : mAction(Invalid), mIndex(0), mGlobal(false) {} + + Action mAction; + uint32_t mIndex; + /** flag whether the command target is a global filter */ + bool mGlobal; + /** machine this command is targeted at (null for global filters) */ + ComPtr<IMachine> mMachine; + USBFilter mFilter; +}; + +RTEXITCODE handleUSBFilter(HandlerArg *a) +{ + HRESULT rc = S_OK; + USBFilterCmd cmd; + + /* at least: 0: command, 1: index, 2: --target, 3: <target value> */ + if (a->argc < 4) + return errorSyntax(USAGE_USBFILTER, "Not enough parameters"); + + /* which command? */ + cmd.mAction = USBFilterCmd::Invalid; + if (!strcmp(a->argv[0], "add")) cmd.mAction = USBFilterCmd::Add; + else if (!strcmp(a->argv[0], "modify")) cmd.mAction = USBFilterCmd::Modify; + else if (!strcmp(a->argv[0], "remove")) cmd.mAction = USBFilterCmd::Remove; + + if (cmd.mAction == USBFilterCmd::Invalid) + return errorSyntax(USAGE_USBFILTER, "Invalid parameter '%s'", a->argv[0]); + + /* which index? */ + if (VINF_SUCCESS != RTStrToUInt32Full(a->argv[1], 10, &cmd.mIndex)) + return errorSyntax(USAGE_USBFILTER, "Invalid index '%s'", a->argv[1]); + + switch (cmd.mAction) + { + case USBFilterCmd::Add: + case USBFilterCmd::Modify: + { + /* at least: 0: command, 1: index, 2: --target, 3: <target value>, 4: --name, 5: <name value> */ + if (a->argc < 6) + { + if (cmd.mAction == USBFilterCmd::Add) + return errorSyntax(USAGE_USBFILTER_ADD, "Not enough parameters"); + + return errorSyntax(USAGE_USBFILTER_MODIFY, "Not enough parameters"); + } + + // set Active to true by default + // (assuming that the user sets up all necessary attributes + // at once and wants the filter to be active immediately) + if (cmd.mAction == USBFilterCmd::Add) + cmd.mFilter.mActive = true; + + for (int i = 2; i < a->argc; i++) + { + if ( !strcmp(a->argv[i], "--target") + || !strcmp(a->argv[i], "-target")) + { + if (a->argc <= i + 1 || !*a->argv[i+1]) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + if (!strcmp(a->argv[i], "global")) + cmd.mGlobal = true; + else + { + /* assume it's a UUID of a machine */ + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[i]).raw(), + cmd.mMachine.asOutParam()), RTEXITCODE_FAILURE); + } + } + else if ( !strcmp(a->argv[i], "--name") + || !strcmp(a->argv[i], "-name")) + { + if (a->argc <= i + 1 || !*a->argv[i+1]) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + cmd.mFilter.mName = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--active") + || !strcmp(a->argv[i], "-active")) + { + if (a->argc <= i + 1) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + if (!strcmp(a->argv[i], "yes")) + cmd.mFilter.mActive = true; + else if (!strcmp(a->argv[i], "no")) + cmd.mFilter.mActive = false; + else + return errorArgument("Invalid --active argument '%s'", a->argv[i]); + } + else if ( !strcmp(a->argv[i], "--vendorid") + || !strcmp(a->argv[i], "-vendorid")) + { + if (a->argc <= i + 1) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + cmd.mFilter.mVendorId = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--productid") + || !strcmp(a->argv[i], "-productid")) + { + if (a->argc <= i + 1) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + cmd.mFilter.mProductId = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--revision") + || !strcmp(a->argv[i], "-revision")) + { + if (a->argc <= i + 1) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + cmd.mFilter.mRevision = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--manufacturer") + || !strcmp(a->argv[i], "-manufacturer")) + { + if (a->argc <= i + 1) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + cmd.mFilter.mManufacturer = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--product") + || !strcmp(a->argv[i], "-product")) + { + if (a->argc <= i + 1) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + cmd.mFilter.mProduct = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--remote") + || !strcmp(a->argv[i], "-remote")) + { + if (a->argc <= i + 1) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + cmd.mFilter.mRemote = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--serialnumber") + || !strcmp(a->argv[i], "-serialnumber")) + { + if (a->argc <= i + 1) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + cmd.mFilter.mSerialNumber = a->argv[i]; + } + else if ( !strcmp(a->argv[i], "--maskedinterfaces") + || !strcmp(a->argv[i], "-maskedinterfaces")) + { + if (a->argc <= i + 1) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + uint32_t u32; + int vrc = RTStrToUInt32Full(a->argv[i], 0, &u32); + if (RT_FAILURE(vrc)) + return errorArgument("Failed to convert the --maskedinterfaces value '%s' to a number, vrc=%Rrc", a->argv[i], vrc); + cmd.mFilter.mMaskedInterfaces = u32; + } + else if ( !strcmp(a->argv[i], "--action") + || !strcmp(a->argv[i], "-action")) + { + if (a->argc <= i + 1) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + if (!strcmp(a->argv[i], "ignore")) + cmd.mFilter.mAction = USBDeviceFilterAction_Ignore; + else if (!strcmp(a->argv[i], "hold")) + cmd.mFilter.mAction = USBDeviceFilterAction_Hold; + else + return errorArgument("Invalid USB filter action '%s'", a->argv[i]); + } + else + return errorSyntax(cmd.mAction == USBFilterCmd::Add ? USAGE_USBFILTER_ADD : USAGE_USBFILTER_MODIFY, + "Unknown option '%s'", a->argv[i]); + } + + if (cmd.mAction == USBFilterCmd::Add) + { + // mandatory/forbidden options + if ( cmd.mFilter.mName.isEmpty() + || + ( cmd.mGlobal + && cmd.mFilter.mAction == USBDeviceFilterAction_Null + ) + || ( !cmd.mGlobal + && !cmd.mMachine) + || ( cmd.mGlobal + && !cmd.mFilter.mRemote.isEmpty()) + ) + { + return errorSyntax(USAGE_USBFILTER_ADD, "Mandatory options not supplied"); + } + } + break; + } + + case USBFilterCmd::Remove: + { + /* at least: 0: command, 1: index, 2: --target, 3: <target value> */ + if (a->argc < 4) + return errorSyntax(USAGE_USBFILTER_REMOVE, "Not enough parameters"); + + for (int i = 2; i < a->argc; i++) + { + if ( !strcmp(a->argv[i], "--target") + || !strcmp(a->argv[i], "-target")) + { + if (a->argc <= i + 1 || !*a->argv[i+1]) + return errorArgument("Missing argument to '%s'", a->argv[i]); + i++; + if (!strcmp(a->argv[i], "global")) + cmd.mGlobal = true; + else + { + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[i]).raw(), + cmd.mMachine.asOutParam()), RTEXITCODE_FAILURE); + } + } + } + + // mandatory options + if (!cmd.mGlobal && !cmd.mMachine) + return errorSyntax(USAGE_USBFILTER_REMOVE, "Mandatory options not supplied"); + + break; + } + + default: break; + } + + USBFilterCmd::USBFilter &f = cmd.mFilter; + + ComPtr<IHost> host; + ComPtr<IUSBDeviceFilters> flts; + if (cmd.mGlobal) + CHECK_ERROR_RET(a->virtualBox, COMGETTER(Host)(host.asOutParam()), RTEXITCODE_FAILURE); + else + { + /* open a session for the VM */ + CHECK_ERROR_RET(cmd.mMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + /* get the mutable session machine */ + a->session->COMGETTER(Machine)(cmd.mMachine.asOutParam()); + /* and get the USB device filters */ + CHECK_ERROR_RET(cmd.mMachine, COMGETTER(USBDeviceFilters)(flts.asOutParam()), RTEXITCODE_FAILURE); + } + + switch (cmd.mAction) + { + case USBFilterCmd::Add: + { + if (cmd.mGlobal) + { + ComPtr<IHostUSBDeviceFilter> flt; + CHECK_ERROR_BREAK(host, CreateUSBDeviceFilter(f.mName.raw(), + flt.asOutParam())); + + if (!f.mActive.isNull()) + CHECK_ERROR_BREAK(flt, COMSETTER(Active)(f.mActive)); + if (!f.mVendorId.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(VendorId)(f.mVendorId.raw())); + if (!f.mProductId.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(ProductId)(f.mProductId.raw())); + if (!f.mRevision.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Revision)(f.mRevision.raw())); + if (!f.mManufacturer.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Manufacturer)(f.mManufacturer.raw())); + if (!f.mSerialNumber.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(SerialNumber)(f.mSerialNumber.raw())); + if (!f.mMaskedInterfaces.isNull()) + CHECK_ERROR_BREAK(flt, COMSETTER(MaskedInterfaces)(f.mMaskedInterfaces)); + + if (f.mAction != USBDeviceFilterAction_Null) + CHECK_ERROR_BREAK(flt, COMSETTER(Action)(f.mAction)); + + CHECK_ERROR_BREAK(host, InsertUSBDeviceFilter(cmd.mIndex, flt)); + } + else + { + ComPtr<IUSBDeviceFilter> flt; + CHECK_ERROR_BREAK(flts, CreateDeviceFilter(f.mName.raw(), + flt.asOutParam())); + + if (!f.mActive.isNull()) + CHECK_ERROR_BREAK(flt, COMSETTER(Active)(f.mActive)); + if (!f.mVendorId.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(VendorId)(f.mVendorId.raw())); + if (!f.mProductId.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(ProductId)(f.mProductId.raw())); + if (!f.mRevision.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Revision)(f.mRevision.raw())); + if (!f.mManufacturer.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Manufacturer)(f.mManufacturer.raw())); + if (!f.mRemote.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Remote)(f.mRemote.raw())); + if (!f.mSerialNumber.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(SerialNumber)(f.mSerialNumber.raw())); + if (!f.mMaskedInterfaces.isNull()) + CHECK_ERROR_BREAK(flt, COMSETTER(MaskedInterfaces)(f.mMaskedInterfaces)); + + CHECK_ERROR_BREAK(flts, InsertDeviceFilter(cmd.mIndex, flt)); + } + break; + } + case USBFilterCmd::Modify: + { + if (cmd.mGlobal) + { + SafeIfaceArray <IHostUSBDeviceFilter> coll; + CHECK_ERROR_BREAK(host, COMGETTER(USBDeviceFilters)(ComSafeArrayAsOutParam(coll))); + + ComPtr<IHostUSBDeviceFilter> flt = coll[cmd.mIndex]; + + if (!f.mName.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Name)(f.mName.raw())); + if (!f.mActive.isNull()) + CHECK_ERROR_BREAK(flt, COMSETTER(Active)(f.mActive)); + if (!f.mVendorId.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(VendorId)(f.mVendorId.raw())); + if (!f.mProductId.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(ProductId)(f.mProductId.raw())); + if (!f.mRevision.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Revision)(f.mRevision.raw())); + if (!f.mManufacturer.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Manufacturer)(f.mManufacturer.raw())); + if (!f.mSerialNumber.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(SerialNumber)(f.mSerialNumber.raw())); + if (!f.mMaskedInterfaces.isNull()) + CHECK_ERROR_BREAK(flt, COMSETTER(MaskedInterfaces)(f.mMaskedInterfaces)); + + if (f.mAction != USBDeviceFilterAction_Null) + CHECK_ERROR_BREAK(flt, COMSETTER(Action)(f.mAction)); + } + else + { + SafeIfaceArray <IUSBDeviceFilter> coll; + CHECK_ERROR_BREAK(flts, COMGETTER(DeviceFilters)(ComSafeArrayAsOutParam(coll))); + + ComPtr<IUSBDeviceFilter> flt = coll[cmd.mIndex]; + + if (!f.mName.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Name)(f.mName.raw())); + if (!f.mActive.isNull()) + CHECK_ERROR_BREAK(flt, COMSETTER(Active)(f.mActive)); + if (!f.mVendorId.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(VendorId)(f.mVendorId.raw())); + if (!f.mProductId.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(ProductId)(f.mProductId.raw())); + if (!f.mRevision.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Revision)(f.mRevision.raw())); + if (!f.mManufacturer.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Manufacturer)(f.mManufacturer.raw())); + if (!f.mRemote.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(Remote)(f.mRemote.raw())); + if (!f.mSerialNumber.isEmpty()) + CHECK_ERROR_BREAK(flt, COMSETTER(SerialNumber)(f.mSerialNumber.raw())); + if (!f.mMaskedInterfaces.isNull()) + CHECK_ERROR_BREAK(flt, COMSETTER(MaskedInterfaces)(f.mMaskedInterfaces)); + } + break; + } + case USBFilterCmd::Remove: + { + if (cmd.mGlobal) + { + ComPtr<IHostUSBDeviceFilter> flt; + CHECK_ERROR_BREAK(host, RemoveUSBDeviceFilter(cmd.mIndex)); + } + else + { + ComPtr<IUSBDeviceFilter> flt; + CHECK_ERROR_BREAK(flts, RemoveDeviceFilter(cmd.mIndex, flt.asOutParam())); + } + break; + } + default: + break; + } + + if (cmd.mMachine) + { + if (SUCCEEDED(rc)) + { + /* commit the session */ + CHECK_ERROR(cmd.mMachine, SaveSettings()); + } + /* close the session */ + a->session->UnlockMachine(); + } + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleUSBDevSource(HandlerArg *a) +{ + HRESULT rc = S_OK; + + /* at least: 0: command, 1: source id */ + if (a->argc < 2) + return errorSyntax(USAGE_USBDEVSOURCE, "Not enough parameters"); + + ComPtr<IHost> host; + if (!strcmp(a->argv[0], "add")) + { + Bstr strBackend; + Bstr strAddress; + if (a->argc != 6) + return errorSyntax(USAGE_USBDEVSOURCE, "Invalid number of parameters"); + + for (int i = 2; i < a->argc; i++) + { + if (!strcmp(a->argv[i], "--backend")) + { + i++; + strBackend = a->argv[i]; + } + else if (!strcmp(a->argv[i], "--address")) + { + i++; + strAddress = a->argv[i]; + } + else + return errorSyntax(USAGE_USBDEVSOURCE, "Parameter \"%s\" is invalid", a->argv[i]); + } + + SafeArray<BSTR> usbSourcePropNames; + SafeArray<BSTR> usbSourcePropValues; + + CHECK_ERROR_RET(a->virtualBox, COMGETTER(Host)(host.asOutParam()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(host, AddUSBDeviceSource(strBackend.raw(), Bstr(a->argv[1]).raw(), strAddress.raw(), + ComSafeArrayAsInParam(usbSourcePropNames), ComSafeArrayAsInParam(usbSourcePropValues)), + RTEXITCODE_FAILURE); + } + else if (!strcmp(a->argv[0], "remove")) + { + CHECK_ERROR_RET(a->virtualBox, COMGETTER(Host)(host.asOutParam()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(host, RemoveUSBDeviceSource(Bstr(a->argv[1]).raw()), RTEXITCODE_FAILURE); + } + + return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ |