From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp | 2753 ++++++++++++++++++++++ 1 file changed, 2753 insertions(+) create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp (limited to 'src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp') diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp new file mode 100644 index 00000000..1e2d8845 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageDisk.cpp @@ -0,0 +1,2753 @@ +/* $Id: VBoxManageDisk.cpp $ */ +/** @file + * VBoxManage - The disk/medium related commands. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "VBoxManage.h" +using namespace com; + +/** Medium category. */ +typedef enum MEDIUMCATEGORY +{ + MEDIUMCATEGORY_NONE = 0, + MEDIUMCATEGORY_DISK, + MEDIUMCATEGORY_DVD, + MEDIUMCATEGORY_FLOPPY +} MEDIUMCATEGORY; + +DECLARE_TRANSLATION_CONTEXT(Disk); + +// funcs +/////////////////////////////////////////////////////////////////////////////// + + +static DECLCALLBACK(void) handleVDError(void *pvUser, int vrc, RT_SRC_POS_DECL, const char *pszFormat, va_list va) +{ + RT_NOREF(pvUser); + RTMsgErrorV(pszFormat, va); + RTMsgError(Disk::tr("Error code %Rrc at %s(%u) in function %s"), vrc, RT_SRC_POS_ARGS); +} + +static int parseMediumVariant(const char *psz, MediumVariant_T *pMediumVariant) +{ + int vrc = VINF_SUCCESS; + unsigned uMediumVariant = (unsigned)(*pMediumVariant); + while (psz && *psz && RT_SUCCESS(vrc)) + { + size_t len; + const char *pszComma = strchr(psz, ','); + if (pszComma) + len = pszComma - psz; + else + len = strlen(psz); + if (len > 0) + { + // 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 if ( !RTStrNICmp(psz, "raw", len) + || !RTStrNICmp(psz, "rawdisk", len)) + uMediumVariant |= MediumVariant_VmdkRawDisk; + else + vrc = VERR_PARSE_ERROR; + } + if (pszComma) + psz += len + 1; + else + psz += len; + } + + if (RT_SUCCESS(vrc)) + *pMediumVariant = (MediumVariant_T)uMediumVariant; + return vrc; +} + +int parseMediumType(const char *psz, MediumType_T *penmMediumType) +{ + int vrc = 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 + vrc = VERR_PARSE_ERROR; + + if (RT_SUCCESS(vrc)) + *penmMediumType = enmMediumType; + return vrc; +} + +/** @todo move this into getopt, as getting bool values is generic */ +int parseBool(const char *psz, bool *pb) +{ + int vrc = VINF_SUCCESS; + if ( !RTStrICmp(psz, "on") + || !RTStrICmp(psz, "yes") + || !RTStrICmp(psz, "true") + || !RTStrCmp(psz, "1") + || !RTStrICmp(psz, "enable") + || !RTStrICmp(psz, "enabled")) + *pb = true; + else if ( !RTStrICmp(psz, "off") + || !RTStrICmp(psz, "no") + || !RTStrICmp(psz, "false") + || !RTStrCmp(psz, "0") + || !RTStrICmp(psz, "disable") + || !RTStrICmp(psz, "disabled")) + *pb = false; + else + vrc = VERR_PARSE_ERROR; + + return vrc; +} + +HRESULT openMedium(HandlerArg *a, const char *pszFilenameOrUuid, + DeviceType_T enmDevType, AccessMode_T enmAccessMode, + ComPtr &pMedium, bool fForceNewUuidOnOpen, + bool fSilent) +{ + HRESULT hrc; + 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(Disk::tr("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 + hrc = a->virtualBox->OpenMedium(Bstr(pszFilenameOrUuid).raw(), + enmDevType, + enmAccessMode, + fForceNewUuidOnOpen, + pMedium.asOutParam()); + + return hrc; +} + +static HRESULT createMedium(HandlerArg *a, const char *pszFormat, + const char *pszFilename, DeviceType_T enmDevType, + AccessMode_T enmAccessMode, ComPtr &pMedium) +{ + HRESULT hrc; + 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(Disk::tr("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 hrc; +} + +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 + { "--property", 'p', RTGETOPT_REQ_STRING }, + { "--property-file",'P', RTGETOPT_REQ_STRING }, +}; + +class MediumProperty +{ +public: + const char *m_pszKey; + const char *m_pszValue; /**< Can be binary too. */ + size_t m_cbValue; + char *m_pszFreeValue; + MediumProperty() : m_pszKey(NULL), m_pszValue(NULL), m_cbValue(0), m_pszFreeValue(NULL) { } + MediumProperty(MediumProperty const &a_rThat) + : m_pszKey(a_rThat.m_pszKey) + , m_pszValue(a_rThat.m_pszValue) + , m_cbValue(a_rThat.m_cbValue) + , m_pszFreeValue(NULL) + { + Assert(a_rThat.m_pszFreeValue == NULL); /* not expected here! */ + } + ~MediumProperty() + { + RTMemFree(m_pszFreeValue); + m_pszFreeValue = NULL; + } + +private: + MediumProperty &operator=(MediumProperty const &a_rThat) + { + m_pszKey = a_rThat.m_pszKey; + m_pszValue = a_rThat.m_pszValue; + m_cbValue = a_rThat.m_cbValue; + m_pszFreeValue = a_rThat.m_pszFreeValue; + if (a_rThat.m_pszFreeValue != NULL) + { + m_pszFreeValue = (char *)RTMemDup(m_pszValue, m_cbValue + 1); + if (!m_pszFreeValue) + { + RTMsgError(Disk::tr("Out of memory copying '%s'"), m_pszValue); + throw std::bad_alloc(); + } + } + return *this; + } +}; + +RTEXITCODE handleCreateMedium(HandlerArg *a) +{ + std::list lstProperties; + + HRESULT hrc; + 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(Disk::tr("Only one command can be specified: '%s'"), ValueUnion.psz); + cmd = CMD_DISK; + break; + + case 'D': // DVD + if (cmd != CMD_NONE) + return errorSyntax(Disk::tr("Only one command can be specified: '%s'"), ValueUnion.psz); + cmd = CMD_DVD; + break; + + case 'L': // floppy + if (cmd != CMD_NONE) + return errorSyntax(Disk::tr("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 'p': // --property + case 'P': // --property-file + { + /* allocate property kvp, parse, and append to end of singly linked list */ + char *pszValue = (char *)strchr(ValueUnion.psz, '='); + if (!pszValue) + return RTMsgErrorExitFailure(Disk::tr("Invalid key value pair: No '='.")); + + lstProperties.push_back(MediumProperty()); + MediumProperty &rNewProp = lstProperties.back(); + *pszValue++ = '\0'; /* Warning! Modifies argument string. */ + rNewProp.m_pszKey = ValueUnion.psz; + if (c == 'p') + { + rNewProp.m_pszValue = pszValue; + rNewProp.m_cbValue = strlen(pszValue); + } + else // 'P' + { + RTFILE hValueFile = NIL_RTFILE; + vrc = RTFileOpen(&hValueFile, pszValue, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_FAILURE(vrc)) + return RTMsgErrorExitFailure(Disk::tr("Cannot open replacement value file '%s': %Rrc"), pszValue, vrc); + + uint64_t cbValue = 0; + vrc = RTFileQuerySize(hValueFile, &cbValue); + if (RT_SUCCESS(vrc)) + { + if (cbValue <= _16M) + { + rNewProp.m_cbValue = (size_t)cbValue; + rNewProp.m_pszValue = rNewProp.m_pszFreeValue = (char *)RTMemAlloc(rNewProp.m_cbValue + 1); + if (rNewProp.m_pszFreeValue) + { + vrc = RTFileReadAt(hValueFile, 0, rNewProp.m_pszFreeValue, cbValue, NULL); + if (RT_SUCCESS(vrc)) + rNewProp.m_pszFreeValue[rNewProp.m_cbValue] = '\0'; + else + RTMsgError(Disk::tr("Error reading replacement MBR file '%s': %Rrc"), pszValue, vrc); + } + else + vrc = RTMsgErrorRc(VERR_NO_MEMORY, Disk::tr("Out of memory reading '%s': %Rrc"), pszValue, vrc); + } + else + vrc = RTMsgErrorRc(VERR_OUT_OF_RANGE, + Disk::tr("Replacement value file '%s' is to big: %Rhcb, max 16MiB"), + pszValue, cbValue); + } + else + RTMsgError(Disk::tr("Cannot get the size of the value file '%s': %Rrc"), pszValue, vrc); + RTFileClose(hValueFile); + if (RT_FAILURE(vrc)) + return RTEXITCODE_FAILURE; + } + 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(Disk::tr("Invalid medium variant '%s'"), ValueUnion.psz); + break; + + case VINF_GETOPT_NOT_OPTION: + return errorSyntax(Disk::tr("Invalid parameter '%s'"), ValueUnion.psz); + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(Disk::tr("Invalid option -%c"), c); + else + return errorSyntax(Disk::tr("Invalid option case %i"), c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Disk::tr("unknown option: %s\n"), ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(Disk::tr("error: %Rrs"), c); + } + } + + /* check the outcome */ + if (cmd == CMD_NONE) + cmd = CMD_DISK; + ComPtr pParentMedium; + if (fBase) + { + if (!filename || !*filename) + return errorSyntax(Disk::tr("Parameter --filename is required")); + if ((enmMediumVariant & MediumVariant_VmdkRawDisk) == 0 && size == 0) + return errorSyntax(Disk::tr("Parameter --size is 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; + } + } + if ((enmMediumVariant & MediumVariant_VmdkRawDisk) && strcmp(format, "VMDK")) + return errorSyntax(Disk::tr("Variant 'Rawdisk' requires '--format=VMDK'")); + } + else + { + if ( !filename + || !*filename) + return errorSyntax(Disk::tr("Parameter --filename is required")); + size = 0; + if (cmd != CMD_DISK) + return errorSyntax(Disk::tr("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; + } + hrc = openMedium(a, diffparent, DeviceType_HardDisk, + AccessMode_ReadWrite, pParentMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + if (pParentMedium.isNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Disk::tr("Invalid parent hard disk reference, avoiding crash")); + MediumState_T state; + CHECK_ERROR(pParentMedium, COMGETTER(State)(&state)); + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + if (state == MediumState_Inaccessible) + { + CHECK_ERROR(pParentMedium, RefreshState(&state)); + if (FAILED(hrc)) + 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) + hrc = createMedium(a, format, filename, DeviceType_HardDisk, + AccessMode_ReadWrite, pMedium); + else if (cmd == CMD_DVD) + hrc = createMedium(a, format, filename, DeviceType_DVD, + AccessMode_ReadOnly, pMedium); + else if (cmd == CMD_FLOPPY) + hrc = createMedium(a, format, filename, DeviceType_Floppy, + AccessMode_ReadWrite, pMedium); + else + hrc = E_INVALIDARG; /* cannot happen but make gcc happy */ + + + if (SUCCEEDED(hrc) && pMedium) + { + if (lstProperties.size() > 0) + { + ComPtr pMediumFormat; + CHECK_ERROR2I_RET(pMedium, COMGETTER(MediumFormat)(pMediumFormat.asOutParam()), RTEXITCODE_FAILURE); + com::SafeArray propertyNames; + com::SafeArray propertyDescriptions; + com::SafeArray propertyTypes; + com::SafeArray propertyFlags; + com::SafeArray propertyDefaults; + CHECK_ERROR2I_RET(pMediumFormat, + DescribeProperties(ComSafeArrayAsOutParam(propertyNames), + ComSafeArrayAsOutParam(propertyDescriptions), + ComSafeArrayAsOutParam(propertyTypes), + ComSafeArrayAsOutParam(propertyFlags), + ComSafeArrayAsOutParam(propertyDefaults)), + RTEXITCODE_FAILURE); + + for (std::list::iterator it = lstProperties.begin(); + it != lstProperties.end(); + ++it) + { + const char * const pszKey = it->m_pszKey; + bool fBinary = true; + bool fPropertyFound = false; + for (size_t i = 0; i < propertyNames.size(); ++i) + if (RTUtf16CmpUtf8(propertyNames[i], pszKey) == 0) + { + fBinary = propertyTypes[i] == DataType_Int8; + fPropertyFound = true; + break; + } + if (!fPropertyFound) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + Disk::tr("Property '%s' was not found in the list of medium properties for the requested medium format (%s)."), + pszKey, format); + if (!fBinary) + CHECK_ERROR2I_RET(pMedium, SetProperty(Bstr(pszKey).raw(), Bstr(it->m_pszValue).raw()), + RTEXITCODE_FAILURE); + else + { + com::Bstr bstrBase64Value; + hrc = bstrBase64Value.base64Encode(it->m_pszValue, it->m_cbValue); + if (FAILED(hrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Disk::tr("Base64 encoding of the property %s failed. (%Rhrc)"), + pszKey, hrc); + CHECK_ERROR2I_RET(pMedium, SetProperty(Bstr(pszKey).raw(), bstrBase64Value.raw()), RTEXITCODE_FAILURE); + } + } + } + + 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(Disk::tr("Only one command can be specified: '%s'"), ValueUnion.psz); + cmd = CMD_DISK; + break; + + case 'D': // DVD + if (cmd != CMD_NONE) + return errorSyntax(Disk::tr("Only one command can be specified: '%s'"), ValueUnion.psz); + cmd = CMD_DVD; + break; + + case 'L': // floppy + if (cmd != CMD_NONE) + return errorSyntax(Disk::tr("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(Disk::tr("Invalid medium type '%s'"), ValueUnion.psz); + fModifyMediumType = true; + break; + + case 'z': // --autoreset + vrc = parseBool(ValueUnion.psz, &AutoReset); + if (RT_FAILURE(vrc)) + return errorArgument(Disk::tr("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(Disk::tr("Invalid --property argument '%s'"), ValueUnion.psz); + hrc = E_FAIL; + } + RTStrFree(pszProperty); + } + else + { + RTStrmPrintf(g_pStdErr, Disk::tr("Error: Failed to allocate memory for medium property '%s'\n"), + ValueUnion.psz); + hrc = 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(Disk::tr("Invalid parameter '%s'"), ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(Disk::tr("Invalid option -%c"), c); + else + return errorSyntax(Disk::tr("Invalid option case %i"), c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Disk::tr("unknown option: %s\n"), ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(Disk::tr("error: %Rrs"), c); + } + } + + if (cmd == CMD_NONE) + cmd = CMD_DISK; + + if (!pszFilenameOrUuid) + return errorSyntax(Disk::tr("Medium name or UUID required")); + + if (!fModifyMediumType + && !fModifyAutoReset + && !fModifyProperties + && !fModifyCompact + && !fModifyResize + && !fMoveMedium + && !fSetNewLocation + && !fModifyDescription + ) + return errorSyntax(Disk::tr("No operation specified")); + + /* Always open the medium if necessary, there is no other way. */ + if (cmd == CMD_DISK) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_DVD, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_Floppy, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else + hrc = E_INVALIDARG; /* cannot happen but make gcc happy */ + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + if (pMedium.isNull()) + { + RTMsgError(Disk::tr("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(Disk::tr("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(hrc)) + hrc = showProgress(pProgress); + if (FAILED(hrc)) + { + if (hrc == E_NOTIMPL) + RTMsgError(Disk::tr("Compact medium operation is not implemented!")); + else if (hrc == VBOX_E_NOT_SUPPORTED) + RTMsgError(Disk::tr("Compact medium operation for this format is not implemented yet!")); + else if (!pProgress.isNull()) + CHECK_PROGRESS_ERROR(pProgress, (Disk::tr("Failed to compact medium"))); + else + RTMsgError(Disk::tr("Failed to compact medium!")); + } + } + + if (fModifyResize) + { + ComPtr pProgress; + CHECK_ERROR(pMedium, Resize(cbResize, pProgress.asOutParam())); + if (SUCCEEDED(hrc)) + hrc = showProgress(pProgress); + if (FAILED(hrc)) + { + if (!pProgress.isNull()) + CHECK_PROGRESS_ERROR(pProgress, (Disk::tr("Failed to resize medium"))); + else if (hrc == E_NOTIMPL) + RTMsgError(Disk::tr("Resize medium operation is not implemented!")); + else if (hrc == VBOX_E_NOT_SUPPORTED) + RTMsgError(Disk::tr("Resize medium operation for this format is not implemented yet!")); + else + RTMsgError(Disk::tr("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(hrc) && !pProgress.isNull()) + { + hrc = showProgress(pProgress); + CHECK_PROGRESS_ERROR(pProgress, (Disk::tr("Failed to move medium"))); + } + + Bstr uuid; + CHECK_ERROR_BREAK(pMedium, COMGETTER(Id)(uuid.asOutParam())); + + RTPrintf(Disk::tr("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(Disk::tr("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(Disk::tr("Medium description has been changed.\n")); + } + + return SUCCEEDED(hrc) ? 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 }, + { "--resize", 'r', RTGETOPT_REQ_UINT64 }, +}; + +RTEXITCODE handleCloneMedium(HandlerArg *a) +{ + HRESULT hrc; + 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; + bool fNeedResize = false; + uint64_t cbResize = 0; + + 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(Disk::tr("Only one command can be specified: '%s'"), ValueUnion.psz); + cmd = CMD_DISK; + break; + + case 'D': // DVD + if (cmd != CMD_NONE) + return errorSyntax(Disk::tr("Only one command can be specified: '%s'"), ValueUnion.psz); + cmd = CMD_DVD; + break; + + case 'f': // floppy + if (cmd != CMD_NONE) + return errorSyntax(Disk::tr("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(Disk::tr("Invalid medium variant '%s'"), ValueUnion.psz); + break; + + case 'r': // --resize + fNeedResize = true; + cbResize = ValueUnion.u64 * _1M; + break; + + case VINF_GETOPT_NOT_OPTION: + if (!pszSrc) + pszSrc = ValueUnion.psz; + else if (!pszDst) + pszDst = ValueUnion.psz; + else + return errorSyntax(Disk::tr("Invalid parameter '%s'"), ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_GRAPH(c)) + return errorSyntax(Disk::tr("unhandled option: -%c"), c); + else + return errorSyntax(Disk::tr("unhandled option: %i"), c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Disk::tr("unknown option: %s"), ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(Disk::tr("error: %Rrs"), c); + } + } + + if (cmd == CMD_NONE) + cmd = CMD_DISK; + if (!pszSrc) + return errorSyntax(Disk::tr("Mandatory UUID or input file parameter missing")); + if (!pszDst) + return errorSyntax(Disk::tr("Mandatory output file parameter missing")); + if (fExisting && (!format.isEmpty() || enmMediumVariant != MediumVariant_Standard)) + return errorSyntax(Disk::tr("Specified options which cannot be used with --existing")); + + ComPtr pSrcMedium; + ComPtr pDstMedium; + + if (cmd == CMD_DISK) + hrc = openMedium(a, pszSrc, DeviceType_HardDisk, AccessMode_ReadOnly, pSrcMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + hrc = openMedium(a, pszSrc, DeviceType_DVD, AccessMode_ReadOnly, pSrcMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + hrc = openMedium(a, pszSrc, DeviceType_Floppy, AccessMode_ReadOnly, pSrcMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else + hrc = E_INVALIDARG; /* cannot happen but make gcc happy */ + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + + do + { + /* open/create destination medium */ + if (fExisting) + { + if (cmd == CMD_DISK) + hrc = openMedium(a, pszDst, DeviceType_HardDisk, AccessMode_ReadWrite, pDstMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + hrc = openMedium(a, pszDst, DeviceType_DVD, AccessMode_ReadOnly, pDstMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + hrc = openMedium(a, pszDst, DeviceType_Floppy, AccessMode_ReadWrite, pDstMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (FAILED(hrc)) + 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) + hrc = createMedium(a, strFormat.c_str(), pszDst, DeviceType_HardDisk, + AccessMode_ReadWrite, pDstMedium); + else if (cmd == CMD_DVD) + hrc = createMedium(a, strFormat.c_str(), pszDst, DeviceType_DVD, + AccessMode_ReadOnly, pDstMedium); + else if (cmd == CMD_FLOPPY) + hrc = createMedium(a, strFormat.c_str(), pszDst, DeviceType_Floppy, + AccessMode_ReadWrite, pDstMedium); + if (FAILED(hrc)) + 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); + AssertRCReturn(vrc, RTEXITCODE_FAILURE); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'u': // --uuid + if (RT_FAILURE(RTUuidFromStr(&uuid, ValueUnion.psz))) + return errorSyntax(Disk::tr("Invalid UUID '%s'"), ValueUnion.psz); + pUuid = &uuid; + break; + case 'o': // --format + format = ValueUnion.psz; + break; + + case 'm': // --variant + { + MediumVariant_T enmMediumVariant = MediumVariant_Standard; + vrc = parseMediumVariant(ValueUnion.psz, &enmMediumVariant); + if (RT_FAILURE(vrc)) + return errorArgument(Disk::tr("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(Disk::tr("Invalid parameter '%s'"), ValueUnion.psz); + break; + + default: + return errorGetOpt(c, &ValueUnion); + } + } + + if (!srcfilename || !dstfilename || (fReadFromStdIn && !filesize)) + return errorSyntax(Disk::tr("Incorrect number of parameters")); + RTStrmPrintf(g_pStdErr, Disk::tr("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; + + vrc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR, + NULL, sizeof(VDINTERFACEERROR), &pVDIfs); + AssertRC(vrc); + + /* open raw image file. */ + RTFILE File; + if (fReadFromStdIn) + vrc = RTFileFromNative(&File, RTFILE_NATIVE_STDIN); + else + vrc = RTFileOpen(&File, srcfilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_FAILURE(vrc)) + { + RTMsgError(Disk::tr("Cannot open file \"%s\": %Rrc"), srcfilename, vrc); + goto out; + } + + uint64_t cbFile; + /* get image size. */ + if (fReadFromStdIn) + cbFile = RTStrToUInt64(filesize); + else + vrc = RTFileQuerySize(File, &cbFile); + if (RT_FAILURE(vrc)) + { + RTMsgError(Disk::tr("Cannot get image size for file \"%s\": %Rrc"), srcfilename, vrc); + goto out; + } + + RTStrmPrintf(g_pStdErr, Disk::tr("Creating %s image with size %RU64 bytes (%RU64MB)...\n", "", cbFile), + (uImageFlags & VD_IMAGE_FLAGS_FIXED) ? Disk::tr("fixed", "adjective") : Disk::tr("dynamic", "adjective"), + cbFile, (cbFile + _1M - 1) / _1M); + char pszComment[256]; + RTStrPrintf(pszComment, sizeof(pszComment), Disk::tr("Converted image from %s"), srcfilename); + vrc = VDCreate(pVDIfs, VDTYPE_HDD, &pDisk); + if (RT_FAILURE(vrc)) + { + RTMsgError(Disk::tr("Cannot create the virtual disk container: %Rrc"), vrc); + 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; + vrc = VDCreateBase(pDisk, format, dstfilename, cbFile, + uImageFlags, pszComment, &PCHS, &LCHS, pUuid, + VD_OPEN_FLAGS_NORMAL, NULL, NULL); + if (RT_FAILURE(vrc)) + { + RTMsgError(Disk::tr("Cannot create the disk image \"%s\": %Rrc"), dstfilename, vrc); + goto out; + } + + size_t cbBuffer; + cbBuffer = _1M; + pvBuf = RTMemAlloc(cbBuffer); + if (!pvBuf) + { + vrc = VERR_NO_MEMORY; + RTMsgError(Disk::tr("Out of memory allocating buffers for image \"%s\": %Rrc"), dstfilename, vrc); + 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); + vrc = RTFileRead(File, pvBuf, cbToRead, &cbRead); + if (RT_FAILURE(vrc) || !cbRead) + break; + vrc = VDWrite(pDisk, offFile, pvBuf, cbRead); + if (RT_FAILURE(vrc)) + { + RTMsgError(Disk::tr("Failed to write to disk image \"%s\": %Rrc"), dstfilename, vrc); + goto out; + } + offFile += cbRead; + } + +out: + if (pvBuf) + RTMemFree(pvBuf); + if (pDisk) + VDClose(pDisk, RT_FAILURE(vrc)); + if (File != NIL_RTFILE) + RTFileClose(File); + + return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +HRESULT showMediumInfo(const ComPtr &pVirtualBox, + const ComPtr &pMedium, + const char *pszParentUUID, + bool fOptLong) +{ + HRESULT hrc = S_OK; + do + { + Bstr uuid; + pMedium->COMGETTER(Id)(uuid.asOutParam()); + RTPrintf("UUID: %ls\n", uuid.raw()); + if (pszParentUUID) + RTPrintf(Disk::tr("Parent UUID: %s\n"), pszParentUUID); + + /* check for accessibility */ + MediumState_T enmState; + CHECK_ERROR_BREAK(pMedium, RefreshState(&enmState)); + const char *pszState = Disk::tr("unknown"); + switch (enmState) + { + case MediumState_NotCreated: + pszState = Disk::tr("not created"); + break; + case MediumState_Created: + pszState = Disk::tr("created"); + break; + case MediumState_LockedRead: + pszState = Disk::tr("locked read"); + break; + case MediumState_LockedWrite: + pszState = Disk::tr("locked write"); + break; + case MediumState_Inaccessible: + pszState = Disk::tr("inaccessible"); + break; + case MediumState_Creating: + pszState = Disk::tr("creating"); + break; + case MediumState_Deleting: + pszState = Disk::tr("deleting"); + break; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case MediumState_32BitHack: break; /* Shut up compiler warnings. */ +#endif + } + RTPrintf(Disk::tr("State: %s\n"), pszState); + + if (fOptLong && enmState == MediumState_Inaccessible) + { + Bstr err; + CHECK_ERROR_BREAK(pMedium, COMGETTER(LastAccessError)(err.asOutParam())); + RTPrintf(Disk::tr("Access Error: %ls\n"), err.raw()); + } + + if (fOptLong) + { + Bstr description; + pMedium->COMGETTER(Description)(description.asOutParam()); + if (!description.isEmpty()) + RTPrintf(Disk::tr("Description: %ls\n"), description.raw()); + } + + MediumType_T type; + pMedium->COMGETTER(Type)(&type); + const char *typeStr = Disk::tr("unknown"); + switch (type) + { + case MediumType_Normal: + if (pszParentUUID && Guid(pszParentUUID).isValid()) + typeStr = Disk::tr("normal (differencing)"); + else + typeStr = Disk::tr("normal (base)"); + break; + case MediumType_Immutable: + typeStr = Disk::tr("immutable"); + break; + case MediumType_Writethrough: + typeStr = Disk::tr("writethrough"); + break; + case MediumType_Shareable: + typeStr = Disk::tr("shareable"); + break; + case MediumType_Readonly: + typeStr = Disk::tr("readonly"); + break; + case MediumType_MultiAttach: + typeStr = Disk::tr("multiattach"); + break; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case MediumType_32BitHack: break; /* Shut up compiler warnings. */ +#endif + } + RTPrintf(Disk::tr("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(Disk::tr("Auto-Reset: %s\n"), autoReset ? Disk::tr("on") : Disk::tr("off")); + } + + Bstr loc; + pMedium->COMGETTER(Location)(loc.asOutParam()); + RTPrintf(Disk::tr("Location: %ls\n"), loc.raw()); + + Bstr format; + pMedium->COMGETTER(Format)(format.asOutParam()); + RTPrintf(Disk::tr("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 = Disk::tr("unknown"); + switch (variant & ~(MediumVariant_Fixed | MediumVariant_Diff)) + { + case MediumVariant_VmdkSplit2G: + variantStr = Disk::tr("split2G"); + break; + case MediumVariant_VmdkStreamOptimized: + variantStr = Disk::tr("streamOptimized"); + break; + case MediumVariant_VmdkESX: + variantStr = Disk::tr("ESX"); + break; + case MediumVariant_Standard: + variantStr = Disk::tr("default"); + break; + } + const char *variantTypeStr = Disk::tr("dynamic"); + if (variant & MediumVariant_Fixed) + variantTypeStr = Disk::tr("fixed"); + else if (variant & MediumVariant_Diff) + variantTypeStr = Disk::tr("differencing"); + RTPrintf(Disk::tr("Format variant: %s %s\n"), variantTypeStr, variantStr); + } + + LONG64 logicalSize; + pMedium->COMGETTER(LogicalSize)(&logicalSize); + RTPrintf(Disk::tr("Capacity: %lld MBytes\n"), logicalSize >> 20); + if (fOptLong) + { + LONG64 actualSize; + pMedium->COMGETTER(Size)(&actualSize); + RTPrintf(Disk::tr("Size on disk: %lld MBytes\n"), actualSize >> 20); + } + + Bstr strCipher; + Bstr strPasswordId; + HRESULT hrc2 = pMedium->GetEncryptionSettings(strCipher.asOutParam(), strPasswordId.asOutParam()); + if (SUCCEEDED(hrc2)) + { + RTPrintf(Disk::tr("Encryption: enabled\n")); + if (fOptLong) + { + RTPrintf(Disk::tr("Cipher: %ls\n"), strCipher.raw()); + RTPrintf(Disk::tr("Password ID: %ls\n"), strPasswordId.raw()); + } + } + else + RTPrintf(Disk::tr("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 ? Disk::tr("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 ? Disk::tr("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 ? Disk::tr("Child UUIDs: ") : " ", + childUUID.raw()); + fFirst = false; + } + } + } + } + while (0); + + return hrc; +} + +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(Disk::tr("Only one command can be specified: '%s'"), ValueUnion.psz); + cmd = CMD_DISK; + break; + + case 'D': // DVD + if (cmd != CMD_NONE) + return errorSyntax(Disk::tr("Only one command can be specified: '%s'"), ValueUnion.psz); + cmd = CMD_DVD; + break; + + case 'f': // floppy + if (cmd != CMD_NONE) + return errorSyntax(Disk::tr("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(Disk::tr("Invalid parameter '%s'"), ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(Disk::tr("Invalid option -%c"), c); + else + return errorSyntax(Disk::tr("Invalid option case %i"), c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Disk::tr("unknown option: %s\n"), ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(Disk::tr("error: %Rrs"), c); + } + } + + if (cmd == CMD_NONE) + cmd = CMD_DISK; + + /* check for required options */ + if (!pszFilenameOrUuid) + return errorSyntax(Disk::tr("Medium name or UUID required")); + + HRESULT hrc = S_OK; /* Prevents warning. */ + + ComPtr pMedium; + if (cmd == CMD_DISK) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_DVD, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_Floppy, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + + Utf8Str strParentUUID(Disk::tr("base")); + ComPtr pParent; + pMedium->COMGETTER(Parent)(pParent.asOutParam()); + if (!pParent.isNull()) + { + Bstr bstrParentUUID; + pParent->COMGETTER(Id)(bstrParentUUID.asOutParam()); + strParentUUID = bstrParentUUID; + } + + hrc = showMediumInfo(a->virtualBox, pMedium, strParentUUID.c_str(), true); + + return SUCCEEDED(hrc) ? 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 hrc = 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(Disk::tr("Only one command can be specified: '%s'"), ValueUnion.psz); + cmd = CMD_DISK; + break; + + case 'D': // DVD + if (cmd != CMD_NONE) + return errorSyntax(Disk::tr("Only one command can be specified: '%s'"), ValueUnion.psz); + cmd = CMD_DVD; + break; + + case 'f': // floppy + if (cmd != CMD_NONE) + return errorSyntax(Disk::tr("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(Disk::tr("Invalid parameter '%s'"), ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(Disk::tr("Invalid option -%c"), c); + else + return errorSyntax(Disk::tr("Invalid option case %i"), c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Disk::tr("unknown option: %s\n"), ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(Disk::tr("error: %Rrs"), c); + } + } + + /* check for required options */ + if (cmd == CMD_NONE) + cmd = CMD_DISK; + if (!pszFilenameOrUuid) + return errorSyntax(Disk::tr("Medium name or UUID required")); + + ComPtr pMedium; + if (cmd == CMD_DISK) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_DVD, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_Floppy, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + + if (SUCCEEDED(hrc) && pMedium) + { + if (fDelete) + { + ComPtr pProgress; + CHECK_ERROR(pMedium, DeleteStorage(pProgress.asOutParam())); + if (SUCCEEDED(hrc)) + { + hrc = showProgress(pProgress); + CHECK_PROGRESS_ERROR(pProgress, (Disk::tr("Failed to delete medium"))); + } + else + RTMsgError(Disk::tr("Failed to delete medium. Error code %Rhrc"), hrc); + } + CHECK_ERROR(pMedium, Close()); + } + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleMediumProperty(HandlerArg *a) +{ + HRESULT hrc = 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((Disk::tr("unexpected parameter %s\n"), pszCmd)); + cmd = CMD_DISK; + } + a->argv++; + a->argc--; + } + else + { + pszCmd = NULL; + cmd = CMD_DISK; + } + + if (a->argc == 0) + return errorSyntax(Disk::tr("Missing action")); + + pszAction = a->argv[0]; + if ( RTStrICmp(pszAction, "set") + && RTStrICmp(pszAction, "get") + && RTStrICmp(pszAction, "delete")) + return errorSyntax(Disk::tr("Invalid action given: %s"), pszAction); + + if ( ( !RTStrICmp(pszAction, "set") + && a->argc != 4) + || ( RTStrICmp(pszAction, "set") + && a->argc != 3)) + return errorSyntax(Disk::tr("Invalid number of arguments given for action: %s"), pszAction); + + pszFilenameOrUuid = a->argv[1]; + pszProperty = a->argv[2]; + + if (cmd == CMD_DISK) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_DVD) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_DVD, + AccessMode_ReadOnly, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + else if (cmd == CMD_FLOPPY) + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_Floppy, + AccessMode_ReadWrite, pMedium, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (SUCCEEDED(hrc) && !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")) + { + /* + * Trigger a call to Medium::i_queryInfo()->VDOpen()->pfnOpen() to + * open the virtual device and populate its properties for + * Medium::getProperty() to retrieve. + */ + MediumState_T state; + CHECK_ERROR(pMedium, RefreshState(&state)); + + Bstr strVal; + CHECK_ERROR(pMedium, GetProperty(Bstr(pszProperty).raw(), strVal.asOutParam())); + if (SUCCEEDED(hrc)) + RTPrintf("%s=%ls\n", pszProperty, strVal.raw()); + } + else if (!RTStrICmp(pszAction, "delete")) + { + CHECK_ERROR(pMedium, SetProperty(Bstr(pszProperty).raw(), Bstr().raw())); + /** @todo */ + } + } + + return SUCCEEDED(hrc) ? 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 hrc; + 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(Disk::tr("Invalid parameter '%s'"), ValueUnion.psz); + break; + + default: + if (c > 0) + { + if (RT_C_IS_PRINT(c)) + return errorSyntax(Disk::tr("Invalid option -%c"), c); + else + return errorSyntax(Disk::tr("Invalid option case %i"), c); + } + else if (c == VERR_GETOPT_UNKNOWN_OPTION) + return errorSyntax(Disk::tr("unknown option: %s\n"), ValueUnion.psz); + else if (ValueUnion.pDef) + return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c); + else + return errorSyntax(Disk::tr("error: %Rrs"), c); + } + } + + if (!pszFilenameOrUuid) + return errorSyntax(Disk::tr("Disk name or UUID required")); + + if (!pszPasswordNew && !pszPasswordOld) + return errorSyntax(Disk::tr("No password specified")); + + if ( (pszPasswordNew && !pszNewPasswordId) + || (!pszPasswordNew && pszNewPasswordId)) + return errorSyntax(Disk::tr("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, Disk::tr("Enter new password:")); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(pszPasswordNew, &strPasswordNew); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError(Disk::tr("Failed to read new password from file")); + return rcExit; + } + } + } + + if (pszPasswordOld) + { + if (!RTStrCmp(pszPasswordOld, "-")) + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPasswordOld, Disk::tr("Enter old password:")); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(pszPasswordOld, &strPasswordOld); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError(Disk::tr("Failed to read old password from file")); + return rcExit; + } + } + } + + /* Always open the medium if necessary, there is no other way. */ + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadWrite, hardDisk, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + if (hardDisk.isNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Disk::tr("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(hrc)) + hrc = showProgress(progress); + if (FAILED(hrc)) + { + if (hrc == E_NOTIMPL) + RTMsgError(Disk::tr("Encrypt hard disk operation is not implemented!")); + else if (hrc == VBOX_E_NOT_SUPPORTED) + RTMsgError(Disk::tr("Encrypt hard disk operation for this cipher is not implemented yet!")); + else if (!progress.isNull()) + CHECK_PROGRESS_ERROR(progress, (Disk::tr("Failed to encrypt hard disk"))); + else + RTMsgError(Disk::tr("Failed to encrypt hard disk!")); + } + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +RTEXITCODE handleCheckMediumPassword(HandlerArg *a) +{ + HRESULT hrc; + ComPtr hardDisk; + const char *pszFilenameOrUuid = NULL; + Utf8Str strPassword; + + if (a->argc != 2) + return errorSyntax(Disk::tr("Invalid number of arguments: %d"), a->argc); + + pszFilenameOrUuid = a->argv[0]; + + if (!RTStrCmp(a->argv[1], "-")) + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, Disk::tr("Enter password:")); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + RTEXITCODE rcExit = readPasswordFile(a->argv[1], &strPassword); + if (rcExit == RTEXITCODE_FAILURE) + { + RTMsgError(Disk::tr("Failed to read password from file")); + return rcExit; + } + } + + /* Always open the medium if necessary, there is no other way. */ + hrc = openMedium(a, pszFilenameOrUuid, DeviceType_HardDisk, + AccessMode_ReadWrite, hardDisk, + false /* fForceNewUuidOnOpen */, false /* fSilent */); + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + if (hardDisk.isNull()) + return RTMsgErrorExit(RTEXITCODE_FAILURE, Disk::tr("Invalid hard disk reference, avoiding crash")); + + CHECK_ERROR(hardDisk, CheckEncryptionPassword(Bstr(strPassword).raw())); + if (SUCCEEDED(hrc)) + RTPrintf(Disk::tr("The given password is correct\n")); + return SUCCEEDED(hrc) ? 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(Disk::tr("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, Disk::tr("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 vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0); + AssertRCReturn(vrc, RTEXITCODE_FAILURE); + RTGETOPTUNION ValueUnion; + while ((vrc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (vrc) + { + MEDIUMIOCOMMONOPT_CASES(pCommonOpts); + + case 'q': + fQuick = true; + break; + + default: + return errorGetOpt(vrc, &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 vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0); + AssertRCReturn(vrc, RTEXITCODE_FAILURE); + RTGETOPTUNION ValueUnion; + while ((vrc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (vrc) + { + 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(vrc, &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')) + { + vrc = RTStrmOpen(pszOutput, fHex ? "wt" : "wb", &pOut); + if (RT_FAILURE(vrc)) + rcExit = RTMsgErrorExitFailure(Disk::tr("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(Disk::tr("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), Disk::tr("Read(%zu bytes at %#RX64)", "", cbToRead), 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(); + if (!fHex) + vrc = RTStrmWrite(pOut, pbBuf, cbReturned); + else + { + /* hexdump -C */ + vrc = VINF_SUCCESS; + 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, Disk::tr("********** \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(Disk::tr("Error writing to '%s': %Rrc"), pszOutput, vrc); + break; + } + } + + /* Advance. */ + if (cbReturned != cbToRead) + { + rcExit = RTMsgErrorExitFailure(Disk::tr("Expected read() at offset %RU64 (%#RX64) to return %#zx bytes, only got %#zx!\n", + "", cbReturned), + off, off, cbReturned, cbToRead); + break; + } + off += cbReturned; + cb -= cbReturned; + } + + /* + * Close output. + */ + if (pOut != g_pStdOut) + { + vrc = RTStrmClose(pOut); + if (RT_FAILURE(vrc)) + rcExit = RTMsgErrorExitFailure(Disk::tr("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 vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), iFirst, 0); + AssertRCReturn(vrc, RTEXITCODE_FAILURE); + RTGETOPTUNION ValueUnion; + while ((vrc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (vrc) + { + MEDIUMIOCOMMONOPT_CASES(pCommonOpts); + + case 'O': + pszOutput = ValueUnion.psz; + break; + case 'F': + strFormat = ValueUnion.psz; + break; + case 'v': // --variant + { + vrc = parseMediumVariant(ValueUnion.psz, &enmMediumVariant); + if (RT_FAILURE(vrc)) + return errorArgument(Disk::tr("Invalid medium variant '%s'"), ValueUnion.psz); + break; + } + + default: + return errorGetOpt(vrc, &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')) + { + vrc = RTStrmOpen(pszOutput, "wb", &pOut); + if (RT_FAILURE(vrc)) + rcExit = RTMsgErrorExitFailure(Disk::tr("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(); + vrc = RTStrmWrite(pOut, pbBuf, cbReturned); + if (RT_FAILURE(vrc)) + { + rcExit = RTMsgErrorExitFailure(Disk::tr("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) + { + vrc = RTStrmClose(pOut); + if (RT_FAILURE(vrc)) + rcExit = RTMsgErrorExitFailure(Disk::tr("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 vrc = RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0); + AssertRCReturn(vrc, RTEXITCODE_FAILURE); + RTGETOPTUNION ValueUnion; + while ((vrc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (vrc) + { + 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(vrc, &ValueUnion); + } + } + return errorNoSubcommand(); +} -- cgit v1.2.3