From f8fe689a81f906d1b91bb3220acde2a4ecb14c5b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 6 May 2024 05:01:46 +0200 Subject: Adding upstream version 6.0.4-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Frontends/VBoxManage/Makefile.kmk | 208 ++ .../Frontends/VBoxManage/VBoxInternalManage.cpp | 2597 +++++++++++++++ src/VBox/Frontends/VBoxManage/VBoxManage.cpp | 760 +++++ src/VBox/Frontends/VBoxManage/VBoxManage.h | 338 ++ src/VBox/Frontends/VBoxManage/VBoxManage.rc | 51 + .../Frontends/VBoxManage/VBoxManageAppliance.cpp | 1516 +++++++++ .../VBoxManage/VBoxManageBandwidthControl.cpp | 373 +++ .../Frontends/VBoxManage/VBoxManageControlVM.cpp | 2290 +++++++++++++ .../Frontends/VBoxManage/VBoxManageDHCPServer.cpp | 525 +++ .../Frontends/VBoxManage/VBoxManageDebugVM.cpp | 899 +++++ src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp | 2560 +++++++++++++++ .../Frontends/VBoxManage/VBoxManageGuestCtrl.cpp | 3435 ++++++++++++++++++++ .../Frontends/VBoxManage/VBoxManageGuestCtrl.h | 236 ++ .../VBoxManage/VBoxManageGuestCtrlListener.cpp | 518 +++ .../Frontends/VBoxManage/VBoxManageGuestProp.cpp | 437 +++ src/VBox/Frontends/VBoxManage/VBoxManageHelp.cpp | 1404 ++++++++ .../Frontends/VBoxManage/VBoxManageHostonly.cpp | 283 ++ src/VBox/Frontends/VBoxManage/VBoxManageInfo.cpp | 2691 +++++++++++++++ src/VBox/Frontends/VBoxManage/VBoxManageList.cpp | 1608 +++++++++ .../Frontends/VBoxManage/VBoxManageMetrics.cpp | 651 ++++ src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp | 1941 +++++++++++ .../Frontends/VBoxManage/VBoxManageModifyVM.cpp | 3202 ++++++++++++++++++ .../Frontends/VBoxManage/VBoxManageNATNetwork.cpp | 523 +++ .../Frontends/VBoxManage/VBoxManageSnapshot.cpp | 641 ++++ .../VBoxManage/VBoxManageStorageController.cpp | 1290 ++++++++ src/VBox/Frontends/VBoxManage/VBoxManageUSB.cpp | 600 ++++ 26 files changed, 31577 insertions(+) create mode 100644 src/VBox/Frontends/VBoxManage/Makefile.kmk create mode 100644 src/VBox/Frontends/VBoxManage/VBoxInternalManage.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManage.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManage.h create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManage.rc create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageAppliance.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageBandwidthControl.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageControlVM.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageDHCPServer.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageDebugVM.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrl.h create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageGuestCtrlListener.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageGuestProp.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageHelp.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageHostonly.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageInfo.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageList.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageMetrics.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageMisc.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageNATNetwork.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageSnapshot.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageStorageController.cpp create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageUSB.cpp (limited to 'src/VBox/Frontends/VBoxManage') 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 ' \ + '' \ + '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 +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "VBoxManage.h" + +/* Includes for the raw disk stuff. */ +#ifdef RT_OS_WINDOWS +# include +# include +#elif defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) \ + || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) +# include +# include +# include +# include +# include +# include +#endif +#ifdef RT_OS_LINUX +# include +# include +# include +# include /* atoi() */ +#endif /* RT_OS_LINUX */ +#ifdef RT_OS_DARWIN +# include +#endif /* RT_OS_DARWIN */ +#ifdef RT_OS_SOLARIS +# include +# include +# include +#endif /* RT_OS_SOLARIS */ +#ifdef RT_OS_FREEBSD +# include +#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 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
[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 [delta] [module] [module address]\n" + " This will instruct DBGF to load the given symbol file\n" + " during initialization.\n" + "\n" + : "", + (u64Cmd & USAGE_SETHDUUID) + ? " sethduuid []\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 \n" + " Assigns a new parent UUID to the given image file.\n" + "\n" + : "", + (u64Cmd & USAGE_DUMPHDINFO) + ? " dumphdinfo \n" + " Prints information about the image at the given location.\n" + "\n" + : "", + (u64Cmd & USAGE_LISTPARTITIONS) + ? " listpartitions -rawdisk \n" + " Lists all partitions on .\n" + "\n" + : "", + (u64Cmd & USAGE_CREATERAWVMDK) + ? " createrawvmdk -filename -rawdisk \n" + " [-partitions [-mbr ] ]\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 -to \n" + " Renames an existing VMDK image, including the base file and all its extents.\n" + "\n" + : "", + (u64Cmd & USAGE_CONVERTTORAW) + ? " converttoraw [-format ] " +#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" + " \n" + " converts hard disk images between formats\n" + "\n" + : "", + (u64Cmd & USAGE_REPAIRHD) + ? " repairhd [-dry-run]\n" + " [-format VDI|VMDK|VHD|...]\n" + " \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 [--enable|--disable] [--flags todo]\n" + " [--groups todo] [--destinations todo]\n" + " Controls debug logging.\n" + "\n" + : "", + (u64Cmd & USAGE_PASSWORDHASH) + ? " passwordhash \n" + " Generates a password hash.\n" + "\n" + : "", + (u64Cmd & USAGE_GUESTSTATS) + ? " gueststats [--interval ]\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 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 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 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 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 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 aVirtualBox, ComPtr aSession) +{ + RT_NOREF(aSession); + HRESULT rc; + + /* + * Get the VM + */ + ComPtr 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 aVirtualBox, ComPtr aSession) +{ + RT_NOREF(aSession); + HRESULT rc; + + /* + * Get the VM + */ + ComPtr 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 aVirtualBox, ComPtr 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 aVirtualBox, ComPtr 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 aVirtualBox, ComPtr 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 aVirtualBox, ComPtr 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 aVirtualBox, ComPtr 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 aVirtualBox, ComPtr 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 aVirtualBox, ComPtr 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 aVirtualBox, ComPtr 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 aVirtualBox, ComPtr 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 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 ptrConsole; + CHECK_ERROR_RET(aSession, COMGETTER(Console)(ptrConsole.asOutParam()), RTEXITCODE_FAILURE); + + ComPtr 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 aVirtualBox, ComPtr 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 aVirtualBox, ComPtr 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 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 ptrConsole; + CHECK_ERROR_RET(aSession, COMGETTER(Console)(ptrConsole.asOutParam()), RTEXITCODE_FAILURE); + + ComPtr 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 +# include +# include +# include +# include +# include +# include + +# include +#endif /* !VBOX_ONLY_DOCS */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 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 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 virtualBoxClient; + ComPtr virtualBox; + hrc = virtualBoxClient.createInprocObject(CLSID_VirtualBoxClient); + if (SUCCEEDED(hrc)) + hrc = virtualBoxClient->COMGETTER(VirtualBox)(virtualBox.asOutParam()); + if (SUCCEEDED(hrc)) + { + ComPtr 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 +#include +#include +#include +#include +#endif /* !VBOX_ONLY_DOCS */ + +#include +#include +#include +#include + +#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 virtualBox; + ComPtr 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 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 *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 &rootSnapshot, + ComPtr ¤tSnapshot, + VMINFO_DETAILS details, + const com::Utf8Str &prefix = "", + int level = 0); +RTEXITCODE handleShowVMInfo(HandlerArg *a); +HRESULT showVMInfo(ComPtr pVirtualBox, + ComPtr pMachine, + ComPtr pSession, + VMINFO_DETAILS details = VMINFO_NONE); +const char *machineStateToName(MachineState_T machineState, bool fShort); +HRESULT showBandwidthGroups(ComPtr &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 &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 &pVirtualBox, + const ComPtr &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 +#include + +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 +#include +#include +#include +#include +#include +#include + +#include +#include +#endif /* !VBOX_ONLY_DOCS */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "VBoxManage.h" +using namespace com; + + +// funcs +/////////////////////////////////////////////////////////////////////////////// + +typedef std::map ArgsMap; // pairs of strings like "vmname" => "newvmname" +typedef std::map ArgsMapsMap; // map of maps, one for each virtual system, sorted by index + +typedef std::map IgnoresMap; // pairs of numeric description entry indices +typedef std::map 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 *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 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 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 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 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 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 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 retTypes; + com::SafeArray aRefs; + com::SafeArray aOvfValues; + com::SafeArray aVBoxValues; + com::SafeArray 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 aEnabled(retTypes.size()); + com::SafeArray 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 \"; 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 \")\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 \")\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", + 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 \")\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 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 pVirtualBox = arg->virtualBox; + ComPtr systemProperties; + com::SafeIfaceArray 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 extensions; + + for (unsigned j = 0; j < mediumFormats.size(); ++j) + { + com::SafeArray deviceType; + ComPtr 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 \")\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 \")\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 \")\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 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 *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 options; + std::list< ComPtr > 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 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 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 >::iterator itM; + uint32_t i=0; + for (itM = llMachines.begin(); + itM != llMachines.end(); + ++itM, ++i) + { + ComPtr pMachine = *itM; + ComPtr 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 aIdentifiers; + + CHECK_ERROR_BREAK(pAppliance, GetPasswordIds(ComSafeArrayAsOutParam(aIdentifiers))); + + if (aIdentifiers.size() > 0) + { + com::SafeArray 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 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 &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 &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 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 &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 &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 machine; + ComPtr 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VBoxManage.h" + +#include + + +/** + * 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 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 &llScancodes) +{ + /* Send scancodes to the VM. */ + com::SafeArray 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 &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 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 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 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 console; + ComPtr 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 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 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 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 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 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 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 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 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 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 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 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 adapter; + CHECK_ERROR_BREAK(sessionMachine, GetNetworkAdapter(n - 1, adapter.asOutParam())); + if (!adapter) + { + rc = E_FAIL; + break; + } + ComPtr 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 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 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 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 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 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 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 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 host; + CHECK_ERROR_BREAK(a->virtualBox, COMGETTER(Host)(host.asOutParam())); + SafeIfaceArray coll; + CHECK_ERROR_BREAK(host, COMGETTER(USBDevices)(ComSafeArrayAsOutParam(coll))); + ComPtr dev; + CHECK_ERROR_BREAK(host, FindUSBDeviceByAddress(Bstr(a->argv[2]).raw(), + dev.asOutParam())); + CHECK_ERROR_BREAK(dev, COMGETTER(Id)(usbId.asOutParam())); + } + else + { + SafeIfaceArray coll; + CHECK_ERROR_BREAK(console, COMGETTER(USBDevices)(ComSafeArrayAsOutParam(coll))); + ComPtr 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 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 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 pDisplay; + CHECK_ERROR_BREAK(console, COMGETTER(Display)(pDisplay.asOutParam())); + if (!pDisplay) + { + RTMsgError("Guest not running"); + rc = E_FAIL; + break; + } + + com::SafeIfaceArray aGuestScreenInfos; + + /* Parse " on|primary | 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 or "); + 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 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 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 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 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 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 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 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 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 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 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 recordingSettings; + CHECK_ERROR_BREAK(machine, COMGETTER(RecordingSettings)(recordingSettings.asOutParam())); + + SafeIfaceArray 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 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 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 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 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 +#include +#include +#include +#include +#endif /* !VBOX_ONLY_DOCS */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "VBoxManage.h" + +#include +#include +#include + +#ifndef VBOX_ONLY_DOCS +using namespace com; + +typedef enum enMainOpCodes +{ + OP_ADD = 1000, + OP_REMOVE, + OP_MODIFY, + OP_RESTART +} OPCODE; + +typedef std::pair DhcpOptSpec; +typedef std::vector DhcpOpts; +typedef DhcpOpts::iterator DhcpOptIterator; + +typedef std::vector 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 VmSlot2OptionsM; +typedef VmSlot2OptionsM::iterator VmSlot2OptionsIterator; +typedef VmSlot2OptionsM::value_type VmSlot2OptionsPair; + +typedef std::map VmSlot2OptionIdsM; +typedef VmSlot2OptionIdsM::iterator VmSlot2OptionIdsIterator; + +typedef std::vector 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) + "" (for net name) + * 2. the misspelled "-netmask" to be treated as 'n' (for -netname) + "" (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) + "" (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 host; + CHECK_ERROR(a->virtualBox, COMGETTER(Host)(host.asOutParam())); + + ComPtr 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 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 aBstrNames; + com::SafeArray 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 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 aBstrNames; + com::SafeArray 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 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 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 ptrConsole; + CHECK_ERROR2(hrc, pArgs->session, COMGETTER(Console)(ptrConsole.asOutParam())); + if (SUCCEEDED(hrc)) + { + if (ptrConsole.isNotNull()) + { + ComPtr 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 &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 &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 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 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 pProgress; + com::SafeArray l_variants(sizeof(MediumVariant_T)*8); + + for (ULONG i = 0; i < l_variants.size(); ++i) + { + ULONG temp = enmMediumVariant; + temp &= 1< pMedium; + MediumType_T enmMediumType = MediumType_Normal; /* Shut up MSC */ + bool AutoReset = false; + SafeArray mediumPropNames; + SafeArray 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 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 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 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 pSrcMedium; + ComPtr 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 pMediumFmt; + com::SafeArray 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 pProgress; + com::SafeArray l_variants(sizeof(MediumVariant_T)*8); + + for (ULONG i = 0; i < l_variants.size(); ++i) + { + ULONG temp = enmMediumVariant; + temp &= 1<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 &pVirtualBox, + const ComPtr &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 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 names; + com::SafeArray 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 machineIds; + pMedium->COMGETTER(MachineIds)(ComSafeArrayAsOutParam(machineIds)); + for (size_t i = 0; i < machineIds.size(); i++) + { + ComPtr 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 snapshotIds; + pMedium->GetSnapshotIds(machineIds[i], + ComSafeArrayAsOutParam(snapshotIds)); + for (size_t j = 0; j < snapshotIds.size(); j++) + { + ComPtr 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 children; + pMedium->COMGETTER(Children)(ComSafeArrayAsOutParam(children)); + bool fFirst = true; + for (size_t i = 0; i < children.size(); i++) + { + ComPtr 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 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 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 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 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 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 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 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 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 &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 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 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 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 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, "********** \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 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 ptrDataStream; + ComPtr ptrProgress; + + com::SafeArray l_variants(sizeof(MediumVariant_T)*8); + + for (ULONG i = 0; i < l_variants.size(); ++i) + { + ULONG temp = enmMediumVariant; + temp &= 1<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 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include /* For RTProcSelf(). */ +#include +#include + +#include +#include + +#ifdef USE_XPCOM_QUEUE +# include +# include +#endif + +#include + +#ifdef RT_OS_DARWIN +# include +#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 pGuest; + /** Pointer to the to be used guest session. */ + ComPtr 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 > DESTDIRMAP, *PDESTDIRMAP; +typedef std::map< Utf8Str, std::vector >::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 [--verbose|-v] [--quiet|-q]\n" + " [--username ] [--domain ]\n" + " [--passwordfile | --password ]\n%s", + pcszSep1, pcszSep2, uSubCmd == ~0U ? "\n" : ""); + if (uSubCmd & USAGE_GSTCTRL_RUN) + RTStrmPrintf(pStrm, + " run [common-options]\n" + " [--exe ] [--timeout ]\n" + " [-E|--putenv [=]] [--unquoted-args]\n" + " [--ignore-operhaned-processes] [--profile]\n" + " [--no-wait-stdout|--wait-stdout]\n" + " [--no-wait-stderr|--wait-stderr]\n" + " [--dos2unix] [--unix2dos]\n" + " -- [argument1] ... [argumentN]]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_START) + RTStrmPrintf(pStrm, + " start [common-options]\n" + " [--exe ] [--timeout ]\n" + " [-E|--putenv [=]] [--unquoted-args]\n" + " [--ignore-operhaned-processes] [--profile]\n" + " -- [argument1] ... [argumentN]]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_COPYFROM) + RTStrmPrintf(pStrm, + " copyfrom [common-options]\n" + " [--follow] [-R|--recursive]\n" + " [guest-src1 [...]] \n" + "\n" + " copyfrom [common-options]\n" + " [--follow] [-R|--recursive]\n" + " [--target-directory ]\n" + " [guest-src1 [...]]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_COPYTO) + RTStrmPrintf(pStrm, + " copyto [common-options]\n" + " [--follow] [-R|--recursive]\n" + " [host-src1 [...]] \n" + "\n" + " copyto [common-options]\n" + " [--follow] [-R|--recursive]\n" + " [--target-directory ]\n" + " [host-src1 [...]]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_MKDIR) + RTStrmPrintf(pStrm, + " mkdir|createdir[ectory] [common-options]\n" + " [--parents] [--mode ]\n" + " [...]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_RMDIR) + RTStrmPrintf(pStrm, + " rmdir|removedir[ectory] [common-options]\n" + " [-R|--recursive]\n" + " [...]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_RM) + RTStrmPrintf(pStrm, + " removefile|rm [common-options] [-f|--force]\n" + " [...]\n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_MV) + RTStrmPrintf(pStrm, + " mv|move|ren[ame] [common-options]\n" + " [source1 [...]] \n" + "\n"); + if (uSubCmd & USAGE_GSTCTRL_MKTEMP) + RTStrmPrintf(pStrm, + " mktemp|createtemp[orary] [common-options]\n" + " [--secure] [--mode ] [--tmpdir ]\n" + "