summaryrefslogtreecommitdiffstats
path: root/src/VBox/Storage
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
commit16f504a9dca3fe3b70568f67b7d41241ae485288 (patch)
treec60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Storage
parentInitial commit. (diff)
downloadvirtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.tar.xz
virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.zip
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Storage')
-rw-r--r--src/VBox/Storage/.scm-settings29
-rw-r--r--src/VBox/Storage/CUE.cpp1971
-rw-r--r--src/VBox/Storage/DMG.cpp2393
-rw-r--r--src/VBox/Storage/ISCSI.cpp5553
-rw-r--r--src/VBox/Storage/Makefile.kmk90
-rw-r--r--src/VBox/Storage/Parallels.cpp1204
-rw-r--r--src/VBox/Storage/QCOW.cpp2572
-rw-r--r--src/VBox/Storage/QED.cpp2400
-rw-r--r--src/VBox/Storage/RAW.cpp1223
-rw-r--r--src/VBox/Storage/VCICache.cpp2054
-rw-r--r--src/VBox/Storage/VD.cpp9682
-rw-r--r--src/VBox/Storage/VDBackends.h62
-rw-r--r--src/VBox/Storage/VDBackendsInline.h125
-rw-r--r--src/VBox/Storage/VDI.cpp3270
-rw-r--r--src/VBox/Storage/VDICore.h645
-rw-r--r--src/VBox/Storage/VDIfTcpNet.cpp630
-rw-r--r--src/VBox/Storage/VDIfVfs.cpp410
-rw-r--r--src/VBox/Storage/VDIfVfs2.cpp346
-rw-r--r--src/VBox/Storage/VDInternal.h300
-rw-r--r--src/VBox/Storage/VDPlugin.cpp970
-rw-r--r--src/VBox/Storage/VDVfs.cpp800
-rw-r--r--src/VBox/Storage/VHD.cpp3179
-rw-r--r--src/VBox/Storage/VHDX.cpp2423
-rw-r--r--src/VBox/Storage/VISO.cpp1119
-rw-r--r--src/VBox/Storage/VMDK.cpp9249
-rw-r--r--src/VBox/Storage/testcase/BuiltinTests.h57
-rw-r--r--src/VBox/Storage/testcase/Makefile.kmk236
-rw-r--r--src/VBox/Storage/testcase/VDDefs.h52
-rw-r--r--src/VBox/Storage/testcase/VDIoBackend.cpp250
-rw-r--r--src/VBox/Storage/testcase/VDIoBackend.h106
-rw-r--r--src/VBox/Storage/testcase/VDIoBackendMem.cpp263
-rw-r--r--src/VBox/Storage/testcase/VDIoBackendMem.h92
-rw-r--r--src/VBox/Storage/testcase/VDIoRnd.cpp118
-rw-r--r--src/VBox/Storage/testcase/VDIoRnd.h69
-rw-r--r--src/VBox/Storage/testcase/VDMemDisk.cpp418
-rw-r--r--src/VBox/Storage/testcase/VDMemDisk.h143
-rw-r--r--src/VBox/Storage/testcase/VDScript.cpp3020
-rw-r--r--src/VBox/Storage/testcase/VDScript.h236
-rw-r--r--src/VBox/Storage/testcase/VDScriptAst.cpp368
-rw-r--r--src/VBox/Storage/testcase/VDScriptAst.h595
-rw-r--r--src/VBox/Storage/testcase/VDScriptChecker.cpp44
-rw-r--r--src/VBox/Storage/testcase/VDScriptInternal.h121
-rw-r--r--src/VBox/Storage/testcase/VDScriptInterp.cpp1073
-rw-r--r--src/VBox/Storage/testcase/VDScriptStack.h156
-rw-r--r--src/VBox/Storage/testcase/tstVD-2.cpp272
-rw-r--r--src/VBox/Storage/testcase/tstVD.cpp1080
-rw-r--r--src/VBox/Storage/testcase/tstVDCompact.vd102
-rw-r--r--src/VBox/Storage/testcase/tstVDCopy.vd100
-rw-r--r--src/VBox/Storage/testcase/tstVDDiscard.vd69
-rw-r--r--src/VBox/Storage/testcase/tstVDFill.cpp244
-rw-r--r--src/VBox/Storage/testcase/tstVDIo.cpp3024
-rw-r--r--src/VBox/Storage/testcase/tstVDIo.vd73
-rw-r--r--src/VBox/Storage/testcase/tstVDMultBackends.vd70
-rw-r--r--src/VBox/Storage/testcase/tstVDResize.vd79
-rw-r--r--src/VBox/Storage/testcase/tstVDShareable.vd66
-rw-r--r--src/VBox/Storage/testcase/tstVDSnap.cpp482
-rw-r--r--src/VBox/Storage/testcase/vbox-img.cpp2186
-rw-r--r--src/VBox/Storage/testcase/vbox-img.rc61
-rw-r--r--src/VBox/Storage/testcase/vdkeystoremgr.cpp288
-rw-r--r--src/VBox/Storage/testcase/vdkeystoremgr.rc61
60 files changed, 68373 insertions, 0 deletions
diff --git a/src/VBox/Storage/.scm-settings b/src/VBox/Storage/.scm-settings
new file mode 100644
index 00000000..371d18ea
--- /dev/null
+++ b/src/VBox/Storage/.scm-settings
@@ -0,0 +1,29 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for the storage library.
+#
+
+#
+# Copyright (C) 2017-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+/testcase/*.vd: --treat-as .c
+
diff --git a/src/VBox/Storage/CUE.cpp b/src/VBox/Storage/CUE.cpp
new file mode 100644
index 00000000..72e6f6f1
--- /dev/null
+++ b/src/VBox/Storage/CUE.cpp
@@ -0,0 +1,1971 @@
+/* $Id: CUE.cpp $ */
+/** @file
+ * CUE - CUE/BIN Disk image, Core Code.
+ */
+
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_CUE
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <VBox/scsiinline.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/alloc.h>
+#include <iprt/cdefs.h>
+#include <iprt/ctype.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#include "VDBackends.h"
+#include "VDBackendsInline.h"
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * CUE descriptor file token type.
+ */
+typedef enum CUETOKENTYPE
+{
+ /** Invalid token type. */
+ CUETOKENTYPE_INVALID = 0,
+ /** Reserved keyword. */
+ CUETOKENTYPE_KEYWORD,
+ /** String token. */
+ CUETOKENTYPE_STRING,
+ /** Unsigned integer. */
+ CUETOKENTYPE_INTEGER_UNSIGNED,
+ /** MSF (mm:ss:ff) location token. */
+ CUETOKENTYPE_MSF,
+ /** Error token (unexpected character found). */
+ CUETOKENTYPE_ERROR,
+ /** End of stream token. */
+ CUETOKENTYPE_EOS
+} CUETOKENTYPE;
+
+/**
+ * CUE reservered keyword type.
+ */
+typedef enum CUEKEYWORD
+{
+ /** Invalid keyword. */
+ CUEKEYWORD_INVALID = 0,
+ /** FILE. */
+ CUEKEYWORD_FILE,
+ /** BINARY */
+ CUEKEYWORD_BINARY,
+ /** MOTOROLA */
+ CUEKEYWORD_MOTOROLA,
+ /** WAVE */
+ CUEKEYWORD_WAVE,
+ /** MP3 */
+ CUEKEYWORD_MP3,
+ /** AIFF */
+ CUEKEYWORD_AIFF,
+ /** CATALOG */
+ CUEKEYWORD_CATALOG,
+ CUEKEYWORD_CDTEXTFILE,
+ CUEKEYWORD_FLAGS,
+ CUEKEYWORD_INDEX,
+ CUEKEYWORD_ISRC,
+ CUEKEYWORD_PERFORMER,
+ CUEKEYWORD_POSTGAP,
+ CUEKEYWORD_PREGAP,
+ CUEKEYWORD_SONGWRITER,
+ CUEKEYWORD_TITLE,
+ CUEKEYWORD_TRACK,
+ CUEKEYWORD_MODE1_2048,
+ CUEKEYWORD_MODE1_2352,
+ CUEKEYWORD_MODE2_2352,
+ CUEKEYWORD_AUDIO,
+ CUEKEYWORD_REM
+} CUEKEYWORD;
+
+/**
+ * CUE sheet token.
+ */
+typedef struct CUETOKEN
+{
+ /** The token type. */
+ CUETOKENTYPE enmType;
+ /** Token type dependent data. */
+ union
+ {
+ /** Keyword token. */
+ struct
+ {
+ /** The keyword enumerator. */
+ CUEKEYWORD enmKeyword;
+ } Keyword;
+ /** String token (without quotation marks). */
+ struct
+ {
+ /** Pointer to the start of the string. */
+ const char *psz;
+ /** Number of characters for the string excluding the null terminator. */
+ size_t cch;
+ } String;
+ /** Integer token. */
+ struct
+ {
+ /** Numerical constant. */
+ uint64_t u64;
+ } Int;
+ /** MSF location token. */
+ struct
+ {
+ /** Minute part. */
+ uint8_t u8Minute;
+ /** Second part. */
+ uint8_t u8Second;
+ /** Frame part. */
+ uint8_t u8Frame;
+ } Msf;
+ } Type;
+} CUETOKEN;
+/** Pointer to a CUE sheet token. */
+typedef CUETOKEN *PCUETOKEN;
+/** Pointer to a const CUE sheet token. */
+typedef const CUETOKEN *PCCUETOKEN;
+
+/**
+ * CUE tokenizer state.
+ */
+typedef struct CUETOKENIZER
+{
+ /** Char buffer to read from. */
+ const char *pszInput;
+ /** Token 1. */
+ CUETOKEN Token1;
+ /** Token 2. */
+ CUETOKEN Token2;
+ /** Pointer to the current active token. */
+ PCUETOKEN pTokenCurr;
+ /** The next token in the input stream (used for peeking). */
+ PCUETOKEN pTokenNext;
+} CUETOKENIZER;
+/** Pointer to a CUE tokenizer state. */
+typedef CUETOKENIZER *PCUETOKENIZER;
+
+/**
+ * CUE keyword entry.
+ */
+typedef struct CUEKEYWORDDESC
+{
+ /** Keyword string. */
+ const char *pszKeyword;
+ /** Size of the string in characters without zero terminator. */
+ size_t cchKeyword;
+ /** Keyword type. */
+ CUEKEYWORD enmKeyword;
+} CUEKEYWORDDESC;
+/** Pointer to a CUE keyword entry. */
+typedef CUEKEYWORDDESC *PCUEKEYWORDDESC;
+/** Pointer to a const CUE keyword entry. */
+typedef const CUEKEYWORDDESC *PCCUEKEYWORDDESC;
+
+/**
+ * CUE image data structure.
+ */
+typedef struct CUEIMAGE
+{
+ /** Image name. */
+ const char *pszFilename;
+ /** Storage handle. */
+ PVDIOSTORAGE pStorage;
+ /** The backing file containing the actual data. */
+ char *pszDataFilename;
+ /** Storage handle for the backing file. */
+ PVDIOSTORAGE pStorageData;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+
+ /** Open flags passed by VD layer. */
+ unsigned uOpenFlags;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Maximum number of tracks the region list can hold. */
+ uint32_t cTracksMax;
+ /** Pointer to our internal region list. */
+ PVDREGIONLIST pRegionList;
+ /** Flag whether the backing file is little (BINARY) or big (MOTOROLA) endian. */
+ bool fLittleEndian;
+} CUEIMAGE, *PCUEIMAGE;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aCueFileExtensions[] =
+{
+ {"cue", VDTYPE_OPTICAL_DISC},
+ {NULL, VDTYPE_INVALID}
+};
+
+/**
+ * Known keywords.
+ */
+static const CUEKEYWORDDESC g_aCueKeywords[] =
+{
+ {RT_STR_TUPLE("FILE"), CUEKEYWORD_FILE},
+ {RT_STR_TUPLE("BINARY"), CUEKEYWORD_BINARY},
+ {RT_STR_TUPLE("MOTOROLA"), CUEKEYWORD_MOTOROLA},
+ {RT_STR_TUPLE("WAVE"), CUEKEYWORD_WAVE},
+ {RT_STR_TUPLE("MP3"), CUEKEYWORD_MP3},
+ {RT_STR_TUPLE("AIFF"), CUEKEYWORD_AIFF},
+ {RT_STR_TUPLE("CATALOG"), CUEKEYWORD_CATALOG},
+ {RT_STR_TUPLE("CDTEXTFILE"), CUEKEYWORD_CDTEXTFILE},
+ {RT_STR_TUPLE("FLAGS"), CUEKEYWORD_FLAGS},
+ {RT_STR_TUPLE("INDEX"), CUEKEYWORD_INDEX},
+ {RT_STR_TUPLE("ISRC"), CUEKEYWORD_ISRC},
+ {RT_STR_TUPLE("PERFORMER"), CUEKEYWORD_PERFORMER},
+ {RT_STR_TUPLE("POSTGAP"), CUEKEYWORD_POSTGAP},
+ {RT_STR_TUPLE("PREGAP"), CUEKEYWORD_PREGAP},
+ {RT_STR_TUPLE("SONGWRITER"), CUEKEYWORD_SONGWRITER},
+ {RT_STR_TUPLE("TITLE"), CUEKEYWORD_TITLE},
+ {RT_STR_TUPLE("TRACK"), CUEKEYWORD_TRACK},
+ {RT_STR_TUPLE("MODE1/2048"), CUEKEYWORD_MODE1_2048},
+ {RT_STR_TUPLE("MODE1/2352"), CUEKEYWORD_MODE1_2352},
+ {RT_STR_TUPLE("MODE2/2352"), CUEKEYWORD_MODE2_2352},
+ {RT_STR_TUPLE("AUDIO"), CUEKEYWORD_AUDIO},
+ {RT_STR_TUPLE("REM"), CUEKEYWORD_REM}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Converts a MSF formatted address value read from the given buffer
+ * to an LBA number. MSF 00:00:00 equals LBA 0.
+ *
+ * @returns The LBA number.
+ * @param pbBuf The buffer to read the MSF formatted address
+ * from.
+ */
+DECLINLINE(uint32_t) cueMSF2LBA(const uint8_t *pbBuf)
+{
+ return (pbBuf[0] * 60 + pbBuf[1]) * 75 + pbBuf[2];
+}
+
+/**
+ * Ensures that the region list can hold up to the given number of tracks.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param cTracksMax Maximum number of tracks.
+ */
+static int cueEnsureRegionListSize(PCUEIMAGE pThis, uint32_t cTracksMax)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pThis->cTracksMax < cTracksMax)
+ {
+ PVDREGIONLIST pRegionListNew = (PVDREGIONLIST)RTMemRealloc(pThis->pRegionList,
+ RT_UOFFSETOF_DYN(VDREGIONLIST, aRegions[cTracksMax]));
+ if (pRegionListNew)
+ {
+ /* Init all the new allocated tracks. */
+ for (uint32_t i = pThis->cTracksMax; i < cTracksMax; i++)
+ pRegionListNew->aRegions[i].offRegion = UINT64_MAX;
+ pThis->pRegionList = pRegionListNew;
+ pThis->cTracksMax = cTracksMax;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Returns whether the tokenizer reached the end of the stream.
+ *
+ * @returns true if the tokenizer reached the end of stream marker
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(bool) cueTokenizerIsEos(PCUETOKENIZER pTokenizer)
+{
+ return *pTokenizer->pszInput == '\0';
+}
+
+/**
+ * Skip one character in the input stream.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) cueTokenizerSkipCh(PCUETOKENIZER pTokenizer)
+{
+ /* Never ever go past EOS. */
+ if (!cueTokenizerIsEos(pTokenizer))
+ pTokenizer->pszInput++;
+}
+
+/**
+ * Returns the next char in the input buffer without advancing it.
+ *
+ * @returns Next character in the input buffer.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(char) cueTokenizerPeekCh(PCUETOKENIZER pTokenizer)
+{
+ return cueTokenizerIsEos(pTokenizer)
+ ? '\0'
+ : *(pTokenizer->pszInput + 1);
+}
+
+/**
+ * Returns the next character in the input buffer advancing the internal
+ * position.
+ *
+ * @returns Next character in the stream.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(char) cueTokenizerGetCh(PCUETOKENIZER pTokenizer)
+{
+ char ch;
+
+ if (cueTokenizerIsEos(pTokenizer))
+ ch = '\0';
+ else
+ ch = *pTokenizer->pszInput;
+
+ return ch;
+}
+
+/**
+ * Sets a new line for the tokenizer.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ * @param cSkip How many characters to skip.
+ */
+DECLINLINE(void) cueTokenizerNewLine(PCUETOKENIZER pTokenizer, unsigned cSkip)
+{
+ pTokenizer->pszInput += cSkip;
+}
+
+/**
+ * Checks whether the current position in the input stream is a new line
+ * and skips it.
+ *
+ * @returns Flag whether there was a new line at the current position
+ * in the input buffer.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(bool) cueTokenizerIsSkipNewLine(PCUETOKENIZER pTokenizer)
+{
+ bool fNewline = true;
+
+ if ( cueTokenizerGetCh(pTokenizer) == '\r'
+ && cueTokenizerPeekCh(pTokenizer) == '\n')
+ cueTokenizerNewLine(pTokenizer, 2);
+ else if (cueTokenizerGetCh(pTokenizer) == '\n')
+ cueTokenizerNewLine(pTokenizer, 1);
+ else
+ fNewline = false;
+
+ return fNewline;
+}
+
+/**
+ * Skip all whitespace starting from the current input buffer position.
+ * Skips all present comments too.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) cueTokenizerSkipWhitespace(PCUETOKENIZER pTokenizer)
+{
+ while (!cueTokenizerIsEos(pTokenizer))
+ {
+ while ( cueTokenizerGetCh(pTokenizer) == ' '
+ || cueTokenizerGetCh(pTokenizer) == '\t')
+ cueTokenizerSkipCh(pTokenizer);
+
+ if ( !cueTokenizerIsEos(pTokenizer)
+ && !cueTokenizerIsSkipNewLine(pTokenizer))
+ break; /* Skipped everything, next is some real content. */
+ }
+}
+
+/**
+ * Skips a multi line comment.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) cueTokenizerSkipComment(PCUETOKENIZER pTokenizer)
+{
+ while ( !cueTokenizerIsEos(pTokenizer)
+ && !cueTokenizerIsSkipNewLine(pTokenizer))
+ cueTokenizerSkipCh(pTokenizer);
+ cueTokenizerSkipWhitespace(pTokenizer);
+}
+
+/**
+ * Get an identifier token from the tokenizer.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void cueTokenizerGetKeyword(PCUETOKENIZER pTokenizer, PCUETOKEN pToken)
+{
+ char ch;
+ unsigned cchKeyword = 0;
+ bool fIsKeyword = false;
+ bool fIsComment;
+ const char *pszKeyword;
+
+ Assert(RT_C_IS_ALPHA(*pTokenizer->pszInput));
+
+ do
+ {
+ fIsComment = false;
+ pszKeyword = pTokenizer->pszInput;
+
+ do
+ {
+ cchKeyword++;
+ cueTokenizerSkipCh(pTokenizer);
+ ch = cueTokenizerGetCh(pTokenizer);
+ }
+ while (RT_C_IS_ALNUM(ch) || ch == '_' || ch == '/' || ch == '.');
+
+ /* Check whether we got a keyword or a string constant. */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aCueKeywords); i++)
+ {
+ if (!RTStrNCmp(g_aCueKeywords[i].pszKeyword, pszKeyword, RT_MIN(cchKeyword, g_aCueKeywords[i].cchKeyword)))
+ {
+ if (g_aCueKeywords[i].enmKeyword == CUEKEYWORD_REM)
+ {
+ /* The REM keyword is handled here as it indicates a comment which we just skip. */
+ cueTokenizerSkipComment(pTokenizer);
+ fIsComment = true;
+ }
+ else
+ {
+ fIsKeyword = true;
+ pToken->enmType = CUETOKENTYPE_KEYWORD;
+ pToken->Type.Keyword.enmKeyword = g_aCueKeywords[i].enmKeyword;
+ }
+ break;
+ }
+ }
+ } while (fIsComment);
+
+ /* Make it a string. */
+ if (ch == '\0')
+ pToken->enmType = CUETOKENTYPE_EOS;
+ else if (!fIsKeyword)
+ {
+ pToken->enmType = CUETOKENTYPE_STRING;
+ pToken->Type.String.psz = pszKeyword;
+ pToken->Type.String.cch = cchKeyword;
+ }
+}
+
+/**
+ * Get an integer value or MSF location indicator from the tokenizer.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void cueTokenizerGetIntegerOrMsf(PCUETOKENIZER pTokenizer, PCUETOKEN pToken)
+{
+ char szInt[20 + 1]; /* Maximum which fits into an unsigned 64bit integer + zero terminator. */
+ unsigned cchInt = 0;
+ bool fMsf = false;
+ char ch = cueTokenizerGetCh(pTokenizer);
+
+ Assert(RT_C_IS_DIGIT(ch));
+ RT_ZERO(szInt);
+
+ /* Go through the characters and check for the : mark which denotes MSF location indicator. */
+ do
+ {
+ szInt[cchInt++] = ch;
+ cueTokenizerSkipCh(pTokenizer);
+ ch = cueTokenizerGetCh(pTokenizer);
+ if (ch == ':')
+ fMsf = true;
+ }
+ while ( (RT_C_IS_DIGIT(ch) || ch == ':')
+ && cchInt < sizeof(szInt));
+
+ if (cchInt < sizeof(szInt) - 1)
+ {
+ if (fMsf)
+ {
+ /* Check that the format matches our expectations (mm:ss:ff). */
+ if ( cchInt == 8 && szInt[2] == ':' && szInt[5] == ':')
+ {
+ /* Parse the single fields. */
+ szInt[2] = '\0';
+ szInt[5] = '\0';
+
+ int rc = RTStrToUInt8Full(&szInt[0], 10, &pToken->Type.Msf.u8Minute);
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt8Full(&szInt[3], 10, &pToken->Type.Msf.u8Second);
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt8Full(&szInt[6], 10, &pToken->Type.Msf.u8Frame);
+ if (RT_SUCCESS(rc))
+ pToken->enmType = CUETOKENTYPE_MSF;
+ else
+ pToken->enmType = CUETOKENTYPE_ERROR;
+ }
+ else
+ pToken->enmType = CUETOKENTYPE_ERROR;
+ }
+ else
+ {
+ pToken->enmType = CUETOKENTYPE_INTEGER_UNSIGNED;
+ int rc = RTStrToUInt64Full(&szInt[0], 10, &pToken->Type.Int.u64);
+ if (RT_FAILURE(rc))
+ pToken->enmType = CUETOKENTYPE_ERROR;
+ }
+ }
+ else
+ pToken->enmType = CUETOKENTYPE_ERROR;
+}
+
+/**
+ * Parses a string constant.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ *
+ * @remarks: No escape sequences allowed at this time.
+ */
+static void cueTokenizerGetStringConst(PCUETOKENIZER pTokenizer, PCUETOKEN pToken)
+{
+ unsigned cchStr = 0;
+
+ Assert(cueTokenizerGetCh(pTokenizer) == '\"');
+ cueTokenizerSkipCh(pTokenizer); /* Skip " */
+
+ pToken->enmType = CUETOKENTYPE_STRING;
+ pToken->Type.String.psz = pTokenizer->pszInput;
+
+ while ( !cueTokenizerIsEos(pTokenizer)
+ && cueTokenizerGetCh(pTokenizer) != '\"')
+ {
+ cchStr++;
+ cueTokenizerSkipCh(pTokenizer);
+ }
+
+ /* End of stream without a closing quote is an error. */
+ if (RT_UNLIKELY(cueTokenizerIsEos(pTokenizer)))
+ pToken->enmType = CUETOKENTYPE_ERROR;
+ else
+ {
+ cueTokenizerSkipCh(pTokenizer); /* Skip closing " */
+ pToken->Type.String.cch = cchStr;
+ }
+}
+
+/**
+ * Get the end of stream token.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void cueTokenizerGetEos(PCUETOKENIZER pTokenizer, PCUETOKEN pToken)
+{
+ Assert(cueTokenizerGetCh(pTokenizer) == '\0'); RT_NOREF(pTokenizer);
+
+ pToken->enmType = CUETOKENTYPE_EOS;
+}
+
+/**
+ * Read the next token from the tokenizer stream.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer to read from.
+ * @param pToken Uninitialized token to fill the token data into.
+ */
+static void cueTokenizerReadNextToken(PCUETOKENIZER pTokenizer, PCUETOKEN pToken)
+{
+ /* Skip all eventually existing whitespace, newlines and comments first. */
+ cueTokenizerSkipWhitespace(pTokenizer);
+
+ char ch = cueTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_ALPHA(ch))
+ cueTokenizerGetKeyword(pTokenizer, pToken);
+ else if (RT_C_IS_DIGIT(ch))
+ cueTokenizerGetIntegerOrMsf(pTokenizer, pToken);
+ else if (ch == '\"')
+ cueTokenizerGetStringConst(pTokenizer, pToken);
+ else if (ch == '\0')
+ cueTokenizerGetEos(pTokenizer, pToken);
+ else
+ pToken->enmType = CUETOKENTYPE_ERROR;
+}
+
+/**
+ * Create a new tokenizer.
+ *
+ * @returns Pointer to the new tokenizer state on success.
+ * NULL if out of memory.
+ * @param pszInput The input to create the tokenizer for.
+ */
+static PCUETOKENIZER cueTokenizerCreate(const char *pszInput)
+{
+ PCUETOKENIZER pTokenizer = (PCUETOKENIZER)RTMemAllocZ(sizeof(CUETOKENIZER));
+ if (pTokenizer)
+ {
+ pTokenizer->pszInput = pszInput;
+ pTokenizer->pTokenCurr = &pTokenizer->Token1;
+ pTokenizer->pTokenNext = &pTokenizer->Token2;
+ /* Fill the tokenizer with two first tokens. */
+ cueTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenCurr);
+ cueTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
+ }
+
+ return pTokenizer;
+}
+
+/**
+ * Get the current token in the input stream.
+ *
+ * @returns Pointer to the next token in the stream.
+ * @param pTokenizer The tokenizer to destroy.
+ */
+DECLINLINE(PCCUETOKEN) cueTokenizerGetToken(PCUETOKENIZER pTokenizer)
+{
+ return pTokenizer->pTokenCurr;
+}
+
+/**
+ * Get the class of the current token.
+ *
+ * @returns Class of the current token.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(CUETOKENTYPE) cueTokenizerGetTokenType(PCUETOKENIZER pTokenizer)
+{
+ return pTokenizer->pTokenCurr->enmType;
+}
+
+/**
+ * Consume the current token advancing to the next in the stream.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ */
+static void cueTokenizerConsume(PCUETOKENIZER pTokenizer)
+{
+ PCUETOKEN pTokenTmp = pTokenizer->pTokenCurr;
+
+ /* Switch next token to current token and read in the next token. */
+ pTokenizer->pTokenCurr = pTokenizer->pTokenNext;
+ pTokenizer->pTokenNext = pTokenTmp;
+ cueTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
+}
+
+/**
+ * Check whether the next token in the input stream is a keyword and matches the given
+ * keyword.
+ *
+ * @returns true if the token matched.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param enmKeyword The keyword to check against.
+ */
+static bool cueTokenizerIsKeywordEqual(PCUETOKENIZER pTokenizer, CUEKEYWORD enmKeyword)
+{
+ PCCUETOKEN pToken = cueTokenizerGetToken(pTokenizer);
+
+ if ( pToken->enmType == CUETOKENTYPE_KEYWORD
+ && pToken->Type.Keyword.enmKeyword == enmKeyword)
+ return true;
+
+ return false;
+}
+
+/**
+ * Check whether the next token in the input stream is a keyword and matches the given
+ * keyword and skips it.
+ *
+ * @returns true if the token matched and was skipped.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param enmKeyword The keyword to check against.
+ */
+static bool cueTokenizerSkipIfIsKeywordEqual(PCUETOKENIZER pTokenizer, CUEKEYWORD enmKeyword)
+{
+ bool fEqual = cueTokenizerIsKeywordEqual(pTokenizer, enmKeyword);
+ if (fEqual)
+ cueTokenizerConsume(pTokenizer);
+
+ return fEqual;
+}
+
+/**
+ * Duplicates the string of the current token and consumes it.
+ *
+ * @returns VBox status code.
+ * @param pTokenizer The tokenizer state.
+ * @param ppszStr Where to store the pointer to the duplicated string on success.
+ * Free with RTStrFree().
+ */
+static int cueTokenizerConsumeStringDup(PCUETOKENIZER pTokenizer, char **ppszStr)
+{
+ int rc = VINF_SUCCESS;
+ Assert(cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_STRING);
+
+ *ppszStr = RTStrDupN(pTokenizer->pTokenCurr->Type.String.psz,
+ pTokenizer->pTokenCurr->Type.String.cch);
+ if (!*ppszStr)
+ rc = VERR_NO_STR_MEMORY;
+
+ cueTokenizerConsume(pTokenizer);
+ return rc;
+}
+
+/**
+ * Consumes an integer token returning the value.
+ *
+ * @returns Integer value in the token.
+ * @param pTokenizer The tokenizer state.
+ */
+static uint64_t cueTokenizerConsumeInteger(PCUETOKENIZER pTokenizer)
+{
+ uint64_t u64 = 0;
+ Assert(cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_INTEGER_UNSIGNED);
+
+ u64 = pTokenizer->pTokenCurr->Type.Int.u64;
+ cueTokenizerConsume(pTokenizer);
+ return u64;
+}
+
+/**
+ * Parses and skips the remaining string part of a directive.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param pTokenizer The tokenizer state.
+ * @param pszDirective The directive we skip the string part for.
+ */
+static int cueParseAndSkipStringRemainder(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer,
+ const char *pszDirective)
+{
+ int rc = VINF_SUCCESS;
+
+ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_STRING)
+ cueTokenizerConsume(pTokenizer);
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected string for %s directive"), pThis->pszFilename,
+ pszDirective);
+
+ return rc;
+}
+
+/**
+ * Parses and skips the remaining MSF part of a directive.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param pTokenizer The tokenizer state.
+ * @param pszDirective The directive we skip the string part for.
+ */
+static int cueParseAndSkipMsfRemainder(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer,
+ const char *pszDirective)
+{
+ int rc = VINF_SUCCESS;
+
+ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_MSF)
+ cueTokenizerConsume(pTokenizer);
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected MSF location for %s directive"), pThis->pszFilename,
+ pszDirective);
+
+ return rc;
+}
+
+/**
+ * Parses the remainder of a INDEX directive.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param pTokenizer The tokenizer state.
+ * @param pu8Index Where to store the parsed index number on success.
+ * @param pu64Lba Where to store the parsed positional information on success.
+ */
+static int cueParseIndex(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer,
+ uint8_t *pu8Index, uint64_t *pu64Lba)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * The index consists of the index number and positional information in MSF format.
+ */
+ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_INTEGER_UNSIGNED)
+ {
+ uint64_t u64Index = cueTokenizerConsumeInteger(pTokenizer);
+ if (u64Index <= 99)
+ {
+ /* Parse the position. */
+ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_MSF)
+ {
+ PCCUETOKEN pToken = cueTokenizerGetToken(pTokenizer);
+ uint8_t abMsf[3];
+ abMsf[0] = pToken->Type.Msf.u8Minute;
+ abMsf[1] = pToken->Type.Msf.u8Second;
+ abMsf[2] = pToken->Type.Msf.u8Frame;
+
+ *pu8Index = (uint8_t)u64Index;
+ *pu64Lba = cueMSF2LBA(&abMsf[0]);
+ cueTokenizerConsume(pTokenizer);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected MSF location"), pThis->pszFilename);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', index number must be between 01 and 99"), pThis->pszFilename);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected index number after INDEX directive"), pThis->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Parses the things coming below a TRACK directive.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param pTokenizer The tokenizer state.
+ * @param pu64LbaStart Where to store the starting LBA for this track on success.
+ */
+static int cueParseTrackNesting(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer, uint64_t *pu64LbaStart)
+{
+ int rc = VINF_SUCCESS;
+ bool fSeenInitialIndex = false;
+
+ do
+ {
+ if ( cueTokenizerIsKeywordEqual(pTokenizer, CUEKEYWORD_TRACK)
+ || cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_EOS)
+ break;
+
+ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_KEYWORD)
+ {
+ if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_TITLE))
+ rc = cueParseAndSkipStringRemainder(pThis, pTokenizer, "TITLE");
+ else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_PERFORMER))
+ rc = cueParseAndSkipStringRemainder(pThis, pTokenizer, "PERFORMER");
+ else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_PREGAP))
+ rc = cueParseAndSkipMsfRemainder(pThis, pTokenizer, "PREGAP");
+ else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_POSTGAP))
+ rc = cueParseAndSkipMsfRemainder(pThis, pTokenizer, "POSTGAP");
+ else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_INDEX))
+ {
+ uint8_t u8Index = 0;
+ uint64_t u64Lba = 0;
+ rc = cueParseIndex(pThis, pTokenizer, &u8Index, &u64Lba);
+ if ( RT_SUCCESS(rc)
+ && u8Index == 1)
+ {
+ if (!fSeenInitialIndex)
+ {
+ fSeenInitialIndex = true;
+ *pu64LbaStart = u64Lba;
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', multiple INDEX 01 directives"), pThis->pszFilename);
+ }
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', unexpected directive for TRACK found"), pThis->pszFilename);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected a CUE sheet keyword"), pThis->pszFilename);
+ }
+ while (RT_SUCCESS(rc));
+
+ if ( RT_SUCCESS(rc)
+ && !fSeenInitialIndex)
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', no initial INDEX directive for this track"), pThis->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Parses the remainder of a TRACK directive.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param pTokenizer The tokenizer state.
+ */
+static int cueParseTrack(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * A track consists of the track number and data type followed by a list of indexes
+ * and other metadata like title and performer we don't care about.
+ */
+ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_INTEGER_UNSIGNED)
+ {
+ uint64_t u64Track = cueTokenizerConsumeInteger(pTokenizer);
+ if (u64Track <= 99)
+ {
+ /* Parse the data mode. */
+ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_KEYWORD)
+ {
+ CUEKEYWORD enmDataMode = pTokenizer->pTokenCurr->Type.Keyword.enmKeyword;
+ if ( cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_AUDIO)
+ || cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_MODE1_2048)
+ || cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_MODE1_2352)
+ || cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_MODE2_2352))
+ {
+ /*
+ * Parse everything coming below the track (index points, etc.), we only need to find
+ * the starting point.
+ */
+ uint64_t uLbaStart = 0;
+ rc = cueParseTrackNesting(pThis, pTokenizer, &uLbaStart);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create a new region for this track. */
+ RT_NOREF1(enmDataMode);
+ rc = cueEnsureRegionListSize(pThis, u64Track);
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[u64Track - 1];
+ pRegion->offRegion = uLbaStart;
+ if (enmDataMode == CUEKEYWORD_MODE1_2352)
+ {
+ pRegion->cbBlock = 2352;
+ pRegion->enmDataForm = VDREGIONDATAFORM_MODE1_2352;
+ }
+ else if (enmDataMode == CUEKEYWORD_MODE2_2352)
+ {
+ pRegion->cbBlock = 2352;
+ pRegion->enmDataForm = VDREGIONDATAFORM_MODE2_2352;
+ }
+ else if (enmDataMode == CUEKEYWORD_AUDIO)
+ {
+ pRegion->cbBlock = 2352;
+ pRegion->enmDataForm = VDREGIONDATAFORM_CDDA;
+ }
+ else
+ {
+ pRegion->cbBlock = 2048;
+ pRegion->enmDataForm = VDREGIONDATAFORM_MODE1_2048;
+ }
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = pRegion->cbBlock;
+ pRegion->cbMetadata = 0;
+ }
+ else
+ rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
+ N_("CUE: Failed to allocate memory for the track list for '%s'"),
+ pThis->pszFilename);
+ }
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', the data mode is not supported"), pThis->pszFilename);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected data mode"), pThis->pszFilename);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', track number must be between 01 and 99"), pThis->pszFilename);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected track number after TRACK directive"), pThis->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Parses a list of tracks which must come after a FILE directive.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param pTokenizer The tokenizer state.
+ */
+static int cueParseTrackList(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Sometimes there is a TITLE/PERFORMER/SONGWRITER directive before the start of the track list,
+ * skip and ignore those.
+ */
+ while ( RT_SUCCESS(rc)
+ && ( cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_TITLE)
+ || cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_PERFORMER)
+ || cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_SONGWRITER)))
+ rc = cueParseAndSkipStringRemainder(pThis, pTokenizer, "TITLE/PERFORMER/SONGWRITER");
+
+ while ( RT_SUCCESS(rc)
+ && cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_TRACK))
+ rc = cueParseTrack(pThis, pTokenizer);
+
+ return rc;
+}
+
+/**
+ * Parses the remainder of a FILE directive.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param pTokenizer The tokenizer state.
+ */
+static int cueParseFile(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer)
+{
+ int rc = VINF_SUCCESS;
+
+ /* First must come a string constant followed by a keyword giving the file type. */
+ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_STRING)
+ {
+ rc = cueTokenizerConsumeStringDup(pTokenizer, &pThis->pszDataFilename);
+ if (RT_SUCCESS(rc))
+ {
+ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_KEYWORD)
+ {
+ if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_BINARY))
+ {
+ pThis->fLittleEndian = true;
+ rc = cueParseTrackList(pThis, pTokenizer);
+ }
+ else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_MOTOROLA))
+ {
+ pThis->fLittleEndian = false;
+ rc = cueParseTrackList(pThis, pTokenizer);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', the file type is not supported (only BINARY)"), pThis->pszFilename);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected file type"), pThis->pszFilename);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', failed to allocate memory for filename"), pThis->pszFilename);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected filename after FILE directive"), pThis->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Parses the keyword in the given tokenizer.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param pTokenizer The tokenizer state.
+ */
+static int cueParseKeyword(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer)
+{
+ int rc = VINF_SUCCESS;
+
+ if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_FILE))
+ rc = cueParseFile(pThis, pTokenizer);
+ else /* Skip all other keywords we don't need/support. */
+ cueTokenizerConsume(pTokenizer);
+
+ return rc;
+}
+
+
+/**
+ * Parses the CUE sheet from the given tokenizer.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param pTokenizer The tokenizer state.
+ */
+static int cueParseFromTokenizer(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%p\n", pThis));
+
+ /* We don't support multiple FILE directives for now. */
+ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_KEYWORD)
+ rc = cueParseKeyword(pThis, pTokenizer);
+ else
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected a keyword"), pThis->pszFilename);
+
+ if ( RT_SUCCESS(rc)
+ && cueTokenizerGetTokenType(pTokenizer) != CUETOKENTYPE_EOS)
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', expected end of stream"), pThis->pszFilename);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Finalizes the track list of the image.
+ *
+ * @returns VBox status code.
+ * @param pThis The CUE image state.
+ * @param cbImage Size of the image data in bytes.
+ */
+static int cueTrackListFinalize(PCUEIMAGE pThis, uint64_t cbImage)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( pThis->cTracksMax == 0
+ || pThis->pRegionList->aRegions[0].offRegion == UINT64_MAX)
+ return vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', detected empty track list"), pThis->pszFilename);
+
+ /*
+ * Fixup the track list to contain the proper sizes now that we parsed all tracks,
+ * check also that there are no gaps in the list.
+ */
+ uint32_t cTracks = 1;
+ uint64_t offDisk = 0;
+ for (uint32_t i = 1; i < pThis->cTracksMax; i++)
+ {
+ PVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[i];
+ PVDREGIONDESC pRegionPrev = &pThis->pRegionList->aRegions[i - 1];
+ if (pRegion->offRegion != UINT64_MAX)
+ {
+ cTracks++;
+ uint64_t cBlocks = pRegion->offRegion - (pRegionPrev->offRegion / pRegionPrev->cbBlock);
+ pRegionPrev->cRegionBlocksOrBytes = pRegionPrev->cbBlock * cBlocks;
+ offDisk += pRegionPrev->cRegionBlocksOrBytes;
+
+ if (cbImage < pRegionPrev->cRegionBlocksOrBytes)
+ return vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', image file is too small for track list"),
+ pThis->pszFilename);
+
+ cbImage -= pRegionPrev->cRegionBlocksOrBytes;
+ pRegion->offRegion = offDisk;
+ }
+ else
+ break;
+ }
+
+ /* Fixup last track. */
+ PVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[cTracks - 1];
+ pRegion->cRegionBlocksOrBytes = cbImage;
+
+ pThis->pRegionList->cRegions = cTracks;
+ pThis->pRegionList->fFlags = 0;
+
+ /* Check that there are no gaps in the track list. */
+ for (uint32_t i = cTracks; cTracks < pThis->cTracksMax; i++)
+ {
+ pRegion = &pThis->pRegionList->aRegions[i];
+ if (pRegion->offRegion != UINT64_MAX)
+ {
+ rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("CUE: Error parsing '%s', detected gaps in the track list"), pThis->pszFilename);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image except pThis,
+ * and optionally delete the image from disk.
+ */
+static int cueFreeImage(PCUEIMAGE pThis, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pThis)
+ {
+ if (pThis->pStorage)
+ {
+ rc = vdIfIoIntFileClose(pThis->pIfIo, pThis->pStorage);
+ pThis->pStorage = NULL;
+ }
+
+ if (pThis->pStorageData)
+ {
+ rc = vdIfIoIntFileClose(pThis->pIfIo, pThis->pStorageData);
+ pThis->pStorageData = NULL;
+ }
+
+ if (pThis->pRegionList)
+ {
+ RTMemFree(pThis->pRegionList);
+ pThis->pRegionList = NULL;
+ }
+
+ if (pThis->pszDataFilename)
+ {
+ RTStrFree(pThis->pszDataFilename);
+ pThis->pszDataFilename = NULL;
+ }
+
+ if (fDelete && pThis->pszFilename)
+ vdIfIoIntFileDelete(pThis->pIfIo, pThis->pszFilename);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int cueOpenImage(PCUEIMAGE pThis, unsigned uOpenFlags)
+{
+ pThis->uOpenFlags = uOpenFlags;
+
+ pThis->pIfError = VDIfErrorGet(pThis->pVDIfsDisk);
+ pThis->pIfIo = VDIfIoIntGet(pThis->pVDIfsImage);
+ AssertPtrReturn(pThis->pIfIo, VERR_INVALID_PARAMETER);
+
+ /* Open the image. */
+ int rc = vdIfIoIntFileOpen(pThis->pIfIo, pThis->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags,
+ false /* fCreate */),
+ &pThis->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile;
+ /* The descriptor file shouldn't be huge, so limit ourselfs to 16KB for now. */
+ rc = vdIfIoIntFileGetSize(pThis->pIfIo, pThis->pStorage, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile <= _16K - 1)
+ {
+ char szInput[_16K];
+ RT_ZERO(szInput);
+
+ rc = vdIfIoIntFileReadSync(pThis->pIfIo, pThis->pStorage, 0,
+ &szInput, cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrPurgeEncoding(&szInput[0]);
+ PCUETOKENIZER pTokenizer = cueTokenizerCreate(&szInput[0]);
+ if (pTokenizer)
+ {
+ rc = cueParseFromTokenizer(pThis, pTokenizer);
+ RTMemFree(pTokenizer);
+ if (RT_SUCCESS(rc))
+ {
+ /* Open the backing file. */
+ char szBackingFile[RTPATH_MAX];
+ rc = RTStrCopy(&szBackingFile[0], sizeof(szBackingFile), pThis->pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ RTPathStripFilename(&szBackingFile[0]);
+ rc = RTPathAppend(&szBackingFile[0], sizeof(szBackingFile), pThis->pszDataFilename);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIfIoIntFileOpen(pThis->pIfIo, szBackingFile,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags,
+ false /* fCreate */),
+ &pThis->pStorageData);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIfIoIntFileGetSize(pThis->pIfIo, pThis->pStorageData, &cbFile);
+ if (RT_SUCCESS(rc))
+ rc = cueTrackListFinalize(pThis, cbFile);
+ else
+ rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
+ N_("CUE: Unable to query size of backing file '%s'"),
+ szBackingFile);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
+ N_("CUE: Unable to open backing file '%s'"),
+ szBackingFile);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
+ N_("CUE: Error constructing backing filename from '%s'"),
+ pThis->pszFilename);
+ }
+ else
+ rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
+ N_("CUE: Error constructing backing filename from '%s'"),
+ pThis->pszFilename);
+ }
+ }
+ }
+ else
+ rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS, N_("CUE: Error reading '%s'"), pThis->pszFilename);
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdIfError(pThis->pIfError, VERR_VD_INVALID_SIZE,
+ RT_SRC_POS, N_("CUE: The descriptor file '%s' is too huge (%llu vs %llu)"),
+ pThis->pszFilename, cbFile, _16K - 1);
+ }
+ /* else: Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+
+ if (RT_FAILURE(rc))
+ cueFreeImage(pThis, false);
+ return rc;
+}
+
+/**
+ * Converts the data form enumeration to a string.
+ *
+ * @returns String name of the given data form.
+ * @param enmDataForm The data form.
+ */
+static const char *cueRegionDataFormStringify(VDREGIONDATAFORM enmDataForm)
+{
+ switch (enmDataForm)
+ {
+ #define DATAFORM2STR(tag) case VDREGIONDATAFORM_##tag: return #tag
+
+ DATAFORM2STR(INVALID);
+ DATAFORM2STR(RAW);
+ DATAFORM2STR(CDDA);
+ DATAFORM2STR(CDDA_PAUSE);
+ DATAFORM2STR(MODE1_2048);
+ DATAFORM2STR(MODE1_2352);
+ DATAFORM2STR(MODE1_0);
+ DATAFORM2STR(XA_2336);
+ DATAFORM2STR(XA_2352);
+ DATAFORM2STR(XA_0);
+ DATAFORM2STR(MODE2_2336);
+ DATAFORM2STR(MODE2_2352);
+ DATAFORM2STR(MODE2_0);
+
+ #undef DATAFORM2STR
+
+ default:
+ {
+ AssertMsgFailed(("Unknown data form %d! forgot to add it to the switch?\n", enmDataForm));
+ return "UNKNOWN!";
+ }
+ }
+}
+
+/**
+ * Converts the data form enumeration to a string.
+ *
+ * @returns String name of the given data form.
+ * @param enmMetadataForm The metadata form.
+ */
+static const char *cueRegionMetadataFormStringify(VDREGIONMETADATAFORM enmMetadataForm)
+{
+ switch (enmMetadataForm)
+ {
+ #define METADATAFORM2STR(tag) case VDREGIONMETADATAFORM_##tag: return #tag
+
+ METADATAFORM2STR(INVALID);
+ METADATAFORM2STR(RAW);
+ METADATAFORM2STR(NONE);
+
+ #undef METADATAFORM2STR
+
+ default:
+ {
+ AssertMsgFailed(("Unknown metadata form %d! forgot to add it to the switch?\n", enmMetadataForm));
+ return "UNKNOWN!";
+ }
+ }
+}
+
+/**
+ * Returns the region containing the given offset.
+ *
+ * @returns Pointer to the region or NULL if not found.
+ * @param pThis The CUE image state.
+ * @param uOffset The offset to look for.
+ */
+static PCVDREGIONDESC cueRegionQueryByOffset(PCUEIMAGE pThis, uint64_t uOffset)
+{
+ for (uint32_t i = 0; i < pThis->pRegionList->cRegions; i++)
+ {
+ PCVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[i];
+ if ( pRegion->offRegion <= uOffset
+ && pRegion->offRegion + pRegion->cRegionBlocksOrBytes > uOffset)
+ return pRegion;
+ }
+
+ return NULL;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) cueProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pVDIfsDisk, enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ PCUEIMAGE pThis = (PCUEIMAGE)RTMemAllocZ(sizeof(CUEIMAGE));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->pszFilename = pszFilename;
+ pThis->pStorage = NULL;
+ pThis->pVDIfsDisk = pVDIfsDisk;
+ pThis->pVDIfsImage = pVDIfsImage;
+
+ rc = cueOpenImage(pThis, VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_READONLY);
+ cueFreeImage(pThis, false);
+ RTMemFree(pThis);
+
+ if (RT_SUCCESS(rc))
+ *penmType = VDTYPE_OPTICAL_DISC;
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) cueOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc;
+ PCUEIMAGE pThis;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+ AssertReturn(enmType == VDTYPE_OPTICAL_DISC, VERR_NOT_SUPPORTED);
+
+ pThis = (PCUEIMAGE)RTMemAllocZ(sizeof(CUEIMAGE));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->pszFilename = pszFilename;
+ pThis->pStorage = NULL;
+ pThis->pVDIfsDisk = pVDIfsDisk;
+ pThis->pVDIfsImage = pVDIfsImage;
+
+ rc = cueOpenImage(pThis, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pThis;
+ else
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) cueClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+ int rc = cueFreeImage(pThis, fDelete);
+ RTMemFree(pThis);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRead */
+static DECLCALLBACK(int) cueRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu cbToRead=%zu pIoCtx=%#p pcbActuallyRead=%#p\n",
+ pBackendData, uOffset, cbToRead, pIoCtx, pcbActuallyRead));
+ int rc = VINF_SUCCESS;
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+
+ /* Get the region */
+ PCVDREGIONDESC pRegion = cueRegionQueryByOffset(pThis, uOffset);
+ if (pRegion)
+ {
+ /* Clip read size to remain in the region (not necessary I think). */
+ uint64_t offRead = uOffset - pRegion->offRegion;
+
+ cbToRead = RT_MIN(cbToRead, pRegion->cRegionBlocksOrBytes - offRead);
+ Assert(!(cbToRead % pRegion->cbBlock));
+
+ /* Need to convert audio data samples to little endian. */
+ if ( pRegion->enmDataForm == VDREGIONDATAFORM_CDDA
+ && !pThis->fLittleEndian)
+ {
+ *pcbActuallyRead = cbToRead;
+
+ while (cbToRead)
+ {
+ RTSGSEG Segment;
+ unsigned cSegments = 1;
+ size_t cbSeg = 0;
+
+ cbSeg = vdIfIoIntIoCtxSegArrayCreate(pThis->pIfIo, pIoCtx, &Segment,
+ &cSegments, cbToRead);
+
+ rc = vdIfIoIntFileReadSync(pThis->pIfIo, pThis->pStorageData, uOffset, Segment.pvSeg, cbSeg);
+ if (RT_FAILURE(rc))
+ break;
+
+ uint16_t *pu16Buf = (uint16_t *)Segment.pvSeg;
+ for (uint32_t i = 0; i < cbSeg / sizeof(uint16_t); i++)
+ {
+ *pu16Buf = RT_BSWAP_U16(*pu16Buf);
+ pu16Buf++;
+ }
+
+ cbToRead -= RT_MIN(cbToRead, cbSeg);
+ uOffset += cbSeg;
+ }
+ }
+ else
+ {
+ rc = vdIfIoIntFileReadUser(pThis->pIfIo, pThis->pStorageData, uOffset,
+ pIoCtx, cbToRead);
+ if (RT_SUCCESS(rc))
+ *pcbActuallyRead = cbToRead;
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnWrite */
+static DECLCALLBACK(int) cueWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ RT_NOREF7(uOffset, cbToWrite, pIoCtx, pcbWriteProcess, pcbPreRead, pcbPostRead, fWrite);
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pThis);
+
+ if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnFlush */
+static DECLCALLBACK(int) cueFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ RT_NOREF2(pBackendData, pIoCtx);
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) cueGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, 0);
+
+ return 1;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) cueGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, 0);
+
+ uint64_t cbFile = 0;
+ if (pThis->pStorage)
+ {
+ int rc = vdIfIoIntFileGetSize(pThis->pIfIo, pThis->pStorageData, &cbFile);
+ if (RT_FAILURE(rc))
+ cbFile = 0; /* Make sure it is 0 */
+ }
+
+ LogFlowFunc(("returns %lld\n", cbFile));
+ return cbFile;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) cueGetPCHSGeometry(void *pBackendData,
+ PVDGEOMETRY pPCHSGeometry)
+{
+ RT_NOREF1(pPCHSGeometry);
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) cueSetPCHSGeometry(void *pBackendData,
+ PCVDGEOMETRY pPCHSGeometry)
+{
+ RT_NOREF1(pPCHSGeometry);
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) cueGetLCHSGeometry(void *pBackendData,
+ PVDGEOMETRY pLCHSGeometry)
+{
+ RT_NOREF1(pLCHSGeometry);
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) cueSetLCHSGeometry(void *pBackendData,
+ PCVDGEOMETRY pLCHSGeometry)
+{
+ RT_NOREF1(pLCHSGeometry);
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n",
+ pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) cueQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = pThis->pRegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) cueRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) cueGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, 0);
+
+ LogFlowFunc(("returns %#x\n", pThis->uImageFlags));
+ return pThis->uImageFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) cueGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, 0);
+
+ LogFlowFunc(("returns %#x\n", pThis->uOpenFlags));
+ return pThis->uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) cueSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pThis || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ rc = cueFreeImage(pThis, false);
+ if (RT_SUCCESS(rc))
+ rc = cueOpenImage(pThis, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(cueGetComment);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(cueSetComment, PCUEIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(cueGetUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(cueSetUuid, PCUEIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(cueGetModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(cueSetModificationUuid, PCUEIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(cueGetParentUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(cueSetParentUuid, PCUEIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(cueGetParentModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(cueSetParentModificationUuid, PCUEIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) cueDump(void *pBackendData)
+{
+ PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pThis);
+ vdIfErrorMessage(pThis->pIfError, "Dumping CUE image \"%s\" mode=%s uOpenFlags=%X File=%#p\n",
+ pThis->pszFilename,
+ (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY) ? "r/o" : "r/w",
+ pThis->uOpenFlags,
+ pThis->pStorage);
+ vdIfErrorMessage(pThis->pIfError, "Backing File \"%s\" File=%#p\n",
+ pThis->pszDataFilename, pThis->pStorageData);
+ vdIfErrorMessage(pThis->pIfError, "Number of tracks: %u\n", pThis->pRegionList->cRegions);
+ for (uint32_t i = 0; i < pThis->pRegionList->cRegions; i++)
+ {
+ PCVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[i];
+
+ vdIfErrorMessage(pThis->pIfError, "------------------------ Track %u ------------------------\n", i);
+ vdIfErrorMessage(pThis->pIfError, "Start=%llu Size=%llu BlockSize=%llu DataSize=%llu MetadataSize=%llu\n",
+ pRegion->offRegion, pRegion->cRegionBlocksOrBytes, pRegion->cbBlock, pRegion->cbData,
+ pRegion->cbMetadata);
+ vdIfErrorMessage(pThis->pIfError, "DataForm=%s MetadataForm=%s\n",
+ cueRegionDataFormStringify(pRegion->enmDataForm),
+ cueRegionMetadataFormStringify(pRegion->enmMetadataForm));
+ }
+}
+
+
+
+const VDIMAGEBACKEND g_CueBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "CUE",
+ /* uBackendCaps */
+ VD_CAP_FILE | VD_CAP_VFS,
+ /* paFileExtensions */
+ s_aCueFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ cueProbe,
+ /* pfnOpen */
+ cueOpen,
+ /* pfnCreate */
+ NULL,
+ /* pfnRename */
+ NULL,
+ /* pfnClose */
+ cueClose,
+ /* pfnRead */
+ cueRead,
+ /* pfnWrite */
+ cueWrite,
+ /* pfnFlush */
+ cueFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ cueGetVersion,
+ /* pfnGetFileSize */
+ cueGetFileSize,
+ /* pfnGetPCHSGeometry */
+ cueGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ cueSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ cueGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ cueSetLCHSGeometry,
+ /* pfnQueryRegions */
+ cueQueryRegions,
+ /* pfnRegionListRelease */
+ cueRegionListRelease,
+ /* pfnGetImageFlags */
+ cueGetImageFlags,
+ /* pfnGetOpenFlags */
+ cueGetOpenFlags,
+ /* pfnSetOpenFlags */
+ cueSetOpenFlags,
+ /* pfnGetComment */
+ cueGetComment,
+ /* pfnSetComment */
+ cueSetComment,
+ /* pfnGetUuid */
+ cueGetUuid,
+ /* pfnSetUuid */
+ cueSetUuid,
+ /* pfnGetModificationUuid */
+ cueGetModificationUuid,
+ /* pfnSetModificationUuid */
+ cueSetModificationUuid,
+ /* pfnGetParentUuid */
+ cueGetParentUuid,
+ /* pfnSetParentUuid */
+ cueSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ cueGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ cueSetParentModificationUuid,
+ /* pfnDump */
+ cueDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ NULL,
+ /* pfnSetParentFilename */
+ NULL,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ NULL,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
diff --git a/src/VBox/Storage/DMG.cpp b/src/VBox/Storage/DMG.cpp
new file mode 100644
index 00000000..0410ab6b
--- /dev/null
+++ b/src/VBox/Storage/DMG.cpp
@@ -0,0 +1,2393 @@
+/* $Id: DMG.cpp $ */
+/** @file
+ * VBoxDMG - Interpreter for Apple Disk Images (DMG).
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_DMG
+#include <VBox/vd-plugin.h>
+#include <VBox/vd-ifs.h>
+#include <VBox/log.h>
+#include <VBox/err.h>
+
+#include <iprt/asm.h>
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/base64.h>
+#include <iprt/ctype.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/zip.h>
+#include <iprt/formats/xar.h>
+
+#include "VDBackends.h"
+#include "VDBackendsInline.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+#if 0
+/** @def VBOX_WITH_DIRECT_XAR_ACCESS
+ * When defined, we will use RTVfs to access the XAR file instead of going
+ * the slightly longer way thru the VFS -> VD wrapper. */
+# define VBOX_WITH_DIRECT_XAR_ACCESS
+#endif
+
+/** Sector size, multiply with all sector counts to get number of bytes. */
+#define DMG_SECTOR_SIZE 512
+
+/** Convert block number/size to byte offset/size. */
+#define DMG_BLOCK2BYTE(u) ((uint64_t)(u) << 9)
+
+/** Convert byte offset/size to block number/size. */
+#define DMG_BYTE2BLOCK(u) ((u) >> 9)
+
+/**
+ * UDIF checksum structure.
+ */
+typedef struct DMGUDIFCKSUM
+{
+ uint32_t u32Kind; /**< The kind of checksum. */
+ uint32_t cBits; /**< The size of the checksum. */
+ union
+ {
+ uint8_t au8[128]; /**< 8-bit view. */
+ uint32_t au32[32]; /**< 32-bit view. */
+ } uSum; /**< The checksum. */
+} DMGUDIFCKSUM;
+AssertCompileSize(DMGUDIFCKSUM, 8 + 128);
+typedef DMGUDIFCKSUM *PDMGUDIFCKSUM;
+typedef const DMGUDIFCKSUM *PCDMGUDIFCKSUM;
+
+/** @name Checksum Kind (DMGUDIFCKSUM::u32Kind)
+ * @{ */
+/** No checksum. */
+#define DMGUDIFCKSUM_NONE UINT32_C(0)
+/** CRC-32. */
+#define DMGUDIFCKSUM_CRC32 UINT32_C(2)
+/** @} */
+
+/**
+ * UDIF ID.
+ * This is kind of like a UUID only it isn't, but we'll use the UUID
+ * representation of it for simplicity.
+ */
+typedef RTUUID DMGUDIFID;
+AssertCompileSize(DMGUDIFID, 16);
+typedef DMGUDIFID *PDMGUDIFID;
+typedef const DMGUDIFID *PCDMGUDIFID;
+
+/**
+ * UDIF footer used by Apple Disk Images (DMG).
+ *
+ * This is a footer placed 512 bytes from the end of the file. Typically a DMG
+ * file starts with the data, which is followed by the block table and then ends
+ * with this structure.
+ *
+ * All fields are stored in big endian format.
+ */
+#pragma pack(1)
+typedef struct DMGUDIF
+{
+ uint32_t u32Magic; /**< 0x000 - Magic, 'koly' (DMGUDIF_MAGIC). (fUDIFSignature) */
+ uint32_t u32Version; /**< 0x004 - The UDIF version (DMGUDIF_VER_CURRENT). (fUDIFVersion) */
+ uint32_t cbFooter; /**< 0x008 - The size of the this structure (512). (fUDIFHeaderSize) */
+ uint32_t fFlags; /**< 0x00c - Flags. (fUDIFFlags) */
+ uint64_t offRunData; /**< 0x010 - Where the running data fork starts (usually 0). (fUDIFRunningDataForkOffset) */
+ uint64_t offData; /**< 0x018 - Where the data fork starts (usually 0). (fUDIFDataForkOffset) */
+ uint64_t cbData; /**< 0x020 - Size of the data fork (in bytes). (fUDIFDataForkLength) */
+ uint64_t offRsrc; /**< 0x028 - Where the resource fork starts (usually cbData or 0). (fUDIFRsrcForkOffset) */
+ uint64_t cbRsrc; /**< 0x030 - The size of the resource fork. (fUDIFRsrcForkLength)*/
+ uint32_t iSegment; /**< 0x038 - The segment number of this file. (fUDIFSegmentNumber) */
+ uint32_t cSegments; /**< 0x03c - The number of segments. (fUDIFSegmentCount) */
+ DMGUDIFID SegmentId; /**< 0x040 - The segment ID. (fUDIFSegmentID) */
+ DMGUDIFCKSUM DataCkSum; /**< 0x050 - The data checksum. (fUDIFDataForkChecksum) */
+ uint64_t offXml; /**< 0x0d8 - The XML offset (.plist kind of data). (fUDIFXMLOffset) */
+ uint64_t cbXml; /**< 0x0e0 - The size of the XML. (fUDIFXMLSize) */
+ uint8_t abUnknown[120]; /**< 0x0e8 - Unknown stuff, hdiutil doesn't dump it... */
+ DMGUDIFCKSUM MasterCkSum; /**< 0x160 - The master checksum. (fUDIFMasterChecksum) */
+ uint32_t u32Type; /**< 0x1e8 - The image type. (fUDIFImageVariant) */
+ uint64_t cSectors; /**< 0x1ec - The sector count. Warning! Unaligned! (fUDISectorCount) */
+ uint32_t au32Unknown[3]; /**< 0x1f4 - Unknown stuff, hdiutil doesn't dump it... */
+} DMGUDIF;
+#pragma pack()
+AssertCompileSize(DMGUDIF, 512);
+AssertCompileMemberOffset(DMGUDIF, cbRsrc, 0x030);
+AssertCompileMemberOffset(DMGUDIF, cbXml, 0x0e0);
+AssertCompileMemberOffset(DMGUDIF, cSectors, 0x1ec);
+
+typedef DMGUDIF *PDMGUDIF;
+typedef const DMGUDIF *PCDMGUDIF;
+
+/** The UDIF magic 'koly' (DMGUDIF::u32Magic). */
+#define DMGUDIF_MAGIC UINT32_C(0x6b6f6c79)
+
+/** The current UDIF version (DMGUDIF::u32Version).
+ * This is currently the only we recognizes and will create. */
+#define DMGUDIF_VER_CURRENT 4
+
+/** @name UDIF flags (DMGUDIF::fFlags).
+ * @{ */
+/** Flatten image whatever that means.
+ * (hdiutil -debug calls it kUDIFFlagsFlattened.) */
+#define DMGUDIF_FLAGS_FLATTENED RT_BIT_32(0)
+/** Internet enabled image.
+ * (hdiutil -debug calls it kUDIFFlagsInternetEnabled) */
+#define DMGUDIF_FLAGS_INET_ENABLED RT_BIT_32(2)
+/** Mask of known bits. */
+#define DMGUDIF_FLAGS_KNOWN_MASK (RT_BIT_32(0) | RT_BIT_32(2))
+/** @} */
+
+/** @name UDIF Image Types (DMGUDIF::u32Type).
+ * @{ */
+/** Device image type. (kUDIFDeviceImageType) */
+#define DMGUDIF_TYPE_DEVICE 1
+/** Device image type. (kUDIFPartitionImageType) */
+#define DMGUDIF_TYPE_PARTITION 2
+/** @} */
+
+/**
+ * BLKX data.
+ *
+ * This contains the start offset and size of raw data stored in the image.
+ *
+ * All fields are stored in big endian format.
+ */
+#pragma pack(1)
+typedef struct DMGBLKX
+{
+ uint32_t u32Magic; /**< 0x000 - Magic, 'mish' (DMGBLKX_MAGIC). */
+ uint32_t u32Version; /**< 0x004 - The BLKX version (DMGBLKX_VER_CURRENT). */
+ uint64_t cSectornumberFirst; /**< 0x008 - The first sector number the block represents in the virtual device. */
+ uint64_t cSectors; /**< 0x010 - Number of sectors this block represents. */
+ uint64_t offDataStart; /**< 0x018 - Start offset for raw data. */
+ uint32_t cSectorsDecompress; /**< 0x020 - Size of the buffer in sectors needed to decompress. */
+ uint32_t u32BlocksDescriptor; /**< 0x024 - Blocks descriptor. */
+ uint8_t abReserved[24];
+ DMGUDIFCKSUM BlkxCkSum; /**< 0x03c - Checksum for the BLKX table. */
+ uint32_t cBlocksRunCount; /**< 0x - Number of entries in the blkx run table afterwards. */
+} DMGBLKX;
+#pragma pack()
+AssertCompileSize(DMGBLKX, 204);
+
+typedef DMGBLKX *PDMGBLKX;
+typedef const DMGBLKX *PCDMGBLKX;
+
+/** The BLKX magic 'mish' (DMGBLKX::u32Magic). */
+#define DMGBLKX_MAGIC UINT32_C(0x6d697368)
+/** BLKX version (DMGBLKX::u32Version). */
+#define DMGBLKX_VERSION UINT32_C(0x00000001)
+
+/** Blocks descriptor type: entire device. */
+#define DMGBLKX_DESC_ENTIRE_DEVICE UINT32_C(0xfffffffe)
+
+/**
+ * BLKX table descriptor.
+ *
+ * All fields are stored in big endian format.
+ */
+#pragma pack(1)
+typedef struct DMGBLKXDESC
+{
+ uint32_t u32Type; /**< 0x000 - Type of the descriptor. */
+ uint32_t u32Reserved; /**< 0x004 - Reserved, but contains +beg or +end in case thisi is a comment descriptor. */
+ uint64_t u64SectorStart; /**< 0x008 - First sector number in the block this entry describes. */
+ uint64_t u64SectorCount; /**< 0x010 - Number of sectors this entry describes. */
+ uint64_t offData; /**< 0x018 - Offset in the image where the data starts. */
+ uint64_t cbData; /**< 0x020 - Number of bytes in the image. */
+} DMGBLKXDESC;
+#pragma pack()
+AssertCompileSize(DMGBLKXDESC, 40);
+
+typedef DMGBLKXDESC *PDMGBLKXDESC;
+typedef const DMGBLKXDESC *PCDMGBLKXDESC;
+
+/** Raw image data type. */
+#define DMGBLKXDESC_TYPE_RAW 1
+/** Ignore type. */
+#define DMGBLKXDESC_TYPE_IGNORE 2
+/** Compressed with zlib type. */
+#define DMGBLKXDESC_TYPE_ZLIB UINT32_C(0x80000005)
+/** Comment type. */
+#define DMGBLKXDESC_TYPE_COMMENT UINT32_C(0x7ffffffe)
+/** Terminator type. */
+#define DMGBLKXDESC_TYPE_TERMINATOR UINT32_C(0xffffffff)
+
+/**
+ * UDIF Resource Entry.
+ */
+typedef struct DMGUDIFRSRCENTRY
+{
+ /** The ID. */
+ int32_t iId;
+ /** Attributes. */
+ uint32_t fAttributes;
+ /** The name. */
+ char *pszName;
+ /** The CoreFoundation name. Can be NULL. */
+ char *pszCFName;
+ /** The size of the data. */
+ size_t cbData;
+ /** The raw data. */
+ uint8_t *pbData;
+} DMGUDIFRSRCENTRY;
+/** Pointer to an UDIF resource entry. */
+typedef DMGUDIFRSRCENTRY *PDMGUDIFRSRCENTRY;
+/** Pointer to a const UDIF resource entry. */
+typedef DMGUDIFRSRCENTRY const *PCDMGUDIFRSRCENTRY;
+
+/**
+ * UDIF Resource Array.
+ */
+typedef struct DMGUDIFRSRCARRAY
+{
+ /** The array name. */
+ char szName[12];
+ /** The number of occupied entries. */
+ uint32_t cEntries;
+ /** The array entries.
+ * A lazy bird ASSUME there are no more than 4 entries in any DMG. Increase the
+ * size if DMGs with more are found.
+ * r=aeichner: Saw one with 6 here (image of a whole DVD) */
+ DMGUDIFRSRCENTRY aEntries[10];
+} DMGUDIFRSRCARRAY;
+/** Pointer to a UDIF resource array. */
+typedef DMGUDIFRSRCARRAY *PDMGUDIFRSRCARRAY;
+/** Pointer to a const UDIF resource array. */
+typedef DMGUDIFRSRCARRAY const *PCDMGUDIFRSRCARRAY;
+
+/**
+ * DMG extent types.
+ */
+typedef enum DMGEXTENTTYPE
+{
+ /** Null, never used. */
+ DMGEXTENTTYPE_NULL = 0,
+ /** Raw image data. */
+ DMGEXTENTTYPE_RAW,
+ /** Zero extent, reads return 0 and writes have no effect. */
+ DMGEXTENTTYPE_ZERO,
+ /** Compressed extent - compression method ZLIB. */
+ DMGEXTENTTYPE_COMP_ZLIB,
+ /** 32bit hack. */
+ DMGEXTENTTYPE_32BIT_HACK = 0x7fffffff
+} DMGEXTENTTYPE, *PDMGEXTENTTYPE;
+
+/**
+ * DMG extent mapping a virtual image block to real file offsets.
+ */
+typedef struct DMGEXTENT
+{
+ /** Extent type. */
+ DMGEXTENTTYPE enmType;
+ /** First sector this extent describes. */
+ uint64_t uSectorExtent;
+ /** Number of sectors this extent describes. */
+ uint64_t cSectorsExtent;
+ /** Start offset in the real file. */
+ uint64_t offFileStart;
+ /** Number of bytes for the extent data in the file. */
+ uint64_t cbFile;
+} DMGEXTENT;
+/** Pointer to an DMG extent. */
+typedef DMGEXTENT *PDMGEXTENT;
+
+/**
+ * VirtualBox Apple Disk Image (DMG) interpreter instance data.
+ */
+typedef struct DMGIMAGE
+{
+ /** Image name.
+ * Kept around for logging and delete-on-close purposes. */
+ const char *pszFilename;
+ /** Storage handle. */
+ PVDIOSTORAGE pStorage;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface - careful accessing this because of hDmgFileInXar. */
+ PVDINTERFACEIOINT pIfIoXxx;
+
+
+ /** The VFS file handle for a DMG within a XAR archive. */
+ RTVFSFILE hDmgFileInXar;
+ /** XAR file system stream handle.
+ * Sitting on this isn't really necessary, but insurance against the XAR code
+ * changes making back references from child objects to the stream itself. */
+ RTVFSFSSTREAM hXarFss;
+
+ /** Flags the image was opened with. */
+ uint32_t uOpenFlags;
+ /** Image flags. */
+ unsigned uImageFlags;
+ /** Total size of the virtual image. */
+ uint64_t cbSize;
+ /** Size of the image. */
+ uint64_t cbFile;
+ /** Physical geometry of this image. */
+ VDGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ VDGEOMETRY LCHSGeometry;
+
+ /** The resources.
+ * A lazy bird ASSUME there are only two arrays in the resource-fork section in
+ * the XML, namely 'blkx' and 'plst'. These have been assigned fixed indexes. */
+ DMGUDIFRSRCARRAY aRsrcs[2];
+ /** The UDIF footer. */
+ DMGUDIF Ftr;
+
+ /** Number of valid extents in the array. */
+ unsigned cExtents;
+ /** Number of entries the array can hold. */
+ unsigned cExtentsMax;
+ /** Pointer to the extent array. */
+ PDMGEXTENT paExtents;
+ /** Index of the last accessed extent. */
+ unsigned idxExtentLast;
+
+ /** Extent which owns the data in the buffer. */
+ PDMGEXTENT pExtentDecomp;
+ /** Buffer holding the decompressed data for a extent. */
+ void *pvDecompExtent;
+ /** Size of the buffer. */
+ size_t cbDecompExtent;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} DMGIMAGE;
+/** Pointer to an instance of the DMG Image Interpreter. */
+typedef DMGIMAGE *PDMGIMAGE;
+
+/** @name Resources indexes (into DMG::aRsrcs).
+ * @{ */
+#define DMG_RSRC_IDX_BLKX 0
+#define DMG_RSRC_IDX_PLST 1
+/** @} */
+
+/** State for the input callout of the inflate reader. */
+typedef struct DMGINFLATESTATE
+{
+ /* Image this operation relates to. */
+ PDMGIMAGE pImage;
+ /* Total size of the data to read. */
+ size_t cbSize;
+ /* Offset in the file to read. */
+ uint64_t uFileOffset;
+ /* Current read position. */
+ ssize_t iOffset;
+} DMGINFLATESTATE;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def DMG_PRINTF
+ * Wrapper for LogRel.
+ */
+#define DMG_PRINTF(a) LogRel(a)
+
+/** @def DMG_VALIDATE
+ * For validating a struct thing and log/print what's wrong.
+ */
+# define DMG_VALIDATE(expr, logstuff) \
+ do { \
+ if (!(expr)) \
+ { \
+ LogRel(("DMG: validation failed: %s\nDMG: ", #expr)); \
+ LogRel(logstuff); \
+ fRc = false; \
+ } \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aDmgFileExtensions[] =
+{
+ {"dmg", VDTYPE_OPTICAL_DISC},
+ {NULL, VDTYPE_INVALID}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#if 0 /* unused */
+static void dmgUdifFtrHost2FileEndian(PDMGUDIF pUdif);
+#endif
+static void dmgUdifFtrFile2HostEndian(PDMGUDIF pUdif);
+
+static void dmgUdifIdHost2FileEndian(PDMGUDIFID pId);
+static void dmgUdifIdFile2HostEndian(PDMGUDIFID pId);
+
+#if 0 /* unused */
+static void dmgUdifCkSumHost2FileEndian(PDMGUDIFCKSUM pCkSum);
+#endif
+static void dmgUdifCkSumFile2HostEndian(PDMGUDIFCKSUM pCkSum);
+static bool dmgUdifCkSumIsValid(PCDMGUDIFCKSUM pCkSum, const char *pszPrefix);
+
+
+
+/**
+ * vdIfIoIntFileReadSync / RTVfsFileReadAt wrapper.
+ */
+static int dmgWrapFileReadSync(PDMGIMAGE pThis, RTFOFF off, void *pvBuf, size_t cbToRead)
+{
+ int rc;
+ if (pThis->hDmgFileInXar == NIL_RTVFSFILE)
+ rc = vdIfIoIntFileReadSync(pThis->pIfIoXxx, pThis->pStorage, off, pvBuf, cbToRead);
+ else
+ rc = RTVfsFileReadAt(pThis->hDmgFileInXar, off, pvBuf, cbToRead, NULL);
+ return rc;
+}
+
+/**
+ * vdIfIoIntFileReadUser / RTVfsFileReadAt wrapper.
+ */
+static int dmgWrapFileReadUser(PDMGIMAGE pThis, RTFOFF off, PVDIOCTX pIoCtx, size_t cbToRead)
+{
+ int rc;
+ if (pThis->hDmgFileInXar == NIL_RTVFSFILE)
+ rc = vdIfIoIntFileReadUser(pThis->pIfIoXxx, pThis->pStorage, off, pIoCtx, cbToRead);
+ else
+ {
+ /*
+ * Alloate a temporary buffer on the stack or heap and use
+ * vdIfIoIntIoCtxCopyTo to work the context.
+ *
+ * The I/O context stuff seems too complicated and undocument that I'm
+ * not going to bother trying to implement this efficiently right now.
+ */
+ void *pvFree = NULL;
+ void *pvBuf;
+ if (cbToRead < _32K)
+ pvBuf = alloca(cbToRead);
+ else
+ pvFree = pvBuf = RTMemTmpAlloc(cbToRead);
+ if (pvBuf)
+ {
+ rc = RTVfsFileReadAt(pThis->hDmgFileInXar, off, pvBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ vdIfIoIntIoCtxCopyTo(pThis->pIfIoXxx, pIoCtx, pvBuf, cbToRead);
+ if (pvFree)
+ RTMemTmpFree(pvFree);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ return rc;
+}
+
+/**
+ * vdIfIoIntFileGetSize / RTVfsFileQuerySize wrapper.
+ */
+static int dmgWrapFileGetSize(PDMGIMAGE pThis, uint64_t *pcbFile)
+{
+ int rc;
+ if (pThis->hDmgFileInXar == NIL_RTVFSFILE)
+ rc = vdIfIoIntFileGetSize(pThis->pIfIoXxx, pThis->pStorage, pcbFile);
+ else
+ rc = RTVfsFileQuerySize(pThis->hDmgFileInXar, pcbFile);
+ return rc;
+}
+
+
+
+static DECLCALLBACK(int) dmgFileInflateHelper(void *pvUser, void *pvBuf, size_t cbBuf, size_t *pcbBuf)
+{
+ DMGINFLATESTATE *pInflateState = (DMGINFLATESTATE *)pvUser;
+
+ Assert(cbBuf);
+ if (pInflateState->iOffset < 0)
+ {
+ *(uint8_t *)pvBuf = RTZIPTYPE_ZLIB;
+ if (pcbBuf)
+ *pcbBuf = 1;
+ pInflateState->iOffset = 0;
+ return VINF_SUCCESS;
+ }
+ cbBuf = RT_MIN(cbBuf, pInflateState->cbSize);
+ int rc = dmgWrapFileReadSync(pInflateState->pImage, pInflateState->uFileOffset, pvBuf, cbBuf);
+ if (RT_FAILURE(rc))
+ return rc;
+ pInflateState->uFileOffset += cbBuf;
+ pInflateState->iOffset += cbBuf;
+ pInflateState->cbSize -= cbBuf;
+ Assert(pcbBuf);
+ *pcbBuf = cbBuf;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: read from a file and inflate the compressed data,
+ * distinguishing between async and normal operation
+ */
+DECLINLINE(int) dmgFileInflateSync(PDMGIMAGE pImage, uint64_t uOffset, size_t cbToRead,
+ void *pvBuf, size_t cbBuf)
+{
+ int rc;
+ PRTZIPDECOMP pZip = NULL;
+ DMGINFLATESTATE InflateState;
+ size_t cbActuallyRead;
+
+ InflateState.pImage = pImage;
+ InflateState.cbSize = cbToRead;
+ InflateState.uFileOffset = uOffset;
+ InflateState.iOffset = -1;
+
+ rc = RTZipDecompCreate(&pZip, &InflateState, dmgFileInflateHelper);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTZipDecompress(pZip, pvBuf, cbBuf, &cbActuallyRead);
+ RTZipDecompDestroy(pZip);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (cbActuallyRead != cbBuf)
+ rc = VERR_VD_VMDK_INVALID_FORMAT;
+ return rc;
+}
+
+/**
+ * Swaps endian.
+ * @param pUdif The structure.
+ */
+static void dmgSwapEndianUdif(PDMGUDIF pUdif)
+{
+#ifndef RT_BIG_ENDIAN
+ pUdif->u32Magic = RT_BSWAP_U32(pUdif->u32Magic);
+ pUdif->u32Version = RT_BSWAP_U32(pUdif->u32Version);
+ pUdif->cbFooter = RT_BSWAP_U32(pUdif->cbFooter);
+ pUdif->fFlags = RT_BSWAP_U32(pUdif->fFlags);
+ pUdif->offRunData = RT_BSWAP_U64(pUdif->offRunData);
+ pUdif->offData = RT_BSWAP_U64(pUdif->offData);
+ pUdif->cbData = RT_BSWAP_U64(pUdif->cbData);
+ pUdif->offRsrc = RT_BSWAP_U64(pUdif->offRsrc);
+ pUdif->cbRsrc = RT_BSWAP_U64(pUdif->cbRsrc);
+ pUdif->iSegment = RT_BSWAP_U32(pUdif->iSegment);
+ pUdif->cSegments = RT_BSWAP_U32(pUdif->cSegments);
+ pUdif->offXml = RT_BSWAP_U64(pUdif->offXml);
+ pUdif->cbXml = RT_BSWAP_U64(pUdif->cbXml);
+ pUdif->u32Type = RT_BSWAP_U32(pUdif->u32Type);
+ pUdif->cSectors = RT_BSWAP_U64(pUdif->cSectors);
+#endif
+}
+
+
+#if 0 /* unused */
+/**
+ * Swaps endian from host cpu to file.
+ * @param pUdif The structure.
+ */
+static void dmgUdifFtrHost2FileEndian(PDMGUDIF pUdif)
+{
+ dmgSwapEndianUdif(pUdif);
+ dmgUdifIdHost2FileEndian(&pUdif->SegmentId);
+ dmgUdifCkSumHost2FileEndian(&pUdif->DataCkSum);
+ dmgUdifCkSumHost2FileEndian(&pUdif->MasterCkSum);
+}
+#endif
+
+
+/**
+ * Swaps endian from file to host cpu.
+ * @param pUdif The structure.
+ */
+static void dmgUdifFtrFile2HostEndian(PDMGUDIF pUdif)
+{
+ dmgSwapEndianUdif(pUdif);
+ dmgUdifIdFile2HostEndian(&pUdif->SegmentId);
+ dmgUdifCkSumFile2HostEndian(&pUdif->DataCkSum);
+ dmgUdifCkSumFile2HostEndian(&pUdif->MasterCkSum);
+}
+
+/**
+ * Swaps endian from file to host cpu.
+ * @param pBlkx The blkx structure.
+ */
+static void dmgBlkxFile2HostEndian(PDMGBLKX pBlkx)
+{
+ pBlkx->u32Magic = RT_BE2H_U32(pBlkx->u32Magic);
+ pBlkx->u32Version = RT_BE2H_U32(pBlkx->u32Version);
+ pBlkx->cSectornumberFirst = RT_BE2H_U64(pBlkx->cSectornumberFirst);
+ pBlkx->cSectors = RT_BE2H_U64(pBlkx->cSectors);
+ pBlkx->offDataStart = RT_BE2H_U64(pBlkx->offDataStart);
+ pBlkx->cSectorsDecompress = RT_BE2H_U32(pBlkx->cSectorsDecompress);
+ pBlkx->u32BlocksDescriptor = RT_BE2H_U32(pBlkx->u32BlocksDescriptor);
+ pBlkx->cBlocksRunCount = RT_BE2H_U32(pBlkx->cBlocksRunCount);
+ dmgUdifCkSumFile2HostEndian(&pBlkx->BlkxCkSum);
+}
+
+/**
+ * Swaps endian from file to host cpu.
+ * @param pBlkxDesc The blkx descriptor structure.
+ */
+static void dmgBlkxDescFile2HostEndian(PDMGBLKXDESC pBlkxDesc)
+{
+ pBlkxDesc->u32Type = RT_BE2H_U32(pBlkxDesc->u32Type);
+ pBlkxDesc->u32Reserved = RT_BE2H_U32(pBlkxDesc->u32Reserved);
+ pBlkxDesc->u64SectorStart = RT_BE2H_U64(pBlkxDesc->u64SectorStart);
+ pBlkxDesc->u64SectorCount = RT_BE2H_U64(pBlkxDesc->u64SectorCount);
+ pBlkxDesc->offData = RT_BE2H_U64(pBlkxDesc->offData);
+ pBlkxDesc->cbData = RT_BE2H_U64(pBlkxDesc->cbData);
+}
+
+/**
+ * Validates an UDIF footer structure.
+ *
+ * @returns true if valid, false and LogRel()s on failure.
+ * @param pFtr The UDIF footer to validate.
+ * @param offFtr The offset of the structure.
+ */
+static bool dmgUdifFtrIsValid(PCDMGUDIF pFtr, uint64_t offFtr)
+{
+ bool fRc = true;
+
+ DMG_VALIDATE(!(pFtr->fFlags & ~DMGUDIF_FLAGS_KNOWN_MASK), ("fFlags=%#RX32 fKnown=%RX32\n", pFtr->fFlags, DMGUDIF_FLAGS_KNOWN_MASK));
+ DMG_VALIDATE(pFtr->offRunData < offFtr, ("offRunData=%#RX64\n", pFtr->offRunData));
+ DMG_VALIDATE(pFtr->cbData <= offFtr && pFtr->offData + pFtr->cbData <= offFtr, ("cbData=%#RX64 offData=%#RX64 offFtr=%#RX64\n", pFtr->cbData, pFtr->offData, offFtr));
+ DMG_VALIDATE(pFtr->offData < offFtr, ("offData=%#RX64\n", pFtr->offData));
+ DMG_VALIDATE(pFtr->cbRsrc <= offFtr && pFtr->offRsrc + pFtr->cbRsrc <= offFtr, ("cbRsrc=%#RX64 offRsrc=%#RX64 offFtr=%#RX64\n", pFtr->cbRsrc, pFtr->offRsrc, offFtr));
+ DMG_VALIDATE(pFtr->offRsrc < offFtr, ("offRsrc=%#RX64\n", pFtr->offRsrc));
+ DMG_VALIDATE(pFtr->cSegments <= 1, ("cSegments=%RU32\n", pFtr->cSegments));
+ DMG_VALIDATE(pFtr->iSegment == 0 || pFtr->iSegment == 1, ("iSegment=%RU32 cSegments=%RU32\n", pFtr->iSegment, pFtr->cSegments));
+ DMG_VALIDATE(pFtr->cbXml <= offFtr && pFtr->offXml + pFtr->cbXml <= offFtr, ("cbXml=%#RX64 offXml=%#RX64 offFtr=%#RX64\n", pFtr->cbXml, pFtr->offXml, offFtr));
+ DMG_VALIDATE(pFtr->offXml < offFtr, ("offXml=%#RX64\n", pFtr->offXml));
+ DMG_VALIDATE(pFtr->cbXml > 128, ("cbXml=%#RX64\n", pFtr->cbXml));
+ DMG_VALIDATE(pFtr->cbXml < 10 * _1M, ("cbXml=%#RX64\n", pFtr->cbXml));
+ DMG_VALIDATE(pFtr->u32Type == DMGUDIF_TYPE_DEVICE || pFtr->u32Type == DMGUDIF_TYPE_PARTITION, ("u32Type=%RU32\n", pFtr->u32Type));
+ DMG_VALIDATE(pFtr->cSectors != 0, ("cSectors=%#RX64\n", pFtr->cSectors));
+ fRc &= dmgUdifCkSumIsValid(&pFtr->DataCkSum, "DataCkSum");
+ fRc &= dmgUdifCkSumIsValid(&pFtr->MasterCkSum, "MasterCkSum");
+
+ return fRc;
+}
+
+
+static bool dmgBlkxIsValid(PCDMGBLKX pBlkx)
+{
+ bool fRc = true;
+
+ fRc &= dmgUdifCkSumIsValid(&pBlkx->BlkxCkSum, "BlkxCkSum");
+ DMG_VALIDATE(pBlkx->u32Magic == DMGBLKX_MAGIC, ("u32Magic=%#RX32 u32MagicExpected=%#RX32\n", pBlkx->u32Magic, DMGBLKX_MAGIC));
+ DMG_VALIDATE(pBlkx->u32Version == DMGBLKX_VERSION, ("u32Version=%#RX32 u32VersionExpected=%#RX32\n", pBlkx->u32Magic, DMGBLKX_VERSION));
+
+ return fRc;
+}
+
+/**
+ * Swaps endian from host cpu to file.
+ * @param pId The structure.
+ */
+static void dmgUdifIdHost2FileEndian(PDMGUDIFID pId)
+{
+ NOREF(pId);
+}
+
+
+/**
+ * Swaps endian from file to host cpu.
+ * @param pId The structure.
+ */
+static void dmgUdifIdFile2HostEndian(PDMGUDIFID pId)
+{
+ dmgUdifIdHost2FileEndian(pId);
+}
+
+
+/**
+ * Swaps endian.
+ * @param pCkSum The structure.
+ * @param u32Kind Kind of the checksum (CRC32, none)
+ * @param cBits Size of the checksum in bits.
+ */
+static void dmgSwapEndianUdifCkSum(PDMGUDIFCKSUM pCkSum, uint32_t u32Kind, uint32_t cBits)
+{
+#ifdef RT_BIG_ENDIAN
+ NOREF(pCkSum);
+ NOREF(u32Kind);
+ NOREF(cBits);
+#else
+ switch (u32Kind)
+ {
+ case DMGUDIFCKSUM_NONE:
+ /* nothing to do here */
+ break;
+
+ case DMGUDIFCKSUM_CRC32:
+ Assert(cBits == 32);
+ pCkSum->u32Kind = RT_BSWAP_U32(pCkSum->u32Kind);
+ pCkSum->cBits = RT_BSWAP_U32(pCkSum->cBits);
+ pCkSum->uSum.au32[0] = RT_BSWAP_U32(pCkSum->uSum.au32[0]);
+ break;
+
+ default:
+ AssertMsgFailed(("%x\n", u32Kind));
+ break;
+ }
+ NOREF(cBits);
+#endif
+}
+
+
+#if 0 /* unused */
+/**
+ * Swaps endian from host cpu to file.
+ * @param pCkSum The structure.
+ */
+static void dmgUdifCkSumHost2FileEndian(PDMGUDIFCKSUM pCkSum)
+{
+ dmgSwapEndianUdifCkSum(pCkSum, pCkSum->u32Kind, pCkSum->cBits);
+}
+#endif
+
+
+/**
+ * Swaps endian from file to host cpu.
+ * @param pCkSum The structure.
+ */
+static void dmgUdifCkSumFile2HostEndian(PDMGUDIFCKSUM pCkSum)
+{
+ dmgSwapEndianUdifCkSum(pCkSum, RT_BE2H_U32(pCkSum->u32Kind), RT_BE2H_U32(pCkSum->cBits));
+}
+
+
+/**
+ * Validates an UDIF checksum structure.
+ *
+ * @returns true if valid, false and LogRel()s on failure.
+ * @param pCkSum The checksum structure.
+ * @param pszPrefix The message prefix.
+ * @remarks This does not check the checksummed data.
+ */
+static bool dmgUdifCkSumIsValid(PCDMGUDIFCKSUM pCkSum, const char *pszPrefix)
+{
+ bool fRc = true;
+
+ switch (pCkSum->u32Kind)
+ {
+ case DMGUDIFCKSUM_NONE:
+ DMG_VALIDATE(pCkSum->cBits == 0, ("%s/NONE: cBits=%d\n", pszPrefix, pCkSum->cBits));
+ break;
+
+ case DMGUDIFCKSUM_CRC32:
+ DMG_VALIDATE(pCkSum->cBits == 32, ("%s/NONE: cBits=%d\n", pszPrefix, pCkSum->cBits));
+ break;
+
+ default:
+ DMG_VALIDATE(0, ("%s: u32Kind=%#RX32\n", pszPrefix, pCkSum->u32Kind));
+ break;
+ }
+ return fRc;
+}
+
+
+/**
+ * Internal. Flush image data to disk.
+ */
+static int dmgFlushImage(PDMGIMAGE pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( pThis
+ && (pThis->pStorage || pThis->hDmgFileInXar != NIL_RTVFSFILE)
+ && !(pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /** @todo handle writable files, update checksums etc. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Internal. Free all allocated space for representing an image except pThis,
+ * and optionally delete the image from disk.
+ */
+static int dmgFreeImage(PDMGIMAGE pThis, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pThis)
+ {
+ RTVfsFileRelease(pThis->hDmgFileInXar);
+ pThis->hDmgFileInXar = NIL_RTVFSFILE;
+
+ RTVfsFsStrmRelease(pThis->hXarFss);
+ pThis->hXarFss = NIL_RTVFSFSSTREAM;
+
+ if (pThis->pStorage)
+ {
+ /* No point updating the file that is deleted anyway. */
+ if (!fDelete)
+ dmgFlushImage(pThis);
+
+ rc = vdIfIoIntFileClose(pThis->pIfIoXxx, pThis->pStorage);
+ pThis->pStorage = NULL;
+ }
+
+ for (unsigned iRsrc = 0; iRsrc < RT_ELEMENTS(pThis->aRsrcs); iRsrc++)
+ for (unsigned i = 0; i < pThis->aRsrcs[iRsrc].cEntries; i++)
+ {
+ if (pThis->aRsrcs[iRsrc].aEntries[i].pbData)
+ {
+ RTMemFree(pThis->aRsrcs[iRsrc].aEntries[i].pbData);
+ pThis->aRsrcs[iRsrc].aEntries[i].pbData = NULL;
+ }
+ if (pThis->aRsrcs[iRsrc].aEntries[i].pszName)
+ {
+ RTMemFree(pThis->aRsrcs[iRsrc].aEntries[i].pszName);
+ pThis->aRsrcs[iRsrc].aEntries[i].pszName = NULL;
+ }
+ if (pThis->aRsrcs[iRsrc].aEntries[i].pszCFName)
+ {
+ RTMemFree(pThis->aRsrcs[iRsrc].aEntries[i].pszCFName);
+ pThis->aRsrcs[iRsrc].aEntries[i].pszCFName = NULL;
+ }
+ }
+
+ if (fDelete && pThis->pszFilename)
+ vdIfIoIntFileDelete(pThis->pIfIoXxx, pThis->pszFilename);
+
+ if (pThis->pvDecompExtent)
+ {
+ RTMemFree(pThis->pvDecompExtent);
+ pThis->pvDecompExtent = NULL;
+ pThis->cbDecompExtent = 0;
+ }
+
+ if (pThis->paExtents)
+ {
+ RTMemFree(pThis->paExtents);
+ pThis->paExtents = NULL;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+#define STARTS_WITH(pszString, szStart) \
+ ( strncmp(pszString, szStart, sizeof(szStart) - 1) == 0 )
+
+#define STARTS_WITH_WORD(pszString, szWord) \
+ ( STARTS_WITH(pszString, szWord) \
+ && !RT_C_IS_ALNUM((pszString)[sizeof(szWord) - 1]) )
+
+#define SKIP_AHEAD(psz, szWord) \
+ do { \
+ (psz) = RTStrStripL((psz) + sizeof(szWord) - 1); \
+ } while (0)
+
+#define REQUIRE_WORD(psz, szWord) \
+ do { \
+ if (!STARTS_WITH_WORD(psz, szWord)) \
+ return psz; \
+ (psz) = RTStrStripL((psz) + sizeof(szWord) - 1); \
+ } while (0)
+
+#define REQUIRE_TAG(psz, szTag) \
+ do { \
+ if (!STARTS_WITH(psz, "<" szTag ">")) \
+ return psz; \
+ (psz) = RTStrStripL((psz) + sizeof("<" szTag ">") - 1); \
+ } while (0)
+
+#define REQUIRE_TAG_NO_STRIP(psz, szTag) \
+ do { \
+ if (!STARTS_WITH(psz, "<" szTag ">")) \
+ return psz; \
+ (psz) += sizeof("<" szTag ">") - 1; \
+ } while (0)
+
+#define REQUIRE_END_TAG(psz, szTag) \
+ do { \
+ if (!STARTS_WITH(psz, "</" szTag ">")) \
+ return psz; \
+ (psz) = RTStrStripL((psz) + sizeof("</" szTag ">") - 1); \
+ } while (0)
+
+
+/**
+ * Finds the next tag end.
+ *
+ * @returns Pointer to a '>' or '\0'.
+ * @param pszCur The current position.
+ */
+static const char *dmgXmlFindTagEnd(const char *pszCur)
+{
+ /* Might want to take quoted '>' into account? */
+ char ch;
+ while ((ch = *pszCur) != '\0' && ch != '>')
+ pszCur++;
+ return pszCur;
+}
+
+
+/**
+ * Finds the end tag.
+ *
+ * Does not deal with @verbatim<tag attr="1"/>@endverbatim style tags.
+ *
+ * @returns Pointer to the first char in the end tag. NULL if another tag
+ * was encountered first or if we hit the end of the file.
+ * @param ppszCur The current position (IN/OUT).
+ * @param pszTag The tag name.
+ */
+static const char *dmgXmlFindEndTag(const char **ppszCur, const char *pszTag)
+{
+ const char *psz = *ppszCur;
+ char ch;
+ while ((ch = *psz))
+ {
+ if (ch == '<')
+ {
+ size_t const cchTag = strlen(pszTag);
+ if ( psz[1] == '/'
+ && !memcmp(&psz[2], pszTag, cchTag)
+ && psz[2 + cchTag] == '>')
+ {
+ *ppszCur = psz + 2 + cchTag + 1;
+ return psz;
+ }
+ break;
+ }
+ psz++;
+ }
+ return NULL;
+}
+
+
+/**
+ * Reads a signed 32-bit value.
+ *
+ * @returns NULL on success, pointer to the offending text on failure.
+ * @param ppszCur The text position (IN/OUT).
+ * @param pi32 Where to store the value.
+ */
+static const char *dmgXmlParseS32(const char **ppszCur, int32_t *pi32)
+{
+ const char *psz = *ppszCur;
+
+ /*
+ * <string>-1</string>
+ */
+ REQUIRE_TAG_NO_STRIP(psz, "string");
+
+ char *pszNext;
+ int rc = RTStrToInt32Ex(psz, &pszNext, 0, pi32);
+ if (rc != VWRN_TRAILING_CHARS)
+ return *ppszCur;
+ psz = pszNext;
+
+ REQUIRE_END_TAG(psz, "string");
+ *ppszCur = psz;
+ return NULL;
+}
+
+
+/**
+ * Reads an unsigned 32-bit value.
+ *
+ * @returns NULL on success, pointer to the offending text on failure.
+ * @param ppszCur The text position (IN/OUT).
+ * @param pu32 Where to store the value.
+ */
+static const char *dmgXmlParseU32(const char **ppszCur, uint32_t *pu32)
+{
+ const char *psz = *ppszCur;
+
+ /*
+ * <string>0x00ff</string>
+ */
+ REQUIRE_TAG_NO_STRIP(psz, "string");
+
+ char *pszNext;
+ int rc = RTStrToUInt32Ex(psz, &pszNext, 0, pu32);
+ if (rc != VWRN_TRAILING_CHARS)
+ return *ppszCur;
+ psz = pszNext;
+
+ REQUIRE_END_TAG(psz, "string");
+ *ppszCur = psz;
+ return NULL;
+}
+
+
+/**
+ * Reads a string value.
+ *
+ * @returns NULL on success, pointer to the offending text on failure.
+ * @param ppszCur The text position (IN/OUT).
+ * @param ppszString Where to store the pointer to the string. The caller
+ * must free this using RTMemFree.
+ */
+static const char *dmgXmlParseString(const char **ppszCur, char **ppszString)
+{
+ const char *psz = *ppszCur;
+
+ /*
+ * <string>Driver Descriptor Map (DDM : 0)</string>
+ */
+ REQUIRE_TAG_NO_STRIP(psz, "string");
+
+ const char *pszStart = psz;
+ const char *pszEnd = dmgXmlFindEndTag(&psz, "string");
+ if (!pszEnd)
+ return *ppszCur;
+ psz = RTStrStripL(psz);
+
+ *ppszString = (char *)RTMemDupEx(pszStart, pszEnd - pszStart, 1);
+ if (!*ppszString)
+ return *ppszCur;
+
+ *ppszCur = psz;
+ return NULL;
+}
+
+
+/**
+ * Parses the BASE-64 coded data tags.
+ *
+ * @returns NULL on success, pointer to the offending text on failure.
+ * @param ppszCur The text position (IN/OUT).
+ * @param ppbData Where to store the pointer to the data we've read. The
+ * caller must free this using RTMemFree.
+ * @param pcbData The number of bytes we're returning.
+ */
+static const char *dmgXmlParseData(const char **ppszCur, uint8_t **ppbData, size_t *pcbData)
+{
+ const char *psz = *ppszCur;
+
+ /*
+ * <data> AAAAA... </data>
+ */
+ REQUIRE_TAG(psz, "data");
+
+ const char *pszStart = psz;
+ ssize_t cbData = RTBase64DecodedSize(pszStart, (char **)&psz);
+ if (cbData == -1)
+ return *ppszCur;
+
+ REQUIRE_END_TAG(psz, "data");
+
+ *ppbData = (uint8_t *)RTMemAlloc(cbData);
+ if (!*ppbData)
+ return *ppszCur;
+ char *pszIgnored;
+ int rc = RTBase64Decode(pszStart, *ppbData, cbData, pcbData, &pszIgnored);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(*ppbData);
+ *ppbData = NULL;
+ return *ppszCur;
+ }
+
+ *ppszCur = psz;
+ return NULL;
+}
+
+
+/**
+ * Parses the XML resource-fork in a rather presumptive manner.
+ *
+ * This function is supposed to construct the DMG::aRsrcs instance data
+ * parts.
+ *
+ * @returns NULL on success, pointer to the problematic text on failure.
+ * @param pThis The DMG instance data.
+ * @param pszXml The XML text to parse, UTF-8.
+ */
+static const char *dmgOpenXmlToRsrc(PDMGIMAGE pThis, char const *pszXml)
+{
+ const char *psz = pszXml;
+
+ /*
+ * Verify the ?xml, !DOCTYPE and plist tags.
+ */
+ SKIP_AHEAD(psz, "");
+
+ /* <?xml version="1.0" encoding="UTF-8"?> */
+ REQUIRE_WORD(psz, "<?xml");
+ while (*psz != '?')
+ {
+ if (!*psz)
+ return psz;
+ if (STARTS_WITH_WORD(psz, "version="))
+ {
+ SKIP_AHEAD(psz, "version=");
+ REQUIRE_WORD(psz, "\"1.0\"");
+ }
+ else if (STARTS_WITH_WORD(psz, "encoding="))
+ {
+ SKIP_AHEAD(psz, "encoding=");
+ REQUIRE_WORD(psz, "\"UTF-8\"");
+ }
+ else
+ return psz;
+ }
+ SKIP_AHEAD(psz, "?>");
+
+ /* <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> */
+ REQUIRE_WORD(psz, "<!DOCTYPE");
+ REQUIRE_WORD(psz, "plist");
+ REQUIRE_WORD(psz, "PUBLIC");
+ psz = dmgXmlFindTagEnd(psz);
+ REQUIRE_WORD(psz, ">");
+
+ /* <plist version="1.0"> */
+ REQUIRE_WORD(psz, "<plist");
+ REQUIRE_WORD(psz, "version=");
+ REQUIRE_WORD(psz, "\"1.0\"");
+ REQUIRE_WORD(psz, ">");
+
+ /*
+ * Descend down to the 'resource-fork' dictionary.
+ * ASSUME it's the only top level dictionary.
+ */
+ /* <dict> <key>resource-fork</key> */
+ REQUIRE_TAG(psz, "dict");
+ REQUIRE_WORD(psz, "<key>resource-fork</key>");
+
+ /*
+ * Parse the keys in the resource-fork dictionary.
+ * ASSUME that there are just two, 'blkx' and 'plst'.
+ */
+ REQUIRE_TAG(psz, "dict");
+ while (!STARTS_WITH_WORD(psz, "</dict>"))
+ {
+ /*
+ * Parse the key and Create the resource-fork entry.
+ */
+ unsigned iRsrc;
+ if (STARTS_WITH_WORD(psz, "<key>blkx</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>blkx</key>");
+ iRsrc = DMG_RSRC_IDX_BLKX;
+ strcpy(&pThis->aRsrcs[iRsrc].szName[0], "blkx");
+ }
+ else if (STARTS_WITH_WORD(psz, "<key>plst</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>plst</key>");
+ iRsrc = DMG_RSRC_IDX_PLST;
+ strcpy(&pThis->aRsrcs[iRsrc].szName[0], "plst");
+ }
+ else
+ {
+ SKIP_AHEAD(psz, "</array>");
+ continue;
+ }
+
+
+ /*
+ * Descend into the array and add the elements to the resource entry.
+ */
+ /* <array> */
+ REQUIRE_TAG(psz, "array");
+ while (!STARTS_WITH_WORD(psz, "</array>"))
+ {
+ REQUIRE_TAG(psz, "dict");
+ uint32_t i = pThis->aRsrcs[iRsrc].cEntries;
+ if (i == RT_ELEMENTS(pThis->aRsrcs[iRsrc].aEntries))
+ return psz;
+
+ while (!STARTS_WITH_WORD(psz, "</dict>"))
+ {
+
+ /* switch on the key. */
+ const char *pszErr;
+ if (STARTS_WITH_WORD(psz, "<key>Attributes</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>Attributes</key>");
+ pszErr = dmgXmlParseU32(&psz, &pThis->aRsrcs[iRsrc].aEntries[i].fAttributes);
+ }
+ else if (STARTS_WITH_WORD(psz, "<key>ID</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>ID</key>");
+ pszErr = dmgXmlParseS32(&psz, &pThis->aRsrcs[iRsrc].aEntries[i].iId);
+ }
+ else if (STARTS_WITH_WORD(psz, "<key>Name</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>Name</key>");
+ pszErr = dmgXmlParseString(&psz, &pThis->aRsrcs[iRsrc].aEntries[i].pszName);
+ }
+ else if (STARTS_WITH_WORD(psz, "<key>CFName</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>CFName</key>");
+ pszErr = dmgXmlParseString(&psz, &pThis->aRsrcs[iRsrc].aEntries[i].pszCFName);
+ }
+ else if (STARTS_WITH_WORD(psz, "<key>Data</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>Data</key>");
+ pszErr = dmgXmlParseData(&psz, &pThis->aRsrcs[iRsrc].aEntries[i].pbData, &pThis->aRsrcs[iRsrc].aEntries[i].cbData);
+ }
+ else
+ pszErr = psz;
+ if (pszErr)
+ return pszErr;
+ } /* while not </dict> */
+ REQUIRE_END_TAG(psz, "dict");
+
+ pThis->aRsrcs[iRsrc].cEntries++;
+ } /* while not </array> */
+ REQUIRE_END_TAG(psz, "array");
+
+ } /* while not </dict> */
+ REQUIRE_END_TAG(psz, "dict");
+
+ /*
+ * ASSUMING there is only the 'resource-fork', we'll now see the end of
+ * the outer dict, plist and text.
+ */
+ /* </dict> </plist> */
+ REQUIRE_END_TAG(psz, "dict");
+ REQUIRE_END_TAG(psz, "plist");
+
+ /* the end */
+ if (*psz)
+ return psz;
+
+ return NULL;
+}
+
+#undef REQUIRE_END_TAG
+#undef REQUIRE_TAG_NO_STRIP
+#undef REQUIRE_TAG
+#undef REQUIRE_WORD
+#undef SKIP_AHEAD
+#undef STARTS_WITH_WORD
+#undef STARTS_WITH
+
+/**
+ * Returns the data attached to a resource.
+ *
+ * @returns VBox status code.
+ * @param pThis The DMG instance data.
+ * @param pcszRsrcName Name of the resource to get.
+ * @param ppcRsrc Where to store the pointer to the resource data on success.
+ */
+static int dmgGetRsrcData(PDMGIMAGE pThis, const char *pcszRsrcName,
+ PCDMGUDIFRSRCARRAY *ppcRsrc)
+{
+ int rc = VERR_NOT_FOUND;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aRsrcs); i++)
+ {
+ if (!strcmp(pThis->aRsrcs[i].szName, pcszRsrcName))
+ {
+ *ppcRsrc = &pThis->aRsrcs[i];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Creates a new extent from the given blkx descriptor.
+ *
+ * @returns VBox status code.
+ * @param pThis DMG instance data.
+ * @param uSectorPart First sector the partition owning the blkx descriptor has.
+ * @param pBlkxDesc The blkx descriptor.
+ */
+static int dmgExtentCreateFromBlkxDesc(PDMGIMAGE pThis, uint64_t uSectorPart, PDMGBLKXDESC pBlkxDesc)
+{
+ int rc = VINF_SUCCESS;
+ DMGEXTENTTYPE enmExtentTypeNew;
+ PDMGEXTENT pExtentNew = NULL;
+
+ if (pBlkxDesc->u32Type == DMGBLKXDESC_TYPE_RAW)
+ enmExtentTypeNew = DMGEXTENTTYPE_RAW;
+ else if (pBlkxDesc->u32Type == DMGBLKXDESC_TYPE_IGNORE)
+ enmExtentTypeNew = DMGEXTENTTYPE_ZERO;
+ else if (pBlkxDesc->u32Type == DMGBLKXDESC_TYPE_ZLIB)
+ enmExtentTypeNew = DMGEXTENTTYPE_COMP_ZLIB;
+ else
+ {
+ AssertMsgFailed(("This method supports only raw or zero extents!\n"));
+ return VERR_NOT_SUPPORTED;
+ }
+
+ /** @todo Merge raw extents if possible to save memory. */
+#if 0
+ pExtentNew = pThis->pExtentLast;
+ if ( pExtentNew
+ && pExtentNew->enmType == enmExtentTypeNew
+ && enmExtentTypeNew == DMGEXTENTTYPE_RAW
+ && pExtentNew->uSectorExtent + pExtentNew->cSectorsExtent == offDevice + pBlkxDesc->u64SectorStart * DMG_SECTOR_SIZE;
+ && pExtentNew->offFileStart + pExtentNew->cbExtent == pBlkxDesc->offData)
+ {
+ /* Increase the last extent. */
+ pExtentNew->cbExtent += pBlkxDesc->cbData;
+ }
+ else
+#endif
+ {
+ if (pThis->cExtentsMax == pThis->cExtents)
+ {
+ pThis->cExtentsMax += 64;
+
+ /* Increase the array. */
+ PDMGEXTENT paExtentsNew = (PDMGEXTENT)RTMemRealloc(pThis->paExtents, sizeof(DMGEXTENT) * pThis->cExtentsMax);
+ if (!paExtentsNew)
+ {
+ rc = VERR_NO_MEMORY;
+ pThis->cExtentsMax -= 64;
+ }
+ else
+ pThis->paExtents = paExtentsNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pExtentNew = &pThis->paExtents[pThis->cExtents++];
+
+ pExtentNew->enmType = enmExtentTypeNew;
+ pExtentNew->uSectorExtent = uSectorPart + pBlkxDesc->u64SectorStart;
+ pExtentNew->cSectorsExtent = pBlkxDesc->u64SectorCount;
+ pExtentNew->offFileStart = pBlkxDesc->offData;
+ pExtentNew->cbFile = pBlkxDesc->cbData;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Find the extent for the given sector number.
+ */
+static PDMGEXTENT dmgExtentGetFromOffset(PDMGIMAGE pThis, uint64_t uSector)
+{
+ /*
+ * We assume that the array is ordered from lower to higher sector
+ * numbers.
+ * This makes it possible to bisect the array to find the extent
+ * faster than using a linked list.
+ */
+ PDMGEXTENT pExtent = NULL;
+ unsigned idxCur = pThis->idxExtentLast;
+ unsigned idxMax = pThis->cExtents;
+ unsigned idxMin = 0;
+
+ while (idxMin < idxMax)
+ {
+ PDMGEXTENT pExtentCur = &pThis->paExtents[idxCur];
+
+ /* Determine the search direction. */
+ if (uSector < pExtentCur->uSectorExtent)
+ {
+ /* Search left from the current extent. */
+ idxMax = idxCur;
+ }
+ else if (uSector >= pExtentCur->uSectorExtent + pExtentCur->cSectorsExtent)
+ {
+ /* Search right from the current extent. */
+ idxMin = idxCur;
+ }
+ else
+ {
+ /* The sector lies in the extent, stop searching. */
+ pExtent = pExtentCur;
+ break;
+ }
+
+ idxCur = idxMin + (idxMax - idxMin) / 2;
+ }
+
+ if (pExtent)
+ pThis->idxExtentLast = idxCur;
+
+ return pExtent;
+}
+
+/**
+ * Goes through the BLKX structure and creates the necessary extents.
+ */
+static int dmgBlkxParse(PDMGIMAGE pThis, PDMGBLKX pBlkx)
+{
+ int rc = VINF_SUCCESS;
+ PDMGBLKXDESC pBlkxDesc = (PDMGBLKXDESC)(pBlkx + 1);
+
+ for (unsigned i = 0; i < pBlkx->cBlocksRunCount; i++)
+ {
+ dmgBlkxDescFile2HostEndian(pBlkxDesc);
+
+ switch (pBlkxDesc->u32Type)
+ {
+ case DMGBLKXDESC_TYPE_RAW:
+ case DMGBLKXDESC_TYPE_IGNORE:
+ case DMGBLKXDESC_TYPE_ZLIB:
+ {
+ rc = dmgExtentCreateFromBlkxDesc(pThis, pBlkx->cSectornumberFirst, pBlkxDesc);
+ break;
+ }
+ case DMGBLKXDESC_TYPE_COMMENT:
+ case DMGBLKXDESC_TYPE_TERMINATOR:
+ break;
+ default:
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ break;
+ }
+
+ if ( pBlkxDesc->u32Type == DMGBLKXDESC_TYPE_TERMINATOR
+ || RT_FAILURE(rc))
+ break;
+
+ pBlkxDesc++;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Worker for dmgOpenImage that tries to open a DMG inside a XAR file.
+ *
+ * We'll select the first .dmg inside the archive that we can get a file
+ * interface to.
+ *
+ * @returns VBox status code.
+ * @param fOpen Flags for defining the open type.
+ * @param pVDIfIoInt The internal VD I/O interface to use.
+ * @param pvStorage The storage pointer that goes with @a pVDIfsIo.
+ * @param pszFilename The input filename, optional.
+ * @param phXarFss Where to return the XAR file system stream handle on
+ * success
+ * @param phDmgFileInXar Where to return the VFS handle to the DMG file
+ * within the XAR image on success.
+ *
+ * @remarks Not using the PDMGIMAGE structure directly here because the function
+ * is being in serveral places.
+ */
+static int dmgOpenImageWithinXar(uint32_t fOpen, PVDINTERFACEIOINT pVDIfIoInt, void *pvStorage, const char *pszFilename,
+ PRTVFSFSSTREAM phXarFss, PRTVFSFILE phDmgFileInXar)
+{
+ /*
+ * Open the XAR file stream.
+ */
+ RTVFSFILE hVfsFile;
+#ifdef VBOX_WITH_DIRECT_XAR_ACCESS
+ int rc = RTVfsFileOpenNormal(pszFilename, fOpen, &hVfsFile);
+#else
+ int rc = VDIfCreateVfsFile(NULL, pVDIfIoInt, pvStorage, fOpen, &hVfsFile);
+#endif
+ if (RT_FAILURE(rc))
+ return rc;
+
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+
+ RTVFSFSSTREAM hXarFss;
+ rc = RTZipXarFsStreamFromIoStream(hVfsIos, 0 /*fFlags*/, &hXarFss);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Look for a DMG in the stream that we can use.
+ */
+ for (;;)
+ {
+ char *pszName;
+ RTVFSOBJTYPE enmType;
+ RTVFSOBJ hVfsObj;
+ rc = RTVfsFsStrmNext(hXarFss, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* It must be a file object so it can be seeked, this also implies that
+ it's uncompressed. Then it must have the .dmg suffix. */
+ if (enmType == RTVFSOBJTYPE_FILE)
+ {
+ size_t cchName = strlen(pszName);
+ const char *pszSuff = pszName + cchName - 4;
+ if ( cchName >= 4
+ && pszSuff[0] == '.'
+ && (pszSuff[1] == 'd' || pszSuff[1] == 'D')
+ && (pszSuff[2] == 'm' || pszSuff[2] == 'M')
+ && (pszSuff[3] == 'g' || pszSuff[3] == 'G'))
+ {
+ RTVFSFILE hDmgFileInXar = RTVfsObjToFile(hVfsObj);
+ AssertBreakStmt(hDmgFileInXar != NIL_RTVFSFILE, rc = VERR_INTERNAL_ERROR_3);
+
+ if (pszFilename)
+ DMG_PRINTF(("DMG: Using '%s' within XAR file '%s'...\n", pszName, pszFilename));
+ *phXarFss = hXarFss;
+ *phDmgFileInXar = hDmgFileInXar;
+
+ RTStrFree(pszName);
+ RTVfsObjRelease(hVfsObj);
+
+ return VINF_SUCCESS;
+ }
+ }
+
+ /* Release the current return values. */
+ RTStrFree(pszName);
+ RTVfsObjRelease(hVfsObj);
+ }
+
+ /* Not found or some kind of error. */
+ RTVfsFsStrmRelease(hXarFss);
+ if (rc == VERR_EOF)
+ rc = VERR_VD_DMG_NOT_FOUND_INSIDE_XAR;
+ AssertStmt(RT_FAILURE_NP(rc), rc = VERR_INTERNAL_ERROR_4);
+ return rc;
+}
+
+
+/**
+ * Worker for dmgOpen that reads in and validates all the necessary
+ * structures from the image.
+ *
+ * @returns VBox status code.
+ * @param pThis The DMG instance data.
+ * @param uOpenFlags Flags for defining the open type.
+ */
+static DECLCALLBACK(int) dmgOpenImage(PDMGIMAGE pThis, unsigned uOpenFlags)
+{
+ pThis->uOpenFlags = uOpenFlags;
+
+ pThis->pIfError = VDIfErrorGet(pThis->pVDIfsDisk);
+ pThis->pIfIoXxx = VDIfIoIntGet(pThis->pVDIfsImage);
+ pThis->hDmgFileInXar = NIL_RTVFSFILE;
+ pThis->hXarFss = NIL_RTVFSFSSTREAM;
+ AssertPtrReturn(pThis->pIfIoXxx, VERR_INVALID_PARAMETER);
+
+ int rc = vdIfIoIntFileOpen(pThis->pIfIoXxx, pThis->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */),
+ &pThis->pStorage);
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ return rc;
+ }
+
+ /*
+ * Check for XAR archive.
+ */
+ uint32_t u32XarMagic;
+ rc = dmgWrapFileReadSync(pThis, 0, &u32XarMagic, sizeof(u32XarMagic));
+ if (RT_FAILURE(rc))
+ return rc;
+ if (u32XarMagic == XAR_HEADER_MAGIC)
+ {
+ rc = dmgOpenImageWithinXar(VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */),
+ pThis->pIfIoXxx,
+ pThis->pStorage,
+ pThis->pszFilename,
+ &pThis->hXarFss, &pThis->hDmgFileInXar);
+ if (RT_FAILURE(rc))
+ return rc;
+#ifdef VBOX_WITH_DIRECT_XAR_ACCESS
+ vdIfIoIntFileClose(pThis->pIfIoXxx, pThis->pStorage);
+ pThis->pStorage = NULL;
+#endif
+ }
+#if 0 /* This is for testing whether the VFS wrappers actually works. */
+ else
+ {
+ rc = RTVfsFileOpenNormal(pThis->pszFilename, VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */),
+ &pThis->hDmgFileInXar);
+ if (RT_FAILURE(rc))
+ return rc;
+ vdIfIoIntFileClose(pThis->pIfIoXxx, pThis->pStorage);
+ pThis->pStorage = NULL;
+ }
+#endif
+
+ /*
+ * Read the footer.
+ */
+ rc = dmgWrapFileGetSize(pThis, &pThis->cbFile);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (pThis->cbFile < 1024)
+ return VERR_VD_DMG_INVALID_HEADER;
+ rc = dmgWrapFileReadSync(pThis, pThis->cbFile - sizeof(pThis->Ftr), &pThis->Ftr, sizeof(pThis->Ftr));
+ if (RT_FAILURE(rc))
+ return rc;
+ dmgUdifFtrFile2HostEndian(&pThis->Ftr);
+
+ /*
+ * Do we recognize the footer structure? If so, is it valid?
+ */
+ if (pThis->Ftr.u32Magic != DMGUDIF_MAGIC)
+ return VERR_VD_DMG_INVALID_HEADER;
+ if (pThis->Ftr.u32Version != DMGUDIF_VER_CURRENT)
+ return VERR_VD_DMG_INVALID_HEADER;
+ if (pThis->Ftr.cbFooter != sizeof(pThis->Ftr))
+ return VERR_VD_DMG_INVALID_HEADER;
+
+ if (!dmgUdifFtrIsValid(&pThis->Ftr, pThis->cbFile - sizeof(pThis->Ftr)))
+ {
+ DMG_PRINTF(("Bad DMG: '%s' cbFile=%RTfoff\n", pThis->pszFilename, pThis->cbFile));
+ return VERR_VD_DMG_INVALID_HEADER;
+ }
+
+ pThis->cbSize = pThis->Ftr.cSectors * DMG_SECTOR_SIZE;
+
+ /*
+ * Read and parse the XML portion.
+ */
+ size_t cchXml = (size_t)pThis->Ftr.cbXml;
+ char *pszXml = (char *)RTMemAlloc(cchXml + 1);
+ if (!pszXml)
+ return VERR_NO_MEMORY;
+ rc = dmgWrapFileReadSync(pThis, pThis->Ftr.offXml, pszXml, cchXml);
+ if (RT_SUCCESS(rc))
+ {
+ pszXml[cchXml] = '\0';
+ const char *pszError = dmgOpenXmlToRsrc(pThis, pszXml);
+ if (!pszError)
+ {
+ PCDMGUDIFRSRCARRAY pRsrcBlkx = NULL;
+
+ rc = dmgGetRsrcData(pThis, "blkx", &pRsrcBlkx);
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned idxBlkx = 0; idxBlkx < pRsrcBlkx->cEntries; idxBlkx++)
+ {
+ PDMGBLKX pBlkx = NULL;
+
+ if (pRsrcBlkx->aEntries[idxBlkx].cbData < sizeof(DMGBLKX))
+ {
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ break;
+ }
+
+ pBlkx = (PDMGBLKX)RTMemAllocZ(pRsrcBlkx->aEntries[idxBlkx].cbData);
+ if (!pBlkx)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ memcpy(pBlkx, pRsrcBlkx->aEntries[idxBlkx].pbData, pRsrcBlkx->aEntries[idxBlkx].cbData);
+
+ dmgBlkxFile2HostEndian(pBlkx);
+
+ if ( dmgBlkxIsValid(pBlkx)
+ && pRsrcBlkx->aEntries[idxBlkx].cbData == pBlkx->cBlocksRunCount * sizeof(DMGBLKXDESC) + sizeof(DMGBLKX))
+ rc = dmgBlkxParse(pThis, pBlkx);
+ else
+ rc = VERR_VD_DMG_INVALID_HEADER;
+
+ RTMemFree(pBlkx);
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ }
+ else
+ {
+ DMG_PRINTF(("**** XML DUMP BEGIN ***\n%s\n**** XML DUMP END ****\n", pszXml));
+ DMG_PRINTF(("**** Bad XML at %#lx (%lu) ***\n%.256s\n**** Bad XML END ****\n",
+ (unsigned long)(pszError - pszXml), (unsigned long)(pszError - pszXml), pszError));
+ rc = VERR_VD_DMG_XML_PARSE_ERROR;
+ }
+ }
+ RTMemFree(pszXml);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pThis->RegionList.aRegions[0];
+ pThis->RegionList.fFlags = 0;
+ pThis->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 2048;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 2048;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pThis->cbSize;
+ }
+ else
+ dmgFreeImage(pThis, false);
+ return rc;
+}
+
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnProbe} */
+static DECLCALLBACK(int) dmgProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pVDIfsDisk, enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p penmType=%#p\n",
+ pszFilename, pVDIfsDisk, pVDIfsImage, penmType));
+
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ /*
+ * Open the file and check for XAR.
+ */
+ PVDIOSTORAGE pStorage = NULL;
+ int rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY, false /* fCreate */),
+ &pStorage);
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("returns %Rrc (error opening file)\n", rc));
+ return rc;
+ }
+
+ /*
+ * Check for XAR file.
+ */
+ RTVFSFSSTREAM hXarFss = NIL_RTVFSFSSTREAM;
+ RTVFSFILE hDmgFileInXar = NIL_RTVFSFILE;
+ uint32_t u32XarMagic;
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, &u32XarMagic, sizeof(u32XarMagic));
+ if ( RT_SUCCESS(rc)
+ && u32XarMagic == XAR_HEADER_MAGIC)
+ {
+ rc = dmgOpenImageWithinXar(RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE,
+ pIfIo, pStorage, pszFilename,
+ &hXarFss, &hDmgFileInXar);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Read the DMG footer.
+ */
+ uint64_t cbFile;
+ if (hDmgFileInXar == NIL_RTVFSFILE)
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ else
+ rc = RTVfsFileQuerySize(hDmgFileInXar, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile >= sizeof(DMGUDIF))
+ {
+ DMGUDIF Ftr;
+ uint64_t offFtr = cbFile - sizeof(Ftr);
+ if (hDmgFileInXar == NIL_RTVFSFILE)
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, offFtr, &Ftr, sizeof(Ftr));
+ else
+ rc = RTVfsFileReadAt(hDmgFileInXar, offFtr, &Ftr, sizeof(Ftr), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do we recognize this stuff? Does it look valid?
+ */
+ if ( Ftr.u32Magic == RT_H2BE_U32_C(DMGUDIF_MAGIC)
+ && Ftr.u32Version == RT_H2BE_U32_C(DMGUDIF_VER_CURRENT)
+ && Ftr.cbFooter == RT_H2BE_U32_C(sizeof(Ftr)))
+ {
+ dmgUdifFtrFile2HostEndian(&Ftr);
+ if (dmgUdifFtrIsValid(&Ftr, offFtr))
+ {
+ rc = VINF_SUCCESS;
+ *penmType = VDTYPE_OPTICAL_DISC;
+ }
+ else
+ {
+ DMG_PRINTF(("Bad DMG: '%s' offFtr=%RTfoff\n", pszFilename, offFtr));
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ }
+ }
+ else
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ }
+ else
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ }
+ else
+ rc = VERR_VD_DMG_INVALID_HEADER;
+
+ /* Clean up. */
+ RTVfsFileRelease(hDmgFileInXar);
+ RTVfsFsStrmRelease(hXarFss);
+ vdIfIoIntFileClose(pIfIo, pStorage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnOpen} */
+static DECLCALLBACK(int) dmgOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n", pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+
+ NOREF(enmType); /**< @todo r=klaus make use of the type info. */
+
+ /* Check open flags. All valid flags are (in principle) supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+
+ /* Check remaining arguments. */
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename, VERR_INVALID_PARAMETER);
+
+ /*
+ * Reject combinations we don't currently support.
+ *
+ * There is no point in being paranoid about the input here as we're just a
+ * simple backend and can expect the caller to be the only user and already
+ * have validate what it passes thru to us.
+ */
+ if ( !(uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ || (uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO))
+ {
+ LogFlowFunc(("Unsupported flag(s): %#x\n", uOpenFlags));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Create the basic instance data structure and open the file,
+ * then hand it over to a worker function that does all the rest.
+ */
+ int rc = VERR_NO_MEMORY;
+ PDMGIMAGE pThis = (PDMGIMAGE)RTMemAllocZ(RT_UOFFSETOF(DMGIMAGE, RegionList.aRegions[1]));
+ if (pThis)
+ {
+ pThis->pszFilename = pszFilename;
+ pThis->pStorage = NULL;
+ pThis->pVDIfsDisk = pVDIfsDisk;
+ pThis->pVDIfsImage = pVDIfsImage;
+
+ rc = dmgOpenImage(pThis, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pThis;
+ else
+ RTMemFree(pThis);
+ }
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnCreate} */
+static DECLCALLBACK(int) dmgCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ RT_NOREF8(pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags);
+ RT_NOREF7(uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData);
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+ int rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnRename} */
+static DECLCALLBACK(int) dmgRename(void *pBackendData, const char *pszFilename)
+{
+ RT_NOREF2(pBackendData, pszFilename);
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnClose} */
+static DECLCALLBACK(int) dmgClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ int rc = dmgFreeImage(pThis, fDelete);
+ RTMemFree(pThis);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnRead} */
+static DECLCALLBACK(int) dmgRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToRead=%zu pcbActuallyRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ PDMGEXTENT pExtent = NULL;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pThis);
+ Assert(uOffset % DMG_SECTOR_SIZE == 0);
+ Assert(cbToRead % DMG_SECTOR_SIZE == 0);
+
+ if ( uOffset + cbToRead > pThis->cbSize
+ || cbToRead == 0)
+ {
+ LogFlowFunc(("returns VERR_INVALID_PARAMETER\n"));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ pExtent = dmgExtentGetFromOffset(pThis, DMG_BYTE2BLOCK(uOffset));
+
+ if (pExtent)
+ {
+ uint64_t uExtentRel = DMG_BYTE2BLOCK(uOffset) - pExtent->uSectorExtent;
+
+ /* Remain in this extent. */
+ cbToRead = RT_MIN(cbToRead, DMG_BLOCK2BYTE(pExtent->cSectorsExtent - uExtentRel));
+
+ switch (pExtent->enmType)
+ {
+ case DMGEXTENTTYPE_RAW:
+ {
+ rc = dmgWrapFileReadUser(pThis, pExtent->offFileStart + DMG_BLOCK2BYTE(uExtentRel), pIoCtx, cbToRead);
+ break;
+ }
+ case DMGEXTENTTYPE_ZERO:
+ {
+ vdIfIoIntIoCtxSet(pThis->pIfIoXxx, pIoCtx, 0, cbToRead);
+ break;
+ }
+ case DMGEXTENTTYPE_COMP_ZLIB:
+ {
+ if (pThis->pExtentDecomp != pExtent)
+ {
+ if (DMG_BLOCK2BYTE(pExtent->cSectorsExtent) > pThis->cbDecompExtent)
+ {
+ if (RT_LIKELY(pThis->pvDecompExtent))
+ RTMemFree(pThis->pvDecompExtent);
+
+ pThis->pvDecompExtent = RTMemAllocZ(DMG_BLOCK2BYTE(pExtent->cSectorsExtent));
+ if (!pThis->pvDecompExtent)
+ rc = VERR_NO_MEMORY;
+ else
+ pThis->cbDecompExtent = DMG_BLOCK2BYTE(pExtent->cSectorsExtent);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = dmgFileInflateSync(pThis, pExtent->offFileStart, pExtent->cbFile,
+ pThis->pvDecompExtent,
+ RT_MIN(pThis->cbDecompExtent, DMG_BLOCK2BYTE(pExtent->cSectorsExtent)));
+ if (RT_SUCCESS(rc))
+ pThis->pExtentDecomp = pExtent;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ vdIfIoIntIoCtxCopyTo(pThis->pIfIoXxx, pIoCtx,
+ (uint8_t *)pThis->pvDecompExtent + DMG_BLOCK2BYTE(uExtentRel),
+ cbToRead);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid extent type\n"));
+ }
+
+ if (RT_SUCCESS(rc))
+ *pcbActuallyRead = cbToRead;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnWrite} */
+static DECLCALLBACK(int) dmgWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ RT_NOREF7(uOffset, cbToWrite, pIoCtx, pcbWriteProcess, pcbPreRead, pcbPostRead, fWrite);
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VERR_NOT_IMPLEMENTED;
+
+ AssertPtr(pThis);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToWrite % 512 == 0);
+
+ if (!(pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ AssertMsgFailed(("Not implemented\n"));
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnFlush} */
+static DECLCALLBACK(int) dmgFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ RT_NOREF1(pIoCtx);
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pThis);
+
+ rc = dmgFlushImage(pThis);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetVersion} */
+static DECLCALLBACK(unsigned) dmgGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, 0);
+
+ return 1;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetFileSize} */
+static DECLCALLBACK(uint64_t) dmgGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, 0);
+
+ uint64_t cbFile = 0;
+ if (pThis->pStorage || pThis->hDmgFileInXar != NIL_RTVFSFILE)
+ {
+ int rc = dmgWrapFileGetSize(pThis, &cbFile);
+ if (RT_FAILURE(rc))
+ cbFile = 0; /* Make sure it is 0 */
+ }
+
+ LogFlowFunc(("returns %lld\n", cbFile));
+ return cbFile;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetPCHSGeometry} */
+static DECLCALLBACK(int) dmgGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ if (pThis->PCHSGeometry.cCylinders)
+ *pPCHSGeometry = pThis->PCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetPCHSGeometry} */
+static DECLCALLBACK(int) dmgSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pThis->PCHSGeometry = *pPCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetLCHSGeometry} */
+static DECLCALLBACK(int) dmgGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ if (pThis->LCHSGeometry.cCylinders)
+ *pLCHSGeometry = pThis->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetLCHSGeometry} */
+static DECLCALLBACK(int) dmgSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n",
+ pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pThis->LCHSGeometry = *pLCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) dmgQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) dmgRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetImageFlags} */
+static DECLCALLBACK(unsigned) dmgGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ AssertPtrReturn(pThis, 0);
+
+ LogFlowFunc(("returns %#x\n", pThis->uImageFlags));
+ return pThis->uImageFlags;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetOpenFlags} */
+static DECLCALLBACK(unsigned) dmgGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, 0);
+
+ LogFlowFunc(("returns %#x\n", pThis->uOpenFlags));
+ return pThis->uOpenFlags;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetOpenFlags} */
+static DECLCALLBACK(int) dmgSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pThis || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_SHAREABLE | VD_OPEN_FLAGS_SEQUENTIAL
+ | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ rc = dmgFreeImage(pThis, false);
+ if (RT_SUCCESS(rc))
+ rc = dmgOpenImage(pThis, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(dmgGetComment);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(dmgSetComment, PDMGIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(dmgGetUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(dmgSetUuid, PDMGIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(dmgGetModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(dmgSetModificationUuid, PDMGIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(dmgGetParentUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(dmgSetParentUuid, PDMGIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(dmgGetParentModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(dmgSetParentModificationUuid, PDMGIMAGE);
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnDump} */
+static DECLCALLBACK(void) dmgDump(void *pBackendData)
+{
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pThis);
+ vdIfErrorMessage(pThis->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cSectors=%llu\n",
+ pThis->PCHSGeometry.cCylinders, pThis->PCHSGeometry.cHeads, pThis->PCHSGeometry.cSectors,
+ pThis->LCHSGeometry.cCylinders, pThis->LCHSGeometry.cHeads, pThis->LCHSGeometry.cSectors,
+ pThis->cbSize / DMG_SECTOR_SIZE);
+}
+
+
+const VDIMAGEBACKEND g_DmgBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "DMG",
+ /* uBackendCaps */
+ VD_CAP_FILE | VD_CAP_VFS,
+ /* paFileExtensions */
+ s_aDmgFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ dmgProbe,
+ /* pfnOpen */
+ dmgOpen,
+ /* pfnCreate */
+ dmgCreate,
+ /* pfnRename */
+ dmgRename,
+ /* pfnClose */
+ dmgClose,
+ /* pfnRead */
+ dmgRead,
+ /* pfnWrite */
+ dmgWrite,
+ /* pfnFlush */
+ dmgFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ dmgGetVersion,
+ /* pfnGetFileSize */
+ dmgGetFileSize,
+ /* pfnGetPCHSGeometry */
+ dmgGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ dmgSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ dmgGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ dmgSetLCHSGeometry,
+ /* pfnQueryRegions */
+ dmgQueryRegions,
+ /* pfnRegionListRelease */
+ dmgRegionListRelease,
+ /* pfnGetImageFlags */
+ dmgGetImageFlags,
+ /* pfnGetOpenFlags */
+ dmgGetOpenFlags,
+ /* pfnSetOpenFlags */
+ dmgSetOpenFlags,
+ /* pfnGetComment */
+ dmgGetComment,
+ /* pfnSetComment */
+ dmgSetComment,
+ /* pfnGetUuid */
+ dmgGetUuid,
+ /* pfnSetUuid */
+ dmgSetUuid,
+ /* pfnGetModificationUuid */
+ dmgGetModificationUuid,
+ /* pfnSetModificationUuid */
+ dmgSetModificationUuid,
+ /* pfnGetParentUuid */
+ dmgGetParentUuid,
+ /* pfnSetParentUuid */
+ dmgSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ dmgGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ dmgSetParentModificationUuid,
+ /* pfnDump */
+ dmgDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ NULL,
+ /* pfnSetParentFilename */
+ NULL,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ NULL,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
+
diff --git a/src/VBox/Storage/ISCSI.cpp b/src/VBox/Storage/ISCSI.cpp
new file mode 100644
index 00000000..5fb24d21
--- /dev/null
+++ b/src/VBox/Storage/ISCSI.cpp
@@ -0,0 +1,5553 @@
+/* $Id: ISCSI.cpp $ */
+/** @file
+ * iSCSI initiator driver, VD backend.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_ISCSI
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/md5.h>
+#include <iprt/tcp.h>
+#include <iprt/time.h>
+#include <VBox/scsi.h>
+
+#include "VDBackends.h"
+#include "VDBackendsInline.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** The maximum number of release log entries per image. */
+#define MAX_LOG_REL_ERRORS 1024
+
+/** Default port number to use for iSCSI. */
+#define ISCSI_DEFAULT_PORT 3260
+
+
+/** Converts a number in the range of 0 - 15 into the corresponding hex char. */
+#define NUM_2_HEX(b) ('0' + (b) + (((b) > 9) ? 39 : 0))
+/** Converts a hex char into the corresponding number in the range 0-15. */
+#define HEX_2_NUM(c) (((c) <= '9') ? ((c) - '0') : (((c - 'A' + 10) & 0xf)))
+/* Converts a base64 char into the corresponding number in the range 0-63. */
+#define B64_2_NUM(c) ((c >= 'A' && c <= 'Z') ? (c - 'A') : (c >= 'a' && c <= 'z') ? (c - 'a' + 26) : (c >= '0' && c <= '9') ? (c - '0' + 52) : (c == '+') ? 62 : (c == '/') ? 63 : -1)
+
+
+/** Minimum CHAP_MD5 challenge length in bytes. */
+#define CHAP_MD5_CHALLENGE_MIN 16
+/** Maximum CHAP_MD5 challenge length in bytes. */
+#define CHAP_MD5_CHALLENGE_MAX 24
+
+
+/**
+ * SCSI peripheral device type. */
+typedef enum SCSIDEVTYPE
+{
+ /** direct-access device. */
+ SCSI_DEVTYPE_DISK = 0,
+ /** sequential-access device. */
+ SCSI_DEVTYPE_TAPE,
+ /** printer device. */
+ SCSI_DEVTYPE_PRINTER,
+ /** processor device. */
+ SCSI_DEVTYPE_PROCESSOR,
+ /** write-once device. */
+ SCSI_DEVTYPE_WORM,
+ /** CD/DVD device. */
+ SCSI_DEVTYPE_CDROM,
+ /** scanner device. */
+ SCSI_DEVTYPE_SCANNER,
+ /** optical memory device. */
+ SCSI_DEVTYPE_OPTICAL,
+ /** medium changer. */
+ SCSI_DEVTYPE_CHANGER,
+ /** communications device. */
+ SCSI_DEVTYPE_COMMUNICATION,
+ /** storage array controller device. */
+ SCSI_DEVTYPE_RAIDCTL = 0x0c,
+ /** enclosure services device. */
+ SCSI_DEVTYPE_ENCLOSURE,
+ /** simplified direct-access device. */
+ SCSI_DEVTYPE_SIMPLEDISK,
+ /** optical card reader/writer device. */
+ SCSI_DEVTYPE_OCRW,
+ /** bridge controller device. */
+ SCSI_DEVTYPE_BRIDGE,
+ /** object-based storage device. */
+ SCSI_DEVTYPE_OSD
+} SCSIDEVTYPE;
+
+/** Mask for extracting the SCSI device type out of the first byte of the INQUIRY response. */
+#define SCSI_DEVTYPE_MASK 0x1f
+
+/** Mask to extract the CmdQue bit out of the seventh byte of the INQUIRY response. */
+#define SCSI_INQUIRY_CMDQUE_MASK 0x02
+
+/** Maximum PDU payload size we can handle in one piece. Greater or equal than
+ * s_iscsiConfigDefaultWriteSplit. */
+#define ISCSI_DATA_LENGTH_MAX _256K
+
+/** Maximum PDU size we can handle in one piece. */
+#define ISCSI_RECV_PDU_BUFFER_SIZE (ISCSI_DATA_LENGTH_MAX + ISCSI_BHS_SIZE)
+
+
+/** Version of the iSCSI standard which this initiator driver can handle. */
+#define ISCSI_MY_VERSION 0
+
+
+/** Length of ISCSI basic header segment. */
+#define ISCSI_BHS_SIZE 48
+
+
+/** Reserved task tag value. */
+#define ISCSI_TASK_TAG_RSVD 0xffffffff
+
+
+/**
+ * iSCSI opcodes. */
+typedef enum ISCSIOPCODE
+{
+ /** NOP-Out. */
+ ISCSIOP_NOP_OUT = 0x00000000,
+ /** SCSI command. */
+ ISCSIOP_SCSI_CMD = 0x01000000,
+ /** SCSI task management request. */
+ ISCSIOP_SCSI_TASKMGMT_REQ = 0x02000000,
+ /** Login request. */
+ ISCSIOP_LOGIN_REQ = 0x03000000,
+ /** Text request. */
+ ISCSIOP_TEXT_REQ = 0x04000000,
+ /** SCSI Data-Out. */
+ ISCSIOP_SCSI_DATA_OUT = 0x05000000,
+ /** Logout request. */
+ ISCSIOP_LOGOUT_REQ = 0x06000000,
+ /** SNACK request. */
+ ISCSIOP_SNACK_REQ = 0x10000000,
+
+ /** NOP-In. */
+ ISCSIOP_NOP_IN = 0x20000000,
+ /** SCSI response. */
+ ISCSIOP_SCSI_RES = 0x21000000,
+ /** SCSI Task Management response. */
+ ISCSIOP_SCSI_TASKMGMT_RES = 0x22000000,
+ /** Login response. */
+ ISCSIOP_LOGIN_RES = 0x23000000,
+ /** Text response. */
+ ISCSIOP_TEXT_RES = 0x24000000,
+ /** SCSI Data-In. */
+ ISCSIOP_SCSI_DATA_IN = 0x25000000,
+ /** Logout response. */
+ ISCSIOP_LOGOUT_RES = 0x26000000,
+ /** Ready To Transfer (R2T). */
+ ISCSIOP_R2T = 0x31000000,
+ /** Asynchronous message. */
+ ISCSIOP_ASYN_MSG = 0x32000000,
+ /** Reject. */
+ ISCSIOP_REJECT = 0x3f000000
+} ISCSIOPCODE;
+
+/** Mask for extracting the iSCSI opcode out of the first header word. */
+#define ISCSIOP_MASK 0x3f000000
+
+
+/** ISCSI BHS word 0: Request should be processed immediately. */
+#define ISCSI_IMMEDIATE_DELIVERY_BIT 0x40000000
+
+/** ISCSI BHS word 0: This is the final PDU for this request/response. */
+#define ISCSI_FINAL_BIT 0x00800000
+/** ISCSI BHS word 0: Mask for extracting the CSG. */
+#define ISCSI_CSG_MASK 0x000c0000
+/** ISCSI BHS word 0: Shift offset for extracting the CSG. */
+#define ISCSI_CSG_SHIFT 18
+/** ISCSI BHS word 0: Mask for extracting the NSG. */
+#define ISCSI_NSG_MASK 0x00030000
+/** ISCSI BHS word 0: Shift offset for extracting the NSG. */
+#define ISCSI_NSG_SHIFT 16
+
+/** ISCSI BHS word 0: task attribute untagged */
+#define ISCSI_TASK_ATTR_UNTAGGED 0x00000000
+/** ISCSI BHS word 0: task attribute simple */
+#define ISCSI_TASK_ATTR_SIMPLE 0x00010000
+/** ISCSI BHS word 0: task attribute ordered */
+#define ISCSI_TASK_ATTR_ORDERED 0x00020000
+/** ISCSI BHS word 0: task attribute head of queue */
+#define ISCSI_TASK_ATTR_HOQ 0x00030000
+/** ISCSI BHS word 0: task attribute ACA */
+#define ISCSI_TASK_ATTR_ACA 0x00040000
+
+/** ISCSI BHS word 0: transit to next login phase. */
+#define ISCSI_TRANSIT_BIT 0x00800000
+/** ISCSI BHS word 0: continue with login negotiation. */
+#define ISCSI_CONTINUE_BIT 0x00400000
+
+/** ISCSI BHS word 0: residual underflow. */
+#define ISCSI_RESIDUAL_UNFL_BIT 0x00020000
+/** ISCSI BHS word 0: residual overflow. */
+#define ISCSI_RESIDUAL_OVFL_BIT 0x00040000
+/** ISCSI BHS word 0: Bidirectional read residual underflow. */
+#define ISCSI_BI_READ_RESIDUAL_UNFL_BIT 0x00080000
+/** ISCSI BHS word 0: Bidirectional read residual overflow. */
+#define ISCSI_BI_READ_RESIDUAL_OVFL_BIT 0x00100000
+
+/** ISCSI BHS word 0: SCSI response mask. */
+#define ISCSI_SCSI_RESPONSE_MASK 0x0000ff00
+/** ISCSI BHS word 0: SCSI status mask. */
+#define ISCSI_SCSI_STATUS_MASK 0x000000ff
+
+/** ISCSI BHS word 0: response includes status. */
+#define ISCSI_STATUS_BIT 0x00010000
+
+/** Maximum number of scatter/gather segments needed to send a PDU. */
+#define ISCSI_SG_SEGMENTS_MAX 4
+
+/** Number of entries in the command table. */
+#define ISCSI_CMD_WAITING_ENTRIES 32
+
+/**
+ * iSCSI login status class. */
+typedef enum ISCSILOGINSTATUSCLASS
+{
+ /** Success. */
+ ISCSI_LOGIN_STATUS_CLASS_SUCCESS = 0,
+ /** Redirection. */
+ ISCSI_LOGIN_STATUS_CLASS_REDIRECTION,
+ /** Initiator error. */
+ ISCSI_LOGIN_STATUS_CLASS_INITIATOR_ERROR,
+ /** Target error. */
+ ISCSI_LOGIN_STATUS_CLASS_TARGET_ERROR
+} ISCSILOGINSTATUSCLASS;
+
+
+/**
+ * iSCSI connection state. */
+typedef enum ISCSISTATE
+{
+ /** Not having a connection/session at all. */
+ ISCSISTATE_FREE,
+ /** Currently trying to login. */
+ ISCSISTATE_IN_LOGIN,
+ /** Normal operation, corresponds roughly to the Full Feature Phase. */
+ ISCSISTATE_NORMAL,
+ /** Currently trying to logout. */
+ ISCSISTATE_IN_LOGOUT
+} ISCSISTATE;
+
+/**
+ * iSCSI PDU send/receive flags (and maybe more in the future). */
+typedef enum ISCSIPDUFLAGS
+{
+ /** No special flags */
+ ISCSIPDU_DEFAULT = 0,
+ /** Do not attempt to re-attach to the target if the connection is lost */
+ ISCSIPDU_NO_REATTACH = RT_BIT(1)
+} ISCSIPDUFLAGS;
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * iSCSI login negotiation parameter
+ */
+typedef struct ISCSIPARAMETER
+{
+ /** Name of the parameter. */
+ const char *pszParamName;
+ /** Value of the parameter. */
+ const char *pszParamValue;
+ /** Length of the binary parameter. 0=zero-terminated string. */
+ size_t cbParamValue;
+} ISCSIPARAMETER;
+
+
+/**
+ * iSCSI Response PDU buffer (scatter).
+ */
+typedef struct ISCSIRES
+{
+ /** Length of PDU segment. */
+ size_t cbSeg;
+ /** Pointer to PDU segment. */
+ void *pvSeg;
+} ISCSIRES;
+/** Pointer to an iSCSI Response PDU buffer. */
+typedef ISCSIRES *PISCSIRES;
+/** Pointer to a const iSCSI Response PDU buffer. */
+typedef ISCSIRES const *PCISCSIRES;
+
+
+/**
+ * iSCSI Request PDU buffer (gather).
+ */
+typedef struct ISCSIREQ
+{
+ /** Length of PDU segment in bytes. */
+ size_t cbSeg;
+ /** Pointer to PDU segment. */
+ const void *pcvSeg;
+} ISCSIREQ;
+/** Pointer to an iSCSI Request PDU buffer. */
+typedef ISCSIREQ *PISCSIREQ;
+/** Pointer to a const iSCSI Request PDU buffer. */
+typedef ISCSIREQ const *PCISCSIREQ;
+
+
+/**
+ * SCSI transfer directions.
+ */
+typedef enum SCSIXFER
+{
+ SCSIXFER_NONE = 0,
+ SCSIXFER_TO_TARGET,
+ SCSIXFER_FROM_TARGET,
+ SCSIXFER_TO_FROM_TARGET
+} SCSIXFER, *PSCSIXFER;
+
+/** Forward declaration. */
+typedef struct ISCSIIMAGE *PISCSIIMAGE;
+
+/**
+ * SCSI request structure.
+ */
+typedef struct SCSIREQ
+{
+ /** I/O context associated with this request. */
+ PVDIOCTX pIoCtx;
+ /** Transfer direction. */
+ SCSIXFER enmXfer;
+ /** Length of command block. */
+ size_t cbCDB;
+ /** Length of Initiator2Target data buffer. */
+ size_t cbI2TData;
+ /** Length of Target2Initiator data buffer. */
+ size_t cbT2IData;
+ /** Length of sense buffer
+ * This contains the number of sense bytes received upon completion. */
+ size_t cbSense;
+ /** Completion status of the command. */
+ uint8_t status;
+ /** The CDB. */
+ uint8_t abCDB[16];
+ /** The sense buffer. */
+ uint8_t abSense[96];
+ /** Status code to return if we got sense data. */
+ int rcSense;
+ /** Pointer to the Initiator2Target S/G list. */
+ PRTSGSEG paI2TSegs;
+ /** Number of entries in the I2T S/G list. */
+ unsigned cI2TSegs;
+ /** Pointer to the Target2Initiator S/G list. */
+ PRTSGSEG paT2ISegs;
+ /** Number of entries in the T2I S/G list. */
+ unsigned cT2ISegs;
+ /** S/G buffer for the target to initiator bits. */
+ RTSGBUF SgBufT2I;
+ /** Number of retries if the command completes with sense
+ * data before we return with an error.
+ */
+ unsigned cSenseRetries;
+ /** The S/G list - variable in size.
+ * This array holds both the I2T and T2I segments.
+ * The I2T segments are first and the T2I are second.
+ */
+ RTSGSEG aSegs[1];
+} SCSIREQ, *PSCSIREQ;
+
+typedef enum ISCSICMDTYPE
+{
+ /** Process a SCSI request. */
+ ISCSICMDTYPE_REQ = 0,
+ /** Call a function in the I/O thread. */
+ ISCSICMDTYPE_EXEC,
+ /** Usual 32bit hack. */
+ ISCSICMDTYPE_32BIT_HACK = 0x7fffffff
+} ISCSICMDTYPE;
+
+
+/** The command completion function. */
+typedef DECLCALLBACKTYPE(void, FNISCSICMDCOMPLETED,(PISCSIIMAGE pImage, int rcReq, void *pvUser));
+/** Pointer to a command completion function. */
+typedef FNISCSICMDCOMPLETED *PFNISCSICMDCOMPLETED;
+
+/** The command execution function. */
+typedef DECLCALLBACKTYPE(int, FNISCSIEXEC,(void *pvUser));
+/** Pointer to a command execution function. */
+typedef FNISCSIEXEC *PFNISCSIEXEC;
+
+/**
+ * Structure used to complete a synchronous request.
+ */
+typedef struct ISCSICMDSYNC
+{
+ /** Event semaphore to wakeup the waiting thread. */
+ RTSEMEVENT EventSem;
+ /** Status code of the command. */
+ int rcCmd;
+} ISCSICMDSYNC, *PISCSICMDSYNC;
+
+/**
+ * iSCSI command.
+ * Used to forward requests to the I/O thread
+ * if existing.
+ */
+typedef struct ISCSICMD
+{
+ /** Next one in the list. */
+ struct ISCSICMD *pNext;
+ /** Assigned ITT. */
+ uint32_t Itt;
+ /** Completion callback. */
+ PFNISCSICMDCOMPLETED pfnComplete;
+ /** Opaque user data. */
+ void *pvUser;
+ /** Command to execute. */
+ ISCSICMDTYPE enmCmdType;
+ /** Command type dependent data. */
+ union
+ {
+ /** Process a SCSI request. */
+ struct
+ {
+ /** The SCSI request to process. */
+ PSCSIREQ pScsiReq;
+ } ScsiReq;
+ /** Call a function in the I/O thread. */
+ struct
+ {
+ /** The method to execute. */
+ PFNISCSIEXEC pfnExec;
+ /** User data. */
+ void *pvUser;
+ } Exec;
+ } CmdType;
+} ISCSICMD, *PISCSICMD;
+
+/**
+ * Send iSCSI PDU.
+ * Contains all necessary data to send a PDU.
+ */
+typedef struct ISCSIPDUTX
+{
+ /** Pointer to the next PDu to send. */
+ struct ISCSIPDUTX *pNext;
+ /** The BHS. */
+ uint32_t aBHS[12];
+ /** Assigned CmdSN for this PDU. */
+ uint32_t CmdSN;
+ /** The S/G buffer used for sending. */
+ RTSGBUF SgBuf;
+ /** Number of bytes to send until the PDU completed. */
+ size_t cbSgLeft;
+ /** The iSCSI command this PDU belongs to. */
+ PISCSICMD pIScsiCmd;
+ /** Number of segments in the request segments array. */
+ unsigned cISCSIReq;
+ /** The request segments - variable in size. */
+ RTSGSEG aISCSIReq[1];
+} ISCSIPDUTX, *PISCSIPDUTX;
+
+/**
+ * Block driver instance data.
+ */
+typedef struct ISCSIIMAGE
+{
+ /** Pointer to the filename (location). Not really used. */
+ const char *pszFilename;
+ /** Pointer to the initiator name. */
+ char *pszInitiatorName;
+ /** Pointer to the target name. */
+ char *pszTargetName;
+ /** Pointer to the target address. */
+ char *pszTargetAddress;
+ /** Pointer to the user name for authenticating the Initiator. */
+ char *pszInitiatorUsername;
+ /** Pointer to the secret for authenticating the Initiator. */
+ uint8_t *pbInitiatorSecret;
+ /** Length of the secret for authenticating the Initiator. */
+ size_t cbInitiatorSecret;
+ /** Pointer to the user name for authenticating the Target. */
+ char *pszTargetUsername;
+ /** Pointer to the secret for authenticating the Initiator. */
+ uint8_t *pbTargetSecret;
+ /** Length of the secret for authenticating the Initiator. */
+ size_t cbTargetSecret;
+ /** Limit for iSCSI writes, essentially limiting the amount of data
+ * written in a single write. This is negotiated with the target, so
+ * the actual size might be smaller. */
+ uint32_t cbWriteSplit;
+ /** Initiator session identifier. */
+ uint64_t ISID;
+ /** SCSI Logical Unit Number. */
+ uint64_t LUN;
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** Config interface. */
+ PVDINTERFACECONFIG pIfConfig;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+ /** TCP network stack interface. */
+ PVDINTERFACETCPNET pIfNet;
+ /** Image open flags. */
+ unsigned uOpenFlags;
+ /** Number of re-login retries when a connection fails. */
+ uint32_t cISCSIRetries;
+ /** Sector size on volume. */
+ uint32_t cbSector;
+ /** Size of volume in sectors. */
+ uint64_t cVolume;
+ /** Total volume size in bytes. Easier than multiplying the above values all the time. */
+ uint64_t cbSize;
+
+ /** Negotiated maximum data length when sending to target. */
+ uint32_t cbSendDataLength;
+ /** Negotiated maximum data length when receiving from target. */
+ uint32_t cbRecvDataLength;
+
+ /** Current state of the connection/session. */
+ ISCSISTATE state;
+ /** Flag whether the first Login Response PDU has been seen. */
+ bool FirstRecvPDU;
+ /** Initiator Task Tag of the last iSCSI request PDU. */
+ uint32_t ITT;
+ /** Sequence number of the last command. */
+ uint32_t CmdSN;
+ /** Sequence number of the next command expected by the target. */
+ uint32_t ExpCmdSN;
+ /** Maximum sequence number accepted by the target (determines size of window). */
+ uint32_t MaxCmdSN;
+ /** Expected sequence number of next status. */
+ uint32_t ExpStatSN;
+ /** Currently active request. */
+ PISCSIREQ paCurrReq;
+ /** Segment number of currently active request. */
+ uint32_t cnCurrReq;
+ /** Pointer to receive PDU buffer. (Freed by RT) */
+ void *pvRecvPDUBuf;
+ /** Length of receive PDU buffer. */
+ size_t cbRecvPDUBuf;
+ /** Mutex protecting against concurrent use from several threads. */
+ RTSEMMUTEX Mutex;
+
+ /** Pointer to the target hostname. */
+ char *pszHostname;
+ /** Port to use on the target host. */
+ uint32_t uPort;
+ /** Socket handle of the TCP connection. */
+ VDSOCKET Socket;
+ /** Timeout for read operations on the TCP connection (in milliseconds). */
+ uint32_t uReadTimeout;
+ /** Flag whether to automatically generate the initiator name. */
+ bool fAutomaticInitiatorName;
+ /** Flag whether to automatically determine the LUN. */
+ bool fAutomaticLUN;
+ /** Flag whether to use the host IP stack or DevINIP. */
+ bool fHostIP;
+ /** Flag whether to dump malformed packets in the release log. */
+ bool fDumpMalformedPackets;
+ /** Flag whtether the target is readonly. */
+ bool fTargetReadOnly;
+ /** Flag whether to retry the connection before processing new requests. */
+ bool fTryReconnect;
+
+ /** Head of request queue */
+ PISCSICMD pScsiReqQueue;
+ /** Mutex protecting the request queue from concurrent access. */
+ RTSEMMUTEX MutexReqQueue;
+ /** I/O thread. */
+ RTTHREAD hThreadIo;
+ /** Flag whether the thread should be still running. */
+ volatile bool fRunning;
+ /* Flag whether the target supports command queuing. */
+ bool fCmdQueuingSupported;
+ /** Flag whether extended select is supported. */
+ bool fExtendedSelectSupported;
+ /** Padding used for aligning the PDUs. */
+ uint8_t aPadding[4];
+ /** Socket events to poll for. */
+ uint32_t fPollEvents;
+ /** Number of bytes to read to complete the current PDU. */
+ size_t cbRecvPDUResidual;
+ /** Current position in the PDU buffer. */
+ uint8_t *pbRecvPDUBufCur;
+ /** Flag whether we are currently reading the BHS. */
+ bool fRecvPDUBHS;
+ /** List of PDUs waiting to get transmitted. */
+ PISCSIPDUTX pIScsiPDUTxHead;
+ /** Tail of PDUs waiting to get transmitted. */
+ PISCSIPDUTX pIScsiPDUTxTail;
+ /** PDU we are currently transmitting. */
+ PISCSIPDUTX pIScsiPDUTxCur;
+ /** Number of commands waiting for an answer from the target.
+ * Used for timeout handling for poll.
+ */
+ unsigned cCmdsWaiting;
+ /** Table of commands waiting for a response from the target. */
+ PISCSICMD aCmdsWaiting[ISCSI_CMD_WAITING_ENTRIES];
+ /** Number of logins since last successful I/O.
+ * Used to catch the case where logging succeeds but
+ * processing read/write/flushes cause a disconnect.
+ */
+ volatile uint32_t cLoginsSinceIo;
+
+ /** Release log counter. */
+ unsigned cLogRelErrors;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} ISCSIIMAGE;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** Default initiator basename. */
+static const char *s_iscsiDefaultInitiatorBasename = "iqn.2009-08.com.sun.virtualbox.initiator";
+
+/** Default LUN. */
+static const char *s_iscsiConfigDefaultLUN = "0";
+
+/** Default timeout, 10 seconds. */
+static const char *s_iscsiConfigDefaultTimeout = "10000";
+
+/** Default write split value, less or equal to ISCSI_DATA_LENGTH_MAX. */
+static const char *s_iscsiConfigDefaultWriteSplit = "262144";
+
+/** Default host IP stack. */
+static const char *s_iscsiConfigDefaultHostIPStack = "1";
+
+/** Default dump malformed packet configuration value. */
+static const char *s_iscsiConfigDefaultDumpMalformedPackets = "0";
+
+/** Description of all accepted config parameters. */
+static const VDCONFIGINFO s_iscsiConfigInfo[] =
+{
+ { "TargetName", NULL, VDCFGVALUETYPE_STRING, VD_CFGKEY_MANDATORY },
+ /* LUN is defined of string type to handle the "enc" prefix. */
+ { "LUN", s_iscsiConfigDefaultLUN, VDCFGVALUETYPE_STRING, VD_CFGKEY_MANDATORY },
+ { "TargetAddress", NULL, VDCFGVALUETYPE_STRING, VD_CFGKEY_MANDATORY },
+ { "InitiatorName", NULL, VDCFGVALUETYPE_STRING, 0 },
+ { "InitiatorUsername", NULL, VDCFGVALUETYPE_STRING, 0 },
+ { "InitiatorSecret", NULL, VDCFGVALUETYPE_BYTES, 0 },
+ { "TargetUsername", NULL, VDCFGVALUETYPE_STRING, VD_CFGKEY_EXPERT },
+ { "TargetSecret", NULL, VDCFGVALUETYPE_BYTES, VD_CFGKEY_EXPERT },
+ { "WriteSplit", s_iscsiConfigDefaultWriteSplit, VDCFGVALUETYPE_INTEGER, VD_CFGKEY_EXPERT },
+ { "Timeout", s_iscsiConfigDefaultTimeout, VDCFGVALUETYPE_INTEGER, VD_CFGKEY_EXPERT },
+ { "HostIPStack", s_iscsiConfigDefaultHostIPStack, VDCFGVALUETYPE_INTEGER, VD_CFGKEY_EXPERT },
+ { "DumpMalformedPackets", s_iscsiConfigDefaultDumpMalformedPackets, VDCFGVALUETYPE_INTEGER, VD_CFGKEY_EXPERT },
+ { NULL, NULL, VDCFGVALUETYPE_INTEGER, 0 }
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/* iSCSI low-level functions (only to be used from the iSCSI high-level functions). */
+static uint32_t iscsiNewITT(PISCSIIMAGE pImage);
+static int iscsiSendPDU(PISCSIIMAGE pImage, PISCSIREQ paReq, uint32_t cnReq, uint32_t uFlags);
+static int iscsiRecvPDU(PISCSIIMAGE pImage, uint32_t itt, PISCSIRES paRes, uint32_t cnRes, uint32_t fFlags);
+static int iscsiRecvPDUAsync(PISCSIIMAGE pImage);
+static int iscsiSendPDUAsync(PISCSIIMAGE pImage);
+static int iscsiValidatePDU(PISCSIRES paRes, uint32_t cnRes);
+static int iscsiRecvPDUProcess(PISCSIIMAGE pImage, PISCSIRES paRes, uint32_t cnRes);
+static int iscsiPDUTxPrepare(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd);
+static int iscsiRecvPDUUpdateRequest(PISCSIIMAGE pImage, PISCSIRES paRes, uint32_t cnRes);
+static void iscsiCmdComplete(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd, int rcCmd);
+static int iscsiTextAddKeyValue(uint8_t *pbBuf, size_t cbBuf, size_t *pcbBufCurr, const char *pcszKey, const char *pcszValue, size_t cbValue);
+static int iscsiTextGetKeyValue(const uint8_t *pbBuf, size_t cbBuf, const char *pcszKey, const char **ppcszValue);
+static int iscsiStrToBinary(const char *pcszValue, uint8_t *pbValue, size_t *pcbValue);
+static int iscsiUpdateParameters(PISCSIIMAGE pImage, const uint8_t *pbBuf, size_t cbBuf);
+
+/* Serial number arithmetic comparison. */
+static bool serial_number_less(uint32_t sn1, uint32_t sn2);
+static bool serial_number_greater(uint32_t sn1, uint32_t sn2);
+
+/* CHAP-MD5 functions. */
+#ifdef IMPLEMENT_TARGET_AUTH
+static void chap_md5_generate_challenge(uint8_t *pbChallenge, size_t *pcbChallenge);
+#endif
+static void chap_md5_compute_response(uint8_t *pbResponse, uint8_t id, const uint8_t *pbChallenge, size_t cbChallenge,
+ const uint8_t *pbSecret, size_t cbSecret);
+
+/**
+ * Internal: release log wrapper limiting the number of entries.
+ */
+DECLINLINE(void) iscsiLogRel(PISCSIIMAGE pImage, const char *pcszFormat, ...)
+{
+ if (pImage->cLogRelErrors++ < MAX_LOG_REL_ERRORS)
+ {
+ va_list va;
+
+ va_start(va, pcszFormat);
+ LogRel(("%N\n", pcszFormat, &va));
+ va_end(va);
+ }
+}
+
+DECLINLINE(bool) iscsiIsClientConnected(PISCSIIMAGE pImage)
+{
+ return pImage->Socket != NIL_VDSOCKET
+ && pImage->pIfNet->pfnIsClientConnected(pImage->Socket);
+}
+
+/**
+ * Calculates the hash for the given ITT used
+ * to look up the command in the table.
+ */
+DECLINLINE(uint32_t) iscsiIttHash(uint32_t Itt)
+{
+ return Itt % ISCSI_CMD_WAITING_ENTRIES;
+}
+
+static PISCSICMD iscsiCmdGetFromItt(PISCSIIMAGE pImage, uint32_t Itt)
+{
+ PISCSICMD pIScsiCmd = NULL;
+
+ pIScsiCmd = pImage->aCmdsWaiting[iscsiIttHash(Itt)];
+
+ while ( pIScsiCmd
+ && pIScsiCmd->Itt != Itt)
+ pIScsiCmd = pIScsiCmd->pNext;
+
+ return pIScsiCmd;
+}
+
+static void iscsiCmdInsert(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd)
+{
+ PISCSICMD pIScsiCmdOld;
+ uint32_t idx = iscsiIttHash(pIScsiCmd->Itt);
+
+ Assert(!pIScsiCmd->pNext);
+
+ pIScsiCmdOld = pImage->aCmdsWaiting[idx];
+ pIScsiCmd->pNext = pIScsiCmdOld;
+ pImage->aCmdsWaiting[idx] = pIScsiCmd;
+ pImage->cCmdsWaiting++;
+}
+
+static PISCSICMD iscsiCmdRemove(PISCSIIMAGE pImage, uint32_t Itt)
+{
+ PISCSICMD pIScsiCmd = NULL;
+ PISCSICMD pIScsiCmdPrev = NULL;
+ uint32_t idx = iscsiIttHash(Itt);
+
+ pIScsiCmd = pImage->aCmdsWaiting[idx];
+
+ while ( pIScsiCmd
+ && pIScsiCmd->Itt != Itt)
+ {
+ pIScsiCmdPrev = pIScsiCmd;
+ pIScsiCmd = pIScsiCmd->pNext;
+ }
+
+ if (pIScsiCmd)
+ {
+ if (pIScsiCmdPrev)
+ {
+ AssertPtrNull(pIScsiCmd->pNext);
+ pIScsiCmdPrev->pNext = pIScsiCmd->pNext;
+ }
+ else
+ {
+ pImage->aCmdsWaiting[idx] = pIScsiCmd->pNext;
+ AssertPtrNull(pImage->aCmdsWaiting[idx]);
+ }
+ pImage->cCmdsWaiting--;
+ }
+
+ return pIScsiCmd;
+}
+
+/**
+ * Removes all commands from the table and returns the
+ * list head
+ *
+ * @returns Pointer to the head of the command list.
+ * @param pImage iSCSI connection to use.
+ */
+static PISCSICMD iscsiCmdRemoveAll(PISCSIIMAGE pImage)
+{
+ PISCSICMD pIScsiCmdHead = NULL;
+
+ for (unsigned idx = 0; idx < RT_ELEMENTS(pImage->aCmdsWaiting); idx++)
+ {
+ PISCSICMD pHead;
+ PISCSICMD pTail;
+
+ pHead = pImage->aCmdsWaiting[idx];
+ pImage->aCmdsWaiting[idx] = NULL;
+
+ if (pHead)
+ {
+ /* Get the tail. */
+ pTail = pHead;
+ while (pTail->pNext)
+ pTail = pTail->pNext;
+
+ /* Concatenate. */
+ pTail->pNext = pIScsiCmdHead;
+ pIScsiCmdHead = pHead;
+ }
+ }
+ pImage->cCmdsWaiting = 0;
+
+ return pIScsiCmdHead;
+}
+
+/**
+ * Dumps an iSCSI packet if enabled.
+ *
+ * @returns nothing.
+ * @param pImage The iSCSI image instance data.
+ * @param paISCSISegs Pointer to the segments array.
+ * @param cnISCSISegs Number of segments in the array.
+ * @param rc Status code for this packet.
+ * @param fRequest Flag whether this is request or response packet.
+ */
+static void iscsiDumpPacket(PISCSIIMAGE pImage, PISCSIREQ paISCSISegs, unsigned cnISCSISegs, int rc, bool fRequest)
+{
+ if (pImage->fDumpMalformedPackets)
+ {
+ LogRel(("iSCSI{%s}: Dumping %s packet completed with status code %Rrc\n", pImage->pszTargetName, fRequest ? "request" : "response", rc));
+ for (unsigned i = 0; i < cnISCSISegs; i++)
+ {
+ if (paISCSISegs[i].cbSeg)
+ {
+ LogRel(("iSCSI{%s}: Segment %u, size %zu\n"
+ "%.*Rhxd\n",
+ pImage->pszTargetName, i, paISCSISegs[i].cbSeg,
+ paISCSISegs[i].cbSeg, paISCSISegs[i].pcvSeg));
+ }
+ }
+ }
+}
+
+static int iscsiTransportConnect(PISCSIIMAGE pImage)
+{
+ int rc;
+ if (!pImage->pszHostname)
+ return VERR_NET_DEST_ADDRESS_REQUIRED;
+
+ rc = pImage->pIfNet->pfnClientConnect(pImage->Socket, pImage->pszHostname, pImage->uPort, pImage->uReadTimeout);
+ if (RT_FAILURE(rc))
+ {
+ if ( rc == VERR_NET_CONNECTION_REFUSED
+ || rc == VERR_NET_CONNECTION_RESET
+ || rc == VERR_NET_UNREACHABLE
+ || rc == VERR_NET_HOST_UNREACHABLE
+ || rc == VERR_NET_CONNECTION_TIMED_OUT)
+ {
+ /* Standardize return value for no connection. */
+ rc = VERR_NET_CONNECTION_REFUSED;
+ }
+ return rc;
+ }
+
+ /* Disable Nagle algorithm, we want things to be sent immediately. */
+ pImage->pIfNet->pfnSetSendCoalescing(pImage->Socket, false);
+
+ /* Make initiator name and ISID unique on this host. */
+ RTNETADDR LocalAddr;
+ rc = pImage->pIfNet->pfnGetLocalAddress(pImage->Socket, &LocalAddr);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ( LocalAddr.uPort == RTNETADDR_PORT_NA
+ || LocalAddr.uPort > 65535)
+ return VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED;
+ pImage->ISID &= ~65535ULL;
+ pImage->ISID |= LocalAddr.uPort;
+ /* Eliminate the port so that it isn't included below. */
+ LocalAddr.uPort = RTNETADDR_PORT_NA;
+ if (pImage->fAutomaticInitiatorName)
+ {
+ if (pImage->pszInitiatorName)
+ RTStrFree(pImage->pszInitiatorName);
+ RTStrAPrintf(&pImage->pszInitiatorName, "%s:01:%RTnaddr",
+ s_iscsiDefaultInitiatorBasename, &LocalAddr);
+ if (!pImage->pszInitiatorName)
+ return VERR_NO_MEMORY;
+ }
+ LogRel(("iSCSI: connect from initiator %s with source port %u\n", pImage->pszInitiatorName, pImage->ISID & 65535));
+ return VINF_SUCCESS;
+}
+
+
+static int iscsiTransportClose(PISCSIIMAGE pImage)
+{
+ int rc;
+
+ LogFlowFunc(("(%s:%d)\n", pImage->pszHostname, pImage->uPort));
+ if (iscsiIsClientConnected(pImage))
+ {
+ LogRel(("iSCSI: disconnect from initiator %s with source port %u\n", pImage->pszInitiatorName, pImage->ISID & 65535));
+ rc = pImage->pIfNet->pfnClientClose(pImage->Socket);
+ }
+ else
+ rc = VINF_SUCCESS;
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+static int iscsiTransportRead(PISCSIIMAGE pImage, PISCSIRES paResponse, unsigned int cnResponse)
+{
+ int rc = VINF_SUCCESS;
+ unsigned int i = 0;
+ size_t cbToRead, cbActuallyRead, residual, cbSegActual = 0, cbAHSLength, cbDataLength;
+ char *pDst;
+
+ LogFlowFunc(("cnResponse=%d (%s:%d)\n", cnResponse, pImage->pszHostname, pImage->uPort));
+ if (!iscsiIsClientConnected(pImage))
+ {
+ /* Reconnecting makes no sense in this case, as there will be nothing
+ * to receive. We would just run into a timeout. */
+ rc = VERR_BROKEN_PIPE;
+ }
+
+ if (RT_SUCCESS(rc) && paResponse[0].cbSeg >= ISCSI_BHS_SIZE)
+ {
+ cbToRead = 0;
+ residual = ISCSI_BHS_SIZE; /* Do not read more than the BHS length before the true PDU length is known. */
+ cbSegActual = residual;
+ pDst = (char *)paResponse[i].pvSeg;
+ uint64_t u64Timeout = RTTimeMilliTS() + pImage->uReadTimeout;
+ do
+ {
+ int64_t cMilliesRemaining = u64Timeout - RTTimeMilliTS();
+ if (cMilliesRemaining <= 0)
+ {
+ rc = VERR_TIMEOUT;
+ break;
+ }
+ Assert(cMilliesRemaining < 1000000);
+ rc = pImage->pIfNet->pfnSelectOne(pImage->Socket, cMilliesRemaining);
+ if (RT_FAILURE(rc))
+ break;
+ rc = pImage->pIfNet->pfnRead(pImage->Socket, pDst, residual, &cbActuallyRead);
+ if (RT_FAILURE(rc))
+ break;
+ if (cbActuallyRead == 0)
+ {
+ /* The other end has closed the connection. */
+ iscsiTransportClose(pImage);
+ pImage->state = ISCSISTATE_FREE;
+ rc = VERR_NET_CONNECTION_RESET;
+ break;
+ }
+ if (cbToRead == 0)
+ {
+ /* Currently reading the BHS. */
+ residual -= cbActuallyRead;
+ pDst += cbActuallyRead;
+ if (residual <= 40)
+ {
+ /* Enough data read to figure out the actual PDU size. */
+ uint32_t word1 = RT_N2H_U32(((uint32_t *)(paResponse[0].pvSeg))[1]);
+ cbAHSLength = (word1 & 0xff000000) >> 24;
+ cbAHSLength = ((cbAHSLength - 1) | 3) + 1; /* Add padding. */
+ cbDataLength = word1 & 0x00ffffff;
+ cbDataLength = ((cbDataLength - 1) | 3) + 1; /* Add padding. */
+ cbToRead = residual + cbAHSLength + cbDataLength;
+ residual += paResponse[0].cbSeg - ISCSI_BHS_SIZE;
+ if (residual > cbToRead)
+ residual = cbToRead;
+ cbSegActual = ISCSI_BHS_SIZE + cbAHSLength + cbDataLength;
+ /* Check whether we are already done with this PDU (no payload). */
+ if (cbToRead == 0)
+ break;
+ }
+ }
+ else
+ {
+ cbToRead -= cbActuallyRead;
+ if (cbToRead == 0)
+ break;
+ pDst += cbActuallyRead;
+ residual -= cbActuallyRead;
+ }
+ if (residual == 0)
+ {
+ i++;
+ if (i >= cnResponse)
+ {
+ /* No space left in receive buffers. */
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ pDst = (char *)paResponse[i].pvSeg;
+ residual = paResponse[i].cbSeg;
+ if (residual > cbToRead)
+ residual = cbToRead;
+ cbSegActual = residual;
+ }
+ LogFlowFunc(("cbToRead=%u residual=%u cbSegActual=%u cbActuallRead=%u\n",
+ cbToRead, residual, cbSegActual, cbActuallyRead));
+ } while (true);
+ }
+ else
+ {
+ if (RT_SUCCESS(rc))
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ paResponse[i].cbSeg = cbSegActual;
+ for (i++; i < cnResponse; i++)
+ paResponse[i].cbSeg = 0;
+ }
+
+ if (RT_UNLIKELY( RT_FAILURE(rc)
+ && ( rc == VERR_NET_CONNECTION_RESET
+ || rc == VERR_NET_CONNECTION_ABORTED
+ || rc == VERR_NET_CONNECTION_RESET_BY_PEER
+ || rc == VERR_NET_CONNECTION_REFUSED
+ || rc == VERR_BROKEN_PIPE)))
+ {
+ /* Standardize return value for broken connection. */
+ rc = VERR_BROKEN_PIPE;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+static int iscsiTransportWrite(PISCSIIMAGE pImage, PISCSIREQ paRequest, unsigned int cnRequest)
+{
+ int rc = VINF_SUCCESS;
+ unsigned int i;
+
+ LogFlowFunc(("cnRequest=%d (%s:%d)\n", cnRequest, pImage->pszHostname, pImage->uPort));
+ if (!iscsiIsClientConnected(pImage))
+ {
+ /* Attempt to reconnect if the connection was previously broken. */
+ rc = iscsiTransportConnect(pImage);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Construct scatter/gather buffer for entire request, worst case
+ * needs twice as many entries to allow for padding. */
+ unsigned cBuf = 0;
+ for (i = 0; i < cnRequest; i++)
+ {
+ cBuf++;
+ if (paRequest[i].cbSeg & 3)
+ cBuf++;
+ }
+ Assert(cBuf < ISCSI_SG_SEGMENTS_MAX);
+ RTSGBUF buf;
+ RTSGSEG aSeg[ISCSI_SG_SEGMENTS_MAX];
+ static char aPad[4] = { 0, 0, 0, 0 };
+ RTSgBufInit(&buf, &aSeg[0], cBuf);
+ unsigned iBuf = 0;
+ for (i = 0; i < cnRequest; i++)
+ {
+ /* Actual data chunk. */
+ aSeg[iBuf].pvSeg = (void *)paRequest[i].pcvSeg;
+ aSeg[iBuf].cbSeg = paRequest[i].cbSeg;
+ iBuf++;
+ /* Insert proper padding before the next chunk. */
+ if (paRequest[i].cbSeg & 3)
+ {
+ aSeg[iBuf].pvSeg = &aPad[0];
+ aSeg[iBuf].cbSeg = 4 - (paRequest[i].cbSeg & 3);
+ iBuf++;
+ }
+ }
+ /* Send out the request, the socket is set to send data immediately,
+ * avoiding unnecessary delays. */
+ rc = pImage->pIfNet->pfnSgWrite(pImage->Socket, &buf);
+
+ }
+
+ if (RT_UNLIKELY( RT_FAILURE(rc)
+ && ( rc == VERR_NET_CONNECTION_RESET
+ || rc == VERR_NET_CONNECTION_ABORTED
+ || rc == VERR_NET_CONNECTION_RESET_BY_PEER
+ || rc == VERR_NET_CONNECTION_REFUSED
+ || rc == VERR_BROKEN_PIPE)))
+ {
+ /* Standardize return value for broken connection. */
+ rc = VERR_BROKEN_PIPE;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+static int iscsiTransportOpen(PISCSIIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbHostname = 0; /* shut up gcc */
+ const char *pcszPort = NULL; /* shut up gcc */
+ char *pszPortEnd;
+ uint16_t uPort;
+
+ /* Clean up previous connection data. */
+ iscsiTransportClose(pImage);
+ if (pImage->pszHostname)
+ {
+ RTMemFree(pImage->pszHostname);
+ pImage->pszHostname = NULL;
+ pImage->uPort = 0;
+ }
+
+ /* Locate the port number via the colon separating the hostname from the port. */
+ if (*pImage->pszTargetAddress)
+ {
+ if (*pImage->pszTargetAddress != '[')
+ {
+ /* Normal hostname or IPv4 dotted decimal. */
+ pcszPort = strchr(pImage->pszTargetAddress, ':');
+ if (pcszPort != NULL)
+ {
+ cbHostname = pcszPort - pImage->pszTargetAddress;
+ pcszPort++;
+ }
+ else
+ cbHostname = strlen(pImage->pszTargetAddress);
+ }
+ else
+ {
+ /* IPv6 literal address. Contains colons, so skip to closing square bracket. */
+ pcszPort = strchr(pImage->pszTargetAddress, ']');
+ if (pcszPort != NULL)
+ {
+ pcszPort++;
+ cbHostname = pcszPort - pImage->pszTargetAddress;
+ if (*pcszPort == '\0')
+ pcszPort = NULL;
+ else if (*pcszPort != ':')
+ rc = VERR_PARSE_ERROR;
+ else
+ pcszPort++;
+ }
+ else
+ rc = VERR_PARSE_ERROR;
+ }
+ }
+ else
+ rc = VERR_PARSE_ERROR;
+
+ /* Now split address into hostname and port. */
+ if (RT_SUCCESS(rc))
+ {
+ pImage->pszHostname = (char *)RTMemAlloc(cbHostname + 1);
+ if (!pImage->pszHostname)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ if (pImage->pszTargetAddress[0] == '[')
+ memcpy(pImage->pszHostname, pImage->pszTargetAddress + 1, cbHostname);
+ else
+ memcpy(pImage->pszHostname, pImage->pszTargetAddress, cbHostname);
+ pImage->pszHostname[cbHostname] = '\0';
+ if (pcszPort != NULL)
+ {
+ rc = RTStrToUInt16Ex(pcszPort, &pszPortEnd, 0, &uPort);
+ /* Note that RT_SUCCESS() macro to check the rc value is not strict enough in this case. */
+ if (rc == VINF_SUCCESS && *pszPortEnd == '\0' && uPort != 0)
+ {
+ pImage->uPort = uPort;
+ }
+ else
+ {
+ rc = VERR_PARSE_ERROR;
+ }
+ }
+ else
+ pImage->uPort = ISCSI_DEFAULT_PORT;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (!iscsiIsClientConnected(pImage))
+ rc = iscsiTransportConnect(pImage);
+ }
+ else
+ {
+ if (pImage->pszHostname)
+ {
+ RTMemFree(pImage->pszHostname);
+ pImage->pszHostname = NULL;
+ }
+ pImage->uPort = 0;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Returns a human readable version of the given initiator login error detail.
+ *
+ * @returns String with the error detail.
+ * @param u8Detail The detail indicator from the response.
+ */
+static const char *iscsiGetLoginErrorDetail(uint8_t u8Detail)
+{
+ const char *pszDetail = NULL;
+
+ switch (u8Detail)
+ {
+ case 0x00:
+ pszDetail = "Miscelleanous iSCSI intiaitor error";
+ break;
+ case 0x01:
+ pszDetail = "Authentication failure";
+ break;
+ case 0x02:
+ pszDetail = "Authorization failure";
+ break;
+ case 0x03:
+ pszDetail = "Not found";
+ break;
+ case 0x04:
+ pszDetail = "Target removed";
+ break;
+ case 0x05:
+ pszDetail = "Unsupported version";
+ break;
+ case 0x06:
+ pszDetail = "Too many connections";
+ break;
+ case 0x07:
+ pszDetail = "Missing parameter";
+ break;
+ case 0x08:
+ pszDetail = "Can't include in session";
+ break;
+ case 0x09:
+ pszDetail = "Session type not supported";
+ break;
+ case 0x0a:
+ pszDetail = "Session does not exist";
+ break;
+ case 0x0b:
+ pszDetail = "Invalid request type during login";
+ break;
+ default:
+ pszDetail = "Unknown status detail";
+ }
+
+ return pszDetail;
+}
+
+/**
+ * Attempts one login attempt to the given target.
+ *
+ * @returns VBox status code.
+ * @retval VINF_TRY_AGAIN when getting redirected and having to start over.
+ * @retval VERR_TRY_AGAIN in case the connection was lost while receiving a reply
+ * from the target and the login attempt can be repeated.
+ * @param pImage The iSCSI connection state to be used.
+ */
+static int iscsiLogin(PISCSIIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t itt;
+ uint32_t csg, nsg, substate;
+ uint64_t isid_tsih;
+ uint8_t bBuf[4096]; /* Should be large enough even for large authentication values. */
+ size_t cbBuf;
+ bool transit;
+ uint8_t pbChallenge[1024]; /* RFC3720 specifies this as maximum. */
+ size_t cbChallenge = 0; /* shut up gcc */
+ uint8_t bChapIdx = 0; /* (MSC is used uninitialized) */
+ uint8_t aResponse[RTMD5HASHSIZE];
+ uint32_t cnISCSIReq = 0;
+ ISCSIREQ aISCSIReq[4];
+ uint32_t aReqBHS[12];
+ uint32_t cnISCSIRes = 0;
+ ISCSIRES aISCSIRes[2];
+ uint32_t aResBHS[12];
+ char *pszNext;
+ bool fParameterNeg = true;
+ pImage->cbRecvDataLength = ISCSI_DATA_LENGTH_MAX;
+ pImage->cbSendDataLength = RT_MIN(ISCSI_DATA_LENGTH_MAX, pImage->cbWriteSplit);
+ char szMaxDataLength[16];
+ RTStrPrintf(szMaxDataLength, sizeof(szMaxDataLength), "%u", ISCSI_DATA_LENGTH_MAX);
+ ISCSIPARAMETER aParameterNeg[] =
+ {
+ { "HeaderDigest", "None", 0 },
+ { "DataDigest", "None", 0 },
+ { "MaxConnections", "1", 0 },
+ { "InitialR2T", "No", 0 },
+ { "ImmediateData", "Yes", 0 },
+ { "MaxRecvDataSegmentLength", szMaxDataLength, 0 },
+ { "MaxBurstLength", szMaxDataLength, 0 },
+ { "FirstBurstLength", szMaxDataLength, 0 },
+ { "DefaultTime2Wait", "0", 0 },
+ { "DefaultTime2Retain", "60", 0 },
+ { "DataPDUInOrder", "Yes", 0 },
+ { "DataSequenceInOrder", "Yes", 0 },
+ { "ErrorRecoveryLevel", "0", 0 },
+ { "MaxOutstandingR2T", "1", 0 }
+ };
+
+ if (!iscsiIsClientConnected(pImage))
+ {
+ rc = iscsiTransportOpen(pImage);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pImage->state = ISCSISTATE_IN_LOGIN;
+ pImage->ITT = 1;
+ pImage->FirstRecvPDU = true;
+ pImage->CmdSN = 1;
+ pImage->ExpCmdSN = 0;
+ pImage->MaxCmdSN = 1;
+ pImage->ExpStatSN = 0;
+
+ /*
+ * Send login request to target.
+ */
+ itt = iscsiNewITT(pImage);
+ csg = 0;
+ nsg = 0;
+ substate = 0;
+ isid_tsih = pImage->ISID << 16; /* TSIH field currently always 0 */
+
+ do
+ {
+ transit = false;
+ cbBuf = 0;
+ /* Handle all cases with a single switch statement. */
+ switch (csg << 8 | substate)
+ {
+ case 0x0000: /* security negotiation, step 0: propose authentication. */
+ rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "SessionType", "Normal", 0);
+ if (RT_FAILURE(rc))
+ break;
+ rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "InitiatorName", pImage->pszInitiatorName, 0);
+ if (RT_FAILURE(rc))
+ break;
+ rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "TargetName", pImage->pszTargetName, 0);
+ if (RT_FAILURE(rc))
+ break;
+ if (pImage->pszInitiatorUsername == NULL)
+ {
+ /* No authentication. Immediately switch to next phase. */
+ rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "AuthMethod", "None", 0);
+ if (RT_FAILURE(rc))
+ break;
+ nsg = 1;
+ transit = true;
+ }
+ else
+ rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "AuthMethod", "CHAP,None", 0);
+ break;
+ case 0x0001: /* security negotiation, step 1: propose CHAP_MD5 variant. */
+ rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "CHAP_A", "5", 0);
+ break;
+ case 0x0002: /* security negotiation, step 2: send authentication info. */
+ rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "CHAP_N", pImage->pszInitiatorUsername, 0);
+ if (RT_FAILURE(rc))
+ break;
+ chap_md5_compute_response(aResponse, bChapIdx, pbChallenge, cbChallenge,
+ pImage->pbInitiatorSecret, pImage->cbInitiatorSecret);
+ rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "CHAP_R", (const char *)aResponse, RTMD5HASHSIZE);
+ if (RT_FAILURE(rc))
+ break;
+ nsg = 1;
+ transit = true;
+ break;
+ case 0x0100: /* login operational negotiation, step 0: set parameters. */
+ if (fParameterNeg)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(aParameterNeg); i++)
+ {
+ rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf,
+ aParameterNeg[i].pszParamName,
+ aParameterNeg[i].pszParamValue,
+ aParameterNeg[i].cbParamValue);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ fParameterNeg = false;
+ }
+
+ nsg = 3;
+ transit = true;
+ break;
+ case 0x0300: /* full feature phase. */
+ default:
+ /* Should never come here. */
+ AssertMsgFailed(("send: Undefined login state %d substate %d\n", csg, substate));
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ aReqBHS[0] = RT_H2N_U32( ISCSI_IMMEDIATE_DELIVERY_BIT
+ | (csg << ISCSI_CSG_SHIFT)
+ | (transit ? (nsg << ISCSI_NSG_SHIFT | ISCSI_TRANSIT_BIT) : 0)
+ | ISCSI_MY_VERSION /* Minimum version. */
+ | (ISCSI_MY_VERSION << 8) /* Maximum version. */
+ | ISCSIOP_LOGIN_REQ); /* C=0 */
+ aReqBHS[1] = RT_H2N_U32((uint32_t)cbBuf); /* TotalAHSLength=0 */
+ aReqBHS[2] = RT_H2N_U32(isid_tsih >> 32);
+ aReqBHS[3] = RT_H2N_U32(isid_tsih & 0xffffffff);
+ aReqBHS[4] = itt;
+ aReqBHS[5] = RT_H2N_U32(1 << 16); /* CID=1,reserved */
+ aReqBHS[6] = RT_H2N_U32(pImage->CmdSN);
+ aReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN);
+ aReqBHS[8] = 0; /* reserved */
+ aReqBHS[9] = 0; /* reserved */
+ aReqBHS[10] = 0; /* reserved */
+ aReqBHS[11] = 0; /* reserved */
+
+ cnISCSIReq = 0;
+ aISCSIReq[cnISCSIReq].pcvSeg = aReqBHS;
+ aISCSIReq[cnISCSIReq].cbSeg = sizeof(aReqBHS);
+ cnISCSIReq++;
+
+ aISCSIReq[cnISCSIReq].pcvSeg = bBuf;
+ aISCSIReq[cnISCSIReq].cbSeg = cbBuf;
+ cnISCSIReq++;
+
+ rc = iscsiSendPDU(pImage, aISCSIReq, cnISCSIReq, ISCSIPDU_NO_REATTACH);
+ if (RT_SUCCESS(rc))
+ {
+ ISCSIOPCODE cmd;
+ ISCSILOGINSTATUSCLASS loginStatusClass;
+
+ cnISCSIRes = 0;
+ aISCSIRes[cnISCSIRes].pvSeg = aResBHS;
+ aISCSIRes[cnISCSIRes].cbSeg = sizeof(aResBHS);
+ cnISCSIRes++;
+ aISCSIRes[cnISCSIRes].pvSeg = bBuf;
+ aISCSIRes[cnISCSIRes].cbSeg = sizeof(bBuf);
+ cnISCSIRes++;
+
+ rc = iscsiRecvPDU(pImage, itt, aISCSIRes, cnISCSIRes, ISCSIPDU_NO_REATTACH);
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * We lost connection to the target while receiving the answer,
+ * start from the beginning.
+ */
+ if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
+ rc = VERR_TRY_AGAIN;
+ break;
+ }
+
+ /** @todo collect partial login responses with Continue bit set. */
+ Assert(aISCSIRes[0].pvSeg == aResBHS);
+ Assert(aISCSIRes[0].cbSeg >= ISCSI_BHS_SIZE);
+ Assert((RT_N2H_U32(aResBHS[0]) & ISCSI_CONTINUE_BIT) == 0);
+
+ cmd = (ISCSIOPCODE)(RT_N2H_U32(aResBHS[0]) & ISCSIOP_MASK);
+ if (cmd == ISCSIOP_LOGIN_RES)
+ {
+ if ((RT_N2H_U32(aResBHS[0]) & 0xff) != ISCSI_MY_VERSION)
+ {
+ iscsiTransportClose(pImage);
+ rc = VERR_PARSE_ERROR;
+ break; /* Give up immediately, as a RFC violation in version fields is very serious. */
+ }
+
+ loginStatusClass = (ISCSILOGINSTATUSCLASS)(RT_N2H_U32(aResBHS[9]) >> 24);
+ switch (loginStatusClass)
+ {
+ case ISCSI_LOGIN_STATUS_CLASS_SUCCESS:
+ uint32_t targetCSG;
+ uint32_t targetNSG;
+ bool targetTransit;
+
+ if (pImage->FirstRecvPDU)
+ {
+ pImage->FirstRecvPDU = false;
+ pImage->ExpStatSN = RT_N2H_U32(aResBHS[6]) + 1;
+ }
+
+ targetCSG = (RT_N2H_U32(aResBHS[0]) & ISCSI_CSG_MASK) >> ISCSI_CSG_SHIFT;
+ targetNSG = (RT_N2H_U32(aResBHS[0]) & ISCSI_NSG_MASK) >> ISCSI_NSG_SHIFT;
+ targetTransit = !!(RT_N2H_U32(aResBHS[0]) & ISCSI_TRANSIT_BIT);
+
+ /* Handle all cases with a single switch statement. */
+ switch (csg << 8 | substate)
+ {
+ case 0x0000: /* security negotiation, step 0: receive final authentication. */
+ rc = iscsiUpdateParameters(pImage, bBuf, aISCSIRes[1].cbSeg);
+ if (RT_FAILURE(rc))
+ break;
+
+ const char *pcszAuthMethod;
+
+ rc = iscsiTextGetKeyValue(bBuf, aISCSIRes[1].cbSeg, "AuthMethod", &pcszAuthMethod);
+ if (RT_FAILURE(rc))
+ {
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ if (strcmp(pcszAuthMethod, "None") == 0)
+ {
+ /* Authentication offered, but none required. Skip to operational parameters. */
+ csg = 1;
+ nsg = 1;
+ transit = true;
+ substate = 0;
+ break;
+ }
+ else if (strcmp(pcszAuthMethod, "CHAP") == 0 && targetNSG == 0 && !targetTransit)
+ {
+ /* CHAP authentication required, continue with next substate. */
+ substate++;
+ break;
+ }
+
+ /* Unknown auth method or login response PDU headers incorrect. */
+ rc = VERR_PARSE_ERROR;
+ break;
+ case 0x0001: /* security negotiation, step 1: receive final CHAP variant and challenge. */
+ {
+ rc = iscsiUpdateParameters(pImage, bBuf, aISCSIRes[1].cbSeg);
+ if (RT_FAILURE(rc))
+ break;
+
+ const char *pcszChapAuthMethod;
+ const char *pcszChapIdxTarget;
+ const char *pcszChapChallengeStr;
+
+ rc = iscsiTextGetKeyValue(bBuf, aISCSIRes[1].cbSeg, "CHAP_A", &pcszChapAuthMethod);
+ if (RT_FAILURE(rc))
+ {
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ if (strcmp(pcszChapAuthMethod, "5") != 0)
+ {
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ rc = iscsiTextGetKeyValue(bBuf, aISCSIRes[1].cbSeg, "CHAP_I", &pcszChapIdxTarget);
+ if (RT_FAILURE(rc))
+ {
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ rc = RTStrToUInt8Ex(pcszChapIdxTarget, &pszNext, 0, &bChapIdx);
+/** @todo r=bird: Unsafe use of pszNext on failure. The code should probably
+ * use RTStrToUInt8Full and check for rc != VINF_SUCCESS. */
+ if (rc > VINF_SUCCESS || *pszNext != '\0')
+ {
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ rc = iscsiTextGetKeyValue(bBuf, aISCSIRes[1].cbSeg, "CHAP_C", &pcszChapChallengeStr);
+ if (RT_FAILURE(rc))
+ {
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ cbChallenge = sizeof(pbChallenge);
+ rc = iscsiStrToBinary(pcszChapChallengeStr, pbChallenge, &cbChallenge);
+ if (RT_FAILURE(rc))
+ break;
+ substate++;
+ transit = true;
+ break;
+ }
+ case 0x0002: /* security negotiation, step 2: check authentication success. */
+ rc = iscsiUpdateParameters(pImage, bBuf, aISCSIRes[1].cbSeg);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (targetCSG == 0 && targetNSG == 1 && targetTransit)
+ {
+ /* Target wants to continue in login operational state, authentication success. */
+ csg = 1;
+ nsg = 3;
+ substate = 0;
+ break;
+ }
+ rc = VERR_PARSE_ERROR;
+ break;
+ case 0x0100: /* login operational negotiation, step 0: check results. */
+ rc = iscsiUpdateParameters(pImage, bBuf, aISCSIRes[1].cbSeg);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (targetCSG == 1 && targetNSG == 3 && targetTransit)
+ {
+ /* Target wants to continue in full feature phase, login finished. */
+ csg = 3;
+ nsg = 3;
+ substate = 0;
+ break;
+ }
+ else if (targetCSG == 1 && (targetNSG == 1 || !targetTransit))
+ {
+ /* Target wants to negotiate certain parameters and
+ * stay in login operational negotiation. */
+ csg = 1;
+ nsg = 3;
+ substate = 0;
+ break;
+ }
+ rc = VERR_PARSE_ERROR;
+ break;
+ case 0x0300: /* full feature phase. */
+ default:
+ AssertMsgFailed(("recv: Undefined login state %d substate %d\n", csg, substate));
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ break;
+ case ISCSI_LOGIN_STATUS_CLASS_REDIRECTION:
+ const char *pcszTargetRedir;
+
+ /* Target has moved to some other location, as indicated in the TargetAddress key. */
+ rc = iscsiTextGetKeyValue(bBuf, aISCSIRes[1].cbSeg, "TargetAddress", &pcszTargetRedir);
+ if (RT_FAILURE(rc))
+ {
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ if (pImage->pszTargetAddress)
+ RTMemFree(pImage->pszTargetAddress);
+ {
+ size_t cb = strlen(pcszTargetRedir) + 1;
+ pImage->pszTargetAddress = (char *)RTMemAlloc(cb);
+ if (!pImage->pszTargetAddress)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ memcpy(pImage->pszTargetAddress, pcszTargetRedir, cb);
+ }
+ rc = VINF_TRY_AGAIN;
+ break;
+ case ISCSI_LOGIN_STATUS_CLASS_INITIATOR_ERROR:
+ {
+ LogRel(("iSCSI: login to target failed with: %s\n",
+ iscsiGetLoginErrorDetail((RT_N2H_U32(aResBHS[9]) >> 16) & 0xff)));
+ iscsiTransportClose(pImage);
+ rc = VERR_IO_GEN_FAILURE;
+ break;
+ }
+ case ISCSI_LOGIN_STATUS_CLASS_TARGET_ERROR:
+ iscsiTransportClose(pImage);
+ rc = VINF_EOF;
+ break;
+ default:
+ rc = VERR_PARSE_ERROR;
+ }
+
+ if (RT_FAILURE(rc) || rc == VINF_TRY_AGAIN)
+ break;
+
+ if (csg == 3)
+ {
+ /*
+ * Finished login, continuing with Full Feature Phase.
+ */
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ else
+ AssertMsgFailed(("%s: ignoring unexpected PDU with first word = %#08x\n", __FUNCTION__, RT_N2H_U32(aResBHS[0])));
+ }
+ else
+ break;
+ } while (true);
+
+ if ( RT_FAILURE(rc)
+ && rc != VERR_TRY_AGAIN)
+ {
+ /*
+ * Dump the last request and response of we are supposed to do so and there is a request
+ * or response.
+ */
+ if (cnISCSIReq)
+ iscsiDumpPacket(pImage, aISCSIReq, cnISCSIReq, VINF_SUCCESS, true /* fRequest */);
+
+ if (cnISCSIRes)
+ iscsiDumpPacket(pImage, (PISCSIREQ)aISCSIRes, cnISCSIRes, rc, false /* fRequest */);
+
+ /*
+ * Close connection to target.
+ */
+ iscsiTransportClose(pImage);
+ pImage->state = ISCSISTATE_FREE;
+ }
+ else if (rc == VINF_SUCCESS)
+ pImage->state = ISCSISTATE_NORMAL;
+
+ return rc;
+}
+
+/**
+ * Attach to an iSCSI target. Performs all operations necessary to enter
+ * Full Feature Phase.
+ *
+ * @returns VBox status code.
+ * @param pvUser The iSCSI connection state to be used as opaque user data.
+ */
+static DECLCALLBACK(int) iscsiAttach(void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ unsigned cRetries = 5;
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pvUser;
+
+ LogFlowFunc(("entering\n"));
+
+ Assert(pImage->state == ISCSISTATE_FREE);
+
+ /*
+ * If there were too many logins without any successful I/O just fail
+ * and assume the target is not working properly.
+ */
+ if (ASMAtomicReadU32(&pImage->cLoginsSinceIo) == 3)
+ return VERR_BROKEN_PIPE;
+
+ RTSemMutexRequest(pImage->Mutex, RT_INDEFINITE_WAIT);
+
+ /* Make 100% sure the connection isn't reused for a new login. */
+ iscsiTransportClose(pImage);
+
+ /* Try to log in a few number of times. */
+ while (cRetries > 0)
+ {
+ rc = iscsiLogin(pImage);
+ if (rc == VINF_SUCCESS) /* Login succeeded, continue with full feature phase. */
+ break;
+ else if (rc == VERR_TRY_AGAIN) /* Lost connection during receive. */
+ cRetries--;
+ else if (RT_FAILURE(rc))
+ break;
+ else /* For redirects try again. */
+ AssertMsg(rc == VINF_TRY_AGAIN, ("Unexpected status code %Rrc\n", rc));
+ }
+
+ if (RT_SUCCESS(rc))
+ ASMAtomicIncU32(&pImage->cLoginsSinceIo);
+
+ RTSemMutexRelease(pImage->Mutex);
+
+ LogFlowFunc(("returning %Rrc\n", rc));
+ LogRel(("iSCSI: login to target %s %s (%Rrc)\n", pImage->pszTargetName, RT_SUCCESS(rc) ? "successful" : "failed", rc));
+ return rc;
+}
+
+
+/**
+ * Detach from an iSCSI target.
+ *
+ * @returns VBox status code.
+ * @param pvUser The iSCSI connection state to be used as opaque user data.
+ */
+static DECLCALLBACK(int) iscsiDetach(void *pvUser)
+{
+ int rc;
+ uint32_t itt;
+ uint32_t cnISCSIReq = 0;
+ ISCSIREQ aISCSIReq[4];
+ uint32_t aReqBHS[12];
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pvUser;
+
+ LogFlowFunc(("entering\n"));
+
+ RTSemMutexRequest(pImage->Mutex, RT_INDEFINITE_WAIT);
+
+ if (pImage->state != ISCSISTATE_FREE && pImage->state != ISCSISTATE_IN_LOGOUT)
+ {
+ pImage->state = ISCSISTATE_IN_LOGOUT;
+
+ /*
+ * Send logout request to target.
+ */
+ itt = iscsiNewITT(pImage);
+ aReqBHS[0] = RT_H2N_U32(ISCSI_FINAL_BIT | ISCSIOP_LOGOUT_REQ); /* I=0,F=1,Reason=close session */
+ aReqBHS[1] = RT_H2N_U32(0); /* TotalAHSLength=0,DataSementLength=0 */
+ aReqBHS[2] = 0; /* reserved */
+ aReqBHS[3] = 0; /* reserved */
+ aReqBHS[4] = itt;
+ aReqBHS[5] = 0; /* reserved */
+ aReqBHS[6] = RT_H2N_U32(pImage->CmdSN);
+ aReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN);
+ aReqBHS[8] = 0; /* reserved */
+ aReqBHS[9] = 0; /* reserved */
+ aReqBHS[10] = 0; /* reserved */
+ aReqBHS[11] = 0; /* reserved */
+ pImage->CmdSN++;
+
+ aISCSIReq[cnISCSIReq].pcvSeg = aReqBHS;
+ aISCSIReq[cnISCSIReq].cbSeg = sizeof(aReqBHS);
+ cnISCSIReq++;
+
+ rc = iscsiSendPDU(pImage, aISCSIReq, cnISCSIReq, ISCSIPDU_NO_REATTACH);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Read logout response from target.
+ */
+ ISCSIRES aISCSIRes;
+ uint32_t aResBHS[12];
+
+ aISCSIRes.pvSeg = aResBHS;
+ aISCSIRes.cbSeg = sizeof(aResBHS);
+ rc = iscsiRecvPDU(pImage, itt, &aISCSIRes, 1, ISCSIPDU_NO_REATTACH);
+ if (RT_SUCCESS(rc))
+ {
+ if (RT_N2H_U32(aResBHS[0]) != (ISCSI_FINAL_BIT | ISCSIOP_LOGOUT_RES))
+ AssertMsgFailed(("iSCSI Logout response invalid\n"));
+ }
+ else
+ AssertMsgFailed(("iSCSI Logout response error, rc=%Rrc\n", rc));
+ }
+ else
+ AssertMsgFailed(("Could not send iSCSI Logout request, rc=%Rrc\n", rc));
+ }
+
+ if (pImage->state != ISCSISTATE_FREE)
+ {
+ /*
+ * Close connection to target.
+ */
+ rc = iscsiTransportClose(pImage);
+ if (RT_FAILURE(rc))
+ AssertMsgFailed(("Could not close connection to target, rc=%Rrc\n", rc));
+ }
+
+ pImage->state = ISCSISTATE_FREE;
+
+ RTSemMutexRelease(pImage->Mutex);
+
+ LogFlowFunc(("leaving\n"));
+ LogRel(("iSCSI: logout to target %s\n", pImage->pszTargetName));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Perform a command on an iSCSI target. Target must be already in
+ * Full Feature Phase.
+ *
+ * @returns VBox status code.
+ * @param pImage The iSCSI connection state to be used.
+ * @param pRequest Command descriptor. Contains all information about
+ * the command, its transfer directions and pointers
+ * to the buffer(s) used for transferring data and
+ * status information.
+ */
+static int iscsiCommand(PISCSIIMAGE pImage, PSCSIREQ pRequest)
+{
+ int rc;
+ uint32_t itt;
+ uint32_t cbData;
+ uint32_t cnISCSIReq = 0;
+ ISCSIREQ aISCSIReq[4];
+ uint32_t aReqBHS[12];
+
+ uint32_t *pDst = NULL;
+ size_t cbBufLength;
+ uint32_t aStatus[256]; /**< Plenty of buffer for status information. */
+ uint32_t ExpDataSN = 0;
+ bool final = false;
+
+
+ LogFlowFunc(("entering, CmdSN=%d\n", pImage->CmdSN));
+
+ Assert(pRequest->enmXfer != SCSIXFER_TO_FROM_TARGET); /**< @todo not yet supported, would require AHS. */
+ Assert(pRequest->cbI2TData <= 0xffffff); /* larger transfers would require R2T support. */
+ Assert(pRequest->cbCDB <= 16); /* would cause buffer overrun below. */
+
+ /* If not in normal state, then the transport connection was dropped. Try
+ * to reestablish by logging in, the target might be responsive again. */
+ if (pImage->state == ISCSISTATE_FREE)
+ rc = iscsiAttach(pImage);
+
+ /* If still not in normal state, then the underlying transport connection
+ * cannot be established. Get out before bad things happen (and make
+ * sure the caller suspends the VM again). */
+ if (pImage->state == ISCSISTATE_NORMAL)
+ {
+ /*
+ * Send SCSI command to target with all I2T data included.
+ */
+ cbData = 0;
+ if (pRequest->enmXfer == SCSIXFER_FROM_TARGET)
+ cbData = (uint32_t)pRequest->cbT2IData;
+ else
+ cbData = (uint32_t)pRequest->cbI2TData;
+
+ RTSemMutexRequest(pImage->Mutex, RT_INDEFINITE_WAIT);
+
+ itt = iscsiNewITT(pImage);
+ memset(aReqBHS, 0, sizeof(aReqBHS));
+ aReqBHS[0] = RT_H2N_U32( ISCSI_FINAL_BIT | ISCSI_TASK_ATTR_SIMPLE | ISCSIOP_SCSI_CMD
+ | (pRequest->enmXfer << 21)); /* I=0,F=1,Attr=Simple */
+ aReqBHS[1] = RT_H2N_U32(0x00000000 | ((uint32_t)pRequest->cbI2TData & 0xffffff)); /* TotalAHSLength=0 */
+ aReqBHS[2] = RT_H2N_U32(pImage->LUN >> 32);
+ aReqBHS[3] = RT_H2N_U32(pImage->LUN & 0xffffffff);
+ aReqBHS[4] = itt;
+ aReqBHS[5] = RT_H2N_U32(cbData);
+ aReqBHS[6] = RT_H2N_U32(pImage->CmdSN);
+ aReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN);
+ memcpy(aReqBHS + 8, pRequest->abCDB, pRequest->cbCDB);
+ pImage->CmdSN++;
+
+ aISCSIReq[cnISCSIReq].pcvSeg = aReqBHS;
+ aISCSIReq[cnISCSIReq].cbSeg = sizeof(aReqBHS);
+ cnISCSIReq++;
+
+ if ( pRequest->enmXfer == SCSIXFER_TO_TARGET
+ || pRequest->enmXfer == SCSIXFER_TO_FROM_TARGET)
+ {
+ Assert(pRequest->cI2TSegs == 1);
+ aISCSIReq[cnISCSIReq].pcvSeg = pRequest->paI2TSegs[0].pvSeg;
+ aISCSIReq[cnISCSIReq].cbSeg = pRequest->paI2TSegs[0].cbSeg; /* Padding done by transport. */
+ cnISCSIReq++;
+ }
+
+ rc = iscsiSendPDU(pImage, aISCSIReq, cnISCSIReq, ISCSIPDU_DEFAULT);
+ if (RT_SUCCESS(rc))
+ {
+ /* Place SCSI request in queue. */
+ pImage->paCurrReq = aISCSIReq;
+ pImage->cnCurrReq = cnISCSIReq;
+
+ /*
+ * Read SCSI response/data in PDUs from target.
+ */
+ if ( pRequest->enmXfer == SCSIXFER_FROM_TARGET
+ || pRequest->enmXfer == SCSIXFER_TO_FROM_TARGET)
+ {
+ Assert(pRequest->cT2ISegs == 1);
+ pDst = (uint32_t *)pRequest->paT2ISegs[0].pvSeg;
+ cbBufLength = pRequest->paT2ISegs[0].cbSeg;
+ }
+ else
+ cbBufLength = 0;
+
+ do
+ {
+ uint32_t cnISCSIRes = 0;
+ ISCSIRES aISCSIRes[4];
+ uint32_t aResBHS[12];
+
+ aISCSIRes[cnISCSIRes].pvSeg = aResBHS;
+ aISCSIRes[cnISCSIRes].cbSeg = sizeof(aResBHS);
+ cnISCSIRes++;
+ if (cbBufLength != 0 &&
+ ( pRequest->enmXfer == SCSIXFER_FROM_TARGET
+ || pRequest->enmXfer == SCSIXFER_TO_FROM_TARGET))
+ {
+ aISCSIRes[cnISCSIRes].pvSeg = pDst;
+ aISCSIRes[cnISCSIRes].cbSeg = cbBufLength;
+ cnISCSIRes++;
+ }
+ /* Always reserve space for the status - it's impossible to tell
+ * beforehand whether this will be the final PDU or not. */
+ aISCSIRes[cnISCSIRes].pvSeg = aStatus;
+ aISCSIRes[cnISCSIRes].cbSeg = sizeof(aStatus);
+ cnISCSIRes++;
+
+ rc = iscsiRecvPDU(pImage, itt, aISCSIRes, cnISCSIRes, ISCSIPDU_DEFAULT);
+ if (RT_FAILURE(rc))
+ break;
+
+ final = !!(RT_N2H_U32(aResBHS[0]) & ISCSI_FINAL_BIT);
+ ISCSIOPCODE cmd = (ISCSIOPCODE)(RT_N2H_U32(aResBHS[0]) & ISCSIOP_MASK);
+ if (cmd == ISCSIOP_SCSI_RES)
+ {
+ /* This is the final PDU which delivers the status (and may be omitted if
+ * the last Data-In PDU included successful completion status). Note
+ * that ExpStatSN has been bumped already in iscsiRecvPDU. */
+ if (!final || ((RT_N2H_U32(aResBHS[0]) & 0x0000ff00) != 0) || (RT_N2H_U32(aResBHS[6]) != pImage->ExpStatSN - 1))
+ {
+ /* SCSI Response in the wrong place or with a (target) failure. */
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ /* The following is a bit tricky, as in error situations we may
+ * get the status only instead of the result data plus optional
+ * status. Thus the status may have ended up partially in the
+ * data area. */
+ pRequest->status = RT_N2H_U32(aResBHS[0]) & 0x000000ff;
+ cbData = RT_N2H_U32(aResBHS[1]) & 0x00ffffff;
+ if (cbData >= 2)
+ {
+ uint32_t cbStat = RT_N2H_U32(((uint32_t *)aISCSIRes[1].pvSeg)[0]) >> 16;
+ if (cbStat + 2 > cbData)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ /* Truncate sense data if it doesn't fit into the buffer. */
+ pRequest->cbSense = RT_MIN(cbStat, pRequest->cbSense);
+ memcpy(pRequest->abSense,
+ ((const char *)aISCSIRes[1].pvSeg) + 2,
+ RT_MIN(aISCSIRes[1].cbSeg - 2, pRequest->cbSense));
+ if ( cnISCSIRes > 2 && aISCSIRes[2].cbSeg
+ && (ssize_t)pRequest->cbSense - aISCSIRes[1].cbSeg + 2 > 0)
+ {
+ memcpy((char *)pRequest->abSense + aISCSIRes[1].cbSeg - 2,
+ aISCSIRes[2].pvSeg,
+ pRequest->cbSense - aISCSIRes[1].cbSeg + 2);
+ }
+ }
+ else if (cbData == 1)
+ {
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ else
+ pRequest->cbSense = 0;
+ break;
+ }
+ else if (cmd == ISCSIOP_SCSI_DATA_IN)
+ {
+ /* A Data-In PDU carries some data that needs to be added to the received
+ * data in response to the command. There may be both partial and complete
+ * Data-In PDUs, so collect data until the status is included or the status
+ * is sent in a separate SCSI Result frame (see above). */
+ if (final && aISCSIRes[2].cbSeg != 0)
+ {
+ /* The received PDU is partially stored in the buffer for status.
+ * Must not happen under normal circumstances and is a target error. */
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ uint32_t len = RT_N2H_U32(aResBHS[1]) & 0x00ffffff;
+ pDst = (uint32_t *)((char *)pDst + len);
+ cbBufLength -= len;
+ ExpDataSN++;
+ if (final && (RT_N2H_U32(aResBHS[0]) & ISCSI_STATUS_BIT) != 0)
+ {
+ pRequest->status = RT_N2H_U32(aResBHS[0]) & 0x000000ff;
+ pRequest->cbSense = 0;
+ break;
+ }
+ }
+ else
+ {
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+ } while (true);
+
+ /* Remove SCSI request from queue. */
+ pImage->paCurrReq = NULL;
+ pImage->cnCurrReq = 0;
+ }
+
+ if (rc == VERR_TIMEOUT)
+ {
+ /* Drop connection in case the target plays dead. Much better than
+ * delaying the next requests until the timed out command actually
+ * finishes. Also keep in mind that command shouldn't take longer than
+ * about 30-40 seconds, or the guest will lose its patience. */
+ iscsiTransportClose(pImage);
+ pImage->state = ISCSISTATE_FREE;
+ rc = VERR_BROKEN_PIPE;
+ }
+ RTSemMutexRelease(pImage->Mutex);
+ }
+ else
+ rc = VERR_NET_CONNECTION_REFUSED;
+
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteU32(&pImage->cLoginsSinceIo, 0);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Generate a new Initiator Task Tag.
+ *
+ * @returns Initiator Task Tag.
+ * @param pImage The iSCSI connection state to be used.
+ */
+static uint32_t iscsiNewITT(PISCSIIMAGE pImage)
+{
+ uint32_t next_itt;
+
+ next_itt = pImage->ITT++;
+ if (pImage->ITT == ISCSI_TASK_TAG_RSVD)
+ pImage->ITT = 0;
+ return RT_H2N_U32(next_itt);
+}
+
+
+/**
+ * Send an iSCSI request. The request can consist of several segments, which
+ * are padded to 4 byte boundaries and concatenated.
+ *
+ * @returns VBOX status
+ * @param pImage The iSCSI connection state to be used.
+ * @param paReq Pointer to array of iSCSI request sections.
+ * @param cnReq Number of valid iSCSI request sections in the array.
+ * @param uFlags Flags controlling the exact send semantics.
+ */
+static int iscsiSendPDU(PISCSIIMAGE pImage, PISCSIREQ paReq, uint32_t cnReq,
+ uint32_t uFlags)
+{
+ int rc = VINF_SUCCESS;
+ /** @todo return VERR_VD_ISCSI_INVALID_STATE in the appropriate situations,
+ * needs cleaning up of timeout/disconnect handling a bit, as otherwise
+ * too many incorrect errors are signalled. */
+ Assert(cnReq >= 1);
+ Assert(paReq[0].cbSeg >= ISCSI_BHS_SIZE);
+
+ for (uint32_t i = 0; i < pImage->cISCSIRetries; i++)
+ {
+ rc = iscsiTransportWrite(pImage, paReq, cnReq);
+ if (RT_SUCCESS(rc))
+ break;
+ if ( (uFlags & ISCSIPDU_NO_REATTACH)
+ || (rc != VERR_BROKEN_PIPE && rc != VERR_NET_CONNECTION_REFUSED))
+ break;
+ /* No point in reestablishing the connection for a logout */
+ if (pImage->state == ISCSISTATE_IN_LOGOUT)
+ break;
+ RTThreadSleep(500);
+ if (pImage->state != ISCSISTATE_IN_LOGIN)
+ {
+ /* Attempt to re-login when a connection fails, but only when not
+ * currently logging in. */
+ rc = iscsiAttach(pImage);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Wait for an iSCSI response with a matching Initiator Target Tag. The response is
+ * split into several segments, as requested by the caller-provided buffer specification.
+ * Remember that the response can be split into several PDUs by the sender, so make
+ * sure that all parts are collected and processed appropriately by the caller.
+ *
+ * @returns VBOX status
+ * @param pImage The iSCSI connection state to be used.
+ * @param itt The initiator task tag.
+ * @param paRes Pointer to array of iSCSI response sections.
+ * @param cnRes Number of valid iSCSI response sections in the array.
+ * @param fRecvFlags PDU receive flags.
+ */
+static int iscsiRecvPDU(PISCSIIMAGE pImage, uint32_t itt, PISCSIRES paRes, uint32_t cnRes,
+ uint32_t fRecvFlags)
+{
+ int rc = VINF_SUCCESS;
+ ISCSIRES aResBuf;
+
+ for (uint32_t i = 0; i < pImage->cISCSIRetries; i++)
+ {
+ aResBuf.pvSeg = pImage->pvRecvPDUBuf;
+ aResBuf.cbSeg = pImage->cbRecvPDUBuf;
+ rc = iscsiTransportRead(pImage, &aResBuf, 1);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
+ {
+ /* No point in reestablishing the connection for a logout */
+ if (pImage->state == ISCSISTATE_IN_LOGOUT)
+ break;
+ /* Connection broken while waiting for a response - wait a while and
+ * try to restart by re-sending the original request (if any).
+ * This also handles the connection reestablishment (login etc.). */
+ RTThreadSleep(500);
+ if ( pImage->state != ISCSISTATE_IN_LOGIN
+ && !(fRecvFlags & ISCSIPDU_NO_REATTACH))
+ {
+ /* Attempt to re-login when a connection fails, but only when not
+ * currently logging in. */
+ rc = iscsiAttach(pImage);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (pImage->paCurrReq != NULL)
+ {
+ rc = iscsiSendPDU(pImage, pImage->paCurrReq, pImage->cnCurrReq, ISCSIPDU_DEFAULT);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* Signal other errors (VERR_BUFFER_OVERFLOW etc.) to the caller. */
+ break;
+ }
+ }
+ else
+ {
+ ISCSIOPCODE cmd;
+ const uint32_t *pcvResSeg = (const uint32_t *)aResBuf.pvSeg;
+
+ /* Check whether the received PDU is valid, and update the internal state of
+ * the iSCSI connection/session. */
+ rc = iscsiValidatePDU(&aResBuf, 1);
+ if (RT_FAILURE(rc))
+ {
+ iscsiDumpPacket(pImage, (PISCSIREQ)&aResBuf, 1, rc, false /* fRequest */);
+ continue;
+ }
+ cmd = (ISCSIOPCODE)(RT_N2H_U32(pcvResSeg[0]) & ISCSIOP_MASK);
+ switch (cmd)
+ {
+ case ISCSIOP_SCSI_RES:
+ case ISCSIOP_SCSI_TASKMGMT_RES:
+ case ISCSIOP_SCSI_DATA_IN:
+ case ISCSIOP_R2T:
+ case ISCSIOP_ASYN_MSG:
+ case ISCSIOP_TEXT_RES:
+ case ISCSIOP_LOGIN_RES:
+ case ISCSIOP_LOGOUT_RES:
+ case ISCSIOP_REJECT:
+ case ISCSIOP_NOP_IN:
+ if (serial_number_less(pImage->MaxCmdSN, RT_N2H_U32(pcvResSeg[8])))
+ pImage->MaxCmdSN = RT_N2H_U32(pcvResSeg[8]);
+ if (serial_number_less(pImage->ExpCmdSN, RT_N2H_U32(pcvResSeg[7])))
+ pImage->ExpCmdSN = RT_N2H_U32(pcvResSeg[7]);
+ break;
+ default:
+ rc = VERR_PARSE_ERROR;
+ iscsiDumpPacket(pImage, (PISCSIREQ)&aResBuf, 1, rc, false /* fRequest */);
+ }
+ if (RT_FAILURE(rc))
+ continue;
+ if ( !pImage->FirstRecvPDU
+ && (cmd != ISCSIOP_SCSI_DATA_IN || (RT_N2H_U32(pcvResSeg[0]) & ISCSI_STATUS_BIT))
+ && ( cmd != ISCSIOP_LOGIN_RES
+ || (ISCSILOGINSTATUSCLASS)((RT_N2H_U32(pcvResSeg[9]) >> 24) == ISCSI_LOGIN_STATUS_CLASS_SUCCESS)))
+ {
+ if (pImage->ExpStatSN == RT_N2H_U32(pcvResSeg[6]))
+ {
+ /* StatSN counter is not advanced on R2T and on a target SN update NOP-In. */
+ if ( (cmd != ISCSIOP_R2T)
+ && ((cmd != ISCSIOP_NOP_IN) || (RT_N2H_U32(pcvResSeg[4]) != ISCSI_TASK_TAG_RSVD)))
+ pImage->ExpStatSN++;
+ }
+ else
+ {
+ rc = VERR_PARSE_ERROR;
+ iscsiDumpPacket(pImage, (PISCSIREQ)&aResBuf, 1, rc, false /* fRequest */);
+ continue;
+ }
+ }
+ /* Finally check whether the received PDU matches what the caller wants. */
+ if ( itt == pcvResSeg[4]
+ && itt != ISCSI_TASK_TAG_RSVD)
+ {
+ /* Copy received PDU (one segment) to caller-provided buffers. */
+ uint32_t j;
+ size_t cbSeg;
+ const uint8_t *pSrc;
+
+ pSrc = (const uint8_t *)aResBuf.pvSeg;
+ cbSeg = aResBuf.cbSeg;
+ for (j = 0; j < cnRes; j++)
+ {
+ if (cbSeg > paRes[j].cbSeg)
+ {
+ memcpy(paRes[j].pvSeg, pSrc, paRes[j].cbSeg);
+ pSrc += paRes[j].cbSeg;
+ cbSeg -= paRes[j].cbSeg;
+ }
+ else
+ {
+ memcpy(paRes[j].pvSeg, pSrc, cbSeg);
+ paRes[j].cbSeg = cbSeg;
+ cbSeg = 0;
+ break;
+ }
+ }
+ if (cbSeg != 0)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ for (j++; j < cnRes; j++)
+ paRes[j].cbSeg = 0;
+ break;
+ }
+ else if ( cmd == ISCSIOP_NOP_IN
+ && RT_N2H_U32(pcvResSeg[5]) != ISCSI_TASK_TAG_RSVD)
+ {
+ uint32_t cnISCSIReq;
+ ISCSIREQ aISCSIReq[4];
+ uint32_t aReqBHS[12];
+
+ aReqBHS[0] = RT_H2N_U32(ISCSI_IMMEDIATE_DELIVERY_BIT | ISCSI_FINAL_BIT | ISCSIOP_NOP_OUT);
+ aReqBHS[1] = RT_H2N_U32(0); /* TotalAHSLength=0,DataSementLength=0 */
+ aReqBHS[2] = pcvResSeg[2]; /* copy LUN from NOP-In */
+ aReqBHS[3] = pcvResSeg[3]; /* copy LUN from NOP-In */
+ aReqBHS[4] = RT_H2N_U32(ISCSI_TASK_TAG_RSVD); /* ITT, reply */
+ aReqBHS[5] = pcvResSeg[5]; /* copy TTT from NOP-In */
+ aReqBHS[6] = RT_H2N_U32(pImage->CmdSN);
+ aReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN);
+ aReqBHS[8] = 0; /* reserved */
+ aReqBHS[9] = 0; /* reserved */
+ aReqBHS[10] = 0; /* reserved */
+ aReqBHS[11] = 0; /* reserved */
+
+ cnISCSIReq = 0;
+ aISCSIReq[cnISCSIReq].pcvSeg = aReqBHS;
+ aISCSIReq[cnISCSIReq].cbSeg = sizeof(aReqBHS);
+ cnISCSIReq++;
+
+ iscsiSendPDU(pImage, aISCSIReq, cnISCSIReq, ISCSIPDU_NO_REATTACH);
+ /* Break if the caller wanted to process the NOP-in only. */
+ if (itt == ISCSI_TASK_TAG_RSVD)
+ break;
+ }
+ }
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Reset the PDU buffer
+ *
+ * @param pImage The iSCSI connection state to be used.
+ */
+static void iscsiRecvPDUReset(PISCSIIMAGE pImage)
+{
+ pImage->cbRecvPDUResidual = ISCSI_BHS_SIZE;
+ pImage->fRecvPDUBHS = true;
+ pImage->pbRecvPDUBufCur = (uint8_t *)pImage->pvRecvPDUBuf;
+}
+
+static void iscsiPDUTxAdd(PISCSIIMAGE pImage, PISCSIPDUTX pIScsiPDUTx, bool fFront)
+{
+ if (!fFront)
+ {
+ /* Insert PDU at the tail of the list. */
+ if (!pImage->pIScsiPDUTxHead)
+ pImage->pIScsiPDUTxHead = pIScsiPDUTx;
+ else
+ pImage->pIScsiPDUTxTail->pNext = pIScsiPDUTx;
+ pImage->pIScsiPDUTxTail = pIScsiPDUTx;
+ }
+ else
+ {
+ /* Insert PDU at the beginning of the list. */
+ pIScsiPDUTx->pNext = pImage->pIScsiPDUTxHead;
+ pImage->pIScsiPDUTxHead = pIScsiPDUTx;
+ if (!pImage->pIScsiPDUTxTail)
+ pImage->pIScsiPDUTxTail = pIScsiPDUTx;
+ }
+}
+
+/**
+ * Receives a PDU in a non blocking way.
+ *
+ * @returns VBOX status code.
+ * @param pImage The iSCSI connection state to be used.
+ */
+static int iscsiRecvPDUAsync(PISCSIIMAGE pImage)
+{
+ size_t cbActuallyRead = 0;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p\n", pImage));
+
+ /* Check if we are in the middle of a PDU receive. */
+ if (pImage->cbRecvPDUResidual == 0)
+ {
+ /*
+ * We are receiving a new PDU, don't read more than the BHS initially
+ * until we know the real size of the PDU.
+ */
+ iscsiRecvPDUReset(pImage);
+ LogFlow(("Receiving new PDU\n"));
+ }
+
+ rc = pImage->pIfNet->pfnReadNB(pImage->Socket, pImage->pbRecvPDUBufCur,
+ pImage->cbRecvPDUResidual, &cbActuallyRead);
+ if (RT_SUCCESS(rc) && cbActuallyRead == 0)
+ rc = VERR_BROKEN_PIPE;
+
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("Received %zu bytes\n", cbActuallyRead));
+ pImage->cbRecvPDUResidual -= cbActuallyRead;
+ pImage->pbRecvPDUBufCur += cbActuallyRead;
+
+ /* Check if we received everything we wanted. */
+ if ( !pImage->cbRecvPDUResidual
+ && pImage->fRecvPDUBHS)
+ {
+ size_t cbAHSLength, cbDataLength;
+
+ /* If we were reading the BHS first get the actual PDU size now. */
+ uint32_t word1 = RT_N2H_U32(((uint32_t *)(pImage->pvRecvPDUBuf))[1]);
+ cbAHSLength = (word1 & 0xff000000) >> 24;
+ cbAHSLength = ((cbAHSLength - 1) | 3) + 1; /* Add padding. */
+ cbDataLength = word1 & 0x00ffffff;
+ cbDataLength = ((cbDataLength - 1) | 3) + 1; /* Add padding. */
+ pImage->cbRecvPDUResidual = cbAHSLength + cbDataLength;
+ pImage->fRecvPDUBHS = false; /* Start receiving the rest of the PDU. */
+ }
+
+ if (!pImage->cbRecvPDUResidual)
+ {
+ /* We received the complete PDU with or without any payload now. */
+ LogFlow(("Received complete PDU\n"));
+ ISCSIRES aResBuf;
+ aResBuf.pvSeg = pImage->pvRecvPDUBuf;
+ aResBuf.cbSeg = pImage->cbRecvPDUBuf;
+ rc = iscsiRecvPDUProcess(pImage, &aResBuf, 1);
+ }
+ }
+ else
+ LogFlowFunc(("Reading from the socket returned with rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+static int iscsiSendPDUAsync(PISCSIIMAGE pImage)
+{
+ size_t cbSent = 0;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p\n", pImage));
+
+ do
+ {
+ /*
+ * If there is no PDU active, get the first one from the list.
+ * Check that we are allowed to transfer the PDU by comparing the
+ * command sequence number and the maximum sequence number allowed by the target.
+ */
+ if (!pImage->pIScsiPDUTxCur)
+ {
+ if ( !pImage->pIScsiPDUTxHead
+ || serial_number_greater(pImage->pIScsiPDUTxHead->CmdSN, pImage->MaxCmdSN))
+ break;
+
+ pImage->pIScsiPDUTxCur = pImage->pIScsiPDUTxHead;
+ pImage->pIScsiPDUTxHead = pImage->pIScsiPDUTxCur->pNext;
+ if (!pImage->pIScsiPDUTxHead)
+ pImage->pIScsiPDUTxTail = NULL;
+ }
+
+ /* Send as much as we can. */
+ rc = pImage->pIfNet->pfnSgWriteNB(pImage->Socket, &pImage->pIScsiPDUTxCur->SgBuf, &cbSent);
+ LogFlow(("SgWriteNB returned rc=%Rrc cbSent=%zu\n", rc, cbSent));
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("Sent %zu bytes for PDU %#p\n", cbSent, pImage->pIScsiPDUTxCur));
+ pImage->pIScsiPDUTxCur->cbSgLeft -= cbSent;
+ RTSgBufAdvance(&pImage->pIScsiPDUTxCur->SgBuf, cbSent);
+ if (!pImage->pIScsiPDUTxCur->cbSgLeft)
+ {
+ /* PDU completed, free it and place the command on the waiting for response list. */
+ if (pImage->pIScsiPDUTxCur->pIScsiCmd)
+ {
+ LogFlow(("Sent complete PDU, placing on waiting list\n"));
+ iscsiCmdInsert(pImage, pImage->pIScsiPDUTxCur->pIScsiCmd);
+ }
+ RTMemFree(pImage->pIScsiPDUTxCur);
+ pImage->pIScsiPDUTxCur = NULL;
+ }
+ }
+ } while ( RT_SUCCESS(rc)
+ && !pImage->pIScsiPDUTxCur);
+
+ if (rc == VERR_TRY_AGAIN)
+ rc = VINF_SUCCESS;
+
+ /* Add the write poll flag if we still have something to send, clear it otherwise. */
+ if (pImage->pIScsiPDUTxCur)
+ pImage->fPollEvents |= VD_INTERFACETCPNET_EVT_WRITE;
+ else
+ pImage->fPollEvents &= ~VD_INTERFACETCPNET_EVT_WRITE;
+
+ LogFlowFunc(("rc=%Rrc pIScsiPDUTxCur=%#p\n", rc, pImage->pIScsiPDUTxCur));
+ return rc;
+}
+
+/**
+ * Process a received PDU.
+ *
+ * @return VBOX status code.
+ * @param pImage The iSCSI connection state to be used.
+ * @param paRes Pointer to the array of iSCSI response sections.
+ * @param cnRes Number of valid iSCSI response sections in the array.
+ */
+static int iscsiRecvPDUProcess(PISCSIIMAGE pImage, PISCSIRES paRes, uint32_t cnRes)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p paRes=%#p cnRes=%u\n", pImage, paRes, cnRes));
+
+ /* Validate the PDU first. */
+ rc = iscsiValidatePDU(paRes, cnRes);
+ if (RT_SUCCESS(rc))
+ {
+ ISCSIOPCODE cmd;
+ const uint32_t *pcvResSeg = (const uint32_t *)paRes[0].pvSeg;
+
+ Assert(paRes[0].cbSeg > 9 * sizeof(uint32_t));
+
+ do
+ {
+ cmd = (ISCSIOPCODE)(RT_N2H_U32(pcvResSeg[0]) & ISCSIOP_MASK);
+ switch (cmd)
+ {
+ case ISCSIOP_SCSI_RES:
+ case ISCSIOP_SCSI_TASKMGMT_RES:
+ case ISCSIOP_SCSI_DATA_IN:
+ case ISCSIOP_R2T:
+ case ISCSIOP_ASYN_MSG:
+ case ISCSIOP_TEXT_RES:
+ case ISCSIOP_LOGIN_RES:
+ case ISCSIOP_LOGOUT_RES:
+ case ISCSIOP_REJECT:
+ case ISCSIOP_NOP_IN:
+ if (serial_number_less(pImage->MaxCmdSN, RT_N2H_U32(pcvResSeg[8])))
+ pImage->MaxCmdSN = RT_N2H_U32(pcvResSeg[8]);
+ if (serial_number_less(pImage->ExpCmdSN, RT_N2H_U32(pcvResSeg[7])))
+ pImage->ExpCmdSN = RT_N2H_U32(pcvResSeg[7]);
+ break;
+ default:
+ rc = VERR_PARSE_ERROR;
+ iscsiDumpPacket(pImage, (PISCSIREQ)paRes, cnRes, rc, false /* fRequest */);
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ if ( !pImage->FirstRecvPDU
+ && (cmd != ISCSIOP_SCSI_DATA_IN || (RT_N2H_U32(pcvResSeg[0]) & ISCSI_STATUS_BIT)))
+ {
+ if (pImage->ExpStatSN == RT_N2H_U32(pcvResSeg[6]))
+ {
+ /* StatSN counter is not advanced on R2T and on a target SN update NOP-In. */
+ if ( (cmd != ISCSIOP_R2T)
+ && ((cmd != ISCSIOP_NOP_IN) || (RT_N2H_U32(pcvResSeg[4]) != ISCSI_TASK_TAG_RSVD)))
+ pImage->ExpStatSN++;
+ }
+ else
+ {
+ rc = VERR_PARSE_ERROR;
+ iscsiDumpPacket(pImage, (PISCSIREQ)paRes, cnRes, rc, false /* fRequest */);
+ break;
+ }
+ }
+
+ if (pcvResSeg[4] != ISCSI_TASK_TAG_RSVD)
+ {
+ /*
+ * This is a response from the target for a request from the initiator.
+ * Get the request and update its state.
+ */
+ rc = iscsiRecvPDUUpdateRequest(pImage, paRes, cnRes);
+ /* Try to send more PDUs now that we updated the MaxCmdSN field */
+ if ( RT_SUCCESS(rc)
+ && !pImage->pIScsiPDUTxCur)
+ rc = iscsiSendPDUAsync(pImage);
+ }
+ else
+ {
+ /* This is a target initiated request (we handle only NOP-In request at the moment). */
+ if ( cmd == ISCSIOP_NOP_IN
+ && RT_N2H_U32(pcvResSeg[5]) != ISCSI_TASK_TAG_RSVD)
+ {
+ PISCSIPDUTX pIScsiPDUTx;
+ uint32_t cnISCSIReq;
+ uint32_t *paReqBHS;
+
+ LogFlowFunc(("Sending NOP-Out\n"));
+
+ /* Allocate a new PDU initialize it and put onto the waiting list. */
+ pIScsiPDUTx = (PISCSIPDUTX)RTMemAllocZ(sizeof(ISCSIPDUTX));
+ if (!pIScsiPDUTx)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ paReqBHS = &pIScsiPDUTx->aBHS[0];
+ paReqBHS[0] = RT_H2N_U32(ISCSI_IMMEDIATE_DELIVERY_BIT | ISCSI_FINAL_BIT | ISCSIOP_NOP_OUT);
+ paReqBHS[1] = RT_H2N_U32(0); /* TotalAHSLength=0,DataSementLength=0 */
+ paReqBHS[2] = pcvResSeg[2]; /* copy LUN from NOP-In */
+ paReqBHS[3] = pcvResSeg[3]; /* copy LUN from NOP-In */
+ paReqBHS[4] = RT_H2N_U32(ISCSI_TASK_TAG_RSVD); /* ITT, reply */
+ paReqBHS[5] = pcvResSeg[5]; /* copy TTT from NOP-In */
+ paReqBHS[6] = RT_H2N_U32(pImage->CmdSN);
+ paReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN);
+ paReqBHS[8] = 0; /* reserved */
+ paReqBHS[9] = 0; /* reserved */
+ paReqBHS[10] = 0; /* reserved */
+ paReqBHS[11] = 0; /* reserved */
+
+ cnISCSIReq = 0;
+ pIScsiPDUTx->aISCSIReq[cnISCSIReq].pvSeg = paReqBHS;
+ pIScsiPDUTx->aISCSIReq[cnISCSIReq].cbSeg = sizeof(pIScsiPDUTx->aBHS);
+ cnISCSIReq++;
+ pIScsiPDUTx->cbSgLeft = sizeof(pIScsiPDUTx->aBHS);
+ RTSgBufInit(&pIScsiPDUTx->SgBuf, pIScsiPDUTx->aISCSIReq, cnISCSIReq);
+
+ /*
+ * Link the PDU to the list.
+ * Insert at the front of the list to send the response as soon as possible
+ * to avoid frequent reconnects for a slow connection when there are many PDUs
+ * waiting.
+ */
+ iscsiPDUTxAdd(pImage, pIScsiPDUTx, true /* fFront */);
+
+ /* Start transfer of a PDU if there is no one active at the moment. */
+ if (!pImage->pIScsiPDUTxCur)
+ rc = iscsiSendPDUAsync(pImage);
+ }
+ }
+ } while (0);
+ }
+ else
+ iscsiDumpPacket(pImage, (PISCSIREQ)paRes, cnRes, rc, false /* fRequest */);
+
+ return rc;
+}
+
+/**
+ * Check the static (not dependent on the connection/session state) validity of an iSCSI response PDU.
+ *
+ * @returns VBOX status
+ * @param paRes Pointer to array of iSCSI response sections.
+ * @param cnRes Number of valid iSCSI response sections in the array.
+ */
+static int iscsiValidatePDU(PISCSIRES paRes, uint32_t cnRes)
+{
+ RT_NOREF1(cnRes);
+ const uint32_t *pcrgResBHS;
+ uint32_t hw0;
+ Assert(cnRes >= 1);
+ Assert(paRes[0].cbSeg >= ISCSI_BHS_SIZE);
+
+ LogFlowFunc(("paRes=%#p cnRes=%u\n", paRes, cnRes));
+
+ pcrgResBHS = (const uint32_t *)(paRes[0].pvSeg);
+ hw0 = RT_N2H_U32(pcrgResBHS[0]);
+ switch (hw0 & ISCSIOP_MASK)
+ {
+ case ISCSIOP_NOP_IN:
+ /* NOP-In responses must not be split into several PDUs nor it may contain
+ * ping data for target-initiated pings nor may both task tags be valid task tags. */
+ if ( (hw0 & ISCSI_FINAL_BIT) == 0
+ || ( RT_N2H_U32(pcrgResBHS[4]) == ISCSI_TASK_TAG_RSVD
+ && RT_N2H_U32(pcrgResBHS[1]) != 0)
+ || ( RT_N2H_U32(pcrgResBHS[4]) != ISCSI_TASK_TAG_RSVD
+ && RT_N2H_U32(pcrgResBHS[5]) != ISCSI_TASK_TAG_RSVD))
+ return VERR_PARSE_ERROR;
+ break;
+ case ISCSIOP_SCSI_RES:
+ /* SCSI responses must not be split into several PDUs nor must the residual
+ * bits be contradicting each other nor may the residual bits be set for PDUs
+ * containing anything else but a completed command response. Underflow
+ * is no reason for declaring a PDU as invalid, as the target may choose
+ * to return less data than we assume to get. */
+ if ( (hw0 & ISCSI_FINAL_BIT) == 0
+ || ((hw0 & ISCSI_BI_READ_RESIDUAL_OVFL_BIT) && (hw0 & ISCSI_BI_READ_RESIDUAL_UNFL_BIT))
+ || ((hw0 & ISCSI_RESIDUAL_OVFL_BIT) && (hw0 & ISCSI_RESIDUAL_UNFL_BIT))
+ || ( ((hw0 & ISCSI_SCSI_RESPONSE_MASK) == 0)
+ && ((hw0 & ISCSI_SCSI_STATUS_MASK) == SCSI_STATUS_OK)
+ && (hw0 & ( ISCSI_BI_READ_RESIDUAL_OVFL_BIT | ISCSI_BI_READ_RESIDUAL_UNFL_BIT
+ | ISCSI_RESIDUAL_OVFL_BIT))))
+ return VERR_PARSE_ERROR;
+ else
+ LogFlowFunc(("good SCSI response, first word %#08x\n", RT_N2H_U32(pcrgResBHS[0])));
+ break;
+ case ISCSIOP_LOGIN_RES:
+ /* Login responses must not contain contradicting transit and continue bits. */
+ if ((hw0 & ISCSI_CONTINUE_BIT) && ((hw0 & ISCSI_TRANSIT_BIT) != 0))
+ return VERR_PARSE_ERROR;
+ break;
+ case ISCSIOP_TEXT_RES:
+ /* Text responses must not contain contradicting final and continue bits nor
+ * may the final bit be set for PDUs containing a target transfer tag other than
+ * the reserved transfer tag (and vice versa). */
+ if ( (((hw0 & ISCSI_CONTINUE_BIT) && (hw0 & ISCSI_FINAL_BIT) != 0))
+ || (((hw0 & ISCSI_FINAL_BIT) && (RT_N2H_U32(pcrgResBHS[5]) != ISCSI_TASK_TAG_RSVD)))
+ || (((hw0 & ISCSI_FINAL_BIT) == 0) && (RT_N2H_U32(pcrgResBHS[5]) == ISCSI_TASK_TAG_RSVD)))
+ return VERR_PARSE_ERROR;
+ break;
+ case ISCSIOP_SCSI_DATA_IN:
+ /* SCSI Data-in responses must not contain contradicting residual bits when
+ * status bit is set. */
+ if ((hw0 & ISCSI_STATUS_BIT) && (hw0 & ISCSI_RESIDUAL_OVFL_BIT) && (hw0 & ISCSI_RESIDUAL_UNFL_BIT))
+ return VERR_PARSE_ERROR;
+ break;
+ case ISCSIOP_LOGOUT_RES:
+ /* Logout responses must not have the final bit unset and may not contain any
+ * data or additional header segments. */
+ if ( ((hw0 & ISCSI_FINAL_BIT) == 0)
+ || (RT_N2H_U32(pcrgResBHS[1]) != 0))
+ return VERR_PARSE_ERROR;
+ break;
+ case ISCSIOP_ASYN_MSG:
+ /* Asynchronous Messages must not have the final bit unset and may not contain
+ * an initiator task tag. */
+ if ( ((hw0 & ISCSI_FINAL_BIT) == 0)
+ || (RT_N2H_U32(pcrgResBHS[4]) != ISCSI_TASK_TAG_RSVD))
+ return VERR_PARSE_ERROR;
+ break;
+ case ISCSIOP_SCSI_TASKMGMT_RES:
+ case ISCSIOP_R2T:
+ case ISCSIOP_REJECT:
+ default:
+ /* Do some logging, ignore PDU. */
+ LogFlowFunc(("ignore unhandled PDU, first word %#08x\n", RT_N2H_U32(pcrgResBHS[0])));
+ return VERR_PARSE_ERROR;
+ }
+ /* A target must not send PDUs with MaxCmdSN less than ExpCmdSN-1. */
+
+ if (serial_number_less(RT_N2H_U32(pcrgResBHS[8]), RT_N2H_U32(pcrgResBHS[7])-1))
+ return VERR_PARSE_ERROR;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Prepares a PDU to transfer for the given command and adds it to the list.
+ */
+static int iscsiPDUTxPrepare(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t *paReqBHS;
+ size_t cbData = 0;
+ size_t cbSegs = 0;
+ PSCSIREQ pScsiReq;
+ PISCSIPDUTX pIScsiPDU = NULL;
+
+ LogFlowFunc(("pImage=%#p pIScsiCmd=%#p\n", pImage, pIScsiCmd));
+
+ Assert(pIScsiCmd->enmCmdType == ISCSICMDTYPE_REQ);
+
+ pIScsiCmd->Itt = iscsiNewITT(pImage);
+ pScsiReq = pIScsiCmd->CmdType.ScsiReq.pScsiReq;
+
+ if (pScsiReq->cT2ISegs)
+ RTSgBufInit(&pScsiReq->SgBufT2I, pScsiReq->paT2ISegs, pScsiReq->cT2ISegs);
+
+ /*
+ * Allocate twice as much entries as required for padding (worst case).
+ * The additional segment is for the BHS.
+ */
+ size_t cI2TSegs = 2*(pScsiReq->cI2TSegs + 1);
+ pIScsiPDU = (PISCSIPDUTX)RTMemAllocZ(RT_UOFFSETOF_DYN(ISCSIPDUTX, aISCSIReq[cI2TSegs]));
+ if (!pIScsiPDU)
+ return VERR_NO_MEMORY;
+
+ pIScsiPDU->pIScsiCmd = pIScsiCmd;
+
+ if (pScsiReq->enmXfer == SCSIXFER_FROM_TARGET)
+ cbData = (uint32_t)pScsiReq->cbT2IData;
+ else
+ cbData = (uint32_t)pScsiReq->cbI2TData;
+
+ paReqBHS = pIScsiPDU->aBHS;
+
+ /* Setup the BHS. */
+ paReqBHS[0] = RT_H2N_U32( ISCSI_FINAL_BIT | ISCSI_TASK_ATTR_SIMPLE | ISCSIOP_SCSI_CMD
+ | (pScsiReq->enmXfer << 21)); /* I=0,F=1,Attr=Simple */
+ paReqBHS[1] = RT_H2N_U32(0x00000000 | ((uint32_t)pScsiReq->cbI2TData & 0xffffff)); /* TotalAHSLength=0 */
+ paReqBHS[2] = RT_H2N_U32(pImage->LUN >> 32);
+ paReqBHS[3] = RT_H2N_U32(pImage->LUN & 0xffffffff);
+ paReqBHS[4] = pIScsiCmd->Itt;
+ paReqBHS[5] = RT_H2N_U32((uint32_t)cbData); Assert((uint32_t)cbData == cbData);
+ paReqBHS[6] = RT_H2N_U32(pImage->CmdSN);
+ paReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN);
+ memcpy(paReqBHS + 8, pScsiReq->abCDB, pScsiReq->cbCDB);
+
+ pIScsiPDU->CmdSN = pImage->CmdSN;
+ pImage->CmdSN++;
+
+ /* Setup the S/G buffers. */
+ uint32_t cnISCSIReq = 0;
+ pIScsiPDU->aISCSIReq[cnISCSIReq].cbSeg = sizeof(pIScsiPDU->aBHS);
+ pIScsiPDU->aISCSIReq[cnISCSIReq].pvSeg = pIScsiPDU->aBHS;
+ cnISCSIReq++;
+ cbSegs = sizeof(pIScsiPDU->aBHS);
+ /* Padding is not necessary for the BHS. */
+
+ if (pScsiReq->cbI2TData)
+ {
+ for (unsigned cSeg = 0; cSeg < pScsiReq->cI2TSegs; cSeg++)
+ {
+ Assert(cnISCSIReq < cI2TSegs);
+ pIScsiPDU->aISCSIReq[cnISCSIReq].cbSeg = pScsiReq->paI2TSegs[cSeg].cbSeg;
+ pIScsiPDU->aISCSIReq[cnISCSIReq].pvSeg = pScsiReq->paI2TSegs[cSeg].pvSeg;
+ cbSegs += pScsiReq->paI2TSegs[cSeg].cbSeg;
+ cnISCSIReq++;
+
+ /* Add padding if necessary. */
+ if (pScsiReq->paI2TSegs[cSeg].cbSeg & 3)
+ {
+ Assert(cnISCSIReq < cI2TSegs);
+ pIScsiPDU->aISCSIReq[cnISCSIReq].pvSeg = &pImage->aPadding[0];
+ pIScsiPDU->aISCSIReq[cnISCSIReq].cbSeg = 4 - (pScsiReq->paI2TSegs[cSeg].cbSeg & 3);
+ cbSegs += pIScsiPDU->aISCSIReq[cnISCSIReq].cbSeg;
+ cnISCSIReq++;
+ }
+ }
+ }
+
+ pIScsiPDU->cISCSIReq = cnISCSIReq;
+ pIScsiPDU->cbSgLeft = cbSegs;
+ RTSgBufInit(&pIScsiPDU->SgBuf, pIScsiPDU->aISCSIReq, cnISCSIReq);
+
+ /* Link the PDU to the list. */
+ iscsiPDUTxAdd(pImage, pIScsiPDU, false /* fFront */);
+
+ /* Start transfer of a PDU if there is no one active at the moment. */
+ if (!pImage->pIScsiPDUTxCur)
+ rc = iscsiSendPDUAsync(pImage);
+
+ return rc;
+}
+
+
+/**
+ * Updates the state of a request from the PDU we received.
+ *
+ * @return VBox status code.
+ * @param pImage iSCSI connection state to use.
+ * @param paRes Pointer to array of iSCSI response sections.
+ * @param cnRes Number of valid iSCSI response sections in the array.
+ */
+static int iscsiRecvPDUUpdateRequest(PISCSIIMAGE pImage, PISCSIRES paRes, uint32_t cnRes)
+{
+ int rc = VINF_SUCCESS;
+ PISCSICMD pIScsiCmd;
+ uint32_t *paResBHS;
+
+ LogFlowFunc(("pImage=%#p paRes=%#p cnRes=%u\n", pImage, paRes, cnRes));
+
+ Assert(cnRes == 1);
+ Assert(paRes[0].cbSeg >= ISCSI_BHS_SIZE);
+
+ paResBHS = (uint32_t *)paRes[0].pvSeg;
+
+ pIScsiCmd = iscsiCmdGetFromItt(pImage, paResBHS[4]);
+
+ if (pIScsiCmd)
+ {
+ bool final = false;
+ PSCSIREQ pScsiReq;
+
+ LogFlow(("Found SCSI command %#p for Itt=%#u\n", pIScsiCmd, paResBHS[4]));
+
+ Assert(pIScsiCmd->enmCmdType == ISCSICMDTYPE_REQ);
+ pScsiReq = pIScsiCmd->CmdType.ScsiReq.pScsiReq;
+
+ final = !!(RT_N2H_U32(paResBHS[0]) & ISCSI_FINAL_BIT);
+ ISCSIOPCODE cmd = (ISCSIOPCODE)(RT_N2H_U32(paResBHS[0]) & ISCSIOP_MASK);
+ if (cmd == ISCSIOP_SCSI_RES)
+ {
+ /* This is the final PDU which delivers the status (and may be omitted if
+ * the last Data-In PDU included successful completion status). Note
+ * that ExpStatSN has been bumped already in iscsiRecvPDU. */
+ if (!final || ((RT_N2H_U32(paResBHS[0]) & 0x0000ff00) != 0) || (RT_N2H_U32(paResBHS[6]) != pImage->ExpStatSN - 1))
+ {
+ /* SCSI Response in the wrong place or with a (target) failure. */
+ LogFlow(("Wrong ExpStatSN value in PDU\n"));
+ rc = VERR_PARSE_ERROR;
+ }
+ else
+ {
+ pScsiReq->status = RT_N2H_U32(paResBHS[0]) & 0x000000ff;
+ size_t cbData = RT_N2H_U32(paResBHS[1]) & 0x00ffffff;
+ void *pvSense = (uint8_t *)paRes[0].pvSeg + ISCSI_BHS_SIZE;
+
+ if (cbData >= 2)
+ {
+ uint32_t cbStat = RT_N2H_U32(((uint32_t *)pvSense)[0]) >> 16;
+ if (cbStat + 2 > cbData)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ {
+ /* Truncate sense data if it doesn't fit into the buffer. */
+ pScsiReq->cbSense = RT_MIN(cbStat, pScsiReq->cbSense);
+ memcpy(pScsiReq->abSense, (uint8_t *)pvSense + 2,
+ RT_MIN(paRes[0].cbSeg - ISCSI_BHS_SIZE - 2, pScsiReq->cbSense));
+ }
+ }
+ else if (cbData == 1)
+ rc = VERR_PARSE_ERROR;
+ else
+ pScsiReq->cbSense = 0;
+ }
+ iscsiCmdComplete(pImage, pIScsiCmd, rc);
+ }
+ else if (cmd == ISCSIOP_SCSI_DATA_IN)
+ {
+ /* A Data-In PDU carries some data that needs to be added to the received
+ * data in response to the command. There may be both partial and complete
+ * Data-In PDUs, so collect data until the status is included or the status
+ * is sent in a separate SCSI Result frame (see above). */
+ size_t cbData = RT_N2H_U32(paResBHS[1]) & 0x00ffffff;
+ void *pvData = (uint8_t *)paRes[0].pvSeg + ISCSI_BHS_SIZE;
+
+ if (final && cbData > pScsiReq->cbT2IData)
+ {
+ /* The received PDU is bigger than what we requested.
+ * Must not happen under normal circumstances and is a target error. */
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ {
+ /* Copy data from the received PDU into the T2I segments. */
+ size_t cbCopied = RTSgBufCopyFromBuf(&pScsiReq->SgBufT2I, pvData, cbData);
+ Assert(cbCopied == cbData); NOREF(cbCopied);
+
+ if (final && (RT_N2H_U32(paResBHS[0]) & ISCSI_STATUS_BIT) != 0)
+ {
+ pScsiReq->status = RT_N2H_U32(paResBHS[0]) & 0x000000ff;
+ pScsiReq->cbSense = 0;
+ iscsiCmdComplete(pImage, pIScsiCmd, VINF_SUCCESS);
+ }
+ }
+ }
+ else
+ rc = VERR_PARSE_ERROR;
+ }
+
+ /* Log any errors here but ignore the PDU. */
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("iSCSI: Received malformed PDU from target %s (rc=%Rrc), ignoring\n", pImage->pszTargetName, rc));
+ iscsiDumpPacket(pImage, (PISCSIREQ)paRes, cnRes, rc, false /* fRequest */);
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+/**
+ * Appends a key-value pair to the buffer. Normal ASCII strings (cbValue == 0) and large binary values
+ * of a given length (cbValue > 0) are directly supported. Other value types must be converted to ASCII
+ * by the caller. Strings must be in UTF-8 encoding.
+ *
+ * @returns VBOX status
+ * @param pbBuf Pointer to the key-value buffer.
+ * @param cbBuf Length of the key-value buffer.
+ * @param pcbBufCurr Currently used portion of the key-value buffer.
+ * @param pcszKey Pointer to a string containing the key.
+ * @param pcszValue Pointer to either a string containing the value or to a large binary value.
+ * @param cbValue Length of the binary value if applicable.
+ */
+static int iscsiTextAddKeyValue(uint8_t *pbBuf, size_t cbBuf, size_t *pcbBufCurr, const char *pcszKey,
+ const char *pcszValue, size_t cbValue)
+{
+ size_t cbBufTmp = *pcbBufCurr;
+ size_t cbKey = strlen(pcszKey);
+ size_t cbValueEnc;
+ uint8_t *pbCurr;
+
+ if (cbValue == 0)
+ cbValueEnc = strlen(pcszValue);
+ else
+ cbValueEnc = cbValue * 2 + 2; /* 2 hex bytes per byte, 2 bytes prefix */
+
+ if (cbBuf < cbBufTmp + cbKey + 1 + cbValueEnc + 1)
+ {
+ /* Buffer would overflow, signal error. */
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /*
+ * Append a key=value pair (zero terminated string) to the end of the buffer.
+ */
+ pbCurr = pbBuf + cbBufTmp;
+ memcpy(pbCurr, pcszKey, cbKey);
+ pbCurr += cbKey;
+ *pbCurr++ = '=';
+ if (cbValue == 0)
+ {
+ memcpy(pbCurr, pcszValue, cbValueEnc);
+ pbCurr += cbValueEnc;
+ }
+ else
+ {
+ *pbCurr++ = '0';
+ *pbCurr++ = 'x';
+ for (uint32_t i = 0; i < cbValue; i++)
+ {
+ uint8_t b;
+ b = pcszValue[i];
+ *pbCurr++ = NUM_2_HEX(b >> 4);
+ *pbCurr++ = NUM_2_HEX(b & 0xf);
+ }
+ }
+ *pbCurr = '\0';
+ *pcbBufCurr = cbBufTmp + cbKey + 1 + cbValueEnc + 1;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Retrieve the value for a given key from the key=value buffer.
+ *
+ * @returns VBox status code.
+ * @param pbBuf Buffer containing key=value pairs.
+ * @param cbBuf Length of buffer with key=value pairs.
+ * @param pcszKey Pointer to key for which to retrieve the value.
+ * @param ppcszValue Pointer to value string pointer.
+ */
+static int iscsiTextGetKeyValue(const uint8_t *pbBuf, size_t cbBuf, const char *pcszKey, const char **ppcszValue)
+{
+ size_t cbKey = strlen(pcszKey);
+
+ while (cbBuf != 0)
+ {
+ size_t cbKeyValNull = strlen((const char *)pbBuf) + 1;
+
+ if (strncmp(pcszKey, (const char *)pbBuf, cbKey) == 0 && pbBuf[cbKey] == '=')
+ {
+ *ppcszValue = (const char *)(pbBuf + cbKey + 1);
+ return VINF_SUCCESS;
+ }
+ pbBuf += cbKeyValNull;
+ cbBuf -= cbKeyValNull;
+ }
+ return VERR_INVALID_NAME;
+}
+
+
+/**
+ * Convert a long-binary value from a value string to the binary representation.
+ *
+ * @returns VBOX status
+ * @param pcszValue Pointer to a string containing the textual value representation.
+ * @param pbValue Pointer to the value buffer for the binary value.
+ * @param pcbValue In: length of value buffer, out: actual length of binary value.
+ */
+static int iscsiStrToBinary(const char *pcszValue, uint8_t *pbValue, size_t *pcbValue)
+{
+ size_t cbValue = *pcbValue;
+ char c1, c2, c3, c4;
+ Assert(cbValue >= 1);
+
+ if (strlen(pcszValue) < 3)
+ return VERR_PARSE_ERROR;
+ if (*pcszValue++ != '0')
+ return VERR_PARSE_ERROR;
+ switch (*pcszValue++)
+ {
+ case 'x':
+ case 'X':
+ if (strlen(pcszValue) & 1)
+ {
+ c1 = *pcszValue++;
+ *pbValue++ = HEX_2_NUM(c1);
+ cbValue--;
+ }
+ while (*pcszValue != '\0')
+ {
+ if (cbValue == 0)
+ return VERR_BUFFER_OVERFLOW;
+ c1 = *pcszValue++;
+ if ((c1 < '0' || c1 > '9') && (c1 < 'a' || c1 > 'f') && (c1 < 'A' || c1 > 'F'))
+ return VERR_PARSE_ERROR;
+ c2 = *pcszValue++;
+ if ((c2 < '0' || c2 > '9') && (c2 < 'a' || c2 > 'f') && (c2 < 'A' || c2 > 'F'))
+ return VERR_PARSE_ERROR;
+ *pbValue++ = (HEX_2_NUM(c1) << 4) | HEX_2_NUM(c2);
+ cbValue--;
+ }
+ *pcbValue -= cbValue;
+ break;
+ case 'b':
+ case 'B':
+ if ((strlen(pcszValue) & 3) != 0)
+ return VERR_PARSE_ERROR;
+ while (*pcszValue != '\0')
+ {
+ uint32_t temp;
+ if (cbValue == 0)
+ return VERR_BUFFER_OVERFLOW;
+ c1 = *pcszValue++;
+ if ((c1 < 'A' || c1 > 'Z') && (c1 < 'a' || c1 >'z') && (c1 < '0' || c1 > '9') && (c1 != '+') && (c1 != '/'))
+ return VERR_PARSE_ERROR;
+ c2 = *pcszValue++;
+ if ((c2 < 'A' || c2 > 'Z') && (c2 < 'a' || c2 >'z') && (c2 < '0' || c2 > '9') && (c2 != '+') && (c2 != '/'))
+ return VERR_PARSE_ERROR;
+ c3 = *pcszValue++;
+ if ((c3 < 'A' || c3 > 'Z') && (c3 < 'a' || c3 >'z') && (c3 < '0' || c3 > '9') && (c3 != '+') && (c3 != '/') && (c3 != '='))
+ return VERR_PARSE_ERROR;
+ c4 = *pcszValue++;
+ if ( (c3 == '=' && c4 != '=')
+ || ((c4 < 'A' || c4 > 'Z') && (c4 < 'a' || c4 >'z') && (c4 < '0' || c4 > '9') && (c4 != '+') && (c4 != '/') && (c4 != '=')))
+ return VERR_PARSE_ERROR;
+ temp = (B64_2_NUM(c1) << 18) | (B64_2_NUM(c2) << 12);
+ if (c3 == '=') {
+ if (*pcszValue != '\0')
+ return VERR_PARSE_ERROR;
+ *pbValue++ = temp >> 16;
+ cbValue--;
+ } else {
+ temp |= B64_2_NUM(c3) << 6;
+ if (c4 == '=') {
+ if (*pcszValue != '\0')
+ return VERR_PARSE_ERROR;
+ if (cbValue < 2)
+ return VERR_BUFFER_OVERFLOW;
+ *pbValue++ = temp >> 16;
+ *pbValue++ = (temp >> 8) & 0xff;
+ cbValue -= 2;
+ }
+ else
+ {
+ temp |= B64_2_NUM(c4);
+ if (cbValue < 3)
+ return VERR_BUFFER_OVERFLOW;
+ *pbValue++ = temp >> 16;
+ *pbValue++ = (temp >> 8) & 0xff;
+ *pbValue++ = temp & 0xff;
+ cbValue -= 3;
+ }
+ }
+ }
+ *pcbValue -= cbValue;
+ break;
+ default:
+ return VERR_PARSE_ERROR;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Retrieve the relevant parameter values and update the initiator state.
+ *
+ * @returns VBox status code.
+ * @param pImage Current iSCSI initiator state.
+ * @param pbBuf Buffer containing key=value pairs.
+ * @param cbBuf Length of buffer with key=value pairs.
+ */
+static int iscsiUpdateParameters(PISCSIIMAGE pImage, const uint8_t *pbBuf, size_t cbBuf)
+{
+ int rc;
+ const char *pcszMaxRecvDataSegmentLength = NULL;
+ const char *pcszMaxBurstLength = NULL;
+ const char *pcszFirstBurstLength = NULL;
+ rc = iscsiTextGetKeyValue(pbBuf, cbBuf, "MaxRecvDataSegmentLength", &pcszMaxRecvDataSegmentLength);
+ if (rc == VERR_INVALID_NAME)
+ rc = VINF_SUCCESS;
+ if (RT_FAILURE(rc))
+ return VERR_PARSE_ERROR;
+ rc = iscsiTextGetKeyValue(pbBuf, cbBuf, "MaxBurstLength", &pcszMaxBurstLength);
+ if (rc == VERR_INVALID_NAME)
+ rc = VINF_SUCCESS;
+ if (RT_FAILURE(rc))
+ return VERR_PARSE_ERROR;
+ rc = iscsiTextGetKeyValue(pbBuf, cbBuf, "FirstBurstLength", &pcszFirstBurstLength);
+ if (rc == VERR_INVALID_NAME)
+ rc = VINF_SUCCESS;
+ if (RT_FAILURE(rc))
+ return VERR_PARSE_ERROR;
+ if (pcszMaxRecvDataSegmentLength)
+ {
+ uint32_t cb = pImage->cbSendDataLength;
+ rc = RTStrToUInt32Full(pcszMaxRecvDataSegmentLength, 0, &cb);
+ AssertRC(rc);
+ pImage->cbSendDataLength = RT_MIN(pImage->cbSendDataLength, cb);
+ }
+ if (pcszMaxBurstLength)
+ {
+ uint32_t cb = pImage->cbSendDataLength;
+ rc = RTStrToUInt32Full(pcszMaxBurstLength, 0, &cb);
+ AssertRC(rc);
+ pImage->cbSendDataLength = RT_MIN(pImage->cbSendDataLength, cb);
+ }
+ if (pcszFirstBurstLength)
+ {
+ uint32_t cb = pImage->cbSendDataLength;
+ rc = RTStrToUInt32Full(pcszFirstBurstLength, 0, &cb);
+ AssertRC(rc);
+ pImage->cbSendDataLength = RT_MIN(pImage->cbSendDataLength, cb);
+ }
+ return VINF_SUCCESS;
+}
+
+
+static bool serial_number_less(uint32_t s1, uint32_t s2)
+{
+ return (s1 < s2 && s2 - s1 < 0x80000000) || (s1 > s2 && s1 - s2 > 0x80000000);
+}
+
+static bool serial_number_greater(uint32_t s1, uint32_t s2)
+{
+ return (s1 < s2 && s2 - s1 > 0x80000000) || (s1 > s2 && s1 - s2 < 0x80000000);
+}
+
+
+#ifdef IMPLEMENT_TARGET_AUTH
+static void chap_md5_generate_challenge(uint8_t *pbChallenge, size_t *pcbChallenge)
+{
+ uint8_t cbChallenge;
+
+ cbChallenge = RTrand_U8(CHAP_MD5_CHALLENGE_MIN, CHAP_MD5_CHALLENGE_MAX);
+ RTrand_bytes(pbChallenge, cbChallenge);
+ *pcbChallenge = cbChallenge;
+}
+#endif
+
+
+static void chap_md5_compute_response(uint8_t *pbResponse, uint8_t id, const uint8_t *pbChallenge, size_t cbChallenge,
+ const uint8_t *pbSecret, size_t cbSecret)
+{
+ RTMD5CONTEXT ctx;
+ uint8_t bId;
+
+ bId = id;
+ RTMd5Init(&ctx);
+ RTMd5Update(&ctx, &bId, 1);
+ RTMd5Update(&ctx, pbSecret, cbSecret);
+ RTMd5Update(&ctx, pbChallenge, cbChallenge);
+ RTMd5Final(pbResponse, &ctx);
+}
+
+/**
+ * Internal. - Wrapper around the extended select callback of the net interface.
+ */
+DECLINLINE(int) iscsiIoThreadWait(PISCSIIMAGE pImage, RTMSINTERVAL cMillies, uint32_t fEvents, uint32_t *pfEvents)
+{
+ return pImage->pIfNet->pfnSelectOneEx(pImage->Socket, fEvents, pfEvents, cMillies);
+}
+
+/**
+ * Internal. - Pokes a thread waiting for I/O.
+ */
+DECLINLINE(int) iscsiIoThreadPoke(PISCSIIMAGE pImage)
+{
+ return pImage->pIfNet->pfnPoke(pImage->Socket);
+}
+
+/**
+ * Internal. - Get the next request from the queue.
+ */
+DECLINLINE(PISCSICMD) iscsiCmdGet(PISCSIIMAGE pImage)
+{
+ int rc;
+ PISCSICMD pIScsiCmd = NULL;
+
+ rc = RTSemMutexRequest(pImage->MutexReqQueue, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+
+ pIScsiCmd = pImage->pScsiReqQueue;
+ if (pIScsiCmd)
+ {
+ pImage->pScsiReqQueue = pIScsiCmd->pNext;
+ pIScsiCmd->pNext = NULL;
+ }
+
+ rc = RTSemMutexRelease(pImage->MutexReqQueue);
+ AssertRC(rc);
+
+ return pIScsiCmd;
+}
+
+
+/**
+ * Internal. - Adds the given command to the queue.
+ */
+DECLINLINE(int) iscsiCmdPut(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd)
+{
+ int rc = RTSemMutexRequest(pImage->MutexReqQueue, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+
+ pIScsiCmd->pNext = pImage->pScsiReqQueue;
+ pImage->pScsiReqQueue = pIScsiCmd;
+
+ rc = RTSemMutexRelease(pImage->MutexReqQueue);
+ AssertRC(rc);
+
+ iscsiIoThreadPoke(pImage);
+
+ return rc;
+}
+
+/**
+ * Internal. - Completes the request with the appropriate action.
+ * Synchronous requests are completed with waking up the thread
+ * and asynchronous ones by continuing the associated I/O context.
+ */
+static void iscsiCmdComplete(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd, int rcCmd)
+{
+ LogFlowFunc(("pImage=%#p pIScsiCmd=%#p rcCmd=%Rrc\n", pImage, pIScsiCmd, rcCmd));
+
+ /* Remove from the table first. */
+ iscsiCmdRemove(pImage, pIScsiCmd->Itt);
+
+ /* Call completion callback. */
+ pIScsiCmd->pfnComplete(pImage, rcCmd, pIScsiCmd->pvUser);
+
+ /* Free command structure. */
+#ifdef DEBUG
+ memset(pIScsiCmd, 0xff, sizeof(ISCSICMD));
+#endif
+ RTMemFree(pIScsiCmd);
+}
+
+/**
+ * Clears all RX/TX PDU states and returns the command for the current
+ * pending TX PDU if existing.
+ *
+ * @returns Pointer to the iSCSI command for the current PDU transmitted or NULL
+ * if none is waiting.
+ * @param pImage iSCSI connection state.
+ */
+static PISCSICMD iscsiPDURxTxClear(PISCSIIMAGE pImage)
+{
+ PISCSICMD pIScsiCmdHead = NULL;
+ PISCSIPDUTX pIScsiPDUTx = NULL;
+
+ /* Reset PDU we are receiving. */
+ iscsiRecvPDUReset(pImage);
+
+ /*
+ * Abort all PDUs we are about to transmit,
+ * the command need a new Itt if the relogin is successful.
+ */
+ while (pImage->pIScsiPDUTxHead)
+ {
+ pIScsiPDUTx = pImage->pIScsiPDUTxHead;
+ pImage->pIScsiPDUTxHead = pIScsiPDUTx->pNext;
+
+ PISCSICMD pIScsiCmd = pIScsiPDUTx->pIScsiCmd;
+ if (pIScsiCmd)
+ {
+ /* Place on command list. */
+ pIScsiCmd->pNext = pIScsiCmdHead;
+ pIScsiCmdHead = pIScsiCmd;
+ }
+ RTMemFree(pIScsiPDUTx);
+ }
+
+ /* Clear the tail pointer (safety precaution). */
+ pImage->pIScsiPDUTxTail = NULL;
+
+ /* Clear the current PDU too. */
+ if (pImage->pIScsiPDUTxCur)
+ {
+ pIScsiPDUTx = pImage->pIScsiPDUTxCur;
+
+ pImage->pIScsiPDUTxCur = NULL;
+ PISCSICMD pIScsiCmd = pIScsiPDUTx->pIScsiCmd;
+ if (pIScsiCmd)
+ {
+ pIScsiCmd->pNext = pIScsiCmdHead;
+ pIScsiCmdHead = pIScsiCmd;
+ }
+ RTMemFree(pIScsiPDUTx);
+ }
+
+ return pIScsiCmdHead;
+}
+
+/**
+ * Rests the iSCSI connection state and returns a list of iSCSI commands pending
+ * when this was called.
+ *
+ * @returns Pointer to the head of the pending iSCSI command list.
+ * @param pImage iSCSI connection state.
+ */
+static PISCSICMD iscsiReset(PISCSIIMAGE pImage)
+{
+ PISCSICMD pIScsiCmdHead = NULL;
+ PISCSICMD pIScsiCmdCur = NULL;
+
+ /* Clear all in flight PDUs. */
+ pIScsiCmdHead = iscsiPDURxTxClear(pImage);
+
+ /*
+ * Get all commands which are waiting for a response
+ * They need to be resend too after a successful reconnect.
+ */
+ PISCSICMD pIScsiCmd = iscsiCmdRemoveAll(pImage);
+ if (pIScsiCmd)
+ {
+ pIScsiCmdCur = pIScsiCmd;
+ while (pIScsiCmdCur->pNext)
+ pIScsiCmdCur = pIScsiCmdCur->pNext;
+
+ /*
+ * Place them in front of the list because they are the oldest requests
+ * and need to be processed first to minimize the risk to time out.
+ */
+ pIScsiCmdCur->pNext = pIScsiCmdHead;
+ pIScsiCmdHead = pIScsiCmd;
+ }
+
+ return pIScsiCmdHead;
+}
+
+/**
+ * Reattaches the to the target after an error aborting
+ * pending commands and resending them.
+ *
+ * @param pImage iSCSI connection state.
+ */
+static void iscsiReattach(PISCSIIMAGE pImage)
+{
+ /* Close connection. */
+ iscsiTransportClose(pImage);
+ pImage->state = ISCSISTATE_FREE;
+
+ /* Reset the state and get the currently pending commands. */
+ PISCSICMD pIScsiCmdHead = iscsiReset(pImage);
+
+ /* Try to attach. */
+ int rc = iscsiAttach(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Phew, we have a connection again.
+ * Prepare new PDUs for the aborted commands.
+ */
+ while (pIScsiCmdHead)
+ {
+ PISCSICMD pIScsiCmd = pIScsiCmdHead;
+ pIScsiCmdHead = pIScsiCmdHead->pNext;
+
+ pIScsiCmd->pNext = NULL;
+
+ rc = iscsiPDUTxPrepare(pImage, pIScsiCmd);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ /* Another error, just give up and report an error. */
+ PISCSICMD pIScsiCmd = iscsiReset(pImage);
+
+ /* Concatenate both lists together so we can abort all requests below. */
+ if (pIScsiCmd)
+ {
+ PISCSICMD pIScsiCmdCur = pIScsiCmd;
+ while (pIScsiCmdCur->pNext)
+ pIScsiCmdCur = pIScsiCmdCur->pNext;
+
+ pIScsiCmdCur->pNext = pIScsiCmdHead;
+ pIScsiCmdHead = pIScsiCmd;
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * Still no luck, complete commands with error so the caller
+ * has a chance to inform the user and maybe resend the command.
+ */
+ while (pIScsiCmdHead)
+ {
+ PISCSICMD pIScsiCmd = pIScsiCmdHead;
+ pIScsiCmdHead = pIScsiCmdHead->pNext;
+
+ iscsiCmdComplete(pImage, pIScsiCmd, VERR_BROKEN_PIPE);
+ }
+ }
+}
+
+/**
+ * Internal. Main iSCSI I/O worker.
+ */
+static DECLCALLBACK(int) iscsiIoThreadWorker(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF1(hThreadSelf);
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pvUser;
+
+ /* Initialize the initial event mask. */
+ pImage->fPollEvents = VD_INTERFACETCPNET_EVT_READ | VD_INTERFACETCPNET_EVT_ERROR;
+
+ while (pImage->fRunning)
+ {
+ uint32_t fEvents;
+ int rc;
+
+ fEvents = 0;
+
+ /* Wait for work or for data from the target. */
+ RTMSINTERVAL msWait;
+
+ if (pImage->cCmdsWaiting)
+ {
+ pImage->fPollEvents &= ~VD_INTERFACETCPNET_HINT_INTERRUPT;
+ msWait = pImage->uReadTimeout;
+ }
+ else
+ {
+ pImage->fPollEvents |= VD_INTERFACETCPNET_HINT_INTERRUPT;
+ msWait = RT_INDEFINITE_WAIT;
+ }
+
+ LogFlow(("Waiting for events fPollEvents=%#x\n", pImage->fPollEvents));
+ rc = iscsiIoThreadWait(pImage, msWait, pImage->fPollEvents, &fEvents);
+ if (rc == VERR_INTERRUPTED)
+ {
+ /* Check the queue. */
+ PISCSICMD pIScsiCmd = iscsiCmdGet(pImage);
+
+ while (pIScsiCmd)
+ {
+ switch (pIScsiCmd->enmCmdType)
+ {
+ case ISCSICMDTYPE_REQ:
+ {
+ if ( !iscsiIsClientConnected(pImage)
+ && pImage->fTryReconnect)
+ {
+ pImage->fTryReconnect = false;
+ iscsiReattach(pImage);
+ }
+
+ /* If there is no connection complete the command with an error. */
+ if (RT_LIKELY(iscsiIsClientConnected(pImage)))
+ {
+ rc = iscsiPDUTxPrepare(pImage, pIScsiCmd);
+ if (RT_FAILURE(rc))
+ iscsiReattach(pImage);
+ }
+ else
+ iscsiCmdComplete(pImage, pIScsiCmd, VERR_NET_CONNECTION_REFUSED);
+ break;
+ }
+ case ISCSICMDTYPE_EXEC:
+ {
+ rc = pIScsiCmd->CmdType.Exec.pfnExec(pIScsiCmd->CmdType.Exec.pvUser);
+ iscsiCmdComplete(pImage, pIScsiCmd, rc);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid command type %d\n", pIScsiCmd->enmCmdType));
+ }
+
+ pIScsiCmd = iscsiCmdGet(pImage);
+ }
+ }
+ else if (rc == VERR_TIMEOUT && pImage->cCmdsWaiting)
+ {
+ /*
+ * We are waiting for a response from the target but
+ * it didn't answered yet.
+ * We assume the connection is broken and try to reconnect.
+ */
+ LogFlow(("Timed out while waiting for an answer from the target, reconnecting\n"));
+ iscsiReattach(pImage);
+ }
+ else if (RT_SUCCESS(rc) || rc == VERR_TIMEOUT)
+ {
+ Assert(pImage->state == ISCSISTATE_NORMAL);
+ LogFlow(("Got socket events %#x\n", fEvents));
+
+ if (fEvents & VD_INTERFACETCPNET_EVT_READ)
+ {
+ /* Continue or start a new PDU receive task */
+ LogFlow(("There is data on the socket\n"));
+ rc = iscsiRecvPDUAsync(pImage);
+ if (rc == VERR_BROKEN_PIPE)
+ iscsiReattach(pImage);
+ else if (RT_FAILURE(rc))
+ iscsiLogRel(pImage, "iSCSI: Handling incoming request failed %Rrc\n", rc);
+ }
+
+ if (fEvents & VD_INTERFACETCPNET_EVT_WRITE)
+ {
+ LogFlow(("The socket is writable\n"));
+ rc = iscsiSendPDUAsync(pImage);
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * Something unexpected happened, log the error and try to reset everything
+ * by reattaching to the target.
+ */
+ iscsiLogRel(pImage, "iSCSI: Sending PDU failed %Rrc\n", rc);
+ iscsiReattach(pImage);
+ }
+ }
+
+ if (fEvents & VD_INTERFACETCPNET_EVT_ERROR)
+ {
+ LogFlow(("An error ocurred\n"));
+ iscsiReattach(pImage);
+ }
+ }
+ else
+ iscsiLogRel(pImage, "iSCSI: Waiting for I/O failed rc=%Rrc\n", rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal. - Enqueues a request asynchronously.
+ */
+static int iscsiCommandAsync(PISCSIIMAGE pImage, PSCSIREQ pScsiReq,
+ PFNISCSICMDCOMPLETED pfnComplete, void *pvUser)
+{
+ int rc;
+
+ if (pImage->fExtendedSelectSupported)
+ {
+ PISCSICMD pIScsiCmd = (PISCSICMD)RTMemAllocZ(sizeof(ISCSICMD));
+ if (!pIScsiCmd)
+ return VERR_NO_MEMORY;
+
+ /* Init the command structure. */
+ pIScsiCmd->pNext = NULL;
+ pIScsiCmd->enmCmdType = ISCSICMDTYPE_REQ;
+ pIScsiCmd->pfnComplete = pfnComplete;
+ pIScsiCmd->pvUser = pvUser;
+ pIScsiCmd->CmdType.ScsiReq.pScsiReq = pScsiReq;
+
+ rc = iscsiCmdPut(pImage, pIScsiCmd);
+ if (RT_FAILURE(rc))
+ RTMemFree(pIScsiCmd);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ return rc;
+}
+
+static DECLCALLBACK(void) iscsiCommandCompleteSync(PISCSIIMAGE pImage, int rcReq, void *pvUser)
+{
+ RT_NOREF1(pImage);
+ PISCSICMDSYNC pIScsiCmdSync = (PISCSICMDSYNC)pvUser;
+
+ pIScsiCmdSync->rcCmd = rcReq;
+ int rc = RTSemEventSignal(pIScsiCmdSync->EventSem);
+ AssertRC(rc);
+}
+
+/**
+ * Internal. - Enqueues a request in a synchronous fashion
+ * i.e. returns when the request completed.
+ */
+static int iscsiCommandSync(PISCSIIMAGE pImage, PSCSIREQ pScsiReq, bool fRetry, int rcSense)
+{
+ int rc;
+
+ if (pImage->fExtendedSelectSupported)
+ {
+ ISCSICMDSYNC IScsiCmdSync;
+
+ /* Create event semaphore. */
+ rc = RTSemEventCreate(&IScsiCmdSync.EventSem);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (fRetry)
+ {
+ for (unsigned i = 0; i < 10; i++)
+ {
+ rc = iscsiCommandAsync(pImage, pScsiReq, iscsiCommandCompleteSync, &IScsiCmdSync);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTSemEventWait(IScsiCmdSync.EventSem, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ rc = IScsiCmdSync.rcCmd;
+
+ if ( (RT_SUCCESS(rc) && !pScsiReq->cbSense)
+ || RT_FAILURE(rc))
+ break;
+ rc = rcSense;
+ }
+ }
+ else
+ {
+ rc = iscsiCommandAsync(pImage, pScsiReq, iscsiCommandCompleteSync, &IScsiCmdSync);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemEventWait(IScsiCmdSync.EventSem, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ rc = IScsiCmdSync.rcCmd;
+
+ if (RT_FAILURE(rc) || pScsiReq->cbSense > 0)
+ rc = rcSense;
+ }
+ }
+
+ RTSemEventDestroy(IScsiCmdSync.EventSem);
+ }
+ else
+ {
+ if (fRetry)
+ {
+ rc = VINF_SUCCESS; /* (MSC incorrectly thinks it can be uninitialized) */
+ for (unsigned i = 0; i < 10; i++)
+ {
+ rc = iscsiCommand(pImage, pScsiReq);
+ if ( (RT_SUCCESS(rc) && !pScsiReq->cbSense)
+ || RT_FAILURE(rc))
+ break;
+ rc = rcSense;
+ }
+ }
+ else
+ {
+ rc = iscsiCommand(pImage, pScsiReq);
+ if (RT_FAILURE(rc) || pScsiReq->cbSense > 0)
+ rc = rcSense;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Internal. - Executes a given function in a synchronous fashion
+ * on the I/O thread if available.
+ */
+static int iscsiExecSync(PISCSIIMAGE pImage, PFNISCSIEXEC pfnExec, void *pvUser)
+{
+ int rc;
+
+ if (pImage->fExtendedSelectSupported)
+ {
+ ISCSICMDSYNC IScsiCmdSync;
+ PISCSICMD pIScsiCmd = (PISCSICMD)RTMemAllocZ(sizeof(ISCSICMD));
+ if (!pIScsiCmd)
+ return VERR_NO_MEMORY;
+
+ /* Create event semaphore. */
+ rc = RTSemEventCreate(&IScsiCmdSync.EventSem);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pIScsiCmd);
+ return rc;
+ }
+
+ /* Init the command structure. */
+ pIScsiCmd->pNext = NULL;
+ pIScsiCmd->enmCmdType = ISCSICMDTYPE_EXEC;
+ pIScsiCmd->pfnComplete = iscsiCommandCompleteSync;
+ pIScsiCmd->pvUser = &IScsiCmdSync;
+ pIScsiCmd->CmdType.Exec.pfnExec = pfnExec;
+ pIScsiCmd->CmdType.Exec.pvUser = pvUser;
+
+ rc = iscsiCmdPut(pImage, pIScsiCmd);
+ if (RT_FAILURE(rc))
+ RTMemFree(pIScsiCmd);
+ else
+ {
+ rc = RTSemEventWait(IScsiCmdSync.EventSem, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ rc = IScsiCmdSync.rcCmd;
+ }
+
+ RTSemEventDestroy(IScsiCmdSync.EventSem);
+ }
+ else
+ {
+ /* No I/O thread, execute in the current thread. */
+ rc = pfnExec(pvUser);
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(void) iscsiCommandAsyncComplete(PISCSIIMAGE pImage, int rcReq, void *pvUser)
+{
+ bool fComplete = true;
+ size_t cbTransfered = 0;
+ PSCSIREQ pScsiReq = (PSCSIREQ)pvUser;
+
+ if (RT_SUCCESS(rcReq))
+ ASMAtomicWriteU32(&pImage->cLoginsSinceIo, 0);
+
+ if ( RT_SUCCESS(rcReq)
+ && pScsiReq->cbSense > 0)
+ {
+ /* Try again if possible. */
+ if (pScsiReq->cSenseRetries > 0)
+ {
+ pScsiReq->cSenseRetries--;
+ pScsiReq->cbSense = sizeof(pScsiReq->abSense);
+ int rc = iscsiCommandAsync(pImage, pScsiReq, iscsiCommandAsyncComplete, pScsiReq);
+ if (RT_SUCCESS(rc))
+ fComplete = false;
+ else
+ rcReq = pScsiReq->rcSense;
+ }
+ else
+ rcReq = pScsiReq->rcSense;
+ }
+
+ if (fComplete)
+ {
+ if (pScsiReq->enmXfer == SCSIXFER_FROM_TARGET)
+ cbTransfered = pScsiReq->cbT2IData;
+ else if (pScsiReq->enmXfer == SCSIXFER_TO_TARGET)
+ cbTransfered = pScsiReq->cbI2TData;
+ else
+ AssertMsg(pScsiReq->enmXfer == SCSIXFER_NONE, ("To/From transfers are not supported yet\n"));
+
+ /* Continue I/O context. */
+ pImage->pIfIo->pfnIoCtxCompleted(pImage->pIfIo->Core.pvUser,
+ pScsiReq->pIoCtx, rcReq,
+ cbTransfered);
+
+ RTMemFree(pScsiReq);
+ }
+}
+
+
+/**
+ * Internal. Free all allocated space for representing an image, and optionally
+ * delete the image from disk.
+ */
+static int iscsiFreeImage(PISCSIIMAGE pImage, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+ Assert(!fDelete); NOREF(fDelete); /* This MUST be false, the flag isn't supported. */
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ if (pImage->Mutex != NIL_RTSEMMUTEX)
+ {
+ /* Detaching only makes sense when the mutex is there. Otherwise the
+ * failure happened long before we could attach to the target. */
+ iscsiExecSync(pImage, iscsiDetach, pImage);
+ RTSemMutexDestroy(pImage->Mutex);
+ pImage->Mutex = NIL_RTSEMMUTEX;
+ }
+ if (pImage->hThreadIo != NIL_RTTHREAD)
+ {
+ ASMAtomicXchgBool(&pImage->fRunning, false);
+ rc = iscsiIoThreadPoke(pImage);
+ AssertRC(rc);
+
+ /* Wait for the thread to terminate. */
+ rc = RTThreadWait(pImage->hThreadIo, RT_INDEFINITE_WAIT, NULL);
+ AssertRC(rc);
+ }
+ /* Destroy the socket. */
+ if (pImage->Socket != NIL_VDSOCKET)
+ {
+ pImage->pIfNet->pfnSocketDestroy(pImage->Socket);
+ }
+ if (pImage->MutexReqQueue != NIL_RTSEMMUTEX)
+ {
+ RTSemMutexDestroy(pImage->MutexReqQueue);
+ pImage->MutexReqQueue = NIL_RTSEMMUTEX;
+ }
+ if (pImage->pszTargetName)
+ {
+ RTMemFree(pImage->pszTargetName);
+ pImage->pszTargetName = NULL;
+ }
+ if (pImage->pszTargetAddress)
+ {
+ RTMemFree(pImage->pszTargetAddress);
+ pImage->pszTargetAddress = NULL;
+ }
+ if (pImage->pszInitiatorName)
+ {
+ if (pImage->fAutomaticInitiatorName)
+ RTStrFree(pImage->pszInitiatorName);
+ else
+ RTMemFree(pImage->pszInitiatorName);
+ pImage->pszInitiatorName = NULL;
+ }
+ if (pImage->pszInitiatorUsername)
+ {
+ RTMemFree(pImage->pszInitiatorUsername);
+ pImage->pszInitiatorUsername = NULL;
+ }
+ if (pImage->pbInitiatorSecret)
+ {
+ RTMemFree(pImage->pbInitiatorSecret);
+ pImage->pbInitiatorSecret = NULL;
+ }
+ if (pImage->pszTargetUsername)
+ {
+ RTMemFree(pImage->pszTargetUsername);
+ pImage->pszTargetUsername = NULL;
+ }
+ if (pImage->pbTargetSecret)
+ {
+ RTMemFree(pImage->pbTargetSecret);
+ pImage->pbTargetSecret = NULL;
+ }
+ if (pImage->pvRecvPDUBuf)
+ {
+ RTMemFree(pImage->pvRecvPDUBuf);
+ pImage->pvRecvPDUBuf = NULL;
+ }
+ if (pImage->pszHostname)
+ {
+ RTMemFree(pImage->pszHostname);
+ pImage->pszHostname = NULL;
+ }
+
+ pImage->cbRecvPDUResidual = 0;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Inits the basic iSCSI image state, allocating vital resources.
+ *
+ * @returns VBox status code.
+ * @param pImage The iSCSI image instance.
+ */
+static int iscsiOpenImageInit(PISCSIIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Get error signalling interface. */
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+
+ /* Get TCP network stack interface. */
+ pImage->pIfNet = VDIfTcpNetGet(pImage->pVDIfsImage);
+ if (pImage->pIfNet)
+ {
+ /* Get configuration interface. */
+ pImage->pIfConfig = VDIfConfigGet(pImage->pVDIfsImage);
+ if (pImage->pIfConfig)
+ {
+ /* Get I/O interface. */
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ if (pImage->pIfIo)
+ {
+ /* This ISID will be adjusted later to make it unique on this host. */
+ pImage->pszHostname = NULL;
+ pImage->uPort = 0;
+ pImage->Socket = NIL_VDSOCKET;
+ pImage->ISID = 0x800000000000ULL | 0x001234560000ULL;
+ pImage->cISCSIRetries = 10;
+ pImage->state = ISCSISTATE_FREE;
+ pImage->cLoginsSinceIo = 0;
+ pImage->Mutex = NIL_RTSEMMUTEX;
+ pImage->MutexReqQueue = NIL_RTSEMMUTEX;
+ pImage->pszInitiatorUsername = NULL;
+ pImage->pbInitiatorSecret = NULL;
+ pImage->cbInitiatorSecret = 0;
+ pImage->pszTargetUsername = NULL;
+ pImage->pbTargetSecret = NULL;
+ pImage->cbTargetSecret = 0;
+
+ memset(pImage->aCmdsWaiting, 0, sizeof(pImage->aCmdsWaiting));
+ pImage->cbRecvPDUResidual = 0;
+
+ pImage->pvRecvPDUBuf = RTMemAlloc(ISCSI_RECV_PDU_BUFFER_SIZE);
+ pImage->cbRecvPDUBuf = ISCSI_RECV_PDU_BUFFER_SIZE;
+ if (!pImage->pvRecvPDUBuf)
+ rc = VERR_NO_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ rc = RTSemMutexCreate(&pImage->Mutex);
+ if (RT_SUCCESS(rc))
+ rc = RTSemMutexCreate(&pImage->MutexReqQueue);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_UNKNOWN_INTERFACE,
+ RT_SRC_POS, N_("iSCSI: I/O interface missing"));
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_UNKNOWN_INTERFACE,
+ RT_SRC_POS, N_("iSCSI: configuration interface missing"));
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_UNKNOWN_INTERFACE,
+ RT_SRC_POS, N_("iSCSI: TCP network stack interface missing"));
+
+ return rc;
+}
+
+/**
+ * Parses the user supplied config before opening the connection to the target.
+ *
+ * @returns VBox status code.
+ * @param pImage The iSCSI image instance.
+ */
+static int iscsiOpenImageParseCfg(PISCSIIMAGE pImage)
+{
+ char *pszLUN = NULL, *pszLUNInitial = NULL;
+ bool fLunEncoded = false;
+ uint32_t uWriteSplitDef = 0;
+ uint32_t uTimeoutDef = 0;
+ uint64_t uCfgTmp = 0;
+ bool fHostIPDef = false;
+ bool fDumpMalformedPacketsDef = false;
+
+ int rc = RTStrToUInt32Full(s_iscsiConfigDefaultWriteSplit, 0, &uWriteSplitDef);
+ AssertRC(rc);
+ rc = RTStrToUInt32Full(s_iscsiConfigDefaultTimeout, 0, &uTimeoutDef);
+ AssertRC(rc);
+ rc = RTStrToUInt64Full(s_iscsiConfigDefaultHostIPStack, 0, &uCfgTmp);
+ AssertRC(rc);
+ fHostIPDef = RT_BOOL(uCfgTmp);
+ rc = RTStrToUInt64Full(s_iscsiConfigDefaultDumpMalformedPackets, 0, &uCfgTmp);
+ AssertRC(rc);
+ fDumpMalformedPacketsDef = RT_BOOL(uCfgTmp);
+
+ /* Validate configuration, detect unknown keys. */
+ if (!VDCFGAreKeysValid(pImage->pIfConfig,
+ "TargetName\0"
+ "InitiatorName\0"
+ "LUN\0"
+ "TargetAddress\0"
+ "InitiatorUsername\0"
+ "InitiatorSecret\0"
+ "InitiatorSecretEncrypted\0"
+ "TargetUsername\0"
+ "TargetSecret\0"
+ "WriteSplit\0"
+ "Timeout\0"
+ "HostIPStack\0"
+ "DumpMalformedPackets\0"))
+ return vdIfError(pImage->pIfError, VERR_VD_UNKNOWN_CFG_VALUES, RT_SRC_POS, N_("iSCSI: configuration error: unknown configuration keys present"));
+
+ /* Query the iSCSI upper level configuration. */
+ rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "TargetName", &pImage->pszTargetName);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read TargetName as string"));
+
+ rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "InitiatorName", &pImage->pszInitiatorName);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT)
+ pImage->fAutomaticInitiatorName = true;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read InitiatorName as string"));
+
+ rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "LUN", &pszLUN);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT)
+ {
+ rc = VINF_SUCCESS;
+ pImage->fAutomaticLUN = true;
+ }
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read LUN as string"));
+
+ if (pImage->fAutomaticLUN)
+ pImage->LUN = 0; /* Default to LUN 0. */
+ else
+ {
+ pszLUNInitial = pszLUN;
+ if (!strncmp(pszLUN, "enc", 3))
+ {
+ fLunEncoded = true;
+ pszLUN += 3;
+ }
+ rc = RTStrToUInt64Full(pszLUN, 0, &pImage->LUN);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to convert LUN to integer"));
+
+ RTMemFree(pszLUNInitial);
+ }
+ if (RT_SUCCESS(rc) && !fLunEncoded)
+ {
+ if (pImage->LUN <= 255)
+ pImage->LUN = pImage->LUN << 48; /* uses peripheral device addressing method */
+ else if (pImage->LUN <= 16383)
+ pImage->LUN = (pImage->LUN << 48) | RT_BIT_64(62); /* uses flat space addressing method */
+ else
+ rc = vdIfError(pImage->pIfError, VERR_OUT_OF_RANGE, RT_SRC_POS, N_("iSCSI: configuration error: LUN number out of range (0-16383)"));
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "TargetAddress", &pImage->pszTargetAddress);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read TargetAddress as string"));
+
+ rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "InitiatorUsername", &pImage->pszInitiatorUsername);
+ if (RT_FAILURE(rc) && rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT)
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read InitiatorUsername as string"));
+
+ rc = VDCFGQueryBytesAlloc(pImage->pIfConfig, "InitiatorSecret",
+ (void **)&pImage->pbInitiatorSecret, &pImage->cbInitiatorSecret);
+ if (RT_FAILURE(rc) && rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT)
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read InitiatorSecret as byte string"));
+
+ void *pvInitiatorSecretEncrypted;
+ size_t cbInitiatorSecretEncrypted;
+ rc = VDCFGQueryBytesAlloc(pImage->pIfConfig, "InitiatorSecretEncrypted",
+ &pvInitiatorSecretEncrypted, &cbInitiatorSecretEncrypted);
+ if (RT_SUCCESS(rc))
+ {
+ RTMemFree(pvInitiatorSecretEncrypted);
+ if (!pImage->pbInitiatorSecret)
+ {
+ /* we have an encrypted initiator secret but not an unencrypted one */
+ return vdIfError(pImage->pIfError, VERR_VD_ISCSI_SECRET_ENCRYPTED, RT_SRC_POS, N_("iSCSI: initiator secret not decrypted"));
+ }
+ }
+
+ rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "TargetUsername", &pImage->pszTargetUsername);
+ if (RT_FAILURE(rc) && rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT)
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read TargetUsername as string"));
+
+ rc = VDCFGQueryBytesAlloc(pImage->pIfConfig, "TargetSecret",
+ (void **)&pImage->pbTargetSecret, &pImage->cbTargetSecret);
+ if (RT_FAILURE(rc) && rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT)
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read TargetSecret as byte string"));
+
+ rc = VDCFGQueryU32Def(pImage->pIfConfig, "WriteSplit", &pImage->cbWriteSplit, uWriteSplitDef);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read WriteSplit as U32"));
+
+ /* Query the iSCSI lower level configuration. */
+ rc = VDCFGQueryU32Def(pImage->pIfConfig, "Timeout", &pImage->uReadTimeout, uTimeoutDef);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read Timeout as U32"));
+
+ rc = VDCFGQueryBoolDef(pImage->pIfConfig, "HostIPStack", &pImage->fHostIP, fHostIPDef);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read HostIPStack as boolean"));
+
+ rc = VDCFGQueryBoolDef(pImage->pIfConfig, "DumpMalformedPackets",
+ &pImage->fDumpMalformedPackets, fDumpMalformedPacketsDef);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read DumpMalformedPackets as boolean"));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Creates the necessary socket structure.
+ *
+ * @returns VBox status code.
+ * @param pImage The iSCSI image instance.
+ */
+static int iscsiOpenImageSocketCreate(PISCSIIMAGE pImage)
+{
+ /* Create the socket structure. */
+ int rc = pImage->pIfNet->pfnSocketCreate(VD_INTERFACETCPNET_CONNECT_EXTENDED_SELECT,
+ &pImage->Socket);
+ if (RT_SUCCESS(rc))
+ {
+ pImage->fExtendedSelectSupported = true;
+ pImage->fRunning = true;
+ rc = RTThreadCreate(&pImage->hThreadIo, iscsiIoThreadWorker, pImage, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "iSCSI-Io");
+ if (RT_FAILURE(rc))
+ LogFunc(("Creating iSCSI I/O thread failed rc=%Rrc\n", rc));
+ }
+ else if (rc == VERR_NOT_SUPPORTED)
+ {
+ /* Async I/O is not supported without extended select. */
+ if ((pImage->uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO))
+ LogFunc(("Extended select is not supported by the interface but async I/O is requested -> %Rrc\n", rc));
+ else
+ {
+ pImage->fExtendedSelectSupported = false;
+ rc = pImage->pIfNet->pfnSocketCreate(0, &pImage->Socket);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogFunc(("Creating socket failed -> %Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Issues a REPORT LUNS to the target.
+ *
+ * @returns VBox status code.
+ * @param pImage The iSCSI image instance.
+ */
+static int iscsiOpenImageReportLuns(PISCSIIMAGE pImage)
+{
+ SCSIREQ sr;
+ RTSGSEG DataSeg;
+ uint8_t rlundata[16];
+
+ /*
+ * Inquire available LUNs.
+ */
+ RT_ZERO(sr.abCDB);
+ sr.abCDB[0] = SCSI_REPORT_LUNS;
+ sr.abCDB[1] = 0; /* reserved */
+ sr.abCDB[2] = 0; /* reserved */
+ sr.abCDB[3] = 0; /* reserved */
+ sr.abCDB[4] = 0; /* reserved */
+ sr.abCDB[5] = 0; /* reserved */
+ sr.abCDB[6] = sizeof(rlundata) >> 24;
+ sr.abCDB[7] = (sizeof(rlundata) >> 16) & 0xff;
+ sr.abCDB[8] = (sizeof(rlundata) >> 8) & 0xff;
+ sr.abCDB[9] = sizeof(rlundata) & 0xff;
+ sr.abCDB[10] = 0; /* reserved */
+ sr.abCDB[11] = 0; /* control */
+
+ DataSeg.pvSeg = rlundata;
+ DataSeg.cbSeg = sizeof(rlundata);
+
+ sr.enmXfer = SCSIXFER_FROM_TARGET;
+ sr.cbCDB = 12;
+ sr.cbI2TData = 0;
+ sr.paI2TSegs = NULL;
+ sr.cI2TSegs = 0;
+ sr.cbT2IData = DataSeg.cbSeg;
+ sr.paT2ISegs = &DataSeg;
+ sr.cT2ISegs = 1;
+ sr.cbSense = sizeof(sr.abSense);
+ int rc = iscsiCommandSync(pImage, &sr, false, VERR_INVALID_STATE);
+ if (RT_FAILURE(rc))
+ LogRel(("iSCSI: Could not get LUN info for target %s, rc=%Rrc\n", pImage->pszTargetName, rc));
+
+ /* If there is a single LUN on the target, then either verify that it matches the explicitly
+ * configured LUN, or just use it if a LUN was not configured (defaulted to 0). For multi-LUN targets,
+ * require a correctly configured LUN.
+ */
+ uint32_t cbLuns = (rlundata[0] << 24) | (rlundata[1] << 16) | (rlundata[2] << 8) | rlundata[3];
+ unsigned cLuns = cbLuns / 8;
+
+ /* Dig out the first LUN. */
+ uint64_t uTgtLun = 0;
+ if ((rlundata[8] & 0xc0) == 0)
+ {
+ /* Single-byte LUN in 0-255 range. */
+ uTgtLun = rlundata[9];
+ }
+ else if ((rlundata[8] & 0xc0) == 0x40)
+ {
+ /* Two-byte LUN in 256-16383 range. */
+ uTgtLun = rlundata[9] | ((rlundata[8] & 0x3f) << 8);
+ uTgtLun = (uTgtLun << 48) | RT_BIT_64(62);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_OUT_OF_RANGE, RT_SRC_POS, N_("iSCSI: Reported LUN number out of range (0-16383)"));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ LogRel(("iSCSI: %u LUN(s), first LUN %RX64\n", cLuns, uTgtLun));
+
+ /* Convert the LUN back into the 64-bit format. */
+ if (uTgtLun <= 255)
+ uTgtLun = uTgtLun << 48;
+ else
+ {
+ Assert(uTgtLun <= 16383);
+ uTgtLun = (uTgtLun << 48) | RT_BIT_64(62);
+ }
+
+ if (cLuns == 1)
+ {
+ /* NB: It is valid to have a single LUN other than zero, at least in SPC-3. */
+ if (pImage->fAutomaticLUN)
+ pImage->LUN = uTgtLun;
+ else if (pImage->LUN != uTgtLun)
+ rc = vdIfError(pImage->pIfError, VERR_VD_ISCSI_INVALID_TYPE, RT_SRC_POS, N_("iSCSI: configuration error: Configured LUN does not match what target provides"));
+ }
+
+ return rc;
+}
+
+/**
+ * Issues the INQUIRY command to the target and checks for the correct device type.
+ *
+ * @returns VBox status code.
+ * @param pImage The iSCSI image instance.
+ */
+static int iscsiOpenImageInquiry(PISCSIIMAGE pImage)
+{
+ SCSIREQ sr;
+ RTSGSEG DataSeg;
+ uint8_t data8[8];
+
+ /*
+ * Inquire device characteristics - no tapes, scanners etc., please.
+ */
+ RT_ZERO(sr.abCDB);
+ sr.abCDB[0] = SCSI_INQUIRY;
+ sr.abCDB[1] = 0; /* reserved */
+ sr.abCDB[2] = 0; /* reserved */
+ sr.abCDB[3] = 0; /* reserved */
+ sr.abCDB[4] = sizeof(data8);
+ sr.abCDB[5] = 0; /* control */
+
+ DataSeg.pvSeg = data8;
+ DataSeg.cbSeg = sizeof(data8);
+
+ sr.enmXfer = SCSIXFER_FROM_TARGET;
+ sr.cbCDB = 6;
+ sr.cbI2TData = 0;
+ sr.paI2TSegs = NULL;
+ sr.cI2TSegs = 0;
+ sr.cbT2IData = DataSeg.cbSeg;
+ sr.paT2ISegs = &DataSeg;
+ sr.cT2ISegs = 1;
+ sr.cbSense = sizeof(sr.abSense);
+ int rc = iscsiCommandSync(pImage, &sr, true /* fRetry */, VERR_INVALID_STATE);
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t devType = (sr.cbT2IData > 0) ? data8[0] & SCSI_DEVTYPE_MASK : 255;
+ if (devType == SCSI_DEVTYPE_DISK)
+ {
+ uint8_t uCmdQueue = (sr.cbT2IData >= 8) ? data8[7] & SCSI_INQUIRY_CMDQUE_MASK : 0;
+ if (uCmdQueue > 0)
+ pImage->fCmdQueuingSupported = true;
+ else if (pImage->uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ LogRel(("iSCSI: target address %s, target name %s, %s command queuing\n",
+ pImage->pszTargetAddress, pImage->pszTargetName,
+ pImage->fCmdQueuingSupported ? "supports" : "doesn't support"));
+ }
+ else
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_ISCSI_INVALID_TYPE,
+ RT_SRC_POS, N_("iSCSI: target address %s, target name %s, SCSI LUN %lld reports device type=%u"),
+ pImage->pszTargetAddress, pImage->pszTargetName,
+ pImage->LUN, devType);
+ LogRel(("iSCSI: Unsupported SCSI peripheral device type %d for target %s\n", devType & SCSI_DEVTYPE_MASK, pImage->pszTargetName));
+ }
+ }
+ else
+ LogRel(("iSCSI: Could not get INQUIRY info for target %s, rc=%Rrc\n", pImage->pszTargetName, rc));
+
+ return rc;
+}
+
+/**
+ * Checks that the target allows write access if the caller requested it.
+ *
+ * @returns VBox status code.
+ * @param pImage The iSCSI image instance.
+ */
+static int iscsiOpenImageCheckWriteAccess(PISCSIIMAGE pImage)
+{
+ SCSIREQ sr;
+ RTSGSEG DataSeg;
+ uint8_t data4[4];
+
+ /*
+ * Query write disable bit in the device specific parameter entry in the
+ * mode parameter header. Refuse read/write opening of read only disks.
+ */
+ RT_ZERO(sr.abCDB);
+ sr.abCDB[0] = SCSI_MODE_SENSE_6;
+ sr.abCDB[1] = 0; /* dbd=0/reserved */
+ sr.abCDB[2] = 0x3f; /* pc=0/page code=0x3f, ask for all pages */
+ sr.abCDB[3] = 0; /* subpage code=0, return everything in page_0 format */
+ sr.abCDB[4] = sizeof(data4); /* allocation length=4 */
+ sr.abCDB[5] = 0; /* control */
+
+ DataSeg.pvSeg = data4;
+ DataSeg.cbSeg = sizeof(data4);
+
+ sr.enmXfer = SCSIXFER_FROM_TARGET;
+ sr.cbCDB = 6;
+ sr.cbI2TData = 0;
+ sr.paI2TSegs = NULL;
+ sr.cI2TSegs = 0;
+ sr.cbT2IData = DataSeg.cbSeg;
+ sr.paT2ISegs = &DataSeg;
+ sr.cT2ISegs = 1;
+ sr.cbSense = sizeof(sr.abSense);
+ int rc = iscsiCommandSync(pImage, &sr, true /* fRetry */, VERR_INVALID_STATE);
+ if (RT_SUCCESS(rc))
+ {
+ pImage->fTargetReadOnly = !!(data4[2] & 0x80);
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) && pImage->fTargetReadOnly)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ LogRel(("iSCSI: Could not get MODE SENSE info for target %s, rc=%Rrc\n", pImage->pszTargetName, rc));
+
+ return rc;
+}
+
+/**
+ * Queries the media and sector size of the target.
+ *
+ * @returns VBox status code.
+ * @param pImage The iSCSI image instance.
+ */
+static int iscsiOpenImageQueryTargetSizes(PISCSIIMAGE pImage)
+{
+ SCSIREQ sr;
+ RTSGSEG DataSeg;
+ uint8_t data12[12];
+
+ /*
+ * Determine sector size and capacity of the volume immediately.
+ */
+ RT_ZERO(data12);
+ RT_ZERO(sr.abCDB);
+ sr.abCDB[0] = SCSI_SERVICE_ACTION_IN_16;
+ sr.abCDB[1] = SCSI_SVC_ACTION_IN_READ_CAPACITY_16; /* subcommand */
+ sr.abCDB[10+3] = sizeof(data12); /* allocation length (dword) */
+
+ DataSeg.pvSeg = data12;
+ DataSeg.cbSeg = sizeof(data12);
+
+ sr.enmXfer = SCSIXFER_FROM_TARGET;
+ sr.cbCDB = 16;
+ sr.cbI2TData = 0;
+ sr.paI2TSegs = NULL;
+ sr.cI2TSegs = 0;
+ sr.cbT2IData = DataSeg.cbSeg;
+ sr.paT2ISegs = &DataSeg;
+ sr.cT2ISegs = 1;
+ sr.cbSense = sizeof(sr.abSense);
+
+ int rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ {
+ bool fEnd = false;
+ uint8_t cMaxRetries = 10;
+ do
+ {
+ switch (sr.status)
+ {
+ case SCSI_STATUS_OK:
+ {
+ pImage->cVolume = RT_BE2H_U64(*(uint64_t *)&data12[0]);
+ pImage->cVolume++;
+ pImage->cbSector = RT_BE2H_U32(*(uint32_t *)&data12[8]);
+ pImage->cbSize = pImage->cVolume * pImage->cbSector;
+ if (pImage->cVolume == 0 || pImage->cbSize < pImage->cVolume)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_ISCSI_INVALID_TYPE,
+ RT_SRC_POS, N_("iSCSI: target address %s, target name %s, SCSI LUN %lld reports media sector count=%llu sector size=%u"),
+ pImage->pszTargetAddress, pImage->pszTargetName,
+ pImage->LUN, pImage->cVolume, pImage->cbSector);
+ }
+ fEnd = true;
+ break;
+ }
+ case SCSI_STATUS_CHECK_CONDITION:
+ {
+ if((sr.abSense[2] & 0x0f) == SCSI_SENSE_UNIT_ATTENTION)
+ {
+ if( sr.abSense[12] == SCSI_ASC_POWER_ON_RESET_BUS_DEVICE_RESET_OCCURRED
+ && sr.abSense[13] == SCSI_ASCQ_POWER_ON_RESET_BUS_DEVICE_RESET_OCCURRED)
+ {
+ /** @todo for future: prepare and send command "REQUEST SENSE" which will
+ * return the status of target and will clear any unit attention
+ * condition that it reports */
+ rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS);
+ if (RT_FAILURE(rc))
+ fEnd = true;
+ cMaxRetries--;
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ {
+ rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS);
+ if (RT_FAILURE(rc))
+ fEnd = true;
+ cMaxRetries--;
+ break;
+ }
+ }
+ if (!cMaxRetries)
+ fEnd = true;
+ } while(!fEnd);
+ }
+ else
+ {
+ uint8_t data8[8];
+
+ RT_ZERO(data8);
+ sr.abCDB[0] = SCSI_READ_CAPACITY;
+ sr.abCDB[1] = 0; /* reserved */
+ sr.abCDB[2] = 0; /* reserved */
+ sr.abCDB[3] = 0; /* reserved */
+ sr.abCDB[4] = 0; /* reserved */
+ sr.abCDB[5] = 0; /* reserved */
+ sr.abCDB[6] = 0; /* reserved */
+ sr.abCDB[7] = 0; /* reserved */
+ sr.abCDB[8] = 0; /* reserved */
+ sr.abCDB[9] = 0; /* control */
+
+ DataSeg.pvSeg = data8;
+ DataSeg.cbSeg = sizeof(data8);
+
+ sr.enmXfer = SCSIXFER_FROM_TARGET;
+ sr.cbCDB = 10;
+ sr.cbI2TData = 0;
+ sr.paI2TSegs = NULL;
+ sr.cI2TSegs = 0;
+ sr.cbT2IData = DataSeg.cbSeg;
+ sr.paT2ISegs = &DataSeg;
+ sr.cT2ISegs = 1;
+ sr.cbSense = sizeof(sr.abSense);
+ rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ {
+ bool fEnd = false;
+ uint8_t cMaxRetries = 10;
+ do
+ {
+ switch (sr.status)
+ {
+ case SCSI_STATUS_OK:
+ {
+ pImage->cVolume = (data8[0] << 24) | (data8[1] << 16) | (data8[2] << 8) | data8[3];
+ pImage->cVolume++;
+ pImage->cbSector = (data8[4] << 24) | (data8[5] << 16) | (data8[6] << 8) | data8[7];
+ pImage->cbSize = pImage->cVolume * pImage->cbSector;
+ if (pImage->cVolume == 0)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_ISCSI_INVALID_TYPE,
+ RT_SRC_POS, N_("iSCSI: fallback capacity detection for target address %s, target name %s, SCSI LUN %lld reports media sector count=%llu sector size=%u"),
+ pImage->pszTargetAddress, pImage->pszTargetName,
+ pImage->LUN, pImage->cVolume, pImage->cbSector);
+ }
+
+ fEnd = true;
+ break;
+ }
+ case SCSI_STATUS_CHECK_CONDITION:
+ {
+ if((sr.abSense[2] & 0x0f) == SCSI_SENSE_UNIT_ATTENTION)
+ {
+ if( sr.abSense[12] == SCSI_ASC_POWER_ON_RESET_BUS_DEVICE_RESET_OCCURRED
+ && sr.abSense[13] == SCSI_ASCQ_POWER_ON_RESET_BUS_DEVICE_RESET_OCCURRED)
+ {
+ /** @todo for future: prepare and send command "REQUEST SENSE" which will
+ * return the status of target and will clear any unit attention
+ * condition that it reports */
+ rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS);
+ if (RT_FAILURE(rc))
+ fEnd = true;
+ cMaxRetries--;
+ break;
+
+ }
+ }
+ break;
+ }
+ default:
+ {
+ rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS);
+ if (RT_FAILURE(rc))
+ fEnd = true;
+ cMaxRetries--;
+ break;
+ }
+ }
+ if (!cMaxRetries)
+ fEnd = true;
+ } while(!fEnd);
+ }
+ else
+ LogRel(("iSCSI: Could not determine capacity of target %s, rc=%Rrc\n", pImage->pszTargetName, rc));
+ }
+
+ return rc;
+}
+
+/**
+ * Queries the state of the read/write caches and tries to enable them if disabled.
+ *
+ * @returns VBox status code.
+ * @param pImage The iSCSI image instance.
+ */
+static int iscsiOpenImageEnableReadWriteCache(PISCSIIMAGE pImage)
+{
+ /*
+ * Check the read and write cache bits.
+ * Try to enable the cache if it is disabled.
+ *
+ * We already checked that this is a block access device. No need
+ * to do it again.
+ */
+ SCSIREQ sr;
+ RTSGSEG DataSeg;
+ uint8_t aCachingModePage[32];
+
+ memset(aCachingModePage, '\0', sizeof(aCachingModePage));
+ sr.abCDB[0] = SCSI_MODE_SENSE_6;
+ sr.abCDB[1] = 0;
+ sr.abCDB[2] = (0x00 << 6) | (0x08 & 0x3f); /* Current values and caching mode page */
+ sr.abCDB[3] = 0; /* Sub page code. */
+ sr.abCDB[4] = sizeof(aCachingModePage) & 0xff;
+ sr.abCDB[5] = 0;
+
+ DataSeg.pvSeg = aCachingModePage;
+ DataSeg.cbSeg = sizeof(aCachingModePage);
+
+ sr.enmXfer = SCSIXFER_FROM_TARGET;
+ sr.cbCDB = 6;
+ sr.cbI2TData = 0;
+ sr.paI2TSegs = NULL;
+ sr.cI2TSegs = 0;
+ sr.cbT2IData = DataSeg.cbSeg;
+ sr.paT2ISegs = &DataSeg;
+ sr.cT2ISegs = 1;
+ sr.cbSense = sizeof(sr.abSense);
+ int rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS);
+ if ( RT_SUCCESS(rc)
+ && (sr.status == SCSI_STATUS_OK)
+ && (aCachingModePage[0] >= 15)
+ && (aCachingModePage[4 + aCachingModePage[3]] & 0x3f) == 0x08
+ && (aCachingModePage[4 + aCachingModePage[3] + 1] > 3))
+ {
+ uint32_t Offset = 4 + aCachingModePage[3];
+ /*
+ * Check if the read and/or the write cache is disabled.
+ * The write cache is disabled if bit 2 (WCE) is zero and
+ * the read cache is disabled if bit 0 (RCD) is set.
+ */
+ if (!ASMBitTest(&aCachingModePage[Offset + 2], 2) || ASMBitTest(&aCachingModePage[Offset + 2], 0))
+ {
+ /*
+ * Write Cache Enable (WCE) bit is zero or the Read Cache Disable (RCD) is one
+ * So one of the caches is disabled. Enable both caches.
+ * The rest is unchanged.
+ */
+ ASMBitSet(&aCachingModePage[Offset + 2], 2);
+ ASMBitClear(&aCachingModePage[Offset + 2], 0);
+
+ sr.abCDB[0] = SCSI_MODE_SELECT_6;
+ sr.abCDB[1] = 0; /* Don't write the page into NV RAM. */
+ sr.abCDB[2] = 0;
+ sr.abCDB[3] = 0;
+ sr.abCDB[4] = sizeof(aCachingModePage) & 0xff;
+ sr.abCDB[5] = 0;
+
+ DataSeg.pvSeg = aCachingModePage;
+ DataSeg.cbSeg = sizeof(aCachingModePage);
+
+ sr.enmXfer = SCSIXFER_TO_TARGET;
+ sr.cbCDB = 6;
+ sr.cbI2TData = DataSeg.cbSeg;
+ sr.paI2TSegs = &DataSeg;
+ sr.cI2TSegs = 1;
+ sr.cbT2IData = 0;
+ sr.paT2ISegs = NULL;
+ sr.cT2ISegs = 0;
+ sr.cbSense = sizeof(sr.abSense);
+ sr.status = 0;
+ rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS);
+ if ( RT_SUCCESS(rc)
+ && (sr.status == SCSI_STATUS_OK))
+ LogRel(("iSCSI: Enabled read and write cache of target %s\n", pImage->pszTargetName));
+ else
+ {
+ /* Log failures but continue. */
+ LogRel(("iSCSI: Could not enable read and write cache of target %s, rc=%Rrc status=%#x\n",
+ pImage->pszTargetName, rc, sr.status));
+ LogRel(("iSCSI: Sense:\n%.*Rhxd\n", sr.cbSense, sr.abSense));
+ rc = VINF_SUCCESS;
+ }
+ }
+ }
+ else
+ {
+ /* Log errors but continue. */
+ LogRel(("iSCSI: Could not check write cache of target %s, rc=%Rrc, got mode page %#x\n", pImage->pszTargetName, rc, aCachingModePage[0] & 0x3f));
+ LogRel(("iSCSI: Sense:\n%.*Rhxd\n", sr.cbSense, sr.abSense));
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int iscsiOpenImage(PISCSIIMAGE pImage, unsigned uOpenFlags)
+{
+ pImage->uOpenFlags = uOpenFlags;
+
+ int rc = iscsiOpenImageInit(pImage);
+ if (RT_SUCCESS(rc))
+ rc = iscsiOpenImageParseCfg(pImage);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Don't actually establish iSCSI transport connection if this is just an
+ * open to query the image information and the host IP stack isn't used.
+ * Even trying is rather useless, as in this context the InTnet IP stack
+ * isn't present. Returning dummies is the best possible result anyway. */
+ if ((uOpenFlags & VD_OPEN_FLAGS_INFO) && !pImage->fHostIP)
+ LogFunc(("Not opening the transport connection as IntNet IP stack is not available. Will return dummies\n"));
+ else
+ {
+ rc = iscsiOpenImageSocketCreate(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Attach to the iSCSI target. This implicitly establishes the iSCSI
+ * transport connection.
+ */
+ rc = iscsiExecSync(pImage, iscsiAttach, pImage);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("target '%s' opened successfully\n", pImage->pszTargetName));
+
+ rc = iscsiOpenImageReportLuns(pImage);
+ if (RT_SUCCESS(rc))
+ rc = iscsiOpenImageInquiry(pImage);
+ if (RT_SUCCESS(rc))
+ rc = iscsiOpenImageCheckWriteAccess(pImage);
+ if (RT_SUCCESS(rc))
+ rc = iscsiOpenImageQueryTargetSizes(pImage);
+ if (RT_SUCCESS(rc))
+ rc = iscsiOpenImageEnableReadWriteCache(pImage);
+ }
+ else
+ LogRel(("iSCSI: could not open target %s, rc=%Rrc\n", pImage->pszTargetName, rc));
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = pImage->cbSector;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = pImage->cbSector;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ iscsiFreeImage(pImage, false);
+ return rc;
+}
+
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) iscsiProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pszFilename, pVDIfsDisk, pVDIfsImage, enmDesiredType, penmType);
+ LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename));
+
+ /* iSCSI images can't be checked for validity this way, as the filename
+ * just can't supply enough configuration information. */
+ int rc = VERR_VD_ISCSI_INVALID_HEADER;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) iscsiOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ RT_NOREF1(enmType); /**< @todo r=klaus make use of the type info. */
+
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+ PISCSIIMAGE pImage = (PISCSIIMAGE)RTMemAllocZ(RT_UOFFSETOF(ISCSIIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pszInitiatorName = NULL;
+ pImage->pszTargetName = NULL;
+ pImage->pszTargetAddress = NULL;
+ pImage->pszInitiatorUsername = NULL;
+ pImage->pbInitiatorSecret = NULL;
+ pImage->pszTargetUsername = NULL;
+ pImage->pbTargetSecret = NULL;
+ pImage->paCurrReq = NULL;
+ pImage->pvRecvPDUBuf = NULL;
+ pImage->pszHostname = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+ pImage->cLogRelErrors = 0;
+
+ rc = iscsiOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("target %s cVolume %d, cbSector %d\n", pImage->pszTargetName, pImage->cVolume, pImage->cbSector));
+ LogRel(("iSCSI: target address %s, target name %s, SCSI LUN %lld\n", pImage->pszTargetAddress, pImage->pszTargetName, pImage->LUN));
+ *ppBackendData = pImage;
+ }
+ else
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnCreate */
+static DECLCALLBACK(int) iscsiCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ RT_NOREF8(pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags);
+ RT_NOREF7(uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData);
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+ int rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) iscsiClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+ int rc;
+
+ Assert(!fDelete); /* This flag is unsupported. */
+
+ rc = iscsiFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRead */
+static DECLCALLBACK(int) iscsiRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pBackendData=%p uOffset=%#llx pIoCtx=%#p cbToRead=%u pcbActuallyRead=%p\n",
+ pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead));
+
+ if ( uOffset + cbToRead > pImage->cbSize
+ || cbToRead == 0)
+ return VERR_INVALID_PARAMETER;
+
+ /*
+ * Clip read size to a value which is supported by the target.
+ */
+ cbToRead = RT_MIN(cbToRead, pImage->cbRecvDataLength);
+
+ unsigned cT2ISegs = 0;
+ size_t cbSegs = 0;
+
+ /* Get the number of segments. */
+ cbSegs = pImage->pIfIo->pfnIoCtxSegArrayCreate(pImage->pIfIo->Core.pvUser, pIoCtx,
+ NULL, &cT2ISegs, cbToRead);
+ Assert(cbSegs == cbToRead);
+
+ PSCSIREQ pReq = (PSCSIREQ)RTMemAllocZ(RT_UOFFSETOF_DYN(SCSIREQ, aSegs[cT2ISegs]));
+ if (RT_LIKELY(pReq))
+ {
+ uint64_t lba;
+ uint16_t tls;
+ uint8_t *pbCDB = &pReq->abCDB[0];
+ size_t cbCDB;
+
+ lba = uOffset / pImage->cbSector;
+ tls = (uint16_t)(cbToRead / pImage->cbSector);
+
+ cbSegs = pImage->pIfIo->pfnIoCtxSegArrayCreate(pImage->pIfIo->Core.pvUser, pIoCtx,
+ &pReq->aSegs[0],
+ &cT2ISegs, cbToRead);
+ Assert(cbSegs == cbToRead);
+
+ if (pImage->cVolume < _4G)
+ {
+ cbCDB = 10;
+ pbCDB[0] = SCSI_READ_10;
+ pbCDB[1] = 0; /* reserved */
+ pbCDB[2] = (lba >> 24) & 0xff;
+ pbCDB[3] = (lba >> 16) & 0xff;
+ pbCDB[4] = (lba >> 8) & 0xff;
+ pbCDB[5] = lba & 0xff;
+ pbCDB[6] = 0; /* reserved */
+ pbCDB[7] = (tls >> 8) & 0xff;
+ pbCDB[8] = tls & 0xff;
+ pbCDB[9] = 0; /* control */
+ }
+ else
+ {
+ cbCDB = 16;
+ pbCDB[0] = SCSI_READ_16;
+ pbCDB[1] = 0; /* reserved */
+ pbCDB[2] = (lba >> 56) & 0xff;
+ pbCDB[3] = (lba >> 48) & 0xff;
+ pbCDB[4] = (lba >> 40) & 0xff;
+ pbCDB[5] = (lba >> 32) & 0xff;
+ pbCDB[6] = (lba >> 24) & 0xff;
+ pbCDB[7] = (lba >> 16) & 0xff;
+ pbCDB[8] = (lba >> 8) & 0xff;
+ pbCDB[9] = lba & 0xff;
+ pbCDB[10] = 0; /* tls unused */
+ pbCDB[11] = 0; /* tls unused */
+ pbCDB[12] = (tls >> 8) & 0xff;
+ pbCDB[13] = tls & 0xff;
+ pbCDB[14] = 0; /* reserved */
+ pbCDB[15] = 0; /* reserved */
+ }
+
+ pReq->enmXfer = SCSIXFER_FROM_TARGET;
+ pReq->cbCDB = cbCDB;
+ pReq->cbI2TData = 0;
+ pReq->paI2TSegs = NULL;
+ pReq->cI2TSegs = 0;
+ pReq->cbT2IData = cbToRead;
+ pReq->paT2ISegs = &pReq->aSegs[pReq->cI2TSegs];
+ pReq->cbSense = sizeof(pReq->abSense);
+ pReq->cT2ISegs = cT2ISegs;
+ pReq->pIoCtx = pIoCtx;
+ pReq->cSenseRetries = 10;
+ pReq->rcSense = VERR_READ_ERROR;
+
+ if (vdIfIoIntIoCtxIsSynchronous(pImage->pIfIo, pIoCtx))
+ {
+ rc = iscsiCommandSync(pImage, pReq, true, VERR_READ_ERROR);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("iscsiCommandSync(%s, %#llx) -> %Rrc\n", pImage->pszTargetName, uOffset, rc));
+ *pcbActuallyRead = 0;
+ }
+ else
+ *pcbActuallyRead = pReq->cbT2IData;
+ }
+ else
+ {
+ rc = iscsiCommandAsync(pImage, pReq, iscsiCommandAsyncComplete, pReq);
+ if (RT_FAILURE(rc))
+ AssertMsgFailed(("iscsiCommandAsync(%s, %#llx) -> %Rrc\n", pImage->pszTargetName, uOffset, rc));
+ else
+ {
+ *pcbActuallyRead = cbToRead;
+ return VERR_VD_IOCTX_HALT; /* Halt the I/O context until further notification from the I/O thread. */
+ }
+ }
+
+ RTMemFree(pReq);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnWrite */
+static DECLCALLBACK(int) iscsiWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ RT_NOREF3(pcbPreRead, pcbPostRead, fWrite);
+ LogFlowFunc(("pBackendData=%p uOffset=%llu pIoCtx=%#p cbToWrite=%u pcbWriteProcess=%p pcbPreRead=%p pcbPostRead=%p fWrite=%u\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead, fWrite));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToWrite % 512 == 0);
+
+ if (uOffset + cbToWrite > pImage->cbSize)
+ return VERR_INVALID_PARAMETER;
+
+ /*
+ * Clip read size to a value which is supported by the target.
+ */
+ cbToWrite = RT_MIN(cbToWrite, pImage->cbSendDataLength);
+
+ unsigned cI2TSegs = 0;
+ size_t cbSegs = 0;
+
+ /* Get the number of segments. */
+ cbSegs = pImage->pIfIo->pfnIoCtxSegArrayCreate(pImage->pIfIo->Core.pvUser, pIoCtx,
+ NULL, &cI2TSegs, cbToWrite);
+ Assert(cbSegs == cbToWrite);
+
+ PSCSIREQ pReq = (PSCSIREQ)RTMemAllocZ(RT_UOFFSETOF_DYN(SCSIREQ, aSegs[cI2TSegs]));
+ if (RT_LIKELY(pReq))
+ {
+ uint64_t lba;
+ uint16_t tls;
+ uint8_t *pbCDB = &pReq->abCDB[0];
+ size_t cbCDB;
+
+ lba = uOffset / pImage->cbSector;
+ tls = (uint16_t)(cbToWrite / pImage->cbSector);
+
+ cbSegs = pImage->pIfIo->pfnIoCtxSegArrayCreate(pImage->pIfIo->Core.pvUser, pIoCtx,
+ &pReq->aSegs[0],
+ &cI2TSegs, cbToWrite);
+ Assert(cbSegs == cbToWrite);
+
+ if (pImage->cVolume < _4G)
+ {
+ cbCDB = 10;
+ pbCDB[0] = SCSI_WRITE_10;
+ pbCDB[1] = 0; /* reserved */
+ pbCDB[2] = (lba >> 24) & 0xff;
+ pbCDB[3] = (lba >> 16) & 0xff;
+ pbCDB[4] = (lba >> 8) & 0xff;
+ pbCDB[5] = lba & 0xff;
+ pbCDB[6] = 0; /* reserved */
+ pbCDB[7] = (tls >> 8) & 0xff;
+ pbCDB[8] = tls & 0xff;
+ pbCDB[9] = 0; /* control */
+ }
+ else
+ {
+ cbCDB = 16;
+ pbCDB[0] = SCSI_WRITE_16;
+ pbCDB[1] = 0; /* reserved */
+ pbCDB[2] = (lba >> 56) & 0xff;
+ pbCDB[3] = (lba >> 48) & 0xff;
+ pbCDB[4] = (lba >> 40) & 0xff;
+ pbCDB[5] = (lba >> 32) & 0xff;
+ pbCDB[6] = (lba >> 24) & 0xff;
+ pbCDB[7] = (lba >> 16) & 0xff;
+ pbCDB[8] = (lba >> 8) & 0xff;
+ pbCDB[9] = lba & 0xff;
+ pbCDB[10] = 0; /* tls unused */
+ pbCDB[11] = 0; /* tls unused */
+ pbCDB[12] = (tls >> 8) & 0xff;
+ pbCDB[13] = tls & 0xff;
+ pbCDB[14] = 0; /* reserved */
+ pbCDB[15] = 0; /* reserved */
+ }
+
+ pReq->enmXfer = SCSIXFER_TO_TARGET;
+ pReq->cbCDB = cbCDB;
+ pReq->cbI2TData = cbToWrite;
+ pReq->paI2TSegs = &pReq->aSegs[0];
+ pReq->cI2TSegs = cI2TSegs;
+ pReq->cbT2IData = 0;
+ pReq->paT2ISegs = NULL;
+ pReq->cT2ISegs = 0;
+ pReq->cbSense = sizeof(pReq->abSense);
+ pReq->pIoCtx = pIoCtx;
+ pReq->cSenseRetries = 10;
+ pReq->rcSense = VERR_WRITE_ERROR;
+
+ if (vdIfIoIntIoCtxIsSynchronous(pImage->pIfIo, pIoCtx))
+ {
+ rc = iscsiCommandSync(pImage, pReq, true, VERR_WRITE_ERROR);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("iscsiCommandSync(%s, %#llx) -> %Rrc\n", pImage->pszTargetName, uOffset, rc));
+ *pcbWriteProcess = 0;
+ }
+ else
+ *pcbWriteProcess = cbToWrite;
+ }
+ else
+ {
+ rc = iscsiCommandAsync(pImage, pReq, iscsiCommandAsyncComplete, pReq);
+ if (RT_FAILURE(rc))
+ AssertMsgFailed(("iscsiCommandAsync(%s, %#llx) -> %Rrc\n", pImage->pszTargetName, uOffset, rc));
+ else
+ {
+ *pcbWriteProcess = cbToWrite;
+ return VERR_VD_IOCTX_HALT; /* Halt the I/O context until further notification from the I/O thread. */
+ }
+ }
+
+ RTMemFree(pReq);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnFlush */
+static DECLCALLBACK(int) iscsiFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ LogFlowFunc(("pBackendData=%p pIoCtx=%#p\n", pBackendData, pIoCtx));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ PSCSIREQ pReq = (PSCSIREQ)RTMemAllocZ(sizeof(SCSIREQ));
+ if (RT_LIKELY(pReq))
+ {
+ uint8_t *pbCDB = &pReq->abCDB[0];
+
+ pbCDB[0] = SCSI_SYNCHRONIZE_CACHE;
+ pbCDB[1] = 0; /* reserved */
+ pbCDB[2] = 0; /* reserved */
+ pbCDB[3] = 0; /* reserved */
+ pbCDB[4] = 0; /* reserved */
+ pbCDB[5] = 0; /* reserved */
+ pbCDB[6] = 0; /* reserved */
+ pbCDB[7] = 0; /* reserved */
+ pbCDB[8] = 0; /* reserved */
+ pbCDB[9] = 0; /* control */
+
+ pReq->enmXfer = SCSIXFER_NONE;
+ pReq->cbCDB = 10;
+ pReq->cbI2TData = 0;
+ pReq->paI2TSegs = NULL;
+ pReq->cI2TSegs = 0;
+ pReq->cbT2IData = 0;
+ pReq->paT2ISegs = NULL;
+ pReq->cT2ISegs = 0;
+ pReq->cbSense = sizeof(pReq->abSense);
+ pReq->pIoCtx = pIoCtx;
+ pReq->cSenseRetries = 0;
+ pReq->rcSense = VINF_SUCCESS;
+
+ if (vdIfIoIntIoCtxIsSynchronous(pImage->pIfIo, pIoCtx))
+ {
+ rc = iscsiCommandSync(pImage, pReq, false, VINF_SUCCESS);
+ if (RT_FAILURE(rc))
+ AssertMsgFailed(("iscsiCommand(%s) -> %Rrc\n", pImage->pszTargetName, rc));
+ }
+ else
+ {
+ rc = iscsiCommandAsync(pImage, pReq, iscsiCommandAsyncComplete, pReq);
+ if (RT_FAILURE(rc))
+ AssertMsgFailed(("iscsiCommand(%s) -> %Rrc\n", pImage->pszTargetName, rc));
+ else
+ return VERR_VD_IOCTX_HALT; /* Halt the I/O context until further notification from the I/O thread. */
+ }
+
+ RTMemFree(pReq);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) iscsiGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+ RT_NOREF1(pImage);
+
+ return 0;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) iscsiGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ return pImage->cbSize;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) iscsiGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry)
+{
+ RT_NOREF1(pPCHSGeometry);
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", VERR_VD_GEOMETRY_NOT_SET,
+ pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return VERR_VD_GEOMETRY_NOT_SET;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) iscsiSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry)
+{
+ RT_NOREF1(pPCHSGeometry);
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) iscsiGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ RT_NOREF1(pLCHSGeometry);
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", VERR_VD_GEOMETRY_NOT_SET,
+ pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return VERR_VD_GEOMETRY_NOT_SET;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) iscsiSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ RT_NOREF1(pLCHSGeometry);
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) iscsiQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pImage->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) iscsiRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+ AssertPtr(pImage); RT_NOREF(pImage);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) iscsiGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", VD_IMAGE_FLAGS_FIXED));
+ return VD_IMAGE_FLAGS_FIXED;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) iscsiGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uOpenFlags));
+ return pImage->uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) iscsiSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p uOpenFlags=%#x\n", pBackendData, uOpenFlags));
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ AssertReturn(pImage && !(uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)),
+ VERR_INVALID_PARAMETER);
+
+ /*
+ * A read/write -> readonly transition is always possible,
+ * for the reverse direction check that the target didn't present itself
+ * as readonly during the first attach.
+ */
+ if ( !(uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && pImage->fTargetReadOnly)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ {
+ pImage->uOpenFlags = uOpenFlags;
+ pImage->fTryReconnect = true;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(iscsiGetComment);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(iscsiSetComment, PISCSIIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(iscsiGetUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(iscsiSetUuid, PISCSIIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(iscsiGetModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(iscsiSetModificationUuid, PISCSIIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(iscsiGetParentUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(iscsiSetParentUuid, PISCSIIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(iscsiGetParentModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(iscsiSetParentModificationUuid, PISCSIIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) iscsiDump(void *pBackendData)
+{
+ PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pImage);
+ /** @todo put something useful here */
+ vdIfErrorMessage(pImage->pIfError, "Header: cVolume=%u\n", pImage->cVolume);
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnComposeLocation */
+static DECLCALLBACK(int) iscsiComposeLocation(PVDINTERFACE pConfig, char **pszLocation)
+{
+ char *pszTarget = NULL;
+ char *pszLUN = NULL;
+ char *pszAddress = NULL;
+ int rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "TargetName", &pszTarget);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "LUN", &pszLUN);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "TargetAddress", &pszAddress);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTStrAPrintf(pszLocation, "iscsi://%s/%s/%s",
+ pszAddress, pszTarget, pszLUN) < 0)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ RTMemFree(pszTarget);
+ RTMemFree(pszLUN);
+ RTMemFree(pszAddress);
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnComposeName */
+static DECLCALLBACK(int) iscsiComposeName(PVDINTERFACE pConfig, char **pszName)
+{
+ char *pszTarget = NULL;
+ char *pszLUN = NULL;
+ char *pszAddress = NULL;
+ int rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "TargetName", &pszTarget);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "LUN", &pszLUN);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "TargetAddress", &pszAddress);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo think about a nicer looking location scheme for iSCSI */
+ if (RTStrAPrintf(pszName, "%s/%s/%s",
+ pszAddress, pszTarget, pszLUN) < 0)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ RTMemFree(pszTarget);
+ RTMemFree(pszLUN);
+ RTMemFree(pszAddress);
+
+ return rc;
+}
+
+
+const VDIMAGEBACKEND g_ISCSIBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "iSCSI",
+ /* uBackendCaps */
+ VD_CAP_CONFIG | VD_CAP_TCPNET | VD_CAP_ASYNC,
+ /* papszFileExtensions */
+ NULL,
+ /* paConfigInfo */
+ s_iscsiConfigInfo,
+ /* prnProbe */
+ iscsiProbe,
+ /* pfnOpen */
+ iscsiOpen,
+ /* pfnCreate */
+ iscsiCreate,
+ /* pfnRename */
+ NULL,
+ /* pfnClose */
+ iscsiClose,
+ /* pfnRead */
+ iscsiRead,
+ /* pfnWrite */
+ iscsiWrite,
+ /* pfnFlush */
+ iscsiFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ iscsiGetVersion,
+ /* pfnGetFileSize */
+ iscsiGetFileSize,
+ /* pfnGetPCHSGeometry */
+ iscsiGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ iscsiSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ iscsiGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ iscsiSetLCHSGeometry,
+ /* pfnQueryRegions */
+ iscsiQueryRegions,
+ /* pfnRegionListRelease */
+ iscsiRegionListRelease,
+ /* pfnGetImageFlags */
+ iscsiGetImageFlags,
+ /* pfnGetOpenFlags */
+ iscsiGetOpenFlags,
+ /* pfnSetOpenFlags */
+ iscsiSetOpenFlags,
+ /* pfnGetComment */
+ iscsiGetComment,
+ /* pfnSetComment */
+ iscsiSetComment,
+ /* pfnGetUuid */
+ iscsiGetUuid,
+ /* pfnSetUuid */
+ iscsiSetUuid,
+ /* pfnGetModificationUuid */
+ iscsiGetModificationUuid,
+ /* pfnSetModificationUuid */
+ iscsiSetModificationUuid,
+ /* pfnGetParentUuid */
+ iscsiGetParentUuid,
+ /* pfnSetParentUuid */
+ iscsiSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ iscsiGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ iscsiSetParentModificationUuid,
+ /* pfnDump */
+ iscsiDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ NULL,
+ /* pfnSetParentFilename */
+ NULL,
+ /* pfnComposeLocation */
+ iscsiComposeLocation,
+ /* pfnComposeName */
+ iscsiComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ NULL,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
diff --git a/src/VBox/Storage/Makefile.kmk b/src/VBox/Storage/Makefile.kmk
new file mode 100644
index 00000000..b29e1b36
--- /dev/null
+++ b/src/VBox/Storage/Makefile.kmk
@@ -0,0 +1,90 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Storage library.
+#
+
+#
+# Copyright (C) 2006-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+VBOX_PATH_STORAGE_SRC := $(PATH_SUB_CURRENT)
+
+if !defined(VBOX_ONLY_EXTPACKS)
+ include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+
+ #
+ # StorageLib - The storage Library.
+ #
+ LIBRARIES += StorageLib #StorageLibNoDB
+
+ StorageLib_TEMPLATE = VBOXR3
+ StorageLib_DEFS = IN_VBOXDDU
+ ifeq ($(USER),bird)
+ StorageLib_DEFS.debug += RTMEM_WRAP_TO_EF_APIS
+ endif
+
+ StorageLib_SOURCES = \
+ VD.cpp \
+ VDPlugin.cpp \
+ VDVfs.cpp \
+ VDIfVfs.cpp \
+ VDIfVfs2.cpp \
+ VDIfTcpNet.cpp \
+ VDI.cpp \
+ VMDK.cpp \
+ VHD.cpp \
+ DMG.cpp \
+ Parallels.cpp \
+ ISCSI.cpp \
+ RAW.cpp \
+ QED.cpp \
+ QCOW.cpp \
+ VHDX.cpp \
+ CUE.cpp \
+ VISO.cpp \
+ VCICache.cpp
+endif # !VBOX_ONLY_EXTPACKS
+
+if defined(VBOX_WITH_EXTPACK_PUEL) && defined(VBOX_WITH_EXTPACK_PUEL_BUILD)
+ if defined(VBOX_WITH_PLUGIN_CRYPT)
+ DLLS += VDPluginCrypt
+ VDPluginCrypt_TEMPLATE = VBoxR3ExtPackPuelWithOpenSsl
+ VDPluginCrypt_LDFLAGS.linux = $(VBOX_GCC_NO_UNDEFINED)
+ VDPluginCrypt_DEFS = $(if $(VD_WITH_IPRT_CRYPTO),VD_WITH_IPRT_CRYPTO,)
+ VDPluginCrypt_SOURCES = \
+ VDFilterCrypt.cpp \
+ VDKeyStore.cpp
+ VDPluginCrypt_SOURCES.win = \
+ VDPluginCrypt.rc
+ endif
+endif # VBOX_WITH_EXTPACK_PUEL
+
+#StorageLibNoDB_TEMPLATE = VBOXR3
+#StorageLibNoDB_DEFS = IN_VBOXDDU VBOX_HDD_NO_DYNAMIC_BACKENDS
+#StorageLibNoDB_SOURCES = \
+# $(StorageLib_SOURCES)
+
+# generate rules
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Storage/Parallels.cpp b/src/VBox/Storage/Parallels.cpp
new file mode 100644
index 00000000..039b4db8
--- /dev/null
+++ b/src/VBox/Storage/Parallels.cpp
@@ -0,0 +1,1204 @@
+/* $Id: Parallels.cpp $ */
+/** @file
+ *
+ * Parallels hdd disk image, core code.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_VD_PARALLELS
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/uuid.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+
+#include "VDBackends.h"
+
+#define PARALLELS_HEADER_MAGIC "WithoutFreeSpace"
+#define PARALLELS_DISK_VERSION 2
+
+/** The header of the parallels disk. */
+#pragma pack(1)
+typedef struct ParallelsHeader
+{
+ /** The magic header to identify a parallels hdd image. */
+ char HeaderIdentifier[16];
+ /** The version of the disk image. */
+ uint32_t uVersion;
+ /** The number of heads the hdd has. */
+ uint32_t cHeads;
+ /** Number of cylinders. */
+ uint32_t cCylinders;
+ /** Number of sectors per track. */
+ uint32_t cSectorsPerTrack;
+ /** Number of entries in the allocation bitmap. */
+ uint32_t cEntriesInAllocationBitmap;
+ /** Total number of sectors. */
+ uint32_t cSectors;
+ /** Padding. */
+ char Padding[24];
+} ParallelsHeader;
+#pragma pack()
+
+/**
+ * Parallels image structure.
+ */
+typedef struct PARALLELSIMAGE
+{
+ /** Image file name. */
+ const char *pszFilename;
+ /** Opaque storage handle. */
+ PVDIOSTORAGE pStorage;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+
+ /** Open flags passed by VBoxHDD layer. */
+ unsigned uOpenFlags;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+
+ /** Physical geometry of this image. */
+ VDGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ VDGEOMETRY LCHSGeometry;
+
+ /** Pointer to the allocation bitmap. */
+ uint32_t *pAllocationBitmap;
+ /** Entries in the allocation bitmap. */
+ uint64_t cAllocationBitmapEntries;
+ /** Flag whether the allocation bitmap was changed. */
+ bool fAllocationBitmapChanged;
+ /** Current file size. */
+ uint64_t cbFileCurrent;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} PARALLELSIMAGE, *PPARALLELSIMAGE;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aParallelsFileExtensions[] =
+{
+ {"hdd", VDTYPE_HDD},
+ {NULL, VDTYPE_INVALID}
+};
+
+/***************************************************
+ * Internal functions *
+ **************************************************/
+
+/**
+ * Internal. Flush image data to disk.
+ */
+static int parallelsFlushImage(PPARALLELSIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ return VINF_SUCCESS;
+
+ if ( !(pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ && (pImage->fAllocationBitmapChanged))
+ {
+ pImage->fAllocationBitmapChanged = false;
+ /* Write the allocation bitmap to the file. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ sizeof(ParallelsHeader), pImage->pAllocationBitmap,
+ pImage->cAllocationBitmapEntries * sizeof(uint32_t));
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /* Flush file. */
+ rc = vdIfIoIntFileFlushSync(pImage->pIfIo, pImage->pStorage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image except pImage,
+ * and optionally delete the image from disk.
+ */
+static int parallelsFreeImage(PPARALLELSIMAGE pImage, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ if (pImage->pStorage)
+ {
+ /* No point updating the file that is deleted anyway. */
+ if (!fDelete)
+ parallelsFlushImage(pImage);
+
+ rc = vdIfIoIntFileClose(pImage->pIfIo, pImage->pStorage);
+ pImage->pStorage = NULL;
+ }
+
+ if (pImage->pAllocationBitmap)
+ {
+ RTMemFree(pImage->pAllocationBitmap);
+ pImage->pAllocationBitmap = NULL;
+ }
+
+ if (fDelete && pImage->pszFilename)
+ vdIfIoIntFileDelete(pImage->pIfIo, pImage->pszFilename);
+ }
+
+ return rc;
+}
+
+static int parallelsOpenImage(PPARALLELSIMAGE pImage, unsigned uOpenFlags)
+{
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ pImage->uOpenFlags = uOpenFlags;
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ int rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags,
+ false /* fCreate */),
+ &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &pImage->cbFileCurrent);
+ if (RT_SUCCESS(rc)
+ && !(pImage->cbFileCurrent % 512))
+ {
+ ParallelsHeader parallelsHeader;
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, 0,
+ &parallelsHeader, sizeof(parallelsHeader));
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(parallelsHeader.HeaderIdentifier, PARALLELS_HEADER_MAGIC, 16))
+ {
+ /* Check if the file has hdd as extension. It is a fixed size raw image then. */
+ char *pszSuffix = RTPathSuffix(pImage->pszFilename);
+ if (!strcmp(pszSuffix, ".hdd"))
+ {
+ /* This is a fixed size image. */
+ pImage->uImageFlags |= VD_IMAGE_FLAGS_FIXED;
+ pImage->cbSize = pImage->cbFileCurrent;
+
+ pImage->PCHSGeometry.cHeads = 16;
+ pImage->PCHSGeometry.cSectors = 63;
+ uint64_t cCylinders = pImage->cbSize / (512 * pImage->PCHSGeometry.cSectors * pImage->PCHSGeometry.cHeads);
+ pImage->PCHSGeometry.cCylinders = (uint32_t)cCylinders;
+ }
+ else
+ rc = VERR_VD_PARALLELS_INVALID_HEADER;
+ }
+ else
+ {
+ if ( parallelsHeader.uVersion == PARALLELS_DISK_VERSION
+ && parallelsHeader.cEntriesInAllocationBitmap <= (1 << 30))
+ {
+ Log(("cSectors=%u\n", parallelsHeader.cSectors));
+ pImage->cbSize = ((uint64_t)parallelsHeader.cSectors) * 512;
+ pImage->uImageFlags = VD_IMAGE_FLAGS_NONE;
+ pImage->PCHSGeometry.cCylinders = parallelsHeader.cCylinders;
+ pImage->PCHSGeometry.cHeads = parallelsHeader.cHeads;
+ pImage->PCHSGeometry.cSectors = parallelsHeader.cSectorsPerTrack;
+ pImage->cAllocationBitmapEntries = parallelsHeader.cEntriesInAllocationBitmap;
+ pImage->pAllocationBitmap = (uint32_t *)RTMemAllocZ((uint32_t)pImage->cAllocationBitmapEntries * sizeof(uint32_t));
+ if (RT_LIKELY(pImage->pAllocationBitmap))
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ sizeof(ParallelsHeader), pImage->pAllocationBitmap,
+ pImage->cAllocationBitmapEntries * sizeof(uint32_t));
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_VD_PARALLELS_INVALID_HEADER;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ parallelsFreeImage(pImage, false);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal: Create a parallels image.
+ */
+static int parallelsCreateImage(PPARALLELSIMAGE pImage, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry, unsigned uOpenFlags,
+ PFNVDPROGRESS pfnProgress, void *pvUser,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ RT_NOREF1(pszComment);
+ int rc = VINF_SUCCESS;
+ int32_t fOpen;
+
+ if (!(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ pImage->uOpenFlags = uOpenFlags & ~VD_OPEN_FLAGS_READONLY;
+ pImage->uImageFlags = uImageFlags;
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ if (!pImage->PCHSGeometry.cCylinders)
+ {
+ /* Set defaults. */
+ pImage->PCHSGeometry.cSectors = 63;
+ pImage->PCHSGeometry.cHeads = 16;
+ pImage->PCHSGeometry.cCylinders = pImage->cbSize / (512 * pImage->PCHSGeometry.cSectors * pImage->PCHSGeometry.cHeads);
+ }
+
+ /* Create image file. */
+ fOpen = VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags, true /* fCreate */);
+ rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename, fOpen, &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ if (pfnProgress)
+ pfnProgress(pvUser, uPercentStart + uPercentSpan * 98 / 100);
+
+ /* Setup image state. */
+ pImage->cbSize = cbSize;
+ pImage->cAllocationBitmapEntries = cbSize / 512 / pImage->PCHSGeometry.cSectors;
+ if (pImage->cAllocationBitmapEntries * pImage->PCHSGeometry.cSectors * 512 < cbSize)
+ pImage->cAllocationBitmapEntries++;
+ pImage->fAllocationBitmapChanged = true;
+ pImage->cbFileCurrent = sizeof(ParallelsHeader) + pImage->cAllocationBitmapEntries * sizeof(uint32_t);
+ /* Round to next sector boundary. */
+ pImage->cbFileCurrent += 512 - pImage->cbFileCurrent % 512;
+ Assert(!(pImage->cbFileCurrent % 512));
+ pImage->pAllocationBitmap = (uint32_t *)RTMemAllocZ(pImage->cAllocationBitmapEntries * sizeof(uint32_t));
+ if (pImage->pAllocationBitmap)
+ {
+ ParallelsHeader Header;
+
+ memcpy(Header.HeaderIdentifier, PARALLELS_HEADER_MAGIC, sizeof(Header.HeaderIdentifier));
+ Header.uVersion = RT_H2LE_U32(PARALLELS_DISK_VERSION);
+ Header.cHeads = RT_H2LE_U32(pImage->PCHSGeometry.cHeads);
+ Header.cCylinders = RT_H2LE_U32(pImage->PCHSGeometry.cCylinders);
+ Header.cSectorsPerTrack = RT_H2LE_U32(pImage->PCHSGeometry.cSectors);
+ Header.cEntriesInAllocationBitmap = RT_H2LE_U32(pImage->cAllocationBitmapEntries);
+ Header.cSectors = RT_H2LE_U32(pImage->cbSize / 512);
+ memset(Header.Padding, 0, sizeof(Header.Padding));
+
+ /* Write header and allocation bitmap. */
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pImage->cbFileCurrent);
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, 0,
+ &Header, sizeof(Header));
+ if (RT_SUCCESS(rc))
+ rc = parallelsFlushImage(pImage); /* Writes the allocation bitmap. */
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("Parallels: cannot create image '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_INVALID_TYPE, RT_SRC_POS, N_("Parallels: cannot create fixed image '%s'. Create a raw image"), pImage->pszFilename);
+
+ if (RT_SUCCESS(rc) && pfnProgress)
+ pfnProgress(pvUser, uPercentStart + uPercentSpan);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ parallelsFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) parallelsProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pVDIfsDisk, enmDesiredType);
+ int rc;
+ PVDIOSTORAGE pStorage;
+ ParallelsHeader parallelsHeader;
+
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, &parallelsHeader,
+ sizeof(ParallelsHeader));
+ if (RT_SUCCESS(rc))
+ {
+ if ( !memcmp(parallelsHeader.HeaderIdentifier, PARALLELS_HEADER_MAGIC, 16)
+ && (parallelsHeader.uVersion == PARALLELS_DISK_VERSION))
+ rc = VINF_SUCCESS;
+ else
+ {
+ /*
+ * The image may be an fixed size image.
+ * Unfortunately fixed sized parallels images
+ * are just raw files hence no magic header to
+ * check for.
+ * The code succeeds if the file is a multiple
+ * of 512 and if the file extensions is *.hdd
+ */
+ uint64_t cbFile;
+ char *pszSuffix;
+
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if (RT_FAILURE(rc) || ((cbFile % 512) != 0))
+ {
+ vdIfIoIntFileClose(pIfIo, pStorage);
+ return VERR_VD_PARALLELS_INVALID_HEADER;
+ }
+
+ pszSuffix = RTPathSuffix(pszFilename);
+ if (!pszSuffix || strcmp(pszSuffix, ".hdd"))
+ rc = VERR_VD_PARALLELS_INVALID_HEADER;
+ else
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ *penmType = VDTYPE_HDD;
+
+ vdIfIoIntFileClose(pIfIo, pStorage);
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) parallelsOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n", pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc;
+ PPARALLELSIMAGE pImage;
+
+ NOREF(enmType); /**< @todo r=klaus make use of the type info. */
+
+ /* Check parameters. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ pImage = (PPARALLELSIMAGE)RTMemAllocZ(RT_UOFFSETOF(PARALLELSIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+ pImage->fAllocationBitmapChanged = false;
+
+ rc = parallelsOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ else
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnCreate */
+static DECLCALLBACK(int) parallelsCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry, PCRTUUID pUuid,
+ unsigned uOpenFlags, unsigned uPercentStart,
+ unsigned uPercentSpan, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+
+ /* Check the VD container type. */
+ if (enmType != VDTYPE_HDD)
+ return VERR_VD_INVALID_TYPE;
+
+ /* Check arguments. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER);
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ PPARALLELSIMAGE pImage;
+ PFNVDPROGRESS pfnProgress = NULL;
+ void *pvUser = NULL;
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+ if (pIfProgress)
+ {
+ pfnProgress = pIfProgress->pfnProgress;
+ pvUser = pIfProgress->Core.pvUser;
+ }
+
+ pImage = (PPARALLELSIMAGE)RTMemAllocZ(RT_UOFFSETOF(PARALLELSIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = parallelsCreateImage(pImage, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, uOpenFlags,
+ pfnProgress, pvUser, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ parallelsFreeImage(pImage, false);
+ rc = parallelsOpenImage(pImage, uOpenFlags);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRename */
+static DECLCALLBACK(int) parallelsRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VINF_SUCCESS;
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ /* Check arguments. */
+ AssertReturn((pImage && pszFilename && *pszFilename), VERR_INVALID_PARAMETER);
+
+ /* Close the image. */
+ rc = parallelsFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ {
+ /* Rename the file. */
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pszFilename, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the old image with new name. */
+ rc = parallelsOpenImage(pImage, pImage->uOpenFlags);
+ }
+ else
+ {
+ /* The move failed, try to reopen the original image. */
+ int rc2 = parallelsOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) parallelsClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+ int rc = parallelsFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRead */
+static DECLCALLBACK(int) parallelsRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToRead=%zu pcbActuallyRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead));
+ int rc = VINF_SUCCESS;
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+ uint64_t uSector;
+ uint64_t uOffsetInFile;
+ uint32_t iIndexInAllocationTable;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToRead % 512 == 0);
+
+ if (pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, uOffset,
+ pIoCtx, cbToRead);
+ else
+ {
+ /* Calculate offset in the real file. */
+ uSector = uOffset / 512;
+ /* One chunk in the file is always one track big. */
+ iIndexInAllocationTable = (uint32_t)(uSector / pImage->PCHSGeometry.cSectors);
+ uSector = uSector % pImage->PCHSGeometry.cSectors;
+
+ cbToRead = RT_MIN(cbToRead, (pImage->PCHSGeometry.cSectors - uSector)*512);
+
+ if (pImage->pAllocationBitmap[iIndexInAllocationTable] == 0)
+ rc = VERR_VD_BLOCK_FREE;
+ else
+ {
+ uOffsetInFile = (pImage->pAllocationBitmap[iIndexInAllocationTable] + uSector) * 512;
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, uOffsetInFile,
+ pIoCtx, cbToRead);
+ }
+ }
+
+ *pcbActuallyRead = cbToRead;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnWrite */
+static DECLCALLBACK(int) parallelsWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess));
+ int rc = VINF_SUCCESS;
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+ uint64_t uSector;
+ uint64_t uOffsetInFile;
+ uint32_t iIndexInAllocationTable;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToWrite % 512 == 0);
+
+ if (pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage, uOffset,
+ pIoCtx, cbToWrite, NULL, NULL);
+ else
+ {
+ /* Calculate offset in the real file. */
+ uSector = uOffset / 512;
+ /* One chunk in the file is always one track big. */
+ iIndexInAllocationTable = (uint32_t)(uSector / pImage->PCHSGeometry.cSectors);
+ uSector = uSector % pImage->PCHSGeometry.cSectors;
+
+ cbToWrite = RT_MIN(cbToWrite, (pImage->PCHSGeometry.cSectors - uSector)*512);
+
+ if (pImage->pAllocationBitmap[iIndexInAllocationTable] == 0)
+ {
+ if (fWrite & VD_WRITE_NO_ALLOC)
+ {
+ *pcbPreRead = uSector * 512;
+ *pcbPostRead = pImage->PCHSGeometry.cSectors * 512 - cbToWrite - *pcbPreRead;
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+ return VERR_VD_BLOCK_FREE;
+ }
+
+ /* Allocate new chunk in the file. */
+ Assert(uSector == 0);
+ AssertMsg(pImage->cbFileCurrent % 512 == 0, ("File size is not a multiple of 512\n"));
+ pImage->pAllocationBitmap[iIndexInAllocationTable] = (uint32_t)(pImage->cbFileCurrent / 512);
+ pImage->cbFileCurrent += pImage->PCHSGeometry.cSectors * 512;
+ pImage->fAllocationBitmapChanged = true;
+ uOffsetInFile = (uint64_t)pImage->pAllocationBitmap[iIndexInAllocationTable] * 512;
+
+ /*
+ * Write the new block at the current end of the file.
+ */
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ uOffsetInFile, pIoCtx, cbToWrite, NULL, NULL);
+ if (RT_SUCCESS(rc) || (rc == VERR_VD_ASYNC_IO_IN_PROGRESS))
+ {
+ /* Write the changed allocation bitmap entry. */
+ /** @todo Error handling. */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ sizeof(ParallelsHeader) + iIndexInAllocationTable * sizeof(uint32_t),
+ &pImage->pAllocationBitmap[iIndexInAllocationTable],
+ sizeof(uint32_t), pIoCtx,
+ NULL, NULL);
+ }
+
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+ }
+ else
+ {
+ uOffsetInFile = (pImage->pAllocationBitmap[iIndexInAllocationTable] + uSector) * 512;
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ uOffsetInFile, pIoCtx, cbToWrite, NULL, NULL);
+ }
+ }
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnFlush */
+static DECLCALLBACK(int) parallelsFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ LogFlowFunc(("pImage=#%p\n", pImage));
+
+ /* Flush the file, everything is up to date already. */
+ rc = vdIfIoIntFileFlush(pImage->pIfIo, pImage->pStorage, pIoCtx, NULL, NULL);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) parallelsGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ return PARALLELS_DISK_VERSION;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) parallelsGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtrReturn(pImage, 0);
+
+ if (pImage->pStorage)
+ cb = pImage->cbFileCurrent;
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) parallelsGetPCHSGeometry(void *pBackendData,
+ PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->PCHSGeometry.cCylinders)
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) parallelsSetPCHSGeometry(void *pBackendData,
+ PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pBackendData,
+ pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->PCHSGeometry = *pPCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) parallelsGetLCHSGeometry(void *pBackendData,
+ PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->LCHSGeometry.cCylinders)
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) parallelsSetLCHSGeometry(void *pBackendData,
+ PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->LCHSGeometry = *pLCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) parallelsQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PPARALLELSIMAGE pThis = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) parallelsRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PPARALLELSIMAGE pThis = (PPARALLELSIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) parallelsGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uImageFlags));
+ return pImage->uImageFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) parallelsGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uOpenFlags));
+ return pImage->uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) parallelsSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pImage || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ parallelsFreeImage(pImage, false);
+ rc = parallelsOpenImage(pImage, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+static DECLCALLBACK(int) parallelsGetComment(void *pBackendData, char *pszComment,
+ size_t cbComment)
+{
+ RT_NOREF2(pszComment, cbComment);
+ LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ LogFlowFunc(("returns %Rrc comment='%s'\n", VERR_NOT_SUPPORTED, pszComment));
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+static DECLCALLBACK(int) parallelsSetComment(void *pBackendData, const char *pszComment)
+{
+ RT_NOREF1(pszComment);
+ LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+static DECLCALLBACK(int) parallelsGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VERR_NOT_SUPPORTED, pUuid));
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+static DECLCALLBACK(int) parallelsSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+static DECLCALLBACK(int) parallelsGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VERR_NOT_SUPPORTED, pUuid));
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+static DECLCALLBACK(int) parallelsSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+static DECLCALLBACK(int) parallelsGetParentUuid(void *pBackendData, PRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VERR_NOT_SUPPORTED, pUuid));
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+static DECLCALLBACK(int) parallelsSetParentUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+static DECLCALLBACK(int) parallelsGetParentModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+static DECLCALLBACK(int) parallelsSetParentModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) parallelsDump(void *pBackendData)
+{
+ PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pImage);
+ vdIfErrorMessage(pImage->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u\n",
+ pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors,
+ pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors);
+}
+
+
+
+const VDIMAGEBACKEND g_ParallelsBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "Parallels",
+ /* uBackendCaps */
+ VD_CAP_FILE | VD_CAP_ASYNC | VD_CAP_VFS | VD_CAP_CREATE_DYNAMIC | VD_CAP_DIFF,
+ /* paFileExtensions */
+ s_aParallelsFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ parallelsProbe,
+ /* pfnOpen */
+ parallelsOpen,
+ /* pfnCreate */
+ parallelsCreate,
+ /* pfnRename */
+ parallelsRename,
+ /* pfnClose */
+ parallelsClose,
+ /* pfnRead */
+ parallelsRead,
+ /* pfnWrite */
+ parallelsWrite,
+ /* pfnFlush */
+ parallelsFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ parallelsGetVersion,
+ /* pfnGetFileSize */
+ parallelsGetFileSize,
+ /* pfnGetPCHSGeometry */
+ parallelsGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ parallelsSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ parallelsGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ parallelsSetLCHSGeometry,
+ /* pfnQueryRegions */
+ parallelsQueryRegions,
+ /* pfnRegionListRelease */
+ parallelsRegionListRelease,
+ /* pfnGetImageFlags */
+ parallelsGetImageFlags,
+ /* pfnGetOpenFlags */
+ parallelsGetOpenFlags,
+ /* pfnSetOpenFlags */
+ parallelsSetOpenFlags,
+ /* pfnGetComment */
+ parallelsGetComment,
+ /* pfnSetComment */
+ parallelsSetComment,
+ /* pfnGetUuid */
+ parallelsGetUuid,
+ /* pfnSetUuid */
+ parallelsSetUuid,
+ /* pfnGetModificationUuid */
+ parallelsGetModificationUuid,
+ /* pfnSetModificationUuid */
+ parallelsSetModificationUuid,
+ /* pfnGetParentUuid */
+ parallelsGetParentUuid,
+ /* pfnSetParentUuid */
+ parallelsSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ parallelsGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ parallelsSetParentModificationUuid,
+ /* pfnDump */
+ parallelsDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ NULL,
+ /* pfnSetParentFilename */
+ NULL,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ NULL,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
diff --git a/src/VBox/Storage/QCOW.cpp b/src/VBox/Storage/QCOW.cpp
new file mode 100644
index 00000000..b4239796
--- /dev/null
+++ b/src/VBox/Storage/QCOW.cpp
@@ -0,0 +1,2572 @@
+/* $Id: QCOW.cpp $ */
+/** @file
+ * QCOW - QCOW Disk image.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_QCOW
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/path.h>
+#include <iprt/list.h>
+#include <iprt/zip.h>
+
+#include "VDBackends.h"
+#include "VDBackendsInline.h"
+
+/** @page pg_storage_qcow QCOW Storage Backend
+ * The QCOW backend implements support for the qemu copy on write format (short QCOW).
+ *
+ * The official specification for qcow is available at
+ * https://github.com/qemu/qemu/blob/master/docs/interop/qcow2.txt version 2 and 3.
+ * For version 1 there is no official specification available but the format is described
+ * at http://people.gnome.org/~markmc/qcow-image-format-version-1.html.
+ *
+ * Missing things to implement:
+ * - v2 image creation and handling of the reference count table. (Blocker to enable support for V2 images)
+ * - cluster encryption
+ * - cluster compression
+ * - compaction
+ * - resizing
+ */
+
+
+/*********************************************************************************************************************************
+* Structures in a QCOW image, big endian *
+*********************************************************************************************************************************/
+
+#pragma pack(1) /* Completely unnecessary. */
+typedef struct QCowHeader
+{
+ /** Magic value. */
+ uint32_t u32Magic;
+ /** Version of the image. */
+ uint32_t u32Version;
+ /** Version dependent data. */
+ union
+ {
+ /** Version 1. */
+ struct
+ {
+ /** Backing file offset. */
+ uint64_t u64BackingFileOffset;
+ /** Size of the backing file. */
+ uint32_t u32BackingFileSize;
+ /** mtime (Modification time?) - can be ignored. */
+ uint32_t u32MTime;
+ /** Logical size of the image in bytes. */
+ uint64_t u64Size;
+ /** Number of bits in the virtual offset used as a cluster offset. */
+ uint8_t u8ClusterBits;
+ /** Number of bits in the virtual offset used for the L2 index. */
+ uint8_t u8L2Bits;
+ /** Padding because the header is not packed in the original source. */
+ uint16_t u16Padding;
+ /** Used cryptographic method. */
+ uint32_t u32CryptMethod;
+ /** Offset of the L1 table in the image in bytes. */
+ uint64_t u64L1TableOffset;
+ } v1;
+ /** Version 2 (and also containing extensions for version 3). */
+ struct
+ {
+ /** Backing file offset. */
+ uint64_t u64BackingFileOffset;
+ /** Size of the backing file. */
+ uint32_t u32BackingFileSize;
+ /** Number of bits in the virtual offset used as a cluster offset. */
+ uint32_t u32ClusterBits;
+ /** Logical size of the image. */
+ uint64_t u64Size;
+ /** Used cryptographic method. */
+ uint32_t u32CryptMethod;
+ /** Size of the L1 table in entries (each 8bytes big). */
+ uint32_t u32L1Size;
+ /** Offset of the L1 table in the image in bytes. */
+ uint64_t u64L1TableOffset;
+ /** Start of the refcount table in the image. */
+ uint64_t u64RefcountTableOffset;
+ /** Size of the refcount table in clusters. */
+ uint32_t u32RefcountTableClusters;
+ /** Number of snapshots in the image. */
+ uint32_t u32NbSnapshots;
+ /** Offset of the first snapshot header in the image. */
+ uint64_t u64SnapshotsOffset;
+ /** Version 3 additional data. */
+ struct
+ {
+ /** Incompatible features. */
+ uint64_t u64IncompatFeat;
+ /** Compatible features. */
+ uint64_t u64CompatFeat;
+ /** Autoclear features. */
+ uint64_t u64AutoClrFeat;
+ /** Width in bits of a reference count block. */
+ uint32_t u32RefCntWidth;
+ /** Lenght of the header structure in bytes (for the header extensions). */
+ uint32_t u32HdrLenBytes;
+ } v3;
+ } v2;
+ } Version;
+} QCowHeader;
+#pragma pack()
+/** Pointer to a on disk QCOW header. */
+typedef QCowHeader *PQCowHeader;
+
+/** QCOW magic value. */
+#define QCOW_MAGIC UINT32_C(0x514649fb) /* QFI\0xfb */
+/** Size of the V1 header. */
+#define QCOW_V1_HDR_SIZE (48)
+/** Size of the V2 header. */
+#define QCOW_V2_HDR_SIZE (72)
+
+/** Cluster is compressed flag for QCOW images. */
+#define QCOW_V1_COMPRESSED_FLAG RT_BIT_64(63)
+
+/** Copied flag for QCOW2 images. */
+#define QCOW_V2_COPIED_FLAG RT_BIT_64(63)
+/** Cluster is compressed flag for QCOW2 images. */
+#define QCOW_V2_COMPRESSED_FLAG RT_BIT_64(62)
+/** The mask for extracting the offset from either the L1 or L2 table. */
+#define QCOW_V2_TBL_OFFSET_MASK UINT64_C(0x00fffffffffffe00)
+
+/** Incompatible feature: Dirty bit, reference count may be inconsistent. */
+#define QCOW_V3_INCOMPAT_FEAT_F_DIRTY RT_BIT_64(0)
+/** Incompatible feature: Image is corrupt and needs repair. */
+#define QCOW_V3_INCOMPAT_FEAT_F_CORRUPT RT_BIT_64(1)
+/** Incompatible feature: External data file. */
+#define QCOW_V3_INCOMPAT_FEAT_F_EXTERNAL_DATA RT_BIT_64(2)
+/** The incompatible features we support currently. */
+#define QCOW_V3_INCOMPAT_FEAT_SUPPORTED_MASK UINT64_C(0x0)
+
+/** Compatible feature: Lazy reference counters. */
+#define QCOW_V3_COMPAT_FEAT_F_LAZY_REF_COUNT RT_BIT_64(0)
+/** The compatible features we support currently. */
+#define QCOW_V3_COMPAT_FEAT_SUPPORTED_MASK UINT64_C(0x0)
+
+/** Auto clear feature: Bitmaps extension. */
+#define QCOW_V3_AUTOCLR_FEAT_F_BITMAPS RT_BIT_64(0)
+/** Auto clear feature: The external data file is raw image which can be accessed standalone. */
+#define QCOW_V3_AUTOCLR_FEAT_F_EXT_RAW_DATA RT_BIT_64(1)
+/** The autoclear features we support currently. */
+#define QCOW_V3_AUTOCLR_FEAT_SUPPORTED_MASK UINT64_C(0x0)
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * QCOW L2 cache entry.
+ */
+typedef struct QCOWL2CACHEENTRY
+{
+ /** List node for the search list. */
+ RTLISTNODE NodeSearch;
+ /** List node for the LRU list. */
+ RTLISTNODE NodeLru;
+ /** Reference counter. */
+ uint32_t cRefs;
+ /** The offset of the L2 table, used as search key. */
+ uint64_t offL2Tbl;
+ /** Pointer to the cached L2 table. */
+ uint64_t *paL2Tbl;
+} QCOWL2CACHEENTRY, *PQCOWL2CACHEENTRY;
+
+/** Maximum amount of memory the cache is allowed to use. */
+#define QCOW_L2_CACHE_MEMORY_MAX (2*_1M)
+
+/** QCOW default cluster size for image version 2. */
+#define QCOW2_CLUSTER_SIZE_DEFAULT (64*_1K)
+/** QCOW default cluster size for image version 1. */
+#define QCOW_CLUSTER_SIZE_DEFAULT (4*_1K)
+/** QCOW default L2 table size in clusters. */
+#define QCOW_L2_CLUSTERS_DEFAULT (1)
+
+/**
+ * QCOW image data structure.
+ */
+typedef struct QCOWIMAGE
+{
+ /** Image name. */
+ const char *pszFilename;
+ /** Storage handle. */
+ PVDIOSTORAGE pStorage;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+
+ /** Open flags passed by VBoxHD layer. */
+ unsigned uOpenFlags;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+ /** Physical geometry of this image. */
+ VDGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ VDGEOMETRY LCHSGeometry;
+
+ /** Image version. */
+ unsigned uVersion;
+ /** MTime field - used only to preserve value in opened images, unmodified otherwise. */
+ uint32_t MTime;
+
+ /** Filename of the backing file if any. */
+ char *pszBackingFilename;
+ /** Offset of the filename in the image. */
+ uint64_t offBackingFilename;
+ /** Size of the backing filename excluding \0. */
+ uint32_t cbBackingFilename;
+
+ /** Next offset of a new cluster, aligned to sector size. */
+ uint64_t offNextCluster;
+ /** Cluster size in bytes. */
+ uint32_t cbCluster;
+ /** Number of bits in the virtual offset used as the cluster offset. */
+ uint32_t cClusterBits;
+ /** Bitmask to extract the offset from a compressed cluster descriptor. */
+ uint64_t fMaskCompressedClusterOffset;
+ /** Bitmask to extract the sector count from a compressed cluster descriptor. */
+ uint64_t fMaskCompressedClusterSectors;
+ /** Number of bits to shift the sector count to the right to get the final value. */
+ uint32_t cBitsShiftRCompressedClusterSectors;
+ /** Number of entries in the L1 table. */
+ uint32_t cL1TableEntries;
+ /** Size of an L1 rounded to the next cluster size. */
+ uint32_t cbL1Table;
+ /** Pointer to the L1 table. */
+ uint64_t *paL1Table;
+ /** Offset of the L1 table. */
+ uint64_t offL1Table;
+
+ /** Size of the L2 table in bytes. */
+ uint32_t cbL2Table;
+ /** Number of entries in the L2 table. */
+ uint32_t cL2TableEntries;
+ /** Memory occupied by the L2 table cache. */
+ size_t cbL2Cache;
+ /** The sorted L2 entry list used for searching. */
+ RTLISTNODE ListSearch;
+ /** The LRU L2 entry list used for eviction. */
+ RTLISTNODE ListLru;
+
+ /** Offset of the refcount table. */
+ uint64_t offRefcountTable;
+ /** Size of the refcount table in bytes. */
+ uint32_t cbRefcountTable;
+ /** Number of entries in the refcount table. */
+ uint32_t cRefcountTableEntries;
+ /** Pointer to the refcount table. */
+ uint64_t *paRefcountTable;
+
+ /** Offset mask for a cluster. */
+ uint64_t fOffsetMask;
+ /** Number of bits to shift to get the L1 index. */
+ uint32_t cL1Shift;
+ /** L2 table mask to get the L2 index. */
+ uint64_t fL2Mask;
+ /** Number of bits to shift to get the L2 index. */
+ uint32_t cL2Shift;
+
+ /** Size of compressed cluster buffer. */
+ size_t cbCompCluster;
+ /** Compressed cluster buffer. */
+ void *pvCompCluster;
+ /** Buffer to hold the uncompressed data. */
+ void *pvCluster;
+
+ /** Pointer to the L2 table we are currently allocating
+ * (can be only one at a time). */
+ PQCOWL2CACHEENTRY pL2TblAlloc;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} QCOWIMAGE, *PQCOWIMAGE;
+
+/**
+ * State of the async cluster allocation.
+ */
+typedef enum QCOWCLUSTERASYNCALLOCSTATE
+{
+ /** Invalid. */
+ QCOWCLUSTERASYNCALLOCSTATE_INVALID = 0,
+ /** L2 table allocation. */
+ QCOWCLUSTERASYNCALLOCSTATE_L2_ALLOC,
+ /** Link L2 table into L1. */
+ QCOWCLUSTERASYNCALLOCSTATE_L2_LINK,
+ /** Allocate user data cluster. */
+ QCOWCLUSTERASYNCALLOCSTATE_USER_ALLOC,
+ /** Link user data cluster. */
+ QCOWCLUSTERASYNCALLOCSTATE_USER_LINK,
+ /** 32bit blowup. */
+ QCOWCLUSTERASYNCALLOCSTATE_32BIT_HACK = 0x7fffffff
+} QCOWCLUSTERASYNCALLOCSTATE, *PQCOWCLUSTERASYNCALLOCSTATE;
+
+/**
+ * Data needed to track async cluster allocation.
+ */
+typedef struct QCOWCLUSTERASYNCALLOC
+{
+ /** The state of the cluster allocation. */
+ QCOWCLUSTERASYNCALLOCSTATE enmAllocState;
+ /** Old image size to rollback in case of an error. */
+ uint64_t offNextClusterOld;
+ /** L1 index to link if any. */
+ uint32_t idxL1;
+ /** L2 index to link, required in any case. */
+ uint32_t idxL2;
+ /** Start offset of the allocated cluster. */
+ uint64_t offClusterNew;
+ /** L2 cache entry if a L2 table is allocated. */
+ PQCOWL2CACHEENTRY pL2Entry;
+ /** Number of bytes to write. */
+ size_t cbToWrite;
+} QCOWCLUSTERASYNCALLOC, *PQCOWCLUSTERASYNCALLOC;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aQCowFileExtensions[] =
+{
+ {"qcow", VDTYPE_HDD},
+ {"qcow2", VDTYPE_HDD},
+ {NULL, VDTYPE_INVALID}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Return power of 2 or 0 if num error.
+ *
+ * @returns The power of 2 or 0 if the given number is not a power of 2.
+ * @param u32 The number.
+ */
+static uint32_t qcowGetPowerOfTwo(uint32_t u32)
+{
+ if (u32 == 0)
+ return 0;
+ uint32_t uPower2 = 0;
+ while ((u32 & 1) == 0)
+ {
+ u32 >>= 1;
+ uPower2++;
+ }
+ return u32 == 1 ? uPower2 : 0;
+}
+
+
+/**
+ * Converts the image header to the host endianess and performs basic checks.
+ *
+ * @returns Whether the given header is valid or not.
+ * @param pHeader Pointer to the header to convert.
+ */
+static bool qcowHdrConvertToHostEndianess(PQCowHeader pHeader)
+{
+ pHeader->u32Magic = RT_BE2H_U32(pHeader->u32Magic);
+ pHeader->u32Version = RT_BE2H_U32(pHeader->u32Version);
+
+ if (pHeader->u32Magic != QCOW_MAGIC)
+ return false;
+
+ if (pHeader->u32Version == 1)
+ {
+ pHeader->Version.v1.u64BackingFileOffset = RT_BE2H_U64(pHeader->Version.v1.u64BackingFileOffset);
+ pHeader->Version.v1.u32BackingFileSize = RT_BE2H_U32(pHeader->Version.v1.u32BackingFileSize);
+ pHeader->Version.v1.u32MTime = RT_BE2H_U32(pHeader->Version.v1.u32MTime);
+ pHeader->Version.v1.u64Size = RT_BE2H_U64(pHeader->Version.v1.u64Size);
+ pHeader->Version.v1.u32CryptMethod = RT_BE2H_U32(pHeader->Version.v1.u32CryptMethod);
+ pHeader->Version.v1.u64L1TableOffset = RT_BE2H_U64(pHeader->Version.v1.u64L1TableOffset);
+ }
+ else if (pHeader->u32Version == 2 || pHeader->u32Version == 3)
+ {
+ pHeader->Version.v2.u64BackingFileOffset = RT_BE2H_U64(pHeader->Version.v2.u64BackingFileOffset);
+ pHeader->Version.v2.u32BackingFileSize = RT_BE2H_U32(pHeader->Version.v2.u32BackingFileSize);
+ pHeader->Version.v2.u32ClusterBits = RT_BE2H_U32(pHeader->Version.v2.u32ClusterBits);
+ pHeader->Version.v2.u64Size = RT_BE2H_U64(pHeader->Version.v2.u64Size);
+ pHeader->Version.v2.u32CryptMethod = RT_BE2H_U32(pHeader->Version.v2.u32CryptMethod);
+ pHeader->Version.v2.u32L1Size = RT_BE2H_U32(pHeader->Version.v2.u32L1Size);
+ pHeader->Version.v2.u64L1TableOffset = RT_BE2H_U64(pHeader->Version.v2.u64L1TableOffset);
+ pHeader->Version.v2.u64RefcountTableOffset = RT_BE2H_U64(pHeader->Version.v2.u64RefcountTableOffset);
+ pHeader->Version.v2.u32RefcountTableClusters = RT_BE2H_U32(pHeader->Version.v2.u32RefcountTableClusters);
+ pHeader->Version.v2.u32NbSnapshots = RT_BE2H_U32(pHeader->Version.v2.u32NbSnapshots);
+ pHeader->Version.v2.u64SnapshotsOffset = RT_BE2H_U64(pHeader->Version.v2.u64SnapshotsOffset);
+
+ if (pHeader->u32Version == 3)
+ {
+ pHeader->Version.v2.v3.u64IncompatFeat = RT_BE2H_U64(pHeader->Version.v2.v3.u64IncompatFeat);
+ pHeader->Version.v2.v3.u64CompatFeat = RT_BE2H_U64(pHeader->Version.v2.v3.u64CompatFeat);
+ pHeader->Version.v2.v3.u64AutoClrFeat = RT_BE2H_U64(pHeader->Version.v2.v3.u64AutoClrFeat);
+ pHeader->Version.v2.v3.u32RefCntWidth = RT_BE2H_U32(pHeader->Version.v2.v3.u32RefCntWidth);
+ pHeader->Version.v2.v3.u32HdrLenBytes = RT_BE2H_U32(pHeader->Version.v2.v3.u32HdrLenBytes);
+ }
+ }
+ else
+ return false;
+
+ return true;
+}
+
+/**
+ * Creates a QCOW header from the given image state.
+ *
+ * @returns nothing.
+ * @param pImage Image instance data.
+ * @param pHeader Pointer to the header to convert.
+ * @param pcbHeader Where to store the size of the header to write.
+ */
+static void qcowHdrConvertFromHostEndianess(PQCOWIMAGE pImage, PQCowHeader pHeader,
+ size_t *pcbHeader)
+{
+ memset(pHeader, 0, sizeof(QCowHeader));
+
+ pHeader->u32Magic = RT_H2BE_U32(QCOW_MAGIC);
+ pHeader->u32Version = RT_H2BE_U32(pImage->uVersion);
+ if (pImage->uVersion == 1)
+ {
+ pHeader->Version.v1.u64BackingFileOffset = RT_H2BE_U64(pImage->offBackingFilename);
+ pHeader->Version.v1.u32BackingFileSize = RT_H2BE_U32(pImage->cbBackingFilename);
+ pHeader->Version.v1.u32MTime = RT_H2BE_U32(pImage->MTime);
+ pHeader->Version.v1.u64Size = RT_H2BE_U64(pImage->cbSize);
+ pHeader->Version.v1.u8ClusterBits = (uint8_t)qcowGetPowerOfTwo(pImage->cbCluster);
+ pHeader->Version.v1.u8L2Bits = (uint8_t)qcowGetPowerOfTwo(pImage->cL2TableEntries);
+ pHeader->Version.v1.u32CryptMethod = RT_H2BE_U32(0);
+ pHeader->Version.v1.u64L1TableOffset = RT_H2BE_U64(pImage->offL1Table);
+ *pcbHeader = QCOW_V1_HDR_SIZE;
+ }
+ else if (pImage->uVersion == 2)
+ {
+ pHeader->Version.v2.u64BackingFileOffset = RT_H2BE_U64(pImage->offBackingFilename);
+ pHeader->Version.v2.u32BackingFileSize = RT_H2BE_U32(pImage->cbBackingFilename);
+ pHeader->Version.v2.u32ClusterBits = RT_H2BE_U32(qcowGetPowerOfTwo(pImage->cbCluster));
+ pHeader->Version.v2.u64Size = RT_H2BE_U64(pImage->cbSize);
+ pHeader->Version.v2.u32CryptMethod = RT_H2BE_U32(0);
+ pHeader->Version.v2.u32L1Size = RT_H2BE_U32(pImage->cL1TableEntries);
+ pHeader->Version.v2.u64L1TableOffset = RT_H2BE_U64(pImage->offL1Table);
+ pHeader->Version.v2.u64RefcountTableOffset = RT_H2BE_U64(pImage->offRefcountTable);
+ pHeader->Version.v2.u32RefcountTableClusters = RT_H2BE_U32(pImage->cbRefcountTable / pImage->cbCluster);
+ pHeader->Version.v2.u32NbSnapshots = RT_H2BE_U32(0);
+ pHeader->Version.v2.u64SnapshotsOffset = RT_H2BE_U64((uint64_t)0);
+ *pcbHeader = QCOW_V2_HDR_SIZE;
+ }
+ else
+ AssertMsgFailed(("Invalid version of the QCOW image format %d\n", pImage->uVersion));
+}
+
+/**
+ * Convert table entries from little endian to host endianess.
+ *
+ * @returns nothing.
+ * @param paTbl Pointer to the table.
+ * @param cEntries Number of entries in the table.
+ */
+static void qcowTableConvertToHostEndianess(uint64_t *paTbl, uint32_t cEntries)
+{
+ while(cEntries-- > 0)
+ {
+ *paTbl = RT_BE2H_U64(*paTbl);
+ paTbl++;
+ }
+}
+
+/**
+ * Convert table entries from host to little endian format.
+ *
+ * @returns nothing.
+ * @param paTblImg Pointer to the table which will store the little endian table.
+ * @param paTbl The source table to convert.
+ * @param cEntries Number of entries in the table.
+ */
+static void qcowTableConvertFromHostEndianess(uint64_t *paTblImg, uint64_t *paTbl,
+ uint32_t cEntries)
+{
+ while(cEntries-- > 0)
+ {
+ *paTblImg = RT_H2BE_U64(*paTbl);
+ paTbl++;
+ paTblImg++;
+ }
+}
+
+/**
+ * Creates the L2 table cache.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ */
+static int qcowL2TblCacheCreate(PQCOWIMAGE pImage)
+{
+ pImage->cbL2Cache = 0;
+ RTListInit(&pImage->ListSearch);
+ RTListInit(&pImage->ListLru);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys the L2 table cache.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ */
+static void qcowL2TblCacheDestroy(PQCOWIMAGE pImage)
+{
+ PQCOWL2CACHEENTRY pL2Entry;
+ PQCOWL2CACHEENTRY pL2Next;
+ RTListForEachSafe(&pImage->ListSearch, pL2Entry, pL2Next, QCOWL2CACHEENTRY, NodeSearch)
+ {
+ Assert(!pL2Entry->cRefs);
+
+ RTListNodeRemove(&pL2Entry->NodeSearch);
+ RTMemPageFree(pL2Entry->paL2Tbl, pImage->cbL2Table);
+ RTMemFree(pL2Entry);
+ }
+
+ pImage->cbL2Cache = 0;
+ RTListInit(&pImage->ListSearch);
+ RTListInit(&pImage->ListLru);
+}
+
+/**
+ * Returns the L2 table matching the given offset or NULL if none could be found.
+ *
+ * @returns Pointer to the L2 table cache entry or NULL.
+ * @param pImage The image instance data.
+ * @param offL2Tbl Offset of the L2 table to search for.
+ */
+static PQCOWL2CACHEENTRY qcowL2TblCacheRetain(PQCOWIMAGE pImage, uint64_t offL2Tbl)
+{
+ if ( pImage->pL2TblAlloc
+ && pImage->pL2TblAlloc->offL2Tbl == offL2Tbl)
+ {
+ pImage->pL2TblAlloc->cRefs++;
+ return pImage->pL2TblAlloc;
+ }
+
+ PQCOWL2CACHEENTRY pL2Entry;
+ RTListForEach(&pImage->ListSearch, pL2Entry, QCOWL2CACHEENTRY, NodeSearch)
+ {
+ if (pL2Entry->offL2Tbl == offL2Tbl)
+ break;
+ }
+
+ if (!RTListNodeIsDummy(&pImage->ListSearch, pL2Entry, QCOWL2CACHEENTRY, NodeSearch))
+ {
+ /* Update LRU list. */
+ RTListNodeRemove(&pL2Entry->NodeLru);
+ RTListPrepend(&pImage->ListLru, &pL2Entry->NodeLru);
+ pL2Entry->cRefs++;
+ return pL2Entry;
+ }
+
+ return NULL;
+}
+
+/**
+ * Releases a L2 table cache entry.
+ *
+ * @returns nothing.
+ * @param pL2Entry The L2 cache entry.
+ */
+static void qcowL2TblCacheEntryRelease(PQCOWL2CACHEENTRY pL2Entry)
+{
+ Assert(pL2Entry->cRefs > 0);
+ pL2Entry->cRefs--;
+}
+
+/**
+ * Allocates a new L2 table from the cache evicting old entries if required.
+ *
+ * @returns Pointer to the L2 cache entry or NULL.
+ * @param pImage The image instance data.
+ */
+static PQCOWL2CACHEENTRY qcowL2TblCacheEntryAlloc(PQCOWIMAGE pImage)
+{
+ PQCOWL2CACHEENTRY pL2Entry = NULL;
+
+ if (pImage->cbL2Cache + pImage->cbL2Table <= QCOW_L2_CACHE_MEMORY_MAX)
+ {
+ /* Add a new entry. */
+ pL2Entry = (PQCOWL2CACHEENTRY)RTMemAllocZ(sizeof(QCOWL2CACHEENTRY));
+ if (pL2Entry)
+ {
+ pL2Entry->paL2Tbl = (uint64_t *)RTMemPageAllocZ(pImage->cbL2Table);
+ if (RT_UNLIKELY(!pL2Entry->paL2Tbl))
+ {
+ RTMemFree(pL2Entry);
+ pL2Entry = NULL;
+ }
+ else
+ {
+ pL2Entry->cRefs = 1;
+ pImage->cbL2Cache += pImage->cbL2Table;
+ }
+ }
+ }
+ else
+ {
+ /* Evict the last not in use entry and use it */
+ Assert(!RTListIsEmpty(&pImage->ListLru));
+
+ RTListForEachReverse(&pImage->ListLru, pL2Entry, QCOWL2CACHEENTRY, NodeLru)
+ {
+ if (!pL2Entry->cRefs)
+ break;
+ }
+
+ if (!RTListNodeIsDummy(&pImage->ListSearch, pL2Entry, QCOWL2CACHEENTRY, NodeSearch))
+ {
+ RTListNodeRemove(&pL2Entry->NodeSearch);
+ RTListNodeRemove(&pL2Entry->NodeLru);
+ pL2Entry->offL2Tbl = 0;
+ pL2Entry->cRefs = 1;
+ }
+ else
+ pL2Entry = NULL;
+ }
+
+ return pL2Entry;
+}
+
+/**
+ * Frees a L2 table cache entry.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ * @param pL2Entry The L2 cache entry to free.
+ */
+static void qcowL2TblCacheEntryFree(PQCOWIMAGE pImage, PQCOWL2CACHEENTRY pL2Entry)
+{
+ Assert(!pL2Entry->cRefs);
+ RTMemPageFree(pL2Entry->paL2Tbl, pImage->cbL2Table);
+ RTMemFree(pL2Entry);
+
+ pImage->cbL2Cache -= pImage->cbL2Table;
+}
+
+/**
+ * Inserts an entry in the L2 table cache.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ * @param pL2Entry The L2 cache entry to insert.
+ */
+static void qcowL2TblCacheEntryInsert(PQCOWIMAGE pImage, PQCOWL2CACHEENTRY pL2Entry)
+{
+ Assert(pL2Entry->offL2Tbl > 0);
+
+ /* Insert at the top of the LRU list. */
+ RTListPrepend(&pImage->ListLru, &pL2Entry->NodeLru);
+
+ if (RTListIsEmpty(&pImage->ListSearch))
+ {
+ RTListAppend(&pImage->ListSearch, &pL2Entry->NodeSearch);
+ }
+ else
+ {
+ /* Insert into search list. */
+ PQCOWL2CACHEENTRY pIt;
+ pIt = RTListGetFirst(&pImage->ListSearch, QCOWL2CACHEENTRY, NodeSearch);
+ if (pIt->offL2Tbl > pL2Entry->offL2Tbl)
+ RTListPrepend(&pImage->ListSearch, &pL2Entry->NodeSearch);
+ else
+ {
+ bool fInserted = false;
+
+ RTListForEach(&pImage->ListSearch, pIt, QCOWL2CACHEENTRY, NodeSearch)
+ {
+ Assert(pIt->offL2Tbl != pL2Entry->offL2Tbl);
+ if (pIt->offL2Tbl < pL2Entry->offL2Tbl)
+ {
+ RTListNodeInsertAfter(&pIt->NodeSearch, &pL2Entry->NodeSearch);
+ fInserted = true;
+ break;
+ }
+ }
+ Assert(fInserted);
+ }
+ }
+}
+
+/**
+ * Fetches the L2 from the given offset trying the LRU cache first and
+ * reading it from the image after a cache miss.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param pIoCtx The I/O context.
+ * @param offL2Tbl The offset of the L2 table in the image.
+ * @param ppL2Entry Where to store the L2 table on success.
+ */
+static int qcowL2TblCacheFetch(PQCOWIMAGE pImage, PVDIOCTX pIoCtx, uint64_t offL2Tbl,
+ PQCOWL2CACHEENTRY *ppL2Entry)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the L2 table from the cache first. */
+ PQCOWL2CACHEENTRY pL2Entry = qcowL2TblCacheRetain(pImage, offL2Tbl);
+ if (!pL2Entry)
+ {
+ pL2Entry = qcowL2TblCacheEntryAlloc(pImage);
+
+ if (pL2Entry)
+ {
+ /* Read from the image. */
+ PVDMETAXFER pMetaXfer;
+
+ pL2Entry->offL2Tbl = offL2Tbl;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage,
+ offL2Tbl, pL2Entry->paL2Tbl,
+ pImage->cbL2Table, pIoCtx,
+ &pMetaXfer, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+#if defined(RT_LITTLE_ENDIAN)
+ qcowTableConvertToHostEndianess(pL2Entry->paL2Tbl, pImage->cL2TableEntries);
+#endif
+ qcowL2TblCacheEntryInsert(pImage, pL2Entry);
+ }
+ else
+ {
+ qcowL2TblCacheEntryRelease(pL2Entry);
+ qcowL2TblCacheEntryFree(pImage, pL2Entry);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppL2Entry = pL2Entry;
+
+ return rc;
+}
+
+/**
+ * Sets the L1, L2 and offset bitmasks and L1 and L2 bit shift members.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ */
+static void qcowTableMasksInit(PQCOWIMAGE pImage)
+{
+ uint32_t cClusterBits, cL2TableBits;
+
+ cClusterBits = qcowGetPowerOfTwo(pImage->cbCluster);
+ cL2TableBits = qcowGetPowerOfTwo(pImage->cL2TableEntries);
+
+ Assert(cClusterBits + cL2TableBits < 64);
+
+ pImage->fOffsetMask = ((uint64_t)pImage->cbCluster - 1);
+ pImage->fL2Mask = ((uint64_t)pImage->cL2TableEntries - 1) << cClusterBits;
+ pImage->cL2Shift = cClusterBits;
+ pImage->cL1Shift = cClusterBits + cL2TableBits;
+}
+
+/**
+ * Converts a given logical offset into the
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ * @param off The logical offset to convert.
+ * @param pidxL1 Where to store the index in the L1 table on success.
+ * @param pidxL2 Where to store the index in the L2 table on success.
+ * @param poffCluster Where to store the offset in the cluster on success.
+ */
+DECLINLINE(void) qcowConvertLogicalOffset(PQCOWIMAGE pImage, uint64_t off, uint32_t *pidxL1,
+ uint32_t *pidxL2, uint32_t *poffCluster)
+{
+ AssertPtr(pidxL1);
+ AssertPtr(pidxL2);
+ AssertPtr(poffCluster);
+
+ *poffCluster = off & pImage->fOffsetMask;
+ *pidxL1 = off >> pImage->cL1Shift;
+ *pidxL2 = (off & pImage->fL2Mask) >> pImage->cL2Shift;
+}
+
+/**
+ * Converts Cluster size to a byte size.
+ *
+ * @returns Number of bytes derived from the given number of clusters.
+ * @param pImage The image instance data.
+ * @param cClusters The clusters to convert.
+ */
+DECLINLINE(uint64_t) qcowCluster2Byte(PQCOWIMAGE pImage, uint64_t cClusters)
+{
+ return cClusters * pImage->cbCluster;
+}
+
+/**
+ * Converts number of bytes to cluster size rounding to the next cluster.
+ *
+ * @returns Number of bytes derived from the given number of clusters.
+ * @param pImage The image instance data.
+ * @param cb Number of bytes to convert.
+ */
+DECLINLINE(uint64_t) qcowByte2Cluster(PQCOWIMAGE pImage, uint64_t cb)
+{
+ return cb / pImage->cbCluster + (cb % pImage->cbCluster ? 1 : 0);
+}
+
+/**
+ * Allocates a new cluster in the image.
+ *
+ * @returns The start offset of the new cluster in the image.
+ * @param pImage The image instance data.
+ * @param cClusters Number of clusters to allocate.
+ */
+DECLINLINE(uint64_t) qcowClusterAllocate(PQCOWIMAGE pImage, uint32_t cClusters)
+{
+ uint64_t offCluster;
+
+ offCluster = pImage->offNextCluster;
+ pImage->offNextCluster += cClusters*pImage->cbCluster;
+
+ return offCluster;
+}
+
+/**
+ * Returns the real image offset for a given cluster or an error if the cluster is not
+ * yet allocated.
+ *
+ * @returns VBox status code.
+ * VERR_VD_BLOCK_FREE if the cluster is not yet allocated.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param idxL1 The L1 index.
+ * @param idxL2 The L2 index.
+ * @param offCluster Offset inside the cluster.
+ * @param poffImage Where to store the image offset on success.
+ * @param pfCompressed Where to store the flag whether the cluster is compressed on success.
+ * @param pcbCompressed Where to store the size of the compressed cluster in bytes on success.
+ * Only valid when the cluster comrpessed flag is true.
+ */
+static int qcowConvertToImageOffset(PQCOWIMAGE pImage, PVDIOCTX pIoCtx,
+ uint32_t idxL1, uint32_t idxL2,
+ uint32_t offCluster, uint64_t *poffImage,
+ bool *pfCompressed, size_t *pcbCompressed)
+{
+ int rc = VERR_VD_BLOCK_FREE;
+
+ AssertReturn(idxL1 < pImage->cL1TableEntries, VERR_INVALID_PARAMETER);
+ AssertReturn(idxL2 < pImage->cL2TableEntries, VERR_INVALID_PARAMETER);
+
+ if (pImage->paL1Table[idxL1])
+ {
+ PQCOWL2CACHEENTRY pL2Entry;
+
+ uint64_t offL2Tbl = pImage->paL1Table[idxL1];
+ if (pImage->uVersion == 2)
+ offL2Tbl &= QCOW_V2_TBL_OFFSET_MASK;
+ rc = qcowL2TblCacheFetch(pImage, pIoCtx, offL2Tbl, &pL2Entry);
+ if (RT_SUCCESS(rc))
+ {
+ /* Get real file offset. */
+ if (pL2Entry->paL2Tbl[idxL2])
+ {
+ uint64_t off = pL2Entry->paL2Tbl[idxL2];
+
+ /* Strip flags */
+ if (pImage->uVersion == 2)
+ {
+ if (RT_UNLIKELY(off & QCOW_V2_COMPRESSED_FLAG))
+ {
+ size_t cCompressedClusterSectors = ((off & pImage->fMaskCompressedClusterSectors) >> pImage->cBitsShiftRCompressedClusterSectors);
+ uint64_t offImage = off & pImage->fMaskCompressedClusterOffset;
+
+ *pfCompressed = true;
+ *poffImage = offImage;
+ *pcbCompressed = (cCompressedClusterSectors + 1) * 512 - (offImage & 511ULL);
+ }
+ else
+ {
+ off &= QCOW_V2_TBL_OFFSET_MASK;
+
+ *pfCompressed = false;
+ *poffImage = off + offCluster;
+ }
+ }
+ else
+ {
+ if (RT_UNLIKELY(off & QCOW_V1_COMPRESSED_FLAG))
+ {
+ size_t cCompressedClusterSectors = (off & pImage->fMaskCompressedClusterSectors) >> pImage->cBitsShiftRCompressedClusterSectors;
+
+ *pfCompressed = true;
+ *poffImage = off & pImage->fMaskCompressedClusterOffset;
+ *pcbCompressed = cCompressedClusterSectors * 512; /* Only additional sectors */
+ /* Add remaining bytes of the sector the offset starts in. */
+ *pcbCompressed += 512 - RT_ALIGN_64(*poffImage, 512) - *poffImage;
+ }
+ else
+ {
+ off &= ~QCOW_V1_COMPRESSED_FLAG;
+
+ *pfCompressed = false;
+ *poffImage = off + offCluster;
+ }
+ }
+ }
+ else
+ rc = VERR_VD_BLOCK_FREE;
+
+ qcowL2TblCacheEntryRelease(pL2Entry);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Write the given table to image converting to the image endianess if required.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param offTbl The offset the table should be written to.
+ * @param paTbl The table to write.
+ * @param cbTbl Size of the table in bytes.
+ * @param cTblEntries Number entries in the table.
+ * @param pfnComplete Callback called when the write completes.
+ * @param pvUser Opaque user data to pass in the completion callback.
+ */
+static int qcowTblWrite(PQCOWIMAGE pImage, PVDIOCTX pIoCtx, uint64_t offTbl, uint64_t *paTbl,
+ size_t cbTbl, unsigned cTblEntries,
+ PFNVDXFERCOMPLETED pfnComplete, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+
+#if defined(RT_LITTLE_ENDIAN)
+ uint64_t *paTblImg = (uint64_t *)RTMemAllocZ(cbTbl);
+ if (paTblImg)
+ {
+ qcowTableConvertFromHostEndianess(paTblImg, paTbl, cTblEntries);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ offTbl, paTblImg, cbTbl,
+ pIoCtx, pfnComplete, pvUser);
+ RTMemFree(paTblImg);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+#else
+ /* Write table directly. */
+ RT_NOREF(cTblEntries);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ offTbl, paTbl, cbTbl, pIoCtx,
+ pfnComplete, pvUser);
+#endif
+
+ return rc;
+}
+
+/**
+ * Internal. Flush image data to disk.
+ */
+static int qcowFlushImage(PQCOWIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( pImage->pStorage
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && pImage->cbL1Table)
+ {
+ QCowHeader Header;
+
+#if defined(RT_LITTLE_ENDIAN)
+ uint64_t *paL1TblImg = (uint64_t *)RTMemAllocZ(pImage->cbL1Table);
+ if (paL1TblImg)
+ {
+ qcowTableConvertFromHostEndianess(paL1TblImg, pImage->paL1Table,
+ pImage->cL1TableEntries);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offL1Table, paL1TblImg,
+ pImage->cbL1Table);
+ RTMemFree(paL1TblImg);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+#else
+ /* Write L1 table directly. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, pImage->offL1Table,
+ pImage->paL1Table, pImage->cbL1Table);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /* Write header. */
+ size_t cbHeader = 0;
+ qcowHdrConvertFromHostEndianess(pImage, &Header, &cbHeader);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, 0, &Header,
+ cbHeader);
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileFlushSync(pImage->pIfIo, pImage->pStorage);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image except pImage,
+ * and optionally delete the image from disk.
+ */
+static int qcowFreeImage(PQCOWIMAGE pImage, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ if (pImage->pStorage)
+ {
+ /* No point updating the file that is deleted anyway. */
+ if (!fDelete)
+ qcowFlushImage(pImage);
+
+ rc = vdIfIoIntFileClose(pImage->pIfIo, pImage->pStorage);
+ pImage->pStorage = NULL;
+ }
+
+ if (pImage->paRefcountTable)
+ RTMemFree(pImage->paRefcountTable);
+ pImage->paRefcountTable = NULL;
+
+ if (pImage->paL1Table)
+ RTMemFree(pImage->paL1Table);
+
+ if (pImage->pszBackingFilename)
+ {
+ RTStrFree(pImage->pszBackingFilename);
+ pImage->pszBackingFilename = NULL;
+ }
+
+ if (pImage->pvCompCluster)
+ {
+ RTMemFree(pImage->pvCompCluster);
+ pImage->pvCompCluster = NULL;
+ pImage->cbCompCluster = 0;
+ }
+
+ if (pImage->pvCluster)
+ {
+ RTMemFree(pImage->pvCluster);
+ pImage->pvCluster = NULL;
+ }
+
+ qcowL2TblCacheDestroy(pImage);
+
+ if (fDelete && pImage->pszFilename)
+ vdIfIoIntFileDelete(pImage->pIfIo, pImage->pszFilename);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Validates the header.
+ *
+ * @returns VBox status code.
+ * @param pImage Image backend instance data.
+ * @param pHdr The header to validate.
+ * @param cbFile The image file size in bytes.
+ */
+static int qcowHdrValidate(PQCOWIMAGE pImage, PQCowHeader pHdr, uint64_t cbFile)
+{
+ if (pHdr->u32Version == 1)
+ {
+ /* Check that the backing filename is contained in the file. */
+ if (pHdr->Version.v1.u64BackingFileOffset + pHdr->Version.v1.u32BackingFileSize > cbFile)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Backing file offset and size exceed size of image '%s' (%u vs %u)"),
+ pImage->pszFilename, pHdr->Version.v1.u64BackingFileOffset + pHdr->Version.v1.u32BackingFileSize,
+ cbFile);
+
+ /* Check that the cluster bits indicate at least a 512byte sector size. */
+ if (RT_BIT_32(pHdr->Version.v1.u8ClusterBits) < 512)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Cluster size is too small for image '%s' (%u vs %u)"),
+ pImage->pszFilename, RT_BIT_32(pHdr->Version.v1.u8ClusterBits), 512);
+
+ /*
+ * Check for possible overflow when multiplying cluster size and L2 entry count because it is used
+ * to calculate the number of L1 table entries later on.
+ */
+ if (RT_BIT_32(pHdr->Version.v1.u8L2Bits) * RT_BIT_32(pHdr->Version.v1.u8ClusterBits) == 0)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Overflow during L1 table size calculation for image '%s'"),
+ pImage->pszFilename);
+ }
+ else if (pHdr->u32Version == 2 || pHdr->u32Version == 3)
+ {
+ /* Check that the backing filename is contained in the file. */
+ if (pHdr->Version.v2.u64BackingFileOffset + pHdr->Version.v2.u32BackingFileSize > cbFile)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Backing file offset and size exceed size of image '%s' (%u vs %u)"),
+ pImage->pszFilename, pHdr->Version.v2.u64BackingFileOffset + pHdr->Version.v2.u32BackingFileSize,
+ cbFile);
+
+ /* Check that the cluster bits indicate at least a 512byte sector size. */
+ if (RT_BIT_32(pHdr->Version.v2.u32ClusterBits) < 512)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Cluster size is too small for image '%s' (%u vs %u)"),
+ pImage->pszFilename, RT_BIT_32(pHdr->Version.v2.u32ClusterBits), 512);
+
+ /* Some additional checks for v3 images. */
+ if (pHdr->u32Version == 3)
+ {
+ if (pHdr->Version.v2.v3.u32RefCntWidth > 6)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Reference count width too big for image '%s' (%u vs %u)"),
+ pImage->pszFilename, RT_BIT_32(pHdr->Version.v2.v3.u32RefCntWidth), 6);
+ }
+ }
+ else
+ return vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCOW: Version %u in image '%s' is not supported"),
+ pHdr->u32Version, pImage->pszFilename);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int qcowOpenImage(PQCOWIMAGE pImage, unsigned uOpenFlags)
+{
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ int rc = qcowL2TblCacheCreate(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Open the image. */
+ rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags,
+ false /* fCreate */),
+ &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile;
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile > sizeof(QCowHeader))
+ {
+ QCowHeader Header;
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, 0, &Header, sizeof(Header));
+ if ( RT_SUCCESS(rc)
+ && qcowHdrConvertToHostEndianess(&Header))
+ {
+ pImage->offNextCluster = RT_ALIGN_64(cbFile, 512); /* Align image to sector boundary. */
+ Assert(pImage->offNextCluster >= cbFile);
+
+ rc = qcowHdrValidate(pImage, &Header, cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (Header.u32Version == 1)
+ {
+ if (!Header.Version.v1.u32CryptMethod)
+ {
+ pImage->uVersion = 1;
+ pImage->offBackingFilename = Header.Version.v1.u64BackingFileOffset;
+ pImage->cbBackingFilename = Header.Version.v1.u32BackingFileSize;
+ pImage->MTime = Header.Version.v1.u32MTime;
+ pImage->cbSize = Header.Version.v1.u64Size;
+ pImage->cClusterBits = Header.Version.v1.u8ClusterBits;
+ pImage->cbCluster = RT_BIT_32(Header.Version.v1.u8ClusterBits);
+ pImage->cL2TableEntries = RT_BIT_32(Header.Version.v1.u8L2Bits);
+ pImage->cbL2Table = RT_ALIGN_64(pImage->cL2TableEntries * sizeof(uint64_t), pImage->cbCluster);
+ pImage->offL1Table = Header.Version.v1.u64L1TableOffset;
+ pImage->cL1TableEntries = pImage->cbSize / (pImage->cbCluster * pImage->cL2TableEntries);
+ if (pImage->cbSize % (pImage->cbCluster * pImage->cL2TableEntries))
+ pImage->cL1TableEntries++;
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCow: Encrypted image '%s' is not supported"),
+ pImage->pszFilename);
+ }
+ else if (Header.u32Version == 2 || Header.u32Version == 3)
+ {
+ if (Header.Version.v2.u32CryptMethod)
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCow: Encrypted image '%s' is not supported"),
+ pImage->pszFilename);
+ else if (Header.Version.v2.u32NbSnapshots)
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCow: Image '%s' contains snapshots which is not supported"),
+ pImage->pszFilename);
+ else
+ {
+ pImage->uVersion = 2;
+ pImage->offBackingFilename = Header.Version.v2.u64BackingFileOffset;
+ pImage->cbBackingFilename = Header.Version.v2.u32BackingFileSize;
+ pImage->cbSize = Header.Version.v2.u64Size;
+ pImage->cClusterBits = Header.Version.v2.u32ClusterBits;
+ pImage->cbCluster = RT_BIT_32(Header.Version.v2.u32ClusterBits);
+ pImage->cL2TableEntries = pImage->cbCluster / sizeof(uint64_t);
+ pImage->cbL2Table = pImage->cbCluster;
+ pImage->offL1Table = Header.Version.v2.u64L1TableOffset;
+ pImage->cL1TableEntries = Header.Version.v2.u32L1Size;
+ pImage->offRefcountTable = Header.Version.v2.u64RefcountTableOffset;
+ pImage->cbRefcountTable = qcowCluster2Byte(pImage, Header.Version.v2.u32RefcountTableClusters);
+ pImage->cRefcountTableEntries = pImage->cbRefcountTable / sizeof(uint64_t);
+
+ /* Init the masks to extract offset and sector count from a compressed cluster descriptor. */
+ uint32_t cBitsCompressedClusterOffset = 62 - (pImage->cClusterBits - 8);
+ pImage->fMaskCompressedClusterOffset = RT_BIT_64(cBitsCompressedClusterOffset) - 1;
+ pImage->fMaskCompressedClusterSectors = (RT_BIT_64(62) - 1) & ~pImage->fMaskCompressedClusterOffset;
+ pImage->cBitsShiftRCompressedClusterSectors = cBitsCompressedClusterOffset;
+
+ if (Header.u32Version == 3)
+ {
+ if (Header.Version.v2.v3.u64IncompatFeat & ~QCOW_V3_INCOMPAT_FEAT_SUPPORTED_MASK)
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCow: Image '%s' contains unsupported incompatible features (%llx vs %llx)"),
+ pImage->pszFilename, Header.Version.v2.v3.u64IncompatFeat, QCOW_V3_INCOMPAT_FEAT_SUPPORTED_MASK);
+
+ /** @todo Auto clear features need to be reset as soon as write support is added. */
+ }
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCow: Image '%s' uses version %u which is not supported"),
+ pImage->pszFilename, Header.u32Version);
+
+ if (RT_SUCCESS(rc))
+ {
+ pImage->cbL1Table = RT_ALIGN_64(pImage->cL1TableEntries * sizeof(uint64_t), pImage->cbCluster);
+ if ((uint64_t)pImage->cbL1Table != RT_ALIGN_64(pImage->cL1TableEntries * sizeof(uint64_t), pImage->cbCluster))
+ rc = vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: L1 table size overflow in image '%s'"),
+ pImage->pszFilename);
+ }
+ }
+
+ /** @todo Check that there are no compressed clusters in the image
+ * (by traversing the L2 tables and checking each offset).
+ * Refuse to open such images.
+ */
+
+ if ( RT_SUCCESS(rc)
+ && pImage->cbBackingFilename
+ && pImage->offBackingFilename)
+ {
+ /* Load backing filename from image. */
+ pImage->pszBackingFilename = RTStrAlloc(pImage->cbBackingFilename + 1); /* +1 for \0 terminator. */
+ if (pImage->pszBackingFilename)
+ {
+ RT_BZERO(pImage->pszBackingFilename, pImage->cbBackingFilename + 1);
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offBackingFilename, pImage->pszBackingFilename,
+ pImage->cbBackingFilename);
+ if (RT_SUCCESS(rc))
+ rc = RTStrValidateEncoding(pImage->pszBackingFilename);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pImage->cbRefcountTable
+ && pImage->offRefcountTable)
+ {
+ /* Load refcount table. */
+ Assert(pImage->cRefcountTableEntries);
+ pImage->paRefcountTable = (uint64_t *)RTMemAllocZ(pImage->cbRefcountTable);
+ if (RT_LIKELY(pImage->paRefcountTable))
+ {
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offRefcountTable, pImage->paRefcountTable,
+ pImage->cbRefcountTable);
+ if (RT_SUCCESS(rc))
+ qcowTableConvertToHostEndianess(pImage->paRefcountTable,
+ pImage->cRefcountTableEntries);
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("QCow: Reading refcount table of image '%s' failed"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("QCow: Allocating memory for refcount table of image '%s' failed"),
+ pImage->pszFilename);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ qcowTableMasksInit(pImage);
+
+ /* Allocate L1 table. */
+ pImage->paL1Table = (uint64_t *)RTMemAllocZ(pImage->cbL1Table);
+ if (pImage->paL1Table)
+ {
+ /* Read from the image. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offL1Table, pImage->paL1Table,
+ pImage->cbL1Table);
+ if (RT_SUCCESS(rc))
+ qcowTableConvertToHostEndianess(pImage->paL1Table, pImage->cL1TableEntries);
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("QCow: Reading the L1 table for image '%s' failed"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("QCow: Out of memory allocating L1 table for image '%s'"),
+ pImage->pszFilename);
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ /* else: Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("Qcow: Creating the L2 table cache for image '%s' failed"),
+ pImage->pszFilename);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ qcowFreeImage(pImage, false);
+ return rc;
+}
+
+/**
+ * Internal: Create a qcow image.
+ */
+static int qcowCreateImage(PQCOWIMAGE pImage, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry, unsigned uOpenFlags,
+ PVDINTERFACEPROGRESS pIfProgress,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ RT_NOREF1(pszComment);
+ int rc;
+ int32_t fOpen;
+
+ if (!(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ rc = qcowL2TblCacheCreate(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ pImage->uOpenFlags = uOpenFlags & ~VD_OPEN_FLAGS_READONLY;
+ pImage->uImageFlags = uImageFlags;
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /* Create image file. */
+ fOpen = VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags, true /* fCreate */);
+ rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename, fOpen, &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Init image state. */
+ pImage->uVersion = 1; /* We create only version 1 images at the moment. */
+ pImage->cbSize = cbSize;
+ pImage->cbCluster = QCOW_CLUSTER_SIZE_DEFAULT;
+ pImage->cbL2Table = qcowCluster2Byte(pImage, QCOW_L2_CLUSTERS_DEFAULT);
+ pImage->cL2TableEntries = pImage->cbL2Table / sizeof(uint64_t);
+ pImage->cL1TableEntries = cbSize / (pImage->cbCluster * pImage->cL2TableEntries);
+ if (cbSize % (pImage->cbCluster * pImage->cL2TableEntries))
+ pImage->cL1TableEntries++;
+ pImage->cbL1Table = RT_ALIGN_64(pImage->cL1TableEntries * sizeof(uint64_t), pImage->cbCluster);
+ pImage->offL1Table = QCOW_V1_HDR_SIZE;
+ pImage->cbBackingFilename = 0;
+ pImage->offBackingFilename = 0;
+ pImage->offNextCluster = RT_ALIGN_64(QCOW_V1_HDR_SIZE + pImage->cbL1Table, pImage->cbCluster);
+ qcowTableMasksInit(pImage);
+
+ /* Init L1 table. */
+ pImage->paL1Table = (uint64_t *)RTMemAllocZ(pImage->cbL1Table);
+ if (RT_LIKELY(pImage->paL1Table))
+ {
+ if (RT_SUCCESS(rc))
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan * 98 / 100);
+
+ rc = qcowFlushImage(pImage);
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pImage->offNextCluster);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS, N_("QCow: cannot allocate memory for L1 table of image '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("QCow: cannot create image '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("QCow: Failed to create L2 cache for image '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_INVALID_TYPE, RT_SRC_POS, N_("QCow: cannot create fixed image '%s'"), pImage->pszFilename);
+
+ if (RT_SUCCESS(rc))
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ qcowFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+/**
+ * Rollback anything done during async cluster allocation.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param pClusterAlloc The cluster allocation to rollback.
+ */
+static int qcowAsyncClusterAllocRollback(PQCOWIMAGE pImage, PVDIOCTX pIoCtx, PQCOWCLUSTERASYNCALLOC pClusterAlloc)
+{
+ RT_NOREF1(pIoCtx);
+ int rc = VINF_SUCCESS;
+
+ switch (pClusterAlloc->enmAllocState)
+ {
+ case QCOWCLUSTERASYNCALLOCSTATE_L2_ALLOC:
+ case QCOWCLUSTERASYNCALLOCSTATE_L2_LINK:
+ {
+ /* Revert the L1 table entry */
+ pImage->paL1Table[pClusterAlloc->idxL1] = 0;
+ pImage->pL2TblAlloc = NULL;
+
+ /* Assumption right now is that the L1 table is not modified on storage if the link fails. */
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pClusterAlloc->offNextClusterOld);
+ qcowL2TblCacheEntryRelease(pClusterAlloc->pL2Entry); /* Release L2 cache entry. */
+ Assert(!pClusterAlloc->pL2Entry->cRefs);
+ qcowL2TblCacheEntryFree(pImage, pClusterAlloc->pL2Entry); /* Free it, it is not in the cache yet. */
+ break;
+ }
+ case QCOWCLUSTERASYNCALLOCSTATE_USER_ALLOC:
+ case QCOWCLUSTERASYNCALLOCSTATE_USER_LINK:
+ {
+ /* Assumption right now is that the L2 table is not modified if the link fails. */
+ pClusterAlloc->pL2Entry->paL2Tbl[pClusterAlloc->idxL2] = 0;
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pClusterAlloc->offNextClusterOld);
+ qcowL2TblCacheEntryRelease(pClusterAlloc->pL2Entry); /* Release L2 cache entry. */
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid cluster allocation state %d\n", pClusterAlloc->enmAllocState));
+ rc = VERR_INVALID_STATE;
+ }
+
+ RTMemFree(pClusterAlloc);
+ return rc;
+}
+
+/**
+ * Updates the state of the async cluster allocation.
+ *
+ * @returns VBox status code.
+ * @param pBackendData The opaque backend data.
+ * @param pIoCtx I/O context associated with this request.
+ * @param pvUser Opaque user data passed during a read/write request.
+ * @param rcReq Status code for the completed request.
+ */
+static DECLCALLBACK(int) qcowAsyncClusterAllocUpdate(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ int rc = VINF_SUCCESS;
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ PQCOWCLUSTERASYNCALLOC pClusterAlloc = (PQCOWCLUSTERASYNCALLOC)pvUser;
+
+ if (RT_FAILURE(rcReq))
+ return qcowAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+
+ AssertPtr(pClusterAlloc->pL2Entry);
+
+ switch (pClusterAlloc->enmAllocState)
+ {
+ case QCOWCLUSTERASYNCALLOCSTATE_L2_ALLOC:
+ {
+ /* Update the link in the in memory L1 table now. */
+ pImage->paL1Table[pClusterAlloc->idxL1] = pClusterAlloc->pL2Entry->offL2Tbl;
+
+ /* Update the link in the on disk L1 table now. */
+ pClusterAlloc->enmAllocState = QCOWCLUSTERASYNCALLOCSTATE_L2_LINK;
+ rc = qcowTblWrite(pImage, pIoCtx, pImage->offL1Table, pImage->paL1Table,
+ pImage->cbL1Table, pImage->cL1TableEntries,
+ qcowAsyncClusterAllocUpdate, pClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ /* Rollback. */
+ qcowAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+ break;
+ }
+ /* Success, fall through. */
+ }
+ RT_FALL_THRU();
+ case QCOWCLUSTERASYNCALLOCSTATE_L2_LINK:
+ {
+ /* L2 link updated in L1 , save L2 entry in cache and allocate new user data cluster. */
+ uint64_t offData = qcowClusterAllocate(pImage, 1);
+
+ pImage->pL2TblAlloc = NULL;
+ qcowL2TblCacheEntryInsert(pImage, pClusterAlloc->pL2Entry);
+
+ pClusterAlloc->enmAllocState = QCOWCLUSTERASYNCALLOCSTATE_USER_ALLOC;
+ pClusterAlloc->offNextClusterOld = offData;
+ pClusterAlloc->offClusterNew = offData;
+
+ /* Write data. */
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ offData, pIoCtx, pClusterAlloc->cbToWrite,
+ qcowAsyncClusterAllocUpdate, pClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ qcowAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+ RTMemFree(pClusterAlloc);
+ break;
+ }
+ }
+ RT_FALL_THRU();
+ case QCOWCLUSTERASYNCALLOCSTATE_USER_ALLOC:
+ {
+ pClusterAlloc->enmAllocState = QCOWCLUSTERASYNCALLOCSTATE_USER_LINK;
+ pClusterAlloc->pL2Entry->paL2Tbl[pClusterAlloc->idxL2] = pClusterAlloc->offClusterNew;
+
+ /* Link L2 table and update it. */
+ rc = qcowTblWrite(pImage, pIoCtx, pImage->paL1Table[pClusterAlloc->idxL1],
+ pClusterAlloc->pL2Entry->paL2Tbl,
+ pImage->cbL2Table, pImage->cL2TableEntries,
+ qcowAsyncClusterAllocUpdate, pClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ qcowAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+ RTMemFree(pClusterAlloc);
+ break;
+ }
+ }
+ RT_FALL_THRU();
+ case QCOWCLUSTERASYNCALLOCSTATE_USER_LINK:
+ {
+ /* Everything done without errors, signal completion. */
+ qcowL2TblCacheEntryRelease(pClusterAlloc->pL2Entry);
+ RTMemFree(pClusterAlloc);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid async cluster allocation state %d\n",
+ pClusterAlloc->enmAllocState));
+ }
+
+ return rc;
+}
+
+/**
+ * Reads a compressed cluster, inflates it and copies the amount of data requested
+ * into the given I/O context.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param offCluster Where to start reading in the uncompressed cluster.
+ * @param cbToRead How much to read in the uncomrpessed cluster.
+ * @param offFile Offset where the compressed cluster is stored in the image.
+ * @param cbCompressedCluster Size of the comrpessed cluster in bytes.
+ */
+static int qcowReadCompressedCluster(PQCOWIMAGE pImage, PVDIOCTX pIoCtx,
+ uint32_t offCluster, size_t cbToRead,
+ uint64_t offFile, size_t cbCompressedCluster)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertReturn(!(pImage->uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO), VERR_NOT_SUPPORTED); /* Only synchronous I/O supported so far. */
+
+ if (cbCompressedCluster > pImage->cbCompCluster)
+ {
+ void *pvCompClusterNew = RTMemRealloc(pImage->pvCompCluster, cbCompressedCluster);
+ if (RT_LIKELY(pvCompClusterNew))
+ {
+ pImage->pvCompCluster = pvCompClusterNew;
+ pImage->cbCompCluster = cbCompressedCluster;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage,
+ offFile, pImage->pvCompCluster,
+ cbCompressedCluster, NULL,
+ NULL, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pImage->pvCluster)
+ {
+ pImage->pvCluster = RTMemAllocZ(pImage->cbCluster);
+ if (!pImage->pvCluster)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbDecomp = 0;
+
+ rc = RTZipBlockDecompress(RTZIPTYPE_ZLIB_NO_HEADER, 0 /*fFlags*/,
+ pImage->pvCompCluster, cbCompressedCluster, NULL,
+ pImage->pvCluster, pImage->cbCluster, &cbDecomp);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbDecomp == pImage->cbCluster);
+ vdIfIoIntIoCtxCopyTo(pImage->pIfIo, pIoCtx,
+ (uint8_t *)pImage->pvCluster + offCluster,
+ cbToRead);
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) qcowProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pVDIfsDisk, enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
+ PVDIOSTORAGE pStorage = NULL;
+ uint64_t cbFile;
+ int rc = VINF_SUCCESS;
+
+ /* Get I/O interface. */
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+ /*
+ * Open the file and read the footer.
+ */
+ rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile > sizeof(QCowHeader))
+ {
+ QCowHeader Header;
+
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, &Header, sizeof(Header));
+ if ( RT_SUCCESS(rc)
+ && qcowHdrConvertToHostEndianess(&Header))
+ *penmType = VDTYPE_HDD;
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+
+ if (pStorage)
+ vdIfIoIntFileClose(pIfIo, pStorage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) qcowOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ RT_NOREF1(enmType); /**< @todo r=klaus make use of the type info. */
+
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ PQCOWIMAGE pImage = (PQCOWIMAGE)RTMemAllocZ(RT_UOFFSETOF(QCOWIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = qcowOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ else
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnCreate */
+static DECLCALLBACK(int) qcowCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+ int rc;
+
+ /* Check the VD container type. */
+ if (enmType != VDTYPE_HDD)
+ return VERR_VD_INVALID_TYPE;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER);
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+
+ PQCOWIMAGE pImage = (PQCOWIMAGE)RTMemAllocZ(RT_UOFFSETOF(QCOWIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = qcowCreateImage(pImage, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, uOpenFlags,
+ pIfProgress, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ qcowFreeImage(pImage, false);
+ rc = qcowOpenImage(pImage, uOpenFlags);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRename */
+static DECLCALLBACK(int) qcowRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VINF_SUCCESS;
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ /* Check arguments. */
+ AssertReturn((pImage && pszFilename && *pszFilename), VERR_INVALID_PARAMETER);
+
+ /* Close the image. */
+ rc = qcowFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ {
+ /* Rename the file. */
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pszFilename, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the old image with new name. */
+ rc = qcowOpenImage(pImage, pImage->uOpenFlags);
+ }
+ else
+ {
+ /* The move failed, try to reopen the original image. */
+ int rc2 = qcowOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) qcowClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ int rc = qcowFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) qcowRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToRead=%zu pcbActuallyRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ uint32_t offCluster = 0;
+ uint32_t idxL1 = 0;
+ uint32_t idxL2 = 0;
+ uint64_t offFile = 0;
+ int rc;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToRead % 512 == 0);
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToRead <= pImage->cbSize, VERR_INVALID_PARAMETER);
+
+ qcowConvertLogicalOffset(pImage, uOffset, &idxL1, &idxL2, &offCluster);
+
+ /* Clip read size to remain in the cluster. */
+ cbToRead = RT_MIN(cbToRead, pImage->cbCluster - offCluster);
+
+ /* Get offset in image. */
+ bool fCompressedCluster = false;
+ size_t cbCompressedCluster = 0;
+ rc = qcowConvertToImageOffset(pImage, pIoCtx, idxL1, idxL2, offCluster,
+ &offFile, &fCompressedCluster, &cbCompressedCluster);
+ if (RT_SUCCESS(rc))
+ {
+ if (!fCompressedCluster)
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, offFile,
+ pIoCtx, cbToRead);
+ else
+ rc = qcowReadCompressedCluster(pImage, pIoCtx, offCluster, cbToRead, offFile, cbCompressedCluster);
+ }
+
+ if ( ( RT_SUCCESS(rc)
+ || rc == VERR_VD_BLOCK_FREE
+ || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ && pcbActuallyRead)
+ *pcbActuallyRead = cbToRead;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) qcowWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ uint32_t offCluster = 0;
+ uint32_t idxL1 = 0;
+ uint32_t idxL2 = 0;
+ uint64_t offImage = 0;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbToWrite % 512));
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToWrite, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToWrite <= pImage->cbSize, VERR_INVALID_PARAMETER);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* Convert offset to L1, L2 index and cluster offset. */
+ qcowConvertLogicalOffset(pImage, uOffset, &idxL1, &idxL2, &offCluster);
+
+ /* Clip write size to remain in the cluster. */
+ cbToWrite = RT_MIN(cbToWrite, pImage->cbCluster - offCluster);
+ Assert(!(cbToWrite % 512));
+
+ /* Get offset in image. */
+ bool fCompressedCluster = false;
+ size_t cbCompressedCluster = 0;
+ rc = qcowConvertToImageOffset(pImage, pIoCtx, idxL1, idxL2, offCluster,
+ &offImage, &fCompressedCluster, &cbCompressedCluster);
+ if (RT_SUCCESS(rc))
+ {
+ if (!fCompressedCluster)
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ offImage, pIoCtx, cbToWrite, NULL, NULL);
+ else
+ rc = VERR_NOT_SUPPORTED; /** @todo Support writing compressed clusters */
+ }
+ else if (rc == VERR_VD_BLOCK_FREE)
+ {
+ if ( cbToWrite == pImage->cbCluster
+ && !(fWrite & VD_WRITE_NO_ALLOC))
+ {
+ PQCOWL2CACHEENTRY pL2Entry = NULL;
+
+ /* Full cluster write to previously unallocated cluster.
+ * Allocate cluster and write data. */
+ Assert(!offCluster);
+
+ do
+ {
+ /* Check if we have to allocate a new cluster for L2 tables. */
+ if (!pImage->paL1Table[idxL1])
+ {
+ uint64_t offL2Tbl;
+ PQCOWCLUSTERASYNCALLOC pL2ClusterAlloc = NULL;
+
+ /* Allocate new async cluster allocation state. */
+ pL2ClusterAlloc = (PQCOWCLUSTERASYNCALLOC)RTMemAllocZ(sizeof(QCOWCLUSTERASYNCALLOC));
+ if (RT_UNLIKELY(!pL2ClusterAlloc))
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pL2Entry = qcowL2TblCacheEntryAlloc(pImage);
+ if (!pL2Entry)
+ {
+ rc = VERR_NO_MEMORY;
+ RTMemFree(pL2ClusterAlloc);
+ break;
+ }
+
+ offL2Tbl = qcowClusterAllocate(pImage, qcowByte2Cluster(pImage, pImage->cbL2Table));
+ pL2Entry->offL2Tbl = offL2Tbl;
+ memset(pL2Entry->paL2Tbl, 0, pImage->cbL2Table);
+
+ pL2ClusterAlloc->enmAllocState = QCOWCLUSTERASYNCALLOCSTATE_L2_ALLOC;
+ pL2ClusterAlloc->offNextClusterOld = offL2Tbl;
+ pL2ClusterAlloc->offClusterNew = offL2Tbl;
+ pL2ClusterAlloc->idxL1 = idxL1;
+ pL2ClusterAlloc->idxL2 = idxL2;
+ pL2ClusterAlloc->cbToWrite = cbToWrite;
+ pL2ClusterAlloc->pL2Entry = pL2Entry;
+
+ pImage->pL2TblAlloc = pL2Entry;
+
+ LogFlowFunc(("Allocating new L2 table at cluster offset %llu\n", offL2Tbl));
+
+ /*
+ * Write the L2 table first and link to the L1 table afterwards.
+ * If something unexpected happens the worst case which can happen
+ * is a leak of some clusters.
+ */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ offL2Tbl, pL2Entry->paL2Tbl, pImage->cbL2Table, pIoCtx,
+ qcowAsyncClusterAllocUpdate, pL2ClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ RTMemFree(pL2ClusterAlloc);
+ qcowL2TblCacheEntryFree(pImage, pL2Entry);
+ break;
+ }
+
+ rc = qcowAsyncClusterAllocUpdate(pImage, pIoCtx, pL2ClusterAlloc, rc);
+ }
+ else
+ {
+ LogFlowFunc(("Fetching L2 table at cluster offset %llu\n", pImage->paL1Table[idxL1]));
+
+ rc = qcowL2TblCacheFetch(pImage, pIoCtx, pImage->paL1Table[idxL1],
+ &pL2Entry);
+ if (RT_SUCCESS(rc))
+ {
+ PQCOWCLUSTERASYNCALLOC pDataClusterAlloc = NULL;
+
+ /* Allocate new async cluster allocation state. */
+ pDataClusterAlloc = (PQCOWCLUSTERASYNCALLOC)RTMemAllocZ(sizeof(QCOWCLUSTERASYNCALLOC));
+ if (RT_UNLIKELY(!pDataClusterAlloc))
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Allocate new cluster for the data. */
+ uint64_t offData = qcowClusterAllocate(pImage, 1);
+
+ pDataClusterAlloc->enmAllocState = QCOWCLUSTERASYNCALLOCSTATE_USER_ALLOC;
+ pDataClusterAlloc->offNextClusterOld = offData;
+ pDataClusterAlloc->offClusterNew = offData;
+ pDataClusterAlloc->idxL1 = idxL1;
+ pDataClusterAlloc->idxL2 = idxL2;
+ pDataClusterAlloc->cbToWrite = cbToWrite;
+ pDataClusterAlloc->pL2Entry = pL2Entry;
+
+ /* Write data. */
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ offData, pIoCtx, cbToWrite,
+ qcowAsyncClusterAllocUpdate, pDataClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ RTMemFree(pDataClusterAlloc);
+ break;
+ }
+
+ rc = qcowAsyncClusterAllocUpdate(pImage, pIoCtx, pDataClusterAlloc, rc);
+ }
+ }
+
+ } while (0);
+
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+ }
+ else
+ {
+ /* Trying to do a partial write to an unallocated cluster. Don't do
+ * anything except letting the upper layer know what to do. */
+ *pcbPreRead = offCluster;
+ *pcbPostRead = pImage->cbCluster - cbToWrite - *pcbPreRead;
+ }
+ }
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) qcowFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ AssertPtrReturn(pIoCtx, VERR_INVALID_PARAMETER);
+
+ if ( pImage->pStorage
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ QCowHeader Header;
+
+ rc = qcowTblWrite(pImage, pIoCtx, pImage->offL1Table, pImage->paL1Table,
+ pImage->cbL1Table, pImage->cL1TableEntries, NULL, NULL);
+ if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ /* Write header. */
+ size_t cbHeader = 0;
+ qcowHdrConvertFromHostEndianess(pImage, &Header, &cbHeader);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ 0, &Header, cbHeader,
+ pIoCtx, NULL, NULL);
+ if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = vdIfIoIntFileFlush(pImage->pIfIo, pImage->pStorage,
+ pIoCtx, NULL, NULL);
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) qcowGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ return pImage->uVersion;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) qcowGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtrReturn(pImage, 0);
+
+ uint64_t cbFile;
+ if (pImage->pStorage)
+ {
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb += cbFile;
+ }
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) qcowGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->PCHSGeometry.cCylinders)
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) qcowSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->PCHSGeometry = *pPCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) qcowGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->LCHSGeometry.cCylinders)
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders,
+ pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) qcowSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData,
+ pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->LCHSGeometry = *pLCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) qcowQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PQCOWIMAGE pThis = (PQCOWIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) qcowRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PQCOWIMAGE pThis = (PQCOWIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) qcowGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uImageFlags));
+ return pImage->uImageFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) qcowGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uOpenFlags));
+ return pImage->uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) qcowSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pImage || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ rc = qcowFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ rc = qcowOpenImage(pImage, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(qcowGetComment);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(qcowSetComment, PQCOWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qcowGetUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qcowSetUuid, PQCOWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qcowGetModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qcowSetModificationUuid, PQCOWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qcowGetParentUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qcowSetParentUuid, PQCOWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qcowGetParentModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qcowSetParentModificationUuid, PQCOWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) qcowDump(void *pBackendData)
+{
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pImage);
+ vdIfErrorMessage(pImage->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cbSector=%llu\n",
+ pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors,
+ pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors,
+ pImage->cbSize / 512);
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentFilename */
+static DECLCALLBACK(int) qcowGetParentFilename(void *pBackendData, char **ppszParentFilename)
+{
+ int rc = VINF_SUCCESS;
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ if (pImage->pszBackingFilename)
+ *ppszParentFilename = RTStrDup(pImage->pszBackingFilename);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentFilename */
+static DECLCALLBACK(int) qcowSetParentFilename(void *pBackendData, const char *pszParentFilename)
+{
+ int rc = VINF_SUCCESS;
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else if ( pImage->pszBackingFilename
+ && (strlen(pszParentFilename) > pImage->cbBackingFilename))
+ rc = VERR_NOT_SUPPORTED; /* The new filename is longer than the old one. */
+ else
+ {
+ if (pImage->pszBackingFilename)
+ RTStrFree(pImage->pszBackingFilename);
+ pImage->pszBackingFilename = RTStrDup(pszParentFilename);
+ if (!pImage->pszBackingFilename)
+ rc = VERR_NO_STR_MEMORY;
+ else
+ {
+ if (!pImage->offBackingFilename)
+ {
+ /* Allocate new cluster. */
+ uint64_t offData = qcowClusterAllocate(pImage, 1);
+
+ Assert((offData & UINT32_MAX) == offData);
+ pImage->offBackingFilename = (uint32_t)offData;
+ pImage->cbBackingFilename = (uint32_t)strlen(pszParentFilename);
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage,
+ offData + pImage->cbCluster);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offBackingFilename,
+ pImage->pszBackingFilename,
+ strlen(pImage->pszBackingFilename));
+ }
+ }
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+
+const VDIMAGEBACKEND g_QCowBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "QCOW",
+ /* uBackendCaps */
+ VD_CAP_FILE | VD_CAP_VFS | VD_CAP_CREATE_DYNAMIC | VD_CAP_DIFF | VD_CAP_ASYNC,
+ /* paFileExtensions */
+ s_aQCowFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ qcowProbe,
+ /* pfnOpen */
+ qcowOpen,
+ /* pfnCreate */
+ qcowCreate,
+ /* pfnRename */
+ qcowRename,
+ /* pfnClose */
+ qcowClose,
+ /* pfnRead */
+ qcowRead,
+ /* pfnWrite */
+ qcowWrite,
+ /* pfnFlush */
+ qcowFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ qcowGetVersion,
+ /* pfnGetFileSize */
+ qcowGetFileSize,
+ /* pfnGetPCHSGeometry */
+ qcowGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ qcowSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ qcowGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ qcowSetLCHSGeometry,
+ /* pfnQueryRegions */
+ qcowQueryRegions,
+ /* pfnRegionListRelease */
+ qcowRegionListRelease,
+ /* pfnGetImageFlags */
+ qcowGetImageFlags,
+ /* pfnGetOpenFlags */
+ qcowGetOpenFlags,
+ /* pfnSetOpenFlags */
+ qcowSetOpenFlags,
+ /* pfnGetComment */
+ qcowGetComment,
+ /* pfnSetComment */
+ qcowSetComment,
+ /* pfnGetUuid */
+ qcowGetUuid,
+ /* pfnSetUuid */
+ qcowSetUuid,
+ /* pfnGetModificationUuid */
+ qcowGetModificationUuid,
+ /* pfnSetModificationUuid */
+ qcowSetModificationUuid,
+ /* pfnGetParentUuid */
+ qcowGetParentUuid,
+ /* pfnSetParentUuid */
+ qcowSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ qcowGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ qcowSetParentModificationUuid,
+ /* pfnDump */
+ qcowDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ qcowGetParentFilename,
+ /* pfnSetParentFilename */
+ qcowSetParentFilename,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ NULL,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
diff --git a/src/VBox/Storage/QED.cpp b/src/VBox/Storage/QED.cpp
new file mode 100644
index 00000000..95a01bc6
--- /dev/null
+++ b/src/VBox/Storage/QED.cpp
@@ -0,0 +1,2400 @@
+/* $Id: QED.cpp $ */
+/** @file
+ * QED - QED Disk image.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_QED
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/path.h>
+#include <iprt/list.h>
+
+#include "VDBackends.h"
+#include "VDBackendsInline.h"
+
+/**
+ * The QED backend implements support for the qemu enhanced disk format (short QED)
+ * The specification for the format is available under http://wiki.qemu.org/Features/QED/Specification
+ *
+ * Missing things to implement:
+ * - compaction
+ * - resizing which requires block relocation (very rare case)
+ */
+
+
+/*********************************************************************************************************************************
+* Structures in a QED image, little endian *
+*********************************************************************************************************************************/
+
+#pragma pack(1)
+typedef struct QedHeader
+{
+ /** Magic value. */
+ uint32_t u32Magic;
+ /** Cluster size in bytes. */
+ uint32_t u32ClusterSize;
+ /** Size of L1 and L2 tables in clusters. */
+ uint32_t u32TableSize;
+ /** size of this header structure in clusters. */
+ uint32_t u32HeaderSize;
+ /** Features used for the image. */
+ uint64_t u64FeatureFlags;
+ /** Compatibility features used for the image. */
+ uint64_t u64CompatFeatureFlags;
+ /** Self resetting feature bits. */
+ uint64_t u64AutoresetFeatureFlags;
+ /** Offset of the L1 table in bytes. */
+ uint64_t u64OffL1Table;
+ /** Logical image size as seen by the guest. */
+ uint64_t u64Size;
+ /** Offset of the backing filename in bytes. */
+ uint32_t u32OffBackingFilename;
+ /** Size of the backing filename. */
+ uint32_t u32BackingFilenameSize;
+} QedHeader;
+#pragma pack()
+/** Pointer to a on disk QED header. */
+typedef QedHeader *PQedHeader;
+
+/** QED magic value. */
+#define QED_MAGIC UINT32_C(0x00444551) /* QED\0 */
+/** Cluster size minimum. */
+#define QED_CLUSTER_SIZE_MIN RT_BIT(12)
+/** Cluster size maximum. */
+#define QED_CLUSTER_SIZE_MAX RT_BIT(26)
+/** L1 and L2 Table size minimum. */
+#define QED_TABLE_SIZE_MIN 1
+/** L1 and L2 Table size maximum. */
+#define QED_TABLE_SIZE_MAX 16
+
+/** QED default cluster size when creating an image. */
+#define QED_CLUSTER_SIZE_DEFAULT (64 * _1K)
+/** The default table size in clusters. */
+#define QED_TABLE_SIZE_DEFAULT 4
+
+/** Feature flags.
+ * @{
+ */
+/** Image uses a backing file to provide data for unallocated clusters. */
+#define QED_FEATURE_BACKING_FILE RT_BIT_64(0)
+/** Image needs checking before use. */
+#define QED_FEATURE_NEED_CHECK RT_BIT_64(1)
+/** Don't probe for format of the backing file, treat as raw image. */
+#define QED_FEATURE_BACKING_FILE_NO_PROBE RT_BIT_64(2)
+/** Mask of valid features. */
+#define QED_FEATURE_MASK (QED_FEATURE_BACKING_FILE | QED_FEATURE_NEED_CHECK | QED_FEATURE_BACKING_FILE_NO_PROBE)
+/** @} */
+
+/** Compatibility feature flags.
+ * @{
+ */
+/** Mask of valid compatibility features. */
+#define QED_COMPAT_FEATURE_MASK (0)
+/** @} */
+
+/** Autoreset feature flags.
+ * @{
+ */
+/** Mask of valid autoreset features. */
+#define QED_AUTORESET_FEATURE_MASK (0)
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * QED L2 cache entry.
+ */
+typedef struct QEDL2CACHEENTRY
+{
+ /** List node for the search list. */
+ RTLISTNODE NodeSearch;
+ /** List node for the LRU list. */
+ RTLISTNODE NodeLru;
+ /** Reference counter. */
+ uint32_t cRefs;
+ /** The offset of the L2 table, used as search key. */
+ uint64_t offL2Tbl;
+ /** Pointer to the cached L2 table. */
+ uint64_t *paL2Tbl;
+} QEDL2CACHEENTRY, *PQEDL2CACHEENTRY;
+
+/** Maximum amount of memory the cache is allowed to use. */
+#define QED_L2_CACHE_MEMORY_MAX (2*_1M)
+
+/**
+ * QED image data structure.
+ */
+typedef struct QEDIMAGE
+{
+ /** Image name. */
+ const char *pszFilename;
+ /** Storage handle. */
+ PVDIOSTORAGE pStorage;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+
+ /** Open flags passed by VBoxHD layer. */
+ unsigned uOpenFlags;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+ /** Physical geometry of this image. */
+ VDGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ VDGEOMETRY LCHSGeometry;
+
+ /** Filename of the backing file if any. */
+ char *pszBackingFilename;
+ /** Offset of the filename in the image. */
+ uint32_t offBackingFilename;
+ /** Size of the backing filename excluding \0. */
+ uint32_t cbBackingFilename;
+
+ /** Size of the image, multiple of clusters. */
+ uint64_t cbImage;
+ /** Cluster size in bytes. */
+ uint32_t cbCluster;
+ /** Number of entries in the L1 and L2 table. */
+ uint32_t cTableEntries;
+ /** Size of an L1 or L2 table rounded to the next cluster size. */
+ uint32_t cbTable;
+ /** Pointer to the L1 table. */
+ uint64_t *paL1Table;
+ /** Offset of the L1 table. */
+ uint64_t offL1Table;
+
+ /** Offset mask for a cluster. */
+ uint64_t fOffsetMask;
+ /** L1 table mask to get the L1 index. */
+ uint64_t fL1Mask;
+ /** Number of bits to shift to get the L1 index. */
+ uint32_t cL1Shift;
+ /** L2 table mask to get the L2 index. */
+ uint64_t fL2Mask;
+ /** Number of bits to shift to get the L2 index. */
+ uint32_t cL2Shift;
+
+ /** Pointer to the L2 table we are currently allocating
+ * (can be only one at a time). */
+ PQEDL2CACHEENTRY pL2TblAlloc;
+
+ /** Memory occupied by the L2 table cache. */
+ size_t cbL2Cache;
+ /** The sorted L2 entry list used for searching. */
+ RTLISTNODE ListSearch;
+ /** The LRU L2 entry list used for eviction. */
+ RTLISTNODE ListLru;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} QEDIMAGE, *PQEDIMAGE;
+
+/**
+ * State of the async cluster allocation.
+ */
+typedef enum QEDCLUSTERASYNCALLOCSTATE
+{
+ /** Invalid. */
+ QEDCLUSTERASYNCALLOCSTATE_INVALID = 0,
+ /** L2 table allocation. */
+ QEDCLUSTERASYNCALLOCSTATE_L2_ALLOC,
+ /** Link L2 table into L1. */
+ QEDCLUSTERASYNCALLOCSTATE_L2_LINK,
+ /** Allocate user data cluster. */
+ QEDCLUSTERASYNCALLOCSTATE_USER_ALLOC,
+ /** Link user data cluster. */
+ QEDCLUSTERASYNCALLOCSTATE_USER_LINK,
+ /** 32bit blowup. */
+ QEDCLUSTERASYNCALLOCSTATE_32BIT_HACK = 0x7fffffff
+} QEDCLUSTERASYNCALLOCSTATE, *PQEDCLUSTERASYNCALLOCSTATE;
+
+/**
+ * Data needed to track async cluster allocation.
+ */
+typedef struct QEDCLUSTERASYNCALLOC
+{
+ /** The state of the cluster allocation. */
+ QEDCLUSTERASYNCALLOCSTATE enmAllocState;
+ /** Old image size to rollback in case of an error. */
+ uint64_t cbImageOld;
+ /** L1 index to link if any. */
+ uint32_t idxL1;
+ /** L2 index to link, required in any case. */
+ uint32_t idxL2;
+ /** Start offset of the allocated cluster. */
+ uint64_t offClusterNew;
+ /** L2 cache entry if a L2 table is allocated. */
+ PQEDL2CACHEENTRY pL2Entry;
+ /** Number of bytes to write. */
+ size_t cbToWrite;
+} QEDCLUSTERASYNCALLOC, *PQEDCLUSTERASYNCALLOC;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aQedFileExtensions[] =
+{
+ {"qed", VDTYPE_HDD},
+ {NULL, VDTYPE_INVALID}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Converts the image header to the host endianess and performs basic checks.
+ *
+ * @returns Whether the given header is valid or not.
+ * @param pHeader Pointer to the header to convert.
+ */
+static bool qedHdrConvertToHostEndianess(PQedHeader pHeader)
+{
+ pHeader->u32Magic = RT_LE2H_U32(pHeader->u32Magic);
+ pHeader->u32ClusterSize = RT_LE2H_U32(pHeader->u32ClusterSize);
+ pHeader->u32TableSize = RT_LE2H_U32(pHeader->u32TableSize);
+ pHeader->u32HeaderSize = RT_LE2H_U32(pHeader->u32HeaderSize);
+ pHeader->u64FeatureFlags = RT_LE2H_U64(pHeader->u64FeatureFlags);
+ pHeader->u64CompatFeatureFlags = RT_LE2H_U64(pHeader->u64CompatFeatureFlags);
+ pHeader->u64AutoresetFeatureFlags = RT_LE2H_U64(pHeader->u64AutoresetFeatureFlags);
+ pHeader->u64OffL1Table = RT_LE2H_U64(pHeader->u64OffL1Table);
+ pHeader->u64Size = RT_LE2H_U64(pHeader->u64Size);
+ pHeader->u32OffBackingFilename = RT_LE2H_U32(pHeader->u32OffBackingFilename);
+ pHeader->u32BackingFilenameSize = RT_LE2H_U32(pHeader->u32BackingFilenameSize);
+
+ if (RT_UNLIKELY(pHeader->u32Magic != QED_MAGIC))
+ return false;
+ if (RT_UNLIKELY( pHeader->u32ClusterSize < QED_CLUSTER_SIZE_MIN
+ || pHeader->u32ClusterSize > QED_CLUSTER_SIZE_MAX))
+ return false;
+ if (RT_UNLIKELY( pHeader->u32TableSize < QED_TABLE_SIZE_MIN
+ || pHeader->u32TableSize > QED_TABLE_SIZE_MAX))
+ return false;
+ if (RT_UNLIKELY(pHeader->u64Size % 512 != 0))
+ return false;
+ if (RT_UNLIKELY( pHeader->u64FeatureFlags & QED_FEATURE_BACKING_FILE
+ && ( pHeader->u32BackingFilenameSize == 0
+ || pHeader->u32BackingFilenameSize == UINT32_MAX)))
+ return false;
+
+ return true;
+}
+
+/**
+ * Creates a QED header from the given image state.
+ *
+ * @returns nothing.
+ * @param pImage Image instance data.
+ * @param pHeader Pointer to the header to convert.
+ */
+static void qedHdrConvertFromHostEndianess(PQEDIMAGE pImage, PQedHeader pHeader)
+{
+ pHeader->u32Magic = RT_H2LE_U32(QED_MAGIC);
+ pHeader->u32ClusterSize = RT_H2LE_U32(pImage->cbCluster);
+ pHeader->u32TableSize = RT_H2LE_U32(pImage->cbTable / pImage->cbCluster);
+ pHeader->u32HeaderSize = RT_H2LE_U32(1);
+ pHeader->u64FeatureFlags = RT_H2LE_U64(pImage->pszBackingFilename ? QED_FEATURE_BACKING_FILE : UINT64_C(0));
+ pHeader->u64CompatFeatureFlags = RT_H2LE_U64(UINT64_C(0));
+ pHeader->u64AutoresetFeatureFlags = RT_H2LE_U64(UINT64_C(0));
+ pHeader->u64OffL1Table = RT_H2LE_U64(pImage->offL1Table);
+ pHeader->u64Size = RT_H2LE_U64(pImage->cbSize);
+ pHeader->u32OffBackingFilename = RT_H2LE_U32(pImage->offBackingFilename);
+ pHeader->u32BackingFilenameSize = RT_H2LE_U32(pImage->cbBackingFilename);
+}
+
+/**
+ * Convert table entries from little endian to host endianess.
+ *
+ * @returns nothing.
+ * @param paTbl Pointer to the table.
+ * @param cEntries Number of entries in the table.
+ */
+static void qedTableConvertToHostEndianess(uint64_t *paTbl, uint32_t cEntries)
+{
+ while(cEntries-- > 0)
+ {
+ *paTbl = RT_LE2H_U64(*paTbl);
+ paTbl++;
+ }
+}
+
+#if defined(RT_BIG_ENDIAN)
+/**
+ * Convert table entries from host to little endian format.
+ *
+ * @returns nothing.
+ * @param paTblImg Pointer to the table which will store the little endian table.
+ * @param paTbl The source table to convert.
+ * @param cEntries Number of entries in the table.
+ */
+static void qedTableConvertFromHostEndianess(uint64_t *paTblImg, uint64_t *paTbl,
+ uint32_t cEntries)
+{
+ while(cEntries-- > 0)
+ {
+ *paTblImg = RT_H2LE_U64(*paTbl);
+ paTbl++;
+ paTblImg++;
+ }
+}
+#endif
+
+/**
+ * Creates the L2 table cache.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ */
+static int qedL2TblCacheCreate(PQEDIMAGE pImage)
+{
+ pImage->cbL2Cache = 0;
+ RTListInit(&pImage->ListSearch);
+ RTListInit(&pImage->ListLru);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys the L2 table cache.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ */
+static void qedL2TblCacheDestroy(PQEDIMAGE pImage)
+{
+ PQEDL2CACHEENTRY pL2Entry;
+ PQEDL2CACHEENTRY pL2Next;
+ RTListForEachSafe(&pImage->ListSearch, pL2Entry, pL2Next, QEDL2CACHEENTRY, NodeSearch)
+ {
+ Assert(!pL2Entry->cRefs);
+
+ RTListNodeRemove(&pL2Entry->NodeSearch);
+ RTMemPageFree(pL2Entry->paL2Tbl, pImage->cbTable);
+ RTMemFree(pL2Entry);
+ }
+
+ pImage->cbL2Cache = 0;
+ RTListInit(&pImage->ListSearch);
+ RTListInit(&pImage->ListLru);
+}
+
+/**
+ * Returns the L2 table matching the given offset or NULL if none could be found.
+ *
+ * @returns Pointer to the L2 table cache entry or NULL.
+ * @param pImage The image instance data.
+ * @param offL2Tbl Offset of the L2 table to search for.
+ */
+static PQEDL2CACHEENTRY qedL2TblCacheRetain(PQEDIMAGE pImage, uint64_t offL2Tbl)
+{
+ if ( pImage->pL2TblAlloc
+ && pImage->pL2TblAlloc->offL2Tbl == offL2Tbl)
+ {
+ pImage->pL2TblAlloc->cRefs++;
+ return pImage->pL2TblAlloc;
+ }
+
+ PQEDL2CACHEENTRY pL2Entry;
+ RTListForEach(&pImage->ListSearch, pL2Entry, QEDL2CACHEENTRY, NodeSearch)
+ {
+ if (pL2Entry->offL2Tbl == offL2Tbl)
+ break;
+ }
+
+ if (!RTListNodeIsDummy(&pImage->ListSearch, pL2Entry, QEDL2CACHEENTRY, NodeSearch))
+ {
+ /* Update LRU list. */
+ RTListNodeRemove(&pL2Entry->NodeLru);
+ RTListPrepend(&pImage->ListLru, &pL2Entry->NodeLru);
+ pL2Entry->cRefs++;
+ return pL2Entry;
+ }
+ else
+ return NULL;
+}
+
+/**
+ * Releases a L2 table cache entry.
+ *
+ * @returns nothing.
+ * @param pL2Entry The L2 cache entry.
+ */
+static void qedL2TblCacheEntryRelease(PQEDL2CACHEENTRY pL2Entry)
+{
+ Assert(pL2Entry->cRefs > 0);
+ pL2Entry->cRefs--;
+}
+
+/**
+ * Allocates a new L2 table from the cache evicting old entries if required.
+ *
+ * @returns Pointer to the L2 cache entry or NULL.
+ * @param pImage The image instance data.
+ */
+static PQEDL2CACHEENTRY qedL2TblCacheEntryAlloc(PQEDIMAGE pImage)
+{
+ PQEDL2CACHEENTRY pL2Entry = NULL;
+
+ if (pImage->cbL2Cache + pImage->cbTable <= QED_L2_CACHE_MEMORY_MAX)
+ {
+ /* Add a new entry. */
+ pL2Entry = (PQEDL2CACHEENTRY)RTMemAllocZ(sizeof(QEDL2CACHEENTRY));
+ if (pL2Entry)
+ {
+ pL2Entry->paL2Tbl = (uint64_t *)RTMemPageAllocZ(pImage->cbTable);
+ if (RT_UNLIKELY(!pL2Entry->paL2Tbl))
+ {
+ RTMemFree(pL2Entry);
+ pL2Entry = NULL;
+ }
+ else
+ {
+ pL2Entry->cRefs = 1;
+ pImage->cbL2Cache += pImage->cbTable;
+ }
+ }
+ }
+ else
+ {
+ /* Evict the last not in use entry and use it */
+ Assert(!RTListIsEmpty(&pImage->ListLru));
+
+ RTListForEachReverse(&pImage->ListLru, pL2Entry, QEDL2CACHEENTRY, NodeLru)
+ {
+ if (!pL2Entry->cRefs)
+ break;
+ }
+
+ if (!RTListNodeIsDummy(&pImage->ListSearch, pL2Entry, QEDL2CACHEENTRY, NodeSearch))
+ {
+ RTListNodeRemove(&pL2Entry->NodeSearch);
+ RTListNodeRemove(&pL2Entry->NodeLru);
+ pL2Entry->offL2Tbl = 0;
+ pL2Entry->cRefs = 1;
+ }
+ else
+ pL2Entry = NULL;
+ }
+
+ return pL2Entry;
+}
+
+/**
+ * Frees a L2 table cache entry.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ * @param pL2Entry The L2 cache entry to free.
+ */
+static void qedL2TblCacheEntryFree(PQEDIMAGE pImage, PQEDL2CACHEENTRY pL2Entry)
+{
+ Assert(!pL2Entry->cRefs);
+ RTMemPageFree(pL2Entry->paL2Tbl, pImage->cbTable);
+ RTMemFree(pL2Entry);
+
+ pImage->cbL2Cache -= pImage->cbTable;
+}
+
+/**
+ * Inserts an entry in the L2 table cache.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ * @param pL2Entry The L2 cache entry to insert.
+ */
+static void qedL2TblCacheEntryInsert(PQEDIMAGE pImage, PQEDL2CACHEENTRY pL2Entry)
+{
+ Assert(pL2Entry->offL2Tbl > 0);
+
+ /* Insert at the top of the LRU list. */
+ RTListPrepend(&pImage->ListLru, &pL2Entry->NodeLru);
+
+ if (RTListIsEmpty(&pImage->ListSearch))
+ {
+ RTListAppend(&pImage->ListSearch, &pL2Entry->NodeSearch);
+ }
+ else
+ {
+ /* Insert into search list. */
+ PQEDL2CACHEENTRY pIt;
+ pIt = RTListGetFirst(&pImage->ListSearch, QEDL2CACHEENTRY, NodeSearch);
+ if (pIt->offL2Tbl > pL2Entry->offL2Tbl)
+ RTListPrepend(&pImage->ListSearch, &pL2Entry->NodeSearch);
+ else
+ {
+ bool fInserted = false;
+
+ RTListForEach(&pImage->ListSearch, pIt, QEDL2CACHEENTRY, NodeSearch)
+ {
+ Assert(pIt->offL2Tbl != pL2Entry->offL2Tbl);
+ if (pIt->offL2Tbl < pL2Entry->offL2Tbl)
+ {
+ RTListNodeInsertAfter(&pIt->NodeSearch, &pL2Entry->NodeSearch);
+ fInserted = true;
+ break;
+ }
+ }
+ Assert(fInserted);
+ }
+ }
+}
+
+/**
+ * Fetches the L2 from the given offset trying the LRU cache first and
+ * reading it from the image after a cache miss - version for async I/O.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param pIoCtx The I/O context.
+ * @param offL2Tbl The offset of the L2 table in the image.
+ * @param ppL2Entry Where to store the L2 table on success.
+ */
+static int qedL2TblCacheFetchAsync(PQEDIMAGE pImage, PVDIOCTX pIoCtx,
+ uint64_t offL2Tbl, PQEDL2CACHEENTRY *ppL2Entry)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the L2 table from the cache first. */
+ PQEDL2CACHEENTRY pL2Entry = qedL2TblCacheRetain(pImage, offL2Tbl);
+ if (!pL2Entry)
+ {
+ pL2Entry = qedL2TblCacheEntryAlloc(pImage);
+
+ if (pL2Entry)
+ {
+ /* Read from the image. */
+ PVDMETAXFER pMetaXfer;
+
+ pL2Entry->offL2Tbl = offL2Tbl;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage,
+ offL2Tbl, pL2Entry->paL2Tbl,
+ pImage->cbTable, pIoCtx,
+ &pMetaXfer, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+#if defined(RT_BIG_ENDIAN)
+ qedTableConvertToHostEndianess(pL2Entry->paL2Tbl, pImage->cTableEntries);
+#endif
+ qedL2TblCacheEntryInsert(pImage, pL2Entry);
+ }
+ else
+ {
+ qedL2TblCacheEntryRelease(pL2Entry);
+ qedL2TblCacheEntryFree(pImage, pL2Entry);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppL2Entry = pL2Entry;
+
+ return rc;
+}
+
+/**
+ * Return power of 2 or 0 if num error.
+ *
+ * @returns The power of 2 or 0 if the given number is not a power of 2.
+ * @param u32 The number.
+ */
+static uint32_t qedGetPowerOfTwo(uint32_t u32)
+{
+ if (u32 == 0)
+ return 0;
+ uint32_t uPower2 = 0;
+ while ((u32 & 1) == 0)
+ {
+ u32 >>= 1;
+ uPower2++;
+ }
+ return u32 == 1 ? uPower2 : 0;
+}
+
+/**
+ * Sets the L1, L2 and offset bitmasks and L1 and L2 bit shift members.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ */
+static void qedTableMasksInit(PQEDIMAGE pImage)
+{
+ uint32_t cClusterBits, cTableBits;
+
+ cClusterBits = qedGetPowerOfTwo(pImage->cbCluster);
+ cTableBits = qedGetPowerOfTwo(pImage->cTableEntries);
+
+ Assert(cClusterBits + 2 * cTableBits <= 64);
+
+ pImage->fOffsetMask = ((uint64_t)pImage->cbCluster - 1);
+ pImage->fL2Mask = ((uint64_t)pImage->cTableEntries - 1) << cClusterBits;
+ pImage->cL2Shift = cClusterBits;
+ pImage->fL1Mask = ((uint64_t)pImage->cTableEntries - 1) << (cClusterBits + cTableBits);
+ pImage->cL1Shift = cClusterBits + cTableBits;
+}
+
+/**
+ * Converts a given logical offset into the
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ * @param off The logical offset to convert.
+ * @param pidxL1 Where to store the index in the L1 table on success.
+ * @param pidxL2 Where to store the index in the L2 table on success.
+ * @param poffCluster Where to store the offset in the cluster on success.
+ */
+DECLINLINE(void) qedConvertLogicalOffset(PQEDIMAGE pImage, uint64_t off, uint32_t *pidxL1,
+ uint32_t *pidxL2, uint32_t *poffCluster)
+{
+ AssertPtr(pidxL1);
+ AssertPtr(pidxL2);
+ AssertPtr(poffCluster);
+
+ *poffCluster = off & pImage->fOffsetMask;
+ *pidxL1 = (off & pImage->fL1Mask) >> pImage->cL1Shift;
+ *pidxL2 = (off & pImage->fL2Mask) >> pImage->cL2Shift;
+}
+
+/**
+ * Converts Cluster size to a byte size.
+ *
+ * @returns Number of bytes derived from the given number of clusters.
+ * @param pImage The image instance data.
+ * @param cClusters The clusters to convert.
+ */
+DECLINLINE(uint64_t) qedCluster2Byte(PQEDIMAGE pImage, uint64_t cClusters)
+{
+ return cClusters * pImage->cbCluster;
+}
+
+/**
+ * Converts number of bytes to cluster size rounding to the next cluster.
+ *
+ * @returns Number of bytes derived from the given number of clusters.
+ * @param pImage The image instance data.
+ * @param cb Number of bytes to convert.
+ */
+DECLINLINE(uint64_t) qedByte2Cluster(PQEDIMAGE pImage, uint64_t cb)
+{
+ return cb / pImage->cbCluster + (cb % pImage->cbCluster ? 1 : 0);
+}
+
+/**
+ * Allocates a new cluster in the image.
+ *
+ * @returns The start offset of the new cluster in the image.
+ * @param pImage The image instance data.
+ * @param cClusters Number of clusters to allocate.
+ */
+DECLINLINE(uint64_t) qedClusterAllocate(PQEDIMAGE pImage, uint32_t cClusters)
+{
+ uint64_t offCluster;
+
+ offCluster = pImage->cbImage;
+ pImage->cbImage += cClusters*pImage->cbCluster;
+
+ return offCluster;
+}
+
+/**
+ * Returns the real image offset for a given cluster or an error if the cluster is not
+ * yet allocated.
+ *
+ * @returns VBox status code.
+ * VERR_VD_BLOCK_FREE if the cluster is not yet allocated.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param idxL1 The L1 index.
+ * @param idxL2 The L2 index.
+ * @param offCluster Offset inside the cluster.
+ * @param poffImage Where to store the image offset on success;
+ */
+static int qedConvertToImageOffset(PQEDIMAGE pImage, PVDIOCTX pIoCtx,
+ uint32_t idxL1, uint32_t idxL2,
+ uint32_t offCluster, uint64_t *poffImage)
+{
+ int rc = VERR_VD_BLOCK_FREE;
+
+ AssertReturn(idxL1 < pImage->cTableEntries, VERR_INVALID_PARAMETER);
+ AssertReturn(idxL2 < pImage->cTableEntries, VERR_INVALID_PARAMETER);
+
+ if (pImage->paL1Table[idxL1])
+ {
+ PQEDL2CACHEENTRY pL2Entry;
+
+ rc = qedL2TblCacheFetchAsync(pImage, pIoCtx, pImage->paL1Table[idxL1],
+ &pL2Entry);
+ if (RT_SUCCESS(rc))
+ {
+ /* Get real file offset. */
+ if (pL2Entry->paL2Tbl[idxL2])
+ *poffImage = pL2Entry->paL2Tbl[idxL2] + offCluster;
+ else
+ rc = VERR_VD_BLOCK_FREE;
+
+ qedL2TblCacheEntryRelease(pL2Entry);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Write the given table to image converting to the image endianess if required.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param offTbl The offset the table should be written to.
+ * @param paTbl The table to write.
+ * @param pfnComplete Callback called when the write completes.
+ * @param pvUser Opaque user data to pass in the completion callback.
+ */
+static int qedTblWrite(PQEDIMAGE pImage, PVDIOCTX pIoCtx, uint64_t offTbl, uint64_t *paTbl,
+ PFNVDXFERCOMPLETED pfnComplete, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+
+#if defined(RT_BIG_ENDIAN)
+ uint64_t *paTblImg = (uint64_t *)RTMemAllocZ(pImage->cbTable);
+ if (paTblImg)
+ {
+ qedTableConvertFromHostEndianess(paTblImg, paTbl,
+ pImage->cTableEntries);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ offTbl, paTblImg, pImage->cbTable,
+ pIoCtx, pfnComplete, pvUser);
+ RTMemFree(paTblImg);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+#else
+ /* Write table directly. */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ offTbl, paTbl, pImage->cbTable, pIoCtx,
+ pfnComplete, pvUser);
+#endif
+
+ return rc;
+}
+
+/**
+ * Internal. Flush image data to disk.
+ */
+static int qedFlushImage(PQEDIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( pImage->pStorage
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ QedHeader Header;
+
+ Assert(!(pImage->cbTable % pImage->cbCluster));
+#if defined(RT_BIG_ENDIAN)
+ uint64_t *paL1TblImg = (uint64_t *)RTMemAllocZ(pImage->cbTable);
+ if (paL1TblImg)
+ {
+ qedTableConvertFromHostEndianess(paL1TblImg, pImage->paL1Table,
+ pImage->cTableEntries);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offL1Table, paL1TblImg,
+ pImage->cbTable);
+ RTMemFree(paL1TblImg);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+#else
+ /* Write L1 table directly. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, pImage->offL1Table,
+ pImage->paL1Table, pImage->cbTable);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /* Write header. */
+ qedHdrConvertFromHostEndianess(pImage, &Header);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, 0, &Header,
+ sizeof(Header));
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileFlushSync(pImage->pIfIo, pImage->pStorage);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Checks whether the given cluster offset is valid.
+ *
+ * @returns Whether the given cluster offset is valid.
+ * @param offCluster The table offset to check.
+ * @param cbFile The real file size of the image.
+ * @param cbCluster The cluster size in bytes.
+ */
+DECLINLINE(bool) qedIsClusterOffsetValid(uint64_t offCluster, uint64_t cbFile, size_t cbCluster)
+{
+ return (offCluster <= cbFile - cbCluster)
+ && !(offCluster & (cbCluster - 1));
+}
+
+/**
+ * Checks whether the given table offset is valid.
+ *
+ * @returns Whether the given table offset is valid.
+ * @param offTbl The table offset to check.
+ * @param cbFile The real file size of the image.
+ * @param cbTable The table size in bytes.
+ * @param cbCluster The cluster size in bytes.
+ */
+DECLINLINE(bool) qedIsTblOffsetValid(uint64_t offTbl, uint64_t cbFile, size_t cbTable, size_t cbCluster)
+{
+ return (offTbl <= cbFile - cbTable)
+ && !(offTbl & (cbCluster - 1));
+}
+
+/**
+ * Sets the specified range in the cluster bitmap checking whether any of the clusters is already
+ * used before.
+ *
+ * @returns Whether the range was clear and is set now.
+ * @param pvClusterBitmap The cluster bitmap to use.
+ * @param offClusterStart The first cluster to check and set.
+ * @param offClusterEnd The first cluster to not check and set anymore.
+ */
+static bool qedClusterBitmapCheckAndSet(void *pvClusterBitmap, uint32_t offClusterStart, uint32_t offClusterEnd)
+{
+ for (uint32_t offCluster = offClusterStart; offCluster < offClusterEnd; offCluster++)
+ if (ASMBitTest(pvClusterBitmap, offCluster))
+ return false;
+
+ ASMBitSetRange(pvClusterBitmap, offClusterStart, offClusterEnd);
+ return true;
+}
+
+/**
+ * Checks the given image for consistency, usually called when the
+ * QED_FEATURE_NEED_CHECK bit is set.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS when the image can be accessed.
+ * @param pImage The image instance data.
+ * @param pHeader The header to use for checking.
+ *
+ * @note It is not required that the image state is fully initialized Only
+ * The I/O interface and storage handle need to be valid.
+ * @note The header must be converted to the host CPU endian format already
+ * and should be validated already.
+ */
+static int qedCheckImage(PQEDIMAGE pImage, PQedHeader pHeader)
+{
+ uint64_t cbFile;
+ uint32_t cbTable;
+ uint32_t cTableEntries;
+ uint64_t *paL1Tbl = NULL;
+ uint64_t *paL2Tbl = NULL;
+ void *pvClusterBitmap = NULL;
+ uint32_t offClusterStart;
+ int rc = VINF_SUCCESS;
+
+ pImage->cbCluster = pHeader->u32ClusterSize;
+ cbTable = pHeader->u32TableSize * pHeader->u32ClusterSize;
+ cTableEntries = cbTable / sizeof(uint64_t);
+
+ do
+ {
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("Qed: Querying the file size of image '%s' failed"),
+ pImage->pszFilename);
+ break;
+ }
+
+ /* Allocate L1 table. */
+ paL1Tbl = (uint64_t *)RTMemAllocZ(cbTable);
+ if (!paL1Tbl)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("Qed: Allocating memory for the L1 table for image '%s' failed"),
+ pImage->pszFilename);
+ break;
+ }
+
+ paL2Tbl = (uint64_t *)RTMemAllocZ(cbTable);
+ if (!paL2Tbl)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("Qed: Allocating memory for the L2 table for image '%s' failed"),
+ pImage->pszFilename);
+ break;
+ }
+
+ pvClusterBitmap = RTMemAllocZ(cbFile / pHeader->u32ClusterSize / 8);
+ if (!pvClusterBitmap)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("Qed: Allocating memory for the cluster bitmap for image '%s' failed"),
+ pImage->pszFilename);
+ break;
+ }
+
+ /* Validate L1 table offset. */
+ if (!qedIsTblOffsetValid(pHeader->u64OffL1Table, cbFile, cbTable, pHeader->u32ClusterSize))
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ N_("Qed: L1 table offset of image '%s' is corrupt (%llu)"),
+ pImage->pszFilename, pHeader->u64OffL1Table);
+ break;
+ }
+
+ /* Read L1 table. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ pHeader->u64OffL1Table, paL1Tbl, cbTable);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ N_("Qed: Reading the L1 table from image '%s' failed"),
+ pImage->pszFilename);
+ break;
+ }
+
+ /* Mark the L1 table in cluster bitmap. */
+ ASMBitSet(pvClusterBitmap, 0); /* Header is always in cluster 0. */
+ offClusterStart = qedByte2Cluster(pImage, pHeader->u64OffL1Table);
+ bool fSet = qedClusterBitmapCheckAndSet(pvClusterBitmap, offClusterStart, offClusterStart + pHeader->u32TableSize);
+ Assert(fSet);
+
+ /* Scan the L1 and L2 tables for invalid entries. */
+ qedTableConvertToHostEndianess(paL1Tbl, cTableEntries);
+
+ for (unsigned iL1 = 0; iL1 < cTableEntries; iL1++)
+ {
+ if (!paL1Tbl[iL1])
+ continue; /* Skip unallocated clusters. */
+
+ if (!qedIsTblOffsetValid(paL1Tbl[iL1], cbFile, cbTable, pHeader->u32ClusterSize))
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ N_("Qed: Entry %d of the L1 table from image '%s' is invalid (%llu)"),
+ iL1, pImage->pszFilename, paL1Tbl[iL1]);
+ break;
+ }
+
+ /* Now check that the clusters are not allocated already. */
+ offClusterStart = qedByte2Cluster(pImage, paL1Tbl[iL1]);
+ fSet = qedClusterBitmapCheckAndSet(pvClusterBitmap, offClusterStart, offClusterStart + pHeader->u32TableSize);
+ if (!fSet)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ N_("Qed: Entry %d of the L1 table from image '%s' points to a already used cluster (%llu)"),
+ iL1, pImage->pszFilename, paL1Tbl[iL1]);
+ break;
+ }
+
+ /* Read the linked L2 table and check it. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ paL1Tbl[iL1], paL2Tbl, cbTable);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("Qed: Reading the L2 table from image '%s' failed"),
+ pImage->pszFilename);
+ break;
+ }
+
+ /* Check all L2 entries. */
+ for (unsigned iL2 = 0; iL2 < cTableEntries; iL2++)
+ {
+ if (paL2Tbl[iL2])
+ continue; /* Skip unallocated clusters. */
+
+ if (!qedIsClusterOffsetValid(paL2Tbl[iL2], cbFile, pHeader->u32ClusterSize))
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ N_("Qed: Entry %d of the L2 table from image '%s' is invalid (%llu)"),
+ iL2, pImage->pszFilename, paL2Tbl[iL2]);
+ break;
+ }
+
+ /* Now check that the clusters are not allocated already. */
+ offClusterStart = qedByte2Cluster(pImage, paL2Tbl[iL2]);
+ fSet = qedClusterBitmapCheckAndSet(pvClusterBitmap, offClusterStart, offClusterStart + 1);
+ if (!fSet)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ N_("Qed: Entry %d of the L2 table from image '%s' points to a already used cluster (%llu)"),
+ iL2, pImage->pszFilename, paL2Tbl[iL2]);
+ break;
+ }
+ }
+ }
+ } while(0);
+
+ if (paL1Tbl)
+ RTMemFree(paL1Tbl);
+ if (paL2Tbl)
+ RTMemFree(paL2Tbl);
+ if (pvClusterBitmap)
+ RTMemFree(pvClusterBitmap);
+
+ return rc;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image except pImage,
+ * and optionally delete the image from disk.
+ */
+static int qedFreeImage(PQEDIMAGE pImage, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ if (pImage->pStorage)
+ {
+ /* No point updating the file that is deleted anyway. */
+ if (!fDelete)
+ qedFlushImage(pImage);
+
+ rc = vdIfIoIntFileClose(pImage->pIfIo, pImage->pStorage);
+ pImage->pStorage = NULL;
+ }
+
+ if (pImage->paL1Table)
+ RTMemFree(pImage->paL1Table);
+
+ if (pImage->pszBackingFilename)
+ {
+ RTStrFree(pImage->pszBackingFilename);
+ pImage->pszBackingFilename = NULL;
+ }
+
+ qedL2TblCacheDestroy(pImage);
+
+ if (fDelete && pImage->pszFilename)
+ vdIfIoIntFileDelete(pImage->pIfIo, pImage->pszFilename);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int qedOpenImage(PQEDIMAGE pImage, unsigned uOpenFlags)
+{
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /*
+ * Create the L2 cache before opening the image so we can call qedFreeImage()
+ * even if opening the image file fails.
+ */
+ int rc = qedL2TblCacheCreate(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Open the image. */
+ rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags,
+ false /* fCreate */),
+ &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile;
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile > sizeof(QedHeader))
+ {
+ QedHeader Header;
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, 0, &Header, sizeof(Header));
+ if ( RT_SUCCESS(rc)
+ && qedHdrConvertToHostEndianess(&Header))
+ {
+ if ( !(Header.u64FeatureFlags & ~QED_FEATURE_MASK)
+ && !(Header.u64FeatureFlags & QED_FEATURE_BACKING_FILE_NO_PROBE))
+ {
+ if (Header.u64FeatureFlags & QED_FEATURE_NEED_CHECK)
+ {
+ /* Image needs checking. */
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rc = qedCheckImage(pImage, &Header);
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("Qed: Image '%s' needs checking but is opened readonly"),
+ pImage->pszFilename);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && (Header.u64FeatureFlags & QED_FEATURE_BACKING_FILE))
+ {
+ /* Load backing filename from image. */
+ pImage->pszBackingFilename = RTStrAlloc(Header.u32BackingFilenameSize + 1); /* +1 for \0 terminator. */
+ if (pImage->pszBackingFilename)
+ {
+ RT_BZERO(pImage->pszBackingFilename, Header.u32BackingFilenameSize + 1);
+ pImage->cbBackingFilename = Header.u32BackingFilenameSize;
+ pImage->offBackingFilename = Header.u32OffBackingFilename;
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ Header.u32OffBackingFilename, pImage->pszBackingFilename,
+ Header.u32BackingFilenameSize);
+ if (RT_SUCCESS(rc))
+ rc = RTStrValidateEncoding(pImage->pszBackingFilename);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pImage->cbImage = cbFile;
+ pImage->cbCluster = Header.u32ClusterSize;
+ pImage->cbTable = Header.u32TableSize * pImage->cbCluster;
+ pImage->cTableEntries = pImage->cbTable / sizeof(uint64_t);
+ pImage->offL1Table = Header.u64OffL1Table;
+ pImage->cbSize = Header.u64Size;
+ qedTableMasksInit(pImage);
+
+ /* Allocate L1 table. */
+ pImage->paL1Table = (uint64_t *)RTMemAllocZ(pImage->cbTable);
+ if (pImage->paL1Table)
+ {
+ /* Read from the image. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offL1Table, pImage->paL1Table,
+ pImage->cbTable);
+ if (RT_SUCCESS(rc))
+ {
+ qedTableConvertToHostEndianess(pImage->paL1Table, pImage->cTableEntries);
+
+ /* If the consistency check succeeded, clear the flag by flushing the image. */
+ if (Header.u64FeatureFlags & QED_FEATURE_NEED_CHECK)
+ rc = qedFlushImage(pImage);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("Qed: Reading the L1 table for image '%s' failed"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("Qed: Out of memory allocating L1 table for image '%s'"),
+ pImage->pszFilename);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("Qed: The image '%s' makes use of unsupported features"),
+ pImage->pszFilename);
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ /* else: Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("Qed: Creating the L2 table cache for image '%s' failed"),
+ pImage->pszFilename);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ qedFreeImage(pImage, false);
+ return rc;
+}
+
+/**
+ * Internal: Create a qed image.
+ */
+static int qedCreateImage(PQEDIMAGE pImage, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry, unsigned uOpenFlags,
+ PVDINTERFACEPROGRESS pIfProgress,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ RT_NOREF1(pszComment);
+ int rc;
+
+ if (!(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ rc = qedL2TblCacheCreate(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ pImage->uOpenFlags = uOpenFlags & ~VD_OPEN_FLAGS_READONLY;
+ pImage->uImageFlags = uImageFlags;
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ pImage->LCHSGeometry = *pLCHSGeometry;
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /* Create image file. */
+ uint32_t fOpen = VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags, true /* fCreate */);
+ rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename, fOpen, &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Init image state. */
+ pImage->cbSize = cbSize;
+ pImage->cbCluster = QED_CLUSTER_SIZE_DEFAULT;
+ pImage->cbTable = qedCluster2Byte(pImage, QED_TABLE_SIZE_DEFAULT);
+ pImage->cTableEntries = pImage->cbTable / sizeof(uint64_t);
+ pImage->offL1Table = qedCluster2Byte(pImage, 1); /* Cluster 0 is the header. */
+ pImage->cbImage = (1 * pImage->cbCluster) + pImage->cbTable; /* Header + L1 table size. */
+ pImage->cbBackingFilename = 0;
+ pImage->offBackingFilename = 0;
+ qedTableMasksInit(pImage);
+
+ /* Init L1 table. */
+ pImage->paL1Table = (uint64_t *)RTMemAllocZ(pImage->cbTable);
+ if (RT_LIKELY(pImage->paL1Table))
+ {
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan * 98 / 100);
+ rc = qedFlushImage(pImage);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS, N_("Qed: cannot allocate memory for L1 table of image '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("Qed: cannot create image '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("Qed: Failed to create L2 cache for image '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_INVALID_TYPE, RT_SRC_POS, N_("Qed: cannot create fixed image '%s'"), pImage->pszFilename);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+ }
+ else
+ qedFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+
+ return rc;
+}
+
+/**
+ * Rollback anything done during async cluster allocation.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param pClusterAlloc The cluster allocation to rollback.
+ */
+static int qedAsyncClusterAllocRollback(PQEDIMAGE pImage, PVDIOCTX pIoCtx, PQEDCLUSTERASYNCALLOC pClusterAlloc)
+{
+ RT_NOREF1(pIoCtx);
+ int rc = VINF_SUCCESS;
+
+ switch (pClusterAlloc->enmAllocState)
+ {
+ case QEDCLUSTERASYNCALLOCSTATE_L2_ALLOC:
+ case QEDCLUSTERASYNCALLOCSTATE_L2_LINK:
+ {
+ /* Revert the L1 table entry */
+ pImage->paL1Table[pClusterAlloc->idxL1] = 0;
+ pImage->pL2TblAlloc = NULL;
+
+ /* Assumption right now is that the L1 table is not modified on storage if the link fails. */
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pClusterAlloc->cbImageOld);
+ qedL2TblCacheEntryRelease(pClusterAlloc->pL2Entry); /* Release L2 cache entry. */
+ Assert(!pClusterAlloc->pL2Entry->cRefs);
+ qedL2TblCacheEntryFree(pImage, pClusterAlloc->pL2Entry); /* Free it, it is not in the cache yet. */
+ break;
+ }
+ case QEDCLUSTERASYNCALLOCSTATE_USER_ALLOC:
+ case QEDCLUSTERASYNCALLOCSTATE_USER_LINK:
+ {
+ /* Assumption right now is that the L2 table is not modified if the link fails. */
+ pClusterAlloc->pL2Entry->paL2Tbl[pClusterAlloc->idxL2] = 0;
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pClusterAlloc->cbImageOld);
+ qedL2TblCacheEntryRelease(pClusterAlloc->pL2Entry); /* Release L2 cache entry. */
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid cluster allocation state %d\n", pClusterAlloc->enmAllocState));
+ rc = VERR_INVALID_STATE;
+ }
+
+ RTMemFree(pClusterAlloc);
+ return rc;
+}
+
+/**
+ * Updates the state of the async cluster allocation.
+ *
+ * @returns VBox status code.
+ * @param pBackendData The opaque backend data.
+ * @param pIoCtx I/O context associated with this request.
+ * @param pvUser Opaque user data passed during a read/write request.
+ * @param rcReq Status code for the completed request.
+ */
+static DECLCALLBACK(int) qedAsyncClusterAllocUpdate(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ int rc = VINF_SUCCESS;
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ PQEDCLUSTERASYNCALLOC pClusterAlloc = (PQEDCLUSTERASYNCALLOC)pvUser;
+
+ if (RT_FAILURE(rcReq))
+ return qedAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+
+ AssertPtr(pClusterAlloc->pL2Entry);
+
+ switch (pClusterAlloc->enmAllocState)
+ {
+ case QEDCLUSTERASYNCALLOCSTATE_L2_ALLOC:
+ {
+ /* Update the link in the in memory L1 table now. */
+ pImage->paL1Table[pClusterAlloc->idxL1] = pClusterAlloc->pL2Entry->offL2Tbl;
+
+ /* Update the link in the on disk L1 table now. */
+ pClusterAlloc->enmAllocState = QEDCLUSTERASYNCALLOCSTATE_L2_LINK;
+ rc = qedTblWrite(pImage, pIoCtx, pImage->offL1Table, pImage->paL1Table,
+ qedAsyncClusterAllocUpdate, pClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ /* Rollback. */
+ qedAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+ break;
+ }
+ }
+ RT_FALL_THRU();
+ case QEDCLUSTERASYNCALLOCSTATE_L2_LINK:
+ {
+ /* L2 link updated in L1 , save L2 entry in cache and allocate new user data cluster. */
+ uint64_t offData = qedClusterAllocate(pImage, 1);
+
+ pImage->pL2TblAlloc = NULL;
+ qedL2TblCacheEntryInsert(pImage, pClusterAlloc->pL2Entry);
+
+ pClusterAlloc->enmAllocState = QEDCLUSTERASYNCALLOCSTATE_USER_ALLOC;
+ pClusterAlloc->cbImageOld = offData;
+ pClusterAlloc->offClusterNew = offData;
+
+ /* Write data. */
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ offData, pIoCtx, pClusterAlloc->cbToWrite,
+ qedAsyncClusterAllocUpdate, pClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ qedAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+ RTMemFree(pClusterAlloc);
+ break;
+ }
+ }
+ RT_FALL_THRU();
+ case QEDCLUSTERASYNCALLOCSTATE_USER_ALLOC:
+ {
+ pClusterAlloc->enmAllocState = QEDCLUSTERASYNCALLOCSTATE_USER_LINK;
+ pClusterAlloc->pL2Entry->paL2Tbl[pClusterAlloc->idxL2] = pClusterAlloc->offClusterNew;
+
+ /* Link L2 table and update it. */
+ rc = qedTblWrite(pImage, pIoCtx, pImage->paL1Table[pClusterAlloc->idxL1],
+ pClusterAlloc->pL2Entry->paL2Tbl,
+ qedAsyncClusterAllocUpdate, pClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ qedAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+ RTMemFree(pClusterAlloc);
+ break;
+ }
+ }
+ RT_FALL_THRU();
+ case QEDCLUSTERASYNCALLOCSTATE_USER_LINK:
+ {
+ /* Everything done without errors, signal completion. */
+ qedL2TblCacheEntryRelease(pClusterAlloc->pL2Entry);
+ RTMemFree(pClusterAlloc);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid async cluster allocation state %d\n",
+ pClusterAlloc->enmAllocState));
+ }
+
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) qedProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pVDIfsDisk, enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
+ PVDIOSTORAGE pStorage = NULL;
+ int rc = VINF_SUCCESS;
+
+ /* Get I/O interface. */
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ /*
+ * Open the file and read the footer.
+ */
+ rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile;
+
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile > sizeof(QedHeader))
+ {
+ QedHeader Header;
+
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, &Header, sizeof(Header));
+ if ( RT_SUCCESS(rc)
+ && qedHdrConvertToHostEndianess(&Header))
+ *penmType = VDTYPE_HDD;
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+
+ if (pStorage)
+ vdIfIoIntFileClose(pIfIo, pStorage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) qedOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ RT_NOREF1(enmType); /**< @todo r=klaus make use of the type info. */
+
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ PQEDIMAGE pImage = (PQEDIMAGE)RTMemAllocZ(RT_UOFFSETOF(QEDIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = qedOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ else
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnCreate */
+static DECLCALLBACK(int) qedCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%d ppBackendData=%#p",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+ int rc;
+
+ /* Check the VD container type. */
+ if (enmType != VDTYPE_HDD)
+ return VERR_VD_INVALID_TYPE;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER);
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+
+ PQEDIMAGE pImage = (PQEDIMAGE)RTMemAllocZ(RT_UOFFSETOF(QEDIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = qedCreateImage(pImage, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, uOpenFlags,
+ pIfProgress, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ qedFreeImage(pImage, false);
+ rc = qedOpenImage(pImage, uOpenFlags);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRename */
+static DECLCALLBACK(int) qedRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VINF_SUCCESS;
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+
+ /* Check arguments. */
+ AssertReturn((pImage && pszFilename && *pszFilename), VERR_INVALID_PARAMETER);
+
+ /* Close the image. */
+ rc = qedFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ {
+ /* Rename the file. */
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pszFilename, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the old image with new name. */
+ rc = qedOpenImage(pImage, pImage->uOpenFlags);
+ }
+ else
+ {
+ /* The move failed, try to reopen the original image. */
+ int rc2 = qedOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) qedClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+
+ int rc = qedFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRead */
+static DECLCALLBACK(int) qedRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToRead=%zu pcbActuallyRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ uint32_t offCluster = 0;
+ uint32_t idxL1 = 0;
+ uint32_t idxL2 = 0;
+ uint64_t offFile = 0;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToRead % 512 == 0);
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToRead <= pImage->cbSize, VERR_INVALID_PARAMETER);
+
+ qedConvertLogicalOffset(pImage, uOffset, &idxL1, &idxL2, &offCluster);
+
+ /* Clip read size to remain in the cluster. */
+ cbToRead = RT_MIN(cbToRead, pImage->cbCluster - offCluster);
+
+ /* Get offset in image. */
+ int rc = qedConvertToImageOffset(pImage, pIoCtx, idxL1, idxL2, offCluster, &offFile);
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, offFile,
+ pIoCtx, cbToRead);
+
+ if ( ( RT_SUCCESS(rc)
+ || rc == VERR_VD_BLOCK_FREE
+ || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ && pcbActuallyRead)
+ *pcbActuallyRead = cbToRead;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnWrite */
+static DECLCALLBACK(int) qedWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ uint32_t offCluster = 0;
+ uint32_t idxL1 = 0;
+ uint32_t idxL2 = 0;
+ uint64_t offImage = 0;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbToWrite % 512));
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToWrite, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToWrite <= pImage->cbSize, VERR_INVALID_PARAMETER);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* Convert offset to L1, L2 index and cluster offset. */
+ qedConvertLogicalOffset(pImage, uOffset, &idxL1, &idxL2, &offCluster);
+
+ /* Clip write size to remain in the cluster. */
+ cbToWrite = RT_MIN(cbToWrite, pImage->cbCluster - offCluster);
+ Assert(!(cbToWrite % 512));
+
+ /* Get offset in image. */
+ rc = qedConvertToImageOffset(pImage, pIoCtx, idxL1, idxL2, offCluster, &offImage);
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ offImage, pIoCtx, cbToWrite, NULL, NULL);
+ else if (rc == VERR_VD_BLOCK_FREE)
+ {
+ if ( cbToWrite == pImage->cbCluster
+ && !(fWrite & VD_WRITE_NO_ALLOC))
+ {
+ PQEDL2CACHEENTRY pL2Entry = NULL;
+
+ /* Full cluster write to previously unallocated cluster.
+ * Allocate cluster and write data. */
+ Assert(!offCluster);
+
+ do
+ {
+ /* Check if we have to allocate a new cluster for L2 tables. */
+ if (!pImage->paL1Table[idxL1])
+ {
+ uint64_t offL2Tbl;
+ PQEDCLUSTERASYNCALLOC pL2ClusterAlloc = NULL;
+
+ /* Allocate new async cluster allocation state. */
+ pL2ClusterAlloc = (PQEDCLUSTERASYNCALLOC)RTMemAllocZ(sizeof(QEDCLUSTERASYNCALLOC));
+ if (RT_UNLIKELY(!pL2ClusterAlloc))
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pL2Entry = qedL2TblCacheEntryAlloc(pImage);
+ if (!pL2Entry)
+ {
+ rc = VERR_NO_MEMORY;
+ RTMemFree(pL2ClusterAlloc);
+ break;
+ }
+
+ offL2Tbl = qedClusterAllocate(pImage, qedByte2Cluster(pImage, pImage->cbTable));
+ pL2Entry->offL2Tbl = offL2Tbl;
+ memset(pL2Entry->paL2Tbl, 0, pImage->cbTable);
+
+ pL2ClusterAlloc->enmAllocState = QEDCLUSTERASYNCALLOCSTATE_L2_ALLOC;
+ pL2ClusterAlloc->cbImageOld = offL2Tbl;
+ pL2ClusterAlloc->offClusterNew = offL2Tbl;
+ pL2ClusterAlloc->idxL1 = idxL1;
+ pL2ClusterAlloc->idxL2 = idxL2;
+ pL2ClusterAlloc->cbToWrite = cbToWrite;
+ pL2ClusterAlloc->pL2Entry = pL2Entry;
+
+ pImage->pL2TblAlloc = pL2Entry;
+
+ LogFlowFunc(("Allocating new L2 table at cluster offset %llu\n", offL2Tbl));
+
+ /*
+ * Write the L2 table first and link to the L1 table afterwards.
+ * If something unexpected happens the worst case which can happen
+ * is a leak of some clusters.
+ */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ offL2Tbl, pL2Entry->paL2Tbl, pImage->cbTable, pIoCtx,
+ qedAsyncClusterAllocUpdate, pL2ClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ RTMemFree(pL2ClusterAlloc);
+ qedL2TblCacheEntryFree(pImage, pL2Entry);
+ break;
+ }
+
+ rc = qedAsyncClusterAllocUpdate(pImage, pIoCtx, pL2ClusterAlloc, rc);
+ }
+ else
+ {
+ LogFlowFunc(("Fetching L2 table at cluster offset %llu\n", pImage->paL1Table[idxL1]));
+
+ rc = qedL2TblCacheFetchAsync(pImage, pIoCtx, pImage->paL1Table[idxL1],
+ &pL2Entry);
+
+ if (RT_SUCCESS(rc))
+ {
+ PQEDCLUSTERASYNCALLOC pDataClusterAlloc = NULL;
+
+ /* Allocate new async cluster allocation state. */
+ pDataClusterAlloc = (PQEDCLUSTERASYNCALLOC)RTMemAllocZ(sizeof(QEDCLUSTERASYNCALLOC));
+ if (RT_UNLIKELY(!pDataClusterAlloc))
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Allocate new cluster for the data. */
+ uint64_t offData = qedClusterAllocate(pImage, 1);
+
+ pDataClusterAlloc->enmAllocState = QEDCLUSTERASYNCALLOCSTATE_USER_ALLOC;
+ pDataClusterAlloc->cbImageOld = offData;
+ pDataClusterAlloc->offClusterNew = offData;
+ pDataClusterAlloc->idxL1 = idxL1;
+ pDataClusterAlloc->idxL2 = idxL2;
+ pDataClusterAlloc->cbToWrite = cbToWrite;
+ pDataClusterAlloc->pL2Entry = pL2Entry;
+
+ /* Write data. */
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ offData, pIoCtx, cbToWrite,
+ qedAsyncClusterAllocUpdate, pDataClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ RTMemFree(pDataClusterAlloc);
+ break;
+ }
+
+ rc = qedAsyncClusterAllocUpdate(pImage, pIoCtx, pDataClusterAlloc, rc);
+ }
+ }
+
+ } while (0);
+
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+ }
+ else
+ {
+ /* Trying to do a partial write to an unallocated cluster. Don't do
+ * anything except letting the upper layer know what to do. */
+ *pcbPreRead = offCluster;
+ *pcbPostRead = pImage->cbCluster - cbToWrite - *pcbPreRead;
+ }
+ }
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnFlush */
+static DECLCALLBACK(int) qedFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ AssertPtrReturn(pIoCtx, VERR_INVALID_PARAMETER);
+
+ if ( pImage->pStorage
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ QedHeader Header;
+
+ Assert(!(pImage->cbTable % pImage->cbCluster));
+ rc = qedTblWrite(pImage, pIoCtx, pImage->offL1Table, pImage->paL1Table,
+ NULL, NULL);
+ if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ /* Write header. */
+ qedHdrConvertFromHostEndianess(pImage, &Header);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ 0, &Header, sizeof(Header),
+ pIoCtx, NULL, NULL);
+ if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = vdIfIoIntFileFlush(pImage->pIfIo, pImage->pStorage,
+ pIoCtx, NULL, NULL);
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) qedGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ return 1;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) qedGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtrReturn(pImage, 0);
+
+ uint64_t cbFile;
+ if (pImage->pStorage)
+ {
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb += cbFile;
+ }
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) qedGetPCHSGeometry(void *pBackendData,
+ PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->PCHSGeometry.cCylinders)
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) qedSetPCHSGeometry(void *pBackendData,
+ PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->PCHSGeometry = *pPCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) qedGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->LCHSGeometry.cCylinders)
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders,
+ pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) qedSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData,
+ pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->LCHSGeometry = *pLCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) qedQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PQEDIMAGE pThis = (PQEDIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) qedRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PQEDIMAGE pThis = (PQEDIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) qedGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uImageFlags));
+ return pImage->uImageFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) qedGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uOpenFlags));
+ return pImage->uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) qedSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pImage || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ rc = qedFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ rc = qedOpenImage(pImage, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(qedGetComment);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(qedSetComment, PQEDIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qedGetUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qedSetUuid, PQEDIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qedGetModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qedSetModificationUuid, PQEDIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qedGetParentUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qedSetParentUuid, PQEDIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qedGetParentModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qedSetParentModificationUuid, PQEDIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) qedDump(void *pBackendData)
+{
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pImage);
+ vdIfErrorMessage(pImage->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cbSector=%llu\n",
+ pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors,
+ pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors,
+ pImage->cbSize / 512);
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentFilename */
+static DECLCALLBACK(int) qedGetParentFilename(void *pBackendData, char **ppszParentFilename)
+{
+ int rc = VINF_SUCCESS;
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->pszBackingFilename)
+ *ppszParentFilename = RTStrDup(pImage->pszBackingFilename);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentFilename */
+static DECLCALLBACK(int) qedSetParentFilename(void *pBackendData, const char *pszParentFilename)
+{
+ int rc = VINF_SUCCESS;
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else if ( pImage->pszBackingFilename
+ && (strlen(pszParentFilename) > pImage->cbBackingFilename))
+ rc = VERR_NOT_SUPPORTED; /* The new filename is longer than the old one. */
+ else
+ {
+ if (pImage->pszBackingFilename)
+ RTStrFree(pImage->pszBackingFilename);
+ pImage->pszBackingFilename = RTStrDup(pszParentFilename);
+ if (!pImage->pszBackingFilename)
+ rc = VERR_NO_STR_MEMORY;
+ else
+ {
+ if (!pImage->offBackingFilename)
+ {
+ /* Allocate new cluster. */
+ uint64_t offData = qedClusterAllocate(pImage, 1);
+
+ Assert((offData & UINT32_MAX) == offData);
+ pImage->offBackingFilename = (uint32_t)offData;
+ pImage->cbBackingFilename = (uint32_t)strlen(pszParentFilename);
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage,
+ offData + pImage->cbCluster);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offBackingFilename,
+ pImage->pszBackingFilename,
+ strlen(pImage->pszBackingFilename));
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnResize */
+static DECLCALLBACK(int) qedResize(void *pBackendData, uint64_t cbSize,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation)
+{
+ RT_NOREF7(pPCHSGeometry, pLCHSGeometry, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation);
+ PQEDIMAGE pImage = (PQEDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Making the image smaller is not supported at the moment. */
+ if (cbSize < pImage->cbSize)
+ rc = VERR_NOT_SUPPORTED;
+ else if (cbSize > pImage->cbSize)
+ {
+ /*
+ * It is enough to just update the size field in the header to complete
+ * growing. With the default cluster and table sizes the image can be expanded
+ * to 64TB without overflowing the L1 and L2 tables making block relocation
+ * superfluous.
+ * @todo: The rare case where block relocation is still required (non default
+ * table and/or cluster size or images with more than 64TB) is not
+ * implemented yet and resizing such an image will fail with an error.
+ */
+ if (qedByte2Cluster(pImage, pImage->cbTable)*pImage->cTableEntries*pImage->cTableEntries*pImage->cbCluster < cbSize)
+ rc = vdIfError(pImage->pIfError, VERR_BUFFER_OVERFLOW, RT_SRC_POS,
+ N_("Qed: Resizing the image '%s' is not supported because it would overflow the L1 and L2 table\n"),
+ pImage->pszFilename);
+ else
+ {
+ uint64_t cbSizeOld = pImage->cbSize;
+
+ pImage->cbSize = cbSize;
+ rc = qedFlushImage(pImage);
+ if (RT_FAILURE(rc))
+ {
+ pImage->cbSize = cbSizeOld; /* Restore */
+
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("Qed: Resizing the image '%s' failed\n"),
+ pImage->pszFilename);
+ }
+ }
+ }
+ /* Same size doesn't change the image at all. */
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+const VDIMAGEBACKEND g_QedBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "QED",
+ /* uBackendCaps */
+ VD_CAP_FILE | VD_CAP_VFS | VD_CAP_CREATE_DYNAMIC | VD_CAP_DIFF | VD_CAP_ASYNC,
+ /* paFileExtensions */
+ s_aQedFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ qedProbe,
+ /* pfnOpen */
+ qedOpen,
+ /* pfnCreate */
+ qedCreate,
+ /* pfnRename */
+ qedRename,
+ /* pfnClose */
+ qedClose,
+ /* pfnRead */
+ qedRead,
+ /* pfnWrite */
+ qedWrite,
+ /* pfnFlush */
+ qedFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ qedGetVersion,
+ /* pfnGetFileSize */
+ qedGetFileSize,
+ /* pfnGetPCHSGeometry */
+ qedGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ qedSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ qedGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ qedSetLCHSGeometry,
+ /* pfnQueryRegions */
+ qedQueryRegions,
+ /* pfnRegionListRelease */
+ qedRegionListRelease,
+ /* pfnGetImageFlags */
+ qedGetImageFlags,
+ /* pfnGetOpenFlags */
+ qedGetOpenFlags,
+ /* pfnSetOpenFlags */
+ qedSetOpenFlags,
+ /* pfnGetComment */
+ qedGetComment,
+ /* pfnSetComment */
+ qedSetComment,
+ /* pfnGetUuid */
+ qedGetUuid,
+ /* pfnSetUuid */
+ qedSetUuid,
+ /* pfnGetModificationUuid */
+ qedGetModificationUuid,
+ /* pfnSetModificationUuid */
+ qedSetModificationUuid,
+ /* pfnGetParentUuid */
+ qedGetParentUuid,
+ /* pfnSetParentUuid */
+ qedSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ qedGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ qedSetParentModificationUuid,
+ /* pfnDump */
+ qedDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ qedGetParentFilename,
+ /* pfnSetParentFilename */
+ qedSetParentFilename,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ qedResize,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32Version */
+ VD_IMGBACKEND_VERSION
+};
diff --git a/src/VBox/Storage/RAW.cpp b/src/VBox/Storage/RAW.cpp
new file mode 100644
index 00000000..5a09eb44
--- /dev/null
+++ b/src/VBox/Storage/RAW.cpp
@@ -0,0 +1,1223 @@
+/* $Id: RAW.cpp $ */
+/** @file
+ * RawHDDCore - Raw Disk image, Core Code.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_RAW
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/path.h>
+#include <iprt/formats/iso9660.h>
+#include <iprt/formats/udf.h>
+
+#include "VDBackends.h"
+#include "VDBackendsInline.h"
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Raw image data structure.
+ */
+typedef struct RAWIMAGE
+{
+ /** Image name. */
+ const char *pszFilename;
+ /** Storage handle. */
+ PVDIOSTORAGE pStorage;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+
+ /** Open flags passed by VBoxHD layer. */
+ unsigned uOpenFlags;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+ /** Position in the image (only truly used for sequential access). */
+ uint64_t offAccess;
+ /** Flag if this is a newly created image. */
+ bool fCreate;
+ /** Physical geometry of this image. */
+ VDGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ VDGEOMETRY LCHSGeometry;
+ /** Sector size of the image. */
+ uint32_t cbSector;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} RAWIMAGE, *PRAWIMAGE;
+
+
+/** Size of write operations when filling an image with zeroes. */
+#define RAW_FILL_SIZE (128 * _1K)
+
+/** The maximum reasonable size of a floppy image (big format 2.88MB medium). */
+#define RAW_MAX_FLOPPY_IMG_SIZE (512 * 82 * 48 * 2)
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aRawFileExtensions[] =
+{
+ {"iso", VDTYPE_OPTICAL_DISC},
+ {"cdr", VDTYPE_OPTICAL_DISC},
+ {"img", VDTYPE_FLOPPY},
+ {"ima", VDTYPE_FLOPPY},
+ {"dsk", VDTYPE_FLOPPY},
+ {"flp", VDTYPE_FLOPPY},
+ {"vfd", VDTYPE_FLOPPY},
+ {NULL, VDTYPE_INVALID}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Internal. Flush image data to disk.
+ */
+static int rawFlushImage(PRAWIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( pImage->pStorage
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rc = vdIfIoIntFileFlushSync(pImage->pIfIo, pImage->pStorage);
+
+ return rc;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image except pImage,
+ * and optionally delete the image from disk.
+ */
+static int rawFreeImage(PRAWIMAGE pImage, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ if (pImage->pStorage)
+ {
+ /* No point updating the file that is deleted anyway. */
+ if (!fDelete)
+ {
+ /* For newly created images in sequential mode fill it to
+ * the nominal size. */
+ if ( pImage->uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && pImage->fCreate)
+ {
+ /* Fill rest of image with zeroes, a must for sequential
+ * images to reach the nominal size. */
+ uint64_t uOff;
+ void *pvBuf = RTMemTmpAllocZ(RAW_FILL_SIZE);
+ if (RT_LIKELY(pvBuf))
+ {
+ uOff = pImage->offAccess;
+ /* Write data to all image blocks. */
+ while (uOff < pImage->cbSize)
+ {
+ unsigned cbChunk = (unsigned)RT_MIN(pImage->cbSize - uOff,
+ RAW_FILL_SIZE);
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ uOff, pvBuf, cbChunk);
+ if (RT_FAILURE(rc))
+ break;
+
+ uOff += cbChunk;
+ }
+
+ RTMemTmpFree(pvBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ rawFlushImage(pImage);
+ }
+
+ rc = vdIfIoIntFileClose(pImage->pIfIo, pImage->pStorage);
+ pImage->pStorage = NULL;
+ }
+
+ if (fDelete && pImage->pszFilename)
+ vdIfIoIntFileDelete(pImage->pIfIo, pImage->pszFilename);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int rawOpenImage(PRAWIMAGE pImage, unsigned uOpenFlags)
+{
+ pImage->uOpenFlags = uOpenFlags;
+ pImage->fCreate = false;
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /* Open the image. */
+ int rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags,
+ false /* fCreate */),
+ &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &pImage->cbSize);
+ if ( RT_SUCCESS(rc)
+ && !(pImage->cbSize % 512))
+ pImage->uImageFlags |= VD_IMAGE_FLAGS_FIXED;
+ else if (RT_SUCCESS(rc))
+ rc = VERR_VD_RAW_SIZE_MODULO_512;
+ }
+ /* else: Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = pImage->cbSector;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = pImage->cbSector;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ rawFreeImage(pImage, false);
+ return rc;
+}
+
+/**
+ * Internal: Create a raw image.
+ */
+static int rawCreateImage(PRAWIMAGE pImage, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry, unsigned uOpenFlags,
+ PVDINTERFACEPROGRESS pIfProgress,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ RT_NOREF1(pszComment);
+ int rc = VINF_SUCCESS;
+
+ pImage->fCreate = true;
+ pImage->uOpenFlags = uOpenFlags & ~VD_OPEN_FLAGS_READONLY;
+ pImage->uImageFlags = uImageFlags | VD_IMAGE_FLAGS_FIXED;
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ if (!(pImage->uImageFlags & VD_IMAGE_FLAGS_DIFF))
+ {
+ /* Create image file. */
+ uint32_t fOpen = VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags, true /* fCreate */);
+ if (uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL)
+ fOpen &= ~RTFILE_O_READ;
+ rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename, fOpen, &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ if (!(uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL))
+ {
+ RTFOFF cbFree = 0;
+
+ /* Check the free space on the disk and leave early if there is not
+ * sufficient space available. */
+ rc = vdIfIoIntFileGetFreeSpace(pImage->pIfIo, pImage->pszFilename, &cbFree);
+ if (RT_FAILURE(rc) /* ignore errors */ || ((uint64_t)cbFree >= cbSize))
+ {
+ rc = vdIfIoIntFileSetAllocationSize(pImage->pIfIo, pImage->pStorage, cbSize, 0 /* fFlags */,
+ pIfProgress, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan * 98 / 100);
+
+ pImage->cbSize = cbSize;
+ rc = rawFlushImage(pImage);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_DISK_FULL, RT_SRC_POS, N_("Raw: disk would overflow creating image '%s'"), pImage->pszFilename);
+ }
+ else
+ {
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, cbSize);
+ if (RT_SUCCESS(rc))
+ pImage->cbSize = cbSize;
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("Raw: cannot create image '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_RAW_INVALID_TYPE, RT_SRC_POS, N_("Raw: cannot create diff image '%s'"), pImage->pszFilename);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = pImage->cbSector;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = pImage->cbSector;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+ }
+
+ if (RT_FAILURE(rc))
+ rawFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+/**
+ * Worker for rawProbe that checks if the file looks like it contains an ISO
+ * 9660 or UDF descriptor sequence at the expected offset.
+ *
+ * Caller already checked if the size is suitable for ISOs.
+ *
+ * @returns IPRT status code. Success if detected ISO 9660 or UDF, failure if
+ * not.
+ *
+ * @note Code is a modified version of rtFsIsoVolTryInit() IPRT (isovfs.cpp).
+ */
+static int rawProbeIsIso9660OrUdf(PVDINTERFACEIOINT pIfIo, PVDIOSTORAGE pStorage)
+{
+ PRTERRINFO pErrInfo = NULL;
+ const uint32_t cbSector = _2K;
+
+ union
+ {
+ uint8_t ab[_2K];
+ ISO9660VOLDESCHDR VolDescHdr;
+ } Buf;
+ Assert(cbSector <= sizeof(Buf));
+ RT_ZERO(Buf);
+
+ uint8_t uUdfLevel = 0;
+ uint64_t offUdfBootVolDesc = UINT64_MAX;
+
+ uint32_t cPrimaryVolDescs = 0;
+ uint32_t cSupplementaryVolDescs = 0;
+ uint32_t cBootRecordVolDescs = 0;
+ uint32_t offVolDesc = 16 * cbSector;
+ enum
+ {
+ kStateStart = 0,
+ kStateNoSeq,
+ kStateCdSeq,
+ kStateUdfSeq
+ } enmState = kStateStart;
+ for (uint32_t iVolDesc = 0; ; iVolDesc++, offVolDesc += cbSector)
+ {
+ if (iVolDesc > 32)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "More than 32 volume descriptors, doesn't seem right...");
+
+ /* Read the next one and check the signature. */
+ int rc = vdIfIoIntFileReadSync(pIfIo, pStorage, offVolDesc, &Buf, cbSector);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Unable to read volume descriptor #%u", iVolDesc);
+
+#define MATCH_STD_ID(a_achStdId1, a_szStdId2) \
+ ( (a_achStdId1)[0] == (a_szStdId2)[0] \
+ && (a_achStdId1)[1] == (a_szStdId2)[1] \
+ && (a_achStdId1)[2] == (a_szStdId2)[2] \
+ && (a_achStdId1)[3] == (a_szStdId2)[3] \
+ && (a_achStdId1)[4] == (a_szStdId2)[4] )
+#define MATCH_HDR(a_pStd, a_bType2, a_szStdId2, a_bVer2) \
+ ( MATCH_STD_ID((a_pStd)->achStdId, a_szStdId2) \
+ && (a_pStd)->bDescType == (a_bType2) \
+ && (a_pStd)->bDescVersion == (a_bVer2) )
+
+ /*
+ * ISO 9660 ("CD001").
+ */
+ if ( ( enmState == kStateStart
+ || enmState == kStateCdSeq
+ || enmState == kStateNoSeq)
+ && MATCH_STD_ID(Buf.VolDescHdr.achStdId, ISO9660VOLDESC_STD_ID) )
+ {
+ enmState = kStateCdSeq;
+
+ /* Do type specific handling. */
+ Log(("RAW/ISO9660: volume desc #%u: type=%#x\n", iVolDesc, Buf.VolDescHdr.bDescType));
+ if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_PRIMARY)
+ {
+ cPrimaryVolDescs++;
+ if (Buf.VolDescHdr.bDescVersion != ISO9660PRIMARYVOLDESC_VERSION)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported primary volume descriptor version: %#x", Buf.VolDescHdr.bDescVersion);
+ if (cPrimaryVolDescs == 1)
+ { /*rc = rtFsIsoVolHandlePrimaryVolDesc(pThis, &Buf.PrimaryVolDesc, offVolDesc, &RootDir, &offRootDirRec, pErrInfo);*/ }
+ else if (cPrimaryVolDescs == 2)
+ Log(("RAW/ISO9660: ignoring 2nd primary descriptor\n")); /* so we can read the w2k3 ifs kit */
+ else
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "More than one primary volume descriptor");
+ }
+ else if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_SUPPLEMENTARY)
+ {
+ cSupplementaryVolDescs++;
+ if (Buf.VolDescHdr.bDescVersion != ISO9660SUPVOLDESC_VERSION)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported supplemental volume descriptor version: %#x", Buf.VolDescHdr.bDescVersion);
+ /*rc = rtFsIsoVolHandleSupplementaryVolDesc(pThis, &Buf.SupVolDesc, offVolDesc, &bJolietUcs2Level, &JolietRootDir,
+ &offJolietRootDirRec, pErrInfo);*/
+ }
+ else if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_BOOT_RECORD)
+ {
+ cBootRecordVolDescs++;
+ }
+ else if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_TERMINATOR)
+ {
+ if (!cPrimaryVolDescs)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "No primary volume descriptor");
+ enmState = kStateNoSeq;
+ }
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unknown volume descriptor: %#x", Buf.VolDescHdr.bDescType);
+ }
+ /*
+ * UDF volume recognition sequence (VRS).
+ */
+ else if ( ( enmState == kStateNoSeq
+ || enmState == kStateStart)
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_BEGIN, UDF_EXT_VOL_DESC_VERSION) )
+ {
+ if (uUdfLevel == 0)
+ enmState = kStateUdfSeq;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Only one BEA01 sequence is supported");
+ }
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_NSR_02, UDF_EXT_VOL_DESC_VERSION) )
+ uUdfLevel = 2;
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_NSR_03, UDF_EXT_VOL_DESC_VERSION) )
+ uUdfLevel = 3;
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_BOOT, UDF_EXT_VOL_DESC_VERSION) )
+ {
+ if (offUdfBootVolDesc == UINT64_MAX)
+ offUdfBootVolDesc = iVolDesc * cbSector;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Only one BOOT2 descriptor is supported");
+ }
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_TERM, UDF_EXT_VOL_DESC_VERSION) )
+ {
+ if (uUdfLevel != 0)
+ enmState = kStateNoSeq;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Found BEA01 & TEA01, but no NSR02 or NSR03 descriptors");
+ }
+ /*
+ * Unknown, probably the end.
+ */
+ else if (enmState == kStateNoSeq)
+ break;
+ else if (enmState == kStateStart)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "Not ISO? Unable to recognize volume descriptor signature: %.5Rhxs", Buf.VolDescHdr.achStdId);
+ else if (enmState == kStateCdSeq)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Missing ISO 9660 terminator volume descriptor? (Found %.5Rhxs)", Buf.VolDescHdr.achStdId);
+ else if (enmState == kStateUdfSeq)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Missing UDF terminator volume descriptor? (Found %.5Rhxs)", Buf.VolDescHdr.achStdId);
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "Unknown volume descriptor signature found at sector %u: %.5Rhxs",
+ 16 + iVolDesc, Buf.VolDescHdr.achStdId);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Checks the given extension array for the given suffix and type.
+ *
+ * @returns true if found in the list, false if not.
+ * @param paExtensions The extension array to check against.
+ * @param pszSuffix The suffix to look for. Can be NULL.
+ * @param enmType The image type to look for.
+ */
+static bool rawProbeContainsExtension(const VDFILEEXTENSION *paExtensions, const char *pszSuffix, VDTYPE enmType)
+{
+ if (pszSuffix)
+ {
+ if (*pszSuffix == '.')
+ pszSuffix++;
+ if (*pszSuffix != '\0')
+ {
+ for (size_t i = 0;; i++)
+ {
+ if (!paExtensions[i].pszExtension)
+ break;
+ if ( paExtensions[i].enmType == enmType
+ && RTStrICmpAscii(paExtensions[i].pszExtension, pszSuffix) == 0)
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) rawProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pVDIfsDisk, enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
+ PVDIOSTORAGE pStorage = NULL;
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+ /*
+ * Open the file and read the footer.
+ */
+ int rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile;
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Detecting raw ISO and floppy images and keeping them apart isn't all
+ * that simple.
+ *
+ * - Both must be a multiple of their sector sizes, though
+ * that means that any ISO can also be a floppy, since 2048 is 512 * 4.
+ * - The ISO images must be 32KB and floppies are generally not larger
+ * than 2.88MB, but that leaves quite a bit of size overlap,
+ *
+ * So, the size cannot conclusively say whether something is one or the other.
+ *
+ * - The content of a normal ISO image is detectable, but not all ISO
+ * images need to follow that spec to work in a DVD ROM drive.
+ * - It is common for ISO images to start like a floppy with a boot sector
+ * at the very start of the image.
+ * - Floppies doesn't need to contain a DOS-style boot sector, it depends
+ * on the system it is formatted and/or intended for.
+ *
+ * So, the content cannot conclusively determine the type either.
+ *
+ * However, there are a number of cases, especially for ISOs, where we can
+ * say we a deal of confidence that something is an ISO image.
+ */
+ const char * const pszSuffix = RTPathSuffix(pszFilename);
+
+ /*
+ * Start by checking for sure signs of an ISO 9660 / UDF image.
+ */
+ rc = VERR_VD_RAW_INVALID_HEADER;
+ if ( (enmDesiredType == VDTYPE_INVALID || enmDesiredType == VDTYPE_OPTICAL_DISC)
+ && (cbFile % 2048) == 0
+ && cbFile > 32768)
+ {
+ int rc2 = rawProbeIsIso9660OrUdf(pIfIo, pStorage);
+ if (RT_SUCCESS(rc2))
+ {
+ /* *puScore = VDPROBE_SCORE_HIGH; */
+ *penmType = VDTYPE_OPTICAL_DISC;
+ rc = VINF_SUCCESS;
+ }
+ /* If that didn't work out, check by extension (the old way): */
+ else if (rawProbeContainsExtension(s_aRawFileExtensions, pszSuffix, VDTYPE_OPTICAL_DISC))
+ {
+ /* *puScore = VDPROBE_SCORE_LOW; */
+ *penmType = VDTYPE_OPTICAL_DISC;
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * We could do something similar for floppies, i.e. check for a
+ * DOS'ish boot sector and thereby get a good match on most of the
+ * relevant floppy images out there.
+ */
+ if ( RT_FAILURE(rc)
+ && (enmDesiredType == VDTYPE_INVALID || enmDesiredType == VDTYPE_FLOPPY)
+ && (cbFile % 512) == 0
+ && cbFile >= 512
+ && cbFile <= RAW_MAX_FLOPPY_IMG_SIZE)
+ {
+ /** @todo check if the content is DOSish. */
+ if (false)
+ {
+ /* *puScore = VDPROBE_SCORE_HIGH; */
+ *penmType = VDTYPE_FLOPPY;
+ rc = VINF_SUCCESS;
+ }
+ else if (rawProbeContainsExtension(s_aRawFileExtensions, pszSuffix, VDTYPE_FLOPPY))
+ {
+ /* *puScore = VDPROBE_SCORE_LOW; */
+ *penmType = VDTYPE_FLOPPY;
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * No luck? Go exclusively by extension like we've done since
+ * for ever and complain about the size if it doesn't fit expectations.
+ * We can get here if the desired type doesn't match the extension and such.
+ */
+ if (RT_FAILURE(rc))
+ {
+ if (rawProbeContainsExtension(s_aRawFileExtensions, pszSuffix, VDTYPE_OPTICAL_DISC))
+ {
+ if (cbFile % 2048)
+ rc = VERR_VD_RAW_SIZE_MODULO_2048;
+ else if (cbFile <= 32768)
+ rc = VERR_VD_RAW_SIZE_OPTICAL_TOO_SMALL;
+ else
+ {
+ Assert(enmDesiredType != VDTYPE_OPTICAL_DISC);
+ *penmType = VDTYPE_OPTICAL_DISC;
+ rc = VINF_SUCCESS;
+ }
+ }
+ else if (rawProbeContainsExtension(s_aRawFileExtensions, pszSuffix, VDTYPE_FLOPPY))
+ {
+ if (cbFile % 512)
+ rc = VERR_VD_RAW_SIZE_MODULO_512;
+ else if (cbFile > RAW_MAX_FLOPPY_IMG_SIZE)
+ rc = VERR_VD_RAW_SIZE_FLOPPY_TOO_BIG;
+ else
+ {
+ Assert(cbFile == 0 || enmDesiredType != VDTYPE_FLOPPY);
+ *penmType = VDTYPE_FLOPPY;
+ rc = VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_VD_RAW_INVALID_HEADER;
+ }
+ }
+ else
+ rc = VERR_VD_RAW_INVALID_HEADER;
+ }
+
+ if (pStorage)
+ vdIfIoIntFileClose(pIfIo, pStorage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) rawOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc;
+ PRAWIMAGE pImage;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+ pImage = (PRAWIMAGE)RTMemAllocZ(RT_UOFFSETOF(RAWIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ if (enmType == VDTYPE_OPTICAL_DISC)
+ pImage->cbSector = 2048;
+ else
+ pImage->cbSector = 512;
+
+ rc = rawOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ else
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnCreate */
+static DECLCALLBACK(int) rawCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+
+ /* Check the VD container type. Yes, hard disk must be allowed, otherwise
+ * various tools using this backend for hard disk images will fail. */
+ if (enmType != VDTYPE_HDD && enmType != VDTYPE_OPTICAL_DISC && enmType != VDTYPE_FLOPPY)
+ return VERR_VD_INVALID_TYPE;
+
+ int rc = VINF_SUCCESS;
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ /* Check arguments. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER);
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+
+ PRAWIMAGE pImage = (PRAWIMAGE)RTMemAllocZ(RT_UOFFSETOF(RAWIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = rawCreateImage(pImage, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, uOpenFlags,
+ pIfProgress, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ rawFreeImage(pImage, false);
+ rc = rawOpenImage(pImage, uOpenFlags);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRename */
+static DECLCALLBACK(int) rawRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ AssertReturn((pImage && pszFilename && *pszFilename), VERR_INVALID_PARAMETER);
+
+ /* Close the image. */
+ int rc = rawFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ {
+ /* Rename the file. */
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pszFilename, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the old image with new name. */
+ rc = rawOpenImage(pImage, pImage->uOpenFlags);
+ }
+ else
+ {
+ /* The move failed, try to reopen the original image. */
+ int rc2 = rawOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) rawClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc = rawFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRead */
+static DECLCALLBACK(int) rawRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ int rc = VINF_SUCCESS;
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ /* For sequential access do not allow to go back. */
+ if ( pImage->uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL
+ && uOffset < pImage->offAccess)
+ {
+ *pcbActuallyRead = 0;
+ return VERR_INVALID_PARAMETER;
+ }
+
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, uOffset,
+ pIoCtx, cbToRead);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbActuallyRead = cbToRead;
+ pImage->offAccess = uOffset + cbToRead;
+ }
+
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnWrite */
+static DECLCALLBACK(int) rawWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ RT_NOREF1(fWrite);
+ int rc = VINF_SUCCESS;
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ /* For sequential access do not allow to go back. */
+ if ( pImage->uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL
+ && uOffset < pImage->offAccess)
+ {
+ *pcbWriteProcess = 0;
+ *pcbPostRead = 0;
+ *pcbPreRead = 0;
+ return VERR_INVALID_PARAMETER;
+ }
+
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage, uOffset,
+ pIoCtx, cbToWrite, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbWriteProcess = cbToWrite;
+ *pcbPostRead = 0;
+ *pcbPreRead = 0;
+ pImage->offAccess = uOffset + cbToWrite;
+ }
+
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnFlush */
+static DECLCALLBACK(int) rawFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rc = vdIfIoIntFileFlush(pImage->pIfIo, pImage->pStorage, pIoCtx,
+ NULL, NULL);
+
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) rawGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ return 1;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) rawGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ uint64_t cbFile = 0;
+ if (pImage->pStorage)
+ {
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ if (RT_FAILURE(rc))
+ cbFile = 0; /* Make sure it is 0 */
+ }
+
+ LogFlowFunc(("returns %lld\n", cbFile));
+ return cbFile;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) rawGetPCHSGeometry(void *pBackendData,
+ PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->PCHSGeometry.cCylinders)
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) rawSetPCHSGeometry(void *pBackendData,
+ PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->PCHSGeometry = *pPCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) rawGetLCHSGeometry(void *pBackendData,
+ PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->LCHSGeometry.cCylinders)
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) rawSetLCHSGeometry(void *pBackendData,
+ PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n",
+ pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->LCHSGeometry = *pLCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) rawQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PRAWIMAGE pThis = (PRAWIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) rawRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PRAWIMAGE pThis = (PRAWIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) rawGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uImageFlags));
+ return pImage->uImageFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) rawGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uOpenFlags));
+ return pImage->uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) rawSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pImage || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ rc = rawFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ rc = rawOpenImage(pImage, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(rawGetComment);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(rawSetComment, PRAWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(rawGetUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(rawSetUuid, PRAWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(rawGetModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(rawSetModificationUuid, PRAWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(rawGetParentUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(rawSetParentUuid, PRAWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(rawGetParentModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(rawSetParentModificationUuid, PRAWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) rawDump(void *pBackendData)
+{
+ PRAWIMAGE pImage = (PRAWIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pImage);
+ vdIfErrorMessage(pImage->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cbSector=%llu\n",
+ pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors,
+ pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors,
+ pImage->cbSize / 512);
+}
+
+
+
+const VDIMAGEBACKEND g_RawBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "RAW",
+ /* uBackendCaps */
+ VD_CAP_CREATE_FIXED | VD_CAP_FILE | VD_CAP_ASYNC | VD_CAP_VFS,
+ /* paFileExtensions */
+ s_aRawFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ rawProbe,
+ /* pfnOpen */
+ rawOpen,
+ /* pfnCreate */
+ rawCreate,
+ /* pfnRename */
+ rawRename,
+ /* pfnClose */
+ rawClose,
+ /* pfnRead */
+ rawRead,
+ /* pfnWrite */
+ rawWrite,
+ /* pfnFlush */
+ rawFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ rawGetVersion,
+ /* pfnGetFileSize */
+ rawGetFileSize,
+ /* pfnGetPCHSGeometry */
+ rawGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ rawSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ rawGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ rawSetLCHSGeometry,
+ /* pfnQueryRegions */
+ rawQueryRegions,
+ /* pfnRegionListRelease */
+ rawRegionListRelease,
+ /* pfnGetImageFlags */
+ rawGetImageFlags,
+ /* pfnGetOpenFlags */
+ rawGetOpenFlags,
+ /* pfnSetOpenFlags */
+ rawSetOpenFlags,
+ /* pfnGetComment */
+ rawGetComment,
+ /* pfnSetComment */
+ rawSetComment,
+ /* pfnGetUuid */
+ rawGetUuid,
+ /* pfnSetUuid */
+ rawSetUuid,
+ /* pfnGetModificationUuid */
+ rawGetModificationUuid,
+ /* pfnSetModificationUuid */
+ rawSetModificationUuid,
+ /* pfnGetParentUuid */
+ rawGetParentUuid,
+ /* pfnSetParentUuid */
+ rawSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ rawGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ rawSetParentModificationUuid,
+ /* pfnDump */
+ rawDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ NULL,
+ /* pfnSetParentFilename */
+ NULL,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ NULL,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
diff --git a/src/VBox/Storage/VCICache.cpp b/src/VBox/Storage/VCICache.cpp
new file mode 100644
index 00000000..97c71b7a
--- /dev/null
+++ b/src/VBox/Storage/VCICache.cpp
@@ -0,0 +1,2054 @@
+/* $Id: VCICache.cpp $ */
+/** @file
+ * VCICacheCore - VirtualBox Cache Image, Core Code.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_RAW /** @todo logging group */
+#include <VBox/vd-cache-backend.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/file.h>
+#include <iprt/asm.h>
+
+#include "VDBackends.h"
+
+/*******************************************************************************
+* On disk data structures *
+*******************************************************************************/
+
+/** @note All structures which are written to the disk are written in camel case
+ * and packed. */
+
+/** Block size used internally, because we cache sectors the smallest unit we
+ * have to care about is 512 bytes. */
+#define VCI_BLOCK_SIZE 512
+
+/** Convert block number/size to byte offset/size. */
+#define VCI_BLOCK2BYTE(u) ((uint64_t)(u) << 9)
+
+/** Convert byte offset/size to block number/size. */
+#define VCI_BYTE2BLOCK(u) ((u) >> 9)
+
+/**
+ * The VCI header - at the beginning of the file.
+ *
+ * All entries a stored in little endian order.
+ */
+#pragma pack(1)
+typedef struct VciHdr
+{
+ /** The signature to identify a cache image. */
+ uint32_t u32Signature;
+ /** Version of the layout of metadata in the cache. */
+ uint32_t u32Version;
+ /** Maximum size of the cache file in blocks.
+ * This includes all metadata. */
+ uint64_t cBlocksCache;
+ /** Flag indicating whether the cache was closed cleanly. */
+ uint8_t fUncleanShutdown;
+ /** Cache type. */
+ uint32_t u32CacheType;
+ /** Offset of the B+-Tree root in the image in blocks. */
+ uint64_t offTreeRoot;
+ /** Offset of the block allocation bitmap in blocks. */
+ uint64_t offBlkMap;
+ /** Size of the block allocation bitmap in blocks. */
+ uint32_t cBlkMap;
+ /** UUID of the image. */
+ RTUUID uuidImage;
+ /** Modification UUID for the cache. */
+ RTUUID uuidModification;
+ /** Reserved for future use. */
+ uint8_t abReserved[951];
+} VciHdr, *PVciHdr;
+#pragma pack()
+AssertCompileSize(VciHdr, 2 * VCI_BLOCK_SIZE);
+
+/** VCI signature to identify a valid image. */
+#define VCI_HDR_SIGNATURE UINT32_C(0x00494356) /* \0ICV */
+/** Current version we support. */
+#define VCI_HDR_VERSION UINT32_C(0x00000001)
+
+/** Value for an unclean cache shutdown. */
+#define VCI_HDR_UNCLEAN_SHUTDOWN UINT8_C(0x01)
+/** Value for a clean cache shutdown. */
+#define VCI_HDR_CLEAN_SHUTDOWN UINT8_C(0x00)
+
+/** Cache type: Dynamic image growing to the maximum value. */
+#define VCI_HDR_CACHE_TYPE_DYNAMIC UINT32_C(0x00000001)
+/** Cache type: Fixed image, space is preallocated. */
+#define VCI_HDR_CACHE_TYPE_FIXED UINT32_C(0x00000002)
+
+/**
+ * On disk representation of an extent describing a range of cached data.
+ *
+ * All entries a stored in little endian order.
+ */
+#pragma pack(1)
+typedef struct VciCacheExtent
+{
+ /** Block address of the previous extent in the LRU list. */
+ uint64_t u64ExtentPrev;
+ /** Block address of the next extent in the LRU list. */
+ uint64_t u64ExtentNext;
+ /** Flags (for compression, encryption etc.) - currently unused and should be always 0. */
+ uint8_t u8Flags;
+ /** Reserved */
+ uint8_t u8Reserved;
+ /** First block of cached data the extent represents. */
+ uint64_t u64BlockOffset;
+ /** Number of blocks the extent represents. */
+ uint32_t u32Blocks;
+ /** First block in the image where the data is stored. */
+ uint64_t u64BlockAddr;
+} VciCacheExtent, *PVciCacheExtent;
+#pragma pack()
+AssertCompileSize(VciCacheExtent, 38);
+
+/**
+ * On disk representation of an internal node.
+ *
+ * All entries a stored in little endian order.
+ */
+#pragma pack(1)
+typedef struct VciTreeNodeInternal
+{
+ /** First block of cached data the internal node represents. */
+ uint64_t u64BlockOffset;
+ /** Number of blocks the internal node represents. */
+ uint32_t u32Blocks;
+ /** Block address in the image where the next node in the tree is stored. */
+ uint64_t u64ChildAddr;
+} VciTreeNodeInternal, *PVciTreeNodeInternal;
+#pragma pack()
+AssertCompileSize(VciTreeNodeInternal, 20);
+
+/**
+ * On-disk representation of a node in the B+-Tree.
+ *
+ * All entries a stored in little endian order.
+ */
+#pragma pack(1)
+typedef struct VciTreeNode
+{
+ /** Type of the node (root, internal, leaf). */
+ uint8_t u8Type;
+ /** Data in the node. */
+ uint8_t au8Data[4095];
+} VciTreeNode, *PVciTreeNode;
+#pragma pack()
+AssertCompileSize(VciTreeNode, 8 * VCI_BLOCK_SIZE);
+
+/** Node type: Internal node containing links to other nodes (VciTreeNodeInternal). */
+#define VCI_TREE_NODE_TYPE_INTERNAL UINT8_C(0x01)
+/** Node type: Leaf of the tree (VciCacheExtent). */
+#define VCI_TREE_NODE_TYPE_LEAF UINT8_C(0x02)
+
+/** Number of cache extents described by one node. */
+#define VCI_TREE_EXTENTS_PER_NODE ((sizeof(VciTreeNode)-1) / sizeof(VciCacheExtent))
+/** Number of internal nodes managed by one tree node. */
+#define VCI_TREE_INTERNAL_NODES_PER_NODE ((sizeof(VciTreeNode)-1) / sizeof(VciTreeNodeInternal))
+
+/**
+ * VCI block bitmap header.
+ *
+ * All entries a stored in little endian order.
+ */
+#pragma pack(1)
+typedef struct VciBlkMap
+{
+ /** Magic of the block bitmap. */
+ uint32_t u32Magic;
+ /** Version of the block bitmap. */
+ uint32_t u32Version;
+ /** Number of blocks this block map manages. */
+ uint64_t cBlocks;
+ /** Number of free blocks. */
+ uint64_t cBlocksFree;
+ /** Number of blocks allocated for metadata. */
+ uint64_t cBlocksAllocMeta;
+ /** Number of blocks allocated for actual cached data. */
+ uint64_t cBlocksAllocData;
+ /** Reserved for future use. */
+ uint8_t au8Reserved[472];
+} VciBlkMap, *PVciBlkMap;
+#pragma pack()
+AssertCompileSize(VciBlkMap, VCI_BLOCK_SIZE);
+
+/** The magic which identifies a block map. */
+#define VCI_BLKMAP_MAGIC UINT32_C(0x4b4c4256) /* KLBV */
+/** Current version. */
+#define VCI_BLKMAP_VERSION UINT32_C(0x00000001)
+
+/** Block bitmap entry */
+typedef uint8_t VciBlkMapEnt;
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Block range descriptor.
+ */
+typedef struct VCIBLKRANGEDESC
+{
+ /** Previous entry in the list. */
+ struct VCIBLKRANGEDESC *pPrev;
+ /** Next entry in the list. */
+ struct VCIBLKRANGEDESC *pNext;
+ /** Start address of the range. */
+ uint64_t offAddrStart;
+ /** Number of blocks in the range. */
+ uint64_t cBlocks;
+ /** Flag whether the range is free or allocated. */
+ bool fFree;
+} VCIBLKRANGEDESC, *PVCIBLKRANGEDESC;
+
+/**
+ * Block map for the cache image - in memory structure.
+ */
+typedef struct VCIBLKMAP
+{
+ /** Number of blocks the map manages. */
+ uint64_t cBlocks;
+ /** Number of blocks allocated for metadata. */
+ uint64_t cBlocksAllocMeta;
+ /** Number of blocks allocated for actual cached data. */
+ uint64_t cBlocksAllocData;
+ /** Number of free blocks. */
+ uint64_t cBlocksFree;
+
+ /** Pointer to the head of the block range list. */
+ PVCIBLKRANGEDESC pRangesHead;
+ /** Pointer to the tail of the block range list. */
+ PVCIBLKRANGEDESC pRangesTail;
+
+} VCIBLKMAP;
+/** Pointer to a block map. */
+typedef VCIBLKMAP *PVCIBLKMAP;
+
+/**
+ * B+-Tree node header.
+ */
+typedef struct VCITREENODE
+{
+ /** Type of the node (VCI_TREE_NODE_TYPE_*). */
+ uint8_t u8Type;
+ /** Block address where the node is stored. */
+ uint64_t u64BlockAddr;
+ /** Pointer to the parent. */
+ struct VCITREENODE *pParent;
+} VCITREENODE, *PVCITREENODE;
+
+/**
+ * B+-Tree node pointer.
+ */
+typedef struct VCITREENODEPTR
+{
+ /** Flag whether the node is in memory or still on the disk. */
+ bool fInMemory;
+ /** Type dependent data. */
+ union
+ {
+ /** Pointer to a in memory node. */
+ PVCITREENODE pNode;
+ /** Start block address of the node. */
+ uint64_t offAddrBlockNode;
+ } u;
+} VCITREENODEPTR, *PVCITREENODEPTR;
+
+/**
+ * Internal node.
+ */
+typedef struct VCINODEINTERNAL
+{
+ /** First block of cached data the internal node represents. */
+ uint64_t u64BlockOffset;
+ /** Number of blocks the internal node represents. */
+ uint32_t u32Blocks;
+ /** Pointer to the child node. */
+ VCITREENODEPTR PtrChild;
+} VCINODEINTERNAL, *PVCINODEINTERNAL;
+
+/**
+ * A in memory internal B+-tree node.
+ */
+typedef struct VCITREENODEINT
+{
+ /** Node core. */
+ VCITREENODE Core;
+ /** Number of used nodes. */
+ unsigned cUsedNodes;
+ /** Array of internal nodes. */
+ VCINODEINTERNAL aIntNodes[VCI_TREE_INTERNAL_NODES_PER_NODE];
+} VCITREENODEINT, *PVCITREENODEINT;
+
+/**
+ * A in memory cache extent.
+ */
+typedef struct VCICACHEEXTENT
+{
+ /** First block of cached data the extent represents. */
+ uint64_t u64BlockOffset;
+ /** Number of blocks the extent represents. */
+ uint32_t u32Blocks;
+ /** First block in the image where the data is stored. */
+ uint64_t u64BlockAddr;
+} VCICACHEEXTENT, *PVCICACHEEXTENT;
+
+/**
+ * A in memory leaf B+-tree node.
+ */
+typedef struct VCITREENODELEAF
+{
+ /** Node core. */
+ VCITREENODE Core;
+ /** Next leaf node in the list. */
+ struct VCITREENODELEAF *pNext;
+ /** Number of used nodes. */
+ unsigned cUsedNodes;
+ /** The extents in the node. */
+ VCICACHEEXTENT aExtents[VCI_TREE_EXTENTS_PER_NODE];
+} VCITREENODELEAF, *PVCITREENODELEAF;
+
+/**
+ * VCI image data structure.
+ */
+typedef struct VCICACHE
+{
+ /** Image name. */
+ const char *pszFilename;
+ /** Storage handle. */
+ PVDIOSTORAGE pStorage;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+
+ /** Open flags passed by VBoxHD layer. */
+ unsigned uOpenFlags;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+
+ /** Offset of the B+-Tree in the image in bytes. */
+ uint64_t offTreeRoot;
+ /** Pointer to the root node of the B+-Tree. */
+ PVCITREENODE pRoot;
+ /** Offset to the block allocation bitmap in bytes. */
+ uint64_t offBlksBitmap;
+ /** Block map. */
+ PVCIBLKMAP pBlkMap;
+} VCICACHE, *PVCICACHE;
+
+/** No block free in bitmap error code. */
+#define VERR_VCI_NO_BLOCKS_FREE (-65536)
+
+/** Flags for the block map allocator. */
+#define VCIBLKMAP_ALLOC_DATA 0
+#define VCIBLKMAP_ALLOC_META RT_BIT(0)
+#define VCIBLKMAP_ALLOC_MASK 0x1
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const char *const s_apszVciFileExtensions[] =
+{
+ "vci",
+ NULL
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Internal. Flush image data to disk.
+ */
+static int vciFlushImage(PVCICACHE pCache)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( pCache->pStorage
+ && !(pCache->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ rc = vdIfIoIntFileFlushSync(pCache->pIfIo, pCache->pStorage);
+ }
+
+ return rc;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image except pCache,
+ * and optionally delete the image from disk.
+ */
+static int vciFreeImage(PVCICACHE pCache, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pCache)
+ {
+ if (pCache->pStorage)
+ {
+ /* No point updating the file that is deleted anyway. */
+ if (!fDelete)
+ vciFlushImage(pCache);
+
+ vdIfIoIntFileClose(pCache->pIfIo, pCache->pStorage);
+ pCache->pStorage = NULL;
+ }
+
+ if (fDelete && pCache->pszFilename)
+ vdIfIoIntFileDelete(pCache->pIfIo, pCache->pszFilename);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Creates a new block map which can manage the given number of blocks.
+ *
+ * The size of the bitmap is aligned to the VCI block size.
+ *
+ * @returns VBox status code.
+ * @param cBlocks The number of blocks the bitmap can manage.
+ * @param ppBlkMap Where to store the pointer to the block bitmap.
+ * @param pcBlkMap Where to store the size of the block bitmap in blocks
+ * needed on the disk.
+ */
+static int vciBlkMapCreate(uint64_t cBlocks, PVCIBLKMAP *ppBlkMap, uint32_t *pcBlkMap)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cbBlkMap = RT_ALIGN_Z(cBlocks / sizeof(VciBlkMapEnt) / 8, VCI_BLOCK_SIZE);
+ PVCIBLKMAP pBlkMap = (PVCIBLKMAP)RTMemAllocZ(sizeof(VCIBLKMAP));
+ PVCIBLKRANGEDESC pFree = (PVCIBLKRANGEDESC)RTMemAllocZ(sizeof(VCIBLKRANGEDESC));
+
+ LogFlowFunc(("cBlocks=%u ppBlkMap=%#p pcBlkMap=%#p\n", cBlocks, ppBlkMap, pcBlkMap));
+
+ if (pBlkMap && pFree)
+ {
+ pBlkMap->cBlocks = cBlocks;
+ pBlkMap->cBlocksAllocMeta = 0;
+ pBlkMap->cBlocksAllocData = 0;
+ pBlkMap->cBlocksFree = cBlocks;
+
+ pFree->pPrev = NULL;
+ pFree->pNext = NULL;
+ pFree->offAddrStart = 0;
+ pFree->cBlocks = cBlocks;
+ pFree->fFree = true;
+
+ pBlkMap->pRangesHead = pFree;
+ pBlkMap->pRangesTail = pFree;
+
+ Assert(!((cbBlkMap + sizeof(VciBlkMap)) % VCI_BLOCK_SIZE));
+ *ppBlkMap = pBlkMap;
+ *pcBlkMap = VCI_BYTE2BLOCK(cbBlkMap + sizeof(VciBlkMap));
+ }
+ else
+ {
+ if (pBlkMap)
+ RTMemFree(pBlkMap);
+ if (pFree)
+ RTMemFree(pFree);
+
+ rc = VERR_NO_MEMORY;
+ }
+
+ LogFlowFunc(("returns rc=%Rrc cBlkMap=%u\n", rc, *pcBlkMap));
+ return rc;
+}
+
+#if 0 /** @todo unsued vciBlkMapDestroy */
+/**
+ * Frees a block map.
+ *
+ * @returns nothing.
+ * @param pBlkMap The block bitmap to destroy.
+ */
+static void vciBlkMapDestroy(PVCIBLKMAP pBlkMap)
+{
+ LogFlowFunc(("pBlkMap=%#p\n", pBlkMap));
+
+ PVCIBLKRANGEDESC pRangeCur = pBlkMap->pRangesHead;
+
+ while (pRangeCur)
+ {
+ PVCIBLKRANGEDESC pTmp = pRangeCur;
+
+ RTMemFree(pTmp);
+
+ pRangeCur = pRangeCur->pNext;
+ }
+
+ RTMemFree(pBlkMap);
+
+ LogFlowFunc(("returns\n"));
+}
+#endif
+
+/**
+ * Loads the block map from the specified medium and creates all necessary
+ * in memory structures to manage used and free blocks.
+ *
+ * @returns VBox status code.
+ * @param pStorage Storage handle to read the block bitmap from.
+ * @param offBlkMap Start of the block bitmap in blocks.
+ * @param cBlkMap Size of the block bitmap on the disk in blocks.
+ * @param ppBlkMap Where to store the block bitmap on success.
+ */
+static int vciBlkMapLoad(PVCICACHE pStorage, uint64_t offBlkMap, uint32_t cBlkMap, PVCIBLKMAP *ppBlkMap)
+{
+ int rc = VINF_SUCCESS;
+ VciBlkMap BlkMap;
+
+ LogFlowFunc(("pStorage=%#p offBlkMap=%llu cBlkMap=%u ppBlkMap=%#p\n",
+ pStorage, offBlkMap, cBlkMap, ppBlkMap));
+
+ if (cBlkMap >= VCI_BYTE2BLOCK(sizeof(VciBlkMap)))
+ {
+ cBlkMap -= VCI_BYTE2BLOCK(sizeof(VciBlkMap));
+
+ rc = vdIfIoIntFileReadSync(pStorage->pIfIo, pStorage->pStorage, offBlkMap,
+ &BlkMap, VCI_BYTE2BLOCK(sizeof(VciBlkMap)));
+ if (RT_SUCCESS(rc))
+ {
+ offBlkMap += VCI_BYTE2BLOCK(sizeof(VciBlkMap));
+
+ BlkMap.u32Magic = RT_LE2H_U32(BlkMap.u32Magic);
+ BlkMap.u32Version = RT_LE2H_U32(BlkMap.u32Version);
+ BlkMap.cBlocks = RT_LE2H_U32(BlkMap.cBlocks);
+ BlkMap.cBlocksFree = RT_LE2H_U32(BlkMap.cBlocksFree);
+ BlkMap.cBlocksAllocMeta = RT_LE2H_U32(BlkMap.cBlocksAllocMeta);
+ BlkMap.cBlocksAllocData = RT_LE2H_U32(BlkMap.cBlocksAllocData);
+
+ if ( BlkMap.u32Magic == VCI_BLKMAP_MAGIC
+ && BlkMap.u32Version == VCI_BLKMAP_VERSION
+ && BlkMap.cBlocks == BlkMap.cBlocksFree + BlkMap.cBlocksAllocMeta + BlkMap.cBlocksAllocData
+ && VCI_BYTE2BLOCK(BlkMap.cBlocks / 8) == cBlkMap)
+ {
+ PVCIBLKMAP pBlkMap = (PVCIBLKMAP)RTMemAllocZ(sizeof(VCIBLKMAP));
+ if (pBlkMap)
+ {
+ pBlkMap->cBlocks = BlkMap.cBlocks;
+ pBlkMap->cBlocksFree = BlkMap.cBlocksFree;
+ pBlkMap->cBlocksAllocMeta = BlkMap.cBlocksAllocMeta;
+ pBlkMap->cBlocksAllocData = BlkMap.cBlocksAllocData;
+
+ /* Load the bitmap and construct the range list. */
+ PVCIBLKRANGEDESC pRangeCur = (PVCIBLKRANGEDESC)RTMemAllocZ(sizeof(VCIBLKRANGEDESC));
+
+ if (pRangeCur)
+ {
+ uint8_t abBitmapBuffer[16 * _1K];
+ uint32_t cBlocksRead = 0;
+ uint64_t cBlocksLeft = VCI_BYTE2BLOCK(pBlkMap->cBlocks / 8);
+
+ cBlocksRead = RT_MIN(VCI_BYTE2BLOCK(sizeof(abBitmapBuffer)), cBlocksLeft);
+ rc = vdIfIoIntFileReadSync(pStorage->pIfIo, pStorage->pStorage,
+ offBlkMap, abBitmapBuffer,
+ cBlocksRead);
+
+ if (RT_SUCCESS(rc))
+ {
+ pRangeCur->fFree = !(abBitmapBuffer[0] & 0x01);
+ pRangeCur->offAddrStart = 0;
+ pRangeCur->cBlocks = 0;
+ pRangeCur->pNext = NULL;
+ pRangeCur->pPrev = NULL;
+ pBlkMap->pRangesHead = pRangeCur;
+ pBlkMap->pRangesTail = pRangeCur;
+ }
+ else
+ RTMemFree(pRangeCur);
+
+ while ( RT_SUCCESS(rc)
+ && cBlocksLeft)
+ {
+ int iBit = 0;
+ uint32_t cBits = VCI_BLOCK2BYTE(cBlocksRead) * 8;
+ uint32_t iBitPrev = 0xffffffff;
+
+ while (cBits)
+ {
+ if (pRangeCur->fFree)
+ {
+ /* Check for the first set bit. */
+ iBit = ASMBitNextSet(abBitmapBuffer, cBits, iBitPrev);
+ }
+ else
+ {
+ /* Check for the first free bit. */
+ iBit = ASMBitNextClear(abBitmapBuffer, cBits, iBitPrev);
+ }
+
+ if (iBit == -1)
+ {
+ /* No change. */
+ pRangeCur->cBlocks += cBits;
+ cBits = 0;
+ }
+ else
+ {
+ Assert((uint32_t)iBit < cBits);
+ pRangeCur->cBlocks += iBit;
+
+ /* Create a new range descriptor. */
+ PVCIBLKRANGEDESC pRangeNew = (PVCIBLKRANGEDESC)RTMemAllocZ(sizeof(VCIBLKRANGEDESC));
+ if (!pRangeNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pRangeNew->fFree = !pRangeCur->fFree;
+ pRangeNew->offAddrStart = pRangeCur->offAddrStart + pRangeCur->cBlocks;
+ pRangeNew->cBlocks = 0;
+ pRangeNew->pPrev = pRangeCur;
+ pRangeCur->pNext = pRangeNew;
+ pBlkMap->pRangesTail = pRangeNew;
+ pRangeCur = pRangeNew;
+ cBits -= iBit;
+ iBitPrev = iBit;
+ }
+ }
+
+ cBlocksLeft -= cBlocksRead;
+ offBlkMap += cBlocksRead;
+
+ if ( RT_SUCCESS(rc)
+ && cBlocksLeft)
+ {
+ /* Read next chunk. */
+ cBlocksRead = RT_MIN(VCI_BYTE2BLOCK(sizeof(abBitmapBuffer)), cBlocksLeft);
+ rc = vdIfIoIntFileReadSync(pStorage->pIfIo, pStorage->pStorage,
+ offBlkMap, abBitmapBuffer, cBlocksRead);
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppBlkMap = pBlkMap;
+ LogFlowFunc(("return success\n"));
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pBlkMap);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Saves the block map in the cache image. All necessary on disk structures
+ * are written.
+ *
+ * @returns VBox status code.
+ * @param pBlkMap The block bitmap to save.
+ * @param pStorage Where the block bitmap should be written to.
+ * @param offBlkMap Start of the block bitmap in blocks.
+ * @param cBlkMap Size of the block bitmap on the disk in blocks.
+ */
+static int vciBlkMapSave(PVCIBLKMAP pBlkMap, PVCICACHE pStorage, uint64_t offBlkMap, uint32_t cBlkMap)
+{
+ int rc = VINF_SUCCESS;
+ VciBlkMap BlkMap;
+
+ LogFlowFunc(("pBlkMap=%#p pStorage=%#p offBlkMap=%llu cBlkMap=%u\n",
+ pBlkMap, pStorage, offBlkMap, cBlkMap));
+
+ /* Make sure the number of blocks allocated for us match our expectations. */
+ if (VCI_BYTE2BLOCK(pBlkMap->cBlocks / 8) + VCI_BYTE2BLOCK(sizeof(VciBlkMap)) == cBlkMap)
+ {
+ /* Setup the header */
+ memset(&BlkMap, 0, sizeof(VciBlkMap));
+
+ BlkMap.u32Magic = RT_H2LE_U32(VCI_BLKMAP_MAGIC);
+ BlkMap.u32Version = RT_H2LE_U32(VCI_BLKMAP_VERSION);
+ BlkMap.cBlocks = RT_H2LE_U32(pBlkMap->cBlocks);
+ BlkMap.cBlocksFree = RT_H2LE_U32(pBlkMap->cBlocksFree);
+ BlkMap.cBlocksAllocMeta = RT_H2LE_U32(pBlkMap->cBlocksAllocMeta);
+ BlkMap.cBlocksAllocData = RT_H2LE_U32(pBlkMap->cBlocksAllocData);
+
+ rc = vdIfIoIntFileWriteSync(pStorage->pIfIo, pStorage->pStorage, offBlkMap,
+ &BlkMap, VCI_BYTE2BLOCK(sizeof(VciBlkMap)));
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t abBitmapBuffer[16*_1K];
+ unsigned iBit = 0;
+ PVCIBLKRANGEDESC pCur = pBlkMap->pRangesHead;
+
+ offBlkMap += VCI_BYTE2BLOCK(sizeof(VciBlkMap));
+
+ /* Write the descriptor ranges. */
+ while (pCur)
+ {
+ uint64_t cBlocks = pCur->cBlocks;
+
+ while (cBlocks)
+ {
+ uint64_t cBlocksMax = RT_MIN(cBlocks, sizeof(abBitmapBuffer) * 8 - iBit);
+
+ if (pCur->fFree)
+ ASMBitClearRange(abBitmapBuffer, iBit, iBit + cBlocksMax);
+ else
+ ASMBitSetRange(abBitmapBuffer, iBit, iBit + cBlocksMax);
+
+ iBit += cBlocksMax;
+ cBlocks -= cBlocksMax;
+
+ if (iBit == sizeof(abBitmapBuffer) * 8)
+ {
+ /* Buffer is full, write to file and reset. */
+ rc = vdIfIoIntFileWriteSync(pStorage->pIfIo, pStorage->pStorage,
+ offBlkMap, abBitmapBuffer,
+ VCI_BYTE2BLOCK(sizeof(abBitmapBuffer)));
+ if (RT_FAILURE(rc))
+ break;
+
+ offBlkMap += VCI_BYTE2BLOCK(sizeof(abBitmapBuffer));
+ iBit = 0;
+ }
+ }
+
+ pCur = pCur->pNext;
+ }
+
+ Assert(iBit % 8 == 0);
+
+ if (RT_SUCCESS(rc) && iBit)
+ rc = vdIfIoIntFileWriteSync(pStorage->pIfIo, pStorage->pStorage,
+ offBlkMap, abBitmapBuffer, VCI_BYTE2BLOCK(iBit / 8));
+ }
+ }
+ else
+ rc = VERR_INTERNAL_ERROR; /** @todo Better error code. */
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+#if 0 /* unused */
+/**
+ * Finds the range block describing the given block address.
+ *
+ * @returns Pointer to the block range descriptor or NULL if none could be found.
+ * @param pBlkMap The block bitmap to search on.
+ * @param offBlockAddr The block address to search for.
+ */
+static PVCIBLKRANGEDESC vciBlkMapFindByBlock(PVCIBLKMAP pBlkMap, uint64_t offBlockAddr)
+{
+ PVCIBLKRANGEDESC pBlk = pBlkMap->pRangesHead;
+
+ while ( pBlk
+ && pBlk->offAddrStart < offBlockAddr)
+ pBlk = pBlk->pNext;
+
+ return pBlk;
+}
+#endif
+
+/**
+ * Allocates the given number of blocks in the bitmap and returns the start block address.
+ *
+ * @returns VBox status code.
+ * @param pBlkMap The block bitmap to allocate the blocks from.
+ * @param cBlocks How many blocks to allocate.
+ * @param fFlags Allocation flags, comgination of VCIBLKMAP_ALLOC_*.
+ * @param poffBlockAddr Where to store the start address of the allocated region.
+ */
+static int vciBlkMapAllocate(PVCIBLKMAP pBlkMap, uint32_t cBlocks, uint32_t fFlags,
+ uint64_t *poffBlockAddr)
+{
+ PVCIBLKRANGEDESC pBestFit = NULL;
+ PVCIBLKRANGEDESC pCur = NULL;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pBlkMap=%#p cBlocks=%u poffBlockAddr=%#p\n",
+ pBlkMap, cBlocks, poffBlockAddr));
+
+ pCur = pBlkMap->pRangesHead;
+
+ while (pCur)
+ {
+ if ( pCur->fFree
+ && pCur->cBlocks >= cBlocks)
+ {
+ if ( !pBestFit
+ || pCur->cBlocks < pBestFit->cBlocks)
+ {
+ pBestFit = pCur;
+ /* Stop searching if the size is matching exactly. */
+ if (pBestFit->cBlocks == cBlocks)
+ break;
+ }
+ }
+ pCur = pCur->pNext;
+ }
+
+ Assert(!pBestFit || pBestFit->fFree);
+
+ if (pBestFit)
+ {
+ pBestFit->fFree = false;
+
+ if (pBestFit->cBlocks > cBlocks)
+ {
+ /* Create a new free block. */
+ PVCIBLKRANGEDESC pFree = (PVCIBLKRANGEDESC)RTMemAllocZ(sizeof(VCIBLKRANGEDESC));
+
+ if (pFree)
+ {
+ pFree->fFree = true;
+ pFree->cBlocks = pBestFit->cBlocks - cBlocks;
+ pBestFit->cBlocks -= pFree->cBlocks;
+ pFree->offAddrStart = pBestFit->offAddrStart + cBlocks;
+
+ /* Link into the list. */
+ pFree->pNext = pBestFit->pNext;
+ pBestFit->pNext = pFree;
+ pFree->pPrev = pBestFit;
+ if (!pFree->pNext)
+ pBlkMap->pRangesTail = pFree;
+
+ *poffBlockAddr = pBestFit->offAddrStart;
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ pBestFit->fFree = true;
+ }
+ }
+ }
+ else
+ rc = VERR_VCI_NO_BLOCKS_FREE;
+
+ if (RT_SUCCESS(rc))
+ {
+ if ((fFlags & VCIBLKMAP_ALLOC_MASK) == VCIBLKMAP_ALLOC_DATA)
+ pBlkMap->cBlocksAllocMeta += cBlocks;
+ else
+ pBlkMap->cBlocksAllocData += cBlocks;
+
+ pBlkMap->cBlocksFree -= cBlocks;
+ }
+
+ LogFlowFunc(("returns rc=%Rrc offBlockAddr=%llu\n", rc, *poffBlockAddr));
+ return rc;
+}
+
+#if 0 /* unused */
+/**
+ * Try to extend the space of an already allocated block.
+ *
+ * @returns VBox status code.
+ * @param pBlkMap The block bitmap to allocate the blocks from.
+ * @param cBlocksNew How many blocks the extended block should have.
+ * @param offBlockAddrOld The start address of the block to reallocate.
+ * @param poffBlockAddr Where to store the start address of the allocated region.
+ */
+static int vciBlkMapRealloc(PVCIBLKMAP pBlkMap, uint32_t cBlocksNew, uint64_t offBlockAddrOld,
+ uint64_t *poffBlockAddr)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pBlkMap=%#p cBlocksNew=%u offBlockAddrOld=%llu poffBlockAddr=%#p\n",
+ pBlkMap, cBlocksNew, offBlockAddrOld, poffBlockAddr));
+
+ AssertMsgFailed(("Implement\n"));
+ RT_NOREF4(pBlkMap, cBlocksNew, offBlockAddrOld, poffBlockAddr);
+
+ LogFlowFunc(("returns rc=%Rrc offBlockAddr=%llu\n", rc, *poffBlockAddr));
+ return rc;
+}
+#endif /* unused */
+
+#if 0 /* unused */
+/**
+ * Frees a range of blocks.
+ *
+ * @returns nothing.
+ * @param pBlkMap The block bitmap.
+ * @param offBlockAddr Address of the first block to free.
+ * @param cBlocks How many blocks to free.
+ * @param fFlags Allocation flags, comgination of VCIBLKMAP_ALLOC_*.
+ */
+static void vciBlkMapFree(PVCIBLKMAP pBlkMap, uint64_t offBlockAddr, uint32_t cBlocks,
+ uint32_t fFlags)
+{
+ PVCIBLKRANGEDESC pBlk;
+
+ LogFlowFunc(("pBlkMap=%#p offBlockAddr=%llu cBlocks=%u\n",
+ pBlkMap, offBlockAddr, cBlocks));
+
+ while (cBlocks)
+ {
+ pBlk = vciBlkMapFindByBlock(pBlkMap, offBlockAddr);
+ AssertPtr(pBlk);
+
+ /* Easy case, the whole block is freed. */
+ if ( pBlk->offAddrStart == offBlockAddr
+ && pBlk->cBlocks <= cBlocks)
+ {
+ pBlk->fFree = true;
+ cBlocks -= pBlk->cBlocks;
+ offBlockAddr += pBlk->cBlocks;
+
+ /* Check if it is possible to merge free blocks. */
+ if ( pBlk->pPrev
+ && pBlk->pPrev->fFree)
+ {
+ PVCIBLKRANGEDESC pBlkPrev = pBlk->pPrev;
+
+ Assert(pBlkPrev->offAddrStart + pBlkPrev->cBlocks == pBlk->offAddrStart);
+ pBlkPrev->cBlocks += pBlk->cBlocks;
+ pBlkPrev->pNext = pBlk->pNext;
+ if (pBlk->pNext)
+ pBlk->pNext->pPrev = pBlkPrev;
+ else
+ pBlkMap->pRangesTail = pBlkPrev;
+
+ RTMemFree(pBlk);
+ pBlk = pBlkPrev;
+ }
+
+ /* Now the one to the right. */
+ if ( pBlk->pNext
+ && pBlk->pNext->fFree)
+ {
+ PVCIBLKRANGEDESC pBlkNext = pBlk->pNext;
+
+ Assert(pBlk->offAddrStart + pBlk->cBlocks == pBlkNext->offAddrStart);
+ pBlk->cBlocks += pBlkNext->cBlocks;
+ pBlk->pNext = pBlkNext->pNext;
+ if (pBlkNext->pNext)
+ pBlkNext->pNext->pPrev = pBlk;
+ else
+ pBlkMap->pRangesTail = pBlk;
+
+ RTMemFree(pBlkNext);
+ }
+ }
+ else
+ {
+ /* The block is intersecting. */
+ AssertMsgFailed(("TODO\n"));
+ }
+ }
+
+ if ((fFlags & VCIBLKMAP_ALLOC_MASK) == VCIBLKMAP_ALLOC_DATA)
+ pBlkMap->cBlocksAllocMeta -= cBlocks;
+ else
+ pBlkMap->cBlocksAllocData -= cBlocks;
+
+ pBlkMap->cBlocksFree += cBlocks;
+
+ LogFlowFunc(("returns\n"));
+}
+#endif /* unused */
+
+/**
+ * Converts a tree node from the image to the in memory structure.
+ *
+ * @returns Pointer to the in memory tree node.
+ * @param offBlockAddrNode Block address of the node.
+ * @param pNodeImage Pointer to the image representation of the node.
+ */
+static PVCITREENODE vciTreeNodeImage2Host(uint64_t offBlockAddrNode, PVciTreeNode pNodeImage)
+{
+ PVCITREENODE pNode = NULL;
+
+ if (pNodeImage->u8Type == VCI_TREE_NODE_TYPE_LEAF)
+ {
+ PVCITREENODELEAF pLeaf = (PVCITREENODELEAF)RTMemAllocZ(sizeof(VCITREENODELEAF));
+
+ if (pLeaf)
+ {
+ PVciCacheExtent pExtent = (PVciCacheExtent)&pNodeImage->au8Data[0];
+
+ pLeaf->Core.u8Type = VCI_TREE_NODE_TYPE_LEAF;
+
+ for (unsigned idx = 0; idx < RT_ELEMENTS(pLeaf->aExtents); idx++)
+ {
+ pLeaf->aExtents[idx].u64BlockOffset = RT_LE2H_U64(pExtent->u64BlockOffset);
+ pLeaf->aExtents[idx].u32Blocks = RT_LE2H_U32(pExtent->u32Blocks);
+ pLeaf->aExtents[idx].u64BlockAddr = RT_LE2H_U64(pExtent->u64BlockAddr);
+ pExtent++;
+
+ if ( pLeaf->aExtents[idx].u32Blocks
+ && pLeaf->aExtents[idx].u64BlockAddr)
+ pLeaf->cUsedNodes++;
+ }
+
+ pNode = &pLeaf->Core;
+ }
+ }
+ else if (pNodeImage->u8Type == VCI_TREE_NODE_TYPE_INTERNAL)
+ {
+ PVCITREENODEINT pInt = (PVCITREENODEINT)RTMemAllocZ(sizeof(VCITREENODEINT));
+
+ if (pInt)
+ {
+ PVciTreeNodeInternal pIntImage = (PVciTreeNodeInternal)&pNodeImage->au8Data[0];
+
+ pInt->Core.u8Type = VCI_TREE_NODE_TYPE_INTERNAL;
+
+ for (unsigned idx = 0; idx < RT_ELEMENTS(pInt->aIntNodes); idx++)
+ {
+ pInt->aIntNodes[idx].u64BlockOffset = RT_LE2H_U64(pIntImage->u64BlockOffset);
+ pInt->aIntNodes[idx].u32Blocks = RT_LE2H_U32(pIntImage->u32Blocks);
+ pInt->aIntNodes[idx].PtrChild.fInMemory = false;
+ pInt->aIntNodes[idx].PtrChild.u.offAddrBlockNode = RT_LE2H_U64(pIntImage->u64ChildAddr);
+ pIntImage++;
+
+ if ( pInt->aIntNodes[idx].u32Blocks
+ && pInt->aIntNodes[idx].PtrChild.u.offAddrBlockNode)
+ pInt->cUsedNodes++;
+ }
+
+ pNode = &pInt->Core;
+ }
+ }
+ else
+ AssertMsgFailed(("Invalid node type %d\n", pNodeImage->u8Type));
+
+ if (pNode)
+ pNode->u64BlockAddr = offBlockAddrNode;
+
+ return pNode;
+}
+
+/**
+ * Looks up the cache extent for the given virtual block address.
+ *
+ * @returns Pointer to the cache extent or NULL if none could be found.
+ * @param pCache The cache image instance.
+ * @param offBlockOffset The block offset to search for.
+ * @param ppNextBestFit Where to store the pointer to the next best fit
+ * cache extent above offBlockOffset if existing. - Optional
+ * This is always filled if possible even if the function returns NULL.
+ */
+static PVCICACHEEXTENT vciCacheExtentLookup(PVCICACHE pCache, uint64_t offBlockOffset,
+ PVCICACHEEXTENT *ppNextBestFit)
+{
+ int rc = VINF_SUCCESS;
+ PVCICACHEEXTENT pExtent = NULL;
+ PVCITREENODE pNodeCur = pCache->pRoot;
+
+ while ( RT_SUCCESS(rc)
+ && pNodeCur
+ && pNodeCur->u8Type != VCI_TREE_NODE_TYPE_LEAF)
+ {
+ PVCITREENODEINT pNodeInt = (PVCITREENODEINT)pNodeCur;
+
+ Assert(pNodeCur->u8Type == VCI_TREE_NODE_TYPE_INTERNAL);
+
+ /* Search for the correct internal node. */
+ unsigned idxMin = 0;
+ unsigned idxMax = pNodeInt->cUsedNodes;
+ unsigned idxCur = pNodeInt->cUsedNodes / 2;
+
+ while (idxMin < idxMax)
+ {
+ PVCINODEINTERNAL pInt = &pNodeInt->aIntNodes[idxCur];
+
+ /* Determine the search direction. */
+ if (offBlockOffset < pInt->u64BlockOffset)
+ {
+ /* Search left from the current extent. */
+ idxMax = idxCur;
+ }
+ else if (offBlockOffset >= pInt->u64BlockOffset + pInt->u32Blocks)
+ {
+ /* Search right from the current extent. */
+ idxMin = idxCur;
+ }
+ else
+ {
+ /* The block lies in the node, stop searching. */
+ if (pInt->PtrChild.fInMemory)
+ pNodeCur = pInt->PtrChild.u.pNode;
+ else
+ {
+ PVCITREENODE pNodeNew;
+ VciTreeNode NodeTree;
+
+ /* Read from disk and add to the tree. */
+ rc = vdIfIoIntFileReadSync(pCache->pIfIo, pCache->pStorage,
+ VCI_BLOCK2BYTE(pInt->PtrChild.u.offAddrBlockNode),
+ &NodeTree, sizeof(NodeTree));
+ AssertRC(rc);
+
+ pNodeNew = vciTreeNodeImage2Host(pInt->PtrChild.u.offAddrBlockNode, &NodeTree);
+ if (pNodeNew)
+ {
+ /* Link to the parent. */
+ pInt->PtrChild.fInMemory = true;
+ pInt->PtrChild.u.pNode = pNodeNew;
+ pNodeNew->pParent = pNodeCur;
+ pNodeCur = pNodeNew;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ break;
+ }
+
+ idxCur = idxMin + (idxMax - idxMin) / 2;
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pNodeCur)
+ {
+ PVCITREENODELEAF pLeaf = (PVCITREENODELEAF)pNodeCur;
+ Assert(pNodeCur->u8Type == VCI_TREE_NODE_TYPE_LEAF);
+
+ /* Search the range. */
+ unsigned idxMin = 0;
+ unsigned idxMax = pLeaf->cUsedNodes;
+ unsigned idxCur = pLeaf->cUsedNodes / 2;
+
+ while (idxMin < idxMax)
+ {
+ PVCICACHEEXTENT pExtentCur = &pLeaf->aExtents[idxCur];
+
+ /* Determine the search direction. */
+ if (offBlockOffset < pExtentCur->u64BlockOffset)
+ {
+ /* Search left from the current extent. */
+ idxMax = idxCur;
+ }
+ else if (offBlockOffset >= pExtentCur->u64BlockOffset + pExtentCur->u32Blocks)
+ {
+ /* Search right from the current extent. */
+ idxMin = idxCur;
+ }
+ else
+ {
+ /* We found the extent, stop searching. */
+ pExtent = pExtentCur;
+ break;
+ }
+
+ idxCur = idxMin + (idxMax - idxMin) / 2;
+ }
+
+ /* Get the next best fit extent if it exists. */
+ if (ppNextBestFit)
+ {
+ if (idxCur < pLeaf->cUsedNodes - 1)
+ *ppNextBestFit = &pLeaf->aExtents[idxCur + 1];
+ else
+ {
+ /*
+ * Go up the tree and find the best extent
+ * in the leftmost tree of the child subtree to the right.
+ */
+ PVCITREENODEINT pInt = (PVCITREENODEINT)pLeaf->Core.pParent;
+
+ while (pInt)
+ {
+
+ }
+ }
+ }
+ }
+
+ return pExtent;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int vciOpenImage(PVCICACHE pCache, unsigned uOpenFlags)
+{
+ VciHdr Hdr;
+ uint64_t cbFile;
+ int rc;
+
+ pCache->uOpenFlags = uOpenFlags;
+
+ pCache->pIfError = VDIfErrorGet(pCache->pVDIfsDisk);
+ pCache->pIfIo = VDIfIoIntGet(pCache->pVDIfsImage);
+ AssertPtrReturn(pCache->pIfIo, VERR_INVALID_PARAMETER);
+
+ /*
+ * Open the image.
+ */
+ rc = vdIfIoIntFileOpen(pCache->pIfIo, pCache->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags,
+ false /* fCreate */),
+ &pCache->pStorage);
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ goto out;
+ }
+
+ rc = vdIfIoIntFileGetSize(pCache->pIfIo, pCache->pStorage, &cbFile);
+ if (RT_FAILURE(rc) || cbFile < sizeof(VciHdr))
+ {
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ goto out;
+ }
+
+ rc = vdIfIoIntFileReadSync(pCache->pIfIo, pCache->pStorage, 0, &Hdr,
+ VCI_BYTE2BLOCK(sizeof(Hdr)));
+ if (RT_FAILURE(rc))
+ {
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ goto out;
+ }
+
+ Hdr.u32Signature = RT_LE2H_U32(Hdr.u32Signature);
+ Hdr.u32Version = RT_LE2H_U32(Hdr.u32Version);
+ Hdr.cBlocksCache = RT_LE2H_U64(Hdr.cBlocksCache);
+ Hdr.u32CacheType = RT_LE2H_U32(Hdr.u32CacheType);
+ Hdr.offTreeRoot = RT_LE2H_U64(Hdr.offTreeRoot);
+ Hdr.offBlkMap = RT_LE2H_U64(Hdr.offBlkMap);
+ Hdr.cBlkMap = RT_LE2H_U32(Hdr.cBlkMap);
+
+ if ( Hdr.u32Signature == VCI_HDR_SIGNATURE
+ && Hdr.u32Version == VCI_HDR_VERSION)
+ {
+ pCache->offTreeRoot = Hdr.offTreeRoot;
+ pCache->offBlksBitmap = Hdr.offBlkMap;
+
+ /* Load the block map. */
+ rc = vciBlkMapLoad(pCache, pCache->offBlksBitmap, Hdr.cBlkMap, &pCache->pBlkMap);
+ if (RT_SUCCESS(rc))
+ {
+ /* Load the first tree node. */
+ VciTreeNode RootNode;
+
+ rc = vdIfIoIntFileReadSync(pCache->pIfIo, pCache->pStorage,
+ pCache->offTreeRoot, &RootNode,
+ VCI_BYTE2BLOCK(sizeof(VciTreeNode)));
+ if (RT_SUCCESS(rc))
+ {
+ pCache->pRoot = vciTreeNodeImage2Host(pCache->offTreeRoot, &RootNode);
+ if (!pCache->pRoot)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+
+out:
+ if (RT_FAILURE(rc))
+ vciFreeImage(pCache, false);
+ return rc;
+}
+
+/**
+ * Internal: Create a vci image.
+ */
+static int vciCreateImage(PVCICACHE pCache, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ unsigned uOpenFlags, PFNVDPROGRESS pfnProgress,
+ void *pvUser, unsigned uPercentStart,
+ unsigned uPercentSpan)
+{
+ RT_NOREF1(pszComment);
+ VciHdr Hdr;
+ VciTreeNode NodeRoot;
+ int rc;
+ uint64_t cBlocks = cbSize / VCI_BLOCK_SIZE; /* Size of the cache in blocks. */
+
+ pCache->uImageFlags = uImageFlags;
+ pCache->uOpenFlags = uOpenFlags & ~VD_OPEN_FLAGS_READONLY;
+
+ pCache->pIfError = VDIfErrorGet(pCache->pVDIfsDisk);
+ pCache->pIfIo = VDIfIoIntGet(pCache->pVDIfsImage);
+ AssertPtrReturn(pCache->pIfIo, VERR_INVALID_PARAMETER);
+
+ if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
+ {
+ rc = vdIfError(pCache->pIfError, VERR_VD_RAW_INVALID_TYPE, RT_SRC_POS, N_("VCI: cannot create diff image '%s'"), pCache->pszFilename);
+ return rc;
+ }
+
+ do
+ {
+ /* Create image file. */
+ rc = vdIfIoIntFileOpen(pCache->pIfIo, pCache->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags & ~VD_OPEN_FLAGS_READONLY,
+ true /* fCreate */),
+ &pCache->pStorage);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pCache->pIfError, rc, RT_SRC_POS, N_("VCI: cannot create image '%s'"), pCache->pszFilename);
+ break;
+ }
+
+ /* Allocate block bitmap. */
+ uint32_t cBlkMap = 0;
+ rc = vciBlkMapCreate(cBlocks, &pCache->pBlkMap, &cBlkMap);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pCache->pIfError, rc, RT_SRC_POS, N_("VCI: cannot create block bitmap '%s'"), pCache->pszFilename);
+ break;
+ }
+
+ /*
+ * Allocate space for the header in the block bitmap.
+ * Because the block map is empty the header has to start at block 0
+ */
+ uint64_t offHdr = 0;
+ rc = vciBlkMapAllocate(pCache->pBlkMap, VCI_BYTE2BLOCK(sizeof(VciHdr)), VCIBLKMAP_ALLOC_META, &offHdr);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pCache->pIfError, rc, RT_SRC_POS, N_("VCI: cannot allocate space for header in block bitmap '%s'"), pCache->pszFilename);
+ break;
+ }
+
+ Assert(offHdr == 0);
+
+ /*
+ * Allocate space for the block map itself.
+ */
+ uint64_t offBlkMap = 0;
+ rc = vciBlkMapAllocate(pCache->pBlkMap, cBlkMap, VCIBLKMAP_ALLOC_META, &offBlkMap);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pCache->pIfError, rc, RT_SRC_POS, N_("VCI: cannot allocate space for block map in block map '%s'"), pCache->pszFilename);
+ break;
+ }
+
+ /*
+ * Allocate space for the tree root node.
+ */
+ uint64_t offTreeRoot = 0;
+ rc = vciBlkMapAllocate(pCache->pBlkMap, VCI_BYTE2BLOCK(sizeof(VciTreeNode)), VCIBLKMAP_ALLOC_META, &offTreeRoot);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pCache->pIfError, rc, RT_SRC_POS, N_("VCI: cannot allocate space for block map in block map '%s'"), pCache->pszFilename);
+ break;
+ }
+
+ /*
+ * Allocate the in memory root node.
+ */
+ pCache->pRoot = (PVCITREENODE)RTMemAllocZ(sizeof(VCITREENODELEAF));
+ if (!pCache->pRoot)
+ {
+ rc = vdIfError(pCache->pIfError, rc, RT_SRC_POS, N_("VCI: cannot allocate B+-Tree root pointer '%s'"), pCache->pszFilename);
+ break;
+ }
+
+ pCache->pRoot->u8Type = VCI_TREE_NODE_TYPE_LEAF;
+ /* Rest remains 0 as the tree is still empty. */
+
+ /*
+ * Now that we are here we have all the basic structures and know where to place them in the image.
+ * It's time to write it now.
+ */
+
+ /* Setup the header. */
+ memset(&Hdr, 0, sizeof(VciHdr));
+ Hdr.u32Signature = RT_H2LE_U32(VCI_HDR_SIGNATURE);
+ Hdr.u32Version = RT_H2LE_U32(VCI_HDR_VERSION);
+ Hdr.cBlocksCache = RT_H2LE_U64(cBlocks);
+ Hdr.fUncleanShutdown = VCI_HDR_UNCLEAN_SHUTDOWN;
+ Hdr.u32CacheType = uImageFlags & VD_IMAGE_FLAGS_FIXED
+ ? RT_H2LE_U32(VCI_HDR_CACHE_TYPE_FIXED)
+ : RT_H2LE_U32(VCI_HDR_CACHE_TYPE_DYNAMIC);
+ Hdr.offTreeRoot = RT_H2LE_U64(offTreeRoot);
+ Hdr.offBlkMap = RT_H2LE_U64(offBlkMap);
+ Hdr.cBlkMap = RT_H2LE_U32(cBlkMap);
+
+ rc = vdIfIoIntFileWriteSync(pCache->pIfIo, pCache->pStorage, offHdr, &Hdr,
+ VCI_BYTE2BLOCK(sizeof(VciHdr)));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pCache->pIfError, rc, RT_SRC_POS, N_("VCI: cannot write header '%s'"), pCache->pszFilename);
+ break;
+ }
+
+ rc = vciBlkMapSave(pCache->pBlkMap, pCache, offBlkMap, cBlkMap);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pCache->pIfError, rc, RT_SRC_POS, N_("VCI: cannot write block map '%s'"), pCache->pszFilename);
+ break;
+ }
+
+ /* Setup the root tree. */
+ memset(&NodeRoot, 0, sizeof(VciTreeNode));
+ NodeRoot.u8Type = VCI_TREE_NODE_TYPE_LEAF;
+
+ rc = vdIfIoIntFileWriteSync(pCache->pIfIo, pCache->pStorage, offTreeRoot,
+ &NodeRoot, VCI_BYTE2BLOCK(sizeof(VciTreeNode)));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pCache->pIfError, rc, RT_SRC_POS, N_("VCI: cannot write root node '%s'"), pCache->pszFilename);
+ break;
+ }
+
+ rc = vciFlushImage(pCache);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pCache->pIfError, rc, RT_SRC_POS, N_("VCI: cannot flush '%s'"), pCache->pszFilename);
+ break;
+ }
+
+ pCache->cbSize = cbSize;
+
+ } while (0);
+
+ if (RT_SUCCESS(rc) && pfnProgress)
+ pfnProgress(pvUser, uPercentStart + uPercentSpan);
+
+ if (RT_FAILURE(rc))
+ vciFreeImage(pCache, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnProbe */
+static DECLCALLBACK(int) vciProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage)
+{
+ RT_NOREF1(pVDIfsDisk);
+ VciHdr Hdr;
+ PVDIOSTORAGE pStorage = NULL;
+ uint64_t cbFile;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename));
+
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_FAILURE(rc))
+ goto out;
+
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if (RT_FAILURE(rc) || cbFile < sizeof(VciHdr))
+ {
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ goto out;
+ }
+
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, &Hdr, sizeof(Hdr));
+ if (RT_FAILURE(rc))
+ {
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ goto out;
+ }
+
+ Hdr.u32Signature = RT_LE2H_U32(Hdr.u32Signature);
+ Hdr.u32Version = RT_LE2H_U32(Hdr.u32Version);
+ Hdr.cBlocksCache = RT_LE2H_U64(Hdr.cBlocksCache);
+ Hdr.u32CacheType = RT_LE2H_U32(Hdr.u32CacheType);
+ Hdr.offTreeRoot = RT_LE2H_U64(Hdr.offTreeRoot);
+ Hdr.offBlkMap = RT_LE2H_U64(Hdr.offBlkMap);
+ Hdr.cBlkMap = RT_LE2H_U32(Hdr.cBlkMap);
+
+ if ( Hdr.u32Signature == VCI_HDR_SIGNATURE
+ && Hdr.u32Version == VCI_HDR_VERSION)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+
+out:
+ if (pStorage)
+ vdIfIoIntFileClose(pIfIo, pStorage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnOpen */
+static DECLCALLBACK(int) vciOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p ppBackendData=%#p\n", pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, ppBackendData));
+ int rc;
+ PVCICACHE pCache;
+
+ /* Check open flags. All valid flags are supported. */
+ if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Check remaining arguments. */
+ if ( !RT_VALID_PTR(pszFilename)
+ || !*pszFilename)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+
+ pCache = (PVCICACHE)RTMemAllocZ(sizeof(VCICACHE));
+ if (!pCache)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pCache->pszFilename = pszFilename;
+ pCache->pStorage = NULL;
+ pCache->pVDIfsDisk = pVDIfsDisk;
+ pCache->pVDIfsImage = pVDIfsImage;
+
+ rc = vciOpenImage(pCache, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pCache;
+ else
+ RTMemFree(pCache);
+
+out:
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnCreate */
+static DECLCALLBACK(int) vciCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, void **ppBackendData)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p ppBackendData=%#p",
+ pszFilename, cbSize, uImageFlags, pszComment, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, ppBackendData));
+ int rc;
+ PVCICACHE pCache;
+
+ PFNVDPROGRESS pfnProgress = NULL;
+ void *pvUser = NULL;
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+ if (pIfProgress)
+ {
+ pfnProgress = pIfProgress->pfnProgress;
+ pvUser = pIfProgress->Core.pvUser;
+ }
+
+ /* Check open flags. All valid flags are supported. */
+ if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Check remaining arguments. */
+ if ( !RT_VALID_PTR(pszFilename)
+ || !*pszFilename)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ pCache = (PVCICACHE)RTMemAllocZ(sizeof(VCICACHE));
+ if (!pCache)
+ {
+ rc = VERR_NO_MEMORY;
+ goto out;
+ }
+ pCache->pszFilename = pszFilename;
+ pCache->pStorage = NULL;
+ pCache->pVDIfsDisk = pVDIfsDisk;
+ pCache->pVDIfsImage = pVDIfsImage;
+
+ rc = vciCreateImage(pCache, cbSize, uImageFlags, pszComment, uOpenFlags,
+ pfnProgress, pvUser, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ vciFreeImage(pCache, false);
+ rc = vciOpenImage(pCache, uOpenFlags);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCache);
+ goto out;
+ }
+ }
+ *ppBackendData = pCache;
+ }
+ else
+ RTMemFree(pCache);
+
+out:
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnClose */
+static DECLCALLBACK(int) vciClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ int rc;
+
+ rc = vciFreeImage(pCache, fDelete);
+ RTMemFree(pCache);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnRead */
+static DECLCALLBACK(int) vciRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu cbToRead=%zu pIoCtx=%#p pcbActuallyRead=%#p\n",
+ pBackendData, uOffset, cbToRead, pIoCtx, pcbActuallyRead));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ int rc = VINF_SUCCESS;
+ PVCICACHEEXTENT pExtent;
+ uint64_t cBlocksToRead = VCI_BYTE2BLOCK(cbToRead);
+ uint64_t offBlockAddr = VCI_BYTE2BLOCK(uOffset);
+
+ AssertPtr(pCache);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToRead % 512 == 0);
+
+ pExtent = vciCacheExtentLookup(pCache, offBlockAddr, NULL);
+ if (pExtent)
+ {
+ uint64_t offRead = offBlockAddr - pExtent->u64BlockOffset;
+ cBlocksToRead = RT_MIN(cBlocksToRead, pExtent->u32Blocks - offRead);
+
+ rc = vdIfIoIntFileReadUser(pCache->pIfIo, pCache->pStorage,
+ pExtent->u64BlockAddr + offRead,
+ pIoCtx, cBlocksToRead);
+ }
+ else
+ {
+ /** @todo Best fit to check whether we have cached data later and set
+ * pcbActuallyRead accordingly. */
+ rc = VERR_VD_BLOCK_FREE;
+ }
+
+ if (pcbActuallyRead)
+ *pcbActuallyRead = VCI_BLOCK2BYTE(cBlocksToRead);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnWrite */
+static DECLCALLBACK(int) vciWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess)
+{
+ RT_NOREF5(pBackendData, uOffset, cbToWrite, pIoCtx, pcbWriteProcess);
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu cbToWrite=%zu pIoCtx=%#p pcbWriteProcess=%#p\n",
+ pBackendData, uOffset, cbToWrite, pIoCtx, pcbWriteProcess));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ int rc = VINF_SUCCESS;
+ uint64_t cBlocksToWrite = VCI_BYTE2BLOCK(cbToWrite);
+ //uint64_t offBlockAddr = VCI_BYTE2BLOCK(uOffset);
+
+ AssertPtr(pCache); NOREF(pCache);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToWrite % 512 == 0);
+ while (cBlocksToWrite)
+ {
+
+ }
+
+ *pcbWriteProcess = cbToWrite; /** @todo Implement. */
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnFlush */
+static DECLCALLBACK(int) vciFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ RT_NOREF1(pIoCtx);
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+
+ int rc = vciFlushImage(pCache);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) vciGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+
+ AssertPtr(pCache);
+
+ if (pCache)
+ return 1;
+ else
+ return 0;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnGetSize */
+static DECLCALLBACK(uint64_t) vciGetSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtr(pCache);
+
+ if (pCache && pCache->pStorage)
+ cb = pCache->cbSize;
+
+ LogFlowFunc(("returns %llu\n", cb));
+ return cb;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) vciGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtr(pCache);
+
+ if (pCache)
+ {
+ uint64_t cbFile;
+ if (pCache->pStorage)
+ {
+ int rc = vdIfIoIntFileGetSize(pCache->pIfIo, pCache->pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb = cbFile;
+ }
+ }
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) vciGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ unsigned uImageFlags;
+
+ AssertPtr(pCache);
+
+ if (pCache)
+ uImageFlags = pCache->uImageFlags;
+ else
+ uImageFlags = 0;
+
+ LogFlowFunc(("returns %#x\n", uImageFlags));
+ return uImageFlags;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) vciGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ unsigned uOpenFlags;
+
+ AssertPtr(pCache);
+
+ if (pCache)
+ uOpenFlags = pCache->uOpenFlags;
+ else
+ uOpenFlags = 0;
+
+ LogFlowFunc(("returns %#x\n", uOpenFlags));
+ return uOpenFlags;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) vciSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ int rc;
+
+ /* Image must be opened and the new flags must be valid. Just readonly and
+ * info flags are supported. */
+ if (!pCache || (uOpenFlags & ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO)))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Implement this operation via reopening the image. */
+ rc = vciFreeImage(pCache, false);
+ if (RT_FAILURE(rc))
+ goto out;
+ rc = vciOpenImage(pCache, uOpenFlags);
+
+out:
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnGetComment */
+static DECLCALLBACK(int) vciGetComment(void *pBackendData, char *pszComment,
+ size_t cbComment)
+{
+ RT_NOREF2(pszComment, cbComment);
+ LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ int rc;
+
+ AssertPtr(pCache);
+
+ if (pCache)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc comment='%s'\n", rc, pszComment));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnSetComment */
+static DECLCALLBACK(int) vciSetComment(void *pBackendData, const char *pszComment)
+{
+ RT_NOREF1(pszComment);
+ LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ int rc;
+
+ AssertPtr(pCache);
+
+ if (pCache)
+ {
+ if (pCache->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnGetUuid */
+static DECLCALLBACK(int) vciGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ int rc;
+
+ AssertPtr(pCache);
+
+ if (pCache)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnSetUuid */
+static DECLCALLBACK(int) vciSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ int rc;
+
+ LogFlowFunc(("%RTuuid\n", pUuid));
+ AssertPtr(pCache);
+
+ if (pCache)
+ {
+ if (!(pCache->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnGetModificationUuid */
+static DECLCALLBACK(int) vciGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ int rc;
+
+ AssertPtr(pCache);
+
+ if (pCache)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnSetModificationUuid */
+static DECLCALLBACK(int) vciSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVCICACHE pCache = (PVCICACHE)pBackendData;
+ int rc;
+
+ AssertPtr(pCache);
+
+ if (pCache)
+ {
+ if (!(pCache->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDCACHEBACKEND::pfnDump */
+static DECLCALLBACK(void) vciDump(void *pBackendData)
+{
+ NOREF(pBackendData);
+}
+
+
+const VDCACHEBACKEND g_VciCacheBackend =
+{
+ /* u32Version */
+ VD_CACHEBACKEND_VERSION,
+ /* pszBackendName */
+ "vci",
+ /* uBackendCaps */
+ VD_CAP_CREATE_FIXED | VD_CAP_CREATE_DYNAMIC | VD_CAP_FILE | VD_CAP_VFS,
+ /* papszFileExtensions */
+ s_apszVciFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ vciProbe,
+ /* pfnOpen */
+ vciOpen,
+ /* pfnCreate */
+ vciCreate,
+ /* pfnClose */
+ vciClose,
+ /* pfnRead */
+ vciRead,
+ /* pfnWrite */
+ vciWrite,
+ /* pfnFlush */
+ vciFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ vciGetVersion,
+ /* pfnGetSize */
+ vciGetSize,
+ /* pfnGetFileSize */
+ vciGetFileSize,
+ /* pfnGetImageFlags */
+ vciGetImageFlags,
+ /* pfnGetOpenFlags */
+ vciGetOpenFlags,
+ /* pfnSetOpenFlags */
+ vciSetOpenFlags,
+ /* pfnGetComment */
+ vciGetComment,
+ /* pfnSetComment */
+ vciSetComment,
+ /* pfnGetUuid */
+ vciGetUuid,
+ /* pfnSetUuid */
+ vciSetUuid,
+ /* pfnGetModificationUuid */
+ vciGetModificationUuid,
+ /* pfnSetModificationUuid */
+ vciSetModificationUuid,
+ /* pfnDump */
+ vciDump,
+ /* pfnComposeLocation */
+ NULL,
+ /* pfnComposeName */
+ NULL,
+ /* u32VersionEnd */
+ VD_CACHEBACKEND_VERSION
+};
+
diff --git a/src/VBox/Storage/VD.cpp b/src/VBox/Storage/VD.cpp
new file mode 100644
index 00000000..788ce415
--- /dev/null
+++ b/src/VBox/Storage/VD.cpp
@@ -0,0 +1,9682 @@
+/* $Id: VD.cpp $ */
+/** @file
+ * VD - Virtual disk container implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD
+#include <VBox/vd.h>
+#include <VBox/err.h>
+#include <VBox/sup.h>
+#include <VBox/log.h>
+
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/sg.h>
+#include <iprt/semaphore.h>
+#include <iprt/vector.h>
+
+#include "VDInternal.h"
+
+/** Buffer size used for merging images. */
+#define VD_MERGE_BUFFER_SIZE (16 * _1M)
+
+/** Maximum number of segments in one I/O task. */
+#define VD_IO_TASK_SEGMENTS_MAX 64
+
+/** Threshold after not recently used blocks are removed from the list. */
+#define VD_DISCARD_REMOVE_THRESHOLD (10 * _1M) /** @todo experiment */
+
+/**
+ * VD async I/O interface storage descriptor.
+ */
+typedef struct VDIIOFALLBACKSTORAGE
+{
+ /** File handle. */
+ RTFILE File;
+ /** Completion callback. */
+ PFNVDCOMPLETED pfnCompleted;
+ /** Thread for async access. */
+ RTTHREAD ThreadAsync;
+} VDIIOFALLBACKSTORAGE, *PVDIIOFALLBACKSTORAGE;
+
+/**
+ * uModified bit flags.
+ */
+#define VD_IMAGE_MODIFIED_FLAG RT_BIT(0)
+#define VD_IMAGE_MODIFIED_FIRST RT_BIT(1)
+#define VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE RT_BIT(2)
+
+
+# define VD_IS_LOCKED(a_pDisk) \
+ do \
+ { \
+ NOREF(a_pDisk); \
+ AssertMsg((a_pDisk)->fLocked, \
+ ("Lock not held\n"));\
+ } while(0)
+
+/**
+ * VBox parent read descriptor, used internally for compaction.
+ */
+typedef struct VDPARENTSTATEDESC
+{
+ /** Pointer to disk descriptor. */
+ PVDISK pDisk;
+ /** Pointer to image descriptor. */
+ PVDIMAGE pImage;
+} VDPARENTSTATEDESC, *PVDPARENTSTATEDESC;
+
+/**
+ * Transfer direction.
+ */
+typedef enum VDIOCTXTXDIR
+{
+ /** Read */
+ VDIOCTXTXDIR_READ = 0,
+ /** Write */
+ VDIOCTXTXDIR_WRITE,
+ /** Flush */
+ VDIOCTXTXDIR_FLUSH,
+ /** Discard */
+ VDIOCTXTXDIR_DISCARD,
+ /** 32bit hack */
+ VDIOCTXTXDIR_32BIT_HACK = 0x7fffffff
+} VDIOCTXTXDIR, *PVDIOCTXTXDIR;
+
+/** Transfer function */
+typedef DECLCALLBACKTYPE(int, FNVDIOCTXTRANSFER ,(PVDIOCTX pIoCtx));
+/** Pointer to a transfer function. */
+typedef FNVDIOCTXTRANSFER *PFNVDIOCTXTRANSFER;
+
+/**
+ * I/O context
+ */
+typedef struct VDIOCTX
+{
+ /** Pointer to the next I/O context. */
+ struct VDIOCTX * volatile pIoCtxNext;
+ /** Disk this is request is for. */
+ PVDISK pDisk;
+ /** Return code. */
+ int rcReq;
+ /** Various flags for the I/O context. */
+ uint32_t fFlags;
+ /** Number of data transfers currently pending. */
+ volatile uint32_t cDataTransfersPending;
+ /** How many meta data transfers are pending. */
+ volatile uint32_t cMetaTransfersPending;
+ /** Flag whether the request finished */
+ volatile bool fComplete;
+ /** Temporary allocated memory which is freed
+ * when the context completes. */
+ void *pvAllocation;
+ /** Transfer function. */
+ PFNVDIOCTXTRANSFER pfnIoCtxTransfer;
+ /** Next transfer part after the current one completed. */
+ PFNVDIOCTXTRANSFER pfnIoCtxTransferNext;
+ /** Transfer direction */
+ VDIOCTXTXDIR enmTxDir;
+ /** Request type dependent data. */
+ union
+ {
+ /** I/O request (read/write). */
+ struct
+ {
+ /** Number of bytes left until this context completes. */
+ volatile uint32_t cbTransferLeft;
+ /** Current offset */
+ volatile uint64_t uOffset;
+ /** Number of bytes to transfer */
+ volatile size_t cbTransfer;
+ /** Current image in the chain. */
+ PVDIMAGE pImageCur;
+ /** Start image to read from. pImageCur is reset to this
+ * value after it reached the first image in the chain. */
+ PVDIMAGE pImageStart;
+ /** S/G buffer */
+ RTSGBUF SgBuf;
+ /** Number of bytes to clear in the buffer before the current read. */
+ size_t cbBufClear;
+ /** Number of images to read. */
+ unsigned cImagesRead;
+ /** Override for the parent image to start reading from. */
+ PVDIMAGE pImageParentOverride;
+ /** Original offset of the transfer - required for filtering read requests. */
+ uint64_t uOffsetXferOrig;
+ /** Original size of the transfer - required for fitlering read requests. */
+ size_t cbXferOrig;
+ } Io;
+ /** Discard requests. */
+ struct
+ {
+ /** Pointer to the range descriptor array. */
+ PCRTRANGE paRanges;
+ /** Number of ranges in the array. */
+ unsigned cRanges;
+ /** Range descriptor index which is processed. */
+ unsigned idxRange;
+ /** Start offset to discard currently. */
+ uint64_t offCur;
+ /** How many bytes left to discard in the current range. */
+ size_t cbDiscardLeft;
+ /** How many bytes to discard in the current block (<= cbDiscardLeft). */
+ size_t cbThisDiscard;
+ /** Discard block handled currently. */
+ PVDDISCARDBLOCK pBlock;
+ } Discard;
+ } Req;
+ /** Parent I/O context if any. Sets the type of the context (root/child) */
+ PVDIOCTX pIoCtxParent;
+ /** Type dependent data (root/child) */
+ union
+ {
+ /** Root data */
+ struct
+ {
+ /** Completion callback */
+ PFNVDASYNCTRANSFERCOMPLETE pfnComplete;
+ /** User argument 1 passed on completion. */
+ void *pvUser1;
+ /** User argument 2 passed on completion. */
+ void *pvUser2;
+ } Root;
+ /** Child data */
+ struct
+ {
+ /** Saved start offset */
+ uint64_t uOffsetSaved;
+ /** Saved transfer size */
+ size_t cbTransferLeftSaved;
+ /** Number of bytes transferred from the parent if this context completes. */
+ size_t cbTransferParent;
+ /** Number of bytes to pre read */
+ size_t cbPreRead;
+ /** Number of bytes to post read. */
+ size_t cbPostRead;
+ /** Number of bytes to write left in the parent. */
+ size_t cbWriteParent;
+ /** Write type dependent data. */
+ union
+ {
+ /** Optimized */
+ struct
+ {
+ /** Bytes to fill to satisfy the block size. Not part of the virtual disk. */
+ size_t cbFill;
+ /** Bytes to copy instead of reading from the parent */
+ size_t cbWriteCopy;
+ /** Bytes to read from the image. */
+ size_t cbReadImage;
+ } Optimized;
+ } Write;
+ } Child;
+ } Type;
+} VDIOCTX;
+
+/** Default flags for an I/O context, i.e. unblocked and async. */
+#define VDIOCTX_FLAGS_DEFAULT (0)
+/** Flag whether the context is blocked. */
+#define VDIOCTX_FLAGS_BLOCKED RT_BIT_32(0)
+/** Flag whether the I/O context is using synchronous I/O. */
+#define VDIOCTX_FLAGS_SYNC RT_BIT_32(1)
+/** Flag whether the read should update the cache. */
+#define VDIOCTX_FLAGS_READ_UPDATE_CACHE RT_BIT_32(2)
+/** Flag whether free blocks should be zeroed.
+ * If false and no image has data for sepcified
+ * range VERR_VD_BLOCK_FREE is returned for the I/O context.
+ * Note that unallocated blocks are still zeroed
+ * if at least one image has valid data for a part
+ * of the range.
+ */
+#define VDIOCTX_FLAGS_ZERO_FREE_BLOCKS RT_BIT_32(3)
+/** Don't free the I/O context when complete because
+ * it was alloacted elsewhere (stack, ...). */
+#define VDIOCTX_FLAGS_DONT_FREE RT_BIT_32(4)
+/** Don't set the modified flag for this I/O context when writing. */
+#define VDIOCTX_FLAGS_DONT_SET_MODIFIED_FLAG RT_BIT_32(5)
+/** The write filter was applied already and shouldn't be applied a second time.
+ * Used at the beginning of vdWriteHelperAsync() because it might be called
+ * multiple times.
+ */
+#define VDIOCTX_FLAGS_WRITE_FILTER_APPLIED RT_BIT_32(6)
+
+/** NIL I/O context pointer value. */
+#define NIL_VDIOCTX ((PVDIOCTX)0)
+
+/**
+ * List node for deferred I/O contexts.
+ */
+typedef struct VDIOCTXDEFERRED
+{
+ /** Node in the list of deferred requests.
+ * A request can be deferred if the image is growing
+ * and the request accesses the same range or if
+ * the backend needs to read or write metadata from the disk
+ * before it can continue. */
+ RTLISTNODE NodeDeferred;
+ /** I/O context this entry points to. */
+ PVDIOCTX pIoCtx;
+} VDIOCTXDEFERRED, *PVDIOCTXDEFERRED;
+
+/**
+ * I/O task.
+ */
+typedef struct VDIOTASK
+{
+ /** Next I/O task waiting in the list. */
+ struct VDIOTASK * volatile pNext;
+ /** Storage this task belongs to. */
+ PVDIOSTORAGE pIoStorage;
+ /** Optional completion callback. */
+ PFNVDXFERCOMPLETED pfnComplete;
+ /** Opaque user data. */
+ void *pvUser;
+ /** Completion status code for the task. */
+ int rcReq;
+ /** Flag whether this is a meta data transfer. */
+ bool fMeta;
+ /** Type dependent data. */
+ union
+ {
+ /** User data transfer. */
+ struct
+ {
+ /** Number of bytes this task transferred. */
+ uint32_t cbTransfer;
+ /** Pointer to the I/O context the task belongs. */
+ PVDIOCTX pIoCtx;
+ } User;
+ /** Meta data transfer. */
+ struct
+ {
+ /** Meta transfer this task is for. */
+ PVDMETAXFER pMetaXfer;
+ } Meta;
+ } Type;
+} VDIOTASK;
+
+/**
+ * Storage handle.
+ */
+typedef struct VDIOSTORAGE
+{
+ /** Image I/O state this storage handle belongs to. */
+ PVDIO pVDIo;
+ /** AVL tree for pending async metadata transfers. */
+ PAVLRFOFFTREE pTreeMetaXfers;
+ /** Storage handle */
+ void *pStorage;
+} VDIOSTORAGE;
+
+/**
+ * Metadata transfer.
+ *
+ * @note This entry can't be freed if either the list is not empty or
+ * the reference counter is not 0.
+ * The assumption is that the backends don't need to read huge amounts of
+ * metadata to complete a transfer so the additional memory overhead should
+ * be relatively small.
+ */
+typedef struct VDMETAXFER
+{
+ /** AVL core for fast search (the file offset is the key) */
+ AVLRFOFFNODECORE Core;
+ /** I/O storage for this transfer. */
+ PVDIOSTORAGE pIoStorage;
+ /** Flags. */
+ uint32_t fFlags;
+ /** List of I/O contexts waiting for this metadata transfer to complete. */
+ RTLISTNODE ListIoCtxWaiting;
+ /** Number of references to this entry. */
+ unsigned cRefs;
+ /** Size of the data stored with this entry. */
+ size_t cbMeta;
+ /** Shadow buffer which is used in case a write is still active and other
+ * writes update the shadow buffer. */
+ uint8_t *pbDataShw;
+ /** List of I/O contexts updating the shadow buffer while there is a write
+ * in progress. */
+ RTLISTNODE ListIoCtxShwWrites;
+ /** Data stored - variable size. */
+ uint8_t abData[1];
+} VDMETAXFER;
+
+/**
+ * The transfer direction for the metadata.
+ */
+#define VDMETAXFER_TXDIR_MASK 0x3
+#define VDMETAXFER_TXDIR_NONE 0x0
+#define VDMETAXFER_TXDIR_WRITE 0x1
+#define VDMETAXFER_TXDIR_READ 0x2
+#define VDMETAXFER_TXDIR_FLUSH 0x3
+#define VDMETAXFER_TXDIR_GET(flags) ((flags) & VDMETAXFER_TXDIR_MASK)
+#define VDMETAXFER_TXDIR_SET(flags, dir) ((flags) = (flags & ~VDMETAXFER_TXDIR_MASK) | (dir))
+
+/** Forward declaration of the async discard helper. */
+static DECLCALLBACK(int) vdDiscardHelperAsync(PVDIOCTX pIoCtx);
+static DECLCALLBACK(int) vdWriteHelperAsync(PVDIOCTX pIoCtx);
+static void vdDiskProcessBlockedIoCtx(PVDISK pDisk);
+static int vdDiskUnlock(PVDISK pDisk, PVDIOCTX pIoCtxRc);
+static DECLCALLBACK(void) vdIoCtxSyncComplete(void *pvUser1, void *pvUser2, int rcReq);
+
+/**
+ * internal: issue error message.
+ */
+static int vdError(PVDISK pDisk, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pDisk->pInterfaceError)
+ pDisk->pInterfaceError->pfnError(pDisk->pInterfaceError->Core.pvUser, rc, RT_SRC_POS_ARGS, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * internal: thread synchronization, start read.
+ */
+DECLINLINE(int) vdThreadStartRead(PVDISK pDisk)
+{
+ int rc = VINF_SUCCESS;
+ if (RT_UNLIKELY(pDisk->pInterfaceThreadSync))
+ rc = pDisk->pInterfaceThreadSync->pfnStartRead(pDisk->pInterfaceThreadSync->Core.pvUser);
+ return rc;
+}
+
+/**
+ * internal: thread synchronization, finish read.
+ */
+DECLINLINE(int) vdThreadFinishRead(PVDISK pDisk)
+{
+ int rc = VINF_SUCCESS;
+ if (RT_UNLIKELY(pDisk->pInterfaceThreadSync))
+ rc = pDisk->pInterfaceThreadSync->pfnFinishRead(pDisk->pInterfaceThreadSync->Core.pvUser);
+ return rc;
+}
+
+/**
+ * internal: thread synchronization, start write.
+ */
+DECLINLINE(int) vdThreadStartWrite(PVDISK pDisk)
+{
+ int rc = VINF_SUCCESS;
+ if (RT_UNLIKELY(pDisk->pInterfaceThreadSync))
+ rc = pDisk->pInterfaceThreadSync->pfnStartWrite(pDisk->pInterfaceThreadSync->Core.pvUser);
+ return rc;
+}
+
+/**
+ * internal: thread synchronization, finish write.
+ */
+DECLINLINE(int) vdThreadFinishWrite(PVDISK pDisk)
+{
+ int rc = VINF_SUCCESS;
+ if (RT_UNLIKELY(pDisk->pInterfaceThreadSync))
+ rc = pDisk->pInterfaceThreadSync->pfnFinishWrite(pDisk->pInterfaceThreadSync->Core.pvUser);
+ return rc;
+}
+
+/**
+ * internal: add image structure to the end of images list.
+ */
+static void vdAddImageToList(PVDISK pDisk, PVDIMAGE pImage)
+{
+ pImage->pPrev = NULL;
+ pImage->pNext = NULL;
+
+ if (pDisk->pBase)
+ {
+ Assert(pDisk->cImages > 0);
+ pImage->pPrev = pDisk->pLast;
+ pDisk->pLast->pNext = pImage;
+ pDisk->pLast = pImage;
+ }
+ else
+ {
+ Assert(pDisk->cImages == 0);
+ pDisk->pBase = pImage;
+ pDisk->pLast = pImage;
+ }
+
+ pDisk->cImages++;
+}
+
+/**
+ * internal: remove image structure from the images list.
+ */
+static void vdRemoveImageFromList(PVDISK pDisk, PVDIMAGE pImage)
+{
+ Assert(pDisk->cImages > 0);
+
+ if (pImage->pPrev)
+ pImage->pPrev->pNext = pImage->pNext;
+ else
+ pDisk->pBase = pImage->pNext;
+
+ if (pImage->pNext)
+ pImage->pNext->pPrev = pImage->pPrev;
+ else
+ pDisk->pLast = pImage->pPrev;
+
+ pImage->pPrev = NULL;
+ pImage->pNext = NULL;
+
+ pDisk->cImages--;
+}
+
+/**
+ * Release a referene to the filter decrementing the counter and destroying the filter
+ * when the counter reaches zero.
+ *
+ * @returns The new reference count.
+ * @param pFilter The filter to release.
+ */
+static uint32_t vdFilterRelease(PVDFILTER pFilter)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pFilter->cRefs);
+ if (!cRefs)
+ {
+ pFilter->pBackend->pfnDestroy(pFilter->pvBackendData);
+ RTMemFree(pFilter);
+ }
+
+ return cRefs;
+}
+
+/**
+ * Increments the reference counter of the given filter.
+ *
+ * @return The new reference count.
+ * @param pFilter The filter.
+ */
+static uint32_t vdFilterRetain(PVDFILTER pFilter)
+{
+ return ASMAtomicIncU32(&pFilter->cRefs);
+}
+
+/**
+ * internal: find image by index into the images list.
+ */
+static PVDIMAGE vdGetImageByNumber(PVDISK pDisk, unsigned nImage)
+{
+ PVDIMAGE pImage = pDisk->pBase;
+ if (nImage == VD_LAST_IMAGE)
+ return pDisk->pLast;
+ while (pImage && nImage)
+ {
+ pImage = pImage->pNext;
+ nImage--;
+ }
+ return pImage;
+}
+
+/**
+ * Creates a new region list from the given one converting to match the flags if necessary.
+ *
+ * @returns VBox status code.
+ * @param pRegionList The region list to convert from.
+ * @param fFlags The flags for the new region list.
+ * @param ppRegionList Where to store the new region list on success.
+ */
+static int vdRegionListConv(PCVDREGIONLIST pRegionList, uint32_t fFlags, PPVDREGIONLIST ppRegionList)
+{
+ int rc = VINF_SUCCESS;
+ PVDREGIONLIST pRegionListNew = (PVDREGIONLIST)RTMemDup(pRegionList,
+ RT_UOFFSETOF_DYN(VDREGIONLIST, aRegions[pRegionList->cRegions]));
+ if (RT_LIKELY(pRegionListNew))
+ {
+ /* Do we have to convert anything? */
+ if (pRegionList->fFlags != fFlags)
+ {
+ uint64_t offRegionNext = 0;
+
+ pRegionListNew->fFlags = fFlags;
+ for (unsigned i = 0; i < pRegionListNew->cRegions; i++)
+ {
+ PVDREGIONDESC pRegion = &pRegionListNew->aRegions[i];
+
+ if ( (fFlags & VD_REGION_LIST_F_LOC_SIZE_BLOCKS)
+ && !(pRegionList->fFlags & VD_REGION_LIST_F_LOC_SIZE_BLOCKS))
+ {
+ Assert(!(pRegion->cRegionBlocksOrBytes % pRegion->cbBlock));
+
+ /* Convert from bytes to logical blocks. */
+ pRegion->offRegion = offRegionNext;
+ pRegion->cRegionBlocksOrBytes = pRegion->cRegionBlocksOrBytes / pRegion->cbBlock;
+ offRegionNext += pRegion->cRegionBlocksOrBytes;
+ }
+ else
+ {
+ /* Convert from logical blocks to bytes. */
+ pRegion->offRegion = offRegionNext;
+ pRegion->cRegionBlocksOrBytes = pRegion->cRegionBlocksOrBytes * pRegion->cbBlock;
+ offRegionNext += pRegion->cRegionBlocksOrBytes;
+ }
+ }
+ }
+
+ *ppRegionList = pRegionListNew;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Returns the virtual size of the image in bytes.
+ *
+ * @returns Size of the given image in bytes.
+ * @param pImage The image to get the size from.
+ */
+static uint64_t vdImageGetSize(PVDIMAGE pImage)
+{
+ uint64_t cbImage = 0;
+
+ if (pImage->cbImage == VD_IMAGE_SIZE_UNINITIALIZED)
+ {
+ PCVDREGIONLIST pRegionList = NULL;
+ int rc = pImage->Backend->pfnQueryRegions(pImage->pBackendData, &pRegionList);
+ if (RT_SUCCESS(rc))
+ {
+ if (pRegionList->fFlags & VD_REGION_LIST_F_LOC_SIZE_BLOCKS)
+ {
+ PVDREGIONLIST pRegionListConv = NULL;
+ rc = vdRegionListConv(pRegionList, 0, &pRegionListConv);
+ if (RT_SUCCESS(rc))
+ {
+ for (uint32_t i = 0; i < pRegionListConv->cRegions; i++)
+ cbImage += pRegionListConv->aRegions[i].cRegionBlocksOrBytes;
+
+ VDRegionListFree(pRegionListConv);
+ }
+ }
+ else
+ for (uint32_t i = 0; i < pRegionList->cRegions; i++)
+ cbImage += pRegionList->aRegions[i].cRegionBlocksOrBytes;
+
+ AssertPtr(pImage->Backend->pfnRegionListRelease);
+ pImage->Backend->pfnRegionListRelease(pImage->pBackendData, pRegionList);
+ pImage->cbImage = cbImage; /* Cache the value. */
+ }
+ }
+ else
+ cbImage = pImage->cbImage;
+
+ return cbImage;
+}
+
+/**
+ * Applies the filter chain to the given write request.
+ *
+ * @returns VBox status code.
+ * @param pDisk The HDD container.
+ * @param uOffset The start offset of the write.
+ * @param cbWrite Number of bytes to write.
+ * @param pIoCtx The I/O context associated with the request.
+ */
+static int vdFilterChainApplyWrite(PVDISK pDisk, uint64_t uOffset, size_t cbWrite,
+ PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ VD_IS_LOCKED(pDisk);
+
+ PVDFILTER pFilter;
+ RTListForEach(&pDisk->ListFilterChainWrite, pFilter, VDFILTER, ListNodeChainWrite)
+ {
+ rc = pFilter->pBackend->pfnFilterWrite(pFilter->pvBackendData, uOffset, cbWrite, pIoCtx);
+ if (RT_FAILURE(rc))
+ break;
+ /* Reset S/G buffer for the next filter. */
+ RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+ }
+
+ return rc;
+}
+
+/**
+ * Applies the filter chain to the given read request.
+ *
+ * @returns VBox status code.
+ * @param pDisk The HDD container.
+ * @param uOffset The start offset of the read.
+ * @param cbRead Number of bytes read.
+ * @param pIoCtx The I/O context associated with the request.
+ */
+static int vdFilterChainApplyRead(PVDISK pDisk, uint64_t uOffset, size_t cbRead,
+ PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ VD_IS_LOCKED(pDisk);
+
+ /* Reset buffer before starting. */
+ RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+
+ PVDFILTER pFilter;
+ RTListForEach(&pDisk->ListFilterChainRead, pFilter, VDFILTER, ListNodeChainRead)
+ {
+ rc = pFilter->pBackend->pfnFilterRead(pFilter->pvBackendData, uOffset, cbRead, pIoCtx);
+ if (RT_FAILURE(rc))
+ break;
+ /* Reset S/G buffer for the next filter. */
+ RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+ }
+
+ return rc;
+}
+
+DECLINLINE(void) vdIoCtxRootComplete(PVDISK pDisk, PVDIOCTX pIoCtx)
+{
+ if ( RT_SUCCESS(pIoCtx->rcReq)
+ && pIoCtx->enmTxDir == VDIOCTXTXDIR_READ)
+ pIoCtx->rcReq = vdFilterChainApplyRead(pDisk, pIoCtx->Req.Io.uOffsetXferOrig,
+ pIoCtx->Req.Io.cbXferOrig, pIoCtx);
+
+ pIoCtx->Type.Root.pfnComplete(pIoCtx->Type.Root.pvUser1,
+ pIoCtx->Type.Root.pvUser2,
+ pIoCtx->rcReq);
+}
+
+/**
+ * Initialize the structure members of a given I/O context.
+ */
+DECLINLINE(void) vdIoCtxInit(PVDIOCTX pIoCtx, PVDISK pDisk, VDIOCTXTXDIR enmTxDir,
+ uint64_t uOffset, size_t cbTransfer, PVDIMAGE pImageStart,
+ PCRTSGBUF pSgBuf, void *pvAllocation,
+ PFNVDIOCTXTRANSFER pfnIoCtxTransfer, uint32_t fFlags)
+{
+ pIoCtx->pDisk = pDisk;
+ pIoCtx->enmTxDir = enmTxDir;
+ pIoCtx->Req.Io.cbTransferLeft = (uint32_t)cbTransfer; Assert((uint32_t)cbTransfer == cbTransfer);
+ pIoCtx->Req.Io.uOffset = uOffset;
+ pIoCtx->Req.Io.cbTransfer = cbTransfer;
+ pIoCtx->Req.Io.pImageStart = pImageStart;
+ pIoCtx->Req.Io.pImageCur = pImageStart;
+ pIoCtx->Req.Io.cbBufClear = 0;
+ pIoCtx->Req.Io.pImageParentOverride = NULL;
+ pIoCtx->Req.Io.uOffsetXferOrig = uOffset;
+ pIoCtx->Req.Io.cbXferOrig = cbTransfer;
+ pIoCtx->cDataTransfersPending = 0;
+ pIoCtx->cMetaTransfersPending = 0;
+ pIoCtx->fComplete = false;
+ pIoCtx->fFlags = fFlags;
+ pIoCtx->pvAllocation = pvAllocation;
+ pIoCtx->pfnIoCtxTransfer = pfnIoCtxTransfer;
+ pIoCtx->pfnIoCtxTransferNext = NULL;
+ pIoCtx->rcReq = VINF_SUCCESS;
+ pIoCtx->pIoCtxParent = NULL;
+
+ /* There is no S/G list for a flush request. */
+ if ( enmTxDir != VDIOCTXTXDIR_FLUSH
+ && enmTxDir != VDIOCTXTXDIR_DISCARD)
+ RTSgBufClone(&pIoCtx->Req.Io.SgBuf, pSgBuf);
+ else
+ memset(&pIoCtx->Req.Io.SgBuf, 0, sizeof(RTSGBUF));
+}
+
+/**
+ * Internal: Tries to read the desired range from the given cache.
+ *
+ * @returns VBox status code.
+ * @retval VERR_VD_BLOCK_FREE if the block is not in the cache.
+ * pcbRead will be set to the number of bytes not in the cache.
+ * Everything thereafter might be in the cache.
+ * @param pCache The cache to read from.
+ * @param uOffset Offset of the virtual disk to read.
+ * @param cbRead How much to read.
+ * @param pIoCtx The I/O context to read into.
+ * @param pcbRead Where to store the number of bytes actually read.
+ * On success this indicates the number of bytes read from the cache.
+ * If VERR_VD_BLOCK_FREE is returned this gives the number of bytes
+ * which are not in the cache.
+ * In both cases everything beyond this value
+ * might or might not be in the cache.
+ */
+static int vdCacheReadHelper(PVDCACHE pCache, uint64_t uOffset,
+ size_t cbRead, PVDIOCTX pIoCtx, size_t *pcbRead)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pCache=%#p uOffset=%llu pIoCtx=%p cbRead=%zu pcbRead=%#p\n",
+ pCache, uOffset, pIoCtx, cbRead, pcbRead));
+
+ AssertPtr(pCache);
+ AssertPtr(pcbRead);
+
+ rc = pCache->Backend->pfnRead(pCache->pBackendData, uOffset, cbRead,
+ pIoCtx, pcbRead);
+
+ LogFlowFunc(("returns rc=%Rrc pcbRead=%zu\n", rc, *pcbRead));
+ return rc;
+}
+
+/**
+ * Internal: Writes data for the given block into the cache.
+ *
+ * @returns VBox status code.
+ * @param pCache The cache to write to.
+ * @param uOffset Offset of the virtual disk to write to the cache.
+ * @param cbWrite How much to write.
+ * @param pIoCtx The I/O context to write from.
+ * @param pcbWritten How much data could be written, optional.
+ */
+static int vdCacheWriteHelper(PVDCACHE pCache, uint64_t uOffset, size_t cbWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWritten)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pCache=%#p uOffset=%llu pIoCtx=%p cbWrite=%zu pcbWritten=%#p\n",
+ pCache, uOffset, pIoCtx, cbWrite, pcbWritten));
+
+ AssertPtr(pCache);
+ AssertPtr(pIoCtx);
+ Assert(cbWrite > 0);
+
+ if (pcbWritten)
+ rc = pCache->Backend->pfnWrite(pCache->pBackendData, uOffset, cbWrite,
+ pIoCtx, pcbWritten);
+ else
+ {
+ size_t cbWritten = 0;
+
+ do
+ {
+ rc = pCache->Backend->pfnWrite(pCache->pBackendData, uOffset, cbWrite,
+ pIoCtx, &cbWritten);
+ uOffset += cbWritten;
+ cbWrite -= cbWritten;
+ } while ( cbWrite
+ && ( RT_SUCCESS(rc)
+ || rc == VERR_VD_ASYNC_IO_IN_PROGRESS));
+ }
+
+ LogFlowFunc(("returns rc=%Rrc pcbWritten=%zu\n",
+ rc, pcbWritten ? *pcbWritten : cbWrite));
+ return rc;
+}
+
+/**
+ * Creates a new empty discard state.
+ *
+ * @returns Pointer to the new discard state or NULL if out of memory.
+ */
+static PVDDISCARDSTATE vdDiscardStateCreate(void)
+{
+ PVDDISCARDSTATE pDiscard = (PVDDISCARDSTATE)RTMemAllocZ(sizeof(VDDISCARDSTATE));
+
+ if (pDiscard)
+ {
+ RTListInit(&pDiscard->ListLru);
+ pDiscard->pTreeBlocks = (PAVLRU64TREE)RTMemAllocZ(sizeof(AVLRU64TREE));
+ if (!pDiscard->pTreeBlocks)
+ {
+ RTMemFree(pDiscard);
+ pDiscard = NULL;
+ }
+ }
+
+ return pDiscard;
+}
+
+/**
+ * Removes the least recently used blocks from the waiting list until
+ * the new value is reached.
+ *
+ * @returns VBox status code.
+ * @param pDisk VD disk container.
+ * @param pDiscard The discard state.
+ * @param cbDiscardingNew How many bytes should be waiting on success.
+ * The number of bytes waiting can be less.
+ */
+static int vdDiscardRemoveBlocks(PVDISK pDisk, PVDDISCARDSTATE pDiscard, size_t cbDiscardingNew)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p pDiscard=%#p cbDiscardingNew=%zu\n",
+ pDisk, pDiscard, cbDiscardingNew));
+
+ while (pDiscard->cbDiscarding > cbDiscardingNew)
+ {
+ PVDDISCARDBLOCK pBlock = RTListGetLast(&pDiscard->ListLru, VDDISCARDBLOCK, NodeLru);
+
+ Assert(!RTListIsEmpty(&pDiscard->ListLru));
+
+ /* Go over the allocation bitmap and mark all discarded sectors as unused. */
+ uint64_t offStart = pBlock->Core.Key;
+ uint32_t idxStart = 0;
+ size_t cbLeft = pBlock->cbDiscard;
+ bool fAllocated = ASMBitTest(pBlock->pbmAllocated, idxStart);
+ uint32_t cSectors = (uint32_t)(pBlock->cbDiscard / 512);
+
+ while (cbLeft > 0)
+ {
+ int32_t idxEnd;
+ size_t cbThis = cbLeft;
+
+ if (fAllocated)
+ {
+ /* Check for the first unallocated bit. */
+ idxEnd = ASMBitNextClear(pBlock->pbmAllocated, cSectors, idxStart);
+ if (idxEnd != -1)
+ {
+ cbThis = (idxEnd - idxStart) * 512;
+ fAllocated = false;
+ }
+ }
+ else
+ {
+ /* Mark as unused and check for the first set bit. */
+ idxEnd = ASMBitNextSet(pBlock->pbmAllocated, cSectors, idxStart);
+ if (idxEnd != -1)
+ cbThis = (idxEnd - idxStart) * 512;
+
+
+ VDIOCTX IoCtx;
+ vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_DISCARD, 0, 0, NULL,
+ NULL, NULL, NULL, VDIOCTX_FLAGS_SYNC);
+ rc = pDisk->pLast->Backend->pfnDiscard(pDisk->pLast->pBackendData,
+ &IoCtx, offStart, cbThis, NULL,
+ NULL, &cbThis, NULL,
+ VD_DISCARD_MARK_UNUSED);
+ if (RT_FAILURE(rc))
+ break;
+
+ fAllocated = true;
+ }
+
+ idxStart = idxEnd;
+ offStart += cbThis;
+ cbLeft -= cbThis;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ PVDDISCARDBLOCK pBlockRemove = (PVDDISCARDBLOCK)RTAvlrU64RangeRemove(pDiscard->pTreeBlocks, pBlock->Core.Key);
+ Assert(pBlockRemove == pBlock); NOREF(pBlockRemove);
+ RTListNodeRemove(&pBlock->NodeLru);
+
+ pDiscard->cbDiscarding -= pBlock->cbDiscard;
+ RTMemFree(pBlock->pbmAllocated);
+ RTMemFree(pBlock);
+ }
+
+ Assert(RT_FAILURE(rc) || pDiscard->cbDiscarding <= cbDiscardingNew);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Destroys the current discard state, writing any waiting blocks to the image.
+ *
+ * @returns VBox status code.
+ * @param pDisk VD disk container.
+ */
+static int vdDiscardStateDestroy(PVDISK pDisk)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pDisk->pDiscard)
+ {
+ rc = vdDiscardRemoveBlocks(pDisk, pDisk->pDiscard, 0 /* Remove all blocks. */);
+ AssertRC(rc);
+ RTMemFree(pDisk->pDiscard->pTreeBlocks);
+ RTMemFree(pDisk->pDiscard);
+ pDisk->pDiscard = NULL;
+ }
+
+ return rc;
+}
+
+/**
+ * Marks the given range as allocated in the image.
+ * Required if there are discards in progress and a write to a block which can get discarded
+ * is written to.
+ *
+ * @returns VBox status code.
+ * @param pDisk VD container data.
+ * @param uOffset First byte to mark as allocated.
+ * @param cbRange Number of bytes to mark as allocated.
+ */
+static int vdDiscardSetRangeAllocated(PVDISK pDisk, uint64_t uOffset, size_t cbRange)
+{
+ PVDDISCARDSTATE pDiscard = pDisk->pDiscard;
+ int rc = VINF_SUCCESS;
+
+ if (pDiscard)
+ {
+ do
+ {
+ size_t cbThisRange = cbRange;
+ PVDDISCARDBLOCK pBlock = (PVDDISCARDBLOCK)RTAvlrU64RangeGet(pDiscard->pTreeBlocks, uOffset);
+
+ if (pBlock)
+ {
+ int32_t idxStart, idxEnd;
+
+ Assert(!(cbThisRange % 512));
+ Assert(!((uOffset - pBlock->Core.Key) % 512));
+
+ cbThisRange = RT_MIN(cbThisRange, pBlock->Core.KeyLast - uOffset + 1);
+
+ idxStart = (uOffset - pBlock->Core.Key) / 512;
+ idxEnd = idxStart + (int32_t)(cbThisRange / 512);
+ ASMBitSetRange(pBlock->pbmAllocated, idxStart, idxEnd);
+ }
+ else
+ {
+ pBlock = (PVDDISCARDBLOCK)RTAvlrU64GetBestFit(pDiscard->pTreeBlocks, uOffset, true);
+ if (pBlock)
+ cbThisRange = RT_MIN(cbThisRange, pBlock->Core.Key - uOffset);
+ }
+
+ Assert(cbRange >= cbThisRange);
+
+ uOffset += cbThisRange;
+ cbRange -= cbThisRange;
+ } while (cbRange != 0);
+ }
+
+ return rc;
+}
+
+DECLINLINE(PVDIOCTX) vdIoCtxAlloc(PVDISK pDisk, VDIOCTXTXDIR enmTxDir,
+ uint64_t uOffset, size_t cbTransfer,
+ PVDIMAGE pImageStart,PCRTSGBUF pSgBuf,
+ void *pvAllocation, PFNVDIOCTXTRANSFER pfnIoCtxTransfer,
+ uint32_t fFlags)
+{
+ PVDIOCTX pIoCtx = NULL;
+
+ pIoCtx = (PVDIOCTX)RTMemCacheAlloc(pDisk->hMemCacheIoCtx);
+ if (RT_LIKELY(pIoCtx))
+ {
+ vdIoCtxInit(pIoCtx, pDisk, enmTxDir, uOffset, cbTransfer, pImageStart,
+ pSgBuf, pvAllocation, pfnIoCtxTransfer, fFlags);
+ }
+
+ return pIoCtx;
+}
+
+DECLINLINE(PVDIOCTX) vdIoCtxRootAlloc(PVDISK pDisk, VDIOCTXTXDIR enmTxDir,
+ uint64_t uOffset, size_t cbTransfer,
+ PVDIMAGE pImageStart, PCRTSGBUF pSgBuf,
+ PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
+ void *pvUser1, void *pvUser2,
+ void *pvAllocation,
+ PFNVDIOCTXTRANSFER pfnIoCtxTransfer,
+ uint32_t fFlags)
+{
+ PVDIOCTX pIoCtx = vdIoCtxAlloc(pDisk, enmTxDir, uOffset, cbTransfer, pImageStart,
+ pSgBuf, pvAllocation, pfnIoCtxTransfer, fFlags);
+
+ if (RT_LIKELY(pIoCtx))
+ {
+ pIoCtx->pIoCtxParent = NULL;
+ pIoCtx->Type.Root.pfnComplete = pfnComplete;
+ pIoCtx->Type.Root.pvUser1 = pvUser1;
+ pIoCtx->Type.Root.pvUser2 = pvUser2;
+ }
+
+ LogFlow(("Allocated root I/O context %#p\n", pIoCtx));
+ return pIoCtx;
+}
+
+DECLINLINE(void) vdIoCtxDiscardInit(PVDIOCTX pIoCtx, PVDISK pDisk, PCRTRANGE paRanges,
+ unsigned cRanges, PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
+ void *pvUser1, void *pvUser2, void *pvAllocation,
+ PFNVDIOCTXTRANSFER pfnIoCtxTransfer, uint32_t fFlags)
+{
+ pIoCtx->pIoCtxNext = NULL;
+ pIoCtx->pDisk = pDisk;
+ pIoCtx->enmTxDir = VDIOCTXTXDIR_DISCARD;
+ pIoCtx->cDataTransfersPending = 0;
+ pIoCtx->cMetaTransfersPending = 0;
+ pIoCtx->fComplete = false;
+ pIoCtx->fFlags = fFlags;
+ pIoCtx->pvAllocation = pvAllocation;
+ pIoCtx->pfnIoCtxTransfer = pfnIoCtxTransfer;
+ pIoCtx->pfnIoCtxTransferNext = NULL;
+ pIoCtx->rcReq = VINF_SUCCESS;
+ pIoCtx->Req.Discard.paRanges = paRanges;
+ pIoCtx->Req.Discard.cRanges = cRanges;
+ pIoCtx->Req.Discard.idxRange = 0;
+ pIoCtx->Req.Discard.cbDiscardLeft = 0;
+ pIoCtx->Req.Discard.offCur = 0;
+ pIoCtx->Req.Discard.cbThisDiscard = 0;
+
+ pIoCtx->pIoCtxParent = NULL;
+ pIoCtx->Type.Root.pfnComplete = pfnComplete;
+ pIoCtx->Type.Root.pvUser1 = pvUser1;
+ pIoCtx->Type.Root.pvUser2 = pvUser2;
+}
+
+DECLINLINE(PVDIOCTX) vdIoCtxDiscardAlloc(PVDISK pDisk, PCRTRANGE paRanges,
+ unsigned cRanges,
+ PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
+ void *pvUser1, void *pvUser2,
+ void *pvAllocation,
+ PFNVDIOCTXTRANSFER pfnIoCtxTransfer,
+ uint32_t fFlags)
+{
+ PVDIOCTX pIoCtx = NULL;
+
+ pIoCtx = (PVDIOCTX)RTMemCacheAlloc(pDisk->hMemCacheIoCtx);
+ if (RT_LIKELY(pIoCtx))
+ {
+ vdIoCtxDiscardInit(pIoCtx, pDisk, paRanges, cRanges, pfnComplete, pvUser1,
+ pvUser2, pvAllocation, pfnIoCtxTransfer, fFlags);
+ }
+
+ LogFlow(("Allocated discard I/O context %#p\n", pIoCtx));
+ return pIoCtx;
+}
+
+DECLINLINE(PVDIOCTX) vdIoCtxChildAlloc(PVDISK pDisk, VDIOCTXTXDIR enmTxDir,
+ uint64_t uOffset, size_t cbTransfer,
+ PVDIMAGE pImageStart, PCRTSGBUF pSgBuf,
+ PVDIOCTX pIoCtxParent, size_t cbTransferParent,
+ size_t cbWriteParent, void *pvAllocation,
+ PFNVDIOCTXTRANSFER pfnIoCtxTransfer)
+{
+ PVDIOCTX pIoCtx = vdIoCtxAlloc(pDisk, enmTxDir, uOffset, cbTransfer, pImageStart,
+ pSgBuf, pvAllocation, pfnIoCtxTransfer, pIoCtxParent->fFlags & ~VDIOCTX_FLAGS_DONT_FREE);
+
+ AssertPtr(pIoCtxParent);
+ Assert(!pIoCtxParent->pIoCtxParent);
+
+ if (RT_LIKELY(pIoCtx))
+ {
+ pIoCtx->pIoCtxParent = pIoCtxParent;
+ pIoCtx->Type.Child.uOffsetSaved = uOffset;
+ pIoCtx->Type.Child.cbTransferLeftSaved = cbTransfer;
+ pIoCtx->Type.Child.cbTransferParent = cbTransferParent;
+ pIoCtx->Type.Child.cbWriteParent = cbWriteParent;
+ }
+
+ LogFlow(("Allocated child I/O context %#p\n", pIoCtx));
+ return pIoCtx;
+}
+
+DECLINLINE(PVDIOTASK) vdIoTaskUserAlloc(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser, PVDIOCTX pIoCtx, uint32_t cbTransfer)
+{
+ PVDIOTASK pIoTask = NULL;
+
+ pIoTask = (PVDIOTASK)RTMemCacheAlloc(pIoStorage->pVDIo->pDisk->hMemCacheIoTask);
+ if (pIoTask)
+ {
+ pIoTask->pIoStorage = pIoStorage;
+ pIoTask->pfnComplete = pfnComplete;
+ pIoTask->pvUser = pvUser;
+ pIoTask->fMeta = false;
+ pIoTask->Type.User.cbTransfer = cbTransfer;
+ pIoTask->Type.User.pIoCtx = pIoCtx;
+ }
+
+ return pIoTask;
+}
+
+DECLINLINE(PVDIOTASK) vdIoTaskMetaAlloc(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser, PVDMETAXFER pMetaXfer)
+{
+ PVDIOTASK pIoTask = NULL;
+
+ pIoTask = (PVDIOTASK)RTMemCacheAlloc(pIoStorage->pVDIo->pDisk->hMemCacheIoTask);
+ if (pIoTask)
+ {
+ pIoTask->pIoStorage = pIoStorage;
+ pIoTask->pfnComplete = pfnComplete;
+ pIoTask->pvUser = pvUser;
+ pIoTask->fMeta = true;
+ pIoTask->Type.Meta.pMetaXfer = pMetaXfer;
+ }
+
+ return pIoTask;
+}
+
+DECLINLINE(void) vdIoCtxFree(PVDISK pDisk, PVDIOCTX pIoCtx)
+{
+ Log(("Freeing I/O context %#p\n", pIoCtx));
+
+ if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_DONT_FREE))
+ {
+ if (pIoCtx->pvAllocation)
+ RTMemFree(pIoCtx->pvAllocation);
+#ifdef DEBUG
+ memset(&pIoCtx->pDisk, 0xff, sizeof(void *));
+#endif
+ RTMemCacheFree(pDisk->hMemCacheIoCtx, pIoCtx);
+ }
+}
+
+DECLINLINE(void) vdIoTaskFree(PVDISK pDisk, PVDIOTASK pIoTask)
+{
+#ifdef DEBUG
+ memset(pIoTask, 0xff, sizeof(VDIOTASK));
+#endif
+ RTMemCacheFree(pDisk->hMemCacheIoTask, pIoTask);
+}
+
+DECLINLINE(void) vdIoCtxChildReset(PVDIOCTX pIoCtx)
+{
+ AssertPtr(pIoCtx->pIoCtxParent);
+
+ RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+ pIoCtx->Req.Io.uOffset = pIoCtx->Type.Child.uOffsetSaved;
+ pIoCtx->Req.Io.cbTransferLeft = (uint32_t)pIoCtx->Type.Child.cbTransferLeftSaved;
+ Assert((uint32_t)pIoCtx->Type.Child.cbTransferLeftSaved == pIoCtx->Type.Child.cbTransferLeftSaved);
+}
+
+DECLINLINE(PVDMETAXFER) vdMetaXferAlloc(PVDIOSTORAGE pIoStorage, uint64_t uOffset, size_t cb)
+{
+ PVDMETAXFER pMetaXfer = (PVDMETAXFER)RTMemAlloc(RT_UOFFSETOF_DYN(VDMETAXFER, abData[cb]));
+
+ if (RT_LIKELY(pMetaXfer))
+ {
+ pMetaXfer->Core.Key = uOffset;
+ pMetaXfer->Core.KeyLast = uOffset + cb - 1;
+ pMetaXfer->fFlags = VDMETAXFER_TXDIR_NONE;
+ pMetaXfer->cbMeta = cb;
+ pMetaXfer->pIoStorage = pIoStorage;
+ pMetaXfer->cRefs = 0;
+ pMetaXfer->pbDataShw = NULL;
+ RTListInit(&pMetaXfer->ListIoCtxWaiting);
+ RTListInit(&pMetaXfer->ListIoCtxShwWrites);
+ }
+ return pMetaXfer;
+}
+
+DECLINLINE(void) vdIoCtxAddToWaitingList(volatile PVDIOCTX *ppList, PVDIOCTX pIoCtx)
+{
+ /* Put it on the waiting list. */
+ PVDIOCTX pNext = ASMAtomicUoReadPtrT(ppList, PVDIOCTX);
+ PVDIOCTX pHeadOld;
+ pIoCtx->pIoCtxNext = pNext;
+ while (!ASMAtomicCmpXchgExPtr(ppList, pIoCtx, pNext, &pHeadOld))
+ {
+ pNext = pHeadOld;
+ Assert(pNext != pIoCtx);
+ pIoCtx->pIoCtxNext = pNext;
+ ASMNopPause();
+ }
+}
+
+DECLINLINE(void) vdIoCtxDefer(PVDISK pDisk, PVDIOCTX pIoCtx)
+{
+ LogFlowFunc(("Deferring I/O context pIoCtx=%#p\n", pIoCtx));
+
+ Assert(!pIoCtx->pIoCtxParent && !(pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED));
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED;
+ vdIoCtxAddToWaitingList(&pDisk->pIoCtxBlockedHead, pIoCtx);
+}
+
+static size_t vdIoCtxCopy(PVDIOCTX pIoCtxDst, PVDIOCTX pIoCtxSrc, size_t cbData)
+{
+ return RTSgBufCopy(&pIoCtxDst->Req.Io.SgBuf, &pIoCtxSrc->Req.Io.SgBuf, cbData);
+}
+
+#if 0 /* unused */
+static int vdIoCtxCmp(PVDIOCTX pIoCtx1, PVDIOCTX pIoCtx2, size_t cbData)
+{
+ return RTSgBufCmp(&pIoCtx1->Req.Io.SgBuf, &pIoCtx2->Req.Io.SgBuf, cbData);
+}
+#endif
+
+static size_t vdIoCtxCopyTo(PVDIOCTX pIoCtx, const uint8_t *pbData, size_t cbData)
+{
+ return RTSgBufCopyFromBuf(&pIoCtx->Req.Io.SgBuf, pbData, cbData);
+}
+
+static size_t vdIoCtxCopyFrom(PVDIOCTX pIoCtx, uint8_t *pbData, size_t cbData)
+{
+ return RTSgBufCopyToBuf(&pIoCtx->Req.Io.SgBuf, pbData, cbData);
+}
+
+static size_t vdIoCtxSet(PVDIOCTX pIoCtx, uint8_t ch, size_t cbData)
+{
+ return RTSgBufSet(&pIoCtx->Req.Io.SgBuf, ch, cbData);
+}
+
+/**
+ * Returns whether the given I/O context has completed.
+ *
+ * @returns Flag whether the I/O context is complete.
+ * @param pIoCtx The I/O context to check.
+ */
+DECLINLINE(bool) vdIoCtxIsComplete(PVDIOCTX pIoCtx)
+{
+ if ( !pIoCtx->cMetaTransfersPending
+ && !pIoCtx->cDataTransfersPending
+ && !pIoCtx->pfnIoCtxTransfer)
+ return true;
+
+ /*
+ * We complete the I/O context in case of an error
+ * if there is no I/O task pending.
+ */
+ if ( RT_FAILURE(pIoCtx->rcReq)
+ && !pIoCtx->cMetaTransfersPending
+ && !pIoCtx->cDataTransfersPending)
+ return true;
+
+ return false;
+}
+
+/**
+ * Returns whether the given I/O context is blocked due to a metadata transfer
+ * or because the backend blocked it.
+ *
+ * @returns Flag whether the I/O context is blocked.
+ * @param pIoCtx The I/O context to check.
+ */
+DECLINLINE(bool) vdIoCtxIsBlocked(PVDIOCTX pIoCtx)
+{
+ /* Don't change anything if there is a metadata transfer pending or we are blocked. */
+ if ( pIoCtx->cMetaTransfersPending
+ || (pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED))
+ return true;
+
+ return false;
+}
+
+/**
+ * Process the I/O context, core method which assumes that the I/O context
+ * acquired the lock.
+ *
+ * @returns VBox status code.
+ * @param pIoCtx I/O context to process.
+ */
+static int vdIoCtxProcessLocked(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ VD_IS_LOCKED(pIoCtx->pDisk);
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ if (!vdIoCtxIsComplete(pIoCtx))
+ {
+ if (!vdIoCtxIsBlocked(pIoCtx))
+ {
+ if (pIoCtx->pfnIoCtxTransfer)
+ {
+ /* Call the transfer function advancing to the next while there is no error. */
+ while ( pIoCtx->pfnIoCtxTransfer
+ && !pIoCtx->cMetaTransfersPending
+ && RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("calling transfer function %#p\n", pIoCtx->pfnIoCtxTransfer));
+ rc = pIoCtx->pfnIoCtxTransfer(pIoCtx);
+
+ /* Advance to the next part of the transfer if the current one succeeded. */
+ if (RT_SUCCESS(rc))
+ {
+ pIoCtx->pfnIoCtxTransfer = pIoCtx->pfnIoCtxTransferNext;
+ pIoCtx->pfnIoCtxTransferNext = NULL;
+ }
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && !pIoCtx->cMetaTransfersPending
+ && !pIoCtx->cDataTransfersPending
+ && !(pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED))
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ else if ( RT_SUCCESS(rc)
+ || rc == VERR_VD_NOT_ENOUGH_METADATA
+ || rc == VERR_VD_IOCTX_HALT)
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ else if ( RT_FAILURE(rc)
+ && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS))
+ {
+ ASMAtomicCmpXchgS32(&pIoCtx->rcReq, rc, VINF_SUCCESS);
+
+ /*
+ * The I/O context completed if we have an error and there is no data
+ * or meta data transfer pending.
+ */
+ if ( !pIoCtx->cMetaTransfersPending
+ && !pIoCtx->cDataTransfersPending)
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ else
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+ }
+ else
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+ else
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+
+ LogFlowFunc(("pIoCtx=%#p rc=%Rrc cDataTransfersPending=%u cMetaTransfersPending=%u fComplete=%RTbool\n",
+ pIoCtx, rc, pIoCtx->cDataTransfersPending, pIoCtx->cMetaTransfersPending,
+ pIoCtx->fComplete));
+
+ return rc;
+}
+
+/**
+ * Processes the list of waiting I/O contexts.
+ *
+ * @returns VBox status code, only valid if pIoCtxRc is not NULL, treat as void
+ * function otherwise.
+ * @param pDisk The disk structure.
+ * @param pIoCtxRc An I/O context handle which waits on the list. When processed
+ * The status code is returned. NULL if there is no I/O context
+ * to return the status code for.
+ */
+static int vdDiskProcessWaitingIoCtx(PVDISK pDisk, PVDIOCTX pIoCtxRc)
+{
+ int rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+
+ LogFlowFunc(("pDisk=%#p pIoCtxRc=%#p\n", pDisk, pIoCtxRc));
+
+ VD_IS_LOCKED(pDisk);
+
+ /* Get the waiting list and process it in FIFO order. */
+ PVDIOCTX pIoCtxHead = ASMAtomicXchgPtrT(&pDisk->pIoCtxHead, NULL, PVDIOCTX);
+
+ /* Reverse it. */
+ PVDIOCTX pCur = pIoCtxHead;
+ pIoCtxHead = NULL;
+ while (pCur)
+ {
+ PVDIOCTX pInsert = pCur;
+ pCur = pCur->pIoCtxNext;
+ pInsert->pIoCtxNext = pIoCtxHead;
+ pIoCtxHead = pInsert;
+ }
+
+ /* Process now. */
+ pCur = pIoCtxHead;
+ while (pCur)
+ {
+ int rcTmp;
+ PVDIOCTX pTmp = pCur;
+
+ pCur = pCur->pIoCtxNext;
+ pTmp->pIoCtxNext = NULL;
+
+ /*
+ * Need to clear the sync flag here if there is a new I/O context
+ * with it set and the context is not given in pIoCtxRc.
+ * This happens most likely on a different thread and that one shouldn't
+ * process the context synchronously.
+ *
+ * The thread who issued the context will wait on the event semaphore
+ * anyway which is signalled when the completion handler is called.
+ */
+ if ( pTmp->fFlags & VDIOCTX_FLAGS_SYNC
+ && pTmp != pIoCtxRc)
+ pTmp->fFlags &= ~VDIOCTX_FLAGS_SYNC;
+
+ rcTmp = vdIoCtxProcessLocked(pTmp);
+ if (pTmp == pIoCtxRc)
+ {
+ if ( rcTmp == VINF_VD_ASYNC_IO_FINISHED
+ && RT_SUCCESS(pTmp->rcReq)
+ && pTmp->enmTxDir == VDIOCTXTXDIR_READ)
+ {
+ int rc2 = vdFilterChainApplyRead(pDisk, pTmp->Req.Io.uOffsetXferOrig,
+ pTmp->Req.Io.cbXferOrig, pTmp);
+ if (RT_FAILURE(rc2))
+ rcTmp = rc2;
+ }
+
+ /* The given I/O context was processed, pass the return code to the caller. */
+ if ( rcTmp == VINF_VD_ASYNC_IO_FINISHED
+ && (pTmp->fFlags & VDIOCTX_FLAGS_SYNC))
+ rc = pTmp->rcReq;
+ else
+ rc = rcTmp;
+ }
+ else if ( rcTmp == VINF_VD_ASYNC_IO_FINISHED
+ && ASMAtomicCmpXchgBool(&pTmp->fComplete, true, false))
+ {
+ LogFlowFunc(("Waiting I/O context completed pTmp=%#p\n", pTmp));
+ vdThreadFinishWrite(pDisk);
+
+ bool fFreeCtx = RT_BOOL(!(pTmp->fFlags & VDIOCTX_FLAGS_DONT_FREE));
+ vdIoCtxRootComplete(pDisk, pTmp);
+
+ if (fFreeCtx)
+ vdIoCtxFree(pDisk, pTmp);
+ }
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Processes the list of blocked I/O contexts.
+ *
+ * @returns nothing.
+ * @param pDisk The disk structure.
+ */
+static void vdDiskProcessBlockedIoCtx(PVDISK pDisk)
+{
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+
+ VD_IS_LOCKED(pDisk);
+
+ /* Get the waiting list and process it in FIFO order. */
+ PVDIOCTX pIoCtxHead = ASMAtomicXchgPtrT(&pDisk->pIoCtxBlockedHead, NULL, PVDIOCTX);
+
+ /* Reverse it. */
+ PVDIOCTX pCur = pIoCtxHead;
+ pIoCtxHead = NULL;
+ while (pCur)
+ {
+ PVDIOCTX pInsert = pCur;
+ pCur = pCur->pIoCtxNext;
+ pInsert->pIoCtxNext = pIoCtxHead;
+ pIoCtxHead = pInsert;
+ }
+
+ /* Process now. */
+ pCur = pIoCtxHead;
+ while (pCur)
+ {
+ int rc;
+ PVDIOCTX pTmp = pCur;
+
+ pCur = pCur->pIoCtxNext;
+ pTmp->pIoCtxNext = NULL;
+
+ Assert(!pTmp->pIoCtxParent);
+ Assert(pTmp->fFlags & VDIOCTX_FLAGS_BLOCKED);
+ pTmp->fFlags &= ~VDIOCTX_FLAGS_BLOCKED;
+
+ rc = vdIoCtxProcessLocked(pTmp);
+ if ( rc == VINF_VD_ASYNC_IO_FINISHED
+ && ASMAtomicCmpXchgBool(&pTmp->fComplete, true, false))
+ {
+ LogFlowFunc(("Waiting I/O context completed pTmp=%#p\n", pTmp));
+ vdThreadFinishWrite(pDisk);
+
+ bool fFreeCtx = RT_BOOL(!(pTmp->fFlags & VDIOCTX_FLAGS_DONT_FREE));
+ vdIoCtxRootComplete(pDisk, pTmp);
+ if (fFreeCtx)
+ vdIoCtxFree(pDisk, pTmp);
+ }
+ }
+
+ LogFlowFunc(("returns\n"));
+}
+
+/**
+ * Processes the I/O context trying to lock the criticial section.
+ * The context is deferred if the critical section is busy.
+ *
+ * @returns VBox status code.
+ * @param pIoCtx The I/O context to process.
+ */
+static int vdIoCtxProcessTryLockDefer(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = pIoCtx->pDisk;
+
+ Log(("Defer pIoCtx=%#p\n", pIoCtx));
+
+ /* Put it on the waiting list first. */
+ vdIoCtxAddToWaitingList(&pDisk->pIoCtxHead, pIoCtx);
+
+ if (ASMAtomicCmpXchgBool(&pDisk->fLocked, true, false))
+ {
+ /* Leave it again, the context will be processed just before leaving the lock. */
+ LogFlowFunc(("Successfully acquired the lock\n"));
+ rc = vdDiskUnlock(pDisk, pIoCtx);
+ }
+ else
+ {
+ LogFlowFunc(("Lock is held\n"));
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+
+ return rc;
+}
+
+/**
+ * Process the I/O context in a synchronous manner, waiting
+ * for it to complete.
+ *
+ * @returns VBox status code of the completed request.
+ * @param pIoCtx The sync I/O context.
+ * @param hEventComplete Event sempahore to wait on for completion.
+ */
+static int vdIoCtxProcessSync(PVDIOCTX pIoCtx, RTSEMEVENT hEventComplete)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = pIoCtx->pDisk;
+
+ LogFlowFunc(("pIoCtx=%p\n", pIoCtx));
+
+ AssertMsg(pIoCtx->fFlags & (VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE),
+ ("I/O context is not marked as synchronous\n"));
+
+ rc = vdIoCtxProcessTryLockDefer(pIoCtx);
+ if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ rc = VINF_SUCCESS;
+
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ rc = RTSemEventWait(hEventComplete, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+
+ rc = pIoCtx->rcReq;
+ vdIoCtxFree(pDisk, pIoCtx);
+
+ return rc;
+}
+
+DECLINLINE(bool) vdIoCtxIsDiskLockOwner(PVDISK pDisk, PVDIOCTX pIoCtx)
+{
+ return pDisk->pIoCtxLockOwner == pIoCtx;
+}
+
+static int vdIoCtxLockDisk(PVDISK pDisk, PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ VD_IS_LOCKED(pDisk);
+
+ LogFlowFunc(("pDisk=%#p pIoCtx=%#p\n", pDisk, pIoCtx));
+
+ if (!ASMAtomicCmpXchgPtr(&pDisk->pIoCtxLockOwner, pIoCtx, NIL_VDIOCTX))
+ {
+ Assert(pDisk->pIoCtxLockOwner != pIoCtx); /* No nesting allowed. */
+ vdIoCtxDefer(pDisk, pIoCtx);
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+
+ LogFlowFunc(("returns -> %Rrc\n", rc));
+ return rc;
+}
+
+static void vdIoCtxUnlockDisk(PVDISK pDisk, PVDIOCTX pIoCtx, bool fProcessBlockedReqs)
+{
+ RT_NOREF1(pIoCtx);
+ LogFlowFunc(("pDisk=%#p pIoCtx=%#p fProcessBlockedReqs=%RTbool\n",
+ pDisk, pIoCtx, fProcessBlockedReqs));
+
+ VD_IS_LOCKED(pDisk);
+
+ LogFlow(("Unlocking disk lock owner is %#p\n", pDisk->pIoCtxLockOwner));
+ Assert(pDisk->pIoCtxLockOwner == pIoCtx);
+ ASMAtomicXchgPtrT(&pDisk->pIoCtxLockOwner, NIL_VDIOCTX, PVDIOCTX);
+
+ if (fProcessBlockedReqs)
+ {
+ /* Process any blocked writes if the current request didn't caused another growing. */
+ vdDiskProcessBlockedIoCtx(pDisk);
+ }
+
+ LogFlowFunc(("returns\n"));
+}
+
+/**
+ * Internal: Reads a given amount of data from the image chain of the disk.
+ **/
+static int vdDiskReadHelper(PVDISK pDisk, PVDIMAGE pImage, PVDIMAGE pImageParentOverride,
+ uint64_t uOffset, size_t cbRead, PVDIOCTX pIoCtx, size_t *pcbThisRead)
+{
+ RT_NOREF1(pDisk);
+ int rc = VINF_SUCCESS;
+ size_t cbThisRead = cbRead;
+
+ AssertPtr(pcbThisRead);
+
+ *pcbThisRead = 0;
+
+ /*
+ * Try to read from the given image.
+ * If the block is not allocated read from override chain if present.
+ */
+ rc = pImage->Backend->pfnRead(pImage->pBackendData,
+ uOffset, cbThisRead, pIoCtx,
+ &cbThisRead);
+
+ if (rc == VERR_VD_BLOCK_FREE)
+ {
+ for (PVDIMAGE pCurrImage = pImageParentOverride ? pImageParentOverride : pImage->pPrev;
+ pCurrImage != NULL && rc == VERR_VD_BLOCK_FREE;
+ pCurrImage = pCurrImage->pPrev)
+ {
+ rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
+ uOffset, cbThisRead, pIoCtx,
+ &cbThisRead);
+ }
+ }
+
+ if (RT_SUCCESS(rc) || rc == VERR_VD_BLOCK_FREE)
+ *pcbThisRead = cbThisRead;
+
+ return rc;
+}
+
+/**
+ * internal: read the specified amount of data in whatever blocks the backend
+ * will give us - async version.
+ */
+static DECLCALLBACK(int) vdReadHelperAsync(PVDIOCTX pIoCtx)
+{
+ int rc;
+ PVDISK pDisk = pIoCtx->pDisk;
+ size_t cbToRead = pIoCtx->Req.Io.cbTransfer;
+ uint64_t uOffset = pIoCtx->Req.Io.uOffset;
+ PVDIMAGE pCurrImage = pIoCtx->Req.Io.pImageCur;
+ PVDIMAGE pImageParentOverride = pIoCtx->Req.Io.pImageParentOverride;
+ unsigned cImagesRead = pIoCtx->Req.Io.cImagesRead;
+ size_t cbThisRead;
+
+ /*
+ * Check whether there is a full block write in progress which was not allocated.
+ * Defer I/O if the range interferes but only if it does not belong to the
+ * write doing the allocation.
+ */
+ if ( pDisk->pIoCtxLockOwner != NIL_VDIOCTX
+ && uOffset >= pDisk->uOffsetStartLocked
+ && uOffset < pDisk->uOffsetEndLocked
+ && ( !pIoCtx->pIoCtxParent
+ || pIoCtx->pIoCtxParent != pDisk->pIoCtxLockOwner))
+ {
+ Log(("Interferring read while allocating a new block => deferring read\n"));
+ vdIoCtxDefer(pDisk, pIoCtx);
+ return VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+
+ /* Loop until all reads started or we have a backend which needs to read metadata. */
+ do
+ {
+ /* Search for image with allocated block. Do not attempt to read more
+ * than the previous reads marked as valid. Otherwise this would return
+ * stale data when different block sizes are used for the images. */
+ cbThisRead = cbToRead;
+
+ if ( pDisk->pCache
+ && !pImageParentOverride)
+ {
+ rc = vdCacheReadHelper(pDisk->pCache, uOffset, cbThisRead,
+ pIoCtx, &cbThisRead);
+ if (rc == VERR_VD_BLOCK_FREE)
+ {
+ rc = vdDiskReadHelper(pDisk, pCurrImage, NULL, uOffset, cbThisRead,
+ pIoCtx, &cbThisRead);
+
+ /* If the read was successful, write the data back into the cache. */
+ if ( RT_SUCCESS(rc)
+ && pIoCtx->fFlags & VDIOCTX_FLAGS_READ_UPDATE_CACHE)
+ {
+ rc = vdCacheWriteHelper(pDisk->pCache, uOffset, cbThisRead,
+ pIoCtx, NULL);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Try to read from the given image.
+ * If the block is not allocated read from override chain if present.
+ */
+ rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
+ uOffset, cbThisRead, pIoCtx,
+ &cbThisRead);
+
+ if ( rc == VERR_VD_BLOCK_FREE
+ && cImagesRead != 1)
+ {
+ unsigned cImagesToProcess = cImagesRead;
+
+ pCurrImage = pImageParentOverride ? pImageParentOverride : pCurrImage->pPrev;
+ pIoCtx->Req.Io.pImageParentOverride = NULL;
+
+ while (pCurrImage && rc == VERR_VD_BLOCK_FREE)
+ {
+ rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
+ uOffset, cbThisRead,
+ pIoCtx, &cbThisRead);
+ if (cImagesToProcess == 1)
+ break;
+ else if (cImagesToProcess > 0)
+ cImagesToProcess--;
+
+ if (rc == VERR_VD_BLOCK_FREE)
+ pCurrImage = pCurrImage->pPrev;
+ }
+ }
+ }
+
+ /* The task state will be updated on success already, don't do it here!. */
+ if (rc == VERR_VD_BLOCK_FREE)
+ {
+ /* No image in the chain contains the data for the block. */
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbThisRead); Assert(cbThisRead == (uint32_t)cbThisRead);
+
+ /* Fill the free space with 0 if we are told to do so
+ * or a previous read returned valid data. */
+ if (pIoCtx->fFlags & VDIOCTX_FLAGS_ZERO_FREE_BLOCKS)
+ vdIoCtxSet(pIoCtx, '\0', cbThisRead);
+ else
+ pIoCtx->Req.Io.cbBufClear += cbThisRead;
+
+ if (pIoCtx->Req.Io.pImageCur->uOpenFlags & VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS)
+ rc = VINF_VD_NEW_ZEROED_BLOCK;
+ else
+ rc = VINF_SUCCESS;
+ }
+ else if (rc == VERR_VD_IOCTX_HALT)
+ {
+ uOffset += cbThisRead;
+ cbToRead -= cbThisRead;
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED;
+ }
+ else if ( RT_SUCCESS(rc)
+ || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ /* First not free block, fill the space before with 0. */
+ if ( pIoCtx->Req.Io.cbBufClear
+ && !(pIoCtx->fFlags & VDIOCTX_FLAGS_ZERO_FREE_BLOCKS))
+ {
+ RTSGBUF SgBuf;
+ RTSgBufClone(&SgBuf, &pIoCtx->Req.Io.SgBuf);
+ RTSgBufReset(&SgBuf);
+ RTSgBufSet(&SgBuf, 0, pIoCtx->Req.Io.cbBufClear);
+ pIoCtx->Req.Io.cbBufClear = 0;
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS;
+ }
+ rc = VINF_SUCCESS;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ cbToRead -= cbThisRead;
+ uOffset += cbThisRead;
+ pCurrImage = pIoCtx->Req.Io.pImageStart; /* Start with the highest image in the chain. */
+ } while (cbToRead != 0 && RT_SUCCESS(rc));
+
+ if ( rc == VERR_VD_NOT_ENOUGH_METADATA
+ || rc == VERR_VD_IOCTX_HALT)
+ {
+ /* Save the current state. */
+ pIoCtx->Req.Io.uOffset = uOffset;
+ pIoCtx->Req.Io.cbTransfer = cbToRead;
+ pIoCtx->Req.Io.pImageCur = pCurrImage ? pCurrImage : pIoCtx->Req.Io.pImageStart;
+ }
+
+ return (!(pIoCtx->fFlags & VDIOCTX_FLAGS_ZERO_FREE_BLOCKS))
+ ? VERR_VD_BLOCK_FREE
+ : rc;
+}
+
+/**
+ * internal: parent image read wrapper for compacting.
+ */
+static DECLCALLBACK(int) vdParentRead(void *pvUser, uint64_t uOffset, void *pvBuf,
+ size_t cbRead)
+{
+ PVDPARENTSTATEDESC pParentState = (PVDPARENTSTATEDESC)pvUser;
+
+ /** @todo
+ * Only used for compaction so far which is not possible to mix with async I/O.
+ * Needs to be changed if we want to support online compaction of images.
+ */
+ bool fLocked = ASMAtomicXchgBool(&pParentState->pDisk->fLocked, true);
+ AssertMsgReturn(!fLocked,
+ ("Calling synchronous parent read while another thread holds the disk lock\n"),
+ VERR_VD_INVALID_STATE);
+
+ /* Fake an I/O context. */
+ RTSGSEG Segment;
+ RTSGBUF SgBuf;
+ VDIOCTX IoCtx;
+
+ Segment.pvSeg = pvBuf;
+ Segment.cbSeg = cbRead;
+ RTSgBufInit(&SgBuf, &Segment, 1);
+ vdIoCtxInit(&IoCtx, pParentState->pDisk, VDIOCTXTXDIR_READ, uOffset, cbRead, pParentState->pImage,
+ &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_ZERO_FREE_BLOCKS);
+ int rc = vdReadHelperAsync(&IoCtx);
+ ASMAtomicXchgBool(&pParentState->pDisk->fLocked, false);
+ return rc;
+}
+
+/**
+ * Extended version of vdReadHelper(), implementing certain optimizations
+ * for image cloning.
+ *
+ * @returns VBox status code.
+ * @param pDisk The disk to read from.
+ * @param pImage The image to start reading from.
+ * @param pImageParentOverride The parent image to read from
+ * if the starting image returns a free block.
+ * If NULL is passed the real parent of the image
+ * in the chain is used.
+ * @param uOffset Offset in the disk to start reading from.
+ * @param pvBuf Where to store the read data.
+ * @param cbRead How much to read.
+ * @param fZeroFreeBlocks Flag whether free blocks should be zeroed.
+ * If false and no image has data for sepcified
+ * range VERR_VD_BLOCK_FREE is returned.
+ * Note that unallocated blocks are still zeroed
+ * if at least one image has valid data for a part
+ * of the range.
+ * @param fUpdateCache Flag whether to update the attached cache if
+ * available.
+ * @param cImagesRead Number of images in the chain to read until
+ * the read is cut off. A value of 0 disables the cut off.
+ */
+static int vdReadHelperEx(PVDISK pDisk, PVDIMAGE pImage, PVDIMAGE pImageParentOverride,
+ uint64_t uOffset, void *pvBuf, size_t cbRead,
+ bool fZeroFreeBlocks, bool fUpdateCache, unsigned cImagesRead)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t fFlags = VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE;
+ RTSGSEG Segment;
+ RTSGBUF SgBuf;
+ VDIOCTX IoCtx;
+ RTSEMEVENT hEventComplete = NIL_RTSEMEVENT;
+
+ rc = RTSemEventCreate(&hEventComplete);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (fZeroFreeBlocks)
+ fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS;
+ if (fUpdateCache)
+ fFlags |= VDIOCTX_FLAGS_READ_UPDATE_CACHE;
+
+ Segment.pvSeg = pvBuf;
+ Segment.cbSeg = cbRead;
+ RTSgBufInit(&SgBuf, &Segment, 1);
+ vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_READ, uOffset, cbRead, pImage, &SgBuf,
+ NULL, vdReadHelperAsync, fFlags);
+
+ IoCtx.Req.Io.pImageParentOverride = pImageParentOverride;
+ IoCtx.Req.Io.cImagesRead = cImagesRead;
+ IoCtx.Type.Root.pfnComplete = vdIoCtxSyncComplete;
+ IoCtx.Type.Root.pvUser1 = pDisk;
+ IoCtx.Type.Root.pvUser2 = hEventComplete;
+ rc = vdIoCtxProcessSync(&IoCtx, hEventComplete);
+ RTSemEventDestroy(hEventComplete);
+ return rc;
+}
+
+/**
+ * internal: read the specified amount of data in whatever blocks the backend
+ * will give us.
+ */
+static int vdReadHelper(PVDISK pDisk, PVDIMAGE pImage, uint64_t uOffset,
+ void *pvBuf, size_t cbRead, bool fUpdateCache)
+{
+ return vdReadHelperEx(pDisk, pImage, NULL, uOffset, pvBuf, cbRead,
+ true /* fZeroFreeBlocks */, fUpdateCache, 0);
+}
+
+/**
+ * internal: mark the disk as not modified.
+ */
+static void vdResetModifiedFlag(PVDISK pDisk)
+{
+ if (pDisk->uModified & VD_IMAGE_MODIFIED_FLAG)
+ {
+ /* generate new last-modified uuid */
+ if (!(pDisk->uModified & VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE))
+ {
+ RTUUID Uuid;
+
+ RTUuidCreate(&Uuid);
+ pDisk->pLast->Backend->pfnSetModificationUuid(pDisk->pLast->pBackendData,
+ &Uuid);
+
+ if (pDisk->pCache)
+ pDisk->pCache->Backend->pfnSetModificationUuid(pDisk->pCache->pBackendData,
+ &Uuid);
+ }
+
+ pDisk->uModified &= ~VD_IMAGE_MODIFIED_FLAG;
+ }
+}
+
+/**
+ * internal: mark the disk as modified.
+ */
+static void vdSetModifiedFlag(PVDISK pDisk)
+{
+ pDisk->uModified |= VD_IMAGE_MODIFIED_FLAG;
+ if (pDisk->uModified & VD_IMAGE_MODIFIED_FIRST)
+ {
+ pDisk->uModified &= ~VD_IMAGE_MODIFIED_FIRST;
+
+ /* First modify, so create a UUID and ensure it's written to disk. */
+ vdResetModifiedFlag(pDisk);
+
+ if (!(pDisk->uModified & VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE))
+ {
+ VDIOCTX IoCtx;
+ vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_FLUSH, 0, 0, NULL,
+ NULL, NULL, NULL, VDIOCTX_FLAGS_SYNC);
+ pDisk->pLast->Backend->pfnFlush(pDisk->pLast->pBackendData, &IoCtx);
+ }
+ }
+}
+
+/**
+ * internal: write buffer to the image, taking care of block boundaries and
+ * write optimizations.
+ */
+static int vdWriteHelperEx(PVDISK pDisk, PVDIMAGE pImage,
+ PVDIMAGE pImageParentOverride, uint64_t uOffset,
+ const void *pvBuf, size_t cbWrite,
+ uint32_t fFlags, unsigned cImagesRead)
+{
+ int rc = VINF_SUCCESS;
+ RTSGSEG Segment;
+ RTSGBUF SgBuf;
+ VDIOCTX IoCtx;
+ RTSEMEVENT hEventComplete = NIL_RTSEMEVENT;
+
+ rc = RTSemEventCreate(&hEventComplete);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ fFlags |= VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE;
+
+ Segment.pvSeg = (void *)pvBuf;
+ Segment.cbSeg = cbWrite;
+ RTSgBufInit(&SgBuf, &Segment, 1);
+ vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_WRITE, uOffset, cbWrite, pImage, &SgBuf,
+ NULL, vdWriteHelperAsync, fFlags);
+
+ IoCtx.Req.Io.pImageParentOverride = pImageParentOverride;
+ IoCtx.Req.Io.cImagesRead = cImagesRead;
+ IoCtx.pIoCtxParent = NULL;
+ IoCtx.Type.Root.pfnComplete = vdIoCtxSyncComplete;
+ IoCtx.Type.Root.pvUser1 = pDisk;
+ IoCtx.Type.Root.pvUser2 = hEventComplete;
+ if (RT_SUCCESS(rc))
+ rc = vdIoCtxProcessSync(&IoCtx, hEventComplete);
+
+ RTSemEventDestroy(hEventComplete);
+ return rc;
+}
+
+/**
+ * internal: write buffer to the image, taking care of block boundaries and
+ * write optimizations.
+ */
+static int vdWriteHelper(PVDISK pDisk, PVDIMAGE pImage, uint64_t uOffset,
+ const void *pvBuf, size_t cbWrite, uint32_t fFlags)
+{
+ return vdWriteHelperEx(pDisk, pImage, NULL, uOffset, pvBuf, cbWrite,
+ fFlags, 0);
+}
+
+/**
+ * Internal: Copies the content of one disk to another one applying optimizations
+ * to speed up the copy process if possible.
+ */
+static int vdCopyHelper(PVDISK pDiskFrom, PVDIMAGE pImageFrom, PVDISK pDiskTo,
+ uint64_t cbSize, unsigned cImagesFromRead, unsigned cImagesToRead,
+ bool fSuppressRedundantIo, PVDINTERFACEPROGRESS pIfProgress,
+ PVDINTERFACEPROGRESS pDstIfProgress)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ uint64_t uOffset = 0;
+ uint64_t cbRemaining = cbSize;
+ void *pvBuf = NULL;
+ bool fLockReadFrom = false;
+ bool fLockWriteTo = false;
+ bool fBlockwiseCopy = false;
+ unsigned uProgressOld = 0;
+
+ LogFlowFunc(("pDiskFrom=%#p pImageFrom=%#p pDiskTo=%#p cbSize=%llu cImagesFromRead=%u cImagesToRead=%u fSuppressRedundantIo=%RTbool pIfProgress=%#p pDstIfProgress=%#p\n",
+ pDiskFrom, pImageFrom, pDiskTo, cbSize, cImagesFromRead, cImagesToRead, fSuppressRedundantIo, pDstIfProgress, pDstIfProgress));
+
+ if ( (fSuppressRedundantIo || (cImagesFromRead > 0))
+ && RTListIsEmpty(&pDiskFrom->ListFilterChainRead))
+ fBlockwiseCopy = true;
+
+ /* Allocate tmp buffer. */
+ pvBuf = RTMemTmpAlloc(VD_MERGE_BUFFER_SIZE);
+ if (!pvBuf)
+ return rc;
+
+ do
+ {
+ size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining);
+
+ /* Note that we don't attempt to synchronize cross-disk accesses.
+ * It wouldn't be very difficult to do, just the lock order would
+ * need to be defined somehow to prevent deadlocks. Postpone such
+ * magic as there is no use case for this. */
+
+ rc2 = vdThreadStartRead(pDiskFrom);
+ AssertRC(rc2);
+ fLockReadFrom = true;
+
+ if (fBlockwiseCopy)
+ {
+ RTSGSEG SegmentBuf;
+ RTSGBUF SgBuf;
+ VDIOCTX IoCtx;
+
+ SegmentBuf.pvSeg = pvBuf;
+ SegmentBuf.cbSeg = VD_MERGE_BUFFER_SIZE;
+ RTSgBufInit(&SgBuf, &SegmentBuf, 1);
+ vdIoCtxInit(&IoCtx, pDiskFrom, VDIOCTXTXDIR_READ, 0, 0, NULL,
+ &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC);
+
+ /* Read the source data. */
+ rc = pImageFrom->Backend->pfnRead(pImageFrom->pBackendData,
+ uOffset, cbThisRead, &IoCtx,
+ &cbThisRead);
+
+ if ( rc == VERR_VD_BLOCK_FREE
+ && cImagesFromRead != 1)
+ {
+ unsigned cImagesToProcess = cImagesFromRead;
+
+ for (PVDIMAGE pCurrImage = pImageFrom->pPrev;
+ pCurrImage != NULL && rc == VERR_VD_BLOCK_FREE;
+ pCurrImage = pCurrImage->pPrev)
+ {
+ rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
+ uOffset, cbThisRead,
+ &IoCtx, &cbThisRead);
+ if (cImagesToProcess == 1)
+ break;
+ else if (cImagesToProcess > 0)
+ cImagesToProcess--;
+ }
+ }
+ }
+ else
+ rc = vdReadHelper(pDiskFrom, pImageFrom, uOffset, pvBuf, cbThisRead,
+ false /* fUpdateCache */);
+
+ if (RT_FAILURE(rc) && rc != VERR_VD_BLOCK_FREE)
+ break;
+
+ rc2 = vdThreadFinishRead(pDiskFrom);
+ AssertRC(rc2);
+ fLockReadFrom = false;
+
+ if (rc != VERR_VD_BLOCK_FREE)
+ {
+ rc2 = vdThreadStartWrite(pDiskTo);
+ AssertRC(rc2);
+ fLockWriteTo = true;
+
+ /* Only do collapsed I/O if we are copying the data blockwise. */
+ rc = vdWriteHelperEx(pDiskTo, pDiskTo->pLast, NULL, uOffset, pvBuf,
+ cbThisRead, VDIOCTX_FLAGS_DONT_SET_MODIFIED_FLAG /* fFlags */,
+ fBlockwiseCopy ? cImagesToRead : 0);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc2 = vdThreadFinishWrite(pDiskTo);
+ AssertRC(rc2);
+ fLockWriteTo = false;
+ }
+ else /* Don't propagate the error to the outside */
+ rc = VINF_SUCCESS;
+
+ uOffset += cbThisRead;
+ cbRemaining -= cbThisRead;
+
+ unsigned uProgressNew = uOffset * 99 / cbSize;
+ if (uProgressNew != uProgressOld)
+ {
+ uProgressOld = uProgressNew;
+
+ if (pIfProgress && pIfProgress->pfnProgress)
+ {
+ rc = pIfProgress->pfnProgress(pIfProgress->Core.pvUser,
+ uProgressOld);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (pDstIfProgress && pDstIfProgress->pfnProgress)
+ {
+ rc = pDstIfProgress->pfnProgress(pDstIfProgress->Core.pvUser,
+ uProgressOld);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ } while (uOffset < cbSize);
+
+ RTMemFree(pvBuf);
+
+ if (fLockReadFrom)
+ {
+ rc2 = vdThreadFinishRead(pDiskFrom);
+ AssertRC(rc2);
+ }
+
+ if (fLockWriteTo)
+ {
+ rc2 = vdThreadFinishWrite(pDiskTo);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Flush helper async version.
+ */
+static DECLCALLBACK(int) vdSetModifiedHelperAsync(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ PVDIMAGE pImage = pIoCtx->Req.Io.pImageCur;
+
+ rc = pImage->Backend->pfnFlush(pImage->pBackendData, pIoCtx);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_SUCCESS;
+
+ return rc;
+}
+
+/**
+ * internal: mark the disk as modified - async version.
+ */
+static int vdSetModifiedFlagAsync(PVDISK pDisk, PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ VD_IS_LOCKED(pDisk);
+
+ pDisk->uModified |= VD_IMAGE_MODIFIED_FLAG;
+ if (pDisk->uModified & VD_IMAGE_MODIFIED_FIRST)
+ {
+ rc = vdIoCtxLockDisk(pDisk, pIoCtx);
+ if (RT_SUCCESS(rc))
+ {
+ pDisk->uModified &= ~VD_IMAGE_MODIFIED_FIRST;
+
+ /* First modify, so create a UUID and ensure it's written to disk. */
+ vdResetModifiedFlag(pDisk);
+
+ if (!(pDisk->uModified & VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE))
+ {
+ PVDIOCTX pIoCtxFlush = vdIoCtxChildAlloc(pDisk, VDIOCTXTXDIR_FLUSH,
+ 0, 0, pDisk->pLast,
+ NULL, pIoCtx, 0, 0, NULL,
+ vdSetModifiedHelperAsync);
+
+ if (pIoCtxFlush)
+ {
+ rc = vdIoCtxProcessLocked(pIoCtxFlush);
+ if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ {
+ vdIoCtxUnlockDisk(pDisk, pIoCtx, false /* fProcessDeferredReqs */);
+ vdIoCtxFree(pDisk, pIoCtxFlush);
+ }
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ ASMAtomicIncU32(&pIoCtx->cDataTransfersPending);
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED;
+ }
+ else /* Another error */
+ vdIoCtxFree(pDisk, pIoCtxFlush);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdWriteHelperCommitAsync(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ PVDIMAGE pImage = pIoCtx->Req.Io.pImageStart;
+ size_t cbPreRead = pIoCtx->Type.Child.cbPreRead;
+ size_t cbPostRead = pIoCtx->Type.Child.cbPostRead;
+ size_t cbThisWrite = pIoCtx->Type.Child.cbTransferParent;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+ rc = pImage->Backend->pfnWrite(pImage->pBackendData,
+ pIoCtx->Req.Io.uOffset - cbPreRead,
+ cbPreRead + cbThisWrite + cbPostRead,
+ pIoCtx, NULL, &cbPreRead, &cbPostRead, 0);
+ Assert(rc != VERR_VD_BLOCK_FREE);
+ Assert(rc == VERR_VD_NOT_ENOUGH_METADATA || cbPreRead == 0);
+ Assert(rc == VERR_VD_NOT_ENOUGH_METADATA || cbPostRead == 0);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_SUCCESS;
+ else if (rc == VERR_VD_IOCTX_HALT)
+ {
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED;
+ rc = VINF_SUCCESS;
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdWriteHelperOptimizedCmpAndWriteAsync(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbThisWrite = 0;
+ size_t cbPreRead = pIoCtx->Type.Child.cbPreRead;
+ size_t cbPostRead = pIoCtx->Type.Child.cbPostRead;
+ size_t cbWriteCopy = pIoCtx->Type.Child.Write.Optimized.cbWriteCopy;
+ size_t cbFill = pIoCtx->Type.Child.Write.Optimized.cbFill;
+ size_t cbReadImage = pIoCtx->Type.Child.Write.Optimized.cbReadImage;
+ PVDIOCTX pIoCtxParent = pIoCtx->pIoCtxParent;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ AssertPtr(pIoCtxParent);
+ Assert(!pIoCtxParent->pIoCtxParent);
+ Assert(!pIoCtx->Req.Io.cbTransferLeft && !pIoCtx->cMetaTransfersPending);
+
+ vdIoCtxChildReset(pIoCtx);
+ cbThisWrite = pIoCtx->Type.Child.cbTransferParent;
+ RTSgBufAdvance(&pIoCtx->Req.Io.SgBuf, cbPreRead);
+
+ /* Check if the write would modify anything in this block. */
+ if (!RTSgBufCmp(&pIoCtx->Req.Io.SgBuf, &pIoCtxParent->Req.Io.SgBuf, cbThisWrite))
+ {
+ RTSGBUF SgBufSrcTmp;
+
+ RTSgBufClone(&SgBufSrcTmp, &pIoCtxParent->Req.Io.SgBuf);
+ RTSgBufAdvance(&SgBufSrcTmp, cbThisWrite);
+ RTSgBufAdvance(&pIoCtx->Req.Io.SgBuf, cbThisWrite);
+
+ if (!cbWriteCopy || !RTSgBufCmp(&pIoCtx->Req.Io.SgBuf, &SgBufSrcTmp, cbWriteCopy))
+ {
+ /* Block is completely unchanged, so no need to write anything. */
+ LogFlowFunc(("Block didn't changed\n"));
+ ASMAtomicWriteU32(&pIoCtx->Req.Io.cbTransferLeft, 0);
+ RTSgBufAdvance(&pIoCtxParent->Req.Io.SgBuf, cbThisWrite);
+ return VINF_VD_ASYNC_IO_FINISHED;
+ }
+ }
+
+ /* Copy the data to the right place in the buffer. */
+ RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+ RTSgBufAdvance(&pIoCtx->Req.Io.SgBuf, cbPreRead);
+ vdIoCtxCopy(pIoCtx, pIoCtxParent, cbThisWrite);
+
+ /* Handle the data that goes after the write to fill the block. */
+ if (cbPostRead)
+ {
+ /* Now assemble the remaining data. */
+ if (cbWriteCopy)
+ {
+ /*
+ * The S/G buffer of the parent needs to be cloned because
+ * it is not allowed to modify the state.
+ */
+ RTSGBUF SgBufParentTmp;
+
+ RTSgBufClone(&SgBufParentTmp, &pIoCtxParent->Req.Io.SgBuf);
+ RTSgBufCopy(&pIoCtx->Req.Io.SgBuf, &SgBufParentTmp, cbWriteCopy);
+ }
+
+ /* Zero out the remainder of this block. Will never be visible, as this
+ * is beyond the limit of the image. */
+ if (cbFill)
+ {
+ RTSgBufAdvance(&pIoCtx->Req.Io.SgBuf, cbReadImage);
+ vdIoCtxSet(pIoCtx, '\0', cbFill);
+ }
+ }
+
+ /* Write the full block to the virtual disk. */
+ RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+ pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdWriteHelperOptimizedPreReadAsync(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS;
+
+ if ( pIoCtx->Req.Io.cbTransferLeft
+ && !pIoCtx->cDataTransfersPending)
+ rc = vdReadHelperAsync(pIoCtx);
+
+ if ( ( RT_SUCCESS(rc)
+ || (rc == VERR_VD_ASYNC_IO_IN_PROGRESS))
+ && ( pIoCtx->Req.Io.cbTransferLeft
+ || pIoCtx->cMetaTransfersPending))
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ else
+ pIoCtx->pfnIoCtxTransferNext = vdWriteHelperOptimizedCmpAndWriteAsync;
+
+ return rc;
+}
+
+/**
+ * internal: write a complete block (only used for diff images), taking the
+ * remaining data from parent images. This implementation optimizes out writes
+ * that do not change the data relative to the state as of the parent images.
+ * All backends which support differential/growing images support this - async version.
+ */
+static DECLCALLBACK(int) vdWriteHelperOptimizedAsync(PVDIOCTX pIoCtx)
+{
+ PVDISK pDisk = pIoCtx->pDisk;
+ uint64_t uOffset = pIoCtx->Type.Child.uOffsetSaved;
+ size_t cbThisWrite = pIoCtx->Type.Child.cbTransferParent;
+ size_t cbPreRead = pIoCtx->Type.Child.cbPreRead;
+ size_t cbPostRead = pIoCtx->Type.Child.cbPostRead;
+ size_t cbWrite = pIoCtx->Type.Child.cbWriteParent;
+ size_t cbFill = 0;
+ size_t cbWriteCopy = 0;
+ size_t cbReadImage = 0;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ AssertPtr(pIoCtx->pIoCtxParent);
+ Assert(!pIoCtx->pIoCtxParent->pIoCtxParent);
+
+ if (cbPostRead)
+ {
+ /* Figure out how much we cannot read from the image, because
+ * the last block to write might exceed the nominal size of the
+ * image for technical reasons. */
+ if (uOffset + cbThisWrite + cbPostRead > pDisk->cbSize)
+ cbFill = uOffset + cbThisWrite + cbPostRead - pDisk->cbSize;
+
+ /* If we have data to be written, use that instead of reading
+ * data from the image. */
+ if (cbWrite > cbThisWrite)
+ cbWriteCopy = RT_MIN(cbWrite - cbThisWrite, cbPostRead);
+
+ /* The rest must be read from the image. */
+ cbReadImage = cbPostRead - cbWriteCopy - cbFill;
+ }
+
+ pIoCtx->Type.Child.Write.Optimized.cbFill = cbFill;
+ pIoCtx->Type.Child.Write.Optimized.cbWriteCopy = cbWriteCopy;
+ pIoCtx->Type.Child.Write.Optimized.cbReadImage = cbReadImage;
+
+ /* Read the entire data of the block so that we can compare whether it will
+ * be modified by the write or not. */
+ size_t cbTmp = cbPreRead + cbThisWrite + cbPostRead - cbFill; Assert(cbTmp == (uint32_t)cbTmp);
+ pIoCtx->Req.Io.cbTransferLeft = (uint32_t)cbTmp;
+ pIoCtx->Req.Io.cbTransfer = pIoCtx->Req.Io.cbTransferLeft;
+ pIoCtx->Req.Io.uOffset -= cbPreRead;
+
+ /* Next step */
+ pIoCtx->pfnIoCtxTransferNext = vdWriteHelperOptimizedPreReadAsync;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vdWriteHelperStandardReadImageAsync(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS;
+
+ if ( pIoCtx->Req.Io.cbTransferLeft
+ && !pIoCtx->cDataTransfersPending)
+ rc = vdReadHelperAsync(pIoCtx);
+
+ if ( RT_SUCCESS(rc)
+ && ( pIoCtx->Req.Io.cbTransferLeft
+ || pIoCtx->cMetaTransfersPending))
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ else
+ {
+ size_t cbFill = pIoCtx->Type.Child.Write.Optimized.cbFill;
+
+ /* Zero out the remainder of this block. Will never be visible, as this
+ * is beyond the limit of the image. */
+ if (cbFill)
+ vdIoCtxSet(pIoCtx, '\0', cbFill);
+
+ /* Write the full block to the virtual disk. */
+ RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+
+ vdIoCtxChildReset(pIoCtx);
+ pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdWriteHelperStandardAssemble(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbPostRead = pIoCtx->Type.Child.cbPostRead;
+ size_t cbThisWrite = pIoCtx->Type.Child.cbTransferParent;
+ PVDIOCTX pIoCtxParent = pIoCtx->pIoCtxParent;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ vdIoCtxCopy(pIoCtx, pIoCtxParent, cbThisWrite);
+ if (cbPostRead)
+ {
+ size_t cbFill = pIoCtx->Type.Child.Write.Optimized.cbFill;
+ size_t cbWriteCopy = pIoCtx->Type.Child.Write.Optimized.cbWriteCopy;
+ size_t cbReadImage = pIoCtx->Type.Child.Write.Optimized.cbReadImage;
+
+ /* Now assemble the remaining data. */
+ if (cbWriteCopy)
+ {
+ /*
+ * The S/G buffer of the parent needs to be cloned because
+ * it is not allowed to modify the state.
+ */
+ RTSGBUF SgBufParentTmp;
+
+ RTSgBufClone(&SgBufParentTmp, &pIoCtxParent->Req.Io.SgBuf);
+ RTSgBufCopy(&pIoCtx->Req.Io.SgBuf, &SgBufParentTmp, cbWriteCopy);
+ }
+
+ if (cbReadImage)
+ {
+ /* Read remaining data. */
+ pIoCtx->pfnIoCtxTransferNext = vdWriteHelperStandardReadImageAsync;
+
+ /* Read the data that goes before the write to fill the block. */
+ pIoCtx->Req.Io.cbTransferLeft = (uint32_t)cbReadImage; Assert(cbReadImage == (uint32_t)cbReadImage);
+ pIoCtx->Req.Io.cbTransfer = pIoCtx->Req.Io.cbTransferLeft;
+ pIoCtx->Req.Io.uOffset += cbWriteCopy;
+ }
+ else
+ {
+ /* Zero out the remainder of this block. Will never be visible, as this
+ * is beyond the limit of the image. */
+ if (cbFill)
+ vdIoCtxSet(pIoCtx, '\0', cbFill);
+
+ /* Write the full block to the virtual disk. */
+ RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+ vdIoCtxChildReset(pIoCtx);
+ pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync;
+ }
+ }
+ else
+ {
+ /* Write the full block to the virtual disk. */
+ RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+ vdIoCtxChildReset(pIoCtx);
+ pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdWriteHelperStandardPreReadAsync(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS;
+
+ if ( pIoCtx->Req.Io.cbTransferLeft
+ && !pIoCtx->cDataTransfersPending)
+ rc = vdReadHelperAsync(pIoCtx);
+
+ if ( RT_SUCCESS(rc)
+ && ( pIoCtx->Req.Io.cbTransferLeft
+ || pIoCtx->cMetaTransfersPending))
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ else
+ pIoCtx->pfnIoCtxTransferNext = vdWriteHelperStandardAssemble;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdWriteHelperStandardAsync(PVDIOCTX pIoCtx)
+{
+ PVDISK pDisk = pIoCtx->pDisk;
+ uint64_t uOffset = pIoCtx->Type.Child.uOffsetSaved;
+ size_t cbThisWrite = pIoCtx->Type.Child.cbTransferParent;
+ size_t cbPreRead = pIoCtx->Type.Child.cbPreRead;
+ size_t cbPostRead = pIoCtx->Type.Child.cbPostRead;
+ size_t cbWrite = pIoCtx->Type.Child.cbWriteParent;
+ size_t cbFill = 0;
+ size_t cbWriteCopy = 0;
+ size_t cbReadImage = 0;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ AssertPtr(pIoCtx->pIoCtxParent);
+ Assert(!pIoCtx->pIoCtxParent->pIoCtxParent);
+
+ /* Calculate the amount of data to read that goes after the write to fill the block. */
+ if (cbPostRead)
+ {
+ /* If we have data to be written, use that instead of reading
+ * data from the image. */
+ if (cbWrite > cbThisWrite)
+ cbWriteCopy = RT_MIN(cbWrite - cbThisWrite, cbPostRead);
+ else
+ cbWriteCopy = 0;
+
+ /* Figure out how much we cannot read from the image, because
+ * the last block to write might exceed the nominal size of the
+ * image for technical reasons. */
+ if (uOffset + cbThisWrite + cbPostRead > pDisk->cbSize)
+ cbFill = uOffset + cbThisWrite + cbPostRead - pDisk->cbSize;
+
+ /* The rest must be read from the image. */
+ cbReadImage = cbPostRead - cbWriteCopy - cbFill;
+ }
+
+ pIoCtx->Type.Child.Write.Optimized.cbFill = cbFill;
+ pIoCtx->Type.Child.Write.Optimized.cbWriteCopy = cbWriteCopy;
+ pIoCtx->Type.Child.Write.Optimized.cbReadImage = cbReadImage;
+
+ /* Next step */
+ if (cbPreRead)
+ {
+ pIoCtx->pfnIoCtxTransferNext = vdWriteHelperStandardPreReadAsync;
+
+ /* Read the data that goes before the write to fill the block. */
+ pIoCtx->Req.Io.cbTransferLeft = (uint32_t)cbPreRead; Assert(cbPreRead == (uint32_t)cbPreRead);
+ pIoCtx->Req.Io.cbTransfer = pIoCtx->Req.Io.cbTransferLeft;
+ pIoCtx->Req.Io.uOffset -= cbPreRead;
+ }
+ else
+ pIoCtx->pfnIoCtxTransferNext = vdWriteHelperStandardAssemble;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * internal: write buffer to the image, taking care of block boundaries and
+ * write optimizations - async version.
+ */
+static DECLCALLBACK(int) vdWriteHelperAsync(PVDIOCTX pIoCtx)
+{
+ int rc;
+ size_t cbWrite = pIoCtx->Req.Io.cbTransfer;
+ uint64_t uOffset = pIoCtx->Req.Io.uOffset;
+ PVDIMAGE pImage = pIoCtx->Req.Io.pImageCur;
+ PVDISK pDisk = pIoCtx->pDisk;
+ unsigned fWrite;
+ size_t cbThisWrite;
+ size_t cbPreRead, cbPostRead;
+
+ /* Apply write filter chain here if it was not done already. */
+ if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_WRITE_FILTER_APPLIED))
+ {
+ rc = vdFilterChainApplyWrite(pDisk, uOffset, cbWrite, pIoCtx);
+ if (RT_FAILURE(rc))
+ return rc;
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_WRITE_FILTER_APPLIED;
+ }
+
+ if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_DONT_SET_MODIFIED_FLAG))
+ {
+ rc = vdSetModifiedFlagAsync(pDisk, pIoCtx);
+ if (RT_FAILURE(rc)) /* Includes I/O in progress. */
+ return rc;
+ }
+
+ rc = vdDiscardSetRangeAllocated(pDisk, uOffset, cbWrite);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Loop until all written. */
+ do
+ {
+ /* Try to write the possibly partial block to the last opened image.
+ * This works when the block is already allocated in this image or
+ * if it is a full-block write (and allocation isn't suppressed below).
+ * For image formats which don't support zero blocks, it's beneficial
+ * to avoid unnecessarily allocating unchanged blocks. This prevents
+ * unwanted expanding of images. VMDK is an example. */
+ cbThisWrite = cbWrite;
+
+ /*
+ * Check whether there is a full block write in progress which was not allocated.
+ * Defer I/O if the range interferes.
+ */
+ if ( pDisk->pIoCtxLockOwner != NIL_VDIOCTX
+ && uOffset >= pDisk->uOffsetStartLocked
+ && uOffset < pDisk->uOffsetEndLocked)
+ {
+ Log(("Interferring write while allocating a new block => deferring write\n"));
+ vdIoCtxDefer(pDisk, pIoCtx);
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ break;
+ }
+
+ fWrite = (pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME)
+ ? 0 : VD_WRITE_NO_ALLOC;
+ rc = pImage->Backend->pfnWrite(pImage->pBackendData, uOffset, cbThisWrite,
+ pIoCtx, &cbThisWrite, &cbPreRead, &cbPostRead,
+ fWrite);
+ if (rc == VERR_VD_BLOCK_FREE)
+ {
+ /* Lock the disk .*/
+ rc = vdIoCtxLockDisk(pDisk, pIoCtx);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate segment and buffer in one go.
+ * A bit hackish but avoids the need to allocate memory twice.
+ */
+ PRTSGBUF pTmp = (PRTSGBUF)RTMemAlloc(cbPreRead + cbThisWrite + cbPostRead + sizeof(RTSGSEG) + sizeof(RTSGBUF));
+ AssertBreakStmt(pTmp, rc = VERR_NO_MEMORY);
+ PRTSGSEG pSeg = (PRTSGSEG)(pTmp + 1);
+
+ pSeg->pvSeg = pSeg + 1;
+ pSeg->cbSeg = cbPreRead + cbThisWrite + cbPostRead;
+ RTSgBufInit(pTmp, pSeg, 1);
+
+ PVDIOCTX pIoCtxWrite = vdIoCtxChildAlloc(pDisk, VDIOCTXTXDIR_WRITE,
+ uOffset, pSeg->cbSeg, pImage,
+ pTmp,
+ pIoCtx, cbThisWrite,
+ cbWrite,
+ pTmp,
+ (pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME)
+ ? vdWriteHelperStandardAsync
+ : vdWriteHelperOptimizedAsync);
+ if (!pIoCtxWrite)
+ {
+ RTMemTmpFree(pTmp);
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ LogFlowFunc(("Disk is growing because of pIoCtx=%#p pIoCtxWrite=%#p\n",
+ pIoCtx, pIoCtxWrite));
+
+ /* Save the current range for the growing operation to check for intersecting requests later. */
+ pDisk->uOffsetStartLocked = uOffset - cbPreRead;
+ pDisk->uOffsetEndLocked = uOffset + cbThisWrite + cbPostRead;
+
+ pIoCtxWrite->Type.Child.cbPreRead = cbPreRead;
+ pIoCtxWrite->Type.Child.cbPostRead = cbPostRead;
+ pIoCtxWrite->Req.Io.pImageParentOverride = pIoCtx->Req.Io.pImageParentOverride;
+
+ /* Process the write request */
+ rc = vdIoCtxProcessLocked(pIoCtxWrite);
+
+ if (RT_FAILURE(rc) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS))
+ {
+ vdIoCtxUnlockDisk(pDisk, pIoCtx, false /* fProcessDeferredReqs*/ );
+ vdIoCtxFree(pDisk, pIoCtxWrite);
+ break;
+ }
+ else if ( rc == VINF_VD_ASYNC_IO_FINISHED
+ && ASMAtomicCmpXchgBool(&pIoCtxWrite->fComplete, true, false))
+ {
+ LogFlow(("Child write request completed\n"));
+ Assert(pIoCtx->Req.Io.cbTransferLeft >= cbThisWrite);
+ Assert(cbThisWrite == (uint32_t)cbThisWrite);
+ rc = pIoCtxWrite->rcReq;
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbThisWrite);
+ vdIoCtxUnlockDisk(pDisk, pIoCtx, false /* fProcessDeferredReqs*/ );
+ vdIoCtxFree(pDisk, pIoCtxWrite);
+ }
+ else
+ {
+ LogFlow(("Child write pending\n"));
+ ASMAtomicIncU32(&pIoCtx->cDataTransfersPending);
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED;
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ cbWrite -= cbThisWrite;
+ uOffset += cbThisWrite;
+ break;
+ }
+ }
+ else
+ {
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ break;
+ }
+ }
+
+ if (rc == VERR_VD_IOCTX_HALT)
+ {
+ cbWrite -= cbThisWrite;
+ uOffset += cbThisWrite;
+ pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED;
+ break;
+ }
+ else if (rc == VERR_VD_NOT_ENOUGH_METADATA)
+ break;
+
+ cbWrite -= cbThisWrite;
+ uOffset += cbThisWrite;
+ } while (cbWrite != 0 && (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS));
+
+ if ( rc == VERR_VD_ASYNC_IO_IN_PROGRESS
+ || rc == VERR_VD_NOT_ENOUGH_METADATA
+ || rc == VERR_VD_IOCTX_HALT)
+ {
+ /*
+ * Tell the caller that we don't need to go back here because all
+ * writes are initiated.
+ */
+ if ( !cbWrite
+ && rc != VERR_VD_IOCTX_HALT)
+ rc = VINF_SUCCESS;
+
+ pIoCtx->Req.Io.uOffset = uOffset;
+ pIoCtx->Req.Io.cbTransfer = cbWrite;
+ }
+
+ return rc;
+}
+
+/**
+ * Flush helper async version.
+ */
+static DECLCALLBACK(int) vdFlushHelperAsync(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = pIoCtx->pDisk;
+ PVDIMAGE pImage = pIoCtx->Req.Io.pImageCur;
+
+ rc = vdIoCtxLockDisk(pDisk, pIoCtx);
+ if (RT_SUCCESS(rc))
+ {
+ /* Mark the whole disk as locked. */
+ pDisk->uOffsetStartLocked = 0;
+ pDisk->uOffsetEndLocked = UINT64_C(0xffffffffffffffff);
+
+ vdResetModifiedFlag(pDisk);
+ rc = pImage->Backend->pfnFlush(pImage->pBackendData, pIoCtx);
+ if ( ( RT_SUCCESS(rc)
+ || rc == VERR_VD_ASYNC_IO_IN_PROGRESS
+ || rc == VERR_VD_IOCTX_HALT)
+ && pDisk->pCache)
+ {
+ rc = pDisk->pCache->Backend->pfnFlush(pDisk->pCache->pBackendData, pIoCtx);
+ if ( RT_SUCCESS(rc)
+ || ( rc != VERR_VD_ASYNC_IO_IN_PROGRESS
+ && rc != VERR_VD_IOCTX_HALT))
+ vdIoCtxUnlockDisk(pDisk, pIoCtx, true /* fProcessBlockedReqs */);
+ else if (rc != VERR_VD_IOCTX_HALT)
+ rc = VINF_SUCCESS;
+ }
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_SUCCESS;
+ else if (rc != VERR_VD_IOCTX_HALT)/* Some other error. */
+ vdIoCtxUnlockDisk(pDisk, pIoCtx, true /* fProcessBlockedReqs */);
+ }
+
+ return rc;
+}
+
+/**
+ * Async discard helper - discards a whole block which is recorded in the block
+ * tree.
+ *
+ * @returns VBox status code.
+ * @param pIoCtx The I/O context to operate on.
+ */
+static DECLCALLBACK(int) vdDiscardWholeBlockAsync(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = pIoCtx->pDisk;
+ PVDDISCARDSTATE pDiscard = pDisk->pDiscard;
+ PVDDISCARDBLOCK pBlock = pIoCtx->Req.Discard.pBlock;
+ size_t cbPreAllocated, cbPostAllocated, cbActuallyDiscarded;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ AssertPtr(pBlock);
+
+ rc = pDisk->pLast->Backend->pfnDiscard(pDisk->pLast->pBackendData, pIoCtx,
+ pBlock->Core.Key, pBlock->cbDiscard,
+ &cbPreAllocated, &cbPostAllocated,
+ &cbActuallyDiscarded, NULL, 0);
+ Assert(rc != VERR_VD_DISCARD_ALIGNMENT_NOT_MET);
+ Assert(!cbPreAllocated);
+ Assert(!cbPostAllocated);
+ Assert(cbActuallyDiscarded == pBlock->cbDiscard || RT_FAILURE(rc));
+
+ /* Remove the block on success. */
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ PVDDISCARDBLOCK pBlockRemove = (PVDDISCARDBLOCK)RTAvlrU64RangeRemove(pDiscard->pTreeBlocks, pBlock->Core.Key);
+ Assert(pBlockRemove == pBlock); RT_NOREF1(pBlockRemove);
+
+ pDiscard->cbDiscarding -= pBlock->cbDiscard;
+ RTListNodeRemove(&pBlock->NodeLru);
+ RTMemFree(pBlock->pbmAllocated);
+ RTMemFree(pBlock);
+ pIoCtx->Req.Discard.pBlock = NULL;/* Safety precaution. */
+ pIoCtx->pfnIoCtxTransferNext = vdDiscardHelperAsync; /* Next part. */
+ rc = VINF_SUCCESS;
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Removes the least recently used blocks from the waiting list until
+ * the new value is reached - version for async I/O.
+ *
+ * @returns VBox status code.
+ * @param pDisk VD disk container.
+ * @param pIoCtx The I/O context associated with this discard operation.
+ * @param cbDiscardingNew How many bytes should be waiting on success.
+ * The number of bytes waiting can be less.
+ */
+static int vdDiscardRemoveBlocksAsync(PVDISK pDisk, PVDIOCTX pIoCtx, size_t cbDiscardingNew)
+{
+ int rc = VINF_SUCCESS;
+ PVDDISCARDSTATE pDiscard = pDisk->pDiscard;
+
+ LogFlowFunc(("pDisk=%#p pDiscard=%#p cbDiscardingNew=%zu\n",
+ pDisk, pDiscard, cbDiscardingNew));
+
+ while (pDiscard->cbDiscarding > cbDiscardingNew)
+ {
+ PVDDISCARDBLOCK pBlock = RTListGetLast(&pDiscard->ListLru, VDDISCARDBLOCK, NodeLru);
+
+ Assert(!RTListIsEmpty(&pDiscard->ListLru));
+
+ /* Go over the allocation bitmap and mark all discarded sectors as unused. */
+ uint64_t offStart = pBlock->Core.Key;
+ uint32_t idxStart = 0;
+ size_t cbLeft = pBlock->cbDiscard;
+ bool fAllocated = ASMBitTest(pBlock->pbmAllocated, idxStart);
+ uint32_t cSectors = (uint32_t)(pBlock->cbDiscard / 512);
+
+ while (cbLeft > 0)
+ {
+ int32_t idxEnd;
+ size_t cbThis = cbLeft;
+
+ if (fAllocated)
+ {
+ /* Check for the first unallocated bit. */
+ idxEnd = ASMBitNextClear(pBlock->pbmAllocated, cSectors, idxStart);
+ if (idxEnd != -1)
+ {
+ cbThis = (idxEnd - idxStart) * 512;
+ fAllocated = false;
+ }
+ }
+ else
+ {
+ /* Mark as unused and check for the first set bit. */
+ idxEnd = ASMBitNextSet(pBlock->pbmAllocated, cSectors, idxStart);
+ if (idxEnd != -1)
+ cbThis = (idxEnd - idxStart) * 512;
+
+ rc = pDisk->pLast->Backend->pfnDiscard(pDisk->pLast->pBackendData, pIoCtx,
+ offStart, cbThis, NULL, NULL, &cbThis,
+ NULL, VD_DISCARD_MARK_UNUSED);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+
+ fAllocated = true;
+ }
+
+ idxStart = idxEnd;
+ offStart += cbThis;
+ cbLeft -= cbThis;
+ }
+
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+
+ PVDDISCARDBLOCK pBlockRemove = (PVDDISCARDBLOCK)RTAvlrU64RangeRemove(pDiscard->pTreeBlocks, pBlock->Core.Key);
+ Assert(pBlockRemove == pBlock); NOREF(pBlockRemove);
+ RTListNodeRemove(&pBlock->NodeLru);
+
+ pDiscard->cbDiscarding -= pBlock->cbDiscard;
+ RTMemFree(pBlock->pbmAllocated);
+ RTMemFree(pBlock);
+ }
+
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_SUCCESS;
+
+ Assert(RT_FAILURE(rc) || pDiscard->cbDiscarding <= cbDiscardingNew);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Async discard helper - discards the current range if there is no matching
+ * block in the tree.
+ *
+ * @returns VBox status code.
+ * @param pIoCtx The I/O context to operate on.
+ */
+static DECLCALLBACK(int) vdDiscardCurrentRangeAsync(PVDIOCTX pIoCtx)
+{
+ PVDISK pDisk = pIoCtx->pDisk;
+ PVDDISCARDSTATE pDiscard = pDisk->pDiscard;
+ uint64_t offStart = pIoCtx->Req.Discard.offCur;
+ size_t cbThisDiscard = pIoCtx->Req.Discard.cbThisDiscard;
+ void *pbmAllocated = NULL;
+ size_t cbPreAllocated, cbPostAllocated;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ /* No block found, try to discard using the backend first. */
+ rc = pDisk->pLast->Backend->pfnDiscard(pDisk->pLast->pBackendData, pIoCtx,
+ offStart, cbThisDiscard, &cbPreAllocated,
+ &cbPostAllocated, &cbThisDiscard,
+ &pbmAllocated, 0);
+ if (rc == VERR_VD_DISCARD_ALIGNMENT_NOT_MET)
+ {
+ /* Create new discard block. */
+ PVDDISCARDBLOCK pBlock = (PVDDISCARDBLOCK)RTMemAllocZ(sizeof(VDDISCARDBLOCK));
+ if (pBlock)
+ {
+ pBlock->Core.Key = offStart - cbPreAllocated;
+ pBlock->Core.KeyLast = offStart + cbThisDiscard + cbPostAllocated - 1;
+ pBlock->cbDiscard = cbPreAllocated + cbThisDiscard + cbPostAllocated;
+ pBlock->pbmAllocated = pbmAllocated;
+ bool fInserted = RTAvlrU64Insert(pDiscard->pTreeBlocks, &pBlock->Core);
+ Assert(fInserted); NOREF(fInserted);
+
+ RTListPrepend(&pDiscard->ListLru, &pBlock->NodeLru);
+ pDiscard->cbDiscarding += pBlock->cbDiscard;
+
+ Assert(pIoCtx->Req.Discard.cbDiscardLeft >= cbThisDiscard);
+ pIoCtx->Req.Discard.cbDiscardLeft -= cbThisDiscard;
+ pIoCtx->Req.Discard.offCur += cbThisDiscard;
+ pIoCtx->Req.Discard.cbThisDiscard = cbThisDiscard;
+
+ if (pDiscard->cbDiscarding > VD_DISCARD_REMOVE_THRESHOLD)
+ rc = vdDiscardRemoveBlocksAsync(pDisk, pIoCtx, VD_DISCARD_REMOVE_THRESHOLD);
+ else
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ pIoCtx->pfnIoCtxTransferNext = vdDiscardHelperAsync; /* Next part. */
+ }
+ else
+ {
+ RTMemFree(pbmAllocated);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else if ( RT_SUCCESS(rc)
+ || rc == VERR_VD_ASYNC_IO_IN_PROGRESS) /* Save state and andvance to next range. */
+ {
+ Assert(pIoCtx->Req.Discard.cbDiscardLeft >= cbThisDiscard);
+ pIoCtx->Req.Discard.cbDiscardLeft -= cbThisDiscard;
+ pIoCtx->Req.Discard.offCur += cbThisDiscard;
+ pIoCtx->Req.Discard.cbThisDiscard = cbThisDiscard;
+ pIoCtx->pfnIoCtxTransferNext = vdDiscardHelperAsync;
+ rc = VINF_SUCCESS;
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Async discard helper - entry point.
+ *
+ * @returns VBox status code.
+ * @param pIoCtx The I/O context to operate on.
+ */
+static DECLCALLBACK(int) vdDiscardHelperAsync(PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = pIoCtx->pDisk;
+ PCRTRANGE paRanges = pIoCtx->Req.Discard.paRanges;
+ unsigned cRanges = pIoCtx->Req.Discard.cRanges;
+ PVDDISCARDSTATE pDiscard = pDisk->pDiscard;
+
+ LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+ /* Check if the I/O context processed all ranges. */
+ if ( pIoCtx->Req.Discard.idxRange == cRanges
+ && !pIoCtx->Req.Discard.cbDiscardLeft)
+ {
+ LogFlowFunc(("All ranges discarded, completing\n"));
+ vdIoCtxUnlockDisk(pDisk, pIoCtx, true /* fProcessDeferredReqs*/);
+ return VINF_SUCCESS;
+ }
+
+ if (pDisk->pIoCtxLockOwner != pIoCtx)
+ rc = vdIoCtxLockDisk(pDisk, pIoCtx);
+
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t offStart = pIoCtx->Req.Discard.offCur;
+ size_t cbDiscardLeft = pIoCtx->Req.Discard.cbDiscardLeft;
+ size_t cbThisDiscard;
+
+ pDisk->uOffsetStartLocked = offStart;
+ pDisk->uOffsetEndLocked = offStart + cbDiscardLeft;
+
+ if (RT_UNLIKELY(!pDiscard))
+ {
+ pDiscard = vdDiscardStateCreate();
+ if (!pDiscard)
+ return VERR_NO_MEMORY;
+
+ pDisk->pDiscard = pDiscard;
+ }
+
+ if (!pIoCtx->Req.Discard.cbDiscardLeft)
+ {
+ offStart = paRanges[pIoCtx->Req.Discard.idxRange].offStart;
+ cbDiscardLeft = paRanges[pIoCtx->Req.Discard.idxRange].cbRange;
+ LogFlowFunc(("New range descriptor loaded (%u) offStart=%llu cbDiscard=%zu\n",
+ pIoCtx->Req.Discard.idxRange, offStart, cbDiscardLeft));
+ pIoCtx->Req.Discard.idxRange++;
+ }
+
+ /* Look for a matching block in the AVL tree first. */
+ PVDDISCARDBLOCK pBlock = (PVDDISCARDBLOCK)RTAvlrU64GetBestFit(pDiscard->pTreeBlocks, offStart, false);
+ if (!pBlock || pBlock->Core.KeyLast < offStart)
+ {
+ PVDDISCARDBLOCK pBlockAbove = (PVDDISCARDBLOCK)RTAvlrU64GetBestFit(pDiscard->pTreeBlocks, offStart, true);
+
+ /* Clip range to remain in the current block. */
+ if (pBlockAbove)
+ cbThisDiscard = RT_MIN(cbDiscardLeft, pBlockAbove->Core.KeyLast - offStart + 1);
+ else
+ cbThisDiscard = cbDiscardLeft;
+
+ Assert(!(cbThisDiscard % 512));
+ pIoCtx->Req.Discard.pBlock = NULL;
+ pIoCtx->pfnIoCtxTransferNext = vdDiscardCurrentRangeAsync;
+ }
+ else
+ {
+ /* Range lies partly in the block, update allocation bitmap. */
+ int32_t idxStart, idxEnd;
+
+ cbThisDiscard = RT_MIN(cbDiscardLeft, pBlock->Core.KeyLast - offStart + 1);
+
+ AssertPtr(pBlock);
+
+ Assert(!(cbThisDiscard % 512));
+ Assert(!((offStart - pBlock->Core.Key) % 512));
+
+ idxStart = (offStart - pBlock->Core.Key) / 512;
+ idxEnd = idxStart + (int32_t)(cbThisDiscard / 512);
+
+ ASMBitClearRange(pBlock->pbmAllocated, idxStart, idxEnd);
+
+ cbDiscardLeft -= cbThisDiscard;
+ offStart += cbThisDiscard;
+
+ /* Call the backend to discard the block if it is completely unallocated now. */
+ if (ASMBitFirstSet((volatile void *)pBlock->pbmAllocated, (uint32_t)(pBlock->cbDiscard / 512)) == -1)
+ {
+ pIoCtx->Req.Discard.pBlock = pBlock;
+ pIoCtx->pfnIoCtxTransferNext = vdDiscardWholeBlockAsync;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ RTListNodeRemove(&pBlock->NodeLru);
+ RTListPrepend(&pDiscard->ListLru, &pBlock->NodeLru);
+
+ /* Start with next range. */
+ pIoCtx->pfnIoCtxTransferNext = vdDiscardHelperAsync;
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ /* Save state in the context. */
+ pIoCtx->Req.Discard.offCur = offStart;
+ pIoCtx->Req.Discard.cbDiscardLeft = cbDiscardLeft;
+ pIoCtx->Req.Discard.cbThisDiscard = cbThisDiscard;
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * VD async I/O interface open callback.
+ */
+static DECLCALLBACK(int) vdIOOpenFallback(void *pvUser, const char *pszLocation,
+ uint32_t fOpen, PFNVDCOMPLETED pfnCompleted,
+ void **ppStorage)
+{
+ RT_NOREF1(pvUser);
+ PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)RTMemAllocZ(sizeof(VDIIOFALLBACKSTORAGE));
+
+ if (!pStorage)
+ return VERR_NO_MEMORY;
+
+ pStorage->pfnCompleted = pfnCompleted;
+
+ /* Open the file. */
+ int rc = RTFileOpen(&pStorage->File, pszLocation, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ *ppStorage = pStorage;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pStorage);
+ return rc;
+}
+
+/**
+ * VD async I/O interface close callback.
+ */
+static DECLCALLBACK(int) vdIOCloseFallback(void *pvUser, void *pvStorage)
+{
+ RT_NOREF1(pvUser);
+ PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage;
+
+ RTFileClose(pStorage->File);
+ RTMemFree(pStorage);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vdIODeleteFallback(void *pvUser, const char *pcszFilename)
+{
+ RT_NOREF1(pvUser);
+ return RTFileDelete(pcszFilename);
+}
+
+static DECLCALLBACK(int) vdIOMoveFallback(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
+{
+ RT_NOREF1(pvUser);
+ return RTFileMove(pcszSrc, pcszDst, fMove);
+}
+
+static DECLCALLBACK(int) vdIOGetFreeSpaceFallback(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
+{
+ RT_NOREF1(pvUser);
+ return RTFsQuerySizes(pcszFilename, NULL, pcbFreeSpace, NULL, NULL);
+}
+
+static DECLCALLBACK(int) vdIOGetModificationTimeFallback(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
+{
+ RT_NOREF1(pvUser);
+ RTFSOBJINFO info;
+ int rc = RTPathQueryInfo(pcszFilename, &info, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ *pModificationTime = info.ModificationTime;
+ return rc;
+}
+
+/**
+ * VD async I/O interface callback for retrieving the file size.
+ */
+static DECLCALLBACK(int) vdIOGetSizeFallback(void *pvUser, void *pvStorage, uint64_t *pcbSize)
+{
+ RT_NOREF1(pvUser);
+ PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage;
+
+ return RTFileQuerySize(pStorage->File, pcbSize);
+}
+
+/**
+ * VD async I/O interface callback for setting the file size.
+ */
+static DECLCALLBACK(int) vdIOSetSizeFallback(void *pvUser, void *pvStorage, uint64_t cbSize)
+{
+ RT_NOREF1(pvUser);
+ PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage;
+
+ return RTFileSetSize(pStorage->File, cbSize);
+}
+
+/**
+ * VD async I/O interface callback for setting the file allocation size.
+ */
+static DECLCALLBACK(int) vdIOSetAllocationSizeFallback(void *pvUser, void *pvStorage, uint64_t cbSize,
+ uint32_t fFlags)
+{
+ RT_NOREF2(pvUser, fFlags);
+ PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage;
+
+ return RTFileSetAllocationSize(pStorage->File, cbSize, RTFILE_ALLOC_SIZE_F_DEFAULT);
+}
+
+/**
+ * VD async I/O interface callback for a synchronous write to the file.
+ */
+static DECLCALLBACK(int) vdIOWriteSyncFallback(void *pvUser, void *pvStorage, uint64_t uOffset,
+ const void *pvBuf, size_t cbWrite, size_t *pcbWritten)
+{
+ RT_NOREF1(pvUser);
+ PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage;
+
+ return RTFileWriteAt(pStorage->File, uOffset, pvBuf, cbWrite, pcbWritten);
+}
+
+/**
+ * VD async I/O interface callback for a synchronous read from the file.
+ */
+static DECLCALLBACK(int) vdIOReadSyncFallback(void *pvUser, void *pvStorage, uint64_t uOffset,
+ void *pvBuf, size_t cbRead, size_t *pcbRead)
+{
+ RT_NOREF1(pvUser);
+ PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage;
+
+ return RTFileReadAt(pStorage->File, uOffset, pvBuf, cbRead, pcbRead);
+}
+
+/**
+ * VD async I/O interface callback for a synchronous flush of the file data.
+ */
+static DECLCALLBACK(int) vdIOFlushSyncFallback(void *pvUser, void *pvStorage)
+{
+ RT_NOREF1(pvUser);
+ PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage;
+
+ return RTFileFlush(pStorage->File);
+}
+
+/**
+ * Internal - Continues an I/O context after
+ * it was halted because of an active transfer.
+ */
+static int vdIoCtxContinue(PVDIOCTX pIoCtx, int rcReq)
+{
+ PVDISK pDisk = pIoCtx->pDisk;
+ int rc = VINF_SUCCESS;
+
+ VD_IS_LOCKED(pDisk);
+
+ if (RT_FAILURE(rcReq))
+ ASMAtomicCmpXchgS32(&pIoCtx->rcReq, rcReq, VINF_SUCCESS);
+
+ if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED))
+ {
+ /* Continue the transfer */
+ rc = vdIoCtxProcessLocked(pIoCtx);
+
+ if ( rc == VINF_VD_ASYNC_IO_FINISHED
+ && ASMAtomicCmpXchgBool(&pIoCtx->fComplete, true, false))
+ {
+ LogFlowFunc(("I/O context completed pIoCtx=%#p\n", pIoCtx));
+ bool fFreeCtx = RT_BOOL(!(pIoCtx->fFlags & VDIOCTX_FLAGS_DONT_FREE));
+ if (pIoCtx->pIoCtxParent)
+ {
+ PVDIOCTX pIoCtxParent = pIoCtx->pIoCtxParent;
+
+ Assert(!pIoCtxParent->pIoCtxParent);
+ if (RT_FAILURE(pIoCtx->rcReq))
+ ASMAtomicCmpXchgS32(&pIoCtxParent->rcReq, pIoCtx->rcReq, VINF_SUCCESS);
+
+ ASMAtomicDecU32(&pIoCtxParent->cDataTransfersPending);
+
+ if (pIoCtx->enmTxDir == VDIOCTXTXDIR_WRITE)
+ {
+ LogFlowFunc(("I/O context transferred %u bytes for the parent pIoCtxParent=%p\n",
+ pIoCtx->Type.Child.cbTransferParent, pIoCtxParent));
+
+ /* Update the parent state. */
+ Assert(pIoCtxParent->Req.Io.cbTransferLeft >= pIoCtx->Type.Child.cbTransferParent);
+ ASMAtomicSubU32(&pIoCtxParent->Req.Io.cbTransferLeft, (uint32_t)pIoCtx->Type.Child.cbTransferParent);
+ }
+ else
+ Assert(pIoCtx->enmTxDir == VDIOCTXTXDIR_FLUSH);
+
+ /*
+ * A completed child write means that we finished growing the image.
+ * We have to process any pending writes now.
+ */
+ vdIoCtxUnlockDisk(pDisk, pIoCtxParent, false /* fProcessDeferredReqs */);
+
+ /* Unblock the parent */
+ pIoCtxParent->fFlags &= ~VDIOCTX_FLAGS_BLOCKED;
+
+ rc = vdIoCtxProcessLocked(pIoCtxParent);
+
+ if ( rc == VINF_VD_ASYNC_IO_FINISHED
+ && ASMAtomicCmpXchgBool(&pIoCtxParent->fComplete, true, false))
+ {
+ LogFlowFunc(("Parent I/O context completed pIoCtxParent=%#p rcReq=%Rrc\n", pIoCtxParent, pIoCtxParent->rcReq));
+ bool fFreeParentCtx = RT_BOOL(!(pIoCtxParent->fFlags & VDIOCTX_FLAGS_DONT_FREE));
+ vdIoCtxRootComplete(pDisk, pIoCtxParent);
+ vdThreadFinishWrite(pDisk);
+
+ if (fFreeParentCtx)
+ vdIoCtxFree(pDisk, pIoCtxParent);
+ vdDiskProcessBlockedIoCtx(pDisk);
+ }
+ else if (!vdIoCtxIsDiskLockOwner(pDisk, pIoCtx))
+ {
+ /* Process any pending writes if the current request didn't caused another growing. */
+ vdDiskProcessBlockedIoCtx(pDisk);
+ }
+ }
+ else
+ {
+ if (pIoCtx->enmTxDir == VDIOCTXTXDIR_FLUSH)
+ {
+ vdIoCtxUnlockDisk(pDisk, pIoCtx, true /* fProcessDerredReqs */);
+ vdThreadFinishWrite(pDisk);
+ }
+ else if ( pIoCtx->enmTxDir == VDIOCTXTXDIR_WRITE
+ || pIoCtx->enmTxDir == VDIOCTXTXDIR_DISCARD)
+ vdThreadFinishWrite(pDisk);
+ else
+ {
+ Assert(pIoCtx->enmTxDir == VDIOCTXTXDIR_READ);
+ vdThreadFinishRead(pDisk);
+ }
+
+ LogFlowFunc(("I/O context completed pIoCtx=%#p rcReq=%Rrc\n", pIoCtx, pIoCtx->rcReq));
+ vdIoCtxRootComplete(pDisk, pIoCtx);
+ }
+
+ if (fFreeCtx)
+ vdIoCtxFree(pDisk, pIoCtx);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal - Called when user transfer completed.
+ */
+static int vdUserXferCompleted(PVDIOSTORAGE pIoStorage, PVDIOCTX pIoCtx,
+ PFNVDXFERCOMPLETED pfnComplete, void *pvUser,
+ size_t cbTransfer, int rcReq)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = pIoCtx->pDisk;
+
+ LogFlowFunc(("pIoStorage=%#p pIoCtx=%#p pfnComplete=%#p pvUser=%#p cbTransfer=%zu rcReq=%Rrc\n",
+ pIoStorage, pIoCtx, pfnComplete, pvUser, cbTransfer, rcReq));
+
+ VD_IS_LOCKED(pDisk);
+
+ Assert(pIoCtx->Req.Io.cbTransferLeft >= cbTransfer);
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbTransfer); Assert(cbTransfer == (uint32_t)cbTransfer);
+ ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
+
+ if (pfnComplete)
+ rc = pfnComplete(pIoStorage->pVDIo->pBackendData, pIoCtx, pvUser, rcReq);
+
+ if (RT_SUCCESS(rc))
+ rc = vdIoCtxContinue(pIoCtx, rcReq);
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_SUCCESS;
+
+ return rc;
+}
+
+static void vdIoCtxContinueDeferredList(PVDIOSTORAGE pIoStorage, PRTLISTANCHOR pListWaiting,
+ PFNVDXFERCOMPLETED pfnComplete, void *pvUser, int rcReq)
+{
+ LogFlowFunc(("pIoStorage=%#p pListWaiting=%#p pfnComplete=%#p pvUser=%#p rcReq=%Rrc\n",
+ pIoStorage, pListWaiting, pfnComplete, pvUser, rcReq));
+
+ /* Go through the waiting list and continue the I/O contexts. */
+ while (!RTListIsEmpty(pListWaiting))
+ {
+ int rc = VINF_SUCCESS;
+ PVDIOCTXDEFERRED pDeferred = RTListGetFirst(pListWaiting, VDIOCTXDEFERRED, NodeDeferred);
+ PVDIOCTX pIoCtx = pDeferred->pIoCtx;
+ RTListNodeRemove(&pDeferred->NodeDeferred);
+
+ RTMemFree(pDeferred);
+ ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending);
+
+ if (pfnComplete)
+ rc = pfnComplete(pIoStorage->pVDIo->pBackendData, pIoCtx, pvUser, rcReq);
+
+ LogFlow(("Completion callback for I/O context %#p returned %Rrc\n", pIoCtx, rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIoCtxContinue(pIoCtx, rcReq);
+ AssertRC(rc);
+ }
+ else
+ Assert(rc == VERR_VD_ASYNC_IO_IN_PROGRESS);
+ }
+}
+
+/**
+ * Internal - Called when a meta transfer completed.
+ */
+static int vdMetaXferCompleted(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser,
+ PVDMETAXFER pMetaXfer, int rcReq)
+{
+ PVDISK pDisk = pIoStorage->pVDIo->pDisk;
+ RTLISTANCHOR ListIoCtxWaiting;
+ bool fFlush;
+
+ LogFlowFunc(("pIoStorage=%#p pfnComplete=%#p pvUser=%#p pMetaXfer=%#p rcReq=%Rrc\n",
+ pIoStorage, pfnComplete, pvUser, pMetaXfer, rcReq));
+
+ VD_IS_LOCKED(pDisk);
+
+ fFlush = VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_FLUSH;
+
+ if (!fFlush)
+ {
+ RTListMove(&ListIoCtxWaiting, &pMetaXfer->ListIoCtxWaiting);
+
+ if (RT_FAILURE(rcReq))
+ {
+ /* Remove from the AVL tree. */
+ LogFlow(("Removing meta xfer=%#p\n", pMetaXfer));
+ bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key) != NULL;
+ Assert(fRemoved); NOREF(fRemoved);
+ /* If this was a write check if there is a shadow buffer with updated data. */
+ if (pMetaXfer->pbDataShw)
+ {
+ Assert(VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_WRITE);
+ Assert(!RTListIsEmpty(&pMetaXfer->ListIoCtxShwWrites));
+ RTListConcatenate(&ListIoCtxWaiting, &pMetaXfer->ListIoCtxShwWrites);
+ RTMemFree(pMetaXfer->pbDataShw);
+ pMetaXfer->pbDataShw = NULL;
+ }
+ RTMemFree(pMetaXfer);
+ }
+ else
+ {
+ /* Increase the reference counter to make sure it doesn't go away before the last context is processed. */
+ pMetaXfer->cRefs++;
+ }
+ }
+ else
+ RTListMove(&ListIoCtxWaiting, &pMetaXfer->ListIoCtxWaiting);
+
+ VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE);
+ vdIoCtxContinueDeferredList(pIoStorage, &ListIoCtxWaiting, pfnComplete, pvUser, rcReq);
+
+ /*
+ * If there is a shadow buffer and the previous write was successful update with the
+ * new data and trigger a new write.
+ */
+ if ( pMetaXfer->pbDataShw
+ && RT_SUCCESS(rcReq)
+ && VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_NONE)
+ {
+ LogFlowFunc(("pMetaXfer=%#p Updating from shadow buffer and triggering new write\n", pMetaXfer));
+ memcpy(pMetaXfer->abData, pMetaXfer->pbDataShw, pMetaXfer->cbMeta);
+ RTMemFree(pMetaXfer->pbDataShw);
+ pMetaXfer->pbDataShw = NULL;
+ Assert(!RTListIsEmpty(&pMetaXfer->ListIoCtxShwWrites));
+
+ /* Setup a new I/O write. */
+ PVDIOTASK pIoTask = vdIoTaskMetaAlloc(pIoStorage, pfnComplete, pvUser, pMetaXfer);
+ if (RT_LIKELY(pIoTask))
+ {
+ void *pvTask = NULL;
+ RTSGSEG Seg;
+
+ Seg.cbSeg = pMetaXfer->cbMeta;
+ Seg.pvSeg = pMetaXfer->abData;
+
+ VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_WRITE);
+ rcReq = pIoStorage->pVDIo->pInterfaceIo->pfnWriteAsync(pIoStorage->pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage,
+ pMetaXfer->Core.Key, &Seg, 1,
+ pMetaXfer->cbMeta, pIoTask,
+ &pvTask);
+ if ( RT_SUCCESS(rcReq)
+ || rcReq != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE);
+ vdIoTaskFree(pDisk, pIoTask);
+ }
+ else
+ RTListMove(&pMetaXfer->ListIoCtxWaiting, &pMetaXfer->ListIoCtxShwWrites);
+ }
+ else
+ rcReq = VERR_NO_MEMORY;
+
+ /* Cleanup if there was an error or the request completed already. */
+ if (rcReq != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ vdIoCtxContinueDeferredList(pIoStorage, &pMetaXfer->ListIoCtxShwWrites, pfnComplete, pvUser, rcReq);
+ }
+
+ /* Remove if not used anymore. */
+ if (!fFlush)
+ {
+ pMetaXfer->cRefs--;
+ if (!pMetaXfer->cRefs && RTListIsEmpty(&pMetaXfer->ListIoCtxWaiting))
+ {
+ /* Remove from the AVL tree. */
+ LogFlow(("Removing meta xfer=%#p\n", pMetaXfer));
+ bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key) != NULL;
+ Assert(fRemoved); NOREF(fRemoved);
+ RTMemFree(pMetaXfer);
+ }
+ }
+ else if (fFlush)
+ RTMemFree(pMetaXfer);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Processes a list of waiting I/O tasks. The disk lock must be held by caller.
+ *
+ * @returns nothing.
+ * @param pDisk The disk to process the list for.
+ */
+static void vdIoTaskProcessWaitingList(PVDISK pDisk)
+{
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+
+ VD_IS_LOCKED(pDisk);
+
+ PVDIOTASK pHead = ASMAtomicXchgPtrT(&pDisk->pIoTasksPendingHead, NULL, PVDIOTASK);
+
+ Log(("I/O task list cleared\n"));
+
+ /* Reverse order. */
+ PVDIOTASK pCur = pHead;
+ pHead = NULL;
+ while (pCur)
+ {
+ PVDIOTASK pInsert = pCur;
+ pCur = pCur->pNext;
+ pInsert->pNext = pHead;
+ pHead = pInsert;
+ }
+
+ while (pHead)
+ {
+ PVDIOSTORAGE pIoStorage = pHead->pIoStorage;
+
+ if (!pHead->fMeta)
+ vdUserXferCompleted(pIoStorage, pHead->Type.User.pIoCtx,
+ pHead->pfnComplete, pHead->pvUser,
+ pHead->Type.User.cbTransfer, pHead->rcReq);
+ else
+ vdMetaXferCompleted(pIoStorage, pHead->pfnComplete, pHead->pvUser,
+ pHead->Type.Meta.pMetaXfer, pHead->rcReq);
+
+ pCur = pHead;
+ pHead = pHead->pNext;
+ vdIoTaskFree(pDisk, pCur);
+ }
+}
+
+/**
+ * Process any I/O context on the halted list.
+ *
+ * @returns nothing.
+ * @param pDisk The disk.
+ */
+static void vdIoCtxProcessHaltedList(PVDISK pDisk)
+{
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+
+ VD_IS_LOCKED(pDisk);
+
+ /* Get the waiting list and process it in FIFO order. */
+ PVDIOCTX pIoCtxHead = ASMAtomicXchgPtrT(&pDisk->pIoCtxHaltedHead, NULL, PVDIOCTX);
+
+ /* Reverse it. */
+ PVDIOCTX pCur = pIoCtxHead;
+ pIoCtxHead = NULL;
+ while (pCur)
+ {
+ PVDIOCTX pInsert = pCur;
+ pCur = pCur->pIoCtxNext;
+ pInsert->pIoCtxNext = pIoCtxHead;
+ pIoCtxHead = pInsert;
+ }
+
+ /* Process now. */
+ pCur = pIoCtxHead;
+ while (pCur)
+ {
+ PVDIOCTX pTmp = pCur;
+
+ pCur = pCur->pIoCtxNext;
+ pTmp->pIoCtxNext = NULL;
+
+ /* Continue */
+ pTmp->fFlags &= ~VDIOCTX_FLAGS_BLOCKED;
+ vdIoCtxContinue(pTmp, pTmp->rcReq);
+ }
+}
+
+/**
+ * Unlock the disk and process pending tasks.
+ *
+ * @returns VBox status code.
+ * @param pDisk The disk to unlock.
+ * @param pIoCtxRc The I/O context to get the status code from, optional.
+ */
+static int vdDiskUnlock(PVDISK pDisk, PVDIOCTX pIoCtxRc)
+{
+ int rc = VINF_SUCCESS;
+
+ VD_IS_LOCKED(pDisk);
+
+ /*
+ * Process the list of waiting I/O tasks first
+ * because they might complete I/O contexts.
+ * Same for the list of halted I/O contexts.
+ * Afterwards comes the list of new I/O contexts.
+ */
+ vdIoTaskProcessWaitingList(pDisk);
+ vdIoCtxProcessHaltedList(pDisk);
+ rc = vdDiskProcessWaitingIoCtx(pDisk, pIoCtxRc);
+ ASMAtomicXchgBool(&pDisk->fLocked, false);
+
+ /*
+ * Need to check for new I/O tasks and waiting I/O contexts now
+ * again as other threads might added them while we processed
+ * previous lists.
+ */
+ while ( ASMAtomicUoReadPtrT(&pDisk->pIoCtxHead, PVDIOCTX) != NULL
+ || ASMAtomicUoReadPtrT(&pDisk->pIoTasksPendingHead, PVDIOTASK) != NULL
+ || ASMAtomicUoReadPtrT(&pDisk->pIoCtxHaltedHead, PVDIOCTX) != NULL)
+ {
+ /* Try lock disk again. */
+ if (ASMAtomicCmpXchgBool(&pDisk->fLocked, true, false))
+ {
+ vdIoTaskProcessWaitingList(pDisk);
+ vdIoCtxProcessHaltedList(pDisk);
+ vdDiskProcessWaitingIoCtx(pDisk, NULL);
+ ASMAtomicXchgBool(&pDisk->fLocked, false);
+ }
+ else /* Let the other thread everything when he unlocks the disk. */
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * Try to lock the disk to complete pressing of the I/O task.
+ * The completion is deferred if the disk is locked already.
+ *
+ * @returns nothing.
+ * @param pIoTask The I/O task to complete.
+ */
+static void vdXferTryLockDiskDeferIoTask(PVDIOTASK pIoTask)
+{
+ PVDIOSTORAGE pIoStorage = pIoTask->pIoStorage;
+ PVDISK pDisk = pIoStorage->pVDIo->pDisk;
+
+ Log(("Deferring I/O task pIoTask=%p\n", pIoTask));
+
+ /* Put it on the waiting list. */
+ PVDIOTASK pNext = ASMAtomicUoReadPtrT(&pDisk->pIoTasksPendingHead, PVDIOTASK);
+ PVDIOTASK pHeadOld;
+ pIoTask->pNext = pNext;
+ while (!ASMAtomicCmpXchgExPtr(&pDisk->pIoTasksPendingHead, pIoTask, pNext, &pHeadOld))
+ {
+ pNext = pHeadOld;
+ Assert(pNext != pIoTask);
+ pIoTask->pNext = pNext;
+ ASMNopPause();
+ }
+
+ if (ASMAtomicCmpXchgBool(&pDisk->fLocked, true, false))
+ {
+ /* Release disk lock, it will take care of processing all lists. */
+ vdDiskUnlock(pDisk, NULL);
+ }
+}
+
+static DECLCALLBACK(int) vdIOIntReqCompleted(void *pvUser, int rcReq)
+{
+ PVDIOTASK pIoTask = (PVDIOTASK)pvUser;
+
+ LogFlowFunc(("Task completed pIoTask=%#p\n", pIoTask));
+
+ pIoTask->rcReq = rcReq;
+ vdXferTryLockDiskDeferIoTask(pIoTask);
+ return VINF_SUCCESS;
+}
+
+/**
+ * VD I/O interface callback for opening a file.
+ */
+static DECLCALLBACK(int) vdIOIntOpen(void *pvUser, const char *pszLocation,
+ unsigned uOpenFlags, PPVDIOSTORAGE ppIoStorage)
+{
+ int rc = VINF_SUCCESS;
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDIOSTORAGE pIoStorage = (PVDIOSTORAGE)RTMemAllocZ(sizeof(VDIOSTORAGE));
+
+ if (!pIoStorage)
+ return VERR_NO_MEMORY;
+
+ /* Create the AVl tree. */
+ pIoStorage->pTreeMetaXfers = (PAVLRFOFFTREE)RTMemAllocZ(sizeof(AVLRFOFFTREE));
+ if (pIoStorage->pTreeMetaXfers)
+ {
+ rc = pVDIo->pInterfaceIo->pfnOpen(pVDIo->pInterfaceIo->Core.pvUser,
+ pszLocation, uOpenFlags,
+ vdIOIntReqCompleted,
+ &pIoStorage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ pIoStorage->pVDIo = pVDIo;
+ *ppIoStorage = pIoStorage;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pIoStorage->pTreeMetaXfers);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTMemFree(pIoStorage);
+ return rc;
+}
+
+static DECLCALLBACK(int) vdIOIntTreeMetaXferDestroy(PAVLRFOFFNODECORE pNode, void *pvUser)
+{
+ RT_NOREF2(pNode, pvUser);
+ AssertMsgFailed(("Tree should be empty at this point!\n"));
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vdIOIntClose(void *pvUser, PVDIOSTORAGE pIoStorage)
+{
+ int rc = VINF_SUCCESS;
+ PVDIO pVDIo = (PVDIO)pvUser;
+
+ /* We free everything here, even if closing the file failed for some reason. */
+ rc = pVDIo->pInterfaceIo->pfnClose(pVDIo->pInterfaceIo->Core.pvUser, pIoStorage->pStorage);
+ RTAvlrFileOffsetDestroy(pIoStorage->pTreeMetaXfers, vdIOIntTreeMetaXferDestroy, NULL);
+ RTMemFree(pIoStorage->pTreeMetaXfers);
+ RTMemFree(pIoStorage);
+ return rc;
+}
+
+static DECLCALLBACK(int) vdIOIntDelete(void *pvUser, const char *pcszFilename)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ return pVDIo->pInterfaceIo->pfnDelete(pVDIo->pInterfaceIo->Core.pvUser,
+ pcszFilename);
+}
+
+static DECLCALLBACK(int) vdIOIntMove(void *pvUser, const char *pcszSrc, const char *pcszDst,
+ unsigned fMove)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ return pVDIo->pInterfaceIo->pfnMove(pVDIo->pInterfaceIo->Core.pvUser,
+ pcszSrc, pcszDst, fMove);
+}
+
+static DECLCALLBACK(int) vdIOIntGetFreeSpace(void *pvUser, const char *pcszFilename,
+ int64_t *pcbFreeSpace)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ return pVDIo->pInterfaceIo->pfnGetFreeSpace(pVDIo->pInterfaceIo->Core.pvUser,
+ pcszFilename, pcbFreeSpace);
+}
+
+static DECLCALLBACK(int) vdIOIntGetModificationTime(void *pvUser, const char *pcszFilename,
+ PRTTIMESPEC pModificationTime)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ return pVDIo->pInterfaceIo->pfnGetModificationTime(pVDIo->pInterfaceIo->Core.pvUser,
+ pcszFilename, pModificationTime);
+}
+
+static DECLCALLBACK(int) vdIOIntGetSize(void *pvUser, PVDIOSTORAGE pIoStorage,
+ uint64_t *pcbSize)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ return pVDIo->pInterfaceIo->pfnGetSize(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, pcbSize);
+}
+
+static DECLCALLBACK(int) vdIOIntSetSize(void *pvUser, PVDIOSTORAGE pIoStorage,
+ uint64_t cbSize)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ return pVDIo->pInterfaceIo->pfnSetSize(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, cbSize);
+}
+
+static DECLCALLBACK(int) vdIOIntSetAllocationSize(void *pvUser, PVDIOSTORAGE pIoStorage,
+ uint64_t cbSize, uint32_t fFlags,
+ PVDINTERFACEPROGRESS pIfProgress,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ int rc = pVDIo->pInterfaceIo->pfnSetAllocationSize(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, cbSize, fFlags);
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ /* Fallback if the underlying medium does not support optimized storage allocation. */
+ uint64_t cbSizeCur = 0;
+ rc = pVDIo->pInterfaceIo->pfnGetSize(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, &cbSizeCur);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbSizeCur < cbSize)
+ {
+ const size_t cbBuf = 128 * _1K;
+ void *pvBuf = RTMemTmpAllocZ(cbBuf);
+ if (RT_LIKELY(pvBuf))
+ {
+ uint64_t cbFill = cbSize - cbSizeCur;
+ uint64_t uOff = 0;
+
+ /* Write data to all blocks. */
+ while ( uOff < cbFill
+ && RT_SUCCESS(rc))
+ {
+ size_t cbChunk = (size_t)RT_MIN(cbFill - uOff, cbBuf);
+
+ rc = pVDIo->pInterfaceIo->pfnWriteSync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, cbSizeCur + uOff,
+ pvBuf, cbChunk, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ uOff += cbChunk;
+
+ rc = vdIfProgress(pIfProgress, uPercentStart + uOff * uPercentSpan / cbFill);
+ }
+ }
+
+ RTMemTmpFree(pvBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (cbSizeCur > cbSize)
+ rc = pVDIo->pInterfaceIo->pfnSetSize(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, cbSize);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdIOIntReadUser(void *pvUser, PVDIOSTORAGE pIoStorage, uint64_t uOffset,
+ PVDIOCTX pIoCtx, size_t cbRead)
+{
+ int rc = VINF_SUCCESS;
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+
+ LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pIoCtx=%#p cbRead=%u\n",
+ pvUser, pIoStorage, uOffset, pIoCtx, cbRead));
+
+ /** @todo Enable check for sync I/O later. */
+ if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC))
+ VD_IS_LOCKED(pDisk);
+
+ Assert(cbRead > 0);
+
+ if ( (pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)
+ || !pVDIo->pInterfaceIo->pfnReadAsync)
+ {
+ RTSGSEG Seg;
+ unsigned cSegments = 1;
+ size_t cbTaskRead = 0;
+
+ /* Synchronous I/O contexts only have one buffer segment. */
+ AssertMsgReturn(pIoCtx->Req.Io.SgBuf.cSegs == 1,
+ ("Invalid number of buffer segments for synchronous I/O context"),
+ VERR_INVALID_PARAMETER);
+
+ cbTaskRead = RTSgBufSegArrayCreate(&pIoCtx->Req.Io.SgBuf, &Seg, &cSegments, cbRead);
+ Assert(cbRead == cbTaskRead);
+ Assert(cSegments == 1);
+ rc = pVDIo->pInterfaceIo->pfnReadSync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, uOffset,
+ Seg.pvSeg, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbRead == (uint32_t)cbRead);
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbRead);
+ }
+ }
+ else
+ {
+ /* Build the S/G array and spawn a new I/O task */
+ while (cbRead)
+ {
+ RTSGSEG aSeg[VD_IO_TASK_SEGMENTS_MAX];
+ unsigned cSegments = VD_IO_TASK_SEGMENTS_MAX;
+ size_t cbTaskRead = RTSgBufSegArrayCreate(&pIoCtx->Req.Io.SgBuf, aSeg, &cSegments, cbRead);
+
+ Assert(cSegments > 0);
+ Assert(cbTaskRead > 0);
+ AssertMsg(cbTaskRead <= cbRead, ("Invalid number of bytes to read\n"));
+
+ LogFlow(("Reading %u bytes into %u segments\n", cbTaskRead, cSegments));
+
+#ifdef RT_STRICT
+ for (unsigned i = 0; i < cSegments; i++)
+ AssertMsg(aSeg[i].pvSeg && !(aSeg[i].cbSeg % 512),
+ ("Segment %u is invalid\n", i));
+#endif
+
+ Assert(cbTaskRead == (uint32_t)cbTaskRead);
+ PVDIOTASK pIoTask = vdIoTaskUserAlloc(pIoStorage, NULL, NULL, pIoCtx, (uint32_t)cbTaskRead);
+
+ if (!pIoTask)
+ return VERR_NO_MEMORY;
+
+ ASMAtomicIncU32(&pIoCtx->cDataTransfersPending);
+
+ void *pvTask;
+ Log(("Spawning pIoTask=%p pIoCtx=%p\n", pIoTask, pIoCtx));
+ rc = pVDIo->pInterfaceIo->pfnReadAsync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, uOffset,
+ aSeg, cSegments, cbTaskRead, pIoTask,
+ &pvTask);
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsg(cbTaskRead <= pIoCtx->Req.Io.cbTransferLeft, ("Impossible!\n"));
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbTaskRead);
+ ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
+ vdIoTaskFree(pDisk, pIoTask);
+ }
+ else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
+ vdIoTaskFree(pDisk, pIoTask);
+ break;
+ }
+
+ uOffset += cbTaskRead;
+ cbRead -= cbTaskRead;
+ }
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdIOIntWriteUser(void *pvUser, PVDIOSTORAGE pIoStorage, uint64_t uOffset,
+ PVDIOCTX pIoCtx, size_t cbWrite, PFNVDXFERCOMPLETED pfnComplete,
+ void *pvCompleteUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+
+ LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pIoCtx=%#p cbWrite=%u\n",
+ pvUser, pIoStorage, uOffset, pIoCtx, cbWrite));
+
+ /** @todo Enable check for sync I/O later. */
+ if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC))
+ VD_IS_LOCKED(pDisk);
+
+ Assert(cbWrite > 0);
+
+ if ( (pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)
+ || !pVDIo->pInterfaceIo->pfnWriteAsync)
+ {
+ RTSGSEG Seg;
+ unsigned cSegments = 1;
+ size_t cbTaskWrite = 0;
+
+ /* Synchronous I/O contexts only have one buffer segment. */
+ AssertMsgReturn(pIoCtx->Req.Io.SgBuf.cSegs == 1,
+ ("Invalid number of buffer segments for synchronous I/O context"),
+ VERR_INVALID_PARAMETER);
+
+ cbTaskWrite = RTSgBufSegArrayCreate(&pIoCtx->Req.Io.SgBuf, &Seg, &cSegments, cbWrite);
+ Assert(cbWrite == cbTaskWrite);
+ Assert(cSegments == 1);
+ rc = pVDIo->pInterfaceIo->pfnWriteSync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, uOffset,
+ Seg.pvSeg, cbWrite, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pIoCtx->Req.Io.cbTransferLeft >= cbWrite);
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbWrite);
+ }
+ }
+ else
+ {
+ /* Build the S/G array and spawn a new I/O task */
+ while (cbWrite)
+ {
+ RTSGSEG aSeg[VD_IO_TASK_SEGMENTS_MAX];
+ unsigned cSegments = VD_IO_TASK_SEGMENTS_MAX;
+ size_t cbTaskWrite = 0;
+
+ cbTaskWrite = RTSgBufSegArrayCreate(&pIoCtx->Req.Io.SgBuf, aSeg, &cSegments, cbWrite);
+
+ Assert(cSegments > 0);
+ Assert(cbTaskWrite > 0);
+ AssertMsg(cbTaskWrite <= cbWrite, ("Invalid number of bytes to write\n"));
+
+ LogFlow(("Writing %u bytes from %u segments\n", cbTaskWrite, cSegments));
+
+#ifdef DEBUG
+ for (unsigned i = 0; i < cSegments; i++)
+ AssertMsg(aSeg[i].pvSeg && !(aSeg[i].cbSeg % 512),
+ ("Segment %u is invalid\n", i));
+#endif
+
+ Assert(cbTaskWrite == (uint32_t)cbTaskWrite);
+ PVDIOTASK pIoTask = vdIoTaskUserAlloc(pIoStorage, pfnComplete, pvCompleteUser, pIoCtx, (uint32_t)cbTaskWrite);
+
+ if (!pIoTask)
+ return VERR_NO_MEMORY;
+
+ ASMAtomicIncU32(&pIoCtx->cDataTransfersPending);
+
+ void *pvTask;
+ Log(("Spawning pIoTask=%p pIoCtx=%p\n", pIoTask, pIoCtx));
+ rc = pVDIo->pInterfaceIo->pfnWriteAsync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage,
+ uOffset, aSeg, cSegments,
+ cbTaskWrite, pIoTask, &pvTask);
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsg(cbTaskWrite <= pIoCtx->Req.Io.cbTransferLeft, ("Impossible!\n"));
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbTaskWrite);
+ ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
+ vdIoTaskFree(pDisk, pIoTask);
+ }
+ else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
+ vdIoTaskFree(pDisk, pIoTask);
+ break;
+ }
+
+ uOffset += cbTaskWrite;
+ cbWrite -= cbTaskWrite;
+ }
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdIOIntReadMeta(void *pvUser, PVDIOSTORAGE pIoStorage, uint64_t uOffset,
+ void *pvBuf, size_t cbRead, PVDIOCTX pIoCtx,
+ PPVDMETAXFER ppMetaXfer, PFNVDXFERCOMPLETED pfnComplete,
+ void *pvCompleteUser)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+ int rc = VINF_SUCCESS;
+ RTSGSEG Seg;
+ PVDIOTASK pIoTask;
+ PVDMETAXFER pMetaXfer = NULL;
+ void *pvTask = NULL;
+
+ LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pvBuf=%#p cbRead=%u\n",
+ pvUser, pIoStorage, uOffset, pvBuf, cbRead));
+
+ AssertMsgReturn( pIoCtx
+ || (!ppMetaXfer && !pfnComplete && !pvCompleteUser),
+ ("A synchronous metadata read is requested but the parameters are wrong\n"),
+ VERR_INVALID_POINTER);
+
+ /** @todo Enable check for sync I/O later. */
+ if ( pIoCtx
+ && !(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC))
+ VD_IS_LOCKED(pDisk);
+
+ if ( !pIoCtx
+ || pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC
+ || !pVDIo->pInterfaceIo->pfnReadAsync)
+ {
+ /* Handle synchronous metadata I/O. */
+ /** @todo Integrate with metadata transfers below. */
+ rc = pVDIo->pInterfaceIo->pfnReadSync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, uOffset,
+ pvBuf, cbRead, NULL);
+ if (ppMetaXfer)
+ *ppMetaXfer = NULL;
+ }
+ else
+ {
+ pMetaXfer = (PVDMETAXFER)RTAvlrFileOffsetGet(pIoStorage->pTreeMetaXfers, uOffset);
+ if (!pMetaXfer)
+ {
+#ifdef RT_STRICT
+ pMetaXfer = (PVDMETAXFER)RTAvlrFileOffsetGetBestFit(pIoStorage->pTreeMetaXfers, uOffset, false /* fAbove */);
+ AssertMsg(!pMetaXfer || (pMetaXfer->Core.Key + (RTFOFF)pMetaXfer->cbMeta <= (RTFOFF)uOffset),
+ ("Overlapping meta transfers!\n"));
+#endif
+
+ /* Allocate a new meta transfer. */
+ pMetaXfer = vdMetaXferAlloc(pIoStorage, uOffset, cbRead);
+ if (!pMetaXfer)
+ return VERR_NO_MEMORY;
+
+ pIoTask = vdIoTaskMetaAlloc(pIoStorage, pfnComplete, pvCompleteUser, pMetaXfer);
+ if (!pIoTask)
+ {
+ RTMemFree(pMetaXfer);
+ return VERR_NO_MEMORY;
+ }
+
+ Seg.cbSeg = cbRead;
+ Seg.pvSeg = pMetaXfer->abData;
+
+ VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_READ);
+ rc = pVDIo->pInterfaceIo->pfnReadAsync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage,
+ uOffset, &Seg, 1,
+ cbRead, pIoTask, &pvTask);
+
+ if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ bool fInserted = RTAvlrFileOffsetInsert(pIoStorage->pTreeMetaXfers, &pMetaXfer->Core);
+ Assert(fInserted); NOREF(fInserted);
+ }
+ else
+ RTMemFree(pMetaXfer);
+
+ if (RT_SUCCESS(rc))
+ {
+ VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE);
+ vdIoTaskFree(pDisk, pIoTask);
+ }
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS && !pfnComplete)
+ rc = VERR_VD_NOT_ENOUGH_METADATA;
+ }
+
+ Assert(RT_VALID_PTR(pMetaXfer) || RT_FAILURE(rc));
+
+ if (RT_SUCCESS(rc) || rc == VERR_VD_NOT_ENOUGH_METADATA || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ /* If it is pending add the request to the list. */
+ if (VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_READ)
+ {
+ PVDIOCTXDEFERRED pDeferred = (PVDIOCTXDEFERRED)RTMemAllocZ(sizeof(VDIOCTXDEFERRED));
+ AssertPtr(pDeferred);
+
+ RTListInit(&pDeferred->NodeDeferred);
+ pDeferred->pIoCtx = pIoCtx;
+
+ ASMAtomicIncU32(&pIoCtx->cMetaTransfersPending);
+ RTListAppend(&pMetaXfer->ListIoCtxWaiting, &pDeferred->NodeDeferred);
+ rc = VERR_VD_NOT_ENOUGH_METADATA;
+ }
+ else
+ {
+ /* Transfer the data. */
+ pMetaXfer->cRefs++;
+ Assert(pMetaXfer->cbMeta >= cbRead);
+ Assert(pMetaXfer->Core.Key == (RTFOFF)uOffset);
+ if (pMetaXfer->pbDataShw)
+ memcpy(pvBuf, pMetaXfer->pbDataShw, cbRead);
+ else
+ memcpy(pvBuf, pMetaXfer->abData, cbRead);
+ *ppMetaXfer = pMetaXfer;
+ }
+ }
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdIOIntWriteMeta(void *pvUser, PVDIOSTORAGE pIoStorage, uint64_t uOffset,
+ const void *pvBuf, size_t cbWrite, PVDIOCTX pIoCtx,
+ PFNVDXFERCOMPLETED pfnComplete, void *pvCompleteUser)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+ int rc = VINF_SUCCESS;
+ RTSGSEG Seg;
+ PVDIOTASK pIoTask;
+ PVDMETAXFER pMetaXfer = NULL;
+ bool fInTree = false;
+ void *pvTask = NULL;
+
+ LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pvBuf=%#p cbWrite=%u\n",
+ pvUser, pIoStorage, uOffset, pvBuf, cbWrite));
+
+ AssertMsgReturn( pIoCtx
+ || (!pfnComplete && !pvCompleteUser),
+ ("A synchronous metadata write is requested but the parameters are wrong\n"),
+ VERR_INVALID_POINTER);
+
+ /** @todo Enable check for sync I/O later. */
+ if ( pIoCtx
+ && !(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC))
+ VD_IS_LOCKED(pDisk);
+
+ if ( !pIoCtx
+ || pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC
+ || !pVDIo->pInterfaceIo->pfnWriteAsync)
+ {
+ /* Handle synchronous metadata I/O. */
+ /** @todo Integrate with metadata transfers below. */
+ rc = pVDIo->pInterfaceIo->pfnWriteSync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage, uOffset,
+ pvBuf, cbWrite, NULL);
+ }
+ else
+ {
+ pMetaXfer = (PVDMETAXFER)RTAvlrFileOffsetGet(pIoStorage->pTreeMetaXfers, uOffset);
+ if (!pMetaXfer)
+ {
+ /* Allocate a new meta transfer. */
+ pMetaXfer = vdMetaXferAlloc(pIoStorage, uOffset, cbWrite);
+ if (!pMetaXfer)
+ return VERR_NO_MEMORY;
+ }
+ else
+ {
+ Assert(pMetaXfer->cbMeta >= cbWrite);
+ Assert(pMetaXfer->Core.Key == (RTFOFF)uOffset);
+ fInTree = true;
+ }
+
+ if (VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_NONE)
+ {
+ pIoTask = vdIoTaskMetaAlloc(pIoStorage, pfnComplete, pvCompleteUser, pMetaXfer);
+ if (!pIoTask)
+ {
+ RTMemFree(pMetaXfer);
+ return VERR_NO_MEMORY;
+ }
+
+ memcpy(pMetaXfer->abData, pvBuf, cbWrite);
+ Seg.cbSeg = cbWrite;
+ Seg.pvSeg = pMetaXfer->abData;
+
+ ASMAtomicIncU32(&pIoCtx->cMetaTransfersPending);
+
+ VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_WRITE);
+ rc = pVDIo->pInterfaceIo->pfnWriteAsync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage,
+ uOffset, &Seg, 1, cbWrite, pIoTask,
+ &pvTask);
+ if (RT_SUCCESS(rc))
+ {
+ VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE);
+ ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending);
+ vdIoTaskFree(pDisk, pIoTask);
+ if (fInTree && !pMetaXfer->cRefs)
+ {
+ LogFlow(("Removing meta xfer=%#p\n", pMetaXfer));
+ bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key) != NULL;
+ AssertMsg(fRemoved, ("Metadata transfer wasn't removed\n")); NOREF(fRemoved);
+ RTMemFree(pMetaXfer);
+ pMetaXfer = NULL;
+ }
+ }
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ PVDIOCTXDEFERRED pDeferred = (PVDIOCTXDEFERRED)RTMemAllocZ(sizeof(VDIOCTXDEFERRED));
+ AssertPtr(pDeferred);
+
+ RTListInit(&pDeferred->NodeDeferred);
+ pDeferred->pIoCtx = pIoCtx;
+
+ if (!fInTree)
+ {
+ bool fInserted = RTAvlrFileOffsetInsert(pIoStorage->pTreeMetaXfers, &pMetaXfer->Core);
+ Assert(fInserted); NOREF(fInserted);
+ }
+
+ RTListAppend(&pMetaXfer->ListIoCtxWaiting, &pDeferred->NodeDeferred);
+ }
+ else
+ {
+ RTMemFree(pMetaXfer);
+ pMetaXfer = NULL;
+ }
+ }
+ else
+ {
+ /* I/O is in progress, update shadow buffer and add to waiting list. */
+ Assert(VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_WRITE);
+ if (!pMetaXfer->pbDataShw)
+ {
+ /* Allocate shadow buffer and set initial state. */
+ LogFlowFunc(("pMetaXfer=%#p Creating shadow buffer\n", pMetaXfer));
+ pMetaXfer->pbDataShw = (uint8_t *)RTMemAlloc(pMetaXfer->cbMeta);
+ if (RT_LIKELY(pMetaXfer->pbDataShw))
+ memcpy(pMetaXfer->pbDataShw, pMetaXfer->abData, pMetaXfer->cbMeta);
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Update with written data and append to waiting list. */
+ PVDIOCTXDEFERRED pDeferred = (PVDIOCTXDEFERRED)RTMemAllocZ(sizeof(VDIOCTXDEFERRED));
+ if (pDeferred)
+ {
+ LogFlowFunc(("pMetaXfer=%#p Updating shadow buffer\n", pMetaXfer));
+
+ RTListInit(&pDeferred->NodeDeferred);
+ pDeferred->pIoCtx = pIoCtx;
+ ASMAtomicIncU32(&pIoCtx->cMetaTransfersPending);
+ memcpy(pMetaXfer->pbDataShw, pvBuf, cbWrite);
+ RTListAppend(&pMetaXfer->ListIoCtxShwWrites, &pDeferred->NodeDeferred);
+ }
+ else
+ {
+ /*
+ * Free shadow buffer if there is no one depending on it, i.e.
+ * we just allocated it.
+ */
+ if (RTListIsEmpty(&pMetaXfer->ListIoCtxShwWrites))
+ {
+ RTMemFree(pMetaXfer->pbDataShw);
+ pMetaXfer->pbDataShw = NULL;
+ }
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(void) vdIOIntMetaXferRelease(void *pvUser, PVDMETAXFER pMetaXfer)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+ PVDIOSTORAGE pIoStorage;
+
+ /*
+ * It is possible that we get called with a NULL metadata xfer handle
+ * for synchronous I/O. Just exit.
+ */
+ if (!pMetaXfer)
+ return;
+
+ pIoStorage = pMetaXfer->pIoStorage;
+
+ VD_IS_LOCKED(pDisk);
+
+ Assert( VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_NONE
+ || VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_WRITE);
+ Assert(pMetaXfer->cRefs > 0);
+
+ pMetaXfer->cRefs--;
+ if ( !pMetaXfer->cRefs
+ && RTListIsEmpty(&pMetaXfer->ListIoCtxWaiting)
+ && VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_NONE)
+ {
+ /* Free the meta data entry. */
+ LogFlow(("Removing meta xfer=%#p\n", pMetaXfer));
+ bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key) != NULL;
+ AssertMsg(fRemoved, ("Metadata transfer wasn't removed\n")); NOREF(fRemoved);
+
+ RTMemFree(pMetaXfer);
+ }
+}
+
+static DECLCALLBACK(int) vdIOIntFlush(void *pvUser, PVDIOSTORAGE pIoStorage, PVDIOCTX pIoCtx,
+ PFNVDXFERCOMPLETED pfnComplete, void *pvCompleteUser)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+ int rc = VINF_SUCCESS;
+ PVDIOTASK pIoTask;
+ PVDMETAXFER pMetaXfer = NULL;
+ void *pvTask = NULL;
+
+ LogFlowFunc(("pvUser=%#p pIoStorage=%#p pIoCtx=%#p\n",
+ pvUser, pIoStorage, pIoCtx));
+
+ AssertMsgReturn( pIoCtx
+ || (!pfnComplete && !pvCompleteUser),
+ ("A synchronous metadata write is requested but the parameters are wrong\n"),
+ VERR_INVALID_POINTER);
+
+ /** @todo Enable check for sync I/O later. */
+ if ( pIoCtx
+ && !(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC))
+ VD_IS_LOCKED(pDisk);
+
+ if (pVDIo->fIgnoreFlush)
+ return VINF_SUCCESS;
+
+ if ( !pIoCtx
+ || pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC
+ || !pVDIo->pInterfaceIo->pfnFlushAsync)
+ {
+ /* Handle synchronous flushes. */
+ /** @todo Integrate with metadata transfers below. */
+ rc = pVDIo->pInterfaceIo->pfnFlushSync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage);
+ }
+ else
+ {
+ /* Allocate a new meta transfer. */
+ pMetaXfer = vdMetaXferAlloc(pIoStorage, 0, 0);
+ if (!pMetaXfer)
+ return VERR_NO_MEMORY;
+
+ pIoTask = vdIoTaskMetaAlloc(pIoStorage, pfnComplete, pvUser, pMetaXfer);
+ if (!pIoTask)
+ {
+ RTMemFree(pMetaXfer);
+ return VERR_NO_MEMORY;
+ }
+
+ ASMAtomicIncU32(&pIoCtx->cMetaTransfersPending);
+
+ PVDIOCTXDEFERRED pDeferred = (PVDIOCTXDEFERRED)RTMemAllocZ(sizeof(VDIOCTXDEFERRED));
+ AssertPtr(pDeferred);
+
+ RTListInit(&pDeferred->NodeDeferred);
+ pDeferred->pIoCtx = pIoCtx;
+
+ RTListAppend(&pMetaXfer->ListIoCtxWaiting, &pDeferred->NodeDeferred);
+ VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_FLUSH);
+ rc = pVDIo->pInterfaceIo->pfnFlushAsync(pVDIo->pInterfaceIo->Core.pvUser,
+ pIoStorage->pStorage,
+ pIoTask, &pvTask);
+ if (RT_SUCCESS(rc))
+ {
+ VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE);
+ ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending);
+ vdIoTaskFree(pDisk, pIoTask);
+ RTMemFree(pDeferred);
+ RTMemFree(pMetaXfer);
+ }
+ else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ RTMemFree(pMetaXfer);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(size_t) vdIOIntIoCtxCopyTo(void *pvUser, PVDIOCTX pIoCtx,
+ const void *pvBuf, size_t cbBuf)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+ size_t cbCopied = 0;
+
+ /** @todo Enable check for sync I/O later. */
+ if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC))
+ VD_IS_LOCKED(pDisk);
+
+ cbCopied = vdIoCtxCopyTo(pIoCtx, (uint8_t *)pvBuf, cbBuf);
+ Assert(cbCopied == cbBuf);
+
+ /// @todo Assert(pIoCtx->Req.Io.cbTransferLeft >= cbCopied); - triggers with vdCopyHelper/dmgRead.
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbCopied);
+
+ return cbCopied;
+}
+
+static DECLCALLBACK(size_t) vdIOIntIoCtxCopyFrom(void *pvUser, PVDIOCTX pIoCtx,
+ void *pvBuf, size_t cbBuf)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+ size_t cbCopied = 0;
+
+ /** @todo Enable check for sync I/O later. */
+ if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC))
+ VD_IS_LOCKED(pDisk);
+
+ cbCopied = vdIoCtxCopyFrom(pIoCtx, (uint8_t *)pvBuf, cbBuf);
+ Assert(cbCopied == cbBuf);
+
+ /// @todo Assert(pIoCtx->Req.Io.cbTransferLeft > cbCopied); - triggers with vdCopyHelper/dmgRead.
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbCopied);
+
+ return cbCopied;
+}
+
+static DECLCALLBACK(size_t) vdIOIntIoCtxSet(void *pvUser, PVDIOCTX pIoCtx, int ch, size_t cb)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+ size_t cbSet = 0;
+
+ /** @todo Enable check for sync I/O later. */
+ if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC))
+ VD_IS_LOCKED(pDisk);
+
+ cbSet = vdIoCtxSet(pIoCtx, ch, cb);
+ Assert(cbSet == cb);
+
+ /// @todo Assert(pIoCtx->Req.Io.cbTransferLeft >= cbSet); - triggers with vdCopyHelper/dmgRead.
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbSet);
+
+ return cbSet;
+}
+
+static DECLCALLBACK(size_t) vdIOIntIoCtxSegArrayCreate(void *pvUser, PVDIOCTX pIoCtx,
+ PRTSGSEG paSeg, unsigned *pcSeg,
+ size_t cbData)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+ size_t cbCreated = 0;
+
+ /** @todo It is possible that this gets called from a filter plugin
+ * outside of the disk lock. Refine assertion or remove completely. */
+#if 0
+ /** @todo Enable check for sync I/O later. */
+ if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC))
+ VD_IS_LOCKED(pDisk);
+#else
+ NOREF(pDisk);
+#endif
+
+ cbCreated = RTSgBufSegArrayCreate(&pIoCtx->Req.Io.SgBuf, paSeg, pcSeg, cbData);
+ Assert(!paSeg || cbData == cbCreated);
+
+ return cbCreated;
+}
+
+static DECLCALLBACK(void) vdIOIntIoCtxCompleted(void *pvUser, PVDIOCTX pIoCtx, int rcReq,
+ size_t cbCompleted)
+{
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+
+ LogFlowFunc(("pvUser=%#p pIoCtx=%#p rcReq=%Rrc cbCompleted=%zu\n",
+ pvUser, pIoCtx, rcReq, cbCompleted));
+
+ /*
+ * Grab the disk critical section to avoid races with other threads which
+ * might still modify the I/O context.
+ * Example is that iSCSI is doing an asynchronous write but calls us already
+ * while the other thread is still hanging in vdWriteHelperAsync and couldn't update
+ * the blocked state yet.
+ * It can overwrite the state to true before we call vdIoCtxContinue and the
+ * the request would hang indefinite.
+ */
+ ASMAtomicCmpXchgS32(&pIoCtx->rcReq, rcReq, VINF_SUCCESS);
+ Assert(pIoCtx->Req.Io.cbTransferLeft >= cbCompleted);
+ ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbCompleted);
+
+ /* Set next transfer function if the current one finished.
+ * @todo: Find a better way to prevent vdIoCtxContinue from calling the current helper again. */
+ if (!pIoCtx->Req.Io.cbTransferLeft)
+ {
+ pIoCtx->pfnIoCtxTransfer = pIoCtx->pfnIoCtxTransferNext;
+ pIoCtx->pfnIoCtxTransferNext = NULL;
+ }
+
+ vdIoCtxAddToWaitingList(&pDisk->pIoCtxHaltedHead, pIoCtx);
+ if (ASMAtomicCmpXchgBool(&pDisk->fLocked, true, false))
+ {
+ /* Immediately drop the lock again, it will take care of processing the list. */
+ vdDiskUnlock(pDisk, NULL);
+ }
+}
+
+static DECLCALLBACK(bool) vdIOIntIoCtxIsSynchronous(void *pvUser, PVDIOCTX pIoCtx)
+{
+ NOREF(pvUser);
+ return !!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC);
+}
+
+static DECLCALLBACK(bool) vdIOIntIoCtxIsZero(void *pvUser, PVDIOCTX pIoCtx, size_t cbCheck,
+ bool fAdvance)
+{
+ NOREF(pvUser);
+
+ bool fIsZero = RTSgBufIsZero(&pIoCtx->Req.Io.SgBuf, cbCheck);
+ if (fIsZero && fAdvance)
+ RTSgBufAdvance(&pIoCtx->Req.Io.SgBuf, cbCheck);
+
+ return fIsZero;
+}
+
+static DECLCALLBACK(size_t) vdIOIntIoCtxGetDataUnitSize(void *pvUser, PVDIOCTX pIoCtx)
+{
+ RT_NOREF1(pIoCtx);
+ PVDIO pVDIo = (PVDIO)pvUser;
+ PVDISK pDisk = pVDIo->pDisk;
+ size_t cbSector = 0;
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, VD_LAST_IMAGE);
+ AssertPtrReturn(pImage, 0);
+
+ PCVDREGIONLIST pRegionList = NULL;
+ int rc = pImage->Backend->pfnQueryRegions(pImage->pBackendData, &pRegionList);
+ if (RT_SUCCESS(rc))
+ {
+ cbSector = pRegionList->aRegions[0].cbBlock;
+
+ AssertPtr(pImage->Backend->pfnRegionListRelease);
+ pImage->Backend->pfnRegionListRelease(pImage->pBackendData, pRegionList);
+ }
+
+ return cbSector;
+}
+
+/**
+ * VD I/O interface callback for opening a file (limited version for VDGetFormat).
+ */
+static DECLCALLBACK(int) vdIOIntOpenLimited(void *pvUser, const char *pszLocation,
+ uint32_t fOpen, PPVDIOSTORAGE ppIoStorage)
+{
+ int rc = VINF_SUCCESS;
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+ PVDIOSTORAGE pIoStorage = (PVDIOSTORAGE)RTMemAllocZ(sizeof(VDIOSTORAGE));
+
+ if (!pIoStorage)
+ return VERR_NO_MEMORY;
+
+ rc = pInterfaceIo->pfnOpen(NULL, pszLocation, fOpen, NULL, &pIoStorage->pStorage);
+ if (RT_SUCCESS(rc))
+ *ppIoStorage = pIoStorage;
+ else
+ RTMemFree(pIoStorage);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdIOIntCloseLimited(void *pvUser, PVDIOSTORAGE pIoStorage)
+{
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+ int rc = pInterfaceIo->pfnClose(NULL, pIoStorage->pStorage);
+
+ RTMemFree(pIoStorage);
+ return rc;
+}
+
+static DECLCALLBACK(int) vdIOIntDeleteLimited(void *pvUser, const char *pcszFilename)
+{
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+ return pInterfaceIo->pfnDelete(NULL, pcszFilename);
+}
+
+static DECLCALLBACK(int) vdIOIntMoveLimited(void *pvUser, const char *pcszSrc,
+ const char *pcszDst, unsigned fMove)
+{
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+ return pInterfaceIo->pfnMove(NULL, pcszSrc, pcszDst, fMove);
+}
+
+static DECLCALLBACK(int) vdIOIntGetFreeSpaceLimited(void *pvUser, const char *pcszFilename,
+ int64_t *pcbFreeSpace)
+{
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+ return pInterfaceIo->pfnGetFreeSpace(NULL, pcszFilename, pcbFreeSpace);
+}
+
+static DECLCALLBACK(int) vdIOIntGetModificationTimeLimited(void *pvUser,
+ const char *pcszFilename,
+ PRTTIMESPEC pModificationTime)
+{
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+ return pInterfaceIo->pfnGetModificationTime(NULL, pcszFilename, pModificationTime);
+}
+
+static DECLCALLBACK(int) vdIOIntGetSizeLimited(void *pvUser, PVDIOSTORAGE pIoStorage,
+ uint64_t *pcbSize)
+{
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+ return pInterfaceIo->pfnGetSize(NULL, pIoStorage->pStorage, pcbSize);
+}
+
+static DECLCALLBACK(int) vdIOIntSetSizeLimited(void *pvUser, PVDIOSTORAGE pIoStorage,
+ uint64_t cbSize)
+{
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+ return pInterfaceIo->pfnSetSize(NULL, pIoStorage->pStorage, cbSize);
+}
+
+static DECLCALLBACK(int) vdIOIntWriteUserLimited(void *pvUser, PVDIOSTORAGE pStorage,
+ uint64_t uOffset, PVDIOCTX pIoCtx,
+ size_t cbWrite,
+ PFNVDXFERCOMPLETED pfnComplete,
+ void *pvCompleteUser)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(uOffset);
+ NOREF(pIoCtx);
+ NOREF(cbWrite);
+ NOREF(pfnComplete);
+ NOREF(pvCompleteUser);
+ AssertMsgFailedReturn(("This needs to be implemented when called\n"), VERR_NOT_IMPLEMENTED);
+}
+
+static DECLCALLBACK(int) vdIOIntReadUserLimited(void *pvUser, PVDIOSTORAGE pStorage,
+ uint64_t uOffset, PVDIOCTX pIoCtx,
+ size_t cbRead)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(uOffset);
+ NOREF(pIoCtx);
+ NOREF(cbRead);
+ AssertMsgFailedReturn(("This needs to be implemented when called\n"), VERR_NOT_IMPLEMENTED);
+}
+
+static DECLCALLBACK(int) vdIOIntWriteMetaLimited(void *pvUser, PVDIOSTORAGE pStorage,
+ uint64_t uOffset, const void *pvBuffer,
+ size_t cbBuffer, PVDIOCTX pIoCtx,
+ PFNVDXFERCOMPLETED pfnComplete,
+ void *pvCompleteUser)
+{
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+
+ AssertMsgReturn(!pIoCtx && !pfnComplete && !pvCompleteUser,
+ ("Async I/O not implemented for the limited interface"),
+ VERR_NOT_SUPPORTED);
+
+ return pInterfaceIo->pfnWriteSync(NULL, pStorage->pStorage, uOffset, pvBuffer, cbBuffer, NULL);
+}
+
+static DECLCALLBACK(int) vdIOIntReadMetaLimited(void *pvUser, PVDIOSTORAGE pStorage,
+ uint64_t uOffset, void *pvBuffer,
+ size_t cbBuffer, PVDIOCTX pIoCtx,
+ PPVDMETAXFER ppMetaXfer,
+ PFNVDXFERCOMPLETED pfnComplete,
+ void *pvCompleteUser)
+{
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+
+ AssertMsgReturn(!pIoCtx && !ppMetaXfer && !pfnComplete && !pvCompleteUser,
+ ("Async I/O not implemented for the limited interface"),
+ VERR_NOT_SUPPORTED);
+
+ return pInterfaceIo->pfnReadSync(NULL, pStorage->pStorage, uOffset, pvBuffer, cbBuffer, NULL);
+}
+
+#if 0 /* unsed */
+static int vdIOIntMetaXferReleaseLimited(void *pvUser, PVDMETAXFER pMetaXfer)
+{
+ /* This is a NOP in this case. */
+ NOREF(pvUser);
+ NOREF(pMetaXfer);
+ return VINF_SUCCESS;
+}
+#endif
+
+static DECLCALLBACK(int) vdIOIntFlushLimited(void *pvUser, PVDIOSTORAGE pStorage,
+ PVDIOCTX pIoCtx,
+ PFNVDXFERCOMPLETED pfnComplete,
+ void *pvCompleteUser)
+{
+ PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser;
+
+ AssertMsgReturn(!pIoCtx && !pfnComplete && !pvCompleteUser,
+ ("Async I/O not implemented for the limited interface"),
+ VERR_NOT_SUPPORTED);
+
+ return pInterfaceIo->pfnFlushSync(NULL, pStorage->pStorage);
+}
+
+/**
+ * internal: send output to the log (unconditionally).
+ */
+static DECLCALLBACK(int) vdLogMessage(void *pvUser, const char *pszFormat, va_list args)
+{
+ NOREF(pvUser);
+ RTLogPrintfV(pszFormat, args);
+ return VINF_SUCCESS;
+}
+
+DECLINLINE(int) vdMessageWrapper(PVDISK pDisk, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = pDisk->pInterfaceError->pfnMessage(pDisk->pInterfaceError->Core.pvUser,
+ pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * internal: adjust PCHS geometry
+ */
+static void vdFixupPCHSGeometry(PVDGEOMETRY pPCHS, uint64_t cbSize)
+{
+ /* Fix broken PCHS geometry. Can happen for two reasons: either the backend
+ * mixes up PCHS and LCHS, or the application used to create the source
+ * image has put garbage in it. Additionally, if the PCHS geometry covers
+ * more than the image size, set it back to the default. */
+ if ( pPCHS->cHeads > 16
+ || pPCHS->cSectors > 63
+ || pPCHS->cCylinders == 0
+ || (uint64_t)pPCHS->cHeads * pPCHS->cSectors * pPCHS->cCylinders * 512 > cbSize)
+ {
+ Assert(!(RT_MIN(cbSize / 512 / 16 / 63, 16383) - (uint32_t)RT_MIN(cbSize / 512 / 16 / 63, 16383)));
+ pPCHS->cCylinders = (uint32_t)RT_MIN(cbSize / 512 / 16 / 63, 16383);
+ pPCHS->cHeads = 16;
+ pPCHS->cSectors = 63;
+ }
+}
+
+/**
+ * internal: adjust LCHS geometry
+ */
+static void vdFixupLCHSGeometry(PVDGEOMETRY pLCHS, uint64_t cbSize)
+{
+ /* Fix broken LCHS geometry. Can happen for two reasons: either the backend
+ * mixes up PCHS and LCHS, or the application used to create the source
+ * image has put garbage in it. The fix in this case is to clear the LCHS
+ * geometry to trigger autodetection when it is used next. If the geometry
+ * already says "please autodetect" (cylinders=0) keep it. */
+ if ( ( pLCHS->cHeads > 255
+ || pLCHS->cHeads == 0
+ || pLCHS->cSectors > 63
+ || pLCHS->cSectors == 0)
+ && pLCHS->cCylinders != 0)
+ {
+ pLCHS->cCylinders = 0;
+ pLCHS->cHeads = 0;
+ pLCHS->cSectors = 0;
+ }
+ /* Always recompute the number of cylinders stored in the LCHS
+ * geometry if it isn't set to "autotedetect" at the moment.
+ * This is very useful if the destination image size is
+ * larger or smaller than the source image size. Do not modify
+ * the number of heads and sectors. Windows guests hate it. */
+ if ( pLCHS->cCylinders != 0
+ && pLCHS->cHeads != 0 /* paranoia */
+ && pLCHS->cSectors != 0 /* paranoia */)
+ {
+ Assert(!(RT_MIN(cbSize / 512 / pLCHS->cHeads / pLCHS->cSectors, 1024) - (uint32_t)RT_MIN(cbSize / 512 / pLCHS->cHeads / pLCHS->cSectors, 1024)));
+ pLCHS->cCylinders = (uint32_t)RT_MIN(cbSize / 512 / pLCHS->cHeads / pLCHS->cSectors, 1024);
+ }
+}
+
+/**
+ * Sets the I/O callbacks of the given interface to the fallback methods
+ *
+ * @returns nothing.
+ * @param pIfIo The I/O interface to setup.
+ */
+static void vdIfIoFallbackCallbacksSetup(PVDINTERFACEIO pIfIo)
+{
+ pIfIo->pfnOpen = vdIOOpenFallback;
+ pIfIo->pfnClose = vdIOCloseFallback;
+ pIfIo->pfnDelete = vdIODeleteFallback;
+ pIfIo->pfnMove = vdIOMoveFallback;
+ pIfIo->pfnGetFreeSpace = vdIOGetFreeSpaceFallback;
+ pIfIo->pfnGetModificationTime = vdIOGetModificationTimeFallback;
+ pIfIo->pfnGetSize = vdIOGetSizeFallback;
+ pIfIo->pfnSetSize = vdIOSetSizeFallback;
+ pIfIo->pfnSetAllocationSize = vdIOSetAllocationSizeFallback;
+ pIfIo->pfnReadSync = vdIOReadSyncFallback;
+ pIfIo->pfnWriteSync = vdIOWriteSyncFallback;
+ pIfIo->pfnFlushSync = vdIOFlushSyncFallback;
+ pIfIo->pfnReadAsync = NULL;
+ pIfIo->pfnWriteAsync = NULL;
+ pIfIo->pfnFlushAsync = NULL;
+}
+
+/**
+ * Sets the internal I/O callbacks of the given interface.
+ *
+ * @returns nothing.
+ * @param pIfIoInt The internal I/O interface to setup.
+ */
+static void vdIfIoIntCallbacksSetup(PVDINTERFACEIOINT pIfIoInt)
+{
+ pIfIoInt->pfnOpen = vdIOIntOpen;
+ pIfIoInt->pfnClose = vdIOIntClose;
+ pIfIoInt->pfnDelete = vdIOIntDelete;
+ pIfIoInt->pfnMove = vdIOIntMove;
+ pIfIoInt->pfnGetFreeSpace = vdIOIntGetFreeSpace;
+ pIfIoInt->pfnGetModificationTime = vdIOIntGetModificationTime;
+ pIfIoInt->pfnGetSize = vdIOIntGetSize;
+ pIfIoInt->pfnSetSize = vdIOIntSetSize;
+ pIfIoInt->pfnSetAllocationSize = vdIOIntSetAllocationSize;
+ pIfIoInt->pfnReadUser = vdIOIntReadUser;
+ pIfIoInt->pfnWriteUser = vdIOIntWriteUser;
+ pIfIoInt->pfnReadMeta = vdIOIntReadMeta;
+ pIfIoInt->pfnWriteMeta = vdIOIntWriteMeta;
+ pIfIoInt->pfnMetaXferRelease = vdIOIntMetaXferRelease;
+ pIfIoInt->pfnFlush = vdIOIntFlush;
+ pIfIoInt->pfnIoCtxCopyFrom = vdIOIntIoCtxCopyFrom;
+ pIfIoInt->pfnIoCtxCopyTo = vdIOIntIoCtxCopyTo;
+ pIfIoInt->pfnIoCtxSet = vdIOIntIoCtxSet;
+ pIfIoInt->pfnIoCtxSegArrayCreate = vdIOIntIoCtxSegArrayCreate;
+ pIfIoInt->pfnIoCtxCompleted = vdIOIntIoCtxCompleted;
+ pIfIoInt->pfnIoCtxIsSynchronous = vdIOIntIoCtxIsSynchronous;
+ pIfIoInt->pfnIoCtxIsZero = vdIOIntIoCtxIsZero;
+ pIfIoInt->pfnIoCtxGetDataUnitSize = vdIOIntIoCtxGetDataUnitSize;
+}
+
+/**
+ * Internally used completion handler for synchronous I/O contexts.
+ */
+static DECLCALLBACK(void) vdIoCtxSyncComplete(void *pvUser1, void *pvUser2, int rcReq)
+{
+ RT_NOREF2(pvUser1, rcReq);
+ RTSEMEVENT hEvent = (RTSEMEVENT)pvUser2;
+
+ RTSemEventSignal(hEvent);
+}
+
+
+VBOXDDU_DECL(int) VDInit(void)
+{
+ int rc = vdPluginInit();
+ LogRel(("VD: VDInit finished with %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDShutdown(void)
+{
+ return vdPluginTerm();
+}
+
+
+VBOXDDU_DECL(int) VDPluginLoadFromFilename(const char *pszFilename)
+{
+ if (!vdPluginIsInitialized())
+ {
+ int rc = VDInit();
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return vdPluginLoadFromFilename(pszFilename);
+}
+
+/**
+ * Load all plugins from a given path.
+ *
+ * @returns VBox statuse code.
+ * @param pszPath The path to load plugins from.
+ */
+VBOXDDU_DECL(int) VDPluginLoadFromPath(const char *pszPath)
+{
+ if (!vdPluginIsInitialized())
+ {
+ int rc = VDInit();
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return vdPluginLoadFromPath(pszPath);
+}
+
+
+VBOXDDU_DECL(int) VDPluginUnloadFromFilename(const char *pszFilename)
+{
+ if (!vdPluginIsInitialized())
+ {
+ int rc = VDInit();
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return vdPluginUnloadFromFilename(pszFilename);
+}
+
+
+VBOXDDU_DECL(int) VDPluginUnloadFromPath(const char *pszPath)
+{
+ if (!vdPluginIsInitialized())
+ {
+ int rc = VDInit();
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return vdPluginUnloadFromPath(pszPath);
+}
+
+
+VBOXDDU_DECL(int) VDBackendInfo(unsigned cEntriesAlloc, PVDBACKENDINFO pEntries,
+ unsigned *pcEntriesUsed)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("cEntriesAlloc=%u pEntries=%#p pcEntriesUsed=%#p\n", cEntriesAlloc, pEntries, pcEntriesUsed));
+ /* Check arguments. */
+ AssertMsgReturn(cEntriesAlloc, ("cEntriesAlloc=%u\n", cEntriesAlloc), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pEntries, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcEntriesUsed, VERR_INVALID_POINTER);
+ if (!vdPluginIsInitialized())
+ VDInit();
+
+ uint32_t cBackends = vdGetImageBackendCount();
+ if (cEntriesAlloc < cBackends)
+ {
+ *pcEntriesUsed = cBackends;
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ for (unsigned i = 0; i < cBackends; i++)
+ {
+ PCVDIMAGEBACKEND pBackend;
+ rc = vdQueryImageBackend(i, &pBackend);
+ AssertRC(rc);
+
+ pEntries[i].pszBackend = pBackend->pszBackendName;
+ pEntries[i].uBackendCaps = pBackend->uBackendCaps;
+ pEntries[i].paFileExtensions = pBackend->paFileExtensions;
+ pEntries[i].paConfigInfo = pBackend->paConfigInfo;
+ pEntries[i].pfnComposeLocation = pBackend->pfnComposeLocation;
+ pEntries[i].pfnComposeName = pBackend->pfnComposeName;
+ }
+
+ LogFlowFunc(("returns %Rrc *pcEntriesUsed=%u\n", rc, cBackends));
+ *pcEntriesUsed = cBackends;
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDBackendInfoOne(const char *pszBackend, PVDBACKENDINFO pEntry)
+{
+ LogFlowFunc(("pszBackend=%#p pEntry=%#p\n", pszBackend, pEntry));
+ /* Check arguments. */
+ AssertPtrReturn(pszBackend, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEntry, VERR_INVALID_POINTER);
+ if (!vdPluginIsInitialized())
+ VDInit();
+
+ PCVDIMAGEBACKEND pBackend;
+ int rc = vdFindImageBackend(pszBackend, &pBackend);
+ if (RT_SUCCESS(rc))
+ {
+ pEntry->pszBackend = pBackend->pszBackendName;
+ pEntry->uBackendCaps = pBackend->uBackendCaps;
+ pEntry->paFileExtensions = pBackend->paFileExtensions;
+ pEntry->paConfigInfo = pBackend->paConfigInfo;
+ }
+
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDFilterInfo(unsigned cEntriesAlloc, PVDFILTERINFO pEntries,
+ unsigned *pcEntriesUsed)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("cEntriesAlloc=%u pEntries=%#p pcEntriesUsed=%#p\n", cEntriesAlloc, pEntries, pcEntriesUsed));
+ /* Check arguments. */
+ AssertMsgReturn(cEntriesAlloc,
+ ("cEntriesAlloc=%u\n", cEntriesAlloc),
+ VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pEntries, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcEntriesUsed, VERR_INVALID_POINTER);
+ if (!vdPluginIsInitialized())
+ VDInit();
+
+ uint32_t cBackends = vdGetFilterBackendCount();
+ if (cEntriesAlloc < cBackends)
+ {
+ *pcEntriesUsed = cBackends;
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ for (unsigned i = 0; i < cBackends; i++)
+ {
+ PCVDFILTERBACKEND pBackend;
+ rc = vdQueryFilterBackend(i, &pBackend);
+ pEntries[i].pszFilter = pBackend->pszBackendName;
+ pEntries[i].paConfigInfo = pBackend->paConfigInfo;
+ }
+
+ LogFlowFunc(("returns %Rrc *pcEntriesUsed=%u\n", rc, cBackends));
+ *pcEntriesUsed = cBackends;
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDFilterInfoOne(const char *pszFilter, PVDFILTERINFO pEntry)
+{
+ LogFlowFunc(("pszFilter=%#p pEntry=%#p\n", pszFilter, pEntry));
+ /* Check arguments. */
+ AssertPtrReturn(pszFilter, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEntry, VERR_INVALID_POINTER);
+ if (!vdPluginIsInitialized())
+ VDInit();
+
+ PCVDFILTERBACKEND pBackend;
+ int rc = vdFindFilterBackend(pszFilter, &pBackend);
+ if (RT_SUCCESS(rc))
+ {
+ pEntry->pszFilter = pBackend->pszBackendName;
+ pEntry->paConfigInfo = pBackend->paConfigInfo;
+ }
+
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDCreate(PVDINTERFACE pVDIfsDisk, VDTYPE enmType, PVDISK *ppDisk)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+
+ LogFlowFunc(("pVDIfsDisk=%#p\n", pVDIfsDisk));
+ /* Check arguments. */
+ AssertPtrReturn(ppDisk, VERR_INVALID_POINTER);
+
+ do
+ {
+ pDisk = (PVDISK)RTMemAllocZ(sizeof(VDISK));
+ if (pDisk)
+ {
+ pDisk->u32Signature = VDISK_SIGNATURE;
+ pDisk->enmType = enmType;
+ pDisk->cImages = 0;
+ pDisk->pBase = NULL;
+ pDisk->pLast = NULL;
+ pDisk->cbSize = 0;
+ pDisk->PCHSGeometry.cCylinders = 0;
+ pDisk->PCHSGeometry.cHeads = 0;
+ pDisk->PCHSGeometry.cSectors = 0;
+ pDisk->LCHSGeometry.cCylinders = 0;
+ pDisk->LCHSGeometry.cHeads = 0;
+ pDisk->LCHSGeometry.cSectors = 0;
+ pDisk->pVDIfsDisk = pVDIfsDisk;
+ pDisk->pInterfaceError = NULL;
+ pDisk->pInterfaceThreadSync = NULL;
+ pDisk->pIoCtxLockOwner = NULL;
+ pDisk->pIoCtxHead = NULL;
+ pDisk->fLocked = false;
+ pDisk->hMemCacheIoCtx = NIL_RTMEMCACHE;
+ pDisk->hMemCacheIoTask = NIL_RTMEMCACHE;
+ RTListInit(&pDisk->ListFilterChainWrite);
+ RTListInit(&pDisk->ListFilterChainRead);
+
+ /* Create the I/O ctx cache */
+ rc = RTMemCacheCreate(&pDisk->hMemCacheIoCtx, sizeof(VDIOCTX), 0, UINT32_MAX,
+ NULL, NULL, NULL, 0);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Create the I/O task cache */
+ rc = RTMemCacheCreate(&pDisk->hMemCacheIoTask, sizeof(VDIOTASK), 0, UINT32_MAX,
+ NULL, NULL, NULL, 0);
+ if (RT_FAILURE(rc))
+ break;
+
+ pDisk->pInterfaceError = VDIfErrorGet(pVDIfsDisk);
+ pDisk->pInterfaceThreadSync = VDIfThreadSyncGet(pVDIfsDisk);
+
+ *ppDisk = pDisk;
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ } while (0);
+
+ if ( RT_FAILURE(rc)
+ && pDisk)
+ {
+ if (pDisk->hMemCacheIoCtx != NIL_RTMEMCACHE)
+ RTMemCacheDestroy(pDisk->hMemCacheIoCtx);
+ if (pDisk->hMemCacheIoTask != NIL_RTMEMCACHE)
+ RTMemCacheDestroy(pDisk->hMemCacheIoTask);
+ }
+
+ LogFlowFunc(("returns %Rrc (pDisk=%#p)\n", rc, pDisk));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDDestroy(PVDISK pDisk)
+{
+ int rc = VINF_SUCCESS;
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreak(pDisk);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+ Assert(!pDisk->fLocked);
+
+ rc = VDCloseAll(pDisk);
+ int rc2 = VDFilterRemoveAll(pDisk);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ RTMemCacheDestroy(pDisk->hMemCacheIoCtx);
+ RTMemCacheDestroy(pDisk->hMemCacheIoTask);
+ RTMemFree(pDisk);
+ } while (0);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDGetFormat(PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ const char *pszFilename, VDTYPE enmDesiredType,
+ char **ppszFormat, VDTYPE *penmType)
+{
+ int rc = VERR_NOT_SUPPORTED;
+ VDINTERFACEIOINT VDIfIoInt;
+ VDINTERFACEIO VDIfIoFallback;
+ PVDINTERFACEIO pInterfaceIo;
+
+ LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename));
+ /* Check arguments. */
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppszFormat, VERR_INVALID_POINTER);
+ AssertPtrReturn(penmType, VERR_INVALID_POINTER);
+ AssertReturn(enmDesiredType >= VDTYPE_INVALID && enmDesiredType <= VDTYPE_FLOPPY, VERR_INVALID_PARAMETER);
+
+ if (!vdPluginIsInitialized())
+ VDInit();
+
+ pInterfaceIo = VDIfIoGet(pVDIfsImage);
+ if (!pInterfaceIo)
+ {
+ /*
+ * Caller doesn't provide an I/O interface, create our own using the
+ * native file API.
+ */
+ vdIfIoFallbackCallbacksSetup(&VDIfIoFallback);
+ pInterfaceIo = &VDIfIoFallback;
+ }
+
+ /* Set up the internal I/O interface. */
+ AssertReturn(!VDIfIoIntGet(pVDIfsImage), VERR_INVALID_PARAMETER);
+ VDIfIoInt.pfnOpen = vdIOIntOpenLimited;
+ VDIfIoInt.pfnClose = vdIOIntCloseLimited;
+ VDIfIoInt.pfnDelete = vdIOIntDeleteLimited;
+ VDIfIoInt.pfnMove = vdIOIntMoveLimited;
+ VDIfIoInt.pfnGetFreeSpace = vdIOIntGetFreeSpaceLimited;
+ VDIfIoInt.pfnGetModificationTime = vdIOIntGetModificationTimeLimited;
+ VDIfIoInt.pfnGetSize = vdIOIntGetSizeLimited;
+ VDIfIoInt.pfnSetSize = vdIOIntSetSizeLimited;
+ VDIfIoInt.pfnReadUser = vdIOIntReadUserLimited;
+ VDIfIoInt.pfnWriteUser = vdIOIntWriteUserLimited;
+ VDIfIoInt.pfnReadMeta = vdIOIntReadMetaLimited;
+ VDIfIoInt.pfnWriteMeta = vdIOIntWriteMetaLimited;
+ VDIfIoInt.pfnFlush = vdIOIntFlushLimited;
+ rc = VDInterfaceAdd(&VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT,
+ pInterfaceIo, sizeof(VDINTERFACEIOINT), &pVDIfsImage);
+ AssertRC(rc);
+
+ /** @todo r=bird: Would be better to do a scoring approach here, where the
+ * backend that scores the highest is choosen. That way we don't have to depend
+ * on registration order and filename suffixes to figure out what RAW should
+ * handle and not. Besides, the registration order won't cut it for plug-ins
+ * anyway, as they end up after the builtin ones.
+ */
+
+ /* Find the backend supporting this file format. */
+ for (unsigned i = 0; i < vdGetImageBackendCount(); i++)
+ {
+ PCVDIMAGEBACKEND pBackend;
+ rc = vdQueryImageBackend(i, &pBackend);
+ AssertRC(rc);
+
+ if (pBackend->pfnProbe)
+ {
+ rc = pBackend->pfnProbe(pszFilename, pVDIfsDisk, pVDIfsImage, enmDesiredType, penmType);
+ if ( RT_SUCCESS(rc)
+ /* The correct backend has been found, but there is a small
+ * incompatibility so that the file cannot be used. Stop here
+ * and signal success - the actual open will of course fail,
+ * but that will create a really sensible error message. */
+
+ /** @todo r=bird: this bit of code is _certifiably_ _insane_ as it allows
+ * simple stuff like VERR_EOF to pass thru. I've just amended it with
+ * disallowing VERR_EOF too, but someone needs to pick up the courage to
+ * fix this stuff properly or at least update the docs!
+ * (Parallels returns VERR_EOF, btw.) */
+
+ || ( rc != VERR_VD_GEN_INVALID_HEADER
+ && rc != VERR_VD_VDI_INVALID_HEADER
+ && rc != VERR_VD_VMDK_INVALID_HEADER
+ && rc != VERR_VD_ISCSI_INVALID_HEADER
+ && rc != VERR_VD_VHD_INVALID_HEADER
+ && rc != VERR_VD_RAW_INVALID_HEADER
+ && rc != VERR_VD_RAW_SIZE_MODULO_512
+ && rc != VERR_VD_RAW_SIZE_MODULO_2048
+ && rc != VERR_VD_RAW_SIZE_OPTICAL_TOO_SMALL
+ && rc != VERR_VD_RAW_SIZE_FLOPPY_TOO_BIG
+ && rc != VERR_VD_PARALLELS_INVALID_HEADER
+ && rc != VERR_VD_DMG_INVALID_HEADER
+ && rc != VERR_EOF /* bird for viso */
+ ))
+ {
+ /* Copy the name into the new string. */
+ char *pszFormat = RTStrDup(pBackend->pszBackendName);
+ if (!pszFormat)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ *ppszFormat = pszFormat;
+ /* Do not consider the typical file access errors as success,
+ * which allows the caller to deal with such issues. */
+ if ( rc != VERR_ACCESS_DENIED
+ && rc != VERR_PATH_NOT_FOUND
+ && rc != VERR_FILE_NOT_FOUND)
+ rc = VINF_SUCCESS;
+ break;
+ }
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ /* Try the cache backends. */
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ for (unsigned i = 0; i < vdGetCacheBackendCount(); i++)
+ {
+ PCVDCACHEBACKEND pBackend;
+ rc = vdQueryCacheBackend(i, &pBackend);
+ AssertRC(rc);
+
+ if (pBackend->pfnProbe)
+ {
+ rc = pBackend->pfnProbe(pszFilename, pVDIfsDisk, pVDIfsImage);
+ if ( RT_SUCCESS(rc)
+ || (rc != VERR_VD_GEN_INVALID_HEADER))
+ {
+ /* Copy the name into the new string. */
+ char *pszFormat = RTStrDup(pBackend->pszBackendName);
+ if (!pszFormat)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ *ppszFormat = pszFormat;
+ rc = VINF_SUCCESS;
+ break;
+ }
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc *ppszFormat=\"%s\"\n", rc, *ppszFormat));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDOpen(PVDISK pDisk, const char *pszBackend,
+ const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsImage)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockWrite = false;
+ PVDIMAGE pImage = NULL;
+
+ LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" uOpenFlags=%#x, pVDIfsImage=%#p\n",
+ pDisk, pszBackend, pszFilename, uOpenFlags, pVDIfsImage));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pszBackend, VERR_INVALID_POINTER);
+ AssertReturn(*pszBackend != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertMsgReturn((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0,
+ ("uOpenFlags=%#x\n", uOpenFlags),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn( !(uOpenFlags & VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)
+ || (uOpenFlags & VD_OPEN_FLAGS_READONLY),
+ ("uOpenFlags=%#x\n", uOpenFlags),
+ VERR_INVALID_PARAMETER);
+
+ do
+ {
+ /*
+ * Destroy the current discard state first which might still have pending blocks
+ * for the currently opened image which will be switched to readonly mode.
+ */
+ /* Lock disk for writing, as we modify pDisk information below. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+ rc = vdDiscardStateDestroy(pDisk);
+ if (RT_FAILURE(rc))
+ break;
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = false;
+
+ /* Set up image descriptor. */
+ pImage = (PVDIMAGE)RTMemAllocZ(sizeof(VDIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pImage->pszFilename = RTStrDup(pszFilename);
+ if (!pImage->pszFilename)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pImage->cbImage = VD_IMAGE_SIZE_UNINITIALIZED;
+ pImage->VDIo.pDisk = pDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vdFindImageBackend(pszBackend, &pImage->Backend);
+ if (RT_FAILURE(rc))
+ break;
+ if (!pImage->Backend)
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: unknown backend name '%s'"), pszBackend);
+ break;
+ }
+
+ /*
+ * Fail if the backend can't do async I/O but the
+ * flag is set.
+ */
+ if ( !(pImage->Backend->uBackendCaps & VD_CAP_ASYNC)
+ && (uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO))
+ {
+ rc = vdError(pDisk, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("VD: Backend '%s' does not support async I/O"), pszBackend);
+ break;
+ }
+
+ /*
+ * Fail if the backend doesn't support the discard operation but the
+ * flag is set.
+ */
+ if ( !(pImage->Backend->uBackendCaps & VD_CAP_DISCARD)
+ && (uOpenFlags & VD_OPEN_FLAGS_DISCARD))
+ {
+ rc = vdError(pDisk, VERR_VD_DISCARD_NOT_SUPPORTED, RT_SRC_POS,
+ N_("VD: Backend '%s' does not support discard"), pszBackend);
+ break;
+ }
+
+ /* Set up the I/O interface. */
+ pImage->VDIo.pInterfaceIo = VDIfIoGet(pVDIfsImage);
+ if (!pImage->VDIo.pInterfaceIo)
+ {
+ vdIfIoFallbackCallbacksSetup(&pImage->VDIo.VDIfIo);
+ rc = VDInterfaceAdd(&pImage->VDIo.VDIfIo.Core, "VD_IO", VDINTERFACETYPE_IO,
+ pDisk, sizeof(VDINTERFACEIO), &pVDIfsImage);
+ pImage->VDIo.pInterfaceIo = &pImage->VDIo.VDIfIo;
+ }
+
+ /* Set up the internal I/O interface. */
+ AssertBreakStmt(!VDIfIoIntGet(pVDIfsImage), rc = VERR_INVALID_PARAMETER);
+ vdIfIoIntCallbacksSetup(&pImage->VDIo.VDIfIoInt);
+ rc = VDInterfaceAdd(&pImage->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT,
+ &pImage->VDIo, sizeof(VDINTERFACEIOINT), &pImage->pVDIfsImage);
+ AssertRC(rc);
+
+ pImage->uOpenFlags = uOpenFlags & (VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_DISCARD | VD_OPEN_FLAGS_IGNORE_FLUSH | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS);
+ pImage->VDIo.fIgnoreFlush = (uOpenFlags & VD_OPEN_FLAGS_IGNORE_FLUSH) != 0;
+ rc = pImage->Backend->pfnOpen(pImage->pszFilename,
+ uOpenFlags & ~(VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_IGNORE_FLUSH | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS),
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ pDisk->enmType,
+ &pImage->pBackendData);
+ /*
+ * If the image is corrupted and there is a repair method try to repair it
+ * first if it was openend in read-write mode and open again afterwards.
+ */
+ if ( RT_UNLIKELY(rc == VERR_VD_IMAGE_CORRUPTED)
+ && !(uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && pImage->Backend->pfnRepair)
+ {
+ rc = pImage->Backend->pfnRepair(pszFilename, pDisk->pVDIfsDisk, pImage->pVDIfsImage, 0 /* fFlags */);
+ if (RT_SUCCESS(rc))
+ rc = pImage->Backend->pfnOpen(pImage->pszFilename,
+ uOpenFlags & ~(VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_IGNORE_FLUSH | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS),
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ pDisk->enmType,
+ &pImage->pBackendData);
+ else
+ {
+ rc = vdError(pDisk, rc, RT_SRC_POS,
+ N_("VD: error %Rrc repairing corrupted image file '%s'"), rc, pszFilename);
+ break;
+ }
+ }
+ else if (RT_UNLIKELY(rc == VERR_VD_IMAGE_CORRUPTED))
+ {
+ rc = vdError(pDisk, rc, RT_SRC_POS,
+ N_("VD: Image file '%s' is corrupted and can't be opened"), pszFilename);
+ break;
+ }
+
+ /* If the open in read-write mode failed, retry in read-only mode. */
+ if (RT_FAILURE(rc))
+ {
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && ( rc == VERR_ACCESS_DENIED
+ || rc == VERR_PERMISSION_DENIED
+ || rc == VERR_WRITE_PROTECT
+ || rc == VERR_SHARING_VIOLATION
+ || rc == VERR_FILE_LOCK_FAILED))
+ rc = pImage->Backend->pfnOpen(pImage->pszFilename,
+ (uOpenFlags & ~(VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS))
+ | VD_OPEN_FLAGS_READONLY,
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ pDisk->enmType,
+ &pImage->pBackendData);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdError(pDisk, rc, RT_SRC_POS,
+ N_("VD: error %Rrc opening image file '%s'"), rc, pszFilename);
+ break;
+ }
+ }
+
+ /* Lock disk for writing, as we modify pDisk information below. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ pImage->VDIo.pBackendData = pImage->pBackendData;
+
+ /* Check image type. As the image itself has only partial knowledge
+ * whether it's a base image or not, this info is derived here. The
+ * base image can be fixed or normal, all others must be normal or
+ * diff images. Some image formats don't distinguish between normal
+ * and diff images, so this must be corrected here. */
+ unsigned uImageFlags;
+ uImageFlags = pImage->Backend->pfnGetImageFlags(pImage->pBackendData);
+ if (RT_FAILURE(rc))
+ uImageFlags = VD_IMAGE_FLAGS_NONE;
+ if ( RT_SUCCESS(rc)
+ && !(uOpenFlags & VD_OPEN_FLAGS_INFO))
+ {
+ if ( pDisk->cImages == 0
+ && (uImageFlags & VD_IMAGE_FLAGS_DIFF))
+ {
+ rc = VERR_VD_INVALID_TYPE;
+ break;
+ }
+ else if (pDisk->cImages != 0)
+ {
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ {
+ rc = VERR_VD_INVALID_TYPE;
+ break;
+ }
+ else
+ uImageFlags |= VD_IMAGE_FLAGS_DIFF;
+ }
+ }
+
+ /* Ensure we always get correct diff information, even if the backend
+ * doesn't actually have a stored flag for this. It must not return
+ * bogus information for the parent UUID if it is not a diff image. */
+ RTUUID parentUuid;
+ RTUuidClear(&parentUuid);
+ rc2 = pImage->Backend->pfnGetParentUuid(pImage->pBackendData, &parentUuid);
+ if (RT_SUCCESS(rc2) && !RTUuidIsNull(&parentUuid))
+ uImageFlags |= VD_IMAGE_FLAGS_DIFF;
+
+ pImage->uImageFlags = uImageFlags;
+
+ /* Force sane optimization settings. It's not worth avoiding writes
+ * to fixed size images. The overhead would have almost no payback. */
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ pImage->uOpenFlags |= VD_OPEN_FLAGS_HONOR_SAME;
+
+ /** @todo optionally check UUIDs */
+
+ /* Cache disk information. */
+ pDisk->cbSize = vdImageGetSize(pImage);
+
+ /* Cache PCHS geometry. */
+ rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData,
+ &pDisk->PCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->PCHSGeometry.cCylinders = 0;
+ pDisk->PCHSGeometry.cHeads = 0;
+ pDisk->PCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the PCHS geometry is properly clipped. */
+ pDisk->PCHSGeometry.cCylinders = RT_MIN(pDisk->PCHSGeometry.cCylinders, 16383);
+ pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 16);
+ pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63);
+ }
+
+ /* Cache LCHS geometry. */
+ rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData,
+ &pDisk->LCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->LCHSGeometry.cCylinders = 0;
+ pDisk->LCHSGeometry.cHeads = 0;
+ pDisk->LCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the LCHS geometry is properly clipped. */
+ pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255);
+ pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63);
+ }
+
+ if (pDisk->cImages != 0)
+ {
+ /* Switch previous image to read-only mode. */
+ unsigned uOpenFlagsPrevImg;
+ uOpenFlagsPrevImg = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pBackendData);
+ if (!(uOpenFlagsPrevImg & VD_OPEN_FLAGS_READONLY))
+ {
+ uOpenFlagsPrevImg |= VD_OPEN_FLAGS_READONLY;
+ rc = pDisk->pLast->Backend->pfnSetOpenFlags(pDisk->pLast->pBackendData, uOpenFlagsPrevImg);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Image successfully opened, make it the last image. */
+ vdAddImageToList(pDisk, pImage);
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ pDisk->uModified = VD_IMAGE_MODIFIED_FIRST;
+ }
+ else
+ {
+ /* Error detected, but image opened. Close image. */
+ rc2 = pImage->Backend->pfnClose(pImage->pBackendData, false);
+ AssertRC(rc2);
+ pImage->pBackendData = NULL;
+ }
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pImage)
+ {
+ if (pImage->pszFilename)
+ RTStrFree(pImage->pszFilename);
+ RTMemFree(pImage);
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDCacheOpen(PVDISK pDisk, const char *pszBackend,
+ const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsCache)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockWrite = false;
+ PVDCACHE pCache = NULL;
+
+ LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" uOpenFlags=%#x, pVDIfsCache=%#p\n",
+ pDisk, pszBackend, pszFilename, uOpenFlags, pVDIfsCache));
+
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pszBackend, VERR_INVALID_POINTER);
+ AssertReturn(*pszBackend != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertMsgReturn((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, ("uOpenFlags=%#x\n", uOpenFlags),
+ VERR_INVALID_PARAMETER);
+
+ do
+ {
+ /* Set up image descriptor. */
+ pCache = (PVDCACHE)RTMemAllocZ(sizeof(VDCACHE));
+ if (!pCache)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pCache->pszFilename = RTStrDup(pszFilename);
+ if (!pCache->pszFilename)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pCache->VDIo.pDisk = pDisk;
+ pCache->pVDIfsCache = pVDIfsCache;
+
+ rc = vdFindCacheBackend(pszBackend, &pCache->Backend);
+ if (RT_FAILURE(rc))
+ break;
+ if (!pCache->Backend)
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: unknown backend name '%s'"), pszBackend);
+ break;
+ }
+
+ /* Set up the I/O interface. */
+ pCache->VDIo.pInterfaceIo = VDIfIoGet(pVDIfsCache);
+ if (!pCache->VDIo.pInterfaceIo)
+ {
+ vdIfIoFallbackCallbacksSetup(&pCache->VDIo.VDIfIo);
+ rc = VDInterfaceAdd(&pCache->VDIo.VDIfIo.Core, "VD_IO", VDINTERFACETYPE_IO,
+ pDisk, sizeof(VDINTERFACEIO), &pVDIfsCache);
+ pCache->VDIo.pInterfaceIo = &pCache->VDIo.VDIfIo;
+ }
+
+ /* Set up the internal I/O interface. */
+ AssertBreakStmt(!VDIfIoIntGet(pVDIfsCache), rc = VERR_INVALID_PARAMETER);
+ vdIfIoIntCallbacksSetup(&pCache->VDIo.VDIfIoInt);
+ rc = VDInterfaceAdd(&pCache->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT,
+ &pCache->VDIo, sizeof(VDINTERFACEIOINT), &pCache->pVDIfsCache);
+ AssertRC(rc);
+
+ pCache->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME;
+ rc = pCache->Backend->pfnOpen(pCache->pszFilename,
+ uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME,
+ pDisk->pVDIfsDisk,
+ pCache->pVDIfsCache,
+ &pCache->pBackendData);
+ /* If the open in read-write mode failed, retry in read-only mode. */
+ if (RT_FAILURE(rc))
+ {
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && ( rc == VERR_ACCESS_DENIED
+ || rc == VERR_PERMISSION_DENIED
+ || rc == VERR_WRITE_PROTECT
+ || rc == VERR_SHARING_VIOLATION
+ || rc == VERR_FILE_LOCK_FAILED))
+ rc = pCache->Backend->pfnOpen(pCache->pszFilename,
+ (uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME)
+ | VD_OPEN_FLAGS_READONLY,
+ pDisk->pVDIfsDisk,
+ pCache->pVDIfsCache,
+ &pCache->pBackendData);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdError(pDisk, rc, RT_SRC_POS,
+ N_("VD: error %Rrc opening image file '%s'"), rc, pszFilename);
+ break;
+ }
+ }
+
+ /* Lock disk for writing, as we modify pDisk information below. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ /*
+ * Check that the modification UUID of the cache and last image
+ * match. If not the image was modified in-between without the cache.
+ * The cache might contain stale data.
+ */
+ RTUUID UuidImage, UuidCache;
+
+ rc = pCache->Backend->pfnGetModificationUuid(pCache->pBackendData,
+ &UuidCache);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pDisk->pLast->Backend->pfnGetModificationUuid(pDisk->pLast->pBackendData,
+ &UuidImage);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTUuidCompare(&UuidImage, &UuidCache))
+ rc = VERR_VD_CACHE_NOT_UP_TO_DATE;
+ }
+ }
+
+ /*
+ * We assume that the user knows what he is doing if one of the images
+ * doesn't support the modification uuid.
+ */
+ if (rc == VERR_NOT_SUPPORTED)
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Cache successfully opened, make it the current one. */
+ if (!pDisk->pCache)
+ pDisk->pCache = pCache;
+ else
+ rc = VERR_VD_CACHE_ALREADY_EXISTS;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ /* Error detected, but image opened. Close image. */
+ rc2 = pCache->Backend->pfnClose(pCache->pBackendData, false);
+ AssertRC(rc2);
+ pCache->pBackendData = NULL;
+ }
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pCache)
+ {
+ if (pCache->pszFilename)
+ RTStrFree(pCache->pszFilename);
+ RTMemFree(pCache);
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDFilterAdd(PVDISK pDisk, const char *pszFilter, uint32_t fFlags,
+ PVDINTERFACE pVDIfsFilter)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockWrite = false;
+ PVDFILTER pFilter = NULL;
+
+ LogFlowFunc(("pDisk=%#p pszFilter=\"%s\" pVDIfsFilter=%#p\n",
+ pDisk, pszFilter, pVDIfsFilter));
+
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pszFilter, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilter != '\0', VERR_INVALID_PARAMETER);
+ AssertMsgReturn(!(fFlags & ~VD_FILTER_FLAGS_MASK), ("Invalid flags set (fFlags=%#x)\n", fFlags),
+ VERR_INVALID_PARAMETER);
+
+ do
+ {
+ /* Set up image descriptor. */
+ pFilter = (PVDFILTER)RTMemAllocZ(sizeof(VDFILTER));
+ if (!pFilter)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc = vdFindFilterBackend(pszFilter, &pFilter->pBackend);
+ if (RT_FAILURE(rc))
+ break;
+ if (!pFilter->pBackend)
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: unknown filter backend name '%s'"), pszFilter);
+ break;
+ }
+
+ pFilter->VDIo.pDisk = pDisk;
+ pFilter->pVDIfsFilter = pVDIfsFilter;
+
+ /* Set up the internal I/O interface. */
+ AssertBreakStmt(!VDIfIoIntGet(pVDIfsFilter), rc = VERR_INVALID_PARAMETER);
+ vdIfIoIntCallbacksSetup(&pFilter->VDIo.VDIfIoInt);
+ rc = VDInterfaceAdd(&pFilter->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT,
+ &pFilter->VDIo, sizeof(VDINTERFACEIOINT), &pFilter->pVDIfsFilter);
+ AssertRC(rc);
+
+ rc = pFilter->pBackend->pfnCreate(pDisk->pVDIfsDisk, fFlags & VD_FILTER_FLAGS_INFO,
+ pFilter->pVDIfsFilter, &pFilter->pvBackendData);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Lock disk for writing, as we modify pDisk information below. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ /* Add filter to chains. */
+ if (fFlags & VD_FILTER_FLAGS_WRITE)
+ {
+ RTListAppend(&pDisk->ListFilterChainWrite, &pFilter->ListNodeChainWrite);
+ vdFilterRetain(pFilter);
+ }
+
+ if (fFlags & VD_FILTER_FLAGS_READ)
+ {
+ RTListAppend(&pDisk->ListFilterChainRead, &pFilter->ListNodeChainRead);
+ vdFilterRetain(pFilter);
+ }
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pFilter)
+ RTMemFree(pFilter);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDCreateBase(PVDISK pDisk, const char *pszBackend,
+ const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockWrite = false, fLockRead = false;
+ PVDIMAGE pImage = NULL;
+ RTUUID uuid;
+
+ LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" PCHS=%u/%u/%u LCHS=%u/%u/%u Uuid=%RTuuid uOpenFlags=%#x pVDIfsImage=%#p pVDIfsOperation=%#p\n",
+ pDisk, pszBackend, pszFilename, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads,
+ pPCHSGeometry->cSectors, pLCHSGeometry->cCylinders,
+ pLCHSGeometry->cHeads, pLCHSGeometry->cSectors, pUuid,
+ uOpenFlags, pVDIfsImage, pVDIfsOperation));
+
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsgReturn(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature),
+ VERR_INVALID_MAGIC);
+
+ /* Check arguments. */
+ AssertPtrReturn(pszBackend, VERR_INVALID_POINTER);
+ AssertReturn(*pszBackend != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertMsgReturn(cbSize || (uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK), ("cbSize=%llu\n", cbSize),
+ VERR_INVALID_PARAMETER);
+ if (cbSize % 512 && !(uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK))
+ {
+ rc = vdError(pDisk, VERR_VD_INVALID_SIZE, RT_SRC_POS,
+ N_("VD: The given disk size %llu is not aligned on a sector boundary (512 bytes)"), cbSize);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+ }
+ AssertMsgReturn( ((uImageFlags & ~VD_IMAGE_FLAGS_MASK) == 0)
+ || ((uImageFlags & (VD_IMAGE_FLAGS_FIXED | VD_IMAGE_FLAGS_DIFF)) != VD_IMAGE_FLAGS_FIXED),
+ ("uImageFlags=%#x\n", uImageFlags),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn( !(uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK)
+ || !(uImageFlags & ~(VD_VMDK_IMAGE_FLAGS_RAWDISK | VD_IMAGE_FLAGS_FIXED)),
+ ("uImageFlags=%#x\n", uImageFlags),
+ VERR_INVALID_PARAMETER);
+ /* The PCHS geometry fields may be 0 to leave it for later. */
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_PARAMETER);
+ AssertMsgReturn( pPCHSGeometry->cHeads <= 16
+ && pPCHSGeometry->cSectors <= 63,
+ ("PCHS=%u/%u/%u\n", pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors),
+ VERR_INVALID_PARAMETER);
+ /* The LCHS geometry fields may be 0 to leave it to later autodetection. */
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+ AssertMsgReturn( pLCHSGeometry->cHeads <= 255
+ && pLCHSGeometry->cSectors <= 63,
+ ("LCHS=%u/%u/%u\n", pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors),
+ VERR_INVALID_PARAMETER);
+ /* The UUID may be NULL. */
+ AssertPtrNullReturn(pUuid, VERR_INVALID_POINTER);
+ AssertMsgReturn((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, ("uOpenFlags=%#x\n", uOpenFlags),
+ VERR_INVALID_PARAMETER);
+
+ AssertPtrNullReturn(pVDIfsOperation, VERR_INVALID_PARAMETER);
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ do
+ {
+ /* Check state. Needs a temporary read lock. Holding the write lock
+ * all the time would be blocking other activities for too long. */
+ rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = true;
+ AssertMsgBreakStmt(pDisk->cImages == 0,
+ ("Create base image cannot be done with other images open\n"),
+ rc = VERR_VD_INVALID_STATE);
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = false;
+
+ /* Set up image descriptor. */
+ pImage = (PVDIMAGE)RTMemAllocZ(sizeof(VDIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pImage->pszFilename = RTStrDup(pszFilename);
+ if (!pImage->pszFilename)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pImage->cbImage = VD_IMAGE_SIZE_UNINITIALIZED;
+ pImage->VDIo.pDisk = pDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ /* Set up the I/O interface. */
+ pImage->VDIo.pInterfaceIo = VDIfIoGet(pVDIfsImage);
+ if (!pImage->VDIo.pInterfaceIo)
+ {
+ vdIfIoFallbackCallbacksSetup(&pImage->VDIo.VDIfIo);
+ rc = VDInterfaceAdd(&pImage->VDIo.VDIfIo.Core, "VD_IO", VDINTERFACETYPE_IO,
+ pDisk, sizeof(VDINTERFACEIO), &pVDIfsImage);
+ pImage->VDIo.pInterfaceIo = &pImage->VDIo.VDIfIo;
+ }
+
+ /* Set up the internal I/O interface. */
+ AssertBreakStmt(!VDIfIoIntGet(pVDIfsImage), rc = VERR_INVALID_PARAMETER);
+ vdIfIoIntCallbacksSetup(&pImage->VDIo.VDIfIoInt);
+ rc = VDInterfaceAdd(&pImage->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT,
+ &pImage->VDIo, sizeof(VDINTERFACEIOINT), &pImage->pVDIfsImage);
+ AssertRC(rc);
+
+ rc = vdFindImageBackend(pszBackend, &pImage->Backend);
+ if (RT_FAILURE(rc))
+ break;
+ if (!pImage->Backend)
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: unknown backend name '%s'"), pszBackend);
+ break;
+ }
+ if (!(pImage->Backend->uBackendCaps & ( VD_CAP_CREATE_FIXED
+ | VD_CAP_CREATE_DYNAMIC)))
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: backend '%s' cannot create base images"), pszBackend);
+ break;
+ }
+ if ( ( (uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G)
+ && !(pImage->Backend->uBackendCaps & VD_CAP_CREATE_SPLIT_2G))
+ || ( (uImageFlags & ( VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED
+ | VD_VMDK_IMAGE_FLAGS_RAWDISK))
+ && RTStrICmp(pszBackend, "VMDK")))
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: backend '%s' does not support the selected image variant"), pszBackend);
+ break;
+ }
+
+ /* Create UUID if the caller didn't specify one. */
+ if (!pUuid)
+ {
+ rc = RTUuidCreate(&uuid);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdError(pDisk, rc, RT_SRC_POS,
+ N_("VD: cannot generate UUID for image '%s'"),
+ pszFilename);
+ break;
+ }
+ pUuid = &uuid;
+ }
+
+ pImage->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME;
+ uImageFlags &= ~VD_IMAGE_FLAGS_DIFF;
+ pImage->VDIo.fIgnoreFlush = (uOpenFlags & VD_OPEN_FLAGS_IGNORE_FLUSH) != 0;
+ rc = pImage->Backend->pfnCreate(pImage->pszFilename, cbSize,
+ uImageFlags, pszComment, pPCHSGeometry,
+ pLCHSGeometry, pUuid,
+ uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME,
+ 0, 99,
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ pVDIfsOperation,
+ pDisk->enmType,
+ &pImage->pBackendData);
+
+ if (RT_SUCCESS(rc))
+ {
+ pImage->VDIo.pBackendData = pImage->pBackendData;
+ pImage->uImageFlags = uImageFlags;
+
+ /* Force sane optimization settings. It's not worth avoiding writes
+ * to fixed size images. The overhead would have almost no payback. */
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ pImage->uOpenFlags |= VD_OPEN_FLAGS_HONOR_SAME;
+
+ /* Lock disk for writing, as we modify pDisk information below. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ /** @todo optionally check UUIDs */
+
+ /* Re-check state, as the lock wasn't held and another image
+ * creation call could have been done by another thread. */
+ AssertMsgStmt(pDisk->cImages == 0,
+ ("Create base image cannot be done with other images open\n"),
+ rc = VERR_VD_INVALID_STATE);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Cache disk information. */
+ pDisk->cbSize = vdImageGetSize(pImage);
+
+ /* Cache PCHS geometry. */
+ rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData,
+ &pDisk->PCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->PCHSGeometry.cCylinders = 0;
+ pDisk->PCHSGeometry.cHeads = 0;
+ pDisk->PCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the CHS geometry is properly clipped. */
+ pDisk->PCHSGeometry.cCylinders = RT_MIN(pDisk->PCHSGeometry.cCylinders, 16383);
+ pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 16);
+ pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63);
+ }
+
+ /* Cache LCHS geometry. */
+ rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData,
+ &pDisk->LCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->LCHSGeometry.cCylinders = 0;
+ pDisk->LCHSGeometry.cHeads = 0;
+ pDisk->LCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the CHS geometry is properly clipped. */
+ pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255);
+ pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63);
+ }
+
+ /* Image successfully opened, make it the last image. */
+ vdAddImageToList(pDisk, pImage);
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ pDisk->uModified = VD_IMAGE_MODIFIED_FIRST;
+ }
+ else
+ {
+ /* Error detected, image may or may not be opened. Close and delete
+ * image if it was opened. */
+ if (pImage->pBackendData)
+ {
+ rc2 = pImage->Backend->pfnClose(pImage->pBackendData, true);
+ AssertRC(rc2);
+ pImage->pBackendData = NULL;
+ }
+ }
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+ else if (RT_UNLIKELY(fLockRead))
+ {
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pImage)
+ {
+ if (pImage->pszFilename)
+ RTStrFree(pImage->pszFilename);
+ RTMemFree(pImage);
+ }
+ }
+
+ if (RT_SUCCESS(rc) && pIfProgress && pIfProgress->pfnProgress)
+ pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDCreateDiff(PVDISK pDisk, const char *pszBackend,
+ const char *pszFilename, unsigned uImageFlags,
+ const char *pszComment, PCRTUUID pUuid,
+ PCRTUUID pParentUuid, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockWrite = false, fLockRead = false;
+ PVDIMAGE pImage = NULL;
+ RTUUID uuid;
+
+ LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" uImageFlags=%#x pszComment=\"%s\" Uuid=%RTuuid uOpenFlags=%#x pVDIfsImage=%#p pVDIfsOperation=%#p\n",
+ pDisk, pszBackend, pszFilename, uImageFlags, pszComment, pUuid, uOpenFlags, pVDIfsImage, pVDIfsOperation));
+
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pszBackend, VERR_INVALID_POINTER);
+ AssertReturn(*pszBackend != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertMsgReturn((uImageFlags & ~VD_IMAGE_FLAGS_MASK) == 0, ("uImageFlags=%#x\n", uImageFlags),
+ VERR_INVALID_PARAMETER);
+ /* The UUID may be NULL. */
+ AssertPtrNullReturn(pUuid, VERR_INVALID_POINTER);
+ /* The parent UUID may be NULL. */
+ AssertPtrNullReturn(pParentUuid, VERR_INVALID_POINTER);
+ AssertMsgReturn((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, ("uOpenFlags=%#x\n", uOpenFlags),
+ VERR_INVALID_PARAMETER);
+
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+ do
+ {
+ /* Check state. Needs a temporary read lock. Holding the write lock
+ * all the time would be blocking other activities for too long. */
+ rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = true;
+ AssertMsgBreakStmt(pDisk->cImages != 0,
+ ("Create diff image cannot be done without other images open\n"),
+ rc = VERR_VD_INVALID_STATE);
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = false;
+
+ /*
+ * Destroy the current discard state first which might still have pending blocks
+ * for the currently opened image which will be switched to readonly mode.
+ */
+ /* Lock disk for writing, as we modify pDisk information below. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+ rc = vdDiscardStateDestroy(pDisk);
+ if (RT_FAILURE(rc))
+ break;
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = false;
+
+ /* Set up image descriptor. */
+ pImage = (PVDIMAGE)RTMemAllocZ(sizeof(VDIMAGE));
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pImage->pszFilename = RTStrDup(pszFilename);
+ if (!pImage->pszFilename)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc = vdFindImageBackend(pszBackend, &pImage->Backend);
+ if (RT_FAILURE(rc))
+ break;
+ if (!pImage->Backend)
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: unknown backend name '%s'"), pszBackend);
+ break;
+ }
+ if ( !(pImage->Backend->uBackendCaps & VD_CAP_DIFF)
+ || !(pImage->Backend->uBackendCaps & ( VD_CAP_CREATE_FIXED
+ | VD_CAP_CREATE_DYNAMIC)))
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: backend '%s' cannot create diff images"), pszBackend);
+ break;
+ }
+
+ pImage->cbImage = VD_IMAGE_SIZE_UNINITIALIZED;
+ pImage->VDIo.pDisk = pDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ /* Set up the I/O interface. */
+ pImage->VDIo.pInterfaceIo = VDIfIoGet(pVDIfsImage);
+ if (!pImage->VDIo.pInterfaceIo)
+ {
+ vdIfIoFallbackCallbacksSetup(&pImage->VDIo.VDIfIo);
+ rc = VDInterfaceAdd(&pImage->VDIo.VDIfIo.Core, "VD_IO", VDINTERFACETYPE_IO,
+ pDisk, sizeof(VDINTERFACEIO), &pVDIfsImage);
+ pImage->VDIo.pInterfaceIo = &pImage->VDIo.VDIfIo;
+ }
+
+ /* Set up the internal I/O interface. */
+ AssertBreakStmt(!VDIfIoIntGet(pVDIfsImage), rc = VERR_INVALID_PARAMETER);
+ vdIfIoIntCallbacksSetup(&pImage->VDIo.VDIfIoInt);
+ rc = VDInterfaceAdd(&pImage->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT,
+ &pImage->VDIo, sizeof(VDINTERFACEIOINT), &pImage->pVDIfsImage);
+ AssertRC(rc);
+
+ /* Create UUID if the caller didn't specify one. */
+ if (!pUuid)
+ {
+ rc = RTUuidCreate(&uuid);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdError(pDisk, rc, RT_SRC_POS,
+ N_("VD: cannot generate UUID for image '%s'"),
+ pszFilename);
+ break;
+ }
+ pUuid = &uuid;
+ }
+
+ pImage->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME;
+ pImage->VDIo.fIgnoreFlush = (uOpenFlags & VD_OPEN_FLAGS_IGNORE_FLUSH) != 0;
+ uImageFlags |= VD_IMAGE_FLAGS_DIFF;
+ rc = pImage->Backend->pfnCreate(pImage->pszFilename, pDisk->cbSize,
+ uImageFlags | VD_IMAGE_FLAGS_DIFF,
+ pszComment, &pDisk->PCHSGeometry,
+ &pDisk->LCHSGeometry, pUuid,
+ uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME,
+ 0, 99,
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ pVDIfsOperation,
+ pDisk->enmType,
+ &pImage->pBackendData);
+
+ if (RT_SUCCESS(rc))
+ {
+ pImage->VDIo.pBackendData = pImage->pBackendData;
+ pImage->uImageFlags = uImageFlags;
+
+ /* Lock disk for writing, as we modify pDisk information below. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ /* Switch previous image to read-only mode. */
+ unsigned uOpenFlagsPrevImg;
+ uOpenFlagsPrevImg = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pBackendData);
+ if (!(uOpenFlagsPrevImg & VD_OPEN_FLAGS_READONLY))
+ {
+ uOpenFlagsPrevImg |= VD_OPEN_FLAGS_READONLY;
+ rc = pDisk->pLast->Backend->pfnSetOpenFlags(pDisk->pLast->pBackendData, uOpenFlagsPrevImg);
+ }
+
+ /** @todo optionally check UUIDs */
+
+ /* Re-check state, as the lock wasn't held and another image
+ * creation call could have been done by another thread. */
+ AssertMsgStmt(pDisk->cImages != 0,
+ ("Create diff image cannot be done without other images open\n"),
+ rc = VERR_VD_INVALID_STATE);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ RTUUID Uuid;
+ RTTIMESPEC ts;
+
+ if (pParentUuid && !RTUuidIsNull(pParentUuid))
+ {
+ Uuid = *pParentUuid;
+ pImage->Backend->pfnSetParentUuid(pImage->pBackendData, &Uuid);
+ }
+ else
+ {
+ rc2 = pDisk->pLast->Backend->pfnGetUuid(pDisk->pLast->pBackendData,
+ &Uuid);
+ if (RT_SUCCESS(rc2))
+ pImage->Backend->pfnSetParentUuid(pImage->pBackendData, &Uuid);
+ }
+ rc2 = pDisk->pLast->Backend->pfnGetModificationUuid(pDisk->pLast->pBackendData,
+ &Uuid);
+ if (RT_SUCCESS(rc2))
+ pImage->Backend->pfnSetParentModificationUuid(pImage->pBackendData,
+ &Uuid);
+ if (pDisk->pLast->Backend->pfnGetTimestamp)
+ rc2 = pDisk->pLast->Backend->pfnGetTimestamp(pDisk->pLast->pBackendData,
+ &ts);
+ else
+ rc2 = VERR_NOT_IMPLEMENTED;
+ if (RT_SUCCESS(rc2) && pImage->Backend->pfnSetParentTimestamp)
+ pImage->Backend->pfnSetParentTimestamp(pImage->pBackendData, &ts);
+
+ if (pImage->Backend->pfnSetParentFilename)
+ rc2 = pImage->Backend->pfnSetParentFilename(pImage->pBackendData, pDisk->pLast->pszFilename);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Image successfully opened, make it the last image. */
+ vdAddImageToList(pDisk, pImage);
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ pDisk->uModified = VD_IMAGE_MODIFIED_FIRST;
+ }
+ else
+ {
+ /* Error detected, but image opened. Close and delete image. */
+ rc2 = pImage->Backend->pfnClose(pImage->pBackendData, true);
+ AssertRC(rc2);
+ pImage->pBackendData = NULL;
+ }
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+ else if (RT_UNLIKELY(fLockRead))
+ {
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pImage)
+ {
+ if (pImage->pszFilename)
+ RTStrFree(pImage->pszFilename);
+ RTMemFree(pImage);
+ }
+ }
+
+ if (RT_SUCCESS(rc) && pIfProgress && pIfProgress->pfnProgress)
+ pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDCreateCache(PVDISK pDisk, const char *pszBackend,
+ const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsCache, PVDINTERFACE pVDIfsOperation)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockWrite = false, fLockRead = false;
+ PVDCACHE pCache = NULL;
+ RTUUID uuid;
+
+ LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" Uuid=%RTuuid uOpenFlags=%#x pVDIfsImage=%#p pVDIfsOperation=%#p\n",
+ pDisk, pszBackend, pszFilename, cbSize, uImageFlags, pszComment, pUuid, uOpenFlags, pVDIfsCache, pVDIfsOperation));
+
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pszBackend, VERR_INVALID_POINTER);
+ AssertReturn(*pszBackend, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER);
+ AssertMsgReturn((uImageFlags & ~VD_IMAGE_FLAGS_MASK) == 0, ("uImageFlags=%#x\n", uImageFlags),
+ VERR_INVALID_PARAMETER);
+ /* The UUID may be NULL. */
+ AssertPtrNullReturn(pUuid, VERR_INVALID_POINTER);
+ AssertMsgReturn((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, ("uOpenFlags=%#x\n", uOpenFlags),
+ VERR_INVALID_PARAMETER);
+
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ do
+ {
+ /* Check state. Needs a temporary read lock. Holding the write lock
+ * all the time would be blocking other activities for too long. */
+ rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = true;
+ AssertMsgBreakStmt(!pDisk->pCache,
+ ("Create cache image cannot be done with a cache already attached\n"),
+ rc = VERR_VD_CACHE_ALREADY_EXISTS);
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = false;
+
+ /* Set up image descriptor. */
+ pCache = (PVDCACHE)RTMemAllocZ(sizeof(VDCACHE));
+ if (!pCache)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pCache->pszFilename = RTStrDup(pszFilename);
+ if (!pCache->pszFilename)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc = vdFindCacheBackend(pszBackend, &pCache->Backend);
+ if (RT_FAILURE(rc))
+ break;
+ if (!pCache->Backend)
+ {
+ rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VD: unknown backend name '%s'"), pszBackend);
+ break;
+ }
+
+ pCache->VDIo.pDisk = pDisk;
+ pCache->pVDIfsCache = pVDIfsCache;
+
+ /* Set up the I/O interface. */
+ pCache->VDIo.pInterfaceIo = VDIfIoGet(pVDIfsCache);
+ if (!pCache->VDIo.pInterfaceIo)
+ {
+ vdIfIoFallbackCallbacksSetup(&pCache->VDIo.VDIfIo);
+ rc = VDInterfaceAdd(&pCache->VDIo.VDIfIo.Core, "VD_IO", VDINTERFACETYPE_IO,
+ pDisk, sizeof(VDINTERFACEIO), &pVDIfsCache);
+ pCache->VDIo.pInterfaceIo = &pCache->VDIo.VDIfIo;
+ }
+
+ /* Set up the internal I/O interface. */
+ AssertBreakStmt(!VDIfIoIntGet(pVDIfsCache), rc = VERR_INVALID_PARAMETER);
+ vdIfIoIntCallbacksSetup(&pCache->VDIo.VDIfIoInt);
+ rc = VDInterfaceAdd(&pCache->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT,
+ &pCache->VDIo, sizeof(VDINTERFACEIOINT), &pCache->pVDIfsCache);
+ AssertRC(rc);
+
+ /* Create UUID if the caller didn't specify one. */
+ if (!pUuid)
+ {
+ rc = RTUuidCreate(&uuid);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdError(pDisk, rc, RT_SRC_POS,
+ N_("VD: cannot generate UUID for image '%s'"),
+ pszFilename);
+ break;
+ }
+ pUuid = &uuid;
+ }
+
+ pCache->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME;
+ pCache->VDIo.fIgnoreFlush = (uOpenFlags & VD_OPEN_FLAGS_IGNORE_FLUSH) != 0;
+ rc = pCache->Backend->pfnCreate(pCache->pszFilename, cbSize,
+ uImageFlags,
+ pszComment, pUuid,
+ uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME,
+ 0, 99,
+ pDisk->pVDIfsDisk,
+ pCache->pVDIfsCache,
+ pVDIfsOperation,
+ &pCache->pBackendData);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Lock disk for writing, as we modify pDisk information below. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ pCache->VDIo.pBackendData = pCache->pBackendData;
+
+ /* Re-check state, as the lock wasn't held and another image
+ * creation call could have been done by another thread. */
+ AssertMsgStmt(!pDisk->pCache,
+ ("Create cache image cannot be done with another cache open\n"),
+ rc = VERR_VD_CACHE_ALREADY_EXISTS);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pDisk->pLast)
+ {
+ RTUUID UuidModification;
+
+ /* Set same modification Uuid as the last image. */
+ rc = pDisk->pLast->Backend->pfnGetModificationUuid(pDisk->pLast->pBackendData,
+ &UuidModification);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pCache->Backend->pfnSetModificationUuid(pCache->pBackendData,
+ &UuidModification);
+ }
+
+ if (rc == VERR_NOT_SUPPORTED)
+ rc = VINF_SUCCESS;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Cache successfully created. */
+ pDisk->pCache = pCache;
+ }
+ else
+ {
+ /* Error detected, but image opened. Close and delete image. */
+ rc2 = pCache->Backend->pfnClose(pCache->pBackendData, true);
+ AssertRC(rc2);
+ pCache->pBackendData = NULL;
+ }
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+ else if (RT_UNLIKELY(fLockRead))
+ {
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pCache)
+ {
+ if (pCache->pszFilename)
+ RTStrFree(pCache->pszFilename);
+ RTMemFree(pCache);
+ }
+ }
+
+ if (RT_SUCCESS(rc) && pIfProgress && pIfProgress->pfnProgress)
+ pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDMerge(PVDISK pDisk, unsigned nImageFrom,
+ unsigned nImageTo, PVDINTERFACE pVDIfsOperation)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockWrite = false, fLockRead = false;
+ void *pvBuf = NULL;
+
+ LogFlowFunc(("pDisk=%#p nImageFrom=%u nImageTo=%u pVDIfsOperation=%#p\n",
+ pDisk, nImageFrom, nImageTo, pVDIfsOperation));
+
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* For simplicity reasons lock for writing as the image reopen below
+ * might need it. After all the reopen is usually needed. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+ PVDIMAGE pImageFrom = vdGetImageByNumber(pDisk, nImageFrom);
+ PVDIMAGE pImageTo = vdGetImageByNumber(pDisk, nImageTo);
+ if (!pImageFrom || !pImageTo)
+ {
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+ break;
+ }
+ AssertBreakStmt(pImageFrom != pImageTo, rc = VERR_INVALID_PARAMETER);
+
+ /* Make sure destination image is writable. */
+ unsigned uOpenFlags = pImageTo->Backend->pfnGetOpenFlags(pImageTo->pBackendData);
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ /*
+ * Clear skip consistency checks because the image is made writable now and
+ * skipping consistency checks is only possible for readonly images.
+ */
+ uOpenFlags &= ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS);
+ rc = pImageTo->Backend->pfnSetOpenFlags(pImageTo->pBackendData,
+ uOpenFlags);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* Get size of destination image. */
+ uint64_t cbSize = vdImageGetSize(pImageTo);
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = false;
+
+ /* Allocate tmp buffer. */
+ pvBuf = RTMemTmpAlloc(VD_MERGE_BUFFER_SIZE);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Merging is done directly on the images itself. This potentially
+ * causes trouble if the disk is full in the middle of operation. */
+ if (nImageFrom < nImageTo)
+ {
+ /* Merge parent state into child. This means writing all not
+ * allocated blocks in the destination image which are allocated in
+ * the images to be merged. */
+ uint64_t uOffset = 0;
+ uint64_t cbRemaining = cbSize;
+
+ do
+ {
+ size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining);
+ RTSGSEG SegmentBuf;
+ RTSGBUF SgBuf;
+ VDIOCTX IoCtx;
+
+ SegmentBuf.pvSeg = pvBuf;
+ SegmentBuf.cbSeg = VD_MERGE_BUFFER_SIZE;
+ RTSgBufInit(&SgBuf, &SegmentBuf, 1);
+ vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_READ, 0, 0, NULL,
+ &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC);
+
+ /* Need to hold the write lock during a read-write operation. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ rc = pImageTo->Backend->pfnRead(pImageTo->pBackendData,
+ uOffset, cbThisRead,
+ &IoCtx, &cbThisRead);
+ if (rc == VERR_VD_BLOCK_FREE)
+ {
+ /* Search for image with allocated block. Do not attempt to
+ * read more than the previous reads marked as valid.
+ * Otherwise this would return stale data when different
+ * block sizes are used for the images. */
+ for (PVDIMAGE pCurrImage = pImageTo->pPrev;
+ pCurrImage != NULL && pCurrImage != pImageFrom->pPrev && rc == VERR_VD_BLOCK_FREE;
+ pCurrImage = pCurrImage->pPrev)
+ {
+ /*
+ * Skip reading when offset exceeds image size which can happen when the target is
+ * bigger than the source.
+ */
+ uint64_t cbImage = vdImageGetSize(pCurrImage);
+ if (uOffset < cbImage)
+ {
+ cbThisRead = RT_MIN(cbThisRead, cbImage - uOffset);
+ rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
+ uOffset, cbThisRead,
+ &IoCtx, &cbThisRead);
+ }
+ else
+ rc = VERR_VD_BLOCK_FREE;
+ }
+
+ if (rc != VERR_VD_BLOCK_FREE)
+ {
+ if (RT_FAILURE(rc))
+ break;
+ /* Updating the cache is required because this might be a live merge. */
+ rc = vdWriteHelperEx(pDisk, pImageTo, pImageFrom->pPrev,
+ uOffset, pvBuf, cbThisRead,
+ VDIOCTX_FLAGS_READ_UPDATE_CACHE, 0);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ break;
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = false;
+
+ uOffset += cbThisRead;
+ cbRemaining -= cbThisRead;
+
+ if (pIfProgress && pIfProgress->pfnProgress)
+ {
+ /** @todo r=klaus: this can update the progress to the same
+ * percentage over and over again if the image format makes
+ * relatively small increments. */
+ rc = pIfProgress->pfnProgress(pIfProgress->Core.pvUser,
+ uOffset * 99 / cbSize);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ } while (uOffset < cbSize);
+ }
+ else
+ {
+ /*
+ * We may need to update the parent uuid of the child coming after
+ * the last image to be merged. We have to reopen it read/write.
+ *
+ * This is done before we do the actual merge to prevent an
+ * inconsistent chain if the mode change fails for some reason.
+ */
+ if (pImageFrom->pNext)
+ {
+ PVDIMAGE pImageChild = pImageFrom->pNext;
+
+ /* Take the write lock. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ /* We need to open the image in read/write mode. */
+ uOpenFlags = pImageChild->Backend->pfnGetOpenFlags(pImageChild->pBackendData);
+
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ uOpenFlags &= ~VD_OPEN_FLAGS_READONLY;
+ rc = pImageChild->Backend->pfnSetOpenFlags(pImageChild->pBackendData,
+ uOpenFlags);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = false;
+ }
+
+ /* If the merge is from the last image we have to relay all writes
+ * to the merge destination as well, so that concurrent writes
+ * (in case of a live merge) are handled correctly. */
+ if (!pImageFrom->pNext)
+ {
+ /* Take the write lock. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ pDisk->pImageRelay = pImageTo;
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = false;
+ }
+
+ /* Merge child state into parent. This means writing all blocks
+ * which are allocated in the image up to the source image to the
+ * destination image. */
+ unsigned uProgressOld = 0;
+ uint64_t uOffset = 0;
+ uint64_t cbRemaining = cbSize;
+ do
+ {
+ size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining);
+ RTSGSEG SegmentBuf;
+ RTSGBUF SgBuf;
+ VDIOCTX IoCtx;
+
+ rc = VERR_VD_BLOCK_FREE;
+
+ SegmentBuf.pvSeg = pvBuf;
+ SegmentBuf.cbSeg = VD_MERGE_BUFFER_SIZE;
+ RTSgBufInit(&SgBuf, &SegmentBuf, 1);
+ vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_READ, 0, 0, NULL,
+ &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC);
+
+ /* Need to hold the write lock during a read-write operation. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ /* Search for image with allocated block. Do not attempt to
+ * read more than the previous reads marked as valid. Otherwise
+ * this would return stale data when different block sizes are
+ * used for the images. */
+ for (PVDIMAGE pCurrImage = pImageFrom;
+ pCurrImage != NULL && pCurrImage != pImageTo && rc == VERR_VD_BLOCK_FREE;
+ pCurrImage = pCurrImage->pPrev)
+ {
+ /*
+ * Skip reading when offset exceeds image size which can happen when the target is
+ * bigger than the source.
+ */
+ uint64_t cbImage = vdImageGetSize(pCurrImage);
+ if (uOffset < cbImage)
+ {
+ cbThisRead = RT_MIN(cbThisRead, cbImage - uOffset);
+ rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
+ uOffset, cbThisRead,
+ &IoCtx, &cbThisRead);
+ }
+ else
+ rc = VERR_VD_BLOCK_FREE;
+ }
+
+ if (rc != VERR_VD_BLOCK_FREE)
+ {
+ if (RT_FAILURE(rc))
+ break;
+ rc = vdWriteHelper(pDisk, pImageTo, uOffset, pvBuf,
+ cbThisRead, VDIOCTX_FLAGS_READ_UPDATE_CACHE);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = false;
+
+ uOffset += cbThisRead;
+ cbRemaining -= cbThisRead;
+
+ unsigned uProgressNew = uOffset * 99 / cbSize;
+ if (uProgressNew != uProgressOld)
+ {
+ uProgressOld = uProgressNew;
+
+ if (pIfProgress && pIfProgress->pfnProgress)
+ {
+ rc = pIfProgress->pfnProgress(pIfProgress->Core.pvUser,
+ uProgressOld);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ } while (uOffset < cbSize);
+
+ /* In case we set up a "write proxy" image above we must clear
+ * this again now to prevent stray writes. Failure or not. */
+ if (!pImageFrom->pNext)
+ {
+ /* Take the write lock. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ pDisk->pImageRelay = NULL;
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = false;
+ }
+ }
+
+ /*
+ * Leave in case of an error to avoid corrupted data in the image chain
+ * (includes cancelling the operation by the user).
+ */
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Need to hold the write lock while finishing the merge. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ /* Update parent UUID so that image chain is consistent.
+ * The two attempts work around the problem that some backends
+ * (e.g. iSCSI) do not support UUIDs, so we exploit the fact that
+ * so far there can only be one such image in the chain. */
+ /** @todo needs a better long-term solution, passing the UUID
+ * knowledge from the caller or some such */
+ RTUUID Uuid;
+ PVDIMAGE pImageChild = NULL;
+ if (nImageFrom < nImageTo)
+ {
+ if (pImageFrom->pPrev)
+ {
+ /* plan A: ask the parent itself for its UUID */
+ rc = pImageFrom->pPrev->Backend->pfnGetUuid(pImageFrom->pPrev->pBackendData,
+ &Uuid);
+ if (RT_FAILURE(rc))
+ {
+ /* plan B: ask the child of the parent for parent UUID */
+ rc = pImageFrom->Backend->pfnGetParentUuid(pImageFrom->pBackendData,
+ &Uuid);
+ }
+ AssertRC(rc);
+ }
+ else
+ RTUuidClear(&Uuid);
+ rc = pImageTo->Backend->pfnSetParentUuid(pImageTo->pBackendData,
+ &Uuid);
+ AssertRC(rc);
+ }
+ else
+ {
+ /* Update the parent uuid of the child of the last merged image. */
+ if (pImageFrom->pNext)
+ {
+ /* plan A: ask the parent itself for its UUID */
+ rc = pImageTo->Backend->pfnGetUuid(pImageTo->pBackendData,
+ &Uuid);
+ if (RT_FAILURE(rc))
+ {
+ /* plan B: ask the child of the parent for parent UUID */
+ rc = pImageTo->pNext->Backend->pfnGetParentUuid(pImageTo->pNext->pBackendData,
+ &Uuid);
+ }
+ AssertRC(rc);
+
+ rc = pImageFrom->Backend->pfnSetParentUuid(pImageFrom->pNext->pBackendData,
+ &Uuid);
+ AssertRC(rc);
+
+ pImageChild = pImageFrom->pNext;
+ }
+ }
+
+ /* Delete the no longer needed images. */
+ PVDIMAGE pImg = pImageFrom, pTmp;
+ while (pImg != pImageTo)
+ {
+ if (nImageFrom < nImageTo)
+ pTmp = pImg->pNext;
+ else
+ pTmp = pImg->pPrev;
+ vdRemoveImageFromList(pDisk, pImg);
+ pImg->Backend->pfnClose(pImg->pBackendData, true);
+ RTStrFree(pImg->pszFilename);
+ RTMemFree(pImg);
+ pImg = pTmp;
+ }
+
+ /* Make sure destination image is back to read only if necessary. */
+ if (pImageTo != pDisk->pLast)
+ {
+ uOpenFlags = pImageTo->Backend->pfnGetOpenFlags(pImageTo->pBackendData);
+ uOpenFlags |= VD_OPEN_FLAGS_READONLY;
+ rc = pImageTo->Backend->pfnSetOpenFlags(pImageTo->pBackendData,
+ uOpenFlags);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /*
+ * Make sure the child is readonly
+ * for the child -> parent merge direction
+ * if necessary.
+ */
+ if ( nImageFrom > nImageTo
+ && pImageChild
+ && pImageChild != pDisk->pLast)
+ {
+ uOpenFlags = pImageChild->Backend->pfnGetOpenFlags(pImageChild->pBackendData);
+ uOpenFlags |= VD_OPEN_FLAGS_READONLY;
+ rc = pImageChild->Backend->pfnSetOpenFlags(pImageChild->pBackendData,
+ uOpenFlags);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+ else if (RT_UNLIKELY(fLockRead))
+ {
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ }
+
+ if (pvBuf)
+ RTMemTmpFree(pvBuf);
+
+ if (RT_SUCCESS(rc) && pIfProgress && pIfProgress->pfnProgress)
+ pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDCopyEx(PVDISK pDiskFrom, unsigned nImage, PVDISK pDiskTo,
+ const char *pszBackend, const char *pszFilename,
+ bool fMoveByRename, uint64_t cbSize,
+ unsigned nImageFromSame, unsigned nImageToSame,
+ unsigned uImageFlags, PCRTUUID pDstUuid,
+ unsigned uOpenFlags, PVDINTERFACE pVDIfsOperation,
+ PVDINTERFACE pDstVDIfsImage,
+ PVDINTERFACE pDstVDIfsOperation)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockReadFrom = false, fLockWriteFrom = false, fLockWriteTo = false;
+ PVDIMAGE pImageTo = NULL;
+
+ LogFlowFunc(("pDiskFrom=%#p nImage=%u pDiskTo=%#p pszBackend=\"%s\" pszFilename=\"%s\" fMoveByRename=%d cbSize=%llu nImageFromSame=%u nImageToSame=%u uImageFlags=%#x pDstUuid=%#p uOpenFlags=%#x pVDIfsOperation=%#p pDstVDIfsImage=%#p pDstVDIfsOperation=%#p\n",
+ pDiskFrom, nImage, pDiskTo, pszBackend, pszFilename, fMoveByRename, cbSize, nImageFromSame, nImageToSame, uImageFlags, pDstUuid, uOpenFlags, pVDIfsOperation, pDstVDIfsImage, pDstVDIfsOperation));
+
+ /* Check arguments. */
+ AssertReturn(pDiskFrom, VERR_INVALID_POINTER);
+ AssertMsg(pDiskFrom->u32Signature == VDISK_SIGNATURE,
+ ("u32Signature=%08x\n", pDiskFrom->u32Signature));
+
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+ PVDINTERFACEPROGRESS pDstIfProgress = VDIfProgressGet(pDstVDIfsOperation);
+
+ do {
+ rc2 = vdThreadStartRead(pDiskFrom);
+ AssertRC(rc2);
+ fLockReadFrom = true;
+ PVDIMAGE pImageFrom = vdGetImageByNumber(pDiskFrom, nImage);
+ AssertPtrBreakStmt(pImageFrom, rc = VERR_VD_IMAGE_NOT_FOUND);
+ AssertPtrBreakStmt(pDiskTo, rc = VERR_INVALID_POINTER);
+ AssertMsg(pDiskTo->u32Signature == VDISK_SIGNATURE,
+ ("u32Signature=%08x\n", pDiskTo->u32Signature));
+ AssertMsgBreakStmt( (nImageFromSame < nImage || nImageFromSame == VD_IMAGE_CONTENT_UNKNOWN)
+ && (nImageToSame < pDiskTo->cImages || nImageToSame == VD_IMAGE_CONTENT_UNKNOWN)
+ && ( (nImageFromSame == VD_IMAGE_CONTENT_UNKNOWN && nImageToSame == VD_IMAGE_CONTENT_UNKNOWN)
+ || (nImageFromSame != VD_IMAGE_CONTENT_UNKNOWN && nImageToSame != VD_IMAGE_CONTENT_UNKNOWN)),
+ ("nImageFromSame=%u nImageToSame=%u\n", nImageFromSame, nImageToSame),
+ rc = VERR_INVALID_PARAMETER);
+
+ /* Move the image. */
+ if (pDiskFrom == pDiskTo)
+ {
+ /* Rename only works when backends are the same, are file based
+ * and the rename method is implemented. */
+ if ( fMoveByRename
+ && !RTStrICmp(pszBackend, pImageFrom->Backend->pszBackendName)
+ && pImageFrom->Backend->uBackendCaps & VD_CAP_FILE
+ && pImageFrom->Backend->pfnRename)
+ {
+ rc2 = vdThreadFinishRead(pDiskFrom);
+ AssertRC(rc2);
+ fLockReadFrom = false;
+
+ rc2 = vdThreadStartWrite(pDiskFrom);
+ AssertRC(rc2);
+ fLockWriteFrom = true;
+ rc = pImageFrom->Backend->pfnRename(pImageFrom->pBackendData, pszFilename ? pszFilename : pImageFrom->pszFilename);
+ break;
+ }
+
+ /** @todo Moving (including shrinking/growing) of the image is
+ * requested, but the rename attempt failed or it wasn't possible.
+ * Must now copy image to temp location. */
+ AssertReleaseMsgFailed(("VDCopy: moving by copy/delete not implemented\n"));
+ }
+
+ /* pszFilename is allowed to be NULL, as this indicates copy to the existing image. */
+ if (pszFilename)
+ {
+ AssertPtrBreakStmt(pszFilename, rc = VERR_INVALID_POINTER);
+ AssertBreakStmt(*pszFilename != '\0', rc = VERR_INVALID_PARAMETER);
+ }
+
+ uint64_t cbSizeFrom;
+ cbSizeFrom = vdImageGetSize(pImageFrom);
+ if (cbSizeFrom == 0)
+ {
+ rc = VERR_VD_VALUE_NOT_FOUND;
+ break;
+ }
+
+ VDGEOMETRY PCHSGeometryFrom = {0, 0, 0};
+ VDGEOMETRY LCHSGeometryFrom = {0, 0, 0};
+ pImageFrom->Backend->pfnGetPCHSGeometry(pImageFrom->pBackendData, &PCHSGeometryFrom);
+ pImageFrom->Backend->pfnGetLCHSGeometry(pImageFrom->pBackendData, &LCHSGeometryFrom);
+
+ RTUUID ImageUuid, ImageModificationUuid;
+ if (pDiskFrom != pDiskTo)
+ {
+ if (pDstUuid)
+ ImageUuid = *pDstUuid;
+ else
+ RTUuidCreate(&ImageUuid);
+ }
+ else
+ {
+ rc = pImageFrom->Backend->pfnGetUuid(pImageFrom->pBackendData, &ImageUuid);
+ if (RT_FAILURE(rc))
+ RTUuidCreate(&ImageUuid);
+ }
+ rc = pImageFrom->Backend->pfnGetModificationUuid(pImageFrom->pBackendData, &ImageModificationUuid);
+ if (RT_FAILURE(rc))
+ RTUuidClear(&ImageModificationUuid);
+
+ char szComment[1024];
+ rc = pImageFrom->Backend->pfnGetComment(pImageFrom->pBackendData, szComment, sizeof(szComment));
+ if (RT_FAILURE(rc))
+ szComment[0] = '\0';
+ else
+ szComment[sizeof(szComment) - 1] = '\0';
+
+ rc2 = vdThreadFinishRead(pDiskFrom);
+ AssertRC(rc2);
+ fLockReadFrom = false;
+
+ rc2 = vdThreadStartRead(pDiskTo);
+ AssertRC(rc2);
+ unsigned cImagesTo = pDiskTo->cImages;
+ rc2 = vdThreadFinishRead(pDiskTo);
+ AssertRC(rc2);
+
+ if (pszFilename)
+ {
+ if (cbSize == 0)
+ cbSize = cbSizeFrom;
+
+ /* Create destination image with the properties of source image. */
+ /** @todo replace the VDCreateDiff/VDCreateBase calls by direct
+ * calls to the backend. Unifies the code and reduces the API
+ * dependencies. Would also make the synchronization explicit. */
+ if (cImagesTo > 0)
+ {
+ rc = VDCreateDiff(pDiskTo, pszBackend, pszFilename,
+ uImageFlags, szComment, &ImageUuid,
+ NULL /* pParentUuid */,
+ uOpenFlags & ~VD_OPEN_FLAGS_READONLY,
+ pDstVDIfsImage, NULL);
+
+ rc2 = vdThreadStartWrite(pDiskTo);
+ AssertRC(rc2);
+ fLockWriteTo = true;
+ } else {
+ /** @todo hack to force creation of a fixed image for
+ * the RAW backend, which can't handle anything else. */
+ if (!RTStrICmp(pszBackend, "RAW"))
+ uImageFlags |= VD_IMAGE_FLAGS_FIXED;
+
+ vdFixupPCHSGeometry(&PCHSGeometryFrom, cbSize);
+ vdFixupLCHSGeometry(&LCHSGeometryFrom, cbSize);
+
+ rc = VDCreateBase(pDiskTo, pszBackend, pszFilename, cbSize,
+ uImageFlags, szComment,
+ &PCHSGeometryFrom, &LCHSGeometryFrom,
+ NULL, uOpenFlags & ~VD_OPEN_FLAGS_READONLY,
+ pDstVDIfsImage, NULL);
+
+ rc2 = vdThreadStartWrite(pDiskTo);
+ AssertRC(rc2);
+ fLockWriteTo = true;
+
+ if (RT_SUCCESS(rc) && !RTUuidIsNull(&ImageUuid))
+ pDiskTo->pLast->Backend->pfnSetUuid(pDiskTo->pLast->pBackendData, &ImageUuid);
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ pImageTo = pDiskTo->pLast;
+ AssertPtrBreakStmt(pImageTo, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ cbSize = RT_MIN(cbSize, cbSizeFrom);
+ }
+ else
+ {
+ pImageTo = pDiskTo->pLast;
+ AssertPtrBreakStmt(pImageTo, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ uint64_t cbSizeTo;
+ cbSizeTo = vdImageGetSize(pImageTo);
+ if (cbSizeTo == 0)
+ {
+ rc = VERR_VD_VALUE_NOT_FOUND;
+ break;
+ }
+
+ if (cbSize == 0)
+ cbSize = RT_MIN(cbSizeFrom, cbSizeTo);
+
+ vdFixupPCHSGeometry(&PCHSGeometryFrom, cbSize);
+ vdFixupLCHSGeometry(&LCHSGeometryFrom, cbSize);
+
+ /* Update the geometry in the destination image. */
+ pImageTo->Backend->pfnSetPCHSGeometry(pImageTo->pBackendData, &PCHSGeometryFrom);
+ pImageTo->Backend->pfnSetLCHSGeometry(pImageTo->pBackendData, &LCHSGeometryFrom);
+ }
+
+ rc2 = vdThreadFinishWrite(pDiskTo);
+ AssertRC(rc2);
+ fLockWriteTo = false;
+
+ /* Whether we can take the optimized copy path (false) or not.
+ * Don't optimize if the image existed or if it is a child image. */
+ bool fSuppressRedundantIo = ( !(pszFilename == NULL || cImagesTo > 0)
+ || (nImageToSame != VD_IMAGE_CONTENT_UNKNOWN));
+ unsigned cImagesFromReadBack, cImagesToReadBack;
+
+ if (nImageFromSame == VD_IMAGE_CONTENT_UNKNOWN)
+ cImagesFromReadBack = 0;
+ else
+ {
+ if (nImage == VD_LAST_IMAGE)
+ cImagesFromReadBack = pDiskFrom->cImages - nImageFromSame - 1;
+ else
+ cImagesFromReadBack = nImage - nImageFromSame;
+ }
+
+ if (nImageToSame == VD_IMAGE_CONTENT_UNKNOWN)
+ cImagesToReadBack = 0;
+ else
+ cImagesToReadBack = pDiskTo->cImages - nImageToSame - 1;
+
+ /* Copy the data. */
+ rc = vdCopyHelper(pDiskFrom, pImageFrom, pDiskTo, cbSize,
+ cImagesFromReadBack, cImagesToReadBack,
+ fSuppressRedundantIo, pIfProgress, pDstIfProgress);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc2 = vdThreadStartWrite(pDiskTo);
+ AssertRC(rc2);
+ fLockWriteTo = true;
+
+ /* Only set modification UUID if it is non-null, since the source
+ * backend might not provide a valid modification UUID. */
+ if (!RTUuidIsNull(&ImageModificationUuid))
+ pImageTo->Backend->pfnSetModificationUuid(pImageTo->pBackendData, &ImageModificationUuid);
+
+ /* Set the requested open flags if they differ from the value
+ * required for creating the image and copying the contents. */
+ if ( pImageTo && pszFilename
+ && uOpenFlags != (uOpenFlags & ~VD_OPEN_FLAGS_READONLY))
+ rc = pImageTo->Backend->pfnSetOpenFlags(pImageTo->pBackendData,
+ uOpenFlags);
+ }
+ } while (0);
+
+ if (RT_FAILURE(rc) && pImageTo && pszFilename)
+ {
+ /* Take the write lock only if it is not taken. Not worth making the
+ * above code even more complicated. */
+ if (RT_UNLIKELY(!fLockWriteTo))
+ {
+ rc2 = vdThreadStartWrite(pDiskTo);
+ AssertRC(rc2);
+ fLockWriteTo = true;
+ }
+ /* Error detected, but new image created. Remove image from list. */
+ vdRemoveImageFromList(pDiskTo, pImageTo);
+
+ /* Close and delete image. */
+ rc2 = pImageTo->Backend->pfnClose(pImageTo->pBackendData, true);
+ AssertRC(rc2);
+ pImageTo->pBackendData = NULL;
+
+ /* Free remaining resources. */
+ if (pImageTo->pszFilename)
+ RTStrFree(pImageTo->pszFilename);
+
+ RTMemFree(pImageTo);
+ }
+
+ if (RT_UNLIKELY(fLockWriteTo))
+ {
+ rc2 = vdThreadFinishWrite(pDiskTo);
+ AssertRC(rc2);
+ }
+ if (RT_UNLIKELY(fLockWriteFrom))
+ {
+ rc2 = vdThreadFinishWrite(pDiskFrom);
+ AssertRC(rc2);
+ }
+ else if (RT_UNLIKELY(fLockReadFrom))
+ {
+ rc2 = vdThreadFinishRead(pDiskFrom);
+ AssertRC(rc2);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pIfProgress && pIfProgress->pfnProgress)
+ pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100);
+ if (pDstIfProgress && pDstIfProgress->pfnProgress)
+ pDstIfProgress->pfnProgress(pDstIfProgress->Core.pvUser, 100);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDCopy(PVDISK pDiskFrom, unsigned nImage, PVDISK pDiskTo,
+ const char *pszBackend, const char *pszFilename,
+ bool fMoveByRename, uint64_t cbSize,
+ unsigned uImageFlags, PCRTUUID pDstUuid,
+ unsigned uOpenFlags, PVDINTERFACE pVDIfsOperation,
+ PVDINTERFACE pDstVDIfsImage,
+ PVDINTERFACE pDstVDIfsOperation)
+{
+ return VDCopyEx(pDiskFrom, nImage, pDiskTo, pszBackend, pszFilename, fMoveByRename,
+ cbSize, VD_IMAGE_CONTENT_UNKNOWN, VD_IMAGE_CONTENT_UNKNOWN,
+ uImageFlags, pDstUuid, uOpenFlags, pVDIfsOperation,
+ pDstVDIfsImage, pDstVDIfsOperation);
+}
+
+
+VBOXDDU_DECL(int) VDCompact(PVDISK pDisk, unsigned nImage,
+ PVDINTERFACE pVDIfsOperation)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockRead = false, fLockWrite = false;
+ void *pvBuf = NULL;
+ void *pvTmp = NULL;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pVDIfsOperation=%#p\n",
+ pDisk, nImage, pVDIfsOperation));
+ /* Check arguments. */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE,
+ ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ do {
+ rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = true;
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ /* If there is no compact callback for not file based backends then
+ * the backend doesn't need compaction. No need to make much fuss about
+ * this. For file based ones signal this as not yet supported. */
+ if (!pImage->Backend->pfnCompact)
+ {
+ if (pImage->Backend->uBackendCaps & VD_CAP_FILE)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ /* Insert interface for reading parent state into per-operation list,
+ * if there is a parent image. */
+ VDINTERFACEPARENTSTATE VDIfParent;
+ VDPARENTSTATEDESC ParentUser;
+ if (pImage->pPrev)
+ {
+ VDIfParent.pfnParentRead = vdParentRead;
+ ParentUser.pDisk = pDisk;
+ ParentUser.pImage = pImage->pPrev;
+ rc = VDInterfaceAdd(&VDIfParent.Core, "VDCompact_ParentState", VDINTERFACETYPE_PARENTSTATE,
+ &ParentUser, sizeof(VDINTERFACEPARENTSTATE), &pVDIfsOperation);
+ AssertRC(rc);
+ }
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = false;
+
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ rc = pImage->Backend->pfnCompact(pImage->pBackendData,
+ 0, 99,
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ pVDIfsOperation);
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+ else if (RT_UNLIKELY(fLockRead))
+ {
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ }
+
+ if (pvBuf)
+ RTMemTmpFree(pvBuf);
+ if (pvTmp)
+ RTMemTmpFree(pvTmp);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pIfProgress && pIfProgress->pfnProgress)
+ pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDResize(PVDISK pDisk, uint64_t cbSize,
+ PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry,
+ PVDINTERFACE pVDIfsOperation)
+{
+ /** @todo r=klaus resizing was designed to be part of VDCopy, so having a separate function is not desirable. */
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockRead = false, fLockWrite = false;
+
+ LogFlowFunc(("pDisk=%#p cbSize=%llu pVDIfsOperation=%#p\n",
+ pDisk, cbSize, pVDIfsOperation));
+ /* Check arguments. */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE,
+ ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ do {
+ rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = true;
+
+ /* Must have at least one image in the chain, will resize last. */
+ AssertMsgBreakStmt(pDisk->cImages >= 1, ("cImages=%u\n", pDisk->cImages),
+ rc = VERR_NOT_SUPPORTED);
+
+ PVDIMAGE pImage = pDisk->pLast;
+
+ /* If there is no compact callback for not file based backends then
+ * the backend doesn't need compaction. No need to make much fuss about
+ * this. For file based ones signal this as not yet supported. */
+ if (!pImage->Backend->pfnResize)
+ {
+ if (pImage->Backend->uBackendCaps & VD_CAP_FILE)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = false;
+
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ VDGEOMETRY PCHSGeometryOld;
+ VDGEOMETRY LCHSGeometryOld;
+ PCVDGEOMETRY pPCHSGeometryNew;
+ PCVDGEOMETRY pLCHSGeometryNew;
+
+ if (pPCHSGeometry->cCylinders == 0)
+ {
+ /* Auto-detect marker, calculate new value ourself. */
+ rc = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData, &PCHSGeometryOld);
+ if (RT_SUCCESS(rc) && (PCHSGeometryOld.cCylinders != 0))
+ PCHSGeometryOld.cCylinders = RT_MIN(cbSize / 512 / PCHSGeometryOld.cHeads / PCHSGeometryOld.cSectors, 16383);
+ else if (rc == VERR_VD_GEOMETRY_NOT_SET)
+ rc = VINF_SUCCESS;
+
+ pPCHSGeometryNew = &PCHSGeometryOld;
+ }
+ else
+ pPCHSGeometryNew = pPCHSGeometry;
+
+ if (pLCHSGeometry->cCylinders == 0)
+ {
+ /* Auto-detect marker, calculate new value ourself. */
+ rc = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData, &LCHSGeometryOld);
+ if (RT_SUCCESS(rc) && (LCHSGeometryOld.cCylinders != 0))
+ LCHSGeometryOld.cCylinders = cbSize / 512 / LCHSGeometryOld.cHeads / LCHSGeometryOld.cSectors;
+ else if (rc == VERR_VD_GEOMETRY_NOT_SET)
+ rc = VINF_SUCCESS;
+
+ pLCHSGeometryNew = &LCHSGeometryOld;
+ }
+ else
+ pLCHSGeometryNew = pLCHSGeometry;
+
+ if (RT_SUCCESS(rc))
+ rc = pImage->Backend->pfnResize(pImage->pBackendData,
+ cbSize,
+ pPCHSGeometryNew,
+ pLCHSGeometryNew,
+ 0, 99,
+ pDisk->pVDIfsDisk,
+ pImage->pVDIfsImage,
+ pVDIfsOperation);
+ /* Mark the image size as uninitialized so it gets recalculated the next time. */
+ if (RT_SUCCESS(rc))
+ pImage->cbImage = VD_IMAGE_SIZE_UNINITIALIZED;
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+ else if (RT_UNLIKELY(fLockRead))
+ {
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pIfProgress && pIfProgress->pfnProgress)
+ pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100);
+
+ pDisk->cbSize = cbSize;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+VBOXDDU_DECL(int) VDPrepareWithFilters(PVDISK pDisk, PVDINTERFACE pVDIfsOperation)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockRead = false, fLockWrite = false;
+
+ LogFlowFunc(("pDisk=%#p pVDIfsOperation=%#p\n", pDisk, pVDIfsOperation));
+ /* Check arguments. */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE,
+ ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ do {
+ rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = true;
+
+ /* Must have at least one image in the chain. */
+ AssertMsgBreakStmt(pDisk->cImages >= 1, ("cImages=%u\n", pDisk->cImages),
+ rc = VERR_VD_NOT_OPENED);
+
+ unsigned uOpenFlags = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pBackendData);
+ AssertMsgBreakStmt(!(uOpenFlags & VD_OPEN_FLAGS_READONLY),
+ ("Last image should be read write"),
+ rc = VERR_VD_IMAGE_READ_ONLY);
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = false;
+
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ /*
+ * Open all images in the chain in read write mode first to avoid running
+ * into an error in the middle of the process.
+ */
+ PVDIMAGE pImage = pDisk->pBase;
+
+ while (pImage)
+ {
+ uOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pBackendData);
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ /*
+ * Clear skip consistency checks because the image is made writable now and
+ * skipping consistency checks is only possible for readonly images.
+ */
+ uOpenFlags &= ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS);
+ rc = pImage->Backend->pfnSetOpenFlags(pImage->pBackendData, uOpenFlags);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ pImage = pImage->pNext;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ unsigned cImgCur = 0;
+ unsigned uPercentStart = 0;
+ unsigned uPercentSpan = 100 / pDisk->cImages - 1;
+
+ /* Allocate tmp buffer. */
+ void *pvBuf = RTMemTmpAlloc(VD_MERGE_BUFFER_SIZE);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pImage = pDisk->pBase;
+ pDisk->fLocked = true;
+
+ while ( pImage
+ && RT_SUCCESS(rc))
+ {
+ /* Get size of image. */
+ uint64_t cbSize = vdImageGetSize(pImage);
+ uint64_t cbSizeFile = pImage->Backend->pfnGetFileSize(pImage->pBackendData);
+ uint64_t cbFileWritten = 0;
+ uint64_t uOffset = 0;
+ uint64_t cbRemaining = cbSize;
+
+ do
+ {
+ size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining);
+ RTSGSEG SegmentBuf;
+ RTSGBUF SgBuf;
+ VDIOCTX IoCtx;
+
+ SegmentBuf.pvSeg = pvBuf;
+ SegmentBuf.cbSeg = VD_MERGE_BUFFER_SIZE;
+ RTSgBufInit(&SgBuf, &SegmentBuf, 1);
+ vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_READ, 0, 0, NULL,
+ &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC);
+
+ rc = pImage->Backend->pfnRead(pImage->pBackendData, uOffset,
+ cbThisRead, &IoCtx, &cbThisRead);
+ if (rc != VERR_VD_BLOCK_FREE)
+ {
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Apply filter chains. */
+ rc = vdFilterChainApplyRead(pDisk, uOffset, cbThisRead, &IoCtx);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vdFilterChainApplyWrite(pDisk, uOffset, cbThisRead, &IoCtx);
+ if (RT_FAILURE(rc))
+ break;
+
+ RTSgBufReset(&SgBuf);
+ size_t cbThisWrite = 0;
+ size_t cbPreRead = 0;
+ size_t cbPostRead = 0;
+ rc = pImage->Backend->pfnWrite(pImage->pBackendData, uOffset,
+ cbThisRead, &IoCtx, &cbThisWrite,
+ &cbPreRead, &cbPostRead, 0);
+ if (RT_FAILURE(rc))
+ break;
+ Assert(cbThisWrite == cbThisRead);
+ cbFileWritten += cbThisWrite;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ uOffset += cbThisRead;
+ cbRemaining -= cbThisRead;
+
+ if (pIfProgress && pIfProgress->pfnProgress)
+ {
+ rc2 = pIfProgress->pfnProgress(pIfProgress->Core.pvUser,
+ uPercentStart + cbFileWritten * uPercentSpan / cbSizeFile);
+ AssertRC(rc2); /* Cancelling this operation without leaving an inconsistent state is not possible. */
+ }
+ } while (uOffset < cbSize);
+
+ pImage = pImage->pNext;
+ cImgCur++;
+ uPercentStart += uPercentSpan;
+ }
+
+ pDisk->fLocked = false;
+ if (pvBuf)
+ RTMemTmpFree(pvBuf);
+ }
+
+ /* Change images except last one back to readonly. */
+ pImage = pDisk->pBase;
+ while ( pImage != pDisk->pLast
+ && pImage)
+ {
+ uOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pBackendData);
+ uOpenFlags |= VD_OPEN_FLAGS_READONLY;
+ rc2 = pImage->Backend->pfnSetOpenFlags(pImage->pBackendData, uOpenFlags);
+ if (RT_FAILURE(rc2))
+ {
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ break;
+ }
+ pImage = pImage->pNext;
+ }
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+ else if (RT_UNLIKELY(fLockRead))
+ {
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pIfProgress
+ && pIfProgress->pfnProgress)
+ pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDClose(PVDISK pDisk, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockWrite = false;
+
+ LogFlowFunc(("pDisk=%#p fDelete=%d\n", pDisk, fDelete));
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Not worth splitting this up into a read lock phase and write
+ * lock phase, as closing an image is a relatively fast operation
+ * dominated by the part which needs the write lock. */
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ PVDIMAGE pImage = pDisk->pLast;
+ if (!pImage)
+ {
+ rc = VERR_VD_NOT_OPENED;
+ break;
+ }
+
+ /* Destroy the current discard state first which might still have pending blocks. */
+ rc = vdDiscardStateDestroy(pDisk);
+ if (RT_FAILURE(rc))
+ break;
+
+ unsigned uOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pBackendData);
+ /* Remove image from list of opened images. */
+ vdRemoveImageFromList(pDisk, pImage);
+ /* Close (and optionally delete) image. */
+ rc = pImage->Backend->pfnClose(pImage->pBackendData, fDelete);
+ /* Free remaining resources related to the image. */
+ RTStrFree(pImage->pszFilename);
+ RTMemFree(pImage);
+
+ pImage = pDisk->pLast;
+ if (!pImage)
+ break;
+
+ /* If disk was previously in read/write mode, make sure it will stay
+ * like this (if possible) after closing this image. Set the open flags
+ * accordingly. */
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ uOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pBackendData);
+ uOpenFlags &= ~ VD_OPEN_FLAGS_READONLY;
+ rc = pImage->Backend->pfnSetOpenFlags(pImage->pBackendData, uOpenFlags);
+ }
+
+ /* Cache disk information. */
+ pDisk->cbSize = vdImageGetSize(pImage);
+
+ /* Cache PCHS geometry. */
+ rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData,
+ &pDisk->PCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->PCHSGeometry.cCylinders = 0;
+ pDisk->PCHSGeometry.cHeads = 0;
+ pDisk->PCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the PCHS geometry is properly clipped. */
+ pDisk->PCHSGeometry.cCylinders = RT_MIN(pDisk->PCHSGeometry.cCylinders, 16383);
+ pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 16);
+ pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63);
+ }
+
+ /* Cache LCHS geometry. */
+ rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData,
+ &pDisk->LCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->LCHSGeometry.cCylinders = 0;
+ pDisk->LCHSGeometry.cHeads = 0;
+ pDisk->LCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the LCHS geometry is properly clipped. */
+ pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255);
+ pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63);
+ }
+ } while (0);
+
+ if (RT_UNLIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDCacheClose(PVDISK pDisk, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockWrite = false;
+ PVDCACHE pCache = NULL;
+
+ LogFlowFunc(("pDisk=%#p fDelete=%d\n", pDisk, fDelete));
+
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ AssertPtrBreakStmt(pDisk->pCache, rc = VERR_VD_CACHE_NOT_FOUND);
+
+ pCache = pDisk->pCache;
+ pDisk->pCache = NULL;
+
+ pCache->Backend->pfnClose(pCache->pBackendData, fDelete);
+ if (pCache->pszFilename)
+ RTStrFree(pCache->pszFilename);
+ RTMemFree(pCache);
+ } while (0);
+
+ if (RT_LIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+VBOXDDU_DECL(int) VDFilterRemove(PVDISK pDisk, uint32_t fFlags)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockWrite = false;
+ PVDFILTER pFilter = NULL;
+
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+
+ do
+ {
+ /* sanity check */
+ AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ AssertMsgBreakStmt(!(fFlags & ~VD_FILTER_FLAGS_MASK),
+ ("Invalid flags set (fFlags=%#x)\n", fFlags),
+ rc = VERR_INVALID_PARAMETER);
+
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+ fLockWrite = true;
+
+ if (fFlags & VD_FILTER_FLAGS_WRITE)
+ {
+ AssertBreakStmt(!RTListIsEmpty(&pDisk->ListFilterChainWrite), rc = VERR_VD_NOT_OPENED);
+ pFilter = RTListGetLast(&pDisk->ListFilterChainWrite, VDFILTER, ListNodeChainWrite);
+ AssertPtr(pFilter);
+ RTListNodeRemove(&pFilter->ListNodeChainWrite);
+ vdFilterRelease(pFilter);
+ }
+
+ if (fFlags & VD_FILTER_FLAGS_READ)
+ {
+ AssertBreakStmt(!RTListIsEmpty(&pDisk->ListFilterChainRead), rc = VERR_VD_NOT_OPENED);
+ pFilter = RTListGetLast(&pDisk->ListFilterChainRead, VDFILTER, ListNodeChainRead);
+ AssertPtr(pFilter);
+ RTListNodeRemove(&pFilter->ListNodeChainRead);
+ vdFilterRelease(pFilter);
+ }
+ } while (0);
+
+ if (RT_LIKELY(fLockWrite))
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDCloseAll(PVDISK pDisk)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Lock the entire operation. */
+ int rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ PVDCACHE pCache = pDisk->pCache;
+ if (pCache)
+ {
+ rc2 = pCache->Backend->pfnClose(pCache->pBackendData, false);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (pCache->pszFilename)
+ RTStrFree(pCache->pszFilename);
+ RTMemFree(pCache);
+ }
+
+ PVDIMAGE pImage = pDisk->pLast;
+ while (RT_VALID_PTR(pImage))
+ {
+ PVDIMAGE pPrev = pImage->pPrev;
+ /* Remove image from list of opened images. */
+ vdRemoveImageFromList(pDisk, pImage);
+ /* Close image. */
+ rc2 = pImage->Backend->pfnClose(pImage->pBackendData, false);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ /* Free remaining resources related to the image. */
+ RTStrFree(pImage->pszFilename);
+ RTMemFree(pImage);
+ pImage = pPrev;
+ }
+ Assert(!RT_VALID_PTR(pDisk->pLast));
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDFilterRemoveAll(PVDISK pDisk)
+{
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Lock the entire operation. */
+ int rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ PVDFILTER pFilter, pFilterNext;
+ RTListForEachSafe(&pDisk->ListFilterChainWrite, pFilter, pFilterNext, VDFILTER, ListNodeChainWrite)
+ {
+ RTListNodeRemove(&pFilter->ListNodeChainWrite);
+ vdFilterRelease(pFilter);
+ }
+
+ RTListForEachSafe(&pDisk->ListFilterChainRead, pFilter, pFilterNext, VDFILTER, ListNodeChainRead)
+ {
+ RTListNodeRemove(&pFilter->ListNodeChainRead);
+ vdFilterRelease(pFilter);
+ }
+ Assert(RTListIsEmpty(&pDisk->ListFilterChainRead));
+ Assert(RTListIsEmpty(&pDisk->ListFilterChainWrite));
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+
+VBOXDDU_DECL(int) VDRead(PVDISK pDisk, uint64_t uOffset, void *pvBuf,
+ size_t cbRead)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+ bool fLockRead = false;
+
+ LogFlowFunc(("pDisk=%#p uOffset=%llu pvBuf=%p cbRead=%zu\n",
+ pDisk, uOffset, pvBuf, cbRead));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbRead > 0, VERR_INVALID_PARAMETER);
+
+ do
+ {
+ rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+ fLockRead = true;
+
+ AssertMsgBreakStmt( uOffset < pDisk->cbSize
+ && cbRead <= pDisk->cbSize - uOffset,
+ ("uOffset=%llu cbRead=%zu pDisk->cbSize=%llu\n",
+ uOffset, cbRead, pDisk->cbSize),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED);
+
+ if (uOffset + cbRead > pDisk->cbSize)
+ {
+ /* Floppy images might be smaller than the standard expected by
+ the floppy controller code. So, we won't fail here. */
+ AssertMsgBreakStmt(pDisk->enmType == VDTYPE_FLOPPY,
+ ("uOffset=%llu cbRead=%zu pDisk->cbSize=%llu\n",
+ uOffset, cbRead, pDisk->cbSize),
+ rc = VERR_EOF);
+ memset(pvBuf, 0xf6, cbRead); /* f6h = format.com filler byte */
+ if (uOffset >= pDisk->cbSize)
+ break;
+ cbRead = pDisk->cbSize - uOffset;
+ }
+
+ rc = vdReadHelper(pDisk, pImage, uOffset, pvBuf, cbRead,
+ true /* fUpdateCache */);
+ } while (0);
+
+ if (RT_UNLIKELY(fLockRead))
+ {
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDWrite(PVDISK pDisk, uint64_t uOffset, const void *pvBuf,
+ size_t cbWrite)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+
+ LogFlowFunc(("pDisk=%#p uOffset=%llu pvBuf=%p cbWrite=%zu\n",
+ pDisk, uOffset, pvBuf, cbWrite));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbWrite > 0, VERR_INVALID_PARAMETER);
+
+ do
+ {
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ AssertMsgBreakStmt( uOffset < pDisk->cbSize
+ && cbWrite <= pDisk->cbSize - uOffset,
+ ("uOffset=%llu cbWrite=%zu pDisk->cbSize=%llu\n",
+ uOffset, cbWrite, pDisk->cbSize),
+ rc = VERR_INVALID_PARAMETER);
+
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED);
+
+ vdSetModifiedFlag(pDisk);
+ rc = vdWriteHelper(pDisk, pImage, uOffset, pvBuf, cbWrite,
+ VDIOCTX_FLAGS_READ_UPDATE_CACHE);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* If there is a merge (in the direction towards a parent) running
+ * concurrently then we have to also "relay" the write to this parent,
+ * as the merge position might be already past the position where
+ * this write is going. The "context" of the write can come from the
+ * natural chain, since merging either already did or will take care
+ * of the "other" content which is might be needed to fill the block
+ * to a full allocation size. The cache doesn't need to be touched
+ * as this write is covered by the previous one. */
+ if (RT_UNLIKELY(pDisk->pImageRelay))
+ rc = vdWriteHelper(pDisk, pDisk->pImageRelay, uOffset,
+ pvBuf, cbWrite, VDIOCTX_FLAGS_DEFAULT);
+ } while (0);
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDFlush(PVDISK pDisk)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ do
+ {
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED);
+
+ VDIOCTX IoCtx;
+ RTSEMEVENT hEventComplete = NIL_RTSEMEVENT;
+
+ rc = RTSemEventCreate(&hEventComplete);
+ if (RT_FAILURE(rc))
+ break;
+
+ vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_FLUSH, 0, 0, pImage, NULL,
+ NULL, vdFlushHelperAsync, VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE);
+
+ IoCtx.Type.Root.pfnComplete = vdIoCtxSyncComplete;
+ IoCtx.Type.Root.pvUser1 = pDisk;
+ IoCtx.Type.Root.pvUser2 = hEventComplete;
+ rc = vdIoCtxProcessSync(&IoCtx, hEventComplete);
+
+ RTSemEventDestroy(hEventComplete);
+ } while (0);
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(unsigned) VDGetCount(PVDISK pDisk)
+{
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+
+ /* sanity check */
+ AssertPtrReturn(pDisk, 0);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ unsigned cImages = pDisk->cImages;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %u\n", cImages));
+ return cImages;
+}
+
+
+VBOXDDU_DECL(bool) VDIsReadOnly(PVDISK pDisk)
+{
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ /* sanity check */
+ AssertPtrReturn(pDisk, true);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ bool fReadOnly = true;
+ PVDIMAGE pImage = pDisk->pLast;
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ unsigned uOpenFlags = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pBackendData);
+ fReadOnly = !!(uOpenFlags & VD_OPEN_FLAGS_READONLY);
+ }
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %d\n", fReadOnly));
+ return fReadOnly;
+}
+
+
+VBOXDDU_DECL(uint32_t) VDGetSectorSize(PVDISK pDisk, unsigned nImage)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u\n", pDisk, nImage));
+ /* sanity check */
+ AssertPtrReturn(pDisk, 0);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Do the job. */
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ uint64_t cbSector = 0;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ PCVDREGIONLIST pRegionList = NULL;
+ int rc = pImage->Backend->pfnQueryRegions(pImage->pBackendData, &pRegionList);
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsg(pRegionList->cRegions == 1, ("%u\n", pRegionList->cRegions));
+ if (pRegionList->cRegions == 1)
+ {
+ cbSector = pRegionList->aRegions[0].cbBlock;
+
+ AssertPtr(pImage->Backend->pfnRegionListRelease);
+ pImage->Backend->pfnRegionListRelease(pImage->pBackendData, pRegionList);
+ }
+ }
+ }
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %u\n", cbSector));
+ return cbSector;
+}
+
+
+VBOXDDU_DECL(uint64_t) VDGetSize(PVDISK pDisk, unsigned nImage)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u\n", pDisk, nImage));
+ /* sanity check */
+ AssertPtrReturn(pDisk, 0);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Do the job. */
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ uint64_t cbSize;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ cbSize = vdImageGetSize(pImage);
+ else
+ cbSize = 0;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %llu (%#RX64)\n", cbSize, cbSize));
+ return cbSize;
+}
+
+
+VBOXDDU_DECL(uint64_t) VDGetFileSize(PVDISK pDisk, unsigned nImage)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u\n", pDisk, nImage));
+
+ /* sanity check */
+ AssertPtrReturn(pDisk, 0);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ uint64_t cbSize = 0;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ cbSize = pImage->Backend->pfnGetFileSize(pImage->pBackendData);
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %llu (%#RX64)\n", cbSize, cbSize));
+ return cbSize;
+}
+
+
+VBOXDDU_DECL(int) VDGetPCHSGeometry(PVDISK pDisk, unsigned nImage,
+ PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pPCHSGeometry=%#p\n",
+ pDisk, nImage, pPCHSGeometry));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER);
+
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ int rc;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ if (pImage == pDisk->pLast)
+ {
+ /* Use cached information if possible. */
+ if (pDisk->PCHSGeometry.cCylinders != 0)
+ {
+ *pPCHSGeometry = pDisk->PCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData, pPCHSGeometry);
+ }
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("%Rrc (PCHS=%u/%u/%u)\n", rc,
+ pDisk->PCHSGeometry.cCylinders, pDisk->PCHSGeometry.cHeads,
+ pDisk->PCHSGeometry.cSectors));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDSetPCHSGeometry(PVDISK pDisk, unsigned nImage,
+ PCVDGEOMETRY pPCHSGeometry)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pDisk, nImage, pPCHSGeometry, pPCHSGeometry->cCylinders,
+ pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER);
+ AssertMsgReturn( pPCHSGeometry->cHeads <= 16
+ && pPCHSGeometry->cSectors <= 63,
+ ("PCHS=%u/%u/%u\n", pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors),
+ VERR_INVALID_PARAMETER);
+ do
+ {
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ if (pImage == pDisk->pLast)
+ {
+ if ( pPCHSGeometry->cCylinders != pDisk->PCHSGeometry.cCylinders
+ || pPCHSGeometry->cHeads != pDisk->PCHSGeometry.cHeads
+ || pPCHSGeometry->cSectors != pDisk->PCHSGeometry.cSectors)
+ {
+ /* Only update geometry if it is changed. Avoids similar checks
+ * in every backend. Most of the time the new geometry is set
+ * to the previous values, so no need to go through the hassle
+ * of updating an image which could be opened in read-only mode
+ * right now. */
+ rc = pImage->Backend->pfnSetPCHSGeometry(pImage->pBackendData,
+ pPCHSGeometry);
+
+ /* Cache new geometry values in any case. */
+ rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData,
+ &pDisk->PCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->PCHSGeometry.cCylinders = 0;
+ pDisk->PCHSGeometry.cHeads = 0;
+ pDisk->PCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the CHS geometry is properly clipped. */
+ pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 255);
+ pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63);
+ }
+ }
+ }
+ else
+ {
+ VDGEOMETRY PCHS;
+ rc = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData,
+ &PCHS);
+ if ( RT_FAILURE(rc)
+ || pPCHSGeometry->cCylinders != PCHS.cCylinders
+ || pPCHSGeometry->cHeads != PCHS.cHeads
+ || pPCHSGeometry->cSectors != PCHS.cSectors)
+ {
+ /* Only update geometry if it is changed. Avoids similar checks
+ * in every backend. Most of the time the new geometry is set
+ * to the previous values, so no need to go through the hassle
+ * of updating an image which could be opened in read-only mode
+ * right now. */
+ rc = pImage->Backend->pfnSetPCHSGeometry(pImage->pBackendData,
+ pPCHSGeometry);
+ }
+ }
+ } while (0);
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDGetLCHSGeometry(PVDISK pDisk, unsigned nImage,
+ PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pLCHSGeometry=%#p\n",
+ pDisk, nImage, pLCHSGeometry));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ int rc = VINF_SUCCESS;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ if (pImage == pDisk->pLast)
+ {
+ /* Use cached information if possible. */
+ if (pDisk->LCHSGeometry.cCylinders != 0)
+ *pLCHSGeometry = pDisk->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData, pLCHSGeometry);
+ }
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc((": %Rrc (LCHS=%u/%u/%u)\n", rc,
+ pDisk->LCHSGeometry.cCylinders, pDisk->LCHSGeometry.cHeads,
+ pDisk->LCHSGeometry.cSectors));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDSetLCHSGeometry(PVDISK pDisk, unsigned nImage,
+ PCVDGEOMETRY pLCHSGeometry)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pLCHSGeometry=%#p LCHS=%u/%u/%u\n",
+ pDisk, nImage, pLCHSGeometry, pLCHSGeometry->cCylinders,
+ pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+ AssertMsgReturn( pLCHSGeometry->cHeads <= 255
+ && pLCHSGeometry->cSectors <= 63,
+ ("LCHS=%u/%u/%u\n", pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors),
+ VERR_INVALID_PARAMETER);
+
+ do
+ {
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND);
+
+ if (pImage == pDisk->pLast)
+ {
+ if ( pLCHSGeometry->cCylinders != pDisk->LCHSGeometry.cCylinders
+ || pLCHSGeometry->cHeads != pDisk->LCHSGeometry.cHeads
+ || pLCHSGeometry->cSectors != pDisk->LCHSGeometry.cSectors)
+ {
+ /* Only update geometry if it is changed. Avoids similar checks
+ * in every backend. Most of the time the new geometry is set
+ * to the previous values, so no need to go through the hassle
+ * of updating an image which could be opened in read-only mode
+ * right now. */
+ rc = pImage->Backend->pfnSetLCHSGeometry(pImage->pBackendData,
+ pLCHSGeometry);
+
+ /* Cache new geometry values in any case. */
+ rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData,
+ &pDisk->LCHSGeometry);
+ if (RT_FAILURE(rc2))
+ {
+ pDisk->LCHSGeometry.cCylinders = 0;
+ pDisk->LCHSGeometry.cHeads = 0;
+ pDisk->LCHSGeometry.cSectors = 0;
+ }
+ else
+ {
+ /* Make sure the CHS geometry is properly clipped. */
+ pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255);
+ pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63);
+ }
+ }
+ }
+ else
+ {
+ VDGEOMETRY LCHS;
+ rc = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData,
+ &LCHS);
+ if ( RT_FAILURE(rc)
+ || pLCHSGeometry->cCylinders != LCHS.cCylinders
+ || pLCHSGeometry->cHeads != LCHS.cHeads
+ || pLCHSGeometry->cSectors != LCHS.cSectors)
+ {
+ /* Only update geometry if it is changed. Avoids similar checks
+ * in every backend. Most of the time the new geometry is set
+ * to the previous values, so no need to go through the hassle
+ * of updating an image which could be opened in read-only mode
+ * right now. */
+ rc = pImage->Backend->pfnSetLCHSGeometry(pImage->pBackendData,
+ pLCHSGeometry);
+ }
+ }
+ } while (0);
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDQueryRegions(PVDISK pDisk, unsigned nImage, uint32_t fFlags,
+ PPVDREGIONLIST ppRegionList)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u fFlags=%#x ppRegionList=%#p\n",
+ pDisk, nImage, fFlags, ppRegionList));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(ppRegionList, VERR_INVALID_POINTER);
+
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ int rc;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ PCVDREGIONLIST pRegionList = NULL;
+ rc = pImage->Backend->pfnQueryRegions(pImage->pBackendData, &pRegionList);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdRegionListConv(pRegionList, fFlags, ppRegionList);
+
+ AssertPtr(pImage->Backend->pfnRegionListRelease);
+ pImage->Backend->pfnRegionListRelease(pImage->pBackendData, pRegionList);
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc((": %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(void) VDRegionListFree(PVDREGIONLIST pRegionList)
+{
+ RTMemFree(pRegionList);
+}
+
+
+VBOXDDU_DECL(int) VDGetVersion(PVDISK pDisk, unsigned nImage,
+ unsigned *puVersion)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u puVersion=%#p\n",
+ pDisk, nImage, puVersion));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(puVersion, VERR_INVALID_POINTER);
+
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ int rc = VINF_SUCCESS;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ *puVersion = pImage->Backend->pfnGetVersion(pImage->pBackendData);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc uVersion=%#x\n", rc, *puVersion));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDBackendInfoSingle(PVDISK pDisk, unsigned nImage,
+ PVDBACKENDINFO pBackendInfo)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDisk=%#p nImage=%u pBackendInfo=%#p\n",
+ pDisk, nImage, pBackendInfo));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pBackendInfo, VERR_INVALID_POINTER);
+
+ /* Do the job. */
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ pBackendInfo->pszBackend = pImage->Backend->pszBackendName;
+ pBackendInfo->uBackendCaps = pImage->Backend->uBackendCaps;
+ pBackendInfo->paFileExtensions = pImage->Backend->paFileExtensions;
+ pBackendInfo->paConfigInfo = pImage->Backend->paConfigInfo;
+ }
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDGetImageFlags(PVDISK pDisk, unsigned nImage,
+ unsigned *puImageFlags)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u puImageFlags=%#p\n",
+ pDisk, nImage, puImageFlags));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(puImageFlags, VERR_INVALID_POINTER);
+
+ /* Do the job. */
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ int rc = VINF_SUCCESS;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ *puImageFlags = pImage->uImageFlags;
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc uImageFlags=%#x\n", rc, *puImageFlags));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDGetOpenFlags(PVDISK pDisk, unsigned nImage,
+ unsigned *puOpenFlags)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u puOpenFlags=%#p\n",
+ pDisk, nImage, puOpenFlags));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(puOpenFlags, VERR_INVALID_POINTER);
+
+ /* Do the job. */
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ int rc = VINF_SUCCESS;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ *puOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pBackendData);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc uOpenFlags=%#x\n", rc, *puOpenFlags));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDSetOpenFlags(PVDISK pDisk, unsigned nImage,
+ unsigned uOpenFlags)
+{
+ LogFlowFunc(("pDisk=%#p uOpenFlags=%#u\n", pDisk, uOpenFlags));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertMsgReturn((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, ("uOpenFlags=%#x\n", uOpenFlags),
+ VERR_INVALID_PARAMETER);
+
+ /* Do the job. */
+ int rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ /* Destroy any discard state because the image might be changed to readonly mode. */
+ int rc = vdDiscardStateDestroy(pDisk);
+ if (RT_SUCCESS(rc))
+ {
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ rc = pImage->Backend->pfnSetOpenFlags(pImage->pBackendData,
+ uOpenFlags & ~(VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_IGNORE_FLUSH
+ | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS));
+ if (RT_SUCCESS(rc))
+ pImage->uOpenFlags = uOpenFlags & (VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_DISCARD | VD_OPEN_FLAGS_IGNORE_FLUSH
+ | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS);
+ }
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+ }
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDGetFilename(PVDISK pDisk, unsigned nImage,
+ char *pszFilename, unsigned cbFilename)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pszFilename=%#p cbFilename=%u\n",
+ pDisk, nImage, pszFilename, cbFilename));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(cbFilename > 0, VERR_INVALID_PARAMETER);
+
+ /* Do the job. */
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ int rc;
+ if (pImage)
+ rc = RTStrCopy(pszFilename, cbFilename, pImage->pszFilename);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc, pszFilename=\"%s\"\n", rc, pszFilename));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDGetComment(PVDISK pDisk, unsigned nImage,
+ char *pszComment, unsigned cbComment)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pszComment=%#p cbComment=%u\n",
+ pDisk, nImage, pszComment, cbComment));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pszComment, VERR_INVALID_POINTER);
+ AssertReturn(cbComment > 0, VERR_INVALID_PARAMETER);
+
+ /* Do the job. */
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ int rc;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ rc = pImage->Backend->pfnGetComment(pImage->pBackendData, pszComment, cbComment);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc, pszComment=\"%s\"\n", rc, pszComment));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDSetComment(PVDISK pDisk, unsigned nImage,
+ const char *pszComment)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pszComment=%#p \"%s\"\n",
+ pDisk, nImage, pszComment, pszComment));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrNullReturn(pszComment, VERR_INVALID_POINTER);
+
+ /* Do the job. */
+ int rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ int rc;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ rc = pImage->Backend->pfnSetComment(pImage->pBackendData, pszComment);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDGetUuid(PVDISK pDisk, unsigned nImage, PRTUUID pUuid)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p\n", pDisk, nImage, pUuid));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pUuid, VERR_INVALID_POINTER);
+
+ /* Do the job. */
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ int rc;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ rc = pImage->Backend->pfnGetUuid(pImage->pBackendData, pUuid);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc, Uuid={%RTuuid}\n", rc, pUuid));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDSetUuid(PVDISK pDisk, unsigned nImage, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p {%RTuuid}\n",
+ pDisk, nImage, pUuid, pUuid));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ RTUUID Uuid;
+ if (pUuid)
+ AssertPtrReturn(pUuid, VERR_INVALID_POINTER);
+ else
+ {
+ int rc = RTUuidCreate(&Uuid);
+ AssertRCReturn(rc, rc);
+ pUuid = &Uuid;
+ }
+
+ /* Do the job. */
+ int rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ int rc;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ rc = pImage->Backend->pfnSetUuid(pImage->pBackendData, pUuid);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDGetModificationUuid(PVDISK pDisk, unsigned nImage, PRTUUID pUuid)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p\n", pDisk, nImage, pUuid));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pUuid, VERR_INVALID_POINTER);
+
+ /* Do the job. */
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ int rc;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ rc = pImage->Backend->pfnGetModificationUuid(pImage->pBackendData, pUuid);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc, Uuid={%RTuuid}\n", rc, pUuid));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDSetModificationUuid(PVDISK pDisk, unsigned nImage, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p {%RTuuid}\n",
+ pDisk, nImage, pUuid, pUuid));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ RTUUID Uuid;
+ if (pUuid)
+ AssertPtrReturn(pUuid, VERR_INVALID_POINTER);
+ else
+ {
+ int rc = RTUuidCreate(&Uuid);
+ AssertRCReturn(rc, rc);
+ pUuid = &Uuid;
+ }
+
+ /* Do the job. */
+ int rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ int rc;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ if (pImage)
+ rc = pImage->Backend->pfnSetModificationUuid(pImage->pBackendData, pUuid);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDGetParentUuid(PVDISK pDisk, unsigned nImage,
+ PRTUUID pUuid)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p\n", pDisk, nImage, pUuid));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertPtrReturn(pUuid, VERR_INVALID_POINTER);
+
+ /* Do the job. */
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ int rc;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ rc = pImage->Backend->pfnGetParentUuid(pImage->pBackendData, pUuid);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc, Uuid={%RTuuid}\n", rc, pUuid));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDSetParentUuid(PVDISK pDisk, unsigned nImage,
+ PCRTUUID pUuid)
+{
+ LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p {%RTuuid}\n",
+ pDisk, nImage, pUuid, pUuid));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ RTUUID Uuid;
+ if (pUuid)
+ AssertPtrReturn(pUuid, VERR_INVALID_POINTER);
+ else
+ {
+ int rc = RTUuidCreate(&Uuid);
+ AssertRCReturn(rc, rc);
+ pUuid = &Uuid;
+ }
+
+ /* Do the job. */
+ int rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ int rc;
+ PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage);
+ AssertPtr(pImage);
+ if (pImage)
+ rc = pImage->Backend->pfnSetParentUuid(pImage->pBackendData, pUuid);
+ else
+ rc = VERR_VD_IMAGE_NOT_FOUND;
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(void) VDDumpImages(PVDISK pDisk)
+{
+ /* sanity check */
+ AssertPtrReturnVoid(pDisk);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ AssertPtrReturnVoid(pDisk->pInterfaceError);
+ if (!RT_VALID_PTR(pDisk->pInterfaceError->pfnMessage))
+ pDisk->pInterfaceError->pfnMessage = vdLogMessage;
+
+ int rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ vdMessageWrapper(pDisk, "--- Dumping VD Disk, Images=%u\n", pDisk->cImages);
+ for (PVDIMAGE pImage = pDisk->pBase; pImage; pImage = pImage->pNext)
+ {
+ vdMessageWrapper(pDisk, "Dumping VD image \"%s\" (Backend=%s)\n",
+ pImage->pszFilename, pImage->Backend->pszBackendName);
+ pImage->Backend->pfnDump(pImage->pBackendData);
+ }
+
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+}
+
+
+VBOXDDU_DECL(int) VDDiscardRanges(PVDISK pDisk, PCRTRANGE paRanges, unsigned cRanges)
+{
+ int rc;
+ int rc2;
+
+ LogFlowFunc(("pDisk=%#p paRanges=%#p cRanges=%u\n",
+ pDisk, paRanges, cRanges));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertReturn(cRanges > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(paRanges, VERR_INVALID_POINTER);
+
+ do
+ {
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ AssertPtrBreakStmt(pDisk->pLast, rc = VERR_VD_NOT_OPENED);
+
+ AssertMsgBreakStmt(pDisk->pLast->uOpenFlags & VD_OPEN_FLAGS_DISCARD,
+ ("Discarding not supported\n"),
+ rc = VERR_NOT_SUPPORTED);
+
+ VDIOCTX IoCtx;
+ RTSEMEVENT hEventComplete = NIL_RTSEMEVENT;
+
+ rc = RTSemEventCreate(&hEventComplete);
+ if (RT_FAILURE(rc))
+ break;
+
+ vdIoCtxDiscardInit(&IoCtx, pDisk, paRanges, cRanges,
+ vdIoCtxSyncComplete, pDisk, hEventComplete, NULL,
+ vdDiscardHelperAsync, VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE);
+ rc = vdIoCtxProcessSync(&IoCtx, hEventComplete);
+
+ RTSemEventDestroy(hEventComplete);
+ } while (0);
+
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDAsyncRead(PVDISK pDisk, uint64_t uOffset, size_t cbRead,
+ PCRTSGBUF pSgBuf,
+ PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
+ void *pvUser1, void *pvUser2)
+{
+ int rc = VERR_VD_BLOCK_FREE;
+ int rc2;
+ PVDIOCTX pIoCtx = NULL;
+
+ LogFlowFunc(("pDisk=%#p uOffset=%llu pSgBuf=%#p cbRead=%zu pvUser1=%#p pvUser2=%#p\n",
+ pDisk, uOffset, pSgBuf, cbRead, pvUser1, pvUser2));
+
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertReturn(cbRead > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pSgBuf, VERR_INVALID_POINTER);
+
+ do
+ {
+ rc2 = vdThreadStartRead(pDisk);
+ AssertRC(rc2);
+
+ AssertMsgBreakStmt( uOffset < pDisk->cbSize
+ && cbRead <= pDisk->cbSize - uOffset,
+ ("uOffset=%llu cbRead=%zu pDisk->cbSize=%llu\n",
+ uOffset, cbRead, pDisk->cbSize),
+ rc = VERR_INVALID_PARAMETER);
+ AssertPtrBreakStmt(pDisk->pLast, rc = VERR_VD_NOT_OPENED);
+
+ pIoCtx = vdIoCtxRootAlloc(pDisk, VDIOCTXTXDIR_READ, uOffset,
+ cbRead, pDisk->pLast, pSgBuf,
+ pfnComplete, pvUser1, pvUser2,
+ NULL, vdReadHelperAsync,
+ VDIOCTX_FLAGS_ZERO_FREE_BLOCKS);
+ if (!pIoCtx)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc = vdIoCtxProcessTryLockDefer(pIoCtx);
+ if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ {
+ if (ASMAtomicCmpXchgBool(&pIoCtx->fComplete, true, false))
+ vdIoCtxFree(pDisk, pIoCtx);
+ else
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS; /* Let the other handler complete the request. */
+ }
+ else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) /* Another error */
+ vdIoCtxFree(pDisk, pIoCtx);
+
+ } while (0);
+
+ if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ rc2 = vdThreadFinishRead(pDisk);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDAsyncWrite(PVDISK pDisk, uint64_t uOffset, size_t cbWrite,
+ PCRTSGBUF pSgBuf,
+ PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
+ void *pvUser1, void *pvUser2)
+{
+ int rc;
+ int rc2;
+ PVDIOCTX pIoCtx = NULL;
+
+ LogFlowFunc(("pDisk=%#p uOffset=%llu pSgBuf=%#p cbWrite=%zu pvUser1=%#p pvUser2=%#p\n",
+ pDisk, uOffset, pSgBuf, cbWrite, pvUser1, pvUser2));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ /* Check arguments. */
+ AssertReturn(cbWrite > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pSgBuf, VERR_INVALID_POINTER);
+
+ do
+ {
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ AssertMsgBreakStmt( uOffset < pDisk->cbSize
+ && cbWrite <= pDisk->cbSize - uOffset,
+ ("uOffset=%llu cbWrite=%zu pDisk->cbSize=%llu\n",
+ uOffset, cbWrite, pDisk->cbSize),
+ rc = VERR_INVALID_PARAMETER);
+ AssertPtrBreakStmt(pDisk->pLast, rc = VERR_VD_NOT_OPENED);
+
+ pIoCtx = vdIoCtxRootAlloc(pDisk, VDIOCTXTXDIR_WRITE, uOffset,
+ cbWrite, pDisk->pLast, pSgBuf,
+ pfnComplete, pvUser1, pvUser2,
+ NULL, vdWriteHelperAsync,
+ VDIOCTX_FLAGS_DEFAULT);
+ if (!pIoCtx)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc = vdIoCtxProcessTryLockDefer(pIoCtx);
+ if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ {
+ if (ASMAtomicCmpXchgBool(&pIoCtx->fComplete, true, false))
+ vdIoCtxFree(pDisk, pIoCtx);
+ else
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS; /* Let the other handler complete the request. */
+ }
+ else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) /* Another error */
+ vdIoCtxFree(pDisk, pIoCtx);
+ } while (0);
+
+ if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDAsyncFlush(PVDISK pDisk, PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
+ void *pvUser1, void *pvUser2)
+{
+ int rc;
+ int rc2;
+ PVDIOCTX pIoCtx = NULL;
+
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ do
+ {
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ AssertPtrBreakStmt(pDisk->pLast, rc = VERR_VD_NOT_OPENED);
+
+ pIoCtx = vdIoCtxRootAlloc(pDisk, VDIOCTXTXDIR_FLUSH, 0,
+ 0, pDisk->pLast, NULL,
+ pfnComplete, pvUser1, pvUser2,
+ NULL, vdFlushHelperAsync,
+ VDIOCTX_FLAGS_DEFAULT);
+ if (!pIoCtx)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc = vdIoCtxProcessTryLockDefer(pIoCtx);
+ if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ {
+ if (ASMAtomicCmpXchgBool(&pIoCtx->fComplete, true, false))
+ vdIoCtxFree(pDisk, pIoCtx);
+ else
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS; /* Let the other handler complete the request. */
+ }
+ else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) /* Another error */
+ vdIoCtxFree(pDisk, pIoCtx);
+ } while (0);
+
+ if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+VBOXDDU_DECL(int) VDAsyncDiscardRanges(PVDISK pDisk, PCRTRANGE paRanges, unsigned cRanges,
+ PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
+ void *pvUser1, void *pvUser2)
+{
+ int rc;
+ int rc2;
+ PVDIOCTX pIoCtx = NULL;
+
+ LogFlowFunc(("pDisk=%#p\n", pDisk));
+ /* sanity check */
+ AssertPtrReturn(pDisk, VERR_INVALID_POINTER);
+ AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+ do
+ {
+ rc2 = vdThreadStartWrite(pDisk);
+ AssertRC(rc2);
+
+ AssertPtrBreakStmt(pDisk->pLast, rc = VERR_VD_NOT_OPENED);
+
+ pIoCtx = vdIoCtxDiscardAlloc(pDisk, paRanges, cRanges,
+ pfnComplete, pvUser1, pvUser2, NULL,
+ vdDiscardHelperAsync,
+ VDIOCTX_FLAGS_DEFAULT);
+ if (!pIoCtx)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc = vdIoCtxProcessTryLockDefer(pIoCtx);
+ if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ {
+ if (ASMAtomicCmpXchgBool(&pIoCtx->fComplete, true, false))
+ vdIoCtxFree(pDisk, pIoCtx);
+ else
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS; /* Let the other handler complete the request. */
+ }
+ else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) /* Another error */
+ vdIoCtxFree(pDisk, pIoCtx);
+ } while (0);
+
+ if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ rc2 = vdThreadFinishWrite(pDisk);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+VBOXDDU_DECL(int) VDRepair(PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ const char *pszFilename, const char *pszBackend,
+ uint32_t fFlags)
+{
+ int rc = VERR_NOT_SUPPORTED;
+ PCVDIMAGEBACKEND pBackend = NULL;
+ VDINTERFACEIOINT VDIfIoInt;
+ VDINTERFACEIO VDIfIoFallback;
+ PVDINTERFACEIO pInterfaceIo;
+
+ LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename));
+ /* Check arguments. */
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszBackend, VERR_INVALID_POINTER);
+ AssertMsgReturn((fFlags & ~VD_REPAIR_FLAGS_MASK) == 0, ("fFlags=%#x\n", fFlags),
+ VERR_INVALID_PARAMETER);
+
+ pInterfaceIo = VDIfIoGet(pVDIfsImage);
+ if (!pInterfaceIo)
+ {
+ /*
+ * Caller doesn't provide an I/O interface, create our own using the
+ * native file API.
+ */
+ vdIfIoFallbackCallbacksSetup(&VDIfIoFallback);
+ pInterfaceIo = &VDIfIoFallback;
+ }
+
+ /* Set up the internal I/O interface. */
+ AssertReturn(!VDIfIoIntGet(pVDIfsImage), VERR_INVALID_PARAMETER);
+ VDIfIoInt.pfnOpen = vdIOIntOpenLimited;
+ VDIfIoInt.pfnClose = vdIOIntCloseLimited;
+ VDIfIoInt.pfnDelete = vdIOIntDeleteLimited;
+ VDIfIoInt.pfnMove = vdIOIntMoveLimited;
+ VDIfIoInt.pfnGetFreeSpace = vdIOIntGetFreeSpaceLimited;
+ VDIfIoInt.pfnGetModificationTime = vdIOIntGetModificationTimeLimited;
+ VDIfIoInt.pfnGetSize = vdIOIntGetSizeLimited;
+ VDIfIoInt.pfnSetSize = vdIOIntSetSizeLimited;
+ VDIfIoInt.pfnReadUser = vdIOIntReadUserLimited;
+ VDIfIoInt.pfnWriteUser = vdIOIntWriteUserLimited;
+ VDIfIoInt.pfnReadMeta = vdIOIntReadMetaLimited;
+ VDIfIoInt.pfnWriteMeta = vdIOIntWriteMetaLimited;
+ VDIfIoInt.pfnFlush = vdIOIntFlushLimited;
+ rc = VDInterfaceAdd(&VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT,
+ pInterfaceIo, sizeof(VDINTERFACEIOINT), &pVDIfsImage);
+ AssertRC(rc);
+
+ rc = vdFindImageBackend(pszBackend, &pBackend);
+ if (RT_SUCCESS(rc))
+ {
+ if (pBackend->pfnRepair)
+ rc = pBackend->pfnRepair(pszFilename, pVDIfsDisk, pVDIfsImage, fFlags);
+ else
+ rc = VERR_VD_IMAGE_REPAIR_NOT_SUPPORTED;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/*
+ * generic plugin functions
+ */
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnComposeLocation}
+ */
+DECLCALLBACK(int) genericFileComposeLocation(PVDINTERFACE pConfig, char **pszLocation)
+{
+ RT_NOREF1(pConfig);
+ *pszLocation = NULL;
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnComposeName}
+ */
+DECLCALLBACK(int) genericFileComposeName(PVDINTERFACE pConfig, char **pszName)
+{
+ RT_NOREF1(pConfig);
+ *pszName = NULL;
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Storage/VDBackends.h b/src/VBox/Storage/VDBackends.h
new file mode 100644
index 00000000..d921a874
--- /dev/null
+++ b/src/VBox/Storage/VDBackends.h
@@ -0,0 +1,62 @@
+/* $Id: VDBackends.h $ */
+/** @file
+ * VD - builtin backends.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+
+#ifndef VBOX_INCLUDED_SRC_Storage_VDBackends_h
+#define VBOX_INCLUDED_SRC_Storage_VDBackends_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/vd-plugin.h>
+
+#include <iprt/cdefs.h>
+
+RT_C_DECLS_BEGIN
+
+extern const VDIMAGEBACKEND g_RawBackend;
+extern const VDIMAGEBACKEND g_VmdkBackend;
+extern const VDIMAGEBACKEND g_VDIBackend;
+extern const VDIMAGEBACKEND g_VhdBackend;
+extern const VDIMAGEBACKEND g_ParallelsBackend;
+extern const VDIMAGEBACKEND g_DmgBackend;
+extern const VDIMAGEBACKEND g_ISCSIBackend;
+extern const VDIMAGEBACKEND g_QedBackend;
+extern const VDIMAGEBACKEND g_QCowBackend;
+extern const VDIMAGEBACKEND g_VhdxBackend;
+extern const VDIMAGEBACKEND g_CueBackend;
+extern const VDIMAGEBACKEND g_VBoxIsoMakerBackend;
+
+extern const VDCACHEBACKEND g_VciCacheBackend;
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_VDBackends_h */
+
diff --git a/src/VBox/Storage/VDBackendsInline.h b/src/VBox/Storage/VDBackendsInline.h
new file mode 100644
index 00000000..976c8692
--- /dev/null
+++ b/src/VBox/Storage/VDBackendsInline.h
@@ -0,0 +1,125 @@
+/* $Id: VDBackendsInline.h $ */
+/** @file
+ * VD - backends inline helpers.
+ */
+
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+
+#ifndef VBOX_INCLUDED_SRC_Storage_VDBackendsInline_h
+#define VBOX_INCLUDED_SRC_Storage_VDBackendsInline_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+
+RT_C_DECLS_BEGIN
+
+/**
+ * Stub for VDIMAGEBACKEND::pfnGetComment when the backend doesn't suport comments.
+ *
+ * @returns VBox status code.
+ * @param a_StubName The name of the stub.
+ */
+#define VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(a_StubName) \
+ static DECLCALLBACK(int) a_StubName(void *pBackendData, char *pszComment, size_t cbComment) \
+ { \
+ RT_NOREF2(pszComment, cbComment); \
+ LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment)); \
+ AssertPtrReturn(pBackendData, VERR_VD_NOT_OPENED); \
+ LogFlowFunc(("returns %Rrc comment='%s'\n", VERR_NOT_SUPPORTED, pszComment)); \
+ return VERR_NOT_SUPPORTED; \
+ } \
+ typedef int ignore_semicolon
+
+
+/**
+ * Stub for VDIMAGEBACKEND::pfnSetComment when the backend doesn't suport comments.
+ *
+ * @returns VBox status code.
+ * @param a_StubName The name of the stub.
+ * @param a_ImageStateType Type of the backend image state.
+ */
+#define VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(a_StubName, a_ImageStateType) \
+ static DECLCALLBACK(int) a_StubName(void *pBackendData, const char *pszComment) \
+ { \
+ RT_NOREF1(pszComment); \
+ LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment)); \
+ a_ImageStateType pThis = (a_ImageStateType)pBackendData; \
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED); \
+ int rc = VERR_NOT_SUPPORTED; \
+ if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY) \
+ rc = VERR_VD_IMAGE_READ_ONLY; \
+ LogFlowFunc(("returns %Rrc\n", rc)); \
+ return rc; \
+ } \
+ typedef int ignore_semicolon
+
+
+/**
+ * Stub for VDIMAGEBACKEND::pfnGetUuid when the backend doesn't suport UUIDs.
+ *
+ * @returns VBox status code.
+ * @param a_StubName The name of the stub.
+ */
+#define VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(a_StubName) \
+ static DECLCALLBACK(int) a_StubName(void *pBackendData, PRTUUID pUuid) \
+ { \
+ RT_NOREF1(pUuid); \
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid)); \
+ AssertPtrReturn(pBackendData, VERR_VD_NOT_OPENED); \
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VERR_NOT_SUPPORTED, pUuid)); \
+ return VERR_NOT_SUPPORTED; \
+ } \
+ typedef int ignore_semicolon
+
+
+/**
+ * Stub for VDIMAGEBACKEND::pfnSetComment when the backend doesn't suport UUIDs.
+ *
+ * @returns VBox status code.
+ * @param a_StubName The name of the stub.
+ * @param a_ImageStateType Type of the backend image state.
+ */
+#define VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(a_StubName, a_ImageStateType) \
+ static DECLCALLBACK(int) a_StubName(void *pBackendData, PCRTUUID pUuid) \
+ { \
+ RT_NOREF1(pUuid); \
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid)); \
+ a_ImageStateType pThis = (a_ImageStateType)pBackendData; \
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED); \
+ int rc = VERR_NOT_SUPPORTED; \
+ if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY) \
+ rc = VERR_VD_IMAGE_READ_ONLY; \
+ LogFlowFunc(("returns %Rrc\n", rc)); \
+ return rc; \
+ } \
+ typedef int ignore_semicolon
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_VDBackendsInline_h */
diff --git a/src/VBox/Storage/VDI.cpp b/src/VBox/Storage/VDI.cpp
new file mode 100644
index 00000000..39b563e6
--- /dev/null
+++ b/src/VBox/Storage/VDI.cpp
@@ -0,0 +1,3270 @@
+/* $Id: VDI.cpp $ */
+/** @file
+ * Virtual Disk Image (VDI), Core Code.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_VDI
+#include <VBox/vd-plugin.h>
+#include "VDICore.h"
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+
+#include "VDBackends.h"
+
+#define VDI_IMAGE_DEFAULT_BLOCK_SIZE _1M
+
+/** Macros for endianess conversion. */
+#define SET_ENDIAN_U32(conv, u32) (conv == VDIECONV_H2F ? RT_H2LE_U32(u32) : RT_LE2H_U32(u32))
+#define SET_ENDIAN_U64(conv, u64) (conv == VDIECONV_H2F ? RT_H2LE_U64(u64) : RT_LE2H_U64(u64))
+
+static const char *vdiAllocationBlockSize = "1048576";
+
+static const VDCONFIGINFO vdiConfigInfo[] =
+{
+ { "AllocationBlockSize", vdiAllocationBlockSize, VDCFGVALUETYPE_INTEGER, VD_CFGKEY_CREATEONLY },
+ { NULL, NULL, VDCFGVALUETYPE_INTEGER, 0 }
+};
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aVdiFileExtensions[] =
+{
+ {"vdi", VDTYPE_HDD},
+ {NULL, VDTYPE_INVALID}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static unsigned getPowerOfTwo(unsigned uNumber);
+static void vdiInitPreHeader(PVDIPREHEADER pPreHdr);
+static int vdiValidatePreHeader(PVDIPREHEADER pPreHdr);
+static int vdiValidateHeader(PVDIHEADER pHeader);
+static void vdiSetupImageDesc(PVDIIMAGEDESC pImage);
+static int vdiUpdateHeader(PVDIIMAGEDESC pImage);
+static int vdiUpdateBlockInfo(PVDIIMAGEDESC pImage, unsigned uBlock);
+static int vdiUpdateHeaderAsync(PVDIIMAGEDESC pImage, PVDIOCTX pIoCtx);
+static int vdiUpdateBlockInfoAsync(PVDIIMAGEDESC pImage, unsigned uBlock, PVDIOCTX pIoCtx,
+ bool fUpdateHdr);
+
+/**
+ * Internal: Convert the PreHeader fields to the appropriate endianess.
+ * @param enmConv Direction of the conversion.
+ * @param pPreHdrConv Where to store the converted pre header.
+ * @param pPreHdr PreHeader pointer.
+ */
+static void vdiConvPreHeaderEndianess(VDIECONV enmConv, PVDIPREHEADER pPreHdrConv,
+ PVDIPREHEADER pPreHdr)
+{
+ memcpy(pPreHdrConv->szFileInfo, pPreHdr->szFileInfo, sizeof(pPreHdr->szFileInfo));
+ pPreHdrConv->u32Signature = SET_ENDIAN_U32(enmConv, pPreHdr->u32Signature);
+ pPreHdrConv->u32Version = SET_ENDIAN_U32(enmConv, pPreHdr->u32Version);
+}
+
+/**
+ * Internal: Convert the VDIDISKGEOMETRY fields to the appropriate endianess.
+ * @param enmConv Direction of the conversion.
+ * @param pDiskGeoConv Where to store the converted geometry.
+ * @param pDiskGeo Pointer to the disk geometry to convert.
+ */
+static void vdiConvGeometryEndianess(VDIECONV enmConv, PVDIDISKGEOMETRY pDiskGeoConv,
+ PVDIDISKGEOMETRY pDiskGeo)
+{
+ pDiskGeoConv->cCylinders = SET_ENDIAN_U32(enmConv, pDiskGeo->cCylinders);
+ pDiskGeoConv->cHeads = SET_ENDIAN_U32(enmConv, pDiskGeo->cHeads);
+ pDiskGeoConv->cSectors = SET_ENDIAN_U32(enmConv, pDiskGeo->cSectors);
+ pDiskGeoConv->cbSector = SET_ENDIAN_U32(enmConv, pDiskGeo->cbSector);
+}
+
+/**
+ * Internal: Convert the Header - version 0 fields to the appropriate endianess.
+ * @param enmConv Direction of the conversion.
+ * @param pHdrConv Where to store the converted header.
+ * @param pHdr Pointer to the version 0 header.
+ */
+static void vdiConvHeaderEndianessV0(VDIECONV enmConv, PVDIHEADER0 pHdrConv,
+ PVDIHEADER0 pHdr)
+{
+ memmove(pHdrConv->szComment, pHdr->szComment, sizeof(pHdr->szComment));
+ pHdrConv->u32Type = SET_ENDIAN_U32(enmConv, pHdr->u32Type);
+ pHdrConv->fFlags = SET_ENDIAN_U32(enmConv, pHdr->fFlags);
+ vdiConvGeometryEndianess(enmConv, &pHdrConv->LegacyGeometry, &pHdr->LegacyGeometry);
+ pHdrConv->cbDisk = SET_ENDIAN_U64(enmConv, pHdr->cbDisk);
+ pHdrConv->cbBlock = SET_ENDIAN_U32(enmConv, pHdr->cbBlock);
+ pHdrConv->cBlocks = SET_ENDIAN_U32(enmConv, pHdr->cBlocks);
+ pHdrConv->cBlocksAllocated = SET_ENDIAN_U32(enmConv, pHdr->cBlocksAllocated);
+ /* Don't convert the RTUUID fields. */
+ pHdrConv->uuidCreate = pHdr->uuidCreate;
+ pHdrConv->uuidModify = pHdr->uuidModify;
+ pHdrConv->uuidLinkage = pHdr->uuidLinkage;
+}
+
+/**
+ * Internal: Set the Header - version 1 fields to the appropriate endianess.
+ * @param enmConv Direction of the conversion.
+ * @param pHdrConv Where to store the converted header.
+ * @param pHdr Version 1 Header pointer.
+ */
+static void vdiConvHeaderEndianessV1(VDIECONV enmConv, PVDIHEADER1 pHdrConv,
+ PVDIHEADER1 pHdr)
+{
+ memmove(pHdrConv->szComment, pHdr->szComment, sizeof(pHdr->szComment));
+ pHdrConv->cbHeader = SET_ENDIAN_U32(enmConv, pHdr->cbHeader);
+ pHdrConv->u32Type = SET_ENDIAN_U32(enmConv, pHdr->u32Type);
+ pHdrConv->fFlags = SET_ENDIAN_U32(enmConv, pHdr->fFlags);
+ pHdrConv->offBlocks = SET_ENDIAN_U32(enmConv, pHdr->offBlocks);
+ pHdrConv->offData = SET_ENDIAN_U32(enmConv, pHdr->offData);
+ vdiConvGeometryEndianess(enmConv, &pHdrConv->LegacyGeometry, &pHdr->LegacyGeometry);
+ pHdrConv->u32Dummy = SET_ENDIAN_U32(enmConv, pHdr->u32Dummy);
+ pHdrConv->cbDisk = SET_ENDIAN_U64(enmConv, pHdr->cbDisk);
+ pHdrConv->cbBlock = SET_ENDIAN_U32(enmConv, pHdr->cbBlock);
+ pHdrConv->cbBlockExtra = SET_ENDIAN_U32(enmConv, pHdr->cbBlockExtra);
+ pHdrConv->cBlocks = SET_ENDIAN_U32(enmConv, pHdr->cBlocks);
+ pHdrConv->cBlocksAllocated = SET_ENDIAN_U32(enmConv, pHdr->cBlocksAllocated);
+ /* Don't convert the RTUUID fields. */
+ pHdrConv->uuidCreate = pHdr->uuidCreate;
+ pHdrConv->uuidModify = pHdr->uuidModify;
+ pHdrConv->uuidLinkage = pHdr->uuidLinkage;
+ pHdrConv->uuidParentModify = pHdr->uuidParentModify;
+}
+
+/**
+ * Internal: Set the Header - version 1plus fields to the appropriate endianess.
+ * @param enmConv Direction of the conversion.
+ * @param pHdrConv Where to store the converted header.
+ * @param pHdr Version 1+ Header pointer.
+ */
+static void vdiConvHeaderEndianessV1p(VDIECONV enmConv, PVDIHEADER1PLUS pHdrConv,
+ PVDIHEADER1PLUS pHdr)
+{
+ memmove(pHdrConv->szComment, pHdr->szComment, sizeof(pHdr->szComment));
+ pHdrConv->cbHeader = SET_ENDIAN_U32(enmConv, pHdr->cbHeader);
+ pHdrConv->u32Type = SET_ENDIAN_U32(enmConv, pHdr->u32Type);
+ pHdrConv->fFlags = SET_ENDIAN_U32(enmConv, pHdr->fFlags);
+ pHdrConv->offBlocks = SET_ENDIAN_U32(enmConv, pHdr->offBlocks);
+ pHdrConv->offData = SET_ENDIAN_U32(enmConv, pHdr->offData);
+ vdiConvGeometryEndianess(enmConv, &pHdrConv->LegacyGeometry, &pHdr->LegacyGeometry);
+ pHdrConv->u32Dummy = SET_ENDIAN_U32(enmConv, pHdr->u32Dummy);
+ pHdrConv->cbDisk = SET_ENDIAN_U64(enmConv, pHdr->cbDisk);
+ pHdrConv->cbBlock = SET_ENDIAN_U32(enmConv, pHdr->cbBlock);
+ pHdrConv->cbBlockExtra = SET_ENDIAN_U32(enmConv, pHdr->cbBlockExtra);
+ pHdrConv->cBlocks = SET_ENDIAN_U32(enmConv, pHdr->cBlocks);
+ pHdrConv->cBlocksAllocated = SET_ENDIAN_U32(enmConv, pHdr->cBlocksAllocated);
+ /* Don't convert the RTUUID fields. */
+ pHdrConv->uuidCreate = pHdr->uuidCreate;
+ pHdrConv->uuidModify = pHdr->uuidModify;
+ pHdrConv->uuidLinkage = pHdr->uuidLinkage;
+ pHdrConv->uuidParentModify = pHdr->uuidParentModify;
+ vdiConvGeometryEndianess(enmConv, &pHdrConv->LCHSGeometry, &pHdr->LCHSGeometry);
+}
+
+
+/**
+ * Internal: Set the appropriate endianess on all the Blocks pointed.
+ * @param enmConv Direction of the conversion.
+ * @param paBlocks Pointer to the block array.
+ * @param cEntries Number of entries in the block array.
+ *
+ * @note Unlike the other conversion functions this method does an in place conversion
+ * to avoid temporary memory allocations when writing the block array.
+ */
+static void vdiConvBlocksEndianess(VDIECONV enmConv, PVDIIMAGEBLOCKPOINTER paBlocks,
+ unsigned cEntries)
+{
+ for (unsigned i = 0; i < cEntries; i++)
+ paBlocks[i] = SET_ENDIAN_U32(enmConv, paBlocks[i]);
+}
+
+/**
+ * Internal: Flush the image file to disk.
+ */
+static void vdiFlushImage(PVDIIMAGEDESC pImage)
+{
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* Save header. */
+ int rc = vdiUpdateHeader(pImage);
+ AssertMsgRC(rc, ("vdiUpdateHeader() failed, filename=\"%s\", rc=%Rrc\n",
+ pImage->pszFilename, rc));
+ vdIfIoIntFileFlushSync(pImage->pIfIo, pImage->pStorage);
+ }
+}
+
+/**
+ * Internal: Free all allocated space for representing an image, and optionally
+ * delete the image from disk.
+ */
+static int vdiFreeImage(PVDIIMAGEDESC pImage, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ if (pImage->pStorage)
+ {
+ /* No point updating the file that is deleted anyway. */
+ if (!fDelete)
+ vdiFlushImage(pImage);
+
+ rc = vdIfIoIntFileClose(pImage->pIfIo, pImage->pStorage);
+ pImage->pStorage = NULL;
+ }
+
+ if (pImage->paBlocks)
+ {
+ RTMemFree(pImage->paBlocks);
+ pImage->paBlocks = NULL;
+ }
+
+ if (pImage->paBlocksRev)
+ {
+ RTMemFree(pImage->paBlocksRev);
+ pImage->paBlocksRev = NULL;
+ }
+
+ if (fDelete && pImage->pszFilename)
+ {
+ int rc2 = vdIfIoIntFileDelete(pImage->pIfIo, pImage->pszFilename);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * internal: return power of 2 or 0 if num error.
+ */
+static unsigned getPowerOfTwo(unsigned uNumber)
+{
+ if (uNumber == 0)
+ return 0;
+ unsigned uPower2 = 0;
+ while ((uNumber & 1) == 0)
+ {
+ uNumber >>= 1;
+ uPower2++;
+ }
+ return uNumber == 1 ? uPower2 : 0;
+}
+
+/**
+ * Internal: Init VDI preheader.
+ */
+static void vdiInitPreHeader(PVDIPREHEADER pPreHdr)
+{
+ pPreHdr->u32Signature = VDI_IMAGE_SIGNATURE;
+ pPreHdr->u32Version = VDI_IMAGE_VERSION;
+ memset(pPreHdr->szFileInfo, 0, sizeof(pPreHdr->szFileInfo));
+ strncat(pPreHdr->szFileInfo, VDI_IMAGE_FILE_INFO, sizeof(pPreHdr->szFileInfo)-1);
+}
+
+/**
+ * Internal: check VDI preheader.
+ */
+static int vdiValidatePreHeader(PVDIPREHEADER pPreHdr)
+{
+ if (pPreHdr->u32Signature != VDI_IMAGE_SIGNATURE)
+ return VERR_VD_VDI_INVALID_HEADER;
+
+ if ( VDI_GET_VERSION_MAJOR(pPreHdr->u32Version) != VDI_IMAGE_VERSION_MAJOR
+ && pPreHdr->u32Version != 0x00000002) /* old version. */
+ return VERR_VD_VDI_UNSUPPORTED_VERSION;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: translate VD image flags to VDI image type enum.
+ */
+static VDIIMAGETYPE vdiTranslateImageFlags2VDI(unsigned uImageFlags)
+{
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ return VDI_IMAGE_TYPE_FIXED;
+ else if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
+ return VDI_IMAGE_TYPE_DIFF;
+ else
+ return VDI_IMAGE_TYPE_NORMAL;
+}
+
+/**
+ * Internal: translate VDI image type enum to VD image type enum.
+ */
+static unsigned vdiTranslateVDI2ImageFlags(VDIIMAGETYPE enmType)
+{
+ switch (enmType)
+ {
+ case VDI_IMAGE_TYPE_NORMAL:
+ return VD_IMAGE_FLAGS_NONE;
+ case VDI_IMAGE_TYPE_FIXED:
+ return VD_IMAGE_FLAGS_FIXED;
+ case VDI_IMAGE_TYPE_DIFF:
+ return VD_IMAGE_FLAGS_DIFF;
+ default:
+ AssertMsgFailed(("invalid VDIIMAGETYPE enmType=%d\n", (int)enmType));
+ return VD_IMAGE_FLAGS_NONE;
+ }
+}
+
+/**
+ * Internal: Init VDI header. Always use latest header version.
+ *
+ * @returns nothing.
+ * @param pHeader Assumes it was initially initialized to all zeros.
+ * @param uImageFlags Flags for this image.
+ * @param pszComment Optional comment to set for the image.
+ * @param cbDisk Size of the disk in bytes.
+ * @param cbBlock Size of one block in the image.
+ * @param cbBlockExtra Extra data for one block private to the image.
+ * @param cbDataAlign The alignment for all data structures.
+ */
+static void vdiInitHeader(PVDIHEADER pHeader, uint32_t uImageFlags,
+ const char *pszComment, uint64_t cbDisk,
+ uint32_t cbBlock, uint32_t cbBlockExtra,
+ uint32_t cbDataAlign)
+{
+ pHeader->uVersion = VDI_IMAGE_VERSION;
+ pHeader->u.v1plus.cbHeader = sizeof(VDIHEADER1PLUS);
+ pHeader->u.v1plus.u32Type = (uint32_t)vdiTranslateImageFlags2VDI(uImageFlags);
+ pHeader->u.v1plus.fFlags = (uImageFlags & VD_VDI_IMAGE_FLAGS_ZERO_EXPAND) ? 1 : 0;
+#ifdef VBOX_STRICT
+ char achZero[VDI_IMAGE_COMMENT_SIZE] = {0};
+ Assert(!memcmp(pHeader->u.v1plus.szComment, achZero, VDI_IMAGE_COMMENT_SIZE));
+#endif
+ pHeader->u.v1plus.szComment[0] = '\0';
+ if (pszComment)
+ {
+ AssertMsg(strlen(pszComment) < sizeof(pHeader->u.v1plus.szComment),
+ ("HDD Comment is too long, cb=%d\n", strlen(pszComment)));
+ strncat(pHeader->u.v1plus.szComment, pszComment, sizeof(pHeader->u.v1plus.szComment)-1);
+ }
+
+ /* Mark the legacy geometry not-calculated. */
+ pHeader->u.v1plus.LegacyGeometry.cCylinders = 0;
+ pHeader->u.v1plus.LegacyGeometry.cHeads = 0;
+ pHeader->u.v1plus.LegacyGeometry.cSectors = 0;
+ pHeader->u.v1plus.LegacyGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+ pHeader->u.v1plus.u32Dummy = 0; /* used to be the translation value */
+
+ pHeader->u.v1plus.cbDisk = cbDisk;
+ pHeader->u.v1plus.cbBlock = cbBlock;
+ pHeader->u.v1plus.cBlocks = (uint32_t)(cbDisk / cbBlock);
+ if (cbDisk % cbBlock)
+ pHeader->u.v1plus.cBlocks++;
+ pHeader->u.v1plus.cbBlockExtra = cbBlockExtra;
+ pHeader->u.v1plus.cBlocksAllocated = 0;
+
+ /* Init offsets. */
+ pHeader->u.v1plus.offBlocks = RT_ALIGN_32(sizeof(VDIPREHEADER) + sizeof(VDIHEADER1PLUS), cbDataAlign);
+ pHeader->u.v1plus.offData = RT_ALIGN_32(pHeader->u.v1plus.offBlocks + (pHeader->u.v1plus.cBlocks * sizeof(VDIIMAGEBLOCKPOINTER)), cbDataAlign);
+
+ /* Init uuids. */
+#ifdef _MSC_VER
+# pragma warning(disable:4366) /* (harmless "misalignment") */
+#endif
+ RTUuidCreate(&pHeader->u.v1plus.uuidCreate);
+ RTUuidClear(&pHeader->u.v1plus.uuidModify);
+ RTUuidClear(&pHeader->u.v1plus.uuidLinkage);
+ RTUuidClear(&pHeader->u.v1plus.uuidParentModify);
+#ifdef _MSC_VER
+# pragma warning(default:4366)
+#endif
+
+ /* Mark LCHS geometry not-calculated. */
+ pHeader->u.v1plus.LCHSGeometry.cCylinders = 0;
+ pHeader->u.v1plus.LCHSGeometry.cHeads = 0;
+ pHeader->u.v1plus.LCHSGeometry.cSectors = 0;
+ pHeader->u.v1plus.LCHSGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+}
+
+/**
+ * Internal: Check VDI header.
+ */
+static int vdiValidateHeader(PVDIHEADER pHeader)
+{
+ /* Check version-dependent header parameters. */
+ switch (GET_MAJOR_HEADER_VERSION(pHeader))
+ {
+ case 0:
+ {
+ /* Old header version. */
+ break;
+ }
+ case 1:
+ {
+ /* Current header version. */
+
+ if (pHeader->u.v1.cbHeader < sizeof(VDIHEADER1))
+ {
+ LogRel(("VDI: v1 header size wrong (%d < %d)\n",
+ pHeader->u.v1.cbHeader, sizeof(VDIHEADER1)));
+ return VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ if (getImageBlocksOffset(pHeader) < (sizeof(VDIPREHEADER) + sizeof(VDIHEADER1)))
+ {
+ LogRel(("VDI: v1 blocks offset wrong (%d < %d)\n",
+ getImageBlocksOffset(pHeader), sizeof(VDIPREHEADER) + sizeof(VDIHEADER1)));
+ return VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ if (getImageDataOffset(pHeader) < (getImageBlocksOffset(pHeader) + getImageBlocks(pHeader) * sizeof(VDIIMAGEBLOCKPOINTER)))
+ {
+ LogRel(("VDI: v1 image data offset wrong (%d < %d)\n",
+ getImageDataOffset(pHeader), getImageBlocksOffset(pHeader) + getImageBlocks(pHeader) * sizeof(VDIIMAGEBLOCKPOINTER)));
+ return VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ break;
+ }
+ default:
+ /* Unsupported. */
+ return VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+
+ /* Check common header parameters. */
+
+ bool fFailed = false;
+
+ if ( getImageType(pHeader) < VDI_IMAGE_TYPE_FIRST
+ || getImageType(pHeader) > VDI_IMAGE_TYPE_LAST)
+ {
+ LogRel(("VDI: bad image type %d\n", getImageType(pHeader)));
+ fFailed = true;
+ }
+
+ if (getImageFlags(pHeader) & ~VD_VDI_IMAGE_FLAGS_MASK)
+ {
+ LogRel(("VDI: bad image flags %08x\n", getImageFlags(pHeader)));
+ fFailed = true;
+ }
+
+ if ( getImageLCHSGeometry(pHeader)
+ && (getImageLCHSGeometry(pHeader))->cbSector != VDI_GEOMETRY_SECTOR_SIZE)
+ {
+ LogRel(("VDI: wrong sector size (%d != %d)\n",
+ (getImageLCHSGeometry(pHeader))->cbSector, VDI_GEOMETRY_SECTOR_SIZE));
+ fFailed = true;
+ }
+
+ if ( getImageDiskSize(pHeader) == 0
+ || getImageBlockSize(pHeader) == 0
+ || getImageBlocks(pHeader) == 0
+ || getPowerOfTwo(getImageBlockSize(pHeader)) == 0)
+ {
+ LogRel(("VDI: wrong size (%lld, %d, %d, %d)\n",
+ getImageDiskSize(pHeader), getImageBlockSize(pHeader),
+ getImageBlocks(pHeader), getPowerOfTwo(getImageBlockSize(pHeader))));
+ fFailed = true;
+ }
+
+ if (getImageBlocksAllocated(pHeader) > getImageBlocks(pHeader))
+ {
+ LogRel(("VDI: too many blocks allocated (%d > %d)\n"
+ " blocksize=%d disksize=%lld\n",
+ getImageBlocksAllocated(pHeader), getImageBlocks(pHeader),
+ getImageBlockSize(pHeader), getImageDiskSize(pHeader)));
+ fFailed = true;
+ }
+
+ if ( getImageExtraBlockSize(pHeader) != 0
+ && getPowerOfTwo(getImageExtraBlockSize(pHeader)) == 0)
+ {
+ LogRel(("VDI: wrong extra size (%d, %d)\n",
+ getImageExtraBlockSize(pHeader), getPowerOfTwo(getImageExtraBlockSize(pHeader))));
+ fFailed = true;
+ }
+
+ if ((uint64_t)getImageBlockSize(pHeader) * getImageBlocks(pHeader) < getImageDiskSize(pHeader))
+ {
+ LogRel(("VDI: wrong disk size (%d, %d, %lld)\n",
+ getImageBlockSize(pHeader), getImageBlocks(pHeader), getImageDiskSize(pHeader)));
+ fFailed = true;
+ }
+
+ if (RTUuidIsNull(getImageCreationUUID(pHeader)))
+ {
+ LogRel(("VDI: uuid of creator is 0\n"));
+ fFailed = true;
+ }
+
+ if (RTUuidIsNull(getImageModificationUUID(pHeader)))
+ {
+ LogRel(("VDI: uuid of modifier is 0\n"));
+ fFailed = true;
+ }
+
+ return fFailed ? VERR_VD_VDI_INVALID_HEADER : VINF_SUCCESS;
+}
+
+/**
+ * Internal: Set up VDIIMAGEDESC structure by image header.
+ */
+static void vdiSetupImageDesc(PVDIIMAGEDESC pImage)
+{
+ pImage->uImageFlags = getImageFlags(&pImage->Header);
+ pImage->uImageFlags |= vdiTranslateVDI2ImageFlags(getImageType(&pImage->Header));
+ pImage->offStartBlocks = getImageBlocksOffset(&pImage->Header);
+ pImage->offStartData = getImageDataOffset(&pImage->Header);
+ pImage->uBlockMask = getImageBlockSize(&pImage->Header) - 1;
+ pImage->uShiftOffset2Index = getPowerOfTwo(getImageBlockSize(&pImage->Header));
+ pImage->offStartBlockData = getImageExtraBlockSize(&pImage->Header);
+ pImage->cbAllocationBlock = getImageBlockSize(&pImage->Header);
+ pImage->cbTotalBlockData = pImage->offStartBlockData
+ + getImageBlockSize(&pImage->Header);
+}
+
+/**
+ * Sets up the complete image state from the given parameters.
+ *
+ * @returns VBox status code.
+ * @param pImage The VDI image descriptor.
+ * @param uImageFlags Image flags.
+ * @param pszComment The comment for the image (optional).
+ * @param cbSize Size of the resulting image in bytes.
+ * @param cbAllocationBlock Size of blocks allocated
+ * @param cbDataAlign Data alignment in bytes.
+ * @param pPCHSGeometry Physical CHS geometry for the image.
+ * @param pLCHSGeometry Logical CHS geometry for the image.
+ */
+static int vdiSetupImageState(PVDIIMAGEDESC pImage, unsigned uImageFlags, const char *pszComment,
+ uint64_t cbSize, uint32_t cbAllocationBlock, uint32_t cbDataAlign, PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry)
+{
+ int rc = VINF_SUCCESS;
+
+ vdiInitPreHeader(&pImage->PreHeader);
+ vdiInitHeader(&pImage->Header, uImageFlags, pszComment, cbSize, cbAllocationBlock, 0,
+ cbDataAlign);
+ /* Save PCHS geometry. Not much work, and makes the flow of information
+ * quite a bit clearer - relying on the higher level isn't obvious. */
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ /* Set LCHS geometry (legacy geometry is ignored for the current 1.1+). */
+ pImage->Header.u.v1plus.LCHSGeometry.cCylinders = pLCHSGeometry->cCylinders;
+ pImage->Header.u.v1plus.LCHSGeometry.cHeads = pLCHSGeometry->cHeads;
+ pImage->Header.u.v1plus.LCHSGeometry.cSectors = pLCHSGeometry->cSectors;
+ pImage->Header.u.v1plus.LCHSGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+
+ pImage->paBlocks = (PVDIIMAGEBLOCKPOINTER)RTMemAlloc(sizeof(VDIIMAGEBLOCKPOINTER) * getImageBlocks(&pImage->Header));
+ if (RT_LIKELY(pImage->paBlocks))
+ {
+ if (!(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ /* for growing images mark all blocks in paBlocks as free. */
+ for (unsigned i = 0; i < pImage->Header.u.v1.cBlocks; i++)
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ }
+ else
+ {
+ /* for fixed images mark all blocks in paBlocks as allocated */
+ for (unsigned i = 0; i < pImage->Header.u.v1.cBlocks; i++)
+ pImage->paBlocks[i] = i;
+ pImage->Header.u.v1.cBlocksAllocated = pImage->Header.u.v1.cBlocks;
+ }
+
+ /* Setup image parameters. */
+ vdiSetupImageDesc(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Creates the image file from the given descriptor.
+ *
+ * @returns VBox status code.
+ * @param pImage The VDI image descriptor.
+ * @param uOpenFlags Open flags.
+ * @param pIfProgress The progress interface.
+ * @param uPercentStart Progress starting point.
+ * @param uPercentSpan How many percent for this part of the operation is used.
+ */
+static int vdiImageCreateFile(PVDIIMAGEDESC pImage, unsigned uOpenFlags,
+ PVDINTERFACEPROGRESS pIfProgress, unsigned uPercentStart,
+ unsigned uPercentSpan)
+{
+ int rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags & ~VD_OPEN_FLAGS_READONLY,
+ true /* fCreate */),
+ &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ if (pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ {
+ uint64_t cbTotal = pImage->offStartData
+ + (uint64_t)getImageBlocks(&pImage->Header) * pImage->cbTotalBlockData;
+
+ /* Check the free space on the disk and leave early if there is not
+ * sufficient space available. */
+ int64_t cbFree = 0;
+ rc = vdIfIoIntFileGetFreeSpace(pImage->pIfIo, pImage->pszFilename, &cbFree);
+ if (RT_SUCCESS(rc) /* ignore errors */ && ((uint64_t)cbFree < cbTotal))
+ rc = vdIfError(pImage->pIfError, VERR_DISK_FULL, RT_SRC_POS,
+ N_("VDI: disk would overflow creating image '%s'"), pImage->pszFilename);
+ else
+ {
+ /*
+ * Allocate & commit whole file if fixed image, it must be more
+ * effective than expanding file by write operations.
+ */
+ rc = vdIfIoIntFileSetAllocationSize(pImage->pIfIo, pImage->pStorage, cbTotal, 0 /* fFlags */,
+ pIfProgress, uPercentStart, uPercentSpan);
+ pImage->cbImage = cbTotal;
+ }
+ }
+ else
+ {
+ /* Set file size to hold header and blocks array. */
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pImage->offStartData);
+ pImage->cbImage = pImage->offStartData;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* Write pre-header. */
+ VDIPREHEADER PreHeader;
+ vdiConvPreHeaderEndianess(VDIECONV_H2F, &PreHeader, &pImage->PreHeader);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, 0,
+ &PreHeader, sizeof(PreHeader));
+ if (RT_SUCCESS(rc))
+ {
+ /* Write header. */
+ VDIHEADER1PLUS Hdr;
+ vdiConvHeaderEndianessV1p(VDIECONV_H2F, &Hdr, &pImage->Header.u.v1plus);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, sizeof(pImage->PreHeader),
+ &Hdr, sizeof(Hdr));
+ if (RT_SUCCESS(rc))
+ {
+ vdiConvBlocksEndianess(VDIECONV_H2F, pImage->paBlocks, getImageBlocks(&pImage->Header));
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, pImage->offStartBlocks, pImage->paBlocks,
+ getImageBlocks(&pImage->Header) * sizeof(VDIIMAGEBLOCKPOINTER));
+ vdiConvBlocksEndianess(VDIECONV_F2H, pImage->paBlocks, getImageBlocks(&pImage->Header));
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: writing block pointers failed for '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: writing header failed for '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: writing pre-header failed for '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: setting image size failed for '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: cannot create image '%s'"),
+ pImage->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Internal: Create VDI image file.
+ */
+static int vdiCreateImage(PVDIIMAGEDESC pImage, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ PVDINTERFACEPROGRESS pIfProgress, unsigned uPercentStart,
+ unsigned uPercentSpan, PVDINTERFACECONFIG pIfCfg)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cbDataAlign = VDI_DATA_ALIGN;
+ AssertPtr(pPCHSGeometry);
+ AssertPtr(pLCHSGeometry);
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /* Special check for comment length. */
+ if ( RT_VALID_PTR(pszComment)
+ && strlen(pszComment) >= VDI_IMAGE_COMMENT_SIZE)
+ rc = vdIfError(pImage->pIfError, VERR_VD_VDI_COMMENT_TOO_LONG, RT_SRC_POS,
+ N_("VDI: comment is too long for '%s'"), pImage->pszFilename);
+
+ PVDINTERFACECONFIG pImgCfg = VDIfConfigGet(pImage->pVDIfsImage);
+ if (pImgCfg)
+ {
+ rc = VDCFGQueryU32Def(pImgCfg, "AllocationBlockSize",
+ &pImage->cbAllocationBlock, VDI_IMAGE_DEFAULT_BLOCK_SIZE);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VDI: Getting AllocationBlockSize for '%s' failed (%Rrc)"), pImage->pszFilename, rc);
+ } else
+ pImage->cbAllocationBlock = VDI_IMAGE_DEFAULT_BLOCK_SIZE;
+
+ if (pIfCfg)
+ {
+ rc = VDCFGQueryU32Def(pIfCfg, "DataAlignment", &cbDataAlign, VDI_DATA_ALIGN);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VDI: Getting data alignment for '%s' failed (%Rrc)"), pImage->pszFilename, rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+
+ rc = vdiSetupImageState(pImage, uImageFlags, pszComment, cbSize,
+ pImage->cbAllocationBlock, cbDataAlign, pPCHSGeometry, pLCHSGeometry);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Use specified image uuid */
+ *getImageCreationUUID(&pImage->Header) = *pUuid;
+ /* Generate image last-modify uuid */
+ RTUuidCreate(getImageModificationUUID(&pImage->Header));
+
+ rc = vdiImageCreateFile(pImage, uOpenFlags, pIfProgress,
+ uPercentStart, uPercentSpan);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = getImageDiskSize(&pImage->Header);
+
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+ }
+
+ if (RT_FAILURE(rc))
+ vdiFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+/**
+ * Reads and validates the header for the given image descriptor.
+ *
+ * @returns VBox status code.
+ * @param pImage The VDI image descriptor.
+ */
+static int vdiImageReadHeader(PVDIIMAGEDESC pImage)
+{
+ /* Get file size. */
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage,
+ &pImage->cbImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read pre-header. */
+ VDIPREHEADER PreHeader;
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, 0,
+ &PreHeader, sizeof(PreHeader));
+ if (RT_SUCCESS(rc))
+ {
+ vdiConvPreHeaderEndianess(VDIECONV_F2H, &pImage->PreHeader, &PreHeader);
+ rc = vdiValidatePreHeader(&pImage->PreHeader);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read header. */
+ pImage->Header.uVersion = pImage->PreHeader.u32Version;
+ switch (GET_MAJOR_HEADER_VERSION(&pImage->Header))
+ {
+ case 0:
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, sizeof(pImage->PreHeader),
+ &pImage->Header.u.v0, sizeof(pImage->Header.u.v0));
+ if (RT_SUCCESS(rc))
+ vdiConvHeaderEndianessV0(VDIECONV_F2H, &pImage->Header.u.v0, &pImage->Header.u.v0);
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: error reading v0 header in '%s'"), pImage->pszFilename);
+ break;
+ case 1:
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, sizeof(pImage->PreHeader),
+ &pImage->Header.u.v1, sizeof(pImage->Header.u.v1));
+ if (RT_SUCCESS(rc))
+ {
+ vdiConvHeaderEndianessV1(VDIECONV_F2H, &pImage->Header.u.v1, &pImage->Header.u.v1);
+ /* Convert VDI 1.1 images to VDI 1.1+ on open in read/write mode.
+ * Conversion is harmless, as any VirtualBox version supporting VDI
+ * 1.1 doesn't touch fields it doesn't know about. */
+ if ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && GET_MINOR_HEADER_VERSION(&pImage->Header) == 1
+ && pImage->Header.u.v1.cbHeader < sizeof(pImage->Header.u.v1plus))
+ {
+ pImage->Header.u.v1plus.cbHeader = sizeof(pImage->Header.u.v1plus);
+ /* Mark LCHS geometry not-calculated. */
+ pImage->Header.u.v1plus.LCHSGeometry.cCylinders = 0;
+ pImage->Header.u.v1plus.LCHSGeometry.cHeads = 0;
+ pImage->Header.u.v1plus.LCHSGeometry.cSectors = 0;
+ pImage->Header.u.v1plus.LCHSGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+ }
+ else if (pImage->Header.u.v1.cbHeader >= sizeof(pImage->Header.u.v1plus))
+ {
+ /* Read the actual VDI 1.1+ header completely. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, sizeof(pImage->PreHeader),
+ &pImage->Header.u.v1plus,
+ sizeof(pImage->Header.u.v1plus));
+ if (RT_SUCCESS(rc))
+ vdiConvHeaderEndianessV1p(VDIECONV_F2H, &pImage->Header.u.v1plus, &pImage->Header.u.v1plus);
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: error reading v1.1+ header in '%s'"), pImage->pszFilename);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: error reading v1 header in '%s'"), pImage->pszFilename);
+ break;
+ default:
+ rc = vdIfError(pImage->pIfError, VERR_VD_VDI_UNSUPPORTED_VERSION, RT_SRC_POS,
+ N_("VDI: unsupported major version %u in '%s'"), GET_MAJOR_HEADER_VERSION(&pImage->Header), pImage->pszFilename);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdiValidateHeader(&pImage->Header);
+ if (RT_SUCCESS(rc))
+ {
+ /* Setup image parameters by header. */
+ vdiSetupImageDesc(pImage);
+
+ /*
+ * Until revision r111992 there was no check that the size was sector aligned
+ * when creating a new image and a bug in the VirtualBox GUI on OS X resulted
+ * in such images being created which caused issues when writing to the
+ * end of the image.
+ *
+ * Detect such images and repair the small damage by rounding down to the next
+ * aligned size. This is no problem as the guest would see a sector count
+ * only anyway from the device emulations so it already sees only the smaller
+ * size as result of the integer division of the size and sector size.
+ *
+ * This might not be written to the image if it is opened readonly
+ * which is not much of a problem because only writing to the last block
+ * causes trouble.
+ */
+ uint64_t cbDisk = getImageDiskSize(&pImage->Header);
+ if (cbDisk & 0x1ff)
+ setImageDiskSize(&pImage->Header, cbDisk & ~UINT64_C(0x1ff));
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_VDI_INVALID_HEADER, RT_SRC_POS,
+ N_("VDI: invalid header in '%s'"), pImage->pszFilename);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: invalid pre-header in '%s'"), pImage->pszFilename);
+ }
+ else
+ {
+ vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: error reading pre-header in '%s'"), pImage->pszFilename);
+ rc = VERR_VD_VDI_INVALID_HEADER;
+ }
+ }
+ else
+ {
+ vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: error getting the image size in '%s'"), pImage->pszFilename);
+ rc = VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ return rc;
+}
+
+/**
+ * Creates the back resolving table for the image for the discard operation.
+ *
+ * @returns VBox status code.
+ * @param pImage The VDI image descriptor.
+ */
+static int vdiImageBackResolvTblCreate(PVDIIMAGEDESC pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Any error or inconsistency results in a fail because this might
+ * get us into trouble later on.
+ */
+ pImage->paBlocksRev = (unsigned *)RTMemAllocZ(sizeof(unsigned) * getImageBlocks(&pImage->Header));
+ if (pImage->paBlocksRev)
+ {
+ unsigned cBlocksAllocated = getImageBlocksAllocated(&pImage->Header);
+ unsigned cBlocks = getImageBlocks(&pImage->Header);
+
+ for (unsigned i = 0; i < cBlocks; i++)
+ pImage->paBlocksRev[i] = VDI_IMAGE_BLOCK_FREE;
+
+ for (unsigned i = 0; i < cBlocks; i++)
+ {
+ VDIIMAGEBLOCKPOINTER ptrBlock = pImage->paBlocks[i];
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(ptrBlock))
+ {
+ if (ptrBlock < cBlocksAllocated)
+ {
+ if (pImage->paBlocksRev[ptrBlock] == VDI_IMAGE_BLOCK_FREE)
+ pImage->paBlocksRev[ptrBlock] = i;
+ else
+ {
+ rc = VERR_VD_VDI_INVALID_HEADER;
+ break;
+ }
+ }
+ else
+ {
+ rc = VERR_VD_VDI_INVALID_HEADER;
+ break;
+ }
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Internal: Open a VDI image.
+ */
+static int vdiOpenImage(PVDIIMAGEDESC pImage, unsigned uOpenFlags)
+{
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /*
+ * Open the image.
+ */
+ int rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */),
+ &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdiImageReadHeader(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Allocate memory for blocks array. */
+ pImage->paBlocks = (PVDIIMAGEBLOCKPOINTER)RTMemAlloc(sizeof(VDIIMAGEBLOCKPOINTER) * getImageBlocks(&pImage->Header));
+ if (RT_LIKELY(pImage->paBlocks))
+ {
+ /* Read blocks array. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, pImage->offStartBlocks, pImage->paBlocks,
+ getImageBlocks(&pImage->Header) * sizeof(VDIIMAGEBLOCKPOINTER));
+ if (RT_SUCCESS(rc))
+ {
+ vdiConvBlocksEndianess(VDIECONV_F2H, pImage->paBlocks, getImageBlocks(&pImage->Header));
+
+ if (uOpenFlags & VD_OPEN_FLAGS_DISCARD)
+ rc = vdiImageBackResolvTblCreate(pImage);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: Error reading the block table in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("VDI: Error allocating memory for the block table in '%s'"), pImage->pszFilename);;
+ }
+ }
+ /* else: Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = getImageDiskSize(&pImage->Header);
+ if (uOpenFlags & VD_OPEN_FLAGS_INFO)
+ {
+ PVDINTERFACECONFIG pImgCfg = VDIfConfigGet(pImage->pVDIfsImage);
+ if (pImgCfg)
+ {
+ rc = VDCFGUpdateU64(pImgCfg, true, "AllocationBlockSize", pImage->cbAllocationBlock);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ }
+ else
+ vdiFreeImage(pImage, false);
+ return rc;
+}
+
+/**
+ * Internal: Save header to file.
+ */
+static int vdiUpdateHeader(PVDIIMAGEDESC pImage)
+{
+ int rc;
+ switch (GET_MAJOR_HEADER_VERSION(&pImage->Header))
+ {
+ case 0:
+ {
+ VDIHEADER0 Hdr;
+ vdiConvHeaderEndianessV0(VDIECONV_H2F, &Hdr, &pImage->Header.u.v0);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, sizeof(VDIPREHEADER),
+ &Hdr, sizeof(Hdr));
+ break;
+ }
+ case 1:
+ if (pImage->Header.u.v1plus.cbHeader < sizeof(pImage->Header.u.v1plus))
+ {
+ VDIHEADER1 Hdr;
+ vdiConvHeaderEndianessV1(VDIECONV_H2F, &Hdr, &pImage->Header.u.v1);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, sizeof(VDIPREHEADER),
+ &Hdr, sizeof(Hdr));
+ }
+ else
+ {
+ VDIHEADER1PLUS Hdr;
+ vdiConvHeaderEndianessV1p(VDIECONV_H2F, &Hdr, &pImage->Header.u.v1plus);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, sizeof(VDIPREHEADER),
+ &Hdr, sizeof(Hdr));
+ }
+ break;
+ default:
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ break;
+ }
+ AssertMsgRC(rc, ("vdiUpdateHeader failed, filename=\"%s\" rc=%Rrc\n", pImage->pszFilename, rc));
+ return rc;
+}
+
+/**
+ * Internal: Save header to file - async version.
+ */
+static int vdiUpdateHeaderAsync(PVDIIMAGEDESC pImage, PVDIOCTX pIoCtx)
+{
+ int rc;
+ switch (GET_MAJOR_HEADER_VERSION(&pImage->Header))
+ {
+ case 0:
+ {
+ VDIHEADER0 Hdr;
+ vdiConvHeaderEndianessV0(VDIECONV_H2F, &Hdr, &pImage->Header.u.v0);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ sizeof(VDIPREHEADER), &Hdr, sizeof(Hdr),
+ pIoCtx, NULL, NULL);
+ break;
+ }
+ case 1:
+ if (pImage->Header.u.v1plus.cbHeader < sizeof(pImage->Header.u.v1plus))
+ {
+ VDIHEADER1 Hdr;
+ vdiConvHeaderEndianessV1(VDIECONV_H2F, &Hdr, &pImage->Header.u.v1);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ sizeof(VDIPREHEADER), &Hdr, sizeof(Hdr),
+ pIoCtx, NULL, NULL);
+ }
+ else
+ {
+ VDIHEADER1PLUS Hdr;
+ vdiConvHeaderEndianessV1p(VDIECONV_H2F, &Hdr, &pImage->Header.u.v1plus);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ sizeof(VDIPREHEADER), &Hdr, sizeof(Hdr),
+ pIoCtx, NULL, NULL);
+ }
+ break;
+ default:
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ break;
+ }
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+ ("vdiUpdateHeader failed, filename=\"%s\" rc=%Rrc\n", pImage->pszFilename, rc));
+ return rc;
+}
+
+/**
+ * Internal: Save block pointer to file, save header to file.
+ */
+static int vdiUpdateBlockInfo(PVDIIMAGEDESC pImage, unsigned uBlock)
+{
+ /* Update image header. */
+ int rc = vdiUpdateHeader(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* write only one block pointer. */
+ VDIIMAGEBLOCKPOINTER ptrBlock = RT_H2LE_U32(pImage->paBlocks[uBlock]);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offStartBlocks + uBlock * sizeof(VDIIMAGEBLOCKPOINTER),
+ &ptrBlock, sizeof(VDIIMAGEBLOCKPOINTER));
+ AssertMsgRC(rc, ("vdiUpdateBlockInfo failed to update block=%u, filename=\"%s\", rc=%Rrc\n",
+ uBlock, pImage->pszFilename, rc));
+ }
+ return rc;
+}
+
+/**
+ * Internal: Save block pointer to file, save header to file - async version.
+ */
+static int vdiUpdateBlockInfoAsync(PVDIIMAGEDESC pImage, unsigned uBlock,
+ PVDIOCTX pIoCtx, bool fUpdateHdr)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Update image header. */
+ if (fUpdateHdr)
+ rc = vdiUpdateHeaderAsync(pImage, pIoCtx);
+
+ if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ /* write only one block pointer. */
+ VDIIMAGEBLOCKPOINTER ptrBlock = RT_H2LE_U32(pImage->paBlocks[uBlock]);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ pImage->offStartBlocks + uBlock * sizeof(VDIIMAGEBLOCKPOINTER),
+ &ptrBlock, sizeof(VDIIMAGEBLOCKPOINTER),
+ pIoCtx, NULL, NULL);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+ ("vdiUpdateBlockInfo failed to update block=%u, filename=\"%s\", rc=%Rrc\n",
+ uBlock, pImage->pszFilename, rc));
+ }
+ return rc;
+}
+
+/**
+ * Internal: Flush the image file to disk - async version.
+ */
+static int vdiFlushImageIoCtx(PVDIIMAGEDESC pImage, PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* Save header. */
+ rc = vdiUpdateHeaderAsync(pImage, pIoCtx);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+ ("vdiUpdateHeaderAsync() failed, filename=\"%s\", rc=%Rrc\n",
+ pImage->pszFilename, rc));
+ rc = vdIfIoIntFileFlush(pImage->pIfIo, pImage->pStorage, pIoCtx, NULL, NULL);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+ ("Flushing data to disk failed rc=%Rrc\n", rc));
+ }
+
+ return rc;
+}
+
+/**
+ * Completion callback for meta/userdata reads or writes.
+ *
+ * @return VBox status code.
+ * VINF_SUCCESS if everything was successful and the transfer can continue.
+ * VERR_VD_ASYNC_IO_IN_PROGRESS if there is another data transfer pending.
+ * @param pBackendData The opaque backend data.
+ * @param pIoCtx I/O context associated with this request.
+ * @param pvUser Opaque user data passed during a read/write request.
+ * @param rcReq Status code for the completed request.
+ */
+static DECLCALLBACK(int) vdiDiscardBlockAsyncUpdate(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ RT_NOREF1(rcReq);
+ int rc = VINF_SUCCESS;
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ PVDIBLOCKDISCARDASYNC pDiscardAsync = (PVDIBLOCKDISCARDASYNC)pvUser;
+
+ switch (pDiscardAsync->enmState)
+ {
+ case VDIBLOCKDISCARDSTATE_READ_BLOCK:
+ {
+ PVDMETAXFER pMetaXfer;
+ uint64_t u64Offset = (uint64_t)pDiscardAsync->idxLastBlock * pImage->cbTotalBlockData + pImage->offStartData;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pDiscardAsync->pvBlock, pImage->cbTotalBlockData, pIoCtx,
+ &pMetaXfer, vdiDiscardBlockAsyncUpdate, pDiscardAsync);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Release immediately and go to next step. */
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+ pDiscardAsync->enmState = VDIBLOCKDISCARDSTATE_WRITE_BLOCK;
+ }
+ RT_FALL_THRU();
+ case VDIBLOCKDISCARDSTATE_WRITE_BLOCK:
+ {
+ /* Block read complete. Write to the new location (discarded block). */
+ uint64_t u64Offset = (uint64_t)pDiscardAsync->ptrBlockDiscard * pImage->cbTotalBlockData + pImage->offStartData;
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pDiscardAsync->pvBlock, pImage->cbTotalBlockData, pIoCtx,
+ vdiDiscardBlockAsyncUpdate, pDiscardAsync);
+
+ pDiscardAsync->enmState = VDIBLOCKDISCARDSTATE_UPDATE_METADATA;
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RT_FALL_THRU();
+ case VDIBLOCKDISCARDSTATE_UPDATE_METADATA:
+ {
+ int rc2;
+
+ /* Block write complete. Update metadata. */
+ pImage->paBlocksRev[pDiscardAsync->idxLastBlock] = VDI_IMAGE_BLOCK_FREE;
+ pImage->paBlocks[pDiscardAsync->uBlock] = VDI_IMAGE_BLOCK_ZERO;
+
+ if (pDiscardAsync->idxLastBlock != pDiscardAsync->ptrBlockDiscard)
+ {
+ pImage->paBlocks[pDiscardAsync->uBlockLast] = pDiscardAsync->ptrBlockDiscard;
+ pImage->paBlocksRev[pDiscardAsync->ptrBlockDiscard] = pDiscardAsync->uBlockLast;
+
+ rc = vdiUpdateBlockInfoAsync(pImage, pDiscardAsync->uBlockLast, pIoCtx, false /* fUpdateHdr */);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ }
+
+ setImageBlocksAllocated(&pImage->Header, pDiscardAsync->idxLastBlock);
+ rc = vdiUpdateBlockInfoAsync(pImage, pDiscardAsync->uBlock, pIoCtx, true /* fUpdateHdr */);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+
+ pImage->cbImage -= pImage->cbTotalBlockData;
+ LogFlowFunc(("Set new size %llu\n", pImage->cbImage));
+ rc2 = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pImage->cbImage);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+
+ /* Free discard state. */
+ RTMemFree(pDiscardAsync->pvBlock);
+ RTMemFree(pDiscardAsync);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid state %d\n", pDiscardAsync->enmState));
+ }
+
+ if (rc == VERR_VD_NOT_ENOUGH_METADATA)
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+
+ return rc;
+}
+
+/**
+ * Internal: Discard a whole block from the image filling the created hole with
+ * data from another block - async I/O version.
+ *
+ * @returns VBox status code.
+ * @param pImage VDI image instance data.
+ * @param pIoCtx I/O context associated with this request.
+ * @param uBlock The block to discard.
+ * @param pvBlock Memory to use for the I/O.
+ */
+static int vdiDiscardBlockAsync(PVDIIMAGEDESC pImage, PVDIOCTX pIoCtx,
+ unsigned uBlock, void *pvBlock)
+{
+ int rc = VINF_SUCCESS;
+ PVDIBLOCKDISCARDASYNC pDiscardAsync = NULL;
+
+ LogFlowFunc(("pImage=%#p uBlock=%u pvBlock=%#p\n",
+ pImage, uBlock, pvBlock));
+
+ pDiscardAsync = (PVDIBLOCKDISCARDASYNC)RTMemAllocZ(sizeof(VDIBLOCKDISCARDASYNC));
+ if (RT_UNLIKELY(!pDiscardAsync))
+ return VERR_NO_MEMORY;
+
+ /* Init block discard state. */
+ pDiscardAsync->uBlock = uBlock;
+ pDiscardAsync->pvBlock = pvBlock;
+ pDiscardAsync->ptrBlockDiscard = pImage->paBlocks[uBlock];
+ pDiscardAsync->idxLastBlock = getImageBlocksAllocated(&pImage->Header) - 1;
+ pDiscardAsync->uBlockLast = pImage->paBlocksRev[pDiscardAsync->idxLastBlock];
+
+ /*
+ * The block is empty, remove it.
+ * Read the last block of the image first.
+ */
+ if (pDiscardAsync->idxLastBlock != pDiscardAsync->ptrBlockDiscard)
+ {
+ LogFlowFunc(("Moving block [%u]=%u into [%u]=%u\n",
+ pDiscardAsync->uBlockLast, pDiscardAsync->idxLastBlock,
+ uBlock, pImage->paBlocks[uBlock]));
+ pDiscardAsync->enmState = VDIBLOCKDISCARDSTATE_READ_BLOCK;
+ }
+ else
+ {
+ pDiscardAsync->enmState = VDIBLOCKDISCARDSTATE_UPDATE_METADATA; /* Start immediately to shrink the image. */
+ LogFlowFunc(("Discard last block [%u]=%u\n", uBlock, pImage->paBlocks[uBlock]));
+ }
+
+ /* Call the update callback directly. */
+ rc = vdiDiscardBlockAsyncUpdate(pImage, pIoCtx, pDiscardAsync, VINF_SUCCESS);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal: Creates a allocation bitmap from the given data.
+ * Sectors which contain only 0 are marked as unallocated and sectors with
+ * other data as allocated.
+ *
+ * @returns Pointer to the allocation bitmap or NULL on failure.
+ * @param pvData The data to create the allocation bitmap for.
+ * @param cbData Number of bytes in the buffer.
+ */
+static void *vdiAllocationBitmapCreate(void *pvData, size_t cbData)
+{
+ Assert(cbData <= UINT32_MAX / 8);
+ uint32_t cSectors = (uint32_t)(cbData / 512);
+ uint32_t uSectorCur = 0;
+ void *pbmAllocationBitmap = NULL;
+
+ Assert(!(cbData % 512));
+ Assert(!(cSectors % 8));
+
+ pbmAllocationBitmap = RTMemAllocZ(cSectors / 8);
+ if (!pbmAllocationBitmap)
+ return NULL;
+
+ while (uSectorCur < cSectors)
+ {
+ int idxSet = ASMBitFirstSet((uint8_t *)pvData + uSectorCur * 512, (uint32_t)cbData * 8);
+
+ if (idxSet != -1)
+ {
+ unsigned idxSectorAlloc = idxSet / 8 / 512;
+ ASMBitSet(pbmAllocationBitmap, uSectorCur + idxSectorAlloc);
+
+ uSectorCur += idxSectorAlloc + 1;
+ cbData -= (idxSectorAlloc + 1) * 512;
+ }
+ else
+ break;
+ }
+
+ return pbmAllocationBitmap;
+}
+
+
+/**
+ * Updates the state of the async cluster allocation.
+ *
+ * @returns VBox status code.
+ * @param pBackendData The opaque backend data.
+ * @param pIoCtx I/O context associated with this request.
+ * @param pvUser Opaque user data passed during a read/write request.
+ * @param rcReq Status code for the completed request.
+ */
+static DECLCALLBACK(int) vdiBlockAllocUpdate(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ int rc = VINF_SUCCESS;
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ PVDIASYNCBLOCKALLOC pBlockAlloc = (PVDIASYNCBLOCKALLOC)pvUser;
+
+ if (RT_SUCCESS(rcReq))
+ {
+ pImage->cbImage += pImage->cbTotalBlockData;
+ pImage->paBlocks[pBlockAlloc->uBlock] = pBlockAlloc->cBlocksAllocated;
+
+ if (pImage->paBlocksRev)
+ pImage->paBlocksRev[pBlockAlloc->cBlocksAllocated] = pBlockAlloc->uBlock;
+
+ setImageBlocksAllocated(&pImage->Header, pBlockAlloc->cBlocksAllocated + 1);
+ rc = vdiUpdateBlockInfoAsync(pImage, pBlockAlloc->uBlock, pIoCtx,
+ true /* fUpdateHdr */);
+ }
+ /* else: I/O error don't update the block table. */
+
+ RTMemFree(pBlockAlloc);
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) vdiProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename));
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)RTMemAllocZ(RT_UOFFSETOF(VDIIMAGEDESC, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->paBlocks = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vdiOpenImage(pImage, VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_READONLY);
+ vdiFreeImage(pImage, false);
+ RTMemFree(pImage);
+
+ if (RT_SUCCESS(rc))
+ *penmType = VDTYPE_HDD;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) vdiOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ RT_NOREF1(enmType); /**< @todo r=klaus make use of the type info. */
+
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)RTMemAllocZ(RT_UOFFSETOF(VDIIMAGEDESC, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->paBlocks = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vdiOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ else
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnCreate */
+static DECLCALLBACK(int) vdiCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+ int rc;
+
+ /* Check the VD container type and image flags. */
+ if ( enmType != VDTYPE_HDD
+ || (uImageFlags & ~VD_VDI_IMAGE_FLAGS_MASK) != 0)
+ return VERR_VD_INVALID_TYPE;
+
+ /* Check size. Maximum 4PB-3M. No tricks with adjusting the 1M block size
+ * so far, which would extend the size. */
+ if ( !cbSize
+ || cbSize >= _1P * 4 - _1M * 3
+ || cbSize < VDI_IMAGE_DEFAULT_BLOCK_SIZE
+ || (cbSize % 512))
+ return VERR_VD_INVALID_SIZE;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER);
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)RTMemAllocZ(RT_UOFFSETOF(VDIIMAGEDESC, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+ PVDINTERFACECONFIG pIfCfg = VDIfConfigGet(pVDIfsOperation);
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->paBlocks = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vdiCreateImage(pImage, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags,
+ pIfProgress, uPercentStart, uPercentSpan, pIfCfg);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ vdiFreeImage(pImage, false);
+ rc = vdiOpenImage(pImage, uOpenFlags);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRename */
+static DECLCALLBACK(int) vdiRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VINF_SUCCESS;
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ /* Check arguments. */
+ AssertReturn((pImage && pszFilename && *pszFilename), VERR_INVALID_PARAMETER);
+
+ /* Close the image. */
+ rc = vdiFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ {
+ /* Rename the file. */
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pszFilename, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the new image. */
+ rc = vdiOpenImage(pImage, pImage->uOpenFlags);
+ }
+ else
+ {
+ /* The move failed, try to reopen the original image. */
+ int rc2 = vdiOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) vdiClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ int rc = vdiFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdiRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToRead=%zu pcbActuallyRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uBlock;
+ unsigned offRead;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbToRead % 512));
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToRead <= getImageDiskSize(&pImage->Header), VERR_INVALID_PARAMETER);
+
+ /* Calculate starting block number and offset inside it. */
+ uBlock = (unsigned)(uOffset >> pImage->uShiftOffset2Index);
+ offRead = (unsigned)uOffset & pImage->uBlockMask;
+
+ /* Clip read range to at most the rest of the block. */
+ cbToRead = RT_MIN(cbToRead, getImageBlockSize(&pImage->Header) - offRead);
+ Assert(!(cbToRead % 512));
+
+ if (pImage->paBlocks[uBlock] == VDI_IMAGE_BLOCK_FREE)
+ rc = VERR_VD_BLOCK_FREE;
+ else if (pImage->paBlocks[uBlock] == VDI_IMAGE_BLOCK_ZERO)
+ {
+ size_t cbSet;
+
+ cbSet = vdIfIoIntIoCtxSet(pImage->pIfIo, pIoCtx, 0, cbToRead);
+ Assert(cbSet == cbToRead);
+ }
+ else
+ {
+ /* Block present in image file, read relevant data. */
+ uint64_t u64Offset = (uint64_t)pImage->paBlocks[uBlock] * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData + offRead);
+
+ if (u64Offset + cbToRead <= pImage->cbImage)
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pIoCtx, cbToRead);
+ else
+ {
+ LogRel(("VDI: Out of range access (%llu) in image %s, image size %llu\n",
+ u64Offset, pImage->pszFilename, pImage->cbImage));
+ vdIfIoIntIoCtxSet(pImage->pIfIo, pIoCtx, 0, cbToRead);
+ rc = VERR_VD_READ_OUT_OF_RANGE;
+ }
+ }
+
+ if (pcbActuallyRead)
+ *pcbActuallyRead = cbToRead;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdiWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uBlock;
+ unsigned offWrite;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbToWrite % 512));
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToWrite, VERR_INVALID_PARAMETER);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* No size check here, will do that later. For dynamic images which are
+ * not multiples of the block size in length, this would prevent writing to
+ * the last block. */
+
+ /* Calculate starting block number and offset inside it. */
+ uBlock = (unsigned)(uOffset >> pImage->uShiftOffset2Index);
+ offWrite = (unsigned)uOffset & pImage->uBlockMask;
+
+ /* Clip write range to at most the rest of the block. */
+ cbToWrite = RT_MIN(cbToWrite, getImageBlockSize(&pImage->Header) - offWrite);
+ Assert(!(cbToWrite % 512));
+
+ do
+ {
+ if (!IS_VDI_IMAGE_BLOCK_ALLOCATED(pImage->paBlocks[uBlock]))
+ {
+ /* Block is either free or zero. */
+ if ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_ZEROES)
+ && ( pImage->paBlocks[uBlock] == VDI_IMAGE_BLOCK_ZERO
+ || cbToWrite == getImageBlockSize(&pImage->Header)))
+ {
+ /* If the destination block is unallocated at this point, it's
+ * either a zero block or a block which hasn't been used so far
+ * (which also means that it's a zero block. Don't need to write
+ * anything to this block if the data consists of just zeroes. */
+ if (vdIfIoIntIoCtxIsZero(pImage->pIfIo, pIoCtx, cbToWrite, true))
+ {
+ pImage->paBlocks[uBlock] = VDI_IMAGE_BLOCK_ZERO;
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+ break;
+ }
+ }
+
+ if ( cbToWrite == getImageBlockSize(&pImage->Header)
+ && !(fWrite & VD_WRITE_NO_ALLOC))
+ {
+ /* Full block write to previously unallocated block.
+ * Allocate block and write data. */
+ Assert(!offWrite);
+ PVDIASYNCBLOCKALLOC pBlockAlloc = (PVDIASYNCBLOCKALLOC)RTMemAllocZ(sizeof(VDIASYNCBLOCKALLOC));
+ if (!pBlockAlloc)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ unsigned cBlocksAllocated = getImageBlocksAllocated(&pImage->Header);
+ uint64_t u64Offset = (uint64_t)cBlocksAllocated * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData);
+
+ pBlockAlloc->cBlocksAllocated = cBlocksAllocated;
+ pBlockAlloc->uBlock = uBlock;
+
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ u64Offset, pIoCtx, cbToWrite,
+ vdiBlockAllocUpdate, pBlockAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ RTMemFree(pBlockAlloc);
+ break;
+ }
+
+ rc = vdiBlockAllocUpdate(pImage, pIoCtx, pBlockAlloc, rc);
+ }
+ else
+ {
+ /* Trying to do a partial write to an unallocated block. Don't do
+ * anything except letting the upper layer know what to do. */
+ *pcbPreRead = offWrite % getImageBlockSize(&pImage->Header);
+ *pcbPostRead = getImageBlockSize(&pImage->Header) - cbToWrite - *pcbPreRead;
+ rc = VERR_VD_BLOCK_FREE;
+ }
+ }
+ else
+ {
+ /* Block present in image file, write relevant data. */
+ uint64_t u64Offset = (uint64_t)pImage->paBlocks[uBlock] * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData + offWrite);
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ u64Offset, pIoCtx, cbToWrite, NULL, NULL);
+ }
+ } while (0);
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdiFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ Assert(pImage);
+
+ rc = vdiFlushImageIoCtx(pImage, pIoCtx);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) vdiGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->PreHeader.u32Version));
+ return pImage->PreHeader.u32Version;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) vdiGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtrReturn(pImage, 0);
+
+ if (pImage->pStorage)
+ {
+ uint64_t cbFile;
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb += cbFile;
+ }
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) vdiGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->PCHSGeometry.cCylinders)
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) vdiSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->PCHSGeometry = *pPCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) vdiGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ VDIDISKGEOMETRY DummyGeo = { 0, 0, 0, VDI_GEOMETRY_SECTOR_SIZE };
+ PVDIDISKGEOMETRY pGeometry = getImageLCHSGeometry(&pImage->Header);
+ if (!pGeometry)
+ pGeometry = &DummyGeo;
+
+ if ( pGeometry->cCylinders > 0
+ && pGeometry->cHeads > 0
+ && pGeometry->cSectors > 0)
+ {
+ pLCHSGeometry->cCylinders = pGeometry->cCylinders;
+ pLCHSGeometry->cHeads = pGeometry->cHeads;
+ pLCHSGeometry->cSectors = pGeometry->cSectors;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) vdiSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n",
+ pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ PVDIDISKGEOMETRY pGeometry;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ pGeometry = getImageLCHSGeometry(&pImage->Header);
+ if (pGeometry)
+ {
+ pGeometry->cCylinders = pLCHSGeometry->cCylinders;
+ pGeometry->cHeads = pLCHSGeometry->cHeads;
+ pGeometry->cSectors = pLCHSGeometry->cSectors;
+ pGeometry->cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+
+ /* Update header information in base image file. */
+ vdiFlushImage(pImage);
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) vdiQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PVDIIMAGEDESC pThis = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) vdiRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PVDIIMAGEDESC pThis = (PVDIIMAGEDESC)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) vdiGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uImageFlags));
+ return pImage->uImageFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) vdiGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uOpenFlags));
+ return pImage->uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) vdiSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p uOpenFlags=%#x\n", pBackendData, uOpenFlags));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+ const char *pszFilename;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pImage || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_DISCARD
+ | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ pszFilename = pImage->pszFilename;
+ rc = vdiFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ rc = vdiOpenImage(pImage, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+static DECLCALLBACK(int) vdiGetComment(void *pBackendData, char *pszComment,
+ size_t cbComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ char *pszTmp = getImageComment(&pImage->Header);
+ /* Make this foolproof even if the image doesn't have the zero
+ * termination. With some luck the repaired header will be saved. */
+ size_t cb = RTStrNLen(pszTmp, VDI_IMAGE_COMMENT_SIZE);
+ if (cb == VDI_IMAGE_COMMENT_SIZE)
+ {
+ pszTmp[VDI_IMAGE_COMMENT_SIZE-1] = '\0';
+ cb--;
+ }
+ if (cb < cbComment)
+ {
+ /* memcpy is much better than strncpy. */
+ memcpy(pszComment, pszTmp, cb + 1);
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+
+ LogFlowFunc(("returns %Rrc comment=\"%s\"\n", rc, pszComment));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+static DECLCALLBACK(int) vdiSetComment(void *pBackendData, const char *pszComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ size_t cchComment = pszComment ? strlen(pszComment) : 0;
+ if (cchComment < VDI_IMAGE_COMMENT_SIZE)
+ {
+ /* we don't support old style images */
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ {
+ /*
+ * Update the comment field, making sure to zero out all of the previous comment.
+ */
+ memset(pImage->Header.u.v1.szComment, '\0', VDI_IMAGE_COMMENT_SIZE);
+ memcpy(pImage->Header.u.v1.szComment, pszComment, cchComment);
+
+ /* write out new the header */
+ rc = vdiUpdateHeader(pImage);
+ }
+ else
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ else
+ {
+ LogFunc(("pszComment is too long, %d bytes!\n", cchComment));
+ rc = VERR_VD_VDI_COMMENT_TOO_LONG;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+static DECLCALLBACK(int) vdiGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = *getImageCreationUUID(&pImage->Header);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+static DECLCALLBACK(int) vdiSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidCreate = *pUuid;
+ /* Make it possible to clone old VDIs. */
+ else if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0)
+ pImage->Header.u.v0.uuidCreate = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+static DECLCALLBACK(int) vdiGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = *getImageModificationUUID(&pImage->Header);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+static DECLCALLBACK(int) vdiSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidModify = *pUuid;
+ /* Make it possible to clone old VDIs. */
+ else if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0)
+ pImage->Header.u.v0.uuidModify = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+static DECLCALLBACK(int) vdiGetParentUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = *getImageParentUUID(&pImage->Header);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+static DECLCALLBACK(int) vdiSetParentUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidLinkage = *pUuid;
+ /* Make it possible to clone old VDIs. */
+ else if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0)
+ pImage->Header.u.v0.uuidLinkage = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+static DECLCALLBACK(int) vdiGetParentModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = *getImageParentModificationUUID(&pImage->Header);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+static DECLCALLBACK(int) vdiSetParentModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidParentModify = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) vdiDump(void *pBackendData)
+{
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturnVoid(pImage);
+ vdIfErrorMessage(pImage->pIfError, "Dumping VDI image \"%s\" mode=%s uOpenFlags=%X File=%#p\n",
+ pImage->pszFilename,
+ (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) ? "r/o" : "r/w",
+ pImage->uOpenFlags,
+ pImage->pStorage);
+ vdIfErrorMessage(pImage->pIfError, "Header: Version=%08X Type=%X Flags=%X Size=%llu\n",
+ pImage->PreHeader.u32Version,
+ getImageType(&pImage->Header),
+ getImageFlags(&pImage->Header),
+ getImageDiskSize(&pImage->Header));
+ vdIfErrorMessage(pImage->pIfError, "Header: cbBlock=%u cbBlockExtra=%u cBlocks=%u cBlocksAllocated=%u\n",
+ getImageBlockSize(&pImage->Header),
+ getImageExtraBlockSize(&pImage->Header),
+ getImageBlocks(&pImage->Header),
+ getImageBlocksAllocated(&pImage->Header));
+ vdIfErrorMessage(pImage->pIfError, "Header: offBlocks=%u offData=%u\n",
+ getImageBlocksOffset(&pImage->Header),
+ getImageDataOffset(&pImage->Header));
+ PVDIDISKGEOMETRY pg = getImageLCHSGeometry(&pImage->Header);
+ if (pg)
+ vdIfErrorMessage(pImage->pIfError, "Header: Geometry: C/H/S=%u/%u/%u cbSector=%u\n",
+ pg->cCylinders, pg->cHeads, pg->cSectors, pg->cbSector);
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidCreation={%RTuuid}\n", getImageCreationUUID(&pImage->Header));
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidModification={%RTuuid}\n", getImageModificationUUID(&pImage->Header));
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidParent={%RTuuid}\n", getImageParentUUID(&pImage->Header));
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) >= 1)
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidParentModification={%RTuuid}\n", getImageParentModificationUUID(&pImage->Header));
+ vdIfErrorMessage(pImage->pIfError, "Image: fFlags=%08X offStartBlocks=%u offStartData=%u\n",
+ pImage->uImageFlags, pImage->offStartBlocks, pImage->offStartData);
+ vdIfErrorMessage(pImage->pIfError, "Image: uBlockMask=%08X cbTotalBlockData=%u uShiftOffset2Index=%u offStartBlockData=%u\n",
+ pImage->uBlockMask,
+ pImage->cbTotalBlockData,
+ pImage->uShiftOffset2Index,
+ pImage->offStartBlockData);
+
+ unsigned uBlock, cBlocksNotFree, cBadBlocks, cBlocks = getImageBlocks(&pImage->Header);
+ for (uBlock=0, cBlocksNotFree=0, cBadBlocks=0; uBlock<cBlocks; uBlock++)
+ {
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(pImage->paBlocks[uBlock]))
+ {
+ cBlocksNotFree++;
+ if (pImage->paBlocks[uBlock] >= cBlocks)
+ cBadBlocks++;
+ }
+ }
+ if (cBlocksNotFree != getImageBlocksAllocated(&pImage->Header))
+ {
+ vdIfErrorMessage(pImage->pIfError, "!! WARNING: %u blocks actually allocated (cBlocksAllocated=%u) !!\n",
+ cBlocksNotFree, getImageBlocksAllocated(&pImage->Header));
+ }
+ if (cBadBlocks)
+ {
+ vdIfErrorMessage(pImage->pIfError, "!! WARNING: %u bad blocks found !!\n",
+ cBadBlocks);
+ }
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnCompact */
+static DECLCALLBACK(int) vdiCompact(void *pBackendData, unsigned uPercentStart,
+ unsigned uPercentSpan, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, PVDINTERFACE pVDIfsOperation)
+{
+ RT_NOREF2(pVDIfsDisk, pVDIfsImage);
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+ void *pvBuf = NULL, *pvTmp = NULL;
+ unsigned *paBlocks2 = NULL;
+
+ PFNVDPARENTREAD pfnParentRead = NULL;
+ void *pvParent = NULL;
+ PVDINTERFACEPARENTSTATE pIfParentState = VDIfParentStateGet(pVDIfsOperation);
+ if (pIfParentState)
+ {
+ pfnParentRead = pIfParentState->pfnParentRead;
+ pvParent = pIfParentState->Core.pvUser;
+ }
+
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+ PVDINTERFACEQUERYRANGEUSE pIfQueryRangeUse = VDIfQueryRangeUseGet(pVDIfsOperation);
+
+ do
+ {
+ AssertBreakStmt(pImage, rc = VERR_INVALID_PARAMETER);
+
+ AssertBreakStmt(!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY),
+ rc = VERR_VD_IMAGE_READ_ONLY);
+
+ unsigned cBlocks;
+ unsigned cBlocksToMove = 0;
+ size_t cbBlock;
+ cBlocks = getImageBlocks(&pImage->Header);
+ cbBlock = getImageBlockSize(&pImage->Header);
+ if (pfnParentRead)
+ {
+ pvBuf = RTMemTmpAlloc(cbBlock);
+ AssertBreakStmt(pvBuf, rc = VERR_NO_MEMORY);
+ }
+ pvTmp = RTMemTmpAlloc(cbBlock);
+ AssertBreakStmt(pvTmp, rc = VERR_NO_MEMORY);
+
+ uint64_t cbFile;
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ AssertRCBreak(rc);
+ unsigned cBlocksAllocated = (unsigned)((cbFile - pImage->offStartData - pImage->offStartBlockData) >> pImage->uShiftOffset2Index);
+ if (cBlocksAllocated == 0)
+ {
+ /* No data blocks in this image, no need to compact. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ /* Allocate block array for back resolving. */
+ paBlocks2 = (unsigned *)RTMemAlloc(sizeof(unsigned *) * cBlocksAllocated);
+ AssertBreakStmt(paBlocks2, rc = VERR_NO_MEMORY);
+ /* Fill out back resolving, check/fix allocation errors before
+ * compacting the image, just to be on the safe side. Update the
+ * image contents straight away, as this enables cancelling. */
+ for (unsigned i = 0; i < cBlocksAllocated; i++)
+ paBlocks2[i] = VDI_IMAGE_BLOCK_FREE;
+ rc = VINF_SUCCESS;
+ for (unsigned i = 0; i < cBlocks; i++)
+ {
+ VDIIMAGEBLOCKPOINTER ptrBlock = pImage->paBlocks[i];
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(ptrBlock))
+ {
+ if (ptrBlock < cBlocksAllocated)
+ {
+ if (paBlocks2[ptrBlock] == VDI_IMAGE_BLOCK_FREE)
+ paBlocks2[ptrBlock] = i;
+ else
+ {
+ LogFunc(("Freed cross-linked block %u in file \"%s\"\n",
+ i, pImage->pszFilename));
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ rc = vdiUpdateBlockInfo(pImage, i);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ {
+ LogFunc(("Freed out of bounds reference for block %u in file \"%s\"\n",
+ i, pImage->pszFilename));
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ rc = vdiUpdateBlockInfo(pImage, i);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Find redundant information and update the block pointers
+ * accordingly, creating bubbles. Keep disk up to date, as this
+ * enables cancelling. */
+ for (unsigned i = 0; i < cBlocks; i++)
+ {
+ VDIIMAGEBLOCKPOINTER ptrBlock = pImage->paBlocks[i];
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(ptrBlock))
+ {
+ /* Block present in image file, read relevant data. */
+ uint64_t u64Offset = (uint64_t)ptrBlock * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData);
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, u64Offset, pvTmp, cbBlock);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (ASMBitFirstSet((volatile void *)pvTmp, (uint32_t)cbBlock * 8) == -1)
+ {
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_ZERO;
+ rc = vdiUpdateBlockInfo(pImage, i);
+ if (RT_FAILURE(rc))
+ break;
+ paBlocks2[ptrBlock] = VDI_IMAGE_BLOCK_FREE;
+ /* Adjust progress info, one block to be relocated. */
+ cBlocksToMove++;
+ }
+ else if (pfnParentRead)
+ {
+ rc = pfnParentRead(pvParent, (uint64_t)i * cbBlock, pvBuf, cbBlock);
+ if (RT_FAILURE(rc))
+ break;
+ if (!memcmp(pvTmp, pvBuf, cbBlock))
+ {
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ rc = vdiUpdateBlockInfo(pImage, i);
+ if (RT_FAILURE(rc))
+ break;
+ paBlocks2[ptrBlock] = VDI_IMAGE_BLOCK_FREE;
+ /* Adjust progress info, one block to be relocated. */
+ cBlocksToMove++;
+ }
+ }
+ }
+
+ /* Check if the range is in use if the block is still allocated. */
+ ptrBlock = pImage->paBlocks[i];
+ if ( IS_VDI_IMAGE_BLOCK_ALLOCATED(ptrBlock)
+ && pIfQueryRangeUse)
+ {
+ bool fUsed = true;
+
+ rc = vdIfQueryRangeUse(pIfQueryRangeUse, (uint64_t)i * cbBlock, cbBlock, &fUsed);
+ if (RT_FAILURE(rc))
+ break;
+ if (!fUsed)
+ {
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_ZERO;
+ rc = vdiUpdateBlockInfo(pImage, i);
+ if (RT_FAILURE(rc))
+ break;
+ paBlocks2[ptrBlock] = VDI_IMAGE_BLOCK_FREE;
+ /* Adjust progress info, one block to be relocated. */
+ cBlocksToMove++;
+ }
+ }
+
+ vdIfProgress(pIfProgress, (uint64_t)i * uPercentSpan / (cBlocks + cBlocksToMove) + uPercentStart);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Fill bubbles with other data (if available). */
+ unsigned cBlocksMoved = 0;
+ unsigned uBlockUsedPos = cBlocksAllocated;
+ for (unsigned i = 0; i < cBlocksAllocated; i++)
+ {
+ unsigned uBlock = paBlocks2[i];
+ if (uBlock == VDI_IMAGE_BLOCK_FREE)
+ {
+ unsigned uBlockData = VDI_IMAGE_BLOCK_FREE;
+ while (uBlockUsedPos > i && uBlockData == VDI_IMAGE_BLOCK_FREE)
+ {
+ uBlockUsedPos--;
+ uBlockData = paBlocks2[uBlockUsedPos];
+ }
+ /* Terminate early if there is no block which needs copying. */
+ if (uBlockUsedPos == i)
+ break;
+ uint64_t u64Offset = (uint64_t)uBlockUsedPos * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData);
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pvTmp, cbBlock);
+ u64Offset = (uint64_t)i * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pvTmp, cbBlock);
+ pImage->paBlocks[uBlockData] = i;
+ setImageBlocksAllocated(&pImage->Header, cBlocksAllocated - cBlocksMoved);
+ rc = vdiUpdateBlockInfo(pImage, uBlockData);
+ if (RT_FAILURE(rc))
+ break;
+ paBlocks2[i] = uBlockData;
+ paBlocks2[uBlockUsedPos] = VDI_IMAGE_BLOCK_FREE;
+ cBlocksMoved++;
+ }
+
+ rc = vdIfProgress(pIfProgress, (uint64_t)(cBlocks + cBlocksMoved) * uPercentSpan / (cBlocks + cBlocksToMove) + uPercentStart);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Update image header. */
+ setImageBlocksAllocated(&pImage->Header, uBlockUsedPos);
+ vdiUpdateHeader(pImage);
+
+ /* Truncate the image to the proper size to finish compacting. */
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage,
+ (uint64_t)uBlockUsedPos * pImage->cbTotalBlockData
+ + pImage->offStartData + pImage->offStartBlockData);
+ } while (0);
+
+ if (paBlocks2)
+ RTMemTmpFree(paBlocks2);
+ if (pvTmp)
+ RTMemTmpFree(pvTmp);
+ if (pvBuf)
+ RTMemTmpFree(pvBuf);
+
+ if (RT_SUCCESS(rc))
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/** @copydoc VDIMAGEBACKEND::pfnResize */
+static DECLCALLBACK(int) vdiResize(void *pBackendData, uint64_t cbSize,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation)
+{
+ RT_NOREF5(uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation);
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Check size. Maximum 4PB-3M. No tricks with adjusting the 1M block size
+ * so far, which would extend the size. */
+ if ( !cbSize
+ || cbSize >= _1P * 4 - _1M * 3
+ || cbSize < VDI_IMAGE_DEFAULT_BLOCK_SIZE)
+ return VERR_VD_INVALID_SIZE;
+
+ /*
+ * Making the image smaller is not supported at the moment.
+ * Resizing is also not supported for fixed size images and
+ * very old images.
+ */
+ /** @todo implement making the image smaller, it is the responsibility of
+ * the user to know what he's doing. */
+ if (cbSize < getImageDiskSize(&pImage->Header))
+ rc = VERR_VD_SHRINK_NOT_SUPPORTED;
+ else if ( GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0
+ || pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ rc = VERR_NOT_SUPPORTED;
+ else if (cbSize > getImageDiskSize(&pImage->Header))
+ {
+ unsigned cBlocksAllocated = getImageBlocksAllocated(&pImage->Header); /** < Blocks currently allocated, doesn't change during resize */
+ unsigned const cbBlock = RT_MAX(getImageBlockSize(&pImage->Header), 1);
+ uint32_t cBlocksNew = cbSize / cbBlock; /** < New number of blocks in the image after the resize */
+ if (cbSize % cbBlock)
+ cBlocksNew++;
+
+ uint32_t cBlocksOld = getImageBlocks(&pImage->Header); /** < Number of blocks before the resize. */
+ uint64_t cbBlockspaceNew = cBlocksNew * sizeof(VDIIMAGEBLOCKPOINTER); /** < Required space for the block array after the resize. */
+ uint64_t offStartDataNew = RT_ALIGN_32(pImage->offStartBlocks + cbBlockspaceNew, VDI_DATA_ALIGN); /** < New start offset for block data after the resize */
+
+ if (pImage->offStartData < offStartDataNew)
+ {
+ if (cBlocksAllocated > 0)
+ {
+ /* Calculate how many sectors need to be relocated. */
+ uint64_t cbOverlapping = offStartDataNew - pImage->offStartData;
+ unsigned cBlocksReloc = cbOverlapping / cbBlock;
+ if (cbOverlapping % cbBlock)
+ cBlocksReloc++;
+
+ /* Since only full blocks can be relocated the new data start is
+ * determined by moving it block by block. */
+ cBlocksReloc = RT_MIN(cBlocksReloc, cBlocksAllocated);
+ offStartDataNew = pImage->offStartData;
+
+ /* Do the relocation. */
+ LogFlow(("Relocating %u blocks\n", cBlocksReloc));
+
+ /*
+ * Get the blocks we need to relocate first, they are appended to the end
+ * of the image.
+ */
+ void *pvBuf = NULL, *pvZero = NULL;
+ do
+ {
+ /* Allocate data buffer. */
+ pvBuf = RTMemAllocZ(pImage->cbTotalBlockData);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Allocate buffer for overwriting with zeroes. */
+ pvZero = RTMemAllocZ(pImage->cbTotalBlockData);
+ if (!pvZero)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ for (unsigned i = 0; i < cBlocksReloc; i++)
+ {
+ /* Search the index in the block table. */
+ for (unsigned idxBlock = 0; idxBlock < cBlocksOld; idxBlock++)
+ {
+ if (!pImage->paBlocks[idxBlock])
+ {
+ /* Read data and append to the end of the image. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ offStartDataNew, pvBuf,
+ pImage->cbTotalBlockData);
+ if (RT_FAILURE(rc))
+ break;
+
+ uint64_t offBlockAppend;
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &offBlockAppend);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ offBlockAppend, pvBuf,
+ pImage->cbTotalBlockData);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Zero out the old block area. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ offStartDataNew, pvZero,
+ pImage->cbTotalBlockData);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Update block counter. */
+ pImage->paBlocks[idxBlock] = cBlocksAllocated - 1;
+
+ /*
+ * Decrease the block number of all other entries in the array.
+ * They were moved one block to the front.
+ * Doing it as a separate step iterating over the array again
+ * because an error while relocating the block might end up
+ * in a corrupted image otherwise.
+ */
+ for (unsigned idxBlock2 = 0; idxBlock2 < cBlocksOld; idxBlock2++)
+ {
+ if ( idxBlock2 != idxBlock
+ && IS_VDI_IMAGE_BLOCK_ALLOCATED(pImage->paBlocks[idxBlock2]))
+ pImage->paBlocks[idxBlock2]--;
+ }
+
+ /* Continue with the next block. */
+ break;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ offStartDataNew += pImage->cbTotalBlockData;
+ }
+ } while (0);
+
+ if (pvBuf)
+ RTMemFree(pvBuf);
+ if (pvZero)
+ RTMemFree(pvZero);
+ }
+
+ /*
+ * We need to update the new offsets for the image data in the out of memory
+ * case too because we relocated the blocks already.
+ */
+ pImage->offStartData = offStartDataNew;
+ setImageDataOffset(&pImage->Header, offStartDataNew);
+ }
+
+ /*
+ * Relocation done, expand the block array and update the header with
+ * the new data.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ PVDIIMAGEBLOCKPOINTER paBlocksNew = (PVDIIMAGEBLOCKPOINTER)RTMemRealloc(pImage->paBlocks, cbBlockspaceNew);
+ if (paBlocksNew)
+ {
+ pImage->paBlocks = paBlocksNew;
+
+ /* Mark the new blocks as unallocated. */
+ for (unsigned idxBlock = cBlocksOld; idxBlock < cBlocksNew; idxBlock++)
+ pImage->paBlocks[idxBlock] = VDI_IMAGE_BLOCK_FREE;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /* Write the block array before updating the rest. */
+ vdiConvBlocksEndianess(VDIECONV_H2F, pImage->paBlocks, cBlocksNew);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, pImage->offStartBlocks,
+ pImage->paBlocks, cbBlockspaceNew);
+ vdiConvBlocksEndianess(VDIECONV_F2H, pImage->paBlocks, cBlocksNew);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Update size and new block count. */
+ setImageDiskSize(&pImage->Header, cbSize);
+ setImageBlocks(&pImage->Header, cBlocksNew);
+ /* Update geometry. */
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ pImage->cbImage = cbSize;
+
+ PVDIDISKGEOMETRY pGeometry = getImageLCHSGeometry(&pImage->Header);
+ if (pGeometry)
+ {
+ pGeometry->cCylinders = pLCHSGeometry->cCylinders;
+ pGeometry->cHeads = pLCHSGeometry->cHeads;
+ pGeometry->cSectors = pLCHSGeometry->cSectors;
+ pGeometry->cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+ }
+ }
+ }
+
+ /* Update header information in base image file. */
+ vdiFlushImage(pImage);
+ }
+ /* Same size doesn't change the image at all. */
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnDiscard */
+static DECLCALLBACK(int) vdiDiscard(void *pBackendData, PVDIOCTX pIoCtx,
+ uint64_t uOffset, size_t cbDiscard,
+ size_t *pcbPreAllocated, size_t *pcbPostAllocated,
+ size_t *pcbActuallyDiscarded, void **ppbmAllocationBitmap,
+ unsigned fDiscard)
+{
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uBlock;
+ unsigned offDiscard;
+ int rc = VINF_SUCCESS;
+ void *pvBlock = NULL;
+
+ LogFlowFunc(("pBackendData=%#p pIoCtx=%#p uOffset=%llu cbDiscard=%zu pcbPreAllocated=%#p pcbPostAllocated=%#p pcbActuallyDiscarded=%#p ppbmAllocationBitmap=%#p fDiscard=%#x\n",
+ pBackendData, pIoCtx, uOffset, cbDiscard, pcbPreAllocated, pcbPostAllocated, pcbActuallyDiscarded, ppbmAllocationBitmap, fDiscard));
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbDiscard % 512));
+
+ AssertMsgReturn(!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY),
+ ("Image is readonly\n"), VERR_VD_IMAGE_READ_ONLY);
+ AssertMsgReturn( uOffset + cbDiscard <= getImageDiskSize(&pImage->Header)
+ && cbDiscard,
+ ("Invalid parameters uOffset=%llu cbDiscard=%zu\n",
+ uOffset, cbDiscard),
+ VERR_INVALID_PARAMETER);
+
+ do
+ {
+ AssertMsgBreakStmt(!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY),
+ ("Image is opened readonly\n"),
+ rc = VERR_VD_IMAGE_READ_ONLY);
+
+ AssertMsgBreakStmt(cbDiscard,
+ ("cbDiscard=%u\n", cbDiscard),
+ rc = VERR_INVALID_PARAMETER);
+
+ /* Calculate starting block number and offset inside it. */
+ uBlock = (unsigned)(uOffset >> pImage->uShiftOffset2Index);
+ offDiscard = (unsigned)uOffset & pImage->uBlockMask;
+
+ /* Clip range to at most the rest of the block. */
+ cbDiscard = RT_MIN(cbDiscard, getImageBlockSize(&pImage->Header) - offDiscard);
+ Assert(!(cbDiscard % 512));
+
+ if (pcbPreAllocated)
+ *pcbPreAllocated = 0;
+
+ if (pcbPostAllocated)
+ *pcbPostAllocated = 0;
+
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(pImage->paBlocks[uBlock]))
+ {
+ unsigned const cbBlock = RT_MAX(getImageBlockSize(&pImage->Header), 1);
+ size_t const cbPreAllocated = offDiscard % cbBlock;
+ size_t const cbPostAllocated = getImageBlockSize(&pImage->Header) - cbDiscard - cbPreAllocated;
+ uint8_t *pbBlockData;
+
+ /* Read the block data. */
+ pvBlock = RTMemAlloc(pImage->cbTotalBlockData);
+ if (!pvBlock)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ if (!cbPreAllocated && !cbPostAllocated)
+ {
+ /*
+ * Discarding a whole block, don't check for allocated sectors.
+ * It is possible to just remove the whole block which avoids
+ * one read and checking the whole block for data.
+ */
+ rc = vdiDiscardBlockAsync(pImage, pIoCtx, uBlock, pvBlock);
+ }
+ else if (fDiscard & VD_DISCARD_MARK_UNUSED)
+ {
+ /* Just zero out the given range. */
+ memset(pvBlock, 0, cbDiscard);
+
+ uint64_t u64Offset = (uint64_t)pImage->paBlocks[uBlock] * pImage->cbTotalBlockData + pImage->offStartData + offDiscard;
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ u64Offset, pvBlock, cbDiscard, pIoCtx,
+ NULL, NULL);
+ RTMemFree(pvBlock);
+ }
+ else
+ {
+ /*
+ * Read complete block as metadata, the I/O context has no memory buffer
+ * and we need to access the content directly anyway.
+ */
+ PVDMETAXFER pMetaXfer;
+ pbBlockData = (uint8_t *)pvBlock + pImage->offStartBlockData;
+
+ uint64_t u64Offset = (uint64_t)pImage->paBlocks[uBlock] * pImage->cbTotalBlockData + pImage->offStartData;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pbBlockData, pImage->cbTotalBlockData,
+ pIoCtx, &pMetaXfer, NULL, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pvBlock);
+ break;
+ }
+
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+
+ /* Clear data. */
+ memset(pbBlockData + offDiscard , 0, cbDiscard);
+
+ Assert(!(cbDiscard % 4));
+ Assert(getImageBlockSize(&pImage->Header) * 8 <= UINT32_MAX);
+ if (ASMBitFirstSet((volatile void *)pbBlockData, getImageBlockSize(&pImage->Header) * 8) == -1)
+ rc = vdiDiscardBlockAsync(pImage, pIoCtx, uBlock, pvBlock);
+ else
+ {
+ /* Block has data, create allocation bitmap. */
+ *pcbPreAllocated = cbPreAllocated;
+ *pcbPostAllocated = cbPostAllocated;
+ *ppbmAllocationBitmap = vdiAllocationBitmapCreate(pbBlockData, getImageBlockSize(&pImage->Header));
+ if (RT_UNLIKELY(!*ppbmAllocationBitmap))
+ rc = VERR_NO_MEMORY;
+ else
+ rc = VERR_VD_DISCARD_ALIGNMENT_NOT_MET;
+
+ RTMemFree(pvBlock);
+ }
+ } /* if: no complete block discarded */
+ } /* if: Block is allocated. */
+ /* else: nothing to do. */
+ } while (0);
+
+ if (pcbActuallyDiscarded)
+ *pcbActuallyDiscarded = cbDiscard;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRepair */
+static DECLCALLBACK(int) vdiRepair(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, uint32_t fFlags)
+{
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
+ int rc;
+ PVDINTERFACEERROR pIfError;
+ PVDINTERFACEIOINT pIfIo;
+ PVDIOSTORAGE pStorage = NULL;
+ uint64_t cbFile;
+ PVDIIMAGEBLOCKPOINTER paBlocks = NULL;
+ uint32_t *pu32BlockBitmap = NULL;
+ VDIPREHEADER PreHdr;
+ VDIHEADER Hdr;
+
+ pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ pIfError = VDIfErrorGet(pVDIfsDisk);
+
+ do
+ {
+ bool fRepairBlockArray = false;
+ bool fRepairHdr = false;
+
+ rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags( fFlags & VD_REPAIR_DRY_RUN
+ ? VD_OPEN_FLAGS_READONLY
+ : 0,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, "VDI: Failed to open image \"%s\"", pszFilename);
+ break;
+ }
+
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, "VDI: Failed to query image size");
+ break;
+ }
+
+ /* Read pre-header. */
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, &PreHdr, sizeof(PreHdr));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, N_("VDI: Error reading pre-header in '%s'"), pszFilename);
+ break;
+ }
+ vdiConvPreHeaderEndianess(VDIECONV_F2H, &PreHdr, &PreHdr);
+ rc = vdiValidatePreHeader(&PreHdr);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ N_("VDI: invalid pre-header in '%s'"), pszFilename);
+ break;
+ }
+
+ /* Read header. */
+ Hdr.uVersion = PreHdr.u32Version;
+ switch (GET_MAJOR_HEADER_VERSION(&Hdr))
+ {
+ case 0:
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, sizeof(PreHdr),
+ &Hdr.u.v0, sizeof(Hdr.u.v0));
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, N_("VDI: error reading v0 header in '%s'"),
+ pszFilename);
+ vdiConvHeaderEndianessV0(VDIECONV_F2H, &Hdr.u.v0, &Hdr.u.v0);
+ break;
+ case 1:
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, sizeof(PreHdr),
+ &Hdr.u.v1, sizeof(Hdr.u.v1));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, N_("VDI: error reading v1 header in '%s'"),
+ pszFilename);
+ }
+ vdiConvHeaderEndianessV1(VDIECONV_F2H, &Hdr.u.v1, &Hdr.u.v1);
+ if (Hdr.u.v1.cbHeader >= sizeof(Hdr.u.v1plus))
+ {
+ /* Read the VDI 1.1+ header completely. */
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, sizeof(PreHdr),
+ &Hdr.u.v1plus, sizeof(Hdr.u.v1plus));
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, N_("VDI: error reading v1.1+ header in '%s'"),
+ pszFilename);
+ vdiConvHeaderEndianessV1p(VDIECONV_F2H, &Hdr.u.v1plus, &Hdr.u.v1plus);
+ }
+ break;
+ default:
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ N_("VDI: unsupported major version %u in '%s'"),
+ GET_MAJOR_HEADER_VERSION(&Hdr), pszFilename);
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdiValidateHeader(&Hdr);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ N_("VDI: invalid header in '%s'"), pszFilename);
+ break;
+ }
+ }
+
+ /*
+ * Check that the disk size is correctly aligned,
+ * see comment above the same check in vdiImageReadHeader().
+ */
+ uint64_t cbDisk = getImageDiskSize(&Hdr);
+ if (cbDisk & 0x1ff)
+ {
+ uint64_t cbDiskNew = cbDisk & ~UINT64_C(0x1ff);
+ vdIfErrorMessage(pIfError, "Disk size in the header is not sector aligned, rounding down (%llu -> %llu)\n",
+ cbDisk, cbDiskNew);
+ setImageDiskSize(&Hdr, cbDiskNew);
+ fRepairHdr = true;
+ }
+
+ /* Setup image parameters by header. */
+ uint64_t offStartBlocks, offStartData;
+ size_t cbTotalBlockData;
+
+ offStartBlocks = getImageBlocksOffset(&Hdr);
+ offStartData = getImageDataOffset(&Hdr);
+ cbTotalBlockData = getImageExtraBlockSize(&Hdr) + getImageBlockSize(&Hdr);
+
+ /* Allocate memory for blocks array. */
+ paBlocks = (PVDIIMAGEBLOCKPOINTER)RTMemAlloc(sizeof(VDIIMAGEBLOCKPOINTER) * getImageBlocks(&Hdr));
+ if (!paBlocks)
+ {
+ rc = vdIfError(pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ "Failed to allocate memory for block array");
+ break;
+ }
+
+ /* Read blocks array. */
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, offStartBlocks, paBlocks,
+ getImageBlocks(&Hdr) * sizeof(VDIIMAGEBLOCKPOINTER));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Failed to read block array (at %llu), %Rrc",
+ offStartBlocks, rc);
+ break;
+ }
+ vdiConvBlocksEndianess(VDIECONV_F2H, paBlocks, getImageBlocks(&Hdr));
+
+ pu32BlockBitmap = (uint32_t *)RTMemAllocZ(RT_ALIGN_Z(getImageBlocks(&Hdr) / 8, 4));
+ if (!pu32BlockBitmap)
+ {
+ rc = vdIfError(pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ "Failed to allocate memory for block bitmap");
+ break;
+ }
+
+ for (uint32_t i = 0; i < getImageBlocks(&Hdr); i++)
+ {
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(paBlocks[i]))
+ {
+ uint64_t offBlock = (uint64_t)paBlocks[i] * cbTotalBlockData
+ + offStartData;
+
+ /*
+ * Check that the offsets are valid (inside of the image) and
+ * that there are no double references.
+ */
+ if (offBlock + cbTotalBlockData > cbFile)
+ {
+ vdIfErrorMessage(pIfError, "Entry %u points to invalid offset %llu, clearing\n",
+ i, offBlock);
+ paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ fRepairBlockArray = true;
+ }
+ else if (ASMBitTestAndSet(pu32BlockBitmap, paBlocks[i]))
+ {
+ vdIfErrorMessage(pIfError, "Entry %u points to an already referenced data block, clearing\n",
+ i);
+ paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ fRepairBlockArray = true;
+ }
+ }
+ }
+
+ /* Write repaired structures now. */
+ if (!fRepairBlockArray && !fRepairHdr)
+ vdIfErrorMessage(pIfError, "VDI image is in a consistent state, no repair required\n");
+ else if (!(fFlags & VD_REPAIR_DRY_RUN))
+ {
+ if (fRepairHdr)
+ {
+ switch (GET_MAJOR_HEADER_VERSION(&Hdr))
+ {
+ case 0:
+ {
+ VDIHEADER0 Hdr0;
+ vdiConvHeaderEndianessV0(VDIECONV_H2F, &Hdr0, &Hdr.u.v0);
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, sizeof(VDIPREHEADER),
+ &Hdr0, sizeof(Hdr0));
+ break;
+ }
+ case 1:
+ if (Hdr.u.v1plus.cbHeader < sizeof(Hdr.u.v1plus))
+ {
+ VDIHEADER1 Hdr1;
+ vdiConvHeaderEndianessV1(VDIECONV_H2F, &Hdr1, &Hdr.u.v1);
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, sizeof(VDIPREHEADER),
+ &Hdr1, sizeof(Hdr1));
+ }
+ else
+ {
+ VDIHEADER1PLUS Hdr1plus;
+ vdiConvHeaderEndianessV1p(VDIECONV_H2F, &Hdr1plus, &Hdr.u.v1plus);
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, sizeof(VDIPREHEADER),
+ &Hdr1plus, sizeof(Hdr1plus));
+ }
+ break;
+ default:
+ AssertMsgFailed(("Header indicates unsupported version which should not happen here!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ break;
+ }
+ }
+
+ if (fRepairBlockArray)
+ {
+ vdIfErrorMessage(pIfError, "Writing repaired block allocation table...\n");
+
+ vdiConvBlocksEndianess(VDIECONV_H2F, paBlocks, getImageBlocks(&Hdr));
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, offStartBlocks, paBlocks,
+ getImageBlocks(&Hdr) * sizeof(VDIIMAGEBLOCKPOINTER));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Could not write repaired block allocation table (at %llu), %Rrc",
+ offStartBlocks, rc);
+ break;
+ }
+ }
+ }
+
+ vdIfErrorMessage(pIfError, "Corrupted VDI image repaired successfully\n");
+ } while(0);
+
+ if (paBlocks)
+ RTMemFree(paBlocks);
+
+ if (pu32BlockBitmap)
+ RTMemFree(pu32BlockBitmap);
+
+ if (pStorage)
+ {
+ int rc2 = vdIfIoIntFileClose(pIfIo, pStorage);
+ if (RT_SUCCESS(rc))
+ rc = rc2; /* Propagate error code only if repairing was successful. */
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+const VDIMAGEBACKEND g_VDIBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "VDI",
+ /* uBackendCaps */
+ VD_CAP_UUID | VD_CAP_CREATE_FIXED | VD_CAP_CREATE_DYNAMIC
+ | VD_CAP_DIFF | VD_CAP_FILE | VD_CAP_ASYNC | VD_CAP_VFS | VD_CAP_DISCARD
+ | VD_CAP_PREFERRED,
+ /* paFileExtensions */
+ s_aVdiFileExtensions,
+ /* paConfigInfo */
+ vdiConfigInfo,
+ /* pfnProbe */
+ vdiProbe,
+ /* pfnOpen */
+ vdiOpen,
+ /* pfnCreate */
+ vdiCreate,
+ /* pfnRename */
+ vdiRename,
+ /* pfnClose */
+ vdiClose,
+ /* pfnRead */
+ vdiRead,
+ /* pfnWrite */
+ vdiWrite,
+ /* pfnFlush */
+ vdiFlush,
+ /* pfnDiscard */
+ vdiDiscard,
+ /* pfnGetVersion */
+ vdiGetVersion,
+ /* pfnGetFileSize */
+ vdiGetFileSize,
+ /* pfnGetPCHSGeometry */
+ vdiGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ vdiSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ vdiGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ vdiSetLCHSGeometry,
+ /* pfnQueryRegions */
+ vdiQueryRegions,
+ /* pfnRegionListRelease */
+ vdiRegionListRelease,
+ /* pfnGetImageFlags */
+ vdiGetImageFlags,
+ /* pfnGetOpenFlags */
+ vdiGetOpenFlags,
+ /* pfnSetOpenFlags */
+ vdiSetOpenFlags,
+ /* pfnGetComment */
+ vdiGetComment,
+ /* pfnSetComment */
+ vdiSetComment,
+ /* pfnGetUuid */
+ vdiGetUuid,
+ /* pfnSetUuid */
+ vdiSetUuid,
+ /* pfnGetModificationUuid */
+ vdiGetModificationUuid,
+ /* pfnSetModificationUuid */
+ vdiSetModificationUuid,
+ /* pfnGetParentUuid */
+ vdiGetParentUuid,
+ /* pfnSetParentUuid */
+ vdiSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ vdiGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ vdiSetParentModificationUuid,
+ /* pfnDump */
+ vdiDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ NULL,
+ /* pfnSetParentFilename */
+ NULL,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ vdiCompact,
+ /* pfnResize */
+ vdiResize,
+ /* pfnRepair */
+ vdiRepair,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
diff --git a/src/VBox/Storage/VDICore.h b/src/VBox/Storage/VDICore.h
new file mode 100644
index 00000000..6d581ded
--- /dev/null
+++ b/src/VBox/Storage/VDICore.h
@@ -0,0 +1,645 @@
+/* $Id: VDICore.h $ */
+/** @file
+ * Virtual Disk Image (VDI), Core Code Header (internal).
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Storage_VDICore_h
+#define VBOX_INCLUDED_SRC_Storage_VDICore_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <VBox/vd.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+
+
+/*******************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*******************************************************************************/
+
+/** Image info, not handled anyhow.
+ * Must be less than 64 bytes in length, including the trailing 0.
+ */
+#define VDI_IMAGE_FILE_INFO "<<< Oracle VM VirtualBox Disk Image >>>\n"
+
+/** The Sector size.
+ * Currently we support only 512 bytes sectors.
+ */
+#define VDI_GEOMETRY_SECTOR_SIZE (512)
+/** 512 = 2^^9 */
+#define VDI_GEOMETRY_SECTOR_SHIFT (9)
+
+/**
+ * Harddisk geometry.
+ */
+#pragma pack(1)
+typedef struct VDIDISKGEOMETRY
+{
+ /** Cylinders. */
+ uint32_t cCylinders;
+ /** Heads. */
+ uint32_t cHeads;
+ /** Sectors per track. */
+ uint32_t cSectors;
+ /** Sector size. (bytes per sector) */
+ uint32_t cbSector;
+} VDIDISKGEOMETRY, *PVDIDISKGEOMETRY;
+#pragma pack()
+
+/** Image signature. */
+#define VDI_IMAGE_SIGNATURE (0xbeda107f)
+
+/**
+ * Pre-Header to be stored in image file - used for version control.
+ */
+#pragma pack(1)
+typedef struct VDIPREHEADER
+{
+ /** Just text info about image type, for eyes only. */
+ char szFileInfo[64];
+ /** The image signature (VDI_IMAGE_SIGNATURE). */
+ uint32_t u32Signature;
+ /** The image version (VDI_IMAGE_VERSION). */
+ uint32_t u32Version;
+} VDIPREHEADER, *PVDIPREHEADER;
+#pragma pack()
+
+/**
+ * Size of szComment field of HDD image header.
+ */
+#define VDI_IMAGE_COMMENT_SIZE 256
+
+/**
+ * Header to be stored in image file, VDI_IMAGE_VERSION_MAJOR = 0.
+ * Prepended by VDIPREHEADER.
+ */
+#pragma pack(1)
+typedef struct VDIHEADER0
+{
+ /** The image type (VDI_IMAGE_TYPE_*). */
+ uint32_t u32Type;
+ /** Image flags (VDI_IMAGE_FLAGS_*). */
+ uint32_t fFlags;
+ /** Image comment. (UTF-8) */
+ char szComment[VDI_IMAGE_COMMENT_SIZE];
+ /** Legacy image geometry (previous code stored PCHS there). */
+ VDIDISKGEOMETRY LegacyGeometry;
+ /** Size of disk (in bytes). */
+ uint64_t cbDisk;
+ /** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) */
+ uint32_t cbBlock;
+ /** Number of blocks. */
+ uint32_t cBlocks;
+ /** Number of allocated blocks. */
+ uint32_t cBlocksAllocated;
+ /** UUID of image. */
+ RTUUID uuidCreate;
+ /** UUID of image's last modification. */
+ RTUUID uuidModify;
+ /** Only for secondary images - UUID of primary image. */
+ RTUUID uuidLinkage;
+} VDIHEADER0, *PVDIHEADER0;
+#pragma pack()
+
+/**
+ * Header to be stored in image file, VDI_IMAGE_VERSION_MAJOR = 1,
+ * VDI_IMAGE_VERSION_MINOR = 1. Prepended by VDIPREHEADER.
+ */
+#pragma pack(1)
+typedef struct VDIHEADER1
+{
+ /** Size of this structure in bytes. */
+ uint32_t cbHeader;
+ /** The image type (VDI_IMAGE_TYPE_*). */
+ uint32_t u32Type;
+ /** Image flags (VDI_IMAGE_FLAGS_*). */
+ uint32_t fFlags;
+ /** Image comment. (UTF-8) */
+ char szComment[VDI_IMAGE_COMMENT_SIZE];
+ /** Offset of Blocks array from the beginning of image file.
+ * Should be sector-aligned for HDD access optimization. */
+ uint32_t offBlocks;
+ /** Offset of image data from the beginning of image file.
+ * Should be sector-aligned for HDD access optimization. */
+ uint32_t offData;
+ /** Legacy image geometry (previous code stored PCHS there). */
+ VDIDISKGEOMETRY LegacyGeometry;
+ /** Was BIOS HDD translation mode, now unused. */
+ uint32_t u32Dummy;
+ /** Size of disk (in bytes). */
+ uint64_t cbDisk;
+ /** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) Should be a power of 2! */
+ uint32_t cbBlock;
+ /** Size of additional service information of every data block.
+ * Prepended before block data. May be 0.
+ * Should be a power of 2 and sector-aligned for optimization reasons. */
+ uint32_t cbBlockExtra;
+ /** Number of blocks. */
+ uint32_t cBlocks;
+ /** Number of allocated blocks. */
+ uint32_t cBlocksAllocated;
+ /** UUID of image. */
+ RTUUID uuidCreate;
+ /** UUID of image's last modification. */
+ RTUUID uuidModify;
+ /** Only for secondary images - UUID of previous image. */
+ RTUUID uuidLinkage;
+ /** Only for secondary images - UUID of previous image's last modification. */
+ RTUUID uuidParentModify;
+} VDIHEADER1, *PVDIHEADER1;
+#pragma pack()
+
+/**
+ * Header to be stored in image file, VDI_IMAGE_VERSION_MAJOR = 1,
+ * VDI_IMAGE_VERSION_MINOR = 1, the slightly changed variant necessary as the
+ * old released code doesn't support changing the minor version at all.
+ */
+#pragma pack(1)
+typedef struct VDIHEADER1PLUS
+{
+ /** Size of this structure in bytes. */
+ uint32_t cbHeader;
+ /** The image type (VDI_IMAGE_TYPE_*). */
+ uint32_t u32Type;
+ /** Image flags (VDI_IMAGE_FLAGS_*). */
+ uint32_t fFlags;
+ /** Image comment. (UTF-8) */
+ char szComment[VDI_IMAGE_COMMENT_SIZE];
+ /** Offset of blocks array from the beginning of image file.
+ * Should be sector-aligned for HDD access optimization. */
+ uint32_t offBlocks;
+ /** Offset of image data from the beginning of image file.
+ * Should be sector-aligned for HDD access optimization. */
+ uint32_t offData;
+ /** Legacy image geometry (previous code stored PCHS there). */
+ VDIDISKGEOMETRY LegacyGeometry;
+ /** Was BIOS HDD translation mode, now unused. */
+ uint32_t u32Dummy;
+ /** Size of disk (in bytes). */
+ uint64_t cbDisk;
+ /** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) Should be a power of 2! */
+ uint32_t cbBlock;
+ /** Size of additional service information of every data block.
+ * Prepended before block data. May be 0.
+ * Should be a power of 2 and sector-aligned for optimization reasons. */
+ uint32_t cbBlockExtra;
+ /** Number of blocks. */
+ uint32_t cBlocks;
+ /** Number of allocated blocks. */
+ uint32_t cBlocksAllocated;
+ /** UUID of image. */
+ RTUUID uuidCreate;
+ /** UUID of image's last modification. */
+ RTUUID uuidModify;
+ /** Only for secondary images - UUID of previous image. */
+ RTUUID uuidLinkage;
+ /** Only for secondary images - UUID of previous image's last modification. */
+ RTUUID uuidParentModify;
+ /** LCHS image geometry (new field in VDI1.2 version. */
+ VDIDISKGEOMETRY LCHSGeometry;
+} VDIHEADER1PLUS, *PVDIHEADER1PLUS;
+#pragma pack()
+
+/**
+ * Header structure for all versions.
+ */
+typedef struct VDIHEADER
+{
+ unsigned uVersion;
+ union
+ {
+ VDIHEADER0 v0;
+ VDIHEADER1 v1;
+ VDIHEADER1PLUS v1plus;
+ } u;
+} VDIHEADER, *PVDIHEADER;
+
+/**
+ * File alignment boundary for both the block array and data area. Should be
+ * at least the size of a physical sector on disk for performance reasons.
+ * Bumped to 1MB because SSDs tend to have 8kb per page so we don't have to worry
+ * about proper alignment in the near future again. */
+#define VDI_DATA_ALIGN _1M
+
+/** Block 'pointer'. */
+typedef uint32_t VDIIMAGEBLOCKPOINTER;
+/** Pointer to a block 'pointer'. */
+typedef VDIIMAGEBLOCKPOINTER *PVDIIMAGEBLOCKPOINTER;
+
+/**
+ * Block marked as free is not allocated in image file, read from this
+ * block may returns any random data.
+ */
+#define VDI_IMAGE_BLOCK_FREE ((VDIIMAGEBLOCKPOINTER)~0)
+
+/**
+ * Block marked as zero is not allocated in image file, read from this
+ * block returns zeroes.
+ */
+#define VDI_IMAGE_BLOCK_ZERO ((VDIIMAGEBLOCKPOINTER)~1)
+
+/**
+ * Block 'pointer' >= VDI_IMAGE_BLOCK_UNALLOCATED indicates block is not
+ * allocated in image file.
+ */
+#define VDI_IMAGE_BLOCK_UNALLOCATED (VDI_IMAGE_BLOCK_ZERO)
+#define IS_VDI_IMAGE_BLOCK_ALLOCATED(bp) (bp < VDI_IMAGE_BLOCK_UNALLOCATED)
+
+#define GET_MAJOR_HEADER_VERSION(ph) (VDI_GET_VERSION_MAJOR((ph)->uVersion))
+#define GET_MINOR_HEADER_VERSION(ph) (VDI_GET_VERSION_MINOR((ph)->uVersion))
+
+/** @name VDI image types
+ * @{ */
+typedef enum VDIIMAGETYPE
+{
+ /** Normal dynamically growing base image file. */
+ VDI_IMAGE_TYPE_NORMAL = 1,
+ /** Preallocated base image file of a fixed size. */
+ VDI_IMAGE_TYPE_FIXED,
+ /** Dynamically growing image file for undo/commit changes support. */
+ VDI_IMAGE_TYPE_UNDO,
+ /** Dynamically growing image file for differencing support. */
+ VDI_IMAGE_TYPE_DIFF,
+
+ /** First valid image type value. */
+ VDI_IMAGE_TYPE_FIRST = VDI_IMAGE_TYPE_NORMAL,
+ /** Last valid image type value. */
+ VDI_IMAGE_TYPE_LAST = VDI_IMAGE_TYPE_DIFF
+} VDIIMAGETYPE;
+/** Pointer to VDI image type. */
+typedef VDIIMAGETYPE *PVDIIMAGETYPE;
+/** @} */
+
+/*******************************************************************************
+* Internal Functions for header access *
+*******************************************************************************/
+DECLINLINE(VDIIMAGETYPE) getImageType(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return (VDIIMAGETYPE)ph->u.v0.u32Type;
+ case 1: return (VDIIMAGETYPE)ph->u.v1.u32Type;
+ }
+ AssertFailed();
+ return (VDIIMAGETYPE)0;
+}
+
+DECLINLINE(unsigned) getImageFlags(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0:
+ /* VDI image flag conversion to VD image flags. */
+ return ph->u.v0.fFlags << 8;
+ case 1:
+ /* VDI image flag conversion to VD image flags. */
+ return ph->u.v1.fFlags << 8;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(char *) getImageComment(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return &ph->u.v0.szComment[0];
+ case 1: return &ph->u.v1.szComment[0];
+ }
+ AssertFailed();
+ return NULL;
+}
+
+DECLINLINE(unsigned) getImageBlocksOffset(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return (sizeof(VDIPREHEADER) + sizeof(VDIHEADER0));
+ case 1: return ph->u.v1.offBlocks;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(uint32_t) getImageDataOffset(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return sizeof(VDIPREHEADER) + sizeof(VDIHEADER0) + \
+ (ph->u.v0.cBlocks * sizeof(VDIIMAGEBLOCKPOINTER));
+ case 1: return ph->u.v1.offData;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(void) setImageDataOffset(PVDIHEADER ph, uint32_t offData)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return;
+ case 1: ph->u.v1.offData = offData; return;
+ }
+ AssertFailed();
+}
+
+DECLINLINE(PVDIDISKGEOMETRY) getImageLCHSGeometry(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return NULL;
+ case 1:
+ switch (GET_MINOR_HEADER_VERSION(ph))
+ {
+ case 1:
+ if (ph->u.v1.cbHeader < sizeof(ph->u.v1plus))
+ return NULL;
+ else
+ return &ph->u.v1plus.LCHSGeometry;
+ }
+ }
+ AssertFailed();
+ return NULL;
+}
+
+DECLINLINE(uint64_t) getImageDiskSize(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return ph->u.v0.cbDisk;
+ case 1: return ph->u.v1.cbDisk;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(void) setImageDiskSize(PVDIHEADER ph, uint64_t cbDisk)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: ph->u.v0.cbDisk = cbDisk; return;
+ case 1: ph->u.v1.cbDisk = cbDisk; return;
+ }
+ AssertFailed();
+}
+
+DECLINLINE(unsigned) getImageBlockSize(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return ph->u.v0.cbBlock;
+ case 1: return ph->u.v1.cbBlock;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(unsigned) getImageExtraBlockSize(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return 0;
+ case 1: return ph->u.v1.cbBlockExtra;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(unsigned) getImageBlocks(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return ph->u.v0.cBlocks;
+ case 1: return ph->u.v1.cBlocks;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(void) setImageBlocks(PVDIHEADER ph, unsigned cBlocks)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: ph->u.v0.cBlocks = cBlocks; return;
+ case 1: ph->u.v1.cBlocks = cBlocks; return;
+ }
+ AssertFailed();
+}
+
+
+DECLINLINE(unsigned) getImageBlocksAllocated(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return ph->u.v0.cBlocksAllocated;
+ case 1: return ph->u.v1.cBlocksAllocated;
+ }
+ AssertFailed();
+ return 0;
+}
+
+DECLINLINE(void) setImageBlocksAllocated(PVDIHEADER ph, unsigned cBlocks)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: ph->u.v0.cBlocksAllocated = cBlocks; return;
+ case 1: ph->u.v1.cBlocksAllocated = cBlocks; return;
+ }
+ AssertFailed();
+}
+
+#ifdef _MSC_VER
+# pragma warning(disable:4366) /* (harmless "misalignment") */
+#endif
+
+DECLINLINE(PRTUUID) getImageCreationUUID(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return &ph->u.v0.uuidCreate;
+ case 1: return &ph->u.v1.uuidCreate;
+ }
+ AssertFailed();
+ return NULL;
+}
+
+DECLINLINE(PRTUUID) getImageModificationUUID(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return &ph->u.v0.uuidModify;
+ case 1: return &ph->u.v1.uuidModify;
+ }
+ AssertFailed();
+ return NULL;
+}
+
+DECLINLINE(PRTUUID) getImageParentUUID(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 0: return &ph->u.v0.uuidLinkage;
+ case 1: return &ph->u.v1.uuidLinkage;
+ }
+ AssertFailed();
+ return NULL;
+}
+
+DECLINLINE(PRTUUID) getImageParentModificationUUID(PVDIHEADER ph)
+{
+ switch (GET_MAJOR_HEADER_VERSION(ph))
+ {
+ case 1: return &ph->u.v1.uuidParentModify;
+ }
+ AssertFailed();
+ return NULL;
+}
+
+#ifdef _MSC_VER
+# pragma warning(default:4366)
+#endif
+
+/**
+ * Image structure
+ */
+typedef struct VDIIMAGEDESC
+{
+ /** Opaque storage handle. */
+ PVDIOSTORAGE pStorage;
+ /** Image open flags, VD_OPEN_FLAGS_*. */
+ unsigned uOpenFlags;
+ /** Image pre-header. */
+ VDIPREHEADER PreHeader;
+ /** Image header. */
+ VDIHEADER Header;
+ /** Pointer to a block array. */
+ PVDIIMAGEBLOCKPOINTER paBlocks;
+ /** Pointer to the block array for back resolving (used if discarding is enabled). */
+ unsigned *paBlocksRev;
+ /** fFlags copy from image header, for speed optimization. */
+ unsigned uImageFlags;
+ /** Start offset of block array in image file, here for speed optimization. */
+ unsigned offStartBlocks;
+ /** Start offset of data in image file, here for speed optimization. */
+ unsigned offStartData;
+ /** Block mask for getting the offset into a block from a byte hdd offset. */
+ unsigned uBlockMask;
+ /** Block shift value for converting byte hdd offset into paBlock index. */
+ unsigned uShiftOffset2Index;
+ /** Offset of data from the beginning of block. */
+ unsigned offStartBlockData;
+ /** Total size of image block (including the extra data). */
+ unsigned cbTotalBlockData;
+ /** Allocation Block Size */
+ unsigned cbAllocationBlock;
+ /** Container filename. (UTF-8) */
+ const char *pszFilename;
+ /** Physical geometry of this image (never actually stored). */
+ VDGEOMETRY PCHSGeometry;
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+ /** Current size of the image (used for range validation when reading). */
+ uint64_t cbImage;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} VDIIMAGEDESC, *PVDIIMAGEDESC;
+
+/**
+ * Async block discard states.
+ */
+typedef enum VDIBLOCKDISCARDSTATE
+{
+ /** Invalid. */
+ VDIBLOCKDISCARDSTATE_INVALID = 0,
+ /** Read the last block. */
+ VDIBLOCKDISCARDSTATE_READ_BLOCK,
+ /** Write block into the hole. */
+ VDIBLOCKDISCARDSTATE_WRITE_BLOCK,
+ /** Update metadata. */
+ VDIBLOCKDISCARDSTATE_UPDATE_METADATA,
+ /** 32bit hack. */
+ VDIBLOCKDISCARDSTATE_32BIT_HACK = 0x7fffffff
+} VDIBLOCKDISCARDSTATE;
+
+/**
+ * Async block discard structure.
+ */
+typedef struct VDIBLOCKDISCARDASYNC
+{
+ /** State of the block discard. */
+ VDIBLOCKDISCARDSTATE enmState;
+ /** Pointer to the block data. */
+ void *pvBlock;
+ /** Block index in the block table. */
+ unsigned uBlock;
+ /** Block pointer to the block to discard. */
+ VDIIMAGEBLOCKPOINTER ptrBlockDiscard;
+ /** Index of the last block in the reverse block table. */
+ unsigned idxLastBlock;
+ /** Index of the last block in the block table (gathered from the reverse block table). */
+ unsigned uBlockLast;
+} VDIBLOCKDISCARDASYNC, *PVDIBLOCKDISCARDASYNC;
+
+/**
+ * Async image expansion state.
+ */
+typedef struct VDIASYNCBLOCKALLOC
+{
+ /** Number of blocks allocated. */
+ unsigned cBlocksAllocated;
+ /** Block index to allocate. */
+ unsigned uBlock;
+} VDIASYNCBLOCKALLOC, *PVDIASYNCBLOCKALLOC;
+
+/**
+ * Endianess conversion direction.
+ */
+typedef enum VDIECONV
+{
+ /** Host to file endianess. */
+ VDIECONV_H2F = 0,
+ /** File to host endianess. */
+ VDIECONV_F2H
+} VDIECONV;
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_VDICore_h */
+
diff --git a/src/VBox/Storage/VDIfTcpNet.cpp b/src/VBox/Storage/VDIfTcpNet.cpp
new file mode 100644
index 00000000..1be5065a
--- /dev/null
+++ b/src/VBox/Storage/VDIfTcpNet.cpp
@@ -0,0 +1,630 @@
+/* $Id: VDIfTcpNet.cpp $ */
+/** @file
+ * VD - Virtual disk container implementation, Default TCP/IP interface implementation.
+ */
+
+/*
+ * Copyright (C) 2019-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/vd.h>
+#include <VBox/err.h>
+#include <iprt/asm.h>
+#include <iprt/log.h>
+#include <iprt/tcp.h>
+#include <iprt/sg.h>
+#include <iprt/poll.h>
+#include <iprt/pipe.h>
+#include <iprt/system.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** Pollset id of the socket. */
+#define VDSOCKET_POLL_ID_SOCKET 0
+/** Pollset id of the pipe. */
+#define VDSOCKET_POLL_ID_PIPE 1
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Socket data.
+ */
+typedef struct VDSOCKETINT
+{
+ /** IPRT socket handle. */
+ RTSOCKET hSocket;
+ /** Pollset with the wakeup pipe and socket. */
+ RTPOLLSET hPollSet;
+ /** Pipe endpoint - read (in the pollset). */
+ RTPIPE hPipeR;
+ /** Pipe endpoint - write. */
+ RTPIPE hPipeW;
+ /** Flag whether the thread was woken up. */
+ volatile bool fWokenUp;
+ /** Flag whether the thread is waiting in the select call. */
+ volatile bool fWaiting;
+ /** Old event mask. */
+ uint32_t fEventsOld;
+} VDSOCKETINT, *PVDSOCKETINT;
+
+
+/**
+ * VD TCP/NET interface instance data.
+ */
+typedef struct VDIFINSTINT
+{
+ /** The TCP/NET interface descriptor. */
+ VDINTERFACETCPNET VdIfTcpNet;
+} VDIFINSTINT;
+/** Pointer to the VD TCP/NET interface instance data. */
+typedef VDIFINSTINT *PVDIFINSTINT;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSocketCreate} */
+static DECLCALLBACK(int) vdIfTcpNetSocketCreate(uint32_t fFlags, PVDSOCKET phVdSock)
+{
+ int rc = VINF_SUCCESS;
+ int rc2 = VINF_SUCCESS;
+ PVDSOCKETINT pSockInt = NULL;
+
+ pSockInt = (PVDSOCKETINT)RTMemAllocZ(sizeof(VDSOCKETINT));
+ if (!pSockInt)
+ return VERR_NO_MEMORY;
+
+ pSockInt->hSocket = NIL_RTSOCKET;
+ pSockInt->hPollSet = NIL_RTPOLLSET;
+ pSockInt->hPipeR = NIL_RTPIPE;
+ pSockInt->hPipeW = NIL_RTPIPE;
+ pSockInt->fWokenUp = false;
+ pSockInt->fWaiting = false;
+
+ if (fFlags & VD_INTERFACETCPNET_CONNECT_EXTENDED_SELECT)
+ {
+ /* Init pipe and pollset. */
+ rc = RTPipeCreate(&pSockInt->hPipeR, &pSockInt->hPipeW, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetCreate(&pSockInt->hPollSet);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetAddPipe(pSockInt->hPollSet, pSockInt->hPipeR,
+ RTPOLL_EVT_READ, VDSOCKET_POLL_ID_PIPE);
+ if (RT_SUCCESS(rc))
+ {
+ *phVdSock = pSockInt;
+ return VINF_SUCCESS;
+ }
+
+ RTPollSetRemove(pSockInt->hPollSet, VDSOCKET_POLL_ID_PIPE);
+ rc2 = RTPollSetDestroy(pSockInt->hPollSet);
+ AssertRC(rc2);
+ }
+
+ rc2 = RTPipeClose(pSockInt->hPipeR);
+ AssertRC(rc2);
+ rc2 = RTPipeClose(pSockInt->hPipeW);
+ AssertRC(rc2);
+ }
+ }
+ else
+ {
+ *phVdSock = pSockInt;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pSockInt);
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSocketDestroy} */
+static DECLCALLBACK(int) vdIfTcpNetSocketDestroy(VDSOCKET hVdSock)
+{
+ int rc = VINF_SUCCESS;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ /* Destroy the pipe and pollset if necessary. */
+ if (pSockInt->hPollSet != NIL_RTPOLLSET)
+ {
+ if (pSockInt->hSocket != NIL_RTSOCKET)
+ {
+ rc = RTPollSetRemove(pSockInt->hPollSet, VDSOCKET_POLL_ID_SOCKET);
+ Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND);
+ }
+ rc = RTPollSetRemove(pSockInt->hPollSet, VDSOCKET_POLL_ID_PIPE);
+ AssertRC(rc);
+ rc = RTPollSetDestroy(pSockInt->hPollSet);
+ AssertRC(rc);
+ rc = RTPipeClose(pSockInt->hPipeR);
+ AssertRC(rc);
+ rc = RTPipeClose(pSockInt->hPipeW);
+ AssertRC(rc);
+ }
+
+ if (pSockInt->hSocket != NIL_RTSOCKET)
+ rc = RTTcpClientCloseEx(pSockInt->hSocket, false /*fGracefulShutdown*/);
+
+ RTMemFree(pSockInt);
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnClientConnect} */
+static DECLCALLBACK(int) vdIfTcpNetClientConnect(VDSOCKET hVdSock, const char *pszAddress, uint32_t uPort,
+ RTMSINTERVAL cMillies)
+{
+ int rc = VINF_SUCCESS;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ rc = RTTcpClientConnectEx(pszAddress, uPort, &pSockInt->hSocket, cMillies, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Add to the pollset if required. */
+ if (pSockInt->hPollSet != NIL_RTPOLLSET)
+ {
+ pSockInt->fEventsOld = RTPOLL_EVT_READ | RTPOLL_EVT_WRITE | RTPOLL_EVT_ERROR;
+
+ rc = RTPollSetAddSocket(pSockInt->hPollSet, pSockInt->hSocket,
+ pSockInt->fEventsOld, VDSOCKET_POLL_ID_SOCKET);
+ }
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ rc = RTTcpClientCloseEx(pSockInt->hSocket, false /*fGracefulShutdown*/);
+ }
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnClientClose} */
+static DECLCALLBACK(int) vdIfTcpNetClientClose(VDSOCKET hVdSock)
+{
+ int rc = VINF_SUCCESS;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ if (pSockInt->hPollSet != NIL_RTPOLLSET)
+ {
+ rc = RTPollSetRemove(pSockInt->hPollSet, VDSOCKET_POLL_ID_SOCKET);
+ AssertRC(rc);
+ }
+
+ rc = RTTcpClientCloseEx(pSockInt->hSocket, false /*fGracefulShutdown*/);
+ pSockInt->hSocket = NIL_RTSOCKET;
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnIsClientConnected} */
+static DECLCALLBACK(bool) vdIfTcpNetIsClientConnected(VDSOCKET hVdSock)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return pSockInt->hSocket != NIL_RTSOCKET;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSelectOne} */
+static DECLCALLBACK(int) vdIfTcpNetSelectOne(VDSOCKET hVdSock, RTMSINTERVAL cMillies)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpSelectOne(pSockInt->hSocket, cMillies);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnRead} */
+static DECLCALLBACK(int) vdIfTcpNetRead(VDSOCKET hVdSock, void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpRead(pSockInt->hSocket, pvBuffer, cbBuffer, pcbRead);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnWrite} */
+static DECLCALLBACK(int) vdIfTcpNetWrite(VDSOCKET hVdSock, const void *pvBuffer, size_t cbBuffer)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpWrite(pSockInt->hSocket, pvBuffer, cbBuffer);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSgWrite} */
+static DECLCALLBACK(int) vdIfTcpNetSgWrite(VDSOCKET hVdSock, PCRTSGBUF pSgBuf)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpSgWrite(pSockInt->hSocket, pSgBuf);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnReadNB} */
+static DECLCALLBACK(int) vdIfTcpNetReadNB(VDSOCKET hVdSock, void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpReadNB(pSockInt->hSocket, pvBuffer, cbBuffer, pcbRead);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnWriteNB} */
+static DECLCALLBACK(int) vdIfTcpNetWriteNB(VDSOCKET hVdSock, const void *pvBuffer, size_t cbBuffer, size_t *pcbWritten)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpWriteNB(pSockInt->hSocket, pvBuffer, cbBuffer, pcbWritten);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSgWriteNB} */
+static DECLCALLBACK(int) vdIfTcpNetSgWriteNB(VDSOCKET hVdSock, PRTSGBUF pSgBuf, size_t *pcbWritten)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpSgWriteNB(pSockInt->hSocket, pSgBuf, pcbWritten);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnFlush} */
+static DECLCALLBACK(int) vdIfTcpNetFlush(VDSOCKET hVdSock)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpFlush(pSockInt->hSocket);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSetSendCoalescing} */
+static DECLCALLBACK(int) vdIfTcpNetSetSendCoalescing(VDSOCKET hVdSock, bool fEnable)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpSetSendCoalescing(pSockInt->hSocket, fEnable);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnGetLocalAddress} */
+static DECLCALLBACK(int) vdIfTcpNetGetLocalAddress(VDSOCKET hVdSock, PRTNETADDR pAddr)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpGetLocalAddress(pSockInt->hSocket, pAddr);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnGetPeerAddress} */
+static DECLCALLBACK(int) vdIfTcpNetGetPeerAddress(VDSOCKET hVdSock, PRTNETADDR pAddr)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpGetPeerAddress(pSockInt->hSocket, pAddr);
+}
+
+static DECLCALLBACK(int) vdIfTcpNetSelectOneExPoll(VDSOCKET hVdSock, uint32_t fEvents,
+ uint32_t *pfEvents, RTMSINTERVAL cMillies)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t id = 0;
+ uint32_t fEventsRecv = 0;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ *pfEvents = 0;
+
+ if ( pSockInt->fEventsOld != fEvents
+ && pSockInt->hSocket != NIL_RTSOCKET)
+ {
+ uint32_t fPollEvents = 0;
+
+ if (fEvents & VD_INTERFACETCPNET_EVT_READ)
+ fPollEvents |= RTPOLL_EVT_READ;
+ if (fEvents & VD_INTERFACETCPNET_EVT_WRITE)
+ fPollEvents |= RTPOLL_EVT_WRITE;
+ if (fEvents & VD_INTERFACETCPNET_EVT_ERROR)
+ fPollEvents |= RTPOLL_EVT_ERROR;
+
+ rc = RTPollSetEventsChange(pSockInt->hPollSet, VDSOCKET_POLL_ID_SOCKET, fPollEvents);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pSockInt->fEventsOld = fEvents;
+ }
+
+ ASMAtomicXchgBool(&pSockInt->fWaiting, true);
+ if (ASMAtomicXchgBool(&pSockInt->fWokenUp, false))
+ {
+ ASMAtomicXchgBool(&pSockInt->fWaiting, false);
+ return VERR_INTERRUPTED;
+ }
+
+ rc = RTPoll(pSockInt->hPollSet, cMillies, &fEventsRecv, &id);
+ Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT);
+
+ ASMAtomicXchgBool(&pSockInt->fWaiting, false);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (id == VDSOCKET_POLL_ID_SOCKET)
+ {
+ fEventsRecv &= RTPOLL_EVT_VALID_MASK;
+
+ if (fEventsRecv & RTPOLL_EVT_READ)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_READ;
+ if (fEventsRecv & RTPOLL_EVT_WRITE)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_WRITE;
+ if (fEventsRecv & RTPOLL_EVT_ERROR)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_ERROR;
+ }
+ else
+ {
+ size_t cbRead = 0;
+ uint8_t abBuf[10];
+ Assert(id == VDSOCKET_POLL_ID_PIPE);
+ Assert((fEventsRecv & RTPOLL_EVT_VALID_MASK) == RTPOLL_EVT_READ);
+
+ /* We got interrupted, drain the pipe. */
+ rc = RTPipeRead(pSockInt->hPipeR, abBuf, sizeof(abBuf), &cbRead);
+ AssertRC(rc);
+
+ ASMAtomicXchgBool(&pSockInt->fWokenUp, false);
+
+ rc = VERR_INTERRUPTED;
+ }
+ }
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSelectOneEx} */
+static DECLCALLBACK(int) vdIfTcpNetSelectOneExNoPoll(VDSOCKET hVdSock, uint32_t fEvents, uint32_t *pfEvents, RTMSINTERVAL cMillies)
+{
+ RT_NOREF(cMillies); /** @todo timeouts */
+ int rc = VINF_SUCCESS;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ *pfEvents = 0;
+
+ ASMAtomicXchgBool(&pSockInt->fWaiting, true);
+ if (ASMAtomicXchgBool(&pSockInt->fWokenUp, false))
+ {
+ ASMAtomicXchgBool(&pSockInt->fWaiting, false);
+ return VERR_INTERRUPTED;
+ }
+
+ if ( pSockInt->hSocket == NIL_RTSOCKET
+ || !fEvents)
+ {
+ /*
+ * Only the pipe is configured or the caller doesn't wait for a socket event,
+ * wait until there is something to read from the pipe.
+ */
+ size_t cbRead = 0;
+ char ch = 0;
+ rc = RTPipeReadBlocking(pSockInt->hPipeR, &ch, 1, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbRead == 1);
+ rc = VERR_INTERRUPTED;
+ ASMAtomicXchgBool(&pSockInt->fWokenUp, false);
+ }
+ }
+ else
+ {
+ uint32_t fSelectEvents = 0;
+
+ if (fEvents & VD_INTERFACETCPNET_EVT_READ)
+ fSelectEvents |= RTSOCKET_EVT_READ;
+ if (fEvents & VD_INTERFACETCPNET_EVT_WRITE)
+ fSelectEvents |= RTSOCKET_EVT_WRITE;
+ if (fEvents & VD_INTERFACETCPNET_EVT_ERROR)
+ fSelectEvents |= RTSOCKET_EVT_ERROR;
+
+ if (fEvents & VD_INTERFACETCPNET_HINT_INTERRUPT)
+ {
+ uint32_t fEventsRecv = 0;
+
+ /* Make sure the socket is not in the pollset. */
+ rc = RTPollSetRemove(pSockInt->hPollSet, VDSOCKET_POLL_ID_SOCKET);
+ Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND);
+
+ for (;;)
+ {
+ uint32_t id = 0;
+ rc = RTPoll(pSockInt->hPollSet, 5, &fEvents, &id);
+ if (rc == VERR_TIMEOUT)
+ {
+ /* Check the socket. */
+ rc = RTTcpSelectOneEx(pSockInt->hSocket, fSelectEvents, &fEventsRecv, 0);
+ if (RT_SUCCESS(rc))
+ {
+ if (fEventsRecv & RTSOCKET_EVT_READ)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_READ;
+ if (fEventsRecv & RTSOCKET_EVT_WRITE)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_WRITE;
+ if (fEventsRecv & RTSOCKET_EVT_ERROR)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_ERROR;
+ break; /* Quit */
+ }
+ else if (rc != VERR_TIMEOUT)
+ break;
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ size_t cbRead = 0;
+ uint8_t abBuf[10];
+ Assert(id == VDSOCKET_POLL_ID_PIPE);
+ Assert((fEventsRecv & RTPOLL_EVT_VALID_MASK) == RTPOLL_EVT_READ);
+
+ /* We got interrupted, drain the pipe. */
+ rc = RTPipeRead(pSockInt->hPipeR, abBuf, sizeof(abBuf), &cbRead);
+ AssertRC(rc);
+
+ ASMAtomicXchgBool(&pSockInt->fWokenUp, false);
+
+ rc = VERR_INTERRUPTED;
+ break;
+ }
+ else
+ break;
+ }
+ }
+ else /* The caller waits for a socket event. */
+ {
+ uint32_t fEventsRecv = 0;
+
+ /* Loop until we got woken up or a socket event occurred. */
+ for (;;)
+ {
+ /** @todo find an adaptive wait algorithm based on the
+ * number of wakeups in the past. */
+ rc = RTTcpSelectOneEx(pSockInt->hSocket, fSelectEvents, &fEventsRecv, 5);
+ if (rc == VERR_TIMEOUT)
+ {
+ /* Check if there is an event pending. */
+ size_t cbRead = 0;
+ char ch = 0;
+ rc = RTPipeRead(pSockInt->hPipeR, &ch, 1, &cbRead);
+ if (RT_SUCCESS(rc) && rc != VINF_TRY_AGAIN)
+ {
+ Assert(cbRead == 1);
+ rc = VERR_INTERRUPTED;
+ ASMAtomicXchgBool(&pSockInt->fWokenUp, false);
+ break; /* Quit */
+ }
+ else
+ Assert(rc == VINF_TRY_AGAIN);
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ if (fEventsRecv & RTSOCKET_EVT_READ)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_READ;
+ if (fEventsRecv & RTSOCKET_EVT_WRITE)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_WRITE;
+ if (fEventsRecv & RTSOCKET_EVT_ERROR)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_ERROR;
+ break; /* Quit */
+ }
+ else
+ break;
+ }
+ }
+ }
+
+ ASMAtomicXchgBool(&pSockInt->fWaiting, false);
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnPoke} */
+static DECLCALLBACK(int) vdIfTcpNetPoke(VDSOCKET hVdSock)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbWritten = 0;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ ASMAtomicXchgBool(&pSockInt->fWokenUp, true);
+
+ if (ASMAtomicReadBool(&pSockInt->fWaiting))
+ {
+ rc = RTPipeWrite(pSockInt->hPipeW, "", 1, &cbWritten);
+ Assert(RT_SUCCESS(rc) || cbWritten == 0);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+VBOXDDU_DECL(int) VDIfTcpNetInstDefaultCreate(PVDIFINST phTcpNetInst, PVDINTERFACE *ppVdIfs)
+{
+ AssertPtrReturn(phTcpNetInst, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppVdIfs, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ PVDIFINSTINT pThis = (PVDIFINSTINT)RTMemAllocZ(sizeof(*pThis));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->VdIfTcpNet.pfnSocketCreate = vdIfTcpNetSocketCreate;
+ pThis->VdIfTcpNet.pfnSocketDestroy = vdIfTcpNetSocketDestroy;
+ pThis->VdIfTcpNet.pfnClientConnect = vdIfTcpNetClientConnect;
+ pThis->VdIfTcpNet.pfnIsClientConnected = vdIfTcpNetIsClientConnected;
+ pThis->VdIfTcpNet.pfnClientClose = vdIfTcpNetClientClose;
+ pThis->VdIfTcpNet.pfnSelectOne = vdIfTcpNetSelectOne;
+ pThis->VdIfTcpNet.pfnRead = vdIfTcpNetRead;
+ pThis->VdIfTcpNet.pfnWrite = vdIfTcpNetWrite;
+ pThis->VdIfTcpNet.pfnSgWrite = vdIfTcpNetSgWrite;
+ pThis->VdIfTcpNet.pfnReadNB = vdIfTcpNetReadNB;
+ pThis->VdIfTcpNet.pfnWriteNB = vdIfTcpNetWriteNB;
+ pThis->VdIfTcpNet.pfnSgWriteNB = vdIfTcpNetSgWriteNB;
+ pThis->VdIfTcpNet.pfnFlush = vdIfTcpNetFlush;
+ pThis->VdIfTcpNet.pfnSetSendCoalescing = vdIfTcpNetSetSendCoalescing;
+ pThis->VdIfTcpNet.pfnGetLocalAddress = vdIfTcpNetGetLocalAddress;
+ pThis->VdIfTcpNet.pfnGetPeerAddress = vdIfTcpNetGetPeerAddress;
+ pThis->VdIfTcpNet.pfnPoke = vdIfTcpNetPoke;
+
+ /*
+ * There is a 15ms delay between receiving the data and marking the socket
+ * as readable on Windows XP which hurts async I/O performance of
+ * TCP backends badly. Provide a different select method without
+ * using poll on XP.
+ * This is only used on XP because it is not as efficient as the one using poll
+ * and all other Windows versions are working fine.
+ */
+ char szOS[64];
+ memset(szOS, 0, sizeof(szOS));
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, &szOS[0], sizeof(szOS));
+
+ if (RT_SUCCESS(rc) && !strncmp(szOS, "Windows XP", 10))
+ {
+ LogRel(("VD: Detected Windows XP, disabled poll based waiting for TCP\n"));
+ pThis->VdIfTcpNet.pfnSelectOneEx = vdIfTcpNetSelectOneExNoPoll;
+ }
+ else
+ pThis->VdIfTcpNet.pfnSelectOneEx = vdIfTcpNetSelectOneExPoll;
+
+ rc = VDInterfaceAdd(&pThis->VdIfTcpNet.Core, "VD_IfTcpNet",
+ VDINTERFACETYPE_TCPNET, NULL,
+ sizeof(VDINTERFACETCPNET), ppVdIfs);
+ AssertRC(rc);
+
+ if (RT_SUCCESS(rc))
+ *phTcpNetInst = pThis;
+ else
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+VBOXDDU_DECL(void) VDIfTcpNetInstDefaultDestroy(VDIFINST hTcpNetInst)
+{
+ PVDIFINSTINT pThis = hTcpNetInst;
+ AssertPtrReturnVoid(pThis);
+
+ RTMemFree(pThis);
+}
+
diff --git a/src/VBox/Storage/VDIfVfs.cpp b/src/VBox/Storage/VDIfVfs.cpp
new file mode 100644
index 00000000..45b9f101
--- /dev/null
+++ b/src/VBox/Storage/VDIfVfs.cpp
@@ -0,0 +1,410 @@
+/* $Id: VDIfVfs.cpp $ */
+/** @file
+ * Virtual Disk Image (VDI), I/O interface to IPRT VFS I/O stream glue.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/types.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/err.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/file.h>
+#include <iprt/sg.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/poll.h>
+#include <VBox/vd.h>
+#include <VBox/vd-ifs-internal.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * The internal data of an VD I/O to VFS file or I/O stream wrapper.
+ */
+typedef struct VDIFVFSIOSFILE
+{
+ /** The VD I/O interface we prefer wrap.
+ * Can be NULL, in which case pVDIfsIoInt must be valid. */
+ PVDINTERFACEIO pVDIfsIo;
+ /** The VD I/O interface we alternatively can wrap.
+ Can be NULL, in which case pVDIfsIo must be valid. */
+ PVDINTERFACEIOINT pVDIfsIoInt;
+ /** User pointer to pass to the VD I/O interface methods. */
+ PVDIOSTORAGE pStorage;
+ /** The current stream position. */
+ RTFOFF offCurPos;
+} VDIFVFSIOSFILE;
+/** Pointer to a the internal data of a DVM volume file. */
+typedef VDIFVFSIOSFILE *PVDIFVFSIOSFILE;
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) vdIfVfsIos_Close(void *pvThis)
+{
+ /* We don't close anything. */
+ RT_NOREF1(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) vdIfVfsIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ NOREF(pvThis);
+ NOREF(pObjInfo);
+ NOREF(enmAddAttr);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) vdIfVfsIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PVDIFVFSIOSFILE pThis = (PVDIFVFSIOSFILE)pvThis;
+ Assert(pSgBuf->cSegs == 1); NOREF(fBlocking);
+ Assert(off >= -1);
+
+ /*
+ * This may end up being a little more complicated, esp. wrt VERR_EOF.
+ */
+ if (off == -1)
+ off = pThis->offCurPos;
+ int rc;
+ if (pThis->pVDIfsIo)
+ rc = vdIfIoFileReadSync(pThis->pVDIfsIo, pThis->pStorage, off, pSgBuf[0].pvSegCur, pSgBuf->paSegs[0].cbSeg, pcbRead);
+ else
+ {
+ rc = vdIfIoIntFileReadSync(pThis->pVDIfsIoInt, (PVDIOSTORAGE)pThis->pStorage, off, pSgBuf[0].pvSegCur, pSgBuf->paSegs[0].cbSeg);
+ if (pcbRead)
+ *pcbRead = RT_SUCCESS(rc) ? pSgBuf->paSegs[0].cbSeg : 0;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbAdvance = pcbRead ? *pcbRead : pSgBuf->paSegs[0].cbSeg;
+ pThis->offCurPos = off + cbAdvance;
+ if (pcbRead && !cbAdvance)
+ rc = VINF_EOF;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) vdIfVfsIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PVDIFVFSIOSFILE pThis = (PVDIFVFSIOSFILE)pvThis;
+ Assert(pSgBuf->cSegs == 1); NOREF(fBlocking);
+ Assert(off >= -1);
+
+ /*
+ * This may end up being a little more complicated, esp. wrt VERR_EOF.
+ */
+ if (off == -1)
+ off = pThis->offCurPos;
+ int rc;
+ if (pThis->pVDIfsIo)
+ rc = vdIfIoFileWriteSync(pThis->pVDIfsIo, pThis->pStorage, off, pSgBuf[0].pvSegCur, pSgBuf->paSegs[0].cbSeg, pcbWritten);
+ else
+ {
+ rc = vdIfIoIntFileWriteSync(pThis->pVDIfsIoInt, pThis->pStorage, off, pSgBuf[0].pvSegCur, pSgBuf->paSegs[0].cbSeg);
+ if (pcbWritten)
+ *pcbWritten = RT_SUCCESS(rc) ? pSgBuf->paSegs[0].cbSeg : 0;
+ }
+ if (RT_SUCCESS(rc))
+ pThis->offCurPos = off + (pcbWritten ? *pcbWritten : pSgBuf->paSegs[0].cbSeg);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) vdIfVfsIos_Flush(void *pvThis)
+{
+ PVDIFVFSIOSFILE pThis = (PVDIFVFSIOSFILE)pvThis;
+ int rc;
+ if (pThis->pVDIfsIo)
+ rc = vdIfIoFileFlushSync(pThis->pVDIfsIo, pThis->pStorage);
+ else
+ rc = vdIfIoIntFileFlushSync(pThis->pVDIfsIoInt, pThis->pStorage);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) vdIfVfsIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PVDIFVFSIOSFILE pThis = (PVDIFVFSIOSFILE)pvThis;
+ *poffActual = pThis->offCurPos;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * VFS I/O stream operations for a VD file or stream.
+ */
+DECL_HIDDEN_CONST(const RTVFSIOSTREAMOPS) g_vdIfVfsIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "VDIfIos",
+ vdIfVfsIos_Close,
+ vdIfVfsIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ vdIfVfsIos_Read,
+ vdIfVfsIos_Write,
+ vdIfVfsIos_Flush,
+ NULL /*PollOne*/,
+ vdIfVfsIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+
+};
+
+VBOXDDU_DECL(int) VDIfCreateVfsStream(PVDINTERFACEIO pVDIfsIo, void *pvStorage, uint32_t fFlags, PRTVFSIOSTREAM phVfsIos)
+{
+ AssertPtrReturn(pVDIfsIo, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phVfsIos, VERR_INVALID_POINTER);
+
+ /*
+ * Create the volume file.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ PVDIFVFSIOSFILE pThis;
+ int rc = RTVfsNewIoStream(&g_vdIfVfsIosOps, sizeof(*pThis), fFlags,
+ NIL_RTVFS, NIL_RTVFSLOCK, &hVfsIos, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pVDIfsIo = pVDIfsIo;
+ pThis->pVDIfsIoInt = NULL;
+ pThis->pStorage = (PVDIOSTORAGE)pvStorage;
+ pThis->offCurPos = 0;
+
+ *phVfsIos = hVfsIos;
+ return VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetMode}
+ */
+static DECLCALLBACK(int) vdIfVfsFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ NOREF(pvThis);
+ NOREF(fMode);
+ NOREF(fMask);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) vdIfVfsFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ NOREF(pvThis);
+ NOREF(pAccessTime);
+ NOREF(pModificationTime);
+ NOREF(pChangeTime);
+ NOREF(pBirthTime);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) vdIfVfsFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ NOREF(pvThis);
+ NOREF(uid);
+ NOREF(gid);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) vdIfVfsFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PVDIFVFSIOSFILE pThis = (PVDIFVFSIOSFILE)pvThis;
+
+ uint64_t cbFile;
+ int rc;
+ if (pThis->pVDIfsIo)
+ rc = vdIfIoFileGetSize(pThis->pVDIfsIo, pThis->pStorage, &cbFile);
+ else
+ rc = vdIfIoIntFileGetSize(pThis->pVDIfsIoInt, pThis->pStorage, &cbFile);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (cbFile >= (uint64_t)RTFOFF_MAX)
+ cbFile = RTFOFF_MAX;
+
+ /* Recalculate the request to RTFILE_SEEK_BEGIN. */
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offSeek += pThis->offCurPos;
+ break;
+ case RTFILE_SEEK_END:
+ offSeek = cbFile + offSeek;
+ break;
+ default:
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+
+ /* Do limit checks. */
+ if (offSeek < 0)
+ offSeek = 0;
+ else if (offSeek > (RTFOFF)cbFile)
+ offSeek = cbFile;
+
+ /* Apply and return. */
+ pThis->offCurPos = offSeek;
+ if (poffActual)
+ *poffActual = offSeek;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) vdIfVfsFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PVDIFVFSIOSFILE pThis = (PVDIFVFSIOSFILE)pvThis;
+ int rc;
+ if (pThis->pVDIfsIo)
+ rc = vdIfIoFileGetSize(pThis->pVDIfsIo, pThis->pStorage, pcbFile);
+ else
+ rc = vdIfIoIntFileGetSize(pThis->pVDIfsIoInt, pThis->pStorage, pcbFile);
+ return rc;
+}
+
+
+
+/**
+ * VFS file operations for a VD file.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_vdIfVfsFileOps =
+{
+ { /* I/O stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "VDIfFile",
+ vdIfVfsIos_Close,
+ vdIfVfsIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ vdIfVfsIos_Read,
+ vdIfVfsIos_Write,
+ vdIfVfsIos_Flush,
+ NULL /*PollOne*/,
+ vdIfVfsIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ vdIfVfsFile_SetMode,
+ vdIfVfsFile_SetTimes,
+ vdIfVfsFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ vdIfVfsFile_Seek,
+ vdIfVfsFile_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ RTVFSFILEOPS_VERSION,
+};
+
+
+VBOXDDU_DECL(int) VDIfCreateVfsFile(PVDINTERFACEIO pVDIfs, struct VDINTERFACEIOINT *pVDIfsInt, void *pvStorage, uint32_t fFlags, PRTVFSFILE phVfsFile)
+{
+ AssertReturn((pVDIfs != NULL) != (pVDIfsInt != NULL), VERR_INVALID_PARAMETER); /* Exactly one needs to be specified. */
+ AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER);
+
+ /*
+ * Create the volume file.
+ */
+ RTVFSFILE hVfsFile;
+ PVDIFVFSIOSFILE pThis;
+ int rc = RTVfsNewFile(&g_vdIfVfsFileOps, sizeof(*pThis), fFlags,
+ NIL_RTVFS, NIL_RTVFSLOCK, &hVfsFile, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pVDIfsIo = pVDIfs;
+ pThis->pVDIfsIoInt = pVDIfsInt;
+ pThis->pStorage = (PVDIOSTORAGE)pvStorage;
+ pThis->offCurPos = 0;
+
+ *phVfsFile = hVfsFile;
+ return VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/Storage/VDIfVfs2.cpp b/src/VBox/Storage/VDIfVfs2.cpp
new file mode 100644
index 00000000..51640bc0
--- /dev/null
+++ b/src/VBox/Storage/VDIfVfs2.cpp
@@ -0,0 +1,346 @@
+/* $Id: VDIfVfs2.cpp $ */
+/** @file
+ * Virtual Disk Image (VDI), I/O interface to IPRT VFS I/O stream glue.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/types.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/err.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/file.h>
+#include <iprt/sg.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/poll.h>
+#include <VBox/vd.h>
+#include <VBox/vd-ifs-internal.h>
+
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Extended VD I/O interface structure that vdIfFromVfs_xxx uses.
+ *
+ * It's passed as pvUser to each call.
+ */
+typedef struct VDIFFROMVFS
+{
+ VDINTERFACEIO CoreIo;
+
+ /** Magic. */
+ uint32_t u32Magic;
+ /** The stream access mode (RTFILE_O_ACCESS_MASK), possibly others. */
+ uint32_t fAccessMode;
+ /** The I/O stream. This is NIL after it's been closed. */
+ RTVFSIOSTREAM hVfsIos;
+ /** Completion callback. */
+ PFNVDCOMPLETED pfnCompleted;
+ /** User parameter for the completion callback. */
+ void *pvCompletedUser;
+ /** Set if hVfsIos has been opened. */
+ bool fOpened;
+} VDIFFROMVFS;
+/** Magic value for VDIFFROMVFS::u32Magic. */
+#define VDIFFROMVFS_MAGIC UINT32_C(0x11223344)
+
+/** Pointer to the instance data for the vdIfFromVfs_ methods. */
+typedef struct VDIFFROMVFS *PVDIFFROMVFS;
+
+
+typedef struct FILESTORAGEINTERNAL
+{
+ /** File handle. */
+ RTFILE file;
+} FILESTORAGEINTERNAL, *PFILESTORAGEINTERNAL;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+#define STATUS_WAIT UINT32_C(0)
+#define STATUS_WRITE UINT32_C(1)
+#define STATUS_WRITING UINT32_C(2)
+#define STATUS_READ UINT32_C(3)
+#define STATUS_READING UINT32_C(4)
+#define STATUS_END UINT32_C(5)
+
+/* Enable for getting some flow history. */
+#if 0
+# define DEBUG_PRINT_FLOW() RTPrintf("%s\n", __FUNCTION__)
+#else
+# define DEBUG_PRINT_FLOW() do {} while (0)
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/** @name VDINTERFACEIO stubs returning not-implemented.
+ * @{
+ */
+
+/** @interface_method_impl{VDINTERFACEIO,pfnDelete} */
+static DECLCALLBACK(int) notImpl_Delete(void *pvUser, const char *pcszFilename)
+{
+ NOREF(pvUser); NOREF(pcszFilename);
+ Log(("%s\n", __FUNCTION__));
+ AssertFailed();
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @interface_method_impl{VDINTERFACEIO,pfnMove} */
+static DECLCALLBACK(int) notImpl_Move(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
+{
+ NOREF(pvUser); NOREF(pcszSrc); NOREF(pcszDst); NOREF(fMove);
+ Log(("%s\n", __FUNCTION__));
+ AssertFailed();
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @interface_method_impl{VDINTERFACEIO,pfnGetFreeSpace} */
+static DECLCALLBACK(int) notImpl_GetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
+{
+ NOREF(pvUser); NOREF(pcszFilename); NOREF(pcbFreeSpace);
+ Log(("%s\n", __FUNCTION__));
+ AssertFailed();
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @interface_method_impl{VDINTERFACEIO,pfnGetModificationTime} */
+static DECLCALLBACK(int) notImpl_GetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
+{
+ NOREF(pvUser); NOREF(pcszFilename); NOREF(pModificationTime);
+ Log(("%s\n", __FUNCTION__));
+ AssertFailed();
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @interface_method_impl{VDINTERFACEIO,pfnSetSize} */
+static DECLCALLBACK(int) notImpl_SetSize(void *pvUser, void *pvStorage, uint64_t cb)
+{
+ NOREF(pvUser); NOREF(pvStorage); NOREF(cb);
+ Log(("%s\n", __FUNCTION__));
+ AssertFailed();
+ return VERR_NOT_IMPLEMENTED;
+}
+
+#if 0 /* unused */
+/** @interface_method_impl{VDINTERFACEIO,pfnWriteSync} */
+static DECLCALLBACK(int) notImpl_WriteSync(void *pvUser, void *pvStorage, uint64_t off, const void *pvBuf,
+ size_t cbWrite, size_t *pcbWritten)
+{
+ RT_NOREF6(pvUser, pvStorage, off, pvBuf, cbWrite, pcbWritten)
+ Log(("%s\n", __FUNCTION__));
+ return VERR_NOT_IMPLEMENTED;
+}
+#endif
+
+/** @interface_method_impl{VDINTERFACEIO,pfnFlushSync} */
+static DECLCALLBACK(int) notImpl_FlushSync(void *pvUser, void *pvStorage)
+{
+ NOREF(pvUser); NOREF(pvStorage);
+ Log(("%s\n", __FUNCTION__));
+ AssertFailed();
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @} */
+
+
+/** @interface_method_impl{VDINTERFACEIO,pfnOpen} */
+static DECLCALLBACK(int) vdIfFromVfs_Open(void *pvUser, const char *pszLocation, uint32_t fOpen,
+ PFNVDCOMPLETED pfnCompleted, void **ppvStorage)
+{
+ RT_NOREF1(pszLocation);
+ PVDIFFROMVFS pThis = (PVDIFFROMVFS)pvUser;
+
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(ppvStorage, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnCompleted, VERR_INVALID_PARAMETER);
+
+ /*
+ * We ignore the name, assuming the caller is opening the stream/file we're .
+ * serving. Thus, after close, all open calls fail.
+ */
+ AssertReturn(!pThis->fOpened, VERR_FILE_NOT_FOUND);
+ AssertReturn(pThis->hVfsIos != NIL_RTVFSIOSTREAM, VERR_FILE_NOT_FOUND); /* paranoia */
+ AssertMsgReturn((pThis->fAccessMode & fOpen & RTFILE_O_ACCESS_MASK) == (fOpen & RTFILE_O_ACCESS_MASK),
+ ("fAccessMode=%#x fOpen=%#x\n", pThis->fAccessMode, fOpen), VERR_ACCESS_DENIED);
+
+ pThis->fAccessMode = fOpen & RTFILE_O_ACCESS_MASK;
+ pThis->fOpened = true;
+ pThis->pfnCompleted = pfnCompleted;
+ pThis->pvCompletedUser = pvUser;
+
+ *ppvStorage = pThis->hVfsIos;
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VDINTERFACEIO,pfnClose} */
+static DECLCALLBACK(int) vdIfFromVfs_Close(void *pvUser, void *pvStorage)
+{
+ PVDIFFROMVFS pThis = (PVDIFFROMVFS)pvUser;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertReturn(pThis->hVfsIos == (RTVFSIOSTREAM)pvStorage, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->fOpened, VERR_INVALID_HANDLE);
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{VDINTERFACEIO,pfnGetSize} */
+static DECLCALLBACK(int) vdIfFromVfs_GetSize(void *pvUser, void *pvStorage, uint64_t *pcb)
+{
+ PVDIFFROMVFS pThis = (PVDIFFROMVFS)pvUser;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertReturn(pThis->hVfsIos == (RTVFSIOSTREAM)pvStorage, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->fOpened, VERR_INVALID_HANDLE);
+
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsIoStrmQueryInfo(pThis->hVfsIos, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ *pcb = ObjInfo.cbObject;
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACEIO,pfnReadSync} */
+static DECLCALLBACK(int) vdIfFromVfs_ReadSync(void *pvUser, void *pvStorage, uint64_t off, void *pvBuf,
+ size_t cbToRead, size_t *pcbRead)
+{
+ PVDIFFROMVFS pThis = (PVDIFFROMVFS)pvUser;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertReturn(pThis->hVfsIos == (RTVFSIOSTREAM)pvStorage, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->fOpened, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
+ AssertReturn(pThis->fAccessMode & RTFILE_O_READ, VERR_ACCESS_DENIED);
+
+ return RTVfsIoStrmReadAt(pThis->hVfsIos, off, pvBuf, cbToRead, true /*fBlocking*/, pcbRead);
+}
+
+
+/** @interface_method_impl{VDINTERFACEIO,pfnWriteSync} */
+static DECLCALLBACK(int) vdIfFromVfs_WriteSync(void *pvUser, void *pvStorage, uint64_t off, void const *pvBuf,
+ size_t cbToWrite, size_t *pcbWritten)
+{
+ PVDIFFROMVFS pThis = (PVDIFFROMVFS)pvUser;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertReturn(pThis->hVfsIos == (RTVFSIOSTREAM)pvStorage, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->fOpened, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER);
+ AssertReturn(pThis->fAccessMode & RTFILE_O_WRITE, VERR_ACCESS_DENIED);
+
+ return RTVfsIoStrmWriteAt(pThis->hVfsIos, off, pvBuf, cbToWrite, true /*fBlocking*/, pcbWritten);
+}
+
+
+VBOXDDU_DECL(int) VDIfCreateFromVfsStream(RTVFSIOSTREAM hVfsIos, uint32_t fAccessMode, PVDINTERFACEIO *ppIoIf)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(ppIoIf, VERR_INVALID_POINTER);
+ *ppIoIf = NULL;
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE);
+ AssertReturn(fAccessMode & RTFILE_O_ACCESS_MASK, VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIos);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Allocate and init a callback + instance data structure.
+ */
+ int rc;
+ PVDIFFROMVFS pThis = (PVDIFFROMVFS)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->CoreIo.pfnOpen = vdIfFromVfs_Open;
+ pThis->CoreIo.pfnClose = vdIfFromVfs_Close;
+ pThis->CoreIo.pfnDelete = notImpl_Delete;
+ pThis->CoreIo.pfnMove = notImpl_Move;
+ pThis->CoreIo.pfnGetFreeSpace = notImpl_GetFreeSpace;
+ pThis->CoreIo.pfnGetModificationTime = notImpl_GetModificationTime;
+ pThis->CoreIo.pfnGetSize = vdIfFromVfs_GetSize;
+ pThis->CoreIo.pfnSetSize = notImpl_SetSize;
+ pThis->CoreIo.pfnReadSync = vdIfFromVfs_ReadSync;
+ pThis->CoreIo.pfnWriteSync = vdIfFromVfs_WriteSync;
+ pThis->CoreIo.pfnFlushSync = notImpl_FlushSync;
+
+ pThis->hVfsIos = hVfsIos;
+ pThis->fAccessMode = fAccessMode;
+ pThis->fOpened = false;
+ pThis->u32Magic = VDIFFROMVFS_MAGIC;
+
+ PVDINTERFACE pFakeList = NULL;
+ rc = VDInterfaceAdd(&pThis->CoreIo.Core, "FromVfsStream", VDINTERFACETYPE_IO, pThis, sizeof(pThis->CoreIo), &pFakeList);
+ if (RT_SUCCESS(rc))
+ {
+ *ppIoIf = &pThis->CoreIo;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ RTVfsIoStrmRelease(hVfsIos);
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VDIfDestroyFromVfsStream(PVDINTERFACEIO pIoIf)
+{
+ if (pIoIf)
+ {
+ PVDIFFROMVFS pThis = (PVDIFFROMVFS)pIoIf;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertReturn(pThis->u32Magic == VDIFFROMVFS_MAGIC, VERR_INVALID_MAGIC);
+
+ if (pThis->hVfsIos != NIL_RTVFSIOSTREAM)
+ {
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+ }
+ pThis->u32Magic = ~VDIFFROMVFS_MAGIC;
+ RTMemFree(pThis);
+ }
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Storage/VDInternal.h b/src/VBox/Storage/VDInternal.h
new file mode 100644
index 00000000..bf71faeb
--- /dev/null
+++ b/src/VBox/Storage/VDInternal.h
@@ -0,0 +1,300 @@
+/* $Id: VDInternal.h $ */
+/** @file
+ * VD - Virtual Disk container implementation, internal header file.
+ */
+
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+
+#ifndef VBOX_INCLUDED_SRC_Storage_VDInternal_h
+#define VBOX_INCLUDED_SRC_Storage_VDInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+#include <VBox/vd.h>
+#include <VBox/vd-plugin.h>
+
+#include <iprt/avl.h>
+#include <iprt/list.h>
+#include <iprt/memcache.h>
+
+#if 0 /* bird: this is nonsense */
+/** Disable dynamic backends on non x86 architectures. This feature
+ * requires the SUPR3 library which is not available there.
+ */
+#if !defined(VBOX_HDD_NO_DYNAMIC_BACKENDS) && !defined(RT_ARCH_X86) && !defined(RT_ARCH_AMD64)
+# define VBOX_HDD_NO_DYNAMIC_BACKENDS
+#endif
+#endif
+
+/** Magic number contained in the VDISK instance data, used for checking that the passed
+ * pointer contains a valid instance in debug builds. */
+#define VDISK_SIGNATURE 0x6f0e2a7d
+
+/**
+ * Structure containing everything I/O related
+ * for the image and cache descriptors.
+ */
+typedef struct VDIO
+{
+ /** I/O interface to the upper layer. */
+ PVDINTERFACEIO pInterfaceIo;
+
+ /** Per image internal I/O interface. */
+ VDINTERFACEIOINT VDIfIoInt;
+
+ /** Fallback I/O interface, only used if the caller doesn't provide it. */
+ VDINTERFACEIO VDIfIo;
+
+ /** Opaque backend data. */
+ void *pBackendData;
+ /** Disk this image is part of */
+ PVDISK pDisk;
+ /** Flag whether to ignore flush requests. */
+ bool fIgnoreFlush;
+} VDIO, *PVDIO;
+
+/** Forward declaration of an I/O task */
+typedef struct VDIOTASK *PVDIOTASK;
+
+/**
+ * Virtual disk container image descriptor.
+ */
+typedef struct VDIMAGE
+{
+ /** Link to parent image descriptor, if any. */
+ struct VDIMAGE *pPrev;
+ /** Link to child image descriptor, if any. */
+ struct VDIMAGE *pNext;
+ /** Cached image size. */
+ uint64_t cbImage;
+ /** Container base filename. (UTF-8) */
+ char *pszFilename;
+ /** Data managed by the backend which keeps the actual info. */
+ void *pBackendData;
+ /** Cached sanitized image flags. */
+ unsigned uImageFlags;
+ /** Image open flags (only those handled generically in this code and which
+ * the backends will never ever see). */
+ unsigned uOpenFlags;
+
+ /** Function pointers for the various backend methods. */
+ PCVDIMAGEBACKEND Backend;
+ /** Pointer to list of VD interfaces, per-image. */
+ PVDINTERFACE pVDIfsImage;
+ /** I/O related things. */
+ VDIO VDIo;
+} VDIMAGE, *PVDIMAGE;
+
+/** The special uninitialized size value for he image. */
+#define VD_IMAGE_SIZE_UNINITIALIZED UINT64_C(0)
+
+/**
+ * Virtual disk cache image descriptor.
+ */
+typedef struct VDCACHE
+{
+ /** Cache base filename. (UTF-8) */
+ char *pszFilename;
+ /** Data managed by the backend which keeps the actual info. */
+ void *pBackendData;
+ /** Cached sanitized image flags. */
+ unsigned uImageFlags;
+ /** Image open flags (only those handled generically in this code and which
+ * the backends will never ever see). */
+ unsigned uOpenFlags;
+
+ /** Function pointers for the various backend methods. */
+ PCVDCACHEBACKEND Backend;
+
+ /** Pointer to list of VD interfaces, per-cache. */
+ PVDINTERFACE pVDIfsCache;
+ /** I/O related things. */
+ VDIO VDIo;
+} VDCACHE, *PVDCACHE;
+
+/**
+ * A block waiting for a discard.
+ */
+typedef struct VDDISCARDBLOCK
+{
+ /** AVL core. */
+ AVLRU64NODECORE Core;
+ /** LRU list node. */
+ RTLISTNODE NodeLru;
+ /** Number of bytes to discard. */
+ size_t cbDiscard;
+ /** Bitmap of allocated sectors. */
+ void *pbmAllocated;
+} VDDISCARDBLOCK, *PVDDISCARDBLOCK;
+
+/**
+ * VD discard state.
+ */
+typedef struct VDDISCARDSTATE
+{
+ /** Number of bytes waiting for a discard. */
+ size_t cbDiscarding;
+ /** AVL tree with blocks waiting for a discard.
+ * The uOffset + cbDiscard range is the search key. */
+ PAVLRU64TREE pTreeBlocks;
+ /** LRU list of the least frequently discarded blocks.
+ * If there are to many blocks waiting the least frequently used
+ * will be removed and the range will be set to 0.
+ */
+ RTLISTNODE ListLru;
+} VDDISCARDSTATE, *PVDDISCARDSTATE;
+
+/**
+ * VD filter instance.
+ */
+typedef struct VDFILTER
+{
+ /** List node for the read filter chain. */
+ RTLISTNODE ListNodeChainRead;
+ /** List node for the write filter chain. */
+ RTLISTNODE ListNodeChainWrite;
+ /** Number of references to this filter. */
+ uint32_t cRefs;
+ /** Opaque VD filter backend instance data. */
+ void *pvBackendData;
+ /** Pointer to the filter backend interface. */
+ PCVDFILTERBACKEND pBackend;
+ /** Pointer to list of VD interfaces, per-filter. */
+ PVDINTERFACE pVDIfsFilter;
+ /** I/O related things. */
+ VDIO VDIo;
+} VDFILTER;
+/** Pointer to a VD filter instance. */
+typedef VDFILTER *PVDFILTER;
+
+/**
+ * Virtual disk container main structure, private part.
+ */
+struct VDISK
+{
+ /** Structure signature (VDISK_SIGNATURE). */
+ uint32_t u32Signature;
+
+ /** Image type. */
+ VDTYPE enmType;
+
+ /** Number of opened images. */
+ unsigned cImages;
+
+ /** Base image. */
+ PVDIMAGE pBase;
+
+ /** Last opened image in the chain.
+ * The same as pBase if only one image is used. */
+ PVDIMAGE pLast;
+
+ /** If a merge to one of the parents is running this may be non-NULL
+ * to indicate to what image the writes should be additionally relayed. */
+ PVDIMAGE pImageRelay;
+
+ /** Flags representing the modification state. */
+ unsigned uModified;
+
+ /** Cached size of this disk. */
+ uint64_t cbSize;
+ /** Cached PCHS geometry for this disk. */
+ VDGEOMETRY PCHSGeometry;
+ /** Cached LCHS geometry for this disk. */
+ VDGEOMETRY LCHSGeometry;
+
+ /** Pointer to list of VD interfaces, per-disk. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the common interface structure for error reporting. */
+ PVDINTERFACEERROR pInterfaceError;
+ /** Pointer to the optional thread synchronization callbacks. */
+ PVDINTERFACETHREADSYNC pInterfaceThreadSync;
+
+ /** Memory cache for I/O contexts */
+ RTMEMCACHE hMemCacheIoCtx;
+ /** Memory cache for I/O tasks. */
+ RTMEMCACHE hMemCacheIoTask;
+ /** An I/O context is currently using the disk structures
+ * Every I/O context must be placed on one of the lists below. */
+ volatile bool fLocked;
+ /** Head of pending I/O tasks waiting for completion - LIFO order. */
+ volatile PVDIOTASK pIoTasksPendingHead;
+ /** Head of newly queued I/O contexts - LIFO order. */
+ volatile PVDIOCTX pIoCtxHead;
+ /** Head of halted I/O contexts which are given back to generic
+ * disk framework by the backend. - LIFO order. */
+ volatile PVDIOCTX pIoCtxHaltedHead;
+
+ /** Head of blocked I/O contexts, processed only
+ * after pIoCtxLockOwner was freed - LIFO order. */
+ volatile PVDIOCTX pIoCtxBlockedHead;
+ /** I/O context which locked the disk for a growing write or flush request.
+ * Other flush or growing write requests need to wait until
+ * the current one completes. - NIL_VDIOCTX if unlocked. */
+ volatile PVDIOCTX pIoCtxLockOwner;
+ /** If the disk was locked by a growing write, flush or discard request this
+ * contains the start offset to check for interfering I/O while it is in progress. */
+ uint64_t uOffsetStartLocked;
+ /** If the disk was locked by a growing write, flush or discard request this contains
+ * the first non affected offset to check for interfering I/O while it is in progress. */
+ uint64_t uOffsetEndLocked;
+
+ /** Pointer to the L2 disk cache if any. */
+ PVDCACHE pCache;
+ /** Pointer to the discard state if any. */
+ PVDDISCARDSTATE pDiscard;
+
+ /** Read filter chain - PVDFILTER. */
+ RTLISTANCHOR ListFilterChainRead;
+ /** Write filter chain - PVDFILTER. */
+ RTLISTANCHOR ListFilterChainWrite;
+};
+
+
+DECLHIDDEN(int) vdPluginInit(void);
+DECLHIDDEN(int) vdPluginTerm(void);
+DECLHIDDEN(bool) vdPluginIsInitialized(void);
+DECLHIDDEN(int) vdPluginUnloadFromPath(const char *pszPath);
+DECLHIDDEN(int) vdPluginUnloadFromFilename(const char *pszFilename);
+DECLHIDDEN(int) vdPluginLoadFromPath(const char *pszPath);
+DECLHIDDEN(int) vdPluginLoadFromFilename(const char *pszFilename);
+
+DECLHIDDEN(uint32_t) vdGetImageBackendCount(void);
+DECLHIDDEN(int) vdQueryImageBackend(uint32_t idx, PCVDIMAGEBACKEND *ppBackend);
+DECLHIDDEN(int) vdFindImageBackend(const char *pszBackend, PCVDIMAGEBACKEND *ppBackend);
+DECLHIDDEN(uint32_t) vdGetCacheBackendCount(void);
+DECLHIDDEN(int) vdQueryCacheBackend(uint32_t idx, PCVDCACHEBACKEND *ppBackend);
+DECLHIDDEN(int) vdFindCacheBackend(const char *pszBackend, PCVDCACHEBACKEND *ppBackend);
+DECLHIDDEN(uint32_t) vdGetFilterBackendCount(void);
+DECLHIDDEN(int) vdQueryFilterBackend(uint32_t idx, PCVDFILTERBACKEND *ppBackend);
+DECLHIDDEN(int) vdFindFilterBackend(const char *pszFilter, PCVDFILTERBACKEND *ppBackend);
+
+DECLHIDDEN(int) vdIoIterQueryStartNext(VDIOITER hVdIoIter, uint64_t *pu64Start);
+DECLHIDDEN(int) vdIoIterQuerySegSizeByStart(VDIOITER hVdIoIter, uint64_t u64Start, size_t *pcRegSize);
+DECLHIDDEN(int) vdIoIterAdvance(VDIOITER hVdIoIter, uint64_t cBlocksOrBytes);
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_VDInternal_h */
+
diff --git a/src/VBox/Storage/VDPlugin.cpp b/src/VBox/Storage/VDPlugin.cpp
new file mode 100644
index 00000000..8943fe6b
--- /dev/null
+++ b/src/VBox/Storage/VDPlugin.cpp
@@ -0,0 +1,970 @@
+/* $Id: VDPlugin.cpp $ */
+/** @file
+ * VD - Virtual disk container implementation, plugin related bits.
+ */
+
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD
+#include <VBox/err.h>
+#include <VBox/sup.h>
+#include <VBox/log.h>
+#include <VBox/vd-plugin.h>
+
+#include <iprt/dir.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+
+#include "VDInternal.h"
+#include "VDBackends.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Plugin structure.
+ */
+typedef struct VDPLUGIN
+{
+ /** Pointer to the next plugin structure. */
+ RTLISTNODE NodePlugin;
+ /** Handle of loaded plugin library. */
+ RTLDRMOD hPlugin;
+ /** Filename of the loaded plugin. */
+ char *pszFilename;
+} VDPLUGIN;
+/** Pointer to a plugin structure. */
+typedef VDPLUGIN *PVDPLUGIN;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+/** Head of loaded plugin list. */
+static RTLISTANCHOR g_ListPluginsLoaded;
+#endif
+
+/** Number of image backends supported. */
+static unsigned g_cBackends = 0;
+/** Array of pointers to the image backends. */
+static PCVDIMAGEBACKEND *g_apBackends = NULL;
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+/** Array of handles to the corresponding plugin. */
+static RTLDRMOD *g_ahBackendPlugins = NULL;
+#endif
+/**
+ * Builtin image backends.
+ *
+ * @note As long as the pfnProb() calls aren't scored, the ordering influences
+ * which backend take precedence. In particular, the RAW backend should
+ * be thowards the end of the list.
+ */
+static PCVDIMAGEBACKEND aStaticBackends[] =
+{
+ &g_VmdkBackend,
+ &g_VDIBackend,
+ &g_VhdBackend,
+ &g_ParallelsBackend,
+ &g_DmgBackend,
+ &g_QedBackend,
+ &g_QCowBackend,
+ &g_VhdxBackend,
+ &g_CueBackend,
+ &g_VBoxIsoMakerBackend,
+ &g_RawBackend,
+ &g_ISCSIBackend
+};
+
+/** Number of supported cache backends. */
+static unsigned g_cCacheBackends = 0;
+/** Array of pointers to the cache backends. */
+static PCVDCACHEBACKEND *g_apCacheBackends = NULL;
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+/** Array of handles to the corresponding plugin.
+ *
+ * @todo r=bird: This looks rather pointless.
+ */
+static RTLDRMOD *g_ahCacheBackendPlugins = NULL;
+#endif
+/** Builtin cache backends. */
+static PCVDCACHEBACKEND aStaticCacheBackends[] =
+{
+ &g_VciCacheBackend
+};
+
+/** Number of supported filter backends. */
+static unsigned g_cFilterBackends = 0;
+/** Array of pointers to the filters backends. */
+static PCVDFILTERBACKEND *g_apFilterBackends = NULL;
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+/** Array of handles to the corresponding plugin. */
+static PRTLDRMOD g_pahFilterBackendPlugins = NULL;
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Add an array of image format backends from the given plugin to the list of known
+ * image formats.
+ *
+ * @returns VBox status code.
+ * @param hPlugin The plugin handle the backends belong to, can be NIL_RTLDRMOD
+ * for compiled in backends.
+ * @param ppBackends The array of image backend descriptors to add.
+ * @param cBackends Number of descriptors in the array.
+ */
+static int vdAddBackends(RTLDRMOD hPlugin, PCVDIMAGEBACKEND *ppBackends, unsigned cBackends)
+{
+ PCVDIMAGEBACKEND *pTmp = (PCVDIMAGEBACKEND *)RTMemRealloc(g_apBackends,
+ (g_cBackends + cBackends) * sizeof(PCVDIMAGEBACKEND));
+ if (RT_UNLIKELY(!pTmp))
+ return VERR_NO_MEMORY;
+ g_apBackends = pTmp;
+ memcpy(&g_apBackends[g_cBackends], ppBackends, cBackends * sizeof(PCVDIMAGEBACKEND));
+
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ RTLDRMOD *pTmpPlugins = (RTLDRMOD*)RTMemRealloc(g_ahBackendPlugins,
+ (g_cBackends + cBackends) * sizeof(RTLDRMOD));
+ if (RT_UNLIKELY(!pTmpPlugins))
+ return VERR_NO_MEMORY;
+ g_ahBackendPlugins = pTmpPlugins;
+ for (unsigned i = g_cBackends; i < g_cBackends + cBackends; i++)
+ g_ahBackendPlugins[i] = hPlugin;
+#else
+ RT_NOREF(hPlugin);
+#endif
+
+ g_cBackends += cBackends;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Add an array of cache format backends from the given plugin to the list of known
+ * cache formats.
+ *
+ * @returns VBox status code.
+ * @param hPlugin The plugin handle the backends belong to, can be NIL_RTLDRMOD
+ * for compiled in backends.
+ * @param ppBackends The array of cache backend descriptors to add.
+ * @param cBackends Number of descriptors in the array.
+ */
+static int vdAddCacheBackends(RTLDRMOD hPlugin, PCVDCACHEBACKEND *ppBackends, unsigned cBackends)
+{
+ PCVDCACHEBACKEND *pTmp = (PCVDCACHEBACKEND*)RTMemReallocTag(g_apCacheBackends,
+ (g_cCacheBackends + cBackends) * sizeof(PCVDCACHEBACKEND),
+ "may-leak:vdAddCacheBackend");
+ if (RT_UNLIKELY(!pTmp))
+ return VERR_NO_MEMORY;
+ g_apCacheBackends = pTmp;
+ memcpy(&g_apCacheBackends[g_cCacheBackends], ppBackends, cBackends * sizeof(PCVDCACHEBACKEND));
+
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ RTLDRMOD *pTmpPlugins = (RTLDRMOD*)RTMemReallocTag(g_ahCacheBackendPlugins,
+ (g_cCacheBackends + cBackends) * sizeof(RTLDRMOD),
+ "may-leak:vdAddCacheBackend");
+ if (RT_UNLIKELY(!pTmpPlugins))
+ return VERR_NO_MEMORY;
+ g_ahCacheBackendPlugins = pTmpPlugins;
+ for (unsigned i = g_cCacheBackends; i < g_cCacheBackends + cBackends; i++)
+ g_ahCacheBackendPlugins[i] = hPlugin;
+#else
+ RT_NOREF(hPlugin);
+#endif
+
+ g_cCacheBackends += cBackends;
+ return VINF_SUCCESS;
+}
+
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+/**
+ * Add a single image format backend to the list of known image formats.
+ *
+ * @returns VBox status code.
+ * @param hPlugin The plugin handle the backend belongs to, can be NIL_RTLDRMOD
+ * for compiled in backends.
+ * @param pBackend The image backend descriptors to add.
+ */
+DECLINLINE(int) vdAddBackend(RTLDRMOD hPlugin, PCVDIMAGEBACKEND pBackend)
+{
+ return vdAddBackends(hPlugin, &pBackend, 1);
+}
+
+
+/**
+ * Add a single cache format backend to the list of known cache formats.
+ *
+ * @returns VBox status code.
+ * @param hPlugin The plugin handle the backend belongs to, can be NIL_RTLDRMOD
+ * for compiled in backends.
+ * @param pBackend The cache backend descriptors to add.
+ */
+DECLINLINE(int) vdAddCacheBackend(RTLDRMOD hPlugin, PCVDCACHEBACKEND pBackend)
+{
+ return vdAddCacheBackends(hPlugin, &pBackend, 1);
+}
+
+
+/**
+ * Add several filter backends.
+ *
+ * @returns VBox status code.
+ * @param hPlugin Plugin handle to add.
+ * @param ppBackends Array of filter backends to add.
+ * @param cBackends Number of backends to add.
+ */
+static int vdAddFilterBackends(RTLDRMOD hPlugin, PCVDFILTERBACKEND *ppBackends, unsigned cBackends)
+{
+ PCVDFILTERBACKEND *pTmp = (PCVDFILTERBACKEND *)RTMemRealloc(g_apFilterBackends,
+ (g_cFilterBackends + cBackends) * sizeof(PCVDFILTERBACKEND));
+ if (RT_UNLIKELY(!pTmp))
+ return VERR_NO_MEMORY;
+ g_apFilterBackends = pTmp;
+
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ PRTLDRMOD pTmpPlugins = (PRTLDRMOD)RTMemRealloc(g_pahFilterBackendPlugins,
+ (g_cFilterBackends + cBackends) * sizeof(RTLDRMOD));
+ if (RT_UNLIKELY(!pTmpPlugins))
+ return VERR_NO_MEMORY;
+
+ g_pahFilterBackendPlugins = pTmpPlugins;
+ memcpy(&g_apFilterBackends[g_cFilterBackends], ppBackends, cBackends * sizeof(PCVDFILTERBACKEND));
+ for (unsigned i = g_cFilterBackends; i < g_cFilterBackends + cBackends; i++)
+ g_pahFilterBackendPlugins[i] = hPlugin;
+#else
+ RT_NOREF(hPlugin);
+#endif
+
+ g_cFilterBackends += cBackends;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Add a single filter backend to the list of supported filters.
+ *
+ * @returns VBox status code.
+ * @param hPlugin Plugin handle to add.
+ * @param pBackend The backend to add.
+ */
+DECLINLINE(int) vdAddFilterBackend(RTLDRMOD hPlugin, PCVDFILTERBACKEND pBackend)
+{
+ return vdAddFilterBackends(hPlugin, &pBackend, 1);
+}
+
+/**
+ * @interface_method_impl{VDBACKENDREGISTER,pfnRegisterImage}
+ */
+static DECLCALLBACK(int) vdPluginRegisterImage(void *pvUser, PCVDIMAGEBACKEND pBackend)
+{
+ int rc = VINF_SUCCESS;
+
+ if (VD_VERSION_ARE_COMPATIBLE(VD_IMGBACKEND_VERSION, pBackend->u32Version))
+ vdAddBackend((RTLDRMOD)pvUser, pBackend);
+ else
+ {
+ LogFunc(("ignored plugin: pBackend->u32Version=%u rc=%Rrc\n", pBackend->u32Version, rc));
+ rc = VERR_IGNORED;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VDBACKENDREGISTER,pfnRegisterCache}
+ */
+static DECLCALLBACK(int) vdPluginRegisterCache(void *pvUser, PCVDCACHEBACKEND pBackend)
+{
+ int rc = VINF_SUCCESS;
+
+ if (VD_VERSION_ARE_COMPATIBLE(VD_CACHEBACKEND_VERSION, pBackend->u32Version))
+ vdAddCacheBackend((RTLDRMOD)pvUser, pBackend);
+ else
+ {
+ LogFunc(("ignored plugin: pBackend->u32Version=%u rc=%Rrc\n", pBackend->u32Version, rc));
+ rc = VERR_IGNORED;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VDBACKENDREGISTER,pfnRegisterFilter}
+ */
+static DECLCALLBACK(int) vdPluginRegisterFilter(void *pvUser, PCVDFILTERBACKEND pBackend)
+{
+ int rc = VINF_SUCCESS;
+
+ if (VD_VERSION_ARE_COMPATIBLE(VD_FLTBACKEND_VERSION, pBackend->u32Version))
+ vdAddFilterBackend((RTLDRMOD)pvUser, pBackend);
+ else
+ {
+ LogFunc(("ignored plugin: pBackend->u32Version=%u rc=%Rrc\n", pBackend->u32Version, rc));
+ rc = VERR_IGNORED;
+ }
+
+ return rc;
+}
+
+/**
+ * Checks whether the given plugin filename was already loaded.
+ *
+ * @returns Pointer to already loaded plugin, NULL if not found.
+ * @param pszFilename The filename to check.
+ */
+static PVDPLUGIN vdPluginFind(const char *pszFilename)
+{
+ PVDPLUGIN pIt;
+ RTListForEach(&g_ListPluginsLoaded, pIt, VDPLUGIN, NodePlugin)
+ {
+ if (!RTStrCmp(pIt->pszFilename, pszFilename))
+ return pIt;
+ }
+
+ return NULL;
+}
+
+/**
+ * Adds a plugin to the list of loaded plugins.
+ *
+ * @returns VBox status code.
+ * @param hPlugin Plugin handle to add.
+ * @param pszFilename The associated filename, used for finding duplicates.
+ */
+static int vdAddPlugin(RTLDRMOD hPlugin, const char *pszFilename)
+{
+ int rc = VINF_SUCCESS;
+ PVDPLUGIN pPlugin = (PVDPLUGIN)RTMemAllocZ(sizeof(VDPLUGIN));
+
+ if (pPlugin)
+ {
+ pPlugin->hPlugin = hPlugin;
+ pPlugin->pszFilename = RTStrDup(pszFilename);
+ if (pPlugin->pszFilename)
+ RTListAppend(&g_ListPluginsLoaded, &pPlugin->NodePlugin);
+ else
+ {
+ RTMemFree(pPlugin);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Removes a single plugin given by the filename.
+ *
+ * @returns VBox status code.
+ * @param pszFilename The plugin filename to remove.
+ */
+static int vdRemovePlugin(const char *pszFilename)
+{
+ /* Find plugin to be removed from the list. */
+ PVDPLUGIN pIt = vdPluginFind(pszFilename);
+ if (!pIt)
+ return VINF_SUCCESS;
+
+ /** @todo r=klaus: need to add a plugin entry point for unregistering the
+ * backends. Only if this doesn't exist (or fails to work) we should fall
+ * back to the following uncoordinated backend cleanup. */
+ for (unsigned i = 0; i < g_cBackends; i++)
+ {
+ while (i < g_cBackends && g_ahBackendPlugins[i] == pIt->hPlugin)
+ {
+ memmove(&g_apBackends[i], &g_apBackends[i + 1], (g_cBackends - i - 1) * sizeof(PCVDIMAGEBACKEND));
+ memmove(&g_ahBackendPlugins[i], &g_ahBackendPlugins[i + 1], (g_cBackends - i - 1) * sizeof(RTLDRMOD));
+ /** @todo for now skip reallocating, doesn't save much */
+ g_cBackends--;
+ }
+ }
+ for (unsigned i = 0; i < g_cCacheBackends; i++)
+ {
+ while (i < g_cCacheBackends && g_ahCacheBackendPlugins[i] == pIt->hPlugin)
+ {
+ memmove(&g_apCacheBackends[i], &g_apCacheBackends[i + 1], (g_cCacheBackends - i - 1) * sizeof(PCVDCACHEBACKEND));
+ memmove(&g_ahCacheBackendPlugins[i], &g_ahCacheBackendPlugins[i + 1], (g_cCacheBackends - i - 1) * sizeof(RTLDRMOD));
+ /** @todo for now skip reallocating, doesn't save much */
+ g_cCacheBackends--;
+ }
+ }
+ for (unsigned i = 0; i < g_cFilterBackends; i++)
+ {
+ while (i < g_cFilterBackends && g_pahFilterBackendPlugins[i] == pIt->hPlugin)
+ {
+ memmove(&g_apFilterBackends[i], &g_apFilterBackends[i + 1], (g_cFilterBackends - i - 1) * sizeof(PCVDFILTERBACKEND));
+ memmove(&g_pahFilterBackendPlugins[i], &g_pahFilterBackendPlugins[i + 1], (g_cFilterBackends - i - 1) * sizeof(RTLDRMOD));
+ /** @todo for now skip reallocating, doesn't save much */
+ g_cFilterBackends--;
+ }
+ }
+
+ /* Remove the plugin node now, all traces of it are gone. */
+ RTListNodeRemove(&pIt->NodePlugin);
+ RTLdrClose(pIt->hPlugin);
+ RTStrFree(pIt->pszFilename);
+ RTMemFree(pIt);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* VBOX_HDD_NO_DYNAMIC_BACKENDS*/
+
+/**
+ * Returns the number of known image format backends.
+ *
+ * @returns Number of image formats known.
+ */
+DECLHIDDEN(uint32_t) vdGetImageBackendCount(void)
+{
+ return g_cBackends;
+}
+
+
+/**
+ * Queries a image backend descriptor by the index.
+ *
+ * @returns VBox status code.
+ * @param idx The index of the backend to query.
+ * @param ppBackend Where to store the pointer to the backend descriptor on success.
+ */
+DECLHIDDEN(int) vdQueryImageBackend(uint32_t idx, PCVDIMAGEBACKEND *ppBackend)
+{
+ if (idx >= g_cBackends)
+ return VERR_OUT_OF_RANGE;
+
+ *ppBackend = g_apBackends[idx];
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Returns the image backend descriptor matching the given identifier if known.
+ *
+ * @returns VBox status code.
+ * @param pszBackend The backend identifier to look for.
+ * @param ppBackend Where to store the pointer to the backend descriptor on success.
+ */
+DECLHIDDEN(int) vdFindImageBackend(const char *pszBackend, PCVDIMAGEBACKEND *ppBackend)
+{
+ int rc = VERR_NOT_FOUND;
+ PCVDIMAGEBACKEND pBackend = NULL;
+
+ if (!g_apBackends)
+ VDInit();
+
+ for (unsigned i = 0; i < g_cBackends; i++)
+ {
+ if (!RTStrICmp(pszBackend, g_apBackends[i]->pszBackendName))
+ {
+ pBackend = g_apBackends[i];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ *ppBackend = pBackend;
+ return rc;
+}
+
+/**
+ * Returns the number of known cache format backends.
+ *
+ * @returns Number of image formats known.
+ */
+DECLHIDDEN(uint32_t) vdGetCacheBackendCount(void)
+{
+ return g_cCacheBackends;
+}
+
+
+/**
+ * Queries a cache backend descriptor by the index.
+ *
+ * @returns VBox status code.
+ * @param idx The index of the backend to query.
+ * @param ppBackend Where to store the pointer to the backend descriptor on success.
+ */
+DECLHIDDEN(int) vdQueryCacheBackend(uint32_t idx, PCVDCACHEBACKEND *ppBackend)
+{
+ if (idx >= g_cCacheBackends)
+ return VERR_OUT_OF_RANGE;
+
+ *ppBackend = g_apCacheBackends[idx];
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Returns the cache backend descriptor matching the given identifier if known.
+ *
+ * @returns VBox status code.
+ * @param pszBackend The backend identifier to look for.
+ * @param ppBackend Where to store the pointer to the backend descriptor on success.
+ */
+DECLHIDDEN(int) vdFindCacheBackend(const char *pszBackend, PCVDCACHEBACKEND *ppBackend)
+{
+ int rc = VERR_NOT_FOUND;
+ PCVDCACHEBACKEND pBackend = NULL;
+
+ if (!g_apCacheBackends)
+ VDInit();
+
+ for (unsigned i = 0; i < g_cCacheBackends; i++)
+ {
+ if (!RTStrICmp(pszBackend, g_apCacheBackends[i]->pszBackendName))
+ {
+ pBackend = g_apCacheBackends[i];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ *ppBackend = pBackend;
+ return rc;
+}
+
+
+/**
+ * Returns the number of known filter backends.
+ *
+ * @returns Number of image formats known.
+ */
+DECLHIDDEN(uint32_t) vdGetFilterBackendCount(void)
+{
+ return g_cFilterBackends;
+}
+
+
+/**
+ * Queries a filter backend descriptor by the index.
+ *
+ * @returns VBox status code.
+ * @param idx The index of the backend to query.
+ * @param ppBackend Where to store the pointer to the backend descriptor on success.
+ */
+DECLHIDDEN(int) vdQueryFilterBackend(uint32_t idx, PCVDFILTERBACKEND *ppBackend)
+{
+ if (idx >= g_cFilterBackends)
+ return VERR_OUT_OF_RANGE;
+
+ *ppBackend = g_apFilterBackends[idx];
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Returns the filter backend descriptor matching the given identifier if known.
+ *
+ * @returns VBox status code.
+ * @param pszFilter The filter identifier to look for.
+ * @param ppBackend Where to store the pointer to the backend descriptor on success.
+ */
+DECLHIDDEN(int) vdFindFilterBackend(const char *pszFilter, PCVDFILTERBACKEND *ppBackend)
+{
+ int rc = VERR_NOT_FOUND;
+ PCVDFILTERBACKEND pBackend = NULL;
+
+ for (unsigned i = 0; i < g_cFilterBackends; i++)
+ {
+ if (!RTStrICmp(pszFilter, g_apFilterBackends[i]->pszBackendName))
+ {
+ pBackend = g_apFilterBackends[i];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ *ppBackend = pBackend;
+ return rc;
+}
+
+
+/**
+ * Worker for VDPluginLoadFromFilename() and vdPluginLoadFromPath().
+ *
+ * @returns VBox status code.
+ * @param pszFilename The plugin filename to load.
+ */
+DECLHIDDEN(int) vdPluginLoadFromFilename(const char *pszFilename)
+{
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ /* Plugin loaded? Nothing to do. */
+ if (vdPluginFind(pszFilename))
+ {
+ LogFlowFunc(("Plugin '%s' already loaded\n", pszFilename));
+ return VINF_SUCCESS;
+ }
+
+ RTLDRMOD hPlugin = NIL_RTLDRMOD;
+ int rc = SUPR3HardenedLdrLoadPlugIn(pszFilename, &hPlugin, NULL);
+ LogFlowFunc(("SUPR3HardenedLdrLoadPlugIn('%s') -> %Rrc\n", pszFilename, rc));
+ if (RT_SUCCESS(rc))
+ {
+ VDBACKENDREGISTER BackendRegister;
+ PFNVDPLUGINLOAD pfnVDPluginLoad = NULL;
+
+ BackendRegister.u32Version = VD_BACKENDREG_CB_VERSION;
+ BackendRegister.pfnRegisterImage = vdPluginRegisterImage;
+ BackendRegister.pfnRegisterCache = vdPluginRegisterCache;
+ BackendRegister.pfnRegisterFilter = vdPluginRegisterFilter;
+
+ rc = RTLdrGetSymbol(hPlugin, VD_PLUGIN_LOAD_NAME, (void**)&pfnVDPluginLoad);
+ if (RT_FAILURE(rc) || !pfnVDPluginLoad)
+ {
+ LogFunc(("error resolving the entry point %s in plugin %s, rc=%Rrc, pfnVDPluginLoad=%#p\n",
+ VD_PLUGIN_LOAD_NAME, pszFilename, rc, pfnVDPluginLoad));
+ if (RT_SUCCESS(rc))
+ rc = VERR_SYMBOL_NOT_FOUND;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Get the function table. */
+ rc = pfnVDPluginLoad(hPlugin, &BackendRegister);
+ }
+ else
+ LogFunc(("ignored plugin '%s': rc=%Rrc\n", pszFilename, rc));
+
+ /* Create a plugin entry on success. */
+ if (RT_SUCCESS(rc))
+ vdAddPlugin(hPlugin, pszFilename);
+ else
+ RTLdrClose(hPlugin);
+ }
+
+ return rc;
+#else
+ RT_NOREF1(pszFilename);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+/**
+ * Worker for VDPluginLoadFromPath() and vdLoadDynamicBackends().
+ *
+ * @returns VBox status code.
+ * @param pszPath The path to load plugins from.
+ */
+DECLHIDDEN(int) vdPluginLoadFromPath(const char *pszPath)
+{
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ /* To get all entries with VBoxHDD as prefix. */
+ char *pszPluginFilter = RTPathJoinA(pszPath, VD_PLUGIN_PREFIX "*");
+ if (!pszPluginFilter)
+ return VERR_NO_STR_MEMORY;
+
+ PRTDIRENTRYEX pPluginDirEntry = NULL;
+ RTDIR hPluginDir;
+ size_t cbPluginDirEntry = sizeof(RTDIRENTRYEX);
+ int rc = RTDirOpenFiltered(&hPluginDir, pszPluginFilter, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ pPluginDirEntry = (PRTDIRENTRYEX)RTMemAllocZ(sizeof(RTDIRENTRYEX));
+ if (pPluginDirEntry)
+ {
+ while ( (rc = RTDirReadEx(hPluginDir, pPluginDirEntry, &cbPluginDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK))
+ != VERR_NO_MORE_FILES)
+ {
+ char *pszPluginPath = NULL;
+
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* allocate new buffer. */
+ RTMemFree(pPluginDirEntry);
+ pPluginDirEntry = (PRTDIRENTRYEX)RTMemAllocZ(cbPluginDirEntry);
+ if (!pPluginDirEntry)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ /* Retry. */
+ rc = RTDirReadEx(hPluginDir, pPluginDirEntry, &cbPluginDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (RT_FAILURE(rc))
+ break;
+
+ /* We got the new entry. */
+ if (!RTFS_IS_FILE(pPluginDirEntry->Info.Attr.fMode))
+ continue;
+
+ /* Prepend the path to the libraries. */
+ pszPluginPath = RTPathJoinA(pszPath, pPluginDirEntry->szName);
+ if (!pszPluginPath)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+
+ rc = vdPluginLoadFromFilename(pszPluginPath);
+ RTStrFree(pszPluginPath);
+ }
+
+ RTMemFree(pPluginDirEntry);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTDirClose(hPluginDir);
+ }
+ else
+ {
+ /* On Windows the above immediately signals that there are no
+ * files matching, while on other platforms enumerating the
+ * files below fails. Either way: no plugins. */
+ }
+
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ RTStrFree(pszPluginFilter);
+ return rc;
+#else
+ RT_NOREF1(pszPath);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+/**
+ * internal: scans plugin directory and loads found plugins.
+ */
+static int vdLoadDynamicBackends(void)
+{
+ /*
+ * Enumerate plugin backends from the application directory where the other
+ * shared libraries are.
+ */
+ char szPath[RTPATH_MAX];
+ int rc = RTPathAppPrivateArch(szPath, sizeof(szPath));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ return vdPluginLoadFromPath(szPath);
+}
+#endif
+
+/**
+ * Worker for VDPluginUnloadFromFilename() and vdPluginUnloadFromPath().
+ *
+ * @returns VBox status code.
+ * @param pszFilename The plugin filename to unload.
+ */
+DECLHIDDEN(int) vdPluginUnloadFromFilename(const char *pszFilename)
+{
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ return vdRemovePlugin(pszFilename);
+#else
+ RT_NOREF1(pszFilename);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+/**
+ * Worker for VDPluginUnloadFromPath().
+ *
+ * @returns VBox status code.
+ * @param pszPath The path to unload plugins from.
+ */
+DECLHIDDEN(int) vdPluginUnloadFromPath(const char *pszPath)
+{
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ /* To get all entries with VBoxHDD as prefix. */
+ char *pszPluginFilter = RTPathJoinA(pszPath, VD_PLUGIN_PREFIX "*");
+ if (!pszPluginFilter)
+ return VERR_NO_STR_MEMORY;
+
+ PRTDIRENTRYEX pPluginDirEntry = NULL;
+ RTDIR hPluginDir;
+ size_t cbPluginDirEntry = sizeof(RTDIRENTRYEX);
+ int rc = RTDirOpenFiltered(&hPluginDir, pszPluginFilter, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ pPluginDirEntry = (PRTDIRENTRYEX)RTMemAllocZ(sizeof(RTDIRENTRYEX));
+ if (pPluginDirEntry)
+ {
+ while ((rc = RTDirReadEx(hPluginDir, pPluginDirEntry, &cbPluginDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK)) != VERR_NO_MORE_FILES)
+ {
+ char *pszPluginPath = NULL;
+
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* allocate new buffer. */
+ RTMemFree(pPluginDirEntry);
+ pPluginDirEntry = (PRTDIRENTRYEX)RTMemAllocZ(cbPluginDirEntry);
+ if (!pPluginDirEntry)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ /* Retry. */
+ rc = RTDirReadEx(hPluginDir, pPluginDirEntry, &cbPluginDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (RT_FAILURE(rc))
+ break;
+
+ /* We got the new entry. */
+ if (!RTFS_IS_FILE(pPluginDirEntry->Info.Attr.fMode))
+ continue;
+
+ /* Prepend the path to the libraries. */
+ pszPluginPath = RTPathJoinA(pszPath, pPluginDirEntry->szName);
+ if (!pszPluginPath)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+
+ rc = vdPluginUnloadFromFilename(pszPluginPath);
+ RTStrFree(pszPluginPath);
+ }
+
+ RTMemFree(pPluginDirEntry);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTDirClose(hPluginDir);
+ }
+ else
+ {
+ /* On Windows the above immediately signals that there are no
+ * files matching, while on other platforms enumerating the
+ * files below fails. Either way: no plugins. */
+ }
+
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ RTStrFree(pszPluginFilter);
+ return rc;
+#else
+ RT_NOREF1(pszPath);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+/**
+ * Initializes the plugin state to be able to load further plugins and populates
+ * the backend lists with the compiled in backends.
+ *
+ * @returns VBox status code.
+ */
+DECLHIDDEN(int) vdPluginInit(void)
+{
+ int rc = vdAddBackends(NIL_RTLDRMOD, aStaticBackends, RT_ELEMENTS(aStaticBackends));
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdAddCacheBackends(NIL_RTLDRMOD, aStaticCacheBackends, RT_ELEMENTS(aStaticCacheBackends));
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&g_ListPluginsLoaded);
+ rc = vdLoadDynamicBackends();
+ }
+#endif
+ }
+
+ return rc;
+}
+
+
+/**
+ * Tears down the plugin related state.
+ *
+ * @returns VBox status code.
+ */
+DECLHIDDEN(int) vdPluginTerm(void)
+{
+ if (!g_apBackends)
+ return VERR_INTERNAL_ERROR;
+
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ if (g_pahFilterBackendPlugins)
+ RTMemFree(g_pahFilterBackendPlugins);
+#endif
+ if (g_apFilterBackends)
+ RTMemFree(g_apFilterBackends);
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ if (g_ahCacheBackendPlugins)
+ RTMemFree(g_ahCacheBackendPlugins);
+#endif
+ if (g_apCacheBackends)
+ RTMemFree(g_apCacheBackends);
+ RTMemFree(g_apBackends);
+
+ g_cBackends = 0;
+ g_apBackends = NULL;
+
+ /* Clear the supported cache backends. */
+ g_cCacheBackends = 0;
+ g_apCacheBackends = NULL;
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ g_ahCacheBackendPlugins = NULL;
+#endif
+
+ /* Clear the supported filter backends. */
+ g_cFilterBackends = 0;
+ g_apFilterBackends = NULL;
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ g_pahFilterBackendPlugins = NULL;
+#endif
+
+#ifndef VBOX_HDD_NO_DYNAMIC_BACKENDS
+ PVDPLUGIN pPlugin, pPluginNext;
+ RTListForEachSafe(&g_ListPluginsLoaded, pPlugin, pPluginNext, VDPLUGIN, NodePlugin)
+ {
+ RTLdrClose(pPlugin->hPlugin);
+ RTStrFree(pPlugin->pszFilename);
+ RTListNodeRemove(&pPlugin->NodePlugin);
+ RTMemFree(pPlugin);
+ }
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Returns whether the plugin related state is initialized.
+ *
+ * @returns true if the plugin state is initialized and plugins can be loaded,
+ * false otherwise.
+ */
+DECLHIDDEN(bool) vdPluginIsInitialized(void)
+{
+ return g_apBackends != NULL;
+}
+
diff --git a/src/VBox/Storage/VDVfs.cpp b/src/VBox/Storage/VDVfs.cpp
new file mode 100644
index 00000000..7a728b1b
--- /dev/null
+++ b/src/VBox/Storage/VDVfs.cpp
@@ -0,0 +1,800 @@
+/* $Id: VDVfs.cpp $ */
+/** @file
+ * Virtual Disk Container implementation. - VFS glue.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/types.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/err.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/file.h>
+#include <iprt/sg.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/poll.h>
+#include <VBox/vd.h>
+
+#include "VDInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * The internal data of a DVM volume I/O stream.
+ */
+typedef struct VDVFSFILE
+{
+ /** The volume the VFS file belongs to. */
+ PVDISK pDisk;
+ /** Current position. */
+ uint64_t offCurPos;
+ /** Flags given during creation. */
+ uint32_t fFlags;
+} VDVFSFILE;
+/** Pointer to a the internal data of a DVM volume file. */
+typedef VDVFSFILE *PVDVFSFILE;
+
+/**
+ * VD read helper taking care of unaligned accesses.
+ *
+ * @return VBox status code.
+ * @param pDisk VD disk container.
+ * @param off Offset to start reading from.
+ * @param pvBuf Pointer to the buffer to read into.
+ * @param cbRead Amount of bytes to read.
+ */
+static int vdReadHelper(PVDISK pDisk, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ int rc;
+
+ /* Take direct route if the request is sector aligned. */
+ uint64_t const offMisalign = off & 511;
+ size_t const cbMisalign = (off + cbRead) & 511;
+ if ( !offMisalign
+ && !cbMisalign)
+ rc = VDRead(pDisk, off, pvBuf, cbRead);
+ else
+ {
+ uint8_t *pbBuf = (uint8_t *)pvBuf;
+ uint8_t abBuf[512];
+
+ /* Unaligned buffered read of head. Aligns the offset. */
+ if (offMisalign)
+ {
+ rc = VDRead(pDisk, off - offMisalign, abBuf, 512);
+ if (RT_SUCCESS(rc))
+ {
+ size_t const cbPart = RT_MIN(512 - offMisalign, cbRead);
+ memcpy(pbBuf, &abBuf[offMisalign], cbPart);
+ pbBuf += cbPart;
+ off += cbPart;
+ cbRead -= cbPart;
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ /* Aligned direct read. */
+ if ( RT_SUCCESS(rc)
+ && cbRead >= 512)
+ {
+ Assert(!(off % 512));
+
+ size_t cbPart = cbRead - cbMisalign;
+ Assert(!(cbPart % 512));
+ rc = VDRead(pDisk, off, pbBuf, cbPart);
+ if (RT_SUCCESS(rc))
+ {
+ pbBuf += cbPart;
+ off += cbPart;
+ cbRead -= cbPart;
+ }
+ }
+
+ /* Unaligned buffered read of tail. */
+ if ( RT_SUCCESS(rc)
+ && cbRead)
+ {
+ Assert(cbRead == cbMisalign);
+ Assert(cbRead < 512);
+ Assert(!(off % 512));
+
+ rc = VDRead(pDisk, off, abBuf, 512);
+ if (RT_SUCCESS(rc))
+ memcpy(pbBuf, abBuf, cbRead);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * VD write helper taking care of unaligned accesses.
+ *
+ * @return VBox status code.
+ * @param pDisk VD disk container.
+ * @param off Offset to start writing to.
+ * @param pvSrc Pointer to the buffer to read from.
+ * @param cbWrite Amount of bytes to write.
+ */
+static int vdWriteHelper(PVDISK pDisk, uint64_t off, const void *pvSrc, size_t cbWrite)
+{
+ uint8_t const *pbSrc = (uint8_t const *)pvSrc;
+ uint8_t abBuf[4096];
+ int rc;
+
+ /*
+ * Take direct route if the request is sector aligned.
+ */
+ uint64_t const offMisalign = off & 511;
+ size_t const cbMisalign = (off + cbWrite) & 511;
+ if ( !offMisalign
+ && !cbMisalign)
+ {
+ if (RTListIsEmpty(&pDisk->ListFilterChainWrite))
+ rc = VDWrite(pDisk, off, pbSrc, cbWrite);
+ else
+ {
+ /* Filtered writes must be double buffered as the filter may need to modify the input buffer directly. */
+ do
+ {
+ size_t cbThisWrite = RT_MIN(cbWrite, sizeof(abBuf));
+ rc = VDWrite(pDisk, off, memcpy(abBuf, pbSrc, cbThisWrite), cbThisWrite);
+ if (RT_SUCCESS(rc))
+ {
+ pbSrc += cbThisWrite;
+ off += cbThisWrite;
+ cbWrite -= cbThisWrite;
+ }
+ else
+ break;
+ } while (cbWrite > 0);
+ }
+ }
+ else
+ {
+
+ /*
+ * Unaligned buffered read+write of head. Aligns the offset.
+ */
+ if (offMisalign)
+ {
+ rc = VDRead(pDisk, off - offMisalign, abBuf, 512);
+ if (RT_SUCCESS(rc))
+ {
+ size_t const cbPart = RT_MIN(512 - offMisalign, cbWrite);
+ memcpy(&abBuf[offMisalign], pbSrc, cbPart);
+ rc = VDWrite(pDisk, off - offMisalign, abBuf, 512);
+ if (RT_SUCCESS(rc))
+ {
+ pbSrc += cbPart;
+ off += cbPart;
+ cbWrite -= cbPart;
+ }
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ /*
+ * Aligned direct write.
+ */
+ if ( RT_SUCCESS(rc)
+ && cbWrite >= 512)
+ {
+ Assert(!(off % 512));
+ size_t cbPart = cbWrite - cbMisalign;
+ Assert(!(cbPart % 512));
+
+ if (RTListIsEmpty(&pDisk->ListFilterChainWrite))
+ {
+ rc = VDWrite(pDisk, off, pbSrc, cbPart);
+ if (RT_SUCCESS(rc))
+ {
+ pbSrc += cbPart;
+ off += cbPart;
+ cbWrite -= cbPart;
+ }
+ }
+ else
+ {
+ /* Filtered writes must be double buffered as the filter may need to modify the input buffer directly. */
+ do
+ {
+ size_t cbThisWrite = RT_MIN(cbPart, sizeof(abBuf));
+ rc = VDWrite(pDisk, off, memcpy(abBuf, pbSrc, cbThisWrite), cbThisWrite);
+ if (RT_SUCCESS(rc))
+ {
+ pbSrc += cbThisWrite;
+ off += cbThisWrite;
+ cbWrite -= cbThisWrite;
+ cbPart -= cbThisWrite;
+ }
+ else
+ break;
+ } while (cbPart > 0);
+ }
+ }
+
+ /*
+ * Unaligned buffered read+write of tail.
+ */
+ if ( RT_SUCCESS(rc)
+ && cbWrite > 0)
+ {
+ Assert(cbWrite == cbMisalign);
+ Assert(cbWrite < 512);
+ Assert(!(off % 512));
+
+ rc = VDRead(pDisk, off, abBuf, 512);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(abBuf, pbSrc, cbWrite);
+ rc = VDWrite(pDisk, off, abBuf, 512);
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) vdVfsFile_Close(void *pvThis)
+{
+ PVDVFSFILE pThis = (PVDVFSFILE)pvThis;
+
+ if (pThis->fFlags & VD_VFSFILE_DESTROY_ON_RELEASE)
+ VDDestroy(pThis->pDisk);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) vdVfsFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PVDVFSFILE pThis = (PVDVFSFILE)pvThis;
+ unsigned const cOpenImages = VDGetCount(pThis->pDisk);
+
+ pObjInfo->cbObject = VDGetSize(pThis->pDisk, cOpenImages - 1);
+ pObjInfo->cbAllocated = 0;
+ for (unsigned iImage = 0; iImage < cOpenImages; iImage++)
+ pObjInfo->cbAllocated += VDGetFileSize(pThis->pDisk, iImage);
+
+ /** @todo enumerate the disk images directly... */
+ RTTimeNow(&pObjInfo->AccessTime);
+ pObjInfo->BirthTime = pObjInfo->AccessTime;
+ pObjInfo->ChangeTime = pObjInfo->AccessTime;
+ pObjInfo->ModificationTime = pObjInfo->AccessTime;
+
+ pObjInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE | 0644;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_UNIX:
+ pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
+ pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = 0;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ break;
+
+ default:
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) vdVfsFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PVDVFSFILE pThis = (PVDVFSFILE)pvThis;
+
+ Assert(pSgBuf->cSegs == 1);
+ NOREF(fBlocking);
+
+ /*
+ * Find the current position and check if it's within the volume.
+ */
+ uint64_t offUnsigned = off < 0 ? pThis->offCurPos : (uint64_t)off;
+ uint64_t const cbImage = VDGetSize(pThis->pDisk, VD_LAST_IMAGE);
+ if (offUnsigned >= cbImage)
+ {
+ if (pcbRead)
+ {
+ *pcbRead = 0;
+ pThis->offCurPos = cbImage;
+ return VINF_EOF;
+ }
+ return VERR_EOF;
+ }
+
+ int rc = VINF_SUCCESS;
+ size_t cbLeftToRead = pSgBuf->paSegs[0].cbSeg;
+ if (offUnsigned + cbLeftToRead <= cbImage)
+ {
+ if (pcbRead)
+ *pcbRead = cbLeftToRead;
+ }
+ else
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ *pcbRead = cbLeftToRead = (size_t)(cbImage - offUnsigned);
+ rc = VINF_EOF;
+ }
+
+ /*
+ * Ok, we've got a valid stretch within the file. Do the reading.
+ */
+ if (cbLeftToRead > 0)
+ {
+ int rc2 = vdReadHelper(pThis->pDisk, offUnsigned, pSgBuf->paSegs[0].pvSeg, cbLeftToRead);
+ if (RT_SUCCESS(rc2))
+ offUnsigned += cbLeftToRead;
+ else
+ rc = rc2;
+ }
+
+ pThis->offCurPos = offUnsigned;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) vdVfsFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PVDVFSFILE pThis = (PVDVFSFILE)pvThis;
+
+ Assert(pSgBuf->cSegs == 1);
+ NOREF(fBlocking);
+
+ /*
+ * Find the current position and check if it's within the volume.
+ * Writing beyond the end of a volume is not supported.
+ */
+ uint64_t offUnsigned = off < 0 ? pThis->offCurPos : (uint64_t)off;
+ uint64_t const cbImage = VDGetSize(pThis->pDisk, VD_LAST_IMAGE);
+ if (offUnsigned >= cbImage)
+ {
+ if (pcbWritten)
+ {
+ *pcbWritten = 0;
+ pThis->offCurPos = cbImage;
+ }
+ return VERR_EOF;
+ }
+
+ size_t cbLeftToWrite;
+ if (offUnsigned + pSgBuf->paSegs[0].cbSeg <= cbImage)
+ {
+ cbLeftToWrite = pSgBuf->paSegs[0].cbSeg;
+ if (pcbWritten)
+ *pcbWritten = cbLeftToWrite;
+ }
+ else
+ {
+ if (!pcbWritten)
+ return VERR_EOF;
+ *pcbWritten = cbLeftToWrite = (size_t)(cbImage - offUnsigned);
+ }
+
+ /*
+ * Ok, we've got a valid stretch within the file. Do the reading.
+ */
+ int rc = VINF_SUCCESS;
+ if (cbLeftToWrite > 0)
+ {
+ rc = vdWriteHelper(pThis->pDisk, offUnsigned, pSgBuf->paSegs[0].pvSeg, cbLeftToWrite);
+ if (RT_SUCCESS(rc))
+ offUnsigned += cbLeftToWrite;
+ }
+
+ pThis->offCurPos = offUnsigned;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) vdVfsFile_Flush(void *pvThis)
+{
+ PVDVFSFILE pThis = (PVDVFSFILE)pvThis;
+ return VDFlush(pThis->pDisk);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) vdVfsFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PVDVFSFILE pThis = (PVDVFSFILE)pvThis;
+ *poffActual = pThis->offCurPos;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetMode}
+ */
+static DECLCALLBACK(int) vdVfsFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ NOREF(pvThis);
+ NOREF(fMode);
+ NOREF(fMask);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) vdVfsFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ NOREF(pvThis);
+ NOREF(pAccessTime);
+ NOREF(pModificationTime);
+ NOREF(pChangeTime);
+ NOREF(pBirthTime);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) vdVfsFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ NOREF(pvThis);
+ NOREF(uid);
+ NOREF(gid);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) vdVfsFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PVDVFSFILE pThis = (PVDVFSFILE)pvThis;
+
+ /*
+ * Seek relative to which position.
+ */
+ uint64_t offWrt;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offWrt = 0;
+ break;
+
+ case RTFILE_SEEK_CURRENT:
+ offWrt = pThis->offCurPos;
+ break;
+
+ case RTFILE_SEEK_END:
+ offWrt = VDGetSize(pThis->pDisk, VD_LAST_IMAGE);
+ break;
+
+ default:
+ return VERR_INTERNAL_ERROR_5;
+ }
+
+ /*
+ * Calc new position, take care to stay without bounds.
+ */
+ uint64_t offNew;
+ if (offSeek == 0)
+ offNew = offWrt;
+ else if (offSeek > 0)
+ {
+ offNew = offWrt + offSeek;
+ if ( offNew < offWrt
+ || offNew > RTFOFF_MAX)
+ offNew = RTFOFF_MAX;
+ }
+ else if ((uint64_t)-offSeek < offWrt)
+ offNew = offWrt + offSeek;
+ else
+ offNew = 0;
+
+ /*
+ * Update the state and set return value.
+ */
+ pThis->offCurPos = offNew;
+
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) vdVfsFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PVDVFSFILE pThis = (PVDVFSFILE)pvThis;
+ *pcbFile = VDGetSize(pThis->pDisk, VD_LAST_IMAGE);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Standard file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_vdVfsStdFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "VDFile",
+ vdVfsFile_Close,
+ vdVfsFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ vdVfsFile_Read,
+ vdVfsFile_Write,
+ vdVfsFile_Flush,
+ NULL /*PollOne*/,
+ vdVfsFile_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ /*RTVFSIOFILEOPS_FEAT_NO_AT_OFFSET*/ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ vdVfsFile_SetMode,
+ vdVfsFile_SetTimes,
+ vdVfsFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ vdVfsFile_Seek,
+ vdVfsFile_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ RTVFSFILEOPS_VERSION
+};
+
+
+VBOXDDU_DECL(int) VDCreateVfsFileFromDisk(PVDISK pDisk, uint32_t fFlags,
+ PRTVFSFILE phVfsFile)
+{
+ AssertPtrReturn(pDisk, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER);
+ AssertReturn((fFlags & ~VD_VFSFILE_FLAGS_MASK) == 0, VERR_INVALID_PARAMETER);
+
+ /*
+ * Create the volume file.
+ */
+ RTVFSFILE hVfsFile;
+ PVDVFSFILE pThis;
+ int rc = RTVfsNewFile(&g_vdVfsStdFileOps, sizeof(*pThis), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_WRITE,
+ NIL_RTVFS, NIL_RTVFSLOCK, &hVfsFile, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offCurPos = 0;
+ pThis->pDisk = pDisk;
+ pThis->fFlags = fFlags;
+
+ *phVfsFile = hVfsFile;
+ return VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) vdVfsChain_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_INVALID)
+ return VERR_VFS_CHAIN_MUST_BE_FIRST_ELEMENT;
+ if ( pElement->enmType != RTVFSOBJTYPE_FILE
+ && pElement->enmType != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_ONLY_FILE_OR_IOS;
+
+ if (pElement->cArgs < 1)
+ return VERR_VFS_CHAIN_AT_LEAST_ONE_ARG;
+
+ /*
+ * Parse the flag if present, save in pElement->uProvider.
+ */
+ uint32_t fFlags = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ
+ ? VD_OPEN_FLAGS_READONLY : VD_OPEN_FLAGS_NORMAL;
+ if (pElement->cArgs > 1)
+ {
+ pElement->paArgs[pElement->cArgs - 1].uProvider = true; /* indicates flags */
+ const char *psz = pElement->paArgs[pElement->cArgs - 1].psz;
+ if (*psz)
+ {
+ if ( !strcmp(psz, "ro")
+ || !strcmp(psz, "r"))
+ {
+ fFlags &= ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_NORMAL);
+ fFlags |= VD_OPEN_FLAGS_READONLY;
+ }
+ else if (!strcmp(psz, "rw"))
+ {
+ fFlags &= ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_NORMAL);
+ fFlags |= VD_OPEN_FLAGS_NORMAL;
+ }
+ else if (strlen(psz) <= 4)
+ {
+ *poffError = pElement->paArgs[pElement->cArgs - 1].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ else
+ pElement->paArgs[pElement->cArgs - 1].uProvider = false; /* indicates no flags */
+ }
+ }
+
+ pElement->uProvider = fFlags;
+ if ( pElement->cArgs > 2
+ || (pElement->cArgs == 2 && !pElement->paArgs[pElement->cArgs - 1].uProvider))
+ pElement->uProvider |= RT_BIT_64(63);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) vdVfsChain_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError, pErrInfo);
+ AssertReturn(hPrevVfsObj == NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE);
+
+ /* Determin the format. */
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ int rc = VDGetFormat(NULL, NULL, pElement->paArgs[0].psz, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_SUCCESS(rc))
+ {
+ PVDISK pDisk = NULL;
+ rc = VDCreate(NULL, enmType, &pDisk);
+ if (RT_SUCCESS(rc))
+ {
+ if (!(pElement->uProvider & RT_BIT_64(63)))
+ rc = VDOpen(pDisk, pszFormat, pElement->paArgs[0].psz, (uint32_t)pElement->uProvider, NULL);
+ else
+ {
+ uint32_t cChain = pElement->cArgs;
+ if (pElement->cArgs >= 2 && pElement->paArgs[pElement->cArgs - 1].uProvider != 0)
+ cChain--;
+ uint32_t const fFinal = (uint32_t)pElement->uProvider;
+ uint32_t const fReadOnly = (fFinal & ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_NORMAL)) | VD_OPEN_FLAGS_READONLY;
+ uint32_t iChain;
+ for (iChain = 0; iChain < cChain && RT_SUCCESS(rc); iChain++)
+ rc = VDOpen(pDisk, pszFormat, pElement->paArgs[iChain].psz, iChain + 1 >= cChain ? fFinal : fReadOnly, NULL);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = VDCreateVfsFileFromDisk(pDisk, VD_VFSFILE_DESTROY_ON_RELEASE, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszFormat);
+
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ return VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ VDDestroy(pDisk);
+ }
+ RTStrFree(pszFormat);
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) vdVfsChain_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement);
+ return false;
+}
+
+
+/** VFS chain element 'file'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainIsoFsVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "vd",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Opens a container image using the VD API.\n"
+ "To open a snapshot chain, start with the root image and end with the more recent diff image.\n"
+ "The final argument can be a flag 'ro' or 'r' for read-only, 'rw' for read-write.",
+ /* pfnValidate = */ vdVfsChain_Validate,
+ /* pfnInstantiate = */ vdVfsChain_Instantiate,
+ /* pfnCanReuseElement = */ vdVfsChain_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainIsoFsVolReg, rtVfsChainIsoFsVolReg);
+
diff --git a/src/VBox/Storage/VHD.cpp b/src/VBox/Storage/VHD.cpp
new file mode 100644
index 00000000..08339155
--- /dev/null
+++ b/src/VBox/Storage/VHD.cpp
@@ -0,0 +1,3179 @@
+/* $Id: VHD.cpp $ */
+/** @file
+ * VHD Disk image, Core Code.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_VHD
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/uuid.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+
+#include "VDBackends.h"
+
+#define VHD_RELATIVE_MAX_PATH 512
+#define VHD_ABSOLUTE_MAX_PATH 512
+
+#define VHD_SECTOR_SIZE 512
+#define VHD_BLOCK_SIZE (2 * _1M)
+
+/** The maximum VHD size is 2TB due to the 32bit sector numbers in the BAT.
+ * Note that this is the maximum file size including all footers and headers
+ * and not the maximum virtual disk size presented to the guest.
+ */
+#define VHD_MAX_SIZE (2 * _1T)
+/** Maximum number of 512 byte sectors for a VHD image. */
+#define VHD_MAX_SECTORS (VHD_MAX_SIZE / VHD_SECTOR_SIZE)
+
+/* This is common to all VHD disk types and is located at the end of the image */
+#pragma pack(1)
+typedef struct VHDFooter
+{
+ char Cookie[8];
+ uint32_t Features;
+ uint32_t Version;
+ uint64_t DataOffset;
+ uint32_t Timestamp;
+ uint8_t CreatorApp[4];
+ uint32_t CreatorVer;
+ uint32_t CreatorOS;
+ uint64_t OrigSize;
+ uint64_t CurSize;
+ uint16_t DiskGeometryCylinder;
+ uint8_t DiskGeometryHeads;
+ uint8_t DiskGeometrySectors;
+ uint32_t DiskType;
+ uint32_t Checksum;
+ char UniqueID[16];
+ uint8_t SavedState;
+ uint8_t Reserved[427];
+} VHDFooter;
+#pragma pack()
+
+/* this really is spelled with only one n */
+#define VHD_FOOTER_COOKIE "conectix"
+#define VHD_FOOTER_COOKIE_SIZE 8
+
+#define VHD_FOOTER_FEATURES_NOT_ENABLED 0
+#define VHD_FOOTER_FEATURES_TEMPORARY 1
+#define VHD_FOOTER_FEATURES_RESERVED 2
+
+#define VHD_FOOTER_FILE_FORMAT_VERSION 0x00010000
+#define VHD_FOOTER_DATA_OFFSET_FIXED UINT64_C(0xffffffffffffffff)
+#define VHD_FOOTER_DISK_TYPE_FIXED 2
+#define VHD_FOOTER_DISK_TYPE_DYNAMIC 3
+#define VHD_FOOTER_DISK_TYPE_DIFFERENCING 4
+
+#define VHD_MAX_LOCATOR_ENTRIES 8
+#define VHD_PLATFORM_CODE_NONE 0
+#define VHD_PLATFORM_CODE_WI2R 0x57693272
+#define VHD_PLATFORM_CODE_WI2K 0x5769326B
+#define VHD_PLATFORM_CODE_W2RU 0x57327275
+#define VHD_PLATFORM_CODE_W2KU 0x57326B75
+#define VHD_PLATFORM_CODE_MAC 0x4D163220
+#define VHD_PLATFORM_CODE_MACX 0x4D163258
+
+/* Header for expanding disk images. */
+#pragma pack(1)
+typedef struct VHDParentLocatorEntry
+{
+ uint32_t u32Code;
+ uint32_t u32DataSpace;
+ uint32_t u32DataLength;
+ uint32_t u32Reserved;
+ uint64_t u64DataOffset;
+} VHDPLE, *PVHDPLE;
+
+typedef struct VHDDynamicDiskHeader
+{
+ char Cookie[8];
+ uint64_t DataOffset;
+ uint64_t TableOffset;
+ uint32_t HeaderVersion;
+ uint32_t MaxTableEntries;
+ uint32_t BlockSize;
+ uint32_t Checksum;
+ uint8_t ParentUuid[16];
+ uint32_t ParentTimestamp;
+ uint32_t Reserved0;
+ uint16_t ParentUnicodeName[256];
+ VHDPLE ParentLocatorEntry[VHD_MAX_LOCATOR_ENTRIES];
+ uint8_t Reserved1[256];
+} VHDDynamicDiskHeader;
+#pragma pack()
+
+#define VHD_DYNAMIC_DISK_HEADER_COOKIE "cxsparse"
+#define VHD_DYNAMIC_DISK_HEADER_COOKIE_SIZE 8
+#define VHD_DYNAMIC_DISK_HEADER_VERSION 0x00010000
+
+/**
+ * Complete VHD image data structure.
+ */
+typedef struct VHDIMAGE
+{
+ /** Image file name. */
+ const char *pszFilename;
+ /** Opaque storage handle. */
+ PVDIOSTORAGE pStorage;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+
+ /** Open flags passed by VBoxHDD layer. */
+ unsigned uOpenFlags;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+
+ /** Physical geometry of this image. */
+ VDGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ VDGEOMETRY LCHSGeometry;
+
+ /** Image UUID. */
+ RTUUID ImageUuid;
+ /** Parent image UUID. */
+ RTUUID ParentUuid;
+
+ /** Parent's time stamp at the time of image creation. */
+ uint32_t u32ParentTimestamp;
+ /** Relative path to the parent image. */
+ char *pszParentFilename;
+
+ /** The Block Allocation Table. */
+ uint32_t *pBlockAllocationTable;
+ /** Number of entries in the table. */
+ uint32_t cBlockAllocationTableEntries;
+
+ /** Size of one data block. */
+ uint32_t cbDataBlock;
+ /** Sectors per data block. */
+ uint32_t cSectorsPerDataBlock;
+ /** Length of the sector bitmap in bytes. */
+ uint32_t cbDataBlockBitmap;
+ /** A copy of the disk footer. */
+ VHDFooter vhdFooterCopy;
+ /** Current end offset of the file (without the disk footer). */
+ uint64_t uCurrentEndOfFile;
+ /** Size of the data block bitmap in sectors. */
+ uint32_t cDataBlockBitmapSectors;
+ /** Start of the block allocation table. */
+ uint64_t uBlockAllocationTableOffset;
+ /** Buffer to hold block's bitmap for bit search operations. */
+ uint8_t *pu8Bitmap;
+ /** Offset to the next data structure (dynamic disk header). */
+ uint64_t u64DataOffset;
+ /** Flag to force dynamic disk header update. */
+ bool fDynHdrNeedsUpdate;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} VHDIMAGE, *PVHDIMAGE;
+
+/**
+ * Structure tracking the expansion process of the image
+ * for async access.
+ */
+typedef struct VHDIMAGEEXPAND
+{
+ /** Flag indicating the status of each step. */
+ volatile uint32_t fFlags;
+ /** The index in the block allocation table which is written. */
+ uint32_t idxBatAllocated;
+ /** Big endian representation of the block index
+ * which is written in the BAT. */
+ uint32_t idxBlockBe;
+ /** Old end of the file - used for rollback in case of an error. */
+ uint64_t cbEofOld;
+ /** Sector bitmap written to the new block - variable in size. */
+ uint8_t au8Bitmap[1];
+} VHDIMAGEEXPAND, *PVHDIMAGEEXPAND;
+
+/**
+ * Flag defines
+ */
+#define VHDIMAGEEXPAND_STEP_IN_PROGRESS (0x0)
+#define VHDIMAGEEXPAND_STEP_FAILED (0x2)
+#define VHDIMAGEEXPAND_STEP_SUCCESS (0x3)
+/** All steps completed successfully. */
+#define VHDIMAGEEXPAND_ALL_SUCCESS (0xff)
+/** All steps completed (no success indicator) */
+#define VHDIMAGEEXPAND_ALL_COMPLETE (0xaa)
+
+/** Every status field has 2 bits so we can encode 4 steps in one byte. */
+#define VHDIMAGEEXPAND_STATUS_MASK 0x03
+#define VHDIMAGEEXPAND_BLOCKBITMAP_STATUS_SHIFT 0x00
+#define VHDIMAGEEXPAND_USERBLOCK_STATUS_SHIFT 0x02
+#define VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT 0x04
+#define VHDIMAGEEXPAND_BAT_STATUS_SHIFT 0x06
+
+/**
+ * Helper macros to get and set the status field.
+ */
+#define VHDIMAGEEXPAND_STATUS_GET(fFlags, cShift) \
+ (((fFlags) >> (cShift)) & VHDIMAGEEXPAND_STATUS_MASK)
+#define VHDIMAGEEXPAND_STATUS_SET(fFlags, cShift, uVal) \
+ ASMAtomicOrU32(&(fFlags), ((uVal) & VHDIMAGEEXPAND_STATUS_MASK) << (cShift))
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aVhdFileExtensions[] =
+{
+ {"vhd", VDTYPE_HDD},
+ {NULL, VDTYPE_INVALID}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Internal: Compute and update header checksum.
+ */
+static uint32_t vhdChecksum(void *pHeader, uint32_t cbSize)
+{
+ uint32_t u32ChkSum = 0;
+ for (uint32_t i = 0; i < cbSize; i++)
+ u32ChkSum += ((unsigned char *)pHeader)[i];
+ return ~u32ChkSum;
+}
+
+/**
+ * Internal: Convert filename to UTF16 with appropriate endianness.
+ */
+static int vhdFilenameToUtf16(const char *pszFilename, uint16_t *pu16Buf,
+ uint32_t cbBufSize, uint32_t *pcbActualSize,
+ bool fBigEndian)
+{
+ int rc;
+ PRTUTF16 pTmp16 = NULL;
+ size_t cTmp16Len;
+
+ rc = RTStrToUtf16(pszFilename, &pTmp16);
+ if (RT_SUCCESS(rc))
+ {
+ cTmp16Len = RTUtf16Len(pTmp16);
+ if (cTmp16Len * sizeof(*pTmp16) <= cbBufSize)
+ {
+ if (fBigEndian)
+ for (unsigned i = 0; i < cTmp16Len; i++)
+ pu16Buf[i] = RT_H2BE_U16(pTmp16[i]);
+ else
+ memcpy(pu16Buf, pTmp16, cTmp16Len * sizeof(*pTmp16));
+ if (pcbActualSize)
+ *pcbActualSize = (uint32_t)(cTmp16Len * sizeof(*pTmp16));
+ }
+ else
+ rc = VERR_FILENAME_TOO_LONG;
+ }
+
+ if (pTmp16)
+ RTUtf16Free(pTmp16);
+ return rc;
+}
+
+/**
+ * Internal: Update one locator entry.
+ */
+static int vhdLocatorUpdate(PVHDIMAGE pImage, PVHDPLE pLocator, const char *pszFilename)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cb = 0;
+ uint32_t cbMaxLen = RT_BE2H_U32(pLocator->u32DataSpace);
+ void *pvBuf = RTMemTmpAllocZ(cbMaxLen);
+ char *pszTmp;
+
+ if (!pvBuf)
+ return VERR_NO_MEMORY;
+
+ switch (RT_BE2H_U32(pLocator->u32Code))
+ {
+ case VHD_PLATFORM_CODE_WI2R:
+ {
+ if (RTPathStartsWithRoot(pszFilename))
+ {
+ /* Convert to relative path. */
+ char szPath[RTPATH_MAX];
+ rc = RTPathCalcRelative(szPath, sizeof(szPath), pImage->pszFilename, true /*fFromFile*/, pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update plain relative name. */
+ cb = (uint32_t)strlen(szPath);
+ if (cb > cbMaxLen)
+ {
+ rc = VERR_FILENAME_TOO_LONG;
+ break;
+ }
+ memcpy(pvBuf, szPath, cb);
+ }
+ }
+ else
+ {
+ /* Update plain relative name. */
+ cb = (uint32_t)strlen(pszFilename);
+ if (cb > cbMaxLen)
+ {
+ rc = VERR_FILENAME_TOO_LONG;
+ break;
+ }
+ memcpy(pvBuf, pszFilename, cb);
+ }
+ if (RT_SUCCESS(rc))
+ pLocator->u32DataLength = RT_H2BE_U32(cb);
+ break;
+ }
+ case VHD_PLATFORM_CODE_WI2K:
+ /* Update plain absolute name. */
+ rc = RTPathAbs(pszFilename, (char *)pvBuf, cbMaxLen);
+ if (RT_SUCCESS(rc))
+ {
+ cb = (uint32_t)strlen((const char *)pvBuf);
+ pLocator->u32DataLength = RT_H2BE_U32(cb);
+ }
+ break;
+ case VHD_PLATFORM_CODE_W2RU:
+ if (RTPathStartsWithRoot(pszFilename))
+ {
+ /* Convert to relative path. */
+ char szPath[RTPATH_MAX];
+ rc = RTPathCalcRelative(szPath, sizeof(szPath), pImage->pszFilename, true /*fFromFile*/, pszFilename);
+ if (RT_SUCCESS(rc))
+ rc = vhdFilenameToUtf16(szPath, (uint16_t *)pvBuf, cbMaxLen, &cb, false);
+ }
+ else
+ {
+ /* Update unicode relative name. */
+ rc = vhdFilenameToUtf16(pszFilename, (uint16_t *)pvBuf, cbMaxLen, &cb, false);
+ }
+
+ if (RT_SUCCESS(rc))
+ pLocator->u32DataLength = RT_H2BE_U32(cb);
+ break;
+ case VHD_PLATFORM_CODE_W2KU:
+ /* Update unicode absolute name. */
+ pszTmp = (char*)RTMemTmpAllocZ(cbMaxLen);
+ if (!pszTmp)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ rc = RTPathAbs(pszFilename, pszTmp, cbMaxLen);
+ if (RT_FAILURE(rc))
+ {
+ RTMemTmpFree(pszTmp);
+ break;
+ }
+ rc = vhdFilenameToUtf16(pszTmp, (uint16_t *)pvBuf, cbMaxLen, &cb, false);
+ RTMemTmpFree(pszTmp);
+ if (RT_SUCCESS(rc))
+ pLocator->u32DataLength = RT_H2BE_U32(cb);
+ break;
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cb > 0);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ RT_BE2H_U64(pLocator->u64DataOffset),
+ pvBuf, cb);
+ }
+
+ if (pvBuf)
+ RTMemTmpFree(pvBuf);
+ return rc;
+}
+
+/**
+ * Internal: Update dynamic disk header from VHDIMAGE.
+ */
+static int vhdDynamicHeaderUpdate(PVHDIMAGE pImage)
+{
+ VHDDynamicDiskHeader ddh;
+ int rc, i;
+
+ if (!pImage)
+ return VERR_VD_NOT_OPENED;
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ pImage->u64DataOffset, &ddh, sizeof(ddh));
+ if (RT_FAILURE(rc))
+ return rc;
+ if (memcmp(ddh.Cookie, VHD_DYNAMIC_DISK_HEADER_COOKIE, VHD_DYNAMIC_DISK_HEADER_COOKIE_SIZE) != 0)
+ return VERR_VD_VHD_INVALID_HEADER;
+
+ uint32_t u32Checksum = RT_BE2H_U32(ddh.Checksum);
+ ddh.Checksum = 0;
+ if (u32Checksum != vhdChecksum(&ddh, sizeof(ddh)))
+ return VERR_VD_VHD_INVALID_HEADER;
+
+ /* Update parent's timestamp. */
+ ddh.ParentTimestamp = RT_H2BE_U32(pImage->u32ParentTimestamp);
+ /* Update parent's filename. */
+ if (pImage->pszParentFilename)
+ {
+ rc = vhdFilenameToUtf16(RTPathFilename(pImage->pszParentFilename),
+ ddh.ParentUnicodeName, sizeof(ddh.ParentUnicodeName) - 1, NULL, true);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /* Update parent's locators. */
+ for (i = 0; i < VHD_MAX_LOCATOR_ENTRIES; i++)
+ {
+ /* Skip empty locators */
+ if ( ddh.ParentLocatorEntry[i].u32Code != RT_H2BE_U32(VHD_PLATFORM_CODE_NONE)
+ && pImage->pszParentFilename)
+ {
+ rc = vhdLocatorUpdate(pImage, &ddh.ParentLocatorEntry[i], pImage->pszParentFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ /* Update parent's UUID */
+ memcpy(ddh.ParentUuid, pImage->ParentUuid.au8, sizeof(ddh.ParentUuid));
+
+ /* Update data offset and number of table entries. */
+ ddh.MaxTableEntries = RT_H2BE_U32(pImage->cBlockAllocationTableEntries);
+
+ ddh.Checksum = 0;
+ ddh.Checksum = RT_H2BE_U32(vhdChecksum(&ddh, sizeof(ddh)));
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->u64DataOffset, &ddh, sizeof(ddh));
+ return rc;
+}
+
+/**
+ * Internal: Update the VHD footer.
+ */
+static int vhdUpdateFooter(PVHDIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Update fields which can change. */
+ pImage->vhdFooterCopy.CurSize = RT_H2BE_U64(pImage->cbSize);
+ pImage->vhdFooterCopy.DiskGeometryCylinder = RT_H2BE_U16(pImage->PCHSGeometry.cCylinders);
+ pImage->vhdFooterCopy.DiskGeometryHeads = pImage->PCHSGeometry.cHeads;
+ pImage->vhdFooterCopy.DiskGeometrySectors = pImage->PCHSGeometry.cSectors;
+
+ pImage->vhdFooterCopy.Checksum = 0;
+ pImage->vhdFooterCopy.Checksum = RT_H2BE_U32(vhdChecksum(&pImage->vhdFooterCopy, sizeof(VHDFooter)));
+
+ if (pImage->pBlockAllocationTable)
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, 0,
+ &pImage->vhdFooterCopy, sizeof(VHDFooter));
+
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->uCurrentEndOfFile, &pImage->vhdFooterCopy,
+ sizeof(VHDFooter));
+
+ return rc;
+}
+
+/**
+ * Internal. Flush image data to disk.
+ */
+static int vhdFlushImage(PVHDIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ return VINF_SUCCESS;
+
+ if (pImage->pBlockAllocationTable)
+ {
+ /*
+ * This is an expanding image. Write the BAT and copy of the disk footer.
+ */
+ size_t cbBlockAllocationTableToWrite = pImage->cBlockAllocationTableEntries * sizeof(uint32_t);
+ uint32_t *pBlockAllocationTableToWrite = (uint32_t *)RTMemAllocZ(cbBlockAllocationTableToWrite);
+
+ if (!pBlockAllocationTableToWrite)
+ return VERR_NO_MEMORY;
+
+ /*
+ * The BAT entries have to be stored in big endian format.
+ */
+ for (unsigned i = 0; i < pImage->cBlockAllocationTableEntries; i++)
+ pBlockAllocationTableToWrite[i] = RT_H2BE_U32(pImage->pBlockAllocationTable[i]);
+
+ /*
+ * Write the block allocation table after the copy of the disk footer and the dynamic disk header.
+ */
+ vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, pImage->uBlockAllocationTableOffset,
+ pBlockAllocationTableToWrite, cbBlockAllocationTableToWrite);
+ if (pImage->fDynHdrNeedsUpdate)
+ rc = vhdDynamicHeaderUpdate(pImage);
+ RTMemFree(pBlockAllocationTableToWrite);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = vhdUpdateFooter(pImage);
+
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileFlushSync(pImage->pIfIo, pImage->pStorage);
+
+ return rc;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image except pImage,
+ * and optionally delete the image from disk.
+ */
+static int vhdFreeImage(PVHDIMAGE pImage, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ if (pImage->pStorage)
+ {
+ /* No point updating the file that is deleted anyway. */
+ if (!fDelete)
+ vhdFlushImage(pImage);
+
+ rc = vdIfIoIntFileClose(pImage->pIfIo, pImage->pStorage);
+ pImage->pStorage = NULL;
+ }
+
+ if (pImage->pszParentFilename)
+ {
+ RTStrFree(pImage->pszParentFilename);
+ pImage->pszParentFilename = NULL;
+ }
+ if (pImage->pBlockAllocationTable)
+ {
+ RTMemFree(pImage->pBlockAllocationTable);
+ pImage->pBlockAllocationTable = NULL;
+ }
+ if (pImage->pu8Bitmap)
+ {
+ RTMemFree(pImage->pu8Bitmap);
+ pImage->pu8Bitmap = NULL;
+ }
+
+ if (fDelete && pImage->pszFilename)
+ {
+ int rc2 = vdIfIoIntFileDelete(pImage->pIfIo, pImage->pszFilename);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/* 946684800 is the number of seconds between 1/1/1970 and 1/1/2000 */
+#define VHD_TO_UNIX_EPOCH_SECONDS UINT64_C(946684800)
+
+static uint32_t vhdRtTime2VhdTime(PCRTTIMESPEC pRtTimestamp)
+{
+ uint64_t u64Seconds = RTTimeSpecGetSeconds(pRtTimestamp);
+ return (uint32_t)(u64Seconds - VHD_TO_UNIX_EPOCH_SECONDS);
+}
+
+static void vhdTime2RtTime(PRTTIMESPEC pRtTimestamp, uint32_t u32VhdTimestamp)
+{
+ RTTimeSpecSetSeconds(pRtTimestamp, VHD_TO_UNIX_EPOCH_SECONDS + u32VhdTimestamp);
+}
+
+/**
+ * Internal: Allocates the block bitmap rounding up to the next 32bit or 64bit boundary.
+ * Can be freed with RTMemFree. The memory is zeroed.
+ */
+DECLINLINE(uint8_t *)vhdBlockBitmapAllocate(PVHDIMAGE pImage)
+{
+#ifdef RT_ARCH_AMD64
+ return (uint8_t *)RTMemAllocZ(pImage->cbDataBlockBitmap + 8);
+#else
+ return (uint8_t *)RTMemAllocZ(pImage->cbDataBlockBitmap + 4);
+#endif
+}
+
+/**
+ * Internal: called when the async expansion process completed (failure or success).
+ * Will do the necessary rollback if an error occurred.
+ */
+static int vhdAsyncExpansionComplete(PVHDIMAGE pImage, PVDIOCTX pIoCtx, PVHDIMAGEEXPAND pExpand)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t fFlags = ASMAtomicReadU32(&pExpand->fFlags);
+ bool fIoInProgress = false;
+
+ /* Quick path, check if everything succeeded. */
+ if (fFlags == VHDIMAGEEXPAND_ALL_SUCCESS)
+ {
+ pImage->pBlockAllocationTable[pExpand->idxBatAllocated] = RT_BE2H_U32(pExpand->idxBlockBe);
+ RTMemFree(pExpand);
+ }
+ else
+ {
+ uint32_t uStatus;
+
+ uStatus = VHDIMAGEEXPAND_STATUS_GET(pExpand->fFlags, VHDIMAGEEXPAND_BAT_STATUS_SHIFT);
+ if ( uStatus == VHDIMAGEEXPAND_STEP_FAILED
+ || uStatus == VHDIMAGEEXPAND_STEP_SUCCESS)
+ {
+ /* Undo and restore the old value. */
+ pImage->pBlockAllocationTable[pExpand->idxBatAllocated] = ~0U;
+
+ /* Restore the old value on the disk.
+ * No need for a completion callback because we can't
+ * do anything if this fails. */
+ if (uStatus == VHDIMAGEEXPAND_STEP_SUCCESS)
+ {
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ pImage->uBlockAllocationTableOffset
+ + pExpand->idxBatAllocated * sizeof(uint32_t),
+ &pImage->pBlockAllocationTable[pExpand->idxBatAllocated],
+ sizeof(uint32_t), pIoCtx, NULL, NULL);
+ fIoInProgress |= rc == VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+ }
+
+ /* Restore old size (including the footer because another application might
+ * fill up the free space making it impossible to add the footer)
+ * and add the footer at the right place again. */
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage,
+ pExpand->cbEofOld + sizeof(VHDFooter));
+ AssertRC(rc);
+
+ pImage->uCurrentEndOfFile = pExpand->cbEofOld;
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ pImage->uCurrentEndOfFile,
+ &pImage->vhdFooterCopy, sizeof(VHDFooter),
+ pIoCtx, NULL, NULL);
+ fIoInProgress |= rc == VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+
+ return fIoInProgress ? VERR_VD_ASYNC_IO_IN_PROGRESS : rc;
+}
+
+static int vhdAsyncExpansionStepCompleted(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq, unsigned iStep)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ PVHDIMAGEEXPAND pExpand = (PVHDIMAGEEXPAND)pvUser;
+
+ LogFlowFunc(("pBackendData=%#p pIoCtx=%#p pvUser=%#p rcReq=%Rrc iStep=%u\n",
+ pBackendData, pIoCtx, pvUser, rcReq, iStep));
+
+ if (RT_SUCCESS(rcReq))
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, iStep, VHDIMAGEEXPAND_STEP_SUCCESS);
+ else
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, iStep, VHDIMAGEEXPAND_STEP_FAILED);
+
+ if ((pExpand->fFlags & VHDIMAGEEXPAND_ALL_COMPLETE) == VHDIMAGEEXPAND_ALL_COMPLETE)
+ return vhdAsyncExpansionComplete(pImage, pIoCtx, pExpand);
+
+ return VERR_VD_ASYNC_IO_IN_PROGRESS;
+}
+
+static DECLCALLBACK(int) vhdAsyncExpansionDataBlockBitmapComplete(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ return vhdAsyncExpansionStepCompleted(pBackendData, pIoCtx, pvUser, rcReq, VHDIMAGEEXPAND_BLOCKBITMAP_STATUS_SHIFT);
+}
+
+static DECLCALLBACK(int) vhdAsyncExpansionDataComplete(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ return vhdAsyncExpansionStepCompleted(pBackendData, pIoCtx, pvUser, rcReq, VHDIMAGEEXPAND_USERBLOCK_STATUS_SHIFT);
+}
+
+static DECLCALLBACK(int) vhdAsyncExpansionBatUpdateComplete(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ return vhdAsyncExpansionStepCompleted(pBackendData, pIoCtx, pvUser, rcReq, VHDIMAGEEXPAND_BAT_STATUS_SHIFT);
+}
+
+static DECLCALLBACK(int) vhdAsyncExpansionFooterUpdateComplete(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ return vhdAsyncExpansionStepCompleted(pBackendData, pIoCtx, pvUser, rcReq, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT);
+}
+
+static int vhdLoadDynamicDisk(PVHDIMAGE pImage, uint64_t uDynamicDiskHeaderOffset)
+{
+ VHDDynamicDiskHeader vhdDynamicDiskHeader;
+ int rc = VINF_SUCCESS;
+ uint32_t *pBlockAllocationTable;
+ uint64_t uBlockAllocationTableOffset;
+ unsigned i = 0;
+
+ Log(("Open a dynamic disk.\n"));
+
+ /*
+ * Read the dynamic disk header.
+ */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, uDynamicDiskHeaderOffset,
+ &vhdDynamicDiskHeader, sizeof(VHDDynamicDiskHeader));
+ if (memcmp(vhdDynamicDiskHeader.Cookie, VHD_DYNAMIC_DISK_HEADER_COOKIE, VHD_DYNAMIC_DISK_HEADER_COOKIE_SIZE))
+ return VERR_INVALID_PARAMETER;
+
+ pImage->cbDataBlock = RT_BE2H_U32(vhdDynamicDiskHeader.BlockSize);
+ LogFlowFunc(("BlockSize=%u\n", pImage->cbDataBlock));
+ pImage->cBlockAllocationTableEntries = RT_BE2H_U32(vhdDynamicDiskHeader.MaxTableEntries);
+ LogFlowFunc(("MaxTableEntries=%lu\n", pImage->cBlockAllocationTableEntries));
+ AssertMsg(!(pImage->cbDataBlock % VHD_SECTOR_SIZE), ("%s: Data block size is not a multiple of %!\n", __FUNCTION__, VHD_SECTOR_SIZE));
+
+ /*
+ * Bail out if the number of BAT entries exceeds the number of sectors for a maximum image.
+ * Lower the number of sectors in the BAT as a few sectors are already occupied by the footers
+ * and headers.
+ */
+ if (pImage->cBlockAllocationTableEntries > (VHD_MAX_SECTORS - 2))
+ return VERR_VD_VHD_INVALID_HEADER;
+
+ pImage->cSectorsPerDataBlock = pImage->cbDataBlock / VHD_SECTOR_SIZE;
+ LogFlowFunc(("SectorsPerDataBlock=%u\n", pImage->cSectorsPerDataBlock));
+
+ /*
+ * Every block starts with a bitmap indicating which sectors are valid and which are not.
+ * We store the size of it to be able to calculate the real offset.
+ */
+ pImage->cbDataBlockBitmap = pImage->cSectorsPerDataBlock / 8;
+ pImage->cDataBlockBitmapSectors = pImage->cbDataBlockBitmap / VHD_SECTOR_SIZE;
+ /* Round up to full sector size */
+ if (pImage->cbDataBlockBitmap % VHD_SECTOR_SIZE > 0)
+ pImage->cDataBlockBitmapSectors++;
+ LogFlowFunc(("cbDataBlockBitmap=%u\n", pImage->cbDataBlockBitmap));
+ LogFlowFunc(("cDataBlockBitmapSectors=%u\n", pImage->cDataBlockBitmapSectors));
+
+ pImage->pu8Bitmap = vhdBlockBitmapAllocate(pImage);
+ if (!pImage->pu8Bitmap)
+ return VERR_NO_MEMORY;
+
+ pBlockAllocationTable = (uint32_t *)RTMemAllocZ(pImage->cBlockAllocationTableEntries * sizeof(uint32_t));
+ if (!pBlockAllocationTable)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Read the table.
+ */
+ uBlockAllocationTableOffset = RT_BE2H_U64(vhdDynamicDiskHeader.TableOffset);
+ LogFlowFunc(("uBlockAllocationTableOffset=%llu\n", uBlockAllocationTableOffset));
+ pImage->uBlockAllocationTableOffset = uBlockAllocationTableOffset;
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ uBlockAllocationTableOffset, pBlockAllocationTable,
+ pImage->cBlockAllocationTableEntries * sizeof(uint32_t));
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pBlockAllocationTable);
+ return rc;
+ }
+
+ /*
+ * Because the offset entries inside the allocation table are stored big endian
+ * we need to convert them into host endian.
+ */
+ pImage->pBlockAllocationTable = (uint32_t *)RTMemAllocZ(pImage->cBlockAllocationTableEntries * sizeof(uint32_t));
+ if (!pImage->pBlockAllocationTable)
+ {
+ RTMemFree(pBlockAllocationTable);
+ return VERR_NO_MEMORY;
+ }
+
+ for (i = 0; i < pImage->cBlockAllocationTableEntries; i++)
+ pImage->pBlockAllocationTable[i] = RT_BE2H_U32(pBlockAllocationTable[i]);
+
+ RTMemFree(pBlockAllocationTable);
+
+ if (pImage->uImageFlags & VD_IMAGE_FLAGS_DIFF)
+ memcpy(pImage->ParentUuid.au8, vhdDynamicDiskHeader.ParentUuid, sizeof(pImage->ParentUuid));
+
+ return rc;
+}
+
+static int vhdOpenImage(PVHDIMAGE pImage, unsigned uOpenFlags)
+{
+ uint64_t FileSize;
+ VHDFooter vhdFooter;
+
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /*
+ * Open the image.
+ */
+ int rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags,
+ false /* fCreate */),
+ &pImage->pStorage);
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ return rc;
+ }
+
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &FileSize);
+ pImage->uCurrentEndOfFile = FileSize - sizeof(VHDFooter);
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, pImage->uCurrentEndOfFile,
+ &vhdFooter, sizeof(VHDFooter));
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(vhdFooter.Cookie, VHD_FOOTER_COOKIE, VHD_FOOTER_COOKIE_SIZE) != 0)
+ {
+ /*
+ * There is also a backup header at the beginning in case the image got corrupted.
+ * Such corrupted images are detected here to let the open handler repair it later.
+ */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, 0,
+ &vhdFooter, sizeof(VHDFooter));
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(vhdFooter.Cookie, VHD_FOOTER_COOKIE, VHD_FOOTER_COOKIE_SIZE) != 0)
+ rc = VERR_VD_VHD_INVALID_HEADER;
+ else
+ rc = VERR_VD_IMAGE_CORRUPTED;
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ vhdFreeImage(pImage, false);
+ return rc;
+ }
+
+ switch (RT_BE2H_U32(vhdFooter.DiskType))
+ {
+ case VHD_FOOTER_DISK_TYPE_FIXED:
+ pImage->uImageFlags |= VD_IMAGE_FLAGS_FIXED;
+ break;
+ case VHD_FOOTER_DISK_TYPE_DYNAMIC:
+ pImage->uImageFlags &= ~VD_IMAGE_FLAGS_FIXED;
+ break;
+ case VHD_FOOTER_DISK_TYPE_DIFFERENCING:
+ pImage->uImageFlags |= VD_IMAGE_FLAGS_DIFF;
+ pImage->uImageFlags &= ~VD_IMAGE_FLAGS_FIXED;
+ break;
+ default:
+ vhdFreeImage(pImage, false);
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ pImage->cbSize = RT_BE2H_U64(vhdFooter.CurSize);
+ pImage->LCHSGeometry.cCylinders = 0;
+ pImage->LCHSGeometry.cHeads = 0;
+ pImage->LCHSGeometry.cSectors = 0;
+ pImage->PCHSGeometry.cCylinders = RT_BE2H_U16(vhdFooter.DiskGeometryCylinder);
+ pImage->PCHSGeometry.cHeads = vhdFooter.DiskGeometryHeads;
+ pImage->PCHSGeometry.cSectors = vhdFooter.DiskGeometrySectors;
+
+ /*
+ * Copy of the disk footer.
+ * If we allocate new blocks in differencing disks on write access
+ * the footer is overwritten. We need to write it at the end of the file.
+ */
+ memcpy(&pImage->vhdFooterCopy, &vhdFooter, sizeof(VHDFooter));
+
+ /*
+ * Is there a better way?
+ */
+ memcpy(&pImage->ImageUuid, &vhdFooter.UniqueID, 16);
+
+ pImage->u64DataOffset = RT_BE2H_U64(vhdFooter.DataOffset);
+ LogFlowFunc(("DataOffset=%llu\n", pImage->u64DataOffset));
+
+ if (!(pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ rc = vhdLoadDynamicDisk(pImage, pImage->u64DataOffset);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ vhdFreeImage(pImage, false);
+ return rc;
+}
+
+/**
+ * Internal: Checks if a sector in the block bitmap is set
+ */
+DECLINLINE(bool) vhdBlockBitmapSectorContainsData(PVHDIMAGE pImage, uint32_t cBlockBitmapEntry)
+{
+ uint32_t iBitmap = (cBlockBitmapEntry / 8); /* Byte in the block bitmap. */
+
+ /*
+ * The index of the bit in the byte of the data block bitmap.
+ * The most significant bit stands for a lower sector number.
+ */
+ uint8_t iBitInByte = (8-1) - (cBlockBitmapEntry % 8);
+ uint8_t *puBitmap = pImage->pu8Bitmap + iBitmap;
+
+ AssertMsg(puBitmap < (pImage->pu8Bitmap + pImage->cbDataBlockBitmap),
+ ("VHD: Current bitmap position exceeds maximum size of the bitmap\n"));
+
+ return ((*puBitmap) & RT_BIT(iBitInByte)) != 0;
+}
+
+/**
+ * Internal: Sets the given sector in the sector bitmap.
+ */
+DECLINLINE(bool) vhdBlockBitmapSectorSet(PVHDIMAGE pImage, uint8_t *pu8Bitmap, uint32_t cBlockBitmapEntry)
+{
+ RT_NOREF1(pImage);
+ uint32_t iBitmap = (cBlockBitmapEntry / 8); /* Byte in the block bitmap. */
+
+ /*
+ * The index of the bit in the byte of the data block bitmap.
+ * The most significant bit stands for a lower sector number.
+ */
+ uint8_t iBitInByte = (8-1) - (cBlockBitmapEntry % 8);
+ uint8_t *puBitmap = pu8Bitmap + iBitmap;
+
+ AssertMsg(puBitmap < (pu8Bitmap + pImage->cbDataBlockBitmap),
+ ("VHD: Current bitmap position exceeds maximum size of the bitmap\n"));
+
+ bool fClear = ((*puBitmap) & RT_BIT(iBitInByte)) == 0;
+ *puBitmap |= RT_BIT(iBitInByte);
+ return fClear;
+}
+
+/**
+ * Internal: Derive drive geometry from its size.
+ */
+static void vhdSetDiskGeometry(PVHDIMAGE pImage, uint64_t cbSize)
+{
+ uint64_t u64TotalSectors = cbSize / VHD_SECTOR_SIZE;
+ uint32_t u32CylinderTimesHeads, u32Heads, u32SectorsPerTrack;
+
+ if (u64TotalSectors > 65535 * 16 * 255)
+ {
+ /* ATA disks limited to 127 GB. */
+ u64TotalSectors = 65535 * 16 * 255;
+ }
+
+ if (u64TotalSectors >= 65535 * 16 * 63)
+ {
+ u32SectorsPerTrack = 255;
+ u32Heads = 16;
+ u32CylinderTimesHeads = u64TotalSectors / u32SectorsPerTrack;
+ }
+ else
+ {
+ u32SectorsPerTrack = 17;
+ u32CylinderTimesHeads = u64TotalSectors / u32SectorsPerTrack;
+
+ u32Heads = (u32CylinderTimesHeads + 1023) / 1024;
+
+ if (u32Heads < 4)
+ {
+ u32Heads = 4;
+ }
+ if (u32CylinderTimesHeads >= (u32Heads * 1024) || u32Heads > 16)
+ {
+ u32SectorsPerTrack = 31;
+ u32Heads = 16;
+ u32CylinderTimesHeads = u64TotalSectors / u32SectorsPerTrack;
+ }
+ if (u32CylinderTimesHeads >= (u32Heads * 1024))
+ {
+ u32SectorsPerTrack = 63;
+ u32Heads = 16;
+ u32CylinderTimesHeads = u64TotalSectors / u32SectorsPerTrack;
+ }
+ }
+ pImage->PCHSGeometry.cCylinders = u32CylinderTimesHeads / u32Heads;
+ pImage->PCHSGeometry.cHeads = u32Heads;
+ pImage->PCHSGeometry.cSectors = u32SectorsPerTrack;
+ pImage->LCHSGeometry.cCylinders = 0;
+ pImage->LCHSGeometry.cHeads = 0;
+ pImage->LCHSGeometry.cSectors = 0;
+}
+
+
+static uint32_t vhdAllocateParentLocators(PVHDIMAGE pImage, VHDDynamicDiskHeader *pDDH, uint64_t u64Offset)
+{
+ RT_NOREF1(pImage);
+ PVHDPLE pLocator = pDDH->ParentLocatorEntry;
+
+ /*
+ * The VHD spec states that the DataSpace field holds the number of sectors
+ * required to store the parent locator path.
+ * As it turned out VPC and Hyper-V store the amount of bytes reserved for the
+ * path and not the number of sectors.
+ */
+
+ /* Unicode absolute Windows path. */
+ pLocator->u32Code = RT_H2BE_U32(VHD_PLATFORM_CODE_W2KU);
+ pLocator->u32DataSpace = RT_H2BE_U32(VHD_ABSOLUTE_MAX_PATH * sizeof(RTUTF16));
+ pLocator->u64DataOffset = RT_H2BE_U64(u64Offset);
+ pLocator++;
+ u64Offset += VHD_ABSOLUTE_MAX_PATH * sizeof(RTUTF16);
+ /* Unicode relative Windows path. */
+ pLocator->u32Code = RT_H2BE_U32(VHD_PLATFORM_CODE_W2RU);
+ pLocator->u32DataSpace = RT_H2BE_U32(VHD_RELATIVE_MAX_PATH * sizeof(RTUTF16));
+ pLocator->u64DataOffset = RT_H2BE_U64(u64Offset);
+ u64Offset += VHD_RELATIVE_MAX_PATH * sizeof(RTUTF16);
+ return u64Offset;
+}
+
+/**
+ * Internal: Additional code for dynamic VHD image creation.
+ */
+static int vhdCreateDynamicImage(PVHDIMAGE pImage, uint64_t cbSize)
+{
+ int rc;
+ VHDDynamicDiskHeader DynamicDiskHeader;
+ uint32_t u32BlockAllocationTableSectors;
+ void *pvTmp = NULL;
+
+ memset(&DynamicDiskHeader, 0, sizeof(DynamicDiskHeader));
+
+ pImage->u64DataOffset = sizeof(VHDFooter);
+ pImage->cbDataBlock = VHD_BLOCK_SIZE; /* 2 MB */
+ pImage->cSectorsPerDataBlock = pImage->cbDataBlock / VHD_SECTOR_SIZE;
+ pImage->cbDataBlockBitmap = pImage->cSectorsPerDataBlock / 8;
+ pImage->cDataBlockBitmapSectors = pImage->cbDataBlockBitmap / VHD_SECTOR_SIZE;
+ /* Align to sector boundary */
+ if (pImage->cbDataBlockBitmap % VHD_SECTOR_SIZE > 0)
+ pImage->cDataBlockBitmapSectors++;
+ pImage->pu8Bitmap = vhdBlockBitmapAllocate(pImage);
+ if (!pImage->pu8Bitmap)
+ return vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS, N_("VHD: cannot allocate memory for bitmap storage"));
+
+ /* Initialize BAT. */
+ pImage->uBlockAllocationTableOffset = (uint64_t)sizeof(VHDFooter) + sizeof(VHDDynamicDiskHeader);
+ pImage->cBlockAllocationTableEntries = (uint32_t)((cbSize + pImage->cbDataBlock - 1) / pImage->cbDataBlock); /* Align table to the block size. */
+ u32BlockAllocationTableSectors = (pImage->cBlockAllocationTableEntries * sizeof(uint32_t) + VHD_SECTOR_SIZE - 1) / VHD_SECTOR_SIZE;
+ pImage->pBlockAllocationTable = (uint32_t *)RTMemAllocZ(pImage->cBlockAllocationTableEntries * sizeof(uint32_t));
+ if (!pImage->pBlockAllocationTable)
+ return vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS, N_("VHD: cannot allocate memory for BAT"));
+
+ for (unsigned i = 0; i < pImage->cBlockAllocationTableEntries; i++)
+ {
+ pImage->pBlockAllocationTable[i] = 0xFFFFFFFF; /* It is actually big endian. */
+ }
+
+ /* Round up to the sector size. */
+ if (pImage->uImageFlags & VD_IMAGE_FLAGS_DIFF) /* fix hyper-v unreadable error */
+ pImage->uCurrentEndOfFile = vhdAllocateParentLocators(pImage, &DynamicDiskHeader,
+ pImage->uBlockAllocationTableOffset + u32BlockAllocationTableSectors * VHD_SECTOR_SIZE);
+ else
+ pImage->uCurrentEndOfFile = pImage->uBlockAllocationTableOffset + u32BlockAllocationTableSectors * VHD_SECTOR_SIZE;
+
+ /* Set dynamic image size. */
+ pvTmp = RTMemTmpAllocZ(pImage->uCurrentEndOfFile + sizeof(VHDFooter));
+ if (!pvTmp)
+ return vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS, N_("VHD: cannot set the file size for '%s'"), pImage->pszFilename);
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, 0, pvTmp,
+ pImage->uCurrentEndOfFile + sizeof(VHDFooter));
+ if (RT_FAILURE(rc))
+ {
+ RTMemTmpFree(pvTmp);
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VHD: cannot set the file size for '%s'"), pImage->pszFilename);
+ }
+
+ RTMemTmpFree(pvTmp);
+
+ /* Initialize and write the dynamic disk header. */
+ memcpy(DynamicDiskHeader.Cookie, VHD_DYNAMIC_DISK_HEADER_COOKIE, sizeof(DynamicDiskHeader.Cookie));
+ DynamicDiskHeader.DataOffset = UINT64_C(0xFFFFFFFFFFFFFFFF); /* Initially the disk has no data. */
+ DynamicDiskHeader.TableOffset = RT_H2BE_U64(pImage->uBlockAllocationTableOffset);
+ DynamicDiskHeader.HeaderVersion = RT_H2BE_U32(VHD_DYNAMIC_DISK_HEADER_VERSION);
+ DynamicDiskHeader.BlockSize = RT_H2BE_U32(pImage->cbDataBlock);
+ DynamicDiskHeader.MaxTableEntries = RT_H2BE_U32(pImage->cBlockAllocationTableEntries);
+ /* Compute and update checksum. */
+ DynamicDiskHeader.Checksum = 0;
+ DynamicDiskHeader.Checksum = RT_H2BE_U32(vhdChecksum(&DynamicDiskHeader, sizeof(DynamicDiskHeader)));
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, sizeof(VHDFooter),
+ &DynamicDiskHeader, sizeof(DynamicDiskHeader));
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VHD: cannot write dynamic disk header to image '%s'"), pImage->pszFilename);
+
+ /* Write BAT. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, pImage->uBlockAllocationTableOffset,
+ pImage->pBlockAllocationTable,
+ pImage->cBlockAllocationTableEntries * sizeof(uint32_t));
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VHD: cannot write BAT to image '%s'"), pImage->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Internal: The actual code for VHD image creation, both fixed and dynamic.
+ */
+static int vhdCreateImage(PVHDIMAGE pImage, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry, PCRTUUID pUuid,
+ unsigned uOpenFlags,
+ PVDINTERFACEPROGRESS pIfProgress,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ RT_NOREF3(pszComment, pPCHSGeometry, pLCHSGeometry);
+ VHDFooter Footer;
+ RTTIMESPEC now;
+
+ pImage->uOpenFlags = uOpenFlags;
+ pImage->uImageFlags = uImageFlags;
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+
+ int rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags & ~VD_OPEN_FLAGS_READONLY,
+ true /* fCreate */),
+ &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ pImage->cbSize = cbSize;
+ pImage->ImageUuid = *pUuid;
+ RTUuidClear(&pImage->ParentUuid);
+ vhdSetDiskGeometry(pImage, cbSize);
+
+ /* Initialize the footer. */
+ memset(&Footer, 0, sizeof(Footer));
+ memcpy(Footer.Cookie, VHD_FOOTER_COOKIE, sizeof(Footer.Cookie));
+ Footer.Features = RT_H2BE_U32(0x2);
+ Footer.Version = RT_H2BE_U32(VHD_FOOTER_FILE_FORMAT_VERSION);
+ Footer.Timestamp = RT_H2BE_U32(vhdRtTime2VhdTime(RTTimeNow(&now)));
+ memcpy(Footer.CreatorApp, "vbox", sizeof(Footer.CreatorApp));
+ Footer.CreatorVer = RT_H2BE_U32(VBOX_VERSION);
+#ifdef RT_OS_DARWIN
+ Footer.CreatorOS = RT_H2BE_U32(0x4D616320); /* "Mac " */
+#else /* Virtual PC supports only two platforms atm, so everything else will be Wi2k. */
+ Footer.CreatorOS = RT_H2BE_U32(0x5769326B); /* "Wi2k" */
+#endif
+ Footer.OrigSize = RT_H2BE_U64(cbSize);
+ Footer.CurSize = Footer.OrigSize;
+ Footer.DiskGeometryCylinder = RT_H2BE_U16(pImage->PCHSGeometry.cCylinders);
+ Footer.DiskGeometryHeads = pImage->PCHSGeometry.cHeads;
+ Footer.DiskGeometrySectors = pImage->PCHSGeometry.cSectors;
+ memcpy(Footer.UniqueID, pImage->ImageUuid.au8, sizeof(Footer.UniqueID));
+ Footer.SavedState = 0;
+
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ {
+ Footer.DiskType = RT_H2BE_U32(VHD_FOOTER_DISK_TYPE_FIXED);
+ /*
+ * Initialize fixed image.
+ * "The size of the entire file is the size of the hard disk in
+ * the guest operating system plus the size of the footer."
+ */
+ pImage->u64DataOffset = VHD_FOOTER_DATA_OFFSET_FIXED;
+ pImage->uCurrentEndOfFile = cbSize;
+ rc = vdIfIoIntFileSetAllocationSize(pImage->pIfIo, pImage->pStorage, pImage->uCurrentEndOfFile + sizeof(VHDFooter),
+ 0 /* fFlags */, pIfProgress,
+ uPercentStart, uPercentSpan);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VHD: cannot set the file size for '%s'"), pImage->pszFilename);
+ }
+ else
+ {
+ /*
+ * Initialize dynamic image.
+ *
+ * The overall structure of dynamic disk is:
+ *
+ * [Copy of hard disk footer (512 bytes)]
+ * [Dynamic disk header (1024 bytes)]
+ * [BAT (Block Allocation Table)]
+ * [Parent Locators]
+ * [Data block 1]
+ * [Data block 2]
+ * ...
+ * [Data block N]
+ * [Hard disk footer (512 bytes)]
+ */
+ Footer.DiskType = (uImageFlags & VD_IMAGE_FLAGS_DIFF)
+ ? RT_H2BE_U32(VHD_FOOTER_DISK_TYPE_DIFFERENCING)
+ : RT_H2BE_U32(VHD_FOOTER_DISK_TYPE_DYNAMIC);
+ /* We are half way thorough with creation of image, let the caller know. */
+ vdIfProgress(pIfProgress, (uPercentStart + uPercentSpan) / 2);
+
+ rc = vhdCreateDynamicImage(pImage, cbSize);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Compute and update the footer checksum. */
+ Footer.DataOffset = RT_H2BE_U64(pImage->u64DataOffset);
+ Footer.Checksum = 0;
+ Footer.Checksum = RT_H2BE_U32(vhdChecksum(&Footer, sizeof(Footer)));
+
+ pImage->vhdFooterCopy = Footer;
+
+ /* Store the footer */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, pImage->uCurrentEndOfFile,
+ &Footer, sizeof(Footer));
+ if (RT_SUCCESS(rc))
+ {
+ /* Dynamic images contain a copy of the footer at the very beginning of the file. */
+ if (!(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ /* Write the copy of the footer. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, 0, &Footer, sizeof(Footer));
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VHD: cannot write a copy of footer to image '%s'"), pImage->pszFilename);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VHD: cannot write footer to image '%s'"), pImage->pszFilename);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VHD: cannot create image '%s'"), pImage->pszFilename);
+
+ if (RT_SUCCESS(rc))
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ vhdFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnProbe} */
+static DECLCALLBACK(int) vhdProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pVDIfsDisk, enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
+ PVDIOSTORAGE pStorage;
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ int rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile;
+
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile >= sizeof(VHDFooter))
+ {
+ VHDFooter vhdFooter;
+
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, cbFile - sizeof(VHDFooter),
+ &vhdFooter, sizeof(VHDFooter));
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(vhdFooter.Cookie, VHD_FOOTER_COOKIE, VHD_FOOTER_COOKIE_SIZE) != 0)
+ {
+ /*
+ * There is also a backup header at the beginning in case the image got corrupted.
+ * Such corrupted images are detected here to let the open handler repair it later.
+ */
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, &vhdFooter, sizeof(VHDFooter));
+ if ( RT_FAILURE(rc)
+ || (memcmp(vhdFooter.Cookie, VHD_FOOTER_COOKIE, VHD_FOOTER_COOKIE_SIZE) != 0))
+ rc = VERR_VD_VHD_INVALID_HEADER;
+ }
+
+ if (RT_SUCCESS(rc))
+ *penmType = VDTYPE_HDD;
+ }
+ else
+ rc = VERR_VD_VHD_INVALID_HEADER;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_VD_VHD_INVALID_HEADER;
+
+ vdIfIoIntFileClose(pIfIo, pStorage);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnOpen} */
+static DECLCALLBACK(int) vhdOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ RT_NOREF1(enmType); /**< @todo r=klaus make use of the type info. */
+
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc = VINF_SUCCESS;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ PVHDIMAGE pImage = (PVHDIMAGE)RTMemAllocZ(RT_UOFFSETOF(VHDIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vhdOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ else
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnCreate} */
+static DECLCALLBACK(int) vhdCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+ int rc;
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ /* Check the VD container type. */
+ if (enmType != VDTYPE_HDD)
+ return VERR_VD_INVALID_TYPE;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER);
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+ /** @todo Check the values of other params */
+
+ PVHDIMAGE pImage = (PVHDIMAGE)RTMemAllocZ(RT_UOFFSETOF(VHDIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ /* Get I/O interface. */
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ if (RT_LIKELY(RT_VALID_PTR(pImage->pIfIo)))
+ {
+ rc = vhdCreateImage(pImage, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags,
+ pIfProgress, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ vhdFreeImage(pImage, false);
+ rc = vhdOpenImage(pImage, uOpenFlags);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnRename} */
+static DECLCALLBACK(int) vhdRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VINF_SUCCESS;
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ /* Check arguments. */
+ AssertReturn((pImage && pszFilename && *pszFilename), VERR_INVALID_PARAMETER);
+
+ /* Close the image. */
+ rc = vhdFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ {
+ /* Rename the file. */
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pszFilename, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the old file with new name. */
+ rc = vhdOpenImage(pImage, pImage->uOpenFlags);
+ }
+ else
+ {
+ /* The move failed, try to reopen the original image. */
+ int rc2 = vhdOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnClose} */
+static DECLCALLBACK(int) vhdClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ int rc = vhdFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnRead} */
+static DECLCALLBACK(int) vhdRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pBackendData=%p uOffset=%#llx pIoCtx=%#p cbToRead=%u pcbActuallyRead=%p\n",
+ pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead));
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToRead % 512 == 0);
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToRead <= pImage->cbSize, VERR_INVALID_PARAMETER);
+
+ /*
+ * If we have a dynamic disk image, we need to find the data block and sector to read.
+ */
+ if (pImage->pBlockAllocationTable)
+ {
+ /*
+ * Get the data block first.
+ */
+ uint32_t cBlockAllocationTableEntry = (uOffset / VHD_SECTOR_SIZE) / pImage->cSectorsPerDataBlock;
+ uint32_t cBATEntryIndex = (uOffset / VHD_SECTOR_SIZE) % pImage->cSectorsPerDataBlock;
+ uint64_t uVhdOffset;
+
+ LogFlowFunc(("cBlockAllocationTableEntry=%u cBatEntryIndex=%u\n", cBlockAllocationTableEntry, cBATEntryIndex));
+ LogFlowFunc(("BlockAllocationEntry=%u\n", pImage->pBlockAllocationTable[cBlockAllocationTableEntry]));
+
+ /*
+ * Clip read range to remain in this data block.
+ */
+ cbToRead = RT_MIN(cbToRead, (pImage->cbDataBlock - (cBATEntryIndex * VHD_SECTOR_SIZE)));
+
+ /*
+ * If the block is not allocated the content of the entry is ~0
+ */
+ if (pImage->pBlockAllocationTable[cBlockAllocationTableEntry] == ~0U)
+ rc = VERR_VD_BLOCK_FREE;
+ else
+ {
+ uVhdOffset = ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry] + pImage->cDataBlockBitmapSectors + cBATEntryIndex) * VHD_SECTOR_SIZE;
+ LogFlowFunc(("uVhdOffset=%llu cbToRead=%u\n", uVhdOffset, cbToRead));
+
+ /* Read in the block's bitmap. */
+ PVDMETAXFER pMetaXfer;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage,
+ ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry]) * VHD_SECTOR_SIZE,
+ pImage->pu8Bitmap, pImage->cbDataBlockBitmap,
+ pIoCtx, &pMetaXfer, NULL, NULL);
+
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cSectors = 0;
+
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+ if (vhdBlockBitmapSectorContainsData(pImage, cBATEntryIndex))
+ {
+ cBATEntryIndex++;
+ cSectors = 1;
+
+ /*
+ * The first sector being read is marked dirty, read as much as we
+ * can from child. Note that only sectors that are marked dirty
+ * must be read from child.
+ */
+ while ( (cSectors < (cbToRead / VHD_SECTOR_SIZE))
+ && vhdBlockBitmapSectorContainsData(pImage, cBATEntryIndex))
+ {
+ cBATEntryIndex++;
+ cSectors++;
+ }
+
+ cbToRead = cSectors * VHD_SECTOR_SIZE;
+
+ LogFlowFunc(("uVhdOffset=%llu cbToRead=%u\n", uVhdOffset, cbToRead));
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage,
+ uVhdOffset, pIoCtx, cbToRead);
+ }
+ else
+ {
+ /*
+ * The first sector being read is marked clean, so we should read from
+ * our parent instead, but only as much as there are the following
+ * clean sectors, because the block may still contain dirty sectors
+ * further on. We just need to compute the number of clean sectors
+ * and pass it to our caller along with the notification that they
+ * should be read from the parent.
+ */
+ cBATEntryIndex++;
+ cSectors = 1;
+
+ while ( (cSectors < (cbToRead / VHD_SECTOR_SIZE))
+ && !vhdBlockBitmapSectorContainsData(pImage, cBATEntryIndex))
+ {
+ cBATEntryIndex++;
+ cSectors++;
+ }
+
+ cbToRead = cSectors * VHD_SECTOR_SIZE;
+ LogFunc(("Sectors free: uVhdOffset=%llu cbToRead=%u\n", uVhdOffset, cbToRead));
+ rc = VERR_VD_BLOCK_FREE;
+ }
+ }
+ else
+ AssertMsg(rc == VERR_VD_NOT_ENOUGH_METADATA, ("Reading block bitmap failed rc=%Rrc\n", rc));
+ }
+ }
+ else
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, uOffset, pIoCtx, cbToRead);
+
+ if (pcbActuallyRead)
+ *pcbActuallyRead = cbToRead;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnWrite} */
+static DECLCALLBACK(int) vhdWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pBackendData=%p uOffset=%llu pIoCtx=%#p cbToWrite=%u pcbWriteProcess=%p pcbPreRead=%p pcbPostRead=%p fWrite=%u\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead, fWrite));
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % VHD_SECTOR_SIZE));
+ Assert(!(cbToWrite % VHD_SECTOR_SIZE));
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToWrite, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToWrite <= RT_ALIGN_64(pImage->cbSize, pImage->cbDataBlock), VERR_INVALID_PARAMETER); /* The image size might not be on a data block size boundary. */
+
+ if (pImage->pBlockAllocationTable)
+ {
+ /*
+ * Get the data block first.
+ */
+ uint32_t cSector = uOffset / VHD_SECTOR_SIZE;
+ uint32_t cBlockAllocationTableEntry = cSector / pImage->cSectorsPerDataBlock;
+ uint32_t cBATEntryIndex = cSector % pImage->cSectorsPerDataBlock;
+ uint64_t uVhdOffset;
+
+ /*
+ * Clip write range.
+ */
+ cbToWrite = RT_MIN(cbToWrite, (pImage->cbDataBlock - (cBATEntryIndex * VHD_SECTOR_SIZE)));
+
+ /*
+ * If the block is not allocated the content of the entry is ~0
+ * and we need to allocate a new block. Note that while blocks are
+ * allocated with a relatively big granularity, each sector has its
+ * own bitmap entry, indicating whether it has been written or not.
+ * So that means for the purposes of the higher level that the
+ * granularity is invisible. This means there's no need to return
+ * VERR_VD_BLOCK_FREE unless the block hasn't been allocated yet.
+ */
+ if (pImage->pBlockAllocationTable[cBlockAllocationTableEntry] == ~0U)
+ {
+ /* Check if the block allocation should be suppressed. */
+ if ( (fWrite & VD_WRITE_NO_ALLOC)
+ || (cbToWrite != pImage->cbDataBlock))
+ {
+ *pcbPreRead = cBATEntryIndex * VHD_SECTOR_SIZE;
+ *pcbPostRead = pImage->cSectorsPerDataBlock * VHD_SECTOR_SIZE - cbToWrite - *pcbPreRead;
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+ return VERR_VD_BLOCK_FREE;
+ }
+
+ PVHDIMAGEEXPAND pExpand;
+ pExpand = (PVHDIMAGEEXPAND)RTMemAllocZ(RT_UOFFSETOF_DYN(VHDIMAGEEXPAND,
+ au8Bitmap[pImage->cDataBlockBitmapSectors * VHD_SECTOR_SIZE]));
+ bool fIoInProgress = false;
+
+ if (!pExpand)
+ return VERR_NO_MEMORY;
+
+ pExpand->cbEofOld = pImage->uCurrentEndOfFile;
+ pExpand->idxBatAllocated = cBlockAllocationTableEntry;
+ pExpand->idxBlockBe = RT_H2BE_U32(pImage->uCurrentEndOfFile / VHD_SECTOR_SIZE);
+
+ /* Set the bits for all sectors having been written. */
+ for (uint32_t iSector = 0; iSector < (cbToWrite / VHD_SECTOR_SIZE); iSector++)
+ {
+ /* No need to check for a changed value because this is an initial write. */
+ vhdBlockBitmapSectorSet(pImage, pExpand->au8Bitmap, cBATEntryIndex);
+ cBATEntryIndex++;
+ }
+
+ do
+ {
+ /*
+ * Start with the sector bitmap.
+ */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ pImage->uCurrentEndOfFile,
+ pExpand->au8Bitmap,
+ pImage->cDataBlockBitmapSectors * VHD_SECTOR_SIZE, pIoCtx,
+ vhdAsyncExpansionDataBlockBitmapComplete,
+ pExpand);
+ if (RT_SUCCESS(rc))
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BLOCKBITMAP_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_SUCCESS);
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ fIoInProgress = true;
+ else
+ {
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BLOCKBITMAP_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_USERBLOCK_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BAT_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+ break;
+ }
+
+
+ /*
+ * Write the new block at the current end of the file.
+ */
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ pImage->uCurrentEndOfFile + (pImage->cDataBlockBitmapSectors + (cSector % pImage->cSectorsPerDataBlock)) * VHD_SECTOR_SIZE,
+ pIoCtx, cbToWrite,
+ vhdAsyncExpansionDataComplete,
+ pExpand);
+ if (RT_SUCCESS(rc))
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_USERBLOCK_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_SUCCESS);
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ fIoInProgress = true;
+ else
+ {
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_USERBLOCK_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BAT_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+ break;
+ }
+
+ /*
+ * Write entry in the BAT.
+ */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ pImage->uBlockAllocationTableOffset + cBlockAllocationTableEntry * sizeof(uint32_t),
+ &pExpand->idxBlockBe, sizeof(uint32_t), pIoCtx,
+ vhdAsyncExpansionBatUpdateComplete,
+ pExpand);
+ if (RT_SUCCESS(rc))
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BAT_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_SUCCESS);
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ fIoInProgress = true;
+ else
+ {
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BAT_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+ break;
+ }
+
+ /*
+ * Set the new end of the file and link the new block into the BAT.
+ */
+ pImage->uCurrentEndOfFile += pImage->cDataBlockBitmapSectors * VHD_SECTOR_SIZE + pImage->cbDataBlock;
+
+ /* Update the footer. */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ pImage->uCurrentEndOfFile,
+ &pImage->vhdFooterCopy,
+ sizeof(VHDFooter), pIoCtx,
+ vhdAsyncExpansionFooterUpdateComplete,
+ pExpand);
+ if (RT_SUCCESS(rc))
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_SUCCESS);
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ fIoInProgress = true;
+ else
+ {
+ VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+ break;
+ }
+
+ } while (0);
+
+ if (!fIoInProgress)
+ vhdAsyncExpansionComplete(pImage, pIoCtx, pExpand);
+ else
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+ else
+ {
+ /*
+ * Calculate the real offset in the file.
+ */
+ uVhdOffset = ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry] + pImage->cDataBlockBitmapSectors + cBATEntryIndex) * VHD_SECTOR_SIZE;
+
+ /* Read in the block's bitmap. */
+ PVDMETAXFER pMetaXfer;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage,
+ ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry]) * VHD_SECTOR_SIZE,
+ pImage->pu8Bitmap,
+ pImage->cbDataBlockBitmap, pIoCtx,
+ &pMetaXfer, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+
+ /* Write data. */
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ uVhdOffset, pIoCtx, cbToWrite,
+ NULL, NULL);
+ if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ bool fChanged = false;
+
+ /* Set the bits for all sectors having been written. */
+ for (uint32_t iSector = 0; iSector < (cbToWrite / VHD_SECTOR_SIZE); iSector++)
+ {
+ fChanged |= vhdBlockBitmapSectorSet(pImage, pImage->pu8Bitmap, cBATEntryIndex);
+ cBATEntryIndex++;
+ }
+
+ /* Only write the bitmap if it was changed. */
+ if (fChanged)
+ {
+ /*
+ * Write the bitmap back.
+ *
+ * @note We don't have a completion callback here because we
+ * can't do anything if the write fails for some reason.
+ * The error will propagated to the device/guest
+ * by the generic VD layer already and we don't need
+ * to rollback anything here.
+ */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry]) * VHD_SECTOR_SIZE,
+ pImage->pu8Bitmap,
+ pImage->cbDataBlockBitmap,
+ pIoCtx, NULL, NULL);
+ }
+ }
+ }
+ }
+ }
+ else
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ uOffset, pIoCtx, cbToWrite, NULL, NULL);
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+
+ /* Stay on the safe side. Do not run the risk of confusing the higher
+ * level, as that can be pretty lethal to image consistency. */
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnFlush} */
+static DECLCALLBACK(int) vhdFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ /* No need to write anything here. Data is always updated on a write. */
+ int rc = vdIfIoIntFileFlush(pImage->pIfIo, pImage->pStorage, pIoCtx, NULL, NULL);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetVersion} */
+static DECLCALLBACK(unsigned) vhdGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ unsigned uVersion = 1; /**< @todo use correct version */
+
+ LogFlowFunc(("returns %u\n", uVersion));
+ return uVersion;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetFileSize} */
+static DECLCALLBACK(uint64_t) vhdGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtrReturn(pImage, 0);
+
+ if (pImage->pStorage)
+ cb = pImage->uCurrentEndOfFile + sizeof(VHDFooter);
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetPCHSGeometry} */
+static DECLCALLBACK(int) vhdGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->PCHSGeometry.cCylinders)
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (CHS=%u/%u/%u)\n", rc, pImage->PCHSGeometry.cCylinders,
+ pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetPCHSGeometry} */
+static DECLCALLBACK(int) vhdSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->PCHSGeometry = *pPCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetLCHSGeometry} */
+static DECLCALLBACK(int) vhdGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->LCHSGeometry.cCylinders)
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (CHS=%u/%u/%u)\n", rc, pImage->LCHSGeometry.cCylinders,
+ pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetLCHSGeometry} */
+static DECLCALLBACK(int) vhdSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->LCHSGeometry = *pLCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) vhdQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PVHDIMAGE pThis = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) vhdRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PVHDIMAGE pThis = (PVHDIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetImageFlags} */
+static DECLCALLBACK(unsigned) vhdGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uImageFlags));
+ return pImage->uImageFlags;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetOpenFlags} */
+static DECLCALLBACK(unsigned) vhdGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uOpenFlags));
+ return pImage->uOpenFlags;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetOpenFlags} */
+static DECLCALLBACK(int) vhdSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pImage || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ rc = vhdFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ rc = vhdOpenImage(pImage, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetComment} */
+static DECLCALLBACK(int) vhdGetComment(void *pBackendData, char *pszComment,
+ size_t cbComment)
+{
+ RT_NOREF2(pszComment, cbComment);
+ LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ LogFlowFunc(("returns %Rrc comment='%s'\n", VERR_NOT_SUPPORTED, pszComment));
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetComment} */
+static DECLCALLBACK(int) vhdSetComment(void *pBackendData, const char *pszComment)
+{
+ RT_NOREF1(pszComment);
+ LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetUuid} */
+static DECLCALLBACK(int) vhdGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = pImage->ImageUuid;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetUuid} */
+static DECLCALLBACK(int) vhdSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ pImage->ImageUuid = *pUuid;
+ /* Update the footer copy. It will get written to disk when the image is closed. */
+ memcpy(&pImage->vhdFooterCopy.UniqueID, pUuid, 16);
+ /* Update checksum. */
+ pImage->vhdFooterCopy.Checksum = 0;
+ pImage->vhdFooterCopy.Checksum = RT_H2BE_U32(vhdChecksum(&pImage->vhdFooterCopy, sizeof(VHDFooter)));
+
+ /* Need to update the dynamic disk header to update the disk footer copy at the beginning. */
+ if (!(pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ pImage->fDynHdrNeedsUpdate = true;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetModificationUuid} */
+static DECLCALLBACK(int) vhdGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VERR_NOT_SUPPORTED, pUuid));
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetModificationUuid} */
+static DECLCALLBACK(int) vhdSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetParentUuid} */
+static DECLCALLBACK(int) vhdGetParentUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = pImage->ParentUuid;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetParentUuid} */
+static DECLCALLBACK(int) vhdSetParentUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ if (pImage && pImage->pStorage)
+ {
+ if (!(pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ pImage->ParentUuid = *pUuid;
+ pImage->fDynHdrNeedsUpdate = true;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetParentModificationUuid} */
+static DECLCALLBACK(int) vhdGetParentModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VERR_NOT_SUPPORTED, pUuid));
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetParentModificationUuid} */
+static DECLCALLBACK(int) vhdSetParentModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc;
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnDump} */
+static DECLCALLBACK(void) vhdDump(void *pBackendData)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pImage);
+ vdIfErrorMessage(pImage->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cbSector=%u\n",
+ pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors,
+ pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors,
+ VHD_SECTOR_SIZE);
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidCreation={%RTuuid}\n", &pImage->ImageUuid);
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidParent={%RTuuid}\n", &pImage->ParentUuid);
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetTimestamp} */
+static DECLCALLBACK(int) vhdGetTimestamp(void *pBackendData, PRTTIMESPEC pTimestamp)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = vdIfIoIntFileGetModificationTime(pImage->pIfIo, pImage->pszFilename, pTimestamp);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetParentTimestamp} */
+static DECLCALLBACK(int) vhdGetParentTimestamp(void *pBackendData, PRTTIMESPEC pTimestamp)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ vhdTime2RtTime(pTimestamp, pImage->u32ParentTimestamp);
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetParentTimestamp} */
+static DECLCALLBACK(int) vhdSetParentTimestamp(void *pBackendData, PCRTTIMESPEC pTimestamp)
+{
+ int rc = VINF_SUCCESS;
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ {
+ pImage->u32ParentTimestamp = vhdRtTime2VhdTime(pTimestamp);
+ pImage->fDynHdrNeedsUpdate = true;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetParentFilename} */
+static DECLCALLBACK(int) vhdGetParentFilename(void *pBackendData, char **ppszParentFilename)
+{
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+ *ppszParentFilename = RTStrDup(pImage->pszParentFilename);
+
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetParentFilename} */
+static DECLCALLBACK(int) vhdSetParentFilename(void *pBackendData, const char *pszParentFilename)
+{
+ int rc = VINF_SUCCESS;
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ {
+ if (pImage->pszParentFilename)
+ RTStrFree(pImage->pszParentFilename);
+ pImage->pszParentFilename = RTStrDup(pszParentFilename);
+ if (!pImage->pszParentFilename)
+ rc = VERR_NO_MEMORY;
+ else
+ pImage->fDynHdrNeedsUpdate = true;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnCompact} */
+static DECLCALLBACK(int) vhdCompact(void *pBackendData, unsigned uPercentStart,
+ unsigned uPercentSpan, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, PVDINTERFACE pVDIfsOperation)
+{
+ RT_NOREF2(pVDIfsDisk, pVDIfsImage);
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+ void *pvBuf = NULL;
+ uint32_t *paBlocks = NULL;
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ PFNVDPARENTREAD pfnParentRead = NULL;
+ void *pvParent = NULL;
+ PVDINTERFACEPARENTSTATE pIfParentState = VDIfParentStateGet(pVDIfsOperation);
+ if (pIfParentState)
+ {
+ pfnParentRead = pIfParentState->pfnParentRead;
+ pvParent = pIfParentState->Core.pvUser;
+ }
+
+ do
+ {
+ AssertBreakStmt(pImage, rc = VERR_INVALID_PARAMETER);
+
+ AssertBreakStmt(!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY),
+ rc = VERR_VD_IMAGE_READ_ONLY);
+
+ /* Reject fixed images as they don't have a BAT. */
+ if (pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (pfnParentRead)
+ {
+ pvParent = RTMemTmpAlloc(pImage->cbDataBlock);
+ AssertBreakStmt(pvParent, rc = VERR_NO_MEMORY);
+ }
+ pvBuf = RTMemTmpAlloc(pImage->cbDataBlock);
+ AssertBreakStmt(pvBuf, rc = VERR_NO_MEMORY);
+
+ unsigned cBlocksAllocated = 0;
+ unsigned cBlocksToMove = 0;
+ unsigned cBlocks = pImage->cBlockAllocationTableEntries;
+ uint32_t offBlocksStart = ~0U; /* Start offset of data blocks in sectors. */
+ uint32_t *paBat = pImage->pBlockAllocationTable;
+
+ /* Count the number of allocated blocks and find the start offset for the data blocks. */
+ for (unsigned i = 0; i < cBlocks; i++)
+ if (paBat[i] != ~0U)
+ {
+ cBlocksAllocated++;
+ if (paBat[i] < offBlocksStart)
+ offBlocksStart = paBat[i];
+ }
+
+ if (!cBlocksAllocated)
+ {
+ /* Nothing to do. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ paBlocks = (uint32_t *)RTMemTmpAllocZ(cBlocksAllocated * sizeof(uint32_t));
+ AssertBreakStmt(paBlocks, rc = VERR_NO_MEMORY);
+
+ /* Invalidate the back resolving array. */
+ for (unsigned i = 0; i < cBlocksAllocated; i++)
+ paBlocks[i] = ~0U;
+
+ /* Fill the back resolving table. */
+ for (unsigned i = 0; i < cBlocks; i++)
+ if (paBat[i] != ~0U)
+ {
+ unsigned idxBlock = (paBat[i] - offBlocksStart) / pImage->cSectorsPerDataBlock;
+ if ( idxBlock < cBlocksAllocated
+ && paBlocks[idxBlock] == ~0U)
+ paBlocks[idxBlock] = i;
+ else
+ {
+ /* The image is in an inconsistent state. Don't go further. */
+ rc = VERR_INVALID_STATE;
+ break;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Find redundant information and update the block pointers
+ * accordingly, creating bubbles. Keep disk up to date, as this
+ * enables cancelling. */
+ for (unsigned i = 0; i < cBlocks; i++)
+ {
+ if (paBat[i] != ~0U)
+ {
+ unsigned idxBlock = (paBat[i] - offBlocksStart) / pImage->cSectorsPerDataBlock;
+
+ /* Block present in image file, read relevant data. */
+ uint64_t u64Offset = ((uint64_t)paBat[i] + pImage->cDataBlockBitmapSectors) * VHD_SECTOR_SIZE;
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ u64Offset, pvBuf, pImage->cbDataBlock);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (ASMBitFirstSet((volatile void *)pvBuf, (uint32_t)pImage->cbDataBlock * 8) == -1)
+ {
+ paBat[i] = UINT32_MAX;
+ paBlocks[idxBlock] = ~0U;
+ /* Adjust progress info, one block to be relocated. */
+ cBlocksToMove++;
+ }
+ else if (pfnParentRead)
+ {
+ rc = pfnParentRead(pvParent, (uint64_t)i * pImage->cbDataBlock, pvParent, pImage->cbDataBlock);
+ if (RT_FAILURE(rc))
+ break;
+ if (!memcmp(pvParent, pvBuf, pImage->cbDataBlock))
+ {
+ paBat[i] = ~0U;
+ paBlocks[idxBlock] = ~0U;
+ /* Adjust progress info, one block to be relocated. */
+ cBlocksToMove++;
+ }
+ }
+ }
+
+ vdIfProgress(pIfProgress, (uint64_t)i * uPercentSpan / (cBlocks + cBlocksToMove) + uPercentStart);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Fill bubbles with other data (if available). */
+ unsigned cBlocksMoved = 0;
+ unsigned uBlockUsedPos = cBlocksAllocated;
+ size_t cbBlock = pImage->cbDataBlock + pImage->cbDataBlockBitmap; /** < Size of whole block containing the bitmap and the user data. */
+
+ /* Allocate data buffer to hold the data block and allocation bitmap in front of the actual data. */
+ RTMemTmpFree(pvBuf);
+ pvBuf = RTMemTmpAllocZ(cbBlock);
+ AssertBreakStmt(pvBuf, rc = VERR_NO_MEMORY);
+
+ for (unsigned i = 0; i < cBlocksAllocated; i++)
+ {
+ unsigned uBlock = paBlocks[i];
+ if (uBlock == ~0U)
+ {
+ unsigned uBlockData = ~0U;
+ while (uBlockUsedPos > i && uBlockData == ~0U)
+ {
+ uBlockUsedPos--;
+ uBlockData = paBlocks[uBlockUsedPos];
+ }
+ /* Terminate early if there is no block which needs copying. */
+ if (uBlockUsedPos == i)
+ break;
+ uint64_t u64Offset = (uint64_t)uBlockUsedPos * cbBlock
+ + (offBlocksStart * VHD_SECTOR_SIZE);
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ u64Offset, pvBuf, cbBlock);
+ if (RT_FAILURE(rc))
+ break;
+
+ u64Offset = (uint64_t)i * cbBlock
+ + (offBlocksStart * VHD_SECTOR_SIZE);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ u64Offset, pvBuf, cbBlock);
+ if (RT_FAILURE(rc))
+ break;
+
+ paBat[uBlockData] = i*(pImage->cSectorsPerDataBlock + pImage->cDataBlockBitmapSectors) + offBlocksStart;
+
+ /* Truncate the file but leave enough room for the footer to avoid
+ * races if other processes fill the whole harddisk. */
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage,
+ pImage->uCurrentEndOfFile - cbBlock + VHD_SECTOR_SIZE);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Update pointers and write footer. */
+ pImage->uCurrentEndOfFile -= cbBlock;
+
+ /* We're kinda screwed if this failes. */
+ rc = vhdUpdateFooter(pImage);
+ if (RT_FAILURE(rc))
+ break;
+
+ paBlocks[i] = uBlockData;
+ paBlocks[uBlockUsedPos] = ~0U;
+ cBlocksMoved++;
+ }
+
+ rc = vdIfProgress(pIfProgress, (uint64_t)(cBlocks + cBlocksMoved) * uPercentSpan / (cBlocks + cBlocksToMove) + uPercentStart);
+ }
+ }
+
+ /* Write the new BAT in any case. */
+ rc = vhdFlushImage(pImage);
+ } while (0);
+
+ if (paBlocks)
+ RTMemTmpFree(paBlocks);
+ if (pvParent)
+ RTMemTmpFree(pvParent);
+ if (pvBuf)
+ RTMemTmpFree(pvBuf);
+
+ if (RT_SUCCESS(rc))
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnResize} */
+static DECLCALLBACK(int) vhdResize(void *pBackendData, uint64_t cbSize,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation)
+{
+ RT_NOREF5(uPercentSpan, uPercentStart, pVDIfsDisk, pVDIfsImage, pVDIfsOperation);
+ PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Making the image smaller is not supported at the moment. */
+ if (cbSize < pImage->cbSize)
+ rc = VERR_VD_SHRINK_NOT_SUPPORTED;
+ else if (pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ rc = VERR_NOT_SUPPORTED;
+ else if (cbSize > pImage->cbSize)
+ {
+ unsigned cBlocksAllocated = 0;
+ size_t cbBlock = pImage->cbDataBlock + pImage->cbDataBlockBitmap; /** < Size of a block including the sector bitmap. */
+ uint32_t cBlocksNew = cbSize / pImage->cbDataBlock; /** < New number of blocks in the image after the resize */
+ if (cbSize % pImage->cbDataBlock)
+ cBlocksNew++;
+
+ uint32_t cBlocksOld = pImage->cBlockAllocationTableEntries; /** < Number of blocks before the resize. */
+ uint64_t cbBlockspaceNew = RT_ALIGN_32(cBlocksNew * sizeof(uint32_t), VHD_SECTOR_SIZE); /** < Required space for the block array after the resize. */
+ uint64_t offStartDataNew = RT_ALIGN_32(pImage->uBlockAllocationTableOffset + cbBlockspaceNew, VHD_SECTOR_SIZE); /** < New start offset for block data after the resize */
+ uint64_t offStartDataOld = ~0ULL;
+
+ /* Go through the BAT and find the data start offset. */
+ for (unsigned idxBlock = 0; idxBlock < pImage->cBlockAllocationTableEntries; idxBlock++)
+ {
+ if (pImage->pBlockAllocationTable[idxBlock] != ~0U)
+ {
+ uint64_t offStartBlock = (uint64_t)pImage->pBlockAllocationTable[idxBlock] * VHD_SECTOR_SIZE;
+ if (offStartBlock < offStartDataOld)
+ offStartDataOld = offStartBlock;
+ cBlocksAllocated++;
+ }
+ }
+
+ if ( offStartDataOld != offStartDataNew
+ && cBlocksAllocated > 0)
+ {
+ /* Calculate how many sectors nee to be relocated. */
+ uint64_t cbOverlapping = offStartDataNew - offStartDataOld;
+ unsigned cBlocksReloc = (unsigned)(cbOverlapping / cbBlock);
+ if (cbOverlapping % cbBlock)
+ cBlocksReloc++;
+
+ cBlocksReloc = RT_MIN(cBlocksReloc, cBlocksAllocated);
+ offStartDataNew = offStartDataOld;
+
+ /* Do the relocation. */
+ LogFlow(("Relocating %u blocks\n", cBlocksReloc));
+
+ /*
+ * Get the blocks we need to relocate first, they are appended to the end
+ * of the image.
+ */
+ void *pvBuf = NULL, *pvZero = NULL;
+ do
+ {
+ /* Allocate data buffer. */
+ pvBuf = RTMemAllocZ(cbBlock);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Allocate buffer for overwriting with zeroes. */
+ pvZero = RTMemAllocZ(cbBlock);
+ if (!pvZero)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ for (unsigned i = 0; i < cBlocksReloc; i++)
+ {
+ uint32_t uBlock = offStartDataNew / VHD_SECTOR_SIZE;
+
+ /* Search the index in the block table. */
+ for (unsigned idxBlock = 0; idxBlock < cBlocksOld; idxBlock++)
+ {
+ if (pImage->pBlockAllocationTable[idxBlock] == uBlock)
+ {
+ /* Read data and append to the end of the image. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ offStartDataNew, pvBuf, cbBlock);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->uCurrentEndOfFile, pvBuf, cbBlock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Zero out the old block area. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ offStartDataNew, pvZero, cbBlock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Update block counter. */
+ pImage->pBlockAllocationTable[idxBlock] = pImage->uCurrentEndOfFile / VHD_SECTOR_SIZE;
+
+ pImage->uCurrentEndOfFile += cbBlock;
+
+ /* Continue with the next block. */
+ break;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ offStartDataNew += cbBlock;
+ }
+ } while (0);
+
+ if (pvBuf)
+ RTMemFree(pvBuf);
+ if (pvZero)
+ RTMemFree(pvZero);
+ }
+
+ /*
+ * Relocation done, expand the block array and update the header with
+ * the new data.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t *paBlocksNew = (uint32_t *)RTMemRealloc(pImage->pBlockAllocationTable, cBlocksNew * sizeof(uint32_t));
+ if (paBlocksNew)
+ {
+ pImage->pBlockAllocationTable = paBlocksNew;
+
+ /* Mark the new blocks as unallocated. */
+ for (unsigned idxBlock = cBlocksOld; idxBlock < cBlocksNew; idxBlock++)
+ pImage->pBlockAllocationTable[idxBlock] = ~0U;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Write the block array before updating the rest. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->uBlockAllocationTableOffset,
+ pImage->pBlockAllocationTable,
+ cBlocksNew * sizeof(uint32_t));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Update size and new block count. */
+ pImage->cBlockAllocationTableEntries = cBlocksNew;
+ pImage->cbSize = cbSize;
+
+ /* Update geometry. */
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ }
+ }
+
+ /* Update header information in base image file. */
+ pImage->fDynHdrNeedsUpdate = true;
+ vhdFlushImage(pImage);
+ }
+ /* Same size doesn't change the image at all. */
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnRepair} */
+static DECLCALLBACK(int) vhdRepair(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, uint32_t fFlags)
+{
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
+ int rc;
+ PVDINTERFACEERROR pIfError;
+ PVDINTERFACEIOINT pIfIo;
+ PVDIOSTORAGE pStorage = NULL;
+ uint64_t cbFile;
+ VHDFooter vhdFooter;
+ VHDDynamicDiskHeader dynamicDiskHeader;
+ uint32_t *paBat = NULL;
+ uint32_t *pu32BlockBitmap = NULL;
+
+ pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ pIfError = VDIfErrorGet(pVDIfsDisk);
+
+ do
+ {
+ uint64_t offDynamicDiskHeader = 0;
+ uint64_t offBat = 0;
+ uint64_t offFooter = 0;
+ uint32_t cBatEntries = 0;
+ bool fDynamic = false;
+ bool fRepairFooter = false;
+ bool fRepairBat = false;
+ bool fRepairDynHeader = false;
+
+ rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags( fFlags & VD_REPAIR_DRY_RUN
+ ? VD_OPEN_FLAGS_READONLY
+ : 0,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, "Failed to open image \"%s\"", pszFilename);
+ break;
+ }
+
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, "Failed to query image size");
+ break;
+ }
+
+ if (cbFile < sizeof(VHDFooter))
+ {
+ rc = vdIfError(pIfError, VERR_VD_INVALID_SIZE, RT_SRC_POS,
+ "Image must be at least %u bytes (got %llu)",
+ sizeof(VHDFooter), cbFile);
+ break;
+ }
+
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, cbFile - sizeof(VHDFooter),
+ &vhdFooter, sizeof(VHDFooter));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, "Failed to read footer of image");
+ break;
+ }
+
+ if (memcmp(vhdFooter.Cookie, VHD_FOOTER_COOKIE, VHD_FOOTER_COOKIE_SIZE) != 0)
+ {
+ /* Dynamic images have a backup at the beginning of the image. */
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0,
+ &vhdFooter, sizeof(VHDFooter));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, "Failed to read header of image");
+ break;
+ }
+
+ /*
+ * Check for the header, if this fails the image is either completely corrupted
+ * and impossible to repair or in another format.
+ */
+ if (memcmp(vhdFooter.Cookie, VHD_FOOTER_COOKIE, VHD_FOOTER_COOKIE_SIZE) != 0)
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "No valid VHD structures found");
+ break;
+ }
+ else
+ vdIfErrorMessage(pIfError, "Missing footer structure, using backup\n");
+
+ /* Remember to fix the footer structure. */
+ fRepairFooter = true;
+ }
+
+ offFooter = cbFile - sizeof(VHDFooter);
+
+ /* Verify that checksums match. */
+ uint32_t u32ChkSumOld = RT_BE2H_U32(vhdFooter.Checksum);
+ vhdFooter.Checksum = 0;
+ uint32_t u32ChkSum = vhdChecksum(&vhdFooter, sizeof(VHDFooter));
+
+ vhdFooter.Checksum = RT_H2BE_U32(u32ChkSum);
+
+ if (u32ChkSumOld != u32ChkSum)
+ {
+ vdIfErrorMessage(pIfError, "Checksum is invalid (should be %u got %u), repairing\n",
+ u32ChkSum, u32ChkSumOld);
+ fRepairFooter = true;
+ break;
+ }
+
+ switch (RT_BE2H_U32(vhdFooter.DiskType))
+ {
+ case VHD_FOOTER_DISK_TYPE_FIXED:
+ fDynamic = false;
+ break;
+ case VHD_FOOTER_DISK_TYPE_DYNAMIC:
+ fDynamic = true;
+ break;
+ case VHD_FOOTER_DISK_TYPE_DIFFERENCING:
+ fDynamic = true;
+ break;
+ default:
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "VHD image type %u is not supported",
+ RT_BE2H_U32(vhdFooter.DiskType));
+ break;
+ }
+ }
+
+ /* Load and check dynamic disk header if required. */
+ if (fDynamic)
+ {
+ size_t cbBlock;
+
+ offDynamicDiskHeader = RT_BE2H_U64(vhdFooter.DataOffset);
+ if (offDynamicDiskHeader + sizeof(VHDDynamicDiskHeader) > cbFile)
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "VHD image type is not supported");
+ break;
+ }
+
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, offDynamicDiskHeader,
+ &dynamicDiskHeader, sizeof(VHDDynamicDiskHeader));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Failed to read dynamic disk header (at %llu), %Rrc",
+ offDynamicDiskHeader, rc);
+ break;
+ }
+
+ /* Verify that checksums match. */
+ u32ChkSumOld = RT_BE2H_U32(dynamicDiskHeader.Checksum);
+ dynamicDiskHeader.Checksum = 0;
+ u32ChkSum = vhdChecksum(&dynamicDiskHeader, sizeof(VHDDynamicDiskHeader));
+
+ dynamicDiskHeader.Checksum = RT_H2BE_U32(u32ChkSum);
+
+ if (u32ChkSumOld != u32ChkSum)
+ {
+ vdIfErrorMessage(pIfError, "Checksum of dynamic disk header is invalid (should be %u got %u), repairing\n",
+ u32ChkSum, u32ChkSumOld);
+ fRepairDynHeader = true;
+ break;
+ }
+
+ /* Read the block allocation table and fix any inconsistencies. */
+ offBat = RT_BE2H_U64(dynamicDiskHeader.TableOffset);
+ cBatEntries = RT_BE2H_U32(dynamicDiskHeader.MaxTableEntries);
+ cbBlock = RT_BE2H_U32(dynamicDiskHeader.BlockSize);
+ cbBlock += cbBlock / VHD_SECTOR_SIZE / 8;
+
+ if (offBat + cBatEntries * sizeof(uint32_t) > cbFile)
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Block allocation table is not inside the image");
+ break;
+ }
+
+ paBat = (uint32_t *)RTMemAllocZ(cBatEntries * sizeof(uint32_t));
+ if (!paBat)
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Could not allocate memory for the block allocation table (%u bytes)",
+ cBatEntries * sizeof(uint32_t));
+ break;
+ }
+
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, offBat, paBat,
+ cBatEntries * sizeof(uint32_t));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Could not read block allocation table (at %llu), %Rrc",
+ offBat, rc);
+ break;
+ }
+
+ pu32BlockBitmap = (uint32_t *)RTMemAllocZ(RT_ALIGN_Z(cBatEntries / 8, 4));
+ if (!pu32BlockBitmap)
+ {
+ rc = vdIfError(pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ "Failed to allocate memory for block bitmap");
+ break;
+ }
+
+ uint32_t idxMinBlock = UINT32_C(0xffffffff);
+ for (uint32_t i = 0; i < cBatEntries; i++)
+ {
+ paBat[i] = RT_BE2H_U32(paBat[i]);
+ if (paBat[i] < idxMinBlock)
+ idxMinBlock = paBat[i];
+ }
+
+ vdIfErrorMessage(pIfError, "First data block at sector %u\n", idxMinBlock);
+
+ for (uint32_t i = 0; i < cBatEntries; i++)
+ {
+ if (paBat[i] != UINT32_C(0xffffffff))
+ {
+ uint64_t offBlock =(uint64_t)paBat[i] * VHD_SECTOR_SIZE;
+
+ /*
+ * Check that the offsets are valid (inside of the image) and
+ * that there are no double references.
+ */
+ if (offBlock + cbBlock > cbFile)
+ {
+ vdIfErrorMessage(pIfError, "Entry %u points to invalid offset %llu, clearing\n",
+ i, offBlock);
+ paBat[i] = UINT32_C(0xffffffff);
+ fRepairBat = true;
+ }
+ else if (offBlock + cbBlock > offFooter)
+ {
+ vdIfErrorMessage(pIfError, "Entry %u intersects with footer, aligning footer\n",
+ i);
+ offFooter = offBlock + cbBlock;
+ fRepairBat = true;
+ }
+
+ if ( paBat[i] != UINT32_C(0xffffffff)
+ && ASMBitTestAndSet(pu32BlockBitmap, (uint32_t)((paBat[i] - idxMinBlock) / (cbBlock / VHD_SECTOR_SIZE))))
+ {
+ vdIfErrorMessage(pIfError, "Entry %u points to an already referenced data block, clearing\n",
+ i);
+ paBat[i] = UINT32_C(0xffffffff);
+ fRepairBat = true;
+ }
+ }
+ }
+ }
+
+ /* Write repaired structures now. */
+ if (!(fRepairBat || fRepairDynHeader || fRepairFooter))
+ vdIfErrorMessage(pIfError, "VHD image is in a consistent state, no repair required\n");
+ else if (!(fFlags & VD_REPAIR_DRY_RUN))
+ {
+ if (fRepairBat)
+ {
+ for (uint32_t i = 0; i < cBatEntries; i++)
+ paBat[i] = RT_H2BE_U32(paBat[i]);
+
+ vdIfErrorMessage(pIfError, "Writing repaired block allocation table...\n");
+
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, offBat, paBat,
+ cBatEntries * sizeof(uint32_t));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Could not write repaired block allocation table (at %llu), %Rrc",
+ offBat, rc);
+ break;
+ }
+ }
+
+ if (fRepairDynHeader)
+ {
+ Assert(fDynamic);
+
+ vdIfErrorMessage(pIfError, "Writing repaired dynamic disk header...\n");
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, offDynamicDiskHeader, &dynamicDiskHeader,
+ sizeof(VHDDynamicDiskHeader));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Could not write repaired dynamic disk header (at %llu), %Rrc",
+ offDynamicDiskHeader, rc);
+ break;
+ }
+ }
+
+ if (fRepairFooter)
+ {
+ vdIfErrorMessage(pIfError, "Writing repaired Footer...\n");
+
+ if (fDynamic)
+ {
+ /* Write backup at image beginning. */
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, 0, &vhdFooter,
+ sizeof(VHDFooter));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Could not write repaired backup footer (at %llu), %Rrc",
+ 0, rc);
+ break;
+ }
+ }
+
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, offFooter, &vhdFooter,
+ sizeof(VHDFooter));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Could not write repaired footer (at %llu), %Rrc",
+ cbFile - sizeof(VHDFooter), rc);
+ break;
+ }
+ }
+
+ vdIfErrorMessage(pIfError, "Corrupted VHD image repaired successfully\n");
+ }
+ } while(0);
+
+ if (paBat)
+ RTMemFree(paBat);
+
+ if (pu32BlockBitmap)
+ RTMemFree(pu32BlockBitmap);
+
+ if (pStorage)
+ {
+ int rc2 = vdIfIoIntFileClose(pIfIo, pStorage);
+ if (RT_SUCCESS(rc))
+ rc = rc2; /* Propagate status code only when repairing the image was successful. */
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+const VDIMAGEBACKEND g_VhdBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "VHD",
+ /* uBackendCaps */
+ VD_CAP_UUID | VD_CAP_DIFF | VD_CAP_FILE |
+ VD_CAP_CREATE_FIXED | VD_CAP_CREATE_DYNAMIC |
+ VD_CAP_ASYNC | VD_CAP_VFS | VD_CAP_PREFERRED,
+ /* paFileExtensions */
+ s_aVhdFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ vhdProbe,
+ /* pfnOpen */
+ vhdOpen,
+ /* pfnCreate */
+ vhdCreate,
+ /* pfnRename */
+ vhdRename,
+ /* pfnClose */
+ vhdClose,
+ /* pfnRead */
+ vhdRead,
+ /* pfnWrite */
+ vhdWrite,
+ /* pfnFlush */
+ vhdFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ vhdGetVersion,
+ /* pfnGetFileSize */
+ vhdGetFileSize,
+ /* pfnGetPCHSGeometry */
+ vhdGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ vhdSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ vhdGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ vhdSetLCHSGeometry,
+ /* pfnQueryRegions */
+ vhdQueryRegions,
+ /* pfnRegionListRelease */
+ vhdRegionListRelease,
+ /* pfnGetImageFlags */
+ vhdGetImageFlags,
+ /* pfnGetOpenFlags */
+ vhdGetOpenFlags,
+ /* pfnSetOpenFlags */
+ vhdSetOpenFlags,
+ /* pfnGetComment */
+ vhdGetComment,
+ /* pfnSetComment */
+ vhdSetComment,
+ /* pfnGetUuid */
+ vhdGetUuid,
+ /* pfnSetUuid */
+ vhdSetUuid,
+ /* pfnGetModificationUuid */
+ vhdGetModificationUuid,
+ /* pfnSetModificationUuid */
+ vhdSetModificationUuid,
+ /* pfnGetParentUuid */
+ vhdGetParentUuid,
+ /* pfnSetParentUuid */
+ vhdSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ vhdGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ vhdSetParentModificationUuid,
+ /* pfnDump */
+ vhdDump,
+ /* pfnGetTimestamp */
+ vhdGetTimestamp,
+ /* pfnGetParentTimestamp */
+ vhdGetParentTimestamp,
+ /* pfnSetParentTimestamp */
+ vhdSetParentTimestamp,
+ /* pfnGetParentFilename */
+ vhdGetParentFilename,
+ /* pfnSetParentFilename */
+ vhdSetParentFilename,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ vhdCompact,
+ /* pfnResize */
+ vhdResize,
+ /* pfnRepair */
+ vhdRepair,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
diff --git a/src/VBox/Storage/VHDX.cpp b/src/VBox/Storage/VHDX.cpp
new file mode 100644
index 00000000..ecccf6ff
--- /dev/null
+++ b/src/VBox/Storage/VHDX.cpp
@@ -0,0 +1,2423 @@
+/* $Id: VHDX.cpp $ */
+/** @file
+ * VHDX - VHDX Disk image, Core Code.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_VHDX
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/path.h>
+#include <iprt/uuid.h>
+#include <iprt/crc.h>
+
+#include "VDBackends.h"
+#include "VDBackendsInline.h"
+
+
+/*********************************************************************************************************************************
+* On disk data structures *
+*********************************************************************************************************************************/
+
+/**
+ * VHDX file type identifier.
+ */
+#pragma pack(1)
+typedef struct VhdxFileIdentifier
+{
+ /** Signature. */
+ uint64_t u64Signature;
+ /** Creator ID - UTF-16 string (not neccessarily null terminated). */
+ uint16_t awszCreator[256];
+} VhdxFileIdentifier;
+#pragma pack()
+/** Pointer to an on disk VHDX file type identifier. */
+typedef VhdxFileIdentifier *PVhdxFileIdentifier;
+
+/** VHDX file type identifier signature ("vhdxfile"). */
+#define VHDX_FILE_IDENTIFIER_SIGNATURE UINT64_C(0x656c696678646876)
+/** Start offset of the VHDX file type identifier. */
+#define VHDX_FILE_IDENTIFIER_OFFSET UINT64_C(0)
+
+/**
+ * VHDX header.
+ */
+#pragma pack(1)
+typedef struct VhdxHeader
+{
+ /** Signature. */
+ uint32_t u32Signature;
+ /** Checksum. */
+ uint32_t u32Checksum;
+ /** Sequence number. */
+ uint64_t u64SequenceNumber;
+ /** File write UUID. */
+ RTUUID UuidFileWrite;
+ /** Data write UUID. */
+ RTUUID UuidDataWrite;
+ /** Log UUID. */
+ RTUUID UuidLog;
+ /** Version of the log format. */
+ uint16_t u16LogVersion;
+ /** VHDX format version. */
+ uint16_t u16Version;
+ /** Length of the log region. */
+ uint32_t u32LogLength;
+ /** Start offset of the log offset in the file. */
+ uint64_t u64LogOffset;
+ /** Reserved bytes. */
+ uint8_t u8Reserved[4016];
+} VhdxHeader;
+#pragma pack()
+/** Pointer to an on disk VHDX header. */
+typedef VhdxHeader *PVhdxHeader;
+
+/** VHDX header signature ("head"). */
+#define VHDX_HEADER_SIGNATURE UINT32_C(0x64616568)
+/** Start offset of the first VHDX header. */
+#define VHDX_HEADER1_OFFSET _64K
+/** Start offset of the second VHDX header. */
+#define VHDX_HEADER2_OFFSET _128K
+/** Current Log format version. */
+#define VHDX_HEADER_LOG_VERSION UINT16_C(0)
+/** Current VHDX format version. */
+#define VHDX_HEADER_VHDX_VERSION UINT16_C(1)
+
+/**
+ * VHDX region table header
+ */
+#pragma pack(1)
+typedef struct VhdxRegionTblHdr
+{
+ /** Signature. */
+ uint32_t u32Signature;
+ /** Checksum. */
+ uint32_t u32Checksum;
+ /** Number of region table entries following this header. */
+ uint32_t u32EntryCount;
+ /** Reserved. */
+ uint32_t u32Reserved;
+} VhdxRegionTblHdr;
+#pragma pack()
+/** Pointer to an on disk VHDX region table header. */
+typedef VhdxRegionTblHdr *PVhdxRegionTblHdr;
+
+/** VHDX region table header signature. */
+#define VHDX_REGION_TBL_HDR_SIGNATURE UINT32_C(0x69676572)
+/** Maximum number of entries which can follow. */
+#define VHDX_REGION_TBL_HDR_ENTRY_COUNT_MAX UINT32_C(2047)
+/** Offset where the region table is stored (192 KB). */
+#define VHDX_REGION_TBL_HDR_OFFSET UINT64_C(196608)
+/** Maximum size of the region table. */
+#define VHDX_REGION_TBL_SIZE_MAX _64K
+
+/**
+ * VHDX region table entry.
+ */
+#pragma pack(1)
+typedef struct VhdxRegionTblEntry
+{
+ /** Object UUID. */
+ RTUUID UuidObject;
+ /** File offset of the region. */
+ uint64_t u64FileOffset;
+ /** Length of the region in bytes. */
+ uint32_t u32Length;
+ /** Flags for this object. */
+ uint32_t u32Flags;
+} VhdxRegionTblEntry;
+#pragma pack()
+/** Pointer to an on disk VHDX region table entry. */
+typedef struct VhdxRegionTblEntry *PVhdxRegionTblEntry;
+
+/** Flag whether this region is required. */
+#define VHDX_REGION_TBL_ENTRY_FLAGS_IS_REQUIRED RT_BIT_32(0)
+/** UUID for the BAT region. */
+#define VHDX_REGION_TBL_ENTRY_UUID_BAT "2dc27766-f623-4200-9d64-115e9bfd4a08"
+/** UUID for the metadata region. */
+#define VHDX_REGION_TBL_ENTRY_UUID_METADATA "8b7ca206-4790-4b9a-b8fe-575f050f886e"
+
+/**
+ * VHDX Log entry header.
+ */
+#pragma pack(1)
+typedef struct VhdxLogEntryHdr
+{
+ /** Signature. */
+ uint32_t u32Signature;
+ /** Checksum. */
+ uint32_t u32Checksum;
+ /** Total length of the entry in bytes. */
+ uint32_t u32EntryLength;
+ /** Tail of the log entries. */
+ uint32_t u32Tail;
+ /** Sequence number. */
+ uint64_t u64SequenceNumber;
+ /** Number of descriptors in this log entry. */
+ uint32_t u32DescriptorCount;
+ /** Reserved. */
+ uint32_t u32Reserved;
+ /** Log UUID. */
+ RTUUID UuidLog;
+ /** VHDX file size in bytes while the log entry was written. */
+ uint64_t u64FlushedFileOffset;
+ /** File size in bytes all allocated file structures fit into when the
+ * log entry was written. */
+ uint64_t u64LastFileOffset;
+} VhdxLogEntryHdr;
+#pragma pack()
+/** Pointer to an on disk VHDX log entry header. */
+typedef struct VhdxLogEntryHdr *PVhdxLogEntryHdr;
+
+/** VHDX log entry signature ("loge"). */
+#define VHDX_LOG_ENTRY_HEADER_SIGNATURE UINT32_C(0x65676f6c)
+
+/**
+ * VHDX log zero descriptor.
+ */
+#pragma pack(1)
+typedef struct VhdxLogZeroDesc
+{
+ /** Signature of this descriptor. */
+ uint32_t u32ZeroSignature;
+ /** Reserved. */
+ uint32_t u32Reserved;
+ /** Length of the section to zero. */
+ uint64_t u64ZeroLength;
+ /** File offset to write zeros to. */
+ uint64_t u64FileOffset;
+ /** Sequence number (must macht the field in the log entry header). */
+ uint64_t u64SequenceNumber;
+} VhdxLogZeroDesc;
+#pragma pack()
+/** Pointer to an on disk VHDX log zero descriptor. */
+typedef struct VhdxLogZeroDesc *PVhdxLogZeroDesc;
+
+/** Signature of a VHDX log zero descriptor ("zero"). */
+#define VHDX_LOG_ZERO_DESC_SIGNATURE UINT32_C(0x6f72657a)
+
+/**
+ * VHDX log data descriptor.
+ */
+#pragma pack(1)
+typedef struct VhdxLogDataDesc
+{
+ /** Signature of this descriptor. */
+ uint32_t u32DataSignature;
+ /** Trailing 4 bytes removed from the update. */
+ uint32_t u32TrailingBytes;
+ /** Leading 8 bytes removed from the update. */
+ uint64_t u64LeadingBytes;
+ /** File offset to write zeros to. */
+ uint64_t u64FileOffset;
+ /** Sequence number (must macht the field in the log entry header). */
+ uint64_t u64SequenceNumber;
+} VhdxLogDataDesc;
+#pragma pack()
+/** Pointer to an on disk VHDX log data descriptor. */
+typedef struct VhdxLogDataDesc *PVhdxLogDataDesc;
+
+/** Signature of a VHDX log data descriptor ("desc"). */
+#define VHDX_LOG_DATA_DESC_SIGNATURE UINT32_C(0x63736564)
+
+/**
+ * VHDX log data sector.
+ */
+#pragma pack(1)
+typedef struct VhdxLogDataSector
+{
+ /** Signature of the data sector. */
+ uint32_t u32DataSignature;
+ /** 4 most significant bytes of the sequence number. */
+ uint32_t u32SequenceHigh;
+ /** Raw data associated with the update. */
+ uint8_t u8Data[4084];
+ /** 4 least significant bytes of the sequence number. */
+ uint32_t u32SequenceLow;
+} VhdxLogDataSector;
+#pragma pack()
+/** Pointer to an on disk VHDX log data sector. */
+typedef VhdxLogDataSector *PVhdxLogDataSector;
+
+/** Signature of a VHDX log data sector ("data"). */
+#define VHDX_LOG_DATA_SECTOR_SIGNATURE UINT32_C(0x61746164)
+
+/**
+ * VHDX BAT entry.
+ */
+#pragma pack(1)
+typedef struct VhdxBatEntry
+{
+ /** The BAT entry, contains state and offset. */
+ uint64_t u64BatEntry;
+} VhdxBatEntry;
+#pragma pack()
+typedef VhdxBatEntry *PVhdxBatEntry;
+
+/** Return the BAT state from a given entry. */
+#define VHDX_BAT_ENTRY_GET_STATE(bat) ((bat) & UINT64_C(0x7))
+/** Get the FileOffsetMB field from a given BAT entry. */
+#define VHDX_BAT_ENTRY_GET_FILE_OFFSET_MB(bat) (((bat) & UINT64_C(0xfffffffffff00000)) >> 20)
+/** Get a byte offset from the BAT entry. */
+#define VHDX_BAT_ENTRY_GET_FILE_OFFSET(bat) (VHDX_BAT_ENTRY_GET_FILE_OFFSET_MB(bat) * (uint64_t)_1M)
+
+/** Block not present and the data is undefined. */
+#define VHDX_BAT_ENTRY_PAYLOAD_BLOCK_NOT_PRESENT (0)
+/** Data in this block is undefined. */
+#define VHDX_BAT_ENTRY_PAYLOAD_BLOCK_UNDEFINED (1)
+/** Data in this block contains zeros. */
+#define VHDX_BAT_ENTRY_PAYLOAD_BLOCK_ZERO (2)
+/** Block was unmapped by the application or system and data is either zero or
+ * the data before the block was unmapped. */
+#define VHDX_BAT_ENTRY_PAYLOAD_BLOCK_UNMAPPED (3)
+/** Block data is in the file pointed to by the FileOffsetMB field. */
+#define VHDX_BAT_ENTRY_PAYLOAD_BLOCK_FULLY_PRESENT (6)
+/** Block is partially present, use sector bitmap to get present sectors. */
+#define VHDX_BAT_ENTRY_PAYLOAD_BLOCK_PARTIALLY_PRESENT (7)
+
+/** The sector bitmap block is undefined and not allocated in the file. */
+#define VHDX_BAT_ENTRY_SB_BLOCK_NOT_PRESENT (0)
+/** The sector bitmap block is defined at the file location. */
+#define VHDX_BAT_ENTRY_SB_BLOCK_PRESENT (6)
+
+/**
+ * VHDX Metadata tabl header.
+ */
+#pragma pack(1)
+typedef struct VhdxMetadataTblHdr
+{
+ /** Signature. */
+ uint64_t u64Signature;
+ /** Reserved. */
+ uint16_t u16Reserved;
+ /** Number of entries in the table. */
+ uint16_t u16EntryCount;
+ /** Reserved */
+ uint32_t u32Reserved2[5];
+} VhdxMetadataTblHdr;
+#pragma pack()
+/** Pointer to an on disk metadata table header. */
+typedef VhdxMetadataTblHdr *PVhdxMetadataTblHdr;
+
+/** Signature of a VHDX metadata table header ("metadata"). */
+#define VHDX_METADATA_TBL_HDR_SIGNATURE UINT64_C(0x617461646174656d)
+/** Maximum number of entries the metadata table can have. */
+#define VHDX_METADATA_TBL_HDR_ENTRY_COUNT_MAX UINT16_C(2047)
+
+/**
+ * VHDX Metadata table entry.
+ */
+#pragma pack(1)
+typedef struct VhdxMetadataTblEntry
+{
+ /** Item UUID. */
+ RTUUID UuidItem;
+ /** Offset of the metadata item. */
+ uint32_t u32Offset;
+ /** Length of the metadata item. */
+ uint32_t u32Length;
+ /** Flags for the metadata item. */
+ uint32_t u32Flags;
+ /** Reserved. */
+ uint32_t u32Reserved;
+} VhdxMetadataTblEntry;
+#pragma pack()
+/** Pointer to an on disk metadata table entry. */
+typedef VhdxMetadataTblEntry *PVhdxMetadataTblEntry;
+
+/** FLag whether the metadata item is system or user metadata. */
+#define VHDX_METADATA_TBL_ENTRY_FLAGS_IS_USER RT_BIT_32(0)
+/** FLag whether the metadata item is file or virtual disk metadata. */
+#define VHDX_METADATA_TBL_ENTRY_FLAGS_IS_VDISK RT_BIT_32(1)
+/** FLag whether the backend must understand the metadata item to load the image. */
+#define VHDX_METADATA_TBL_ENTRY_FLAGS_IS_REQUIRED RT_BIT_32(2)
+
+/** File parameters item UUID. */
+#define VHDX_METADATA_TBL_ENTRY_ITEM_FILE_PARAMS "caa16737-fa36-4d43-b3b6-33f0aa44e76b"
+/** Virtual disk size item UUID. */
+#define VHDX_METADATA_TBL_ENTRY_ITEM_VDISK_SIZE "2fa54224-cd1b-4876-b211-5dbed83bf4b8"
+/** Page 83 UUID. */
+#define VHDX_METADATA_TBL_ENTRY_ITEM_PAGE83_DATA "beca12ab-b2e6-4523-93ef-c309e000c746"
+/** Logical sector size UUID. */
+#define VHDX_METADATA_TBL_ENTRY_ITEM_LOG_SECT_SIZE "8141bf1d-a96f-4709-ba47-f233a8faab5f"
+/** Physical sector size UUID. */
+#define VHDX_METADATA_TBL_ENTRY_ITEM_PHYS_SECT_SIZE "cda348c7-445d-4471-9cc9-e9885251c556"
+/** Parent locator UUID. */
+#define VHDX_METADATA_TBL_ENTRY_ITEM_PARENT_LOCATOR "a8d35f2d-b30b-454d-abf7-d3d84834ab0c"
+
+/**
+ * VHDX File parameters metadata item.
+ */
+#pragma pack(1)
+typedef struct VhdxFileParameters
+{
+ /** Block size. */
+ uint32_t u32BlockSize;
+ /** Flags. */
+ uint32_t u32Flags;
+} VhdxFileParameters;
+#pragma pack()
+/** Pointer to an on disk VHDX file parameters metadata item. */
+typedef struct VhdxFileParameters *PVhdxFileParameters;
+
+/** Flag whether to leave blocks allocated in the file or if it is possible to unmap them. */
+#define VHDX_FILE_PARAMETERS_FLAGS_LEAVE_BLOCKS_ALLOCATED RT_BIT_32(0)
+/** Flag whether this file has a parent VHDX file. */
+#define VHDX_FILE_PARAMETERS_FLAGS_HAS_PARENT RT_BIT_32(1)
+
+/**
+ * VHDX virtual disk size metadata item.
+ */
+#pragma pack(1)
+typedef struct VhdxVDiskSize
+{
+ /** Virtual disk size. */
+ uint64_t u64VDiskSize;
+} VhdxVDiskSize;
+#pragma pack()
+/** Pointer to an on disk VHDX virtual disk size metadata item. */
+typedef struct VhdxVDiskSize *PVhdxVDiskSize;
+
+/**
+ * VHDX page 83 data metadata item.
+ */
+#pragma pack(1)
+typedef struct VhdxPage83Data
+{
+ /** UUID for the SCSI device. */
+ RTUUID UuidPage83Data;
+} VhdxPage83Data;
+#pragma pack()
+/** Pointer to an on disk VHDX vpage 83 data metadata item. */
+typedef struct VhdxPage83Data *PVhdxPage83Data;
+
+/**
+ * VHDX virtual disk logical sector size.
+ */
+#pragma pack(1)
+typedef struct VhdxVDiskLogicalSectorSize
+{
+ /** Logical sector size. */
+ uint32_t u32LogicalSectorSize;
+} VhdxVDiskLogicalSectorSize;
+#pragma pack()
+/** Pointer to an on disk VHDX virtual disk logical sector size metadata item. */
+typedef struct VhdxVDiskLogicalSectorSize *PVhdxVDiskLogicalSectorSize;
+
+/**
+ * VHDX virtual disk physical sector size.
+ */
+#pragma pack(1)
+typedef struct VhdxVDiskPhysicalSectorSize
+{
+ /** Physical sector size. */
+ uint64_t u64PhysicalSectorSize;
+} VhdxVDiskPhysicalSectorSize;
+#pragma pack()
+/** Pointer to an on disk VHDX virtual disk physical sector size metadata item. */
+typedef struct VhdxVDiskPhysicalSectorSize *PVhdxVDiskPhysicalSectorSize;
+
+/**
+ * VHDX parent locator header.
+ */
+#pragma pack(1)
+typedef struct VhdxParentLocatorHeader
+{
+ /** Locator type UUID. */
+ RTUUID UuidLocatorType;
+ /** Reserved. */
+ uint16_t u16Reserved;
+ /** Number of key value pairs. */
+ uint16_t u16KeyValueCount;
+} VhdxParentLocatorHeader;
+#pragma pack()
+/** Pointer to an on disk VHDX parent locator header metadata item. */
+typedef struct VhdxParentLocatorHeader *PVhdxParentLocatorHeader;
+
+/** VHDX parent locator type. */
+#define VHDX_PARENT_LOCATOR_TYPE_VHDX "b04aefb7-d19e-4a81-b789-25b8e9445913"
+
+/**
+ * VHDX parent locator entry.
+ */
+#pragma pack(1)
+typedef struct VhdxParentLocatorEntry
+{
+ /** Offset of the key. */
+ uint32_t u32KeyOffset;
+ /** Offset of the value. */
+ uint32_t u32ValueOffset;
+ /** Length of the key. */
+ uint16_t u16KeyLength;
+ /** Length of the value. */
+ uint16_t u16ValueLength;
+} VhdxParentLocatorEntry;
+#pragma pack()
+/** Pointer to an on disk VHDX parent locator entry. */
+typedef struct VhdxParentLocatorEntry *PVhdxParentLocatorEntry;
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+typedef enum VHDXMETADATAITEM
+{
+ VHDXMETADATAITEM_UNKNOWN = 0,
+ VHDXMETADATAITEM_FILE_PARAMS,
+ VHDXMETADATAITEM_VDISK_SIZE,
+ VHDXMETADATAITEM_PAGE83_DATA,
+ VHDXMETADATAITEM_LOGICAL_SECTOR_SIZE,
+ VHDXMETADATAITEM_PHYSICAL_SECTOR_SIZE,
+ VHDXMETADATAITEM_PARENT_LOCATOR,
+ VHDXMETADATAITEM_32BIT_HACK = 0x7fffffff
+} VHDXMETADATAITEM;
+
+/**
+ * Table to validate the metadata item UUIDs and the flags.
+ */
+typedef struct VHDXMETADATAITEMPROPS
+{
+ /** Item UUID. */
+ const char *pszItemUuid;
+ /** Flag whether this is a user or system metadata item. */
+ bool fIsUser;
+ /** Flag whether this is a virtual disk or file metadata item. */
+ bool fIsVDisk;
+ /** Flag whether this metadata item is required to load the file. */
+ bool fIsRequired;
+ /** Metadata item enum associated with this UUID. */
+ VHDXMETADATAITEM enmMetadataItem;
+} VHDXMETADATAITEMPROPS;
+
+/**
+ * VHDX image data structure.
+ */
+typedef struct VHDXIMAGE
+{
+ /** Image name. */
+ const char *pszFilename;
+ /** Storage handle. */
+ PVDIOSTORAGE pStorage;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+
+ /** Open flags passed by VBoxHD layer. */
+ unsigned uOpenFlags;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Version of the VHDX image format. */
+ unsigned uVersion;
+ /** Total size of the image. */
+ uint64_t cbSize;
+ /** Logical sector size of the image. */
+ uint32_t cbLogicalSector;
+ /** Block size of the image. */
+ size_t cbBlock;
+ /** Physical geometry of this image. */
+ VDGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ VDGEOMETRY LCHSGeometry;
+
+ /** The BAT. */
+ PVhdxBatEntry paBat;
+ /** Chunk ratio. */
+ uint32_t uChunkRatio;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} VHDXIMAGE, *PVHDXIMAGE;
+
+/**
+ * Endianess conversion direction.
+ */
+typedef enum VHDXECONV
+{
+ /** Host to file endianess. */
+ VHDXECONV_H2F = 0,
+ /** File to host endianess. */
+ VHDXECONV_F2H
+} VHDXECONV;
+
+/** Macros for endianess conversion. */
+#define SET_ENDIAN_U16(u16) (enmConv == VHDXECONV_H2F ? RT_H2LE_U16(u16) : RT_LE2H_U16(u16))
+#define SET_ENDIAN_U32(u32) (enmConv == VHDXECONV_H2F ? RT_H2LE_U32(u32) : RT_LE2H_U32(u32))
+#define SET_ENDIAN_U64(u64) (enmConv == VHDXECONV_H2F ? RT_H2LE_U64(u64) : RT_LE2H_U64(u64))
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/**
+ * NULL-terminated array of supported file extensions.
+ */
+static const VDFILEEXTENSION s_aVhdxFileExtensions[] =
+{
+ {"vhdx", VDTYPE_HDD},
+ {NULL, VDTYPE_INVALID}
+};
+
+/**
+ * Static table to verify the metadata item properties and the flags.
+ */
+static const VHDXMETADATAITEMPROPS s_aVhdxMetadataItemProps[] =
+{
+ /* pcszItemUuid fIsUser, fIsVDisk, fIsRequired, enmMetadataItem */
+ {VHDX_METADATA_TBL_ENTRY_ITEM_FILE_PARAMS, false, false, true, VHDXMETADATAITEM_FILE_PARAMS},
+ {VHDX_METADATA_TBL_ENTRY_ITEM_VDISK_SIZE, false, true, true, VHDXMETADATAITEM_VDISK_SIZE},
+ {VHDX_METADATA_TBL_ENTRY_ITEM_PAGE83_DATA, false, true, true, VHDXMETADATAITEM_PAGE83_DATA},
+ {VHDX_METADATA_TBL_ENTRY_ITEM_LOG_SECT_SIZE, false, true, true, VHDXMETADATAITEM_LOGICAL_SECTOR_SIZE},
+ {VHDX_METADATA_TBL_ENTRY_ITEM_PHYS_SECT_SIZE, false, true, true, VHDXMETADATAITEM_PHYSICAL_SECTOR_SIZE},
+ {VHDX_METADATA_TBL_ENTRY_ITEM_PARENT_LOCATOR, false, false, true, VHDXMETADATAITEM_PARENT_LOCATOR}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Converts the file identifier between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pFileIdentifierConv Where to store the converted file identifier.
+ * @param pFileIdentifier The file identifier to convert.
+ *
+ * @note It is safe to use the same pointer for pFileIdentifierConv and pFileIdentifier.
+ */
+DECLINLINE(void) vhdxConvFileIdentifierEndianess(VHDXECONV enmConv, PVhdxFileIdentifier pFileIdentifierConv,
+ PVhdxFileIdentifier pFileIdentifier)
+{
+ pFileIdentifierConv->u64Signature = SET_ENDIAN_U64(pFileIdentifier->u64Signature);
+ for (unsigned i = 0; i < RT_ELEMENTS(pFileIdentifierConv->awszCreator); i++)
+ pFileIdentifierConv->awszCreator[i] = SET_ENDIAN_U16(pFileIdentifier->awszCreator[i]);
+}
+
+/**
+ * Converts a UUID between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pUuidConv Where to store the converted UUID.
+ * @param pUuid The UUID to convert.
+ *
+ * @note It is safe to use the same pointer for pUuidConv and pUuid.
+ */
+DECLINLINE(void) vhdxConvUuidEndianess(VHDXECONV enmConv, PRTUUID pUuidConv, PRTUUID pUuid)
+{
+ RT_NOREF1(enmConv);
+#if 1
+ /** @todo r=andy Code looks temporary disabled to me, fixes strict release builds:
+ * "accessing 16 bytes at offsets 0 and 0 overlaps 16 bytes at offset 0 [-Werror=restrict]" */
+ RTUUID uuidTmp;
+ memcpy(&uuidTmp, pUuid, sizeof(RTUUID));
+ memcpy(pUuidConv, &uuidTmp, sizeof(RTUUID));
+#else
+ pUuidConv->Gen.u32TimeLow = SET_ENDIAN_U32(pUuid->Gen.u32TimeLow);
+ pUuidConv->Gen.u16TimeMid = SET_ENDIAN_U16(pUuid->Gen.u16TimeMid);
+ pUuidConv->Gen.u16TimeHiAndVersion = SET_ENDIAN_U16(pUuid->Gen.u16TimeHiAndVersion);
+ pUuidConv->Gen.u8ClockSeqHiAndReserved = pUuid->Gen.u8ClockSeqHiAndReserved;
+ pUuidConv->Gen.u8ClockSeqLow = pUuid->Gen.u8ClockSeqLow;
+ for (unsigned i = 0; i < RT_ELEMENTS(pUuidConv->Gen.au8Node); i++)
+ pUuidConv->Gen.au8Node[i] = pUuid->Gen.au8Node[i];
+#endif
+}
+
+/**
+ * Converts a VHDX header between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pHdrConv Where to store the converted header.
+ * @param pHdr The VHDX header to convert.
+ *
+ * @note It is safe to use the same pointer for pHdrConv and pHdr.
+ */
+DECLINLINE(void) vhdxConvHeaderEndianess(VHDXECONV enmConv, PVhdxHeader pHdrConv, PVhdxHeader pHdr)
+{
+ pHdrConv->u32Signature = SET_ENDIAN_U32(pHdr->u32Signature);
+ pHdrConv->u32Checksum = SET_ENDIAN_U32(pHdr->u32Checksum);
+ pHdrConv->u64SequenceNumber = SET_ENDIAN_U64(pHdr->u64SequenceNumber);
+ vhdxConvUuidEndianess(enmConv, &pHdrConv->UuidFileWrite, &pHdrConv->UuidFileWrite);
+ vhdxConvUuidEndianess(enmConv, &pHdrConv->UuidDataWrite, &pHdrConv->UuidDataWrite);
+ vhdxConvUuidEndianess(enmConv, &pHdrConv->UuidLog, &pHdrConv->UuidLog);
+ pHdrConv->u16LogVersion = SET_ENDIAN_U16(pHdr->u16LogVersion);
+ pHdrConv->u16Version = SET_ENDIAN_U16(pHdr->u16Version);
+ pHdrConv->u32LogLength = SET_ENDIAN_U32(pHdr->u32LogLength);
+ pHdrConv->u64LogOffset = SET_ENDIAN_U64(pHdr->u64LogOffset);
+}
+
+/**
+ * Converts a VHDX region table header between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pRegTblHdrConv Where to store the converted header.
+ * @param pRegTblHdr The VHDX region table header to convert.
+ *
+ * @note It is safe to use the same pointer for pRegTblHdrConv and pRegTblHdr.
+ */
+DECLINLINE(void) vhdxConvRegionTblHdrEndianess(VHDXECONV enmConv, PVhdxRegionTblHdr pRegTblHdrConv,
+ PVhdxRegionTblHdr pRegTblHdr)
+{
+ pRegTblHdrConv->u32Signature = SET_ENDIAN_U32(pRegTblHdr->u32Signature);
+ pRegTblHdrConv->u32Checksum = SET_ENDIAN_U32(pRegTblHdr->u32Checksum);
+ pRegTblHdrConv->u32EntryCount = SET_ENDIAN_U32(pRegTblHdr->u32EntryCount);
+ pRegTblHdrConv->u32Reserved = SET_ENDIAN_U32(pRegTblHdr->u32Reserved);
+}
+
+/**
+ * Converts a VHDX region table entry between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pRegTblEntConv Where to store the converted region table entry.
+ * @param pRegTblEnt The VHDX region table entry to convert.
+ *
+ * @note It is safe to use the same pointer for pRegTblEntConv and pRegTblEnt.
+ */
+DECLINLINE(void) vhdxConvRegionTblEntryEndianess(VHDXECONV enmConv, PVhdxRegionTblEntry pRegTblEntConv,
+ PVhdxRegionTblEntry pRegTblEnt)
+{
+ vhdxConvUuidEndianess(enmConv, &pRegTblEntConv->UuidObject, &pRegTblEnt->UuidObject);
+ pRegTblEntConv->u64FileOffset = SET_ENDIAN_U64(pRegTblEnt->u64FileOffset);
+ pRegTblEntConv->u32Length = SET_ENDIAN_U32(pRegTblEnt->u32Length);
+ pRegTblEntConv->u32Flags = SET_ENDIAN_U32(pRegTblEnt->u32Flags);
+}
+
+#if 0 /* unused */
+
+/**
+ * Converts a VHDX log entry header between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pLogEntryHdrConv Where to store the converted log entry header.
+ * @param pLogEntryHdr The VHDX log entry header to convert.
+ *
+ * @note It is safe to use the same pointer for pLogEntryHdrConv and pLogEntryHdr.
+ */
+DECLINLINE(void) vhdxConvLogEntryHdrEndianess(VHDXECONV enmConv, PVhdxLogEntryHdr pLogEntryHdrConv,
+ PVhdxLogEntryHdr pLogEntryHdr)
+{
+ pLogEntryHdrConv->u32Signature = SET_ENDIAN_U32(pLogEntryHdr->u32Signature);
+ pLogEntryHdrConv->u32Checksum = SET_ENDIAN_U32(pLogEntryHdr->u32Checksum);
+ pLogEntryHdrConv->u32EntryLength = SET_ENDIAN_U32(pLogEntryHdr->u32EntryLength);
+ pLogEntryHdrConv->u32Tail = SET_ENDIAN_U32(pLogEntryHdr->u32Tail);
+ pLogEntryHdrConv->u64SequenceNumber = SET_ENDIAN_U64(pLogEntryHdr->u64SequenceNumber);
+ pLogEntryHdrConv->u32DescriptorCount = SET_ENDIAN_U32(pLogEntryHdr->u32DescriptorCount);
+ pLogEntryHdrConv->u32Reserved = SET_ENDIAN_U32(pLogEntryHdr->u32Reserved);
+ vhdxConvUuidEndianess(enmConv, &pLogEntryHdrConv->UuidLog, &pLogEntryHdr->UuidLog);
+ pLogEntryHdrConv->u64FlushedFileOffset = SET_ENDIAN_U64(pLogEntryHdr->u64FlushedFileOffset);
+}
+
+/**
+ * Converts a VHDX log zero descriptor between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pLogZeroDescConv Where to store the converted log zero descriptor.
+ * @param pLogZeroDesc The VHDX log zero descriptor to convert.
+ *
+ * @note It is safe to use the same pointer for pLogZeroDescConv and pLogZeroDesc.
+ */
+DECLINLINE(void) vhdxConvLogZeroDescEndianess(VHDXECONV enmConv, PVhdxLogZeroDesc pLogZeroDescConv,
+ PVhdxLogZeroDesc pLogZeroDesc)
+{
+ pLogZeroDescConv->u32ZeroSignature = SET_ENDIAN_U32(pLogZeroDesc->u32ZeroSignature);
+ pLogZeroDescConv->u32Reserved = SET_ENDIAN_U32(pLogZeroDesc->u32Reserved);
+ pLogZeroDescConv->u64ZeroLength = SET_ENDIAN_U64(pLogZeroDesc->u64ZeroLength);
+ pLogZeroDescConv->u64FileOffset = SET_ENDIAN_U64(pLogZeroDesc->u64FileOffset);
+ pLogZeroDescConv->u64SequenceNumber = SET_ENDIAN_U64(pLogZeroDesc->u64SequenceNumber);
+}
+
+
+/**
+ * Converts a VHDX log data descriptor between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pLogDataDescConv Where to store the converted log data descriptor.
+ * @param pLogDataDesc The VHDX log data descriptor to convert.
+ *
+ * @note It is safe to use the same pointer for pLogDataDescConv and pLogDataDesc.
+ */
+DECLINLINE(void) vhdxConvLogDataDescEndianess(VHDXECONV enmConv, PVhdxLogDataDesc pLogDataDescConv,
+ PVhdxLogDataDesc pLogDataDesc)
+{
+ pLogDataDescConv->u32DataSignature = SET_ENDIAN_U32(pLogDataDesc->u32DataSignature);
+ pLogDataDescConv->u32TrailingBytes = SET_ENDIAN_U32(pLogDataDesc->u32TrailingBytes);
+ pLogDataDescConv->u64LeadingBytes = SET_ENDIAN_U64(pLogDataDesc->u64LeadingBytes);
+ pLogDataDescConv->u64FileOffset = SET_ENDIAN_U64(pLogDataDesc->u64FileOffset);
+ pLogDataDescConv->u64SequenceNumber = SET_ENDIAN_U64(pLogDataDesc->u64SequenceNumber);
+}
+
+
+/**
+ * Converts a VHDX log data sector between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pLogDataSectorConv Where to store the converted log data sector.
+ * @param pLogDataSector The VHDX log data sector to convert.
+ *
+ * @note It is safe to use the same pointer for pLogDataSectorConv and pLogDataSector.
+ */
+DECLINLINE(void) vhdxConvLogDataSectorEndianess(VHDXECONV enmConv, PVhdxLogDataSector pLogDataSectorConv,
+ PVhdxLogDataSector pLogDataSector)
+{
+ pLogDataSectorConv->u32DataSignature = SET_ENDIAN_U32(pLogDataSector->u32DataSignature);
+ pLogDataSectorConv->u32SequenceHigh = SET_ENDIAN_U32(pLogDataSector->u32SequenceHigh);
+ pLogDataSectorConv->u32SequenceLow = SET_ENDIAN_U32(pLogDataSector->u32SequenceLow);
+}
+
+#endif /* unused */
+
+/**
+ * Converts a BAT between file and host endianess.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param paBatEntriesConv Where to store the converted BAT.
+ * @param paBatEntries The VHDX BAT to convert.
+ * @param cBatEntries Number of entries in the BAT.
+ *
+ * @note It is safe to use the same pointer for paBatEntriesConv and paBatEntries.
+ */
+DECLINLINE(void) vhdxConvBatTableEndianess(VHDXECONV enmConv, PVhdxBatEntry paBatEntriesConv,
+ PVhdxBatEntry paBatEntries, uint32_t cBatEntries)
+{
+ for (uint32_t i = 0; i < cBatEntries; i++)
+ paBatEntriesConv[i].u64BatEntry = SET_ENDIAN_U64(paBatEntries[i].u64BatEntry);
+}
+
+/**
+ * Converts a VHDX metadata table header between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pMetadataTblHdrConv Where to store the converted metadata table header.
+ * @param pMetadataTblHdr The VHDX metadata table header to convert.
+ *
+ * @note It is safe to use the same pointer for pMetadataTblHdrConv and pMetadataTblHdr.
+ */
+DECLINLINE(void) vhdxConvMetadataTblHdrEndianess(VHDXECONV enmConv, PVhdxMetadataTblHdr pMetadataTblHdrConv,
+ PVhdxMetadataTblHdr pMetadataTblHdr)
+{
+ pMetadataTblHdrConv->u64Signature = SET_ENDIAN_U64(pMetadataTblHdr->u64Signature);
+ pMetadataTblHdrConv->u16Reserved = SET_ENDIAN_U16(pMetadataTblHdr->u16Reserved);
+ pMetadataTblHdrConv->u16EntryCount = SET_ENDIAN_U16(pMetadataTblHdr->u16EntryCount);
+ for (unsigned i = 0; i < RT_ELEMENTS(pMetadataTblHdr->u32Reserved2); i++)
+ pMetadataTblHdrConv->u32Reserved2[i] = SET_ENDIAN_U32(pMetadataTblHdr->u32Reserved2[i]);
+}
+
+/**
+ * Converts a VHDX metadata table entry between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pMetadataTblEntryConv Where to store the converted metadata table entry.
+ * @param pMetadataTblEntry The VHDX metadata table entry to convert.
+ *
+ * @note It is safe to use the same pointer for pMetadataTblEntryConv and pMetadataTblEntry.
+ */
+DECLINLINE(void) vhdxConvMetadataTblEntryEndianess(VHDXECONV enmConv, PVhdxMetadataTblEntry pMetadataTblEntryConv,
+ PVhdxMetadataTblEntry pMetadataTblEntry)
+{
+ vhdxConvUuidEndianess(enmConv, &pMetadataTblEntryConv->UuidItem, &pMetadataTblEntry->UuidItem);
+ pMetadataTblEntryConv->u32Offset = SET_ENDIAN_U32(pMetadataTblEntry->u32Offset);
+ pMetadataTblEntryConv->u32Length = SET_ENDIAN_U32(pMetadataTblEntry->u32Length);
+ pMetadataTblEntryConv->u32Flags = SET_ENDIAN_U32(pMetadataTblEntry->u32Flags);
+ pMetadataTblEntryConv->u32Reserved = SET_ENDIAN_U32(pMetadataTblEntry->u32Reserved);
+}
+
+/**
+ * Converts a VHDX file parameters item between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pFileParamsConv Where to store the converted file parameters item entry.
+ * @param pFileParams The VHDX file parameters item to convert.
+ *
+ * @note It is safe to use the same pointer for pFileParamsConv and pFileParams.
+ */
+DECLINLINE(void) vhdxConvFileParamsEndianess(VHDXECONV enmConv, PVhdxFileParameters pFileParamsConv,
+ PVhdxFileParameters pFileParams)
+{
+ pFileParamsConv->u32BlockSize = SET_ENDIAN_U32(pFileParams->u32BlockSize);
+ pFileParamsConv->u32Flags = SET_ENDIAN_U32(pFileParams->u32Flags);
+}
+
+/**
+ * Converts a VHDX virtual disk size item between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pVDiskSizeConv Where to store the converted virtual disk size item entry.
+ * @param pVDiskSize The VHDX virtual disk size item to convert.
+ *
+ * @note It is safe to use the same pointer for pVDiskSizeConv and pVDiskSize.
+ */
+DECLINLINE(void) vhdxConvVDiskSizeEndianess(VHDXECONV enmConv, PVhdxVDiskSize pVDiskSizeConv,
+ PVhdxVDiskSize pVDiskSize)
+{
+ pVDiskSizeConv->u64VDiskSize = SET_ENDIAN_U64(pVDiskSize->u64VDiskSize);
+}
+
+#if 0 /* unused */
+
+/**
+ * Converts a VHDX page 83 data item between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pPage83DataConv Where to store the converted page 83 data item entry.
+ * @param pPage83Data The VHDX page 83 data item to convert.
+ *
+ * @note It is safe to use the same pointer for pPage83DataConv and pPage83Data.
+ */
+DECLINLINE(void) vhdxConvPage83DataEndianess(VHDXECONV enmConv, PVhdxPage83Data pPage83DataConv,
+ PVhdxPage83Data pPage83Data)
+{
+ vhdxConvUuidEndianess(enmConv, &pPage83DataConv->UuidPage83Data, &pPage83Data->UuidPage83Data);
+}
+#endif /* unused */
+
+/**
+ * Converts a VHDX logical sector size item between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pVDiskLogSectSizeConv Where to store the converted logical sector size item entry.
+ * @param pVDiskLogSectSize The VHDX logical sector size item to convert.
+ *
+ * @note It is safe to use the same pointer for pVDiskLogSectSizeConv and pVDiskLogSectSize.
+ */
+DECLINLINE(void) vhdxConvVDiskLogSectSizeEndianess(VHDXECONV enmConv, PVhdxVDiskLogicalSectorSize pVDiskLogSectSizeConv,
+ PVhdxVDiskLogicalSectorSize pVDiskLogSectSize)
+{
+ pVDiskLogSectSizeConv->u32LogicalSectorSize = SET_ENDIAN_U32(pVDiskLogSectSize->u32LogicalSectorSize);
+}
+
+#if 0 /* unused */
+
+/**
+ * Converts a VHDX physical sector size item between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pVDiskPhysSectSizeConv Where to store the converted physical sector size item entry.
+ * @param pVDiskPhysSectSize The VHDX physical sector size item to convert.
+ *
+ * @note It is safe to use the same pointer for pVDiskPhysSectSizeConv and pVDiskPhysSectSize.
+ */
+DECLINLINE(void) vhdxConvVDiskPhysSectSizeEndianess(VHDXECONV enmConv, PVhdxVDiskPhysicalSectorSize pVDiskPhysSectSizeConv,
+ PVhdxVDiskPhysicalSectorSize pVDiskPhysSectSize)
+{
+ pVDiskPhysSectSizeConv->u64PhysicalSectorSize = SET_ENDIAN_U64(pVDiskPhysSectSize->u64PhysicalSectorSize);
+}
+
+
+/**
+ * Converts a VHDX parent locator header item between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pParentLocatorHdrConv Where to store the converted parent locator header item entry.
+ * @param pParentLocatorHdr The VHDX parent locator header item to convert.
+ *
+ * @note It is safe to use the same pointer for pParentLocatorHdrConv and pParentLocatorHdr.
+ */
+DECLINLINE(void) vhdxConvParentLocatorHeaderEndianness(VHDXECONV enmConv, PVhdxParentLocatorHeader pParentLocatorHdrConv,
+ PVhdxParentLocatorHeader pParentLocatorHdr)
+{
+ vhdxConvUuidEndianess(enmConv, &pParentLocatorHdrConv->UuidLocatorType, &pParentLocatorHdr->UuidLocatorType);
+ pParentLocatorHdrConv->u16Reserved = SET_ENDIAN_U16(pParentLocatorHdr->u16Reserved);
+ pParentLocatorHdrConv->u16KeyValueCount = SET_ENDIAN_U16(pParentLocatorHdr->u16KeyValueCount);
+}
+
+
+/**
+ * Converts a VHDX parent locator entry between file and host endianness.
+ *
+ * @returns nothing.
+ * @param enmConv Direction of the conversion.
+ * @param pParentLocatorEntryConv Where to store the converted parent locator entry.
+ * @param pParentLocatorEntry The VHDX parent locator entry to convert.
+ *
+ * @note It is safe to use the same pointer for pParentLocatorEntryConv and pParentLocatorEntry.
+ */
+DECLINLINE(void) vhdxConvParentLocatorEntryEndianess(VHDXECONV enmConv, PVhdxParentLocatorEntry pParentLocatorEntryConv,
+ PVhdxParentLocatorEntry pParentLocatorEntry)
+{
+ pParentLocatorEntryConv->u32KeyOffset = SET_ENDIAN_U32(pParentLocatorEntry->u32KeyOffset);
+ pParentLocatorEntryConv->u32ValueOffset = SET_ENDIAN_U32(pParentLocatorEntry->u32ValueOffset);
+ pParentLocatorEntryConv->u16KeyLength = SET_ENDIAN_U16(pParentLocatorEntry->u16KeyLength);
+ pParentLocatorEntryConv->u16ValueLength = SET_ENDIAN_U16(pParentLocatorEntry->u16ValueLength);
+}
+
+#endif /* unused */
+
+/**
+ * Internal. Free all allocated space for representing an image except pImage,
+ * and optionally delete the image from disk.
+ */
+static int vhdxFreeImage(PVHDXIMAGE pImage, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ if (pImage->pStorage)
+ {
+ rc = vdIfIoIntFileClose(pImage->pIfIo, pImage->pStorage);
+ pImage->pStorage = NULL;
+ }
+
+ if (pImage->paBat)
+ {
+ RTMemFree(pImage->paBat);
+ pImage->paBat = NULL;
+ }
+
+ if (fDelete && pImage->pszFilename)
+ vdIfIoIntFileDelete(pImage->pIfIo, pImage->pszFilename);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Loads all required fields from the given VHDX header.
+ * The header must be converted to the host endianess and validated already.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param pHdr The header to load.
+ */
+static int vhdxLoadHeader(PVHDXIMAGE pImage, PVhdxHeader pHdr)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p pHdr=%#p\n", pImage, pHdr));
+
+ /*
+ * Most fields in the header are not required because the backend implements
+ * readonly access only so far.
+ * We just have to check that the log is empty, we have to refuse to load the
+ * image otherwsie because replaying the log is not implemented.
+ */
+ if (pHdr->u16Version == VHDX_HEADER_VHDX_VERSION)
+ {
+ /* Check that the log UUID is zero. */
+ pImage->uVersion = pHdr->u16Version;
+ if (!RTUuidIsNull(&pHdr->UuidLog))
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ "VHDX: Image \'%s\' has a non empty log which is not supported",
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ "VHDX: Image \'%s\' uses an unsupported version (%u) of the VHDX format",
+ pImage->pszFilename, pHdr->u16Version);
+
+ LogFlowFunc(("return rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Determines the current header and loads it.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ */
+static int vhdxFindAndLoadCurrentHeader(PVHDXIMAGE pImage)
+{
+ PVhdxHeader pHdr1, pHdr2;
+ uint32_t u32ChkSum = 0;
+ uint32_t u32ChkSumSaved = 0;
+ bool fHdr1Valid = false;
+ bool fHdr2Valid = false;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p\n", pImage));
+
+ /*
+ * The VHDX format defines two headers at different offsets to provide failure
+ * consistency. Only one header is current. This can be determined using the
+ * sequence number and checksum fields in the header.
+ */
+ pHdr1 = (PVhdxHeader)RTMemAllocZ(sizeof(VhdxHeader));
+ pHdr2 = (PVhdxHeader)RTMemAllocZ(sizeof(VhdxHeader));
+
+ if (pHdr1 && pHdr2)
+ {
+ /* Read the first header. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, VHDX_HEADER1_OFFSET,
+ pHdr1, sizeof(*pHdr1));
+ if (RT_SUCCESS(rc))
+ {
+ vhdxConvHeaderEndianess(VHDXECONV_F2H, pHdr1, pHdr1);
+
+ /* Validate checksum. */
+ u32ChkSumSaved = pHdr1->u32Checksum;
+ pHdr1->u32Checksum = 0;
+ u32ChkSum = RTCrc32C(pHdr1, sizeof(VhdxHeader));
+
+ if ( pHdr1->u32Signature == VHDX_HEADER_SIGNATURE
+ && u32ChkSum == u32ChkSumSaved)
+ fHdr1Valid = true;
+ }
+
+ /* Try to read the second header in any case (even if reading the first failed). */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, VHDX_HEADER2_OFFSET,
+ pHdr2, sizeof(*pHdr2));
+ if (RT_SUCCESS(rc))
+ {
+ vhdxConvHeaderEndianess(VHDXECONV_F2H, pHdr2, pHdr2);
+
+ /* Validate checksum. */
+ u32ChkSumSaved = pHdr2->u32Checksum;
+ pHdr2->u32Checksum = 0;
+ u32ChkSum = RTCrc32C(pHdr2, sizeof(VhdxHeader));
+
+ if ( pHdr2->u32Signature == VHDX_HEADER_SIGNATURE
+ && u32ChkSum == u32ChkSumSaved)
+ fHdr2Valid = true;
+ }
+
+ /* Determine the current header. */
+ if (fHdr1Valid != fHdr2Valid)
+ {
+ /* Only one header is valid - use it. */
+ rc = vhdxLoadHeader(pImage, fHdr1Valid ? pHdr1 : pHdr2);
+ }
+ else if (!fHdr1Valid && !fHdr2Valid)
+ {
+ /* Crap, both headers are corrupt, refuse to load the image. */
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Can not load the image because both headers are corrupt");
+ }
+ else
+ {
+ /* Both headers are valid. Use the sequence number to find the current one. */
+ if (pHdr1->u64SequenceNumber > pHdr2->u64SequenceNumber)
+ rc = vhdxLoadHeader(pImage, pHdr1);
+ else
+ rc = vhdxLoadHeader(pImage, pHdr2);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ "VHDX: Out of memory while allocating memory for the header");
+
+ if (pHdr1)
+ RTMemFree(pHdr1);
+ if (pHdr2)
+ RTMemFree(pHdr2);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Loads the BAT region.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param offRegion Start offset of the region.
+ * @param cbRegion Size of the region.
+ */
+static int vhdxLoadBatRegion(PVHDXIMAGE pImage, uint64_t offRegion,
+ size_t cbRegion)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cDataBlocks;
+ uint32_t uChunkRatio;
+ uint32_t cSectorBitmapBlocks;
+ uint32_t cBatEntries;
+ uint32_t cbBatEntries;
+ PVhdxBatEntry paBatEntries = NULL;
+
+ LogFlowFunc(("pImage=%#p\n", pImage));
+
+ /* Calculate required values first. */
+ uint64_t uChunkRatio64 = (RT_BIT_64(23) * pImage->cbLogicalSector) / pImage->cbBlock;
+ uChunkRatio = (uint32_t)uChunkRatio64; Assert(uChunkRatio == uChunkRatio64);
+ uint64_t cDataBlocks64 = pImage->cbSize / pImage->cbBlock;
+ cDataBlocks = (uint32_t)cDataBlocks64; Assert(cDataBlocks == cDataBlocks64);
+
+ if (pImage->cbSize % pImage->cbBlock)
+ cDataBlocks++;
+
+ cSectorBitmapBlocks = cDataBlocks / uChunkRatio;
+ if (cDataBlocks % uChunkRatio)
+ cSectorBitmapBlocks++;
+
+ cBatEntries = cDataBlocks + (cDataBlocks - 1)/uChunkRatio;
+ cbBatEntries = cBatEntries * sizeof(VhdxBatEntry);
+
+ if (cbBatEntries <= cbRegion)
+ {
+ /*
+ * Load the complete BAT region first, convert to host endianess and process
+ * it afterwards. The SB entries can be removed because they are not needed yet.
+ */
+ paBatEntries = (PVhdxBatEntry)RTMemAlloc(cbBatEntries);
+ if (paBatEntries)
+ {
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, offRegion,
+ paBatEntries, cbBatEntries);
+ if (RT_SUCCESS(rc))
+ {
+ vhdxConvBatTableEndianess(VHDXECONV_F2H, paBatEntries, paBatEntries,
+ cBatEntries);
+
+ /* Go through the table and validate it. */
+ for (unsigned i = 0; i < cBatEntries; i++)
+ {
+ if ( i != 0
+ && (i % uChunkRatio) == 0)
+ {
+/**
+ * Disabled the verification because there are images out there with the sector bitmap
+ * marked as present. The entry is never accessed and the image is readonly anyway,
+ * so no harm done.
+ */
+#if 0
+ /* Sector bitmap block. */
+ if ( VHDX_BAT_ENTRY_GET_STATE(paBatEntries[i].u64BatEntry)
+ != VHDX_BAT_ENTRY_SB_BLOCK_NOT_PRESENT)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Sector bitmap block at entry %u of image \'%s\' marked as present, violation of the specification",
+ i, pImage->pszFilename);
+ break;
+ }
+#endif
+ }
+ else
+ {
+ /* Payload block. */
+ if ( VHDX_BAT_ENTRY_GET_STATE(paBatEntries[i].u64BatEntry)
+ == VHDX_BAT_ENTRY_PAYLOAD_BLOCK_PARTIALLY_PRESENT)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Payload block at entry %u of image \'%s\' marked as partially present, violation of the specification",
+ i, pImage->pszFilename);
+ break;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pImage->paBat = paBatEntries;
+ pImage->uChunkRatio = uChunkRatio;
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ "VHDX: Error reading the BAT from image \'%s\'",
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ "VHDX: Out of memory allocating memory for %u BAT entries of image \'%s\'",
+ cBatEntries, pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Mismatch between calculated number of BAT entries and region size (expected %u got %u) for image \'%s\'",
+ cbBatEntries, cbRegion, pImage->pszFilename);
+
+ if ( RT_FAILURE(rc)
+ && paBatEntries)
+ RTMemFree(paBatEntries);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Load the file parameters metadata item from the file.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param offItem File offset where the data is stored.
+ * @param cbItem Size of the item in the file.
+ */
+static int vhdxLoadFileParametersMetadata(PVHDXIMAGE pImage, uint64_t offItem, size_t cbItem)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p offItem=%llu cbItem=%zu\n", pImage, offItem, cbItem));
+
+ if (cbItem != sizeof(VhdxFileParameters))
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: File parameters item size mismatch (expected %u got %zu) in image \'%s\'",
+ sizeof(VhdxFileParameters), cbItem, pImage->pszFilename);
+ else
+ {
+ VhdxFileParameters FileParameters;
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, offItem,
+ &FileParameters, sizeof(FileParameters));
+ if (RT_SUCCESS(rc))
+ {
+ vhdxConvFileParamsEndianess(VHDXECONV_F2H, &FileParameters, &FileParameters);
+ pImage->cbBlock = FileParameters.u32BlockSize;
+
+ /** @todo No support for differencing images yet. */
+ if (FileParameters.u32Flags & VHDX_FILE_PARAMETERS_FLAGS_HAS_PARENT)
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ "VHDX: Image \'%s\' is a differencing image which is not supported yet",
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ "VHDX: Reading the file parameters metadata item from image \'%s\' failed",
+ pImage->pszFilename);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Load the virtual disk size metadata item from the file.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param offItem File offset where the data is stored.
+ * @param cbItem Size of the item in the file.
+ */
+static int vhdxLoadVDiskSizeMetadata(PVHDXIMAGE pImage, uint64_t offItem, size_t cbItem)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p offItem=%llu cbItem=%zu\n", pImage, offItem, cbItem));
+
+ if (cbItem != sizeof(VhdxVDiskSize))
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Virtual disk size item size mismatch (expected %u got %zu) in image \'%s\'",
+ sizeof(VhdxVDiskSize), cbItem, pImage->pszFilename);
+ else
+ {
+ VhdxVDiskSize VDiskSize;
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, offItem,
+ &VDiskSize, sizeof(VDiskSize));
+ if (RT_SUCCESS(rc))
+ {
+ vhdxConvVDiskSizeEndianess(VHDXECONV_F2H, &VDiskSize, &VDiskSize);
+ pImage->cbSize = VDiskSize.u64VDiskSize;
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ "VHDX: Reading the virtual disk size metadata item from image \'%s\' failed",
+ pImage->pszFilename);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Load the logical sector size metadata item from the file.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param offItem File offset where the data is stored.
+ * @param cbItem Size of the item in the file.
+ */
+static int vhdxLoadVDiskLogSectorSizeMetadata(PVHDXIMAGE pImage, uint64_t offItem, size_t cbItem)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p offItem=%llu cbItem=%zu\n", pImage, offItem, cbItem));
+
+ if (cbItem != sizeof(VhdxVDiskLogicalSectorSize))
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Virtual disk logical sector size item size mismatch (expected %u got %zu) in image \'%s\'",
+ sizeof(VhdxVDiskLogicalSectorSize), cbItem, pImage->pszFilename);
+ else
+ {
+ VhdxVDiskLogicalSectorSize VDiskLogSectSize;
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, offItem,
+ &VDiskLogSectSize, sizeof(VDiskLogSectSize));
+ if (RT_SUCCESS(rc))
+ {
+ vhdxConvVDiskLogSectSizeEndianess(VHDXECONV_F2H, &VDiskLogSectSize,
+ &VDiskLogSectSize);
+ pImage->cbLogicalSector = VDiskLogSectSize.u32LogicalSectorSize;
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ "VHDX: Reading the virtual disk logical sector size metadata item from image \'%s\' failed",
+ pImage->pszFilename);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Loads the metadata region.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param offRegion Start offset of the region.
+ * @param cbRegion Size of the region.
+ */
+static int vhdxLoadMetadataRegion(PVHDXIMAGE pImage, uint64_t offRegion,
+ size_t cbRegion)
+{
+ VhdxMetadataTblHdr MetadataTblHdr;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p\n", pImage));
+
+ /* Load the header first. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, offRegion,
+ &MetadataTblHdr, sizeof(MetadataTblHdr));
+ if (RT_SUCCESS(rc))
+ {
+ vhdxConvMetadataTblHdrEndianess(VHDXECONV_F2H, &MetadataTblHdr, &MetadataTblHdr);
+
+ /* Validate structure. */
+ if (MetadataTblHdr.u64Signature != VHDX_METADATA_TBL_HDR_SIGNATURE)
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Incorrect metadata table header signature for image \'%s\'",
+ pImage->pszFilename);
+ else if (MetadataTblHdr.u16EntryCount > VHDX_METADATA_TBL_HDR_ENTRY_COUNT_MAX)
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Incorrect entry count in metadata table header of image \'%s\'",
+ pImage->pszFilename);
+ else if (cbRegion < (MetadataTblHdr.u16EntryCount * sizeof(VhdxMetadataTblEntry) + sizeof(VhdxMetadataTblHdr)))
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Metadata table of image \'%s\' exceeds region size",
+ pImage->pszFilename);
+
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t offMetadataTblEntry = offRegion + sizeof(VhdxMetadataTblHdr);
+
+ for (unsigned i = 0; i < MetadataTblHdr.u16EntryCount; i++)
+ {
+ uint64_t offMetadataItem = 0;
+ VHDXMETADATAITEM enmMetadataItem = VHDXMETADATAITEM_UNKNOWN;
+ VhdxMetadataTblEntry MetadataTblEntry;
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, offMetadataTblEntry,
+ &MetadataTblEntry, sizeof(MetadataTblEntry));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ "VHDX: Reading metadata table entry from image \'%s\' failed",
+ pImage->pszFilename);
+ break;
+ }
+
+ vhdxConvMetadataTblEntryEndianess(VHDXECONV_F2H, &MetadataTblEntry, &MetadataTblEntry);
+
+ /* Check whether the flags match the expectations. */
+ for (unsigned idxProp = 0; idxProp < RT_ELEMENTS(s_aVhdxMetadataItemProps); idxProp++)
+ {
+ if (!RTUuidCompareStr(&MetadataTblEntry.UuidItem,
+ s_aVhdxMetadataItemProps[idxProp].pszItemUuid))
+ {
+ /*
+ * Check for specification violations and bail out, except
+ * for the required flag of the physical sector size metadata item.
+ * Early images had the required flag not set opposed to the specification.
+ * We don't want to brerak those images.
+ */
+ if ( !!(MetadataTblEntry.u32Flags & VHDX_METADATA_TBL_ENTRY_FLAGS_IS_USER)
+ != s_aVhdxMetadataItemProps[idxProp].fIsUser)
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: User flag of metadata item does not meet expectations \'%s\'",
+ pImage->pszFilename);
+ else if ( !!(MetadataTblEntry.u32Flags & VHDX_METADATA_TBL_ENTRY_FLAGS_IS_VDISK)
+ != s_aVhdxMetadataItemProps[idxProp].fIsVDisk)
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Virtual disk flag of metadata item does not meet expectations \'%s\'",
+ pImage->pszFilename);
+ else if ( !!(MetadataTblEntry.u32Flags & VHDX_METADATA_TBL_ENTRY_FLAGS_IS_REQUIRED)
+ != s_aVhdxMetadataItemProps[idxProp].fIsRequired
+ && (s_aVhdxMetadataItemProps[idxProp].enmMetadataItem != VHDXMETADATAITEM_PHYSICAL_SECTOR_SIZE))
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Required flag of metadata item does not meet expectations \'%s\'",
+ pImage->pszFilename);
+ else
+ enmMetadataItem = s_aVhdxMetadataItemProps[idxProp].enmMetadataItem;
+
+ break;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ offMetadataItem = offRegion + MetadataTblEntry.u32Offset;
+
+ switch (enmMetadataItem)
+ {
+ case VHDXMETADATAITEM_FILE_PARAMS:
+ {
+ rc = vhdxLoadFileParametersMetadata(pImage, offMetadataItem,
+ MetadataTblEntry.u32Length);
+ break;
+ }
+ case VHDXMETADATAITEM_VDISK_SIZE:
+ {
+ rc = vhdxLoadVDiskSizeMetadata(pImage, offMetadataItem,
+ MetadataTblEntry.u32Length);
+ break;
+ }
+ case VHDXMETADATAITEM_PAGE83_DATA:
+ {
+ /*
+ * Nothing to do here for now (marked as required but
+ * there is no API to pass this information to the caller)
+ * so far.
+ */
+ break;
+ }
+ case VHDXMETADATAITEM_LOGICAL_SECTOR_SIZE:
+ {
+ rc = vhdxLoadVDiskLogSectorSizeMetadata(pImage, offMetadataItem,
+ MetadataTblEntry.u32Length);
+ break;
+ }
+ case VHDXMETADATAITEM_PHYSICAL_SECTOR_SIZE:
+ {
+ /*
+ * Nothing to do here for now (marked as required but
+ * there is no API to pass this information to the caller)
+ * so far.
+ */
+ break;
+ }
+ case VHDXMETADATAITEM_PARENT_LOCATOR:
+ {
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ "VHDX: Image \'%s\' is a differencing image which is not supported yet",
+ pImage->pszFilename);
+ break;
+ }
+ case VHDXMETADATAITEM_UNKNOWN:
+ default:
+ if (MetadataTblEntry.u32Flags & VHDX_METADATA_TBL_ENTRY_FLAGS_IS_REQUIRED)
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ "VHDX: Unsupported but required metadata item in image \'%s\'",
+ pImage->pszFilename);
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ offMetadataTblEntry += sizeof(MetadataTblEntry);
+ }
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ "VHDX: Reading the metadata table header for image \'%s\' failed",
+ pImage->pszFilename);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Loads the region table and the associated regions.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ */
+static int vhdxLoadRegionTable(PVHDXIMAGE pImage)
+{
+ uint8_t *pbRegionTbl = NULL;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p\n", pImage));
+
+ /* Load the complete region table into memory. */
+ pbRegionTbl = (uint8_t *)RTMemTmpAlloc(VHDX_REGION_TBL_SIZE_MAX);
+ if (pbRegionTbl)
+ {
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, VHDX_REGION_TBL_HDR_OFFSET,
+ pbRegionTbl, VHDX_REGION_TBL_SIZE_MAX);
+ if (RT_SUCCESS(rc))
+ {
+ PVhdxRegionTblHdr pRegionTblHdr;
+ VhdxRegionTblHdr RegionTblHdr;
+ uint32_t u32ChkSum = 0;
+
+ /*
+ * Copy the region table header to a dedicated structure where we can
+ * convert it to host endianess.
+ */
+ memcpy(&RegionTblHdr, pbRegionTbl, sizeof(RegionTblHdr));
+ vhdxConvRegionTblHdrEndianess(VHDXECONV_F2H, &RegionTblHdr, &RegionTblHdr);
+
+ /* Set checksum field to 0 during crc computation. */
+ pRegionTblHdr = (PVhdxRegionTblHdr)pbRegionTbl;
+ pRegionTblHdr->u32Checksum = 0;
+
+ /* Verify the region table integrity. */
+ u32ChkSum = RTCrc32C(pbRegionTbl, VHDX_REGION_TBL_SIZE_MAX);
+
+ if (RegionTblHdr.u32Signature != VHDX_REGION_TBL_HDR_SIGNATURE)
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Invalid signature for region table header of image \'%s\'",
+ pImage->pszFilename);
+ else if (u32ChkSum != RegionTblHdr.u32Checksum)
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: CRC32 checksum mismatch for the region table of image \'%s\' (expected %#x got %#x)",
+ pImage->pszFilename, RegionTblHdr.u32Checksum, u32ChkSum);
+ else if (RegionTblHdr.u32EntryCount > VHDX_REGION_TBL_HDR_ENTRY_COUNT_MAX)
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Invalid entry count field in the region table header of image \'%s\'",
+ pImage->pszFilename);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Parse the region table entries. */
+ PVhdxRegionTblEntry pRegTblEntry = (PVhdxRegionTblEntry)(pbRegionTbl + sizeof(VhdxRegionTblHdr));
+ VhdxRegionTblEntry RegTblEntryBat; /* BAT region table entry. */
+ bool fBatRegPresent = false;
+ RT_ZERO(RegTblEntryBat); /* Maybe uninitialized, gcc. */
+
+ for (unsigned i = 0; i < RegionTblHdr.u32EntryCount; i++)
+ {
+ vhdxConvRegionTblEntryEndianess(VHDXECONV_F2H, pRegTblEntry, pRegTblEntry);
+
+ /* Check the uuid for known regions. */
+ if (!RTUuidCompareStr(&pRegTblEntry->UuidObject, VHDX_REGION_TBL_ENTRY_UUID_BAT))
+ {
+ /*
+ * Save the BAT region and process it later.
+ * It may come before the metadata region but needs the block size.
+ */
+ if (pRegTblEntry->u32Flags & VHDX_REGION_TBL_ENTRY_FLAGS_IS_REQUIRED)
+ {
+ fBatRegPresent = true;
+ RegTblEntryBat.u32Length = pRegTblEntry->u32Length;
+ RegTblEntryBat.u64FileOffset = pRegTblEntry->u64FileOffset;
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: BAT region not marked as required in image \'%s\'",
+ pImage->pszFilename);
+ }
+ else if (!RTUuidCompareStr(&pRegTblEntry->UuidObject, VHDX_REGION_TBL_ENTRY_UUID_METADATA))
+ {
+ if (pRegTblEntry->u32Flags & VHDX_REGION_TBL_ENTRY_FLAGS_IS_REQUIRED)
+ rc = vhdxLoadMetadataRegion(pImage, pRegTblEntry->u64FileOffset, pRegTblEntry->u32Length);
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: Metadata region not marked as required in image \'%s\'",
+ pImage->pszFilename);
+ }
+ else if (pRegTblEntry->u32Flags & VHDX_REGION_TBL_ENTRY_FLAGS_IS_REQUIRED)
+ {
+ /* The region is not known but marked as required, fail to load the image. */
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ "VHDX: Unknown required region in image \'%s\'",
+ pImage->pszFilename);
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ pRegTblEntry++;
+ }
+
+ if (fBatRegPresent)
+ rc = vhdxLoadBatRegion(pImage, RegTblEntryBat.u64FileOffset, RegTblEntryBat.u32Length);
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_GEN_INVALID_HEADER, RT_SRC_POS,
+ "VHDX: BAT region in image \'%s\' is missing",
+ pImage->pszFilename);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ "VHDX: Reading the region table for image \'%s\' failed",
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ "VHDX: Out of memory allocating memory for the region table of image \'%s\'",
+ pImage->pszFilename);
+
+ if (pbRegionTbl)
+ RTMemTmpFree(pbRegionTbl);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int vhdxOpenImage(PVHDXIMAGE pImage, unsigned uOpenFlags)
+{
+ uint64_t cbFile = 0;
+ VhdxFileIdentifier FileIdentifier;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pImage=%#p uOpenFlags=%#x\n", pImage, uOpenFlags));
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /* Refuse write access, it is not implemented so far. */
+ if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ return VERR_NOT_SUPPORTED;
+
+ /*
+ * Open the image.
+ */
+ rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags,
+ false /* fCreate */),
+ &pImage->pStorage);
+
+ /* Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (cbFile > sizeof(FileIdentifier))
+ {
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, VHDX_FILE_IDENTIFIER_OFFSET,
+ &FileIdentifier, sizeof(FileIdentifier));
+ if (RT_SUCCESS(rc))
+ {
+ vhdxConvFileIdentifierEndianess(VHDXECONV_F2H, &FileIdentifier,
+ &FileIdentifier);
+ if (FileIdentifier.u64Signature != VHDX_FILE_IDENTIFIER_SIGNATURE)
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ else
+ rc = vhdxFindAndLoadCurrentHeader(pImage);
+
+ /* Load the region table. */
+ if (RT_SUCCESS(rc))
+ rc = vhdxLoadRegionTable(pImage);
+ }
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = pImage->cbLogicalSector;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = pImage->cbLogicalSector;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ vhdxFreeImage(pImage, false);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) vhdxProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pVDIfsDisk, enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
+ PVDIOSTORAGE pStorage = NULL;
+ uint64_t cbFile;
+ int rc = VINF_SUCCESS;
+ VhdxFileIdentifier FileIdentifier;
+
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ if ( !RT_VALID_PTR(pszFilename)
+ || !*pszFilename)
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /*
+ * Open the file and read the file identifier.
+ */
+ rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbFile > sizeof(FileIdentifier))
+ {
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, VHDX_FILE_IDENTIFIER_OFFSET,
+ &FileIdentifier, sizeof(FileIdentifier));
+ if (RT_SUCCESS(rc))
+ {
+ vhdxConvFileIdentifierEndianess(VHDXECONV_F2H, &FileIdentifier,
+ &FileIdentifier);
+ if (FileIdentifier.u64Signature != VHDX_FILE_IDENTIFIER_SIGNATURE)
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ else
+ *penmType = VDTYPE_HDD;
+ }
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ }
+
+ if (pStorage)
+ vdIfIoIntFileClose(pIfIo, pStorage);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) vhdxOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n", pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc;
+ PVHDXIMAGE pImage;
+
+ NOREF(enmType); /**< @todo r=klaus make use of the type info. */
+
+ /* Check open flags. All valid flags are supported. */
+ if ( uOpenFlags & ~VD_OPEN_FLAGS_MASK
+ || !RT_VALID_PTR(pszFilename)
+ || !*pszFilename)
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ pImage = (PVHDXIMAGE)RTMemAllocZ(RT_UOFFSETOF(VHDXIMAGE, RegionList.aRegions[1]));
+ if (!pImage)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vhdxOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ else
+ RTMemFree(pImage);
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnCreate} */
+static DECLCALLBACK(int) vhdxCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ RT_NOREF8(pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags);
+ RT_NOREF7(uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData);
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+ int rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRename */
+static DECLCALLBACK(int) vhdxRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VINF_SUCCESS;
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+
+ /* Check arguments. */
+ if ( !pImage
+ || !pszFilename
+ || !*pszFilename)
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Close the image. */
+ rc = vhdxFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ {
+ /* Rename the file. */
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pszFilename, 0);
+ if (RT_FAILURE(rc))
+ {
+ /* The move failed, try to reopen the original image. */
+ int rc2 = vhdxOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ else
+ {
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the old image with new name. */
+ rc = vhdxOpenImage(pImage, pImage->uOpenFlags);
+ }
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) vhdxClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ int rc;
+
+ rc = vhdxFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRead */
+static DECLCALLBACK(int) vhdxRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToRead=%zu pcbActuallyRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToRead % 512 == 0);
+
+ if ( uOffset + cbToRead > pImage->cbSize
+ || cbToRead == 0)
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ uint32_t idxBat = (uint32_t)(uOffset / pImage->cbBlock); Assert(idxBat == uOffset / pImage->cbBlock);
+ uint32_t offRead = uOffset % pImage->cbBlock;
+ uint64_t uBatEntry;
+
+ idxBat += idxBat / pImage->uChunkRatio; /* Add interleaving sector bitmap entries. */
+ uBatEntry = pImage->paBat[idxBat].u64BatEntry;
+
+ cbToRead = RT_MIN(cbToRead, pImage->cbBlock - offRead);
+
+ switch (VHDX_BAT_ENTRY_GET_STATE(uBatEntry))
+ {
+ case VHDX_BAT_ENTRY_PAYLOAD_BLOCK_NOT_PRESENT:
+ case VHDX_BAT_ENTRY_PAYLOAD_BLOCK_UNDEFINED:
+ case VHDX_BAT_ENTRY_PAYLOAD_BLOCK_ZERO:
+ case VHDX_BAT_ENTRY_PAYLOAD_BLOCK_UNMAPPED:
+ {
+ vdIfIoIntIoCtxSet(pImage->pIfIo, pIoCtx, 0, cbToRead);
+ break;
+ }
+ case VHDX_BAT_ENTRY_PAYLOAD_BLOCK_FULLY_PRESENT:
+ {
+ uint64_t offFile = VHDX_BAT_ENTRY_GET_FILE_OFFSET(uBatEntry) + offRead;
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, offFile,
+ pIoCtx, cbToRead);
+ break;
+ }
+ case VHDX_BAT_ENTRY_PAYLOAD_BLOCK_PARTIALLY_PRESENT:
+ default:
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ if (pcbActuallyRead)
+ *pcbActuallyRead = cbToRead;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnWrite */
+static DECLCALLBACK(int) vhdxWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ RT_NOREF5(pIoCtx, pcbWriteProcess, pcbPreRead, pcbPostRead, fWrite);
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToWrite % 512 == 0);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else if ( uOffset + cbToWrite > pImage->cbSize
+ || cbToWrite == 0)
+ rc = VERR_INVALID_PARAMETER;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnFlush */
+static DECLCALLBACK(int) vhdxFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ RT_NOREF1(pIoCtx);
+ LogFlowFunc(("pBackendData=%#p pIoCtx=%#p\n", pBackendData, pIoCtx));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ int rc;
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) vhdxGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ return pImage->uVersion;
+ else
+ return 0;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) vhdxGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ uint64_t cbFile;
+ if (pImage->pStorage)
+ {
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb = cbFile;
+ }
+ }
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) vhdxGetPCHSGeometry(void *pBackendData,
+ PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->PCHSGeometry.cCylinders)
+ {
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) vhdxSetPCHSGeometry(void *pBackendData,
+ PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) vhdxGetLCHSGeometry(void *pBackendData,
+ PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->LCHSGeometry.cCylinders)
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) vhdxSetLCHSGeometry(void *pBackendData,
+ PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) vhdxQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PVHDXIMAGE pThis = (PVHDXIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) vhdxRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PVHDXIMAGE pThis = (PVHDXIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) vhdxGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ unsigned uImageFlags;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ uImageFlags = pImage->uImageFlags;
+ else
+ uImageFlags = 0;
+
+ LogFlowFunc(("returns %#x\n", uImageFlags));
+ return uImageFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) vhdxGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ unsigned uOpenFlags;
+
+ AssertPtr(pImage);
+
+ if (pImage)
+ uOpenFlags = pImage->uOpenFlags;
+ else
+ uOpenFlags = 0;
+
+ LogFlowFunc(("returns %#x\n", uOpenFlags));
+ return uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) vhdxSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pImage || (uOpenFlags & ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ rc = vhdxFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ rc = vhdxOpenImage(pImage, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(vhdxGetComment);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(vhdxSetComment, PVHDXIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(vhdxGetUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(vhdxSetUuid, PVHDXIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(vhdxGetModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(vhdxSetModificationUuid, PVHDXIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(vhdxGetParentUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(vhdxSetParentUuid, PVHDXIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(vhdxGetParentModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(vhdxSetParentModificationUuid, PVHDXIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) vhdxDump(void *pBackendData)
+{
+ PVHDXIMAGE pImage = (PVHDXIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ vdIfErrorMessage(pImage->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cbSector=%u\n",
+ pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors,
+ pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors,
+ pImage->cbLogicalSector);
+ }
+}
+
+
+const VDIMAGEBACKEND g_VhdxBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "VHDX",
+ /* uBackendCaps */
+ VD_CAP_FILE | VD_CAP_VFS,
+ /* paFileExtensions */
+ s_aVhdxFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ vhdxProbe,
+ /* pfnOpen */
+ vhdxOpen,
+ /* pfnCreate */
+ vhdxCreate,
+ /* pfnRename */
+ vhdxRename,
+ /* pfnClose */
+ vhdxClose,
+ /* pfnRead */
+ vhdxRead,
+ /* pfnWrite */
+ vhdxWrite,
+ /* pfnFlush */
+ vhdxFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ vhdxGetVersion,
+ /* pfnGetFileSize */
+ vhdxGetFileSize,
+ /* pfnGetPCHSGeometry */
+ vhdxGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ vhdxSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ vhdxGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ vhdxSetLCHSGeometry,
+ /* pfnQueryRegions */
+ vhdxQueryRegions,
+ /* pfnRegionListRelease */
+ vhdxRegionListRelease,
+ /* pfnGetImageFlags */
+ vhdxGetImageFlags,
+ /* pfnGetOpenFlags */
+ vhdxGetOpenFlags,
+ /* pfnSetOpenFlags */
+ vhdxSetOpenFlags,
+ /* pfnGetComment */
+ vhdxGetComment,
+ /* pfnSetComment */
+ vhdxSetComment,
+ /* pfnGetUuid */
+ vhdxGetUuid,
+ /* pfnSetUuid */
+ vhdxSetUuid,
+ /* pfnGetModificationUuid */
+ vhdxGetModificationUuid,
+ /* pfnSetModificationUuid */
+ vhdxSetModificationUuid,
+ /* pfnGetParentUuid */
+ vhdxGetParentUuid,
+ /* pfnSetParentUuid */
+ vhdxSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ vhdxGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ vhdxSetParentModificationUuid,
+ /* pfnDump */
+ vhdxDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ NULL,
+ /* pfnSetParentFilename */
+ NULL,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ NULL,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
diff --git a/src/VBox/Storage/VISO.cpp b/src/VBox/Storage/VISO.cpp
new file mode 100644
index 00000000..38d6fd14
--- /dev/null
+++ b/src/VBox/Storage/VISO.cpp
@@ -0,0 +1,1119 @@
+/* $Id: VISO.cpp $ */
+/** @file
+ * VISO - Virtual ISO disk image, Core Code.
+ */
+
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/fsisomaker.h>
+#include <iprt/getopt.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "VDBackends.h"
+#include "VDBackendsInline.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The maximum file size. */
+#if ARCH_BITS >= 64
+# define VISO_MAX_FILE_SIZE _32M
+#else
+# define VISO_MAX_FILE_SIZE _8M
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * VBox ISO maker image instance.
+ */
+typedef struct VISOIMAGE
+{
+ /** The ISO maker output file handle.
+ * This is NIL if in VD_OPEN_FLAGS_INFO mode. */
+ RTVFSFILE hIsoFile;
+ /** The image size. */
+ uint64_t cbImage;
+ /** The UUID ofr the image. */
+ RTUUID Uuid;
+
+ /** Open flags passed by VD layer. */
+ uint32_t fOpenFlags;
+ /** Image name. Allocation follows the region list, no need to free. */
+ const char *pszFilename;
+ /** The parent director of pszFilename.
+ * Allocation follows the region list, no need to free. */
+ const char *pszCwd;
+
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+
+ /** Internal region list (variable size). */
+ VDREGIONLIST RegionList;
+} VISOIMAGE;
+/** Pointer to an VBox ISO make image instance. */
+typedef VISOIMAGE *PVISOIMAGE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION g_aVBoXIsoMakerFileExtensions[] =
+{
+ //{ "vbox-iso-maker", VDTYPE_OPTICAL_DISC }, - clumsy.
+ { "viso", VDTYPE_OPTICAL_DISC },
+ { NULL, VDTYPE_INVALID }
+};
+
+/** NULL-terminated array of configuration option. */
+static const VDCONFIGINFO s_aVisoConfigInfo[] =
+{
+ /* Options for VMDK raw disks */
+ { "UnattendedInstall", NULL, VDCFGVALUETYPE_STRING, VD_CFGKEY_EXPERT },
+ /* End of options list */
+ { NULL, NULL, VDCFGVALUETYPE_INTEGER, 0 }
+};
+
+/**
+ * Parses the UUID that follows the marker argument.
+ *
+ * @returns IPRT status code.
+ * @param pszMarker The marker.
+ * @param pUuid Where to return the UUID.
+ */
+static int visoParseUuid(char *pszMarker, PRTUUID pUuid)
+{
+ /* Skip the marker. */
+ char ch;
+ while ( (ch = *pszMarker) != '\0'
+ && !RT_C_IS_SPACE(ch)
+ && ch != ':'
+ && ch != '=')
+ pszMarker++;
+
+ /* Skip chars before the value. */
+ if ( ch == ':'
+ || ch == '=')
+ ch = *++pszMarker;
+ else
+ while (RT_C_IS_SPACE(ch))
+ ch = *++pszMarker;
+ const char * const pszUuid = pszMarker;
+
+ /* Find the end of the UUID value. */
+ while ( ch != '\0'
+ && !RT_C_IS_SPACE(ch))
+ ch = *++pszMarker;
+
+ /* Validate the value (temporarily terminate the value string) */
+ *pszMarker = '\0';
+ int rc = RTUuidFromStr(pUuid, pszUuid);
+ if (RT_SUCCESS(rc))
+ {
+ *pszMarker = ch;
+ return VINF_SUCCESS;
+ }
+
+ /* Complain and return VERR_VD_IMAGE_CORRUPTED to indicate we've identified
+ the right image format, but the producer got something wrong. */
+ if (pszUuid != pszMarker)
+ LogRel(("visoParseUuid: Malformed UUID '%s': %Rrc\n", pszUuid, rc));
+ else
+ LogRel(("visoParseUuid: Empty/missing UUID!\n"));
+ *pszMarker = ch;
+
+ return VERR_VD_IMAGE_CORRUPTED;
+}
+
+
+static int visoProbeWorker(const char *pszFilename, PVDINTERFACEIOINT pIfIo, PRTUUID pUuid)
+{
+ PVDIOSTORAGE pStorage = NULL;
+ int rc = vdIfIoIntFileOpen(pIfIo, pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Read the first part of the file.
+ */
+ uint64_t cbFile = 0;
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ char szChunk[_1K];
+ size_t cbToRead = (size_t)RT_MIN(sizeof(szChunk) - 1, cbFile);
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0 /*off*/, szChunk, cbToRead);
+ if (RT_SUCCESS(rc))
+ {
+ szChunk[cbToRead] = '\0';
+
+ /*
+ * Skip leading spaces and check for the eye-catcher.
+ */
+ char *psz = szChunk;
+ while (RT_C_IS_SPACE(*psz))
+ psz++;
+ if (strncmp(psz, RT_STR_TUPLE("--iprt-iso-maker-file-marker")) == 0)
+ {
+ rc = visoParseUuid(psz, pUuid);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check the file size.
+ */
+ if (cbFile <= VISO_MAX_FILE_SIZE)
+ rc = VINF_SUCCESS;
+ else
+ {
+ LogRel(("visoProbeWorker: VERR_VD_INVALID_SIZE - cbFile=%#RX64 cbMaxFile=%#RX64\n",
+ cbFile, (uint64_t)VISO_MAX_FILE_SIZE));
+ rc = VERR_VD_INVALID_SIZE;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_CORRUPTED;
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ }
+ vdIfIoIntFileClose(pIfIo, pStorage);
+ }
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnProbe}
+ */
+static DECLCALLBACK(int) visoProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(penmType, VERR_INVALID_POINTER);
+ *penmType = VDTYPE_INVALID;
+
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename, VERR_INVALID_POINTER);
+
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ RT_NOREF(pVDIfsDisk);
+
+ /*
+ * We can only fake DVD stuff, so fail if the desired type doesn't match up
+ */
+ if (enmDesiredType != VDTYPE_OPTICAL_DISC && enmDesiredType != VDTYPE_INVALID)
+ return VERR_VD_GEN_INVALID_HEADER; /* Caller has strict, though undocument, status code expectations. */
+
+ /*
+ * Share worker with visoOpen and visoSetFlags.
+ */
+ RTUUID UuidIgn;
+ int rc = visoProbeWorker(pszFilename, pIfIo, &UuidIgn);
+ if (RT_SUCCESS(rc))
+ *penmType = VDTYPE_OPTICAL_DISC;
+ else if (rc == VERR_VD_IMAGE_CORRUPTED || rc == VERR_VD_INVALID_SIZE)
+ *penmType = VDTYPE_OPTICAL_DISC;
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER; /* Caller has strict, though undocument, status code expectations. */
+
+ LogFlowFunc(("returns %Rrc - *penmType=%d\n", rc, *penmType));
+ return rc;
+}
+
+
+/**
+ * Worker for visoOpen and visoSetOpenFlags that creates a VFS file for the ISO.
+ *
+ * This also updates cbImage and the Uuid members.
+ *
+ * @returns VBox status code.
+ * @param pThis The VISO image instance.
+ */
+static int visoOpenWorker(PVISOIMAGE pThis)
+{
+ /*
+ * Open the file and read it into memory.
+ */
+ PVDIOSTORAGE pStorage = NULL;
+ int rc = vdIfIoIntFileOpen(pThis->pIfIo, pThis->pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &pStorage);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("VISO: Unable to open file '%s': %Rrc\n", pThis->pszFilename, rc));
+ return rc;
+ }
+
+ LogRel(("VISO: Handling file '%s'\n", pThis->pszFilename));
+
+ /*
+ * Read the file into memory, prefixing it with a dummy command name.
+ */
+ uint64_t cbFile = 0;
+ rc = vdIfIoIntFileGetSize(pThis->pIfIo, pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbFile <= VISO_MAX_FILE_SIZE)
+ {
+ static char const s_szCmdPrefix[] = "VBox-Iso-Maker ";
+
+ char *pszContent = (char *)RTMemTmpAlloc(sizeof(s_szCmdPrefix) + cbFile);
+ if (pszContent)
+ {
+ char *pszReadDst = &pszContent[sizeof(s_szCmdPrefix) - 1];
+ rc = vdIfIoIntFileReadSync(pThis->pIfIo, pStorage, 0 /*off*/, pszReadDst, (size_t)cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check the file marker and get the UUID that follows it.
+ * Ignore leading blanks.
+ */
+ pszReadDst[(size_t)cbFile] = '\0';
+ memcpy(pszContent, s_szCmdPrefix, sizeof(s_szCmdPrefix) - 1);
+
+ while (RT_C_IS_SPACE(*pszReadDst))
+ pszReadDst++;
+ if (strncmp(pszReadDst, RT_STR_TUPLE("--iprt-iso-maker-file-marker")) == 0)
+ {
+ rc = visoParseUuid(pszReadDst, &pThis->Uuid);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Make sure it's valid UTF-8 before letting
+ */
+ rc = RTStrValidateEncodingEx(pszContent, sizeof(s_szCmdPrefix) + cbFile,
+ RTSTR_VALIDATE_ENCODING_EXACT_LENGTH
+ | RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert it into an argument vector.
+ * Free the content afterwards to reduce memory pressure.
+ */
+ uint32_t fGetOpt = strncmp(pszReadDst, RT_STR_TUPLE("--iprt-iso-maker-file-marker-ms")) != 0
+ ? RTGETOPTARGV_CNV_QUOTE_BOURNE_SH : RTGETOPTARGV_CNV_QUOTE_MS_CRT;
+ fGetOpt |= RTGETOPTARGV_CNV_MODIFY_INPUT;
+ char **papszArgs;
+ int cArgs;
+ rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszContent, fGetOpt, NULL);
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Open the parent directory and use that as CWD for relative references.
+ */
+ RTVFSDIR hVfsCwd;
+ rc = RTVfsChainOpenDir(pThis->pszCwd, 0 /*fOpen*/, &hVfsCwd, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Try instantiate the ISO image maker.
+ * Free the argument vector afterward to reduce memory pressure.
+ */
+ RTVFSFILE hVfsFile;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTFsIsoMakerCmdEx(cArgs, papszArgs, hVfsCwd, pThis->pszCwd,
+ &hVfsFile, RTErrInfoInitStatic(&ErrInfo));
+
+ RTVfsDirRelease(hVfsCwd);
+
+ RTGetOptArgvFreeEx(papszArgs, fGetOpt);
+ papszArgs = NULL;
+
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbImage;
+ rc = RTVfsFileQuerySize(hVfsFile, &cbImage);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Update the state.
+ */
+ pThis->cbImage = cbImage;
+ pThis->RegionList.aRegions[0].cRegionBlocksOrBytes = cbImage;
+
+ pThis->hIsoFile = hVfsFile;
+ hVfsFile = NIL_RTVFSFILE;
+
+ rc = VINF_SUCCESS;
+ LogRel(("VISO: %'RU64 bytes (%#RX64) - %s\n", cbImage, cbImage, pThis->pszFilename));
+ }
+
+ RTVfsFileRelease(hVfsFile);
+ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ {
+ LogRel(("visoOpenWorker: RTFsIsoMakerCmdEx failed: %Rrc - %s\n", rc, ErrInfo.Core.pszMsg));
+ vdIfError(pThis->pIfError, rc, RT_SRC_POS, "VISO: %s", ErrInfo.Core.pszMsg);
+ }
+ else
+ {
+ LogRel(("visoOpenWorker: RTFsIsoMakerCmdEx failed: %Rrc\n", rc));
+ vdIfError(pThis->pIfError, rc, RT_SRC_POS, "VISO: RTFsIsoMakerCmdEx failed: %Rrc", rc);
+ }
+ }
+ else
+ vdIfError(pThis->pIfError, rc, RT_SRC_POS,
+ "VISO: Failed to open parent dir of: %s", pThis->pszFilename);
+ }
+ else
+ vdIfError(pThis->pIfError, rc, RT_SRC_POS, "VISO: RTGetOptArgvFromString failed: %Rrc", rc);
+ }
+ else
+ vdIfError(pThis->pIfError, rc, RT_SRC_POS, "VISO: Invalid file encoding");
+ }
+ else
+ vdIfError(pThis->pIfError, rc, RT_SRC_POS, "VISO: Parsing UUID failed: %Rrc", rc);
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else
+ vdIfError(pThis->pIfError, rc, RT_SRC_POS, "VISO: Reading file failed: %Rrc", rc);
+
+ RTMemTmpFree(pszContent);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ else
+ {
+ LogRel(("visoOpen: VERR_VD_INVALID_SIZE - cbFile=%#RX64 cbMaxFile=%#RX64\n",
+ cbFile, (uint64_t)VISO_MAX_FILE_SIZE));
+ rc = VERR_VD_INVALID_SIZE;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("VISO: Handling of file '%s' failed with %Rrc\n", pThis->pszFilename, rc));
+
+ vdIfIoIntFileClose(pThis->pIfIo, pStorage);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnOpen}
+ */
+static DECLCALLBACK(int) visoOpen(const char *pszFilename, unsigned uOpenFlags, PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ uint32_t const fOpenFlags = uOpenFlags;
+ LogFlowFunc(("pszFilename='%s' fOpenFlags=%#x pVDIfsDisk=%p pVDIfsImage=%p enmType=%u ppBackendData=%p\n",
+ pszFilename, fOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(ppBackendData, VERR_INVALID_POINTER);
+ *ppBackendData = NULL;
+
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename, VERR_INVALID_POINTER);
+
+ AssertReturn(!(fOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_FLAGS);
+
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ PVDINTERFACEERROR pIfError = VDIfErrorGet(pVDIfsDisk);
+
+ AssertReturn(enmType == VDTYPE_OPTICAL_DISC, VERR_NOT_SUPPORTED);
+
+ /*
+ * Allocate and initialize the backend image instance data.
+ */
+ int rc;
+ size_t cbFilename = strlen(pszFilename) + 1;
+ PVISOIMAGE pThis = (PVISOIMAGE)RTMemAllocZ(RT_UOFFSETOF(VISOIMAGE, RegionList.aRegions[1]) + cbFilename * 2);
+ if (pThis)
+ {
+ pThis->hIsoFile = NIL_RTVFSFILE;
+ pThis->cbImage = 0;
+ pThis->fOpenFlags = fOpenFlags;
+ pThis->pIfIo = pIfIo;
+ pThis->pIfError = pIfError;
+
+ pThis->RegionList.fFlags = 0;
+ pThis->RegionList.cRegions = 1;
+ pThis->RegionList.aRegions[0].offRegion = 0;
+ pThis->RegionList.aRegions[0].cRegionBlocksOrBytes = 0;
+ pThis->RegionList.aRegions[0].cbBlock = 2048;
+ pThis->RegionList.aRegions[0].enmDataForm = VDREGIONDATAFORM_RAW;
+ pThis->RegionList.aRegions[0].enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pThis->RegionList.aRegions[0].cbData = 2048;
+ pThis->RegionList.aRegions[0].cbMetadata = 0;
+
+ char *pszDst = (char *)&pThis->RegionList.aRegions[1];
+ memcpy(pszDst, pszFilename, cbFilename);
+ pThis->pszFilename = pszDst;
+ pszDst[cbFilename - 1] = '\0';
+ pszDst += cbFilename;
+
+ memcpy(pszDst, pszFilename, cbFilename);
+ pThis->pszCwd = pszDst;
+ pszDst[cbFilename - 1] = '\0';
+ RTPathStripFilename(pszDst);
+
+ /*
+ * Only go all the way if this isn't an info query. Re-mastering an ISO
+ * can potentially be a lot of work and we don't want to go thru with it
+ * just because the GUI wants to display the image size.
+ */
+ if (!(fOpenFlags & VD_OPEN_FLAGS_INFO))
+ rc = visoOpenWorker(pThis);
+ else
+ rc = visoProbeWorker(pThis->pszFilename, pThis->pIfIo, &pThis->Uuid);
+ if (RT_SUCCESS(rc))
+ {
+ *ppBackendData = pThis;
+ LogFlowFunc(("returns VINF_SUCCESS (UUID=%RTuuid, pszFilename=%s)\n", &pThis->Uuid, pThis->pszFilename));
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Scans the VISO file and removes all references to files
+ * which are in the same folder as the VISO and
+ * whose names begin with "Unattended-".
+ *
+ * @return VBox status code.
+ *
+ * @param pThis Pointer to VISO backend data.
+ */
+static int deleteReferences(PVISOIMAGE pThis)
+{
+ /*
+ * Open the file and read it into memory.
+ */
+ PVDIOSTORAGE pStorage = NULL;
+ int vrc = vdIfIoIntFileOpen(pThis->pIfIo, pThis->pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &pStorage);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("VISO: Unable to open file '%s': %Rrc\n", pThis->pszFilename, vrc));
+ return vrc;
+ }
+
+ LogRel(("VISO: Handling file '%s' references\n", pThis->pszFilename));
+
+ /*
+ * Read the file into memory.
+ */
+ uint64_t cbFile = 0;
+ vrc = vdIfIoIntFileGetSize(pThis->pIfIo, pStorage, &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ if (cbFile <= VISO_MAX_FILE_SIZE)
+ {
+ char *pszContent = (char *)RTMemTmpAlloc(cbFile + 1);
+ if (pszContent)
+ {
+ vrc = vdIfIoIntFileReadSync(pThis->pIfIo, pStorage, 0 /*off*/, pszContent, (size_t)cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Check the file marker.
+ * Ignore leading blanks.
+ */
+ pszContent[(size_t)cbFile] = '\0';
+
+ char *pszReadDst = pszContent;
+ while (RT_C_IS_SPACE(*pszReadDst))
+ pszReadDst++;
+ if (strncmp(pszReadDst, RT_STR_TUPLE("--iprt-iso-maker-file-marker")) == 0)
+ {
+ vrc = visoParseUuid(pszReadDst, &pThis->Uuid);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Make sure it's valid UTF-8 before letting
+ */
+ vrc = RTStrValidateEncodingEx(pszContent, cbFile + 1,
+ RTSTR_VALIDATE_ENCODING_EXACT_LENGTH
+ | RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Convert it into an argument vector.
+ * Free the content afterwards to reduce memory pressure.
+ */
+ uint32_t fGetOpt = strncmp(pszReadDst, RT_STR_TUPLE("--iprt-iso-maker-file-marker-ms")) != 0
+ ? RTGETOPTARGV_CNV_QUOTE_BOURNE_SH : RTGETOPTARGV_CNV_QUOTE_MS_CRT;
+ fGetOpt |= RTGETOPTARGV_CNV_MODIFY_INPUT;
+ char **papszArgs;
+ int cArgs;
+ vrc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszContent, fGetOpt, NULL);
+
+ if (RT_SUCCESS(vrc))
+ {
+ for (int i = 0; i < cArgs; ++i)
+ {
+ char *pszArg = papszArgs[i];
+ char *pszOffset = strrchr(pszArg, '=');
+ if (pszOffset != NULL)
+ pszArg = pszOffset + 1;
+
+ /* if it isn't option */
+ if (pszArg[0] != '-')
+ {
+ char *pszPath = RTPathAbsExDup(pThis->pszCwd, pszArg, 0);
+ if (RTStrStartsWith((const char *)pszPath, pThis->pszCwd))
+ {
+ char *pszFileName = RTPathFilename(pszPath);
+ if ( pszFileName != NULL
+ && RTStrStartsWith((const char *)pszFileName, "Unattended-"))
+ {
+ vrc = RTFileDelete(pszPath);
+ if (RT_SUCCESS(vrc))
+ LogRel(("VISO: file '%s' deleted\n", pszPath));
+ else
+ LogRel(("VISO: Failed to delete the file '%s' (%Rrc)\n", pszPath, vrc));
+ vrc = VINF_SUCCESS;
+ }
+ }
+ RTStrFree(pszPath);
+ }
+ }
+ RTGetOptArgvFreeEx(papszArgs, fGetOpt);
+ papszArgs = NULL;
+ }
+ else
+ vdIfError(pThis->pIfError, vrc, RT_SRC_POS, "VISO: RTGetOptArgvFromString failed: %Rrc", vrc);
+ }
+ else
+ vdIfError(pThis->pIfError, vrc, RT_SRC_POS, "VISO: Invalid file encoding");
+ }
+ else
+ vdIfError(pThis->pIfError, vrc, RT_SRC_POS, "VISO: Parsing UUID failed: %Rrc", vrc);
+ }
+ else
+ vrc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else
+ vdIfError(pThis->pIfError, vrc, RT_SRC_POS, "VISO: Reading file failed: %Rrc", vrc);
+
+ RTMemTmpFree(pszContent);
+ }
+ else
+ vrc = VERR_NO_TMP_MEMORY;
+ }
+ else
+ {
+ LogRel(("visoOpen: VERR_VD_INVALID_SIZE - cbFile=%#RX64 cbMaxFile=%#RX64\n",
+ cbFile, (uint64_t)VISO_MAX_FILE_SIZE));
+ vrc = VERR_VD_INVALID_SIZE;
+ }
+ }
+ if (RT_FAILURE(vrc))
+ LogRel(("VISO: Handling of file '%s' failed with %Rrc\n", pThis->pszFilename, vrc));
+
+ vdIfIoIntFileClose(pThis->pIfIo, pStorage);
+ return vrc;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnClose}
+ */
+static DECLCALLBACK(int) visoClose(void *pBackendData, bool fDelete)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ LogFlowFunc(("pThis=%p fDelete=%RTbool\n", pThis, fDelete));
+
+ if (pThis)
+ {
+ if (fDelete)
+ {
+ PVDINTERFACECONFIG pImgCfg = VDIfConfigGet(&pThis->pIfIo->Core);
+
+ bool fUnattendedInstall = false;
+ int vrc = VDCFGQueryBool(pImgCfg, "UnattendedInstall", &fUnattendedInstall);
+
+ /*
+ * The VISO created by unattended installer, so delete all generated files
+ * included in the VISO. the file is considered generated if it is located
+ * in the same folder as VISO and its name begins with "Unattended-"
+ */
+ if (RT_SUCCESS(vrc) && fUnattendedInstall)
+ deleteReferences(pThis);
+ vdIfIoIntFileDelete(pThis->pIfIo, pThis->pszFilename);
+ }
+
+ if (pThis->hIsoFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(pThis->hIsoFile);
+ pThis->hIsoFile = NIL_RTVFSFILE;
+ }
+
+ RTMemFree(pThis);
+ }
+
+ LogFlowFunc(("returns VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnRead}
+ */
+static DECLCALLBACK(int) visoRead(void *pBackendData, uint64_t uOffset, size_t cbToRead, PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ uint64_t off = uOffset;
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+ AssertReturn(pThis->hIsoFile != NIL_RTVFSFILE, VERR_VD_NOT_OPENED);
+ LogFlowFunc(("pThis=%p off=%#RX64 cbToRead=%#zx pIoCtx=%p pcbActuallyRead=%p\n", pThis, off, cbToRead, pIoCtx, pcbActuallyRead));
+
+ /*
+ * Check request.
+ */
+ AssertReturn( off < pThis->cbImage
+ || (off == pThis->cbImage && cbToRead == 0), VERR_EOF);
+
+ uint64_t cbLeftInImage = pThis->cbImage - off;
+ if (cbToRead >= cbLeftInImage)
+ cbToRead = cbLeftInImage; /* ASSUMES the caller can deal with this, given the pcbActuallyRead parameter... */
+
+ /*
+ * Work the I/O context using vdIfIoIntIoCtxSegArrayCreate.
+ */
+ int rc = VINF_SUCCESS;
+ size_t cbActuallyRead = 0;
+ while (cbToRead > 0)
+ {
+ RTSGSEG Seg;
+ unsigned cSegs = 1;
+ size_t cbThisRead = vdIfIoIntIoCtxSegArrayCreate(pThis->pIfIo, pIoCtx, &Seg, &cSegs, cbToRead);
+ AssertBreakStmt(cbThisRead != 0, rc = VERR_INTERNAL_ERROR_2);
+ Assert(cbThisRead == Seg.cbSeg);
+
+ rc = RTVfsFileReadAt(pThis->hIsoFile, off, Seg.pvSeg, cbThisRead, NULL);
+ AssertRCBreak(rc);
+
+ /* advance. */
+ cbActuallyRead += cbThisRead;
+ off += cbThisRead;
+ cbToRead -= cbThisRead;
+ }
+
+ *pcbActuallyRead = cbActuallyRead;
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnWrite}
+ */
+static DECLCALLBACK(int) visoWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ RT_NOREF(uOffset, cbToWrite, pIoCtx, pcbWriteProcess, pcbPreRead, pcbPostRead, fWrite);
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+ AssertReturn(pThis->hIsoFile != NIL_RTVFSFILE, VERR_VD_NOT_OPENED);
+ LogFlowFunc(("pThis=%p off=%#RX64 pIoCtx=%p cbToWrite=%#zx pcbWriteProcess=%p pcbPreRead=%p pcbPostRead=%p -> VERR_VD_IMAGE_READ_ONLY\n",
+ pThis, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ return VERR_VD_IMAGE_READ_ONLY;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnFlush}
+ */
+static DECLCALLBACK(int) visoFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+ AssertReturn(pThis->hIsoFile != NIL_RTVFSFILE, VERR_VD_NOT_OPENED);
+ RT_NOREF(pIoCtx);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetVersion}
+ */
+static DECLCALLBACK(unsigned) visoGetVersion(void *pBackendData)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ AssertPtrReturn(pThis, 0);
+ LogFlowFunc(("pThis=%#p -> 1\n", pThis));
+ return 1;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetFileSize}
+ */
+static DECLCALLBACK(uint64_t) visoGetFileSize(void *pBackendData)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ AssertPtrReturn(pThis, 0);
+ LogFlowFunc(("pThis=%p -> %RX64 (%s)\n", pThis, pThis->cbImage, pThis->hIsoFile == NIL_RTVFSFILE ? "fake!" : "real"));
+ return pThis->cbImage;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetPCHSGeometry}
+ */
+static DECLCALLBACK(int) visoGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+ LogFlowFunc(("pThis=%p pPCHSGeometry=%p -> VERR_NOT_SUPPORTED\n", pThis, pPCHSGeometry));
+ RT_NOREF(pPCHSGeometry);
+ return VERR_NOT_SUPPORTED;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnSetPCHSGeometry}
+ */
+static DECLCALLBACK(int) visoSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+ LogFlowFunc(("pThis=%p pPCHSGeometry=%p:{%u/%u/%u} -> VERR_VD_IMAGE_READ_ONLY\n",
+ pThis, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ RT_NOREF(pPCHSGeometry);
+ return VERR_VD_IMAGE_READ_ONLY;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetLCHSGeometry}
+ */
+static DECLCALLBACK(int) visoGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+ LogFlowFunc(("pThis=%p pLCHSGeometry=%p -> VERR_NOT_SUPPORTED\n", pThis, pLCHSGeometry));
+ RT_NOREF(pLCHSGeometry);
+ return VERR_NOT_SUPPORTED;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnSetLCHSGeometry}
+ */
+static DECLCALLBACK(int) visoSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+ LogFlowFunc(("pThis=%p pLCHSGeometry=%p:{%u/%u/%u} -> VERR_VD_IMAGE_READ_ONLY\n",
+ pThis, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ RT_NOREF(pLCHSGeometry);
+ return VERR_VD_IMAGE_READ_ONLY;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnQueryRegions}
+ */
+static DECLCALLBACK(int) visoQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ *ppRegionList = NULL;
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns VINF_SUCCESS (one region: 0 LB %RX64; pThis=%p)\n", pThis->RegionList.aRegions[0].cbData, pThis));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnRegionListRelease}
+ */
+static DECLCALLBACK(void) visoRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ /* Nothing to do here. Just assert the input to avoid unused parameter warnings. */
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ LogFlowFunc(("pThis=%p pRegionList=%p\n", pThis, pRegionList));
+ AssertPtrReturnVoid(pThis);
+ AssertReturnVoid(pRegionList == &pThis->RegionList || pRegionList == 0);
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetImageFlags}
+ */
+static DECLCALLBACK(unsigned) visoGetImageFlags(void *pBackendData)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ LogFlowFunc(("pThis=%p -> VD_IMAGE_FLAGS_NONE\n", pThis));
+ AssertPtrReturn(pThis, VD_IMAGE_FLAGS_NONE);
+ return VD_IMAGE_FLAGS_NONE;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetOpenFlags}
+ */
+static DECLCALLBACK(unsigned) visoGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ AssertPtrReturn(pThis, 0);
+
+ LogFlowFunc(("returns %#x\n", pThis->fOpenFlags));
+ return pThis->fOpenFlags;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnSetOpenFlags}
+ */
+static DECLCALLBACK(int) visoSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ LogFlowFunc(("pThis=%p fOpenFlags=%#x\n", pThis, uOpenFlags));
+
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ uint32_t const fSupported = VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS;
+ AssertMsgReturn(!(uOpenFlags & ~fSupported), ("fOpenFlags=%#x\n", uOpenFlags), VERR_INVALID_FLAGS);
+
+ /*
+ * Only react if we switch from VD_OPEN_FLAGS_INFO to non-VD_OPEN_FLAGS_INFO mode,
+ * becuase that means we need to open the image.
+ */
+ if ( (pThis->fOpenFlags & VD_OPEN_FLAGS_INFO)
+ && !(uOpenFlags & VD_OPEN_FLAGS_INFO)
+ && pThis->hIsoFile == NIL_RTVFSFILE)
+ {
+ int rc = visoOpenWorker(pThis);
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+ }
+ }
+
+ /*
+ * Update the flags.
+ */
+ pThis->fOpenFlags &= ~fSupported;
+ pThis->fOpenFlags |= fSupported & uOpenFlags;
+ pThis->fOpenFlags |= VD_OPEN_FLAGS_READONLY;
+ if (pThis->hIsoFile != NIL_RTVFSFILE)
+ pThis->fOpenFlags &= ~VD_OPEN_FLAGS_INFO;
+
+ return VINF_SUCCESS;
+}
+
+#define uOpenFlags fOpenFlags /* sigh */
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetComment}
+ */
+VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(visoGetComment);
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnSetComment}
+ */
+VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(visoSetComment, PVISOIMAGE);
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetUuid}
+ */
+static DECLCALLBACK(int) visoGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ *pUuid = pThis->Uuid;
+ LogFlowFunc(("returns VIF_SUCCESS (%RTuuid)\n", pUuid));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnSetUuid}
+ */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(visoSetUuid, PVISOIMAGE);
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetModificationUuid}
+ */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(visoGetModificationUuid);
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnSetModificationUuid}
+ */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(visoSetModificationUuid, PVISOIMAGE);
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetParentUuid}
+ */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(visoGetParentUuid);
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnSetParentUuid}
+ */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(visoSetParentUuid, PVISOIMAGE);
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnGetParentModificationUuid}
+ */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(visoGetParentModificationUuid);
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnSetParentModificationUuid}
+ */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(visoSetParentModificationUuid, PVISOIMAGE);
+
+#undef uOpenFlags
+
+/**
+ * @interface_method_impl{VDIMAGEBACKEND,pfnDump}
+ */
+static DECLCALLBACK(void) visoDump(void *pBackendData)
+{
+ PVISOIMAGE pThis = (PVISOIMAGE)pBackendData;
+ AssertPtrReturnVoid(pThis);
+
+ vdIfErrorMessage(pThis->pIfError, "Dumping CUE image '%s' fOpenFlags=%x cbImage=%#RX64\n",
+ pThis->pszFilename, pThis->fOpenFlags, pThis->cbImage);
+}
+
+
+
+/**
+ * VBox ISO maker backend.
+ */
+const VDIMAGEBACKEND g_VBoxIsoMakerBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "VBoxIsoMaker",
+ /* uBackendCaps */
+ VD_CAP_FILE,
+ /* paFileExtensions */
+ g_aVBoXIsoMakerFileExtensions,
+ /* paConfigInfo */
+ s_aVisoConfigInfo,
+ /* pfnProbe */
+ visoProbe,
+ /* pfnOpen */
+ visoOpen,
+ /* pfnCreate */
+ NULL,
+ /* pfnRename */
+ NULL,
+ /* pfnClose */
+ visoClose,
+ /* pfnRead */
+ visoRead,
+ /* pfnWrite */
+ visoWrite,
+ /* pfnFlush */
+ visoFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ visoGetVersion,
+ /* pfnGetFileSize */
+ visoGetFileSize,
+ /* pfnGetPCHSGeometry */
+ visoGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ visoSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ visoGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ visoSetLCHSGeometry,
+ /* pfnQueryRegions */
+ visoQueryRegions,
+ /* pfnRegionListRelease */
+ visoRegionListRelease,
+ /* pfnGetImageFlags */
+ visoGetImageFlags,
+ /* pfnGetOpenFlags */
+ visoGetOpenFlags,
+ /* pfnSetOpenFlags */
+ visoSetOpenFlags,
+ /* pfnGetComment */
+ visoGetComment,
+ /* pfnSetComment */
+ visoSetComment,
+ /* pfnGetUuid */
+ visoGetUuid,
+ /* pfnSetUuid */
+ visoSetUuid,
+ /* pfnGetModificationUuid */
+ visoGetModificationUuid,
+ /* pfnSetModificationUuid */
+ visoSetModificationUuid,
+ /* pfnGetParentUuid */
+ visoGetParentUuid,
+ /* pfnSetParentUuid */
+ visoSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ visoGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ visoSetParentModificationUuid,
+ /* pfnDump */
+ visoDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ NULL,
+ /* pfnSetParentFilename */
+ NULL,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ NULL,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
+
diff --git a/src/VBox/Storage/VMDK.cpp b/src/VBox/Storage/VMDK.cpp
new file mode 100644
index 00000000..d802034e
--- /dev/null
+++ b/src/VBox/Storage/VMDK.cpp
@@ -0,0 +1,9249 @@
+/* $Id: VMDK.cpp $ */
+/** @file
+ * VMDK disk image, core code.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_VMDK
+#include <VBox/log.h> /* before VBox/vd-ifs.h */
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/base64.h>
+#include <iprt/ctype.h>
+#include <iprt/crc.h>
+#include <iprt/dvm.h>
+#include <iprt/uuid.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+#include <iprt/sort.h>
+#include <iprt/zip.h>
+#include <iprt/asm.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/utf16.h>
+# include <iprt/uni.h>
+# include <iprt/uni.h>
+# include <iprt/nt/nt-and-windows.h>
+# include <winioctl.h>
+#endif
+#ifdef RT_OS_LINUX
+# include <errno.h>
+# include <sys/stat.h>
+# include <iprt/dir.h>
+# include <iprt/symlink.h>
+# include <iprt/linux/sysfs.h>
+#endif
+#ifdef RT_OS_FREEBSD
+#include <libgeom.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#endif
+#ifdef RT_OS_SOLARIS
+#include <sys/dkio.h>
+#include <sys/vtoc.h>
+#include <sys/efi_partition.h>
+#include <unistd.h>
+#include <errno.h>
+#endif
+#ifdef RT_OS_DARWIN
+# include <sys/stat.h>
+# include <sys/disk.h>
+# include <errno.h>
+/* The following structure and IOCTLs are defined in znu bsd/sys/disk.h but
+ inside KERNEL ifdefs and thus stripped from the SDK edition of the header.
+ While we could try include the header from the Kernel.framework, it's a lot
+ easier to just add the structure and 4 defines here. */
+typedef struct
+{
+ uint64_t offset;
+ uint64_t length;
+ uint8_t reserved0128[12];
+ dev_t dev;
+} dk_physical_extent_t;
+# define DKIOCGETBASE _IOR( 'd', 73, uint64_t)
+# define DKIOCLOCKPHYSICALEXTENTS _IO( 'd', 81)
+# define DKIOCGETPHYSICALEXTENT _IOWR('d', 82, dk_physical_extent_t)
+# define DKIOCUNLOCKPHYSICALEXTENTS _IO( 'd', 83)
+#endif /* RT_OS_DARWIN */
+
+#include "VDBackends.h"
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** Maximum encoded string size (including NUL) we allow for VMDK images.
+ * Deliberately not set high to avoid running out of descriptor space. */
+#define VMDK_ENCODED_COMMENT_MAX 1024
+
+/** VMDK descriptor DDB entry for PCHS cylinders. */
+#define VMDK_DDB_GEO_PCHS_CYLINDERS "ddb.geometry.cylinders"
+
+/** VMDK descriptor DDB entry for PCHS heads. */
+#define VMDK_DDB_GEO_PCHS_HEADS "ddb.geometry.heads"
+
+/** VMDK descriptor DDB entry for PCHS sectors. */
+#define VMDK_DDB_GEO_PCHS_SECTORS "ddb.geometry.sectors"
+
+/** VMDK descriptor DDB entry for LCHS cylinders. */
+#define VMDK_DDB_GEO_LCHS_CYLINDERS "ddb.geometry.biosCylinders"
+
+/** VMDK descriptor DDB entry for LCHS heads. */
+#define VMDK_DDB_GEO_LCHS_HEADS "ddb.geometry.biosHeads"
+
+/** VMDK descriptor DDB entry for LCHS sectors. */
+#define VMDK_DDB_GEO_LCHS_SECTORS "ddb.geometry.biosSectors"
+
+/** VMDK descriptor DDB entry for image UUID. */
+#define VMDK_DDB_IMAGE_UUID "ddb.uuid.image"
+
+/** VMDK descriptor DDB entry for image modification UUID. */
+#define VMDK_DDB_MODIFICATION_UUID "ddb.uuid.modification"
+
+/** VMDK descriptor DDB entry for parent image UUID. */
+#define VMDK_DDB_PARENT_UUID "ddb.uuid.parent"
+
+/** VMDK descriptor DDB entry for parent image modification UUID. */
+#define VMDK_DDB_PARENT_MODIFICATION_UUID "ddb.uuid.parentmodification"
+
+/** No compression for streamOptimized files. */
+#define VMDK_COMPRESSION_NONE 0
+
+/** Deflate compression for streamOptimized files. */
+#define VMDK_COMPRESSION_DEFLATE 1
+
+/** Marker that the actual GD value is stored in the footer. */
+#define VMDK_GD_AT_END 0xffffffffffffffffULL
+
+/** Marker for end-of-stream in streamOptimized images. */
+#define VMDK_MARKER_EOS 0
+
+/** Marker for grain table block in streamOptimized images. */
+#define VMDK_MARKER_GT 1
+
+/** Marker for grain directory block in streamOptimized images. */
+#define VMDK_MARKER_GD 2
+
+/** Marker for footer in streamOptimized images. */
+#define VMDK_MARKER_FOOTER 3
+
+/** Marker for unknown purpose in streamOptimized images.
+ * Shows up in very recent images created by vSphere, but only sporadically.
+ * They "forgot" to document that one in the VMDK specification. */
+#define VMDK_MARKER_UNSPECIFIED 4
+
+/** Dummy marker for "don't check the marker value". */
+#define VMDK_MARKER_IGNORE 0xffffffffU
+
+/**
+ * Magic number for hosted images created by VMware Workstation 4, VMware
+ * Workstation 5, VMware Server or VMware Player. Not necessarily sparse.
+ */
+#define VMDK_SPARSE_MAGICNUMBER 0x564d444b /* 'V' 'M' 'D' 'K' */
+
+/** VMDK sector size in bytes. */
+#define VMDK_SECTOR_SIZE 512
+/** Max string buffer size for uint64_t with null term */
+#define UINT64_MAX_BUFF_SIZE 21
+/** Grain directory entry size in bytes */
+#define VMDK_GRAIN_DIR_ENTRY_SIZE 4
+/** Grain table size in bytes */
+#define VMDK_GRAIN_TABLE_SIZE 2048
+
+/**
+ * VMDK hosted binary extent header. The "Sparse" is a total misnomer, as
+ * this header is also used for monolithic flat images.
+ */
+#pragma pack(1)
+typedef struct SparseExtentHeader
+{
+ uint32_t magicNumber;
+ uint32_t version;
+ uint32_t flags;
+ uint64_t capacity;
+ uint64_t grainSize;
+ uint64_t descriptorOffset;
+ uint64_t descriptorSize;
+ uint32_t numGTEsPerGT;
+ uint64_t rgdOffset;
+ uint64_t gdOffset;
+ uint64_t overHead;
+ bool uncleanShutdown;
+ char singleEndLineChar;
+ char nonEndLineChar;
+ char doubleEndLineChar1;
+ char doubleEndLineChar2;
+ uint16_t compressAlgorithm;
+ uint8_t pad[433];
+} SparseExtentHeader;
+#pragma pack()
+
+/** The maximum allowed descriptor size in the extent header in sectors. */
+#define VMDK_SPARSE_DESCRIPTOR_SIZE_MAX UINT64_C(20480) /* 10MB */
+
+/** VMDK capacity for a single chunk when 2G splitting is turned on. Should be
+ * divisible by the default grain size (64K) */
+#define VMDK_2G_SPLIT_SIZE (2047 * 1024 * 1024)
+
+/** VMDK streamOptimized file format marker. The type field may or may not
+ * be actually valid, but there's always data to read there. */
+#pragma pack(1)
+typedef struct VMDKMARKER
+{
+ uint64_t uSector;
+ uint32_t cbSize;
+ uint32_t uType;
+} VMDKMARKER, *PVMDKMARKER;
+#pragma pack()
+
+
+/** Convert sector number/size to byte offset/size. */
+#define VMDK_SECTOR2BYTE(u) ((uint64_t)(u) << 9)
+
+/** Convert byte offset/size to sector number/size. */
+#define VMDK_BYTE2SECTOR(u) ((u) >> 9)
+
+/**
+ * VMDK extent type.
+ */
+typedef enum VMDKETYPE
+{
+ /** Hosted sparse extent. */
+ VMDKETYPE_HOSTED_SPARSE = 1,
+ /** Flat extent. */
+ VMDKETYPE_FLAT,
+ /** Zero extent. */
+ VMDKETYPE_ZERO,
+ /** VMFS extent, used by ESX. */
+ VMDKETYPE_VMFS
+} VMDKETYPE, *PVMDKETYPE;
+
+/**
+ * VMDK access type for a extent.
+ */
+typedef enum VMDKACCESS
+{
+ /** No access allowed. */
+ VMDKACCESS_NOACCESS = 0,
+ /** Read-only access. */
+ VMDKACCESS_READONLY,
+ /** Read-write access. */
+ VMDKACCESS_READWRITE
+} VMDKACCESS, *PVMDKACCESS;
+
+/** Forward declaration for PVMDKIMAGE. */
+typedef struct VMDKIMAGE *PVMDKIMAGE;
+
+/**
+ * Extents files entry. Used for opening a particular file only once.
+ */
+typedef struct VMDKFILE
+{
+ /** Pointer to file path. Local copy. */
+ const char *pszFilename;
+ /** Pointer to base name. Local copy. */
+ const char *pszBasename;
+ /** File open flags for consistency checking. */
+ unsigned fOpen;
+ /** Handle for sync/async file abstraction.*/
+ PVDIOSTORAGE pStorage;
+ /** Reference counter. */
+ unsigned uReferences;
+ /** Flag whether the file should be deleted on last close. */
+ bool fDelete;
+ /** Pointer to the image we belong to (for debugging purposes). */
+ PVMDKIMAGE pImage;
+ /** Pointer to next file descriptor. */
+ struct VMDKFILE *pNext;
+ /** Pointer to the previous file descriptor. */
+ struct VMDKFILE *pPrev;
+} VMDKFILE, *PVMDKFILE;
+
+/**
+ * VMDK extent data structure.
+ */
+typedef struct VMDKEXTENT
+{
+ /** File handle. */
+ PVMDKFILE pFile;
+ /** Base name of the image extent. */
+ const char *pszBasename;
+ /** Full name of the image extent. */
+ const char *pszFullname;
+ /** Number of sectors in this extent. */
+ uint64_t cSectors;
+ /** Number of sectors per block (grain in VMDK speak). */
+ uint64_t cSectorsPerGrain;
+ /** Starting sector number of descriptor. */
+ uint64_t uDescriptorSector;
+ /** Size of descriptor in sectors. */
+ uint64_t cDescriptorSectors;
+ /** Starting sector number of grain directory. */
+ uint64_t uSectorGD;
+ /** Starting sector number of redundant grain directory. */
+ uint64_t uSectorRGD;
+ /** Total number of metadata sectors. */
+ uint64_t cOverheadSectors;
+ /** Nominal size (i.e. as described by the descriptor) of this extent. */
+ uint64_t cNominalSectors;
+ /** Sector offset (i.e. as described by the descriptor) of this extent. */
+ uint64_t uSectorOffset;
+ /** Number of entries in a grain table. */
+ uint32_t cGTEntries;
+ /** Number of sectors reachable via a grain directory entry. */
+ uint32_t cSectorsPerGDE;
+ /** Number of entries in the grain directory. */
+ uint32_t cGDEntries;
+ /** Pointer to the next free sector. Legacy information. Do not use. */
+ uint32_t uFreeSector;
+ /** Number of this extent in the list of images. */
+ uint32_t uExtent;
+ /** Pointer to the descriptor (NULL if no descriptor in this extent). */
+ char *pDescData;
+ /** Pointer to the grain directory. */
+ uint32_t *pGD;
+ /** Pointer to the redundant grain directory. */
+ uint32_t *pRGD;
+ /** VMDK version of this extent. 1=1.0/1.1 */
+ uint32_t uVersion;
+ /** Type of this extent. */
+ VMDKETYPE enmType;
+ /** Access to this extent. */
+ VMDKACCESS enmAccess;
+ /** Flag whether this extent is marked as unclean. */
+ bool fUncleanShutdown;
+ /** Flag whether the metadata in the extent header needs to be updated. */
+ bool fMetaDirty;
+ /** Flag whether there is a footer in this extent. */
+ bool fFooter;
+ /** Compression type for this extent. */
+ uint16_t uCompression;
+ /** Append position for writing new grain. Only for sparse extents. */
+ uint64_t uAppendPosition;
+ /** Last grain which was accessed. Only for streamOptimized extents. */
+ uint32_t uLastGrainAccess;
+ /** Starting sector corresponding to the grain buffer. */
+ uint32_t uGrainSectorAbs;
+ /** Grain number corresponding to the grain buffer. */
+ uint32_t uGrain;
+ /** Actual size of the compressed data, only valid for reading. */
+ uint32_t cbGrainStreamRead;
+ /** Size of compressed grain buffer for streamOptimized extents. */
+ size_t cbCompGrain;
+ /** Compressed grain buffer for streamOptimized extents, with marker. */
+ void *pvCompGrain;
+ /** Decompressed grain buffer for streamOptimized extents. */
+ void *pvGrain;
+ /** Reference to the image in which this extent is used. Do not use this
+ * on a regular basis to avoid passing pImage references to functions
+ * explicitly. */
+ struct VMDKIMAGE *pImage;
+} VMDKEXTENT, *PVMDKEXTENT;
+
+/**
+ * Grain table cache size. Allocated per image.
+ */
+#define VMDK_GT_CACHE_SIZE 256
+
+/**
+ * Grain table block size. Smaller than an actual grain table block to allow
+ * more grain table blocks to be cached without having to allocate excessive
+ * amounts of memory for the cache.
+ */
+#define VMDK_GT_CACHELINE_SIZE 128
+
+
+/**
+ * Maximum number of lines in a descriptor file. Not worth the effort of
+ * making it variable. Descriptor files are generally very short (~20 lines),
+ * with the exception of sparse files split in 2G chunks, which need for the
+ * maximum size (almost 2T) exactly 1025 lines for the disk database.
+ */
+#define VMDK_DESCRIPTOR_LINES_MAX 1100U
+
+/**
+ * Parsed descriptor information. Allows easy access and update of the
+ * descriptor (whether separate file or not). Free form text files suck.
+ */
+typedef struct VMDKDESCRIPTOR
+{
+ /** Line number of first entry of the disk descriptor. */
+ unsigned uFirstDesc;
+ /** Line number of first entry in the extent description. */
+ unsigned uFirstExtent;
+ /** Line number of first disk database entry. */
+ unsigned uFirstDDB;
+ /** Total number of lines. */
+ unsigned cLines;
+ /** Total amount of memory available for the descriptor. */
+ size_t cbDescAlloc;
+ /** Set if descriptor has been changed and not yet written to disk. */
+ bool fDirty;
+ /** Array of pointers to the data in the descriptor. */
+ char *aLines[VMDK_DESCRIPTOR_LINES_MAX];
+ /** Array of line indices pointing to the next non-comment line. */
+ unsigned aNextLines[VMDK_DESCRIPTOR_LINES_MAX];
+} VMDKDESCRIPTOR, *PVMDKDESCRIPTOR;
+
+
+/**
+ * Cache entry for translating extent/sector to a sector number in that
+ * extent.
+ */
+typedef struct VMDKGTCACHEENTRY
+{
+ /** Extent number for which this entry is valid. */
+ uint32_t uExtent;
+ /** GT data block number. */
+ uint64_t uGTBlock;
+ /** Data part of the cache entry. */
+ uint32_t aGTData[VMDK_GT_CACHELINE_SIZE];
+} VMDKGTCACHEENTRY, *PVMDKGTCACHEENTRY;
+
+/**
+ * Cache data structure for blocks of grain table entries. For now this is a
+ * fixed size direct mapping cache, but this should be adapted to the size of
+ * the sparse image and maybe converted to a set-associative cache. The
+ * implementation below implements a write-through cache with write allocate.
+ */
+typedef struct VMDKGTCACHE
+{
+ /** Cache entries. */
+ VMDKGTCACHEENTRY aGTCache[VMDK_GT_CACHE_SIZE];
+ /** Number of cache entries (currently unused). */
+ unsigned cEntries;
+} VMDKGTCACHE, *PVMDKGTCACHE;
+
+/**
+ * Complete VMDK image data structure. Mainly a collection of extents and a few
+ * extra global data fields.
+ */
+typedef struct VMDKIMAGE
+{
+ /** Image name. */
+ const char *pszFilename;
+ /** Descriptor file if applicable. */
+ PVMDKFILE pFile;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+
+
+ /** Pointer to the image extents. */
+ PVMDKEXTENT pExtents;
+ /** Number of image extents. */
+ unsigned cExtents;
+ /** Pointer to the files list, for opening a file referenced multiple
+ * times only once (happens mainly with raw partition access). */
+ PVMDKFILE pFiles;
+
+ /**
+ * Pointer to an array of segment entries for async I/O.
+ * This is an optimization because the task number to submit is not known
+ * and allocating/freeing an array in the read/write functions every time
+ * is too expensive.
+ */
+ PPDMDATASEG paSegments;
+ /** Entries available in the segments array. */
+ unsigned cSegments;
+
+ /** Open flags passed by VBoxHD layer. */
+ unsigned uOpenFlags;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+ /** Physical geometry of this image. */
+ VDGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ VDGEOMETRY LCHSGeometry;
+ /** Image UUID. */
+ RTUUID ImageUuid;
+ /** Image modification UUID. */
+ RTUUID ModificationUuid;
+ /** Parent image UUID. */
+ RTUUID ParentUuid;
+ /** Parent image modification UUID. */
+ RTUUID ParentModificationUuid;
+
+ /** Pointer to grain table cache, if this image contains sparse extents. */
+ PVMDKGTCACHE pGTCache;
+ /** Pointer to the descriptor (NULL if no separate descriptor file). */
+ char *pDescData;
+ /** Allocation size of the descriptor file. */
+ size_t cbDescAlloc;
+ /** Parsed descriptor file content. */
+ VMDKDESCRIPTOR Descriptor;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} VMDKIMAGE;
+
+
+/** State for the input/output callout of the inflate reader/deflate writer. */
+typedef struct VMDKCOMPRESSIO
+{
+ /* Image this operation relates to. */
+ PVMDKIMAGE pImage;
+ /* Current read position. */
+ ssize_t iOffset;
+ /* Size of the compressed grain buffer (available data). */
+ size_t cbCompGrain;
+ /* Pointer to the compressed grain buffer. */
+ void *pvCompGrain;
+} VMDKCOMPRESSIO;
+
+
+/** Tracks async grain allocation. */
+typedef struct VMDKGRAINALLOCASYNC
+{
+ /** Flag whether the allocation failed. */
+ bool fIoErr;
+ /** Current number of transfers pending.
+ * If reached 0 and there is an error the old state is restored. */
+ unsigned cIoXfersPending;
+ /** Sector number */
+ uint64_t uSector;
+ /** Flag whether the grain table needs to be updated. */
+ bool fGTUpdateNeeded;
+ /** Extent the allocation happens. */
+ PVMDKEXTENT pExtent;
+ /** Position of the new grain, required for the grain table update. */
+ uint64_t uGrainOffset;
+ /** Grain table sector. */
+ uint64_t uGTSector;
+ /** Backup grain table sector. */
+ uint64_t uRGTSector;
+} VMDKGRAINALLOCASYNC, *PVMDKGRAINALLOCASYNC;
+
+/**
+ * State information for vmdkRename() and helpers.
+ */
+typedef struct VMDKRENAMESTATE
+{
+ /** Array of old filenames. */
+ char **apszOldName;
+ /** Array of new filenames. */
+ char **apszNewName;
+ /** Array of new lines in the extent descriptor. */
+ char **apszNewLines;
+ /** Name of the old descriptor file if not a sparse image. */
+ char *pszOldDescName;
+ /** Flag whether we called vmdkFreeImage(). */
+ bool fImageFreed;
+ /** Flag whther the descriptor is embedded in the image (sparse) or
+ * in a separate file. */
+ bool fEmbeddedDesc;
+ /** Number of extents in the image. */
+ unsigned cExtents;
+ /** New base filename. */
+ char *pszNewBaseName;
+ /** The old base filename. */
+ char *pszOldBaseName;
+ /** New full filename. */
+ char *pszNewFullName;
+ /** Old full filename. */
+ char *pszOldFullName;
+ /** The old image name. */
+ const char *pszOldImageName;
+ /** Copy of the original VMDK descriptor. */
+ VMDKDESCRIPTOR DescriptorCopy;
+ /** Copy of the extent state for sparse images. */
+ VMDKEXTENT ExtentCopy;
+} VMDKRENAMESTATE;
+/** Pointer to a VMDK rename state. */
+typedef VMDKRENAMESTATE *PVMDKRENAMESTATE;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aVmdkFileExtensions[] =
+{
+ {"vmdk", VDTYPE_HDD},
+ {NULL, VDTYPE_INVALID}
+};
+
+/** NULL-terminated array of configuration option. */
+static const VDCONFIGINFO s_aVmdkConfigInfo[] =
+{
+ /* Options for VMDK raw disks */
+ { "RawDrive", NULL, VDCFGVALUETYPE_STRING, 0 },
+ { "Partitions", NULL, VDCFGVALUETYPE_STRING, 0 },
+ { "BootSector", NULL, VDCFGVALUETYPE_BYTES, 0 },
+ { "Relative", NULL, VDCFGVALUETYPE_INTEGER, 0 },
+
+ /* End of options list */
+ { NULL, NULL, VDCFGVALUETYPE_INTEGER, 0 }
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+static void vmdkFreeStreamBuffers(PVMDKEXTENT pExtent);
+static int vmdkFreeExtentData(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ bool fDelete);
+
+static int vmdkCreateExtents(PVMDKIMAGE pImage, unsigned cExtents);
+static int vmdkFlushImage(PVMDKIMAGE pImage, PVDIOCTX pIoCtx);
+static int vmdkSetImageComment(PVMDKIMAGE pImage, const char *pszComment);
+static int vmdkFreeImage(PVMDKIMAGE pImage, bool fDelete, bool fFlush);
+
+static DECLCALLBACK(int) vmdkAllocGrainComplete(void *pBackendData, PVDIOCTX pIoCtx,
+ void *pvUser, int rcReq);
+
+/**
+ * Internal: open a file (using a file descriptor cache to ensure each file
+ * is only opened once - anything else can cause locking problems).
+ */
+static int vmdkFileOpen(PVMDKIMAGE pImage, PVMDKFILE *ppVmdkFile,
+ const char *pszBasename, const char *pszFilename, uint32_t fOpen)
+{
+ int rc = VINF_SUCCESS;
+ PVMDKFILE pVmdkFile;
+
+ for (pVmdkFile = pImage->pFiles;
+ pVmdkFile != NULL;
+ pVmdkFile = pVmdkFile->pNext)
+ {
+ if (!strcmp(pszFilename, pVmdkFile->pszFilename))
+ {
+ Assert(fOpen == pVmdkFile->fOpen);
+ pVmdkFile->uReferences++;
+
+ *ppVmdkFile = pVmdkFile;
+
+ return rc;
+ }
+ }
+
+ /* If we get here, there's no matching entry in the cache. */
+ pVmdkFile = (PVMDKFILE)RTMemAllocZ(sizeof(VMDKFILE));
+ if (!pVmdkFile)
+ {
+ *ppVmdkFile = NULL;
+ return VERR_NO_MEMORY;
+ }
+
+ pVmdkFile->pszFilename = RTStrDup(pszFilename);
+ if (!pVmdkFile->pszFilename)
+ {
+ RTMemFree(pVmdkFile);
+ *ppVmdkFile = NULL;
+ return VERR_NO_MEMORY;
+ }
+
+ if (pszBasename)
+ {
+ pVmdkFile->pszBasename = RTStrDup(pszBasename);
+ if (!pVmdkFile->pszBasename)
+ {
+ RTStrFree((char *)(void *)pVmdkFile->pszFilename);
+ RTMemFree(pVmdkFile);
+ *ppVmdkFile = NULL;
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ pVmdkFile->fOpen = fOpen;
+
+ rc = vdIfIoIntFileOpen(pImage->pIfIo, pszFilename, fOpen,
+ &pVmdkFile->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ pVmdkFile->uReferences = 1;
+ pVmdkFile->pImage = pImage;
+ pVmdkFile->pNext = pImage->pFiles;
+ if (pImage->pFiles)
+ pImage->pFiles->pPrev = pVmdkFile;
+ pImage->pFiles = pVmdkFile;
+ *ppVmdkFile = pVmdkFile;
+ }
+ else
+ {
+ RTStrFree((char *)(void *)pVmdkFile->pszFilename);
+ RTMemFree(pVmdkFile);
+ *ppVmdkFile = NULL;
+ }
+
+ return rc;
+}
+
+/**
+ * Internal: close a file, updating the file descriptor cache.
+ */
+static int vmdkFileClose(PVMDKIMAGE pImage, PVMDKFILE *ppVmdkFile, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+ PVMDKFILE pVmdkFile = *ppVmdkFile;
+
+ AssertPtr(pVmdkFile);
+
+ pVmdkFile->fDelete |= fDelete;
+ Assert(pVmdkFile->uReferences);
+ pVmdkFile->uReferences--;
+ if (pVmdkFile->uReferences == 0)
+ {
+ PVMDKFILE pPrev;
+ PVMDKFILE pNext;
+
+ /* Unchain the element from the list. */
+ pPrev = pVmdkFile->pPrev;
+ pNext = pVmdkFile->pNext;
+
+ if (pNext)
+ pNext->pPrev = pPrev;
+ if (pPrev)
+ pPrev->pNext = pNext;
+ else
+ pImage->pFiles = pNext;
+
+ rc = vdIfIoIntFileClose(pImage->pIfIo, pVmdkFile->pStorage);
+
+ bool fFileDel = pVmdkFile->fDelete;
+ if ( pVmdkFile->pszBasename
+ && fFileDel)
+ {
+ const char *pszSuffix = RTPathSuffix(pVmdkFile->pszBasename);
+ if ( RTPathHasPath(pVmdkFile->pszBasename)
+ || !pszSuffix
+ || ( strcmp(pszSuffix, ".vmdk")
+ && strcmp(pszSuffix, ".bin")
+ && strcmp(pszSuffix, ".img")))
+ fFileDel = false;
+ }
+
+ if (fFileDel)
+ {
+ int rc2 = vdIfIoIntFileDelete(pImage->pIfIo, pVmdkFile->pszFilename);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ else if (pVmdkFile->fDelete)
+ LogRel(("VMDK: Denying deletion of %s\n", pVmdkFile->pszBasename));
+ RTStrFree((char *)(void *)pVmdkFile->pszFilename);
+ if (pVmdkFile->pszBasename)
+ RTStrFree((char *)(void *)pVmdkFile->pszBasename);
+ RTMemFree(pVmdkFile);
+ }
+
+ *ppVmdkFile = NULL;
+ return rc;
+}
+
+/*#define VMDK_USE_BLOCK_DECOMP_API - test and enable */
+#ifndef VMDK_USE_BLOCK_DECOMP_API
+static DECLCALLBACK(int) vmdkFileInflateHelper(void *pvUser, void *pvBuf, size_t cbBuf, size_t *pcbBuf)
+{
+ VMDKCOMPRESSIO *pInflateState = (VMDKCOMPRESSIO *)pvUser;
+ size_t cbInjected = 0;
+
+ Assert(cbBuf);
+ if (pInflateState->iOffset < 0)
+ {
+ *(uint8_t *)pvBuf = RTZIPTYPE_ZLIB;
+ pvBuf = (uint8_t *)pvBuf + 1;
+ cbBuf--;
+ cbInjected = 1;
+ pInflateState->iOffset = RT_UOFFSETOF(VMDKMARKER, uType);
+ }
+ if (!cbBuf)
+ {
+ if (pcbBuf)
+ *pcbBuf = cbInjected;
+ return VINF_SUCCESS;
+ }
+ cbBuf = RT_MIN(cbBuf, pInflateState->cbCompGrain - pInflateState->iOffset);
+ memcpy(pvBuf,
+ (uint8_t *)pInflateState->pvCompGrain + pInflateState->iOffset,
+ cbBuf);
+ pInflateState->iOffset += cbBuf;
+ Assert(pcbBuf);
+ *pcbBuf = cbBuf + cbInjected;
+ return VINF_SUCCESS;
+}
+#endif
+
+/**
+ * Internal: read from a file and inflate the compressed data,
+ * distinguishing between async and normal operation
+ */
+DECLINLINE(int) vmdkFileInflateSync(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ uint64_t uOffset, void *pvBuf,
+ size_t cbToRead, const void *pcvMarker,
+ uint64_t *puLBA, uint32_t *pcbMarkerData)
+{
+ int rc;
+#ifndef VMDK_USE_BLOCK_DECOMP_API
+ PRTZIPDECOMP pZip = NULL;
+#endif
+ VMDKMARKER *pMarker = (VMDKMARKER *)pExtent->pvCompGrain;
+ size_t cbCompSize, cbActuallyRead;
+
+ if (!pcvMarker)
+ {
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ uOffset, pMarker, RT_UOFFSETOF(VMDKMARKER, uType));
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ memcpy(pMarker, pcvMarker, RT_UOFFSETOF(VMDKMARKER, uType));
+ /* pcvMarker endianness has already been partially transformed, fix it */
+ pMarker->uSector = RT_H2LE_U64(pMarker->uSector);
+ pMarker->cbSize = RT_H2LE_U32(pMarker->cbSize);
+ }
+
+ cbCompSize = RT_LE2H_U32(pMarker->cbSize);
+ if (cbCompSize == 0)
+ {
+ AssertMsgFailed(("VMDK: corrupted marker\n"));
+ return VERR_VD_VMDK_INVALID_FORMAT;
+ }
+
+ /* Sanity check - the expansion ratio should be much less than 2. */
+ Assert(cbCompSize < 2 * cbToRead);
+ if (cbCompSize >= 2 * cbToRead)
+ return VERR_VD_VMDK_INVALID_FORMAT;
+
+ /* Compressed grain marker. Data follows immediately. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ uOffset + RT_UOFFSETOF(VMDKMARKER, uType),
+ (uint8_t *)pExtent->pvCompGrain
+ + RT_UOFFSETOF(VMDKMARKER, uType),
+ RT_ALIGN_Z( cbCompSize
+ + RT_UOFFSETOF(VMDKMARKER, uType),
+ 512)
+ - RT_UOFFSETOF(VMDKMARKER, uType));
+
+ if (puLBA)
+ *puLBA = RT_LE2H_U64(pMarker->uSector);
+ if (pcbMarkerData)
+ *pcbMarkerData = RT_ALIGN( cbCompSize
+ + RT_UOFFSETOF(VMDKMARKER, uType),
+ 512);
+
+#ifdef VMDK_USE_BLOCK_DECOMP_API
+ rc = RTZipBlockDecompress(RTZIPTYPE_ZLIB, 0 /*fFlags*/,
+ pExtent->pvCompGrain, cbCompSize + RT_UOFFSETOF(VMDKMARKER, uType), NULL,
+ pvBuf, cbToRead, &cbActuallyRead);
+#else
+ VMDKCOMPRESSIO InflateState;
+ InflateState.pImage = pImage;
+ InflateState.iOffset = -1;
+ InflateState.cbCompGrain = cbCompSize + RT_UOFFSETOF(VMDKMARKER, uType);
+ InflateState.pvCompGrain = pExtent->pvCompGrain;
+
+ rc = RTZipDecompCreate(&pZip, &InflateState, vmdkFileInflateHelper);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTZipDecompress(pZip, pvBuf, cbToRead, &cbActuallyRead);
+ RTZipDecompDestroy(pZip);
+#endif /* !VMDK_USE_BLOCK_DECOMP_API */
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_ZIP_CORRUPTED)
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: Compressed image is corrupted '%s'"), pExtent->pszFullname);
+ return rc;
+ }
+ if (cbActuallyRead != cbToRead)
+ rc = VERR_VD_VMDK_INVALID_FORMAT;
+ return rc;
+}
+
+static DECLCALLBACK(int) vmdkFileDeflateHelper(void *pvUser, const void *pvBuf, size_t cbBuf)
+{
+ VMDKCOMPRESSIO *pDeflateState = (VMDKCOMPRESSIO *)pvUser;
+
+ Assert(cbBuf);
+ if (pDeflateState->iOffset < 0)
+ {
+ pvBuf = (const uint8_t *)pvBuf + 1;
+ cbBuf--;
+ pDeflateState->iOffset = RT_UOFFSETOF(VMDKMARKER, uType);
+ }
+ if (!cbBuf)
+ return VINF_SUCCESS;
+ if (pDeflateState->iOffset + cbBuf > pDeflateState->cbCompGrain)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy((uint8_t *)pDeflateState->pvCompGrain + pDeflateState->iOffset,
+ pvBuf, cbBuf);
+ pDeflateState->iOffset += cbBuf;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: deflate the uncompressed data and write to a file,
+ * distinguishing between async and normal operation
+ */
+DECLINLINE(int) vmdkFileDeflateSync(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ uint64_t uOffset, const void *pvBuf,
+ size_t cbToWrite, uint64_t uLBA,
+ uint32_t *pcbMarkerData)
+{
+ int rc;
+ PRTZIPCOMP pZip = NULL;
+ VMDKCOMPRESSIO DeflateState;
+
+ DeflateState.pImage = pImage;
+ DeflateState.iOffset = -1;
+ DeflateState.cbCompGrain = pExtent->cbCompGrain;
+ DeflateState.pvCompGrain = pExtent->pvCompGrain;
+
+ rc = RTZipCompCreate(&pZip, &DeflateState, vmdkFileDeflateHelper,
+ RTZIPTYPE_ZLIB, RTZIPLEVEL_DEFAULT);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTZipCompress(pZip, pvBuf, cbToWrite);
+ if (RT_SUCCESS(rc))
+ rc = RTZipCompFinish(pZip);
+ RTZipCompDestroy(pZip);
+ if (RT_SUCCESS(rc))
+ {
+ Assert( DeflateState.iOffset > 0
+ && (size_t)DeflateState.iOffset <= DeflateState.cbCompGrain);
+
+ /* pad with zeroes to get to a full sector size */
+ uint32_t uSize = DeflateState.iOffset;
+ if (uSize % 512)
+ {
+ uint32_t uSizeAlign = RT_ALIGN(uSize, 512);
+ memset((uint8_t *)pExtent->pvCompGrain + uSize, '\0',
+ uSizeAlign - uSize);
+ uSize = uSizeAlign;
+ }
+
+ if (pcbMarkerData)
+ *pcbMarkerData = uSize;
+
+ /* Compressed grain marker. Data follows immediately. */
+ VMDKMARKER *pMarker = (VMDKMARKER *)pExtent->pvCompGrain;
+ pMarker->uSector = RT_H2LE_U64(uLBA);
+ pMarker->cbSize = RT_H2LE_U32( DeflateState.iOffset
+ - RT_UOFFSETOF(VMDKMARKER, uType));
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ uOffset, pMarker, uSize);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Internal: check if all files are closed, prevent leaking resources.
+ */
+static int vmdkFileCheckAllClose(PVMDKIMAGE pImage)
+{
+ int rc = VINF_SUCCESS, rc2;
+ PVMDKFILE pVmdkFile;
+
+ Assert(pImage->pFiles == NULL);
+ for (pVmdkFile = pImage->pFiles;
+ pVmdkFile != NULL;
+ pVmdkFile = pVmdkFile->pNext)
+ {
+ LogRel(("VMDK: leaking reference to file \"%s\"\n",
+ pVmdkFile->pszFilename));
+ pImage->pFiles = pVmdkFile->pNext;
+
+ rc2 = vmdkFileClose(pImage, &pVmdkFile, pVmdkFile->fDelete);
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
+/**
+ * Internal: truncate a string (at a UTF8 code point boundary) and encode the
+ * critical non-ASCII characters.
+ */
+static char *vmdkEncodeString(const char *psz)
+{
+ char szEnc[VMDK_ENCODED_COMMENT_MAX + 3];
+ char *pszDst = szEnc;
+
+ AssertPtr(psz);
+
+ for (; *psz; psz = RTStrNextCp(psz))
+ {
+ char *pszDstPrev = pszDst;
+ RTUNICP Cp = RTStrGetCp(psz);
+ if (Cp == '\\')
+ {
+ pszDst = RTStrPutCp(pszDst, Cp);
+ pszDst = RTStrPutCp(pszDst, Cp);
+ }
+ else if (Cp == '\n')
+ {
+ pszDst = RTStrPutCp(pszDst, '\\');
+ pszDst = RTStrPutCp(pszDst, 'n');
+ }
+ else if (Cp == '\r')
+ {
+ pszDst = RTStrPutCp(pszDst, '\\');
+ pszDst = RTStrPutCp(pszDst, 'r');
+ }
+ else
+ pszDst = RTStrPutCp(pszDst, Cp);
+ if (pszDst - szEnc >= VMDK_ENCODED_COMMENT_MAX - 1)
+ {
+ pszDst = pszDstPrev;
+ break;
+ }
+ }
+ *pszDst = '\0';
+ return RTStrDup(szEnc);
+}
+
+/**
+ * Internal: decode a string and store it into the specified string.
+ */
+static int vmdkDecodeString(const char *pszEncoded, char *psz, size_t cb)
+{
+ int rc = VINF_SUCCESS;
+ char szBuf[4];
+
+ if (!cb)
+ return VERR_BUFFER_OVERFLOW;
+
+ AssertPtr(psz);
+
+ for (; *pszEncoded; pszEncoded = RTStrNextCp(pszEncoded))
+ {
+ char *pszDst = szBuf;
+ RTUNICP Cp = RTStrGetCp(pszEncoded);
+ if (Cp == '\\')
+ {
+ pszEncoded = RTStrNextCp(pszEncoded);
+ RTUNICP CpQ = RTStrGetCp(pszEncoded);
+ if (CpQ == 'n')
+ RTStrPutCp(pszDst, '\n');
+ else if (CpQ == 'r')
+ RTStrPutCp(pszDst, '\r');
+ else if (CpQ == '\0')
+ {
+ rc = VERR_VD_VMDK_INVALID_HEADER;
+ break;
+ }
+ else
+ RTStrPutCp(pszDst, CpQ);
+ }
+ else
+ pszDst = RTStrPutCp(pszDst, Cp);
+
+ /* Need to leave space for terminating NUL. */
+ if ((size_t)(pszDst - szBuf) + 1 >= cb)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ memcpy(psz, szBuf, pszDst - szBuf);
+ psz += pszDst - szBuf;
+ }
+ *psz = '\0';
+ return rc;
+}
+
+/**
+ * Internal: free all buffers associated with grain directories.
+ */
+static void vmdkFreeGrainDirectory(PVMDKEXTENT pExtent)
+{
+ if (pExtent->pGD)
+ {
+ RTMemFree(pExtent->pGD);
+ pExtent->pGD = NULL;
+ }
+ if (pExtent->pRGD)
+ {
+ RTMemFree(pExtent->pRGD);
+ pExtent->pRGD = NULL;
+ }
+}
+
+/**
+ * Internal: allocate the compressed/uncompressed buffers for streamOptimized
+ * images.
+ */
+static int vmdkAllocStreamBuffers(PVMDKIMAGE pImage, PVMDKEXTENT pExtent)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ /* streamOptimized extents need a compressed grain buffer, which must
+ * be big enough to hold uncompressible data (which needs ~8 bytes
+ * more than the uncompressed data), the marker and padding. */
+ pExtent->cbCompGrain = RT_ALIGN_Z( VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain)
+ + 8 + sizeof(VMDKMARKER), 512);
+ pExtent->pvCompGrain = RTMemAlloc(pExtent->cbCompGrain);
+ if (RT_LIKELY(pExtent->pvCompGrain))
+ {
+ /* streamOptimized extents need a decompressed grain buffer. */
+ pExtent->pvGrain = RTMemAlloc(VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ if (!pExtent->pvGrain)
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ vmdkFreeStreamBuffers(pExtent);
+ return rc;
+}
+
+/**
+ * Internal: allocate all buffers associated with grain directories.
+ */
+static int vmdkAllocGrainDirectory(PVMDKIMAGE pImage, PVMDKEXTENT pExtent)
+{
+ RT_NOREF1(pImage);
+ int rc = VINF_SUCCESS;
+ size_t cbGD = pExtent->cGDEntries * sizeof(uint32_t);
+
+ pExtent->pGD = (uint32_t *)RTMemAllocZ(cbGD);
+ if (RT_LIKELY(pExtent->pGD))
+ {
+ if (pExtent->uSectorRGD)
+ {
+ pExtent->pRGD = (uint32_t *)RTMemAllocZ(cbGD);
+ if (RT_UNLIKELY(!pExtent->pRGD))
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ vmdkFreeGrainDirectory(pExtent);
+ return rc;
+}
+
+/**
+ * Converts the grain directory from little to host endianess.
+ *
+ * @returns nothing.
+ * @param pGD The grain directory.
+ * @param cGDEntries Number of entries in the grain directory to convert.
+ */
+DECLINLINE(void) vmdkGrainDirectoryConvToHost(uint32_t *pGD, uint32_t cGDEntries)
+{
+ uint32_t *pGDTmp = pGD;
+
+ for (uint32_t i = 0; i < cGDEntries; i++, pGDTmp++)
+ *pGDTmp = RT_LE2H_U32(*pGDTmp);
+}
+
+/**
+ * Read the grain directory and allocated grain tables verifying them against
+ * their back up copies if available.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param pExtent The VMDK extent.
+ */
+static int vmdkReadGrainDirectory(PVMDKIMAGE pImage, PVMDKEXTENT pExtent)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbGD = pExtent->cGDEntries * sizeof(uint32_t);
+
+ AssertReturn(( pExtent->enmType == VMDKETYPE_HOSTED_SPARSE
+ && pExtent->uSectorGD != VMDK_GD_AT_END
+ && pExtent->uSectorRGD != VMDK_GD_AT_END), VERR_INTERNAL_ERROR);
+
+ rc = vmdkAllocGrainDirectory(pImage, pExtent);
+ if (RT_SUCCESS(rc))
+ {
+ /* The VMDK 1.1 spec seems to talk about compressed grain directories,
+ * but in reality they are not compressed. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorGD),
+ pExtent->pGD, cbGD);
+ if (RT_SUCCESS(rc))
+ {
+ vmdkGrainDirectoryConvToHost(pExtent->pGD, pExtent->cGDEntries);
+
+ if ( pExtent->uSectorRGD
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS))
+ {
+ /* The VMDK 1.1 spec seems to talk about compressed grain directories,
+ * but in reality they are not compressed. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorRGD),
+ pExtent->pRGD, cbGD);
+ if (RT_SUCCESS(rc))
+ {
+ vmdkGrainDirectoryConvToHost(pExtent->pRGD, pExtent->cGDEntries);
+
+ /* Check grain table and redundant grain table for consistency. */
+ size_t cbGT = pExtent->cGTEntries * sizeof(uint32_t);
+ size_t cbGTBuffers = cbGT; /* Start with space for one GT. */
+ size_t cbGTBuffersMax = _1M;
+
+ uint32_t *pTmpGT1 = (uint32_t *)RTMemAlloc(cbGTBuffers);
+ uint32_t *pTmpGT2 = (uint32_t *)RTMemAlloc(cbGTBuffers);
+
+ if ( !pTmpGT1
+ || !pTmpGT2)
+ rc = VERR_NO_MEMORY;
+
+ size_t i = 0;
+ uint32_t *pGDTmp = pExtent->pGD;
+ uint32_t *pRGDTmp = pExtent->pRGD;
+
+ /* Loop through all entries. */
+ while (i < pExtent->cGDEntries)
+ {
+ uint32_t uGTStart = *pGDTmp;
+ uint32_t uRGTStart = *pRGDTmp;
+ size_t cbGTRead = cbGT;
+
+ /* If no grain table is allocated skip the entry. */
+ if (*pGDTmp == 0 && *pRGDTmp == 0)
+ {
+ i++;
+ continue;
+ }
+
+ if (*pGDTmp == 0 || *pRGDTmp == 0 || *pGDTmp == *pRGDTmp)
+ {
+ /* Just one grain directory entry refers to a not yet allocated
+ * grain table or both grain directory copies refer to the same
+ * grain table. Not allowed. */
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: inconsistent references to grain directory in '%s'"), pExtent->pszFullname);
+ break;
+ }
+
+ i++;
+ pGDTmp++;
+ pRGDTmp++;
+
+ /*
+ * Read a few tables at once if adjacent to decrease the number
+ * of I/O requests. Read at maximum 1MB at once.
+ */
+ while ( i < pExtent->cGDEntries
+ && cbGTRead < cbGTBuffersMax)
+ {
+ /* If no grain table is allocated skip the entry. */
+ if (*pGDTmp == 0 && *pRGDTmp == 0)
+ {
+ i++;
+ continue;
+ }
+
+ if (*pGDTmp == 0 || *pRGDTmp == 0 || *pGDTmp == *pRGDTmp)
+ {
+ /* Just one grain directory entry refers to a not yet allocated
+ * grain table or both grain directory copies refer to the same
+ * grain table. Not allowed. */
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: inconsistent references to grain directory in '%s'"), pExtent->pszFullname);
+ break;
+ }
+
+ /* Check that the start offsets are adjacent.*/
+ if ( VMDK_SECTOR2BYTE(uGTStart) + cbGTRead != VMDK_SECTOR2BYTE(*pGDTmp)
+ || VMDK_SECTOR2BYTE(uRGTStart) + cbGTRead != VMDK_SECTOR2BYTE(*pRGDTmp))
+ break;
+
+ i++;
+ pGDTmp++;
+ pRGDTmp++;
+ cbGTRead += cbGT;
+ }
+
+ /* Increase buffers if required. */
+ if ( RT_SUCCESS(rc)
+ && cbGTBuffers < cbGTRead)
+ {
+ uint32_t *pTmp;
+ pTmp = (uint32_t *)RTMemRealloc(pTmpGT1, cbGTRead);
+ if (pTmp)
+ {
+ pTmpGT1 = pTmp;
+ pTmp = (uint32_t *)RTMemRealloc(pTmpGT2, cbGTRead);
+ if (pTmp)
+ pTmpGT2 = pTmp;
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (rc == VERR_NO_MEMORY)
+ {
+ /* Reset to the old values. */
+ rc = VINF_SUCCESS;
+ i -= cbGTRead / cbGT;
+ cbGTRead = cbGT;
+
+ /* Don't try to increase the buffer again in the next run. */
+ cbGTBuffersMax = cbGTBuffers;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* The VMDK 1.1 spec seems to talk about compressed grain tables,
+ * but in reality they are not compressed. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uGTStart),
+ pTmpGT1, cbGTRead);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: error reading grain table in '%s'"), pExtent->pszFullname);
+ break;
+ }
+ /* The VMDK 1.1 spec seems to talk about compressed grain tables,
+ * but in reality they are not compressed. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uRGTStart),
+ pTmpGT2, cbGTRead);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: error reading backup grain table in '%s'"), pExtent->pszFullname);
+ break;
+ }
+ if (memcmp(pTmpGT1, pTmpGT2, cbGTRead))
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: inconsistency between grain table and backup grain table in '%s'"), pExtent->pszFullname);
+ break;
+ }
+ }
+ } /* while (i < pExtent->cGDEntries) */
+
+ /** @todo figure out what to do for unclean VMDKs. */
+ if (pTmpGT1)
+ RTMemFree(pTmpGT1);
+ if (pTmpGT2)
+ RTMemFree(pTmpGT2);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: could not read redundant grain directory in '%s'"), pExtent->pszFullname);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: could not read grain directory in '%s': %Rrc"), pExtent->pszFullname, rc);
+ }
+
+ if (RT_FAILURE(rc))
+ vmdkFreeGrainDirectory(pExtent);
+ return rc;
+}
+
+/**
+ * Creates a new grain directory for the given extent at the given start sector.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param pExtent The VMDK extent.
+ * @param uStartSector Where the grain directory should be stored in the image.
+ * @param fPreAlloc Flag whether to pre allocate the grain tables at this point.
+ */
+static int vmdkCreateGrainDirectory(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ uint64_t uStartSector, bool fPreAlloc)
+{
+ int rc = VINF_SUCCESS;
+ unsigned i;
+ size_t cbGD = pExtent->cGDEntries * sizeof(uint32_t);
+ size_t cbGDRounded = RT_ALIGN_64(cbGD, 512);
+ size_t cbGTRounded;
+ uint64_t cbOverhead;
+
+ if (fPreAlloc)
+ {
+ cbGTRounded = RT_ALIGN_64(pExtent->cGDEntries * pExtent->cGTEntries * sizeof(uint32_t), 512);
+ cbOverhead = VMDK_SECTOR2BYTE(uStartSector) + cbGDRounded + cbGTRounded;
+ }
+ else
+ {
+ /* Use a dummy start sector for layout computation. */
+ if (uStartSector == VMDK_GD_AT_END)
+ uStartSector = 1;
+ cbGTRounded = 0;
+ cbOverhead = VMDK_SECTOR2BYTE(uStartSector) + cbGDRounded;
+ }
+
+ /* For streamOptimized extents there is only one grain directory,
+ * and for all others take redundant grain directory into account. */
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ cbOverhead = RT_ALIGN_64(cbOverhead,
+ VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ }
+ else
+ {
+ cbOverhead += cbGDRounded + cbGTRounded;
+ cbOverhead = RT_ALIGN_64(cbOverhead,
+ VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pExtent->pFile->pStorage, cbOverhead);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pExtent->uAppendPosition = cbOverhead;
+ pExtent->cOverheadSectors = VMDK_BYTE2SECTOR(cbOverhead);
+
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ pExtent->uSectorRGD = 0;
+ pExtent->uSectorGD = uStartSector;
+ }
+ else
+ {
+ pExtent->uSectorRGD = uStartSector;
+ pExtent->uSectorGD = uStartSector + VMDK_BYTE2SECTOR(cbGDRounded + cbGTRounded);
+ }
+
+ rc = vmdkAllocStreamBuffers(pImage, pExtent);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vmdkAllocGrainDirectory(pImage, pExtent);
+ if ( RT_SUCCESS(rc)
+ && fPreAlloc)
+ {
+ uint32_t uGTSectorLE;
+ uint64_t uOffsetSectors;
+
+ if (pExtent->pRGD)
+ {
+ uOffsetSectors = pExtent->uSectorRGD + VMDK_BYTE2SECTOR(cbGDRounded);
+ for (i = 0; i < pExtent->cGDEntries; i++)
+ {
+ pExtent->pRGD[i] = uOffsetSectors;
+ uGTSectorLE = RT_H2LE_U64(uOffsetSectors);
+ /* Write the redundant grain directory entry to disk. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorRGD) + i * sizeof(uGTSectorLE),
+ &uGTSectorLE, sizeof(uGTSectorLE));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write new redundant grain directory entry in '%s'"), pExtent->pszFullname);
+ break;
+ }
+ uOffsetSectors += VMDK_BYTE2SECTOR(pExtent->cGTEntries * sizeof(uint32_t));
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ uOffsetSectors = pExtent->uSectorGD + VMDK_BYTE2SECTOR(cbGDRounded);
+ for (i = 0; i < pExtent->cGDEntries; i++)
+ {
+ pExtent->pGD[i] = uOffsetSectors;
+ uGTSectorLE = RT_H2LE_U64(uOffsetSectors);
+ /* Write the grain directory entry to disk. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorGD) + i * sizeof(uGTSectorLE),
+ &uGTSectorLE, sizeof(uGTSectorLE));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write new grain directory entry in '%s'"), pExtent->pszFullname);
+ break;
+ }
+ uOffsetSectors += VMDK_BYTE2SECTOR(pExtent->cGTEntries * sizeof(uint32_t));
+ }
+ }
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ vmdkFreeGrainDirectory(pExtent);
+ return rc;
+}
+
+/**
+ * Unquotes the given string returning the result in a separate buffer.
+ *
+ * @returns VBox status code.
+ * @param pImage The VMDK image state.
+ * @param pszStr The string to unquote.
+ * @param ppszUnquoted Where to store the return value, use RTMemTmpFree to
+ * free.
+ * @param ppszNext Where to store the pointer to any character following
+ * the quoted value, optional.
+ */
+static int vmdkStringUnquote(PVMDKIMAGE pImage, const char *pszStr,
+ char **ppszUnquoted, char **ppszNext)
+{
+ const char *pszStart = pszStr;
+ char *pszQ;
+ char *pszUnquoted;
+
+ /* Skip over whitespace. */
+ while (*pszStr == ' ' || *pszStr == '\t')
+ pszStr++;
+
+ if (*pszStr != '"')
+ {
+ pszQ = (char *)pszStr;
+ while (*pszQ && *pszQ != ' ' && *pszQ != '\t')
+ pszQ++;
+ }
+ else
+ {
+ pszStr++;
+ pszQ = (char *)strchr(pszStr, '"');
+ if (pszQ == NULL)
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrectly quoted value in descriptor in '%s' (raw value %s)"),
+ pImage->pszFilename, pszStart);
+ }
+
+ pszUnquoted = (char *)RTMemTmpAlloc(pszQ - pszStr + 1);
+ if (!pszUnquoted)
+ return VERR_NO_MEMORY;
+ memcpy(pszUnquoted, pszStr, pszQ - pszStr);
+ pszUnquoted[pszQ - pszStr] = '\0';
+ *ppszUnquoted = pszUnquoted;
+ if (ppszNext)
+ *ppszNext = pszQ + 1;
+ return VINF_SUCCESS;
+}
+
+static int vmdkDescInitStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszLine)
+{
+ char *pEnd = pDescriptor->aLines[pDescriptor->cLines];
+ ssize_t cbDiff = strlen(pszLine) + 1;
+
+ if ( pDescriptor->cLines >= VMDK_DESCRIPTOR_LINES_MAX - 1
+ && pEnd - pDescriptor->aLines[0] > (ptrdiff_t)pDescriptor->cbDescAlloc - cbDiff)
+ return vdIfError(pImage->pIfError, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
+
+ memcpy(pEnd, pszLine, cbDiff);
+ pDescriptor->cLines++;
+ pDescriptor->aLines[pDescriptor->cLines] = pEnd + cbDiff;
+ pDescriptor->fDirty = true;
+
+ return VINF_SUCCESS;
+}
+
+static bool vmdkDescGetStr(PVMDKDESCRIPTOR pDescriptor, unsigned uStart,
+ const char *pszKey, const char **ppszValue)
+{
+ size_t cbKey = strlen(pszKey);
+ const char *pszValue;
+
+ while (uStart != 0)
+ {
+ if (!strncmp(pDescriptor->aLines[uStart], pszKey, cbKey))
+ {
+ /* Key matches, check for a '=' (preceded by whitespace). */
+ pszValue = pDescriptor->aLines[uStart] + cbKey;
+ while (*pszValue == ' ' || *pszValue == '\t')
+ pszValue++;
+ if (*pszValue == '=')
+ {
+ *ppszValue = pszValue + 1;
+ break;
+ }
+ }
+ uStart = pDescriptor->aNextLines[uStart];
+ }
+ return !!uStart;
+}
+
+static int vmdkDescSetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ unsigned uStart,
+ const char *pszKey, const char *pszValue)
+{
+ char *pszTmp = NULL; /* (MSC naturally cannot figure this isn't used uninitialized) */
+ size_t cbKey = strlen(pszKey);
+ unsigned uLast = 0;
+
+ while (uStart != 0)
+ {
+ if (!strncmp(pDescriptor->aLines[uStart], pszKey, cbKey))
+ {
+ /* Key matches, check for a '=' (preceded by whitespace). */
+ pszTmp = pDescriptor->aLines[uStart] + cbKey;
+ while (*pszTmp == ' ' || *pszTmp == '\t')
+ pszTmp++;
+ if (*pszTmp == '=')
+ {
+ pszTmp++;
+ /** @todo r=bird: Doesn't skipping trailing blanks here just cause unecessary
+ * bloat and potentially out of space error? */
+ while (*pszTmp == ' ' || *pszTmp == '\t')
+ pszTmp++;
+ break;
+ }
+ }
+ if (!pDescriptor->aNextLines[uStart])
+ uLast = uStart;
+ uStart = pDescriptor->aNextLines[uStart];
+ }
+ if (uStart)
+ {
+ if (pszValue)
+ {
+ /* Key already exists, replace existing value. */
+ size_t cbOldVal = strlen(pszTmp);
+ size_t cbNewVal = strlen(pszValue);
+ ssize_t cbDiff = cbNewVal - cbOldVal;
+ /* Check for buffer overflow. */
+ if ( pDescriptor->aLines[pDescriptor->cLines] - pDescriptor->aLines[0]
+ > (ptrdiff_t)pDescriptor->cbDescAlloc - cbDiff)
+ return vdIfError(pImage->pIfError, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
+
+ memmove(pszTmp + cbNewVal, pszTmp + cbOldVal,
+ pDescriptor->aLines[pDescriptor->cLines] - pszTmp - cbOldVal);
+ memcpy(pszTmp, pszValue, cbNewVal + 1);
+ for (unsigned i = uStart + 1; i <= pDescriptor->cLines; i++)
+ pDescriptor->aLines[i] += cbDiff;
+ }
+ else
+ {
+ memmove(pDescriptor->aLines[uStart], pDescriptor->aLines[uStart+1],
+ pDescriptor->aLines[pDescriptor->cLines] - pDescriptor->aLines[uStart+1] + 1);
+ for (unsigned i = uStart + 1; i <= pDescriptor->cLines; i++)
+ {
+ pDescriptor->aLines[i-1] = pDescriptor->aLines[i];
+ if (pDescriptor->aNextLines[i])
+ pDescriptor->aNextLines[i-1] = pDescriptor->aNextLines[i] - 1;
+ else
+ pDescriptor->aNextLines[i-1] = 0;
+ }
+ pDescriptor->cLines--;
+ /* Adjust starting line numbers of following descriptor sections. */
+ if (uStart < pDescriptor->uFirstExtent)
+ pDescriptor->uFirstExtent--;
+ if (uStart < pDescriptor->uFirstDDB)
+ pDescriptor->uFirstDDB--;
+ }
+ }
+ else
+ {
+ /* Key doesn't exist, append after the last entry in this category. */
+ if (!pszValue)
+ {
+ /* Key doesn't exist, and it should be removed. Simply a no-op. */
+ return VINF_SUCCESS;
+ }
+ cbKey = strlen(pszKey);
+ size_t cbValue = strlen(pszValue);
+ ssize_t cbDiff = cbKey + 1 + cbValue + 1;
+ /* Check for buffer overflow. */
+ if ( (pDescriptor->cLines >= VMDK_DESCRIPTOR_LINES_MAX - 1)
+ || ( pDescriptor->aLines[pDescriptor->cLines]
+ - pDescriptor->aLines[0] > (ptrdiff_t)pDescriptor->cbDescAlloc - cbDiff))
+ return vdIfError(pImage->pIfError, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
+ for (unsigned i = pDescriptor->cLines + 1; i > uLast + 1; i--)
+ {
+ pDescriptor->aLines[i] = pDescriptor->aLines[i - 1];
+ if (pDescriptor->aNextLines[i - 1])
+ pDescriptor->aNextLines[i] = pDescriptor->aNextLines[i - 1] + 1;
+ else
+ pDescriptor->aNextLines[i] = 0;
+ }
+ uStart = uLast + 1;
+ pDescriptor->aNextLines[uLast] = uStart;
+ pDescriptor->aNextLines[uStart] = 0;
+ pDescriptor->cLines++;
+ pszTmp = pDescriptor->aLines[uStart];
+ memmove(pszTmp + cbDiff, pszTmp,
+ pDescriptor->aLines[pDescriptor->cLines] - pszTmp);
+ memcpy(pDescriptor->aLines[uStart], pszKey, cbKey);
+ pDescriptor->aLines[uStart][cbKey] = '=';
+ memcpy(pDescriptor->aLines[uStart] + cbKey + 1, pszValue, cbValue + 1);
+ for (unsigned i = uStart + 1; i <= pDescriptor->cLines; i++)
+ pDescriptor->aLines[i] += cbDiff;
+
+ /* Adjust starting line numbers of following descriptor sections. */
+ if (uStart <= pDescriptor->uFirstExtent)
+ pDescriptor->uFirstExtent++;
+ if (uStart <= pDescriptor->uFirstDDB)
+ pDescriptor->uFirstDDB++;
+ }
+ pDescriptor->fDirty = true;
+ return VINF_SUCCESS;
+}
+
+static int vmdkDescBaseGetU32(PVMDKDESCRIPTOR pDescriptor, const char *pszKey,
+ uint32_t *puValue)
+{
+ const char *pszValue;
+
+ if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDesc, pszKey,
+ &pszValue))
+ return VERR_VD_VMDK_VALUE_NOT_FOUND;
+ return RTStrToUInt32Ex(pszValue, NULL, 10, puValue);
+}
+
+/**
+ * Returns the value of the given key as a string allocating the necessary memory.
+ *
+ * @returns VBox status code.
+ * @retval VERR_VD_VMDK_VALUE_NOT_FOUND if the value could not be found.
+ * @param pImage The VMDK image state.
+ * @param pDescriptor The descriptor to fetch the value from.
+ * @param pszKey The key to get the value from.
+ * @param ppszValue Where to store the return value, use RTMemTmpFree to
+ * free.
+ */
+static int vmdkDescBaseGetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, char **ppszValue)
+{
+ const char *pszValue;
+ char *pszValueUnquoted;
+
+ if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDesc, pszKey,
+ &pszValue))
+ return VERR_VD_VMDK_VALUE_NOT_FOUND;
+ int rc = vmdkStringUnquote(pImage, pszValue, &pszValueUnquoted, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ *ppszValue = pszValueUnquoted;
+ return rc;
+}
+
+static int vmdkDescBaseSetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, const char *pszValue)
+{
+ char *pszValueQuoted;
+
+ RTStrAPrintf(&pszValueQuoted, "\"%s\"", pszValue);
+ if (!pszValueQuoted)
+ return VERR_NO_STR_MEMORY;
+ int rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDesc, pszKey,
+ pszValueQuoted);
+ RTStrFree(pszValueQuoted);
+ return rc;
+}
+
+static void vmdkDescExtRemoveDummy(PVMDKIMAGE pImage,
+ PVMDKDESCRIPTOR pDescriptor)
+{
+ RT_NOREF1(pImage);
+ unsigned uEntry = pDescriptor->uFirstExtent;
+ ssize_t cbDiff;
+
+ if (!uEntry)
+ return;
+
+ cbDiff = strlen(pDescriptor->aLines[uEntry]) + 1;
+ /* Move everything including \0 in the entry marking the end of buffer. */
+ memmove(pDescriptor->aLines[uEntry], pDescriptor->aLines[uEntry + 1],
+ pDescriptor->aLines[pDescriptor->cLines] - pDescriptor->aLines[uEntry + 1] + 1);
+ for (unsigned i = uEntry + 1; i <= pDescriptor->cLines; i++)
+ {
+ pDescriptor->aLines[i - 1] = pDescriptor->aLines[i] - cbDiff;
+ if (pDescriptor->aNextLines[i])
+ pDescriptor->aNextLines[i - 1] = pDescriptor->aNextLines[i] - 1;
+ else
+ pDescriptor->aNextLines[i - 1] = 0;
+ }
+ pDescriptor->cLines--;
+ if (pDescriptor->uFirstDDB)
+ pDescriptor->uFirstDDB--;
+
+ return;
+}
+
+static void vmdkDescExtRemoveByLine(PVMDKIMAGE pImage,
+ PVMDKDESCRIPTOR pDescriptor, unsigned uLine)
+{
+ RT_NOREF1(pImage);
+ unsigned uEntry = uLine;
+ ssize_t cbDiff;
+ if (!uEntry)
+ return;
+ cbDiff = strlen(pDescriptor->aLines[uEntry]) + 1;
+ /* Move everything including \0 in the entry marking the end of buffer. */
+ memmove(pDescriptor->aLines[uEntry], pDescriptor->aLines[uEntry + 1],
+ pDescriptor->aLines[pDescriptor->cLines] - pDescriptor->aLines[uEntry + 1] + 1);
+ for (unsigned i = uEntry; i <= pDescriptor->cLines; i++)
+ {
+ if (i != uEntry)
+ pDescriptor->aLines[i - 1] = pDescriptor->aLines[i] - cbDiff;
+ if (pDescriptor->aNextLines[i])
+ pDescriptor->aNextLines[i - 1] = pDescriptor->aNextLines[i] - 1;
+ else
+ pDescriptor->aNextLines[i - 1] = 0;
+ }
+ pDescriptor->cLines--;
+ if (pDescriptor->uFirstDDB)
+ pDescriptor->uFirstDDB--;
+ return;
+}
+
+static int vmdkDescExtInsert(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ VMDKACCESS enmAccess, uint64_t cNominalSectors,
+ VMDKETYPE enmType, const char *pszBasename,
+ uint64_t uSectorOffset)
+{
+ static const char *apszAccess[] = { "NOACCESS", "RDONLY", "RW" };
+ static const char *apszType[] = { "", "SPARSE", "FLAT", "ZERO", "VMFS" };
+ char *pszTmp;
+ unsigned uStart = pDescriptor->uFirstExtent, uLast = 0;
+ char szExt[1024];
+ ssize_t cbDiff;
+
+ Assert((unsigned)enmAccess < RT_ELEMENTS(apszAccess));
+ Assert((unsigned)enmType < RT_ELEMENTS(apszType));
+
+ /* Find last entry in extent description. */
+ while (uStart)
+ {
+ if (!pDescriptor->aNextLines[uStart])
+ uLast = uStart;
+ uStart = pDescriptor->aNextLines[uStart];
+ }
+
+ if (enmType == VMDKETYPE_ZERO)
+ {
+ RTStrPrintf(szExt, sizeof(szExt), "%s %llu %s ", apszAccess[enmAccess],
+ cNominalSectors, apszType[enmType]);
+ }
+ else if (enmType == VMDKETYPE_FLAT)
+ {
+ RTStrPrintf(szExt, sizeof(szExt), "%s %llu %s \"%s\" %llu",
+ apszAccess[enmAccess], cNominalSectors,
+ apszType[enmType], pszBasename, uSectorOffset);
+ }
+ else
+ {
+ RTStrPrintf(szExt, sizeof(szExt), "%s %llu %s \"%s\"",
+ apszAccess[enmAccess], cNominalSectors,
+ apszType[enmType], pszBasename);
+ }
+ cbDiff = strlen(szExt) + 1;
+
+ /* Check for buffer overflow. */
+ if ( (pDescriptor->cLines >= VMDK_DESCRIPTOR_LINES_MAX - 1)
+ || ( pDescriptor->aLines[pDescriptor->cLines]
+ - pDescriptor->aLines[0] > (ptrdiff_t)pDescriptor->cbDescAlloc - cbDiff))
+ {
+ if ((pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G)
+ && !(pDescriptor->cLines >= VMDK_DESCRIPTOR_LINES_MAX - 1))
+ {
+ pImage->cbDescAlloc *= 2;
+ pDescriptor->cbDescAlloc *= 2;
+ }
+ else
+ return vdIfError(pImage->pIfError, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
+ }
+
+ for (unsigned i = pDescriptor->cLines + 1; i > uLast + 1; i--)
+ {
+ pDescriptor->aLines[i] = pDescriptor->aLines[i - 1];
+ if (pDescriptor->aNextLines[i - 1])
+ pDescriptor->aNextLines[i] = pDescriptor->aNextLines[i - 1] + 1;
+ else
+ pDescriptor->aNextLines[i] = 0;
+ }
+ uStart = uLast + 1;
+ pDescriptor->aNextLines[uLast] = uStart;
+ pDescriptor->aNextLines[uStart] = 0;
+ pDescriptor->cLines++;
+ pszTmp = pDescriptor->aLines[uStart];
+ memmove(pszTmp + cbDiff, pszTmp,
+ pDescriptor->aLines[pDescriptor->cLines] - pszTmp);
+ memcpy(pDescriptor->aLines[uStart], szExt, cbDiff);
+ for (unsigned i = uStart + 1; i <= pDescriptor->cLines; i++)
+ pDescriptor->aLines[i] += cbDiff;
+
+ /* Adjust starting line numbers of following descriptor sections. */
+ if (uStart <= pDescriptor->uFirstDDB)
+ pDescriptor->uFirstDDB++;
+
+ pDescriptor->fDirty = true;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns the value of the given key from the DDB as a string allocating
+ * the necessary memory.
+ *
+ * @returns VBox status code.
+ * @retval VERR_VD_VMDK_VALUE_NOT_FOUND if the value could not be found.
+ * @param pImage The VMDK image state.
+ * @param pDescriptor The descriptor to fetch the value from.
+ * @param pszKey The key to get the value from.
+ * @param ppszValue Where to store the return value, use RTMemTmpFree to
+ * free.
+ */
+static int vmdkDescDDBGetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, char **ppszValue)
+{
+ const char *pszValue;
+ char *pszValueUnquoted;
+
+ if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ &pszValue))
+ return VERR_VD_VMDK_VALUE_NOT_FOUND;
+ int rc = vmdkStringUnquote(pImage, pszValue, &pszValueUnquoted, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ *ppszValue = pszValueUnquoted;
+ return rc;
+}
+
+static int vmdkDescDDBGetU32(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, uint32_t *puValue)
+{
+ const char *pszValue;
+ char *pszValueUnquoted;
+
+ if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ &pszValue))
+ return VERR_VD_VMDK_VALUE_NOT_FOUND;
+ int rc = vmdkStringUnquote(pImage, pszValue, &pszValueUnquoted, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTStrToUInt32Ex(pszValueUnquoted, NULL, 10, puValue);
+ RTMemTmpFree(pszValueUnquoted);
+ return rc;
+}
+
+static int vmdkDescDDBGetUuid(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, PRTUUID pUuid)
+{
+ const char *pszValue;
+ char *pszValueUnquoted;
+
+ if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ &pszValue))
+ return VERR_VD_VMDK_VALUE_NOT_FOUND;
+ int rc = vmdkStringUnquote(pImage, pszValue, &pszValueUnquoted, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTUuidFromStr(pUuid, pszValueUnquoted);
+ RTMemTmpFree(pszValueUnquoted);
+ return rc;
+}
+
+static int vmdkDescDDBSetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, const char *pszVal)
+{
+ int rc;
+ char *pszValQuoted;
+
+ if (pszVal)
+ {
+ RTStrAPrintf(&pszValQuoted, "\"%s\"", pszVal);
+ if (!pszValQuoted)
+ return VERR_NO_STR_MEMORY;
+ }
+ else
+ pszValQuoted = NULL;
+ rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ pszValQuoted);
+ if (pszValQuoted)
+ RTStrFree(pszValQuoted);
+ return rc;
+}
+
+static int vmdkDescDDBSetUuid(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, PCRTUUID pUuid)
+{
+ char *pszUuid;
+
+ RTStrAPrintf(&pszUuid, "\"%RTuuid\"", pUuid);
+ if (!pszUuid)
+ return VERR_NO_STR_MEMORY;
+ int rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ pszUuid);
+ RTStrFree(pszUuid);
+ return rc;
+}
+
+static int vmdkDescDDBSetU32(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+ const char *pszKey, uint32_t uValue)
+{
+ char *pszValue;
+
+ RTStrAPrintf(&pszValue, "\"%d\"", uValue);
+ if (!pszValue)
+ return VERR_NO_STR_MEMORY;
+ int rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDDB, pszKey,
+ pszValue);
+ RTStrFree(pszValue);
+ return rc;
+}
+
+/**
+ * Splits the descriptor data into individual lines checking for correct line
+ * endings and descriptor size.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance.
+ * @param pDesc The descriptor.
+ * @param pszTmp The raw descriptor data from the image.
+ */
+static int vmdkDescSplitLines(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDesc, char *pszTmp)
+{
+ unsigned cLine = 0;
+ int rc = VINF_SUCCESS;
+
+ while ( RT_SUCCESS(rc)
+ && *pszTmp != '\0')
+ {
+ pDesc->aLines[cLine++] = pszTmp;
+ if (cLine >= VMDK_DESCRIPTOR_LINES_MAX)
+ {
+ vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
+ rc = VERR_VD_VMDK_INVALID_HEADER;
+ break;
+ }
+
+ while (*pszTmp != '\0' && *pszTmp != '\n')
+ {
+ if (*pszTmp == '\r')
+ {
+ if (*(pszTmp + 1) != '\n')
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: unsupported end of line in descriptor in '%s'"), pImage->pszFilename);
+ break;
+ }
+ else
+ {
+ /* Get rid of CR character. */
+ *pszTmp = '\0';
+ }
+ }
+ pszTmp++;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Get rid of LF character. */
+ if (*pszTmp == '\n')
+ {
+ *pszTmp = '\0';
+ pszTmp++;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pDesc->cLines = cLine;
+ /* Pointer right after the end of the used part of the buffer. */
+ pDesc->aLines[cLine] = pszTmp;
+ }
+
+ return rc;
+}
+
+static int vmdkPreprocessDescriptor(PVMDKIMAGE pImage, char *pDescData,
+ size_t cbDescData, PVMDKDESCRIPTOR pDescriptor)
+{
+ pDescriptor->cbDescAlloc = cbDescData;
+ int rc = vmdkDescSplitLines(pImage, pDescriptor, pDescData);
+ if (RT_SUCCESS(rc))
+ {
+ if ( strcmp(pDescriptor->aLines[0], "# Disk DescriptorFile")
+ && strcmp(pDescriptor->aLines[0], "# Disk Descriptor File")
+ && strcmp(pDescriptor->aLines[0], "#Disk Descriptor File")
+ && strcmp(pDescriptor->aLines[0], "#Disk DescriptorFile"))
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: descriptor does not start as expected in '%s'"), pImage->pszFilename);
+ else
+ {
+ unsigned uLastNonEmptyLine = 0;
+
+ /* Initialize those, because we need to be able to reopen an image. */
+ pDescriptor->uFirstDesc = 0;
+ pDescriptor->uFirstExtent = 0;
+ pDescriptor->uFirstDDB = 0;
+ for (unsigned i = 0; i < pDescriptor->cLines; i++)
+ {
+ if (*pDescriptor->aLines[i] != '#' && *pDescriptor->aLines[i] != '\0')
+ {
+ if ( !strncmp(pDescriptor->aLines[i], "RW", 2)
+ || !strncmp(pDescriptor->aLines[i], "RDONLY", 6)
+ || !strncmp(pDescriptor->aLines[i], "NOACCESS", 8) )
+ {
+ /* An extent descriptor. */
+ if (!pDescriptor->uFirstDesc || pDescriptor->uFirstDDB)
+ {
+ /* Incorrect ordering of entries. */
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: incorrect ordering of entries in descriptor in '%s'"), pImage->pszFilename);
+ break;
+ }
+ if (!pDescriptor->uFirstExtent)
+ {
+ pDescriptor->uFirstExtent = i;
+ uLastNonEmptyLine = 0;
+ }
+ }
+ else if (!strncmp(pDescriptor->aLines[i], "ddb.", 4))
+ {
+ /* A disk database entry. */
+ if (!pDescriptor->uFirstDesc || !pDescriptor->uFirstExtent)
+ {
+ /* Incorrect ordering of entries. */
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: incorrect ordering of entries in descriptor in '%s'"), pImage->pszFilename);
+ break;
+ }
+ if (!pDescriptor->uFirstDDB)
+ {
+ pDescriptor->uFirstDDB = i;
+ uLastNonEmptyLine = 0;
+ }
+ }
+ else
+ {
+ /* A normal entry. */
+ if (pDescriptor->uFirstExtent || pDescriptor->uFirstDDB)
+ {
+ /* Incorrect ordering of entries. */
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: incorrect ordering of entries in descriptor in '%s'"), pImage->pszFilename);
+ break;
+ }
+ if (!pDescriptor->uFirstDesc)
+ {
+ pDescriptor->uFirstDesc = i;
+ uLastNonEmptyLine = 0;
+ }
+ }
+ if (uLastNonEmptyLine)
+ pDescriptor->aNextLines[uLastNonEmptyLine] = i;
+ uLastNonEmptyLine = i;
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+static int vmdkDescSetPCHSGeometry(PVMDKIMAGE pImage,
+ PCVDGEOMETRY pPCHSGeometry)
+{
+ int rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_CYLINDERS,
+ pPCHSGeometry->cCylinders);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_HEADS,
+ pPCHSGeometry->cHeads);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_SECTORS,
+ pPCHSGeometry->cSectors);
+ return rc;
+}
+
+static int vmdkDescSetLCHSGeometry(PVMDKIMAGE pImage,
+ PCVDGEOMETRY pLCHSGeometry)
+{
+ int rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_CYLINDERS,
+ pLCHSGeometry->cCylinders);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_HEADS,
+
+ pLCHSGeometry->cHeads);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_SECTORS,
+ pLCHSGeometry->cSectors);
+ return rc;
+}
+
+static int vmdkCreateDescriptor(PVMDKIMAGE pImage, char *pDescData,
+ size_t cbDescData, PVMDKDESCRIPTOR pDescriptor)
+{
+ pDescriptor->uFirstDesc = 0;
+ pDescriptor->uFirstExtent = 0;
+ pDescriptor->uFirstDDB = 0;
+ pDescriptor->cLines = 0;
+ pDescriptor->cbDescAlloc = cbDescData;
+ pDescriptor->fDirty = false;
+ pDescriptor->aLines[pDescriptor->cLines] = pDescData;
+ memset(pDescriptor->aNextLines, '\0', sizeof(pDescriptor->aNextLines));
+
+ int rc = vmdkDescInitStr(pImage, pDescriptor, "# Disk DescriptorFile");
+ if (RT_SUCCESS(rc))
+ rc = vmdkDescInitStr(pImage, pDescriptor, "version=1");
+ if (RT_SUCCESS(rc))
+ {
+ pDescriptor->uFirstDesc = pDescriptor->cLines - 1;
+ rc = vmdkDescInitStr(pImage, pDescriptor, "");
+ }
+ if (RT_SUCCESS(rc))
+ rc = vmdkDescInitStr(pImage, pDescriptor, "# Extent description");
+ if (RT_SUCCESS(rc))
+ rc = vmdkDescInitStr(pImage, pDescriptor, "NOACCESS 0 ZERO ");
+ if (RT_SUCCESS(rc))
+ {
+ pDescriptor->uFirstExtent = pDescriptor->cLines - 1;
+ rc = vmdkDescInitStr(pImage, pDescriptor, "");
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* The trailing space is created by VMware, too. */
+ rc = vmdkDescInitStr(pImage, pDescriptor, "# The disk Data Base ");
+ }
+ if (RT_SUCCESS(rc))
+ rc = vmdkDescInitStr(pImage, pDescriptor, "#DDB");
+ if (RT_SUCCESS(rc))
+ rc = vmdkDescInitStr(pImage, pDescriptor, "");
+ if (RT_SUCCESS(rc))
+ rc = vmdkDescInitStr(pImage, pDescriptor, "ddb.virtualHWVersion = \"4\"");
+ if (RT_SUCCESS(rc))
+ {
+ pDescriptor->uFirstDDB = pDescriptor->cLines - 1;
+
+ /* Now that the framework is in place, use the normal functions to insert
+ * the remaining keys. */
+ char szBuf[9];
+ RTStrPrintf(szBuf, sizeof(szBuf), "%08x", RTRandU32());
+ rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDesc,
+ "CID", szBuf);
+ }
+ if (RT_SUCCESS(rc))
+ rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDesc,
+ "parentCID", "ffffffff");
+ if (RT_SUCCESS(rc))
+ rc = vmdkDescDDBSetStr(pImage, pDescriptor, "ddb.adapterType", "ide");
+
+ return rc;
+}
+
+static int vmdkParseDescriptor(PVMDKIMAGE pImage, char *pDescData, size_t cbDescData)
+{
+ int rc;
+ unsigned cExtents;
+ unsigned uLine;
+ unsigned i;
+
+ rc = vmdkPreprocessDescriptor(pImage, pDescData, cbDescData,
+ &pImage->Descriptor);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Check version, must be 1. */
+ uint32_t uVersion;
+ rc = vmdkDescBaseGetU32(&pImage->Descriptor, "version", &uVersion);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error finding key 'version' in descriptor in '%s'"), pImage->pszFilename);
+ if (uVersion != 1)
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_UNSUPPORTED_VERSION, RT_SRC_POS, N_("VMDK: unsupported format version in descriptor in '%s'"), pImage->pszFilename);
+
+ /* Get image creation type and determine image flags. */
+ char *pszCreateType = NULL; /* initialized to make gcc shut up */
+ rc = vmdkDescBaseGetStr(pImage, &pImage->Descriptor, "createType",
+ &pszCreateType);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot get image type from descriptor in '%s'"), pImage->pszFilename);
+ if ( !strcmp(pszCreateType, "twoGbMaxExtentSparse")
+ || !strcmp(pszCreateType, "twoGbMaxExtentFlat"))
+ pImage->uImageFlags |= VD_VMDK_IMAGE_FLAGS_SPLIT_2G;
+ else if ( !strcmp(pszCreateType, "partitionedDevice")
+ || !strcmp(pszCreateType, "fullDevice"))
+ pImage->uImageFlags |= VD_VMDK_IMAGE_FLAGS_RAWDISK;
+ else if (!strcmp(pszCreateType, "streamOptimized"))
+ pImage->uImageFlags |= VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED;
+ else if (!strcmp(pszCreateType, "vmfs"))
+ pImage->uImageFlags |= VD_IMAGE_FLAGS_FIXED | VD_VMDK_IMAGE_FLAGS_ESX;
+ RTMemTmpFree(pszCreateType);
+
+ /* Count the number of extent config entries. */
+ for (uLine = pImage->Descriptor.uFirstExtent, cExtents = 0;
+ uLine != 0;
+ uLine = pImage->Descriptor.aNextLines[uLine], cExtents++)
+ /* nothing */;
+
+ if (!pImage->pDescData && cExtents != 1)
+ {
+ /* Monolithic image, must have only one extent (already opened). */
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: monolithic image may only have one extent in '%s'"), pImage->pszFilename);
+ }
+
+ if (pImage->pDescData)
+ {
+ /* Non-monolithic image, extents need to be allocated. */
+ rc = vmdkCreateExtents(pImage, cExtents);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ for (i = 0, uLine = pImage->Descriptor.uFirstExtent;
+ i < cExtents; i++, uLine = pImage->Descriptor.aNextLines[uLine])
+ {
+ char *pszLine = pImage->Descriptor.aLines[uLine];
+
+ /* Access type of the extent. */
+ if (!strncmp(pszLine, "RW", 2))
+ {
+ pImage->pExtents[i].enmAccess = VMDKACCESS_READWRITE;
+ pszLine += 2;
+ }
+ else if (!strncmp(pszLine, "RDONLY", 6))
+ {
+ pImage->pExtents[i].enmAccess = VMDKACCESS_READONLY;
+ pszLine += 6;
+ }
+ else if (!strncmp(pszLine, "NOACCESS", 8))
+ {
+ pImage->pExtents[i].enmAccess = VMDKACCESS_NOACCESS;
+ pszLine += 8;
+ }
+ else
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ if (*pszLine++ != ' ')
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+
+ /* Nominal size of the extent. */
+ rc = RTStrToUInt64Ex(pszLine, &pszLine, 10,
+ &pImage->pExtents[i].cNominalSectors);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ if (*pszLine++ != ' ')
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+
+ /* Type of the extent. */
+ if (!strncmp(pszLine, "SPARSE", 6))
+ {
+ pImage->pExtents[i].enmType = VMDKETYPE_HOSTED_SPARSE;
+ pszLine += 6;
+ }
+ else if (!strncmp(pszLine, "FLAT", 4))
+ {
+ pImage->pExtents[i].enmType = VMDKETYPE_FLAT;
+ pszLine += 4;
+ }
+ else if (!strncmp(pszLine, "ZERO", 4))
+ {
+ pImage->pExtents[i].enmType = VMDKETYPE_ZERO;
+ pszLine += 4;
+ }
+ else if (!strncmp(pszLine, "VMFS", 4))
+ {
+ pImage->pExtents[i].enmType = VMDKETYPE_VMFS;
+ pszLine += 4;
+ }
+ else
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+
+ if (pImage->pExtents[i].enmType == VMDKETYPE_ZERO)
+ {
+ /* This one has no basename or offset. */
+ if (*pszLine == ' ')
+ pszLine++;
+ if (*pszLine != '\0')
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ pImage->pExtents[i].pszBasename = NULL;
+ }
+ else
+ {
+ /* All other extent types have basename and optional offset. */
+ if (*pszLine++ != ' ')
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+
+ /* Basename of the image. Surrounded by quotes. */
+ char *pszBasename;
+ rc = vmdkStringUnquote(pImage, pszLine, &pszBasename, &pszLine);
+ if (RT_FAILURE(rc))
+ return rc;
+ pImage->pExtents[i].pszBasename = pszBasename;
+ if (*pszLine == ' ')
+ {
+ pszLine++;
+ if (*pszLine != '\0')
+ {
+ /* Optional offset in extent specified. */
+ rc = RTStrToUInt64Ex(pszLine, &pszLine, 10,
+ &pImage->pExtents[i].uSectorOffset);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ }
+ }
+
+ if (*pszLine != '\0')
+ return vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
+ }
+ }
+
+ /* Determine PCHS geometry (autogenerate if necessary). */
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_CYLINDERS,
+ &pImage->PCHSGeometry.cCylinders);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->PCHSGeometry.cCylinders = 0;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error getting PCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_HEADS,
+ &pImage->PCHSGeometry.cHeads);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->PCHSGeometry.cHeads = 0;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error getting PCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_PCHS_SECTORS,
+ &pImage->PCHSGeometry.cSectors);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->PCHSGeometry.cSectors = 0;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error getting PCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ if ( pImage->PCHSGeometry.cCylinders == 0
+ || pImage->PCHSGeometry.cHeads == 0
+ || pImage->PCHSGeometry.cHeads > 16
+ || pImage->PCHSGeometry.cSectors == 0
+ || pImage->PCHSGeometry.cSectors > 63)
+ {
+ /* Mark PCHS geometry as not yet valid (can't do the calculation here
+ * as the total image size isn't known yet). */
+ pImage->PCHSGeometry.cCylinders = 0;
+ pImage->PCHSGeometry.cHeads = 16;
+ pImage->PCHSGeometry.cSectors = 63;
+ }
+
+ /* Determine LCHS geometry (set to 0 if not specified). */
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_CYLINDERS,
+ &pImage->LCHSGeometry.cCylinders);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->LCHSGeometry.cCylinders = 0;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error getting LCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_HEADS,
+ &pImage->LCHSGeometry.cHeads);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->LCHSGeometry.cHeads = 0;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error getting LCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+ VMDK_DDB_GEO_LCHS_SECTORS,
+ &pImage->LCHSGeometry.cSectors);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ pImage->LCHSGeometry.cSectors = 0;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error getting LCHS geometry from extent description in '%s'"), pImage->pszFilename);
+ if ( pImage->LCHSGeometry.cCylinders == 0
+ || pImage->LCHSGeometry.cHeads == 0
+ || pImage->LCHSGeometry.cSectors == 0)
+ {
+ pImage->LCHSGeometry.cCylinders = 0;
+ pImage->LCHSGeometry.cHeads = 0;
+ pImage->LCHSGeometry.cSectors = 0;
+ }
+
+ /* Get image UUID. */
+ rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor, VMDK_DDB_IMAGE_UUID,
+ &pImage->ImageUuid);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ {
+ /* Image without UUID. Probably created by VMware and not yet used
+ * by VirtualBox. Can only be added for images opened in read/write
+ * mode, so don't bother producing a sensible UUID otherwise. */
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ RTUuidClear(&pImage->ImageUuid);
+ else
+ {
+ rc = RTUuidCreate(&pImage->ImageUuid);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_IMAGE_UUID, &pImage->ImageUuid);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error storing image UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ }
+ else if (RT_FAILURE(rc))
+ return rc;
+
+ /* Get image modification UUID. */
+ rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_MODIFICATION_UUID,
+ &pImage->ModificationUuid);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ {
+ /* Image without UUID. Probably created by VMware and not yet used
+ * by VirtualBox. Can only be added for images opened in read/write
+ * mode, so don't bother producing a sensible UUID otherwise. */
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ RTUuidClear(&pImage->ModificationUuid);
+ else
+ {
+ rc = RTUuidCreate(&pImage->ModificationUuid);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_MODIFICATION_UUID,
+ &pImage->ModificationUuid);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error storing image modification UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ }
+ else if (RT_FAILURE(rc))
+ return rc;
+
+ /* Get UUID of parent image. */
+ rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor, VMDK_DDB_PARENT_UUID,
+ &pImage->ParentUuid);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ {
+ /* Image without UUID. Probably created by VMware and not yet used
+ * by VirtualBox. Can only be added for images opened in read/write
+ * mode, so don't bother producing a sensible UUID otherwise. */
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ RTUuidClear(&pImage->ParentUuid);
+ else
+ {
+ rc = RTUuidClear(&pImage->ParentUuid);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_UUID, &pImage->ParentUuid);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error storing parent UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ }
+ else if (RT_FAILURE(rc))
+ return rc;
+
+ /* Get parent image modification UUID. */
+ rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_MODIFICATION_UUID,
+ &pImage->ParentModificationUuid);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ {
+ /* Image without UUID. Probably created by VMware and not yet used
+ * by VirtualBox. Can only be added for images opened in read/write
+ * mode, so don't bother producing a sensible UUID otherwise. */
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ RTUuidClear(&pImage->ParentModificationUuid);
+ else
+ {
+ RTUuidClear(&pImage->ParentModificationUuid);
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_MODIFICATION_UUID,
+ &pImage->ParentModificationUuid);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error storing parent modification UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ }
+ else if (RT_FAILURE(rc))
+ return rc;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal : Prepares the descriptor to write to the image.
+ */
+static int vmdkDescriptorPrepare(PVMDKIMAGE pImage, uint64_t cbLimit,
+ void **ppvData, size_t *pcbData)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Allocate temporary descriptor buffer.
+ * In case there is no limit allocate a default
+ * and increase if required.
+ */
+ size_t cbDescriptor = cbLimit ? cbLimit : 4 * _1K;
+ char *pszDescriptor = (char *)RTMemAllocZ(cbDescriptor);
+ size_t offDescriptor = 0;
+
+ if (!pszDescriptor)
+ return VERR_NO_MEMORY;
+
+ for (unsigned i = 0; i < pImage->Descriptor.cLines; i++)
+ {
+ const char *psz = pImage->Descriptor.aLines[i];
+ size_t cb = strlen(psz);
+
+ /*
+ * Increase the descriptor if there is no limit and
+ * there is not enough room left for this line.
+ */
+ if (offDescriptor + cb + 1 > cbDescriptor)
+ {
+ if (cbLimit)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too long in '%s'"), pImage->pszFilename);
+ break;
+ }
+ else
+ {
+ char *pszDescriptorNew = NULL;
+ LogFlow(("Increasing descriptor cache\n"));
+
+ pszDescriptorNew = (char *)RTMemRealloc(pszDescriptor, cbDescriptor + cb + 4 * _1K);
+ if (!pszDescriptorNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pszDescriptor = pszDescriptorNew;
+ cbDescriptor += cb + 4 * _1K;
+ }
+ }
+
+ if (cb > 0)
+ {
+ memcpy(pszDescriptor + offDescriptor, psz, cb);
+ offDescriptor += cb;
+ }
+
+ memcpy(pszDescriptor + offDescriptor, "\n", 1);
+ offDescriptor++;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppvData = pszDescriptor;
+ *pcbData = offDescriptor;
+ }
+ else if (pszDescriptor)
+ RTMemFree(pszDescriptor);
+
+ return rc;
+}
+
+/**
+ * Internal: write/update the descriptor part of the image.
+ */
+static int vmdkWriteDescriptor(PVMDKIMAGE pImage, PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+ uint64_t cbLimit;
+ uint64_t uOffset;
+ PVMDKFILE pDescFile;
+ void *pvDescriptor = NULL;
+ size_t cbDescriptor;
+
+ if (pImage->pDescData)
+ {
+ /* Separate descriptor file. */
+ uOffset = 0;
+ cbLimit = 0;
+ pDescFile = pImage->pFile;
+ }
+ else
+ {
+ /* Embedded descriptor file. */
+ uOffset = VMDK_SECTOR2BYTE(pImage->pExtents[0].uDescriptorSector);
+ cbLimit = VMDK_SECTOR2BYTE(pImage->pExtents[0].cDescriptorSectors);
+ pDescFile = pImage->pExtents[0].pFile;
+ }
+ /* Bail out if there is no file to write to. */
+ if (pDescFile == NULL)
+ return VERR_INVALID_PARAMETER;
+
+ rc = vmdkDescriptorPrepare(pImage, cbLimit, &pvDescriptor, &cbDescriptor);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pDescFile->pStorage,
+ uOffset, pvDescriptor,
+ cbLimit ? cbLimit : cbDescriptor,
+ pIoCtx, NULL, NULL);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error writing descriptor in '%s'"), pImage->pszFilename);
+ }
+
+ if (RT_SUCCESS(rc) && !cbLimit)
+ {
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pDescFile->pStorage, cbDescriptor);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error truncating descriptor in '%s'"), pImage->pszFilename);
+ }
+
+ if (RT_SUCCESS(rc))
+ pImage->Descriptor.fDirty = false;
+
+ if (pvDescriptor)
+ RTMemFree(pvDescriptor);
+ return rc;
+
+}
+
+/**
+ * Internal: validate the consistency check values in a binary header.
+ */
+static int vmdkValidateHeader(PVMDKIMAGE pImage, PVMDKEXTENT pExtent, const SparseExtentHeader *pHeader)
+{
+ int rc = VINF_SUCCESS;
+ if (RT_LE2H_U32(pHeader->magicNumber) != VMDK_SPARSE_MAGICNUMBER)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect magic in sparse extent header in '%s'"), pExtent->pszFullname);
+ return rc;
+ }
+ if (RT_LE2H_U32(pHeader->version) != 1 && RT_LE2H_U32(pHeader->version) != 3)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_UNSUPPORTED_VERSION, RT_SRC_POS, N_("VMDK: incorrect version in sparse extent header in '%s', not a VMDK 1.0/1.1 conforming file"), pExtent->pszFullname);
+ return rc;
+ }
+ if ( (RT_LE2H_U32(pHeader->flags) & 1)
+ && ( pHeader->singleEndLineChar != '\n'
+ || pHeader->nonEndLineChar != ' '
+ || pHeader->doubleEndLineChar1 != '\r'
+ || pHeader->doubleEndLineChar2 != '\n') )
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: corrupted by CR/LF translation in '%s'"), pExtent->pszFullname);
+ return rc;
+ }
+ if (RT_LE2H_U64(pHeader->descriptorSize) > VMDK_SPARSE_DESCRIPTOR_SIZE_MAX)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: descriptor size out of bounds (%llu vs %llu) '%s'"),
+ pExtent->pszFullname, RT_LE2H_U64(pHeader->descriptorSize), VMDK_SPARSE_DESCRIPTOR_SIZE_MAX);
+ return rc;
+ }
+ return rc;
+}
+
+/**
+ * Internal: read metadata belonging to an extent with binary header, i.e.
+ * as found in monolithic files.
+ */
+static int vmdkReadBinaryMetaExtent(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ bool fMagicAlreadyRead)
+{
+ SparseExtentHeader Header;
+ int rc;
+
+ if (!fMagicAlreadyRead)
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage, 0,
+ &Header, sizeof(Header));
+ else
+ {
+ Header.magicNumber = RT_H2LE_U32(VMDK_SPARSE_MAGICNUMBER);
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ RT_UOFFSETOF(SparseExtentHeader, version),
+ &Header.version,
+ sizeof(Header)
+ - RT_UOFFSETOF(SparseExtentHeader, version));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vmdkValidateHeader(pImage, pExtent, &Header);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile = 0;
+
+ if ( (RT_LE2H_U32(Header.flags) & RT_BIT(17))
+ && RT_LE2H_U64(Header.gdOffset) == VMDK_GD_AT_END)
+ pExtent->fFooter = true;
+
+ if ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ || ( pExtent->fFooter
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL)))
+ {
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pExtent->pFile->pStorage, &cbFile);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot get size of '%s'"), pExtent->pszFullname);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ pExtent->uAppendPosition = RT_ALIGN_64(cbFile, 512);
+
+ if ( pExtent->fFooter
+ && ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ || !(pImage->uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL)))
+ {
+ /* Read the footer, which comes before the end-of-stream marker. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ cbFile - 2*512, &Header,
+ sizeof(Header));
+ if (RT_FAILURE(rc))
+ {
+ vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error reading extent footer in '%s'"), pExtent->pszFullname);
+ rc = VERR_VD_VMDK_INVALID_HEADER;
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = vmdkValidateHeader(pImage, pExtent, &Header);
+ /* Prohibit any writes to this extent. */
+ pExtent->uAppendPosition = 0;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pExtent->uVersion = RT_LE2H_U32(Header.version);
+ pExtent->enmType = VMDKETYPE_HOSTED_SPARSE; /* Just dummy value, changed later. */
+ pExtent->cSectors = RT_LE2H_U64(Header.capacity);
+ pExtent->cSectorsPerGrain = RT_LE2H_U64(Header.grainSize);
+ pExtent->uDescriptorSector = RT_LE2H_U64(Header.descriptorOffset);
+ pExtent->cDescriptorSectors = RT_LE2H_U64(Header.descriptorSize);
+ pExtent->cGTEntries = RT_LE2H_U32(Header.numGTEsPerGT);
+ pExtent->cOverheadSectors = RT_LE2H_U64(Header.overHead);
+ pExtent->fUncleanShutdown = !!Header.uncleanShutdown;
+ pExtent->uCompression = RT_LE2H_U16(Header.compressAlgorithm);
+ if (RT_LE2H_U32(Header.flags) & RT_BIT(1))
+ {
+ pExtent->uSectorRGD = RT_LE2H_U64(Header.rgdOffset);
+ pExtent->uSectorGD = RT_LE2H_U64(Header.gdOffset);
+ }
+ else
+ {
+ pExtent->uSectorGD = RT_LE2H_U64(Header.gdOffset);
+ pExtent->uSectorRGD = 0;
+ }
+
+ if (pExtent->uDescriptorSector && !pExtent->cDescriptorSectors)
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: inconsistent embedded descriptor config in '%s'"), pExtent->pszFullname);
+
+ if ( RT_SUCCESS(rc)
+ && ( pExtent->uSectorGD == VMDK_GD_AT_END
+ || pExtent->uSectorRGD == VMDK_GD_AT_END)
+ && ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ || !(pImage->uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL)))
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: cannot resolve grain directory offset in '%s'"), pExtent->pszFullname);
+
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cSectorsPerGDE = pExtent->cGTEntries * pExtent->cSectorsPerGrain;
+ if (!cSectorsPerGDE || cSectorsPerGDE > UINT32_MAX)
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: incorrect grain directory size in '%s'"), pExtent->pszFullname);
+ else
+ {
+ pExtent->cSectorsPerGDE = cSectorsPerGDE;
+ pExtent->cGDEntries = (pExtent->cSectors + cSectorsPerGDE - 1) / cSectorsPerGDE;
+
+ /* Fix up the number of descriptor sectors, as some flat images have
+ * really just one, and this causes failures when inserting the UUID
+ * values and other extra information. */
+ if (pExtent->cDescriptorSectors != 0 && pExtent->cDescriptorSectors < 4)
+ {
+ /* Do it the easy way - just fix it for flat images which have no
+ * other complicated metadata which needs space too. */
+ if ( pExtent->uDescriptorSector + 4 < pExtent->cOverheadSectors
+ && pExtent->cGTEntries * pExtent->cGDEntries == 0)
+ pExtent->cDescriptorSectors = 4;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error reading extent header in '%s'"), pExtent->pszFullname);
+ rc = VERR_VD_VMDK_INVALID_HEADER;
+ }
+
+ if (RT_FAILURE(rc))
+ vmdkFreeExtentData(pImage, pExtent, false);
+
+ return rc;
+}
+
+/**
+ * Internal: read additional metadata belonging to an extent. For those
+ * extents which have no additional metadata just verify the information.
+ */
+static int vmdkReadMetaExtent(PVMDKIMAGE pImage, PVMDKEXTENT pExtent)
+{
+ int rc = VINF_SUCCESS;
+
+/* disabled the check as there are too many truncated vmdk images out there */
+#ifdef VBOX_WITH_VMDK_STRICT_SIZE_CHECK
+ uint64_t cbExtentSize;
+ /* The image must be a multiple of a sector in size and contain the data
+ * area (flat images only). If not, it means the image is at least
+ * truncated, or even seriously garbled. */
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pExtent->pFile->pStorage, &cbExtentSize);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error getting size in '%s'"), pExtent->pszFullname);
+ else if ( cbExtentSize != RT_ALIGN_64(cbExtentSize, 512)
+ && (pExtent->enmType != VMDKETYPE_FLAT || pExtent->cNominalSectors + pExtent->uSectorOffset > VMDK_BYTE2SECTOR(cbExtentSize)))
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: file size is not a multiple of 512 in '%s', file is truncated or otherwise garbled"), pExtent->pszFullname);
+#endif /* VBOX_WITH_VMDK_STRICT_SIZE_CHECK */
+ if ( RT_SUCCESS(rc)
+ && pExtent->enmType == VMDKETYPE_HOSTED_SPARSE)
+ {
+ /* The spec says that this must be a power of two and greater than 8,
+ * but probably they meant not less than 8. */
+ if ( (pExtent->cSectorsPerGrain & (pExtent->cSectorsPerGrain - 1))
+ || pExtent->cSectorsPerGrain < 8)
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: invalid extent grain size %u in '%s'"), pExtent->cSectorsPerGrain, pExtent->pszFullname);
+ else
+ {
+ /* This code requires that a grain table must hold a power of two multiple
+ * of the number of entries per GT cache entry. */
+ if ( (pExtent->cGTEntries & (pExtent->cGTEntries - 1))
+ || pExtent->cGTEntries < VMDK_GT_CACHELINE_SIZE)
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: grain table cache size problem in '%s'"), pExtent->pszFullname);
+ else
+ {
+ rc = vmdkAllocStreamBuffers(pImage, pExtent);
+ if (RT_SUCCESS(rc))
+ {
+ /* Prohibit any writes to this streamOptimized extent. */
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ pExtent->uAppendPosition = 0;
+
+ if ( !(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ || !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ || !(pImage->uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL))
+ rc = vmdkReadGrainDirectory(pImage, pExtent);
+ else
+ {
+ pExtent->uGrainSectorAbs = pExtent->cOverheadSectors;
+ pExtent->cbGrainStreamRead = 0;
+ }
+ }
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ vmdkFreeExtentData(pImage, pExtent, false);
+
+ return rc;
+}
+
+/**
+ * Internal: write/update the metadata for a sparse extent.
+ */
+static int vmdkWriteMetaSparseExtent(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ uint64_t uOffset, PVDIOCTX pIoCtx)
+{
+ SparseExtentHeader Header;
+
+ memset(&Header, '\0', sizeof(Header));
+ Header.magicNumber = RT_H2LE_U32(VMDK_SPARSE_MAGICNUMBER);
+ Header.version = RT_H2LE_U32(pExtent->uVersion);
+ Header.flags = RT_H2LE_U32(RT_BIT(0));
+ if (pExtent->pRGD)
+ Header.flags |= RT_H2LE_U32(RT_BIT(1));
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ Header.flags |= RT_H2LE_U32(RT_BIT(16) | RT_BIT(17));
+ Header.capacity = RT_H2LE_U64(pExtent->cSectors);
+ Header.grainSize = RT_H2LE_U64(pExtent->cSectorsPerGrain);
+ Header.descriptorOffset = RT_H2LE_U64(pExtent->uDescriptorSector);
+ Header.descriptorSize = RT_H2LE_U64(pExtent->cDescriptorSectors);
+ Header.numGTEsPerGT = RT_H2LE_U32(pExtent->cGTEntries);
+ if (pExtent->fFooter && uOffset == 0)
+ {
+ if (pExtent->pRGD)
+ {
+ Assert(pExtent->uSectorRGD);
+ Header.rgdOffset = RT_H2LE_U64(VMDK_GD_AT_END);
+ Header.gdOffset = RT_H2LE_U64(VMDK_GD_AT_END);
+ }
+ else
+ Header.gdOffset = RT_H2LE_U64(VMDK_GD_AT_END);
+ }
+ else
+ {
+ if (pExtent->pRGD)
+ {
+ Assert(pExtent->uSectorRGD);
+ Header.rgdOffset = RT_H2LE_U64(pExtent->uSectorRGD);
+ Header.gdOffset = RT_H2LE_U64(pExtent->uSectorGD);
+ }
+ else
+ Header.gdOffset = RT_H2LE_U64(pExtent->uSectorGD);
+ }
+ Header.overHead = RT_H2LE_U64(pExtent->cOverheadSectors);
+ Header.uncleanShutdown = pExtent->fUncleanShutdown;
+ Header.singleEndLineChar = '\n';
+ Header.nonEndLineChar = ' ';
+ Header.doubleEndLineChar1 = '\r';
+ Header.doubleEndLineChar2 = '\n';
+ Header.compressAlgorithm = RT_H2LE_U16(pExtent->uCompression);
+
+ int rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pExtent->pFile->pStorage,
+ uOffset, &Header, sizeof(Header),
+ pIoCtx, NULL, NULL);
+ if (RT_FAILURE(rc) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error writing extent header in '%s'"), pExtent->pszFullname);
+ return rc;
+}
+
+/**
+ * Internal: free the buffers used for streamOptimized images.
+ */
+static void vmdkFreeStreamBuffers(PVMDKEXTENT pExtent)
+{
+ if (pExtent->pvCompGrain)
+ {
+ RTMemFree(pExtent->pvCompGrain);
+ pExtent->pvCompGrain = NULL;
+ }
+ if (pExtent->pvGrain)
+ {
+ RTMemFree(pExtent->pvGrain);
+ pExtent->pvGrain = NULL;
+ }
+}
+
+/**
+ * Internal: free the memory used by the extent data structure, optionally
+ * deleting the referenced files.
+ *
+ * @returns VBox status code.
+ * @param pImage Pointer to the image instance data.
+ * @param pExtent The extent to free.
+ * @param fDelete Flag whether to delete the backing storage.
+ */
+static int vmdkFreeExtentData(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ vmdkFreeGrainDirectory(pExtent);
+ if (pExtent->pDescData)
+ {
+ RTMemFree(pExtent->pDescData);
+ pExtent->pDescData = NULL;
+ }
+ if (pExtent->pFile != NULL)
+ {
+ /* Do not delete raw extents, these have full and base names equal. */
+ rc = vmdkFileClose(pImage, &pExtent->pFile,
+ fDelete
+ && pExtent->pszFullname
+ && pExtent->pszBasename
+ && strcmp(pExtent->pszFullname, pExtent->pszBasename));
+ }
+ if (pExtent->pszBasename)
+ {
+ RTMemTmpFree((void *)pExtent->pszBasename);
+ pExtent->pszBasename = NULL;
+ }
+ if (pExtent->pszFullname)
+ {
+ RTStrFree((char *)(void *)pExtent->pszFullname);
+ pExtent->pszFullname = NULL;
+ }
+ vmdkFreeStreamBuffers(pExtent);
+
+ return rc;
+}
+
+/**
+ * Internal: allocate grain table cache if necessary for this image.
+ */
+static int vmdkAllocateGrainTableCache(PVMDKIMAGE pImage)
+{
+ PVMDKEXTENT pExtent;
+
+ /* Allocate grain table cache if any sparse extent is present. */
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ pExtent = &pImage->pExtents[i];
+ if (pExtent->enmType == VMDKETYPE_HOSTED_SPARSE)
+ {
+ /* Allocate grain table cache. */
+ pImage->pGTCache = (PVMDKGTCACHE)RTMemAllocZ(sizeof(VMDKGTCACHE));
+ if (!pImage->pGTCache)
+ return VERR_NO_MEMORY;
+ for (unsigned j = 0; j < VMDK_GT_CACHE_SIZE; j++)
+ {
+ PVMDKGTCACHEENTRY pGCE = &pImage->pGTCache->aGTCache[j];
+ pGCE->uExtent = UINT32_MAX;
+ }
+ pImage->pGTCache->cEntries = VMDK_GT_CACHE_SIZE;
+ break;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: allocate the given number of extents.
+ */
+static int vmdkCreateExtents(PVMDKIMAGE pImage, unsigned cExtents)
+{
+ int rc = VINF_SUCCESS;
+ PVMDKEXTENT pExtents = (PVMDKEXTENT)RTMemAllocZ(cExtents * sizeof(VMDKEXTENT));
+ if (pExtents)
+ {
+ for (unsigned i = 0; i < cExtents; i++)
+ {
+ pExtents[i].pFile = NULL;
+ pExtents[i].pszBasename = NULL;
+ pExtents[i].pszFullname = NULL;
+ pExtents[i].pGD = NULL;
+ pExtents[i].pRGD = NULL;
+ pExtents[i].pDescData = NULL;
+ pExtents[i].uVersion = 1;
+ pExtents[i].uCompression = VMDK_COMPRESSION_NONE;
+ pExtents[i].uExtent = i;
+ pExtents[i].pImage = pImage;
+ }
+ pImage->pExtents = pExtents;
+ pImage->cExtents = cExtents;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Internal: Create an additional file backed extent in split images.
+ * Supports split sparse and flat images.
+ *
+ * @returns VBox status code.
+ * @param pImage VMDK image instance.
+ * @param cbSize Desiried size in bytes of new extent.
+ */
+static int vmdkAddFileBackedExtent(PVMDKIMAGE pImage, uint64_t cbSize)
+{
+ int rc = VINF_SUCCESS;
+ unsigned uImageFlags = pImage->uImageFlags;
+
+ /* Check for unsupported image type. */
+ if ((uImageFlags & VD_VMDK_IMAGE_FLAGS_ESX)
+ || (uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ || (uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK))
+ {
+ return VERR_NOT_SUPPORTED;
+ }
+
+ /* Allocate array of extents and copy existing extents to it. */
+ PVMDKEXTENT pNewExtents = (PVMDKEXTENT)RTMemAllocZ((pImage->cExtents + 1) * sizeof(VMDKEXTENT));
+ if (!pNewExtents)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ memcpy(pNewExtents, pImage->pExtents, pImage->cExtents * sizeof(VMDKEXTENT));
+
+ /* Locate newly created extent and populate default metadata. */
+ PVMDKEXTENT pExtent = &pNewExtents[pImage->cExtents];
+
+ pExtent->pFile = NULL;
+ pExtent->pszBasename = NULL;
+ pExtent->pszFullname = NULL;
+ pExtent->pGD = NULL;
+ pExtent->pRGD = NULL;
+ pExtent->pDescData = NULL;
+ pExtent->uVersion = 1;
+ pExtent->uCompression = VMDK_COMPRESSION_NONE;
+ pExtent->uExtent = pImage->cExtents;
+ pExtent->pImage = pImage;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(cbSize);
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->uSectorOffset = 0;
+ pExtent->fMetaDirty = true;
+
+ /* Apply image type specific meta data. */
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ {
+ pExtent->enmType = VMDKETYPE_FLAT;
+ }
+ else
+ {
+ uint64_t cSectorsPerGDE, cSectorsPerGD;
+ pExtent->enmType = VMDKETYPE_HOSTED_SPARSE;
+ pExtent->cSectors = VMDK_BYTE2SECTOR(RT_ALIGN_64(cbSize, _64K));
+ pExtent->cSectorsPerGrain = VMDK_BYTE2SECTOR(_64K);
+ pExtent->cGTEntries = 512;
+ cSectorsPerGDE = pExtent->cGTEntries * pExtent->cSectorsPerGrain;
+ pExtent->cSectorsPerGDE = cSectorsPerGDE;
+ pExtent->cGDEntries = (pExtent->cSectors + cSectorsPerGDE - 1) / cSectorsPerGDE;
+ cSectorsPerGD = (pExtent->cGDEntries + (512 / sizeof(uint32_t) - 1)) / (512 / sizeof(uint32_t));
+ }
+
+ /* Allocate and set file name for extent. */
+ char *pszBasenameSubstr = RTPathFilename(pImage->pszFilename);
+ AssertPtr(pszBasenameSubstr);
+
+ char *pszBasenameSuff = RTPathSuffix(pszBasenameSubstr);
+ char *pszBasenameBase = RTStrDup(pszBasenameSubstr);
+ RTPathStripSuffix(pszBasenameBase);
+ char *pszTmp;
+ size_t cbTmp;
+
+ if (pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ RTStrAPrintf(&pszTmp, "%s-f%03d%s", pszBasenameBase,
+ pExtent->uExtent + 1, pszBasenameSuff);
+ else
+ RTStrAPrintf(&pszTmp, "%s-s%03d%s", pszBasenameBase, pExtent->uExtent + 1,
+ pszBasenameSuff);
+
+ RTStrFree(pszBasenameBase);
+ if (!pszTmp)
+ return VERR_NO_STR_MEMORY;
+ cbTmp = strlen(pszTmp) + 1;
+ char *pszBasename = (char *)RTMemTmpAlloc(cbTmp);
+ if (!pszBasename)
+ {
+ RTStrFree(pszTmp);
+ return VERR_NO_MEMORY;
+ }
+
+ memcpy(pszBasename, pszTmp, cbTmp);
+ RTStrFree(pszTmp);
+
+ pExtent->pszBasename = pszBasename;
+
+ char *pszBasedirectory = RTStrDup(pImage->pszFilename);
+ if (!pszBasedirectory)
+ return VERR_NO_STR_MEMORY;
+ RTPathStripFilename(pszBasedirectory);
+ char *pszFullname = RTPathJoinA(pszBasedirectory, pExtent->pszBasename);
+ RTStrFree(pszBasedirectory);
+ if (!pszFullname)
+ return VERR_NO_STR_MEMORY;
+ pExtent->pszFullname = pszFullname;
+
+ /* Create file for extent. */
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszBasename, pExtent->pszFullname,
+ VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags,
+ true /* fCreate */));
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new file '%s'"), pExtent->pszFullname);
+
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ {
+ /* For flat images: Pre allocate file space. */
+ rc = vdIfIoIntFileSetAllocationSize(pImage->pIfIo, pExtent->pFile->pStorage, cbSize,
+ 0 /* fFlags */, NULL, 0, 0);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not set size of new file '%s'"), pExtent->pszFullname);
+ }
+ else
+ {
+ /* For sparse images: Allocate new grain directories/tables. */
+ /* fPreAlloc should never be false because VMware can't use such images. */
+ rc = vmdkCreateGrainDirectory(pImage, pExtent,
+ RT_MAX( pExtent->uDescriptorSector
+ + pExtent->cDescriptorSectors,
+ 1),
+ true /* fPreAlloc */);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new grain directory in '%s'"), pExtent->pszFullname);
+ }
+
+ /* Insert new extent into descriptor file. */
+ rc = vmdkDescExtInsert(pImage, &pImage->Descriptor, pExtent->enmAccess,
+ pExtent->cNominalSectors, pExtent->enmType,
+ pExtent->pszBasename, pExtent->uSectorOffset);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not insert the extent list into descriptor in '%s'"), pImage->pszFilename);
+
+ pImage->pExtents = pNewExtents;
+ pImage->cExtents++;
+
+ return rc;
+}
+
+/**
+ * Reads and processes the descriptor embedded in sparse images.
+ *
+ * @returns VBox status code.
+ * @param pImage VMDK image instance.
+ * @param pFile The sparse file handle.
+ */
+static int vmdkDescriptorReadSparse(PVMDKIMAGE pImage, PVMDKFILE pFile)
+{
+ /* It's a hosted single-extent image. */
+ int rc = vmdkCreateExtents(pImage, 1);
+ if (RT_SUCCESS(rc))
+ {
+ /* The opened file is passed to the extent. No separate descriptor
+ * file, so no need to keep anything open for the image. */
+ PVMDKEXTENT pExtent = &pImage->pExtents[0];
+ pExtent->pFile = pFile;
+ pImage->pFile = NULL;
+ pExtent->pszFullname = RTPathAbsDup(pImage->pszFilename);
+ if (RT_LIKELY(pExtent->pszFullname))
+ {
+ /* As we're dealing with a monolithic image here, there must
+ * be a descriptor embedded in the image file. */
+ rc = vmdkReadBinaryMetaExtent(pImage, pExtent, true /* fMagicAlreadyRead */);
+ if ( RT_SUCCESS(rc)
+ && pExtent->uDescriptorSector
+ && pExtent->cDescriptorSectors)
+ {
+ /* HACK: extend the descriptor if it is unusually small and it fits in
+ * the unused space after the image header. Allows opening VMDK files
+ * with extremely small descriptor in read/write mode.
+ *
+ * The previous version introduced a possible regression for VMDK stream
+ * optimized images from VMware which tend to have only a single sector sized
+ * descriptor. Increasing the descriptor size resulted in adding the various uuid
+ * entries required to make it work with VBox but for stream optimized images
+ * the updated binary header wasn't written to the disk creating a mismatch
+ * between advertised and real descriptor size.
+ *
+ * The descriptor size will be increased even if opened readonly now if there
+ * enough room but the new value will not be written back to the image.
+ */
+ if ( pExtent->cDescriptorSectors < 3
+ && (int64_t)pExtent->uSectorGD - pExtent->uDescriptorSector >= 4
+ && (!pExtent->uSectorRGD || (int64_t)pExtent->uSectorRGD - pExtent->uDescriptorSector >= 4))
+ {
+ uint64_t cDescriptorSectorsOld = pExtent->cDescriptorSectors;
+
+ pExtent->cDescriptorSectors = 4;
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /*
+ * Update the on disk number now to make sure we don't introduce inconsistencies
+ * in case of stream optimized images from VMware where the descriptor is just
+ * one sector big (the binary header is not written to disk for complete
+ * stream optimized images in vmdkFlushImage()).
+ */
+ uint64_t u64DescSizeNew = RT_H2LE_U64(pExtent->cDescriptorSectors);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pFile->pStorage,
+ RT_UOFFSETOF(SparseExtentHeader, descriptorSize),
+ &u64DescSizeNew, sizeof(u64DescSizeNew));
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("Increasing the descriptor size failed with %Rrc\n", rc));
+ /* Restore the old size and carry on. */
+ pExtent->cDescriptorSectors = cDescriptorSectorsOld;
+ }
+ }
+ }
+ /* Read the descriptor from the extent. */
+ pExtent->pDescData = (char *)RTMemAllocZ(VMDK_SECTOR2BYTE(pExtent->cDescriptorSectors));
+ if (RT_LIKELY(pExtent->pDescData))
+ {
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uDescriptorSector),
+ pExtent->pDescData,
+ VMDK_SECTOR2BYTE(pExtent->cDescriptorSectors));
+ if (RT_SUCCESS(rc))
+ {
+ rc = vmdkParseDescriptor(pImage, pExtent->pDescData,
+ VMDK_SECTOR2BYTE(pExtent->cDescriptorSectors));
+ if ( RT_SUCCESS(rc)
+ && ( !(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ || !(pImage->uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)))
+ {
+ rc = vmdkReadMetaExtent(pImage, pExtent);
+ if (RT_SUCCESS(rc))
+ {
+ /* Mark the extent as unclean if opened in read-write mode. */
+ if ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && !(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ {
+ pExtent->fUncleanShutdown = true;
+ pExtent->fMetaDirty = true;
+ }
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: read error for descriptor in '%s'"), pExtent->pszFullname);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: monolithic image without descriptor in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Reads the descriptor from a pure text file.
+ *
+ * @returns VBox status code.
+ * @param pImage VMDK image instance.
+ * @param pFile The descriptor file handle.
+ */
+static int vmdkDescriptorReadAscii(PVMDKIMAGE pImage, PVMDKFILE pFile)
+{
+ /* Allocate at least 10K, and make sure that there is 5K free space
+ * in case new entries need to be added to the descriptor. Never
+ * allocate more than 128K, because that's no valid descriptor file
+ * and will result in the correct "truncated read" error handling. */
+ uint64_t cbFileSize;
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pFile->pStorage, &cbFileSize);
+ if ( RT_SUCCESS(rc)
+ && cbFileSize >= 50)
+ {
+ uint64_t cbSize = cbFileSize;
+ if (cbSize % VMDK_SECTOR2BYTE(10))
+ cbSize += VMDK_SECTOR2BYTE(20) - cbSize % VMDK_SECTOR2BYTE(10);
+ else
+ cbSize += VMDK_SECTOR2BYTE(10);
+ cbSize = RT_MIN(cbSize, _128K);
+ pImage->cbDescAlloc = RT_MAX(VMDK_SECTOR2BYTE(20), cbSize);
+ pImage->pDescData = (char *)RTMemAllocZ(pImage->cbDescAlloc);
+ if (RT_LIKELY(pImage->pDescData))
+ {
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pFile->pStorage, 0, pImage->pDescData,
+ RT_MIN(pImage->cbDescAlloc, cbFileSize));
+ if (RT_SUCCESS(rc))
+ {
+#if 0 /** @todo Revisit */
+ cbRead += sizeof(u32Magic);
+ if (cbRead == pImage->cbDescAlloc)
+ {
+ /* Likely the read is truncated. Better fail a bit too early
+ * (normally the descriptor is much smaller than our buffer). */
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: cannot read descriptor in '%s'"), pImage->pszFilename);
+ goto out;
+ }
+#endif
+ rc = vmdkParseDescriptor(pImage, pImage->pDescData,
+ pImage->cbDescAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned i = 0; i < pImage->cExtents && RT_SUCCESS(rc); i++)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[i];
+ if (pExtent->pszBasename)
+ {
+ /* Hack to figure out whether the specified name in the
+ * extent descriptor is absolute. Doesn't always work, but
+ * should be good enough for now. */
+ char *pszFullname;
+ /** @todo implement proper path absolute check. */
+ if (pExtent->pszBasename[0] == RTPATH_SLASH)
+ {
+ pszFullname = RTStrDup(pExtent->pszBasename);
+ if (!pszFullname)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+ else
+ {
+ char *pszDirname = RTStrDup(pImage->pszFilename);
+ if (!pszDirname)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ RTPathStripFilename(pszDirname);
+ pszFullname = RTPathJoinA(pszDirname, pExtent->pszBasename);
+ RTStrFree(pszDirname);
+ if (!pszFullname)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+ }
+ pExtent->pszFullname = pszFullname;
+ }
+ else
+ pExtent->pszFullname = NULL;
+
+ unsigned uOpenFlags = pImage->uOpenFlags | ((pExtent->enmAccess == VMDKACCESS_READONLY) ? VD_OPEN_FLAGS_READONLY : 0);
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_HOSTED_SPARSE:
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszBasename, pExtent->pszFullname,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */));
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD
+ * layer has the choice of retrying the open if it
+ * failed. */
+ break;
+ }
+ rc = vmdkReadBinaryMetaExtent(pImage, pExtent,
+ false /* fMagicAlreadyRead */);
+ if (RT_FAILURE(rc))
+ break;
+ rc = vmdkReadMetaExtent(pImage, pExtent);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Mark extent as unclean if opened in read-write mode. */
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ pExtent->fUncleanShutdown = true;
+ pExtent->fMetaDirty = true;
+ }
+ break;
+ case VMDKETYPE_VMFS:
+ case VMDKETYPE_FLAT:
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszBasename, pExtent->pszFullname,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */));
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD
+ * layer has the choice of retrying the open if it
+ * failed. */
+ break;
+ }
+ break;
+ case VMDKETYPE_ZERO:
+ /* Nothing to do. */
+ break;
+ default:
+ AssertMsgFailed(("unknown vmdk extent type %d\n", pExtent->enmType));
+ }
+ }
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: read error for descriptor in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS, N_("VMDK: descriptor in '%s' is too short"), pImage->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Read and process the descriptor based on the image type.
+ *
+ * @returns VBox status code.
+ * @param pImage VMDK image instance.
+ * @param pFile VMDK file handle.
+ */
+static int vmdkDescriptorRead(PVMDKIMAGE pImage, PVMDKFILE pFile)
+{
+ uint32_t u32Magic;
+
+ /* Read magic (if present). */
+ int rc = vdIfIoIntFileReadSync(pImage->pIfIo, pFile->pStorage, 0,
+ &u32Magic, sizeof(u32Magic));
+ if (RT_SUCCESS(rc))
+ {
+ /* Handle the file according to its magic number. */
+ if (RT_LE2H_U32(u32Magic) == VMDK_SPARSE_MAGICNUMBER)
+ rc = vmdkDescriptorReadSparse(pImage, pFile);
+ else
+ rc = vmdkDescriptorReadAscii(pImage, pFile);
+ }
+ else
+ {
+ vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error reading the magic number in '%s'"), pImage->pszFilename);
+ rc = VERR_VD_VMDK_INVALID_HEADER;
+ }
+
+ return rc;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int vmdkOpenImage(PVMDKIMAGE pImage, unsigned uOpenFlags)
+{
+ pImage->uOpenFlags = uOpenFlags;
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /*
+ * Open the image.
+ * We don't have to check for asynchronous access because
+ * we only support raw access and the opened file is a description
+ * file were no data is stored.
+ */
+ PVMDKFILE pFile;
+ int rc = vmdkFileOpen(pImage, &pFile, NULL, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */));
+ if (RT_SUCCESS(rc))
+ {
+ pImage->pFile = pFile;
+
+ rc = vmdkDescriptorRead(pImage, pFile);
+ if (RT_SUCCESS(rc))
+ {
+ /* Determine PCHS geometry if not set. */
+ if (pImage->PCHSGeometry.cCylinders == 0)
+ {
+ uint64_t cCylinders = VMDK_BYTE2SECTOR(pImage->cbSize)
+ / pImage->PCHSGeometry.cHeads
+ / pImage->PCHSGeometry.cSectors;
+ pImage->PCHSGeometry.cCylinders = (unsigned)RT_MIN(cCylinders, 16383);
+ if ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && !(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ {
+ rc = vmdkDescSetPCHSGeometry(pImage, &pImage->PCHSGeometry);
+ AssertRC(rc);
+ }
+ }
+
+ /* Update the image metadata now in case has changed. */
+ rc = vmdkFlushImage(pImage, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Figure out a few per-image constants from the extents. */
+ pImage->cbSize = 0;
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[i];
+ if (pExtent->enmType == VMDKETYPE_HOSTED_SPARSE)
+ {
+ /* Here used to be a check whether the nominal size of an extent
+ * is a multiple of the grain size. The spec says that this is
+ * always the case, but unfortunately some files out there in the
+ * wild violate the spec (e.g. ReactOS 0.3.1). */
+ }
+ else if ( pExtent->enmType == VMDKETYPE_FLAT
+ || pExtent->enmType == VMDKETYPE_ZERO)
+ pImage->uImageFlags |= VD_IMAGE_FLAGS_FIXED;
+
+ pImage->cbSize += VMDK_SECTOR2BYTE(pExtent->cNominalSectors);
+ }
+
+ if ( !(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ || !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ || !(pImage->uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL))
+ rc = vmdkAllocateGrainTableCache(pImage);
+ }
+ }
+ }
+ /* else: Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ vmdkFreeImage(pImage, false, false /*fFlush*/); /* Don't try to flush anything if opening failed. */
+ return rc;
+}
+
+/**
+ * Frees a raw descriptor.
+ * @internal
+ */
+static int vmdkRawDescFree(PVDISKRAW pRawDesc)
+{
+ if (!pRawDesc)
+ return VINF_SUCCESS;
+
+ RTStrFree(pRawDesc->pszRawDisk);
+ pRawDesc->pszRawDisk = NULL;
+
+ /* Partitions: */
+ for (unsigned i = 0; i < pRawDesc->cPartDescs; i++)
+ {
+ RTStrFree(pRawDesc->pPartDescs[i].pszRawDevice);
+ pRawDesc->pPartDescs[i].pszRawDevice = NULL;
+
+ RTMemFree(pRawDesc->pPartDescs[i].pvPartitionData);
+ pRawDesc->pPartDescs[i].pvPartitionData = NULL;
+ }
+
+ RTMemFree(pRawDesc->pPartDescs);
+ pRawDesc->pPartDescs = NULL;
+
+ RTMemFree(pRawDesc);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Helper that grows the raw partition descriptor table by @a cToAdd entries,
+ * returning the pointer to the first new entry.
+ * @internal
+ */
+static int vmdkRawDescAppendPartDesc(PVMDKIMAGE pImage, PVDISKRAW pRawDesc, uint32_t cToAdd, PVDISKRAWPARTDESC *ppRet)
+{
+ uint32_t const cOld = pRawDesc->cPartDescs;
+ uint32_t const cNew = cOld + cToAdd;
+ PVDISKRAWPARTDESC paNew = (PVDISKRAWPARTDESC)RTMemReallocZ(pRawDesc->pPartDescs,
+ cOld * sizeof(pRawDesc->pPartDescs[0]),
+ cNew * sizeof(pRawDesc->pPartDescs[0]));
+ if (paNew)
+ {
+ pRawDesc->cPartDescs = cNew;
+ pRawDesc->pPartDescs = paNew;
+
+ *ppRet = &paNew[cOld];
+ return VINF_SUCCESS;
+ }
+ *ppRet = NULL;
+ return vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Out of memory growing the partition descriptors (%u -> %u)."),
+ pImage->pszFilename, cOld, cNew);
+}
+
+/**
+ * @callback_method_impl{FNRTSORTCMP}
+ */
+static DECLCALLBACK(int) vmdkRawDescPartComp(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ int64_t const iDelta = ((PVDISKRAWPARTDESC)pvElement1)->offStartInVDisk - ((PVDISKRAWPARTDESC)pvElement2)->offStartInVDisk;
+ return iDelta < 0 ? -1 : iDelta > 0 ? 1 : 0;
+}
+
+/**
+ * Post processes the partition descriptors.
+ *
+ * Sorts them and check that they don't overlap.
+ */
+static int vmdkRawDescPostProcessPartitions(PVMDKIMAGE pImage, PVDISKRAW pRawDesc, uint64_t cbSize)
+{
+ /*
+ * Sort data areas in ascending order of start.
+ */
+ RTSortShell(pRawDesc->pPartDescs, pRawDesc->cPartDescs, sizeof(pRawDesc->pPartDescs[0]), vmdkRawDescPartComp, NULL);
+
+ /*
+ * Check that we don't have overlapping descriptors. If we do, that's an
+ * indication that the drive is corrupt or that the RTDvm code is buggy.
+ */
+ VDISKRAWPARTDESC const *paPartDescs = pRawDesc->pPartDescs;
+ for (uint32_t i = 0; i < pRawDesc->cPartDescs; i++)
+ {
+ uint64_t offLast = paPartDescs[i].offStartInVDisk + paPartDescs[i].cbData;
+ if (offLast <= paPartDescs[i].offStartInVDisk)
+ return vdIfError(pImage->pIfError, VERR_FILESYSTEM_CORRUPT /*?*/, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Bogus partition descriptor #%u (%#RX64 LB %#RX64%s): Wrap around or zero"),
+ pImage->pszFilename, i, paPartDescs[i].offStartInVDisk, paPartDescs[i].cbData,
+ paPartDescs[i].pvPartitionData ? " (data)" : "");
+ offLast -= 1;
+
+ if (i + 1 < pRawDesc->cPartDescs && offLast >= paPartDescs[i + 1].offStartInVDisk)
+ return vdIfError(pImage->pIfError, VERR_FILESYSTEM_CORRUPT /*?*/, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition descriptor #%u (%#RX64 LB %#RX64%s) overlaps with the next (%#RX64 LB %#RX64%s)"),
+ pImage->pszFilename, i, paPartDescs[i].offStartInVDisk, paPartDescs[i].cbData,
+ paPartDescs[i].pvPartitionData ? " (data)" : "", paPartDescs[i + 1].offStartInVDisk,
+ paPartDescs[i + 1].cbData, paPartDescs[i + 1].pvPartitionData ? " (data)" : "");
+ if (offLast >= cbSize)
+ return vdIfError(pImage->pIfError, VERR_FILESYSTEM_CORRUPT /*?*/, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition descriptor #%u (%#RX64 LB %#RX64%s) goes beyond the end of the drive (%#RX64)"),
+ pImage->pszFilename, i, paPartDescs[i].offStartInVDisk, paPartDescs[i].cbData,
+ paPartDescs[i].pvPartitionData ? " (data)" : "", cbSize);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+#ifdef RT_OS_LINUX
+/**
+ * Searches the dir specified in @a pszBlockDevDir for subdirectories with a
+ * 'dev' file matching @a uDevToLocate.
+ *
+ * This is used both
+ *
+ * @returns IPRT status code, errors have been reported properly.
+ * @param pImage For error reporting.
+ * @param pszBlockDevDir Input: Path to the directory search under.
+ * Output: Path to the directory containing information
+ * for @a uDevToLocate.
+ * @param cbBlockDevDir The size of the buffer @a pszBlockDevDir points to.
+ * @param uDevToLocate The device number of the block device info dir to
+ * locate.
+ * @param pszDevToLocate For error reporting.
+ */
+static int vmdkFindSysBlockDevPath(PVMDKIMAGE pImage, char *pszBlockDevDir, size_t cbBlockDevDir,
+ dev_t uDevToLocate, const char *pszDevToLocate)
+{
+ size_t const cchDir = RTPathEnsureTrailingSeparator(pszBlockDevDir, cbBlockDevDir);
+ AssertReturn(cchDir > 0, VERR_BUFFER_OVERFLOW);
+
+ RTDIR hDir = NIL_RTDIR;
+ int rc = RTDirOpen(&hDir, pszBlockDevDir);
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ RTDIRENTRY Entry;
+ rc = RTDirRead(hDir, &Entry, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* We're interested in directories and symlinks. */
+ if ( Entry.enmType == RTDIRENTRYTYPE_DIRECTORY
+ || Entry.enmType == RTDIRENTRYTYPE_SYMLINK
+ || Entry.enmType == RTDIRENTRYTYPE_UNKNOWN)
+ {
+ rc = RTStrCopy(&pszBlockDevDir[cchDir], cbBlockDevDir - cchDir, Entry.szName);
+ AssertContinue(RT_SUCCESS(rc)); /* should not happen! */
+
+ dev_t uThisDevNo = ~uDevToLocate;
+ rc = RTLinuxSysFsReadDevNumFile(&uThisDevNo, "%s/dev", pszBlockDevDir);
+ if (RT_SUCCESS(rc) && uThisDevNo == uDevToLocate)
+ break;
+ }
+ }
+ else
+ {
+ pszBlockDevDir[cchDir] = '\0';
+ if (rc == VERR_NO_MORE_FILES)
+ rc = vdIfError(pImage->pIfError, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to locate device corresponding to '%s' under '%s'"),
+ pImage->pszFilename, pszDevToLocate, pszBlockDevDir);
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. RTDirRead failed enumerating '%s': %Rrc"),
+ pImage->pszFilename, pszBlockDevDir, rc);
+ break;
+ }
+ }
+ RTDirClose(hDir);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to open dir '%s' for listing: %Rrc"),
+ pImage->pszFilename, pszBlockDevDir, rc);
+ return rc;
+}
+#endif /* RT_OS_LINUX */
+
+#ifdef RT_OS_FREEBSD
+
+
+/**
+ * Reads the config data from the provider and returns offset and size
+ *
+ * @return IPRT status code
+ * @param pProvider GEOM provider representing partition
+ * @param pcbOffset Placeholder for the offset of the partition
+ * @param pcbSize Placeholder for the size of the partition
+ */
+static int vmdkReadPartitionsParamsFromProvider(gprovider *pProvider, uint64_t *pcbOffset, uint64_t *pcbSize)
+{
+ gconfig *pConfEntry;
+ int rc = VERR_NOT_FOUND;
+
+ /*
+ * Required parameters are located in the list containing key/value pairs.
+ * Both key and value are in text form. Manuals tells nothing about the fact
+ * that the both parameters should be present in the list. Thus, there are
+ * cases when only one parameter is presented. To handle such cases we treat
+ * absent params as zero allowing the caller decide the case is either correct
+ * or an error.
+ */
+ uint64_t cbOffset = 0;
+ uint64_t cbSize = 0;
+ LIST_FOREACH(pConfEntry, &pProvider->lg_config, lg_config)
+ {
+ if (RTStrCmp(pConfEntry->lg_name, "offset") == 0)
+ {
+ cbOffset = RTStrToUInt64(pConfEntry->lg_val);
+ rc = VINF_SUCCESS;
+ }
+ else if (RTStrCmp(pConfEntry->lg_name, "length") == 0)
+ {
+ cbSize = RTStrToUInt64(pConfEntry->lg_val);
+ rc = VINF_SUCCESS;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *pcbOffset = cbOffset;
+ *pcbSize = cbSize;
+ }
+ return rc;
+}
+
+
+/**
+ * Searches the partition specified by name and calculates its size and absolute offset.
+ *
+ * @return IPRT status code.
+ * @param pParentClass Class containing pParentGeom
+ * @param pszParentGeomName Name of the parent geom where we are looking for provider
+ * @param pszProviderName Name of the provider we are looking for
+ * @param pcbAbsoluteOffset Placeholder for the absolute offset of the partition, i.e. offset from the beginning of the disk
+ * @param psbSize Placeholder for the size of the partition.
+ */
+static int vmdkFindPartitionParamsByName(gclass *pParentClass, const char *pszParentGeomName, const char *pszProviderName,
+ uint64_t *pcbAbsoluteOffset, uint64_t *pcbSize)
+{
+ AssertReturn(pParentClass, VERR_INVALID_PARAMETER);
+ AssertReturn(pszParentGeomName, VERR_INVALID_PARAMETER);
+ AssertReturn(pszProviderName, VERR_INVALID_PARAMETER);
+ AssertReturn(pcbAbsoluteOffset, VERR_INVALID_PARAMETER);
+ AssertReturn(pcbSize, VERR_INVALID_PARAMETER);
+
+ ggeom *pParentGeom;
+ int rc = VERR_NOT_FOUND;
+ LIST_FOREACH(pParentGeom, &pParentClass->lg_geom, lg_geom)
+ {
+ if (RTStrCmp(pParentGeom->lg_name, pszParentGeomName) == 0)
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+
+ gprovider *pProvider;
+ /*
+ * First, go over providers without handling EBR or BSDLabel
+ * partitions for case when looking provider is child
+ * of the givng geom, to reduce searching time
+ */
+ LIST_FOREACH(pProvider, &pParentGeom->lg_provider, lg_provider)
+ {
+ if (RTStrCmp(pProvider->lg_name, pszProviderName) == 0)
+ return vmdkReadPartitionsParamsFromProvider(pProvider, pcbAbsoluteOffset, pcbSize);
+ }
+
+ /*
+ * No provider found. Go over the parent geom again
+ * and make recursions if geom represents EBR or BSDLabel.
+ * In this case given parent geom contains only EBR or BSDLabel
+ * partition itself and their own partitions are in the separate
+ * geoms. Also, partition offsets are relative to geom, so
+ * we have to add offset from child provider with parent geoms
+ * provider
+ */
+
+ LIST_FOREACH(pProvider, &pParentGeom->lg_provider, lg_provider)
+ {
+ uint64_t cbOffset = 0;
+ uint64_t cbSize = 0;
+ rc = vmdkReadPartitionsParamsFromProvider(pProvider, &cbOffset, &cbSize);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint64_t cbProviderOffset = 0;
+ uint64_t cbProviderSize = 0;
+ rc = vmdkFindPartitionParamsByName(pParentClass, pProvider->lg_name, pszProviderName, &cbProviderOffset, &cbProviderSize);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbAbsoluteOffset = cbOffset + cbProviderOffset;
+ *pcbSize = cbProviderSize;
+ return rc;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+#endif
+
+
+/**
+ * Attempts to verify the raw partition path.
+ *
+ * We don't want to trust RTDvm and the partition device node morphing blindly.
+ */
+static int vmdkRawDescVerifyPartitionPath(PVMDKIMAGE pImage, PVDISKRAWPARTDESC pPartDesc, uint32_t idxPartition,
+ const char *pszRawDrive, RTFILE hRawDrive, uint32_t cbSector, RTDVMVOLUME hVol)
+{
+ RT_NOREF(pImage, pPartDesc, idxPartition, pszRawDrive, hRawDrive, cbSector, hVol);
+
+ /*
+ * Try open the raw partition device.
+ */
+ RTFILE hRawPart = NIL_RTFILE;
+ int rc = RTFileOpen(&hRawPart, pPartDesc->pszRawDevice, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to open partition #%u on '%s' via '%s' (%Rrc)"),
+ pImage->pszFilename, idxPartition, pszRawDrive, pPartDesc->pszRawDevice, rc);
+
+ /*
+ * Compare the partition UUID if we can get it.
+ */
+#ifdef RT_OS_WINDOWS
+ DWORD cbReturned;
+
+ /* 1. Get the device numbers for both handles, they should have the same disk. */
+ STORAGE_DEVICE_NUMBER DevNum1;
+ RT_ZERO(DevNum1);
+ if (!DeviceIoControl((HANDLE)RTFileToNative(hRawDrive), IOCTL_STORAGE_GET_DEVICE_NUMBER,
+ NULL /*pvInBuffer*/, 0 /*cbInBuffer*/, &DevNum1, sizeof(DevNum1), &cbReturned, NULL /*pOverlapped*/))
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromWin32(GetLastError()), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. IOCTL_STORAGE_GET_DEVICE_NUMBER failed on '%s': %u"),
+ pImage->pszFilename, pszRawDrive, GetLastError());
+
+ STORAGE_DEVICE_NUMBER DevNum2;
+ RT_ZERO(DevNum2);
+ if (!DeviceIoControl((HANDLE)RTFileToNative(hRawPart), IOCTL_STORAGE_GET_DEVICE_NUMBER,
+ NULL /*pvInBuffer*/, 0 /*cbInBuffer*/, &DevNum2, sizeof(DevNum2), &cbReturned, NULL /*pOverlapped*/))
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromWin32(GetLastError()), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. IOCTL_STORAGE_GET_DEVICE_NUMBER failed on '%s': %u"),
+ pImage->pszFilename, pPartDesc->pszRawDevice, GetLastError());
+ if ( RT_SUCCESS(rc)
+ && ( DevNum1.DeviceNumber != DevNum2.DeviceNumber
+ || DevNum1.DeviceType != DevNum2.DeviceType))
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s' (%#x != %#x || %#x != %#x)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive,
+ DevNum1.DeviceNumber, DevNum2.DeviceNumber, DevNum1.DeviceType, DevNum2.DeviceType);
+ if (RT_SUCCESS(rc))
+ {
+ /* Get the partitions from the raw drive and match up with the volume info
+ from RTDvm. The partition number is found in DevNum2. */
+ DWORD cbNeeded = 0;
+ if ( DeviceIoControl((HANDLE)RTFileToNative(hRawDrive), IOCTL_DISK_GET_DRIVE_LAYOUT_EX,
+ NULL /*pvInBuffer*/, 0 /*cbInBuffer*/, NULL, 0, &cbNeeded, NULL /*pOverlapped*/)
+ || cbNeeded < RT_UOFFSETOF_DYN(DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry[1]))
+ cbNeeded = RT_UOFFSETOF_DYN(DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry[64]);
+ cbNeeded += sizeof(PARTITION_INFORMATION_EX) * 2; /* just in case */
+ DRIVE_LAYOUT_INFORMATION_EX *pLayout = (DRIVE_LAYOUT_INFORMATION_EX *)RTMemTmpAllocZ(cbNeeded);
+ if (pLayout)
+ {
+ cbReturned = 0;
+ if (DeviceIoControl((HANDLE)RTFileToNative(hRawDrive), IOCTL_DISK_GET_DRIVE_LAYOUT_EX,
+ NULL /*pvInBuffer*/, 0 /*cbInBuffer*/, pLayout, cbNeeded, &cbReturned, NULL /*pOverlapped*/))
+ {
+ /* Find the entry with the given partition number (it's not an index, array contains empty MBR entries ++). */
+ unsigned iEntry = 0;
+ while ( iEntry < pLayout->PartitionCount
+ && pLayout->PartitionEntry[iEntry].PartitionNumber != DevNum2.PartitionNumber)
+ iEntry++;
+ if (iEntry < pLayout->PartitionCount)
+ {
+ /* Compare the basics */
+ PARTITION_INFORMATION_EX const * const pLayoutEntry = &pLayout->PartitionEntry[iEntry];
+ if (pLayoutEntry->StartingOffset.QuadPart != (int64_t)pPartDesc->offStartInVDisk)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': StartingOffset %RU64, expected %RU64"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive,
+ pLayoutEntry->StartingOffset.QuadPart, pPartDesc->offStartInVDisk);
+ else if (pLayoutEntry->PartitionLength.QuadPart != (int64_t)pPartDesc->cbData)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': PartitionLength %RU64, expected %RU64"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive,
+ pLayoutEntry->PartitionLength.QuadPart, pPartDesc->cbData);
+ /** @todo We could compare the MBR type, GPT type and ID. */
+ RT_NOREF(hVol);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': PartitionCount (%#x vs %#x)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive,
+ DevNum2.PartitionNumber, pLayout->PartitionCount);
+# ifndef LOG_ENABLED
+ if (RT_FAILURE(rc))
+# endif
+ {
+ LogRel(("VMDK: Windows reports %u partitions for '%s':\n", pLayout->PartitionCount, pszRawDrive));
+ PARTITION_INFORMATION_EX const *pEntry = &pLayout->PartitionEntry[0];
+ for (DWORD i = 0; i < pLayout->PartitionCount; i++, pEntry++)
+ {
+ LogRel(("VMDK: #%u/%u: %016RU64 LB %016RU64 style=%d rewrite=%d",
+ i, pEntry->PartitionNumber, pEntry->StartingOffset.QuadPart, pEntry->PartitionLength.QuadPart,
+ pEntry->PartitionStyle, pEntry->RewritePartition));
+ if (pEntry->PartitionStyle == PARTITION_STYLE_MBR)
+ LogRel((" type=%#x boot=%d rec=%d hidden=%u\n", pEntry->Mbr.PartitionType, pEntry->Mbr.BootIndicator,
+ pEntry->Mbr.RecognizedPartition, pEntry->Mbr.HiddenSectors));
+ else if (pEntry->PartitionStyle == PARTITION_STYLE_GPT)
+ LogRel((" type=%RTuuid id=%RTuuid aatrib=%RX64 name=%.36ls\n", &pEntry->Gpt.PartitionType,
+ &pEntry->Gpt.PartitionId, pEntry->Gpt.Attributes, &pEntry->Gpt.Name[0]));
+ else
+ LogRel(("\n"));
+ }
+ LogRel(("VMDK: Looked for partition #%u (%u, '%s') at %RU64 LB %RU64\n", DevNum2.PartitionNumber,
+ idxPartition, pPartDesc->pszRawDevice, pPartDesc->offStartInVDisk, pPartDesc->cbData));
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromWin32(GetLastError()), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. IOCTL_DISK_GET_DRIVE_LAYOUT_EX failed on '%s': %u (cb %u, cbRet %u)"),
+ pImage->pszFilename, pPartDesc->pszRawDevice, GetLastError(), cbNeeded, cbReturned);
+ RTMemTmpFree(pLayout);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+
+#elif defined(RT_OS_LINUX)
+ RT_NOREF(hVol);
+
+ /* Stat the two devices first to get their device numbers. (We probably
+ could make some assumptions here about the major & minor number assignments
+ for legacy nodes, but it doesn't hold up for nvme, so we'll skip that.) */
+ struct stat StDrive, StPart;
+ if (fstat((int)RTFileToNative(hRawDrive), &StDrive) != 0)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. fstat failed on '%s': %d"), pImage->pszFilename, pszRawDrive, errno);
+ else if (fstat((int)RTFileToNative(hRawPart), &StPart) != 0)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. fstat failed on '%s': %d"), pImage->pszFilename, pPartDesc->pszRawDevice, errno);
+ else
+ {
+ /* Scan the directories immediately under /sys/block/ for one with a
+ 'dev' file matching the drive's device number: */
+ char szSysPath[RTPATH_MAX];
+ rc = RTLinuxConstructPath(szSysPath, sizeof(szSysPath), "block/");
+ AssertRCReturn(rc, rc); /* this shall not fail */
+ if (RTDirExists(szSysPath))
+ {
+ rc = vmdkFindSysBlockDevPath(pImage, szSysPath, sizeof(szSysPath), StDrive.st_rdev, pszRawDrive);
+
+ /* Now, scan the directories under that again for a partition device
+ matching the hRawPart device's number: */
+ if (RT_SUCCESS(rc))
+ rc = vmdkFindSysBlockDevPath(pImage, szSysPath, sizeof(szSysPath), StPart.st_rdev, pPartDesc->pszRawDevice);
+
+ /* Having found the /sys/block/device/partition/ path, we can finally
+ read the partition attributes and compare with hVol. */
+ if (RT_SUCCESS(rc))
+ {
+ /* partition number: */
+ int64_t iLnxPartition = 0;
+ rc = RTLinuxSysFsReadIntFile(10, &iLnxPartition, "%s/partition", szSysPath);
+ if (RT_SUCCESS(rc) && iLnxPartition != idxPartition)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': Partition number %RI64, expected %RU32"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, iLnxPartition, idxPartition);
+ /* else: ignore failure? */
+
+ /* start offset: */
+ uint32_t const cbLnxSector = 512; /* It's hardcoded in the Linux kernel */
+ if (RT_SUCCESS(rc))
+ {
+ int64_t offLnxStart = -1;
+ rc = RTLinuxSysFsReadIntFile(10, &offLnxStart, "%s/start", szSysPath);
+ offLnxStart *= cbLnxSector;
+ if (RT_SUCCESS(rc) && offLnxStart != (int64_t)pPartDesc->offStartInVDisk)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': Start offset %RI64, expected %RU64"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, offLnxStart, pPartDesc->offStartInVDisk);
+ /* else: ignore failure? */
+ }
+
+ /* the size: */
+ if (RT_SUCCESS(rc))
+ {
+ int64_t cbLnxData = -1;
+ rc = RTLinuxSysFsReadIntFile(10, &cbLnxData, "%s/size", szSysPath);
+ cbLnxData *= cbLnxSector;
+ if (RT_SUCCESS(rc) && cbLnxData != (int64_t)pPartDesc->cbData)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': Size %RI64, expected %RU64"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, cbLnxData, pPartDesc->cbData);
+ /* else: ignore failure? */
+ }
+ }
+ }
+ /* else: We've got nothing to work on, so only do content comparison. */
+ }
+
+#elif defined(RT_OS_FREEBSD)
+ char szDriveDevName[256];
+ char* pszDevName = fdevname_r(RTFileToNative(hRawDrive), szDriveDevName, 256);
+ if (pszDevName == NULL)
+ rc = vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. '%s' is not a drive path"), pImage->pszFilename, pszRawDrive);
+ char szPartDevName[256];
+ if (RT_SUCCESS(rc))
+ {
+ pszDevName = fdevname_r(RTFileToNative(hRawPart), szPartDevName, 256);
+ if (pszDevName == NULL)
+ rc = vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. '%s' is not a partition path"), pImage->pszFilename, pPartDesc->pszRawDevice);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ gmesh geomMesh;
+ int err = geom_gettree(&geomMesh);
+ if (err == 0)
+ {
+ /* Find root class containg partitions info */
+ gclass* pPartClass;
+ LIST_FOREACH(pPartClass, &geomMesh.lg_class, lg_class)
+ {
+ if (RTStrCmp(pPartClass->lg_name, "PART") == 0)
+ break;
+ }
+ if (pPartClass == NULL || RTStrCmp(pPartClass->lg_name, "PART") != 0)
+ rc = vdIfError(pImage->pIfError, VERR_GENERAL_FAILURE, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. 'PART' class not found in the GEOM tree"), pImage->pszFilename);
+
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Find provider representing partition device */
+ uint64_t cbOffset;
+ uint64_t cbSize;
+ rc = vmdkFindPartitionParamsByName(pPartClass, szDriveDevName, szPartDevName, &cbOffset, &cbSize);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbOffset != pPartDesc->offStartInVDisk)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': Start offset %RU64, expected %RU64"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, cbOffset, pPartDesc->offStartInVDisk);
+ if (cbSize != pPartDesc->cbData)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': Size %RU64, expected %RU64"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, cbSize, pPartDesc->cbData);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Error getting geom provider for the partition '%s' of the drive '%s' in the GEOM tree: %Rrc"),
+ pImage->pszFilename, pPartDesc->pszRawDevice, pszRawDrive, rc);
+ }
+
+ geom_deletetree(&geomMesh);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(err), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. geom_gettree failed: %d"), pImage->pszFilename, err);
+ }
+
+#elif defined(RT_OS_SOLARIS)
+ RT_NOREF(hVol);
+
+ dk_cinfo dkiDriveInfo;
+ dk_cinfo dkiPartInfo;
+ if (ioctl(RTFileToNative(hRawDrive), DKIOCINFO, (caddr_t)&dkiDriveInfo) == -1)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. DKIOCINFO failed on '%s': %d"), pImage->pszFilename, pszRawDrive, errno);
+ else if (ioctl(RTFileToNative(hRawPart), DKIOCINFO, (caddr_t)&dkiPartInfo) == -1)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. DKIOCINFO failed on '%s': %d"), pImage->pszFilename, pszRawDrive, errno);
+ else if ( dkiDriveInfo.dki_ctype != dkiPartInfo.dki_ctype
+ || dkiDriveInfo.dki_cnum != dkiPartInfo.dki_cnum
+ || dkiDriveInfo.dki_addr != dkiPartInfo.dki_addr
+ || dkiDriveInfo.dki_unit != dkiPartInfo.dki_unit
+ || dkiDriveInfo.dki_slave != dkiPartInfo.dki_slave)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s' (%#x != %#x || %#x != %#x || %#x != %#x || %#x != %#x || %#x != %#x)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive,
+ dkiDriveInfo.dki_ctype, dkiPartInfo.dki_ctype, dkiDriveInfo.dki_cnum, dkiPartInfo.dki_cnum,
+ dkiDriveInfo.dki_addr, dkiPartInfo.dki_addr, dkiDriveInfo.dki_unit, dkiPartInfo.dki_unit,
+ dkiDriveInfo.dki_slave, dkiPartInfo.dki_slave);
+ else
+ {
+ uint64_t cbOffset = 0;
+ uint64_t cbSize = 0;
+ dk_gpt *pEfi = NULL;
+ int idxEfiPart = efi_alloc_and_read(RTFileToNative(hRawPart), &pEfi);
+ if (idxEfiPart >= 0)
+ {
+ if ((uint32_t)dkiPartInfo.dki_partition + 1 == idxPartition)
+ {
+ cbOffset = pEfi->efi_parts[idxEfiPart].p_start * pEfi->efi_lbasize;
+ cbSize = pEfi->efi_parts[idxEfiPart].p_size * pEfi->efi_lbasize;
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u number ('%s') verification failed on '%s' (%#x != %#x)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive,
+ idxPartition, (uint32_t)dkiPartInfo.dki_partition + 1);
+ efi_free(pEfi);
+ }
+ else
+ {
+ /*
+ * Manual says the efi_alloc_and_read returns VT_EINVAL if no EFI partition table found.
+ * Actually, the function returns any error, e.g. VT_ERROR. Thus, we are not sure, is it
+ * real error or just no EFI table found. Therefore, let's try to obtain partition info
+ * using another way. If there is an error, it returns errno which will be handled below.
+ */
+
+ uint32_t numPartition = (uint32_t)dkiPartInfo.dki_partition;
+ if (numPartition > NDKMAP)
+ numPartition -= NDKMAP;
+ if (numPartition != idxPartition)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u number ('%s') verification failed on '%s' (%#x != %#x)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive,
+ idxPartition, numPartition);
+ else
+ {
+ dk_minfo_ext mediaInfo;
+ if (ioctl(RTFileToNative(hRawPart), DKIOCGMEDIAINFOEXT, (caddr_t)&mediaInfo) == -1)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u number ('%s') verification failed on '%s'. Can not obtain partition info: %d"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, errno);
+ else
+ {
+ extpart_info extPartInfo;
+ if (ioctl(RTFileToNative(hRawPart), DKIOCEXTPARTINFO, (caddr_t)&extPartInfo) != -1)
+ {
+ cbOffset = (uint64_t)extPartInfo.p_start * mediaInfo.dki_lbsize;
+ cbSize = (uint64_t)extPartInfo.p_length * mediaInfo.dki_lbsize;
+ }
+ else
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u number ('%s') verification failed on '%s'. Can not obtain partition info: %d"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, errno);
+ }
+ }
+ }
+ if (RT_SUCCESS(rc) && cbOffset != pPartDesc->offStartInVDisk)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': Start offset %RI64, expected %RU64"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, cbOffset, pPartDesc->offStartInVDisk);
+
+ if (RT_SUCCESS(rc) && cbSize != pPartDesc->cbData)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': Size %RI64, expected %RU64"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, cbSize, pPartDesc->cbData);
+ }
+
+#elif defined(RT_OS_DARWIN)
+ /* Stat the drive get its device number. */
+ struct stat StDrive;
+ if (fstat((int)RTFileToNative(hRawDrive), &StDrive) != 0)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. fstat failed on '%s' (errno=%d)"), pImage->pszFilename, pszRawDrive, errno);
+ else
+ {
+ if (ioctl(RTFileToNative(hRawPart), DKIOCLOCKPHYSICALEXTENTS, NULL) == -1)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u number ('%s') verification failed on '%s': Unable to lock the partition (errno=%d)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, errno);
+ else
+ {
+ uint32_t cbBlockSize = 0;
+ uint64_t cbOffset = 0;
+ uint64_t cbSize = 0;
+ if (ioctl(RTFileToNative(hRawPart), DKIOCGETBLOCKSIZE, (caddr_t)&cbBlockSize) == -1)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u number ('%s') verification failed on '%s': Unable to obtain the sector size of the partition (errno=%d)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, errno);
+ else if (ioctl(RTFileToNative(hRawPart), DKIOCGETBASE, (caddr_t)&cbOffset) == -1)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u number ('%s') verification failed on '%s': Unable to obtain the start offset of the partition (errno=%d)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, errno);
+ else if (ioctl(RTFileToNative(hRawPart), DKIOCGETBLOCKCOUNT, (caddr_t)&cbSize) == -1)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u number ('%s') verification failed on '%s': Unable to obtain the size of the partition (errno=%d)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, errno);
+ else
+ {
+ cbSize *= (uint64_t)cbBlockSize;
+ dk_physical_extent_t dkPartExtent = {0};
+ dkPartExtent.offset = 0;
+ dkPartExtent.length = cbSize;
+ if (ioctl(RTFileToNative(hRawPart), DKIOCGETPHYSICALEXTENT, (caddr_t)&dkPartExtent) == -1)
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u number ('%s') verification failed on '%s': Unable to obtain partition info (errno=%d)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, errno);
+ else
+ {
+ if (dkPartExtent.dev != StDrive.st_rdev)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': Drive does not contain the partition"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive);
+ else if (cbOffset != pPartDesc->offStartInVDisk)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': Start offset %RU64, expected %RU64"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, cbOffset, pPartDesc->offStartInVDisk);
+ else if (cbSize != pPartDesc->cbData)
+ rc = vdIfError(pImage->pIfError, VERR_MISMATCH, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s': Size %RU64, expected %RU64"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, cbSize, pPartDesc->cbData);
+ }
+ }
+
+ if (ioctl(RTFileToNative(hRawPart), DKIOCUNLOCKPHYSICALEXTENTS, NULL) == -1)
+ {
+ int rc2 = vdIfError(pImage->pIfError, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u number ('%s') verification failed on '%s': Unable to unlock the partition (errno=%d)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, errno);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ }
+
+#else
+ RT_NOREF(hVol); /* PORTME */
+ rc = VERR_NOT_SUPPORTED;
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Compare the first 32 sectors of the partition.
+ *
+ * This might not be conclusive, but for partitions formatted with the more
+ * common file systems it should be as they have a superblock copy at or near
+ * the start of the partition (fat, fat32, ntfs, and ext4 does at least).
+ */
+ size_t const cbToCompare = (size_t)RT_MIN(pPartDesc->cbData / cbSector, 32) * cbSector;
+ uint8_t *pbSector1 = (uint8_t *)RTMemTmpAlloc(cbToCompare * 2);
+ if (pbSector1 != NULL)
+ {
+ uint8_t *pbSector2 = pbSector1 + cbToCompare;
+
+ /* Do the comparing, we repeat if it fails and the data might be volatile. */
+ uint64_t uPrevCrc1 = 0;
+ uint64_t uPrevCrc2 = 0;
+ uint32_t cStable = 0;
+ for (unsigned iTry = 0; iTry < 256; iTry++)
+ {
+ rc = RTFileReadAt(hRawDrive, pPartDesc->offStartInVDisk, pbSector1, cbToCompare, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileReadAt(hRawPart, pPartDesc->offStartInDevice, pbSector2, cbToCompare, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(pbSector1, pbSector2, cbToCompare) != 0)
+ {
+ rc = VERR_MISMATCH;
+
+ /* Do data stability checks before repeating: */
+ uint64_t const uCrc1 = RTCrc64(pbSector1, cbToCompare);
+ uint64_t const uCrc2 = RTCrc64(pbSector2, cbToCompare);
+ if ( uPrevCrc1 != uCrc1
+ || uPrevCrc2 != uCrc2)
+ cStable = 0;
+ else if (++cStable > 4)
+ break;
+ uPrevCrc1 = uCrc1;
+ uPrevCrc2 = uCrc2;
+ continue;
+ }
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Error reading %zu bytes from '%s' at offset %RU64 (%Rrc)"),
+ pImage->pszFilename, cbToCompare, pPartDesc->pszRawDevice, pPartDesc->offStartInDevice, rc);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Error reading %zu bytes from '%s' at offset %RU64 (%Rrc)"),
+ pImage->pszFilename, cbToCompare, pszRawDrive, pPartDesc->offStartInVDisk, rc);
+ break;
+ }
+ if (rc == VERR_MISMATCH)
+ {
+ /* Find the first mismatching bytes: */
+ size_t offMissmatch = 0;
+ while (offMissmatch < cbToCompare && pbSector1[offMissmatch] == pbSector2[offMissmatch])
+ offMissmatch++;
+ int cbSample = (int)RT_MIN(cbToCompare - offMissmatch, 16);
+
+ if (cStable > 0)
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition #%u path ('%s') verification failed on '%s' (cStable=%d @%#zx: %.*Rhxs vs %.*Rhxs)"),
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive, cStable,
+ offMissmatch, cbSample, &pbSector1[offMissmatch], cbSample, &pbSector2[offMissmatch]);
+ else
+ {
+ LogRel(("VMDK: Image path: '%s'. Partition #%u path ('%s') verification undecided on '%s' because of unstable data! (@%#zx: %.*Rhxs vs %.*Rhxs)\n",
+ pImage->pszFilename, idxPartition, pPartDesc->pszRawDevice, pszRawDrive,
+ offMissmatch, cbSample, &pbSector1[offMissmatch], cbSample, &pbSector2[offMissmatch]));
+ rc = -rc;
+ }
+ }
+
+ RTMemTmpFree(pbSector1);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_TMP_MEMORY, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to allocate %zu bytes for a temporary read buffer\n"),
+ pImage->pszFilename, cbToCompare * 2);
+ }
+ RTFileClose(hRawPart);
+ return rc;
+}
+
+#ifdef RT_OS_WINDOWS
+/**
+ * Construct the device name for the given partition number.
+ */
+static int vmdkRawDescWinMakePartitionName(PVMDKIMAGE pImage, const char *pszRawDrive, RTFILE hRawDrive, uint32_t idxPartition,
+ char **ppszRawPartition)
+{
+ int rc = VINF_SUCCESS;
+ DWORD cbReturned = 0;
+ STORAGE_DEVICE_NUMBER DevNum;
+ RT_ZERO(DevNum);
+ if (DeviceIoControl((HANDLE)RTFileToNative(hRawDrive), IOCTL_STORAGE_GET_DEVICE_NUMBER,
+ NULL /*pvInBuffer*/, 0 /*cbInBuffer*/, &DevNum, sizeof(DevNum), &cbReturned, NULL /*pOverlapped*/))
+ RTStrAPrintf(ppszRawPartition, "\\\\.\\Harddisk%uPartition%u", DevNum.DeviceNumber, idxPartition);
+ else
+ rc = vdIfError(pImage->pIfError, RTErrConvertFromWin32(GetLastError()), RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. IOCTL_STORAGE_GET_DEVICE_NUMBER failed on '%s': %u"),
+ pImage->pszFilename, pszRawDrive, GetLastError());
+ return rc;
+}
+#endif /* RT_OS_WINDOWS */
+
+/**
+ * Worker for vmdkMakeRawDescriptor that adds partition descriptors when the
+ * 'Partitions' configuration value is present.
+ *
+ * @returns VBox status code, error message has been set on failure.
+ *
+ * @note Caller is assumed to clean up @a pRawDesc and release
+ * @a *phVolToRelease.
+ * @internal
+ */
+static int vmdkRawDescDoPartitions(PVMDKIMAGE pImage, RTDVM hVolMgr, PVDISKRAW pRawDesc,
+ RTFILE hRawDrive, const char *pszRawDrive, uint32_t cbSector,
+ uint32_t fPartitions, uint32_t fPartitionsReadOnly, bool fRelative,
+ PRTDVMVOLUME phVolToRelease)
+{
+ *phVolToRelease = NIL_RTDVMVOLUME;
+
+ /* Check sanity/understanding. */
+ Assert(fPartitions);
+ Assert((fPartitions & fPartitionsReadOnly) == fPartitionsReadOnly); /* RO should be a sub-set */
+
+ /*
+ * Allocate on descriptor for each volume up front.
+ */
+ uint32_t const cVolumes = RTDvmMapGetValidVolumes(hVolMgr);
+
+ PVDISKRAWPARTDESC paPartDescs = NULL;
+ int rc = vmdkRawDescAppendPartDesc(pImage, pRawDesc, cVolumes, &paPartDescs);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Enumerate the partitions (volumes) on the disk and create descriptors for each of them.
+ */
+ uint32_t fPartitionsLeft = fPartitions;
+ RTDVMVOLUME hVol = NIL_RTDVMVOLUME; /* the current volume, needed for getting the next. */
+ for (uint32_t i = 0; i < cVolumes; i++)
+ {
+ /*
+ * Get the next/first volume and release the current.
+ */
+ RTDVMVOLUME hVolNext = NIL_RTDVMVOLUME;
+ if (i == 0)
+ rc = RTDvmMapQueryFirstVolume(hVolMgr, &hVolNext);
+ else
+ rc = RTDvmMapQueryNextVolume(hVolMgr, hVol, &hVolNext);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Volume enumeration failed at volume #%u on '%s' (%Rrc)"),
+ pImage->pszFilename, i, pszRawDrive, rc);
+ uint32_t cRefs = RTDvmVolumeRelease(hVol);
+ Assert(cRefs != UINT32_MAX); RT_NOREF(cRefs);
+ *phVolToRelease = hVol = hVolNext;
+
+ /*
+ * Depending on the fPartitions selector and associated read-only mask,
+ * the guest either gets read-write or read-only access (bits set)
+ * or no access (selector bit clear, access directed to the VMDK).
+ */
+ paPartDescs[i].cbData = RTDvmVolumeGetSize(hVol);
+
+ uint64_t offVolumeEndIgnored = 0;
+ rc = RTDvmVolumeQueryRange(hVol, &paPartDescs[i].offStartInVDisk, &offVolumeEndIgnored);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to get location of volume #%u on '%s' (%Rrc)"),
+ pImage->pszFilename, i, pszRawDrive, rc);
+ Assert(paPartDescs[i].cbData == offVolumeEndIgnored + 1 - paPartDescs[i].offStartInVDisk);
+
+ /* Note! The index must match IHostDrivePartition::number. */
+ uint32_t idxPartition = RTDvmVolumeGetIndex(hVol, RTDVMVOLIDX_HOST);
+ if ( idxPartition < 32
+ && (fPartitions & RT_BIT_32(idxPartition)))
+ {
+ fPartitionsLeft &= ~RT_BIT_32(idxPartition);
+ if (fPartitionsReadOnly & RT_BIT_32(idxPartition))
+ paPartDescs[i].uFlags |= VDISKRAW_READONLY;
+
+ if (!fRelative)
+ {
+ /*
+ * Accessing the drive thru the main device node (pRawDesc->pszRawDisk).
+ */
+ paPartDescs[i].offStartInDevice = paPartDescs[i].offStartInVDisk;
+ paPartDescs[i].pszRawDevice = RTStrDup(pszRawDrive);
+ AssertPtrReturn(paPartDescs[i].pszRawDevice, VERR_NO_STR_MEMORY);
+ }
+ else
+ {
+ /*
+ * Relative means access the partition data via the device node for that
+ * partition, allowing the sysadmin/OS to allow a user access to individual
+ * partitions without necessarily being able to compromise the host OS.
+ * Obviously, the creation of the VMDK requires read access to the main
+ * device node for the drive, but that's a one-time thing and can be done
+ * by the sysadmin. Here data starts at offset zero in the device node.
+ */
+ paPartDescs[i].offStartInDevice = 0;
+
+#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
+ /* /dev/rdisk1 -> /dev/rdisk1s2 (s=slice) */
+ RTStrAPrintf(&paPartDescs[i].pszRawDevice, "%ss%u", pszRawDrive, idxPartition);
+#elif defined(RT_OS_LINUX)
+ /* Two naming schemes here: /dev/nvme0n1 -> /dev/nvme0n1p1; /dev/sda -> /dev/sda1 */
+ RTStrAPrintf(&paPartDescs[i].pszRawDevice,
+ RT_C_IS_DIGIT(pszRawDrive[strlen(pszRawDrive) - 1]) ? "%sp%u" : "%s%u", pszRawDrive, idxPartition);
+#elif defined(RT_OS_WINDOWS)
+ rc = vmdkRawDescWinMakePartitionName(pImage, pszRawDrive, hRawDrive, idxPartition, &paPartDescs[i].pszRawDevice);
+ AssertRCReturn(rc, rc);
+#elif defined(RT_OS_SOLARIS)
+ if (pRawDesc->enmPartitioningType == VDISKPARTTYPE_MBR)
+ {
+ /*
+ * MBR partitions have device nodes in form /dev/(r)dsk/cXtYdZpK
+ * where X is the controller,
+ * Y is target (SCSI device number),
+ * Z is disk number,
+ * K is partition number,
+ * where p0 is the whole disk
+ * p1-pN are the partitions of the disk
+ */
+ const char *pszRawDrivePath = pszRawDrive;
+ char szDrivePath[RTPATH_MAX];
+ size_t cbRawDrive = strlen(pszRawDrive);
+ if ( cbRawDrive > 1 && strcmp(&pszRawDrive[cbRawDrive - 2], "p0") == 0)
+ {
+ memcpy(szDrivePath, pszRawDrive, cbRawDrive - 2);
+ szDrivePath[cbRawDrive - 2] = '\0';
+ pszRawDrivePath = szDrivePath;
+ }
+ RTStrAPrintf(&paPartDescs[i].pszRawDevice, "%sp%u", pszRawDrivePath, idxPartition);
+ }
+ else /* GPT */
+ {
+ /*
+ * GPT partitions have device nodes in form /dev/(r)dsk/cXtYdZsK
+ * where X is the controller,
+ * Y is target (SCSI device number),
+ * Z is disk number,
+ * K is partition number, zero based. Can be only from 0 to 6.
+ * Thus, only partitions numbered 0 through 6 have device nodes.
+ */
+ if (idxPartition > 7)
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. the partition #%u on '%s' has no device node and can not be specified with 'Relative' property"),
+ pImage->pszFilename, idxPartition, pszRawDrive);
+ RTStrAPrintf(&paPartDescs[i].pszRawDevice, "%ss%u", pszRawDrive, idxPartition - 1);
+ }
+#else
+ AssertFailedReturn(VERR_INTERNAL_ERROR_4); /* The option parsing code should have prevented this - PORTME */
+#endif
+ AssertPtrReturn(paPartDescs[i].pszRawDevice, VERR_NO_STR_MEMORY);
+
+ rc = vmdkRawDescVerifyPartitionPath(pImage, &paPartDescs[i], idxPartition, pszRawDrive, hRawDrive, cbSector, hVol);
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else
+ {
+ /* Not accessible to the guest. */
+ paPartDescs[i].offStartInDevice = 0;
+ paPartDescs[i].pszRawDevice = NULL;
+ }
+ } /* for each volume */
+
+ RTDvmVolumeRelease(hVol);
+ *phVolToRelease = NIL_RTDVMVOLUME;
+
+ /*
+ * Check that we found all the partitions the user selected.
+ */
+ if (fPartitionsLeft)
+ {
+ char szLeft[3 * sizeof(fPartitions) * 8];
+ size_t cchLeft = 0;
+ for (unsigned i = 0; i < sizeof(fPartitions) * 8; i++)
+ if (fPartitionsLeft & RT_BIT_32(i))
+ cchLeft += RTStrPrintf(&szLeft[cchLeft], sizeof(szLeft) - cchLeft, cchLeft ? "%u" : ",%u", i);
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Not all the specified partitions for drive '%s' was found: %s"),
+ pImage->pszFilename, pszRawDrive, szLeft);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Worker for vmdkMakeRawDescriptor that adds partition descriptors with copies
+ * of the partition tables and associated padding areas when the 'Partitions'
+ * configuration value is present.
+ *
+ * The guest is not allowed access to the partition tables, however it needs
+ * them to be able to access the drive. So, create descriptors for each of the
+ * tables and attach the current disk content. vmdkCreateRawImage() will later
+ * write the content to the VMDK. Any changes the guest later makes to the
+ * partition tables will then go to the VMDK copy, rather than the host drive.
+ *
+ * @returns VBox status code, error message has been set on failure.
+ *
+ * @note Caller is assumed to clean up @a pRawDesc
+ * @internal
+ */
+static int vmdkRawDescDoCopyPartitionTables(PVMDKIMAGE pImage, RTDVM hVolMgr, PVDISKRAW pRawDesc,
+ const char *pszRawDrive, RTFILE hRawDrive, void *pvBootSector, size_t cbBootSector)
+{
+ /*
+ * Query the locations.
+ */
+ /* Determin how many locations there are: */
+ size_t cLocations = 0;
+ int rc = RTDvmMapQueryTableLocations(hVolMgr, RTDVMMAPQTABLOC_F_INCLUDE_LEGACY, NULL, 0, &cLocations);
+ if (rc != VERR_BUFFER_OVERFLOW)
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. RTDvmMapQueryTableLocations failed on '%s' (%Rrc)"),
+ pImage->pszFilename, pszRawDrive, rc);
+ AssertReturn(cLocations > 0 && cLocations < _16M, VERR_INTERNAL_ERROR_5);
+
+ /* We can allocate the partition descriptors here to save an intentation level. */
+ PVDISKRAWPARTDESC paPartDescs = NULL;
+ rc = vmdkRawDescAppendPartDesc(pImage, pRawDesc, (uint32_t)cLocations, &paPartDescs);
+ AssertRCReturn(rc, rc);
+
+ /* Allocate the result table and repeat the location table query: */
+ PRTDVMTABLELOCATION paLocations = (PRTDVMTABLELOCATION)RTMemAllocZ(sizeof(paLocations[0]) * cLocations);
+ if (!paLocations)
+ return vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS, N_("VMDK: Image path: '%s'. Failed to allocate %zu bytes"),
+ pImage->pszFilename, sizeof(paLocations[0]) * cLocations);
+ rc = RTDvmMapQueryTableLocations(hVolMgr, RTDVMMAPQTABLOC_F_INCLUDE_LEGACY, paLocations, cLocations, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Translate them into descriptors.
+ *
+ * We restrict the amount of partition alignment padding to 4MiB as more
+ * will just be a waste of space. The use case for including the padding
+ * are older boot loaders and boot manager (including one by a team member)
+ * that put data and code in the 62 sectors between the MBR and the first
+ * partition (total of 63). Later CHS was abandond and partition started
+ * being aligned on power of two sector boundraries (typically 64KiB or
+ * 1MiB depending on the media size).
+ */
+ for (size_t i = 0; i < cLocations && RT_SUCCESS(rc); i++)
+ {
+ Assert(paLocations[i].cb > 0);
+ if (paLocations[i].cb <= _64M)
+ {
+ /* Create the partition descriptor entry: */
+ //paPartDescs[i].pszRawDevice = NULL;
+ //paPartDescs[i].offStartInDevice = 0;
+ //paPartDescs[i].uFlags = 0;
+ paPartDescs[i].offStartInVDisk = paLocations[i].off;
+ paPartDescs[i].cbData = paLocations[i].cb;
+ if (paPartDescs[i].cbData < _4M)
+ paPartDescs[i].cbData = RT_MIN(paPartDescs[i].cbData + paLocations[i].cbPadding, _4M);
+ paPartDescs[i].pvPartitionData = RTMemAllocZ((size_t)paPartDescs[i].cbData);
+ if (paPartDescs[i].pvPartitionData)
+ {
+ /* Read the content from the drive: */
+ rc = RTFileReadAt(hRawDrive, paPartDescs[i].offStartInVDisk, paPartDescs[i].pvPartitionData,
+ (size_t)paPartDescs[i].cbData, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Do we have custom boot sector code? */
+ if (pvBootSector && cbBootSector && paPartDescs[i].offStartInVDisk == 0)
+ {
+ /* Note! Old code used to quietly drop the bootsector if it was considered too big.
+ Instead we fail as we weren't able to do what the user requested us to do.
+ Better if the user knows than starts questioning why the guest isn't
+ booting as expected. */
+ if (cbBootSector <= paPartDescs[i].cbData)
+ memcpy(paPartDescs[i].pvPartitionData, pvBootSector, cbBootSector);
+ else
+ rc = vdIfError(pImage->pIfError, VERR_TOO_MUCH_DATA, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. The custom boot sector is too big: %zu bytes, %RU64 bytes available"),
+ pImage->pszFilename, cbBootSector, paPartDescs[i].cbData);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to read partition at off %RU64 length %zu from '%s' (%Rrc)"),
+ pImage->pszFilename, paPartDescs[i].offStartInVDisk,
+ (size_t)paPartDescs[i].cbData, pszRawDrive, rc);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to allocate %zu bytes for copying the partition table at off %RU64"),
+ pImage->pszFilename, (size_t)paPartDescs[i].cbData, paPartDescs[i].offStartInVDisk);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_TOO_MUCH_DATA, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Partition table #%u at offset %RU64 in '%s' is to big: %RU64 bytes"),
+ pImage->pszFilename, i, paLocations[i].off, pszRawDrive, paLocations[i].cb);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. RTDvmMapQueryTableLocations failed on '%s' (%Rrc)"),
+ pImage->pszFilename, pszRawDrive, rc);
+ RTMemFree(paLocations);
+ return rc;
+}
+
+/**
+ * Opens the volume manager for the raw drive when in selected-partition mode.
+ *
+ * @param pImage The VMDK image (for errors).
+ * @param hRawDrive The raw drive handle.
+ * @param pszRawDrive The raw drive device path (for errors).
+ * @param cbSector The sector size.
+ * @param phVolMgr Where to return the handle to the volume manager on
+ * success.
+ * @returns VBox status code, errors have been reported.
+ * @internal
+ */
+static int vmdkRawDescOpenVolMgr(PVMDKIMAGE pImage, RTFILE hRawDrive, const char *pszRawDrive, uint32_t cbSector, PRTDVM phVolMgr)
+{
+ *phVolMgr = NIL_RTDVM;
+
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ int rc = RTVfsFileFromRTFile(hRawDrive, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, true /*fLeaveOpen*/, &hVfsFile);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. RTVfsFileFromRTFile failed for '%s' handle (%Rrc)"),
+ pImage->pszFilename, pszRawDrive, rc);
+
+ RTDVM hVolMgr = NIL_RTDVM;
+ rc = RTDvmCreate(&hVolMgr, hVfsFile, cbSector, 0 /*fFlags*/);
+
+ RTVfsFileRelease(hVfsFile);
+
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to create volume manager instance for '%s' (%Rrc)"),
+ pImage->pszFilename, pszRawDrive, rc);
+
+ rc = RTDvmMapOpen(hVolMgr);
+ if (RT_SUCCESS(rc))
+ {
+ *phVolMgr = hVolMgr;
+ return VINF_SUCCESS;
+ }
+ RTDvmRelease(hVolMgr);
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: Image path: '%s'. RTDvmMapOpen failed for '%s' (%Rrc)"),
+ pImage->pszFilename, pszRawDrive, rc);
+}
+
+/**
+ * Opens the raw drive device and get the sizes for it.
+ *
+ * @param pImage The image (for error reporting).
+ * @param pszRawDrive The device/whatever to open.
+ * @param phRawDrive Where to return the file handle.
+ * @param pcbRawDrive Where to return the size.
+ * @param pcbSector Where to return the sector size.
+ * @returns IPRT status code, errors have been reported.
+ * @internal
+ */
+static int vmkdRawDescOpenDevice(PVMDKIMAGE pImage, const char *pszRawDrive,
+ PRTFILE phRawDrive, uint64_t *pcbRawDrive, uint32_t *pcbSector)
+{
+ /*
+ * Open the device for the raw drive.
+ */
+ RTFILE hRawDrive = NIL_RTFILE;
+ int rc = RTFileOpen(&hRawDrive, pszRawDrive, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to open the raw drive '%s' for reading (%Rrc)"),
+ pImage->pszFilename, pszRawDrive, rc);
+
+ /*
+ * Get the sector size.
+ */
+ uint32_t cbSector = 0;
+ rc = RTFileQuerySectorSize(hRawDrive, &cbSector);
+ if (RT_SUCCESS(rc))
+ {
+ /* sanity checks */
+ if ( cbSector >= 512
+ && cbSector <= _64K
+ && RT_IS_POWER_OF_TWO(cbSector))
+ {
+ /*
+ * Get the size.
+ */
+ uint64_t cbRawDrive = 0;
+ rc = RTFileQuerySize(hRawDrive, &cbRawDrive);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check whether cbSize is actually sensible. */
+ if (cbRawDrive > cbSector && (cbRawDrive % cbSector) == 0)
+ {
+ *phRawDrive = hRawDrive;
+ *pcbRawDrive = cbRawDrive;
+ *pcbSector = cbSector;
+ return VINF_SUCCESS;
+ }
+ rc = vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Got a bogus size for the raw drive '%s': %RU64 (sector size %u)"),
+ pImage->pszFilename, pszRawDrive, cbRawDrive, cbSector);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to query size of the drive '%s' (%Rrc)"),
+ pImage->pszFilename, pszRawDrive, rc);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_OUT_OF_RANGE, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Unsupported sector size for '%s': %u (%#x)"),
+ pImage->pszFilename, pszRawDrive, cbSector, cbSector);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to get the sector size for '%s' (%Rrc)"),
+ pImage->pszFilename, pszRawDrive, rc);
+ RTFileClose(hRawDrive);
+ return rc;
+}
+
+/**
+ * Reads the raw disk configuration, leaving initalization and cleanup to the
+ * caller (regardless of return status).
+ *
+ * @returns VBox status code, errors properly reported.
+ * @internal
+ */
+static int vmdkRawDescParseConfig(PVMDKIMAGE pImage, char **ppszRawDrive,
+ uint32_t *pfPartitions, uint32_t *pfPartitionsReadOnly,
+ void **ppvBootSector, size_t *pcbBootSector, bool *pfRelative,
+ char **ppszFreeMe)
+{
+ PVDINTERFACECONFIG pImgCfg = VDIfConfigGet(pImage->pVDIfsImage);
+ if (!pImgCfg)
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Getting config interface failed"), pImage->pszFilename);
+
+ /*
+ * RawDrive = path
+ */
+ int rc = VDCFGQueryStringAlloc(pImgCfg, "RawDrive", ppszRawDrive);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Getting 'RawDrive' configuration failed (%Rrc)"), pImage->pszFilename, rc);
+ AssertPtrReturn(*ppszRawDrive, VERR_INTERNAL_ERROR_3);
+
+ /*
+ * Partitions=n[r][,...]
+ */
+ uint32_t const cMaxPartitionBits = sizeof(*pfPartitions) * 8 /* ASSUMES 8 bits per char */;
+ *pfPartitions = *pfPartitionsReadOnly = 0;
+
+ rc = VDCFGQueryStringAlloc(pImgCfg, "Partitions", ppszFreeMe);
+ if (RT_SUCCESS(rc))
+ {
+ char *psz = *ppszFreeMe;
+ while (*psz != '\0')
+ {
+ char *pszNext;
+ uint32_t u32;
+ rc = RTStrToUInt32Ex(psz, &pszNext, 0, &u32);
+ if (rc == VWRN_NUMBER_TOO_BIG || rc == VWRN_NEGATIVE_UNSIGNED)
+ rc = -rc;
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Parsing 'Partitions' config value failed. Incorrect value (%Rrc): %s"),
+ pImage->pszFilename, rc, psz);
+ if (u32 >= cMaxPartitionBits)
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. 'Partitions' config sub-value out of range: %RU32, max %RU32"),
+ pImage->pszFilename, u32, cMaxPartitionBits);
+ *pfPartitions |= RT_BIT_32(u32);
+ psz = pszNext;
+ if (*psz == 'r')
+ {
+ *pfPartitionsReadOnly |= RT_BIT_32(u32);
+ psz++;
+ }
+ if (*psz == ',')
+ psz++;
+ else if (*psz != '\0')
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Malformed 'Partitions' config value, expected separator: %s"),
+ pImage->pszFilename, psz);
+ }
+
+ RTStrFree(*ppszFreeMe);
+ *ppszFreeMe = NULL;
+ }
+ else if (rc != VERR_CFGM_VALUE_NOT_FOUND)
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Getting 'Partitions' configuration failed (%Rrc)"), pImage->pszFilename, rc);
+
+ /*
+ * BootSector=base64
+ */
+ rc = VDCFGQueryStringAlloc(pImgCfg, "BootSector", ppszFreeMe);
+ if (RT_SUCCESS(rc))
+ {
+ ssize_t cbBootSector = RTBase64DecodedSize(*ppszFreeMe, NULL);
+ if (cbBootSector < 0)
+ return vdIfError(pImage->pIfError, VERR_INVALID_BASE64_ENCODING, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. BASE64 decoding failed on the custom bootsector for '%s'"),
+ pImage->pszFilename, *ppszRawDrive);
+ if (cbBootSector == 0)
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Custom bootsector for '%s' is zero bytes big"),
+ pImage->pszFilename, *ppszRawDrive);
+ if (cbBootSector > _4M) /* this is just a preliminary max */
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Custom bootsector for '%s' is way too big: %zu bytes, max 4MB"),
+ pImage->pszFilename, *ppszRawDrive, cbBootSector);
+
+ /* Refuse the boot sector if whole-drive. This used to be done quietly,
+ however, bird disagrees and thinks the user should be told that what
+ he/she/it tries to do isn't possible. There should be less head
+ scratching this way when the guest doesn't do the expected thing. */
+ if (!*pfPartitions)
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Custom bootsector for '%s' is not supported for whole-drive configurations, only when selecting partitions"),
+ pImage->pszFilename, *ppszRawDrive);
+
+ *pcbBootSector = (size_t)cbBootSector;
+ *ppvBootSector = RTMemAlloc((size_t)cbBootSector);
+ if (!*ppvBootSector)
+ return vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to allocate %zd bytes for the custom bootsector for '%s'"),
+ pImage->pszFilename, cbBootSector, *ppszRawDrive);
+
+ rc = RTBase64Decode(*ppszFreeMe, *ppvBootSector, cbBootSector, NULL /*pcbActual*/, NULL /*ppszEnd*/);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Base64 decoding of the custom boot sector for '%s' failed (%Rrc)"),
+ pImage->pszFilename, *ppszRawDrive, rc);
+
+ RTStrFree(*ppszFreeMe);
+ *ppszFreeMe = NULL;
+ }
+ else if (rc != VERR_CFGM_VALUE_NOT_FOUND)
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Getting 'BootSector' configuration failed (%Rrc)"), pImage->pszFilename, rc);
+
+ /*
+ * Relative=0/1
+ */
+ *pfRelative = false;
+ rc = VDCFGQueryBool(pImgCfg, "Relative", pfRelative);
+ if (RT_SUCCESS(rc))
+ {
+ if (!*pfPartitions && *pfRelative != false)
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. The 'Relative' option is not supported for whole-drive configurations, only when selecting partitions"),
+ pImage->pszFilename);
+#if !defined(RT_OS_DARWIN) && !defined(RT_OS_LINUX) && !defined(RT_OS_FREEBSD) && !defined(RT_OS_WINDOWS) && !defined(RT_OS_SOLARIS) /* PORTME */
+ if (*pfRelative == true)
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. The 'Relative' option is not supported on this host OS"),
+ pImage->pszFilename);
+#endif
+ }
+ else if (rc != VERR_CFGM_VALUE_NOT_FOUND)
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Getting 'Relative' configuration failed (%Rrc)"), pImage->pszFilename, rc);
+ else
+#ifdef RT_OS_DARWIN /* different default on macOS, see ticketref:1461 (comment 20). */
+ *pfRelative = true;
+#else
+ *pfRelative = false;
+#endif
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Creates a raw drive (nee disk) descriptor.
+ *
+ * This was originally done in VBoxInternalManage.cpp, but was copied (not move)
+ * here much later. That's one of the reasons why we produce a descriptor just
+ * like it does, rather than mixing directly into the vmdkCreateRawImage code.
+ *
+ * @returns VBox status code.
+ * @param pImage The image.
+ * @param ppRaw Where to return the raw drive descriptor. Caller must
+ * free it using vmdkRawDescFree regardless of the status
+ * code.
+ * @internal
+ */
+static int vmdkMakeRawDescriptor(PVMDKIMAGE pImage, PVDISKRAW *ppRaw)
+{
+ /* Make sure it's NULL. */
+ *ppRaw = NULL;
+
+ /*
+ * Read the configuration.
+ */
+ char *pszRawDrive = NULL;
+ uint32_t fPartitions = 0; /* zero if whole-drive */
+ uint32_t fPartitionsReadOnly = 0; /* (subset of fPartitions) */
+ void *pvBootSector = NULL;
+ size_t cbBootSector = 0;
+ bool fRelative = false;
+ char *pszFreeMe = NULL; /* lazy bird cleanup. */
+ int rc = vmdkRawDescParseConfig(pImage, &pszRawDrive, &fPartitions, &fPartitionsReadOnly,
+ &pvBootSector, &cbBootSector, &fRelative, &pszFreeMe);
+ RTStrFree(pszFreeMe);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Open the device, getting the sector size and drive size.
+ */
+ uint64_t cbSize = 0;
+ uint32_t cbSector = 0;
+ RTFILE hRawDrive = NIL_RTFILE;
+ rc = vmkdRawDescOpenDevice(pImage, pszRawDrive, &hRawDrive, &cbSize, &cbSector);
+ if (RT_SUCCESS(rc))
+ {
+ pImage->cbSize = cbSize;
+ /*
+ * Create the raw-drive descriptor
+ */
+ PVDISKRAW pRawDesc = (PVDISKRAW)RTMemAllocZ(sizeof(*pRawDesc));
+ if (pRawDesc)
+ {
+ pRawDesc->szSignature[0] = 'R';
+ pRawDesc->szSignature[1] = 'A';
+ pRawDesc->szSignature[2] = 'W';
+ //pRawDesc->szSignature[3] = '\0';
+ if (!fPartitions)
+ {
+ /*
+ * It's simple for when doing the whole drive.
+ */
+ pRawDesc->uFlags = VDISKRAW_DISK;
+ rc = RTStrDupEx(&pRawDesc->pszRawDisk, pszRawDrive);
+ }
+ else
+ {
+ /*
+ * In selected partitions mode we've got a lot more work ahead of us.
+ */
+ pRawDesc->uFlags = VDISKRAW_NORMAL;
+ //pRawDesc->pszRawDisk = NULL;
+ //pRawDesc->cPartDescs = 0;
+ //pRawDesc->pPartDescs = NULL;
+
+ /* We need to parse the partition map to complete the descriptor: */
+ RTDVM hVolMgr = NIL_RTDVM;
+ rc = vmdkRawDescOpenVolMgr(pImage, hRawDrive, pszRawDrive, cbSector, &hVolMgr);
+ if (RT_SUCCESS(rc))
+ {
+ RTDVMFORMATTYPE enmFormatType = RTDvmMapGetFormatType(hVolMgr);
+ if ( enmFormatType == RTDVMFORMATTYPE_MBR
+ || enmFormatType == RTDVMFORMATTYPE_GPT)
+ {
+ pRawDesc->enmPartitioningType = enmFormatType == RTDVMFORMATTYPE_MBR
+ ? VDISKPARTTYPE_MBR : VDISKPARTTYPE_GPT;
+
+ /* Add copies of the partition tables: */
+ rc = vmdkRawDescDoCopyPartitionTables(pImage, hVolMgr, pRawDesc, pszRawDrive, hRawDrive,
+ pvBootSector, cbBootSector);
+ if (RT_SUCCESS(rc))
+ {
+ /* Add descriptors for the partitions/volumes, indicating which
+ should be accessible and how to access them: */
+ RTDVMVOLUME hVolRelease = NIL_RTDVMVOLUME;
+ rc = vmdkRawDescDoPartitions(pImage, hVolMgr, pRawDesc, hRawDrive, pszRawDrive, cbSector,
+ fPartitions, fPartitionsReadOnly, fRelative, &hVolRelease);
+ RTDvmVolumeRelease(hVolRelease);
+
+ /* Finally, sort the partition and check consistency (overlaps, etc): */
+ if (RT_SUCCESS(rc))
+ rc = vmdkRawDescPostProcessPartitions(pImage, pRawDesc, cbSize);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Unsupported partitioning for the disk '%s': %s"),
+ pImage->pszFilename, pszRawDrive, RTDvmMapGetFormatType(hVolMgr));
+ RTDvmRelease(hVolMgr);
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * We succeeded.
+ */
+ *ppRaw = pRawDesc;
+ Log(("vmdkMakeRawDescriptor: fFlags=%#x enmPartitioningType=%d cPartDescs=%u pszRawDisk=%s\n",
+ pRawDesc->uFlags, pRawDesc->enmPartitioningType, pRawDesc->cPartDescs, pRawDesc->pszRawDisk));
+ if (pRawDesc->cPartDescs)
+ {
+ Log(("# VMDK offset Length Device offset PartDataPtr Device\n"));
+ for (uint32_t i = 0; i < pRawDesc->cPartDescs; i++)
+ Log(("%2u %14RU64 %14RU64 %14RU64 %#18p %s\n", i, pRawDesc->pPartDescs[i].offStartInVDisk,
+ pRawDesc->pPartDescs[i].cbData, pRawDesc->pPartDescs[i].offStartInDevice,
+ pRawDesc->pPartDescs[i].pvPartitionData, pRawDesc->pPartDescs[i].pszRawDevice));
+ }
+ }
+ else
+ vmdkRawDescFree(pRawDesc);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("VMDK: Image path: '%s'. Failed to allocate %u bytes for the raw drive descriptor"),
+ pImage->pszFilename, sizeof(*pRawDesc));
+ RTFileClose(hRawDrive);
+ }
+ }
+ RTStrFree(pszRawDrive);
+ RTMemFree(pvBootSector);
+ return rc;
+}
+
+/**
+ * Internal: create VMDK images for raw disk/partition access.
+ */
+static int vmdkCreateRawImage(PVMDKIMAGE pImage, const PVDISKRAW pRaw,
+ uint64_t cbSize)
+{
+ int rc = VINF_SUCCESS;
+ PVMDKEXTENT pExtent;
+
+ if (pRaw->uFlags & VDISKRAW_DISK)
+ {
+ /* Full raw disk access. This requires setting up a descriptor
+ * file and open the (flat) raw disk. */
+ rc = vmdkCreateExtents(pImage, 1);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new extent list in '%s'"), pImage->pszFilename);
+ pExtent = &pImage->pExtents[0];
+ /* Create raw disk descriptor file. */
+ rc = vmdkFileOpen(pImage, &pImage->pFile, NULL, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags,
+ true /* fCreate */));
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new file '%s'"), pImage->pszFilename);
+
+ /* Set up basename for extent description. Cannot use StrDup. */
+ size_t cbBasename = strlen(pRaw->pszRawDisk) + 1;
+ char *pszBasename = (char *)RTMemTmpAlloc(cbBasename);
+ if (!pszBasename)
+ return VERR_NO_MEMORY;
+ memcpy(pszBasename, pRaw->pszRawDisk, cbBasename);
+ pExtent->pszBasename = pszBasename;
+ /* For raw disks the full name is identical to the base name. */
+ pExtent->pszFullname = RTStrDup(pszBasename);
+ if (!pExtent->pszFullname)
+ return VERR_NO_MEMORY;
+ pExtent->enmType = VMDKETYPE_FLAT;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(cbSize);
+ pExtent->uSectorOffset = 0;
+ pExtent->enmAccess = (pRaw->uFlags & VDISKRAW_READONLY) ? VMDKACCESS_READONLY : VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+
+ /* Open flat image, the raw disk. */
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszBasename, pExtent->pszFullname,
+ VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags | ((pExtent->enmAccess == VMDKACCESS_READONLY) ? VD_OPEN_FLAGS_READONLY : 0),
+ false /* fCreate */));
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not open raw disk file '%s'"), pExtent->pszFullname);
+ }
+ else
+ {
+ /* Raw partition access. This requires setting up a descriptor
+ * file, write the partition information to a flat extent and
+ * open all the (flat) raw disk partitions. */
+
+ /* First pass over the partition data areas to determine how many
+ * extents we need. One data area can require up to 2 extents, as
+ * it might be necessary to skip over unpartitioned space. */
+ unsigned cExtents = 0;
+ uint64_t uStart = 0;
+ for (unsigned i = 0; i < pRaw->cPartDescs; i++)
+ {
+ PVDISKRAWPARTDESC pPart = &pRaw->pPartDescs[i];
+ if (uStart > pPart->offStartInVDisk)
+ return vdIfError(pImage->pIfError, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("VMDK: incorrect partition data area ordering set up by the caller in '%s'"), pImage->pszFilename);
+
+ if (uStart < pPart->offStartInVDisk)
+ cExtents++;
+ uStart = pPart->offStartInVDisk + pPart->cbData;
+ cExtents++;
+ }
+ /* Another extent for filling up the rest of the image. */
+ if (uStart != cbSize)
+ cExtents++;
+
+ rc = vmdkCreateExtents(pImage, cExtents);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new extent list in '%s'"), pImage->pszFilename);
+
+ /* Create raw partition descriptor file. */
+ rc = vmdkFileOpen(pImage, &pImage->pFile, NULL, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags,
+ true /* fCreate */));
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new file '%s'"), pImage->pszFilename);
+
+ /* Create base filename for the partition table extent. */
+ /** @todo remove fixed buffer without creating memory leaks. */
+ char pszPartition[1024];
+ const char *pszBase = RTPathFilename(pImage->pszFilename);
+ const char *pszSuff = RTPathSuffix(pszBase);
+ if (pszSuff == NULL)
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: invalid filename '%s'"), pImage->pszFilename);
+ char *pszBaseBase = RTStrDup(pszBase);
+ if (!pszBaseBase)
+ return VERR_NO_MEMORY;
+ RTPathStripSuffix(pszBaseBase);
+ RTStrPrintf(pszPartition, sizeof(pszPartition), "%s-pt%s",
+ pszBaseBase, pszSuff);
+ RTStrFree(pszBaseBase);
+
+ /* Second pass over the partitions, now define all extents. */
+ uint64_t uPartOffset = 0;
+ cExtents = 0;
+ uStart = 0;
+ for (unsigned i = 0; i < pRaw->cPartDescs; i++)
+ {
+ PVDISKRAWPARTDESC pPart = &pRaw->pPartDescs[i];
+ pExtent = &pImage->pExtents[cExtents++];
+
+ if (uStart < pPart->offStartInVDisk)
+ {
+ pExtent->pszBasename = NULL;
+ pExtent->pszFullname = NULL;
+ pExtent->enmType = VMDKETYPE_ZERO;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(pPart->offStartInVDisk - uStart);
+ pExtent->uSectorOffset = 0;
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+ /* go to next extent */
+ pExtent = &pImage->pExtents[cExtents++];
+ }
+ uStart = pPart->offStartInVDisk + pPart->cbData;
+
+ if (pPart->pvPartitionData)
+ {
+ /* Set up basename for extent description. Can't use StrDup. */
+ size_t cbBasename = strlen(pszPartition) + 1;
+ char *pszBasename = (char *)RTMemTmpAlloc(cbBasename);
+ if (!pszBasename)
+ return VERR_NO_MEMORY;
+ memcpy(pszBasename, pszPartition, cbBasename);
+ pExtent->pszBasename = pszBasename;
+
+ /* Set up full name for partition extent. */
+ char *pszDirname = RTStrDup(pImage->pszFilename);
+ if (!pszDirname)
+ return VERR_NO_STR_MEMORY;
+ RTPathStripFilename(pszDirname);
+ char *pszFullname = RTPathJoinA(pszDirname, pExtent->pszBasename);
+ RTStrFree(pszDirname);
+ if (!pszFullname)
+ return VERR_NO_STR_MEMORY;
+ pExtent->pszFullname = pszFullname;
+ pExtent->enmType = VMDKETYPE_FLAT;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(pPart->cbData);
+ pExtent->uSectorOffset = uPartOffset;
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+
+ /* Create partition table flat image. */
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszBasename, pExtent->pszFullname,
+ VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags | ((pExtent->enmAccess == VMDKACCESS_READONLY) ? VD_OPEN_FLAGS_READONLY : 0),
+ true /* fCreate */));
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new partition data file '%s'"), pExtent->pszFullname);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uPartOffset),
+ pPart->pvPartitionData,
+ pPart->cbData);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not write partition data to '%s'"), pExtent->pszFullname);
+ uPartOffset += VMDK_BYTE2SECTOR(pPart->cbData);
+ }
+ else
+ {
+ if (pPart->pszRawDevice)
+ {
+ /* Set up basename for extent descr. Can't use StrDup. */
+ size_t cbBasename = strlen(pPart->pszRawDevice) + 1;
+ char *pszBasename = (char *)RTMemTmpAlloc(cbBasename);
+ if (!pszBasename)
+ return VERR_NO_MEMORY;
+ memcpy(pszBasename, pPart->pszRawDevice, cbBasename);
+ pExtent->pszBasename = pszBasename;
+ /* For raw disks full name is identical to base name. */
+ pExtent->pszFullname = RTStrDup(pszBasename);
+ if (!pExtent->pszFullname)
+ return VERR_NO_MEMORY;
+ pExtent->enmType = VMDKETYPE_FLAT;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(pPart->cbData);
+ pExtent->uSectorOffset = VMDK_BYTE2SECTOR(pPart->offStartInDevice);
+ pExtent->enmAccess = (pPart->uFlags & VDISKRAW_READONLY) ? VMDKACCESS_READONLY : VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+
+ /* Open flat image, the raw partition. */
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszBasename, pExtent->pszFullname,
+ VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags | ((pExtent->enmAccess == VMDKACCESS_READONLY) ? VD_OPEN_FLAGS_READONLY : 0),
+ false /* fCreate */));
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not open raw partition file '%s'"), pExtent->pszFullname);
+ }
+ else
+ {
+ pExtent->pszBasename = NULL;
+ pExtent->pszFullname = NULL;
+ pExtent->enmType = VMDKETYPE_ZERO;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(pPart->cbData);
+ pExtent->uSectorOffset = 0;
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+ }
+ }
+ }
+ /* Another extent for filling up the rest of the image. */
+ if (uStart != cbSize)
+ {
+ pExtent = &pImage->pExtents[cExtents++];
+ pExtent->pszBasename = NULL;
+ pExtent->pszFullname = NULL;
+ pExtent->enmType = VMDKETYPE_ZERO;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(cbSize - uStart);
+ pExtent->uSectorOffset = 0;
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fMetaDirty = false;
+ }
+ }
+
+ rc = vmdkDescBaseSetStr(pImage, &pImage->Descriptor, "createType",
+ (pRaw->uFlags & VDISKRAW_DISK) ?
+ "fullDevice" : "partitionedDevice");
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not set the image type in '%s'"), pImage->pszFilename);
+ return rc;
+}
+
+/**
+ * Internal: create a regular (i.e. file-backed) VMDK image.
+ */
+static int vmdkCreateRegularImage(PVMDKIMAGE pImage, uint64_t cbSize,
+ unsigned uImageFlags, PVDINTERFACEPROGRESS pIfProgress,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ int rc = VINF_SUCCESS;
+ unsigned cExtents = 1;
+ uint64_t cbOffset = 0;
+ uint64_t cbRemaining = cbSize;
+
+ if (uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G)
+ {
+ cExtents = cbSize / VMDK_2G_SPLIT_SIZE;
+ /* Do proper extent computation: need one smaller extent if the total
+ * size isn't evenly divisible by the split size. */
+ if (cbSize % VMDK_2G_SPLIT_SIZE)
+ cExtents++;
+ }
+ rc = vmdkCreateExtents(pImage, cExtents);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new extent list in '%s'"), pImage->pszFilename);
+
+ /* Basename strings needed for constructing the extent names. */
+ char *pszBasenameSubstr = RTPathFilename(pImage->pszFilename);
+ AssertPtr(pszBasenameSubstr);
+ size_t cbBasenameSubstr = strlen(pszBasenameSubstr) + 1;
+
+ /* Create separate descriptor file if necessary. */
+ if (cExtents != 1 || (uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ rc = vmdkFileOpen(pImage, &pImage->pFile, NULL, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags,
+ true /* fCreate */));
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new sparse descriptor file '%s'"), pImage->pszFilename);
+ }
+ else
+ pImage->pFile = NULL;
+
+ /* Set up all extents. */
+ for (unsigned i = 0; i < cExtents; i++)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[i];
+ uint64_t cbExtent = cbRemaining;
+
+ /* Set up fullname/basename for extent description. Cannot use StrDup
+ * for basename, as it is not guaranteed that the memory can be freed
+ * with RTMemTmpFree, which must be used as in other code paths
+ * StrDup is not usable. */
+ if (cExtents == 1 && !(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ char *pszBasename = (char *)RTMemTmpAlloc(cbBasenameSubstr);
+ if (!pszBasename)
+ return VERR_NO_MEMORY;
+ memcpy(pszBasename, pszBasenameSubstr, cbBasenameSubstr);
+ pExtent->pszBasename = pszBasename;
+ }
+ else
+ {
+ char *pszBasenameSuff = RTPathSuffix(pszBasenameSubstr);
+ char *pszBasenameBase = RTStrDup(pszBasenameSubstr);
+ RTPathStripSuffix(pszBasenameBase);
+ char *pszTmp;
+ size_t cbTmp;
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ {
+ if (cExtents == 1)
+ RTStrAPrintf(&pszTmp, "%s-flat%s", pszBasenameBase,
+ pszBasenameSuff);
+ else
+ RTStrAPrintf(&pszTmp, "%s-f%03d%s", pszBasenameBase,
+ i+1, pszBasenameSuff);
+ }
+ else
+ RTStrAPrintf(&pszTmp, "%s-s%03d%s", pszBasenameBase, i+1,
+ pszBasenameSuff);
+ RTStrFree(pszBasenameBase);
+ if (!pszTmp)
+ return VERR_NO_STR_MEMORY;
+ cbTmp = strlen(pszTmp) + 1;
+ char *pszBasename = (char *)RTMemTmpAlloc(cbTmp);
+ if (!pszBasename)
+ {
+ RTStrFree(pszTmp);
+ return VERR_NO_MEMORY;
+ }
+ memcpy(pszBasename, pszTmp, cbTmp);
+ RTStrFree(pszTmp);
+ pExtent->pszBasename = pszBasename;
+ if (uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G)
+ cbExtent = RT_MIN(cbRemaining, VMDK_2G_SPLIT_SIZE);
+ }
+ char *pszBasedirectory = RTStrDup(pImage->pszFilename);
+ if (!pszBasedirectory)
+ return VERR_NO_STR_MEMORY;
+ RTPathStripFilename(pszBasedirectory);
+ char *pszFullname = RTPathJoinA(pszBasedirectory, pExtent->pszBasename);
+ RTStrFree(pszBasedirectory);
+ if (!pszFullname)
+ return VERR_NO_STR_MEMORY;
+ pExtent->pszFullname = pszFullname;
+
+ /* Create file for extent. */
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszBasename, pExtent->pszFullname,
+ VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags,
+ true /* fCreate */));
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new file '%s'"), pExtent->pszFullname);
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ {
+ rc = vdIfIoIntFileSetAllocationSize(pImage->pIfIo, pExtent->pFile->pStorage, cbExtent,
+ 0 /* fFlags */, pIfProgress,
+ uPercentStart + cbOffset * uPercentSpan / cbSize,
+ cbExtent * uPercentSpan / cbSize);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not set size of new file '%s'"), pExtent->pszFullname);
+ }
+
+ /* Place descriptor file information (where integrated). */
+ if (cExtents == 1 && !(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ pExtent->uDescriptorSector = 1;
+ pExtent->cDescriptorSectors = VMDK_BYTE2SECTOR(pImage->cbDescAlloc);
+ /* The descriptor is part of the (only) extent. */
+ pExtent->pDescData = pImage->pDescData;
+ pImage->pDescData = NULL;
+ }
+
+ if (!(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ uint64_t cSectorsPerGDE, cSectorsPerGD;
+ pExtent->enmType = VMDKETYPE_HOSTED_SPARSE;
+ pExtent->cSectors = VMDK_BYTE2SECTOR(RT_ALIGN_64(cbExtent, _64K));
+ pExtent->cSectorsPerGrain = VMDK_BYTE2SECTOR(_64K);
+ pExtent->cGTEntries = 512;
+ cSectorsPerGDE = pExtent->cGTEntries * pExtent->cSectorsPerGrain;
+ pExtent->cSectorsPerGDE = cSectorsPerGDE;
+ pExtent->cGDEntries = (pExtent->cSectors + cSectorsPerGDE - 1) / cSectorsPerGDE;
+ cSectorsPerGD = (pExtent->cGDEntries + (512 / sizeof(uint32_t) - 1)) / (512 / sizeof(uint32_t));
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ /* The spec says version is 1 for all VMDKs, but the vast
+ * majority of streamOptimized VMDKs actually contain
+ * version 3 - so go with the majority. Both are accepted. */
+ pExtent->uVersion = 3;
+ pExtent->uCompression = VMDK_COMPRESSION_DEFLATE;
+ }
+ }
+ else
+ {
+ if (uImageFlags & VD_VMDK_IMAGE_FLAGS_ESX)
+ pExtent->enmType = VMDKETYPE_VMFS;
+ else
+ pExtent->enmType = VMDKETYPE_FLAT;
+ }
+
+ pExtent->enmAccess = VMDKACCESS_READWRITE;
+ pExtent->fUncleanShutdown = true;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(cbExtent);
+ pExtent->uSectorOffset = 0;
+ pExtent->fMetaDirty = true;
+
+ if (!(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ /* fPreAlloc should never be false because VMware can't use such images. */
+ rc = vmdkCreateGrainDirectory(pImage, pExtent,
+ RT_MAX( pExtent->uDescriptorSector
+ + pExtent->cDescriptorSectors,
+ 1),
+ true /* fPreAlloc */);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new grain directory in '%s'"), pExtent->pszFullname);
+ }
+
+ cbOffset += cbExtent;
+
+ if (RT_SUCCESS(rc))
+ vdIfProgress(pIfProgress, uPercentStart + cbOffset * uPercentSpan / cbSize);
+
+ cbRemaining -= cbExtent;
+ }
+
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_ESX)
+ {
+ /* VirtualBox doesn't care, but VMWare ESX freaks out if the wrong
+ * controller type is set in an image. */
+ rc = vmdkDescDDBSetStr(pImage, &pImage->Descriptor, "ddb.adapterType", "lsilogic");
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not set controller type to lsilogic in '%s'"), pImage->pszFilename);
+ }
+
+ const char *pszDescType = NULL;
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ {
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_ESX)
+ pszDescType = "vmfs";
+ else
+ pszDescType = (cExtents == 1)
+ ? "monolithicFlat" : "twoGbMaxExtentFlat";
+ }
+ else
+ {
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ pszDescType = "streamOptimized";
+ else
+ {
+ pszDescType = (cExtents == 1)
+ ? "monolithicSparse" : "twoGbMaxExtentSparse";
+ }
+ }
+ rc = vmdkDescBaseSetStr(pImage, &pImage->Descriptor, "createType",
+ pszDescType);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not set the image type in '%s'"), pImage->pszFilename);
+ return rc;
+}
+
+/**
+ * Internal: Create a real stream optimized VMDK using only linear writes.
+ */
+static int vmdkCreateStreamImage(PVMDKIMAGE pImage, uint64_t cbSize)
+{
+ int rc = vmdkCreateExtents(pImage, 1);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new extent list in '%s'"), pImage->pszFilename);
+
+ /* Basename strings needed for constructing the extent names. */
+ const char *pszBasenameSubstr = RTPathFilename(pImage->pszFilename);
+ AssertPtr(pszBasenameSubstr);
+ size_t cbBasenameSubstr = strlen(pszBasenameSubstr) + 1;
+
+ /* No separate descriptor file. */
+ pImage->pFile = NULL;
+
+ /* Set up all extents. */
+ PVMDKEXTENT pExtent = &pImage->pExtents[0];
+
+ /* Set up fullname/basename for extent description. Cannot use StrDup
+ * for basename, as it is not guaranteed that the memory can be freed
+ * with RTMemTmpFree, which must be used as in other code paths
+ * StrDup is not usable. */
+ char *pszBasename = (char *)RTMemTmpAlloc(cbBasenameSubstr);
+ if (!pszBasename)
+ return VERR_NO_MEMORY;
+ memcpy(pszBasename, pszBasenameSubstr, cbBasenameSubstr);
+ pExtent->pszBasename = pszBasename;
+
+ char *pszBasedirectory = RTStrDup(pImage->pszFilename);
+ RTPathStripFilename(pszBasedirectory);
+ char *pszFullname = RTPathJoinA(pszBasedirectory, pExtent->pszBasename);
+ RTStrFree(pszBasedirectory);
+ if (!pszFullname)
+ return VERR_NO_STR_MEMORY;
+ pExtent->pszFullname = pszFullname;
+
+ /* Create file for extent. Make it write only, no reading allowed. */
+ rc = vmdkFileOpen(pImage, &pExtent->pFile, pExtent->pszBasename, pExtent->pszFullname,
+ VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags,
+ true /* fCreate */)
+ & ~RTFILE_O_READ);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new file '%s'"), pExtent->pszFullname);
+
+ /* Place descriptor file information. */
+ pExtent->uDescriptorSector = 1;
+ pExtent->cDescriptorSectors = VMDK_BYTE2SECTOR(pImage->cbDescAlloc);
+ /* The descriptor is part of the (only) extent. */
+ pExtent->pDescData = pImage->pDescData;
+ pImage->pDescData = NULL;
+
+ uint64_t cSectorsPerGDE, cSectorsPerGD;
+ pExtent->enmType = VMDKETYPE_HOSTED_SPARSE;
+ pExtent->cSectors = VMDK_BYTE2SECTOR(RT_ALIGN_64(cbSize, _64K));
+ pExtent->cSectorsPerGrain = VMDK_BYTE2SECTOR(_64K);
+ pExtent->cGTEntries = 512;
+ cSectorsPerGDE = pExtent->cGTEntries * pExtent->cSectorsPerGrain;
+ pExtent->cSectorsPerGDE = cSectorsPerGDE;
+ pExtent->cGDEntries = (pExtent->cSectors + cSectorsPerGDE - 1) / cSectorsPerGDE;
+ cSectorsPerGD = (pExtent->cGDEntries + (512 / sizeof(uint32_t) - 1)) / (512 / sizeof(uint32_t));
+
+ /* The spec says version is 1 for all VMDKs, but the vast
+ * majority of streamOptimized VMDKs actually contain
+ * version 3 - so go with the majority. Both are accepted. */
+ pExtent->uVersion = 3;
+ pExtent->uCompression = VMDK_COMPRESSION_DEFLATE;
+ pExtent->fFooter = true;
+
+ pExtent->enmAccess = VMDKACCESS_READONLY;
+ pExtent->fUncleanShutdown = false;
+ pExtent->cNominalSectors = VMDK_BYTE2SECTOR(cbSize);
+ pExtent->uSectorOffset = 0;
+ pExtent->fMetaDirty = true;
+
+ /* Create grain directory, without preallocating it straight away. It will
+ * be constructed on the fly when writing out the data and written when
+ * closing the image. The end effect is that the full grain directory is
+ * allocated, which is a requirement of the VMDK specs. */
+ rc = vmdkCreateGrainDirectory(pImage, pExtent, VMDK_GD_AT_END,
+ false /* fPreAlloc */);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new grain directory in '%s'"), pExtent->pszFullname);
+
+ rc = vmdkDescBaseSetStr(pImage, &pImage->Descriptor, "createType",
+ "streamOptimized");
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not set the image type in '%s'"), pImage->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Initializes the UUID fields in the DDB.
+ *
+ * @returns VBox status code.
+ * @param pImage The VMDK image instance.
+ */
+static int vmdkCreateImageDdbUuidsInit(PVMDKIMAGE pImage)
+{
+ int rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor, VMDK_DDB_IMAGE_UUID, &pImage->ImageUuid);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor, VMDK_DDB_PARENT_UUID, &pImage->ParentUuid);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor, VMDK_DDB_MODIFICATION_UUID,
+ &pImage->ModificationUuid);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor, VMDK_DDB_PARENT_MODIFICATION_UUID,
+ &pImage->ParentModificationUuid);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: error storing parent modification UUID in new descriptor in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: error storing modification UUID in new descriptor in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: error storing parent image UUID in new descriptor in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: error storing image UUID in new descriptor in '%s'"), pImage->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Internal: The actual code for creating any VMDK variant currently in
+ * existence on hosted environments.
+ */
+static int vmdkCreateImage(PVMDKIMAGE pImage, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry, PCRTUUID pUuid,
+ PVDINTERFACEPROGRESS pIfProgress,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ pImage->uImageFlags = uImageFlags;
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ int rc = vmdkCreateDescriptor(pImage, pImage->pDescData, pImage->cbDescAlloc,
+ &pImage->Descriptor);
+ if (RT_SUCCESS(rc))
+ {
+ if (uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK)
+ {
+ /* Raw disk image (includes raw partition). */
+ PVDISKRAW pRaw = NULL;
+ rc = vmdkMakeRawDescriptor(pImage, &pRaw);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create raw descriptor for '%s'"),
+ pImage->pszFilename);
+ if (!cbSize)
+ cbSize = pImage->cbSize;
+
+ rc = vmdkCreateRawImage(pImage, pRaw, cbSize);
+ vmdkRawDescFree(pRaw);
+ }
+ else if (uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ /* Stream optimized sparse image (monolithic). */
+ rc = vmdkCreateStreamImage(pImage, cbSize);
+ }
+ else
+ {
+ /* Regular fixed or sparse image (monolithic or split). */
+ rc = vmdkCreateRegularImage(pImage, cbSize, uImageFlags,
+ pIfProgress, uPercentStart,
+ uPercentSpan * 95 / 100);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan * 98 / 100);
+
+ pImage->cbSize = cbSize;
+
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[i];
+
+ rc = vmdkDescExtInsert(pImage, &pImage->Descriptor, pExtent->enmAccess,
+ pExtent->cNominalSectors, pExtent->enmType,
+ pExtent->pszBasename, pExtent->uSectorOffset);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not insert the extent list into descriptor in '%s'"), pImage->pszFilename);
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ vmdkDescExtRemoveDummy(pImage, &pImage->Descriptor);
+
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ pImage->PCHSGeometry = *pPCHSGeometry;
+
+ if (RT_SUCCESS(rc))
+ {
+ if ( pPCHSGeometry->cCylinders != 0
+ && pPCHSGeometry->cHeads != 0
+ && pPCHSGeometry->cSectors != 0)
+ rc = vmdkDescSetPCHSGeometry(pImage, pPCHSGeometry);
+ else if (uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK)
+ {
+ VDGEOMETRY RawDiskPCHSGeometry;
+ RawDiskPCHSGeometry.cCylinders = (uint32_t)RT_MIN(pImage->cbSize / 512 / 16 / 63, 16383);
+ RawDiskPCHSGeometry.cHeads = 16;
+ RawDiskPCHSGeometry.cSectors = 63;
+ rc = vmdkDescSetPCHSGeometry(pImage, &RawDiskPCHSGeometry);
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pLCHSGeometry->cCylinders != 0
+ && pLCHSGeometry->cHeads != 0
+ && pLCHSGeometry->cSectors != 0)
+ rc = vmdkDescSetLCHSGeometry(pImage, pLCHSGeometry);
+
+ pImage->ImageUuid = *pUuid;
+ RTUuidClear(&pImage->ParentUuid);
+ RTUuidClear(&pImage->ModificationUuid);
+ RTUuidClear(&pImage->ParentModificationUuid);
+
+ if (RT_SUCCESS(rc))
+ rc = vmdkCreateImageDdbUuidsInit(pImage);
+
+ if (RT_SUCCESS(rc))
+ rc = vmdkAllocateGrainTableCache(pImage);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vmdkSetImageComment(pImage, pszComment);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot set image comment in '%s'"), pImage->pszFilename);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan * 99 / 100);
+
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ /* streamOptimized is a bit special, we cannot trigger the flush
+ * until all data has been written. So we write the necessary
+ * information explicitly. */
+ pImage->pExtents[0].cDescriptorSectors = VMDK_BYTE2SECTOR(RT_ALIGN_64( pImage->Descriptor.aLines[pImage->Descriptor.cLines]
+ - pImage->Descriptor.aLines[0], 512));
+ rc = vmdkWriteMetaSparseExtent(pImage, &pImage->pExtents[0], 0, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vmdkWriteDescriptor(pImage, NULL);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write VMDK descriptor in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write VMDK header in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vmdkFlushImage(pImage, NULL);
+ }
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not create new descriptor in '%s'"), pImage->pszFilename);
+
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+ }
+ else
+ vmdkFreeImage(pImage, rc != VERR_ALREADY_EXISTS, false /*fFlush*/);
+ return rc;
+}
+
+/**
+ * Internal: Update image comment.
+ */
+static int vmdkSetImageComment(PVMDKIMAGE pImage, const char *pszComment)
+{
+ char *pszCommentEncoded = NULL;
+ if (pszComment)
+ {
+ pszCommentEncoded = vmdkEncodeString(pszComment);
+ if (!pszCommentEncoded)
+ return VERR_NO_MEMORY;
+ }
+
+ int rc = vmdkDescDDBSetStr(pImage, &pImage->Descriptor,
+ "ddb.comment", pszCommentEncoded);
+ if (pszCommentEncoded)
+ RTStrFree(pszCommentEncoded);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error storing image comment in descriptor in '%s'"), pImage->pszFilename);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal. Clear the grain table buffer for real stream optimized writing.
+ */
+static void vmdkStreamClearGT(PVMDKIMAGE pImage, PVMDKEXTENT pExtent)
+{
+ uint32_t cCacheLines = RT_ALIGN(pExtent->cGTEntries, VMDK_GT_CACHELINE_SIZE) / VMDK_GT_CACHELINE_SIZE;
+ for (uint32_t i = 0; i < cCacheLines; i++)
+ memset(&pImage->pGTCache->aGTCache[i].aGTData[0], '\0',
+ VMDK_GT_CACHELINE_SIZE * sizeof(uint32_t));
+}
+
+/**
+ * Internal. Flush the grain table buffer for real stream optimized writing.
+ */
+static int vmdkStreamFlushGT(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ uint32_t uGDEntry)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cCacheLines = RT_ALIGN(pExtent->cGTEntries, VMDK_GT_CACHELINE_SIZE) / VMDK_GT_CACHELINE_SIZE;
+
+ /* VMware does not write out completely empty grain tables in the case
+ * of streamOptimized images, which according to my interpretation of
+ * the VMDK 1.1 spec is bending the rules. Since they do it and we can
+ * handle it without problems do it the same way and save some bytes. */
+ bool fAllZero = true;
+ for (uint32_t i = 0; i < cCacheLines; i++)
+ {
+ /* Convert the grain table to little endian in place, as it will not
+ * be used at all after this function has been called. */
+ uint32_t *pGTTmp = &pImage->pGTCache->aGTCache[i].aGTData[0];
+ for (uint32_t j = 0; j < VMDK_GT_CACHELINE_SIZE; j++, pGTTmp++)
+ if (*pGTTmp)
+ {
+ fAllZero = false;
+ break;
+ }
+ if (!fAllZero)
+ break;
+ }
+ if (fAllZero)
+ return VINF_SUCCESS;
+
+ uint64_t uFileOffset = pExtent->uAppendPosition;
+ if (!uFileOffset)
+ return VERR_INTERNAL_ERROR;
+ /* Align to sector, as the previous write could have been any size. */
+ uFileOffset = RT_ALIGN_64(uFileOffset, 512);
+
+ /* Grain table marker. */
+ uint8_t aMarker[512];
+ PVMDKMARKER pMarker = (PVMDKMARKER)&aMarker[0];
+ memset(pMarker, '\0', sizeof(aMarker));
+ pMarker->uSector = RT_H2LE_U64(VMDK_BYTE2SECTOR((uint64_t)pExtent->cGTEntries * sizeof(uint32_t)));
+ pMarker->uType = RT_H2LE_U32(VMDK_MARKER_GT);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage, uFileOffset,
+ aMarker, sizeof(aMarker));
+ AssertRC(rc);
+ uFileOffset += 512;
+
+ if (!pExtent->pGD || pExtent->pGD[uGDEntry])
+ return VERR_INTERNAL_ERROR;
+
+ pExtent->pGD[uGDEntry] = VMDK_BYTE2SECTOR(uFileOffset);
+
+ for (uint32_t i = 0; i < cCacheLines; i++)
+ {
+ /* Convert the grain table to little endian in place, as it will not
+ * be used at all after this function has been called. */
+ uint32_t *pGTTmp = &pImage->pGTCache->aGTCache[i].aGTData[0];
+ for (uint32_t j = 0; j < VMDK_GT_CACHELINE_SIZE; j++, pGTTmp++)
+ *pGTTmp = RT_H2LE_U32(*pGTTmp);
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage, uFileOffset,
+ &pImage->pGTCache->aGTCache[i].aGTData[0],
+ VMDK_GT_CACHELINE_SIZE * sizeof(uint32_t));
+ uFileOffset += VMDK_GT_CACHELINE_SIZE * sizeof(uint32_t);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ Assert(!(uFileOffset % 512));
+ pExtent->uAppendPosition = RT_ALIGN_64(uFileOffset, 512);
+ return rc;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image, and optionally
+ * delete the image from disk.
+ */
+static int vmdkFreeImage(PVMDKIMAGE pImage, bool fDelete, bool fFlush)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ /* Check if all extents are clean. */
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ Assert(!pImage->pExtents[i].fUncleanShutdown);
+ }
+ }
+ else
+ {
+ /* Mark all extents as clean. */
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ if ( pImage->pExtents[i].enmType == VMDKETYPE_HOSTED_SPARSE
+ && pImage->pExtents[i].fUncleanShutdown)
+ {
+ pImage->pExtents[i].fUncleanShutdown = false;
+ pImage->pExtents[i].fMetaDirty = true;
+ }
+
+ /* From now on it's not safe to append any more data. */
+ pImage->pExtents[i].uAppendPosition = 0;
+ }
+ }
+ }
+
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ /* No need to write any pending data if the file will be deleted
+ * or if the new file wasn't successfully created. */
+ if ( !fDelete && pImage->pExtents
+ && pImage->pExtents[0].cGTEntries
+ && pImage->pExtents[0].uAppendPosition)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[0];
+ uint32_t uLastGDEntry = pExtent->uLastGrainAccess / pExtent->cGTEntries;
+ rc = vmdkStreamFlushGT(pImage, pExtent, uLastGDEntry);
+ AssertRC(rc);
+ vmdkStreamClearGT(pImage, pExtent);
+ for (uint32_t i = uLastGDEntry + 1; i < pExtent->cGDEntries; i++)
+ {
+ rc = vmdkStreamFlushGT(pImage, pExtent, i);
+ AssertRC(rc);
+ }
+
+ uint64_t uFileOffset = pExtent->uAppendPosition;
+ if (!uFileOffset)
+ return VERR_INTERNAL_ERROR;
+ uFileOffset = RT_ALIGN_64(uFileOffset, 512);
+
+ /* From now on it's not safe to append any more data. */
+ pExtent->uAppendPosition = 0;
+
+ /* Grain directory marker. */
+ uint8_t aMarker[512];
+ PVMDKMARKER pMarker = (PVMDKMARKER)&aMarker[0];
+ memset(pMarker, '\0', sizeof(aMarker));
+ pMarker->uSector = VMDK_BYTE2SECTOR(RT_ALIGN_64(RT_H2LE_U64((uint64_t)pExtent->cGDEntries * sizeof(uint32_t)), 512));
+ pMarker->uType = RT_H2LE_U32(VMDK_MARKER_GD);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage, uFileOffset,
+ aMarker, sizeof(aMarker));
+ AssertRC(rc);
+ uFileOffset += 512;
+
+ /* Write grain directory in little endian style. The array will
+ * not be used after this, so convert in place. */
+ uint32_t *pGDTmp = pExtent->pGD;
+ for (uint32_t i = 0; i < pExtent->cGDEntries; i++, pGDTmp++)
+ *pGDTmp = RT_H2LE_U32(*pGDTmp);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ uFileOffset, pExtent->pGD,
+ pExtent->cGDEntries * sizeof(uint32_t));
+ AssertRC(rc);
+
+ pExtent->uSectorGD = VMDK_BYTE2SECTOR(uFileOffset);
+ pExtent->uSectorRGD = VMDK_BYTE2SECTOR(uFileOffset);
+ uFileOffset = RT_ALIGN_64( uFileOffset
+ + pExtent->cGDEntries * sizeof(uint32_t),
+ 512);
+
+ /* Footer marker. */
+ memset(pMarker, '\0', sizeof(aMarker));
+ pMarker->uSector = VMDK_BYTE2SECTOR(512);
+ pMarker->uType = RT_H2LE_U32(VMDK_MARKER_FOOTER);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ uFileOffset, aMarker, sizeof(aMarker));
+ AssertRC(rc);
+
+ uFileOffset += 512;
+ rc = vmdkWriteMetaSparseExtent(pImage, pExtent, uFileOffset, NULL);
+ AssertRC(rc);
+
+ uFileOffset += 512;
+ /* End-of-stream marker. */
+ memset(pMarker, '\0', sizeof(aMarker));
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ uFileOffset, aMarker, sizeof(aMarker));
+ AssertRC(rc);
+ }
+ }
+ else if (!fDelete && fFlush)
+ vmdkFlushImage(pImage, NULL);
+
+ if (pImage->pExtents != NULL)
+ {
+ for (unsigned i = 0 ; i < pImage->cExtents; i++)
+ {
+ int rc2 = vmdkFreeExtentData(pImage, &pImage->pExtents[i], fDelete);
+ if (RT_SUCCESS(rc))
+ rc = rc2; /* Propogate any error when closing the file. */
+ }
+ RTMemFree(pImage->pExtents);
+ pImage->pExtents = NULL;
+ }
+ pImage->cExtents = 0;
+ if (pImage->pFile != NULL)
+ {
+ int rc2 = vmdkFileClose(pImage, &pImage->pFile, fDelete);
+ if (RT_SUCCESS(rc))
+ rc = rc2; /* Propogate any error when closing the file. */
+ }
+ int rc2 = vmdkFileCheckAllClose(pImage);
+ if (RT_SUCCESS(rc))
+ rc = rc2; /* Propogate any error when closing the file. */
+
+ if (pImage->pGTCache)
+ {
+ RTMemFree(pImage->pGTCache);
+ pImage->pGTCache = NULL;
+ }
+ if (pImage->pDescData)
+ {
+ RTMemFree(pImage->pDescData);
+ pImage->pDescData = NULL;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal. Flush image data (and metadata) to disk.
+ */
+static int vmdkFlushImage(PVMDKIMAGE pImage, PVDIOCTX pIoCtx)
+{
+ PVMDKEXTENT pExtent;
+ int rc = VINF_SUCCESS;
+
+ /* Update descriptor if changed. */
+ if (pImage->Descriptor.fDirty)
+ rc = vmdkWriteDescriptor(pImage, pIoCtx);
+
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ pExtent = &pImage->pExtents[i];
+ if (pExtent->pFile != NULL && pExtent->fMetaDirty)
+ {
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_HOSTED_SPARSE:
+ if (!pExtent->fFooter)
+ rc = vmdkWriteMetaSparseExtent(pImage, pExtent, 0, pIoCtx);
+ else
+ {
+ uint64_t uFileOffset = pExtent->uAppendPosition;
+ /* Simply skip writing anything if the streamOptimized
+ * image hasn't been just created. */
+ if (!uFileOffset)
+ break;
+ uFileOffset = RT_ALIGN_64(uFileOffset, 512);
+ rc = vmdkWriteMetaSparseExtent(pImage, pExtent,
+ uFileOffset, pIoCtx);
+ }
+ break;
+ case VMDKETYPE_VMFS:
+ case VMDKETYPE_FLAT:
+ /* Nothing to do. */
+ break;
+ case VMDKETYPE_ZERO:
+ default:
+ AssertMsgFailed(("extent with type %d marked as dirty\n",
+ pExtent->enmType));
+ break;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_HOSTED_SPARSE:
+ case VMDKETYPE_VMFS:
+ case VMDKETYPE_FLAT:
+ /** @todo implement proper path absolute check. */
+ if ( pExtent->pFile != NULL
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && !(pExtent->pszBasename[0] == RTPATH_SLASH))
+ rc = vdIfIoIntFileFlush(pImage->pIfIo, pExtent->pFile->pStorage, pIoCtx,
+ NULL, NULL);
+ break;
+ case VMDKETYPE_ZERO:
+ /* No need to do anything for this extent. */
+ break;
+ default:
+ AssertMsgFailed(("unknown extent type %d\n", pExtent->enmType));
+ break;
+ }
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Internal. Find extent corresponding to the sector number in the disk.
+ */
+static int vmdkFindExtent(PVMDKIMAGE pImage, uint64_t offSector,
+ PVMDKEXTENT *ppExtent, uint64_t *puSectorInExtent)
+{
+ PVMDKEXTENT pExtent = NULL;
+ int rc = VINF_SUCCESS;
+
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ if (offSector < pImage->pExtents[i].cNominalSectors)
+ {
+ pExtent = &pImage->pExtents[i];
+ *puSectorInExtent = offSector + pImage->pExtents[i].uSectorOffset;
+ break;
+ }
+ offSector -= pImage->pExtents[i].cNominalSectors;
+ }
+
+ if (pExtent)
+ *ppExtent = pExtent;
+ else
+ rc = VERR_IO_SECTOR_NOT_FOUND;
+
+ return rc;
+}
+
+/**
+ * Internal. Hash function for placing the grain table hash entries.
+ */
+static uint32_t vmdkGTCacheHash(PVMDKGTCACHE pCache, uint64_t uSector,
+ unsigned uExtent)
+{
+ /** @todo this hash function is quite simple, maybe use a better one which
+ * scrambles the bits better. */
+ return (uSector + uExtent) % pCache->cEntries;
+}
+
+/**
+ * Internal. Get sector number in the extent file from the relative sector
+ * number in the extent.
+ */
+static int vmdkGetSector(PVMDKIMAGE pImage, PVDIOCTX pIoCtx,
+ PVMDKEXTENT pExtent, uint64_t uSector,
+ uint64_t *puExtentSector)
+{
+ PVMDKGTCACHE pCache = pImage->pGTCache;
+ uint64_t uGDIndex, uGTSector, uGTBlock;
+ uint32_t uGTHash, uGTBlockIndex;
+ PVMDKGTCACHEENTRY pGTCacheEntry;
+ uint32_t aGTDataTmp[VMDK_GT_CACHELINE_SIZE];
+ int rc;
+
+ /* For newly created and readonly/sequentially opened streamOptimized
+ * images this must be a no-op, as the grain directory is not there. */
+ if ( ( pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED
+ && pExtent->uAppendPosition)
+ || ( pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED
+ && pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY
+ && pImage->uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL))
+ {
+ *puExtentSector = 0;
+ return VINF_SUCCESS;
+ }
+
+ uGDIndex = uSector / pExtent->cSectorsPerGDE;
+ if (uGDIndex >= pExtent->cGDEntries)
+ return VERR_OUT_OF_RANGE;
+ uGTSector = pExtent->pGD[uGDIndex];
+ if (!uGTSector)
+ {
+ /* There is no grain table referenced by this grain directory
+ * entry. So there is absolutely no data in this area. */
+ *puExtentSector = 0;
+ return VINF_SUCCESS;
+ }
+
+ uGTBlock = uSector / (pExtent->cSectorsPerGrain * VMDK_GT_CACHELINE_SIZE);
+ uGTHash = vmdkGTCacheHash(pCache, uGTBlock, pExtent->uExtent);
+ pGTCacheEntry = &pCache->aGTCache[uGTHash];
+ if ( pGTCacheEntry->uExtent != pExtent->uExtent
+ || pGTCacheEntry->uGTBlock != uGTBlock)
+ {
+ /* Cache miss, fetch data from disk. */
+ PVDMETAXFER pMetaXfer;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+ aGTDataTmp, sizeof(aGTDataTmp), pIoCtx, &pMetaXfer, NULL, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ /* We can release the metadata transfer immediately. */
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+ pGTCacheEntry->uExtent = pExtent->uExtent;
+ pGTCacheEntry->uGTBlock = uGTBlock;
+ for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
+ pGTCacheEntry->aGTData[i] = RT_LE2H_U32(aGTDataTmp[i]);
+ }
+ uGTBlockIndex = (uSector / pExtent->cSectorsPerGrain) % VMDK_GT_CACHELINE_SIZE;
+ uint32_t uGrainSector = pGTCacheEntry->aGTData[uGTBlockIndex];
+ if (uGrainSector)
+ *puExtentSector = uGrainSector + uSector % pExtent->cSectorsPerGrain;
+ else
+ *puExtentSector = 0;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal. Writes the grain and also if necessary the grain tables.
+ * Uses the grain table cache as a true grain table.
+ */
+static int vmdkStreamAllocGrain(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ uint64_t uSector, PVDIOCTX pIoCtx,
+ uint64_t cbWrite)
+{
+ uint32_t uGrain;
+ uint32_t uGDEntry, uLastGDEntry;
+ uint32_t cbGrain = 0;
+ uint32_t uCacheLine, uCacheEntry;
+ const void *pData;
+ int rc;
+
+ /* Very strict requirements: always write at least one full grain, with
+ * proper alignment. Everything else would require reading of already
+ * written data, which we don't support for obvious reasons. The only
+ * exception is the last grain, and only if the image size specifies
+ * that only some portion holds data. In any case the write must be
+ * within the image limits, no "overshoot" allowed. */
+ if ( cbWrite == 0
+ || ( cbWrite < VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain)
+ && pExtent->cNominalSectors - uSector >= pExtent->cSectorsPerGrain)
+ || uSector % pExtent->cSectorsPerGrain
+ || uSector + VMDK_BYTE2SECTOR(cbWrite) > pExtent->cNominalSectors)
+ return VERR_INVALID_PARAMETER;
+
+ /* Clip write range to at most the rest of the grain. */
+ cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - uSector % pExtent->cSectorsPerGrain));
+
+ /* Do not allow to go back. */
+ uGrain = uSector / pExtent->cSectorsPerGrain;
+ uCacheLine = uGrain % pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE;
+ uCacheEntry = uGrain % VMDK_GT_CACHELINE_SIZE;
+ uGDEntry = uGrain / pExtent->cGTEntries;
+ uLastGDEntry = pExtent->uLastGrainAccess / pExtent->cGTEntries;
+ if (uGrain < pExtent->uLastGrainAccess)
+ return VERR_VD_VMDK_INVALID_WRITE;
+
+ /* Zero byte write optimization. Since we don't tell VBoxHDD that we need
+ * to allocate something, we also need to detect the situation ourself. */
+ if ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_ZEROES)
+ && vdIfIoIntIoCtxIsZero(pImage->pIfIo, pIoCtx, cbWrite, true /* fAdvance */))
+ return VINF_SUCCESS;
+
+ if (uGDEntry != uLastGDEntry)
+ {
+ rc = vmdkStreamFlushGT(pImage, pExtent, uLastGDEntry);
+ if (RT_FAILURE(rc))
+ return rc;
+ vmdkStreamClearGT(pImage, pExtent);
+ for (uint32_t i = uLastGDEntry + 1; i < uGDEntry; i++)
+ {
+ rc = vmdkStreamFlushGT(pImage, pExtent, i);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+
+ uint64_t uFileOffset;
+ uFileOffset = pExtent->uAppendPosition;
+ if (!uFileOffset)
+ return VERR_INTERNAL_ERROR;
+ /* Align to sector, as the previous write could have been any size. */
+ uFileOffset = RT_ALIGN_64(uFileOffset, 512);
+
+ /* Paranoia check: extent type, grain table buffer presence and
+ * grain table buffer space. Also grain table entry must be clear. */
+ if ( pExtent->enmType != VMDKETYPE_HOSTED_SPARSE
+ || !pImage->pGTCache
+ || pExtent->cGTEntries > VMDK_GT_CACHE_SIZE * VMDK_GT_CACHELINE_SIZE
+ || pImage->pGTCache->aGTCache[uCacheLine].aGTData[uCacheEntry])
+ return VERR_INTERNAL_ERROR;
+
+ /* Update grain table entry. */
+ pImage->pGTCache->aGTCache[uCacheLine].aGTData[uCacheEntry] = VMDK_BYTE2SECTOR(uFileOffset);
+
+ if (cbWrite != VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain))
+ {
+ vdIfIoIntIoCtxCopyFrom(pImage->pIfIo, pIoCtx, pExtent->pvGrain, cbWrite);
+ memset((char *)pExtent->pvGrain + cbWrite, '\0',
+ VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain) - cbWrite);
+ pData = pExtent->pvGrain;
+ }
+ else
+ {
+ RTSGSEG Segment;
+ unsigned cSegments = 1;
+ size_t cbSeg = 0;
+
+ cbSeg = vdIfIoIntIoCtxSegArrayCreate(pImage->pIfIo, pIoCtx, &Segment,
+ &cSegments, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ Assert(cbSeg == VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ pData = Segment.pvSeg;
+ }
+ rc = vmdkFileDeflateSync(pImage, pExtent, uFileOffset, pData,
+ VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain),
+ uSector, &cbGrain);
+ if (RT_FAILURE(rc))
+ {
+ pExtent->uGrainSectorAbs = 0;
+ AssertRC(rc);
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write compressed data block in '%s'"), pExtent->pszFullname);
+ }
+ pExtent->uLastGrainAccess = uGrain;
+ pExtent->uAppendPosition += cbGrain;
+
+ return rc;
+}
+
+/**
+ * Internal: Updates the grain table during grain allocation.
+ */
+static int vmdkAllocGrainGTUpdate(PVMDKIMAGE pImage, PVMDKEXTENT pExtent, PVDIOCTX pIoCtx,
+ PVMDKGRAINALLOCASYNC pGrainAlloc)
+{
+ int rc = VINF_SUCCESS;
+ PVMDKGTCACHE pCache = pImage->pGTCache;
+ uint32_t aGTDataTmp[VMDK_GT_CACHELINE_SIZE];
+ uint32_t uGTHash, uGTBlockIndex;
+ uint64_t uGTSector, uRGTSector, uGTBlock;
+ uint64_t uSector = pGrainAlloc->uSector;
+ PVMDKGTCACHEENTRY pGTCacheEntry;
+
+ LogFlowFunc(("pImage=%#p pExtent=%#p pCache=%#p pIoCtx=%#p pGrainAlloc=%#p\n",
+ pImage, pExtent, pCache, pIoCtx, pGrainAlloc));
+
+ uGTSector = pGrainAlloc->uGTSector;
+ uRGTSector = pGrainAlloc->uRGTSector;
+ LogFlow(("uGTSector=%llu uRGTSector=%llu\n", uGTSector, uRGTSector));
+
+ /* Update the grain table (and the cache). */
+ uGTBlock = uSector / (pExtent->cSectorsPerGrain * VMDK_GT_CACHELINE_SIZE);
+ uGTHash = vmdkGTCacheHash(pCache, uGTBlock, pExtent->uExtent);
+ pGTCacheEntry = &pCache->aGTCache[uGTHash];
+ if ( pGTCacheEntry->uExtent != pExtent->uExtent
+ || pGTCacheEntry->uGTBlock != uGTBlock)
+ {
+ /* Cache miss, fetch data from disk. */
+ LogFlow(("Cache miss, fetch data from disk\n"));
+ PVDMETAXFER pMetaXfer = NULL;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+ aGTDataTmp, sizeof(aGTDataTmp), pIoCtx,
+ &pMetaXfer, vmdkAllocGrainComplete, pGrainAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ pGrainAlloc->cIoXfersPending++;
+ pGrainAlloc->fGTUpdateNeeded = true;
+ /* Leave early, we will be called again after the read completed. */
+ LogFlowFunc(("Metadata read in progress, leaving\n"));
+ return rc;
+ }
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot read allocated grain table entry in '%s'"), pExtent->pszFullname);
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+ pGTCacheEntry->uExtent = pExtent->uExtent;
+ pGTCacheEntry->uGTBlock = uGTBlock;
+ for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
+ pGTCacheEntry->aGTData[i] = RT_LE2H_U32(aGTDataTmp[i]);
+ }
+ else
+ {
+ /* Cache hit. Convert grain table block back to disk format, otherwise
+ * the code below will write garbage for all but the updated entry. */
+ for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
+ aGTDataTmp[i] = RT_H2LE_U32(pGTCacheEntry->aGTData[i]);
+ }
+ pGrainAlloc->fGTUpdateNeeded = false;
+ uGTBlockIndex = (uSector / pExtent->cSectorsPerGrain) % VMDK_GT_CACHELINE_SIZE;
+ aGTDataTmp[uGTBlockIndex] = RT_H2LE_U32(VMDK_BYTE2SECTOR(pGrainAlloc->uGrainOffset));
+ pGTCacheEntry->aGTData[uGTBlockIndex] = VMDK_BYTE2SECTOR(pGrainAlloc->uGrainOffset);
+ /* Update grain table on disk. */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+ aGTDataTmp, sizeof(aGTDataTmp), pIoCtx,
+ vmdkAllocGrainComplete, pGrainAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ pGrainAlloc->cIoXfersPending++;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write updated grain table in '%s'"), pExtent->pszFullname);
+ if (pExtent->pRGD)
+ {
+ /* Update backup grain table on disk. */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uRGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+ aGTDataTmp, sizeof(aGTDataTmp), pIoCtx,
+ vmdkAllocGrainComplete, pGrainAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ pGrainAlloc->cIoXfersPending++;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write updated backup grain table in '%s'"), pExtent->pszFullname);
+ }
+
+ LogFlowFunc(("leaving rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal - complete the grain allocation by updating disk grain table if required.
+ */
+static DECLCALLBACK(int) vmdkAllocGrainComplete(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ RT_NOREF1(rcReq);
+ int rc = VINF_SUCCESS;
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ PVMDKGRAINALLOCASYNC pGrainAlloc = (PVMDKGRAINALLOCASYNC)pvUser;
+
+ LogFlowFunc(("pBackendData=%#p pIoCtx=%#p pvUser=%#p rcReq=%Rrc\n",
+ pBackendData, pIoCtx, pvUser, rcReq));
+
+ pGrainAlloc->cIoXfersPending--;
+ if (!pGrainAlloc->cIoXfersPending && pGrainAlloc->fGTUpdateNeeded)
+ rc = vmdkAllocGrainGTUpdate(pImage, pGrainAlloc->pExtent, pIoCtx, pGrainAlloc);
+
+ if (!pGrainAlloc->cIoXfersPending)
+ {
+ /* Grain allocation completed. */
+ RTMemFree(pGrainAlloc);
+ }
+
+ LogFlowFunc(("Leaving rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal. Allocates a new grain table (if necessary).
+ */
+static int vmdkAllocGrain(PVMDKIMAGE pImage, PVMDKEXTENT pExtent, PVDIOCTX pIoCtx,
+ uint64_t uSector, uint64_t cbWrite)
+{
+ PVMDKGTCACHE pCache = pImage->pGTCache; NOREF(pCache);
+ uint64_t uGDIndex, uGTSector, uRGTSector;
+ uint64_t uFileOffset;
+ PVMDKGRAINALLOCASYNC pGrainAlloc = NULL;
+ int rc;
+
+ LogFlowFunc(("pCache=%#p pExtent=%#p pIoCtx=%#p uSector=%llu cbWrite=%llu\n",
+ pCache, pExtent, pIoCtx, uSector, cbWrite));
+
+ pGrainAlloc = (PVMDKGRAINALLOCASYNC)RTMemAllocZ(sizeof(VMDKGRAINALLOCASYNC));
+ if (!pGrainAlloc)
+ return VERR_NO_MEMORY;
+
+ pGrainAlloc->pExtent = pExtent;
+ pGrainAlloc->uSector = uSector;
+
+ uGDIndex = uSector / pExtent->cSectorsPerGDE;
+ if (uGDIndex >= pExtent->cGDEntries)
+ {
+ RTMemFree(pGrainAlloc);
+ return VERR_OUT_OF_RANGE;
+ }
+ uGTSector = pExtent->pGD[uGDIndex];
+ if (pExtent->pRGD)
+ uRGTSector = pExtent->pRGD[uGDIndex];
+ else
+ uRGTSector = 0; /**< avoid compiler warning */
+ if (!uGTSector)
+ {
+ LogFlow(("Allocating new grain table\n"));
+
+ /* There is no grain table referenced by this grain directory
+ * entry. So there is absolutely no data in this area. Allocate
+ * a new grain table and put the reference to it in the GDs. */
+ uFileOffset = pExtent->uAppendPosition;
+ if (!uFileOffset)
+ {
+ RTMemFree(pGrainAlloc);
+ return VERR_INTERNAL_ERROR;
+ }
+ Assert(!(uFileOffset % 512));
+
+ uFileOffset = RT_ALIGN_64(uFileOffset, 512);
+ uGTSector = VMDK_BYTE2SECTOR(uFileOffset);
+
+ /* Normally the grain table is preallocated for hosted sparse extents
+ * that support more than 32 bit sector numbers. So this shouldn't
+ * ever happen on a valid extent. */
+ if (uGTSector > UINT32_MAX)
+ {
+ RTMemFree(pGrainAlloc);
+ return VERR_VD_VMDK_INVALID_HEADER;
+ }
+
+ /* Write grain table by writing the required number of grain table
+ * cache chunks. Allocate memory dynamically here or we flood the
+ * metadata cache with very small entries. */
+ size_t cbGTDataTmp = pExtent->cGTEntries * sizeof(uint32_t);
+ uint32_t *paGTDataTmp = (uint32_t *)RTMemTmpAllocZ(cbGTDataTmp);
+
+ if (!paGTDataTmp)
+ {
+ RTMemFree(pGrainAlloc);
+ return VERR_NO_MEMORY;
+ }
+
+ memset(paGTDataTmp, '\0', cbGTDataTmp);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uGTSector),
+ paGTDataTmp, cbGTDataTmp, pIoCtx,
+ vmdkAllocGrainComplete, pGrainAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ pGrainAlloc->cIoXfersPending++;
+ else if (RT_FAILURE(rc))
+ {
+ RTMemTmpFree(paGTDataTmp);
+ RTMemFree(pGrainAlloc);
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write grain table allocation in '%s'"), pExtent->pszFullname);
+ }
+ pExtent->uAppendPosition = RT_ALIGN_64( pExtent->uAppendPosition
+ + cbGTDataTmp, 512);
+
+ if (pExtent->pRGD)
+ {
+ AssertReturn(!uRGTSector, VERR_VD_VMDK_INVALID_HEADER);
+ uFileOffset = pExtent->uAppendPosition;
+ if (!uFileOffset)
+ return VERR_INTERNAL_ERROR;
+ Assert(!(uFileOffset % 512));
+ uRGTSector = VMDK_BYTE2SECTOR(uFileOffset);
+
+ /* Normally the redundant grain table is preallocated for hosted
+ * sparse extents that support more than 32 bit sector numbers. So
+ * this shouldn't ever happen on a valid extent. */
+ if (uRGTSector > UINT32_MAX)
+ {
+ RTMemTmpFree(paGTDataTmp);
+ return VERR_VD_VMDK_INVALID_HEADER;
+ }
+
+ /* Write grain table by writing the required number of grain table
+ * cache chunks. Allocate memory dynamically here or we flood the
+ * metadata cache with very small entries. */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uRGTSector),
+ paGTDataTmp, cbGTDataTmp, pIoCtx,
+ vmdkAllocGrainComplete, pGrainAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ pGrainAlloc->cIoXfersPending++;
+ else if (RT_FAILURE(rc))
+ {
+ RTMemTmpFree(paGTDataTmp);
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write backup grain table allocation in '%s'"), pExtent->pszFullname);
+ }
+
+ pExtent->uAppendPosition = pExtent->uAppendPosition + cbGTDataTmp;
+ }
+
+ RTMemTmpFree(paGTDataTmp);
+
+ /* Update the grain directory on disk (doing it before writing the
+ * grain table will result in a garbled extent if the operation is
+ * aborted for some reason. Otherwise the worst that can happen is
+ * some unused sectors in the extent. */
+ uint32_t uGTSectorLE = RT_H2LE_U64(uGTSector);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorGD) + uGDIndex * sizeof(uGTSectorLE),
+ &uGTSectorLE, sizeof(uGTSectorLE), pIoCtx,
+ vmdkAllocGrainComplete, pGrainAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ pGrainAlloc->cIoXfersPending++;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write grain directory entry in '%s'"), pExtent->pszFullname);
+ if (pExtent->pRGD)
+ {
+ uint32_t uRGTSectorLE = RT_H2LE_U64(uRGTSector);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorRGD) + uGDIndex * sizeof(uGTSectorLE),
+ &uRGTSectorLE, sizeof(uRGTSectorLE), pIoCtx,
+ vmdkAllocGrainComplete, pGrainAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ pGrainAlloc->cIoXfersPending++;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write backup grain directory entry in '%s'"), pExtent->pszFullname);
+ }
+
+ /* As the final step update the in-memory copy of the GDs. */
+ pExtent->pGD[uGDIndex] = uGTSector;
+ if (pExtent->pRGD)
+ pExtent->pRGD[uGDIndex] = uRGTSector;
+ }
+
+ LogFlow(("uGTSector=%llu uRGTSector=%llu\n", uGTSector, uRGTSector));
+ pGrainAlloc->uGTSector = uGTSector;
+ pGrainAlloc->uRGTSector = uRGTSector;
+
+ uFileOffset = pExtent->uAppendPosition;
+ if (!uFileOffset)
+ return VERR_INTERNAL_ERROR;
+ Assert(!(uFileOffset % 512));
+
+ pGrainAlloc->uGrainOffset = uFileOffset;
+
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ AssertMsgReturn(vdIfIoIntIoCtxIsSynchronous(pImage->pIfIo, pIoCtx),
+ ("Accesses to stream optimized images must be synchronous\n"),
+ VERR_INVALID_STATE);
+
+ if (cbWrite != VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain))
+ return vdIfError(pImage->pIfError, VERR_INTERNAL_ERROR, RT_SRC_POS, N_("VMDK: not enough data for a compressed data block in '%s'"), pExtent->pszFullname);
+
+ /* Invalidate cache, just in case some code incorrectly allows mixing
+ * of reads and writes. Normally shouldn't be needed. */
+ pExtent->uGrainSectorAbs = 0;
+
+ /* Write compressed data block and the markers. */
+ uint32_t cbGrain = 0;
+ size_t cbSeg = 0;
+ RTSGSEG Segment;
+ unsigned cSegments = 1;
+
+ cbSeg = vdIfIoIntIoCtxSegArrayCreate(pImage->pIfIo, pIoCtx, &Segment,
+ &cSegments, cbWrite);
+ Assert(cbSeg == cbWrite);
+
+ rc = vmdkFileDeflateSync(pImage, pExtent, uFileOffset,
+ Segment.pvSeg, cbWrite, uSector, &cbGrain);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write allocated compressed data block in '%s'"), pExtent->pszFullname);
+ }
+ pExtent->uLastGrainAccess = uSector / pExtent->cSectorsPerGrain;
+ pExtent->uAppendPosition += cbGrain;
+ }
+ else
+ {
+ /* Write the data. Always a full grain, or we're in big trouble. */
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pExtent->pFile->pStorage,
+ uFileOffset, pIoCtx, cbWrite,
+ vmdkAllocGrainComplete, pGrainAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ pGrainAlloc->cIoXfersPending++;
+ else if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: cannot write allocated data block in '%s'"), pExtent->pszFullname);
+
+ pExtent->uAppendPosition += cbWrite;
+ }
+
+ rc = vmdkAllocGrainGTUpdate(pImage, pExtent, pIoCtx, pGrainAlloc);
+
+ if (!pGrainAlloc->cIoXfersPending)
+ {
+ /* Grain allocation completed. */
+ RTMemFree(pGrainAlloc);
+ }
+
+ LogFlowFunc(("leaving rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Internal. Reads the contents by sequentially going over the compressed
+ * grains (hoping that they are in sequence).
+ */
+static int vmdkStreamReadSequential(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ uint64_t uSector, PVDIOCTX pIoCtx,
+ uint64_t cbRead)
+{
+ int rc;
+
+ LogFlowFunc(("pImage=%#p pExtent=%#p uSector=%llu pIoCtx=%#p cbRead=%llu\n",
+ pImage, pExtent, uSector, pIoCtx, cbRead));
+
+ AssertMsgReturn(vdIfIoIntIoCtxIsSynchronous(pImage->pIfIo, pIoCtx),
+ ("Async I/O not supported for sequential stream optimized images\n"),
+ VERR_INVALID_STATE);
+
+ /* Do not allow to go back. */
+ uint32_t uGrain = uSector / pExtent->cSectorsPerGrain;
+ if (uGrain < pExtent->uLastGrainAccess)
+ return VERR_VD_VMDK_INVALID_STATE;
+ pExtent->uLastGrainAccess = uGrain;
+
+ /* After a previous error do not attempt to recover, as it would need
+ * seeking (in the general case backwards which is forbidden). */
+ if (!pExtent->uGrainSectorAbs)
+ return VERR_VD_VMDK_INVALID_STATE;
+
+ /* Check if we need to read something from the image or if what we have
+ * in the buffer is good to fulfill the request. */
+ if (!pExtent->cbGrainStreamRead || uGrain > pExtent->uGrain)
+ {
+ uint32_t uGrainSectorAbs = pExtent->uGrainSectorAbs
+ + VMDK_BYTE2SECTOR(pExtent->cbGrainStreamRead);
+
+ /* Get the marker from the next data block - and skip everything which
+ * is not a compressed grain. If it's a compressed grain which is for
+ * the requested sector (or after), read it. */
+ VMDKMARKER Marker;
+ do
+ {
+ RT_ZERO(Marker);
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uGrainSectorAbs),
+ &Marker, RT_UOFFSETOF(VMDKMARKER, uType));
+ if (RT_FAILURE(rc))
+ return rc;
+ Marker.uSector = RT_LE2H_U64(Marker.uSector);
+ Marker.cbSize = RT_LE2H_U32(Marker.cbSize);
+
+ if (Marker.cbSize == 0)
+ {
+ /* A marker for something else than a compressed grain. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uGrainSectorAbs)
+ + RT_UOFFSETOF(VMDKMARKER, uType),
+ &Marker.uType, sizeof(Marker.uType));
+ if (RT_FAILURE(rc))
+ return rc;
+ Marker.uType = RT_LE2H_U32(Marker.uType);
+ switch (Marker.uType)
+ {
+ case VMDK_MARKER_EOS:
+ uGrainSectorAbs++;
+ /* Read (or mostly skip) to the end of file. Uses the
+ * Marker (LBA sector) as it is unused anyway. This
+ * makes sure that really everything is read in the
+ * success case. If this read fails it means the image
+ * is truncated, but this is harmless so ignore. */
+ vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uGrainSectorAbs)
+ + 511,
+ &Marker.uSector, 1);
+ break;
+ case VMDK_MARKER_GT:
+ uGrainSectorAbs += 1 + VMDK_BYTE2SECTOR(pExtent->cGTEntries * sizeof(uint32_t));
+ break;
+ case VMDK_MARKER_GD:
+ uGrainSectorAbs += 1 + VMDK_BYTE2SECTOR(RT_ALIGN(pExtent->cGDEntries * sizeof(uint32_t), 512));
+ break;
+ case VMDK_MARKER_FOOTER:
+ uGrainSectorAbs += 2;
+ break;
+ case VMDK_MARKER_UNSPECIFIED:
+ /* Skip over the contents of the unspecified marker
+ * type 4 which exists in some vSphere created files. */
+ /** @todo figure out what the payload means. */
+ uGrainSectorAbs += 1;
+ break;
+ default:
+ AssertMsgFailed(("VMDK: corrupted marker, type=%#x\n", Marker.uType));
+ pExtent->uGrainSectorAbs = 0;
+ return VERR_VD_VMDK_INVALID_STATE;
+ }
+ pExtent->cbGrainStreamRead = 0;
+ }
+ else
+ {
+ /* A compressed grain marker. If it is at/after what we're
+ * interested in read and decompress data. */
+ if (uSector > Marker.uSector + pExtent->cSectorsPerGrain)
+ {
+ uGrainSectorAbs += VMDK_BYTE2SECTOR(RT_ALIGN(Marker.cbSize + RT_UOFFSETOF(VMDKMARKER, uType), 512));
+ continue;
+ }
+ uint64_t uLBA = 0;
+ uint32_t cbGrainStreamRead = 0;
+ rc = vmdkFileInflateSync(pImage, pExtent,
+ VMDK_SECTOR2BYTE(uGrainSectorAbs),
+ pExtent->pvGrain,
+ VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain),
+ &Marker, &uLBA, &cbGrainStreamRead);
+ if (RT_FAILURE(rc))
+ {
+ pExtent->uGrainSectorAbs = 0;
+ return rc;
+ }
+ if ( pExtent->uGrain
+ && uLBA / pExtent->cSectorsPerGrain <= pExtent->uGrain)
+ {
+ pExtent->uGrainSectorAbs = 0;
+ return VERR_VD_VMDK_INVALID_STATE;
+ }
+ pExtent->uGrain = uLBA / pExtent->cSectorsPerGrain;
+ pExtent->cbGrainStreamRead = cbGrainStreamRead;
+ break;
+ }
+ } while (Marker.uType != VMDK_MARKER_EOS);
+
+ pExtent->uGrainSectorAbs = uGrainSectorAbs;
+
+ if (!pExtent->cbGrainStreamRead && Marker.uType == VMDK_MARKER_EOS)
+ {
+ pExtent->uGrain = UINT32_MAX;
+ /* Must set a non-zero value for pExtent->cbGrainStreamRead or
+ * the next read would try to get more data, and we're at EOF. */
+ pExtent->cbGrainStreamRead = 1;
+ }
+ }
+
+ if (pExtent->uGrain > uSector / pExtent->cSectorsPerGrain)
+ {
+ /* The next data block we have is not for this area, so just return
+ * that there is no data. */
+ LogFlowFunc(("returns VERR_VD_BLOCK_FREE\n"));
+ return VERR_VD_BLOCK_FREE;
+ }
+
+ uint32_t uSectorInGrain = uSector % pExtent->cSectorsPerGrain;
+ vdIfIoIntIoCtxCopyTo(pImage->pIfIo, pIoCtx,
+ (uint8_t *)pExtent->pvGrain + VMDK_SECTOR2BYTE(uSectorInGrain),
+ cbRead);
+ LogFlowFunc(("returns VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Replaces a fragment of a string with the specified string.
+ *
+ * @returns Pointer to the allocated UTF-8 string.
+ * @param pszWhere UTF-8 string to search in.
+ * @param pszWhat UTF-8 string to search for.
+ * @param pszByWhat UTF-8 string to replace the found string with.
+ *
+ * @note r=bird: This is only used by vmdkRenameWorker(). The first use is
+ * for updating the base name in the descriptor, the second is for
+ * generating new filenames for extents. This code borked when
+ * RTPathAbs started correcting the driver letter case on windows,
+ * when strstr failed because the pExtent->pszFullname was not
+ * subjected to RTPathAbs but while pExtent->pszFullname was. I fixed
+ * this by apply RTPathAbs to the places it wasn't applied.
+ *
+ * However, this highlights some undocumented ASSUMPTIONS as well as
+ * terrible short commings of the approach.
+ *
+ * Given the right filename, it may also screw up the descriptor. Take
+ * the descriptor text 'RW 2048 SPARSE "Test0.vmdk"' for instance,
+ * we'll be asked to replace "Test0" with something, no problem. No,
+ * imagine 'RW 2048 SPARSE "SPARSE.vmdk"', 'RW 2048 SPARSE "RW.vmdk"'
+ * or 'RW 2048 SPARSE "2048.vmdk"', and the strstr approach falls on
+ * its bum. The descriptor string must be parsed and reconstructed,
+ * the lazy strstr approach doesn't cut it.
+ *
+ * I'm also curious as to what would be the correct escaping of '"' in
+ * the file name and how that is supposed to be handled, because it
+ * needs to be or such names must be rejected in several places (maybe
+ * they are, I didn't check).
+ *
+ * When this function is used to replace the start of a path, I think
+ * the assumption from the prep/setup code is that we kind of knows
+ * what we're working on (I could be wrong). However, using strstr
+ * instead of strncmp/RTStrNICmp makes no sense and isn't future proof.
+ * Especially on unix systems, weird stuff could happen if someone
+ * unwittingly tinkers with the prep/setup code. What should really be
+ * done here is using a new RTPathStartEx function that (via flags)
+ * allows matching partial final component and returns the length of
+ * what it matched up (in case it skipped slashes and '.' components).
+ *
+ */
+static char *vmdkStrReplace(const char *pszWhere, const char *pszWhat,
+ const char *pszByWhat)
+{
+ AssertPtr(pszWhere);
+ AssertPtr(pszWhat);
+ AssertPtr(pszByWhat);
+ const char *pszFoundStr = strstr(pszWhere, pszWhat);
+ if (!pszFoundStr)
+ {
+ LogFlowFunc(("Failed to find '%s' in '%s'!\n", pszWhat, pszWhere));
+ return NULL;
+ }
+ size_t cbFinal = strlen(pszWhere) + 1 + strlen(pszByWhat) - strlen(pszWhat);
+ char *pszNewStr = RTStrAlloc(cbFinal);
+ if (pszNewStr)
+ {
+ char *pszTmp = pszNewStr;
+ memcpy(pszTmp, pszWhere, pszFoundStr - pszWhere);
+ pszTmp += pszFoundStr - pszWhere;
+ memcpy(pszTmp, pszByWhat, strlen(pszByWhat));
+ pszTmp += strlen(pszByWhat);
+ strcpy(pszTmp, pszFoundStr + strlen(pszWhat));
+ }
+ return pszNewStr;
+}
+
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) vmdkProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p penmType=%#p\n",
+ pszFilename, pVDIfsDisk, pVDIfsImage, penmType));
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ PVMDKIMAGE pImage = (PVMDKIMAGE)RTMemAllocZ(RT_UOFFSETOF(VMDKIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pFile = NULL;
+ pImage->pExtents = NULL;
+ pImage->pFiles = NULL;
+ pImage->pGTCache = NULL;
+ pImage->pDescData = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+ /** @todo speed up this test open (VD_OPEN_FLAGS_INFO) by skipping as
+ * much as possible in vmdkOpenImage. */
+ rc = vmdkOpenImage(pImage, VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_READONLY);
+ vmdkFreeImage(pImage, false, false /*fFlush*/);
+ RTMemFree(pImage);
+
+ if (RT_SUCCESS(rc))
+ *penmType = VDTYPE_HDD;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) vmdkOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ RT_NOREF1(enmType); /**< @todo r=klaus make use of the type info. */
+
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ PVMDKIMAGE pImage = (PVMDKIMAGE)RTMemAllocZ(RT_UOFFSETOF(VMDKIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pFile = NULL;
+ pImage->pExtents = NULL;
+ pImage->pFiles = NULL;
+ pImage->pGTCache = NULL;
+ pImage->pDescData = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vmdkOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ else
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnCreate */
+static DECLCALLBACK(int) vmdkCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+ int rc;
+
+ /* Check the VD container type and image flags. */
+ if ( enmType != VDTYPE_HDD
+ || (uImageFlags & ~VD_VMDK_IMAGE_FLAGS_MASK) != 0)
+ return VERR_VD_INVALID_TYPE;
+
+ /* Check size. Maximum 256TB-64K for sparse images, otherwise unlimited. */
+ if ( !(uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK)
+ && ( !cbSize
+ || (!(uImageFlags & VD_IMAGE_FLAGS_FIXED) && cbSize >= _1T * 256 - _64K)))
+ return VERR_VD_INVALID_SIZE;
+
+ /* Check image flags for invalid combinations. */
+ if ( (uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ && (uImageFlags & ~(VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED | VD_IMAGE_FLAGS_DIFF)))
+ return VERR_INVALID_PARAMETER;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER);
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+ AssertReturn(!( uImageFlags & VD_VMDK_IMAGE_FLAGS_ESX
+ && !(uImageFlags & VD_IMAGE_FLAGS_FIXED)),
+ VERR_INVALID_PARAMETER);
+
+ PVMDKIMAGE pImage = (PVMDKIMAGE)RTMemAllocZ(RT_UOFFSETOF(VMDKIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ pImage->pszFilename = pszFilename;
+ pImage->pFile = NULL;
+ pImage->pExtents = NULL;
+ pImage->pFiles = NULL;
+ pImage->pGTCache = NULL;
+ pImage->pDescData = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+ /* Descriptors for split images can be pretty large, especially if the
+ * filename is long. So prepare for the worst, and allocate quite some
+ * memory for the descriptor in this case. */
+ if (uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G)
+ pImage->cbDescAlloc = VMDK_SECTOR2BYTE(200);
+ else
+ pImage->cbDescAlloc = VMDK_SECTOR2BYTE(20);
+ pImage->pDescData = (char *)RTMemAllocZ(pImage->cbDescAlloc);
+ if (RT_LIKELY(pImage->pDescData))
+ {
+ rc = vmdkCreateImage(pImage, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, pUuid,
+ pIfProgress, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ vmdkFreeImage(pImage, false, true /*fFlush*/);
+ rc = vmdkOpenImage(pImage, uOpenFlags);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pImage->pDescData);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/**
+ * Prepares the state for renaming a VMDK image, setting up the state and allocating
+ * memory.
+ *
+ * @returns VBox status code.
+ * @param pImage VMDK image instance.
+ * @param pRenameState The state to initialize.
+ * @param pszFilename The new filename.
+ */
+static int vmdkRenameStatePrepare(PVMDKIMAGE pImage, PVMDKRENAMESTATE pRenameState, const char *pszFilename)
+{
+ AssertReturn(RTPathFilename(pszFilename) != NULL, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+
+ memset(&pRenameState->DescriptorCopy, 0, sizeof(pRenameState->DescriptorCopy));
+
+ /*
+ * Allocate an array to store both old and new names of renamed files
+ * in case we have to roll back the changes. Arrays are initialized
+ * with zeros. We actually save stuff when and if we change it.
+ */
+ pRenameState->cExtents = pImage->cExtents;
+ pRenameState->apszOldName = (char **)RTMemTmpAllocZ((pRenameState->cExtents + 1) * sizeof(char *));
+ pRenameState->apszNewName = (char **)RTMemTmpAllocZ((pRenameState->cExtents + 1) * sizeof(char *));
+ pRenameState->apszNewLines = (char **)RTMemTmpAllocZ(pRenameState->cExtents * sizeof(char *));
+ if ( pRenameState->apszOldName
+ && pRenameState->apszNewName
+ && pRenameState->apszNewLines)
+ {
+ /* Save the descriptor size and position. */
+ if (pImage->pDescData)
+ {
+ /* Separate descriptor file. */
+ pRenameState->fEmbeddedDesc = false;
+ }
+ else
+ {
+ /* Embedded descriptor file. */
+ pRenameState->ExtentCopy = pImage->pExtents[0];
+ pRenameState->fEmbeddedDesc = true;
+ }
+
+ /* Save the descriptor content. */
+ pRenameState->DescriptorCopy.cLines = pImage->Descriptor.cLines;
+ for (unsigned i = 0; i < pRenameState->DescriptorCopy.cLines; i++)
+ {
+ pRenameState->DescriptorCopy.aLines[i] = RTStrDup(pImage->Descriptor.aLines[i]);
+ if (!pRenameState->DescriptorCopy.aLines[i])
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Prepare both old and new base names used for string replacement. */
+ pRenameState->pszNewBaseName = RTStrDup(RTPathFilename(pszFilename));
+ AssertReturn(pRenameState->pszNewBaseName, VERR_NO_STR_MEMORY);
+ RTPathStripSuffix(pRenameState->pszNewBaseName);
+
+ pRenameState->pszOldBaseName = RTStrDup(RTPathFilename(pImage->pszFilename));
+ AssertReturn(pRenameState->pszOldBaseName, VERR_NO_STR_MEMORY);
+ RTPathStripSuffix(pRenameState->pszOldBaseName);
+
+ /* Prepare both old and new full names used for string replacement.
+ Note! Must abspath the stuff here, so the strstr weirdness later in
+ the renaming process get a match against abspath'ed extent paths.
+ See RTPathAbsDup call in vmdkDescriptorReadSparse(). */
+ pRenameState->pszNewFullName = RTPathAbsDup(pszFilename);
+ AssertReturn(pRenameState->pszNewFullName, VERR_NO_STR_MEMORY);
+ RTPathStripSuffix(pRenameState->pszNewFullName);
+
+ pRenameState->pszOldFullName = RTPathAbsDup(pImage->pszFilename);
+ AssertReturn(pRenameState->pszOldFullName, VERR_NO_STR_MEMORY);
+ RTPathStripSuffix(pRenameState->pszOldFullName);
+
+ /* Save the old name for easy access to the old descriptor file. */
+ pRenameState->pszOldDescName = RTStrDup(pImage->pszFilename);
+ AssertReturn(pRenameState->pszOldDescName, VERR_NO_STR_MEMORY);
+
+ /* Save old image name. */
+ pRenameState->pszOldImageName = pImage->pszFilename;
+ }
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Destroys the given rename state, freeing all allocated memory.
+ *
+ * @returns nothing.
+ * @param pRenameState The rename state to destroy.
+ */
+static void vmdkRenameStateDestroy(PVMDKRENAMESTATE pRenameState)
+{
+ for (unsigned i = 0; i < pRenameState->DescriptorCopy.cLines; i++)
+ if (pRenameState->DescriptorCopy.aLines[i])
+ RTStrFree(pRenameState->DescriptorCopy.aLines[i]);
+ if (pRenameState->apszOldName)
+ {
+ for (unsigned i = 0; i <= pRenameState->cExtents; i++)
+ if (pRenameState->apszOldName[i])
+ RTStrFree(pRenameState->apszOldName[i]);
+ RTMemTmpFree(pRenameState->apszOldName);
+ }
+ if (pRenameState->apszNewName)
+ {
+ for (unsigned i = 0; i <= pRenameState->cExtents; i++)
+ if (pRenameState->apszNewName[i])
+ RTStrFree(pRenameState->apszNewName[i]);
+ RTMemTmpFree(pRenameState->apszNewName);
+ }
+ if (pRenameState->apszNewLines)
+ {
+ for (unsigned i = 0; i < pRenameState->cExtents; i++)
+ if (pRenameState->apszNewLines[i])
+ RTStrFree(pRenameState->apszNewLines[i]);
+ RTMemTmpFree(pRenameState->apszNewLines);
+ }
+ if (pRenameState->pszOldDescName)
+ RTStrFree(pRenameState->pszOldDescName);
+ if (pRenameState->pszOldBaseName)
+ RTStrFree(pRenameState->pszOldBaseName);
+ if (pRenameState->pszNewBaseName)
+ RTStrFree(pRenameState->pszNewBaseName);
+ if (pRenameState->pszOldFullName)
+ RTStrFree(pRenameState->pszOldFullName);
+ if (pRenameState->pszNewFullName)
+ RTStrFree(pRenameState->pszNewFullName);
+}
+
+/**
+ * Rolls back the rename operation to the original state.
+ *
+ * @returns VBox status code.
+ * @param pImage VMDK image instance.
+ * @param pRenameState The rename state.
+ */
+static int vmdkRenameRollback(PVMDKIMAGE pImage, PVMDKRENAMESTATE pRenameState)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!pRenameState->fImageFreed)
+ {
+ /*
+ * Some extents may have been closed, close the rest. We will
+ * re-open the whole thing later.
+ */
+ vmdkFreeImage(pImage, false, true /*fFlush*/);
+ }
+
+ /* Rename files back. */
+ for (unsigned i = 0; i <= pRenameState->cExtents; i++)
+ {
+ if (pRenameState->apszOldName[i])
+ {
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pRenameState->apszNewName[i], pRenameState->apszOldName[i], 0);
+ AssertRC(rc);
+ }
+ }
+ /* Restore the old descriptor. */
+ PVMDKFILE pFile;
+ rc = vmdkFileOpen(pImage, &pFile, NULL, pRenameState->pszOldDescName,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_NORMAL,
+ false /* fCreate */));
+ AssertRC(rc);
+ if (pRenameState->fEmbeddedDesc)
+ {
+ pRenameState->ExtentCopy.pFile = pFile;
+ pImage->pExtents = &pRenameState->ExtentCopy;
+ }
+ else
+ {
+ /* Shouldn't be null for separate descriptor.
+ * There will be no access to the actual content.
+ */
+ pImage->pDescData = pRenameState->pszOldDescName;
+ pImage->pFile = pFile;
+ }
+ pImage->Descriptor = pRenameState->DescriptorCopy;
+ vmdkWriteDescriptor(pImage, NULL);
+ vmdkFileClose(pImage, &pFile, false);
+ /* Get rid of the stuff we implanted. */
+ pImage->pExtents = NULL;
+ pImage->pFile = NULL;
+ pImage->pDescData = NULL;
+ /* Re-open the image back. */
+ pImage->pszFilename = pRenameState->pszOldImageName;
+ rc = vmdkOpenImage(pImage, pImage->uOpenFlags);
+
+ return rc;
+}
+
+/**
+ * Rename worker doing the real work.
+ *
+ * @returns VBox status code.
+ * @param pImage VMDK image instance.
+ * @param pRenameState The rename state.
+ * @param pszFilename The new filename.
+ */
+static int vmdkRenameWorker(PVMDKIMAGE pImage, PVMDKRENAMESTATE pRenameState, const char *pszFilename)
+{
+ int rc = VINF_SUCCESS;
+ unsigned i, line;
+
+ /* Update the descriptor with modified extent names. */
+ for (i = 0, line = pImage->Descriptor.uFirstExtent;
+ i < pRenameState->cExtents;
+ i++, line = pImage->Descriptor.aNextLines[line])
+ {
+ /* Update the descriptor. */
+ pRenameState->apszNewLines[i] = vmdkStrReplace(pImage->Descriptor.aLines[line],
+ pRenameState->pszOldBaseName,
+ pRenameState->pszNewBaseName);
+ if (!pRenameState->apszNewLines[i])
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pImage->Descriptor.aLines[line] = pRenameState->apszNewLines[i];
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Make sure the descriptor gets written back. */
+ pImage->Descriptor.fDirty = true;
+ /* Flush the descriptor now, in case it is embedded. */
+ vmdkFlushImage(pImage, NULL);
+
+ /* Close and rename/move extents. */
+ for (i = 0; i < pRenameState->cExtents; i++)
+ {
+ PVMDKEXTENT pExtent = &pImage->pExtents[i];
+ /* Compose new name for the extent. */
+ pRenameState->apszNewName[i] = vmdkStrReplace(pExtent->pszFullname,
+ pRenameState->pszOldFullName,
+ pRenameState->pszNewFullName);
+ if (!pRenameState->apszNewName[i])
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ /* Close the extent file. */
+ rc = vmdkFileClose(pImage, &pExtent->pFile, false);
+ if (RT_FAILURE(rc))
+ break;;
+
+ /* Rename the extent file. */
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pExtent->pszFullname, pRenameState->apszNewName[i], 0);
+ if (RT_FAILURE(rc))
+ break;
+ /* Remember the old name. */
+ pRenameState->apszOldName[i] = RTStrDup(pExtent->pszFullname);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Release all old stuff. */
+ rc = vmdkFreeImage(pImage, false, true /*fFlush*/);
+ if (RT_SUCCESS(rc))
+ {
+ pRenameState->fImageFreed = true;
+
+ /* Last elements of new/old name arrays are intended for
+ * storing descriptor's names.
+ */
+ pRenameState->apszNewName[pRenameState->cExtents] = RTStrDup(pszFilename);
+ /* Rename the descriptor file if it's separate. */
+ if (!pRenameState->fEmbeddedDesc)
+ {
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pRenameState->apszNewName[pRenameState->cExtents], 0);
+ if (RT_SUCCESS(rc))
+ {
+ /* Save old name only if we may need to change it back. */
+ pRenameState->apszOldName[pRenameState->cExtents] = RTStrDup(pszFilename);
+ }
+ }
+
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the new image. */
+ rc = vmdkOpenImage(pImage, pImage->uOpenFlags);
+ }
+ }
+ }
+
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRename */
+static DECLCALLBACK(int) vmdkRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ VMDKRENAMESTATE RenameState;
+
+ memset(&RenameState, 0, sizeof(RenameState));
+
+ /* Check arguments. */
+ AssertPtrReturn(pImage, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertReturn(!(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK), VERR_INVALID_PARAMETER);
+
+ int rc = vmdkRenameStatePrepare(pImage, &RenameState, pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ /* --- Up to this point we have not done any damage yet. --- */
+
+ rc = vmdkRenameWorker(pImage, &RenameState, pszFilename);
+ /* Roll back all changes in case of failure. */
+ if (RT_FAILURE(rc))
+ {
+ int rrc = vmdkRenameRollback(pImage, &RenameState);
+ AssertRC(rrc);
+ }
+ }
+
+ vmdkRenameStateDestroy(&RenameState);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) vmdkClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ int rc = vmdkFreeImage(pImage, fDelete, true /*fFlush*/);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRead */
+static DECLCALLBACK(int) vmdkRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToRead=%zu pcbActuallyRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToRead % 512 == 0);
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToRead <= pImage->cbSize, VERR_INVALID_PARAMETER);
+
+ /* Find the extent and check access permissions as defined in the extent descriptor. */
+ PVMDKEXTENT pExtent;
+ uint64_t uSectorExtentRel;
+ int rc = vmdkFindExtent(pImage, VMDK_BYTE2SECTOR(uOffset),
+ &pExtent, &uSectorExtentRel);
+ if ( RT_SUCCESS(rc)
+ && pExtent->enmAccess != VMDKACCESS_NOACCESS)
+ {
+ /* Clip read range to remain in this extent. */
+ cbToRead = RT_MIN(cbToRead, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
+
+ /* Handle the read according to the current extent type. */
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_HOSTED_SPARSE:
+ {
+ uint64_t uSectorExtentAbs;
+
+ rc = vmdkGetSector(pImage, pIoCtx, pExtent, uSectorExtentRel, &uSectorExtentAbs);
+ if (RT_FAILURE(rc))
+ break;
+ /* Clip read range to at most the rest of the grain. */
+ cbToRead = RT_MIN(cbToRead, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - uSectorExtentRel % pExtent->cSectorsPerGrain));
+ Assert(!(cbToRead % 512));
+ if (uSectorExtentAbs == 0)
+ {
+ if ( !(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ || !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ || !(pImage->uOpenFlags & VD_OPEN_FLAGS_SEQUENTIAL))
+ rc = VERR_VD_BLOCK_FREE;
+ else
+ rc = vmdkStreamReadSequential(pImage, pExtent,
+ uSectorExtentRel,
+ pIoCtx, cbToRead);
+ }
+ else
+ {
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ AssertMsg(vdIfIoIntIoCtxIsSynchronous(pImage->pIfIo, pIoCtx),
+ ("Async I/O is not supported for stream optimized VMDK's\n"));
+
+ uint32_t uSectorInGrain = uSectorExtentRel % pExtent->cSectorsPerGrain;
+ uSectorExtentAbs -= uSectorInGrain;
+ if (pExtent->uGrainSectorAbs != uSectorExtentAbs)
+ {
+ uint64_t uLBA = 0; /* gcc maybe uninitialized */
+ rc = vmdkFileInflateSync(pImage, pExtent,
+ VMDK_SECTOR2BYTE(uSectorExtentAbs),
+ pExtent->pvGrain,
+ VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain),
+ NULL, &uLBA, NULL);
+ if (RT_FAILURE(rc))
+ {
+ pExtent->uGrainSectorAbs = 0;
+ break;
+ }
+ pExtent->uGrainSectorAbs = uSectorExtentAbs;
+ pExtent->uGrain = uSectorExtentRel / pExtent->cSectorsPerGrain;
+ Assert(uLBA == uSectorExtentRel);
+ }
+ vdIfIoIntIoCtxCopyTo(pImage->pIfIo, pIoCtx,
+ (uint8_t *)pExtent->pvGrain
+ + VMDK_SECTOR2BYTE(uSectorInGrain),
+ cbToRead);
+ }
+ else
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uSectorExtentAbs),
+ pIoCtx, cbToRead);
+ }
+ break;
+ }
+ case VMDKETYPE_VMFS:
+ case VMDKETYPE_FLAT:
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uSectorExtentRel),
+ pIoCtx, cbToRead);
+ break;
+ case VMDKETYPE_ZERO:
+ {
+ size_t cbSet;
+
+ cbSet = vdIfIoIntIoCtxSet(pImage->pIfIo, pIoCtx, 0, cbToRead);
+ Assert(cbSet == cbToRead);
+ break;
+ }
+ }
+ if (pcbActuallyRead)
+ *pcbActuallyRead = cbToRead;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_VD_VMDK_INVALID_STATE;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnWrite */
+static DECLCALLBACK(int) vmdkWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToWrite % 512 == 0);
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToWrite, VERR_INVALID_PARAMETER);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ PVMDKEXTENT pExtent;
+ uint64_t uSectorExtentRel;
+ uint64_t uSectorExtentAbs;
+
+ /* No size check here, will do that later when the extent is located.
+ * There are sparse images out there which according to the spec are
+ * invalid, because the total size is not a multiple of the grain size.
+ * Also for sparse images which are stitched together in odd ways (not at
+ * grain boundaries, and with the nominal size not being a multiple of the
+ * grain size), this would prevent writing to the last grain. */
+
+ rc = vmdkFindExtent(pImage, VMDK_BYTE2SECTOR(uOffset),
+ &pExtent, &uSectorExtentRel);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pExtent->enmAccess != VMDKACCESS_READWRITE
+ && ( !(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ && !pImage->pExtents[0].uAppendPosition
+ && pExtent->enmAccess != VMDKACCESS_READONLY))
+ rc = VERR_VD_VMDK_INVALID_STATE;
+ else
+ {
+ /* Handle the write according to the current extent type. */
+ switch (pExtent->enmType)
+ {
+ case VMDKETYPE_HOSTED_SPARSE:
+ rc = vmdkGetSector(pImage, pIoCtx, pExtent, uSectorExtentRel, &uSectorExtentAbs);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED
+ && uSectorExtentRel < (uint64_t)pExtent->uLastGrainAccess * pExtent->cSectorsPerGrain)
+ rc = VERR_VD_VMDK_INVALID_WRITE;
+ else
+ {
+ /* Clip write range to at most the rest of the grain. */
+ cbToWrite = RT_MIN(cbToWrite,
+ VMDK_SECTOR2BYTE( pExtent->cSectorsPerGrain
+ - uSectorExtentRel % pExtent->cSectorsPerGrain));
+ if (uSectorExtentAbs == 0)
+ {
+ if (!(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ {
+ if (cbToWrite == VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain))
+ {
+ /* Full block write to a previously unallocated block.
+ * Check if the caller wants to avoid the automatic alloc. */
+ if (!(fWrite & VD_WRITE_NO_ALLOC))
+ {
+ /* Allocate GT and find out where to store the grain. */
+ rc = vmdkAllocGrain(pImage, pExtent, pIoCtx,
+ uSectorExtentRel, cbToWrite);
+ }
+ else
+ rc = VERR_VD_BLOCK_FREE;
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+ }
+ else
+ {
+ /* Clip write range to remain in this extent. */
+ cbToWrite = RT_MIN(cbToWrite,
+ VMDK_SECTOR2BYTE( pExtent->uSectorOffset
+ + pExtent->cNominalSectors - uSectorExtentRel));
+ *pcbPreRead = VMDK_SECTOR2BYTE(uSectorExtentRel % pExtent->cSectorsPerGrain);
+ *pcbPostRead = VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain) - cbToWrite - *pcbPreRead;
+ rc = VERR_VD_BLOCK_FREE;
+ }
+ }
+ else
+ rc = vmdkStreamAllocGrain(pImage, pExtent, uSectorExtentRel,
+ pIoCtx, cbToWrite);
+ }
+ else
+ {
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ /* A partial write to a streamOptimized image is simply
+ * invalid. It requires rewriting already compressed data
+ * which is somewhere between expensive and impossible. */
+ rc = VERR_VD_VMDK_INVALID_STATE;
+ pExtent->uGrainSectorAbs = 0;
+ AssertRC(rc);
+ }
+ else
+ {
+ Assert(!(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED));
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uSectorExtentAbs),
+ pIoCtx, cbToWrite, NULL, NULL);
+ }
+ }
+ }
+ }
+ break;
+ case VMDKETYPE_VMFS:
+ case VMDKETYPE_FLAT:
+ /* Clip write range to remain in this extent. */
+ cbToWrite = RT_MIN(cbToWrite, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uSectorExtentRel),
+ pIoCtx, cbToWrite, NULL, NULL);
+ break;
+ case VMDKETYPE_ZERO:
+ /* Clip write range to remain in this extent. */
+ cbToWrite = RT_MIN(cbToWrite, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
+ break;
+ }
+ }
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnFlush */
+static DECLCALLBACK(int) vmdkFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ return vmdkFlushImage(pImage, pIoCtx);
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) vmdkGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ return VMDK_IMAGE_VERSION;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) vmdkGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtrReturn(pImage, 0);
+
+ if (pImage->pFile != NULL)
+ {
+ uint64_t cbFile;
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pFile->pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb += cbFile;
+ }
+ for (unsigned i = 0; i < pImage->cExtents; i++)
+ {
+ if (pImage->pExtents[i].pFile != NULL)
+ {
+ uint64_t cbFile;
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pExtents[i].pFile->pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb += cbFile;
+ }
+ }
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) vmdkGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->PCHSGeometry.cCylinders)
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) vmdkSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (!(pImage->uOpenFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ {
+ rc = vmdkDescSetPCHSGeometry(pImage, pPCHSGeometry);
+ if (RT_SUCCESS(rc))
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) vmdkGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->LCHSGeometry.cCylinders)
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) vmdkSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n",
+ pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (!(pImage->uOpenFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ {
+ rc = vmdkDescSetLCHSGeometry(pImage, pLCHSGeometry);
+ if (RT_SUCCESS(rc))
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) vmdkQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PVMDKIMAGE pThis = (PVMDKIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) vmdkRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PVMDKIMAGE pThis = (PVMDKIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) vmdkGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uImageFlags));
+ return pImage->uImageFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) vmdkGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uOpenFlags));
+ return pImage->uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) vmdkSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p uOpenFlags=%#x\n", pBackendData, uOpenFlags));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pImage || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* StreamOptimized images need special treatment: reopen is prohibited. */
+ if (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)
+ {
+ if (pImage->uOpenFlags == uOpenFlags)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ vmdkFreeImage(pImage, false, true /*fFlush*/);
+ rc = vmdkOpenImage(pImage, uOpenFlags);
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+static DECLCALLBACK(int) vmdkGetComment(void *pBackendData, char *pszComment, size_t cbComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ char *pszCommentEncoded = NULL;
+ int rc = vmdkDescDDBGetStr(pImage, &pImage->Descriptor,
+ "ddb.comment", &pszCommentEncoded);
+ if (rc == VERR_VD_VMDK_VALUE_NOT_FOUND)
+ {
+ pszCommentEncoded = NULL;
+ rc = VINF_SUCCESS;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pszComment && pszCommentEncoded)
+ rc = vmdkDecodeString(pszCommentEncoded, pszComment, cbComment);
+ else if (pszComment)
+ *pszComment = '\0';
+
+ if (pszCommentEncoded)
+ RTMemTmpFree(pszCommentEncoded);
+ }
+
+ LogFlowFunc(("returns %Rrc comment='%s'\n", rc, pszComment));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+static DECLCALLBACK(int) vmdkSetComment(void *pBackendData, const char *pszComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (!(pImage->uOpenFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ rc = vmdkSetImageComment(pImage, pszComment);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+static DECLCALLBACK(int) vmdkGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = pImage->ImageUuid;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+static DECLCALLBACK(int) vmdkSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (!(pImage->uOpenFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ {
+ pImage->ImageUuid = *pUuid;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_IMAGE_UUID, pUuid);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: error storing image UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+static DECLCALLBACK(int) vmdkGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = pImage->ModificationUuid;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+static DECLCALLBACK(int) vmdkSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (!(pImage->uOpenFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ {
+ /* Only touch the modification uuid if it changed. */
+ if (RTUuidCompare(&pImage->ModificationUuid, pUuid))
+ {
+ pImage->ModificationUuid = *pUuid;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_MODIFICATION_UUID, pUuid);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error storing modification UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+static DECLCALLBACK(int) vmdkGetParentUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = pImage->ParentUuid;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+static DECLCALLBACK(int) vmdkSetParentUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (!(pImage->uOpenFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ {
+ pImage->ParentUuid = *pUuid;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_UUID, pUuid);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VMDK: error storing parent image UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+static DECLCALLBACK(int) vmdkGetParentModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = pImage->ParentModificationUuid;
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+static DECLCALLBACK(int) vmdkSetParentModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (!(pImage->uOpenFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ {
+ pImage->ParentModificationUuid = *pUuid;
+ rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+ VMDK_DDB_PARENT_MODIFICATION_UUID, pUuid);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: error storing parent image UUID in descriptor in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) vmdkDump(void *pBackendData)
+{
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pImage);
+ vdIfErrorMessage(pImage->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cbSector=%llu\n",
+ pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors,
+ pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors,
+ VMDK_BYTE2SECTOR(pImage->cbSize));
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidCreation={%RTuuid}\n", &pImage->ImageUuid);
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidModification={%RTuuid}\n", &pImage->ModificationUuid);
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidParent={%RTuuid}\n", &pImage->ParentUuid);
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidParentModification={%RTuuid}\n", &pImage->ParentModificationUuid);
+}
+
+
+/**
+ * Returns the size, in bytes, of the sparse extent overhead for
+ * the number of desired total sectors and based on the current
+ * sectors of the extent.
+ *
+ * @returns uint64_t size of new overhead in bytes.
+ * @param pExtent VMDK extent instance.
+ * @param cSectorsNew Number of desired total sectors.
+ */
+static uint64_t vmdkGetNewOverhead(PVMDKEXTENT pExtent, uint64_t cSectorsNew)
+{
+ uint64_t cNewDirEntries = cSectorsNew / pExtent->cSectorsPerGDE;
+ if (cSectorsNew % pExtent->cSectorsPerGDE)
+ cNewDirEntries++;
+
+ size_t cbNewGD = cNewDirEntries * sizeof(uint32_t);
+ uint64_t cbNewDirSize = RT_ALIGN_64(cbNewGD, 512);
+ uint64_t cbNewAllTablesSize = RT_ALIGN_64(cNewDirEntries * pExtent->cGTEntries * sizeof(uint32_t), 512);
+ uint64_t cbNewOverhead = RT_ALIGN_Z(RT_MAX(pExtent->uDescriptorSector
+ + pExtent->cDescriptorSectors, 1)
+ + cbNewDirSize + cbNewAllTablesSize, 512);
+ cbNewOverhead += cbNewDirSize + cbNewAllTablesSize;
+ cbNewOverhead = RT_ALIGN_64(cbNewOverhead,
+ VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+
+ return cbNewOverhead;
+}
+
+/**
+ * Internal: Replaces the size (in sectors) of an extent in the descriptor file.
+ *
+ * @returns VBox status code.
+ * @param pImage VMDK image instance.
+ * @param pExtent VMDK extent instance.
+ * @param uLine Line number of descriptor to change.
+ * @param cSectorsOld Existing number of sectors.
+ * @param cSectorsNew New number of sectors.
+ */
+static int vmdkReplaceExtentSize(PVMDKIMAGE pImage, PVMDKEXTENT pExtent, unsigned uLine, uint64_t cSectorsOld,
+ uint64_t cSectorsNew)
+{
+ char szOldExtentSectors[UINT64_MAX_BUFF_SIZE];
+ char szNewExtentSectors[UINT64_MAX_BUFF_SIZE];
+
+ ssize_t cbWritten = RTStrPrintf2(szOldExtentSectors, sizeof(szOldExtentSectors), "%llu", cSectorsOld);
+ if (cbWritten <= 0 || cbWritten > (ssize_t)sizeof(szOldExtentSectors))
+ return VERR_BUFFER_OVERFLOW;
+
+ cbWritten = RTStrPrintf2(szNewExtentSectors, sizeof(szNewExtentSectors), "%llu", cSectorsNew);
+ if (cbWritten <= 0 || cbWritten > (ssize_t)sizeof(szNewExtentSectors))
+ return VERR_BUFFER_OVERFLOW;
+
+ char *pszNewExtentLine = vmdkStrReplace(pImage->Descriptor.aLines[uLine],
+ szOldExtentSectors,
+ szNewExtentSectors);
+
+ if (RT_UNLIKELY(!pszNewExtentLine))
+ return VERR_INVALID_PARAMETER;
+
+ vmdkDescExtRemoveByLine(pImage, &pImage->Descriptor, uLine);
+ vmdkDescExtInsert(pImage, &pImage->Descriptor,
+ pExtent->enmAccess, cSectorsNew,
+ pExtent->enmType, pExtent->pszBasename, pExtent->uSectorOffset);
+
+ RTStrFree(pszNewExtentLine);
+ pszNewExtentLine = NULL;
+
+ pImage->Descriptor.fDirty = true;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Moves sectors down to make room for new overhead.
+ * Used for sparse extent resize.
+ *
+ * @returns VBox status code.
+ * @param pImage VMDK image instance.
+ * @param pExtent VMDK extent instance.
+ * @param cSectorsNew Number of sectors after resize.
+ */
+static int vmdkRelocateSectorsForSparseResize(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ uint64_t cSectorsNew)
+{
+ int rc = VINF_SUCCESS;
+
+ uint64_t cbNewOverhead = vmdkGetNewOverhead(pExtent, cSectorsNew);
+
+ uint64_t cNewOverheadSectors = VMDK_BYTE2SECTOR(cbNewOverhead);
+ uint64_t cOverheadSectorDiff = cNewOverheadSectors - pExtent->cOverheadSectors;
+
+ uint64_t cbFile = 0;
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pExtent->pFile->pStorage, &cbFile);
+
+ uint64_t uNewAppendPosition;
+
+ /* Calculate how many sectors need to be relocated. */
+ unsigned cSectorsReloc = cOverheadSectorDiff;
+ if (cbNewOverhead % VMDK_SECTOR_SIZE)
+ cSectorsReloc++;
+
+ if (cSectorsReloc < pExtent->cSectors)
+ uNewAppendPosition = RT_ALIGN_Z(cbFile + VMDK_SECTOR2BYTE(cOverheadSectorDiff), 512);
+ else
+ uNewAppendPosition = cbFile;
+
+ /*
+ * Get the blocks we need to relocate first, they are appended to the end
+ * of the image.
+ */
+ void *pvBuf = NULL, *pvZero = NULL;
+ do
+ {
+ /* Allocate data buffer. */
+ pvBuf = RTMemAllocZ(VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Allocate buffer for overwriting with zeroes. */
+ pvZero = RTMemAllocZ(VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ if (!pvZero)
+ {
+ RTMemFree(pvBuf);
+ pvBuf = NULL;
+
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ uint32_t *aGTDataTmp = (uint32_t *)RTMemAllocZ(sizeof(uint32_t) * pExtent->cGTEntries);
+ if(!aGTDataTmp)
+ {
+ RTMemFree(pvBuf);
+ pvBuf = NULL;
+
+ RTMemFree(pvZero);
+ pvZero = NULL;
+
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ uint32_t *aRGTDataTmp = (uint32_t *)RTMemAllocZ(sizeof(uint32_t) * pExtent->cGTEntries);
+ if(!aRGTDataTmp)
+ {
+ RTMemFree(pvBuf);
+ pvBuf = NULL;
+
+ RTMemFree(pvZero);
+ pvZero = NULL;
+
+ RTMemFree(aGTDataTmp);
+ aGTDataTmp = NULL;
+
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Search for overlap sector in the grain table. */
+ for (uint32_t idxGD = 0; idxGD < pExtent->cGDEntries; idxGD++)
+ {
+ uint64_t uGTSector = pExtent->pGD[idxGD];
+ uint64_t uRGTSector = pExtent->pRGD[idxGD];
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uGTSector),
+ aGTDataTmp, sizeof(uint32_t) * pExtent->cGTEntries);
+
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uRGTSector),
+ aRGTDataTmp, sizeof(uint32_t) * pExtent->cGTEntries);
+
+ if (RT_FAILURE(rc))
+ break;
+
+ for (uint32_t idxGT = 0; idxGT < pExtent->cGTEntries; idxGT++)
+ {
+ uint64_t aGTEntryLE = RT_LE2H_U64(aGTDataTmp[idxGT]);
+ uint64_t aRGTEntryLE = RT_LE2H_U64(aRGTDataTmp[idxGT]);
+
+ /**
+ * Check if grain table is valid. If not dump out with an error.
+ * Shoudln't ever get here (given other checks) but good sanity check.
+ */
+ if (aGTEntryLE != aRGTEntryLE)
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: inconsistent references within grain table in '%s'"), pExtent->pszFullname);
+ break;
+ }
+
+ if (aGTEntryLE < cNewOverheadSectors
+ && aGTEntryLE != 0)
+ {
+ /* Read data and append grain to the end of the image. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(aGTEntryLE), pvBuf,
+ VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ uNewAppendPosition, pvBuf,
+ VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Zero out the old block area. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(aGTEntryLE), pvZero,
+ VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Write updated grain tables to file */
+ aGTDataTmp[idxGT] = VMDK_BYTE2SECTOR(uNewAppendPosition);
+ aRGTDataTmp[idxGT] = VMDK_BYTE2SECTOR(uNewAppendPosition);
+
+ if (memcmp(aGTDataTmp, aRGTDataTmp, sizeof(uint32_t) * pExtent->cGTEntries))
+ {
+ rc = vdIfError(pImage->pIfError, VERR_VD_VMDK_INVALID_HEADER, RT_SRC_POS,
+ N_("VMDK: inconsistency between grain table and backup grain table in '%s'"), pExtent->pszFullname);
+ break;
+ }
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uGTSector),
+ aGTDataTmp, sizeof(uint32_t) * pExtent->cGTEntries);
+
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uRGTSector),
+ aRGTDataTmp, sizeof(uint32_t) * pExtent->cGTEntries);
+
+ break;
+ }
+ }
+ }
+
+ RTMemFree(aGTDataTmp);
+ aGTDataTmp = NULL;
+
+ RTMemFree(aRGTDataTmp);
+ aRGTDataTmp = NULL;
+
+ if (RT_FAILURE(rc))
+ break;
+
+ uNewAppendPosition += VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain);
+ } while (0);
+
+ if (pvBuf)
+ {
+ RTMemFree(pvBuf);
+ pvBuf = NULL;
+ }
+
+ if (pvZero)
+ {
+ RTMemFree(pvZero);
+ pvZero = NULL;
+ }
+
+ // Update append position for extent
+ pExtent->uAppendPosition = uNewAppendPosition;
+
+ return rc;
+}
+
+/**
+ * Resizes meta/overhead for sparse extent resize.
+ *
+ * @returns VBox status code.
+ * @param pImage VMDK image instance.
+ * @param pExtent VMDK extent instance.
+ * @param cSectorsNew Number of sectors after resize.
+ */
+static int vmdkResizeSparseMeta(PVMDKIMAGE pImage, PVMDKEXTENT pExtent,
+ uint64_t cSectorsNew)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cOldGDEntries = pExtent->cGDEntries;
+
+ uint64_t cNewDirEntries = cSectorsNew / pExtent->cSectorsPerGDE;
+ if (cSectorsNew % pExtent->cSectorsPerGDE)
+ cNewDirEntries++;
+
+ size_t cbNewGD = cNewDirEntries * sizeof(uint32_t);
+
+ uint64_t cbNewDirSize = RT_ALIGN_64(cbNewGD, 512);
+ uint64_t cbCurrDirSize = RT_ALIGN_64(pExtent->cGDEntries * VMDK_GRAIN_DIR_ENTRY_SIZE, 512);
+ uint64_t cDirSectorDiff = VMDK_BYTE2SECTOR(cbNewDirSize - cbCurrDirSize);
+
+ uint64_t cbNewAllTablesSize = RT_ALIGN_64(cNewDirEntries * pExtent->cGTEntries * sizeof(uint32_t), 512);
+ uint64_t cbCurrAllTablesSize = RT_ALIGN_64(pExtent->cGDEntries * VMDK_GRAIN_TABLE_SIZE, 512);
+ uint64_t cTableSectorDiff = VMDK_BYTE2SECTOR(cbNewAllTablesSize - cbCurrAllTablesSize);
+
+ uint64_t cbNewOverhead = vmdkGetNewOverhead(pExtent, cSectorsNew);
+ uint64_t cNewOverheadSectors = VMDK_BYTE2SECTOR(cbNewOverhead);
+ uint64_t cOverheadSectorDiff = cNewOverheadSectors - pExtent->cOverheadSectors;
+
+ /*
+ * Get the blocks we need to relocate first, they are appended to the end
+ * of the image.
+ */
+ void *pvBuf = NULL, *pvZero = NULL;
+
+ do
+ {
+ /* Allocate data buffer. */
+ pvBuf = RTMemAllocZ(VMDK_GRAIN_TABLE_SIZE);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Allocate buffer for overwriting with zeroes. */
+ pvZero = RTMemAllocZ(VMDK_GRAIN_TABLE_SIZE);
+ if (!pvZero)
+ {
+ RTMemFree(pvBuf);
+ pvBuf = NULL;
+
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ uint32_t uGTStart = VMDK_SECTOR2BYTE(pExtent->uSectorGD) + (cOldGDEntries * VMDK_GRAIN_DIR_ENTRY_SIZE);
+
+ // points to last element in the grain table
+ uint32_t uGTTail = uGTStart + (pExtent->cGDEntries * VMDK_GRAIN_TABLE_SIZE) - VMDK_GRAIN_TABLE_SIZE;
+ uint32_t cbGTOff = RT_ALIGN_Z(VMDK_SECTOR2BYTE(cDirSectorDiff + cTableSectorDiff + cDirSectorDiff), 512);
+
+ for (int i = pExtent->cGDEntries - 1; i >= 0; i--)
+ {
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ uGTTail, pvBuf,
+ VMDK_GRAIN_TABLE_SIZE);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ RT_ALIGN_Z(uGTTail + cbGTOff, 512), pvBuf,
+ VMDK_GRAIN_TABLE_SIZE);
+ if (RT_FAILURE(rc))
+ break;
+
+ // This overshoots when i == 0, but we don't need it anymore.
+ uGTTail -= VMDK_GRAIN_TABLE_SIZE;
+ }
+
+
+ /* Find the end of the grain directory and start bumping everything down. Update locations of GT entries. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorGD), pvBuf,
+ pExtent->cGDEntries * VMDK_GRAIN_DIR_ENTRY_SIZE);
+ if (RT_FAILURE(rc))
+ break;
+
+ int * tmpBuf = (int *)pvBuf;
+
+ for (uint32_t i = 0; i < pExtent->cGDEntries; i++)
+ {
+ tmpBuf[i] = tmpBuf[i] + VMDK_BYTE2SECTOR(cbGTOff);
+ pExtent->pGD[i] = pExtent->pGD[i] + VMDK_BYTE2SECTOR(cbGTOff);
+ }
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ RT_ALIGN_Z(VMDK_SECTOR2BYTE(pExtent->uSectorGD + cTableSectorDiff + cDirSectorDiff), 512), pvBuf,
+ pExtent->cGDEntries * VMDK_GRAIN_DIR_ENTRY_SIZE);
+ if (RT_FAILURE(rc))
+ break;
+
+ pExtent->uSectorGD = pExtent->uSectorGD + cDirSectorDiff + cTableSectorDiff;
+
+ /* Repeat both steps with the redundant grain table/directory. */
+
+ uint32_t uRGTStart = VMDK_SECTOR2BYTE(pExtent->uSectorRGD) + (cOldGDEntries * VMDK_GRAIN_DIR_ENTRY_SIZE);
+
+ // points to last element in the grain table
+ uint32_t uRGTTail = uRGTStart + (pExtent->cGDEntries * VMDK_GRAIN_TABLE_SIZE) - VMDK_GRAIN_TABLE_SIZE;
+ uint32_t cbRGTOff = RT_ALIGN_Z(VMDK_SECTOR2BYTE(cDirSectorDiff), 512);
+
+ for (int i = pExtent->cGDEntries - 1; i >= 0; i--)
+ {
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ uRGTTail, pvBuf,
+ VMDK_GRAIN_TABLE_SIZE);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ RT_ALIGN_Z(uRGTTail + cbRGTOff, 512), pvBuf,
+ VMDK_GRAIN_TABLE_SIZE);
+ if (RT_FAILURE(rc))
+ break;
+
+ // This overshoots when i == 0, but we don't need it anymore.
+ uRGTTail -= VMDK_GRAIN_TABLE_SIZE;
+ }
+
+ /* Update locations of GT entries. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorRGD), pvBuf,
+ pExtent->cGDEntries * VMDK_GRAIN_DIR_ENTRY_SIZE);
+ if (RT_FAILURE(rc))
+ break;
+
+ tmpBuf = (int *)pvBuf;
+
+ for (uint32_t i = 0; i < pExtent->cGDEntries; i++)
+ {
+ tmpBuf[i] = tmpBuf[i] + cDirSectorDiff;
+ pExtent->pRGD[i] = pExtent->pRGD[i] + cDirSectorDiff;
+ }
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorRGD), pvBuf,
+ pExtent->cGDEntries * VMDK_GRAIN_DIR_ENTRY_SIZE);
+ if (RT_FAILURE(rc))
+ break;
+
+ pExtent->uSectorRGD = pExtent->uSectorRGD;
+ pExtent->cOverheadSectors += cOverheadSectorDiff;
+
+ } while (0);
+
+ if (pvBuf)
+ {
+ RTMemFree(pvBuf);
+ pvBuf = NULL;
+ }
+
+ if (pvZero)
+ {
+ RTMemFree(pvZero);
+ pvZero = NULL;
+ }
+
+ pExtent->cGDEntries = cNewDirEntries;
+
+ /* Allocate buffer for overwriting with zeroes. */
+ pvZero = RTMemAllocZ(VMDK_GRAIN_TABLE_SIZE);
+ if (!pvZero)
+ return VERR_NO_MEMORY;
+
+ // Allocate additional grain dir
+ pExtent->pGD = (uint32_t *) RTMemReallocZ(pExtent->pGD, pExtent->cGDEntries * sizeof(uint32_t), cbNewGD);
+ if (RT_LIKELY(pExtent->pGD))
+ {
+ if (pExtent->uSectorRGD)
+ {
+ pExtent->pRGD = (uint32_t *)RTMemReallocZ(pExtent->pRGD, pExtent->cGDEntries * sizeof(uint32_t), cbNewGD);
+ if (RT_UNLIKELY(!pExtent->pRGD))
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ return VERR_NO_MEMORY;
+
+
+ uint32_t uTmpDirVal = pExtent->pGD[cOldGDEntries - 1] + VMDK_GRAIN_DIR_ENTRY_SIZE;
+ for (uint32_t i = cOldGDEntries; i < pExtent->cGDEntries; i++)
+ {
+ pExtent->pGD[i] = uTmpDirVal;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uTmpDirVal), pvZero,
+ VMDK_GRAIN_TABLE_SIZE);
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uTmpDirVal += VMDK_GRAIN_DIR_ENTRY_SIZE;
+ }
+
+ uint32_t uRTmpDirVal = pExtent->pRGD[cOldGDEntries - 1] + VMDK_GRAIN_DIR_ENTRY_SIZE;
+ for (uint32_t i = cOldGDEntries; i < pExtent->cGDEntries; i++)
+ {
+ pExtent->pRGD[i] = uRTmpDirVal;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(uRTmpDirVal), pvZero,
+ VMDK_GRAIN_TABLE_SIZE);
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uRTmpDirVal += VMDK_GRAIN_DIR_ENTRY_SIZE;
+ }
+
+ RTMemFree(pvZero);
+ pvZero = NULL;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorGD), pExtent->pGD,
+ pExtent->cGDEntries * VMDK_GRAIN_DIR_ENTRY_SIZE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(pExtent->uSectorRGD), pExtent->pRGD,
+ pExtent->cGDEntries * VMDK_GRAIN_DIR_ENTRY_SIZE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = vmdkReplaceExtentSize(pImage, pExtent, pImage->Descriptor.uFirstExtent + pExtent->uExtent,
+ pExtent->cNominalSectors, cSectorsNew);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnResize */
+static DECLCALLBACK(int) vmdkResize(void *pBackendData, uint64_t cbSize,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation)
+{
+ RT_NOREF5(uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation);
+
+ // Establish variables and objects needed
+ int rc = VINF_SUCCESS;
+ PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+ unsigned uImageFlags = pImage->uImageFlags;
+ PVMDKEXTENT pExtent = &pImage->pExtents[0];
+ pExtent->fMetaDirty = true;
+
+ uint64_t cSectorsNew = cbSize / VMDK_SECTOR_SIZE; /** < New number of sectors in the image after the resize */
+ if (cbSize % VMDK_SECTOR_SIZE)
+ cSectorsNew++;
+
+ uint64_t cSectorsOld = pImage->cbSize / VMDK_SECTOR_SIZE; /** < Number of sectors before the resize. Only for FLAT images. */
+ if (pImage->cbSize % VMDK_SECTOR_SIZE)
+ cSectorsOld++;
+ unsigned cExtents = pImage->cExtents;
+
+ /* Check size is within min/max bounds. */
+ if ( !(uImageFlags & VD_VMDK_IMAGE_FLAGS_RAWDISK)
+ && ( !cbSize
+ || (!(uImageFlags & VD_IMAGE_FLAGS_FIXED) && cbSize >= _1T * 256 - _64K)) )
+ return VERR_VD_INVALID_SIZE;
+
+ /*
+ * Making the image smaller is not supported at the moment.
+ */
+ /** @todo implement making the image smaller, it is the responsibility of
+ * the user to know what they're doing. */
+ if (cbSize < pImage->cbSize)
+ rc = VERR_VD_SHRINK_NOT_SUPPORTED;
+ else if (cbSize > pImage->cbSize)
+ {
+ /**
+ * monolithicFlat. FIXED flag and not split up into 2 GB parts.
+ */
+ if ((uImageFlags & VD_IMAGE_FLAGS_FIXED) && !(uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G))
+ {
+ /** Required space in bytes for the extent after the resize. */
+ uint64_t cbSectorSpaceNew = cSectorsNew * VMDK_SECTOR_SIZE;
+ pExtent = &pImage->pExtents[0];
+
+ rc = vdIfIoIntFileSetAllocationSize(pImage->pIfIo, pExtent->pFile->pStorage, cbSectorSpaceNew,
+ 0 /* fFlags */, NULL,
+ uPercentStart, uPercentSpan);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not set size of new file '%s'"), pExtent->pszFullname);
+
+ rc = vmdkReplaceExtentSize(pImage, pExtent, pImage->Descriptor.uFirstExtent, cSectorsOld, cSectorsNew);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /**
+ * twoGbMaxExtentFlat. FIXED flag and SPLIT into 2 GB parts.
+ */
+ if ((uImageFlags & VD_IMAGE_FLAGS_FIXED) && (uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G))
+ {
+ /* Check to see how much space remains in last extent */
+ bool fSpaceAvailible = false;
+ uint64_t cLastExtentRemSectors = cSectorsOld % VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE);
+ if (cLastExtentRemSectors)
+ fSpaceAvailible = true;
+
+ uint64_t cSectorsNeeded = cSectorsNew - cSectorsOld;
+
+ /** Space remaining in current last extent file that we don't need to create another one. */
+ if (fSpaceAvailible && cSectorsNeeded + cLastExtentRemSectors <= VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE))
+ {
+ pExtent = &pImage->pExtents[cExtents - 1];
+ rc = vdIfIoIntFileSetAllocationSize(pImage->pIfIo, pExtent->pFile->pStorage,
+ VMDK_SECTOR2BYTE(cSectorsNeeded + cLastExtentRemSectors),
+ 0 /* fFlags */, NULL, uPercentStart, uPercentSpan);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not set size of new file '%s'"), pExtent->pszFullname);
+
+ rc = vmdkReplaceExtentSize(pImage, pExtent, pImage->Descriptor.uFirstExtent + cExtents - 1,
+ pExtent->cNominalSectors, cSectorsNeeded + cLastExtentRemSectors);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ //** Need more extent files to handle all the requested space. */
+ else
+ {
+ if (fSpaceAvailible)
+ {
+ pExtent = &pImage->pExtents[cExtents - 1];
+ rc = vdIfIoIntFileSetAllocationSize(pImage->pIfIo, pExtent->pFile->pStorage, VMDK_2G_SPLIT_SIZE,
+ 0 /* fFlags */, NULL,
+ uPercentStart, uPercentSpan);
+ if (RT_FAILURE(rc))
+ return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VMDK: could not set size of new file '%s'"), pExtent->pszFullname);
+
+ cSectorsNeeded = cSectorsNeeded - VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE) + cLastExtentRemSectors;
+
+ rc = vmdkReplaceExtentSize(pImage, pExtent, pImage->Descriptor.uFirstExtent + cExtents - 1,
+ pExtent->cNominalSectors, VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE));
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ unsigned cNewExtents = VMDK_SECTOR2BYTE(cSectorsNeeded) / VMDK_2G_SPLIT_SIZE;
+ if (cNewExtents % VMDK_2G_SPLIT_SIZE || cNewExtents < VMDK_2G_SPLIT_SIZE)
+ cNewExtents++;
+
+ for (unsigned i = cExtents;
+ i < cExtents + cNewExtents && cSectorsNeeded >= VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE);
+ i++)
+ {
+ rc = vmdkAddFileBackedExtent(pImage, VMDK_2G_SPLIT_SIZE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pExtent = &pImage->pExtents[i];
+
+ pExtent->cSectors = VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE);
+ cSectorsNeeded -= VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE);
+ }
+
+ if (cSectorsNeeded)
+ {
+ rc = vmdkAddFileBackedExtent(pImage, VMDK_SECTOR2BYTE(cSectorsNeeded));
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ }
+
+ /**
+ * monolithicSparse.
+ */
+ if (pExtent->enmType == VMDKETYPE_HOSTED_SPARSE && !(uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G))
+ {
+ // 1. Calculate sectors needed for new overhead.
+
+ uint64_t cbNewOverhead = vmdkGetNewOverhead(pExtent, cSectorsNew);
+ uint64_t cNewOverheadSectors = VMDK_BYTE2SECTOR(cbNewOverhead);
+ uint64_t cOverheadSectorDiff = cNewOverheadSectors - pExtent->cOverheadSectors;
+
+ // 2. Relocate sectors to make room for new GD/GT, update entries in GD/GT
+ if (cOverheadSectorDiff > 0)
+ {
+ if (pExtent->cSectors > 0)
+ {
+ /* Do the relocation. */
+ LogFlow(("Relocating VMDK sectors\n"));
+ rc = vmdkRelocateSectorsForSparseResize(pImage, pExtent, cSectorsNew);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = vmdkFlushImage(pImage, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ rc = vmdkResizeSparseMeta(pImage, pExtent, cSectorsNew);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+
+ /**
+ * twoGbSparseExtent
+ */
+ if (pExtent->enmType == VMDKETYPE_HOSTED_SPARSE && (uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G))
+ {
+ /* Check to see how much space remains in last extent */
+ bool fSpaceAvailible = false;
+ uint64_t cLastExtentRemSectors = cSectorsOld % VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE);
+ if (cLastExtentRemSectors)
+ fSpaceAvailible = true;
+
+ uint64_t cSectorsNeeded = cSectorsNew - cSectorsOld;
+
+ if (fSpaceAvailible && cSectorsNeeded + cLastExtentRemSectors <= VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE))
+ {
+ pExtent = &pImage->pExtents[cExtents - 1];
+ rc = vmdkRelocateSectorsForSparseResize(pImage, pExtent, cSectorsNeeded + cLastExtentRemSectors);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = vmdkFlushImage(pImage, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = vmdkResizeSparseMeta(pImage, pExtent, cSectorsNeeded + cLastExtentRemSectors);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ if (fSpaceAvailible)
+ {
+ pExtent = &pImage->pExtents[cExtents - 1];
+ rc = vmdkRelocateSectorsForSparseResize(pImage, pExtent, VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = vmdkFlushImage(pImage, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = vmdkResizeSparseMeta(pImage, pExtent, VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ cSectorsNeeded = cSectorsNeeded - VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE) + cLastExtentRemSectors;
+ }
+
+ unsigned cNewExtents = VMDK_SECTOR2BYTE(cSectorsNeeded) / VMDK_2G_SPLIT_SIZE;
+ if (cNewExtents % VMDK_2G_SPLIT_SIZE || cNewExtents < VMDK_2G_SPLIT_SIZE)
+ cNewExtents++;
+
+ for (unsigned i = cExtents;
+ i < cExtents + cNewExtents && cSectorsNeeded >= VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE);
+ i++)
+ {
+ rc = vmdkAddFileBackedExtent(pImage, VMDK_2G_SPLIT_SIZE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pExtent = &pImage->pExtents[i];
+
+ rc = vmdkFlushImage(pImage, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pExtent->cSectors = VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE);
+ cSectorsNeeded -= VMDK_BYTE2SECTOR(VMDK_2G_SPLIT_SIZE);
+ }
+
+ if (cSectorsNeeded)
+ {
+ rc = vmdkAddFileBackedExtent(pImage, VMDK_SECTOR2BYTE(cSectorsNeeded));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pExtent = &pImage->pExtents[pImage->cExtents];
+
+ rc = vmdkFlushImage(pImage, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ }
+
+ /* Successful resize. Update metadata */
+ if (RT_SUCCESS(rc))
+ {
+ /* Update size and new block count. */
+ pImage->cbSize = cbSize;
+ pExtent->cNominalSectors = cSectorsNew;
+ pExtent->cSectors = cSectorsNew;
+
+ /* Update geometry. */
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ }
+
+ /* Update header information in base image file. */
+ pImage->Descriptor.fDirty = true;
+ rc = vmdkWriteDescriptor(pImage, NULL);
+
+ if (RT_SUCCESS(rc))
+ rc = vmdkFlushImage(pImage, NULL);
+ }
+ /* Same size doesn't change the image at all. */
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+const VDIMAGEBACKEND g_VmdkBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "VMDK",
+ /* uBackendCaps */
+ VD_CAP_UUID | VD_CAP_CREATE_FIXED | VD_CAP_CREATE_DYNAMIC
+ | VD_CAP_CREATE_SPLIT_2G | VD_CAP_DIFF | VD_CAP_FILE | VD_CAP_ASYNC
+ | VD_CAP_VFS | VD_CAP_PREFERRED,
+ /* paFileExtensions */
+ s_aVmdkFileExtensions,
+ /* paConfigInfo */
+ s_aVmdkConfigInfo,
+ /* pfnProbe */
+ vmdkProbe,
+ /* pfnOpen */
+ vmdkOpen,
+ /* pfnCreate */
+ vmdkCreate,
+ /* pfnRename */
+ vmdkRename,
+ /* pfnClose */
+ vmdkClose,
+ /* pfnRead */
+ vmdkRead,
+ /* pfnWrite */
+ vmdkWrite,
+ /* pfnFlush */
+ vmdkFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ vmdkGetVersion,
+ /* pfnGetFileSize */
+ vmdkGetFileSize,
+ /* pfnGetPCHSGeometry */
+ vmdkGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ vmdkSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ vmdkGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ vmdkSetLCHSGeometry,
+ /* pfnQueryRegions */
+ vmdkQueryRegions,
+ /* pfnRegionListRelease */
+ vmdkRegionListRelease,
+ /* pfnGetImageFlags */
+ vmdkGetImageFlags,
+ /* pfnGetOpenFlags */
+ vmdkGetOpenFlags,
+ /* pfnSetOpenFlags */
+ vmdkSetOpenFlags,
+ /* pfnGetComment */
+ vmdkGetComment,
+ /* pfnSetComment */
+ vmdkSetComment,
+ /* pfnGetUuid */
+ vmdkGetUuid,
+ /* pfnSetUuid */
+ vmdkSetUuid,
+ /* pfnGetModificationUuid */
+ vmdkGetModificationUuid,
+ /* pfnSetModificationUuid */
+ vmdkSetModificationUuid,
+ /* pfnGetParentUuid */
+ vmdkGetParentUuid,
+ /* pfnSetParentUuid */
+ vmdkSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ vmdkGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ vmdkSetParentModificationUuid,
+ /* pfnDump */
+ vmdkDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ NULL,
+ /* pfnSetParentFilename */
+ NULL,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ vmdkResize,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};
diff --git a/src/VBox/Storage/testcase/BuiltinTests.h b/src/VBox/Storage/testcase/BuiltinTests.h
new file mode 100644
index 00000000..52ee4d31
--- /dev/null
+++ b/src/VBox/Storage/testcase/BuiltinTests.h
@@ -0,0 +1,57 @@
+/** @file
+ *
+ * tstVDIo testing utility - builtin tests.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_BuiltinTests_h
+#define VBOX_INCLUDED_SRC_testcase_BuiltinTests_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/**
+ * Builtin Tests (in generated BuiltinTests.cpp)
+ */
+typedef struct TSTVDIOTESTENTRY
+{
+ /** Test name. */
+ const char *pszName;
+ /** Pointer to the raw bytes. */
+ const unsigned char *pch;
+ /** Number of bytes. */
+ unsigned cb;
+} TSTVDIOTESTENTRY;
+/** Pointer to a trust anchor table entry. */
+typedef TSTVDIOTESTENTRY const *PCTSTVDIOTESTENTRY;
+
+/** Macro for simplifying generating the trust anchor tables. */
+#define TSTVDIOTESTENTRY_GEN(a_szName, a_abTest) { #a_szName, &a_abTest[0], sizeof(a_abTest) }
+
+/** All tests we know. */
+extern TSTVDIOTESTENTRY const g_aVDIoTests[];
+/** Number of entries in g_aVDIoTests. */
+extern unsigned const g_cVDIoTests;
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_BuiltinTests_h */
diff --git a/src/VBox/Storage/testcase/Makefile.kmk b/src/VBox/Storage/testcase/Makefile.kmk
new file mode 100644
index 00000000..418bc73d
--- /dev/null
+++ b/src/VBox/Storage/testcase/Makefile.kmk
@@ -0,0 +1,236 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the storage device & driver testcases.
+#
+
+#
+# Copyright (C) 2006-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Basic testcases for the VD code.
+#
+ifdef VBOX_WITH_TESTCASES
+ PROGRAMS += tstVD tstVD-2 tstVDSnap tstVDFill
+
+ tstVD_TEMPLATE = VBOXR3TSTEXE
+ tstVD_SOURCES = tstVD.cpp
+ tstVD_LIBS = $(LIB_DDU)
+
+ tstVD-2_TEMPLATE = VBOXR3TSTEXE
+ tstVD-2_SOURCES = tstVD-2.cpp
+ tstVD-2_LIBS = $(LIB_DDU)
+
+ tstVDFill_TEMPLATE = VBOXR3TSTEXE
+ tstVDFill_SOURCES = tstVDFill.cpp
+ tstVDFill_LIBS = $(LIB_DDU)
+
+ PROGRAMS += tstVDIo
+
+ #
+ # VD I/O test scripts to built in -> .cpp
+ #
+ TSTVDIO_BUILTIN_TESTS_FILE = $(tstVDIo_0_OUTDIR)/BuiltinTests.cpp
+ TSTVDIO_BUILTIN_TESTS := \
+ tstVDIo=tstVDIo.vd \
+ tstVDResize=tstVDResize.vd \
+ tstVDCompact=tstVDCompact.vd \
+ tstVDCopy=tstVDCopy.vd \
+ tstVDDiscard=tstVDDiscard.vd \
+ tstVDShareable=tstVDShareable.vd
+ TSTVDIO_BUILTIN_TEST_NAMES := $(foreach test,$(TSTVDIO_BUILTIN_TESTS),$(firstword $(subst =,$(SPACE) ,$(test))))
+ TSTVDIO_PATH_TESTS := $(PATH_SUB_CURRENT)
+
+ # 1=name, 2=filter
+ TSTVDIO_GEN_TEST_MACRO = 'TSTVDIOTESTENTRY const g_a$(1)[] =' '{' \
+ $(foreach testnm,$(filter $(2),$(TSTVDIO_BUILTIN_TEST_NAMES)), ' TSTVDIOTESTENTRY_GEN($(testnm), g_ab$(testnm)),') \
+ '};' 'unsigned const g_c$(1) = RT_ELEMENTS(g_a$(1));' '' ''
+
+ $$(TSTVDIO_BUILTIN_TESTS_FILE): $(MAKEFILE_CURRENT) \
+ $(foreach test,$(TSTVDIO_BUILTIN_TESTS),$(TSTVDIO_PATH_TESTS)/$(lastword $(subst =,$(SPACE) ,$(test)))) \
+ $(VBOX_BIN2C) \
+ | $$(dir $$@)
+ $(QUIET)$(RM) -f -- $@ $@.vd
+ $(QUIET)$(APPEND) -n "$@" \
+ '' \
+ '#include "BuiltinTests.h"' \
+ ''
+ $(foreach test,$(TSTVDIO_BUILTIN_TESTS), $(NLTAB)$(VBOX_BIN2C) -ascii --append --no-size \
+ "$(firstword $(subst =,$(SP) ,$(test)))" \
+ "$(TSTVDIO_PATH_TESTS)/$(lastword $(subst =,$(SP) ,$(test)))" \
+ "$@")
+
+# Generate test lists.
+ $(QUIET)$(APPEND) -n "$@" '' \
+ $(call TSTVDIO_GEN_TEST_MACRO,VDIoTests,%) \
+
+ tstVDIo_TEMPLATE = VBOXR3TSTEXE
+ tstVDIo_INCS := $(PATH_SUB_CURRENT)
+
+ ifdef VBOX_TSTVDIO_WITH_LOG_REPLAY
+ tstVDIo_DEFS += VBOX_TSTVDIO_WITH_LOG_REPLAY
+ endif
+
+ tstVDIo_SOURCES = \
+ tstVDIo.cpp \
+ VDIoBackend.cpp \
+ VDIoBackendMem.cpp \
+ VDMemDisk.cpp \
+ VDIoRnd.cpp \
+ VDScript.cpp \
+ VDScriptAst.cpp \
+ VDScriptChecker.cpp \
+ VDScriptInterp.cpp \
+ $(TSTVDIO_BUILTIN_TESTS_FILE)
+ tstVDIo_LIBS = \
+ $(LIB_DDU)
+ tstVDIo_CLEAN = \
+ $(TSTVDIO_BUILTIN_TESTS_FILE)
+ tstVDSnap_TEMPLATE = VBOXR3TSTEXE
+ tstVDSnap_LIBS = $(LIB_DDU)
+ tstVDSnap_SOURCES = tstVDSnap.cpp
+endif
+
+if defined(VBOX_WITH_TESTCASES) || defined(VBOX_WITH_VBOX_IMG)
+ PROGRAMS += vbox-img
+
+ #
+ # vbox-img - static because it might be used as a standalone tool.
+ #
+ ifneq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH), solaris.sparc32 solaris.sparc64)
+ vbox-img_TEMPLATE := VBOXR3EXE
+ vbox-img_DEFS := IN_VBOXDDU IN_VBOXDDU_STATIC VBOX_HDD_NO_DYNAMIC_BACKENDS
+ else
+ vbox-img_TEMPLATE := VBoxR3Static
+ vbox-img_DEFS := IN_VBOXDDU IN_VBOXDDU_STATIC VBOX_HDD_NO_DYNAMIC_BACKENDS
+ endif
+ vbox-img_INCS := \
+ ../../Main/include
+ vbox-img_SOURCES := \
+ vbox-img.cpp \
+ ../VD.cpp \
+ ../VDPlugin.cpp \
+ ../VDVfs.cpp \
+ ../VDI.cpp \
+ ../VMDK.cpp \
+ ../VHD.cpp \
+ ../DMG.cpp \
+ ../Parallels.cpp \
+ ../ISCSI.cpp \
+ ../RAW.cpp \
+ ../QED.cpp \
+ ../QCOW.cpp \
+ ../VHDX.cpp \
+ ../CUE.cpp \
+ ../VISO.cpp \
+ ../VCICache.cpp \
+ ../VDIfVfs.cpp
+ vbox-img_SOURCES.win = \
+ vbox-img.rc
+ ifeq ($(vbox-img_TEMPLATE),VBOXR3EXE)
+ vbox-img_LIBS = \
+ $(LIB_RUNTIME)
+ ifeq ($(KBUILD_TARGET),freebsd)
+ vbox-img_LIBS += geom bsdxml sbuf
+ else ifeq ($(KBUILD_TARGET),solaris)
+ vbox-img_LIBS += kstat efi
+ endif
+
+ else
+ vbox-img_LIBS = \
+ $(VBOX_LIB_RUNTIME_STATIC)
+ if1of ($(KBUILD_TARGET),os2 win)
+ vbox-img_LIBS += \
+ $(SDK_VBOX_LZF_STATIC_LIBS) \
+ $(SDK_VBOX_ZLIB_STATIC_LIBS)
+ else
+ vbox-img_LIBS += \
+ $(SDK_VBOX_LZF_LIBS) \
+ $(SDK_VBOX_ZLIB_LIBS)
+ endif
+ ifeq ($(KBUILD_TARGET),linux)
+ ifdef SDK_VBOX_LIBXML2_LIBS
+ vbox-img_LIBS += xml2
+ endif
+ else ifeq ($(KBUILD_TARGET),freebsd)
+ vbox-img_LIBS += iconv geom bsdxml sbuf
+ ifdef SDK_VBOX_LIBXML2_LIBS
+ vbox-img_LIBS += xml2 lzma
+ endif
+ else ifeq ($(KBUILD_TARGET),darwin)
+ vbox-img_LIBS += iconv
+ else ifeq ($(KBUILD_TARGET),win)
+ vbox-img_SDKS.win = VBOX_NTDLL
+ else ifeq ($(KBUILD_TARGET),solaris)
+ vbox-img_LIBS += kstat efi
+ ifdef SDK_VBOX_LIBXML2_LIBS
+ vbox-img_LIBS += xml2
+ endif
+ endif
+ endif
+
+endif
+
+if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_PLUGIN_CRYPT) \
+ && defined(VBOX_WITH_EXTPACK_PUEL) && defined(VBOX_WITH_EXTPACK_PUEL_BUILD) \
+ && defined(VBOX_WITH_VDKEYSTOREMGR)
+ PROGRAMS += vdkeystoremgr
+
+ #
+ # vdkeystoremgr - static because it might be used as a standalone tool.
+ #
+ vdkeystoremgr_TEMPLATE = VBoxR3Static
+ vdkeystoremgr_DEFS += IN_VBOXDDU IN_VBOXDDU_STATIC VBOX_HDD_NO_DYNAMIC_BACKENDS
+ vdkeystoremgr_SOURCES = \
+ vdkeystoremgr.cpp \
+ ../VDKeyStore.cpp
+ vdkeystoremgr_SOURCES.win = \
+ vdkeystoremgr_SOURCES.rc
+ vdkeystoremgr_LIBS = \
+ $(VBOX_LIB_RUNTIME_STATIC) \
+ $(PATH_STAGE_LIB)/SUPR3$(VBOX_SUFF_LIB)
+ if1of ($(KBUILD_TARGET),os2 win)
+ vdkeystoremgr_LIBS += \
+ $(SDK_VBOX_LZF_STATIC_LIBS) \
+ $(SDK_VBOX_ZLIB_STATIC_LIBS)
+ else
+ vdkeystoremgr_LIBS += \
+ $(SDK_VBOX_LZF_LIBS) \
+ $(SDK_VBOX_ZLIB_LIBS)
+ endif
+ ifeq ($(KBUILD_TARGET),linux)
+ ifdef SDK_VBOX_LIBXML2_LIBS
+ vdkeystoremgr_LIBS += xml2
+ endif
+ else if1of ($(KBUILD_TARGET),darwin freebsd)
+ vdkeystoremgr_LIBS += iconv
+ else ifeq ($(KBUILD_TARGET),win)
+ vdkeystoremgr_SDKS.win = VBOX_NTDLL
+ else ifeq ($(KBUILD_TARGET),solaris)
+ vdkeystoremgr_LIBS += kstat
+ endif
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Storage/testcase/VDDefs.h b/src/VBox/Storage/testcase/VDDefs.h
new file mode 100644
index 00000000..a781665d
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDDefs.h
@@ -0,0 +1,52 @@
+/** $Id: VDDefs.h $ */
+/** @file
+ *
+ * VBox HDD container test utility, common definitions.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDDefs_h
+#define VBOX_INCLUDED_SRC_testcase_VDDefs_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/sg.h>
+
+/**
+ * I/O transfer direction.
+ */
+typedef enum VDIOTXDIR
+{
+ /** Read. */
+ VDIOTXDIR_READ = 0,
+ /** Write. */
+ VDIOTXDIR_WRITE,
+ /** Flush. */
+ VDIOTXDIR_FLUSH,
+ /** Invalid. */
+ VDIOTXDIR_INVALID
+} VDIOTXDIR;
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDDefs_h */
diff --git a/src/VBox/Storage/testcase/VDIoBackend.cpp b/src/VBox/Storage/testcase/VDIoBackend.cpp
new file mode 100644
index 00000000..0cd1e9fb
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoBackend.cpp
@@ -0,0 +1,250 @@
+/* $Id: VDIoBackend.cpp $ */
+/** @file
+ * VBox HDD container test utility, I/O backend API
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#define LOGGROUP LOGGROUP_DEFAULT /** @todo Log group */
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+
+#include "VDIoBackend.h"
+#include "VDMemDisk.h"
+#include "VDIoBackendMem.h"
+
+typedef struct VDIOBACKEND
+{
+ /** Memory I/O backend handle. */
+ PVDIOBACKENDMEM pIoMem;
+ /** Users of the memory backend. */
+ volatile uint32_t cRefsIoMem;
+ /** Users of the file backend. */
+ volatile uint32_t cRefsFile;
+} VDIOBACKEND;
+
+typedef struct VDIOSTORAGE
+{
+ /** Pointer to the I/O backend parent. */
+ PVDIOBACKEND pIoBackend;
+ /** Completion callback. */
+ PFNVDIOCOMPLETE pfnComplete;
+ /** Flag whether this storage is backed by a file or memory.disk. */
+ bool fMemory;
+ /** Type dependent data. */
+ union
+ {
+ /** Memory disk handle. */
+ PVDMEMDISK pMemDisk;
+ /** file handle. */
+ RTFILE hFile;
+ } u;
+} VDIOSTORAGE;
+
+
+int VDIoBackendCreate(PPVDIOBACKEND ppIoBackend)
+{
+ int rc = VINF_SUCCESS;
+ PVDIOBACKEND pIoBackend;
+
+ pIoBackend = (PVDIOBACKEND)RTMemAllocZ(sizeof(VDIOBACKEND));
+ if (pIoBackend)
+ *ppIoBackend = pIoBackend;
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+void VDIoBackendDestroy(PVDIOBACKEND pIoBackend)
+{
+ if (pIoBackend->pIoMem)
+ VDIoBackendMemDestroy(pIoBackend->pIoMem);
+ RTMemFree(pIoBackend);
+}
+
+int VDIoBackendStorageCreate(PVDIOBACKEND pIoBackend, const char *pszBackend,
+ const char *pszName, PFNVDIOCOMPLETE pfnComplete,
+ PPVDIOSTORAGE ppIoStorage)
+{
+ int rc = VINF_SUCCESS;
+ PVDIOSTORAGE pIoStorage = (PVDIOSTORAGE)RTMemAllocZ(sizeof(VDIOSTORAGE));
+
+ if (pIoStorage)
+ {
+ pIoStorage->pIoBackend = pIoBackend;
+ pIoStorage->pfnComplete = pfnComplete;
+ if (!strcmp(pszBackend, "memory"))
+ {
+ pIoStorage->fMemory = true;
+ rc = VDMemDiskCreate(&pIoStorage->u.pMemDisk, 0 /* Growing */);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pIoBackend->cRefsIoMem);
+ if ( cRefs == 1
+ && !pIoBackend->pIoMem)
+ {
+ rc = VDIoBackendMemCreate(&pIoBackend->pIoMem);
+ if (RT_FAILURE(rc))
+ VDMemDiskDestroy(pIoStorage->u.pMemDisk);
+ }
+ }
+ }
+ else if (!strcmp(pszBackend, "file"))
+ {
+ /* Create file. */
+ rc = RTFileOpen(&pIoStorage->u.hFile, pszName,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_ASYNC_IO | RTFILE_O_NO_CACHE | RTFILE_O_DENY_NONE);
+
+ if (RT_FAILURE(rc))
+ ASMAtomicDecU32(&pIoBackend->cRefsFile);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pIoStorage);
+ else
+ *ppIoStorage = pIoStorage;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+void VDIoBackendStorageDestroy(PVDIOSTORAGE pIoStorage)
+{
+ if (pIoStorage->fMemory)
+ {
+ VDMemDiskDestroy(pIoStorage->u.pMemDisk);
+ ASMAtomicDecU32(&pIoStorage->pIoBackend->cRefsIoMem);
+ }
+ else
+ {
+ RTFileClose(pIoStorage->u.hFile);
+ ASMAtomicDecU32(&pIoStorage->pIoBackend->cRefsFile);
+ }
+ RTMemFree(pIoStorage);
+}
+
+int VDIoBackendTransfer(PVDIOSTORAGE pIoStorage, VDIOTXDIR enmTxDir, uint64_t off,
+ size_t cbTransfer, PRTSGBUF pSgBuf, void *pvUser, bool fSync)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pIoStorage->fMemory)
+ {
+ if (!fSync)
+ {
+ rc = VDIoBackendMemTransfer(pIoStorage->pIoBackend->pIoMem, pIoStorage->u.pMemDisk,
+ enmTxDir, off, cbTransfer, pSgBuf, pIoStorage->pfnComplete,
+ pvUser);
+ }
+ else
+ {
+ switch (enmTxDir)
+ {
+ case VDIOTXDIR_READ:
+ rc = VDMemDiskRead(pIoStorage->u.pMemDisk, off, cbTransfer, pSgBuf);
+ break;
+ case VDIOTXDIR_WRITE:
+ rc = VDMemDiskWrite(pIoStorage->u.pMemDisk, off, cbTransfer, pSgBuf);
+ break;
+ case VDIOTXDIR_FLUSH:
+ break;
+ default:
+ AssertMsgFailed(("Invalid transfer type %d\n", enmTxDir));
+ }
+ }
+ }
+ else
+ {
+ if (!fSync)
+ rc = VERR_NOT_IMPLEMENTED;
+ else
+ {
+ switch (enmTxDir)
+ {
+ case VDIOTXDIR_READ:
+ rc = RTFileSgReadAt(pIoStorage->u.hFile, off, pSgBuf, cbTransfer, NULL);
+ break;
+ case VDIOTXDIR_WRITE:
+ rc = RTFileSgWriteAt(pIoStorage->u.hFile, off, pSgBuf, cbTransfer, NULL);
+ break;
+ case VDIOTXDIR_FLUSH:
+ rc = RTFileFlush(pIoStorage->u.hFile);
+ break;
+ default:
+ AssertMsgFailed(("Invalid transfer type %d\n", enmTxDir));
+ }
+ }
+ }
+
+ return rc;
+}
+
+int VDIoBackendStorageSetSize(PVDIOSTORAGE pIoStorage, uint64_t cbSize)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pIoStorage->fMemory)
+ {
+ rc = VDMemDiskSetSize(pIoStorage->u.pMemDisk, cbSize);
+ }
+ else
+ rc = RTFileSetSize(pIoStorage->u.hFile, cbSize);
+
+ return rc;
+}
+
+int VDIoBackendStorageGetSize(PVDIOSTORAGE pIoStorage, uint64_t *pcbSize)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pIoStorage->fMemory)
+ {
+ rc = VDMemDiskGetSize(pIoStorage->u.pMemDisk, pcbSize);
+ }
+ else
+ rc = RTFileQuerySize(pIoStorage->u.hFile, pcbSize);
+
+ return rc;
+}
+
+DECLHIDDEN(int) VDIoBackendDumpToFile(PVDIOSTORAGE pIoStorage, const char *pszPath)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pIoStorage->fMemory)
+ rc = VDMemDiskWriteToFile(pIoStorage->u.pMemDisk, pszPath);
+ else
+ rc = VERR_NOT_IMPLEMENTED;
+
+ return rc;
+}
+
diff --git a/src/VBox/Storage/testcase/VDIoBackend.h b/src/VBox/Storage/testcase/VDIoBackend.h
new file mode 100644
index 00000000..bd832324
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoBackend.h
@@ -0,0 +1,106 @@
+/** $Id: VDIoBackend.h $ */
+/** @file
+ *
+ * VBox HDD container test utility, async I/O backend
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDIoBackend_h
+#define VBOX_INCLUDED_SRC_testcase_VDIoBackend_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/sg.h>
+
+#include "VDDefs.h"
+
+/** I/O backend handle. */
+typedef struct VDIOBACKEND *PVDIOBACKEND;
+/** Pointer to a I/O backend handle. */
+typedef PVDIOBACKEND *PPVDIOBACKEND;
+
+/** Storage handle. */
+typedef struct VDIOSTORAGE *PVDIOSTORAGE;
+/** Pointer to a storage handle. */
+typedef PVDIOSTORAGE *PPVDIOSTORAGE;
+
+/**
+ * Completion handler.
+ *
+ * @returns nothing.
+ * @param pvUser Opaque user data.
+ * @param rcReq Completion code for the request.
+ */
+typedef DECLCALLBACKTYPE(int, FNVDIOCOMPLETE,(void *pvUser, int rcReq));
+/** Pointer to a completion handler. */
+typedef FNVDIOCOMPLETE *PFNVDIOCOMPLETE;
+
+/**
+ * Creates a new memory I/O backend.
+ *
+ * @returns IPRT status code.
+ *
+ * @param ppIoBackend Where to store the handle on success.
+ */
+int VDIoBackendCreate(PPVDIOBACKEND ppIoBackend);
+
+/**
+ * Destroys a memory I/O backend.
+ *
+ * @returns nothing.
+ *
+ * @param pIoBackend The backend to destroy.
+ */
+void VDIoBackendDestroy(PVDIOBACKEND pIoBackend);
+
+int VDIoBackendStorageCreate(PVDIOBACKEND pIoBackend, const char *pszBackend,
+ const char *pszName, PFNVDIOCOMPLETE pfnComplete,
+ PPVDIOSTORAGE ppIoStorage);
+
+void VDIoBackendStorageDestroy(PVDIOSTORAGE pIoStorage);
+
+int VDIoBackendStorageSetSize(PVDIOSTORAGE pIoStorage, uint64_t cbSize);
+
+int VDIoBackendStorageGetSize(PVDIOSTORAGE pIoStorage, uint64_t *pcbSize);
+
+DECLHIDDEN(int) VDIoBackendDumpToFile(PVDIOSTORAGE pIoStorage, const char *pszPath);
+
+/**
+ * Enqueues a new I/O request.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pIoStorage Storage handle.
+ * @param enmTxDir The transfer direction.
+ * @param off Start offset of the transfer.
+ * @param cbTransfer Size of the transfer.
+ * @param pSgBuf S/G buffer to use.
+ * @param pvUser Opaque user data.
+ * @param fSync Flag whether to wait for the operation to complete.
+ */
+int VDIoBackendTransfer(PVDIOSTORAGE pIoStorage, VDIOTXDIR enmTxDir, uint64_t off,
+ size_t cbTransfer, PRTSGBUF pSgBuf, void *pvUser, bool fSync);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDIoBackend_h */
diff --git a/src/VBox/Storage/testcase/VDIoBackendMem.cpp b/src/VBox/Storage/testcase/VDIoBackendMem.cpp
new file mode 100644
index 00000000..125db13f
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoBackendMem.cpp
@@ -0,0 +1,263 @@
+/* $Id: VDIoBackendMem.cpp $ */
+/** @file
+ * VBox HDD container test utility, async I/O memory backend
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#define LOGGROUP LOGGROUP_DEFAULT /** @todo Log group */
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/thread.h>
+#include <iprt/circbuf.h>
+#include <iprt/semaphore.h>
+
+#include "VDMemDisk.h"
+#include "VDIoBackendMem.h"
+
+#define VDMEMIOBACKEND_REQS 1024
+
+/**
+ * Memory I/O request.
+ */
+typedef struct VDIOBACKENDREQ
+{
+ /** I/O request direction. */
+ VDIOTXDIR enmTxDir;
+ /** Memory disk handle. */
+ PVDMEMDISK pMemDisk;
+ /** Start offset. */
+ uint64_t off;
+ /** Size of the transfer. */
+ size_t cbTransfer;
+ /** Completion handler to call. */
+ PFNVDIOCOMPLETE pfnComplete;
+ /** Opaque user data. */
+ void *pvUser;
+ /** S/G buffer. */
+ RTSGBUF SgBuf;
+ /** Segment array - variable size. */
+ RTSGSEG aSegs[1];
+} VDIOBACKENDREQ, *PVDIOBACKENDREQ;
+
+typedef PVDIOBACKENDREQ *PPVDIOBACKENDREQ;
+
+/**
+ * I/O memory backend
+ */
+typedef struct VDIOBACKENDMEM
+{
+ /** Thread handle for the backend. */
+ RTTHREAD hThreadIo;
+ /** Circular buffer used for submitting requests. */
+ PRTCIRCBUF pRequestRing;
+ /** Size of the buffer in request items. */
+ unsigned cReqsRing;
+ /** Event semaphore the thread waits on for more work. */
+ RTSEMEVENT EventSem;
+ /** Flag whether the server should be still running. */
+ volatile bool fRunning;
+ /** Number of requests waiting in the request buffer. */
+ volatile uint32_t cReqsWaiting;
+} VDIOBACKENDMEM;
+
+static DECLCALLBACK(int) vdIoBackendMemThread(RTTHREAD hThread, void *pvUser);
+
+/**
+ * Pokes the I/O thread that something interesting happened.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pIoBackend The backend to poke.
+ */
+static int vdIoBackendMemThreadPoke(PVDIOBACKENDMEM pIoBackend)
+{
+ return RTSemEventSignal(pIoBackend->EventSem);
+}
+
+int VDIoBackendMemCreate(PPVDIOBACKENDMEM ppIoBackend)
+{
+ int rc = VINF_SUCCESS;
+ PVDIOBACKENDMEM pIoBackend = NULL;
+
+ pIoBackend = (PVDIOBACKENDMEM)RTMemAllocZ(sizeof(VDIOBACKENDMEM));
+ if (pIoBackend)
+ {
+ rc = RTCircBufCreate(&pIoBackend->pRequestRing, VDMEMIOBACKEND_REQS * sizeof(PVDIOBACKENDREQ));
+ if (RT_SUCCESS(rc))
+ {
+ pIoBackend->cReqsRing = VDMEMIOBACKEND_REQS * sizeof(VDIOBACKENDREQ);
+ pIoBackend->fRunning = true;
+
+ rc = RTSemEventCreate(&pIoBackend->EventSem);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadCreate(&pIoBackend->hThreadIo, vdIoBackendMemThread, pIoBackend, 0, RTTHREADTYPE_IO,
+ RTTHREADFLAGS_WAITABLE, "MemIo");
+ if (RT_SUCCESS(rc))
+ {
+ *ppIoBackend = pIoBackend;
+
+ LogFlowFunc(("returns success\n"));
+ return VINF_SUCCESS;
+ }
+ RTSemEventDestroy(pIoBackend->EventSem);
+ }
+
+ RTCircBufDestroy(pIoBackend->pRequestRing);
+ }
+
+ RTMemFree(pIoBackend);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+int VDIoBackendMemDestroy(PVDIOBACKENDMEM pIoBackend)
+{
+ ASMAtomicXchgBool(&pIoBackend->fRunning, false);
+ vdIoBackendMemThreadPoke(pIoBackend);
+
+ RTThreadWait(pIoBackend->hThreadIo, RT_INDEFINITE_WAIT, NULL);
+ RTSemEventDestroy(pIoBackend->EventSem);
+ RTCircBufDestroy(pIoBackend->pRequestRing);
+ RTMemFree(pIoBackend);
+
+ return VINF_SUCCESS;
+}
+
+int VDIoBackendMemTransfer(PVDIOBACKENDMEM pIoBackend, PVDMEMDISK pMemDisk,
+ VDIOTXDIR enmTxDir, uint64_t off, size_t cbTransfer,
+ PRTSGBUF pSgBuf, PFNVDIOCOMPLETE pfnComplete, void *pvUser)
+{
+ PVDIOBACKENDREQ pReq = NULL;
+ PPVDIOBACKENDREQ ppReq = NULL;
+ size_t cbData;
+ unsigned cSegs = 0;
+
+ LogFlowFunc(("Queuing request\n"));
+
+ if (enmTxDir != VDIOTXDIR_FLUSH)
+ RTSgBufSegArrayCreate(pSgBuf, NULL, &cSegs, cbTransfer);
+
+ pReq = (PVDIOBACKENDREQ)RTMemAlloc(RT_UOFFSETOF_DYN(VDIOBACKENDREQ, aSegs[cSegs]));
+ if (!pReq)
+ return VERR_NO_MEMORY;
+
+ RTCircBufAcquireWriteBlock(pIoBackend->pRequestRing, sizeof(PVDIOBACKENDREQ), (void **)&ppReq, &cbData);
+ if (!ppReq)
+ {
+ RTMemFree(pReq);
+ return VERR_NO_MEMORY;
+ }
+
+ Assert(cbData == sizeof(PVDIOBACKENDREQ));
+ pReq->enmTxDir = enmTxDir;
+ pReq->cbTransfer = cbTransfer;
+ pReq->off = off;
+ pReq->pMemDisk = pMemDisk;
+ pReq->pfnComplete = pfnComplete;
+ pReq->pvUser = pvUser;
+ if (enmTxDir != VDIOTXDIR_FLUSH)
+ {
+ RTSgBufSegArrayCreate(pSgBuf, &pReq->aSegs[0], &cSegs, cbTransfer);
+ RTSgBufInit(&pReq->SgBuf, pReq->aSegs, cSegs);
+ }
+
+ *ppReq = pReq;
+ RTCircBufReleaseWriteBlock(pIoBackend->pRequestRing, sizeof(PVDIOBACKENDREQ));
+ uint32_t cReqsWaiting = ASMAtomicIncU32(&pIoBackend->cReqsWaiting);
+ if (cReqsWaiting == 1)
+ vdIoBackendMemThreadPoke(pIoBackend);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * I/O thread for the memory backend.
+ *
+ * @returns IPRT status code.
+ *
+ * @param hThread The thread handle.
+ * @param pvUser Opaque user data.
+ */
+static DECLCALLBACK(int) vdIoBackendMemThread(RTTHREAD hThread, void *pvUser)
+{
+ PVDIOBACKENDMEM pIoBackend = (PVDIOBACKENDMEM)pvUser;
+ RT_NOREF1(hThread);
+
+ while (pIoBackend->fRunning)
+ {
+ int rc = RTSemEventWait(pIoBackend->EventSem, RT_INDEFINITE_WAIT);
+ if (RT_FAILURE(rc) || !pIoBackend->fRunning)
+ break;
+
+ PVDIOBACKENDREQ pReq;
+ PPVDIOBACKENDREQ ppReq;
+ size_t cbData;
+ uint32_t cReqsWaiting = ASMAtomicXchgU32(&pIoBackend->cReqsWaiting, 0);
+
+ while (cReqsWaiting)
+ {
+ int rcReq = VINF_SUCCESS;
+
+ /* Do we have another request? */
+ RTCircBufAcquireReadBlock(pIoBackend->pRequestRing, sizeof(PVDIOBACKENDREQ), (void **)&ppReq, &cbData);
+ Assert(!ppReq || cbData == sizeof(PVDIOBACKENDREQ));
+ RTCircBufReleaseReadBlock(pIoBackend->pRequestRing, cbData);
+
+ pReq = *ppReq;
+ cReqsWaiting--;
+
+ LogFlowFunc(("Processing request\n"));
+ switch (pReq->enmTxDir)
+ {
+ case VDIOTXDIR_READ:
+ {
+ rcReq = VDMemDiskRead(pReq->pMemDisk, pReq->off, pReq->cbTransfer, &pReq->SgBuf);
+ break;
+ }
+ case VDIOTXDIR_WRITE:
+ {
+ rcReq = VDMemDiskWrite(pReq->pMemDisk, pReq->off, pReq->cbTransfer, &pReq->SgBuf);
+ break;
+ }
+ case VDIOTXDIR_FLUSH:
+ break;
+ default:
+ AssertMsgFailed(("Invalid TX direction!\n"));
+ }
+
+ /* Notify completion. */
+ pReq->pfnComplete(pReq->pvUser, rcReq);
+ RTMemFree(pReq);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Storage/testcase/VDIoBackendMem.h b/src/VBox/Storage/testcase/VDIoBackendMem.h
new file mode 100644
index 00000000..663cacae
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoBackendMem.h
@@ -0,0 +1,92 @@
+/** $Id: VDIoBackendMem.h $ */
+/** @file
+ *
+ * VBox HDD container test utility, async I/O memory backend
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDIoBackendMem_h
+#define VBOX_INCLUDED_SRC_testcase_VDIoBackendMem_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/sg.h>
+
+#include "VDDefs.h"
+
+/** Memory backend handle. */
+typedef struct VDIOBACKENDMEM *PVDIOBACKENDMEM;
+/** Pointer to a memory backend handle. */
+typedef PVDIOBACKENDMEM *PPVDIOBACKENDMEM;
+
+/**
+ * Completion handler.
+ *
+ * @returns nothing.
+ * @param pvUser Opaque user data.
+ * @param rcReq Completion code for the request.
+ */
+typedef DECLCALLBACKTYPE(int, FNVDIOCOMPLETE,(void *pvUser, int rcReq));
+/** Pointer to a completion handler. */
+typedef FNVDIOCOMPLETE *PFNVDIOCOMPLETE;
+
+/**
+ * Creates a new memory I/O backend.
+ *
+ * @returns IPRT status code.
+ *
+ * @param ppIoBackend Where to store the handle on success.
+ */
+int VDIoBackendMemCreate(PPVDIOBACKENDMEM ppIoBackend);
+
+/**
+ * Destroys a memory I/O backend.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pIoBackend The backend to destroy.
+ */
+int VDIoBackendMemDestroy(PVDIOBACKENDMEM pIoBackend);
+
+/**
+ * Enqueues a new I/O request.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pIoBackend The backend which should handle the
+ * transfer.
+ * @param pMemDisk The memory disk the request is for.
+ * @param enmTxDir The transfer direction.
+ * @param off Start offset of the transfer.
+ * @param cbTransfer Size of the transfer.
+ * @param pSgBuf S/G buffer to use.
+ * @param pfnComplete Completion handler to call.
+ * @param pvUser Opaque user data.
+ */
+int VDIoBackendMemTransfer(PVDIOBACKENDMEM pIoBackend, PVDMEMDISK pMemDisk,
+ VDIOTXDIR enmTxDir, uint64_t off, size_t cbTransfer,
+ PRTSGBUF pSgBuf, PFNVDIOCOMPLETE pfnComplete, void *pvUser);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDIoBackendMem_h */
diff --git a/src/VBox/Storage/testcase/VDIoRnd.cpp b/src/VBox/Storage/testcase/VDIoRnd.cpp
new file mode 100644
index 00000000..610a213c
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoRnd.cpp
@@ -0,0 +1,118 @@
+/* $Id: VDIoRnd.cpp $ */
+/** @file
+ * VBox HDD container test utility - I/O data generator.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOGGROUP LOGGROUP_DEFAULT
+#include <iprt/log.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/assert.h>
+
+#include "VDIoRnd.h"
+
+/**
+ * I/O random data generator instance data.
+ */
+typedef struct VDIORND
+{
+ /** Pointer to the buffer holding the random data. */
+ uint8_t *pbPattern;
+ /** Size of the buffer. */
+ size_t cbPattern;
+ /** RNG */
+ RTRAND hRand;
+} VDIORND;
+
+int VDIoRndCreate(PPVDIORND ppIoRnd, size_t cbPattern, uint64_t uSeed)
+{
+ PVDIORND pIoRnd = NULL;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(ppIoRnd, VERR_INVALID_POINTER);
+
+ pIoRnd = (PVDIORND)RTMemAllocZ(sizeof(VDIORND));
+ if (pIoRnd)
+ pIoRnd->pbPattern = (uint8_t *)RTMemPageAllocZ(cbPattern);
+
+ if ( pIoRnd
+ && pIoRnd->pbPattern)
+ {
+ pIoRnd->cbPattern = cbPattern;
+
+ rc = RTRandAdvCreateParkMiller(&pIoRnd->hRand);
+ if (RT_SUCCESS(rc))
+ {
+ RTRandAdvSeed(pIoRnd->hRand, uSeed);
+ RTRandAdvBytes(pIoRnd->hRand, pIoRnd->pbPattern, cbPattern);
+ }
+ else
+ {
+ RTMemPageFree(pIoRnd->pbPattern, cbPattern);
+ RTMemFree(pIoRnd);
+ }
+ }
+ else
+ {
+ if (pIoRnd)
+ RTMemFree(pIoRnd);
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppIoRnd = pIoRnd;
+
+ return rc;
+}
+
+void VDIoRndDestroy(PVDIORND pIoRnd)
+{
+ AssertPtrReturnVoid(pIoRnd);
+
+ RTRandAdvDestroy(pIoRnd->hRand);
+ RTMemPageFree(pIoRnd->pbPattern, pIoRnd->cbPattern);
+ RTMemFree(pIoRnd);
+}
+
+int VDIoRndGetBuffer(PVDIORND pIoRnd, void **ppv, size_t cb)
+{
+ AssertPtrReturn(pIoRnd, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppv, VERR_INVALID_POINTER);
+ AssertReturn(cb > 0, VERR_INVALID_PARAMETER);
+
+ if (cb > pIoRnd->cbPattern - 512)
+ return VERR_INVALID_PARAMETER;
+
+ *ppv = pIoRnd->pbPattern + RT_ALIGN_64(RTRandAdvU64Ex(pIoRnd->hRand, 0, pIoRnd->cbPattern - cb - 512), 512);
+ return VINF_SUCCESS;
+}
+
+
+uint32_t VDIoRndGetU32Ex(PVDIORND pIoRnd, uint32_t uMin, uint32_t uMax)
+{
+ return RTRandAdvU32Ex(pIoRnd->hRand, uMin, uMax);
+}
+
diff --git a/src/VBox/Storage/testcase/VDIoRnd.h b/src/VBox/Storage/testcase/VDIoRnd.h
new file mode 100644
index 00000000..e95dab13
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoRnd.h
@@ -0,0 +1,69 @@
+/** @file
+ *
+ * VBox HDD container test utility - I/O data generator.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDIoRnd_h
+#define VBOX_INCLUDED_SRC_testcase_VDIoRnd_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** Pointer to the I/O random number generator. */
+typedef struct VDIORND *PVDIORND;
+/** Pointer to a I/O random number generator pointer. */
+typedef PVDIORND *PPVDIORND;
+
+/**
+ * Creates a I/O RNG.
+ *
+ * @returns VBox status code.
+ *
+ * @param ppIoRnd Where to store the handle on success.
+ * @param cbPattern Size of the test pattern to create.
+ * @param uSeed Seed for the RNG.
+ */
+int VDIoRndCreate(PPVDIORND ppIoRnd, size_t cbPattern, uint64_t uSeed);
+
+/**
+ * Destroys the I/O RNG.
+ *
+ * @param pIoRnd I/O RNG handle.
+ */
+void VDIoRndDestroy(PVDIORND pIoRnd);
+
+/**
+ * Returns a pointer filled with random data of the given size.
+ *
+ * @returns VBox status code.
+ *
+ * @param pIoRnd I/O RNG handle.
+ * @param ppv Where to store the pointer on success.
+ * @param cb Size of the buffer.
+ */
+int VDIoRndGetBuffer(PVDIORND pIoRnd, void **ppv, size_t cb);
+
+uint32_t VDIoRndGetU32Ex(PVDIORND pIoRnd, uint32_t uMin, uint32_t uMax);
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDIoRnd_h */
diff --git a/src/VBox/Storage/testcase/VDMemDisk.cpp b/src/VBox/Storage/testcase/VDMemDisk.cpp
new file mode 100644
index 00000000..fa153e95
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDMemDisk.cpp
@@ -0,0 +1,418 @@
+/* $Id: VDMemDisk.cpp $ */
+/** @file
+ * VBox HDD container test utility, memory disk/file.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#define LOGGROUP LOGGROUP_DEFAULT /** @todo Log group */
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/mem.h>
+#include <iprt/file.h>
+
+#include "VDMemDisk.h"
+
+/**
+ * Memory disk/file.
+ */
+typedef struct VDMEMDISK
+{
+ /** Current size of the disk. */
+ uint64_t cbDisk;
+ /** Flag whether the disk can grow. */
+ bool fGrowable;
+ /** Pointer to the AVL tree holding the segments. */
+ PAVLRU64TREE pTreeSegments;
+} VDMEMDISK;
+
+/**
+ * A disk segment.
+ */
+typedef struct VDMEMDISKSEG
+{
+ /** AVL tree core. */
+ AVLRU64NODECORE Core;
+ /** Pointer to the data. */
+ void *pvSeg;
+} VDMEMDISKSEG, *PVDMEMDISKSEG;
+
+
+int VDMemDiskCreate(PPVDMEMDISK ppMemDisk, uint64_t cbSize)
+{
+ AssertPtrReturn(ppMemDisk, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ PVDMEMDISK pMemDisk = (PVDMEMDISK)RTMemAllocZ(sizeof(VDMEMDISK));
+ if (pMemDisk)
+ {
+ pMemDisk->fGrowable = cbSize ? false : true;
+ pMemDisk->cbDisk = cbSize;
+ pMemDisk->pTreeSegments = (PAVLRU64TREE)RTMemAllocZ(sizeof(AVLRU64TREE));
+ if (pMemDisk->pTreeSegments)
+ *ppMemDisk = pMemDisk;
+ else
+ {
+ RTMemFree(pMemDisk);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdMemDiskDestroy(PAVLRU64NODECORE pNode, void *pvUser)
+{
+ RT_NOREF1(pvUser);
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)pNode;
+ RTMemFree(pSeg->pvSeg);
+ RTMemFree(pSeg);
+ return VINF_SUCCESS;
+}
+
+void VDMemDiskDestroy(PVDMEMDISK pMemDisk)
+{
+ AssertPtrReturnVoid(pMemDisk);
+
+ RTAvlrU64Destroy(pMemDisk->pTreeSegments, vdMemDiskDestroy, NULL);
+ RTMemFree(pMemDisk->pTreeSegments);
+ RTMemFree(pMemDisk);
+}
+
+int VDMemDiskWrite(PVDMEMDISK pMemDisk, uint64_t off, size_t cbWrite, PRTSGBUF pSgBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pMemDisk=%#p off=%llu cbWrite=%zu pSgBuf=%#p\n",
+ pMemDisk, off, cbWrite, pSgBuf));
+
+ AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSgBuf, VERR_INVALID_POINTER);
+
+ /* Check for a write beyond the end of a disk. */
+ if ( !pMemDisk->fGrowable
+ && (off + cbWrite) > pMemDisk->cbDisk)
+ return VERR_INVALID_PARAMETER;
+
+ /* Update the segments */
+ size_t cbLeft = cbWrite;
+ uint64_t offCurr = off;
+
+ while ( cbLeft
+ && RT_SUCCESS(rc))
+ {
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64RangeGet(pMemDisk->pTreeSegments, offCurr);
+ size_t cbRange = 0;
+ unsigned offSeg = 0;
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, offCurr, true);
+ if ( !pSeg
+ || offCurr + cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+
+ /* Create new segment */
+ pSeg = (PVDMEMDISKSEG)RTMemAllocZ(sizeof(VDMEMDISKSEG));
+ if (pSeg)
+ {
+ pSeg->Core.Key = offCurr;
+ pSeg->Core.KeyLast = offCurr + cbRange - 1;
+ pSeg->pvSeg = RTMemAllocZ(cbRange);
+
+ if (!pSeg->pvSeg)
+ {
+ RTMemFree(pSeg);
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ bool fInserted = RTAvlrU64Insert(pMemDisk->pTreeSegments, &pSeg->Core);
+ AssertMsg(fInserted, ("Bug!\n")); NOREF(fInserted);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ AssertPtr(pSeg);
+ size_t cbCopied = RTSgBufCopyToBuf(pSgBuf, (uint8_t *)pSeg->pvSeg + offSeg, cbRange);
+ Assert(cbCopied == cbRange); NOREF(cbCopied);
+ }
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ /* Update size of the disk. */
+ if ( RT_SUCCESS(rc)
+ && pMemDisk->fGrowable
+ && (off + cbWrite) > pMemDisk->cbDisk)
+ {
+ pMemDisk->cbDisk = off + cbWrite;
+ }
+
+ return rc;
+}
+
+
+int VDMemDiskRead(PVDMEMDISK pMemDisk, uint64_t off, size_t cbRead, PRTSGBUF pSgBuf)
+{
+ LogFlowFunc(("pMemDisk=%#p off=%llu cbRead=%zu pSgBuf=%#p\n",
+ pMemDisk, off, cbRead, pSgBuf));
+
+ AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSgBuf, VERR_INVALID_POINTER);
+
+ /* Check for a read beyond the end of a disk. */
+ if ((off + cbRead) > pMemDisk->cbDisk)
+ return VERR_INVALID_PARAMETER;
+
+ /* Compare read data */
+ size_t cbLeft = cbRead;
+ uint64_t offCurr = off;
+
+ while (cbLeft)
+ {
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64RangeGet(pMemDisk->pTreeSegments, offCurr);
+ size_t cbRange = 0;
+ unsigned offSeg = 0;
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, offCurr, true);
+ if ( !pSeg
+ || offCurr + cbLeft <= pSeg->Core.Key)
+ {
+ /* No data in the tree for this read. Fill with 0. */
+ cbRange = cbLeft;
+ }
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+
+ RTSgBufSet(pSgBuf, 0, cbRange);
+ }
+ else
+ {
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+
+ RTSgBufCopyFromBuf(pSgBuf, (uint8_t *)pSeg->pvSeg + offSeg, cbRange);
+ }
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ return VINF_SUCCESS;
+}
+
+int VDMemDiskSetSize(PVDMEMDISK pMemDisk, uint64_t cbSize)
+{
+ AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER);
+
+ if (!pMemDisk->fGrowable)
+ return VERR_NOT_SUPPORTED;
+
+ if (pMemDisk->cbDisk <= cbSize)
+ {
+ /* Increase. */
+ pMemDisk->cbDisk = cbSize;
+ }
+ else
+ {
+ /* We have to delete all parts beyond the new end. */
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64Get(pMemDisk->pTreeSegments, cbSize);
+ if (pSeg)
+ {
+ RTAvlrU64Remove(pMemDisk->pTreeSegments, pSeg->Core.Key);
+ if (pSeg->Core.Key < cbSize)
+ {
+ /* Cut off the part which is not in the file anymore. */
+ pSeg->pvSeg = RTMemRealloc(pSeg->pvSeg, pSeg->Core.KeyLast - cbSize + 1);
+ pSeg->Core.KeyLast = cbSize - pSeg->Core.Key - 1;
+
+ bool fInserted = RTAvlrU64Insert(pMemDisk->pTreeSegments, &pSeg->Core);
+ AssertMsg(fInserted, ("Bug!\n")); NOREF(fInserted);
+ }
+ else
+ {
+ /* Free the whole block. */
+ RTMemFree(pSeg->pvSeg);
+ RTMemFree(pSeg);
+ }
+ }
+
+ /* Kill all blocks coming after. */
+ do
+ {
+ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, cbSize, true);
+ if (pSeg)
+ {
+ RTAvlrU64Remove(pMemDisk->pTreeSegments, pSeg->Core.Key);
+ RTMemFree(pSeg->pvSeg);
+ pSeg->pvSeg = NULL;
+ RTMemFree(pSeg);
+ }
+ else
+ break;
+ } while (true);
+
+ pMemDisk->cbDisk = cbSize;
+ }
+
+ return VINF_SUCCESS;
+}
+
+int VDMemDiskGetSize(PVDMEMDISK pMemDisk, uint64_t *pcbSize)
+{
+ AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+
+ *pcbSize = pMemDisk->cbDisk;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes a segment to the given file.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pNode The disk segment to write to the file.
+ * @param pvParam Opaque user data containing the pointer to
+ * the file handle.
+ */
+static DECLCALLBACK(int) vdMemDiskSegmentWriteToFile(PAVLRU64NODECORE pNode, void *pvParam)
+{
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)pNode;
+ RTFILE hFile = *(PRTFILE)pvParam;
+
+ return RTFileWriteAt(hFile, pSeg->Core.Key, pSeg->pvSeg, pSeg->Core.KeyLast - pSeg->Core.Key + 1, NULL);
+}
+
+int VDMemDiskWriteToFile(PVDMEMDISK pMemDisk, const char *pcszFilename)
+{
+ int rc = VINF_SUCCESS;
+ RTFILE hFile = NIL_RTFILE;
+
+ LogFlowFunc(("pMemDisk=%#p pcszFilename=%s\n", pMemDisk, pcszFilename));
+ AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszFilename, VERR_INVALID_POINTER);
+
+ rc = RTFileOpen(&hFile, pcszFilename, RTFILE_O_DENY_NONE | RTFILE_O_CREATE | RTFILE_O_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAvlrU64DoWithAll(pMemDisk->pTreeSegments, true, vdMemDiskSegmentWriteToFile, &hFile);
+
+ RTFileClose(hFile);
+ if (RT_FAILURE(rc))
+ RTFileDelete(pcszFilename);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+int VDMemDiskReadFromFile(PVDMEMDISK pMemDisk, const char *pcszFilename)
+{
+ RT_NOREF2(pMemDisk, pcszFilename);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int VDMemDiskCmp(PVDMEMDISK pMemDisk, uint64_t off, size_t cbCmp, PRTSGBUF pSgBuf)
+{
+ LogFlowFunc(("pMemDisk=%#p off=%llx cbCmp=%u pSgBuf=%#p\n",
+ pMemDisk, off, cbCmp, pSgBuf));
+
+ /* Compare data */
+ size_t cbLeft = cbCmp;
+ uint64_t offCurr = off;
+
+ while (cbLeft)
+ {
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64Get(pMemDisk->pTreeSegments, offCurr);
+ size_t cbRange = 0;
+ bool fCmp = false;
+ unsigned offSeg = 0;
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, offCurr, true);
+ if (!pSeg)
+ {
+ /* No data in the tree for this read. Assume everything is ok. */
+ cbRange = cbLeft;
+ }
+ else if (offCurr + cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+ }
+ else
+ {
+ fCmp = true;
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+ }
+
+ if (fCmp)
+ {
+ RTSGSEG Seg;
+ RTSGBUF SgBufCmp;
+ size_t cbOff = 0;
+ int rc = 0;
+
+ Seg.cbSeg = cbRange;
+ Seg.pvSeg = (uint8_t *)pSeg->pvSeg + offSeg;
+
+ RTSgBufInit(&SgBufCmp, &Seg, 1);
+ rc = RTSgBufCmpEx(pSgBuf, &SgBufCmp, cbRange, &cbOff, true);
+ if (rc)
+ return rc;
+ }
+ else
+ RTSgBufAdvance(pSgBuf, cbRange);
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ return 0;
+}
+
diff --git a/src/VBox/Storage/testcase/VDMemDisk.h b/src/VBox/Storage/testcase/VDMemDisk.h
new file mode 100644
index 00000000..b848e212
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDMemDisk.h
@@ -0,0 +1,143 @@
+/** @file
+ *
+ * VBox HDD container test utility, memory disk/file.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDMemDisk_h
+#define VBOX_INCLUDED_SRC_testcase_VDMemDisk_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/sg.h>
+
+/** Handle to the a memory disk. */
+typedef struct VDMEMDISK *PVDMEMDISK;
+/** Pointer to a memory disk handle. */
+typedef PVDMEMDISK *PPVDMEMDISK;
+
+/**
+ * Creates a new memory disk with the given size.
+ *
+ * @returns VBOX status code.
+ *
+ * @param ppMemDisk Where to store the memory disk handle.
+ * @param cbSize Size of the disk if it is fixed.
+ * If 0 the disk grows when it is written to
+ * and the size can be changed with
+ * VDMemDiskSetSize().
+ */
+int VDMemDiskCreate(PPVDMEMDISK ppMemDisk, uint64_t cbSize);
+
+/**
+ * Destroys a memory disk.
+ *
+ * @returns nothing.
+ *
+ * @param pMemDisk The memory disk to destroy.
+ */
+void VDMemDiskDestroy(PVDMEMDISK pMemDisk);
+
+/**
+ * Writes the specified amount of data from the S/G buffer at
+ * the given offset.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param off Where to start writing to.
+ * @param cbWrite How many bytes to write.
+ * @param pSgBuf The S/G buffer to write from.
+ */
+int VDMemDiskWrite(PVDMEMDISK pMemDisk, uint64_t off, size_t cbWrite, PRTSGBUF pSgBuf);
+
+/**
+ * Reads the specified amount of data into the S/G buffer
+ * starting from the given offset.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param off Where to start reading from.
+ * @param cbRead The amount of bytes to read.
+ * @param pSgBuf The S/G buffer to read into.
+ */
+int VDMemDiskRead(PVDMEMDISK pMemDisk, uint64_t off, size_t cbRead, PRTSGBUF pSgBuf);
+
+/**
+ * Sets the size of the memory disk.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param cbSize The new size to set.
+ */
+int VDMemDiskSetSize(PVDMEMDISK pMemDisk, uint64_t cbSize);
+
+/**
+ * Gets the current size of the memory disk.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param pcbSize Where to store the size of the memory
+ * disk.
+ */
+int VDMemDiskGetSize(PVDMEMDISK pMemDisk, uint64_t *pcbSize);
+
+/**
+ * Dumps the memory disk to a file.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param pcszFilename Where to dump the content.
+ */
+int VDMemDiskWriteToFile(PVDMEMDISK pMemDisk, const char *pcszFilename);
+
+/**
+ * Reads the content of a file into the given memory disk.
+ * All data stored in the memory disk will be overwritten.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param pcszFilename The file to load from.
+ */
+int VDMemDiskReadFromFile(PVDMEMDISK pMemDisk, const char *pcszFilename);
+
+/**
+ * Compares the given range of the memory disk with a provided S/G buffer.
+ *
+ * @returns whatever memcmp returns.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param off Where to start comparing.
+ * @param cbCmp How many bytes to compare.
+ * @param pSgBuf The S/G buffer to compare with.
+ */
+int VDMemDiskCmp(PVDMEMDISK pMemDisk, uint64_t off, size_t cbCmp, PRTSGBUF pSgBuf);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDMemDisk_h */
diff --git a/src/VBox/Storage/testcase/VDScript.cpp b/src/VBox/Storage/testcase/VDScript.cpp
new file mode 100644
index 00000000..95a4c118
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScript.cpp
@@ -0,0 +1,3020 @@
+/* $Id: VDScript.cpp $ */
+/** @file
+ * VBox HDD container test utility - scripting engine.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_vd_script VDScript - Simple scripting language for VD I/O testing.
+ *
+ * This component implements a very simple scripting language to make testing the VD
+ * library more flexible and testcases faster to implement without the need to recompile
+ * everything after it changed.
+ * The language is a small subset of the C language. It doesn't support unions, structs,
+ * global variables, typedefed types or pointers (yet). It also adds a boolean and a string type.
+ * Strings are immutable and only to print messages from the script.
+ * There are also not the default types like int or unsigned because theire ranges are architecture
+ * dependent. Instead VDScript uses uint8_t, int8_t, ... as primitive types.
+ *
+ * Why inventing a completely new language?
+ *
+ * Well it is not a completely new language to start with, it is a subset of C and the
+ * language can be extended later on to reach the full C language later on.
+ * Second, there is no static typed scripting language I like which could be implemented
+ * and finally because I can ;)
+ * The code implementing the scripting engine is designed to be easily incorporated into other
+ * code. Could be used as a scripting language for the VBox debugger for example or in the scm
+ * tool to automatically rewrite C code using the AST VDSCript generates...
+ *
+ * The syntax of VDSCript is derived from the C syntax. The syntax of C in BNF was taken
+ * from: http://www.csci.csusb.edu/dick/samples/c.syntax.html
+ * and: http://slps.github.com/zoo/c/iso-9899-tc3.html
+ * and: http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf
+ */
+
+#define LOGGROUP LOGGROUP_DEFAULT
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+
+#include "VDScriptAst.h"
+#include "VDScriptInternal.h"
+
+/**
+ * VD script token class.
+ */
+typedef enum VDTOKENCLASS
+{
+ /** Invalid. */
+ VDTOKENCLASS_INVALID = 0,
+ /** Identifier class. */
+ VDTOKENCLASS_IDENTIFIER,
+ /** Numerical constant. */
+ VDTOKENCLASS_NUMCONST,
+ /** String constant. */
+ VDTOKENCLASS_STRINGCONST,
+ /** Operators */
+ VDTOKENCLASS_OPERATORS,
+ /** Reserved keyword */
+ VDTOKENCLASS_KEYWORD,
+ /** Punctuator */
+ VDTOKENCLASS_PUNCTUATOR,
+ /** End of stream */
+ VDTOKENCLASS_EOS,
+ /** 32bit hack. */
+ VDTOKENCLASS_32BIT_HACK = 0x7fffffff
+} VDTOKENCLASS;
+/** Pointer to a token class. */
+typedef VDTOKENCLASS *PVDTOKENCLASS;
+
+/**
+ * Keyword types.
+ */
+typedef enum VDSCRIPTTOKENKEYWORD
+{
+ VDSCRIPTTOKENKEYWORD_INVALID = 0,
+ VDSCRIPTTOKENKEYWORD_CONTINUE,
+ VDSCRIPTTOKENKEYWORD_REGISTER,
+ VDSCRIPTTOKENKEYWORD_RESTRICT,
+ VDSCRIPTTOKENKEYWORD_VOLATILE,
+ VDSCRIPTTOKENKEYWORD_TYPEDEF,
+ VDSCRIPTTOKENKEYWORD_DEFAULT,
+ VDSCRIPTTOKENKEYWORD_EXTERN,
+ VDSCRIPTTOKENKEYWORD_STATIC,
+ VDSCRIPTTOKENKEYWORD_RETURN,
+ VDSCRIPTTOKENKEYWORD_SWITCH,
+ VDSCRIPTTOKENKEYWORD_STRUCT,
+ VDSCRIPTTOKENKEYWORD_WHILE,
+ VDSCRIPTTOKENKEYWORD_BREAK,
+ VDSCRIPTTOKENKEYWORD_CONST,
+ VDSCRIPTTOKENKEYWORD_FALSE,
+ VDSCRIPTTOKENKEYWORD_TRUE,
+ VDSCRIPTTOKENKEYWORD_ELSE,
+ VDSCRIPTTOKENKEYWORD_CASE,
+ VDSCRIPTTOKENKEYWORD_AUTO,
+ VDSCRIPTTOKENKEYWORD_FOR,
+ VDSCRIPTTOKENKEYWORD_IF,
+ VDSCRIPTTOKENKEYWORD_DO,
+ VDSCRIPTTOKENKEYWORD_32BIT_HACK = 0x7fffffff
+} VDSCRIPTTOKENKEYWORD;
+/** Pointer to a keyword type. */
+typedef VDSCRIPTTOKENKEYWORD *PVDSCRIPTTOKENKEYWORD;
+
+/**
+ * VD script token.
+ */
+typedef struct VDSCRIPTTOKEN
+{
+ /** Token class. */
+ VDTOKENCLASS enmClass;
+ /** Token position in the source buffer. */
+ VDSRCPOS Pos;
+ /** Data based on the token class. */
+ union
+ {
+ /** Identifier. */
+ struct
+ {
+ /** Pointer to the start of the identifier. */
+ const char *pszIde;
+ /** Number of characters for the identifier excluding the null terminator. */
+ size_t cchIde;
+ } Ide;
+ /** Numerical constant. */
+ struct
+ {
+ uint64_t u64;
+ } NumConst;
+ /** String constant */
+ struct
+ {
+ /** Pointer to the start of the string constant. */
+ const char *pszString;
+ /** Number of characters of the string, including the null terminator. */
+ size_t cchString;
+ } StringConst;
+ /** Operator */
+ struct
+ {
+ /** The operator string. */
+ char aszOp[4]; /** Maximum of 3 for >>= + null terminator. */
+ } Operator;
+ /** Keyword. */
+ struct
+ {
+ /** The keyword type. */
+ VDSCRIPTTOKENKEYWORD enmKeyword;
+ } Keyword;
+ /** Punctuator. */
+ struct
+ {
+ /** The punctuator in question. */
+ char chPunctuator;
+ } Punctuator;
+ } Class;
+} VDSCRIPTTOKEN;
+/** Pointer to a script token. */
+typedef VDSCRIPTTOKEN *PVDSCRIPTTOKEN;
+/** Pointer to a const script token. */
+typedef const VDSCRIPTTOKEN *PCVDSCRIPTTOKEN;
+
+/**
+ * Tokenizer state.
+ */
+typedef struct VDTOKENIZER
+{
+ /** Char buffer to read from. */
+ const char *pszInput;
+ /** Current position ininput buffer. */
+ VDSRCPOS Pos;
+ /** Token 1. */
+ VDSCRIPTTOKEN Token1;
+ /** Token 2. */
+ VDSCRIPTTOKEN Token2;
+ /** Pointer to the current active token. */
+ PVDSCRIPTTOKEN pTokenCurr;
+ /** The next token in the input stream (used for peeking). */
+ PVDSCRIPTTOKEN pTokenNext;
+} VDTOKENIZER;
+
+/**
+ * Operators entry.
+ */
+typedef struct VDSCRIPTOP
+{
+ /** Operator string. */
+ const char *pszOp;
+ /** Size of the operator in characters without zero terminator. */
+ size_t cchOp;
+} VDSCRIPTOP;
+/** Pointer to a script operator. */
+typedef VDSCRIPTOP *PVDSCRIPTOP;
+
+/**
+ * Known operators array, sort from higest character count to lowest.
+ */
+static VDSCRIPTOP g_aScriptOps[] =
+{
+ {">>=", 3},
+ {"<<=", 3},
+ {"+=", 2},
+ {"-=", 2},
+ {"/=", 2},
+ {"%=", 2},
+ {"&=", 2},
+ {"|=", 2},
+ {"^=", 2},
+ {"&&", 2},
+ {"||", 2},
+ {"<<", 2},
+ {">>", 2},
+ {"++", 2},
+ {"--", 2},
+ {"==", 2},
+ {"!=", 2},
+ {">=", 2},
+ {"<=", 2},
+ {"->", 2},
+ {"=", 1},
+ {"+", 1},
+ {"-", 1},
+ {"*", 1},
+ {"/", 1},
+ {"%", 1},
+ {"|", 1},
+ {"&", 1},
+ {"^", 1},
+ {"<", 1},
+ {">", 1},
+ {"!", 1},
+ {"~", 1},
+ {".", 1}
+};
+
+/**
+ * Known punctuators.
+ */
+static VDSCRIPTOP g_aScriptPunctuators[] =
+{
+ {"(", 1},
+ {")", 1},
+ {"{", 1},
+ {"}", 1},
+ {",", 1},
+ {";", 1},
+};
+
+/**
+ * Keyword entry.
+ */
+typedef struct VDSCRIPTKEYWORD
+{
+ /** Keyword string. */
+ const char *pszKeyword;
+ /** Size of the string in characters without zero terminator. */
+ size_t cchKeyword;
+ /** Keyword type. */
+ VDSCRIPTTOKENKEYWORD enmKeyword;
+} VDSCRIPTKEYWORD;
+/** */
+typedef VDSCRIPTKEYWORD *PVDSCRIPTKEYWORD;
+
+/**
+ * Known keywords.
+ */
+static VDSCRIPTKEYWORD g_aKeywords[] =
+{
+ {RT_STR_TUPLE("continue"), VDSCRIPTTOKENKEYWORD_CONTINUE},
+ {RT_STR_TUPLE("register"), VDSCRIPTTOKENKEYWORD_REGISTER},
+ {RT_STR_TUPLE("restrict"), VDSCRIPTTOKENKEYWORD_RESTRICT},
+ {RT_STR_TUPLE("volatile"), VDSCRIPTTOKENKEYWORD_VOLATILE},
+ {RT_STR_TUPLE("typedef"), VDSCRIPTTOKENKEYWORD_TYPEDEF},
+ {RT_STR_TUPLE("default"), VDSCRIPTTOKENKEYWORD_DEFAULT},
+ {RT_STR_TUPLE("extern"), VDSCRIPTTOKENKEYWORD_EXTERN},
+ {RT_STR_TUPLE("static"), VDSCRIPTTOKENKEYWORD_STATIC},
+ {RT_STR_TUPLE("return"), VDSCRIPTTOKENKEYWORD_RETURN},
+ {RT_STR_TUPLE("switch"), VDSCRIPTTOKENKEYWORD_SWITCH},
+ {RT_STR_TUPLE("struct"), VDSCRIPTTOKENKEYWORD_STRUCT},
+ {RT_STR_TUPLE("while"), VDSCRIPTTOKENKEYWORD_WHILE},
+ {RT_STR_TUPLE("break"), VDSCRIPTTOKENKEYWORD_BREAK},
+ {RT_STR_TUPLE("const"), VDSCRIPTTOKENKEYWORD_CONST},
+ {RT_STR_TUPLE("false"), VDSCRIPTTOKENKEYWORD_FALSE},
+ {RT_STR_TUPLE("true"), VDSCRIPTTOKENKEYWORD_TRUE},
+ {RT_STR_TUPLE("else"), VDSCRIPTTOKENKEYWORD_ELSE},
+ {RT_STR_TUPLE("case"), VDSCRIPTTOKENKEYWORD_CASE},
+ {RT_STR_TUPLE("auto"), VDSCRIPTTOKENKEYWORD_AUTO},
+ {RT_STR_TUPLE("for"), VDSCRIPTTOKENKEYWORD_FOR},
+ {RT_STR_TUPLE("if"), VDSCRIPTTOKENKEYWORD_IF},
+ {RT_STR_TUPLE("do"), VDSCRIPTTOKENKEYWORD_DO}
+};
+
+static int vdScriptParseCompoundStatement(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSTMT *ppAstNodeCompound);
+static int vdScriptParseStatement(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSTMT *ppAstNodeStmt);
+static int vdScriptParseExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr);
+static int vdScriptParseAssignmentExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr);
+static int vdScriptParseCastExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr);
+#if 0 /* unused */
+static int vdScriptParseConstExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr);
+#endif
+
+/**
+ * Returns whether the tokenizer reached the end of the stream.
+ *
+ * @returns true if the tokenizer reached the end of stream marker
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(bool) vdScriptTokenizerIsEos(PVDTOKENIZER pTokenizer)
+{
+ return *pTokenizer->pszInput == '\0';
+}
+
+/**
+ * Skip one character in the input stream.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) vdScriptTokenizerSkipCh(PVDTOKENIZER pTokenizer)
+{
+ pTokenizer->pszInput++;
+ pTokenizer->Pos.iChStart++;
+ pTokenizer->Pos.iChEnd++;
+}
+
+/**
+ * Returns the next char in the input buffer without advancing it.
+ *
+ * @returns Next character in the input buffer.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(char) vdScriptTokenizerPeekCh(PVDTOKENIZER pTokenizer)
+{
+ return vdScriptTokenizerIsEos(pTokenizer)
+ ? '\0'
+ : *(pTokenizer->pszInput + 1);
+}
+
+/**
+ * Returns the next character in the input buffer advancing the internal
+ * position.
+ *
+ * @returns Next character in the stream.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(char) vdScriptTokenizerGetCh(PVDTOKENIZER pTokenizer)
+{
+ char ch;
+
+ if (vdScriptTokenizerIsEos(pTokenizer))
+ ch = '\0';
+ else
+ ch = *pTokenizer->pszInput;
+
+ return ch;
+}
+
+/**
+ * Sets a new line for the tokenizer.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) vdScriptTokenizerNewLine(PVDTOKENIZER pTokenizer, unsigned cSkip)
+{
+ pTokenizer->pszInput += cSkip;
+ pTokenizer->Pos.iLine++;
+ pTokenizer->Pos.iChStart = 1;
+ pTokenizer->Pos.iChEnd = 1;
+}
+
+/**
+ * Checks whether the current position in the input stream is a new line
+ * and skips it.
+ *
+ * @returns Flag whether there was a new line at the current position
+ * in the input buffer.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(bool) vdScriptTokenizerIsSkipNewLine(PVDTOKENIZER pTokenizer)
+{
+ bool fNewline = true;
+
+ if ( vdScriptTokenizerGetCh(pTokenizer) == '\r'
+ && vdScriptTokenizerPeekCh(pTokenizer) == '\n')
+ vdScriptTokenizerNewLine(pTokenizer, 2);
+ else if (vdScriptTokenizerGetCh(pTokenizer) == '\n')
+ vdScriptTokenizerNewLine(pTokenizer, 1);
+ else
+ fNewline = false;
+
+ return fNewline;
+}
+
+/**
+ * Skips a multi line comment.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) vdScriptTokenizerSkipComment(PVDTOKENIZER pTokenizer)
+{
+ while ( !vdScriptTokenizerIsEos(pTokenizer)
+ && ( vdScriptTokenizerGetCh(pTokenizer) != '*'
+ || vdScriptTokenizerPeekCh(pTokenizer) != '/'))
+ {
+ if (!vdScriptTokenizerIsSkipNewLine(pTokenizer))
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+
+ if (!vdScriptTokenizerIsEos(pTokenizer))
+ vdScriptTokenizerSkipCh(pTokenizer);
+ if (!vdScriptTokenizerIsEos(pTokenizer))
+ vdScriptTokenizerSkipCh(pTokenizer);
+}
+
+/**
+ * Skip all whitespace starting from the current input buffer position.
+ * Skips all present comments too.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) vdScriptTokenizerSkipWhitespace(PVDTOKENIZER pTokenizer)
+{
+ while (!vdScriptTokenizerIsEos(pTokenizer))
+ {
+ while ( vdScriptTokenizerGetCh(pTokenizer) == ' '
+ || vdScriptTokenizerGetCh(pTokenizer) == '\t')
+ vdScriptTokenizerSkipCh(pTokenizer);
+
+ if ( !vdScriptTokenizerIsEos(pTokenizer)
+ && !vdScriptTokenizerIsSkipNewLine(pTokenizer))
+ {
+ if ( vdScriptTokenizerGetCh(pTokenizer) == '/'
+ && vdScriptTokenizerPeekCh(pTokenizer) == '*')
+ {
+ vdScriptTokenizerSkipCh(pTokenizer);
+ vdScriptTokenizerSkipCh(pTokenizer);
+ vdScriptTokenizerSkipComment(pTokenizer);
+ }
+ else
+ break; /* Skipped everything, next is some real content. */
+ }
+ }
+}
+
+/**
+ * Get an identifier token from the tokenizer.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void vdScriptTokenizerGetIdeOrKeyword(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ char ch;
+ unsigned cchIde = 0;
+ bool fIsKeyword = false;
+ const char *pszIde = pTokenizer->pszInput;
+
+ pToken->Pos = pTokenizer->Pos;
+
+ Assert(RT_C_IS_ALPHA(*pszIde) || *pszIde == '_' );
+
+ do
+ {
+ cchIde++;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ ch = vdScriptTokenizerGetCh(pTokenizer);
+ }
+ while (RT_C_IS_ALNUM(ch) || ch == '_');
+
+ /* Check whether we got an identifier or an reserved keyword. */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aKeywords); i++)
+ {
+ if (!RTStrNCmp(g_aKeywords[i].pszKeyword, pszIde, g_aKeywords[i].cchKeyword))
+ {
+ fIsKeyword = true;
+ pToken->enmClass = VDTOKENCLASS_KEYWORD;
+ pToken->Class.Keyword.enmKeyword = g_aKeywords[i].enmKeyword;
+ break;
+ }
+ }
+
+ if (!fIsKeyword)
+ {
+ pToken->enmClass = VDTOKENCLASS_IDENTIFIER;
+ pToken->Class.Ide.pszIde = pszIde;
+ pToken->Class.Ide.cchIde = cchIde;
+ }
+ pToken->Pos.iChEnd += cchIde;
+}
+
+/**
+ * Get a numerical constant from the tokenizer.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void vdScriptTokenizerGetNumberConst(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ char *pszNext = NULL;
+
+ Assert(RT_C_IS_DIGIT(vdScriptTokenizerGetCh(pTokenizer)));
+
+ /* Let RTStrToUInt64Ex() do all the work, looks C compliant :). */
+ pToken->enmClass = VDTOKENCLASS_NUMCONST;
+ int rc = RTStrToUInt64Ex(pTokenizer->pszInput, &pszNext, 0, &pToken->Class.NumConst.u64);
+ Assert(RT_SUCCESS(rc) || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES); NOREF(rc);
+ /** @todo Handle number to big, throw a warning */
+
+ unsigned cchNumber = pszNext - pTokenizer->pszInput;
+ for (unsigned i = 0; i < cchNumber; i++)
+ vdScriptTokenizerSkipCh(pTokenizer);
+
+ /* Check for a supported suffix, supported are K|M|G. */
+ if (vdScriptTokenizerGetCh(pTokenizer) == 'K')
+ {
+ pToken->Class.NumConst.u64 *= _1K;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+ else if (vdScriptTokenizerGetCh(pTokenizer) == 'M')
+ {
+ pToken->Class.NumConst.u64 *= _1M;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+ else if (vdScriptTokenizerGetCh(pTokenizer) == 'G')
+ {
+ pToken->Class.NumConst.u64 *= _1G;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+ else if (vdScriptTokenizerGetCh(pTokenizer) == 'T')
+ {
+ pToken->Class.NumConst.u64 *= _1T;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+}
+
+/**
+ * Parses a string constant.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ *
+ * @remarks: No escape sequences allowed at this time.
+ */
+static void vdScriptTokenizerGetStringConst(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ unsigned cchStr = 0;
+
+ Assert(vdScriptTokenizerGetCh(pTokenizer) == '\"');
+ vdScriptTokenizerSkipCh(pTokenizer); /* Skip " */
+
+ pToken->enmClass = VDTOKENCLASS_STRINGCONST;
+ pToken->Pos = pTokenizer->Pos;
+ pToken->Class.StringConst.pszString = pTokenizer->pszInput;
+
+ while (vdScriptTokenizerGetCh(pTokenizer) != '\"')
+ {
+ cchStr++;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+
+ vdScriptTokenizerSkipCh(pTokenizer); /* Skip closing " */
+
+ pToken->Class.StringConst.cchString = cchStr;
+ pToken->Pos.iChEnd += cchStr;
+}
+
+/**
+ * Get the end of stream token.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void vdScriptTokenizerGetEos(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ Assert(vdScriptTokenizerGetCh(pTokenizer) == '\0');
+
+ pToken->enmClass = VDTOKENCLASS_EOS;
+ pToken->Pos = pTokenizer->Pos;
+}
+
+/**
+ * Get operator or punctuator token.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void vdScriptTokenizerGetOperatorOrPunctuator(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ bool fOpFound = false;
+
+ pToken->enmClass = VDTOKENCLASS_INVALID;
+ pToken->Pos = pTokenizer->Pos;
+
+ /*
+ * Use table based approach here, not the fastest solution but enough for our purpose
+ * for now.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aScriptOps); i++)
+ {
+ if (!RTStrNCmp(g_aScriptOps[i].pszOp, pTokenizer->pszInput, g_aScriptOps[i].cchOp))
+ {
+ memset(pToken->Class.Operator.aszOp, 0, sizeof(pToken->Class.Operator.aszOp));
+
+ int rc = RTStrCopy(pToken->Class.Operator.aszOp, sizeof(pToken->Class.Operator.aszOp), g_aScriptOps[i].pszOp);
+ AssertRC(rc);
+
+ pToken->enmClass = VDTOKENCLASS_OPERATORS;
+ pToken->Pos.iChEnd += (unsigned)g_aScriptOps[i].cchOp;
+
+ /** @todo Make this prettier. */
+ for (unsigned j = 0; j < g_aScriptOps[i].cchOp; j++)
+ vdScriptTokenizerSkipCh(pTokenizer);
+ fOpFound = true;
+ break;
+ }
+ }
+
+ if (!fOpFound)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aScriptPunctuators); i++)
+ {
+ if (!RTStrNCmp(g_aScriptPunctuators[i].pszOp, pTokenizer->pszInput, g_aScriptPunctuators[i].cchOp))
+ {
+ pToken->Pos.iChEnd += (unsigned)g_aScriptPunctuators[i].cchOp;
+ pToken->enmClass = VDTOKENCLASS_PUNCTUATOR;
+ pToken->Class.Punctuator.chPunctuator = *g_aScriptPunctuators[i].pszOp;
+
+ vdScriptTokenizerSkipCh(pTokenizer);
+ fOpFound = true;
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Read the next token from the tokenizer stream.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer to read from.
+ * @param pToken Uninitialized token to fill the token data into.
+ */
+static void vdScriptTokenizerReadNextToken(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ /* Skip all eventually existing whitespace, newlines and comments first. */
+ vdScriptTokenizerSkipWhitespace(pTokenizer);
+
+ char ch = vdScriptTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_ALPHA(ch) || ch == '_')
+ vdScriptTokenizerGetIdeOrKeyword(pTokenizer, pToken);
+ else if (RT_C_IS_DIGIT(ch))
+ vdScriptTokenizerGetNumberConst(pTokenizer, pToken);
+ else if (ch == '\"')
+ vdScriptTokenizerGetStringConst(pTokenizer, pToken);
+ else if (ch == '\0')
+ vdScriptTokenizerGetEos(pTokenizer, pToken);
+ else
+ vdScriptTokenizerGetOperatorOrPunctuator(pTokenizer, pToken);
+}
+
+/**
+ * Create a new tokenizer.
+ *
+ * @returns Pointer to the new tokenizer state on success.
+ * NULL if out of memory.
+ * @param pszInput The input to create the tokenizer for.
+ */
+static PVDTOKENIZER vdScriptTokenizerCreate(const char *pszInput)
+{
+ PVDTOKENIZER pTokenizer = (PVDTOKENIZER)RTMemAllocZ(sizeof(VDTOKENIZER));
+ if (pTokenizer)
+ {
+ pTokenizer->pszInput = pszInput;
+ pTokenizer->Pos.iLine = 1;
+ pTokenizer->Pos.iChStart = 1;
+ pTokenizer->Pos.iChEnd = 1;
+ pTokenizer->pTokenCurr = &pTokenizer->Token1;
+ pTokenizer->pTokenNext = &pTokenizer->Token2;
+ /* Fill the tokenizer with two first tokens. */
+ vdScriptTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenCurr);
+ vdScriptTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
+ }
+
+ return pTokenizer;
+}
+
+#if 0 /** @todo unused */
+/**
+ * Destroys a given tokenizer state.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer to destroy.
+ */
+static void vdScriptTokenizerDestroy(PVDTOKENIZER pTokenizer)
+{
+ RTMemFree(pTokenizer);
+}
+#endif
+
+/**
+ * Get the current token in the input stream.
+ *
+ * @returns Pointer to the next token in the stream.
+ * @param pTokenizer The tokenizer to destroy.
+ */
+DECLINLINE(PCVDSCRIPTTOKEN) vdScriptTokenizerGetToken(PVDTOKENIZER pTokenizer)
+{
+ return pTokenizer->pTokenCurr;
+}
+
+/**
+ * Get the class of the current token.
+ *
+ * @returns Class of the current token.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(VDTOKENCLASS) vdScriptTokenizerGetTokenClass(PVDTOKENIZER pTokenizer)
+{
+ return pTokenizer->pTokenCurr->enmClass;
+}
+
+/**
+ * Returns the token class of the next token in the stream.
+ *
+ * @returns Token class of the next token.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(VDTOKENCLASS) vdScriptTokenizerPeekNextClass(PVDTOKENIZER pTokenizer)
+{
+ return pTokenizer->pTokenNext->enmClass;
+}
+
+/**
+ * Consume the current token advancing to the next in the stream.
+ *
+ * @returns nothing.
+ * @param pTokenizer The tokenizer state.
+ */
+static void vdScriptTokenizerConsume(PVDTOKENIZER pTokenizer)
+{
+ PVDSCRIPTTOKEN pTokenTmp = pTokenizer->pTokenCurr;
+
+ /* Switch next token to current token and read in the next token. */
+ pTokenizer->pTokenCurr = pTokenizer->pTokenNext;
+ pTokenizer->pTokenNext = pTokenTmp;
+ vdScriptTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
+}
+
+/**
+ * Check whether the next token in the input stream is a punctuator and matches the given
+ * character.
+ *
+ * @returns true if the token matched.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param chCheck The punctuator to check against.
+ */
+static bool vdScriptTokenizerIsPunctuatorEqual(PVDTOKENIZER pTokenizer, char chCheck)
+{
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pTokenizer);
+
+ if ( pToken->enmClass == VDTOKENCLASS_PUNCTUATOR
+ && pToken->Class.Punctuator.chPunctuator == chCheck)
+ return true;
+
+ return false;
+}
+
+/**
+ * Check whether the next token in the input stream is a punctuator and matches the given
+ * character and skips it.
+ *
+ * @returns true if the token matched and was skipped.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param chCheck The punctuator to check against.
+ */
+static bool vdScriptTokenizerSkipIfIsPunctuatorEqual(PVDTOKENIZER pTokenizer, char chCheck)
+{
+ bool fEqual = vdScriptTokenizerIsPunctuatorEqual(pTokenizer, chCheck);
+ if (fEqual)
+ vdScriptTokenizerConsume(pTokenizer);
+
+ return fEqual;
+}
+
+/**
+ * Check whether the next token in the input stream is a keyword and matches the given
+ * keyword.
+ *
+ * @returns true if the token matched.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param enmKey The keyword to check against.
+ */
+static bool vdScriptTokenizerIsKeywordEqual(PVDTOKENIZER pTokenizer, VDSCRIPTTOKENKEYWORD enmKeyword)
+{
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pTokenizer);
+
+ if ( pToken->enmClass == VDTOKENCLASS_KEYWORD
+ && pToken->Class.Keyword.enmKeyword == enmKeyword)
+ return true;
+
+ return false;
+}
+
+/**
+ * Check whether the next token in the input stream is a keyword and matches the given
+ * keyword and skips it.
+ *
+ * @returns true if the token matched and was skipped.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param enmKey The keyword to check against.
+ */
+static bool vdScriptTokenizerSkipIfIsKeywordEqual(PVDTOKENIZER pTokenizer, VDSCRIPTTOKENKEYWORD enmKeyword)
+{
+ bool fEqual = vdScriptTokenizerIsKeywordEqual(pTokenizer, enmKeyword);
+ if (fEqual)
+ vdScriptTokenizerConsume(pTokenizer);
+
+ return fEqual;
+}
+
+/**
+ * Check whether the next token in the input stream is a keyword and matches the given
+ * keyword.
+ *
+ * @returns true if the token matched.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param pszOp The operation to check against.
+ */
+static bool vdScriptTokenizerIsOperatorEqual(PVDTOKENIZER pTokenizer, const char *pszOp)
+{
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pTokenizer);
+
+ if ( pToken->enmClass == VDTOKENCLASS_OPERATORS
+ && !RTStrCmp(pToken->Class.Operator.aszOp, pszOp))
+ return true;
+
+ return false;
+}
+
+/**
+ * Check whether the next token in the input stream is an operator and matches the given
+ * keyword and skips it.
+ *
+ * @returns true if the token matched and was skipped.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param pszOp The operation to check against.
+ */
+static bool vdScriptTokenizerSkipIfIsOperatorEqual(PVDTOKENIZER pTokenizer, const char *pszOp)
+{
+ bool fEqual = vdScriptTokenizerIsOperatorEqual(pTokenizer, pszOp);
+ if (fEqual)
+ vdScriptTokenizerConsume(pTokenizer);
+
+ return fEqual;
+}
+
+/**
+ * Record an error while parsing.
+ *
+ * @returns VBox status code passed.
+ */
+static int vdScriptParserError(PVDSCRIPTCTXINT pThis, int rc, RT_SRC_POS_DECL, const char *pszFmt, ...)
+{
+ RT_NOREF1(pThis); RT_SRC_POS_NOREF();
+ va_list va;
+ va_start(va, pszFmt);
+ RTPrintfV(pszFmt, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Puts the next identifier AST node on the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeIde Where to store the identifier AST node on success.
+ */
+static int vdScriptParseIde(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTIDE *ppAstNodeIde)
+{
+ int rc = VINF_SUCCESS;
+ PCVDSCRIPTTOKEN pToken;
+
+ LogFlowFunc(("pThis=%p ppAstNodeIde=%p\n", pThis, ppAstNodeIde));
+
+ pToken = vdScriptTokenizerGetToken(pThis->pTokenizer);
+ if (pToken->enmClass != VDTOKENCLASS_IDENTIFIER)
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected identifer got...\n");
+ else
+ {
+ /* Create new AST node and push onto stack. */
+ PVDSCRIPTASTIDE pAstNodeIde = vdScriptAstNodeIdeAlloc(pToken->Class.Ide.cchIde);
+ if (pAstNodeIde)
+ {
+ rc = RTStrCopyEx(pAstNodeIde->aszIde, pToken->Class.Ide.cchIde + 1, pToken->Class.Ide.pszIde, pToken->Class.Ide.cchIde);
+ AssertRC(rc);
+ pAstNodeIde->cchIde = (unsigned)pToken->Class.Ide.cchIde;
+
+ *ppAstNodeIde = pAstNodeIde;
+ vdScriptTokenizerConsume(pThis->pTokenizer);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating identifier AST node\n");
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a primary expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the primary expression on success.
+ */
+static int vdScriptParsePrimaryExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ rc = vdScriptParseExpression(pThis, ppAstNodeExpr);
+ if (RT_SUCCESS(rc)
+ && !vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ }
+ else
+ {
+ PVDSCRIPTASTEXPR pExpr = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExpr)
+ {
+ if (vdScriptTokenizerGetTokenClass(pThis->pTokenizer) == VDTOKENCLASS_IDENTIFIER)
+ {
+ PVDSCRIPTASTIDE pIde = NULL;
+ rc = vdScriptParseIde(pThis, &pIde);
+ if (RT_SUCCESS(rc))
+ {
+ pExpr->enmType = VDSCRIPTEXPRTYPE_PRIMARY_IDENTIFIER;
+ pExpr->pIde = pIde;
+ }
+ }
+ else if (vdScriptTokenizerGetTokenClass(pThis->pTokenizer) == VDTOKENCLASS_NUMCONST)
+ {
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pThis->pTokenizer);
+ pExpr->enmType = VDSCRIPTEXPRTYPE_PRIMARY_NUMCONST;
+ pExpr->u64 = pToken->Class.NumConst.u64;
+ vdScriptTokenizerConsume(pThis->pTokenizer);
+ }
+ else if (vdScriptTokenizerGetTokenClass(pThis->pTokenizer) == VDTOKENCLASS_STRINGCONST)
+ {
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pThis->pTokenizer);
+ pExpr->enmType = VDSCRIPTEXPRTYPE_PRIMARY_STRINGCONST;
+ pExpr->pszStr = RTStrDupN(pToken->Class.StringConst.pszString, pToken->Class.StringConst.cchString);
+ vdScriptTokenizerConsume(pThis->pTokenizer);
+
+ if (!pExpr->pszStr)
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating string\n");
+ }
+ else if (vdScriptTokenizerGetTokenClass(pThis->pTokenizer) == VDTOKENCLASS_KEYWORD)
+ {
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pThis->pTokenizer);
+ pExpr->enmType = VDSCRIPTEXPRTYPE_PRIMARY_BOOLEAN;
+
+ if (pToken->Class.Keyword.enmKeyword == VDSCRIPTTOKENKEYWORD_TRUE)
+ pExpr->f = true;
+ else if (pToken->Class.Keyword.enmKeyword == VDSCRIPTTOKENKEYWORD_FALSE)
+ pExpr->f = false;
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Unexpected keyword, expected true or false\n");
+ vdScriptTokenizerConsume(pThis->pTokenizer);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\" | identifier | constant | string, got ...\n");
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pExpr->Core);
+ else
+ *ppAstNodeExpr = pExpr;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse an argument list for a function call.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pFnCall The function call AST node.
+ */
+static int vdScriptParseFnCallArgumentList(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR pFnCall)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p pFnCall=%p\n", pThis, pFnCall));
+
+ rc = vdScriptParseAssignmentExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ RTListAppend(&pFnCall->FnCall.ListArgs, &pExpr->Core.ListNode);
+ while (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ','))
+ {
+ rc = vdScriptParseAssignmentExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pFnCall->FnCall.ListArgs, &pExpr->Core.ListNode);
+ else
+ break;
+ }
+ if ( RT_SUCCESS(rc)
+ && !vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a postfix expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * postfix-expression:
+ * primary-expression
+ * postfix-expression ( argument-expression )
+ * postfix-expression ++
+ * postfix-expression --
+ * postfix-expression . identifier
+ * postfix-expression -> identifier
+ * @note: Not supported so far are:
+ * ( type-name ) { initializer-list }
+ * ( type-name ) { initializer-list , }
+ */
+static int vdScriptParsePostfixExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParsePrimaryExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ while (true)
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "++"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_POSTFIX_INCREMENT;
+ pExprNew->pExpr = pExpr;
+ pExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "--"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_POSTFIX_DECREMENT;
+ pExprNew->pExpr = pExpr;
+ pExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "->"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ PVDSCRIPTASTIDE pIde = NULL;
+ rc = vdScriptParseIde(pThis, &pIde);
+ if (RT_SUCCESS(rc))
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_POSTFIX_DEREFERENCE;
+ pExprNew->Deref.pIde = pIde;
+ pExprNew->Deref.pExpr = pExpr;
+ pExpr = pExprNew;
+ }
+ else
+ vdScriptAstNodeFree(&pExprNew->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "."))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ PVDSCRIPTASTIDE pIde = NULL;
+ rc = vdScriptParseIde(pThis, &pIde);
+ if (RT_SUCCESS(rc))
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_POSTFIX_DOT;
+ pExprNew->Deref.pIde = pIde;
+ pExprNew->Deref.pExpr = pExpr;
+ pExpr = pExprNew;
+ }
+ else
+ vdScriptAstNodeFree(&pExprNew->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_POSTFIX_FNCALL;
+ RTListInit(&pExprNew->FnCall.ListArgs);
+ if (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ rc = vdScriptParseFnCallArgumentList(pThis, pExprNew);
+ pExprNew->FnCall.pFnIde = pExpr;
+ pExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse an unary expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * unary-expression:
+ * postfix-expression
+ * ++ unary-expression
+ * -- unary-expression
+ * + cast-expression
+ * - cast-expression
+ * ~ cast-expression
+ * ! cast-expression
+ * & cast-expression
+ * * cast-expression
+ */
+static int vdScriptParseUnaryExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+ PVDSCRIPTASTEXPR pExprTop = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ /** @todo Think about a more beautiful way of parsing this. */
+ while (true)
+ {
+ bool fQuit = false;
+ bool fCastExprFollows = false;
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ VDSCRIPTEXPRTYPE enmType = VDSCRIPTEXPRTYPE_INVALID;
+
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "++"))
+ enmType = VDSCRIPTEXPRTYPE_UNARY_INCREMENT;
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "--"))
+ enmType = VDSCRIPTEXPRTYPE_UNARY_DECREMENT;
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "+"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_POSSIGN;
+ fCastExprFollows = true;
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "-"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_NEGSIGN;
+ fCastExprFollows = true;
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "~"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_INVERT;
+ fCastExprFollows = true;
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "!"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_NEGATE;
+ fCastExprFollows = true;
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "&"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_REFERENCE;
+ fCastExprFollows = true;
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "*"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_DEREFERENCE;
+ fCastExprFollows = true;
+ }
+
+ if (enmType != VDSCRIPTEXPRTYPE_INVALID)
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = enmType;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+
+ if ( RT_SUCCESS(rc)
+ && fCastExprFollows)
+ {
+ PVDSCRIPTASTEXPR pCastExpr = NULL;
+
+ rc = vdScriptParseCastExpression(pThis, &pCastExpr);
+ if (RT_SUCCESS(rc))
+ pExprNew->pExpr = pCastExpr;
+ else
+ vdScriptAstNodeFree(&pExprNew->Core);
+ fQuit = true;
+ }
+ }
+ else
+ {
+ /* Must be a postfix expression. */
+ rc = vdScriptParsePostfixExpression(pThis, &pExprNew);
+ fQuit = true;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (!pExprTop)
+ {
+ pExprTop = pExprNew;
+ pExpr = pExprNew;
+ }
+ else
+ {
+ pExpr->pExpr = pExprNew;
+ pExpr = pExprNew;
+ }
+ if (fQuit)
+ break;
+ }
+ else
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExprTop;
+ else if (pExprTop)
+ vdScriptAstNodeFree(&pExprTop->Core);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+#if 0 /* unused */
+/**
+ * Parse a storage class specifier.
+ *
+ * @returns nothing.
+ * @param pThis The script context.
+ * @param penmStorageClass Where to return the parsed storage classe.
+ * Contains VDSCRIPTASTSTORAGECLASS_INVALID if no
+ * valid storage class specifier was found.
+ *
+ * @note Syntax:
+ * typedef
+ * extern
+ * static
+ * auto
+ * register
+ */
+static void vdScriptParseStorageClassSpecifier(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSTORAGECLASS penmStorageClass)
+{
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_INVALID;
+
+ if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_TYPEDEF))
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_TYPEDEF;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_EXTERN))
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_EXTERN;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_STATIC))
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_STATIC;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_AUTO))
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_AUTO;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_REGISTER))
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_REGISTER;
+}
+#endif /* unused */
+
+#if 0 /* unused */
+/**
+ * Parse a type qualifier.
+ *
+ * @returns nothing.
+ * @param pThis The script context.
+ * @param penmTypeQualifier Where to return the parsed type qualifier.
+ * Contains VDSCRIPTASTTYPEQUALIFIER_INVALID if no
+ * valid type qualifier was found.
+ *
+ * @note Syntax:
+ * const
+ * restrict
+ * volatile
+ */
+static void vdScriptParseTypeQualifier(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTTYPEQUALIFIER penmTypeQualifier)
+{
+ *penmTypeQualifier = VDSCRIPTASTTYPEQUALIFIER_INVALID;
+
+ if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_CONST))
+ *penmTypeQualifier = VDSCRIPTASTTYPEQUALIFIER_CONST;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_RESTRICT))
+ *penmTypeQualifier = VDSCRIPTASTTYPEQUALIFIER_RESTRICT;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_VOLATILE))
+ *penmTypeQualifier = VDSCRIPTASTTYPEQUALIFIER_VOLATILE;
+}
+#endif /* unused */
+
+#if 0
+/**
+ * Parse a struct or union specifier.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstTypeSpec Where to store the type specifier AST node on success.
+ * @param enmTypeSpecifier The type specifier to identify whete this is a struct or a union.
+ */
+static int vdScriptParseStructOrUnionSpecifier(PVDSCRIPTCTXINT pThis, , enmTypeSpecifier)
+{
+ int rc = VINF_SUCCESS;
+
+ return rc;
+}
+
+/**
+ * Parse a type specifier.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstTypeSpec Where to store the type specifier AST node on success.
+ *
+ * @note Syntax:
+ * struct-or-union-specifier
+ * enum-specifier
+ * typedef-name (identifier: includes void, bool, uint8_t, int8_t, ... for basic integer types)
+ */
+static int vdScriptParseTypeSpecifier(PVDSCRIPTCTXINT pThis, )
+{
+ int rc = VINF_SUCCESS;
+
+ if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_STRUCT))
+ rc = vdScriptParseStructOrUnionSpecifier(pThis, , VDSCRIPTASTTYPESPECIFIER_STRUCT);
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_UNION))
+ rc = vdScriptParseStructOrUnionSpecifier(pThis, , VDSCRIPTASTTYPESPECIFIER_UNION);
+ else
+ {
+ PVDSCRIPTASTIDE pIde = NULL;
+
+ rc = vdScriptParseIde(pThis, &pIde);
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsgFailed(("TODO\n")); /* Parse identifier. */
+ }
+ }
+
+ return rc;
+}
+#endif
+
+/**
+ * Parse a cast expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * cast-expression:
+ * unary-expression
+ * ( type-name ) cast-expression
+ */
+static int vdScriptParseCastExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+#if 0
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTTYPE pTypeName = NULL;
+ rc = vdScriptParseTypeName(pThis, &pTypeName);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTEXPR pExpr = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExpr)
+ {
+ pExpr->enmType = VDSCRIPTEXPRTYPE_CAST;
+ rc = vdScriptParseCastExpression(pThis, &pExpr->Cast.pExpr); /** @todo Kill recursion. */
+ if (RT_SUCCESS(rc))
+ pExpr->Cast.pTypeName = pTypeName;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pTypeName->Core);
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ }
+ else
+#endif
+ rc = vdScriptParseUnaryExpression(pThis, ppAstNodeExpr);
+
+ return rc;
+}
+
+/**
+ * Parse a multiplicative expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * multiplicative-expression:
+ * cast-expression
+ * multiplicative-expression * cast-expression
+ * multiplicative-expression / cast-expression
+ * multiplicative-expression % cast-expression
+ */
+static int vdScriptParseMultiplicativeExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseCastExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ while (RT_SUCCESS(rc))
+ {
+ VDSCRIPTEXPRTYPE enmType = VDSCRIPTEXPRTYPE_INVALID;
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "*"))
+ enmType = VDSCRIPTEXPRTYPE_MULTIPLICATION;
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "/"))
+ enmType = VDSCRIPTEXPRTYPE_DIVISION;
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "%"))
+ enmType = VDSCRIPTEXPRTYPE_MODULUS;
+ else
+ break;
+
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = enmType;
+ else
+ {
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ break;
+ }
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseCastExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a additive expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * additive-expression:
+ * multiplicative-expression
+ * additive-expression + multiplicative-expression
+ * additive-expression - multiplicative-expression
+ */
+static int vdScriptParseAdditiveExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseMultiplicativeExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "+"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ADDITION;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "-"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_SUBTRACTION;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseMultiplicativeExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a shift expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * shift-expression:
+ * additive-expression
+ * shift-expression << additive-expression
+ * shift-expression >> additive-expression
+ */
+static int vdScriptParseShiftExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseAdditiveExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "<<"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LSL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, ">>"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LSR;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseAdditiveExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a relational expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * relational-expression:
+ * shift-expression
+ * relational-expression < shift-expression
+ * relational-expression > shift-expression
+ * relational-expression >= shift-expression
+ * relational-expression <= shift-expression
+ */
+static int vdScriptParseRelationalExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseShiftExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "<"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LOWER;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, ">"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_HIGHER;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, ">="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_HIGHEREQUAL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "<="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LOWEREQUAL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseShiftExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a equality expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * equality-expression:
+ * relational-expression
+ * equality-expression == relational-expression
+ * equality-expression != relational-expression
+ */
+static int vdScriptParseEqualityExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseRelationalExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "=="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_EQUAL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "!="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_NOTEQUAL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseRelationalExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a bitwise and expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * and-expression:
+ * equality-expression
+ * and-expression & equality-expression
+ */
+static int vdScriptParseBitwiseAndExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseEqualityExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "&"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_EQUAL;
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseEqualityExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a bitwise xor expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * xor-expression:
+ * and-expression
+ * xor-expression ^ equality-expression
+ */
+static int vdScriptParseBitwiseXorExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseBitwiseAndExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "^"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_BITWISE_XOR;
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseBitwiseAndExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a bitwise or expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * or-expression:
+ * xor-expression
+ * or-expression | xor-expression
+ */
+static int vdScriptParseBitwiseOrExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseBitwiseXorExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "|"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_BITWISE_OR;
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseBitwiseXorExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a logical and expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * logical-and-expression:
+ * or-expression
+ * logical-and-expression | or-expression
+ */
+static int vdScriptParseLogicalAndExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseBitwiseOrExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "&&"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LOGICAL_AND;
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseBitwiseOrExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a logical or expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * logical-or-expression:
+ * logical-and-expression
+ * logical-or-expression | logical-and-expression
+ */
+static int vdScriptParseLogicalOrExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseLogicalAndExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "||"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LOGICAL_OR;
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseLogicalAndExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a conditional expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note: VDScript doesn't support logical-or-expression ? expression : conditional-expression
+ * so a conditional expression is equal to a logical-or-expression.
+ */
+static int vdScriptParseCondExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ return vdScriptParseLogicalOrExpression(pThis, ppAstNodeExpr);
+}
+
+#if 0 /* unused */
+/**
+ * Parse a constant expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * constant-expression:
+ * conditional-expression
+ */
+static int vdScriptParseConstExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ return vdScriptParseCondExpression(pThis, ppAstNodeExpr);
+}
+#endif
+
+/**
+ * Parse an assignment expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * assignment-expression:
+ * conditional-expression
+ * unary-expression assignment-operator assignment-expression
+ */
+static int vdScriptParseAssignmentExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseLogicalOrExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "*="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_MULT;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "/="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_DIV;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "%="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_MOD;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "+="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_ADD;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "-="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_SUB;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "<<="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_LSL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, ">>="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_LSR;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "&="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_AND;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "^="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_XOR;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "|="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_OR;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseLogicalOrExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse an expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * expression:
+ * assignment-expression
+ * expression , assignment-expression
+ */
+static int vdScriptParseExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pAssignExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseAssignmentExpression(pThis, &pAssignExpr);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ','))
+ {
+ PVDSCRIPTASTEXPR pListAssignExpr = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pListAssignExpr)
+ {
+ pListAssignExpr->enmType = VDSCRIPTEXPRTYPE_ASSIGNMENT_LIST;
+ RTListInit(&pListAssignExpr->ListExpr);
+ RTListAppend(&pListAssignExpr->ListExpr, &pAssignExpr->Core.ListNode);
+ do
+ {
+ rc = vdScriptParseAssignmentExpression(pThis, &pAssignExpr);
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pListAssignExpr->ListExpr, &pAssignExpr->Core.ListNode);
+ } while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ','));
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pListAssignExpr->Core);
+ else
+ *ppAstNodeExpr = pListAssignExpr;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pAssignExpr;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse an if statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pAstNodeIf Uninitialized if AST node.
+ *
+ * @note The caller skipped the "if" token already.
+ */
+static int vdScriptParseIf(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTIF pAstNodeIf)
+{
+ int rc = VINF_SUCCESS;
+
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTEXPR pCondExpr = NULL;
+ rc = vdScriptParseExpression(pThis, &pCondExpr);
+ if (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTSTMT pStmt = NULL;
+ PVDSCRIPTASTSTMT pElseStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pStmt);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_ELSE))
+ rc = vdScriptParseStatement(pThis, &pElseStmt);
+
+ if (RT_SUCCESS(rc))
+ {
+ pAstNodeIf->pCond = pCondExpr;
+ pAstNodeIf->pTrueStmt = pStmt;
+ pAstNodeIf->pElseStmt = pElseStmt;
+ }
+ else if (pStmt)
+ vdScriptAstNodeFree(&pStmt->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pCondExpr->Core);
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\", got ...\n");
+
+ return rc;
+}
+
+/**
+ * Parse a switch statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pAstNodeSwitch Uninitialized switch AST node.
+ *
+ * @note The caller skipped the "switch" token already.
+ */
+static int vdScriptParseSwitch(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSWITCH pAstNodeSwitch)
+{
+ int rc = VINF_SUCCESS;
+
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ rc = vdScriptParseExpression(pThis, &pExpr);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTSTMT pStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pStmt);
+ if (RT_SUCCESS(rc))
+ {
+ pAstNodeSwitch->pCond = pExpr;
+ pAstNodeSwitch->pStmt = pStmt;
+ }
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\", got ...\n");
+
+ return rc;
+}
+
+/**
+ * Parse a while or do ... while statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pAstNodeWhile Uninitialized while AST node.
+ *
+ * @note The caller skipped the "while" or "do" token already.
+ */
+static int vdScriptParseWhile(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTWHILE pAstNodeWhile, bool fDoWhile)
+{
+ int rc = VINF_SUCCESS;
+
+ pAstNodeWhile->fDoWhile = fDoWhile;
+
+ if (fDoWhile)
+ {
+ PVDSCRIPTASTSTMT pStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pStmt);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_WHILE))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ rc = vdScriptParseExpression(pThis, &pExpr);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ {
+ pAstNodeWhile->pCond = pExpr;
+ pAstNodeWhile->pStmt = pStmt;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\", got ...\n");
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"while\", got ...\n");
+
+ if ( RT_FAILURE(rc)
+ && pStmt)
+ vdScriptAstNodeFree(&pStmt->Core);
+ }
+ else
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ rc = vdScriptParseExpression(pThis, &pExpr);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTSTMT pStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pStmt);
+ if (RT_SUCCESS(rc))
+ {
+ pAstNodeWhile->pCond = pExpr;
+ pAstNodeWhile->pStmt = pStmt;
+ }
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\", got ...\n");
+ }
+
+ return rc;
+}
+
+/**
+ * Parse a for statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pAstNodeFor Uninitialized for AST node.
+ *
+ * @note The caller skipped the "for" token already.
+ */
+static int vdScriptParseFor(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTFOR pAstNodeFor)
+{
+ int rc = VINF_SUCCESS;
+
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTEXPR pExprStart = NULL;
+ PVDSCRIPTASTEXPR pExprCond = NULL;
+ PVDSCRIPTASTEXPR pExpr3 = NULL;
+
+ rc = vdScriptParseExpression(pThis, &pExprStart);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ {
+ rc = vdScriptParseExpression(pThis, &pExprCond);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ {
+ rc = vdScriptParseExpression(pThis, &pExpr3);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTSTMT pStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pStmt);
+ if (RT_SUCCESS(rc))
+ {
+ pAstNodeFor->pExprStart = pExprStart;
+ pAstNodeFor->pExprCond = pExprCond;
+ pAstNodeFor->pExpr3 = pExpr3;
+ pAstNodeFor->pStmt = pStmt;
+ }
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+
+ if (RT_FAILURE(rc))
+ {
+ if (pExprStart)
+ vdScriptAstNodeFree(&pExprStart->Core);
+ if (pExprCond)
+ vdScriptAstNodeFree(&pExprCond->Core);
+ if (pExpr3)
+ vdScriptAstNodeFree(&pExpr3->Core);
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\", got ...\n");
+
+ return rc;
+}
+
+/**
+ * Parse a declaration.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeDecl Where to store the declaration AST node on success.
+ */
+static int vdScriptParseDeclaration(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTDECL *ppAstNodeDecl)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ RT_NOREF2(pThis, ppAstNodeDecl);
+ return rc;
+}
+
+/**
+ * Parse a statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeStmt Where to store the statement AST node on success.
+ */
+static int vdScriptParseStatement(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSTMT *ppAstNodeStmt)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Shortcut for a new compound statement. */
+ if (vdScriptTokenizerIsPunctuatorEqual(pThis->pTokenizer, '{'))
+ rc = vdScriptParseCompoundStatement(pThis, ppAstNodeStmt);
+ else
+ {
+ PVDSCRIPTASTSTMT pAstNodeStmt = (PVDSCRIPTASTSTMT)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_STATEMENT);
+
+ if (pAstNodeStmt)
+ {
+
+ if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_DEFAULT))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ':'))
+ {
+ PVDSCRIPTASTSTMT pAstNodeStmtDef = NULL;
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_DEFAULT;
+ rc = vdScriptParseStatement(pThis, &pAstNodeStmtDef);
+ if (RT_SUCCESS(rc))
+ pAstNodeStmt->pStmt = pAstNodeStmtDef;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \":\", got ...\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_CASE))
+ {
+ PVDSCRIPTASTEXPR pAstNodeExpr = NULL;
+ rc = vdScriptParseCondExpression(pThis, &pAstNodeExpr);
+ if (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ':'))
+ {
+ PVDSCRIPTASTSTMT pAstNodeCaseStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pAstNodeCaseStmt);
+ if (RT_SUCCESS(rc))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_CASE;
+ pAstNodeStmt->Case.pExpr = pAstNodeExpr;
+ pAstNodeStmt->Case.pStmt = pAstNodeCaseStmt;
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \":\", got ...\n");
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pAstNodeExpr->Core);
+ }
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_IF))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_IF;
+ rc = vdScriptParseIf(pThis, &pAstNodeStmt->If);
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_SWITCH))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_SWITCH;
+ rc = vdScriptParseSwitch(pThis, &pAstNodeStmt->Switch);
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_WHILE))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_WHILE;
+ rc = vdScriptParseWhile(pThis, &pAstNodeStmt->While, false /* fDoWhile */);
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_DO))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_WHILE;
+ rc = vdScriptParseWhile(pThis, &pAstNodeStmt->While, true /* fDoWhile */);
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_FOR))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_FOR;
+ rc = vdScriptParseFor(pThis, &pAstNodeStmt->For);
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_CONTINUE))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_CONTINUE;
+ if (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_BREAK))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_BREAK;
+ if (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_RETURN))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_RETURN;
+ if (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ {
+ rc = vdScriptParseExpression(pThis, &pAstNodeStmt->pExpr);
+ if ( RT_SUCCESS(rc)
+ && !vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+ else
+ pAstNodeStmt->pExpr = NULL; /* No expression for return. */
+ }
+ else
+ {
+ /* Must be an expression. */
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_EXPRESSION;
+ rc = vdScriptParseExpression(pThis, &pAstNodeStmt->pExpr);
+ if ( RT_SUCCESS(rc)
+ && !vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeStmt = pAstNodeStmt;
+ else
+ vdScriptAstNodeFree(&pAstNodeStmt->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory creating statement node\n");
+ }
+
+ return rc;
+}
+
+/**
+ * Parses a compound statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeCompound Where to store the compound AST node on success.
+ */
+static int vdScriptParseCompoundStatement(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSTMT *ppAstNodeCompound)
+{
+ int rc = VINF_SUCCESS;
+
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '{'))
+ {
+ PVDSCRIPTASTSTMT pAstNodeCompound = (PVDSCRIPTASTSTMT)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_STATEMENT);
+ if (pAstNodeCompound)
+ {
+ pAstNodeCompound->enmStmtType = VDSCRIPTSTMTTYPE_COMPOUND;
+ RTListInit(&pAstNodeCompound->Compound.ListDecls);
+ RTListInit(&pAstNodeCompound->Compound.ListStmts);
+ while (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '}'))
+ {
+ /*
+ * Check whether we have a declaration or a statement.
+ * For now we assume that 2 identifier tokens specify a declaration
+ * (type + variable name). Having two consecutive identifers is not possible
+ * for a statement.
+ */
+ if ( vdScriptTokenizerGetTokenClass(pThis->pTokenizer) == VDTOKENCLASS_IDENTIFIER
+ && vdScriptTokenizerPeekNextClass(pThis->pTokenizer) == VDTOKENCLASS_IDENTIFIER)
+ {
+ PVDSCRIPTASTDECL pAstNodeDecl = NULL;
+ rc = vdScriptParseDeclaration(pThis, &pAstNodeDecl);
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pAstNodeCompound->Compound.ListDecls, &pAstNodeDecl->Core.ListNode);
+ }
+ else
+ {
+ PVDSCRIPTASTSTMT pAstNodeStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pAstNodeStmt);
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pAstNodeCompound->Compound.ListStmts, &pAstNodeStmt->Core.ListNode);
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeCompound = pAstNodeCompound;
+ else
+ vdScriptAstNodeFree(&pAstNodeCompound->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory creating compound statement node\n");
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"{\" got...\n");
+
+
+ return rc;
+}
+
+/**
+ * Parses a function definition from the given tokenizer.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ */
+static int vdScriptParseAddFnDef(PVDSCRIPTCTXINT pThis)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTIDE pRetType = NULL;
+ PVDSCRIPTASTIDE pFnIde = NULL;
+
+ LogFlowFunc(("pThis=%p\n", pThis));
+
+ /* Put return type on the stack. */
+ rc = vdScriptParseIde(pThis, &pRetType);
+ if (RT_SUCCESS(rc))
+ {
+ /* Function name */
+ rc = vdScriptParseIde(pThis, &pFnIde);
+ if (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTFN pAstNodeFn = (PVDSCRIPTASTFN)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_FUNCTION);
+
+ if (pAstNodeFn)
+ {
+ pAstNodeFn->pFnIde = pFnIde;
+ pAstNodeFn->pRetType = pRetType;
+ RTListInit(&pAstNodeFn->ListArgs);
+
+ pFnIde = NULL;
+ pRetType = NULL;
+
+ /* Parse parameter list, create empty parameter list AST node and put it on the stack. */
+ while (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTIDE pArgType = NULL;
+ PVDSCRIPTASTIDE pArgIde = NULL;
+ /* Parse two identifiers, first one is the type, second the name. */
+ rc = vdScriptParseIde(pThis, &pArgType);
+ if (RT_SUCCESS(rc))
+ rc = vdScriptParseIde(pThis, &pArgIde);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTFNARG pAstNodeFnArg = (PVDSCRIPTASTFNARG)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_FUNCTIONARG);
+ if (pAstNodeFnArg)
+ {
+ pAstNodeFnArg->pArgIde = pArgIde;
+ pAstNodeFnArg->pType = pArgType;
+ RTListAppend(&pAstNodeFn->ListArgs, &pAstNodeFnArg->Core.ListNode);
+ pAstNodeFn->cArgs++;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating function argument AST node\n");
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pArgType)
+ vdScriptAstNodeFree(&pArgType->Core);
+ if (pArgIde)
+ vdScriptAstNodeFree(&pArgIde->Core);
+ }
+
+ if ( !vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ',')
+ && !vdScriptTokenizerIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \",\" or \")\" got...\n");
+ break;
+ }
+ }
+
+ /* Parse the compound or statement block now. */
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTSTMT pAstCompound = NULL;
+
+ rc = vdScriptParseCompoundStatement(pThis, &pAstCompound);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Link compound statement block to function AST node and add it to the
+ * list of functions.
+ */
+ pAstNodeFn->pCompoundStmts = pAstCompound;
+ RTListAppend(&pThis->ListAst, &pAstNodeFn->Core.ListNode);
+
+ PVDSCRIPTFN pFn = (PVDSCRIPTFN)RTMemAllocZ(sizeof(VDSCRIPTFN));
+ if (pFn)
+ {
+ pFn->Core.pszString = pAstNodeFn->pFnIde->aszIde;
+ pFn->Core.cchString = strlen(pFn->Core.pszString);
+ pFn->fExternal = false;
+ pFn->Type.Internal.pAstFn = pAstNodeFn;
+ /** @todo Parameters. */
+ RTStrSpaceInsert(&pThis->hStrSpaceFn, &pFn->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory allocating memory for function\n");
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pAstNodeFn->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating function AST node\n");
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\" got...\n");
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pRetType)
+ vdScriptAstNodeFree(&pRetType->Core);
+ if (pFnIde)
+ vdScriptAstNodeFree(&pFnIde->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parses the script from the given tokenizer.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ */
+static int vdScriptParseFromTokenizer(PVDSCRIPTCTXINT pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%p\n", pThis));
+
+ /* This is a very very simple LL(1) parser, don't expect much from it for now :). */
+ while ( RT_SUCCESS(rc)
+ && !vdScriptTokenizerIsEos(pThis->pTokenizer))
+ rc = vdScriptParseAddFnDef(pThis);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+DECLHIDDEN(int) VDScriptCtxCreate(PVDSCRIPTCTX phScriptCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("phScriptCtx=%p\n", phScriptCtx));
+
+ AssertPtrReturn(phScriptCtx, VERR_INVALID_POINTER);
+
+ PVDSCRIPTCTXINT pThis = (PVDSCRIPTCTXINT)RTMemAllocZ(sizeof(VDSCRIPTCTXINT));
+ if (pThis)
+ {
+ pThis->hStrSpaceFn = NULL;
+ RTListInit(&pThis->ListAst);
+ *phScriptCtx = pThis;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptCtxDestroyFnSpace(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ NOREF(pvUser);
+
+ /*
+ * Just free the whole structure, the AST for internal functions will be
+ * destroyed later.
+ */
+ RTMemFree(pStr);
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(void) VDScriptCtxDestroy(VDSCRIPTCTX hScriptCtx)
+{
+ PVDSCRIPTCTXINT pThis = hScriptCtx;
+
+ AssertPtrReturnVoid(pThis);
+
+ LogFlowFunc(("hScriptCtx=%p\n", pThis));
+
+ RTStrSpaceDestroy(&pThis->hStrSpaceFn, vdScriptCtxDestroyFnSpace, NULL);
+
+ /* Go through list of function ASTs and destroy them. */
+ PVDSCRIPTASTCORE pIter;
+ PVDSCRIPTASTCORE pIterNext;
+ RTListForEachSafe(&pThis->ListAst, pIter, pIterNext, VDSCRIPTASTCORE, ListNode)
+ {
+ RTListNodeRemove(&pIter->ListNode);
+ RTListInit(&pIter->ListNode);
+ vdScriptAstNodeFree(pIter);
+ }
+
+ RTMemFree(pThis);
+}
+
+DECLHIDDEN(int) VDScriptCtxCallbacksRegister(VDSCRIPTCTX hScriptCtx, PCVDSCRIPTCALLBACK paCallbacks,
+ unsigned cCallbacks, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTCTXINT pThis = hScriptCtx;
+
+ LogFlowFunc(("hScriptCtx=%p paCallbacks=%p cCallbacks=%u pvUser=%p\n",
+ pThis, paCallbacks, cCallbacks, pvUser));
+
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(paCallbacks, VERR_INVALID_POINTER);
+ AssertReturn(cCallbacks > 0, VERR_INVALID_PARAMETER);
+
+ /** @todo Unregister already registered callbacks in case of an error. */
+ do
+ {
+ PVDSCRIPTFN pFn = NULL;
+
+ if (RTStrSpaceGet(&pThis->hStrSpaceFn, paCallbacks->pszFnName))
+ {
+ rc = VERR_DUPLICATE;
+ break;
+ }
+
+ pFn = (PVDSCRIPTFN)RTMemAllocZ(RT_UOFFSETOF_DYN(VDSCRIPTFN, aenmArgTypes[paCallbacks->cArgs]));
+ if (!pFn)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /** @todo Validate argument and returns types. */
+ pFn->Core.pszString = paCallbacks->pszFnName;
+ pFn->Core.cchString = strlen(pFn->Core.pszString);
+ pFn->fExternal = true;
+ pFn->Type.External.pfnCallback = paCallbacks->pfnCallback;
+ pFn->Type.External.pvUser = pvUser;
+ pFn->enmTypeRetn = paCallbacks->enmTypeReturn;
+ pFn->cArgs = paCallbacks->cArgs;
+
+ for (unsigned i = 0; i < paCallbacks->cArgs; i++)
+ pFn->aenmArgTypes[i] = paCallbacks->paArgs[i];
+
+ RTStrSpaceInsert(&pThis->hStrSpaceFn, &pFn->Core);
+ cCallbacks--;
+ paCallbacks++;
+ }
+ while (cCallbacks);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+DECLHIDDEN(int) VDScriptCtxLoadScript(VDSCRIPTCTX hScriptCtx, const char *pszScript)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTCTXINT pThis = hScriptCtx;
+
+ LogFlowFunc(("hScriptCtx=%p pszScript=%p\n", pThis, pszScript));
+
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszScript, VERR_INVALID_POINTER);
+
+ PVDTOKENIZER pTokenizer = vdScriptTokenizerCreate(pszScript);
+ if (pTokenizer)
+ {
+ pThis->pTokenizer = pTokenizer;
+ rc = vdScriptParseFromTokenizer(pThis);
+ pThis->pTokenizer = NULL;
+ RTMemFree(pTokenizer);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+DECLHIDDEN(int) VDScriptCtxCallFn(VDSCRIPTCTX hScriptCtx, const char *pszFnCall,
+ PVDSCRIPTARG paArgs, unsigned cArgs)
+{
+ PVDSCRIPTCTXINT pThis = hScriptCtx;
+ VDSCRIPTARG Ret;
+ return vdScriptCtxInterprete(pThis, pszFnCall, paArgs, cArgs, &Ret);
+}
diff --git a/src/VBox/Storage/testcase/VDScript.h b/src/VBox/Storage/testcase/VDScript.h
new file mode 100644
index 00000000..6c9507ba
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScript.h
@@ -0,0 +1,236 @@
+/** @file
+ *
+ * VBox HDD container test utility - scripting engine.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDScript_h
+#define VBOX_INCLUDED_SRC_testcase_VDScript_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** Handle to the scripting context. */
+typedef struct VDSCRIPTCTXINT *VDSCRIPTCTX;
+/** Pointer to a scripting context handle. */
+typedef VDSCRIPTCTX *PVDSCRIPTCTX;
+
+/**
+ * Supprted primitive types in the scripting engine.
+ */
+typedef enum VDSCRIPTTYPE
+{
+ /** Invalid type, do not use. */
+ VDSCRIPTTYPE_INVALID = 0,
+ /** void type, used for no return value of methods. */
+ VDSCRIPTTYPE_VOID,
+ /** unsigned 8bit integer. */
+ VDSCRIPTTYPE_UINT8,
+ VDSCRIPTTYPE_INT8,
+ VDSCRIPTTYPE_UINT16,
+ VDSCRIPTTYPE_INT16,
+ VDSCRIPTTYPE_UINT32,
+ VDSCRIPTTYPE_INT32,
+ VDSCRIPTTYPE_UINT64,
+ VDSCRIPTTYPE_INT64,
+ VDSCRIPTTYPE_STRING,
+ VDSCRIPTTYPE_BOOL,
+ VDSCRIPTTYPE_POINTER,
+ /** As usual, the 32bit blowup hack. */
+ VDSCRIPTTYPE_32BIT_HACK = 0x7fffffff
+} VDSCRIPTTYPE;
+/** Pointer to a type. */
+typedef VDSCRIPTTYPE *PVDSCRIPTTYPE;
+/** Pointer to a const type. */
+typedef const VDSCRIPTTYPE *PCVDSCRIPTTYPE;
+
+/**
+ * Script argument.
+ */
+typedef struct VDSCRIPTARG
+{
+ /** Type of the argument. */
+ VDSCRIPTTYPE enmType;
+ /** Value */
+ union
+ {
+ uint8_t u8;
+ int8_t i8;
+ uint16_t u16;
+ int16_t i16;
+ uint32_t u32;
+ int32_t i32;
+ uint64_t u64;
+ int64_t i64;
+ const char *psz;
+ bool f;
+ void *p;
+ };
+} VDSCRIPTARG;
+/** Pointer to an argument. */
+typedef VDSCRIPTARG *PVDSCRIPTARG;
+
+/** Script callback. */
+typedef DECLCALLBACKTYPE(int, FNVDSCRIPTCALLBACK,(PVDSCRIPTARG paScriptArgs, void *pvUser));
+/** Pointer to a script callback. */
+typedef FNVDSCRIPTCALLBACK *PFNVDSCRIPTCALLBACK;
+
+/**
+ * Callback registration structure.
+ */
+typedef struct VDSCRIPTCALLBACK
+{
+ /** The function name. */
+ const char *pszFnName;
+ /** The return type of the function. */
+ VDSCRIPTTYPE enmTypeReturn;
+ /** Pointer to the array of argument types. */
+ PCVDSCRIPTTYPE paArgs;
+ /** Number of arguments this method takes. */
+ unsigned cArgs;
+ /** The callback handler. */
+ PFNVDSCRIPTCALLBACK pfnCallback;
+} VDSCRIPTCALLBACK;
+/** Pointer to a callback register entry. */
+typedef VDSCRIPTCALLBACK *PVDSCRIPTCALLBACK;
+/** Pointer to a const callback register entry. */
+typedef const VDSCRIPTCALLBACK *PCVDSCRIPTCALLBACK;
+
+/**
+ * @{
+ */
+/** The address space stays assigned to a variable
+ * even if the pointer is casted to another type.
+ */
+#define VDSCRIPT_AS_FLAGS_TRANSITIVE RT_BIT(0)
+/** @} */
+
+/**
+ * Address space read callback
+ *
+ * @returns VBox status code.
+ * @param pvUser Opaque user data given on registration.
+ * @param Address The address to read from, address is stored in the member for
+ * base type given on registration.
+ * @param pvBuf Where to store the read bits.
+ * @param cbRead How much to read.
+ */
+typedef DECLCALLBACKTYPE(int, FNVDSCRIPTASREAD,(void *pvUser, VDSCRIPTARG Address, void *pvBuf, size_t cbRead));
+/** Pointer to a read callback. */
+typedef FNVDSCRIPTASREAD *PFNVDSCRIPTASREAD;
+
+/**
+ * Address space write callback
+ *
+ * @returns VBox status code.
+ * @param pvUser Opaque user data given on registration.
+ * @param Address The address to write to, address is stored in the member for
+ * base type given on registration.
+ * @param pvBuf Data to write.
+ * @param cbWrite How much to write.
+ */
+typedef DECLCALLBACKTYPE(int, FNVDSCRIPTASWRITE,(void *pvUser, VDSCRIPTARG Address, const void *pvBuf, size_t cbWrite));
+/** Pointer to a write callback. */
+typedef FNVDSCRIPTASWRITE *PFNVDSCRIPTASWRITE;
+
+/**
+ * Create a new scripting context.
+ *
+ * @returns VBox status code.
+ * @param phScriptCtx Where to store the scripting context on success.
+ */
+DECLHIDDEN(int) VDScriptCtxCreate(PVDSCRIPTCTX phScriptCtx);
+
+/**
+ * Destroys the given scripting context.
+ *
+ * @returns nothing.
+ * @param hScriptCtx The script context to destroy.
+ */
+DECLHIDDEN(void) VDScriptCtxDestroy(VDSCRIPTCTX hScriptCtx);
+
+/**
+ * Register callbacks for the scripting context.
+ *
+ * @returns VBox status code.
+ * @param hScriptCtx The script context handle.
+ * @param paCallbacks Pointer to the callbacks to register.
+ * @param cCallbacks Number of callbacks in the array.
+ * @param pvUser Opaque user data to pass on the callback invocation.
+ */
+DECLHIDDEN(int) VDScriptCtxCallbacksRegister(VDSCRIPTCTX hScriptCtx, PCVDSCRIPTCALLBACK paCallbacks,
+ unsigned cCallbacks, void *pvUser);
+
+/**
+ * Load a given script into the context.
+ *
+ * @returns VBox status code.
+ * @param hScriptCtx The script context handle.
+ * @param pszScript Pointer to the char buffer containing the script.
+ */
+DECLHIDDEN(int) VDScriptCtxLoadScript(VDSCRIPTCTX hScriptCtx, const char *pszScript);
+
+/**
+ * Execute a given method in the script context.
+ *
+ * @returns VBox status code.
+ * @param hScriptCtx The script context handle.
+ * @param pszFnCall The method to call.
+ * @param paArgs Pointer to arguments to pass.
+ * @param cArgs Number of arguments.
+ */
+DECLHIDDEN(int) VDScriptCtxCallFn(VDSCRIPTCTX hScriptCtx, const char *pszFnCall,
+ PVDSCRIPTARG paArgs, unsigned cArgs);
+
+/**
+ * Registers a new address space provider.
+ *
+ * @returns VBox status code.
+ * @param hScriptCtx The script context handle.
+ * @param pszType The type string.
+ * @param enmBaseType The base integer type to use for the address space.
+ * Bool and String are not supported of course.
+ * @param pfnRead The read callback for the registered address space.
+ * @param pfnWrite The write callback for the registered address space.
+ * @param pvUser Opaque user data to pass to the read and write callbacks.
+ * @param fFlags Flags, see VDSCRIPT_AS_FLAGS_*.
+ *
+ * @note This will automatically register a new type with the identifier given in pszType
+ * used for the pointer. Every variable with this type is automatically treated as a pointer.
+ *
+ * @note If the transitive flag is set the address space stays assigned even if the pointer value
+ * is casted to another pointer type.
+ * In the following example the pointer pStruct will use the registered address space for RTGCPHYS
+ * and dereferencing the pointer causes the read/write callbacks to be triggered.
+ *
+ * ...
+ * Struct *pStruct = (Struct *)(RTGCPHYS)0x12345678;
+ * pStruct->count++;
+ * ...
+ */
+DECLHIDDEN(int) VDScriptCtxAsRegister(VDSCRIPTCTX hScriptCtx, const char *pszType, VDSCRIPTTYPE enmBaseType,
+ PFNVDSCRIPTASREAD pfnRead, PFNVDSCRIPTASWRITE pfnWrite, void *pvUser,
+ uint32_t fFlags);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDScript_h */
diff --git a/src/VBox/Storage/testcase/VDScriptAst.cpp b/src/VBox/Storage/testcase/VDScriptAst.cpp
new file mode 100644
index 00000000..3fae1dbd
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptAst.cpp
@@ -0,0 +1,368 @@
+/* $Id: VDScriptAst.cpp $ */
+/** @file
+ * VBox HDD container test utility - scripting engine AST node related functions.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#define LOGGROUP LOGGROUP_DEFAULT
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+
+#include "VDScriptAst.h"
+
+/**
+ * Put all child nodes of the given expression AST node onto the given to free list.
+ *
+ * @returns nothing.
+ * @param pList The free list to append everything to.
+ * @param pAstNode The expression node to free.
+ */
+static void vdScriptAstNodeExpressionPutOnFreeList(PRTLISTANCHOR pList, PVDSCRIPTASTCORE pAstNode)
+{
+ AssertMsgReturnVoid(pAstNode->enmClass == VDSCRIPTASTCLASS_EXPRESSION,
+ ("Given AST node is not a statement\n"));
+
+ PVDSCRIPTASTEXPR pExpr = (PVDSCRIPTASTEXPR)pAstNode;
+ switch (pExpr->enmType)
+ {
+ case VDSCRIPTEXPRTYPE_PRIMARY_NUMCONST:
+ case VDSCRIPTEXPRTYPE_PRIMARY_BOOLEAN:
+ break;
+ case VDSCRIPTEXPRTYPE_PRIMARY_STRINGCONST:
+ RTStrFree((char *)pExpr->pszStr);
+ break;
+ case VDSCRIPTEXPRTYPE_PRIMARY_IDENTIFIER:
+ {
+ RTListAppend(pList, &pExpr->pIde->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_ASSIGNMENT_LIST:
+ {
+ while (!RTListIsEmpty(&pExpr->ListExpr))
+ {
+ PVDSCRIPTASTCORE pNode = RTListGetFirst(&pExpr->ListExpr, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pNode->ListNode);
+ RTListAppend(pList, &pNode->ListNode);
+ }
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_POSTFIX_FNCALL:
+ {
+ RTListAppend(pList, &pExpr->FnCall.pFnIde->Core.ListNode);
+ while (!RTListIsEmpty(&pExpr->FnCall.ListArgs))
+ {
+ PVDSCRIPTASTCORE pNode = RTListGetFirst(&pExpr->FnCall.ListArgs, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pNode->ListNode);
+ RTListAppend(pList, &pNode->ListNode);
+ }
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_POSTFIX_DEREFERENCE:
+ case VDSCRIPTEXPRTYPE_POSTFIX_DOT:
+ {
+ RTListAppend(pList, &pExpr->Deref.pIde->Core.ListNode);
+ RTListAppend(pList, &pExpr->Deref.pExpr->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_POSTFIX_INCREMENT:
+ case VDSCRIPTEXPRTYPE_POSTFIX_DECREMENT:
+ case VDSCRIPTEXPRTYPE_UNARY_INCREMENT:
+ case VDSCRIPTEXPRTYPE_UNARY_DECREMENT:
+ case VDSCRIPTEXPRTYPE_UNARY_POSSIGN:
+ case VDSCRIPTEXPRTYPE_UNARY_NEGSIGN:
+ case VDSCRIPTEXPRTYPE_UNARY_INVERT:
+ case VDSCRIPTEXPRTYPE_UNARY_NEGATE:
+ case VDSCRIPTEXPRTYPE_UNARY_REFERENCE:
+ case VDSCRIPTEXPRTYPE_UNARY_DEREFERENCE:
+ {
+ RTListAppend(pList, &pExpr->pExpr->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_MULTIPLICATION:
+ case VDSCRIPTEXPRTYPE_DIVISION:
+ case VDSCRIPTEXPRTYPE_MODULUS:
+ case VDSCRIPTEXPRTYPE_ADDITION:
+ case VDSCRIPTEXPRTYPE_SUBTRACTION:
+ case VDSCRIPTEXPRTYPE_LSR:
+ case VDSCRIPTEXPRTYPE_LSL:
+ case VDSCRIPTEXPRTYPE_LOWER:
+ case VDSCRIPTEXPRTYPE_HIGHER:
+ case VDSCRIPTEXPRTYPE_LOWEREQUAL:
+ case VDSCRIPTEXPRTYPE_HIGHEREQUAL:
+ case VDSCRIPTEXPRTYPE_EQUAL:
+ case VDSCRIPTEXPRTYPE_NOTEQUAL:
+ case VDSCRIPTEXPRTYPE_BITWISE_AND:
+ case VDSCRIPTEXPRTYPE_BITWISE_XOR:
+ case VDSCRIPTEXPRTYPE_BITWISE_OR:
+ case VDSCRIPTEXPRTYPE_LOGICAL_AND:
+ case VDSCRIPTEXPRTYPE_LOGICAL_OR:
+ case VDSCRIPTEXPRTYPE_ASSIGN:
+ case VDSCRIPTEXPRTYPE_ASSIGN_MULT:
+ case VDSCRIPTEXPRTYPE_ASSIGN_DIV:
+ case VDSCRIPTEXPRTYPE_ASSIGN_MOD:
+ case VDSCRIPTEXPRTYPE_ASSIGN_ADD:
+ case VDSCRIPTEXPRTYPE_ASSIGN_SUB:
+ case VDSCRIPTEXPRTYPE_ASSIGN_LSL:
+ case VDSCRIPTEXPRTYPE_ASSIGN_LSR:
+ case VDSCRIPTEXPRTYPE_ASSIGN_AND:
+ case VDSCRIPTEXPRTYPE_ASSIGN_XOR:
+ case VDSCRIPTEXPRTYPE_ASSIGN_OR:
+ {
+ RTListAppend(pList, &pExpr->BinaryOp.pLeftExpr->Core.ListNode);
+ RTListAppend(pList, &pExpr->BinaryOp.pRightExpr->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_INVALID:
+ default:
+ AssertMsgFailedReturnVoid(("Invalid AST node expression type %d\n",
+ pExpr->enmType));
+ }
+}
+
+/**
+ * Free a given statement AST node and put everything on the given to free list.
+ *
+ * @returns nothing.
+ * @param pList The free list to append everything to.
+ * @param pAstNode The statement node to free.
+ */
+static void vdScriptAstNodeStatmentPutOnFreeList(PRTLISTANCHOR pList, PVDSCRIPTASTCORE pAstNode)
+{
+ AssertMsgReturnVoid(pAstNode->enmClass == VDSCRIPTASTCLASS_STATEMENT,
+ ("Given AST node is not a statement\n"));
+
+ PVDSCRIPTASTSTMT pStmt = (PVDSCRIPTASTSTMT)pAstNode;
+ switch (pStmt->enmStmtType)
+ {
+ case VDSCRIPTSTMTTYPE_COMPOUND:
+ {
+ /* Put all declarations on the to free list. */
+ while (!RTListIsEmpty(&pStmt->Compound.ListDecls))
+ {
+ PVDSCRIPTASTCORE pNode = RTListGetFirst(&pStmt->Compound.ListDecls, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pNode->ListNode);
+ RTListAppend(pList, &pNode->ListNode);
+ }
+
+ /* Put all statements on the to free list. */
+ while (!RTListIsEmpty(&pStmt->Compound.ListStmts))
+ {
+ PVDSCRIPTASTCORE pNode = RTListGetFirst(&pStmt->Compound.ListStmts, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pNode->ListNode);
+ RTListAppend(pList, &pNode->ListNode);
+ }
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_EXPRESSION:
+ {
+ if (pStmt->pExpr)
+ RTListAppend(pList, &pStmt->pExpr->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_IF:
+ {
+ RTListAppend(pList, &pStmt->If.pCond->Core.ListNode);
+ RTListAppend(pList, &pStmt->If.pTrueStmt->Core.ListNode);
+ if (pStmt->If.pElseStmt)
+ RTListAppend(pList, &pStmt->If.pElseStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_SWITCH:
+ {
+ RTListAppend(pList, &pStmt->Switch.pCond->Core.ListNode);
+ RTListAppend(pList, &pStmt->Switch.pStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_WHILE:
+ {
+ RTListAppend(pList, &pStmt->While.pCond->Core.ListNode);
+ RTListAppend(pList, &pStmt->While.pStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_FOR:
+ {
+ RTListAppend(pList, &pStmt->For.pExprStart->Core.ListNode);
+ RTListAppend(pList, &pStmt->For.pExprCond->Core.ListNode);
+ RTListAppend(pList, &pStmt->For.pExpr3->Core.ListNode);
+ RTListAppend(pList, &pStmt->For.pStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_RETURN:
+ {
+ if (pStmt->pExpr)
+ RTListAppend(pList, &pStmt->pExpr->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_CASE:
+ {
+ RTListAppend(pList, &pStmt->Case.pExpr->Core.ListNode);
+ RTListAppend(pList, &pStmt->Case.pStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_DEFAULT:
+ {
+ RTListAppend(pList, &pStmt->Case.pStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_CONTINUE:
+ case VDSCRIPTSTMTTYPE_BREAK:
+ break;
+ case VDSCRIPTSTMTTYPE_INVALID:
+ default:
+ AssertMsgFailedReturnVoid(("Invalid AST node statement type %d\n",
+ pStmt->enmStmtType));
+ }
+}
+
+DECLHIDDEN(void) vdScriptAstNodeFree(PVDSCRIPTASTCORE pAstNode)
+{
+ RTLISTANCHOR ListFree;
+
+ /*
+ * The node is not allowed to be part of a list because we need it
+ * for the nodes to free list.
+ */
+ Assert(RTListIsEmpty(&pAstNode->ListNode));
+ RTListInit(&ListFree);
+ RTListAppend(&ListFree, &pAstNode->ListNode);
+
+ do
+ {
+ pAstNode = RTListGetFirst(&ListFree, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pAstNode->ListNode);
+
+ switch (pAstNode->enmClass)
+ {
+ case VDSCRIPTASTCLASS_FUNCTION:
+ {
+ PVDSCRIPTASTFN pFn = (PVDSCRIPTASTFN)pAstNode;
+
+ if (pFn->pRetType)
+ RTListAppend(&ListFree, &pFn->pRetType->Core.ListNode);
+ if (pFn->pFnIde)
+ RTListAppend(&ListFree, &pFn->pFnIde->Core.ListNode);
+
+ /* Put argument list on the to free list. */
+ while (!RTListIsEmpty(&pFn->ListArgs))
+ {
+ PVDSCRIPTASTCORE pArg = RTListGetFirst(&pFn->ListArgs, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pArg->ListNode);
+ RTListAppend(&ListFree, &pArg->ListNode);
+ }
+
+ /* Put compound statement onto the list. */
+ RTListAppend(&ListFree, &pFn->pCompoundStmts->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTASTCLASS_FUNCTIONARG:
+ {
+ PVDSCRIPTASTFNARG pAstNodeArg = (PVDSCRIPTASTFNARG)pAstNode;
+ if (pAstNodeArg->pType)
+ RTListAppend(&ListFree, &pAstNodeArg->pType->Core.ListNode);
+ if (pAstNodeArg->pArgIde)
+ RTListAppend(&ListFree, &pAstNodeArg->pArgIde->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTASTCLASS_IDENTIFIER:
+ break;
+ case VDSCRIPTASTCLASS_DECLARATION:
+ case VDSCRIPTASTCLASS_TYPENAME:
+ {
+ AssertMsgFailed(("TODO\n"));
+ break;
+ }
+ case VDSCRIPTASTCLASS_STATEMENT:
+ {
+ vdScriptAstNodeStatmentPutOnFreeList(&ListFree, pAstNode);
+ break;
+ }
+ case VDSCRIPTASTCLASS_EXPRESSION:
+ {
+ vdScriptAstNodeExpressionPutOnFreeList(&ListFree, pAstNode);
+ break;
+ }
+ case VDSCRIPTASTCLASS_INVALID:
+ default:
+ AssertMsgFailedReturnVoid(("Invalid AST node class given %d\n", pAstNode->enmClass));
+ }
+
+ RTMemFree(pAstNode);
+ } while (!RTListIsEmpty(&ListFree));
+
+}
+
+DECLHIDDEN(PVDSCRIPTASTCORE) vdScriptAstNodeAlloc(VDSCRIPTASTCLASS enmClass)
+{
+ size_t cbAlloc = 0;
+
+ switch (enmClass)
+ {
+ case VDSCRIPTASTCLASS_FUNCTION:
+ cbAlloc = sizeof(VDSCRIPTASTFN);
+ break;
+ case VDSCRIPTASTCLASS_FUNCTIONARG:
+ cbAlloc = sizeof(VDSCRIPTASTFNARG);
+ break;
+ case VDSCRIPTASTCLASS_DECLARATION:
+ cbAlloc = sizeof(VDSCRIPTASTDECL);
+ break;
+ case VDSCRIPTASTCLASS_STATEMENT:
+ cbAlloc = sizeof(VDSCRIPTASTSTMT);
+ break;
+ case VDSCRIPTASTCLASS_EXPRESSION:
+ cbAlloc = sizeof(VDSCRIPTASTEXPR);
+ break;
+ case VDSCRIPTASTCLASS_TYPENAME:
+ cbAlloc = sizeof(VDSCRIPTASTTYPENAME);
+ break;
+ case VDSCRIPTASTCLASS_IDENTIFIER:
+ case VDSCRIPTASTCLASS_INVALID:
+ default:
+ AssertMsgFailedReturn(("Invalid AST node class given %d\n", enmClass), NULL);
+ }
+
+ PVDSCRIPTASTCORE pAstNode = (PVDSCRIPTASTCORE)RTMemAllocZ(cbAlloc);
+ if (pAstNode)
+ {
+ pAstNode->enmClass = enmClass;
+ RTListInit(&pAstNode->ListNode);
+ }
+
+ return pAstNode;
+}
+
+DECLHIDDEN(PVDSCRIPTASTIDE) vdScriptAstNodeIdeAlloc(size_t cchIde)
+{
+ PVDSCRIPTASTIDE pAstNode = (PVDSCRIPTASTIDE)RTMemAllocZ(RT_UOFFSETOF_DYN(VDSCRIPTASTIDE, aszIde[cchIde + 1]));
+ if (pAstNode)
+ {
+ pAstNode->Core.enmClass = VDSCRIPTASTCLASS_IDENTIFIER;
+ RTListInit(&pAstNode->Core.ListNode);
+ }
+
+ return pAstNode;
+}
diff --git a/src/VBox/Storage/testcase/VDScriptAst.h b/src/VBox/Storage/testcase/VDScriptAst.h
new file mode 100644
index 00000000..4fc78305
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptAst.h
@@ -0,0 +1,595 @@
+/** @file
+ * VBox HDD container test utility - scripting engine, AST related structures.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDScriptAst_h
+#define VBOX_INCLUDED_SRC_testcase_VDScriptAst_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/list.h>
+
+/**
+ * Position information.
+ */
+typedef struct VDSRCPOS
+{
+ /** Line in the source. */
+ unsigned iLine;
+ /** Current start character .*/
+ unsigned iChStart;
+ /** Current end character. */
+ unsigned iChEnd;
+} VDSRCPOS;
+/** Pointer to a source position. */
+typedef struct VDSRCPOS *PVDSRCPOS;
+
+/**
+ * AST node classes.
+ */
+typedef enum VDSCRIPTASTCLASS
+{
+ /** Invalid. */
+ VDSCRIPTASTCLASS_INVALID = 0,
+ /** Function node. */
+ VDSCRIPTASTCLASS_FUNCTION,
+ /** Function argument. */
+ VDSCRIPTASTCLASS_FUNCTIONARG,
+ /** Identifier node. */
+ VDSCRIPTASTCLASS_IDENTIFIER,
+ /** Declaration node. */
+ VDSCRIPTASTCLASS_DECLARATION,
+ /** Statement node. */
+ VDSCRIPTASTCLASS_STATEMENT,
+ /** Expression node. */
+ VDSCRIPTASTCLASS_EXPRESSION,
+ /** Type name node. */
+ VDSCRIPTASTCLASS_TYPENAME,
+ /** Type specifier node. */
+ VDSCRIPTASTCLASS_TYPESPECIFIER,
+ /** 32bit blowup. */
+ VDSCRIPTASTCLASS_32BIT_HACK = 0x7fffffff
+} VDSCRIPTASTCLASS;
+/** Pointer to an AST node class. */
+typedef VDSCRIPTASTCLASS *PVDSCRIPTASTCLASS;
+
+/**
+ * Core AST structure.
+ */
+typedef struct VDSCRIPTASTCORE
+{
+ /** The node class, used for verification. */
+ VDSCRIPTASTCLASS enmClass;
+ /** List which might be used. */
+ RTLISTNODE ListNode;
+ /** Position in the source file of this node. */
+ VDSRCPOS Pos;
+} VDSCRIPTASTCORE;
+/** Pointer to an AST core structure. */
+typedef VDSCRIPTASTCORE *PVDSCRIPTASTCORE;
+
+/** Pointer to an statement node - forward declaration. */
+typedef struct VDSCRIPTASTSTMT *PVDSCRIPTASTSTMT;
+/** Pointer to an expression node - forward declaration. */
+typedef struct VDSCRIPTASTEXPR *PVDSCRIPTASTEXPR;
+
+/**
+ * AST identifier node.
+ */
+typedef struct VDSCRIPTASTIDE
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Number of characters in the identifier, excluding the zero terminator. */
+ unsigned cchIde;
+ /** Identifier, variable size. */
+ char aszIde[1];
+} VDSCRIPTASTIDE;
+/** Pointer to an identifer node. */
+typedef VDSCRIPTASTIDE *PVDSCRIPTASTIDE;
+
+/**
+ * Type specifier.
+ */
+typedef enum VDSCRIPTASTTYPESPECIFIER
+{
+ /** Invalid type specifier. */
+ VDSCRIPTASTTYPESPECIFIER_INVALID = 0,
+ /** Union type specifier. */
+ VDSCRIPTASTTYPESPECIFIER_UNION,
+ /** Struct type specifier. */
+ VDSCRIPTASTTYPESPECIFIER_STRUCT,
+ /** Identifier of a typedefed type. */
+ VDSCRIPTASTTYPESPECIFIER_IDE,
+ /** 32bit hack. */
+ VDSCRIPTASTTYPESPECIFIER_32BIT_HACK = 0x7fffffff
+} VDSCRIPTASTTYPESPECIFIER;
+/** Pointer to a typespecifier. */
+typedef VDSCRIPTASTTYPESPECIFIER *PVDSCRIPTASTTYPESPECIFIER;
+
+/**
+ * AST type specifier.
+ */
+typedef struct VDSCRIPTASTTYPESPEC
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Specifier type. */
+ VDSCRIPTASTTYPESPECIFIER enmType;
+ /** Type dependent data .*/
+ union
+ {
+ /** Pointer to an identifier for typedefed types. */
+ PVDSCRIPTASTIDE pIde;
+ /** struct or union specifier. */
+ struct
+ {
+ /** Pointer to the identifier, optional. */
+ PVDSCRIPTASTIDE pIde;
+ /** Declaration list - VDSCRIPTAST. */
+ RTLISTANCHOR ListDecl;
+ } StructUnion;
+ };
+} VDSCRIPTASTTYPESPEC;
+/** Pointer to an AST type specifier. */
+typedef VDSCRIPTASTTYPESPEC *PVDSCRIPTASTTYPESPEC;
+
+/**
+ * Storage clase specifier.
+ */
+typedef enum VDSCRIPTASTSTORAGECLASS
+{
+ /** Invalid storage class sepcifier. */
+ VDSCRIPTASTSTORAGECLASS_INVALID = 0,
+ /** A typedef type. */
+ VDSCRIPTASTSTORAGECLASS_TYPEDEF,
+ /** An external declared object. */
+ VDSCRIPTASTSTORAGECLASS_EXTERN,
+ /** A static declared object. */
+ VDSCRIPTASTSTORAGECLASS_STATIC,
+ /** Auto object. */
+ VDSCRIPTASTSTORAGECLASS_AUTO,
+ /** Object should be stored in a register. */
+ VDSCRIPTASTSTORAGECLASS_REGISTER,
+ /** 32bit hack. */
+ VDSCRIPTASTSTORAGECLASS_32BIT_HACK = 0x7fffffff
+} VDSCRIPTASTSTORAGECLASS;
+/** Pointer to a storage class. */
+typedef VDSCRIPTASTSTORAGECLASS *PVDSCRIPTASTSTORAGECLASS;
+
+/**
+ * Type qualifier.
+ */
+typedef enum VDSCRIPTASTTYPEQUALIFIER
+{
+ /** Invalid type qualifier. */
+ VDSCRIPTASTTYPEQUALIFIER_INVALID = 0,
+ /** Const type qualifier. */
+ VDSCRIPTASTTYPEQUALIFIER_CONST,
+ /** Restrict type qualifier. */
+ VDSCRIPTASTTYPEQUALIFIER_RESTRICT,
+ /** Volatile type qualifier. */
+ VDSCRIPTASTTYPEQUALIFIER_VOLATILE,
+ /** 32bit hack. */
+ VDSCRIPTASTTYPEQUALIFIER_32BIT_HACK = 0x7fffffff
+} VDSCRIPTASTTYPEQUALIFIER;
+/** Pointer to a type qualifier. */
+typedef VDSCRIPTASTTYPEQUALIFIER *PVDSCRIPTASTTYPEQUALIFIER;
+
+/**
+ * AST type name node.
+ */
+typedef struct VDSCRIPTASTTYPENAME
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+} VDSCRIPTASTTYPENAME;
+/** Pointer to a type name node. */
+typedef VDSCRIPTASTTYPENAME *PVDSCRIPTASTTYPENAME;
+
+/**
+ * AST declaration node.
+ */
+typedef struct VDSCRIPTASTDECL
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** @todo */
+} VDSCRIPTASTDECL;
+/** Pointer to an declaration node. */
+typedef VDSCRIPTASTDECL *PVDSCRIPTASTDECL;
+
+/**
+ * Expression types.
+ */
+typedef enum VDSCRIPTEXPRTYPE
+{
+ /** Invalid. */
+ VDSCRIPTEXPRTYPE_INVALID = 0,
+ /** Numerical constant. */
+ VDSCRIPTEXPRTYPE_PRIMARY_NUMCONST,
+ /** String constant. */
+ VDSCRIPTEXPRTYPE_PRIMARY_STRINGCONST,
+ /** Boolean constant. */
+ VDSCRIPTEXPRTYPE_PRIMARY_BOOLEAN,
+ /** Identifier. */
+ VDSCRIPTEXPRTYPE_PRIMARY_IDENTIFIER,
+ /** List of assignment expressions as in a = b = c = ... . */
+ VDSCRIPTEXPRTYPE_ASSIGNMENT_LIST,
+ /** Postfix increment expression. */
+ VDSCRIPTEXPRTYPE_POSTFIX_INCREMENT,
+ /** Postfix decrement expression. */
+ VDSCRIPTEXPRTYPE_POSTFIX_DECREMENT,
+ /** Postfix function call expression. */
+ VDSCRIPTEXPRTYPE_POSTFIX_FNCALL,
+ /** Postfix dereference expression. */
+ VDSCRIPTEXPRTYPE_POSTFIX_DEREFERENCE,
+ /** Dot operator (@todo: Is there a better name for it?). */
+ VDSCRIPTEXPRTYPE_POSTFIX_DOT,
+ /** Unary increment expression. */
+ VDSCRIPTEXPRTYPE_UNARY_INCREMENT,
+ /** Unary decrement expression. */
+ VDSCRIPTEXPRTYPE_UNARY_DECREMENT,
+ /** Unary positive sign expression. */
+ VDSCRIPTEXPRTYPE_UNARY_POSSIGN,
+ /** Unary negtive sign expression. */
+ VDSCRIPTEXPRTYPE_UNARY_NEGSIGN,
+ /** Unary invert expression. */
+ VDSCRIPTEXPRTYPE_UNARY_INVERT,
+ /** Unary negate expression. */
+ VDSCRIPTEXPRTYPE_UNARY_NEGATE,
+ /** Unary reference expression. */
+ VDSCRIPTEXPRTYPE_UNARY_REFERENCE,
+ /** Unary dereference expression. */
+ VDSCRIPTEXPRTYPE_UNARY_DEREFERENCE,
+ /** Cast expression. */
+ VDSCRIPTEXPRTYPE_CAST,
+ /** Multiplicative expression. */
+ VDSCRIPTEXPRTYPE_MULTIPLICATION,
+ /** Division expression. */
+ VDSCRIPTEXPRTYPE_DIVISION,
+ /** Modulus expression. */
+ VDSCRIPTEXPRTYPE_MODULUS,
+ /** Addition expression. */
+ VDSCRIPTEXPRTYPE_ADDITION,
+ /** Subtraction expression. */
+ VDSCRIPTEXPRTYPE_SUBTRACTION,
+ /** Logical shift right. */
+ VDSCRIPTEXPRTYPE_LSR,
+ /** Logical shift left. */
+ VDSCRIPTEXPRTYPE_LSL,
+ /** Lower than expression */
+ VDSCRIPTEXPRTYPE_LOWER,
+ /** Higher than expression */
+ VDSCRIPTEXPRTYPE_HIGHER,
+ /** Lower or equal than expression */
+ VDSCRIPTEXPRTYPE_LOWEREQUAL,
+ /** Higher or equal than expression */
+ VDSCRIPTEXPRTYPE_HIGHEREQUAL,
+ /** Equals expression */
+ VDSCRIPTEXPRTYPE_EQUAL,
+ /** Not equal expression */
+ VDSCRIPTEXPRTYPE_NOTEQUAL,
+ /** Bitwise and expression */
+ VDSCRIPTEXPRTYPE_BITWISE_AND,
+ /** Bitwise xor expression */
+ VDSCRIPTEXPRTYPE_BITWISE_XOR,
+ /** Bitwise or expression */
+ VDSCRIPTEXPRTYPE_BITWISE_OR,
+ /** Logical and expression */
+ VDSCRIPTEXPRTYPE_LOGICAL_AND,
+ /** Logical or expression */
+ VDSCRIPTEXPRTYPE_LOGICAL_OR,
+ /** Assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN,
+ /** Multiplicative assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_MULT,
+ /** Division assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_DIV,
+ /** Modulus assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_MOD,
+ /** Additive assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_ADD,
+ /** Subtractive assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_SUB,
+ /** Bitwise left shift assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_LSL,
+ /** Bitwise right shift assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_LSR,
+ /** Bitwise and assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_AND,
+ /** Bitwise xor assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_XOR,
+ /** Bitwise or assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_OR,
+ /** 32bit hack. */
+ VDSCRIPTEXPRTYPE_32BIT_HACK = 0x7fffffff
+} VDSCRIPTEXPRTYPE;
+/** Pointer to an expression type. */
+typedef VDSCRIPTEXPRTYPE *PVDSCRIPTEXPRTYPE;
+
+/**
+ * AST expression node.
+ */
+typedef struct VDSCRIPTASTEXPR
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Expression type. */
+ VDSCRIPTEXPRTYPE enmType;
+ /** Expression type dependent data. */
+ union
+ {
+ /** Numerical constant. */
+ uint64_t u64;
+ /** Primary identifier. */
+ PVDSCRIPTASTIDE pIde;
+ /** String literal */
+ const char *pszStr;
+ /** Boolean constant. */
+ bool f;
+ /** List of expressions - VDSCRIPTASTEXPR. */
+ RTLISTANCHOR ListExpr;
+ /** Pointer to another expression. */
+ PVDSCRIPTASTEXPR pExpr;
+ /** Function call expression. */
+ struct
+ {
+ /** Other postfix expression used as the identifier for the function. */
+ PVDSCRIPTASTEXPR pFnIde;
+ /** Argument list if existing. */
+ RTLISTANCHOR ListArgs;
+ } FnCall;
+ /** Binary operation. */
+ struct
+ {
+ /** Left operator. */
+ PVDSCRIPTASTEXPR pLeftExpr;
+ /** Right operator. */
+ PVDSCRIPTASTEXPR pRightExpr;
+ } BinaryOp;
+ /** Dereference or dot operation. */
+ struct
+ {
+ /** The identifier to access. */
+ PVDSCRIPTASTIDE pIde;
+ /** Postfix expression coming after this. */
+ PVDSCRIPTASTEXPR pExpr;
+ } Deref;
+ /** Cast expression. */
+ struct
+ {
+ /** Type name. */
+ PVDSCRIPTASTTYPENAME pTypeName;
+ /** Following cast expression. */
+ PVDSCRIPTASTEXPR pExpr;
+ } Cast;
+ };
+} VDSCRIPTASTEXPR;
+
+/**
+ * AST if node.
+ */
+typedef struct VDSCRIPTASTIF
+{
+ /** Conditional expression. */
+ PVDSCRIPTASTEXPR pCond;
+ /** The true branch */
+ PVDSCRIPTASTSTMT pTrueStmt;
+ /** The else branch, can be NULL if no else branch. */
+ PVDSCRIPTASTSTMT pElseStmt;
+} VDSCRIPTASTIF;
+/** Pointer to an expression node. */
+typedef VDSCRIPTASTIF *PVDSCRIPTASTIF;
+
+/**
+ * AST switch node.
+ */
+typedef struct VDSCRIPTASTSWITCH
+{
+ /** Conditional expression. */
+ PVDSCRIPTASTEXPR pCond;
+ /** The statement to follow. */
+ PVDSCRIPTASTSTMT pStmt;
+} VDSCRIPTASTSWITCH;
+/** Pointer to an expression node. */
+typedef VDSCRIPTASTSWITCH *PVDSCRIPTASTSWITCH;
+
+/**
+ * AST while or do ... while node.
+ */
+typedef struct VDSCRIPTASTWHILE
+{
+ /** Flag whether this is a do while loop. */
+ bool fDoWhile;
+ /** Conditional expression. */
+ PVDSCRIPTASTEXPR pCond;
+ /** The statement to follow. */
+ PVDSCRIPTASTSTMT pStmt;
+} VDSCRIPTASTWHILE;
+/** Pointer to an expression node. */
+typedef VDSCRIPTASTWHILE *PVDSCRIPTASTWHILE;
+
+/**
+ * AST for node.
+ */
+typedef struct VDSCRIPTASTFOR
+{
+ /** Initializer expression. */
+ PVDSCRIPTASTEXPR pExprStart;
+ /** The exit condition. */
+ PVDSCRIPTASTEXPR pExprCond;
+ /** The third expression (normally used to increase/decrease loop variable). */
+ PVDSCRIPTASTEXPR pExpr3;
+ /** The for loop body. */
+ PVDSCRIPTASTSTMT pStmt;
+} VDSCRIPTASTFOR;
+/** Pointer to an expression node. */
+typedef VDSCRIPTASTFOR *PVDSCRIPTASTFOR;
+
+/**
+ * Statement types.
+ */
+typedef enum VDSCRIPTSTMTTYPE
+{
+ /** Invalid. */
+ VDSCRIPTSTMTTYPE_INVALID = 0,
+ /** Compound statement. */
+ VDSCRIPTSTMTTYPE_COMPOUND,
+ /** Expression statement. */
+ VDSCRIPTSTMTTYPE_EXPRESSION,
+ /** if statement. */
+ VDSCRIPTSTMTTYPE_IF,
+ /** switch statement. */
+ VDSCRIPTSTMTTYPE_SWITCH,
+ /** while statement. */
+ VDSCRIPTSTMTTYPE_WHILE,
+ /** for statement. */
+ VDSCRIPTSTMTTYPE_FOR,
+ /** continue statement. */
+ VDSCRIPTSTMTTYPE_CONTINUE,
+ /** break statement. */
+ VDSCRIPTSTMTTYPE_BREAK,
+ /** return statement. */
+ VDSCRIPTSTMTTYPE_RETURN,
+ /** case statement. */
+ VDSCRIPTSTMTTYPE_CASE,
+ /** default statement. */
+ VDSCRIPTSTMTTYPE_DEFAULT,
+ /** 32bit hack. */
+ VDSCRIPTSTMTTYPE_32BIT_HACK = 0x7fffffff
+} VDSCRIPTSTMTTYPE;
+/** Pointer to a statement type. */
+typedef VDSCRIPTSTMTTYPE *PVDSCRIPTSTMTTYPE;
+
+/**
+ * AST statement node.
+ */
+typedef struct VDSCRIPTASTSTMT
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Statement type */
+ VDSCRIPTSTMTTYPE enmStmtType;
+ /** Statement type dependent data. */
+ union
+ {
+ /** Compound statement. */
+ struct
+ {
+ /** List of declarations - VDSCRIPTASTDECL. */
+ RTLISTANCHOR ListDecls;
+ /** List of statements - VDSCRIPTASTSTMT. */
+ RTLISTANCHOR ListStmts;
+ } Compound;
+ /** case, default statement. */
+ struct
+ {
+ /** Pointer to the expression. */
+ PVDSCRIPTASTEXPR pExpr;
+ /** Pointer to the statement. */
+ PVDSCRIPTASTSTMT pStmt;
+ } Case;
+ /** "if" statement. */
+ VDSCRIPTASTIF If;
+ /** "switch" statement. */
+ VDSCRIPTASTSWITCH Switch;
+ /** "while" or "do ... while" loop. */
+ VDSCRIPTASTWHILE While;
+ /** "for" loop. */
+ VDSCRIPTASTFOR For;
+ /** Pointer to another statement. */
+ PVDSCRIPTASTSTMT pStmt;
+ /** Expression statement. */
+ PVDSCRIPTASTEXPR pExpr;
+ };
+} VDSCRIPTASTSTMT;
+
+/**
+ * AST node for one function argument.
+ */
+typedef struct VDSCRIPTASTFNARG
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Identifier describing the type of the argument. */
+ PVDSCRIPTASTIDE pType;
+ /** The name of the argument. */
+ PVDSCRIPTASTIDE pArgIde;
+} VDSCRIPTASTFNARG;
+/** Pointer to a AST function argument node. */
+typedef VDSCRIPTASTFNARG *PVDSCRIPTASTFNARG;
+
+/**
+ * AST node describing a function.
+ */
+typedef struct VDSCRIPTASTFN
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Identifier describing the return type. */
+ PVDSCRIPTASTIDE pRetType;
+ /** Name of the function. */
+ PVDSCRIPTASTIDE pFnIde;
+ /** Number of arguments in the list. */
+ unsigned cArgs;
+ /** Argument list - VDSCRIPTASTFNARG. */
+ RTLISTANCHOR ListArgs;
+ /** Compound statement node. */
+ PVDSCRIPTASTSTMT pCompoundStmts;
+} VDSCRIPTASTFN;
+/** Pointer to a function AST node. */
+typedef VDSCRIPTASTFN *PVDSCRIPTASTFN;
+
+/**
+ * Free the given AST node and all subsequent nodes pointed to
+ * by the given node.
+ *
+ * @returns nothing.
+ * @param pAstNode The AST node to free.
+ */
+DECLHIDDEN(void) vdScriptAstNodeFree(PVDSCRIPTASTCORE pAstNode);
+
+/**
+ * Allocate a non variable in size AST node of the given class.
+ *
+ * @returns Pointer to the allocated node.
+ * NULL if out of memory.
+ * @param enmClass The class of the AST node.
+ */
+DECLHIDDEN(PVDSCRIPTASTCORE) vdScriptAstNodeAlloc(VDSCRIPTASTCLASS enmClass);
+
+/**
+ * Allocate a IDE node which can hold the given number of characters.
+ *
+ * @returns Pointer to the allocated node.
+ * NULL if out of memory.
+ * @param cchIde Number of characters which can be stored in the node.
+ */
+DECLHIDDEN(PVDSCRIPTASTIDE) vdScriptAstNodeIdeAlloc(size_t cchIde);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDScriptAst_h */
+
diff --git a/src/VBox/Storage/testcase/VDScriptChecker.cpp b/src/VBox/Storage/testcase/VDScriptChecker.cpp
new file mode 100644
index 00000000..2989afd6
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptChecker.cpp
@@ -0,0 +1,44 @@
+/* $Id: VDScriptChecker.cpp $ */
+/** @file
+ * VBox HDD container test utility - scripting engine, type and context checker.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOGGROUP LOGGROUP_DEFAULT
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+
+#include <VBox/log.h>
+
+#include "VDScriptAst.h"
+#include "VDScriptInternal.h"
+
+
+DECLHIDDEN(int) vdScriptCtxCheck(PVDSCRIPTCTXINT pThis)
+{
+ RT_NOREF1(pThis);
+ return VERR_NOT_IMPLEMENTED;
+}
diff --git a/src/VBox/Storage/testcase/VDScriptInternal.h b/src/VBox/Storage/testcase/VDScriptInternal.h
new file mode 100644
index 00000000..63417113
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptInternal.h
@@ -0,0 +1,121 @@
+/** @file
+ *
+ * VBox HDD container test utility - scripting engine, internal script structures.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDScriptInternal_h
+#define VBOX_INCLUDED_SRC_testcase_VDScriptInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/list.h>
+#include <iprt/string.h>
+
+#include "VDScript.h"
+
+/**
+ * Script function which can be called.
+ */
+typedef struct VDSCRIPTFN
+{
+ /** String space core. */
+ RTSTRSPACECORE Core;
+ /** Flag whether function is defined in the source or was
+ * registered from the outside. */
+ bool fExternal;
+ /** Flag dependent data. */
+ union
+ {
+ /** Data for functions defined in the source. */
+ struct
+ {
+ /** Pointer to the AST defining the function. */
+ PVDSCRIPTASTFN pAstFn;
+ } Internal;
+ /** Data for external defined functions. */
+ struct
+ {
+ /** Callback function. */
+ PFNVDSCRIPTCALLBACK pfnCallback;
+ /** Opaque user data. */
+ void *pvUser;
+ } External;
+ } Type;
+ /** Return type of the function. */
+ VDSCRIPTTYPE enmTypeRetn;
+ /** Number of arguments the function takes. */
+ unsigned cArgs;
+ /** Variable sized array of argument types. */
+ VDSCRIPTTYPE aenmArgTypes[1];
+} VDSCRIPTFN;
+/** Pointer to a script function registration structure. */
+typedef VDSCRIPTFN *PVDSCRIPTFN;
+
+/** Pointer to a tokenize state. */
+typedef struct VDTOKENIZER *PVDTOKENIZER;
+
+/**
+ * Script context.
+ */
+typedef struct VDSCRIPTCTXINT
+{
+ /** String space of external registered and source defined functions. */
+ RTSTRSPACE hStrSpaceFn;
+ /** List of ASTs for functions - VDSCRIPTASTFN. */
+ RTLISTANCHOR ListAst;
+ /** Pointer to the current tokenizer state. */
+ PVDTOKENIZER pTokenizer;
+} VDSCRIPTCTXINT;
+/** Pointer to a script context. */
+typedef VDSCRIPTCTXINT *PVDSCRIPTCTXINT;
+
+/**
+ * Check the context for type correctness.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ */
+DECLHIDDEN(int) vdScriptCtxCheck(PVDSCRIPTCTXINT pThis);
+
+/**
+ * Interprete a given function AST. The executed functions
+ * must be type correct, otherwise the behavior is undefined
+ * (Will assert in debug builds).
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pszFn The function name to interprete.
+ * @param paArgs Arguments to pass to the function.
+ * @param cArgs Number of arguments.
+ * @param pRet Where to store the return value on success.
+ *
+ * @note: The AST is not modified in any way during the interpretation process.
+ */
+DECLHIDDEN(int) vdScriptCtxInterprete(PVDSCRIPTCTXINT pThis, const char *pszFn,
+ PVDSCRIPTARG paArgs, unsigned cArgs,
+ PVDSCRIPTARG pRet);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDScriptInternal_h */
diff --git a/src/VBox/Storage/testcase/VDScriptInterp.cpp b/src/VBox/Storage/testcase/VDScriptInterp.cpp
new file mode 100644
index 00000000..e0faedda
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptInterp.cpp
@@ -0,0 +1,1073 @@
+/* $Id: VDScriptInterp.cpp $ */
+/** @file
+ * VBox HDD container test utility - scripting engine, interpreter.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOGGROUP LOGGROUP_DEFAULT
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+
+#include "VDScriptAst.h"
+#include "VDScriptStack.h"
+#include "VDScriptInternal.h"
+
+/**
+ * Interpreter variable.
+ */
+typedef struct VDSCRIPTINTERPVAR
+{
+ /** String space core. */
+ RTSTRSPACECORE Core;
+ /** Value. */
+ VDSCRIPTARG Value;
+} VDSCRIPTINTERPVAR;
+/** Pointer to an interpreter variable. */
+typedef VDSCRIPTINTERPVAR *PVDSCRIPTINTERPVAR;
+
+/**
+ * Block scope.
+ */
+typedef struct VDSCRIPTINTERPSCOPE
+{
+ /** Pointer to the enclosing scope if available. */
+ struct VDSCRIPTINTERPSCOPE *pParent;
+ /** String space of declared variables in this scope. */
+ RTSTRSPACE hStrSpaceVar;
+} VDSCRIPTINTERPSCOPE;
+/** Pointer to a scope block. */
+typedef VDSCRIPTINTERPSCOPE *PVDSCRIPTINTERPSCOPE;
+
+/**
+ * Function call.
+ */
+typedef struct VDSCRIPTINTERPFNCALL
+{
+ /** Pointer to the caller of this function. */
+ struct VDSCRIPTINTERPFNCALL *pCaller;
+ /** Root scope of this function. */
+ VDSCRIPTINTERPSCOPE ScopeRoot;
+ /** Current scope in this function. */
+ PVDSCRIPTINTERPSCOPE pScopeCurr;
+} VDSCRIPTINTERPFNCALL;
+/** Pointer to a function call. */
+typedef VDSCRIPTINTERPFNCALL *PVDSCRIPTINTERPFNCALL;
+
+/**
+ * Interpreter context.
+ */
+typedef struct VDSCRIPTINTERPCTX
+{
+ /** Pointer to the script context. */
+ PVDSCRIPTCTXINT pScriptCtx;
+ /** Current function call entry. */
+ PVDSCRIPTINTERPFNCALL pFnCallCurr;
+ /** Stack of calculated values. */
+ VDSCRIPTSTACK StackValues;
+ /** Evaluation control stack. */
+ VDSCRIPTSTACK StackCtrl;
+} VDSCRIPTINTERPCTX;
+/** Pointer to an interpreter context. */
+typedef VDSCRIPTINTERPCTX *PVDSCRIPTINTERPCTX;
+
+/**
+ * Interpreter control type.
+ */
+typedef enum VDSCRIPTINTERPCTRLTYPE
+{
+ VDSCRIPTINTERPCTRLTYPE_INVALID = 0,
+ /** Function call to evaluate now, all values are computed
+ * and are stored on the value stack.
+ */
+ VDSCRIPTINTERPCTRLTYPE_FN_CALL,
+ /** Cleanup the function call, deleting the scope and restoring the previous one. */
+ VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP,
+ /** If statement to evaluate now, the guard is on the stack. */
+ VDSCRIPTINTERPCTRLTYPE_IF,
+ /** While or for statement. */
+ VDSCRIPTINTERPCTRLTYPE_LOOP,
+ /** switch statement. */
+ VDSCRIPTINTERPCTRLTYPE_SWITCH,
+ /** Compound statement. */
+ VDSCRIPTINTERPCTRLTYPE_COMPOUND,
+ /** 32bit blowup. */
+ VDSCRIPTINTERPCTRLTYPE_32BIT_HACK = 0x7fffffff
+} VDSCRIPTINTERPCTRLTYPE;
+/** Pointer to a control type. */
+typedef VDSCRIPTINTERPCTRLTYPE *PVDSCRIPTINTERPCTRLTYPE;
+
+/**
+ * Interpreter stack control entry.
+ */
+typedef struct VDSCRIPTINTERPCTRL
+{
+ /** Flag whether this entry points to an AST node to evaluate. */
+ bool fEvalAst;
+ /** Flag dependent data. */
+ union
+ {
+ /** Pointer to the AST node to interprete. */
+ PVDSCRIPTASTCORE pAstNode;
+ /** Interpreter control structure. */
+ struct
+ {
+ /** Type of control. */
+ VDSCRIPTINTERPCTRLTYPE enmCtrlType;
+ /** Function call data. */
+ struct
+ {
+ /** Function to call. */
+ PVDSCRIPTFN pFn;
+ } FnCall;
+ /** Compound statement. */
+ struct
+ {
+ /** The compound statement node. */
+ PVDSCRIPTASTSTMT pStmtCompound;
+ /** Current statement evaluated. */
+ PVDSCRIPTASTSTMT pStmtCurr;
+ } Compound;
+ /** Pointer to an AST node. */
+ PVDSCRIPTASTCORE pAstNode;
+ } Ctrl;
+ };
+} VDSCRIPTINTERPCTRL;
+/** Pointer to an exec stack control entry. */
+typedef VDSCRIPTINTERPCTRL *PVDSCRIPTINTERPCTRL;
+
+/**
+ * Record an error while interpreting.
+ *
+ * @returns VBox status code passed.
+ * @param pThis The interpreter context.
+ * @param rc The status code to record.
+ * @param RT_SRC_POS Position in the source code.
+ * @param pszFmt Format string.
+ */
+static int vdScriptInterpreterError(PVDSCRIPTINTERPCTX pThis, int rc, RT_SRC_POS_DECL, const char *pszFmt, ...)
+{
+ RT_NOREF1(pThis); RT_SRC_POS_NOREF();
+ va_list va;
+ va_start(va, pszFmt);
+ RTPrintfV(pszFmt, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Pops the topmost value from the value stack.
+ *
+ * @returns nothing.
+ * @param pThis The interpreter context.
+ * @param pVal Where to store the value.
+ */
+DECLINLINE(void) vdScriptInterpreterPopValue(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTARG pVal)
+{
+ PVDSCRIPTARG pValStack = (PVDSCRIPTARG)vdScriptStackGetUsed(&pThis->StackValues);
+ if (!pValStack)
+ {
+ RT_ZERO(*pVal);
+ AssertPtrReturnVoid(pValStack);
+ }
+
+ *pVal = *pValStack;
+ vdScriptStackPop(&pThis->StackValues);
+}
+
+/**
+ * Pushes a given value onto the value stack.
+ */
+DECLINLINE(int) vdScriptInterpreterPushValue(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTARG pVal)
+{
+ PVDSCRIPTARG pValStack = (PVDSCRIPTARG)vdScriptStackGetUnused(&pThis->StackValues);
+ if (!pValStack)
+ return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory pushing a value on the value stack");
+
+ *pValStack = *pVal;
+ vdScriptStackPush(&pThis->StackValues);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Pushes an AST node onto the control stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param enmCtrlType The control entry type.
+ */
+DECLINLINE(int) vdScriptInterpreterPushAstEntry(PVDSCRIPTINTERPCTX pThis,
+ PVDSCRIPTASTCORE pAstNode)
+{
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = true;
+ pCtrl->pAstNode = pAstNode;
+ vdScriptStackPush(&pThis->StackCtrl);
+ return VINF_SUCCESS;
+ }
+
+ return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+}
+
+/**
+ * Pushes a control entry without additional data onto the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param enmCtrlType The control entry type.
+ */
+DECLINLINE(int) vdScriptInterpreterPushNonDataCtrlEntry(PVDSCRIPTINTERPCTX pThis,
+ VDSCRIPTINTERPCTRLTYPE enmCtrlType)
+{
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = false;
+ pCtrl->Ctrl.enmCtrlType = enmCtrlType;
+ vdScriptStackPush(&pThis->StackCtrl);
+ return VINF_SUCCESS;
+ }
+
+ return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+}
+
+/**
+ * Pushes a compound statement control entry onto the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pStmtFirst The first statement of the compound statement
+ */
+DECLINLINE(int) vdScriptInterpreterPushCompoundCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
+{
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = false;
+ pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_COMPOUND;
+ pCtrl->Ctrl.Compound.pStmtCompound = pStmt;
+ pCtrl->Ctrl.Compound.pStmtCurr = RTListGetFirst(&pStmt->Compound.ListStmts, VDSCRIPTASTSTMT, Core.ListNode);
+ vdScriptStackPush(&pThis->StackCtrl);
+ return VINF_SUCCESS;
+ }
+
+ return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+}
+
+/**
+ * Pushes a while statement control entry onto the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pStmt The while statement.
+ */
+DECLINLINE(int) vdScriptInterpreterPushWhileCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = false;
+ pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_LOOP;
+ pCtrl->Ctrl.pAstNode = &pStmt->Core;
+ vdScriptStackPush(&pThis->StackCtrl);
+
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->While.pCond->Core);
+ if ( RT_SUCCESS(rc)
+ && pStmt->While.fDoWhile)
+ {
+ /* Push the statement to execute for do ... while loops because they run at least once. */
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->While.pStmt->Core);
+ if (RT_FAILURE(rc))
+ {
+ /* Cleanup while control statement and AST node. */
+ vdScriptStackPop(&pThis->StackCtrl);
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+ }
+ else if (RT_FAILURE(rc))
+ vdScriptStackPop(&pThis->StackCtrl); /* Cleanup the while control statement. */
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+
+ return rc;
+}
+
+/**
+ * Pushes an if statement control entry onto the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pStmt The if statement.
+ */
+static int vdScriptInterpreterPushIfCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = false;
+ pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_IF;
+ pCtrl->Ctrl.pAstNode = &pStmt->Core;
+ vdScriptStackPush(&pThis->StackCtrl);
+
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->If.pCond->Core);
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+
+ return rc;
+}
+
+/**
+ * Pushes a for statement control entry onto the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pStmt The while statement.
+ */
+DECLINLINE(int) vdScriptInterpreterPushForCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = false;
+ pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_LOOP;
+ pCtrl->Ctrl.pAstNode = &pStmt->Core;
+ vdScriptStackPush(&pThis->StackCtrl);
+
+ /* Push the conditional first and the initializer .*/
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->For.pExprCond->Core);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->For.pExprStart->Core);
+ if (RT_FAILURE(rc))
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+
+ return rc;
+}
+
+/**
+ * Destroy variable string space callback.
+ */
+static DECLCALLBACK(int) vdScriptInterpreterVarSpaceDestroy(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ RT_NOREF1(pvUser);
+ RTMemFree(pStr);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Setsup a new scope in the current function call.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ */
+static int vdScriptInterpreterScopeCreate(PVDSCRIPTINTERPCTX pThis)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTINTERPSCOPE pScope = (PVDSCRIPTINTERPSCOPE)RTMemAllocZ(sizeof(VDSCRIPTINTERPSCOPE));
+ if (pScope)
+ {
+ pScope->pParent = pThis->pFnCallCurr->pScopeCurr;
+ pScope->hStrSpaceVar = NULL;
+ pThis->pFnCallCurr->pScopeCurr = pScope;
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory allocating new scope");
+
+ return rc;
+}
+
+/**
+ * Destroys the current scope.
+ *
+ * @returns nothing.
+ * @param pThis The interpreter context.
+ */
+static void vdScriptInterpreterScopeDestroyCurr(PVDSCRIPTINTERPCTX pThis)
+{
+ AssertMsgReturnVoid(pThis->pFnCallCurr->pScopeCurr != &pThis->pFnCallCurr->ScopeRoot,
+ ("Current scope is root scope of function call\n"));
+
+ PVDSCRIPTINTERPSCOPE pScope = pThis->pFnCallCurr->pScopeCurr;
+ pThis->pFnCallCurr->pScopeCurr = pScope->pParent;
+ RTStrSpaceDestroy(&pScope->hStrSpaceVar, vdScriptInterpreterVarSpaceDestroy, NULL);
+ RTMemFree(pScope);
+}
+
+/**
+ * Get the content of the given variable identifier from the current or parent scope.
+ */
+static PVDSCRIPTINTERPVAR vdScriptInterpreterGetVar(PVDSCRIPTINTERPCTX pThis, const char *pszVar)
+{
+ PVDSCRIPTINTERPSCOPE pScopeCurr = pThis->pFnCallCurr->pScopeCurr;
+ PVDSCRIPTINTERPVAR pVar = NULL;
+
+ while ( !pVar
+ && pScopeCurr)
+ {
+ pVar = (PVDSCRIPTINTERPVAR)RTStrSpaceGet(&pScopeCurr->hStrSpaceVar, pszVar);
+ if (pVar)
+ break;
+ pScopeCurr = pScopeCurr->pParent;
+ }
+
+
+ return pVar;
+}
+
+/**
+ * Evaluate an expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pExpr The expression to evaluate.
+ */
+static int vdScriptInterpreterEvaluateExpression(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTEXPR pExpr)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (pExpr->enmType)
+ {
+ case VDSCRIPTEXPRTYPE_PRIMARY_NUMCONST:
+ {
+ /* Push the numerical constant on the value stack. */
+ VDSCRIPTARG NumConst;
+ NumConst.enmType = VDSCRIPTTYPE_UINT64;
+ NumConst.u64 = pExpr->u64;
+ rc = vdScriptInterpreterPushValue(pThis, &NumConst);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_PRIMARY_STRINGCONST:
+ {
+ /* Push the string literal on the value stack. */
+ VDSCRIPTARG StringConst;
+ StringConst.enmType = VDSCRIPTTYPE_STRING;
+ StringConst.psz = pExpr->pszStr;
+ rc = vdScriptInterpreterPushValue(pThis, &StringConst);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_PRIMARY_BOOLEAN:
+ {
+ VDSCRIPTARG BoolConst;
+ BoolConst.enmType = VDSCRIPTTYPE_BOOL;
+ BoolConst.f = pExpr->f;
+ rc = vdScriptInterpreterPushValue(pThis, &BoolConst);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_PRIMARY_IDENTIFIER:
+ {
+ /* Look it up and push the value onto the value stack. */
+ PVDSCRIPTINTERPVAR pVar = vdScriptInterpreterGetVar(pThis, pExpr->pIde->aszIde);
+
+ AssertPtrReturn(pVar, VERR_IPE_UNINITIALIZED_STATUS);
+ rc = vdScriptInterpreterPushValue(pThis, &pVar->Value);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_POSTFIX_INCREMENT:
+ case VDSCRIPTEXPRTYPE_POSTFIX_DECREMENT:
+ AssertMsgFailed(("TODO\n"));
+ RT_FALL_THRU();
+ case VDSCRIPTEXPRTYPE_POSTFIX_FNCALL:
+ {
+ PVDSCRIPTFN pFn = (PVDSCRIPTFN)RTStrSpaceGet(&pThis->pScriptCtx->hStrSpaceFn, pExpr->FnCall.pFnIde->pIde->aszIde);
+ if (pFn)
+ {
+ /* Push a function call control entry on the stack. */
+ PVDSCRIPTINTERPCTRL pCtrlFn = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrlFn)
+ {
+ pCtrlFn->fEvalAst = false;
+ pCtrlFn->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_FN_CALL;
+ pCtrlFn->Ctrl.FnCall.pFn = pFn;
+ vdScriptStackPush(&pThis->StackCtrl);
+
+ /* Push parameter expressions on the stack. */
+ PVDSCRIPTASTEXPR pArg = RTListGetFirst(&pExpr->FnCall.ListArgs, VDSCRIPTASTEXPR, Core.ListNode);
+ while (pArg)
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pArg->Core);
+ if (RT_FAILURE(rc))
+ break;
+ pArg = RTListGetNext(&pExpr->FnCall.ListArgs, pArg, VDSCRIPTASTEXPR, Core.ListNode);
+ }
+ }
+ }
+ else
+ AssertMsgFailed(("Invalid program given, unknown function: %s\n", pExpr->FnCall.pFnIde->pIde->aszIde));
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_UNARY_INCREMENT:
+ case VDSCRIPTEXPRTYPE_UNARY_DECREMENT:
+ case VDSCRIPTEXPRTYPE_UNARY_POSSIGN:
+ case VDSCRIPTEXPRTYPE_UNARY_NEGSIGN:
+ case VDSCRIPTEXPRTYPE_UNARY_INVERT:
+ case VDSCRIPTEXPRTYPE_UNARY_NEGATE:
+ case VDSCRIPTEXPRTYPE_MULTIPLICATION:
+ case VDSCRIPTEXPRTYPE_DIVISION:
+ case VDSCRIPTEXPRTYPE_MODULUS:
+ case VDSCRIPTEXPRTYPE_ADDITION:
+ case VDSCRIPTEXPRTYPE_SUBTRACTION:
+ case VDSCRIPTEXPRTYPE_LSR:
+ case VDSCRIPTEXPRTYPE_LSL:
+ case VDSCRIPTEXPRTYPE_LOWER:
+ case VDSCRIPTEXPRTYPE_HIGHER:
+ case VDSCRIPTEXPRTYPE_LOWEREQUAL:
+ case VDSCRIPTEXPRTYPE_HIGHEREQUAL:
+ case VDSCRIPTEXPRTYPE_EQUAL:
+ case VDSCRIPTEXPRTYPE_NOTEQUAL:
+ case VDSCRIPTEXPRTYPE_BITWISE_AND:
+ case VDSCRIPTEXPRTYPE_BITWISE_XOR:
+ case VDSCRIPTEXPRTYPE_BITWISE_OR:
+ case VDSCRIPTEXPRTYPE_LOGICAL_AND:
+ case VDSCRIPTEXPRTYPE_LOGICAL_OR:
+ case VDSCRIPTEXPRTYPE_ASSIGN:
+ case VDSCRIPTEXPRTYPE_ASSIGN_MULT:
+ case VDSCRIPTEXPRTYPE_ASSIGN_DIV:
+ case VDSCRIPTEXPRTYPE_ASSIGN_MOD:
+ case VDSCRIPTEXPRTYPE_ASSIGN_ADD:
+ case VDSCRIPTEXPRTYPE_ASSIGN_SUB:
+ case VDSCRIPTEXPRTYPE_ASSIGN_LSL:
+ case VDSCRIPTEXPRTYPE_ASSIGN_LSR:
+ case VDSCRIPTEXPRTYPE_ASSIGN_AND:
+ case VDSCRIPTEXPRTYPE_ASSIGN_XOR:
+ case VDSCRIPTEXPRTYPE_ASSIGN_OR:
+ case VDSCRIPTEXPRTYPE_ASSIGNMENT_LIST:
+ AssertMsgFailed(("TODO\n"));
+ RT_FALL_THRU();
+ default:
+ AssertMsgFailed(("Invalid expression type: %d\n", pExpr->enmType));
+ }
+ return rc;
+}
+
+/**
+ * Evaluate a statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pStmt The statement to evaluate.
+ */
+static int vdScriptInterpreterEvaluateStatement(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (pStmt->enmStmtType)
+ {
+ case VDSCRIPTSTMTTYPE_COMPOUND:
+ {
+ /* Setup new scope. */
+ rc = vdScriptInterpreterScopeCreate(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Declarations */
+ rc = vdScriptInterpreterPushCompoundCtrlEntry(pThis, pStmt);
+ }
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_EXPRESSION:
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->pExpr->Core);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_IF:
+ {
+ rc = vdScriptInterpreterPushIfCtrlEntry(pThis, pStmt);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_SWITCH:
+ AssertMsgFailed(("TODO\n"));
+ break;
+ case VDSCRIPTSTMTTYPE_WHILE:
+ {
+ rc = vdScriptInterpreterPushWhileCtrlEntry(pThis, pStmt);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_RETURN:
+ {
+ /* Walk up the control stack until we reach a function cleanup entry. */
+ PVDSCRIPTINTERPCTRL pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ while ( pCtrl
+ && ( pCtrl->fEvalAst
+ || pCtrl->Ctrl.enmCtrlType != VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP))
+ {
+ /* Cleanup up any compound statement scope. */
+ if ( !pCtrl->fEvalAst
+ && pCtrl->Ctrl.enmCtrlType == VDSCRIPTINTERPCTRLTYPE_COMPOUND)
+ vdScriptInterpreterScopeDestroyCurr(pThis);
+
+ vdScriptStackPop(&pThis->StackCtrl);
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ }
+ AssertMsg(RT_VALID_PTR(pCtrl), ("Incorrect program, return outside of function\n"));
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_FOR:
+ {
+ rc = vdScriptInterpreterPushForCtrlEntry(pThis, pStmt);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_CONTINUE:
+ {
+ /* Remove everything up to a loop control entry. */
+ PVDSCRIPTINTERPCTRL pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ while ( pCtrl
+ && ( pCtrl->fEvalAst
+ || pCtrl->Ctrl.enmCtrlType != VDSCRIPTINTERPCTRLTYPE_LOOP))
+ {
+ /* Cleanup up any compound statement scope. */
+ if ( !pCtrl->fEvalAst
+ && pCtrl->Ctrl.enmCtrlType == VDSCRIPTINTERPCTRLTYPE_COMPOUND)
+ vdScriptInterpreterScopeDestroyCurr(pThis);
+
+ vdScriptStackPop(&pThis->StackCtrl);
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ }
+ AssertMsg(RT_VALID_PTR(pCtrl), ("Incorrect program, continue outside of loop\n"));
+
+ /* Put the conditionals for while and for loops onto the control stack again. */
+ PVDSCRIPTASTSTMT pLoopStmt = (PVDSCRIPTASTSTMT)pCtrl->Ctrl.pAstNode;
+
+ AssertMsg( pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_WHILE
+ || pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_FOR,
+ ("Invalid statement type, must be for or while loop\n"));
+
+ if (pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_FOR)
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pExprCond->Core);
+ else if (!pLoopStmt->While.fDoWhile)
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->While.pCond->Core);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_BREAK:
+ {
+ /* Remove everything including the loop control statement. */
+ PVDSCRIPTINTERPCTRL pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ while ( pCtrl
+ && ( pCtrl->fEvalAst
+ || pCtrl->Ctrl.enmCtrlType != VDSCRIPTINTERPCTRLTYPE_LOOP))
+ {
+ /* Cleanup up any compound statement scope. */
+ if ( !pCtrl->fEvalAst
+ && pCtrl->Ctrl.enmCtrlType == VDSCRIPTINTERPCTRLTYPE_COMPOUND)
+ vdScriptInterpreterScopeDestroyCurr(pThis);
+
+ vdScriptStackPop(&pThis->StackCtrl);
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ }
+ AssertMsg(RT_VALID_PTR(pCtrl), ("Incorrect program, break outside of loop\n"));
+ vdScriptStackPop(&pThis->StackCtrl); /* Remove loop control statement. */
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_CASE:
+ case VDSCRIPTSTMTTYPE_DEFAULT:
+ AssertMsgFailed(("TODO\n"));
+ break;
+ default:
+ AssertMsgFailed(("Invalid statement type: %d\n", pStmt->enmStmtType));
+ }
+
+ return rc;
+}
+
+/**
+ * Evaluates the given AST node.
+ *
+ * @returns VBox statuse code.
+ * @param pThis The interpreter context.
+ * @param pAstNode The AST node to interpret.
+ */
+static int vdScriptInterpreterEvaluateAst(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTCORE pAstNode)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+
+ switch (pAstNode->enmClass)
+ {
+ case VDSCRIPTASTCLASS_DECLARATION:
+ {
+ AssertMsgFailed(("TODO\n"));
+ break;
+ }
+ case VDSCRIPTASTCLASS_STATEMENT:
+ {
+ rc = vdScriptInterpreterEvaluateStatement(pThis, (PVDSCRIPTASTSTMT)pAstNode);
+ break;
+ }
+ case VDSCRIPTASTCLASS_EXPRESSION:
+ {
+ rc = vdScriptInterpreterEvaluateExpression(pThis, (PVDSCRIPTASTEXPR)pAstNode);
+ break;
+ }
+ /* These should never ever appear here. */
+ case VDSCRIPTASTCLASS_IDENTIFIER:
+ case VDSCRIPTASTCLASS_FUNCTION:
+ case VDSCRIPTASTCLASS_FUNCTIONARG:
+ case VDSCRIPTASTCLASS_INVALID:
+ default:
+ AssertMsgFailed(("Invalid AST node class: %d\n", pAstNode->enmClass));
+ }
+
+ return rc;
+}
+
+/**
+ * Evaluate a function call.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pFn The function execute.
+ */
+static int vdScriptInterpreterFnCall(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTFN pFn)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!pFn->fExternal)
+ {
+ PVDSCRIPTASTFN pAstFn = pFn->Type.Internal.pAstFn;
+
+ /* Add function call cleanup marker on the stack first. */
+ rc = vdScriptInterpreterPushNonDataCtrlEntry(pThis, VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create function call frame and set it up. */
+ PVDSCRIPTINTERPFNCALL pFnCall = (PVDSCRIPTINTERPFNCALL)RTMemAllocZ(sizeof(VDSCRIPTINTERPFNCALL));
+ if (pFnCall)
+ {
+ pFnCall->pCaller = pThis->pFnCallCurr;
+ pFnCall->ScopeRoot.pParent = NULL;
+ pFnCall->ScopeRoot.hStrSpaceVar = NULL;
+ pFnCall->pScopeCurr = &pFnCall->ScopeRoot;
+
+ /* Add the variables, remember order. The first variable in the argument has the value at the top of the value stack. */
+ PVDSCRIPTASTFNARG pArg = RTListGetFirst(&pAstFn->ListArgs, VDSCRIPTASTFNARG, Core.ListNode);
+ for (unsigned i = 0; i < pAstFn->cArgs; i++)
+ {
+ PVDSCRIPTINTERPVAR pVar = (PVDSCRIPTINTERPVAR)RTMemAllocZ(sizeof(VDSCRIPTINTERPVAR));
+ if (pVar)
+ {
+ pVar->Core.pszString = pArg->pArgIde->aszIde;
+ pVar->Core.cchString = pArg->pArgIde->cchIde;
+ vdScriptInterpreterPopValue(pThis, &pVar->Value);
+ bool fInserted = RTStrSpaceInsert(&pFnCall->ScopeRoot.hStrSpaceVar, &pVar->Core);
+ Assert(fInserted); RT_NOREF_PV(fInserted);
+ }
+ else
+ {
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory creating a variable");
+ break;
+ }
+ pArg = RTListGetNext(&pAstFn->ListArgs, pArg, VDSCRIPTASTFNARG, Core.ListNode);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Push compount statement on the control stack and make the newly created
+ * call frame the current one.
+ */
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pAstFn->pCompoundStmts->Core);
+ if (RT_SUCCESS(rc))
+ pThis->pFnCallCurr = pFnCall;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTStrSpaceDestroy(&pFnCall->ScopeRoot.hStrSpaceVar, vdScriptInterpreterVarSpaceDestroy, NULL);
+ RTMemFree(pFnCall);
+ }
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory creating a call frame");
+ }
+ }
+ else
+ {
+ /* External function call, build the argument list. */
+ if (pFn->cArgs)
+ {
+ PVDSCRIPTARG paArgs = (PVDSCRIPTARG)RTMemAllocZ(pFn->cArgs * sizeof(VDSCRIPTARG));
+ if (paArgs)
+ {
+ for (unsigned i = 0; i < pFn->cArgs; i++)
+ vdScriptInterpreterPopValue(pThis, &paArgs[i]);
+
+ rc = pFn->Type.External.pfnCallback(paArgs, pFn->Type.External.pvUser);
+ RTMemFree(paArgs);
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS,
+ "Out of memory creating argument array for external function call");
+ }
+ else
+ rc = pFn->Type.External.pfnCallback(NULL, pFn->Type.External.pvUser);
+ }
+
+ return rc;
+}
+
+/**
+ * Evaluate interpreter control statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pCtrl The control entry to evaluate.
+ */
+static int vdScriptInterpreterEvaluateCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTINTERPCTRL pCtrl)
+{
+ int rc = VINF_SUCCESS;
+
+ Assert(!pCtrl->fEvalAst);
+ switch (pCtrl->Ctrl.enmCtrlType)
+ {
+ case VDSCRIPTINTERPCTRLTYPE_FN_CALL:
+ {
+ PVDSCRIPTFN pFn = pCtrl->Ctrl.FnCall.pFn;
+
+ vdScriptStackPop(&pThis->StackCtrl);
+ rc = vdScriptInterpreterFnCall(pThis, pFn);
+ break;
+ }
+ case VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP:
+ {
+ vdScriptStackPop(&pThis->StackCtrl);
+
+ /* Delete function call entry. */
+ AssertPtr(pThis->pFnCallCurr);
+ PVDSCRIPTINTERPFNCALL pFnCallFree = pThis->pFnCallCurr;
+
+ pThis->pFnCallCurr = pFnCallFree->pCaller;
+ Assert(pFnCallFree->pScopeCurr == &pFnCallFree->ScopeRoot);
+ RTStrSpaceDestroy(&pFnCallFree->ScopeRoot.hStrSpaceVar, vdScriptInterpreterVarSpaceDestroy, NULL);
+ RTMemFree(pFnCallFree);
+ break;
+ }
+ case VDSCRIPTINTERPCTRLTYPE_COMPOUND:
+ {
+ if (!pCtrl->Ctrl.Compound.pStmtCurr)
+ {
+ /* Evaluated last statement, cleanup and remove the control statement from the stack. */
+ vdScriptInterpreterScopeDestroyCurr(pThis);
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+ else
+ {
+ /* Push the current statement onto the control stack and move on. */
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pCtrl->Ctrl.Compound.pStmtCurr->Core);
+ if (RT_SUCCESS(rc))
+ {
+ pCtrl->Ctrl.Compound.pStmtCurr = RTListGetNext(&pCtrl->Ctrl.Compound.pStmtCompound->Compound.ListStmts,
+ pCtrl->Ctrl.Compound.pStmtCurr, VDSCRIPTASTSTMT, Core.ListNode);
+ }
+ }
+ break;
+ }
+ case VDSCRIPTINTERPCTRLTYPE_LOOP:
+ {
+ PVDSCRIPTASTSTMT pLoopStmt = (PVDSCRIPTASTSTMT)pCtrl->Ctrl.pAstNode;
+
+ /* Check whether the condition passed. */
+ VDSCRIPTARG Cond;
+ vdScriptInterpreterPopValue(pThis, &Cond);
+ AssertMsg(Cond.enmType == VDSCRIPTTYPE_BOOL,
+ ("Value on stack is not of boolean type\n"));
+
+ if (Cond.f)
+ {
+ /* Execute the loop another round. */
+ if (pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_WHILE)
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->While.pCond->Core);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->While.pStmt->Core);
+ if (RT_FAILURE(rc))
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+ }
+ else
+ {
+ AssertMsg(pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_FOR,
+ ("Not a for statement\n"));
+
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pExprCond->Core);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pExpr3->Core);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pStmt->Core);
+ if (RT_FAILURE(rc))
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+
+ if (RT_FAILURE(rc))
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+ }
+ }
+ else
+ vdScriptStackPop(&pThis->StackCtrl); /* Remove loop control statement. */
+ break;
+ }
+ case VDSCRIPTINTERPCTRLTYPE_IF:
+ {
+ PVDSCRIPTASTSTMT pIfStmt = (PVDSCRIPTASTSTMT)pCtrl->Ctrl.pAstNode;
+
+ vdScriptStackPop(&pThis->StackCtrl); /* Remove if control statement. */
+
+ /* Check whether the condition passed. */
+ VDSCRIPTARG Cond;
+ vdScriptInterpreterPopValue(pThis, &Cond);
+ AssertMsg(Cond.enmType == VDSCRIPTTYPE_BOOL,
+ ("Value on stack is not of boolean type\n"));
+
+ if (Cond.f)
+ {
+ /* Execute the true branch. */
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pIfStmt->If.pTrueStmt->Core);
+ }
+ else if (pIfStmt->If.pElseStmt)
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pIfStmt->If.pElseStmt->Core);
+
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid evaluation control type on the stack: %d\n",
+ pCtrl->Ctrl.enmCtrlType));
+ }
+
+ return rc;
+}
+
+/**
+ * The interpreter evaluation core loop.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ */
+static int vdScriptInterpreterEvaluate(PVDSCRIPTINTERPCTX pThis)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ while (pCtrl)
+ {
+ if (pCtrl->fEvalAst)
+ {
+ PVDSCRIPTASTCORE pAstNode = pCtrl->pAstNode;
+ vdScriptStackPop(&pThis->StackCtrl);
+
+ rc = vdScriptInterpreterEvaluateAst(pThis, pAstNode);
+ }
+ else
+ rc = vdScriptInterpreterEvaluateCtrlEntry(pThis, pCtrl);
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ }
+
+ return rc;
+}
+
+DECLHIDDEN(int) vdScriptCtxInterprete(PVDSCRIPTCTXINT pThis, const char *pszFn,
+ PVDSCRIPTARG paArgs, unsigned cArgs,
+ PVDSCRIPTARG pRet)
+{
+ RT_NOREF1(pRet);
+ int rc = VINF_SUCCESS;
+ VDSCRIPTINTERPCTX InterpCtx;
+ PVDSCRIPTFN pFn = NULL;
+
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFn, VERR_INVALID_POINTER);
+ AssertReturn( (!cArgs && !paArgs)
+ || (cArgs && paArgs), VERR_INVALID_PARAMETER);
+
+ InterpCtx.pScriptCtx = pThis;
+ InterpCtx.pFnCallCurr = NULL;
+ vdScriptStackInit(&InterpCtx.StackValues, sizeof(VDSCRIPTARG));
+ vdScriptStackInit(&InterpCtx.StackCtrl, sizeof(VDSCRIPTINTERPCTRL));
+
+ pFn = (PVDSCRIPTFN)RTStrSpaceGet(&pThis->hStrSpaceFn, pszFn);
+ if (pFn)
+ {
+ if (cArgs == pFn->cArgs)
+ {
+ /* Push the arguments onto the stack. */
+ /** @todo Check expected and given argument types. */
+ for (unsigned i = 0; i < cArgs; i++)
+ {
+ PVDSCRIPTARG pArg = (PVDSCRIPTARG)vdScriptStackGetUnused(&InterpCtx.StackValues);
+ *pArg = paArgs[i];
+ vdScriptStackPush(&InterpCtx.StackValues);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Setup function call frame and parameters. */
+ rc = vdScriptInterpreterFnCall(&InterpCtx, pFn);
+ if (RT_SUCCESS(rc))
+ {
+ /* Run the interpreter. */
+ rc = vdScriptInterpreterEvaluate(&InterpCtx);
+ vdScriptStackDestroy(&InterpCtx.StackValues);
+ vdScriptStackDestroy(&InterpCtx.StackCtrl);
+ }
+ }
+ }
+ else
+ rc = vdScriptInterpreterError(&InterpCtx, VERR_INVALID_PARAMETER, RT_SRC_POS, "Invalid number of parameters, expected %d got %d", pFn->cArgs, cArgs);
+ }
+ else
+ rc = vdScriptInterpreterError(&InterpCtx, VERR_NOT_FOUND, RT_SRC_POS, "Function with identifier \"%s\" not found", pszFn);
+
+
+ return rc;
+}
diff --git a/src/VBox/Storage/testcase/VDScriptStack.h b/src/VBox/Storage/testcase/VDScriptStack.h
new file mode 100644
index 00000000..2252026c
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptStack.h
@@ -0,0 +1,156 @@
+/** @file
+ *
+ * VBox HDD container test utility - scripting engine, internal stack implementation.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDScriptStack_h
+#define VBOX_INCLUDED_SRC_testcase_VDScriptStack_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/list.h>
+#include <iprt/string.h>
+
+#include "VDScript.h"
+
+/**
+ * Stack structure.
+ */
+typedef struct VDSCRIPTSTACK
+{
+ /** Size of one stack element. */
+ size_t cbStackEntry;
+ /** Stack memory. */
+ void *pvStack;
+ /** Number of elements on the stack. */
+ unsigned cOnStack;
+ /** Maximum number of elements the stack can hold. */
+ unsigned cOnStackMax;
+} VDSCRIPTSTACK;
+/** Pointer to a stack. */
+typedef VDSCRIPTSTACK *PVDSCRIPTSTACK;
+
+/**
+ * Init the stack structure.
+ *
+ * @returns nothing.
+ * @param pStack The stack to initialize.
+ * @param cbStackEntry The size of one stack entry.
+ */
+DECLINLINE(void) vdScriptStackInit(PVDSCRIPTSTACK pStack, size_t cbStackEntry)
+{
+ pStack->cbStackEntry = cbStackEntry;
+ pStack->pvStack = NULL;
+ pStack->cOnStack = 0;
+ pStack->cOnStackMax = 0;
+}
+
+/**
+ * Destroys the given stack freeing all memory.
+ *
+ * @returns nothing.
+ * @param pStack The stack to destroy.
+ */
+DECLINLINE(void) vdScriptStackDestroy(PVDSCRIPTSTACK pStack)
+{
+ if (pStack->pvStack)
+ RTMemFree(pStack->pvStack);
+ pStack->cbStackEntry = 0;
+ pStack->pvStack = NULL;
+ pStack->cOnStack = 0;
+ pStack->cOnStackMax = 0;
+}
+
+/**
+ * Gets the topmost unused stack entry.
+ *
+ * @returns Pointer to the first unused entry.
+ * NULL if there is no room left and increasing the stack failed.
+ * @param pStack The stack.
+ */
+DECLINLINE(void *)vdScriptStackGetUnused(PVDSCRIPTSTACK pStack)
+{
+ void *pvElem = NULL;
+
+ if (pStack->cOnStack >= pStack->cOnStackMax)
+ {
+ unsigned cOnStackMaxNew = pStack->cOnStackMax + 10;
+ void *pvStackNew = NULL;
+
+ /* Try to increase stack space. */
+ pvStackNew = RTMemRealloc(pStack->pvStack, cOnStackMaxNew * pStack->cbStackEntry);
+ if (pvStackNew)
+ {
+ pStack->pvStack = pvStackNew;
+ pStack->cOnStackMax = cOnStackMaxNew;
+ }
+
+ }
+
+ if (pStack->cOnStack < pStack->cOnStackMax)
+ pvElem = (char *)pStack->pvStack + pStack->cOnStack * pStack->cbStackEntry;
+
+ return pvElem;
+}
+
+/**
+ * Gets the topmost used entry on the stack.
+ *
+ * @returns Pointer to the first used entry
+ * or NULL if the stack is empty.
+ * @param pStack The stack.
+ */
+DECLINLINE(void *)vdScriptStackGetUsed(PVDSCRIPTSTACK pStack)
+{
+ if (!pStack->cOnStack)
+ return NULL;
+ else
+ return (char *)pStack->pvStack + (pStack->cOnStack - 1) * pStack->cbStackEntry;
+}
+
+/**
+ * Increases the used element count for the given stack.
+ *
+ * @returns nothing.
+ * @param pStack The stack.
+ */
+DECLINLINE(void) vdScriptStackPush(PVDSCRIPTSTACK pStack)
+{
+ pStack->cOnStack++;
+}
+
+/**
+ * Decreases the used element count for the given stack.
+ *
+ * @returns nothing.
+ * @param pStack The stack.
+ */
+DECLINLINE(void) vdScriptStackPop(PVDSCRIPTSTACK pStack)
+{
+ pStack->cOnStack--;
+}
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDScriptStack_h */
diff --git a/src/VBox/Storage/testcase/tstVD-2.cpp b/src/VBox/Storage/testcase/tstVD-2.cpp
new file mode 100644
index 00000000..146b1be6
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVD-2.cpp
@@ -0,0 +1,272 @@
+/** @file
+ *
+ * Simple VBox HDD container test utility. Only fast tests.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/err.h>
+#include <VBox/vd.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/initterm.h>
+#include <iprt/rand.h>
+#include "stdio.h"
+#include "stdlib.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The error count. */
+unsigned g_cErrors = 0;
+
+static struct KeyValuePair {
+ const char *key;
+ const char *value;
+} aCfgNode[] = {
+ { "TargetName", "test" },
+ { "LUN", "1" },
+ { "TargetAddress", "address" },
+ { NULL, NULL }
+};
+
+static DECLCALLBACK(bool) tstAreKeysValid(void *pvUser, const char *pszzValid)
+{
+ RT_NOREF2(pvUser, pszzValid);
+ return true;
+}
+
+static const char *tstGetValueByKey(const char *pszKey)
+{
+ for (int i = 0; aCfgNode[i].key; i++)
+ if (!strcmp(aCfgNode[i].key, pszKey))
+ return aCfgNode[i].value;
+ return NULL;
+}
+
+static DECLCALLBACK(int) tstQuerySize(void *pvUser, const char *pszName, size_t *pcbValue)
+{
+ RT_NOREF1(pvUser);
+ const char *pszValue = tstGetValueByKey(pszName);
+ if (!pszValue)
+ return VERR_CFGM_VALUE_NOT_FOUND;
+ *pcbValue = strlen(pszValue) + 1;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstQuery(void *pvUser, const char *pszName, char *pszValue, size_t cchValue)
+{
+ RT_NOREF1(pvUser);
+ const char *pszTmp = tstGetValueByKey(pszName);
+ if (!pszValue)
+ return VERR_CFGM_VALUE_NOT_FOUND;
+ size_t cchTmp = strlen(pszTmp) + 1;
+ if (cchValue < cchTmp)
+ return VERR_CFGM_NOT_ENOUGH_SPACE;
+ memcpy(pszValue, pszTmp, cchTmp);
+ return VINF_SUCCESS;
+}
+
+static const char *tstVDDeviceType(VDTYPE enmType)
+{
+ switch (enmType)
+ {
+ case VDTYPE_HDD:
+ return "HardDisk";
+ case VDTYPE_OPTICAL_DISC:
+ return "OpticalDisc";
+ case VDTYPE_FLOPPY:
+ return "Floppy";
+ default:
+ return "Unknown";
+ }
+}
+
+static int tstVDBackendInfo(void)
+{
+ int rc;
+#define MAX_BACKENDS 100
+ VDBACKENDINFO aVDInfo[MAX_BACKENDS];
+ unsigned cEntries;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ } while (0)
+
+ rc = VDBackendInfo(MAX_BACKENDS, aVDInfo, &cEntries);
+ CHECK("VDBackendInfo()");
+
+ for (unsigned i=0; i < cEntries; i++)
+ {
+ RTPrintf("Backend %u: name=%s capabilities=%#06x extensions=",
+ i, aVDInfo[i].pszBackend, aVDInfo[i].uBackendCaps);
+ if (aVDInfo[i].paFileExtensions)
+ {
+ PCVDFILEEXTENSION pa = aVDInfo[i].paFileExtensions;
+ while (pa->pszExtension != NULL)
+ {
+ if (pa != aVDInfo[i].paFileExtensions)
+ RTPrintf(",");
+ RTPrintf("%s (%s)", pa->pszExtension, tstVDDeviceType(pa->enmType));
+ pa++;
+ }
+ if (pa == aVDInfo[i].paFileExtensions)
+ RTPrintf("<EMPTY>");
+ }
+ else
+ RTPrintf("<NONE>");
+ RTPrintf(" config=");
+ if (aVDInfo[i].paConfigInfo)
+ {
+ PCVDCONFIGINFO pa = aVDInfo[i].paConfigInfo;
+ while (pa->pszKey != NULL)
+ {
+ if (pa != aVDInfo[i].paConfigInfo)
+ RTPrintf(",");
+ RTPrintf("(key=%s type=", pa->pszKey);
+ switch (pa->enmValueType)
+ {
+ case VDCFGVALUETYPE_INTEGER:
+ RTPrintf("integer");
+ break;
+ case VDCFGVALUETYPE_STRING:
+ RTPrintf("string");
+ break;
+ case VDCFGVALUETYPE_BYTES:
+ RTPrintf("bytes");
+ break;
+ default:
+ RTPrintf("INVALID!");
+ }
+ RTPrintf(" default=");
+ if (pa->pszDefaultValue)
+ RTPrintf("%s", pa->pszDefaultValue);
+ else
+ RTPrintf("<NONE>");
+ RTPrintf(" flags=");
+ if (!pa->uKeyFlags)
+ RTPrintf("none");
+ unsigned cFlags = 0;
+ if (pa->uKeyFlags & VD_CFGKEY_MANDATORY)
+ {
+ if (cFlags)
+ RTPrintf(",");
+ RTPrintf("mandatory");
+ cFlags++;
+ }
+ if (pa->uKeyFlags & VD_CFGKEY_EXPERT)
+ {
+ if (cFlags)
+ RTPrintf(",");
+ RTPrintf("expert");
+ cFlags++;
+ }
+ RTPrintf(")");
+ pa++;
+ }
+ if (pa == aVDInfo[i].paConfigInfo)
+ RTPrintf("<EMPTY>");
+ }
+ else
+ RTPrintf("<NONE>");
+ RTPrintf("\n");
+
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACECONFIG ic;
+
+ ic.pfnAreKeysValid = tstAreKeysValid;
+ ic.pfnQuerySize = tstQuerySize;
+ ic.pfnQuery = tstQuery;
+
+ rc = VDInterfaceAdd(&ic.Core, "tstVD-2_Config", VDINTERFACETYPE_CONFIG,
+ NULL, sizeof(VDINTERFACECONFIG), &pVDIfs);
+ AssertRC(rc);
+
+ char *pszLocation, *pszName;
+ rc = aVDInfo[i].pfnComposeLocation(pVDIfs, &pszLocation);
+ CHECK("pfnComposeLocation()");
+ if (pszLocation)
+ {
+ RTMemFree(pszLocation);
+ if (aVDInfo[i].uBackendCaps & VD_CAP_FILE)
+ {
+ RTPrintf("Non-NULL location returned for file-based backend!\n");
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ rc = aVDInfo[i].pfnComposeName(pVDIfs, &pszName);
+ CHECK("pfnComposeName()");
+ if (pszName)
+ {
+ RTMemFree(pszName);
+ if (aVDInfo[i].uBackendCaps & VD_CAP_FILE)
+ {
+ RTPrintf("Non-NULL name returned for file-based backend!\n");
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ }
+
+#undef CHECK
+ return 0;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int rc;
+
+ RTR3InitExe(argc, &argv, 0);
+ RTPrintf("tstVD-2: TESTING...\n");
+
+ rc = tstVDBackendInfo();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD-2: getting backend info test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD-2: unloading backends failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ /*
+ * Summary
+ */
+ if (!g_cErrors)
+ RTPrintf("tstVD-2: SUCCESS\n");
+ else
+ RTPrintf("tstVD-2: FAILURE - %d errors\n", g_cErrors);
+
+ return !!g_cErrors;
+}
+
diff --git a/src/VBox/Storage/testcase/tstVD.cpp b/src/VBox/Storage/testcase/tstVD.cpp
new file mode 100644
index 00000000..3840c391
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVD.cpp
@@ -0,0 +1,1080 @@
+/* $Id: tstVD.cpp $ */
+/** @file
+ * Simple VBox HDD container test utility.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/vd.h>
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+# include <iprt/asm-amd64-x86.h>
+#endif
+#include <iprt/dir.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/initterm.h>
+#include <iprt/rand.h>
+#include "stdio.h"
+#include "stdlib.h"
+
+#define VHD_TEST
+#define VDI_TEST
+#define VMDK_TEST
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The error count. */
+unsigned g_cErrors = 0;
+
+
+static DECLCALLBACK(void) tstVDError(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va)
+{
+ RT_NOREF1(pvUser);
+ g_cErrors++;
+ RTPrintf("tstVD: Error %Rrc at %s:%u (%s): ", rc, RT_SRC_POS_ARGS);
+ RTPrintfV(pszFormat, va);
+ RTPrintf("\n");
+}
+
+static DECLCALLBACK(int) tstVDMessage(void *pvUser, const char *pszFormat, va_list va)
+{
+ RT_NOREF1(pvUser);
+ RTPrintf("tstVD: ");
+ RTPrintfV(pszFormat, va);
+ return VINF_SUCCESS;
+}
+
+static int tstVDCreateDelete(const char *pszBackend, const char *pszFilename,
+ uint64_t cbSize, unsigned uFlags, bool fDelete)
+{
+ int rc;
+ PVDISK pVD = NULL;
+ VDGEOMETRY PCHS = { 0, 0, 0 };
+ VDGEOMETRY LCHS = { 0, 0, 0 };
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACEERROR VDIfError;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ VDDestroy(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ /* Create error interface. */
+ VDIfError.pfnError = tstVDError;
+ VDIfError.pfnMessage = tstVDMessage;
+
+ rc = VDInterfaceAdd(&VDIfError.Core, "tstVD_Error", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+ AssertRC(rc);
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDCreateBase(pVD, pszBackend, pszFilename, cbSize,
+ uFlags, "Test image", &PCHS, &LCHS, NULL,
+ VD_OPEN_FLAGS_NORMAL, NULL, NULL);
+ CHECK("VDCreateBase()");
+
+ VDDumpImages(pVD);
+
+ VDClose(pVD, fDelete);
+ if (fDelete)
+ {
+ RTFILE File;
+ rc = RTFileOpen(&File, pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ RTFileClose(File);
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+
+ VDDestroy(pVD);
+#undef CHECK
+ return 0;
+}
+
+static int tstVDOpenDelete(const char *pszBackend, const char *pszFilename)
+{
+ int rc;
+ PVDISK pVD = NULL;
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACEERROR VDIfError;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ VDDestroy(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ /* Create error interface. */
+ VDIfError.pfnError = tstVDError;
+ VDIfError.pfnMessage = tstVDMessage;
+
+ rc = VDInterfaceAdd(&VDIfError.Core, "tstVD_Error", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+ AssertRC(rc);
+
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDOpen(pVD, pszBackend, pszFilename, VD_OPEN_FLAGS_NORMAL, NULL);
+ CHECK("VDOpen()");
+
+ VDDumpImages(pVD);
+
+ VDClose(pVD, true);
+ RTFILE File;
+ rc = RTFileOpen(&File, pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ RTFileClose(File);
+ return VERR_INTERNAL_ERROR;
+ }
+
+ VDDestroy(pVD);
+#undef CHECK
+ return 0;
+}
+
+
+#undef RTDECL
+#define RTDECL(x) static x
+
+/* Start of IPRT code */
+
+/**
+ * The following code is based on the work of George Marsaglia
+ * taken from
+ * http://groups.google.ws/group/comp.sys.sun.admin/msg/7c667186f6cbf354
+ * and
+ * http://groups.google.ws/group/comp.lang.c/msg/0e170777c6e79e8d
+ */
+
+/*
+A C version of a very very good 64-bit RNG is given below.
+You should be able to adapt it to your particular needs.
+
+It is based on the complimentary-multiple-with-carry
+sequence
+ x(n)=a*x(n-4)+carry mod 2^64-1,
+which works as follows:
+Assume a certain multiplier 'a' and a base 'b'.
+Given a current x value and a current carry 'c',
+form: t=a*x+c
+Then the new carry is c=floor(t/b)
+and the new x value is x = b-1-(t mod b).
+
+
+Ordinarily, for 32-bit mwc or cmwc sequences, the
+value t=a*x+c can be formed in 64 bits, then the new c
+is the top and the new x the bottom 32 bits (with a little
+fiddling when b=2^32-1 and cmwc rather than mwc.)
+
+
+To generate 64-bit x's, it is difficult to form
+t=a*x+c in 128 bits then get the new c and new x
+from the top and bottom halves.
+But if 'a' has a special form, for example,
+a=2^62+2^47+2 and b=2^64-1, then the new c and
+the new x can be formed with shifts, tests and +/-'s,
+again with a little fiddling because b=2^64-1 rather
+than 2^64. (The latter is not an optimal choice because,
+being a square, it cannot be a primitive root of the
+prime a*b^k+1, where 'k' is the 'lag':
+ x(n)=a*x(n-k)+carry mod b.)
+But the multiplier a=2^62+2^47+2 makes a*b^4+1 a prime for
+which b=2^64-1 is a primitive root, and getting the new x and
+new c can be done with arithmetic on integers the size of x.
+*/
+
+struct RndCtx
+{
+ uint64_t x;
+ uint64_t y;
+ uint64_t z;
+ uint64_t w;
+ uint64_t c;
+ uint32_t u32x;
+ uint32_t u32y;
+};
+typedef struct RndCtx RNDCTX;
+typedef RNDCTX *PRNDCTX;
+
+/**
+ * Initialize seeds.
+ *
+ * @remarks You should choose ANY 4 random 64-bit
+ * seeds x,y,z,w < 2^64-1 and a random seed c in
+ * 0<= c < a = 2^62+2^47+2.
+ * There are P=(2^62+2^46+2)*(2^64-1)^4 > 2^318 possible choices
+ * for seeds, the period of the RNG.
+ */
+RTDECL(int) RTPRandInit(PRNDCTX pCtx, uint32_t u32Seed)
+{
+ if (u32Seed == 0)
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ u32Seed = (uint32_t)(ASMReadTSC() >> 8);
+#else
+ u32Seed = (uint32_t)(RTTimeNanoTS() >> 19);
+#endif
+ /* Zero is not a good seed. */
+ if (u32Seed == 0)
+ u32Seed = 362436069;
+ pCtx->x = u32Seed;
+ pCtx->y = 17280675555674358941ULL;
+ pCtx->z = 6376492577913983186ULL;
+ pCtx->w = 9064188857900113776ULL;
+ pCtx->c = 123456789;
+ pCtx->u32x = 2282008;
+ pCtx->u32y = u32Seed;
+ return VINF_SUCCESS;
+}
+
+RTDECL(uint32_t) RTPRandGetSeedInfo(PRNDCTX pCtx)
+{
+ return pCtx->u32y;
+}
+
+/**
+ * Generate a 64-bit unsigned random number.
+ *
+ * @returns The pseudo random number.
+ */
+RTDECL(uint64_t) RTPRandU64(PRNDCTX pCtx)
+{
+ uint64_t t;
+ t = (pCtx->x<<47) + (pCtx->x<<62) + (pCtx->x<<1);
+ t += pCtx->c; t+= (t < pCtx->c);
+ pCtx->c = (t<pCtx->c) + (pCtx->x>>17) + (pCtx->x>>2) + (pCtx->x>>63);
+ pCtx->x = pCtx->y; pCtx->y = pCtx->z ; pCtx->z = pCtx->w;
+ return (pCtx->w = ~(t + pCtx->c)-1);
+}
+
+/**
+ * Generate a 64-bit unsigned pseudo random number in the set
+ * [u64First..u64Last].
+ *
+ * @returns The pseudo random number.
+ * @param u64First First number in the set.
+ * @param u64Last Last number in the set.
+ */
+RTDECL(uint64_t) RTPRandU64Ex(PRNDCTX pCtx, uint64_t u64First, uint64_t u64Last)
+{
+ if (u64First == 0 && u64Last == UINT64_MAX)
+ return RTPRandU64(pCtx);
+
+ uint64_t u64Tmp;
+ uint64_t u64Range = u64Last - u64First + 1;
+ uint64_t u64Scale = UINT64_MAX / u64Range;
+
+ do
+ {
+ u64Tmp = RTPRandU64(pCtx) / u64Scale;
+ } while (u64Tmp >= u64Range);
+ return u64First + u64Tmp;
+}
+
+/**
+ * Generate a 32-bit unsigned random number.
+ *
+ * @returns The pseudo random number.
+ */
+RTDECL(uint32_t) RTPRandU32(PRNDCTX pCtx)
+{
+ return ( pCtx->u32x = 69069 * pCtx->u32x + 123,
+ pCtx->u32y ^= pCtx->u32y<<13,
+ pCtx->u32y ^= pCtx->u32y>>17,
+ pCtx->u32y ^= pCtx->u32y<<5,
+ pCtx->u32x + pCtx->u32y );
+}
+
+/**
+ * Generate a 32-bit unsigned pseudo random number in the set
+ * [u32First..u32Last].
+ *
+ * @returns The pseudo random number.
+ * @param u32First First number in the set.
+ * @param u32Last Last number in the set.
+ */
+RTDECL(uint32_t) RTPRandU32Ex(PRNDCTX pCtx, uint32_t u32First, uint32_t u32Last)
+{
+ if (u32First == 0 && u32Last == UINT32_MAX)
+ return RTPRandU32(pCtx);
+
+ uint32_t u32Tmp;
+ uint32_t u32Range = u32Last - u32First + 1;
+ uint32_t u32Scale = UINT32_MAX / u32Range;
+
+ do
+ {
+ u32Tmp = RTPRandU32(pCtx) / u32Scale;
+ } while (u32Tmp >= u32Range);
+ return u32First + u32Tmp;
+}
+
+/* End of IPRT code */
+
+struct Segment
+{
+ uint64_t u64Offset;
+ uint32_t u32Length;
+ uint32_t u8Value;
+};
+typedef struct Segment *PSEGMENT;
+
+static void initializeRandomGenerator(PRNDCTX pCtx, uint32_t u32Seed)
+{
+ int rc = RTPRandInit(pCtx, u32Seed);
+ if (RT_FAILURE(rc))
+ RTPrintf("ERROR: Failed to initialize random generator. RC=%Rrc\n", rc);
+ else
+ {
+ RTPrintf("INFO: Random generator seed used: %x\n", RTPRandGetSeedInfo(pCtx));
+ RTLogPrintf("INFO: Random generator seed used: %x\n", RTPRandGetSeedInfo(pCtx));
+ }
+}
+
+static int compareSegments(const void *left, const void *right) RT_NOTHROW_DEF
+{
+ /* Note that no duplicates are allowed in the array being sorted. */
+ return ((PSEGMENT)left)->u64Offset < ((PSEGMENT)right)->u64Offset ? -1 : 1;
+}
+
+static void generateRandomSegments(PRNDCTX pCtx, PSEGMENT pSegment, uint32_t nSegments, uint32_t u32MaxSegmentSize, uint64_t u64DiskSize, uint32_t u32SectorSize, uint8_t u8ValueLow, uint8_t u8ValueHigh)
+{
+ uint32_t i;
+ /* Generate segment offsets. */
+ for (i = 0; i < nSegments; i++)
+ {
+ bool fDuplicateFound;
+ do
+ {
+ pSegment[i].u64Offset = RTPRandU64Ex(pCtx, 0, u64DiskSize / u32SectorSize - 1) * u32SectorSize;
+ fDuplicateFound = false;
+ for (uint32_t j = 0; j < i; j++)
+ if (pSegment[i].u64Offset == pSegment[j].u64Offset)
+ {
+ fDuplicateFound = true;
+ break;
+ }
+ } while (fDuplicateFound);
+ }
+ /* Sort in offset-ascending order. */
+ qsort(pSegment, nSegments, sizeof(*pSegment), compareSegments);
+ /* Put a sentinel at the end. */
+ pSegment[nSegments].u64Offset = u64DiskSize;
+ pSegment[nSegments].u32Length = 0;
+ /* Generate segment lengths and values. */
+ for (i = 0; i < nSegments; i++)
+ {
+ pSegment[i].u32Length = RTPRandU32Ex(pCtx, 1, RT_MIN(pSegment[i+1].u64Offset - pSegment[i].u64Offset,
+ u32MaxSegmentSize) / u32SectorSize) * u32SectorSize;
+ Assert(pSegment[i].u32Length <= u32MaxSegmentSize);
+ pSegment[i].u8Value = RTPRandU32Ex(pCtx, (uint32_t)u8ValueLow, (uint32_t)u8ValueHigh);
+ }
+}
+
+static void mergeSegments(PSEGMENT pBaseSegment, PSEGMENT pDiffSegment, PSEGMENT pMergeSegment, uint32_t u32MaxLength)
+{
+ RT_NOREF1(u32MaxLength);
+
+ while (pBaseSegment->u32Length > 0 || pDiffSegment->u32Length > 0)
+ {
+ if (pBaseSegment->u64Offset < pDiffSegment->u64Offset)
+ {
+ *pMergeSegment = *pBaseSegment;
+ if (pMergeSegment->u64Offset + pMergeSegment->u32Length <= pDiffSegment->u64Offset)
+ pBaseSegment++;
+ else
+ {
+ pMergeSegment->u32Length = pDiffSegment->u64Offset - pMergeSegment->u64Offset;
+ Assert(pMergeSegment->u32Length <= u32MaxLength);
+ if (pBaseSegment->u64Offset + pBaseSegment->u32Length >
+ pDiffSegment->u64Offset + pDiffSegment->u32Length)
+ {
+ pBaseSegment->u32Length -= pDiffSegment->u64Offset + pDiffSegment->u32Length - pBaseSegment->u64Offset;
+ Assert(pBaseSegment->u32Length <= u32MaxLength);
+ pBaseSegment->u64Offset = pDiffSegment->u64Offset + pDiffSegment->u32Length;
+ }
+ else
+ pBaseSegment++;
+ }
+ pMergeSegment++;
+ }
+ else
+ {
+ *pMergeSegment = *pDiffSegment;
+ if (pMergeSegment->u64Offset + pMergeSegment->u32Length <= pBaseSegment->u64Offset)
+ {
+ pDiffSegment++;
+ pMergeSegment++;
+ }
+ else
+ {
+ if (pBaseSegment->u64Offset + pBaseSegment->u32Length > pDiffSegment->u64Offset + pDiffSegment->u32Length)
+ {
+ pBaseSegment->u32Length -= pDiffSegment->u64Offset + pDiffSegment->u32Length - pBaseSegment->u64Offset;
+ Assert(pBaseSegment->u32Length <= u32MaxLength);
+ pBaseSegment->u64Offset = pDiffSegment->u64Offset + pDiffSegment->u32Length;
+ pDiffSegment++;
+ pMergeSegment++;
+ }
+ else
+ pBaseSegment++;
+ }
+ }
+ }
+}
+
+static void writeSegmentsToDisk(PVDISK pVD, void *pvBuf, PSEGMENT pSegment)
+{
+ while (pSegment->u32Length)
+ {
+ //memset((uint8_t*)pvBuf + pSegment->u64Offset, pSegment->u8Value, pSegment->u32Length);
+ memset(pvBuf, pSegment->u8Value, pSegment->u32Length);
+ VDWrite(pVD, pSegment->u64Offset, pvBuf, pSegment->u32Length);
+ pSegment++;
+ }
+}
+
+static int readAndCompareSegments(PVDISK pVD, void *pvBuf, PSEGMENT pSegment)
+{
+ while (pSegment->u32Length)
+ {
+ int rc = VDRead(pVD, pSegment->u64Offset, pvBuf, pSegment->u32Length);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("ERROR: Failed to read from virtual disk\n");
+ return rc;
+ }
+ else
+ {
+ for (unsigned i = 0; i < pSegment->u32Length; i++)
+ if (((uint8_t*)pvBuf)[i] != pSegment->u8Value)
+ {
+ RTPrintf("ERROR: Segment at %Lx of %x bytes is corrupt at offset %x (found %x instead of %x)\n",
+ pSegment->u64Offset, pSegment->u32Length, i, ((uint8_t*)pvBuf)[i],
+ pSegment->u8Value);
+ RTLogPrintf("ERROR: Segment at %Lx of %x bytes is corrupt at offset %x (found %x instead of %x)\n",
+ pSegment->u64Offset, pSegment->u32Length, i, ((uint8_t*)pvBuf)[i],
+ pSegment->u8Value);
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ pSegment++;
+ }
+
+ return VINF_SUCCESS;
+}
+
+static int tstVDOpenCreateWriteMerge(const char *pszBackend,
+ const char *pszBaseFilename,
+ const char *pszDiffFilename,
+ uint32_t u32Seed)
+{
+ int rc;
+ PVDISK pVD = NULL;
+ char *pszFormat;
+ VDTYPE enmType = VDTYPE_INVALID;
+ VDGEOMETRY PCHS = { 0, 0, 0 };
+ VDGEOMETRY LCHS = { 0, 0, 0 };
+ uint64_t u64DiskSize = 1000 * _1M;
+ uint32_t u32SectorSize = 512;
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACEERROR VDIfError;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ if (pvBuf) \
+ RTMemFree(pvBuf); \
+ VDDestroy(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ void *pvBuf = RTMemAlloc(_1M);
+
+ /* Create error interface. */
+ VDIfError.pfnError = tstVDError;
+ VDIfError.pfnMessage = tstVDMessage;
+
+ rc = VDInterfaceAdd(&VDIfError.Core, "tstVD_Error", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+ AssertRC(rc);
+
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pVD);
+ CHECK("VDCreate()");
+
+ RTFILE File;
+ rc = RTFileOpen(&File, pszBaseFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ RTFileClose(File);
+ rc = VDGetFormat(NULL /* pVDIfsDisk */, NULL /* pVDIfsImage */,
+ pszBaseFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ RTPrintf("VDGetFormat() pszFormat=%s rc=%Rrc\n", pszFormat, rc);
+ if (RT_SUCCESS(rc) && strcmp(pszFormat, pszBackend))
+ {
+ rc = VERR_GENERAL_FAILURE;
+ RTPrintf("VDGetFormat() returned incorrect backend name\n");
+ }
+ RTStrFree(pszFormat);
+ CHECK("VDGetFormat()");
+
+ rc = VDOpen(pVD, pszBackend, pszBaseFilename, VD_OPEN_FLAGS_NORMAL,
+ NULL);
+ CHECK("VDOpen()");
+ }
+ else
+ {
+ rc = VDCreateBase(pVD, pszBackend, pszBaseFilename, u64DiskSize,
+ VD_IMAGE_FLAGS_NONE, "Test image",
+ &PCHS, &LCHS, NULL, VD_OPEN_FLAGS_NORMAL,
+ NULL, NULL);
+ CHECK("VDCreateBase()");
+ }
+
+ int nSegments = 100;
+ /* Allocate one extra element for a sentinel. */
+ PSEGMENT paBaseSegments = (PSEGMENT)RTMemAllocZ(sizeof(struct Segment) * (nSegments + 1));
+ PSEGMENT paDiffSegments = (PSEGMENT)RTMemAllocZ(sizeof(struct Segment) * (nSegments + 1));
+ PSEGMENT paMergeSegments = (PSEGMENT)RTMemAllocZ(sizeof(struct Segment) * (nSegments + 1) * 3);
+
+ RNDCTX ctx;
+ initializeRandomGenerator(&ctx, u32Seed);
+ generateRandomSegments(&ctx, paBaseSegments, nSegments, _1M, u64DiskSize, u32SectorSize, 0u, 127u);
+ generateRandomSegments(&ctx, paDiffSegments, nSegments, _1M, u64DiskSize, u32SectorSize, 128u, 255u);
+
+ /*PSEGMENT pSegment;
+ RTPrintf("Base segments:\n");
+ for (pSegment = paBaseSegments; pSegment->u32Length; pSegment++)
+ RTPrintf("off: %08Lx len: %05x val: %02x\n", pSegment->u64Offset, pSegment->u32Length, pSegment->u8Value);*/
+ writeSegmentsToDisk(pVD, pvBuf, paBaseSegments);
+
+ rc = VDCreateDiff(pVD, pszBackend, pszDiffFilename,
+ VD_IMAGE_FLAGS_NONE, "Test diff image", NULL, NULL,
+ VD_OPEN_FLAGS_NORMAL, NULL, NULL);
+ CHECK("VDCreateDiff()");
+
+ /*RTPrintf("\nDiff segments:\n");
+ for (pSegment = paDiffSegments; pSegment->u32Length; pSegment++)
+ RTPrintf("off: %08Lx len: %05x val: %02x\n", pSegment->u64Offset, pSegment->u32Length, pSegment->u8Value);*/
+ writeSegmentsToDisk(pVD, pvBuf, paDiffSegments);
+
+ VDDumpImages(pVD);
+
+ RTPrintf("Merging diff into base..\n");
+ rc = VDMerge(pVD, VD_LAST_IMAGE, 0, NULL);
+ CHECK("VDMerge()");
+
+ mergeSegments(paBaseSegments, paDiffSegments, paMergeSegments, _1M);
+ /*RTPrintf("\nMerged segments:\n");
+ for (pSegment = paMergeSegments; pSegment->u32Length; pSegment++)
+ RTPrintf("off: %08Lx len: %05x val: %02x\n", pSegment->u64Offset, pSegment->u32Length, pSegment->u8Value);*/
+ rc = readAndCompareSegments(pVD, pvBuf, paMergeSegments);
+ CHECK("readAndCompareSegments()");
+
+ RTMemFree(paMergeSegments);
+ RTMemFree(paDiffSegments);
+ RTMemFree(paBaseSegments);
+
+ VDDumpImages(pVD);
+
+ VDDestroy(pVD);
+ if (pvBuf)
+ RTMemFree(pvBuf);
+#undef CHECK
+ return 0;
+}
+
+static int tstVDCreateWriteOpenRead(const char *pszBackend,
+ const char *pszFilename,
+ uint32_t u32Seed)
+{
+ int rc;
+ PVDISK pVD = NULL;
+ VDGEOMETRY PCHS = { 0, 0, 0 };
+ VDGEOMETRY LCHS = { 0, 0, 0 };
+ uint64_t u64DiskSize = 1000 * _1M;
+ uint32_t u32SectorSize = 512;
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACEERROR VDIfError;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ if (pvBuf) \
+ RTMemFree(pvBuf); \
+ VDDestroy(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ void *pvBuf = RTMemAlloc(_1M);
+
+ /* Create error interface. */
+ VDIfError.pfnError = tstVDError;
+ VDIfError.pfnMessage = tstVDMessage;
+
+ rc = VDInterfaceAdd(&VDIfError.Core, "tstVD_Error", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+ AssertRC(rc);
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pVD);
+ CHECK("VDCreate()");
+
+ RTFILE File;
+ rc = RTFileOpen(&File, pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ RTFileClose(File);
+ RTFileDelete(pszFilename);
+ }
+
+ rc = VDCreateBase(pVD, pszBackend, pszFilename, u64DiskSize,
+ VD_IMAGE_FLAGS_NONE, "Test image",
+ &PCHS, &LCHS, NULL, VD_OPEN_FLAGS_NORMAL,
+ NULL, NULL);
+ CHECK("VDCreateBase()");
+
+ int nSegments = 100;
+ /* Allocate one extra element for a sentinel. */
+ PSEGMENT paSegments = (PSEGMENT)RTMemAllocZ(sizeof(struct Segment) * (nSegments + 1));
+
+ RNDCTX ctx;
+ initializeRandomGenerator(&ctx, u32Seed);
+ generateRandomSegments(&ctx, paSegments, nSegments, _1M, u64DiskSize, u32SectorSize, 0u, 127u);
+ /*for (PSEGMENT pSegment = paSegments; pSegment->u32Length; pSegment++)
+ RTPrintf("off: %08Lx len: %05x val: %02x\n", pSegment->u64Offset, pSegment->u32Length, pSegment->u8Value);*/
+
+ writeSegmentsToDisk(pVD, pvBuf, paSegments);
+
+ VDCloseAll(pVD);
+
+ rc = VDOpen(pVD, pszBackend, pszFilename, VD_OPEN_FLAGS_NORMAL, NULL);
+ CHECK("VDOpen()");
+ rc = readAndCompareSegments(pVD, pvBuf, paSegments);
+ CHECK("readAndCompareSegments()");
+
+ RTMemFree(paSegments);
+
+ VDDestroy(pVD);
+ if (pvBuf)
+ RTMemFree(pvBuf);
+#undef CHECK
+ return 0;
+}
+
+static int tstVmdkRename(const char *src, const char *dst)
+{
+ int rc;
+ PVDISK pVD = NULL;
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACEERROR VDIfError;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ VDDestroy(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ /* Create error interface. */
+ VDIfError.pfnError = tstVDError;
+ VDIfError.pfnMessage = tstVDMessage;
+
+ rc = VDInterfaceAdd(&VDIfError.Core, "tstVD_Error", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+ AssertRC(rc);
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDOpen(pVD, "VMDK", src, VD_OPEN_FLAGS_NORMAL, NULL);
+ CHECK("VDOpen()");
+ rc = VDCopy(pVD, 0, pVD, "VMDK", dst, true, 0, VD_IMAGE_FLAGS_NONE, NULL,
+ VD_OPEN_FLAGS_NORMAL, NULL, NULL, NULL);
+ CHECK("VDCopy()");
+
+ VDDestroy(pVD);
+#undef CHECK
+ return 0;
+}
+
+static int tstVmdkCreateRenameOpen(const char *src, const char *dst,
+ uint64_t cbSize, unsigned uFlags)
+{
+ int rc = tstVDCreateDelete("VMDK", src, cbSize, uFlags, false);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = tstVmdkRename(src, dst);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ PVDISK pVD = NULL;
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACEERROR VDIfError;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ VDCloseAll(pVD); \
+ return rc; \
+ } \
+ } while (0)
+
+ /* Create error interface. */
+ VDIfError.pfnError = tstVDError;
+ VDIfError.pfnMessage = tstVDMessage;
+
+ rc = VDInterfaceAdd(&VDIfError.Core, "tstVD_Error", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+ AssertRC(rc);
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDOpen(pVD, "VMDK", dst, VD_OPEN_FLAGS_NORMAL, NULL);
+ CHECK("VDOpen()");
+
+ VDClose(pVD, true);
+ CHECK("VDClose()");
+ VDDestroy(pVD);
+#undef CHECK
+ return rc;
+}
+
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+#define DST_PATH "tmp\\tmpVDRename.vmdk"
+#else
+#define DST_PATH "tmp/tmpVDRename.vmdk"
+#endif
+
+static void tstVmdk()
+{
+ int rc = tstVmdkCreateRenameOpen("tmpVDCreate.vmdk", "tmpVDRename.vmdk", _4G,
+ VD_IMAGE_FLAGS_NONE);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK rename (single extent, embedded descriptor, same dir) test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVmdkCreateRenameOpen("tmpVDCreate.vmdk", "tmpVDRename.vmdk", _4G,
+ VD_VMDK_IMAGE_FLAGS_SPLIT_2G);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK rename (multiple extent, separate descriptor, same dir) test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVmdkCreateRenameOpen("tmpVDCreate.vmdk", DST_PATH, _4G,
+ VD_IMAGE_FLAGS_NONE);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK rename (single extent, embedded descriptor, another dir) test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVmdkCreateRenameOpen("tmpVDCreate.vmdk", DST_PATH, _4G,
+ VD_VMDK_IMAGE_FLAGS_SPLIT_2G);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK rename (multiple extent, separate descriptor, another dir) test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+
+ RTFILE File;
+ rc = RTFileOpen(&File, DST_PATH, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ RTFileClose(File);
+
+ rc = tstVmdkCreateRenameOpen("tmpVDCreate.vmdk", DST_PATH, _4G,
+ VD_VMDK_IMAGE_FLAGS_SPLIT_2G);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("tstVD: VMDK rename (multiple extent, separate descriptor, another dir, already exists) test failed!\n");
+ g_cErrors++;
+ }
+ RTFileDelete(DST_PATH);
+ RTFileDelete("tmpVDCreate.vmdk");
+ RTFileDelete("tmpVDCreate-s001.vmdk");
+ RTFileDelete("tmpVDCreate-s002.vmdk");
+ RTFileDelete("tmpVDCreate-s003.vmdk");
+}
+
+int main(int argc, char *argv[])
+{
+ RTR3InitExe(argc, &argv, 0);
+ int rc;
+
+ uint32_t u32Seed = 0; // Means choose random
+
+ if (argc > 1)
+ if (sscanf(argv[1], "%x", &u32Seed) != 1)
+ {
+ RTPrintf("ERROR: Invalid parameter %s. Valid usage is %s <32-bit seed>.\n",
+ argv[1], argv[0]);
+ return 1;
+ }
+
+ RTPrintf("tstVD: TESTING...\n");
+
+ /*
+ * Clean up potential leftovers from previous unsuccessful runs.
+ */
+ RTFileDelete("tmpVDCreate.vdi");
+ RTFileDelete("tmpVDCreate.vmdk");
+ RTFileDelete("tmpVDCreate.vhd");
+ RTFileDelete("tmpVDBase.vdi");
+ RTFileDelete("tmpVDDiff.vdi");
+ RTFileDelete("tmpVDBase.vmdk");
+ RTFileDelete("tmpVDDiff.vmdk");
+ RTFileDelete("tmpVDBase.vhd");
+ RTFileDelete("tmpVDDiff.vhd");
+ RTFileDelete("tmpVDCreate-s001.vmdk");
+ RTFileDelete("tmpVDCreate-s002.vmdk");
+ RTFileDelete("tmpVDCreate-s003.vmdk");
+ RTFileDelete("tmpVDRename.vmdk");
+ RTFileDelete("tmpVDRename-s001.vmdk");
+ RTFileDelete("tmpVDRename-s002.vmdk");
+ RTFileDelete("tmpVDRename-s003.vmdk");
+ RTFileDelete("tmp/tmpVDRename.vmdk");
+ RTFileDelete("tmp/tmpVDRename-s001.vmdk");
+ RTFileDelete("tmp/tmpVDRename-s002.vmdk");
+ RTFileDelete("tmp/tmpVDRename-s003.vmdk");
+
+ if (!RTDirExists("tmp"))
+ {
+ rc = RTDirCreate("tmp", RTFS_UNIX_IRWXU, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: Failed to create 'tmp' directory! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ }
+
+#ifdef VMDK_TEST
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_FLAGS_NONE, true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_FLAGS_NONE, false);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDOpenDelete("VMDK", "tmpVDCreate.vmdk");
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK delete test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+
+ tstVmdk();
+#endif /* VMDK_TEST */
+#ifdef VDI_TEST
+ rc = tstVDCreateDelete("VDI", "tmpVDCreate.vdi", 2 * _4G,
+ VD_IMAGE_FLAGS_NONE, true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic VDI create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VDI", "tmpVDCreate.vdi", 2 * _4G,
+ VD_IMAGE_FLAGS_NONE, true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: fixed VDI create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VDI_TEST */
+#ifdef VMDK_TEST
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_FLAGS_NONE, true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_VMDK_IMAGE_FLAGS_SPLIT_2G, true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic split VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_FLAGS_FIXED, true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: fixed VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VMDK", "tmpVDCreate.vmdk", 2 * _4G,
+ VD_IMAGE_FLAGS_FIXED | VD_VMDK_IMAGE_FLAGS_SPLIT_2G,
+ true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: fixed split VMDK create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VMDK_TEST */
+#ifdef VHD_TEST
+ rc = tstVDCreateDelete("VHD", "tmpVDCreate.vhd", 2 * _4G,
+ VD_IMAGE_FLAGS_NONE, true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: dynamic VHD create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDCreateDelete("VHD", "tmpVDCreate.vhd", 2 * _4G,
+ VD_IMAGE_FLAGS_FIXED, true);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: fixed VHD create test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VHD_TEST */
+#ifdef VDI_TEST
+ rc = tstVDOpenCreateWriteMerge("VDI", "tmpVDBase.vdi", "tmpVDDiff.vdi", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VDI test failed (new image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDOpenCreateWriteMerge("VDI", "tmpVDBase.vdi", "tmpVDDiff.vdi", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VDI test failed (existing image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VDI_TEST */
+#ifdef VMDK_TEST
+ rc = tstVDOpenCreateWriteMerge("VMDK", "tmpVDBase.vmdk", "tmpVDDiff.vmdk", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK test failed (new image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ rc = tstVDOpenCreateWriteMerge("VMDK", "tmpVDBase.vmdk", "tmpVDDiff.vmdk", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VMDK test failed (existing image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VMDK_TEST */
+#ifdef VHD_TEST
+ rc = tstVDCreateWriteOpenRead("VHD", "tmpVDCreate.vhd", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VHD test failed (creating image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+
+ rc = tstVDOpenCreateWriteMerge("VHD", "tmpVDBase.vhd", "tmpVDDiff.vhd", u32Seed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: VHD test failed (existing image)! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+#endif /* VHD_TEST */
+
+ /*
+ * Clean up any leftovers.
+ */
+ RTFileDelete("tmpVDCreate.vdi");
+ RTFileDelete("tmpVDCreate.vmdk");
+ RTFileDelete("tmpVDCreate.vhd");
+ RTFileDelete("tmpVDBase.vdi");
+ RTFileDelete("tmpVDDiff.vdi");
+ RTFileDelete("tmpVDBase.vmdk");
+ RTFileDelete("tmpVDDiff.vmdk");
+ RTFileDelete("tmpVDBase.vhd");
+ RTFileDelete("tmpVDDiff.vhd");
+ RTFileDelete("tmpVDCreate-s001.vmdk");
+ RTFileDelete("tmpVDCreate-s002.vmdk");
+ RTFileDelete("tmpVDCreate-s003.vmdk");
+ RTFileDelete("tmpVDRename.vmdk");
+ RTFileDelete("tmpVDRename-s001.vmdk");
+ RTFileDelete("tmpVDRename-s002.vmdk");
+ RTFileDelete("tmpVDRename-s003.vmdk");
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD: unloading backends failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ /*
+ * Summary
+ */
+ if (!g_cErrors)
+ RTPrintf("tstVD: SUCCESS\n");
+ else
+ RTPrintf("tstVD: FAILURE - %d errors\n", g_cErrors);
+
+ return !!g_cErrors;
+}
+
diff --git a/src/VBox/Storage/testcase/tstVDCompact.vd b/src/VBox/Storage/testcase/tstVDCompact.vd
new file mode 100644
index 00000000..3035872c
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDCompact.vd
@@ -0,0 +1,102 @@
+/* $Id: tstVDCompact.vd $ */
+/**
+ * Storage: Testcase for compacting disks.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+void tstCompact(string strMsg, string strBackend)
+{
+ print(strMsg);
+
+ /* Create disk containers, read verification is on. */
+ createdisk("disk", true);
+ create("disk", "base", "tstCompact.disk", "dynamic", strBackend, 200M, false, false);
+
+ /* Fill the disk with random data. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 100, "none");
+ /* Read the data to verify it once. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 0, "none");
+ /* Fill a part with 0's. */
+ io("disk", false, 1, "seq", 64K, 100M, 150M, 50M, 100, "zero");
+
+ /* Now compact. */
+ compact("disk", 0);
+ /* Read again to verify that the content hasn't changed. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 0, "none");
+ /* Fill everything with 0. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 100, "zero");
+ /* Now compact again. */
+ compact("disk", 0);
+ /* Read again to verify that the content hasn't changed. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 0, "none");
+
+ close("disk", "single", true);
+ destroydisk("disk");
+}
+
+void tstSnapshotCompact(string strMsg, string strBackend)
+{
+ print(strMsg);
+
+ /* Create disk containers, read verification is on. */
+ createdisk("disk", true);
+ create("disk", "base", "tstCompact.disk", "dynamic", strBackend, 200M, false, false);
+
+ /* Fill the disk with random data. */
+ io("disk", false, 1, "seq", 64K, 0, 100M, 100M, 100, "none");
+
+ create("test", "diff", "tst2.disk", "dynamic", strBackend, 200M, false /* fIgnoreFlush */, true /* fHonorSame */);
+
+ io("disk", false, 1, "seq", 64K, 100M, 200M, 100M, 100, "none");
+ io("disk", false, 1, "seq", 64K, 100M, 150M, 50M, 100, "zero");
+
+ create("disk", "diff", "tst3.disk", "dynamic", strBackend, 200M, false /* fIgnoreFlush */, true /* fHonorSame */);
+ merge("disk", 1, 2);
+
+ compact("disk", 1);
+
+ close("disk", "single", true);
+ close("disk", "single", true);
+ close("disk", "single", true);
+ destroydisk("disk");
+}
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes. */
+ iorngcreate(10M, "manual", 1234567890);
+
+ /* Create zero pattern */
+ iopatterncreatefromnumber("zero", 1M, 0);
+
+ tstCompact("Testing VDI", "VDI");
+ tstCompact("Testing VHD", "VHD");
+
+ tstSnapshotCompact("Testing Snapshot VDI", "VDI");
+ tstSnapshotCompact("Testing Snapshot VHD", "VHD");
+
+ /* Destroy RNG and pattern */
+ iopatterndestroy("zero");
+ iorngdestroy();
+}
diff --git a/src/VBox/Storage/testcase/tstVDCopy.vd b/src/VBox/Storage/testcase/tstVDCopy.vd
new file mode 100644
index 00000000..ecde8d28
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDCopy.vd
@@ -0,0 +1,100 @@
+/* $Id: tstVDCopy.vd $ */
+/**
+ * Storage: Testcase for VDCopy with snapshots and optimizations.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes. */
+ iorngcreate(10M, "manual", 1234567890);
+
+ /* Create source disk and fill data. */
+ print("Creating Source Disk");
+ createdisk("source", false);
+ create("source", "base", "source_base.vdi", "dynamic", "VDI", 1G, false, false);
+ io("source", false, 1, "rnd", 64K, 0, 512M, 256M, 100, "none");
+
+ print("Creating first diff");
+ create("source", "diff", "source_diff1.vdi", "dynamic", "VDI", 1G, false, false);
+ io("source", false, 1, "rnd", 64K, 512M, 1G, 256M, 50, "none");
+
+ print("Creating second diff");
+ create("source", "diff", "source_diff2.vdi", "dynamic", "VDI", 1G, false, false);
+ io("source", false, 1, "rnd", 1M, 0, 1G, 45M, 100, "none");
+
+ print("Creating third diff");
+ create("source", "diff", "source_diff3.vdi", "dynamic", "VDI", 1G, false, false);
+ io("source", false, 1, "rnd", 1M, 0, 1G, 45M, 100, "none");
+
+ print("Creating fourth diff");
+ create("source", "diff", "source_diff4.vdi", "dynamic", "VDI", 1G, false, false);
+ io("source", false, 1, "rnd", 1M, 0, 1G, 45M, 100, "none");
+
+ print("Creating destination disk");
+ createdisk("dest", false);
+
+ print("Copying base image");
+ copy("source", "dest", 0, "VDI", "dest_base.vdi", false, 0, 0xffffffff, 0xffffffff); /* Image content unknown */
+
+ print("Copying first diff optimized");
+ copy("source", "dest", 1, "VDI", "dest_diff1.vdi", false, 0, 0, 0);
+
+ print("Copying other diffs optimized");
+ copy("source", "dest", 2, "VDI", "dest_diff2.vdi", false, 0, 1, 1);
+ copy("source", "dest", 3, "VDI", "dest_diff3.vdi", false, 0, 2, 2);
+ copy("source", "dest", 4, "VDI", "dest_diff4.vdi", false, 0, 3, 3);
+
+ print("Comparing disks");
+ comparedisks("source", "dest");
+
+ printfilesize("source", 0);
+ printfilesize("source", 1);
+ printfilesize("source", 2);
+ printfilesize("source", 3);
+ printfilesize("source", 4);
+
+ printfilesize("dest", 0);
+ printfilesize("dest", 1);
+ printfilesize("dest", 2);
+ printfilesize("dest", 3);
+ printfilesize("dest", 4);
+
+ print("Cleaning up");
+ close("dest", "single", true);
+ close("dest", "single", true);
+ close("dest", "single", true);
+ close("dest", "single", true);
+ close("dest", "single", true);
+
+ close("source", "single", true);
+ close("source", "single", true);
+ close("source", "single", true);
+ close("source", "single", true);
+ close("source", "single", true);
+ destroydisk("source");
+ destroydisk("dest");
+
+ iorngdestroy();
+}
diff --git a/src/VBox/Storage/testcase/tstVDDiscard.vd b/src/VBox/Storage/testcase/tstVDDiscard.vd
new file mode 100644
index 00000000..48a2877d
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDDiscard.vd
@@ -0,0 +1,69 @@
+/* $Id: tstVDDiscard.vd $ */
+/**
+ * Storage: Testcase for discarding data in a disk.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes. */
+ iorngcreate(10M, "manual", 1234567890);
+
+ print("Testing VDI");
+
+ /* Create disk containers, read verification is on. */
+ createdisk("disk", true /* fVerify */);
+ /* Create the disk. */
+ create("disk", "base", "tstCompact.vdi", "dynamic", "VDI", 2G, false /* fIgnoreFlush */, false);
+ /* Fill the disk with random data */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 100, "none");
+ /* Read the data to verify it once. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 0, "none");
+ close("disk", "single", false);
+
+ open("disk", "tstCompact.vdi", "VDI", true, false, false, true, false, false);
+ printfilesize("disk", 0);
+ discard("disk", true, "6,0M,512K,1M,512K,2M,512K,3M,512K,4M,512K,5M,512K");
+ discard("disk", true, "6,6M,512K,7M,512K,8M,512K,9M,512K,10M,512K,11M,512K");
+ discard("disk", true, "1,512K,512K");
+ discard("disk", false, "1,1024K,64K");
+ printfilesize("disk", 0);
+
+ print("Discard whole block");
+ discard("disk", true, "1,20M,1M");
+ printfilesize("disk", 0);
+
+ print("Split Discard");
+ discard("disk", true, "1,21M,512K");
+ printfilesize("disk", 0);
+ discard("disk", true, "1,22016K,512K");
+ printfilesize("disk", 0);
+
+ /* Cleanup */
+ close("disk", "single", true);
+ destroydisk("disk");
+
+ /* Destroy RNG and pattern */
+ iorngdestroy();
+}
diff --git a/src/VBox/Storage/testcase/tstVDFill.cpp b/src/VBox/Storage/testcase/tstVDFill.cpp
new file mode 100644
index 00000000..5d46eddd
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDFill.cpp
@@ -0,0 +1,244 @@
+/** @file
+ *
+ * Test utility to fill a given image with random data up to a certain size (sequentially).
+ */
+
+/*
+ * Copyright (C) 2016-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/vd.h>
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include <iprt/dir.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/initterm.h>
+#include <iprt/getopt.h>
+#include <iprt/rand.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The error count. */
+unsigned g_cErrors = 0;
+/** Global RNG state. */
+RTRAND g_hRand;
+
+#define TSTVDFILL_TEST_PATTERN_SIZE _1M
+
+static DECLCALLBACK(void) tstVDError(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va)
+{
+ RT_NOREF1(pvUser);
+ g_cErrors++;
+ RTPrintf("tstVDFill: Error %Rrc at %s:%u (%s): ", rc, RT_SRC_POS_ARGS);
+ RTPrintfV(pszFormat, va);
+ RTPrintf("\n");
+}
+
+static DECLCALLBACK(int) tstVDMessage(void *pvUser, const char *pszFormat, va_list va)
+{
+ RT_NOREF1(pvUser);
+ RTPrintf("tstVDFill: ");
+ RTPrintfV(pszFormat, va);
+ return VINF_SUCCESS;
+}
+
+static int tstFill(const char *pszFilename, const char *pszFormat, bool fStreamOptimized, uint64_t cbDisk, uint64_t cbFill)
+{
+ int rc;
+ PVDISK pVD = NULL;
+ VDGEOMETRY PCHS = { 0, 0, 0 };
+ VDGEOMETRY LCHS = { 0, 0, 0 };
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACEERROR VDIfError;
+
+ /** Buffer storing the random test pattern. */
+ uint8_t *pbTestPattern = NULL;
+
+ /* Create the virtual disk test data */
+ pbTestPattern = (uint8_t *)RTMemAlloc(TSTVDFILL_TEST_PATTERN_SIZE);
+
+ RTRandAdvBytes(g_hRand, pbTestPattern, TSTVDFILL_TEST_PATTERN_SIZE);
+
+ RTPrintf("Disk size is %llu bytes\n", cbDisk);
+
+ /* Create error interface. */
+ /* Create error interface. */
+ VDIfError.pfnError = tstVDError;
+ VDIfError.pfnMessage = tstVDMessage;
+
+ rc = VDInterfaceAdd(&VDIfError.Core, "tstVD_Error", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+ AssertRC(rc);
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ if (pbTestPattern) \
+ RTMemFree(pbTestPattern); \
+ VDDestroy(pVD); \
+ g_cErrors++; \
+ return rc; \
+ } \
+ } while (0)
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDCreateBase(pVD, pszFormat, pszFilename, cbDisk,
+ fStreamOptimized ? VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED : VD_IMAGE_FLAGS_NONE,
+ "Test image", &PCHS, &LCHS, NULL, VD_OPEN_FLAGS_NORMAL,
+ NULL, NULL);
+ CHECK("VDCreateBase()");
+
+ uint64_t uOff = 0;
+ uint64_t cbGb = 0;
+ while ( uOff < cbFill
+ && RT_SUCCESS(rc))
+ {
+ size_t cbThisWrite = RT_MIN(TSTVDFILL_TEST_PATTERN_SIZE, cbFill - uOff);
+ rc = VDWrite(pVD, uOff, pbTestPattern, cbThisWrite);
+ if (RT_SUCCESS(rc))
+ {
+ uOff += cbThisWrite;
+ cbGb += cbThisWrite;
+ /* Print a message for every GB we wrote. */
+ if (cbGb >= _1G)
+ {
+ RTStrmPrintf(g_pStdErr, "Wrote %llu bytes\n", uOff);
+ cbGb = 0;
+ }
+ }
+ }
+
+ VDDestroy(pVD);
+ if (pbTestPattern)
+ RTMemFree(pbTestPattern);
+
+#undef CHECK
+ return rc;
+}
+
+/**
+ * Shows help message.
+ */
+static void printUsage(void)
+{
+ RTPrintf("Usage:\n"
+ "--disk-size <size in MB> Size of the disk\n"
+ "--fill-size <size in MB> How much to fill\n"
+ "--filename <filename> Filename of the image\n"
+ "--format <VDI|VMDK|...> Format to use\n"
+ "--streamoptimized Use the stream optimized format\n"
+ "--help Show this text\n");
+}
+
+static const RTGETOPTDEF g_aOptions[] =
+{
+ { "--disk-size", 's', RTGETOPT_REQ_UINT64 },
+ { "--fill-size", 'f', RTGETOPT_REQ_UINT64 },
+ { "--filename", 'p', RTGETOPT_REQ_STRING },
+ { "--format", 't', RTGETOPT_REQ_STRING },
+ { "--streamoptimized", 'r', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING }
+};
+
+int main(int argc, char *argv[])
+{
+ RTR3InitExe(argc, &argv, 0);
+ int rc;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ char c;
+ uint64_t cbDisk = 0;
+ uint64_t cbFill = 0;
+ const char *pszFilename = NULL;
+ const char *pszFormat = NULL;
+ bool fStreamOptimized = false;
+
+ rc = VDInit();
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ RTGetOptInit(&GetState, argc, argv, g_aOptions,
+ RT_ELEMENTS(g_aOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
+
+ while ( RT_SUCCESS(rc)
+ && (c = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (c)
+ {
+ case 's':
+ cbDisk = ValueUnion.u64 * _1M;
+ break;
+ case 'f':
+ cbFill = ValueUnion.u64 * _1M;
+ break;
+ case 'p':
+ pszFilename = ValueUnion.psz;
+ break;
+ case 't':
+ pszFormat = ValueUnion.psz;
+ break;
+ case 'r':
+ fStreamOptimized = true;
+ break;
+ case 'h':
+ default:
+ printUsage();
+ break;
+ }
+ }
+
+ if (!cbDisk || !cbFill || !pszFilename || !pszFormat)
+ {
+ RTPrintf("tstVDFill: Arguments missing!\n");
+ return 1;
+ }
+
+ rc = RTRandAdvCreateParkMiller(&g_hRand);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVDFill: Creating RNG failed rc=%Rrc\n", rc);
+ return 1;
+ }
+
+ RTRandAdvSeed(g_hRand, 0x12345678);
+
+ rc = tstFill(pszFilename, pszFormat, fStreamOptimized, cbDisk, cbFill);
+ if (RT_FAILURE(rc))
+ RTPrintf("tstVDFill: Filling disk failed! rc=%Rrc\n", rc);
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ RTPrintf("tstVDFill: unloading backends failed! rc=%Rrc\n", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Storage/testcase/tstVDIo.cpp b/src/VBox/Storage/testcase/tstVDIo.cpp
new file mode 100644
index 00000000..cb785082
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDIo.cpp
@@ -0,0 +1,3024 @@
+/* $Id: tstVDIo.cpp $ */
+/** @file
+ * VBox HDD container test utility - I/O replay.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOGGROUP LOGGROUP_DEFAULT
+#include <VBox/vd.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/mem.h>
+#include <iprt/initterm.h>
+#include <iprt/getopt.h>
+#include <iprt/list.h>
+#include <iprt/ctype.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/rand.h>
+#include <iprt/critsect.h>
+#include <iprt/test.h>
+#include <iprt/system.h>
+#include <iprt/tracelog.h>
+
+#include "VDMemDisk.h"
+#include "VDIoBackend.h"
+#include "VDIoRnd.h"
+
+#include "VDScript.h"
+#include "BuiltinTests.h"
+
+/** forward declaration for the global test data pointer. */
+typedef struct VDTESTGLOB *PVDTESTGLOB;
+
+/**
+ * A virtual file backed by memory.
+ */
+typedef struct VDFILE
+{
+ /** Pointer to the next file. */
+ RTLISTNODE Node;
+ /** Name of the file. */
+ char *pszName;
+ /** Storage backing the file. */
+ PVDIOSTORAGE pIoStorage;
+ /** Flag whether the file is read locked. */
+ bool fReadLock;
+ /** Flag whether the file is write locked. */
+ bool fWriteLock;
+ /** Statistics: Number of reads. */
+ unsigned cReads;
+ /** Statistics: Number of writes. */
+ unsigned cWrites;
+ /** Statistics: Number of flushes. */
+ unsigned cFlushes;
+ /** Statistics: Number of async reads. */
+ unsigned cAsyncReads;
+ /** Statistics: Number of async writes. */
+ unsigned cAsyncWrites;
+ /** Statistics: Number of async flushes. */
+ unsigned cAsyncFlushes;
+} VDFILE, *PVDFILE;
+
+/**
+ * VD storage object.
+ */
+typedef struct VDSTORAGE
+{
+ /** Pointer to the file. */
+ PVDFILE pFile;
+ /** Completion callback of the VD layer. */
+ PFNVDCOMPLETED pfnComplete;
+} VDSTORAGE, *PVDSTORAGE;
+
+/**
+ * A virtual disk.
+ */
+typedef struct VDDISK
+{
+ /** List node. */
+ RTLISTNODE ListNode;
+ /** Name of the disk handle for identification. */
+ char *pszName;
+ /** HDD handle to operate on. */
+ PVDISK pVD;
+ /** Memory disk used for data verification. */
+ PVDMEMDISK pMemDiskVerify;
+ /** Critical section to serialize access to the memory disk. */
+ RTCRITSECT CritSectVerify;
+ /** Physical CHS Geometry. */
+ VDGEOMETRY PhysGeom;
+ /** Logical CHS geometry. */
+ VDGEOMETRY LogicalGeom;
+ /** Global test data. */
+ PVDTESTGLOB pTestGlob;
+} VDDISK, *PVDDISK;
+
+/**
+ * A data buffer with a pattern.
+ */
+typedef struct VDPATTERN
+{
+ /** List node. */
+ RTLISTNODE ListNode;
+ /** Name of the pattern. */
+ char *pszName;
+ /** Size of the pattern. */
+ size_t cbPattern;
+ /** Pointer to the buffer containing the pattern. */
+ void *pvPattern;
+} VDPATTERN, *PVDPATTERN;
+
+/**
+ * Global VD test state.
+ */
+typedef struct VDTESTGLOB
+{
+ /** List of active virtual disks. */
+ RTLISTNODE ListDisks;
+ /** Head of the active file list. */
+ RTLISTNODE ListFiles;
+ /** Head of the pattern list. */
+ RTLISTNODE ListPatterns;
+ /** I/O backend, common data. */
+ PVDIOBACKEND pIoBackend;
+ /** Error interface. */
+ VDINTERFACEERROR VDIfError;
+ /** Pointer to the per disk interface list. */
+ PVDINTERFACE pInterfacesDisk;
+ /** I/O interface. */
+ VDINTERFACEIO VDIfIo;
+ /** Pointer to the per image interface list. */
+ PVDINTERFACE pInterfacesImages;
+ /** I/O RNG handle. */
+ PVDIORND pIoRnd;
+ /** Current storage backend to use. */
+ char *pszIoBackend;
+ /** Testcase handle. */
+ RTTEST hTest;
+} VDTESTGLOB;
+
+/**
+ * Transfer direction.
+ */
+typedef enum TSTVDIOREQTXDIR
+{
+ TSTVDIOREQTXDIR_READ = 0,
+ TSTVDIOREQTXDIR_WRITE,
+ TSTVDIOREQTXDIR_FLUSH,
+ TSTVDIOREQTXDIR_DISCARD
+} TSTVDIOREQTXDIR;
+
+/**
+ * I/O request.
+ */
+typedef struct TSTVDIOREQ
+{
+ /** Transfer type. */
+ TSTVDIOREQTXDIR enmTxDir;
+ /** slot index. */
+ unsigned idx;
+ /** Start offset. */
+ uint64_t off;
+ /** Size to transfer. */
+ size_t cbReq;
+ /** S/G Buffer */
+ RTSGBUF SgBuf;
+ /** Flag whether the request is outstanding or not. */
+ volatile bool fOutstanding;
+ /** Buffer to use for reads. */
+ void *pvBufRead;
+ /** Contiguous buffer pointer backing the segments. */
+ void *pvBuf;
+ /** Opaque user data. */
+ void *pvUser;
+ /** Number of segments used for the data buffer. */
+ uint32_t cSegs;
+ /** Array of data segments. */
+ RTSGSEG aSegs[10];
+} TSTVDIOREQ, *PTSTVDIOREQ;
+
+/**
+ * I/O test data.
+ */
+typedef struct VDIOTEST
+{
+ /** Start offset. */
+ uint64_t offStart;
+ /** End offset. */
+ uint64_t offEnd;
+ /** Flag whether random or sequential access is wanted */
+ bool fRandomAccess;
+ /** Block size. */
+ size_t cbBlkIo;
+ /** Number of bytes to transfer. */
+ uint64_t cbIo;
+ /** Chance in percent to get a write. */
+ unsigned uWriteChance;
+ /** Maximum number of segments to create for one request. */
+ uint32_t cSegsMax;
+ /** Pointer to the I/O data generator. */
+ PVDIORND pIoRnd;
+ /** Pointer to the data pattern to use. */
+ PVDPATTERN pPattern;
+ /** Data dependent on the I/O mode (sequential or random). */
+ union
+ {
+ /** Next offset for sequential access. */
+ uint64_t offNext;
+ /** Data for random acess. */
+ struct
+ {
+ /** Number of valid entries in the bitmap. */
+ uint32_t cBlocks;
+ /** Pointer to the bitmap marking accessed blocks. */
+ uint8_t *pbMapAccessed;
+ /** Number of unaccessed blocks. */
+ uint32_t cBlocksLeft;
+ } Rnd;
+ } u;
+} VDIOTEST, *PVDIOTEST;
+
+static DECLCALLBACK(int) vdScriptHandlerCreate(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerOpen(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerIo(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerFlush(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerMerge(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerCompact(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerDiscard(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerCopy(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerClose(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerPrintFileSize(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerIoRngCreate(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerIoRngDestroy(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerIoPatternCreateFromNumber(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerIoPatternCreateFromFile(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerIoPatternDestroy(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerSleep(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerDumpFile(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerCreateDisk(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerDestroyDisk(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerCompareDisks(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerDumpDiskInfo(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerPrintMsg(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerShowStatistics(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerResetStatistics(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerResize(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerSetFileBackend(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerLoadPlugin(PVDSCRIPTARG paScriptArgs, void *pvUser);
+static DECLCALLBACK(int) vdScriptHandlerIoLogReplay(PVDSCRIPTARG paScriptArgs, void *pvUser);
+
+/* create action */
+const VDSCRIPTTYPE g_aArgCreate[] =
+{
+ VDSCRIPTTYPE_STRING,
+ VDSCRIPTTYPE_STRING,
+ VDSCRIPTTYPE_STRING,
+ VDSCRIPTTYPE_STRING,
+ VDSCRIPTTYPE_STRING,
+ VDSCRIPTTYPE_UINT64,
+ VDSCRIPTTYPE_BOOL,
+ VDSCRIPTTYPE_BOOL
+};
+
+/* open action */
+const VDSCRIPTTYPE g_aArgOpen[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk */
+ VDSCRIPTTYPE_STRING, /* name */
+ VDSCRIPTTYPE_STRING, /* backend */
+ VDSCRIPTTYPE_BOOL, /* async */
+ VDSCRIPTTYPE_BOOL, /* shareable */
+ VDSCRIPTTYPE_BOOL, /* readonly */
+ VDSCRIPTTYPE_BOOL, /* discard */
+ VDSCRIPTTYPE_BOOL, /* ignoreflush */
+ VDSCRIPTTYPE_BOOL, /* honorsame */
+};
+
+/* I/O action */
+const VDSCRIPTTYPE g_aArgIo[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk */
+ VDSCRIPTTYPE_BOOL, /* async */
+ VDSCRIPTTYPE_UINT32, /* max-reqs */
+ VDSCRIPTTYPE_STRING, /* mode */
+ VDSCRIPTTYPE_UINT64, /* size */
+ VDSCRIPTTYPE_UINT64, /* blocksize */
+ VDSCRIPTTYPE_UINT64, /* offStart */
+ VDSCRIPTTYPE_UINT64, /* offEnd */
+ VDSCRIPTTYPE_UINT32, /* writes */
+ VDSCRIPTTYPE_STRING /* pattern */
+};
+
+/* flush action */
+const VDSCRIPTTYPE g_aArgFlush[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk */
+ VDSCRIPTTYPE_BOOL /* async */
+};
+
+/* merge action */
+const VDSCRIPTTYPE g_aArgMerge[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk */
+ VDSCRIPTTYPE_UINT32, /* from */
+ VDSCRIPTTYPE_UINT32 /* to */
+};
+
+/* Compact a disk */
+const VDSCRIPTTYPE g_aArgCompact[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk */
+ VDSCRIPTTYPE_UINT32 /* image */
+};
+
+/* Discard a part of a disk */
+const VDSCRIPTTYPE g_aArgDiscard[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk */
+ VDSCRIPTTYPE_BOOL, /* async */
+ VDSCRIPTTYPE_STRING /* ranges */
+};
+
+/* Compact a disk */
+const VDSCRIPTTYPE g_aArgCopy[] =
+{
+ VDSCRIPTTYPE_STRING, /* diskfrom */
+ VDSCRIPTTYPE_STRING, /* diskto */
+ VDSCRIPTTYPE_UINT32, /* imagefrom */
+ VDSCRIPTTYPE_STRING, /* backend */
+ VDSCRIPTTYPE_STRING, /* filename */
+ VDSCRIPTTYPE_BOOL, /* movebyrename */
+ VDSCRIPTTYPE_UINT64, /* size */
+ VDSCRIPTTYPE_UINT32, /* fromsame */
+ VDSCRIPTTYPE_UINT32 /* tosame */
+};
+
+/* close action */
+const VDSCRIPTTYPE g_aArgClose[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk */
+ VDSCRIPTTYPE_STRING, /* mode */
+ VDSCRIPTTYPE_BOOL /* delete */
+};
+
+/* print file size action */
+const VDSCRIPTTYPE g_aArgPrintFileSize[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk */
+ VDSCRIPTTYPE_UINT32 /* image */
+};
+
+/* I/O log replay action */
+const VDSCRIPTTYPE g_aArgIoLogReplay[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk */
+ VDSCRIPTTYPE_STRING /* iolog */
+};
+
+/* I/O RNG create action */
+const VDSCRIPTTYPE g_aArgIoRngCreate[] =
+{
+ VDSCRIPTTYPE_UINT32, /* size */
+ VDSCRIPTTYPE_STRING, /* mode */
+ VDSCRIPTTYPE_UINT32, /* seed */
+};
+
+/* I/O pattern create action */
+const VDSCRIPTTYPE g_aArgIoPatternCreateFromNumber[] =
+{
+ VDSCRIPTTYPE_STRING, /* name */
+ VDSCRIPTTYPE_UINT32, /* size */
+ VDSCRIPTTYPE_UINT32 /* pattern */
+};
+
+/* I/O pattern create action */
+const VDSCRIPTTYPE g_aArgIoPatternCreateFromFile[] =
+{
+ VDSCRIPTTYPE_STRING, /* name */
+ VDSCRIPTTYPE_STRING /* file */
+};
+
+/* I/O pattern destroy action */
+const VDSCRIPTTYPE g_aArgIoPatternDestroy[] =
+{
+ VDSCRIPTTYPE_STRING /* name */
+};
+
+/* Sleep */
+const VDSCRIPTTYPE g_aArgSleep[] =
+{
+ VDSCRIPTTYPE_UINT32 /* time */
+};
+
+/* Dump memory file */
+const VDSCRIPTTYPE g_aArgDumpFile[] =
+{
+ VDSCRIPTTYPE_STRING, /* file */
+ VDSCRIPTTYPE_STRING /* path */
+};
+
+/* Create virtual disk handle */
+const VDSCRIPTTYPE g_aArgCreateDisk[] =
+{
+ VDSCRIPTTYPE_STRING, /* name */
+ VDSCRIPTTYPE_BOOL /* verify */
+};
+
+/* Create virtual disk handle */
+const VDSCRIPTTYPE g_aArgDestroyDisk[] =
+{
+ VDSCRIPTTYPE_STRING /* name */
+};
+
+/* Compare virtual disks */
+const VDSCRIPTTYPE g_aArgCompareDisks[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk1 */
+ VDSCRIPTTYPE_STRING /* disk2 */
+};
+
+/* Dump disk info */
+const VDSCRIPTTYPE g_aArgDumpDiskInfo[] =
+{
+ VDSCRIPTTYPE_STRING /* disk */
+};
+
+/* Print message */
+const VDSCRIPTTYPE g_aArgPrintMsg[] =
+{
+ VDSCRIPTTYPE_STRING /* msg */
+};
+
+/* Show statistics */
+const VDSCRIPTTYPE g_aArgShowStatistics[] =
+{
+ VDSCRIPTTYPE_STRING /* file */
+};
+
+/* Reset statistics */
+const VDSCRIPTTYPE g_aArgResetStatistics[] =
+{
+ VDSCRIPTTYPE_STRING /* file */
+};
+
+/* Resize disk. */
+const VDSCRIPTTYPE g_aArgResize[] =
+{
+ VDSCRIPTTYPE_STRING, /* disk */
+ VDSCRIPTTYPE_UINT64 /* size */
+};
+
+/* Set file backend. */
+const VDSCRIPTTYPE g_aArgSetFileBackend[] =
+{
+ VDSCRIPTTYPE_STRING /* new file backend */
+};
+
+/* Load plugin. */
+const VDSCRIPTTYPE g_aArgLoadPlugin[] =
+{
+ VDSCRIPTTYPE_STRING /* plugin name */
+};
+
+const VDSCRIPTCALLBACK g_aScriptActions[] =
+{
+ /* pcszFnName enmTypeReturn paArgDesc cArgDescs pfnHandler */
+ {"create", VDSCRIPTTYPE_VOID, g_aArgCreate, RT_ELEMENTS(g_aArgCreate), vdScriptHandlerCreate},
+ {"open", VDSCRIPTTYPE_VOID, g_aArgOpen, RT_ELEMENTS(g_aArgOpen), vdScriptHandlerOpen},
+ {"io", VDSCRIPTTYPE_VOID, g_aArgIo, RT_ELEMENTS(g_aArgIo), vdScriptHandlerIo},
+ {"flush", VDSCRIPTTYPE_VOID, g_aArgFlush, RT_ELEMENTS(g_aArgFlush), vdScriptHandlerFlush},
+ {"close", VDSCRIPTTYPE_VOID, g_aArgClose, RT_ELEMENTS(g_aArgClose), vdScriptHandlerClose},
+ {"printfilesize", VDSCRIPTTYPE_VOID, g_aArgPrintFileSize, RT_ELEMENTS(g_aArgPrintFileSize), vdScriptHandlerPrintFileSize},
+ {"ioreplay", VDSCRIPTTYPE_VOID, g_aArgIoLogReplay, RT_ELEMENTS(g_aArgIoLogReplay), vdScriptHandlerIoLogReplay},
+ {"merge", VDSCRIPTTYPE_VOID, g_aArgMerge, RT_ELEMENTS(g_aArgMerge), vdScriptHandlerMerge},
+ {"compact", VDSCRIPTTYPE_VOID, g_aArgCompact, RT_ELEMENTS(g_aArgCompact), vdScriptHandlerCompact},
+ {"discard", VDSCRIPTTYPE_VOID, g_aArgDiscard, RT_ELEMENTS(g_aArgDiscard), vdScriptHandlerDiscard},
+ {"copy", VDSCRIPTTYPE_VOID, g_aArgCopy, RT_ELEMENTS(g_aArgCopy), vdScriptHandlerCopy},
+ {"iorngcreate", VDSCRIPTTYPE_VOID, g_aArgIoRngCreate, RT_ELEMENTS(g_aArgIoRngCreate), vdScriptHandlerIoRngCreate},
+ {"iorngdestroy", VDSCRIPTTYPE_VOID, NULL, 0, vdScriptHandlerIoRngDestroy},
+ {"iopatterncreatefromnumber", VDSCRIPTTYPE_VOID, g_aArgIoPatternCreateFromNumber, RT_ELEMENTS(g_aArgIoPatternCreateFromNumber), vdScriptHandlerIoPatternCreateFromNumber},
+ {"iopatterncreatefromfile", VDSCRIPTTYPE_VOID, g_aArgIoPatternCreateFromFile, RT_ELEMENTS(g_aArgIoPatternCreateFromFile), vdScriptHandlerIoPatternCreateFromFile},
+ {"iopatterndestroy", VDSCRIPTTYPE_VOID, g_aArgIoPatternDestroy, RT_ELEMENTS(g_aArgIoPatternDestroy), vdScriptHandlerIoPatternDestroy},
+ {"sleep", VDSCRIPTTYPE_VOID, g_aArgSleep, RT_ELEMENTS(g_aArgSleep), vdScriptHandlerSleep},
+ {"dumpfile", VDSCRIPTTYPE_VOID, g_aArgDumpFile, RT_ELEMENTS(g_aArgDumpFile), vdScriptHandlerDumpFile},
+ {"createdisk", VDSCRIPTTYPE_VOID, g_aArgCreateDisk, RT_ELEMENTS(g_aArgCreateDisk), vdScriptHandlerCreateDisk},
+ {"destroydisk", VDSCRIPTTYPE_VOID, g_aArgDestroyDisk, RT_ELEMENTS(g_aArgDestroyDisk), vdScriptHandlerDestroyDisk},
+ {"comparedisks", VDSCRIPTTYPE_VOID, g_aArgCompareDisks, RT_ELEMENTS(g_aArgCompareDisks), vdScriptHandlerCompareDisks},
+ {"dumpdiskinfo", VDSCRIPTTYPE_VOID, g_aArgDumpDiskInfo, RT_ELEMENTS(g_aArgDumpDiskInfo), vdScriptHandlerDumpDiskInfo},
+ {"print", VDSCRIPTTYPE_VOID, g_aArgPrintMsg, RT_ELEMENTS(g_aArgPrintMsg), vdScriptHandlerPrintMsg},
+ {"showstatistics", VDSCRIPTTYPE_VOID, g_aArgShowStatistics, RT_ELEMENTS(g_aArgShowStatistics), vdScriptHandlerShowStatistics},
+ {"resetstatistics", VDSCRIPTTYPE_VOID, g_aArgResetStatistics, RT_ELEMENTS(g_aArgResetStatistics), vdScriptHandlerResetStatistics},
+ {"resize", VDSCRIPTTYPE_VOID, g_aArgResize, RT_ELEMENTS(g_aArgResize), vdScriptHandlerResize},
+ {"setfilebackend", VDSCRIPTTYPE_VOID, g_aArgSetFileBackend, RT_ELEMENTS(g_aArgSetFileBackend), vdScriptHandlerSetFileBackend},
+ {"loadplugin", VDSCRIPTTYPE_VOID, g_aArgLoadPlugin, RT_ELEMENTS(g_aArgLoadPlugin), vdScriptHandlerLoadPlugin}
+};
+
+const unsigned g_cScriptActions = RT_ELEMENTS(g_aScriptActions);
+
+#if 0 /* unused */
+static DECLCALLBACK(int) vdScriptCallbackPrint(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ NOREF(pvUser);
+ RTPrintf(paScriptArgs[0].psz);
+ return VINF_SUCCESS;
+}
+#endif /* unused */
+
+static DECLCALLBACK(void) tstVDError(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va)
+{
+ NOREF(pvUser);
+ RTPrintf("tstVDIo: Error %Rrc at %s:%u (%s): ", rc, RT_SRC_POS_ARGS);
+ RTPrintfV(pszFormat, va);
+ RTPrintf("\n");
+}
+
+static DECLCALLBACK(int) tstVDMessage(void *pvUser, const char *pszFormat, va_list va)
+{
+ NOREF(pvUser);
+ RTPrintf("tstVDIo: ");
+ RTPrintfV(pszFormat, va);
+ return VINF_SUCCESS;
+}
+
+static int tstVDIoTestInit(PVDIOTEST pIoTest, PVDTESTGLOB pGlob, bool fRandomAcc, uint32_t cSegsMax,
+ uint64_t cbIo, size_t cbBlkSize, uint64_t offStart, uint64_t offEnd,
+ unsigned uWriteChance, PVDPATTERN pPattern);
+static bool tstVDIoTestRunning(PVDIOTEST pIoTest);
+static void tstVDIoTestDestroy(PVDIOTEST pIoTest);
+static bool tstVDIoTestReqOutstanding(PTSTVDIOREQ pIoReq);
+static int tstVDIoTestReqInit(PVDIOTEST pIoTest, PTSTVDIOREQ pIoReq, void *pvUser);
+static DECLCALLBACK(void) tstVDIoTestReqComplete(void *pvUser1, void *pvUser2, int rcReq);
+
+static PVDDISK tstVDIoGetDiskByName(PVDTESTGLOB pGlob, const char *pcszDisk);
+static PVDPATTERN tstVDIoGetPatternByName(PVDTESTGLOB pGlob, const char *pcszName);
+static PVDPATTERN tstVDIoPatternCreate(const char *pcszName, size_t cbPattern);
+static int tstVDIoPatternGetBuffer(PVDPATTERN pPattern, void **ppv, size_t cb);
+
+static DECLCALLBACK(int) vdScriptHandlerCreate(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ PVDDISK pDisk = NULL;
+ bool fBase = false;
+ bool fDynamic = true;
+ bool fSplit = false;
+
+ const char *pcszDisk = paScriptArgs[0].psz;
+ if (!RTStrICmp(paScriptArgs[1].psz, "base"))
+ fBase = true;
+ else if (!RTStrICmp(paScriptArgs[1].psz, "diff"))
+ fBase = false;
+ else
+ {
+ RTPrintf("Invalid image mode '%s' given\n", paScriptArgs[1].psz);
+ rc = VERR_INVALID_PARAMETER;
+ }
+ const char *pcszImage = paScriptArgs[2].psz;
+ if (!RTStrICmp(paScriptArgs[3].psz, "fixed"))
+ fDynamic = false;
+ else if (!RTStrICmp(paScriptArgs[3].psz, "dynamic"))
+ fDynamic = true;
+ else if (!RTStrICmp(paScriptArgs[3].psz, "vmdk-dynamic-split"))
+ fSplit = true;
+ else if (!RTStrICmp(paScriptArgs[3].psz, "vmdk-fixed-split"))
+ {
+ fDynamic = false;
+ fSplit = true;
+ }
+ else
+ {
+ RTPrintf("Invalid image type '%s' given\n", paScriptArgs[3].psz);
+ rc = VERR_INVALID_PARAMETER;
+ }
+ const char *pcszBackend = paScriptArgs[4].psz;
+ uint64_t cbSize = paScriptArgs[5].u64;
+ bool fIgnoreFlush = paScriptArgs[6].f;
+ bool fHonorSame = paScriptArgs[7].f;
+
+ if (RT_SUCCESS(rc))
+ {
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (pDisk)
+ {
+ unsigned fOpenFlags = VD_OPEN_FLAGS_ASYNC_IO;
+ unsigned fImageFlags = VD_IMAGE_FLAGS_NONE;
+
+ if (!fDynamic)
+ fImageFlags |= VD_IMAGE_FLAGS_FIXED;
+
+ if (fIgnoreFlush)
+ fOpenFlags |= VD_OPEN_FLAGS_IGNORE_FLUSH;
+
+ if (fHonorSame)
+ fOpenFlags |= VD_OPEN_FLAGS_HONOR_SAME;
+
+ if (fSplit)
+ fImageFlags |= VD_VMDK_IMAGE_FLAGS_SPLIT_2G;
+
+ if (fBase)
+ rc = VDCreateBase(pDisk->pVD, pcszBackend, pcszImage, cbSize, fImageFlags, NULL,
+ &pDisk->PhysGeom, &pDisk->LogicalGeom,
+ NULL, fOpenFlags, pGlob->pInterfacesImages, NULL);
+ else
+ rc = VDCreateDiff(pDisk->pVD, pcszBackend, pcszImage, fImageFlags, NULL, NULL, NULL,
+ fOpenFlags, pGlob->pInterfacesImages, NULL);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerOpen(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ PVDDISK pDisk = NULL;
+
+ const char *pcszDisk = paScriptArgs[0].psz;
+ const char *pcszImage = paScriptArgs[1].psz;
+ const char *pcszBackend = paScriptArgs[2].psz;
+ bool fShareable = paScriptArgs[3].f;
+ bool fReadonly = paScriptArgs[4].f;
+ bool fAsyncIo = paScriptArgs[5].f;
+ bool fDiscard = paScriptArgs[6].f;
+ bool fIgnoreFlush = paScriptArgs[7].f;
+ bool fHonorSame = paScriptArgs[8].f;
+
+ if (RT_SUCCESS(rc))
+ {
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (pDisk)
+ {
+ unsigned fOpenFlags = 0;
+
+ if (fAsyncIo)
+ fOpenFlags |= VD_OPEN_FLAGS_ASYNC_IO;
+ if (fShareable)
+ fOpenFlags |= VD_OPEN_FLAGS_SHAREABLE;
+ if (fReadonly)
+ fOpenFlags |= VD_OPEN_FLAGS_READONLY;
+ if (fDiscard)
+ fOpenFlags |= VD_OPEN_FLAGS_DISCARD;
+ if (fIgnoreFlush)
+ fOpenFlags |= VD_OPEN_FLAGS_IGNORE_FLUSH;
+ if (fHonorSame)
+ fOpenFlags |= VD_OPEN_FLAGS_HONOR_SAME;
+
+ rc = VDOpen(pDisk->pVD, pcszBackend, pcszImage, fOpenFlags, pGlob->pInterfacesImages);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+/**
+ * Returns the speed in KB/s from the amount of and the time in nanoseconds it
+ * took to complete the test.
+ *
+ * @returns Speed in KB/s
+ * @param cbIo Size of the I/O test
+ * @param tsNano Time in nanoseconds it took to complete the test.
+ */
+static uint64_t tstVDIoGetSpeedKBs(uint64_t cbIo, uint64_t tsNano)
+{
+ /* Seen on one of the testboxes, avoid division by 0 below. */
+ if (tsNano == 0)
+ return 0;
+
+ /*
+ * Blow up the value until we can do the calculation without getting 0 as
+ * a result.
+ */
+ uint64_t cbIoTemp = cbIo;
+ unsigned cRounds = 0;
+ while (cbIoTemp < tsNano)
+ {
+ cbIoTemp *= 1000;
+ cRounds++;
+ }
+
+ uint64_t uSpeedKBs = ((cbIoTemp / tsNano) * RT_NS_1SEC) / 1024;
+
+ while (cRounds-- > 0)
+ uSpeedKBs /= 1000;
+
+ return uSpeedKBs;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerIo(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ bool fRandomAcc = false;
+ PVDDISK pDisk = NULL;
+ PVDPATTERN pPattern = NULL;
+
+ const char *pcszDisk = paScriptArgs[0].psz;
+ bool fAsync = paScriptArgs[1].f;
+ unsigned cMaxReqs = (unsigned)paScriptArgs[2].u64;
+ if (!RTStrICmp(paScriptArgs[3].psz, "seq"))
+ fRandomAcc = false;
+ else if (!RTStrICmp(paScriptArgs[3].psz, "rnd"))
+ fRandomAcc = true;
+ else
+ {
+ RTPrintf("Invalid access mode '%s'\n", paScriptArgs[3].psz);
+ rc = VERR_INVALID_PARAMETER;
+ }
+ uint64_t cbBlkSize = paScriptArgs[4].u64;
+ uint64_t offStart = paScriptArgs[5].u64;
+ uint64_t offEnd = paScriptArgs[6].u64;
+ uint64_t cbIo = paScriptArgs[7].u64;
+ uint8_t uWriteChance = (uint8_t)paScriptArgs[8].u64;
+ const char *pcszPattern = paScriptArgs[9].psz;
+
+ if ( RT_SUCCESS(rc)
+ && fAsync
+ && !cMaxReqs)
+ rc = VERR_INVALID_PARAMETER;
+
+ if (RT_SUCCESS(rc))
+ {
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (!pDisk)
+ rc = VERR_NOT_FOUND;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Set defaults if not set by the user. */
+ if (offStart == 0 && offEnd == 0)
+ {
+ offEnd = VDGetSize(pDisk->pVD, VD_LAST_IMAGE);
+ if (offEnd == 0)
+ return VERR_INVALID_STATE;
+ }
+
+ if (!cbIo)
+ cbIo = offEnd;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && RTStrCmp(pcszPattern, "none"))
+ {
+ pPattern = tstVDIoGetPatternByName(pGlob, pcszPattern);
+ if (!pPattern)
+ rc = VERR_NOT_FOUND;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ VDIOTEST IoTest;
+
+ RTTestSub(pGlob->hTest, "Basic I/O");
+ rc = tstVDIoTestInit(&IoTest, pGlob, fRandomAcc, 5, cbIo, cbBlkSize, offStart, offEnd, uWriteChance, pPattern);
+ if (RT_SUCCESS(rc))
+ {
+ PTSTVDIOREQ paIoReq = NULL;
+ unsigned cMaxTasksOutstanding = fAsync ? cMaxReqs : 1;
+ RTSEMEVENT EventSem;
+
+ rc = RTSemEventCreate(&EventSem);
+ paIoReq = (PTSTVDIOREQ)RTMemAllocZ(cMaxTasksOutstanding * sizeof(TSTVDIOREQ));
+ if (paIoReq && RT_SUCCESS(rc))
+ {
+ uint64_t NanoTS = RTTimeNanoTS();
+
+ /* Init requests. */
+ for (unsigned i = 0; i < cMaxTasksOutstanding; i++)
+ {
+ paIoReq[i].idx = i;
+ paIoReq[i].pvBufRead = RTMemAlloc(cbBlkSize);
+ if (!paIoReq[i].pvBufRead)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+
+ while ( tstVDIoTestRunning(&IoTest)
+ && RT_SUCCESS(rc))
+ {
+ bool fTasksOutstanding = false;
+ unsigned idx = 0;
+
+ /* Submit all idling requests. */
+ while ( idx < cMaxTasksOutstanding
+ && tstVDIoTestRunning(&IoTest))
+ {
+ if (!tstVDIoTestReqOutstanding(&paIoReq[idx]))
+ {
+ rc = tstVDIoTestReqInit(&IoTest, &paIoReq[idx], pDisk);
+ AssertRC(rc);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (!fAsync)
+ {
+ switch (paIoReq[idx].enmTxDir)
+ {
+ case TSTVDIOREQTXDIR_READ:
+ {
+ rc = VDRead(pDisk->pVD, paIoReq[idx].off, paIoReq[idx].aSegs[0].pvSeg, paIoReq[idx].cbReq);
+
+ if (RT_SUCCESS(rc)
+ && pDisk->pMemDiskVerify)
+ {
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &paIoReq[idx].aSegs[0], paIoReq[idx].cSegs);
+
+ if (VDMemDiskCmp(pDisk->pMemDiskVerify, paIoReq[idx].off, paIoReq[idx].cbReq, &SgBuf))
+ {
+ RTTestFailed(pGlob->hTest, "Corrupted disk at offset %llu!\n", paIoReq[idx].off);
+ rc = VERR_INVALID_STATE;
+ }
+ }
+ break;
+ }
+ case TSTVDIOREQTXDIR_WRITE:
+ {
+ rc = VDWrite(pDisk->pVD, paIoReq[idx].off, paIoReq[idx].aSegs[0].pvSeg, paIoReq[idx].cbReq);
+
+ if (RT_SUCCESS(rc)
+ && pDisk->pMemDiskVerify)
+ {
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &paIoReq[idx].aSegs[0], paIoReq[idx].cSegs);
+ rc = VDMemDiskWrite(pDisk->pMemDiskVerify, paIoReq[idx].off, paIoReq[idx].cbReq, &SgBuf);
+ }
+ break;
+ }
+ case TSTVDIOREQTXDIR_FLUSH:
+ {
+ rc = VDFlush(pDisk->pVD);
+ break;
+ }
+ case TSTVDIOREQTXDIR_DISCARD:
+ AssertMsgFailed(("Invalid\n"));
+ }
+
+ ASMAtomicXchgBool(&paIoReq[idx].fOutstanding, false);
+ if (RT_SUCCESS(rc))
+ idx++;
+ }
+ else
+ {
+ LogFlow(("Queuing request %d\n", idx));
+ switch (paIoReq[idx].enmTxDir)
+ {
+ case TSTVDIOREQTXDIR_READ:
+ {
+ rc = VDAsyncRead(pDisk->pVD, paIoReq[idx].off, paIoReq[idx].cbReq, &paIoReq[idx].SgBuf,
+ tstVDIoTestReqComplete, &paIoReq[idx], EventSem);
+ break;
+ }
+ case TSTVDIOREQTXDIR_WRITE:
+ {
+ rc = VDAsyncWrite(pDisk->pVD, paIoReq[idx].off, paIoReq[idx].cbReq, &paIoReq[idx].SgBuf,
+ tstVDIoTestReqComplete, &paIoReq[idx], EventSem);
+ break;
+ }
+ case TSTVDIOREQTXDIR_FLUSH:
+ {
+ rc = VDAsyncFlush(pDisk->pVD, tstVDIoTestReqComplete, &paIoReq[idx], EventSem);
+ break;
+ }
+ case TSTVDIOREQTXDIR_DISCARD:
+ AssertMsgFailed(("Invalid\n"));
+ }
+
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ idx++;
+ fTasksOutstanding = true;
+ rc = VINF_SUCCESS;
+ }
+ else if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ {
+ LogFlow(("Request %d completed\n", idx));
+ switch (paIoReq[idx].enmTxDir)
+ {
+ case TSTVDIOREQTXDIR_READ:
+ {
+ if (pDisk->pMemDiskVerify)
+ {
+ RTCritSectEnter(&pDisk->CritSectVerify);
+ RTSgBufReset(&paIoReq[idx].SgBuf);
+
+ if (VDMemDiskCmp(pDisk->pMemDiskVerify, paIoReq[idx].off, paIoReq[idx].cbReq,
+ &paIoReq[idx].SgBuf))
+ {
+ RTTestFailed(pGlob->hTest, "Corrupted disk at offset %llu!\n", paIoReq[idx].off);
+ rc = VERR_INVALID_STATE;
+ }
+ RTCritSectLeave(&pDisk->CritSectVerify);
+ }
+ break;
+ }
+ case TSTVDIOREQTXDIR_WRITE:
+ {
+ if (pDisk->pMemDiskVerify)
+ {
+ RTCritSectEnter(&pDisk->CritSectVerify);
+ RTSgBufReset(&paIoReq[idx].SgBuf);
+
+ rc = VDMemDiskWrite(pDisk->pMemDiskVerify, paIoReq[idx].off, paIoReq[idx].cbReq,
+ &paIoReq[idx].SgBuf);
+ RTCritSectLeave(&pDisk->CritSectVerify);
+ }
+ break;
+ }
+ case TSTVDIOREQTXDIR_FLUSH:
+ break;
+ case TSTVDIOREQTXDIR_DISCARD:
+ AssertMsgFailed(("Invalid\n"));
+ }
+
+ ASMAtomicXchgBool(&paIoReq[idx].fOutstanding, false);
+ if (rc != VERR_INVALID_STATE)
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ RTPrintf("Error submitting task %u rc=%Rrc\n", paIoReq[idx].idx, rc);
+ }
+ }
+ }
+
+ /* Wait for a request to complete. */
+ if ( fAsync
+ && fTasksOutstanding)
+ {
+ rc = RTSemEventWait(EventSem, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ }
+
+ /* Cleanup, wait for all tasks to complete. */
+ while (fAsync)
+ {
+ unsigned idx = 0;
+ bool fAllIdle = true;
+
+ while (idx < cMaxTasksOutstanding)
+ {
+ if (tstVDIoTestReqOutstanding(&paIoReq[idx]))
+ {
+ fAllIdle = false;
+ break;
+ }
+ idx++;
+ }
+
+ if (!fAllIdle)
+ {
+ rc = RTSemEventWait(EventSem, 100);
+ Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT);
+ }
+ else
+ break;
+ }
+
+ NanoTS = RTTimeNanoTS() - NanoTS;
+ uint64_t SpeedKBs = tstVDIoGetSpeedKBs(cbIo, NanoTS);
+ RTTestValue(pGlob->hTest, "Throughput", SpeedKBs, RTTESTUNIT_KILOBYTES_PER_SEC);
+
+ for (unsigned i = 0; i < cMaxTasksOutstanding; i++)
+ {
+ if (paIoReq[i].pvBufRead)
+ RTMemFree(paIoReq[i].pvBufRead);
+ }
+
+ RTSemEventDestroy(EventSem);
+ RTMemFree(paIoReq);
+ }
+ else
+ {
+ if (paIoReq)
+ RTMemFree(paIoReq);
+ if (RT_SUCCESS(rc))
+ RTSemEventDestroy(EventSem);
+ rc = VERR_NO_MEMORY;
+ }
+
+ tstVDIoTestDestroy(&IoTest);
+ }
+ RTTestSubDone(pGlob->hTest);
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerFlush(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ PVDDISK pDisk = NULL;
+ const char *pcszDisk = paScriptArgs[0].psz;
+ bool fAsync = paScriptArgs[1].f;
+
+ if (RT_SUCCESS(rc))
+ {
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (!pDisk)
+ rc = VERR_NOT_FOUND;
+ else if (fAsync)
+ {
+ TSTVDIOREQ IoReq;
+ RTSEMEVENT EventSem;
+
+ rc = RTSemEventCreate(&EventSem);
+ if (RT_SUCCESS(rc))
+ {
+ memset(&IoReq, 0, sizeof(TSTVDIOREQ));
+ IoReq.enmTxDir = TSTVDIOREQTXDIR_FLUSH;
+ IoReq.pvUser = pDisk;
+ IoReq.idx = 0;
+ rc = VDAsyncFlush(pDisk->pVD, tstVDIoTestReqComplete, &IoReq, EventSem);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ rc = RTSemEventWait(EventSem, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ else if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ rc = VINF_SUCCESS;
+
+ RTSemEventDestroy(EventSem);
+ }
+ }
+ else
+ rc = VDFlush(pDisk->pVD);
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerMerge(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ PVDDISK pDisk = NULL;
+ const char *pcszDisk = paScriptArgs[0].psz;
+ unsigned nImageFrom = paScriptArgs[1].u32;
+ unsigned nImageTo = paScriptArgs[2].u32;
+
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (!pDisk)
+ rc = VERR_NOT_FOUND;
+ else
+ {
+ /** @todo Provide progress interface to test that cancelation
+ * doesn't corrupt the data.
+ */
+ rc = VDMerge(pDisk->pVD, nImageFrom, nImageTo, NULL);
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerCompact(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ PVDDISK pDisk = NULL;
+ const char *pcszDisk = paScriptArgs[0].psz;
+ unsigned nImage = paScriptArgs[1].u32;
+
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (!pDisk)
+ rc = VERR_NOT_FOUND;
+ else
+ {
+ /** @todo Provide progress interface to test that cancelation
+ * doesn't corrupt the data.
+ */
+ rc = VDCompact(pDisk->pVD, nImage, NULL);
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerDiscard(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ PVDDISK pDisk = NULL;
+ const char *pcszDisk = paScriptArgs[0].psz;
+ bool fAsync = paScriptArgs[1].f;
+ const char *pcszRanges = paScriptArgs[2].psz;
+
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (!pDisk)
+ rc = VERR_NOT_FOUND;
+ else
+ {
+ unsigned cRanges = 0;
+ PRTRANGE paRanges = NULL;
+
+ /*
+ * Parse the range string which should look like this:
+ * n,off1,cb1,off2,cb2,...
+ *
+ * <n> gives the number of ranges in the string and every off<i>,cb<i>
+ * pair afterwards is a start offset + number of bytes to discard entry.
+ */
+ do
+ {
+ rc = RTStrToUInt32Ex(pcszRanges, (char **)&pcszRanges, 10, &cRanges);
+ if (RT_FAILURE(rc) && (rc != VWRN_TRAILING_CHARS))
+ break;
+
+ if (!cRanges)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ paRanges = (PRTRANGE)RTMemAllocZ(cRanges * sizeof(RTRANGE));
+ if (!paRanges)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ if (*pcszRanges != ',')
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ pcszRanges++;
+
+ /* Retrieve each pair from the string. */
+ for (unsigned i = 0; i < cRanges; i++)
+ {
+ uint64_t off;
+ uint32_t cb;
+
+ rc = RTStrToUInt64Ex(pcszRanges, (char **)&pcszRanges, 10, &off);
+ if (RT_FAILURE(rc) && (rc != VWRN_TRAILING_CHARS))
+ break;
+
+ if (*pcszRanges != ',')
+ {
+ switch (*pcszRanges)
+ {
+ case 'k':
+ case 'K':
+ {
+ off *= _1K;
+ break;
+ }
+ case 'm':
+ case 'M':
+ {
+ off *= _1M;
+ break;
+ }
+ case 'g':
+ case 'G':
+ {
+ off *= _1G;
+ break;
+ }
+ default:
+ {
+ RTPrintf("Invalid size suffix '%s'\n", pcszRanges);
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ pcszRanges++;
+ }
+
+ if (*pcszRanges != ',')
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ pcszRanges++;
+
+ rc = RTStrToUInt32Ex(pcszRanges, (char **)&pcszRanges, 10, &cb);
+ if (RT_FAILURE(rc) && (rc != VWRN_TRAILING_CHARS))
+ break;
+
+ if (*pcszRanges != ',')
+ {
+ switch (*pcszRanges)
+ {
+ case 'k':
+ case 'K':
+ {
+ cb *= _1K;
+ break;
+ }
+ case 'm':
+ case 'M':
+ {
+ cb *= _1M;
+ break;
+ }
+ case 'g':
+ case 'G':
+ {
+ cb *= _1G;
+ break;
+ }
+ default:
+ {
+ RTPrintf("Invalid size suffix '%s'\n", pcszRanges);
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ pcszRanges++;
+ }
+
+ if ( *pcszRanges != ','
+ && !(i == cRanges - 1 && *pcszRanges == '\0'))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ pcszRanges++;
+
+ paRanges[i].offStart = off;
+ paRanges[i].cbRange = cb;
+ }
+ } while (0);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (!fAsync)
+ rc = VDDiscardRanges(pDisk->pVD, paRanges, cRanges);
+ else
+ {
+ TSTVDIOREQ IoReq;
+ RTSEMEVENT EventSem;
+
+ rc = RTSemEventCreate(&EventSem);
+ if (RT_SUCCESS(rc))
+ {
+ memset(&IoReq, 0, sizeof(TSTVDIOREQ));
+ IoReq.enmTxDir = TSTVDIOREQTXDIR_FLUSH;
+ IoReq.pvUser = pDisk;
+ IoReq.idx = 0;
+ rc = VDAsyncDiscardRanges(pDisk->pVD, paRanges, cRanges, tstVDIoTestReqComplete, &IoReq, EventSem);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ rc = RTSemEventWait(EventSem, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ else if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ rc = VINF_SUCCESS;
+
+ RTSemEventDestroy(EventSem);
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pDisk->pMemDiskVerify)
+ {
+ for (unsigned i = 0; i < cRanges; i++)
+ {
+ void *pv = RTMemAllocZ(paRanges[i].cbRange);
+ if (pv)
+ {
+ RTSGSEG SgSeg;
+ RTSGBUF SgBuf;
+
+ SgSeg.pvSeg = pv;
+ SgSeg.cbSeg = paRanges[i].cbRange;
+ RTSgBufInit(&SgBuf, &SgSeg, 1);
+ rc = VDMemDiskWrite(pDisk->pMemDiskVerify, paRanges[i].offStart, paRanges[i].cbRange, &SgBuf);
+ RTMemFree(pv);
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+ }
+ }
+
+ if (paRanges)
+ RTMemFree(paRanges);
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerCopy(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ PVDDISK pDiskFrom = NULL;
+ PVDDISK pDiskTo = NULL;
+ const char *pcszDiskFrom = paScriptArgs[0].psz;
+ const char *pcszDiskTo = paScriptArgs[1].psz;
+ unsigned nImageFrom = paScriptArgs[2].u32;
+ const char *pcszBackend = paScriptArgs[3].psz;
+ const char *pcszFilename = paScriptArgs[4].psz;
+ bool fMoveByRename = paScriptArgs[5].f;
+ uint64_t cbSize = paScriptArgs[6].u64;
+ unsigned nImageFromSame = paScriptArgs[7].u32;
+ unsigned nImageToSame = paScriptArgs[8].u32;
+
+ pDiskFrom = tstVDIoGetDiskByName(pGlob, pcszDiskFrom);
+ pDiskTo = tstVDIoGetDiskByName(pGlob, pcszDiskTo);
+ if (!pDiskFrom || !pDiskTo)
+ rc = VERR_NOT_FOUND;
+ else
+ {
+ /** @todo Provide progress interface to test that cancelation
+ * works as intended.
+ */
+ rc = VDCopyEx(pDiskFrom->pVD, nImageFrom, pDiskTo->pVD, pcszBackend, pcszFilename,
+ fMoveByRename, cbSize, nImageFromSame, nImageToSame,
+ VD_IMAGE_FLAGS_NONE, NULL, VD_OPEN_FLAGS_ASYNC_IO,
+ NULL, pGlob->pInterfacesImages, NULL);
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerClose(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ bool fAll = false;
+ bool fDelete = false;
+ const char *pcszDisk = NULL;
+ PVDDISK pDisk = NULL;
+
+ pcszDisk = paScriptArgs[0].psz;
+ if (!RTStrICmp(paScriptArgs[1].psz, "all"))
+ fAll = true;
+ else if (!RTStrICmp(paScriptArgs[1].psz, "single"))
+ fAll = false;
+ else
+ {
+ RTPrintf("Invalid mode '%s' given\n", paScriptArgs[1].psz);
+ rc = VERR_INVALID_PARAMETER;
+ }
+ fDelete = paScriptArgs[2].f;
+
+ if ( fAll
+ && fDelete)
+ {
+ RTPrintf("mode=all doesn't work with delete=yes\n");
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (pDisk)
+ {
+ if (fAll)
+ rc = VDCloseAll(pDisk->pVD);
+ else
+ rc = VDClose(pDisk->pVD, fDelete);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vdScriptHandlerPrintFileSize(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ PVDDISK pDisk = NULL;
+ const char *pcszDisk = paScriptArgs[0].psz;
+ uint32_t nImage = paScriptArgs[1].u32;
+
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (pDisk)
+ RTPrintf("%s: size of image %u is %llu\n", pcszDisk, nImage, VDGetFileSize(pDisk->pVD, nImage));
+ else
+ rc = VERR_NOT_FOUND;
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vdScriptHandlerIoLogReplay(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ PVDDISK pDisk = NULL;
+ const char *pcszDisk = paScriptArgs[0].psz;
+ const char *pcszIoLog = paScriptArgs[1].psz;
+ size_t cbBuf = 0;
+ void *pvBuf = NULL;
+
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (pDisk)
+ {
+ RTTRACELOGRDR hIoLogRdr = NIL_RTTRACELOGRDR;
+
+ rc = RTTraceLogRdrCreateFromFile(&hIoLogRdr, pcszIoLog);
+ if (RT_SUCCESS(rc))
+ {
+ RTTRACELOGRDRPOLLEVT enmEvt = RTTRACELOGRDRPOLLEVT_INVALID;
+
+ rc = RTTraceLogRdrEvtPoll(hIoLogRdr, &enmEvt, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsg(enmEvt == RTTRACELOGRDRPOLLEVT_HDR_RECVD, ("Expected a header received event but got: %#x\n", enmEvt));
+
+ /* Loop through events. */
+ rc = RTTraceLogRdrEvtPoll(hIoLogRdr, &enmEvt, RT_INDEFINITE_WAIT);
+ while (RT_SUCCESS(rc))
+ {
+ AssertMsg(enmEvt == RTTRACELOGRDRPOLLEVT_TRACE_EVENT_RECVD,
+ ("Expected a trace event received event but got: %#x\n", enmEvt));
+
+ RTTRACELOGRDREVT hEvt = NIL_RTTRACELOGRDREVT;
+ rc = RTTraceLogRdrQueryLastEvt(hIoLogRdr, &hEvt);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTTRACELOGEVTDESC pEvtDesc = RTTraceLogRdrEvtGetDesc(hEvt);
+
+ if (!RTStrCmp(pEvtDesc->pszId, "Read"))
+ {
+ RTTRACELOGEVTVAL aVals[3];
+ unsigned cVals = 0;
+ rc = RTTraceLogRdrEvtFillVals(hEvt, 0, &aVals[0], RT_ELEMENTS(aVals), &cVals);
+ if ( RT_SUCCESS(rc)
+ && cVals == 3
+ && aVals[0].pItemDesc->enmType == RTTRACELOGTYPE_BOOL
+ && aVals[1].pItemDesc->enmType == RTTRACELOGTYPE_UINT64
+ && aVals[2].pItemDesc->enmType == RTTRACELOGTYPE_SIZE)
+ {
+ bool fAsync = aVals[0].u.f;
+ uint64_t off = aVals[1].u.u64;
+ size_t cbIo = (size_t)aVals[2].u.sz;
+
+ if (cbIo > cbBuf)
+ {
+ pvBuf = RTMemRealloc(pvBuf, cbIo);
+ if (pvBuf)
+ cbBuf = cbIo;
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && !fAsync)
+ rc = VDRead(pDisk->pVD, off, pvBuf, cbIo);
+ else if (RT_SUCCESS(rc))
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else if (!RTStrCmp(pEvtDesc->pszId, "Write"))
+ {
+ RTTRACELOGEVTVAL aVals[3];
+ unsigned cVals = 0;
+ rc = RTTraceLogRdrEvtFillVals(hEvt, 0, &aVals[0], RT_ELEMENTS(aVals), &cVals);
+ if ( RT_SUCCESS(rc)
+ && cVals == 3
+ && aVals[0].pItemDesc->enmType == RTTRACELOGTYPE_BOOL
+ && aVals[1].pItemDesc->enmType == RTTRACELOGTYPE_UINT64
+ && aVals[2].pItemDesc->enmType == RTTRACELOGTYPE_SIZE)
+ {
+ bool fAsync = aVals[0].u.f;
+ uint64_t off = aVals[1].u.u64;
+ size_t cbIo = (size_t)aVals[2].u.sz;
+
+ if (cbIo > cbBuf)
+ {
+ pvBuf = RTMemRealloc(pvBuf, cbIo);
+ if (pvBuf)
+ cbBuf = cbIo;
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && !fAsync)
+ rc = VDWrite(pDisk->pVD, off, pvBuf, cbIo);
+ else if (RT_SUCCESS(rc))
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else if (!RTStrCmp(pEvtDesc->pszId, "Flush"))
+ {
+ RTTRACELOGEVTVAL Val;
+ unsigned cVals = 0;
+ rc = RTTraceLogRdrEvtFillVals(hEvt, 0, &Val, 1, &cVals);
+ if ( RT_SUCCESS(rc)
+ && cVals == 1
+ && Val.pItemDesc->enmType == RTTRACELOGTYPE_BOOL)
+ {
+ bool fAsync = Val.u.f;
+
+ if ( RT_SUCCESS(rc)
+ && !fAsync)
+ rc = VDFlush(pDisk->pVD);
+ else if (RT_SUCCESS(rc))
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else if (!RTStrCmp(pEvtDesc->pszId, "Discard"))
+ {}
+ else
+ AssertMsgFailed(("Invalid event ID: %s\n", pEvtDesc->pszId));
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTraceLogRdrEvtPoll(hIoLogRdr, &enmEvt, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsg(enmEvt == RTTRACELOGRDRPOLLEVT_TRACE_EVENT_RECVD,
+ ("Expected a trace event received event but got: %#x\n", enmEvt));
+
+ hEvt = NIL_RTTRACELOGRDREVT;
+ rc = RTTraceLogRdrQueryLastEvt(hIoLogRdr, &hEvt);
+ if (RT_SUCCESS(rc))
+ {
+ pEvtDesc = RTTraceLogRdrEvtGetDesc(hEvt);
+ AssertMsg(!RTStrCmp(pEvtDesc->pszId, "Complete"),
+ ("Expected a completion event but got: %s\n", pEvtDesc->pszId));
+ }
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTTraceLogRdrEvtPoll(hIoLogRdr, &enmEvt, RT_INDEFINITE_WAIT);
+ }
+ }
+
+ RTTraceLogRdrDestroy(hIoLogRdr);
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ if (pvBuf)
+ RTMemFree(pvBuf);
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vdScriptHandlerIoRngCreate(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ size_t cbPattern = (size_t)paScriptArgs[0].u64;
+ const char *pcszSeeder = paScriptArgs[1].psz;
+ uint64_t uSeed = paScriptArgs[2].u64;
+
+ if (pGlob->pIoRnd)
+ {
+ RTPrintf("I/O RNG already exists\n");
+ rc = VERR_INVALID_STATE;
+ }
+ else
+ {
+ uint64_t uSeedToUse = 0;
+
+ if (!RTStrICmp(pcszSeeder, "manual"))
+ uSeedToUse = uSeed;
+ else if (!RTStrICmp(pcszSeeder, "time"))
+ uSeedToUse = RTTimeSystemMilliTS();
+ else if (!RTStrICmp(pcszSeeder, "system"))
+ {
+ RTRAND hRand;
+ rc = RTRandAdvCreateSystemTruer(&hRand);
+ if (RT_SUCCESS(rc))
+ {
+ RTRandAdvBytes(hRand, &uSeedToUse, sizeof(uSeedToUse));
+ RTRandAdvDestroy(hRand);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = VDIoRndCreate(&pGlob->pIoRnd, cbPattern, uSeed);
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerIoRngDestroy(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ RT_NOREF1(paScriptArgs);
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+
+ if (pGlob->pIoRnd)
+ {
+ VDIoRndDestroy(pGlob->pIoRnd);
+ pGlob->pIoRnd = NULL;
+ }
+ else
+ RTPrintf("WARNING: No I/O RNG active, faulty script. Continuing\n");
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerIoPatternCreateFromNumber(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszName = paScriptArgs[0].psz;
+ size_t cbPattern = (size_t)paScriptArgs[1].u64;
+ uint64_t u64Pattern = paScriptArgs[2].u64;
+
+ PVDPATTERN pPattern = tstVDIoGetPatternByName(pGlob, pcszName);
+ if (!pPattern)
+ {
+ pPattern = tstVDIoPatternCreate(pcszName, RT_ALIGN_Z(cbPattern, sizeof(uint64_t)));
+ if (pPattern)
+ {
+ /* Fill the buffer. */
+ void *pv = pPattern->pvPattern;
+
+ while (pPattern->cbPattern > 0)
+ {
+ *((uint64_t*)pv) = u64Pattern;
+ pPattern->cbPattern -= sizeof(uint64_t);
+ pv = (uint64_t *)pv + 1;
+ }
+ pPattern->cbPattern = cbPattern; /* Set to the desired size. (could be unaligned) */
+
+ RTListAppend(&pGlob->ListPatterns, &pPattern->ListNode);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerIoPatternCreateFromFile(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszName = paScriptArgs[0].psz;
+ const char *pcszFile = paScriptArgs[1].psz;
+
+ PVDPATTERN pPattern = tstVDIoGetPatternByName(pGlob, pcszName);
+ if (!pPattern)
+ {
+ RTFILE hFile;
+ uint64_t cbPattern = 0;
+
+ rc = RTFileOpen(&hFile, pcszFile, RTFILE_O_DENY_NONE | RTFILE_O_OPEN | RTFILE_O_READ);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileQuerySize(hFile, &cbPattern);
+ if (RT_SUCCESS(rc))
+ {
+ pPattern = tstVDIoPatternCreate(pcszName, (size_t)cbPattern);
+ if (pPattern)
+ {
+ rc = RTFileRead(hFile, pPattern->pvPattern, (size_t)cbPattern, NULL);
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pGlob->ListPatterns, &pPattern->ListNode);
+ else
+ {
+ RTMemFree(pPattern->pvPattern);
+ RTStrFree(pPattern->pszName);
+ RTMemFree(pPattern);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ RTFileClose(hFile);
+ }
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerIoPatternDestroy(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszName = paScriptArgs[0].psz;
+
+ PVDPATTERN pPattern = tstVDIoGetPatternByName(pGlob, pcszName);
+ if (pPattern)
+ {
+ RTListNodeRemove(&pPattern->ListNode);
+ RTMemFree(pPattern->pvPattern);
+ RTStrFree(pPattern->pszName);
+ RTMemFree(pPattern);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerSleep(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ RT_NOREF1(pvUser);
+ uint64_t cMillies = paScriptArgs[0].u64;
+
+ int rc = RTThreadSleep(cMillies);
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerDumpFile(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszFile = paScriptArgs[0].psz;
+ const char *pcszPathToDump = paScriptArgs[1].psz;
+
+ /* Check for the file. */
+ bool fFound = false;
+ PVDFILE pIt;
+ RTListForEach(&pGlob->ListFiles, pIt, VDFILE, Node)
+ {
+ if (!RTStrCmp(pIt->pszName, pcszFile))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ if (fFound)
+ {
+ RTPrintf("Dumping memory file %s to %s, this might take some time\n", pcszFile, pcszPathToDump);
+ rc = VDIoBackendDumpToFile(pIt->pIoStorage, pcszPathToDump);
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerCreateDisk(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszDisk = NULL;
+ PVDDISK pDisk = NULL;
+ bool fVerify = false;
+
+ pcszDisk = paScriptArgs[0].psz;
+ fVerify = paScriptArgs[1].f;
+
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (pDisk)
+ rc = VERR_ALREADY_EXISTS;
+ else
+ {
+ pDisk = (PVDDISK)RTMemAllocZ(sizeof(VDDISK));
+ if (pDisk)
+ {
+ pDisk->pTestGlob = pGlob;
+ pDisk->pszName = RTStrDup(pcszDisk);
+ if (pDisk->pszName)
+ {
+ rc = VINF_SUCCESS;
+
+ if (fVerify)
+ {
+ rc = VDMemDiskCreate(&pDisk->pMemDiskVerify, 0 /* Growing */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pDisk->CritSectVerify);
+ if (RT_FAILURE(rc))
+ VDMemDiskDestroy(pDisk->pMemDiskVerify);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = VDCreate(pGlob->pInterfacesDisk, VDTYPE_HDD, &pDisk->pVD);
+
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pGlob->ListDisks, &pDisk->ListNode);
+ else
+ {
+ if (fVerify)
+ {
+ RTCritSectDelete(&pDisk->CritSectVerify);
+ VDMemDiskDestroy(pDisk->pMemDiskVerify);
+ }
+ RTStrFree(pDisk->pszName);
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pDisk);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerDestroyDisk(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszDisk = NULL;
+ PVDDISK pDisk = NULL;
+
+ pcszDisk = paScriptArgs[0].psz;
+
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (pDisk)
+ {
+ RTListNodeRemove(&pDisk->ListNode);
+ VDDestroy(pDisk->pVD);
+ if (pDisk->pMemDiskVerify)
+ {
+ VDMemDiskDestroy(pDisk->pMemDiskVerify);
+ RTCritSectDelete(&pDisk->CritSectVerify);
+ }
+ RTStrFree(pDisk->pszName);
+ RTMemFree(pDisk);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerCompareDisks(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszDisk1 = NULL;
+ PVDDISK pDisk1 = NULL;
+ const char *pcszDisk2 = NULL;
+ PVDDISK pDisk2 = NULL;
+
+ pcszDisk1 = paScriptArgs[0].psz;
+ pcszDisk2 = paScriptArgs[1].psz;
+
+ pDisk1 = tstVDIoGetDiskByName(pGlob, pcszDisk1);
+ pDisk2 = tstVDIoGetDiskByName(pGlob, pcszDisk2);
+
+ if (pDisk1 && pDisk2)
+ {
+ uint8_t *pbBuf1 = (uint8_t *)RTMemAllocZ(16 * _1M);
+ uint8_t *pbBuf2 = (uint8_t *)RTMemAllocZ(16 * _1M);
+ if (pbBuf1 && pbBuf2)
+ {
+ uint64_t cbDisk1, cbDisk2;
+ uint64_t uOffCur = 0;
+
+ cbDisk1 = VDGetSize(pDisk1->pVD, VD_LAST_IMAGE);
+ cbDisk2 = VDGetSize(pDisk2->pVD, VD_LAST_IMAGE);
+
+ RTTestSub(pGlob->hTest, "Comparing two disks for equal content");
+ if (cbDisk1 != cbDisk2)
+ RTTestFailed(pGlob->hTest, "Disks differ in size %llu vs %llu\n", cbDisk1, cbDisk2);
+ else
+ {
+ while (uOffCur < cbDisk1)
+ {
+ size_t cbRead = RT_MIN(cbDisk1, 16 * _1M);
+
+ rc = VDRead(pDisk1->pVD, uOffCur, pbBuf1, cbRead);
+ if (RT_SUCCESS(rc))
+ rc = VDRead(pDisk2->pVD, uOffCur, pbBuf2, cbRead);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(pbBuf1, pbBuf2, cbRead))
+ {
+ RTTestFailed(pGlob->hTest, "Disks differ at offset %llu\n", uOffCur);
+ rc = VERR_DEV_IO_ERROR;
+ break;
+ }
+ }
+ else
+ {
+ RTTestFailed(pGlob->hTest, "Reading one disk at offset %llu failed\n", uOffCur);
+ break;
+ }
+
+ uOffCur += cbRead;
+ cbDisk1 -= cbRead;
+ }
+ }
+ RTMemFree(pbBuf1);
+ RTMemFree(pbBuf2);
+ }
+ else
+ {
+ if (pbBuf1)
+ RTMemFree(pbBuf1);
+ if (pbBuf2)
+ RTMemFree(pbBuf2);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerDumpDiskInfo(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszDisk = NULL;
+ PVDDISK pDisk = NULL;
+
+ pcszDisk = paScriptArgs[0].psz;
+
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+
+ if (pDisk)
+ VDDumpImages(pDisk->pVD);
+ else
+ rc = VERR_NOT_FOUND;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerPrintMsg(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ RT_NOREF1(pvUser);
+ RTPrintf("%s\n", paScriptArgs[0].psz);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerShowStatistics(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszFile = paScriptArgs[0].psz;
+
+ /* Check for the file. */
+ bool fFound = false;
+ PVDFILE pIt;
+ RTListForEach(&pGlob->ListFiles, pIt, VDFILE, Node)
+ {
+ if (!RTStrCmp(pIt->pszName, pcszFile))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ if (fFound)
+ {
+ RTPrintf("Statistics %s: \n"
+ " sync reads=%u writes=%u flushes=%u\n"
+ " async reads=%u writes=%u flushes=%u\n",
+ pcszFile,
+ pIt->cReads, pIt->cWrites, pIt->cFlushes,
+ pIt->cAsyncReads, pIt->cAsyncWrites, pIt->cAsyncFlushes);
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerResetStatistics(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszFile = paScriptArgs[0].psz;
+
+ /* Check for the file. */
+ bool fFound = false;
+ PVDFILE pIt;
+ RTListForEach(&pGlob->ListFiles, pIt, VDFILE, Node)
+ {
+ if (!RTStrCmp(pIt->pszName, pcszFile))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ if (fFound)
+ {
+ pIt->cReads = 0;
+ pIt->cWrites = 0;
+ pIt->cFlushes = 0;
+
+ pIt->cAsyncReads = 0;
+ pIt->cAsyncWrites = 0;
+ pIt->cAsyncFlushes = 0;
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerResize(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszDisk = paScriptArgs[0].psz;
+ uint64_t cbDiskNew = paScriptArgs[1].u64;
+ PVDDISK pDisk = NULL;
+
+ pDisk = tstVDIoGetDiskByName(pGlob, pcszDisk);
+ if (pDisk)
+ {
+ rc = VDResize(pDisk->pVD, cbDiskNew, &pDisk->PhysGeom, &pDisk->LogicalGeom, NULL);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerSetFileBackend(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ const char *pcszBackend = paScriptArgs[0].psz;
+
+ RTStrFree(pGlob->pszIoBackend);
+ pGlob->pszIoBackend = RTStrDup(pcszBackend);
+ if (!pGlob->pszIoBackend)
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptHandlerLoadPlugin(PVDSCRIPTARG paScriptArgs, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ const char *pcszPlugin = paScriptArgs[0].psz;
+
+ return VDPluginLoadFromFilename(pcszPlugin);
+}
+
+static DECLCALLBACK(int) tstVDIoFileOpen(void *pvUser, const char *pszLocation,
+ uint32_t fOpen,
+ PFNVDCOMPLETED pfnCompleted,
+ void **ppStorage)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ bool fFound = false;
+
+ /*
+ * Some backends use ./ for paths, strip it.
+ * @todo: Implement proper directory support for the
+ * memory filesystem.
+ */
+ if ( strlen(pszLocation) >= 2
+ && *pszLocation == '.'
+ && ( pszLocation[1] == '/'
+ || pszLocation[1] == '\\'))
+ pszLocation += 2;
+
+ /* Check if the file exists. */
+ PVDFILE pIt;
+ RTListForEach(&pGlob->ListFiles, pIt, VDFILE, Node)
+ {
+ if (!RTStrCmp(pIt->pszName, pszLocation))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ if ((fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE)
+ {
+ /* If the file exists delete the memory disk. */
+ if (fFound)
+ rc = VDIoBackendStorageSetSize(pIt->pIoStorage, 0);
+ else
+ {
+ /* Create completey new. */
+ pIt = (PVDFILE)RTMemAllocZ(sizeof(VDFILE));
+ if (pIt)
+ {
+ pIt->pszName = RTStrDup(pszLocation);
+
+ if (pIt->pszName)
+ {
+ rc = VDIoBackendStorageCreate(pGlob->pIoBackend, pGlob->pszIoBackend,
+ pszLocation, pfnCompleted, &pIt->pIoStorage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ {
+ if (pIt->pszName)
+ RTStrFree(pIt->pszName);
+ RTMemFree(pIt);
+ }
+ else
+ RTListAppend(&pGlob->ListFiles, &pIt->Node);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else if ((fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN)
+ {
+ if (!fFound)
+ rc = VERR_FILE_NOT_FOUND;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ if (RT_SUCCESS(rc))
+ {
+ AssertPtr(pIt);
+ PVDSTORAGE pStorage = (PVDSTORAGE)RTMemAllocZ(sizeof(VDSTORAGE));
+ if (pStorage)
+ {
+ pStorage->pFile = pIt;
+ pStorage->pfnComplete = pfnCompleted;
+ *ppStorage = pStorage;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) tstVDIoFileClose(void *pvUser, void *pStorage)
+{
+ RT_NOREF1(pvUser);
+ PVDSTORAGE pIoStorage = (PVDSTORAGE)pStorage;
+
+ RTMemFree(pIoStorage);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstVDIoFileDelete(void *pvUser, const char *pcszFilename)
+{
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ bool fFound = false;
+
+ /*
+ * Some backends use ./ for paths, strip it.
+ * @todo: Implement proper directory support for the
+ * memory filesystem.
+ */
+ if ( strlen(pcszFilename) >= 2
+ && *pcszFilename == '.'
+ && pcszFilename[1] == '/')
+ pcszFilename += 2;
+
+ /* Check if the file exists. */
+ PVDFILE pIt;
+ RTListForEach(&pGlob->ListFiles, pIt, VDFILE, Node)
+ {
+ if (!RTStrCmp(pIt->pszName, pcszFilename))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ if (fFound)
+ {
+ RTListNodeRemove(&pIt->Node);
+ VDIoBackendStorageDestroy(pIt->pIoStorage);
+ RTStrFree(pIt->pszName);
+ RTMemFree(pIt);
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) tstVDIoFileMove(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
+{
+ RT_NOREF1(fMove);
+ int rc = VINF_SUCCESS;
+ PVDTESTGLOB pGlob = (PVDTESTGLOB)pvUser;
+ bool fFound = false;
+
+ /* Check if the file exists. */
+ PVDFILE pIt;
+ RTListForEach(&pGlob->ListFiles, pIt, VDFILE, Node)
+ {
+ if (!RTStrCmp(pIt->pszName, pcszSrc))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ if (fFound)
+ {
+ char *pszNew = RTStrDup(pcszDst);
+ if (pszNew)
+ {
+ RTStrFree(pIt->pszName);
+ pIt->pszName = pszNew;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) tstVDIoFileGetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
+{
+ RT_NOREF2(pvUser, pcszFilename);
+ AssertPtrReturn(pcbFreeSpace, VERR_INVALID_POINTER);
+
+ *pcbFreeSpace = ~0ULL; /** @todo Implement */
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstVDIoFileGetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
+{
+ RT_NOREF2(pvUser, pcszFilename);
+ AssertPtrReturn(pModificationTime, VERR_INVALID_POINTER);
+
+ /** @todo Implement */
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstVDIoFileGetSize(void *pvUser, void *pStorage, uint64_t *pcbSize)
+{
+ RT_NOREF1(pvUser);
+ PVDSTORAGE pIoStorage = (PVDSTORAGE)pStorage;
+
+ return VDIoBackendStorageGetSize(pIoStorage->pFile->pIoStorage, pcbSize);
+}
+
+static DECLCALLBACK(int) tstVDIoFileSetSize(void *pvUser, void *pStorage, uint64_t cbSize)
+{
+ RT_NOREF1(pvUser);
+ PVDSTORAGE pIoStorage = (PVDSTORAGE)pStorage;
+
+ return VDIoBackendStorageSetSize(pIoStorage->pFile->pIoStorage, cbSize);
+}
+
+static DECLCALLBACK(int) tstVDIoFileSetAllocationSize(void *pvUser, void *pStorage, uint64_t cbSize, uint32_t fFlags)
+{
+ RT_NOREF4(pvUser, pStorage, cbSize, fFlags);
+ return VERR_NOT_SUPPORTED;
+}
+
+static DECLCALLBACK(int) tstVDIoFileWriteSync(void *pvUser, void *pStorage, uint64_t uOffset,
+ const void *pvBuffer, size_t cbBuffer, size_t *pcbWritten)
+{
+ RT_NOREF1(pvUser);
+ int rc = VINF_SUCCESS;
+ PVDSTORAGE pIoStorage = (PVDSTORAGE)pStorage;
+
+ RTSGBUF SgBuf;
+ RTSGSEG Seg;
+
+ Seg.pvSeg = (void *)pvBuffer;
+ Seg.cbSeg = cbBuffer;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ rc = VDIoBackendTransfer(pIoStorage->pFile->pIoStorage, VDIOTXDIR_WRITE, uOffset,
+ cbBuffer, &SgBuf, NULL, true /* fSync */);
+ if (RT_SUCCESS(rc))
+ {
+ pIoStorage->pFile->cWrites++;
+ if (pcbWritten)
+ *pcbWritten = cbBuffer;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) tstVDIoFileReadSync(void *pvUser, void *pStorage, uint64_t uOffset,
+ void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
+{
+ RT_NOREF1(pvUser);
+ int rc = VINF_SUCCESS;
+ PVDSTORAGE pIoStorage = (PVDSTORAGE)pStorage;
+
+ RTSGBUF SgBuf;
+ RTSGSEG Seg;
+
+ Seg.pvSeg = pvBuffer;
+ Seg.cbSeg = cbBuffer;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ rc = VDIoBackendTransfer(pIoStorage->pFile->pIoStorage, VDIOTXDIR_READ, uOffset,
+ cbBuffer, &SgBuf, NULL, true /* fSync */);
+ if (RT_SUCCESS(rc))
+ {
+ pIoStorage->pFile->cReads++;
+ if (pcbRead)
+ *pcbRead = cbBuffer;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) tstVDIoFileFlushSync(void *pvUser, void *pStorage)
+{
+ RT_NOREF1(pvUser);
+ PVDSTORAGE pIoStorage = (PVDSTORAGE)pStorage;
+ int rc = VDIoBackendTransfer(pIoStorage->pFile->pIoStorage, VDIOTXDIR_FLUSH, 0,
+ 0, NULL, NULL, true /* fSync */);
+ pIoStorage->pFile->cFlushes++;
+ return rc;
+}
+
+static DECLCALLBACK(int) tstVDIoFileReadAsync(void *pvUser, void *pStorage, uint64_t uOffset,
+ PCRTSGSEG paSegments, size_t cSegments,
+ size_t cbRead, void *pvCompletion,
+ void **ppTask)
+{
+ RT_NOREF2(pvUser, ppTask);
+ int rc = VINF_SUCCESS;
+ PVDSTORAGE pIoStorage = (PVDSTORAGE)pStorage;
+ RTSGBUF SgBuf;
+
+ RTSgBufInit(&SgBuf, paSegments, cSegments);
+ rc = VDIoBackendTransfer(pIoStorage->pFile->pIoStorage, VDIOTXDIR_READ, uOffset,
+ cbRead, &SgBuf, pvCompletion, false /* fSync */);
+ if (RT_SUCCESS(rc))
+ {
+ pIoStorage->pFile->cAsyncReads++;
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) tstVDIoFileWriteAsync(void *pvUser, void *pStorage, uint64_t uOffset,
+ PCRTSGSEG paSegments, size_t cSegments,
+ size_t cbWrite, void *pvCompletion,
+ void **ppTask)
+{
+ RT_NOREF2(pvUser, ppTask);
+ int rc = VINF_SUCCESS;
+ PVDSTORAGE pIoStorage = (PVDSTORAGE)pStorage;
+ RTSGBUF SgBuf;
+
+ RTSgBufInit(&SgBuf, paSegments, cSegments);
+ rc = VDIoBackendTransfer(pIoStorage->pFile->pIoStorage, VDIOTXDIR_WRITE, uOffset,
+ cbWrite, &SgBuf, pvCompletion, false /* fSync */);
+ if (RT_SUCCESS(rc))
+ {
+ pIoStorage->pFile->cAsyncWrites++;
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) tstVDIoFileFlushAsync(void *pvUser, void *pStorage, void *pvCompletion,
+ void **ppTask)
+{
+ RT_NOREF2(pvUser, ppTask);
+ int rc = VINF_SUCCESS;
+ PVDSTORAGE pIoStorage = (PVDSTORAGE)pStorage;
+
+ rc = VDIoBackendTransfer(pIoStorage->pFile->pIoStorage, VDIOTXDIR_FLUSH, 0,
+ 0, NULL, pvCompletion, false /* fSync */);
+ if (RT_SUCCESS(rc))
+ {
+ pIoStorage->pFile->cAsyncFlushes++;
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+
+ return rc;
+}
+
+static int tstVDIoTestInit(PVDIOTEST pIoTest, PVDTESTGLOB pGlob, bool fRandomAcc, uint32_t cSegsMax,
+ uint64_t cbIo, size_t cbBlkSize, uint64_t offStart, uint64_t offEnd,
+ unsigned uWriteChance, PVDPATTERN pPattern)
+{
+ int rc = VINF_SUCCESS;
+
+ RT_ZERO(*pIoTest);
+ pIoTest->fRandomAccess = fRandomAcc;
+ pIoTest->cbIo = cbIo;
+ pIoTest->cbBlkIo = cbBlkSize;
+ pIoTest->offStart = offStart;
+ pIoTest->offEnd = offEnd;
+ pIoTest->uWriteChance = uWriteChance;
+ pIoTest->cSegsMax = cSegsMax;
+ pIoTest->pIoRnd = pGlob->pIoRnd;
+ pIoTest->pPattern = pPattern;
+
+ if (fRandomAcc)
+ {
+ uint64_t cbRange = pIoTest->offEnd < pIoTest->offStart
+ ? pIoTest->offStart - pIoTest->offEnd
+ : pIoTest->offEnd - pIoTest->offStart;
+
+ pIoTest->u.Rnd.cBlocks = (uint32_t)(cbRange / cbBlkSize + (cbRange % cbBlkSize ? 1 : 0));
+ pIoTest->u.Rnd.cBlocksLeft = pIoTest->u.Rnd.cBlocks;
+ pIoTest->u.Rnd.pbMapAccessed = (uint8_t *)RTMemAllocZ(pIoTest->u.Rnd.cBlocks / 8
+ + ((pIoTest->u.Rnd.cBlocks % 8)
+ ? 1
+ : 0));
+ if (!pIoTest->u.Rnd.pbMapAccessed)
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ pIoTest->u.offNext = pIoTest->offEnd < pIoTest->offStart ? pIoTest->offStart - cbBlkSize : offStart;
+
+ return rc;
+}
+
+static void tstVDIoTestDestroy(PVDIOTEST pIoTest)
+{
+ if (pIoTest->fRandomAccess)
+ RTMemFree(pIoTest->u.Rnd.pbMapAccessed);
+}
+
+static bool tstVDIoTestRunning(PVDIOTEST pIoTest)
+{
+ return pIoTest->cbIo > 0;
+}
+
+static bool tstVDIoTestReqOutstanding(PTSTVDIOREQ pIoReq)
+{
+ return pIoReq->fOutstanding;
+}
+
+static uint32_t tstVDIoTestReqInitSegments(PVDIOTEST pIoTest, PRTSGSEG paSegs, uint32_t cSegs, void *pvBuf, size_t cbBuf)
+{
+ uint8_t *pbBuf = (uint8_t *)pvBuf;
+ size_t cSectorsLeft = cbBuf / 512;
+ uint32_t iSeg = 0;
+
+ /* Init all but the last segment which needs to take the rest. */
+ while ( iSeg < cSegs - 1
+ && cSectorsLeft)
+ {
+ uint32_t cThisSectors = VDIoRndGetU32Ex(pIoTest->pIoRnd, 1, (uint32_t)cSectorsLeft / 2);
+ size_t cbThisBuf = cThisSectors * 512;
+
+ paSegs[iSeg].pvSeg = pbBuf;
+ paSegs[iSeg].cbSeg = cbThisBuf;
+ pbBuf += cbThisBuf;
+ cSectorsLeft -= cThisSectors;
+ iSeg++;
+ }
+
+ if (cSectorsLeft)
+ {
+ paSegs[iSeg].pvSeg = pbBuf;
+ paSegs[iSeg].cbSeg = cSectorsLeft * 512;
+ iSeg++;
+ }
+
+ return iSeg;
+}
+
+/**
+ * Returns true with the given chance in percent.
+ *
+ * @returns true or false
+ * @param iPercentage The percentage of the chance to return true.
+ */
+static bool tstVDIoTestIsTrue(PVDIOTEST pIoTest, int iPercentage)
+{
+ int uRnd = VDIoRndGetU32Ex(pIoTest->pIoRnd, 0, 100);
+
+ return (uRnd < iPercentage); /* This should be enough for our purpose */
+}
+
+static int tstVDIoTestReqInit(PVDIOTEST pIoTest, PTSTVDIOREQ pIoReq, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pIoTest->cbIo)
+ {
+ /* Read or Write? */
+ pIoReq->enmTxDir = tstVDIoTestIsTrue(pIoTest, pIoTest->uWriteChance) ? TSTVDIOREQTXDIR_WRITE : TSTVDIOREQTXDIR_READ;
+ pIoReq->cbReq = RT_MIN(pIoTest->cbBlkIo, pIoTest->cbIo);
+ pIoTest->cbIo -= pIoReq->cbReq;
+
+ void *pvBuf = NULL;
+
+ if (pIoReq->enmTxDir == TSTVDIOREQTXDIR_WRITE)
+ {
+ if (pIoTest->pPattern)
+ rc = tstVDIoPatternGetBuffer(pIoTest->pPattern, &pvBuf, pIoReq->cbReq);
+ else
+ rc = VDIoRndGetBuffer(pIoTest->pIoRnd, &pvBuf, pIoReq->cbReq);
+ AssertRC(rc);
+ }
+ else
+ {
+ /* Read */
+ pvBuf = pIoReq->pvBufRead;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pIoReq->pvBuf = pvBuf;
+ uint32_t cSegsMax = VDIoRndGetU32Ex(pIoTest->pIoRnd, 1, RT_MIN(pIoTest->cSegsMax, RT_ELEMENTS(pIoReq->aSegs)));
+ pIoReq->cSegs = tstVDIoTestReqInitSegments(pIoTest, &pIoReq->aSegs[0], cSegsMax, pvBuf, pIoReq->cbReq);
+ RTSgBufInit(&pIoReq->SgBuf, &pIoReq->aSegs[0], pIoReq->cSegs);
+
+ if (pIoTest->fRandomAccess)
+ {
+ int idx = -1;
+
+ idx = ASMBitFirstClear(pIoTest->u.Rnd.pbMapAccessed, pIoTest->u.Rnd.cBlocks);
+
+ /* In case this is the last request we don't need to search further. */
+ if (pIoTest->u.Rnd.cBlocksLeft > 1)
+ {
+ int idxIo;
+ idxIo = VDIoRndGetU32Ex(pIoTest->pIoRnd, idx, pIoTest->u.Rnd.cBlocks - 1);
+
+ /*
+ * If the bit is marked free use it, otherwise search for the next free bit
+ * and if that doesn't work use the first free bit.
+ */
+ if (ASMBitTest(pIoTest->u.Rnd.pbMapAccessed, idxIo))
+ {
+ idxIo = ASMBitNextClear(pIoTest->u.Rnd.pbMapAccessed, pIoTest->u.Rnd.cBlocks, idxIo);
+ if (idxIo != -1)
+ idx = idxIo;
+ }
+ else
+ idx = idxIo;
+ }
+
+ Assert(idx != -1);
+ pIoReq->off = (uint64_t)idx * pIoTest->cbBlkIo;
+ pIoTest->u.Rnd.cBlocksLeft--;
+ if (!pIoTest->u.Rnd.cBlocksLeft)
+ {
+ /* New round, clear everything. */
+ ASMBitClearRange(pIoTest->u.Rnd.pbMapAccessed, 0, pIoTest->u.Rnd.cBlocks);
+ pIoTest->u.Rnd.cBlocksLeft = pIoTest->u.Rnd.cBlocks;
+ }
+ else
+ ASMBitSet(pIoTest->u.Rnd.pbMapAccessed, idx);
+ }
+ else
+ {
+ pIoReq->off = pIoTest->u.offNext;
+ if (pIoTest->offEnd < pIoTest->offStart)
+ {
+ pIoTest->u.offNext = pIoTest->u.offNext == 0
+ ? pIoTest->offEnd - pIoTest->cbBlkIo
+ : RT_MAX(pIoTest->offEnd, pIoTest->u.offNext - pIoTest->cbBlkIo);
+ }
+ else
+ {
+ pIoTest->u.offNext = pIoTest->u.offNext + pIoTest->cbBlkIo >= pIoTest->offEnd
+ ? 0
+ : RT_MIN(pIoTest->offEnd, pIoTest->u.offNext + pIoTest->cbBlkIo);
+ }
+ }
+ pIoReq->pvUser = pvUser;
+ pIoReq->fOutstanding = true;
+ }
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+
+ return rc;
+}
+
+static DECLCALLBACK(void) tstVDIoTestReqComplete(void *pvUser1, void *pvUser2, int rcReq)
+{
+ RT_NOREF1(rcReq);
+ PTSTVDIOREQ pIoReq = (PTSTVDIOREQ)pvUser1;
+ RTSEMEVENT hEventSem = (RTSEMEVENT)pvUser2;
+ PVDDISK pDisk = (PVDDISK)pIoReq->pvUser;
+
+ LogFlow(("Request %d completed\n", pIoReq->idx));
+
+ if (pDisk->pMemDiskVerify)
+ {
+ switch (pIoReq->enmTxDir)
+ {
+ case TSTVDIOREQTXDIR_READ:
+ {
+ RTCritSectEnter(&pDisk->CritSectVerify);
+
+ RTSGBUF SgBufCmp;
+ RTSGSEG SegCmp;
+ SegCmp.pvSeg = pIoReq->pvBuf;
+ SegCmp.cbSeg = pIoReq->cbReq;
+ RTSgBufInit(&SgBufCmp, &SegCmp, 1);
+
+ if (VDMemDiskCmp(pDisk->pMemDiskVerify, pIoReq->off, pIoReq->cbReq,
+ &SgBufCmp))
+ RTTestFailed(pDisk->pTestGlob->hTest, "Corrupted disk at offset %llu!\n", pIoReq->off);
+ RTCritSectLeave(&pDisk->CritSectVerify);
+ break;
+ }
+ case TSTVDIOREQTXDIR_WRITE:
+ {
+ RTCritSectEnter(&pDisk->CritSectVerify);
+
+ RTSGBUF SgBuf;
+ RTSGSEG Seg;
+ Seg.pvSeg = pIoReq->pvBuf;
+ Seg.cbSeg = pIoReq->cbReq;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+
+ int rc = VDMemDiskWrite(pDisk->pMemDiskVerify, pIoReq->off, pIoReq->cbReq,
+ &SgBuf);
+ AssertRC(rc);
+ RTCritSectLeave(&pDisk->CritSectVerify);
+ break;
+ }
+ case TSTVDIOREQTXDIR_FLUSH:
+ case TSTVDIOREQTXDIR_DISCARD:
+ break;
+ }
+ }
+
+ ASMAtomicXchgBool(&pIoReq->fOutstanding, false);
+ RTSemEventSignal(hEventSem);
+ return;
+}
+
+/**
+ * Returns the disk handle by name or NULL if not found
+ *
+ * @returns Disk handle or NULL if the disk could not be found.
+ *
+ * @param pGlob Global test state.
+ * @param pcszDisk Name of the disk to get.
+ */
+static PVDDISK tstVDIoGetDiskByName(PVDTESTGLOB pGlob, const char *pcszDisk)
+{
+ bool fFound = false;
+
+ LogFlowFunc(("pGlob=%#p pcszDisk=%s\n", pGlob, pcszDisk));
+
+ PVDDISK pIt;
+ RTListForEach(&pGlob->ListDisks, pIt, VDDISK, ListNode)
+ {
+ if (!RTStrCmp(pIt->pszName, pcszDisk))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ LogFlowFunc(("return %#p\n", fFound ? pIt : NULL));
+ return fFound ? pIt : NULL;
+}
+
+/**
+ * Returns the I/O pattern handle by name of NULL if not found.
+ *
+ * @returns I/O pattern handle or NULL if the pattern could not be found.
+ *
+ * @param pGlob Global test state.
+ * @param pcszName Name of the pattern.
+ */
+static PVDPATTERN tstVDIoGetPatternByName(PVDTESTGLOB pGlob, const char *pcszName)
+{
+ bool fFound = false;
+
+ LogFlowFunc(("pGlob=%#p pcszName=%s\n", pGlob, pcszName));
+
+ PVDPATTERN pIt;
+ RTListForEach(&pGlob->ListPatterns, pIt, VDPATTERN, ListNode)
+ {
+ if (!RTStrCmp(pIt->pszName, pcszName))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ LogFlowFunc(("return %#p\n", fFound ? pIt : NULL));
+ return fFound ? pIt : NULL;
+}
+
+/**
+ * Creates a new pattern with the given name and an
+ * allocated pattern buffer.
+ *
+ * @returns Pointer to a new pattern buffer or NULL on failure.
+ * @param pcszName Name of the pattern.
+ * @param cbPattern Size of the pattern buffer.
+ */
+static PVDPATTERN tstVDIoPatternCreate(const char *pcszName, size_t cbPattern)
+{
+ PVDPATTERN pPattern = (PVDPATTERN)RTMemAllocZ(sizeof(VDPATTERN));
+ char *pszName = RTStrDup(pcszName);
+ void *pvPattern = RTMemAllocZ(cbPattern);
+
+ if (pPattern && pszName && pvPattern)
+ {
+ pPattern->pszName = pszName;
+ pPattern->pvPattern = pvPattern;
+ pPattern->cbPattern = cbPattern;
+ }
+ else
+ {
+ if (pPattern)
+ RTMemFree(pPattern);
+ if (pszName)
+ RTStrFree(pszName);
+ if (pvPattern)
+ RTMemFree(pvPattern);
+
+ pPattern = NULL;
+ pszName = NULL;
+ pvPattern = NULL;
+ }
+
+ return pPattern;
+}
+
+static int tstVDIoPatternGetBuffer(PVDPATTERN pPattern, void **ppv, size_t cb)
+{
+ AssertPtrReturn(pPattern, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppv, VERR_INVALID_POINTER);
+ AssertReturn(cb > 0, VERR_INVALID_PARAMETER);
+
+ if (cb > pPattern->cbPattern)
+ return VERR_INVALID_PARAMETER;
+
+ *ppv = pPattern->pvPattern;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Executes the given script.
+ *
+ * @returns nothing.
+ * @param pszName The script name.
+ * @param pszScript The script to execute.
+ */
+static void tstVDIoScriptExec(const char *pszName, const char *pszScript)
+{
+ int rc = VINF_SUCCESS;
+ VDTESTGLOB GlobTest; /**< Global test data. */
+
+ memset(&GlobTest, 0, sizeof(VDTESTGLOB));
+ RTListInit(&GlobTest.ListFiles);
+ RTListInit(&GlobTest.ListDisks);
+ RTListInit(&GlobTest.ListPatterns);
+ GlobTest.pszIoBackend = RTStrDup("memory");
+ if (!GlobTest.pszIoBackend)
+ {
+ RTPrintf("Out of memory allocating I/O backend string\n");
+ return;
+ }
+
+ /* Init global test data. */
+ GlobTest.VDIfError.pfnError = tstVDError;
+ GlobTest.VDIfError.pfnMessage = tstVDMessage;
+
+ rc = VDInterfaceAdd(&GlobTest.VDIfError.Core, "tstVDIo_VDIError", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &GlobTest.pInterfacesDisk);
+ AssertRC(rc);
+
+ GlobTest.VDIfIo.pfnOpen = tstVDIoFileOpen;
+ GlobTest.VDIfIo.pfnClose = tstVDIoFileClose;
+ GlobTest.VDIfIo.pfnDelete = tstVDIoFileDelete;
+ GlobTest.VDIfIo.pfnMove = tstVDIoFileMove;
+ GlobTest.VDIfIo.pfnGetFreeSpace = tstVDIoFileGetFreeSpace;
+ GlobTest.VDIfIo.pfnGetModificationTime = tstVDIoFileGetModificationTime;
+ GlobTest.VDIfIo.pfnGetSize = tstVDIoFileGetSize;
+ GlobTest.VDIfIo.pfnSetSize = tstVDIoFileSetSize;
+ GlobTest.VDIfIo.pfnSetAllocationSize = tstVDIoFileSetAllocationSize;
+ GlobTest.VDIfIo.pfnWriteSync = tstVDIoFileWriteSync;
+ GlobTest.VDIfIo.pfnReadSync = tstVDIoFileReadSync;
+ GlobTest.VDIfIo.pfnFlushSync = tstVDIoFileFlushSync;
+ GlobTest.VDIfIo.pfnReadAsync = tstVDIoFileReadAsync;
+ GlobTest.VDIfIo.pfnWriteAsync = tstVDIoFileWriteAsync;
+ GlobTest.VDIfIo.pfnFlushAsync = tstVDIoFileFlushAsync;
+
+ rc = VDInterfaceAdd(&GlobTest.VDIfIo.Core, "tstVDIo_VDIIo", VDINTERFACETYPE_IO,
+ &GlobTest, sizeof(VDINTERFACEIO), &GlobTest.pInterfacesImages);
+ AssertRC(rc);
+
+ rc = RTTestCreate(pszName, &GlobTest.hTest);
+ if (RT_SUCCESS(rc))
+ {
+ /* Init I/O backend. */
+ rc = VDIoBackendCreate(&GlobTest.pIoBackend);
+ if (RT_SUCCESS(rc))
+ {
+ VDSCRIPTCTX hScriptCtx = NULL;
+ rc = VDScriptCtxCreate(&hScriptCtx);
+ if (RT_SUCCESS(rc))
+ {
+ RTTEST_CHECK_RC_OK(GlobTest.hTest,
+ VDScriptCtxCallbacksRegister(hScriptCtx, g_aScriptActions, g_cScriptActions, &GlobTest));
+
+ RTTestBanner(GlobTest.hTest);
+ rc = VDScriptCtxLoadScript(hScriptCtx, pszScript);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("Loading the script failed rc=%Rrc\n", rc);
+ }
+ else
+ rc = VDScriptCtxCallFn(hScriptCtx, "main", NULL, 0);
+ VDScriptCtxDestroy(hScriptCtx);
+ }
+
+ /* Clean up all leftover resources. */
+ PVDPATTERN pPatternIt, pPatternItNext;
+ RTListForEachSafe(&GlobTest.ListPatterns, pPatternIt, pPatternItNext, VDPATTERN, ListNode)
+ {
+ RTPrintf("Cleanup: Leftover pattern \"%s\", deleting...\n", pPatternIt->pszName);
+ RTListNodeRemove(&pPatternIt->ListNode);
+ RTMemFree(pPatternIt->pvPattern);
+ RTStrFree(pPatternIt->pszName);
+ RTMemFree(pPatternIt);
+ }
+
+ PVDDISK pDiskIt, pDiskItNext;
+ RTListForEachSafe(&GlobTest.ListDisks, pDiskIt, pDiskItNext, VDDISK, ListNode)
+ {
+ RTPrintf("Cleanup: Leftover disk \"%s\", deleting...\n", pDiskIt->pszName);
+ RTListNodeRemove(&pDiskIt->ListNode);
+ VDDestroy(pDiskIt->pVD);
+ if (pDiskIt->pMemDiskVerify)
+ {
+ VDMemDiskDestroy(pDiskIt->pMemDiskVerify);
+ RTCritSectDelete(&pDiskIt->CritSectVerify);
+ }
+ RTStrFree(pDiskIt->pszName);
+ RTMemFree(pDiskIt);
+ }
+
+ PVDFILE pFileIt, pFileItNext;
+ RTListForEachSafe(&GlobTest.ListFiles, pFileIt, pFileItNext, VDFILE, Node)
+ {
+ RTPrintf("Cleanup: Leftover file \"%s\", deleting...\n", pFileIt->pszName);
+ RTListNodeRemove(&pFileIt->Node);
+ VDIoBackendStorageDestroy(pFileIt->pIoStorage);
+ RTStrFree(pFileIt->pszName);
+ RTMemFree(pFileIt);
+ }
+
+ VDIoBackendDestroy(GlobTest.pIoBackend);
+ }
+ else
+ RTPrintf("Creating the I/O backend failed rc=%Rrc\n", rc);
+
+ RTTestSummaryAndDestroy(GlobTest.hTest);
+ }
+ else
+ RTStrmPrintf(g_pStdErr, "tstVDIo: fatal error: RTTestCreate failed with rc=%Rrc\n", rc);
+
+ RTStrFree(GlobTest.pszIoBackend);
+}
+
+/**
+ * Executes the given I/O script using the new scripting engine.
+ *
+ * @returns nothing.
+ *
+ * @param pcszFilename The script to execute.
+ */
+static void tstVDIoScriptRun(const char *pcszFilename)
+{
+ int rc = VINF_SUCCESS;
+ void *pvFile = NULL;
+ size_t cbFile = 0;
+
+ rc = RTFileReadAll(pcszFilename, &pvFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszScript = RTStrDupN((char *)pvFile, cbFile);
+ RTFileReadAllFree(pvFile, cbFile);
+
+ AssertPtr(pszScript);
+ tstVDIoScriptExec(pcszFilename, pszScript);
+ RTStrFree(pszScript);
+ }
+ else
+ RTPrintf("Opening the script failed: %Rrc\n", rc);
+
+}
+
+/**
+ * Run builtin tests.
+ *
+ * @returns nothing.
+ */
+static void tstVDIoRunBuiltinTests(void)
+{
+ /* 32bit hosts are excluded because of the 4GB address space. */
+#if HC_ARCH_BITS == 32
+ RTStrmPrintf(g_pStdErr, "tstVDIo: Running on a 32bit host is not supported for the builtin tests, skipping\n");
+ return;
+#else
+ /*
+ * We need quite a bit of RAM for the builtin tests. Skip it if there
+ * is not enough free RAM available.
+ */
+ uint64_t cbFree = 0;
+ int rc = RTSystemQueryAvailableRam(&cbFree);
+ if ( RT_FAILURE(rc)
+ || cbFree < (UINT64_C(6) * _1G))
+ {
+ RTStrmPrintf(g_pStdErr, "tstVDIo: fatal error: Failed to query available RAM or not enough available, skipping (rc=%Rrc cbFree=%llu)\n",
+ rc, cbFree);
+ return;
+ }
+
+ for (unsigned i = 0; i < g_cVDIoTests; i++)
+ {
+ char *pszScript = RTStrDupN((const char *)g_aVDIoTests[i].pch, g_aVDIoTests[i].cb);
+
+ AssertPtr(pszScript);
+ tstVDIoScriptExec(g_aVDIoTests[i].pszName, pszScript);
+ RTStrFree(pszScript);
+ }
+#endif
+}
+
+/**
+ * Shows help message.
+ */
+static void printUsage(void)
+{
+ RTPrintf("Usage:\n"
+ "--script <filename> Script to execute\n");
+}
+
+static const RTGETOPTDEF g_aOptions[] =
+{
+ { "--script", 's', RTGETOPT_REQ_STRING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING }
+};
+
+int main(int argc, char *argv[])
+{
+ RTR3InitExe(argc, &argv, 0);
+ int rc;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ char c;
+
+ rc = VDInit();
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ if (argc == 1)
+ {
+ tstVDIoRunBuiltinTests();
+ return RTEXITCODE_SUCCESS;
+ }
+
+ RTGetOptInit(&GetState, argc, argv, g_aOptions,
+ RT_ELEMENTS(g_aOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
+
+ while ( RT_SUCCESS(rc)
+ && (c = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (c)
+ {
+ case 's':
+ tstVDIoScriptRun(ValueUnion.psz);
+ break;
+ case 'h':
+ printUsage();
+ break;
+ default: /* Default is to run built in tests if no arguments are given (automated testing). */
+ tstVDIoRunBuiltinTests();
+ }
+ }
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ RTPrintf("tstVDIo: unloading backends failed! rc=%Rrc\n", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
diff --git a/src/VBox/Storage/testcase/tstVDIo.vd b/src/VBox/Storage/testcase/tstVDIo.vd
new file mode 100644
index 00000000..fa6b372e
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDIo.vd
@@ -0,0 +1,73 @@
+/* $Id: tstVDIo.vd $ */
+/**
+ * Storage: Simple I/O testing for most backends.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+void tstIo(string strMessage, string strBackend)
+{
+ print(strMessage);
+ createdisk("test", true /* fVerify */);
+ create("test", "base", "tst.disk", "dynamic", strBackend, 200M, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "seq", 64K, 0, 200M, 200M, 100, "none");
+ io("test", false, 1, "seq", 64K, 0, 200M, 200M, 100, "none");
+ io("test", true, 32, "seq", 64K, 0, 200M, 200M, 0, "none");
+ io("test", false, 1, "seq", 64K, 0, 200M, 200M, 0, "none");
+ create("test", "diff", "tst2.disk", "dynamic", strBackend, 200M, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 200M, 200M, 50, "none");
+ io("test", false, 1, "rnd", 64K, 0, 200M, 200M, 50, "none");
+ create("test", "diff", "tst3.disk", "dynamic", strBackend, 200M, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 200M, 200M, 50, "none");
+ io("test", false, 1, "rnd", 64K, 0, 200M, 200M, 50, "none");
+ close("test", "single", true /* fDelete */);
+ close("test", "single", true /* fDelete */);
+ close("test", "single", true /* fDelete */);
+ destroydisk("test");
+}
+
+void tstIoUnaligned(string strMessage, string strBackend)
+{
+ print(strMessage);
+ createdisk("test", true);
+ create("test", "base", "tst.disk", "dynamic", strBackend, 2G, false);
+ io("test", false, 1, "seq", 512, 3584, 4096, 512, 100, "none");
+ io("test", false, 1, "seq", 512, 3584, 4096, 512, 0, "none");
+ destroydisk("test");
+}
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes */
+ iorngcreate(10M, "manual", 1234567890);
+
+ tstIo("Testing VDI", "VDI");
+ tstIo("Testing VMDK", "VMDK");
+ tstIo("Testing VHD", "VHD");
+ tstIo("Testing Parallels", "Parallels");
+ tstIo("Testing QED", "QED");
+ tstIo("Testing QCOW", "QCOW");
+
+ iorngdestroy();
+}
+
diff --git a/src/VBox/Storage/testcase/tstVDMultBackends.vd b/src/VBox/Storage/testcase/tstVDMultBackends.vd
new file mode 100644
index 00000000..c8a5e4e0
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDMultBackends.vd
@@ -0,0 +1,70 @@
+/* $Id: tstVDMultBackends.vd $ */
+/**
+ * Storage: Simple I/O test with different backends in one chain.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+void tstIo(string strMessage, string strBackend)
+{
+ print(strMessage);
+ createdisk("test", true /* fVerify */);
+ create("test", "base", "tst.disk", "dynamic", strBackend, 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "seq", 64K, 0, 2G, 200M, 100, "none");
+ io("test", false, 1, "seq", 64K, 0, 2G, 200M, 100, "none");
+ io("test", true, 32, "seq", 64K, 0, 2G, 200M, 0, "none");
+ io("test", false, 1, "seq", 64K, 0, 2G, 200M, 0, "none");
+ create("test", "diff", "tst2.disk", "dynamic", "VMDK", 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ io("test", false, 1, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ create("test", "diff", "tst3.disk", "dynamic", "VMDK", 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ io("test", false, 1, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ create("test", "diff", "tst4.disk", "dynamic", "VMDK", 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 0, "none");
+
+ create("test", "diff", "tst5.disk", "dynamic", "VMDK", 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 0, "none");
+
+ create("test", "diff", "tst6.disk", "dynamic", "VMDK", 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 0, "none");
+
+ close("test", "single", true /* fDelete */);
+ close("test", "single", true /* fDelete */);
+ close("test", "single", true /* fDelete */);
+ destroydisk("test");
+}
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes */
+ iorngcreate(10M, "manual", 1234567890);
+
+ tstIo("Testing VDI", "VDI");
+
+ iorngdestroy();
+}
+
diff --git a/src/VBox/Storage/testcase/tstVDResize.vd b/src/VBox/Storage/testcase/tstVDResize.vd
new file mode 100644
index 00000000..8b01d863
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDResize.vd
@@ -0,0 +1,79 @@
+/* $Id: tstVDResize.vd $ */
+/**
+ * Storage: Resize testing for VDI.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes. */
+ iorngcreate(10M, "manual", 1234567890);
+
+ print("Testing VDI");
+ createdisk("test", true);
+ create("test", "base", "tst.vdi", "dynamic", "VDI", 1T, false, false);
+ io("test", false, 1, "seq", 64K, 255G, 257G, 2G, 100, "none");
+ resize("test", 1331200M);
+ io("test", false, 1, "seq", 64K, 255G, 257G, 2G, 0, "none");
+ close("test", "single", true /* fDelete */);
+ destroydisk("test");
+
+ print("Testing VMDK Monolithic Flat");
+ createdisk("test-vmdk-mflat", true);
+ create("test-vmdk-mflat", "base", "test-vmdk-mflat.vmdk", "Fixed", "VMDK", 4G, false, false);
+ io("test-vmdk-mflat", false, 1, "seq", 64K, 1G, 2G, 1G, 100, "none");
+ resize("test-vmdk-mflat", 6000M);
+ io("test-vmdk-mflat", false, 1, "seq", 64K, 4G, 5G, 1G, 100, "none");
+ close("test-vmdk-mflat", "single", true /* fDelete */);
+ destroydisk("test-vmdk-mflat");
+
+ print("Testing VMDK Split Flat");
+ createdisk("test-vmdk-sflat", true);
+ create("test-vmdk-sflat", "base", "test-vmdk-sflat.vmdk", "vmdk-fixed-split", "VMDK", 4G, false, false);
+ io("test-vmdk-sflat", false, 1, "seq", 64K, 1G, 2G, 1G, 100, "none");
+ resize("test-vmdk-sflat", 6000M);
+ io("test-vmdk-sflat", false, 1, "seq", 64K, 4G, 5G, 1G, 100, "none");
+ close("test-vmdk-sflat", "single", true /* fDelete */);
+ destroydisk("test-vmdk-sflat");
+
+ print("Testing VMDK Sparse");
+ createdisk("test-vmdk-sparse", true);
+ create("test-vmdk-sparse", "base", "test-vmdk-sparse.vmdk", "Dynamic", "VMDK", 4G, false, false);
+ io("test-vmdk-sparse", false, 1, "seq", 64K, 1G, 2G, 1G, 100, "none");
+ resize("test-vmdk-sparse", 6000M);
+ io("test-vmdk-sparse", false, 1, "seq", 64K, 4G, 5G, 1G, 100, "none");
+ close("test-vmdk-sparse", "single", true /* fDelete */);
+ destroydisk("test-vmdk-sparse");
+
+ print("Testing VMDK Sparse Split");
+ createdisk("test-vmdk-sparse-split", true);
+ create("test-vmdk-sparse-split", "base", "test-vmdk-sparse-split.vmdk", "vmdk-dynamic-split", "VMDK", 4G, false, false);
+ io("test-vmdk-sparse-split", false, 1, "seq", 64K, 1G, 2G, 1G, 100, "none");
+ resize("test-vmdk-sparse-split", 6000M);
+ io("test-vmdk-sparse-split", false, 1, "seq", 64K, 4G, 5G, 1G, 100, "none");
+ close("test-vmdk-sparse-split", "single", true /* fDelete */);
+ destroydisk("test-vmdk-sparse-split");
+
+ iorngdestroy();
+}
diff --git a/src/VBox/Storage/testcase/tstVDShareable.vd b/src/VBox/Storage/testcase/tstVDShareable.vd
new file mode 100644
index 00000000..f45628f2
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDShareable.vd
@@ -0,0 +1,66 @@
+/* $Id: tstVDShareable.vd $ */
+/**
+ * Storage: Testcase for shareable disks.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes. */
+ iorngcreate(10M, "manual", 1234567890);
+
+ /* Create disk containers. */
+ createdisk("shared1", false);
+ createdisk("shared2", false);
+
+ /* Create the disk and close it. */
+ create("shared1", "base", "tstShared.vdi", "fixed", "VDI", 20M, false, false);
+ close("shared1", "all", false);
+
+ /* Open the disk with sharing enabled. */
+ open("shared1", "tstShared.vdi", "VDI", true /* fAsync */, true /* fShareable */, false, false, false, false);
+ open("shared2", "tstShared.vdi", "VDI", true /* fAsync */, true /* fShareable */, false, false, false, false);
+
+ /* Write to one disk and verify that the other disk can see the content. */
+ io("shared1", true, 32, "seq", 64K, 0, 20M, 20M, 100, "none");
+ comparedisks("shared1", "shared2");
+
+ /* Write to the second disk and verify that the first can see the content. */
+ io("shared2", true, 64, "seq", 8K, 0, 20M, 20M, 50, "none");
+ comparedisks("shared1", "shared2");
+
+ /* Close but don't delete yet. */
+ close("shared1", "all", false);
+ close("shared2", "all", false);
+
+ /* Open and delete. */
+ open("shared1", "tstShared.vdi", "VDI", false /* fAsync */, false /* fShareable */, false, false, false, false);
+ close("shared1", "single", true);
+
+ /* Cleanup */
+ destroydisk("shared1");
+ destroydisk("shared2");
+ iorngdestroy();
+}
+
diff --git a/src/VBox/Storage/testcase/tstVDSnap.cpp b/src/VBox/Storage/testcase/tstVDSnap.cpp
new file mode 100644
index 00000000..28e2099b
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDSnap.cpp
@@ -0,0 +1,482 @@
+/* $Id: tstVDSnap.cpp $ */
+/** @file
+ * Snapshot VBox HDD container test utility.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/vd.h>
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include <iprt/dir.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/initterm.h>
+#include <iprt/rand.h>
+
+/**
+ * A VD snapshot test.
+ */
+typedef struct VDSNAPTEST
+{
+ /** Backend to use */
+ const char *pcszBackend;
+ /** Base image name */
+ const char *pcszBaseImage;
+ /** Diff image ending */
+ const char *pcszDiffSuff;
+ /** Number of iterations before the test exits */
+ uint32_t cIterations;
+ /** Test pattern size */
+ size_t cbTestPattern;
+ /** Minimum number of disk segments */
+ uint32_t cDiskSegsMin;
+ /** Miaximum number of disk segments */
+ uint32_t cDiskSegsMax;
+ /** Minimum number of diffs needed before a merge
+ * operation can occur */
+ unsigned cDiffsMinBeforeMerge;
+ /** Chance to get create instead of a merge operation */
+ uint32_t uCreateDiffChance;
+ /** Chance to change a segment after a diff was created */
+ uint32_t uChangeSegChance;
+ /** Numer of allocated blocks in the base image in percent */
+ uint32_t uAllocatedBlocks;
+ /** Merge direction */
+ bool fForward;
+} VDSNAPTEST, *PVDSNAPTEST;
+
+/**
+ * Structure defining a disk segment.
+ */
+typedef struct VDDISKSEG
+{
+ /** Start offset in the disk. */
+ uint64_t off;
+ /** Size of the segment. */
+ uint64_t cbSeg;
+ /** Pointer to the start of the data in the test pattern used for the segment. */
+ uint8_t *pbData;
+ /** Pointer to the data for a diff write */
+ uint8_t *pbDataDiff;
+} VDDISKSEG, *PVDDISKSEG;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The error count. */
+unsigned g_cErrors = 0;
+/** Global RNG state. */
+RTRAND g_hRand;
+
+static DECLCALLBACK(void) tstVDError(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va)
+{
+ RT_NOREF1(pvUser);
+ g_cErrors++;
+ RTPrintf("tstVDSnap: Error %Rrc at %s:%u (%s): ", rc, RT_SRC_POS_ARGS);
+ RTPrintfV(pszFormat, va);
+ RTPrintf("\n");
+}
+
+static DECLCALLBACK(int) tstVDMessage(void *pvUser, const char *pszFormat, va_list va)
+{
+ RT_NOREF1(pvUser);
+ RTPrintf("tstVDSnap: ");
+ RTPrintfV(pszFormat, va);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns true with the given chance in percent.
+ *
+ * @returns true or false
+ * @param iPercentage The percentage of the chance to return true.
+ */
+static bool tstVDSnapIsTrue(int iPercentage)
+{
+ int uRnd = RTRandAdvU32Ex(g_hRand, 0, 100);
+
+ return (uRnd <= iPercentage); /* This should be enough for our purpose */
+}
+
+static void tstVDSnapSegmentsDice(PVDSNAPTEST pTest, PVDDISKSEG paDiskSeg, uint32_t cDiskSegments,
+ uint8_t *pbTestPattern, size_t cbTestPattern)
+{
+ for (uint32_t i = 0; i < cDiskSegments; i++)
+ {
+ /* Do we want to change the current segment? */
+ if (tstVDSnapIsTrue(pTest->uChangeSegChance))
+ paDiskSeg[i].pbDataDiff = pbTestPattern + RT_ALIGN_64(RTRandAdvU64Ex(g_hRand, 0, cbTestPattern - paDiskSeg[i].cbSeg - 512), 512);
+ }
+}
+
+static int tstVDSnapWrite(PVDISK pVD, PVDDISKSEG paDiskSegments, uint32_t cDiskSegments, uint64_t cbDisk, bool fInit)
+{
+ RT_NOREF1(cbDisk);
+ int rc = VINF_SUCCESS;
+
+ for (uint32_t i = 0; i < cDiskSegments; i++)
+ {
+ if (fInit || paDiskSegments[i].pbDataDiff)
+ {
+ size_t cbWrite = paDiskSegments[i].cbSeg;
+ uint64_t off = paDiskSegments[i].off;
+ uint8_t *pbData = fInit
+ ? paDiskSegments[i].pbData
+ : paDiskSegments[i].pbDataDiff;
+
+ if (pbData)
+ {
+ rc = VDWrite(pVD, off, pbData, cbWrite);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ }
+
+ return rc;
+}
+
+static int tstVDSnapReadVerify(PVDISK pVD, PVDDISKSEG paDiskSegments, uint32_t cDiskSegments, uint64_t cbDisk)
+{
+ RT_NOREF1(cbDisk);
+ int rc = VINF_SUCCESS;
+ uint8_t *pbBuf = (uint8_t *)RTMemAlloc(_1M);
+
+ for (uint32_t i = 0; i < cDiskSegments; i++)
+ {
+ size_t cbRead = paDiskSegments[i].cbSeg;
+ uint64_t off = paDiskSegments[i].off;
+ uint8_t *pbCmp = paDiskSegments[i].pbData;
+
+ Assert(!paDiskSegments[i].pbDataDiff);
+
+ while (cbRead)
+ {
+ size_t cbToRead = RT_MIN(cbRead, _1M);
+
+ rc = VDRead(pVD, off, pbBuf, cbToRead);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pbCmp)
+ {
+ if (memcmp(pbCmp, pbBuf, cbToRead))
+ {
+ for (unsigned iCmp = 0; iCmp < cbToRead; iCmp++)
+ {
+ if (pbCmp[iCmp] != pbBuf[iCmp])
+ {
+ RTPrintf("Unexpected data at %llu expected %#x got %#x\n", off+iCmp, pbCmp[iCmp], pbBuf[iCmp]);
+ break;
+ }
+ }
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ else
+ {
+ /* Verify that the block is 0 */
+ for (unsigned iCmp = 0; iCmp < cbToRead; iCmp++)
+ {
+ if (pbBuf[iCmp] != 0)
+ {
+ RTPrintf("Zero block contains data at %llu\n", off+iCmp);
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ }
+
+ cbRead -= cbToRead;
+ off += cbToRead;
+
+ if (pbCmp)
+ pbCmp += cbToRead;
+ }
+ }
+
+ RTMemFree(pbBuf);
+
+ return rc;
+}
+
+static int tstVDOpenCreateWriteMerge(PVDSNAPTEST pTest)
+{
+ int rc;
+ PVDISK pVD = NULL;
+ VDGEOMETRY PCHS = { 0, 0, 0 };
+ VDGEOMETRY LCHS = { 0, 0, 0 };
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACEERROR VDIfError;
+
+ /** Buffer storing the random test pattern. */
+ uint8_t *pbTestPattern = NULL;
+ /** Number of disk segments */
+ uint32_t cDiskSegments;
+ /** Array of disk segments */
+ PVDDISKSEG paDiskSeg = NULL;
+ unsigned cDiffs = 0;
+ unsigned idDiff = 0; /* Diff ID counter for the filename */
+
+ /* Delete all images from a previous run. */
+ RTFileDelete(pTest->pcszBaseImage);
+ for (unsigned i = 0; i < pTest->cIterations; i++)
+ {
+ char *pszDiffFilename = NULL;
+
+ rc = RTStrAPrintf(&pszDiffFilename, "tstVDSnapDiff%u.%s", i, pTest->pcszDiffSuff);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFileExists(pszDiffFilename))
+ RTFileDelete(pszDiffFilename);
+ RTStrFree(pszDiffFilename);
+ }
+ }
+
+ /* Create the virtual disk test data */
+ pbTestPattern = (uint8_t *)RTMemAlloc(pTest->cbTestPattern);
+
+ RTRandAdvBytes(g_hRand, pbTestPattern, pTest->cbTestPattern);
+ cDiskSegments = RTRandAdvU32Ex(g_hRand, pTest->cDiskSegsMin, pTest->cDiskSegsMax);
+
+ uint64_t cbDisk = 0;
+
+ paDiskSeg = (PVDDISKSEG)RTMemAllocZ(cDiskSegments * sizeof(VDDISKSEG));
+ if (!paDiskSeg)
+ {
+ RTPrintf("Failed to allocate memory for random disk segments\n");
+ g_cErrors++;
+ return VERR_NO_MEMORY;
+ }
+
+ for (unsigned i = 0; i < cDiskSegments; i++)
+ {
+ paDiskSeg[i].off = cbDisk;
+ paDiskSeg[i].cbSeg = RT_ALIGN_64(RTRandAdvU64Ex(g_hRand, 512, pTest->cbTestPattern), 512);
+ if (tstVDSnapIsTrue(pTest->uAllocatedBlocks))
+ paDiskSeg[i].pbData = pbTestPattern + RT_ALIGN_64(RTRandAdvU64Ex(g_hRand, 0, pTest->cbTestPattern - paDiskSeg[i].cbSeg - 512), 512);
+ else
+ paDiskSeg[i].pbData = NULL; /* Not allocated initially */
+ cbDisk += paDiskSeg[i].cbSeg;
+ }
+
+ RTPrintf("Disk size is %llu bytes\n", cbDisk);
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ if (pbTestPattern) \
+ RTMemFree(pbTestPattern); \
+ if (paDiskSeg) \
+ RTMemFree(paDiskSeg); \
+ VDDestroy(pVD); \
+ g_cErrors++; \
+ return rc; \
+ } \
+ } while (0)
+
+#define CHECK_BREAK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ g_cErrors++; \
+ break; \
+ } \
+ } while (0)
+
+ /* Create error interface. */
+ /* Create error interface. */
+ VDIfError.pfnError = tstVDError;
+ VDIfError.pfnMessage = tstVDMessage;
+
+ rc = VDInterfaceAdd(&VDIfError.Core, "tstVD_Error", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+ AssertRC(rc);
+
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDCreateBase(pVD, pTest->pcszBackend, pTest->pcszBaseImage, cbDisk,
+ VD_IMAGE_FLAGS_NONE, "Test image",
+ &PCHS, &LCHS, NULL, VD_OPEN_FLAGS_NORMAL,
+ NULL, NULL);
+ CHECK("VDCreateBase()");
+
+ bool fInit = true;
+ uint32_t cIteration = 0;
+
+ /* Do the real work now */
+ while ( RT_SUCCESS(rc)
+ && cIteration < pTest->cIterations)
+ {
+ /* Write */
+ rc = tstVDSnapWrite(pVD, paDiskSeg, cDiskSegments, cbDisk, fInit);
+ CHECK_BREAK("tstVDSnapWrite()");
+
+ fInit = false;
+
+ /* Write returned, do we want to create a new diff or merge them? */
+ bool fCreate = cDiffs < pTest->cDiffsMinBeforeMerge
+ ? true
+ : tstVDSnapIsTrue(pTest->uCreateDiffChance);
+
+ if (fCreate)
+ {
+ char *pszDiffFilename = NULL;
+
+ RTStrAPrintf(&pszDiffFilename, "tstVDSnapDiff%u.%s", idDiff, pTest->pcszDiffSuff);
+ CHECK("RTStrAPrintf()");
+ idDiff++;
+ cDiffs++;
+
+ rc = VDCreateDiff(pVD, pTest->pcszBackend, pszDiffFilename,
+ VD_IMAGE_FLAGS_NONE, "Test diff image", NULL, NULL,
+ VD_OPEN_FLAGS_NORMAL, NULL, NULL);
+ CHECK_BREAK("VDCreateDiff()");
+
+ RTStrFree(pszDiffFilename);
+ VDDumpImages(pVD);
+
+ /* Change data */
+ tstVDSnapSegmentsDice(pTest, paDiskSeg, cDiskSegments, pbTestPattern, pTest->cbTestPattern);
+ }
+ else
+ {
+ uint32_t uStartMerge = RTRandAdvU32Ex(g_hRand, 1, cDiffs - 1);
+ uint32_t uEndMerge = RTRandAdvU32Ex(g_hRand, uStartMerge + 1, cDiffs);
+ RTPrintf("Merging %u diffs from %u to %u...\n",
+ uEndMerge - uStartMerge,
+ uStartMerge,
+ uEndMerge);
+ if (pTest->fForward)
+ rc = VDMerge(pVD, uStartMerge, uEndMerge, NULL);
+ else
+ rc = VDMerge(pVD, uEndMerge, uStartMerge, NULL);
+ CHECK_BREAK("VDMerge()");
+
+ cDiffs -= uEndMerge - uStartMerge;
+
+ VDDumpImages(pVD);
+
+ /* Go through the disk segments and reset pointers. */
+ for (uint32_t i = 0; i < cDiskSegments; i++)
+ {
+ if (paDiskSeg[i].pbDataDiff)
+ {
+ paDiskSeg[i].pbData = paDiskSeg[i].pbDataDiff;
+ paDiskSeg[i].pbDataDiff = NULL;
+ }
+ }
+
+ /* Now compare the result with our test pattern */
+ rc = tstVDSnapReadVerify(pVD, paDiskSeg, cDiskSegments, cbDisk);
+ CHECK_BREAK("tstVDSnapReadVerify()");
+ }
+ cIteration++;
+ }
+
+ VDDumpImages(pVD);
+
+ VDDestroy(pVD);
+ if (paDiskSeg)
+ RTMemFree(paDiskSeg);
+ if (pbTestPattern)
+ RTMemFree(pbTestPattern);
+
+ RTFileDelete(pTest->pcszBaseImage);
+ for (unsigned i = 0; i < idDiff; i++)
+ {
+ char *pszDiffFilename = NULL;
+
+ RTStrAPrintf(&pszDiffFilename, "tstVDSnapDiff%u.%s", i, pTest->pcszDiffSuff);
+ RTFileDelete(pszDiffFilename);
+ RTStrFree(pszDiffFilename);
+ }
+#undef CHECK
+ return rc;
+}
+
+int main(int argc, char *argv[])
+{
+ RTR3InitExe(argc, &argv, 0);
+ int rc;
+ VDSNAPTEST Test;
+
+ RTPrintf("tstVDSnap: TESTING...\n");
+
+ rc = RTRandAdvCreateParkMiller(&g_hRand);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVDSnap: Creating RNG failed rc=%Rrc\n", rc);
+ return 1;
+ }
+
+ RTRandAdvSeed(g_hRand, 0x12345678);
+
+ Test.pcszBackend = "vmdk";
+ Test.pcszBaseImage = "tstVDSnapBase.vmdk";
+ Test.pcszDiffSuff = "vmdk";
+ Test.cIterations = 30;
+ Test.cbTestPattern = 10 * _1M;
+ Test.cDiskSegsMin = 10;
+ Test.cDiskSegsMax = 50;
+ Test.cDiffsMinBeforeMerge = 5;
+ Test.uCreateDiffChance = 50; /* % */
+ Test.uChangeSegChance = 50; /* % */
+ Test.uAllocatedBlocks = 50; /* 50% allocated */
+ Test.fForward = true;
+ tstVDOpenCreateWriteMerge(&Test);
+
+ /* Same test with backwards merge */
+ Test.fForward = false;
+ tstVDOpenCreateWriteMerge(&Test);
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVDSnap: unloading backends failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ /*
+ * Summary
+ */
+ if (!g_cErrors)
+ RTPrintf("tstVDSnap: SUCCESS\n");
+ else
+ RTPrintf("tstVDSnap: FAILURE - %d errors\n", g_cErrors);
+
+ RTRandAdvDestroy(g_hRand);
+
+ return !!g_cErrors;
+}
+
diff --git a/src/VBox/Storage/testcase/vbox-img.cpp b/src/VBox/Storage/testcase/vbox-img.cpp
new file mode 100644
index 00000000..6980ed5d
--- /dev/null
+++ b/src/VBox/Storage/testcase/vbox-img.cpp
@@ -0,0 +1,2186 @@
+/* $Id: vbox-img.cpp $ */
+/** @file
+ * Standalone image manipulation tool
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/vd.h>
+#include <VBox/err.h>
+#include <VBox/version.h>
+#include <iprt/initterm.h>
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/fsvfs.h>
+#include <iprt/fsisomaker.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/stream.h>
+#include <iprt/message.h>
+#include <iprt/getopt.h>
+#include <iprt/assert.h>
+#include <iprt/dvm.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const char *g_pszProgName = "";
+
+
+
+static void printUsage(PRTSTREAM pStrm)
+{
+ RTStrmPrintf(pStrm,
+ "Usage: %s\n"
+ " setuuid --filename <filename>\n"
+ " [--format VDI|VMDK|VHD|...]\n"
+ " [--uuid <uuid>]\n"
+ " [--parentuuid <uuid>]\n"
+ " [--zeroparentuuid]\n"
+ "\n"
+ " geometry --filename <filename>\n"
+ " [--format VDI|VMDK|VHD|...]\n"
+ " [--clearchs]\n"
+ " [--cylinders <number>]\n"
+ " [--heads <number>]\n"
+ " [--sectors <number>]\n"
+ "\n"
+ " convert --srcfilename <filename>\n"
+ " --dstfilename <filename>\n"
+ " [--stdin]|[--stdout]\n"
+ " [--srcformat VDI|VMDK|VHD|RAW|..]\n"
+ " [--dstformat VDI|VMDK|VHD|RAW|..]\n"
+ " [--variant Standard,Fixed,Split2G,Stream,ESX]\n"
+ "\n"
+ " info --filename <filename>\n"
+ "\n"
+ " compact --filename <filename>\n"
+ " [--filesystemaware]\n"
+ "\n"
+ " createcache --filename <filename>\n"
+ " --size <cache size>\n"
+ "\n"
+ " createbase --filename <filename>\n"
+ " --size <size in bytes>\n"
+ " [--format VDI|VMDK|VHD] (default: VDI)\n"
+ " [--variant Standard,Fixed,Split2G,Stream,ESX]\n"
+ " [--dataalignment <alignment in bytes>]\n"
+ "\n"
+ " createfloppy --filename <filename>\n"
+ " [--size <size in bytes>]\n"
+ " [--root-dir-entries <value>]\n"
+ " [--sector-size <bytes>]\n"
+ " [--heads <value>]\n"
+ " [--sectors-per-track <count>]\n"
+ " [--media-byte <byte>]\n"
+ "\n"
+ " createiso [too-many-options]\n"
+ "\n"
+ " repair --filename <filename>\n"
+ " [--dry-run]\n"
+ " [--format VDI|VMDK|VHD] (default: autodetect)\n"
+ "\n"
+ " clearcomment --filename <filename>\n"
+ "\n"
+ " resize --filename <filename>\n"
+ " --size <new size>\n",
+ g_pszProgName);
+}
+
+static void showLogo(PRTSTREAM pStrm)
+{
+ static bool s_fShown; /* show only once */
+
+ if (!s_fShown)
+ {
+ RTStrmPrintf(pStrm, VBOX_PRODUCT " Disk Utility " VBOX_VERSION_STRING "\n"
+ "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+ s_fShown = true;
+ }
+}
+
+/** command handler argument */
+struct HandlerArg
+{
+ int argc;
+ char **argv;
+};
+
+static PVDINTERFACE pVDIfs;
+
+static DECLCALLBACK(void) handleVDError(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va)
+{
+ RT_NOREF2(pvUser, rc);
+ RT_SRC_POS_NOREF();
+ RTMsgErrorV(pszFormat, va);
+}
+
+static DECLCALLBACK(int) handleVDMessage(void *pvUser, const char *pszFormat, va_list va)
+{
+ NOREF(pvUser);
+ RTPrintfV(pszFormat, va);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Print a usage synopsis and the syntax error message.
+ */
+static int errorSyntax(const char *pszFormat, ...)
+{
+ va_list args;
+ showLogo(g_pStdErr); // show logo even if suppressed
+ va_start(args, pszFormat);
+ RTStrmPrintf(g_pStdErr, "\nSyntax error: %N\n", pszFormat, &args);
+ va_end(args);
+ printUsage(g_pStdErr);
+ return 1;
+}
+
+static int errorRuntime(const char *pszFormat, ...)
+{
+ va_list args;
+
+ va_start(args, pszFormat);
+ RTMsgErrorV(pszFormat, args);
+ va_end(args);
+ return 1;
+}
+
+static int parseDiskVariant(const char *psz, unsigned *puImageFlags)
+{
+ int rc = VINF_SUCCESS;
+ unsigned uImageFlags = *puImageFlags;
+
+ 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))
+ uImageFlags = VD_IMAGE_FLAGS_NONE;
+ else if ( !RTStrNICmp(psz, "fixed", len)
+ || !RTStrNICmp(psz, "static", len))
+ uImageFlags |= VD_IMAGE_FLAGS_FIXED;
+ else if (!RTStrNICmp(psz, "Diff", len))
+ uImageFlags |= VD_IMAGE_FLAGS_DIFF;
+ else if (!RTStrNICmp(psz, "split2g", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_SPLIT_2G;
+ else if ( !RTStrNICmp(psz, "stream", len)
+ || !RTStrNICmp(psz, "streamoptimized", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED;
+ else if (!RTStrNICmp(psz, "esx", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_ESX;
+ else
+ rc = VERR_PARSE_ERROR;
+ }
+ if (pszComma)
+ psz += len + 1;
+ else
+ psz += len;
+ }
+
+ if (RT_SUCCESS(rc))
+ *puImageFlags = uImageFlags;
+ return rc;
+}
+
+
+static int handleSetUUID(HandlerArg *a)
+{
+ const char *pszFilename = NULL;
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ RTUUID imageUuid;
+ RTUUID parentUuid;
+ bool fSetImageUuid = false;
+ bool fSetParentUuid = false;
+ RTUuidClear(&imageUuid);
+ RTUuidClear(&parentUuid);
+ int rc;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--format", 'o', RTGETOPT_REQ_STRING },
+ { "--uuid", 'u', RTGETOPT_REQ_UUID },
+ { "--parentuuid", 'p', RTGETOPT_REQ_UUID },
+ { "--zeroparentuuid", 'P', RTGETOPT_REQ_NOTHING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+ case 'o': // --format
+ pszFormat = RTStrDup(ValueUnion.psz);
+ break;
+ case 'u': // --uuid
+ imageUuid = ValueUnion.Uuid;
+ fSetImageUuid = true;
+ break;
+ case 'p': // --parentuuid
+ parentUuid = ValueUnion.Uuid;
+ fSetParentUuid = true;
+ break;
+ case 'P': // --zeroparentuuid
+ RTUuidClear(&parentUuid);
+ fSetParentUuid = true;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* Check for consistency of optional parameters. */
+ if (fSetImageUuid && RTUuidIsNull(&imageUuid))
+ return errorSyntax("Invalid parameter to --uuid option\n");
+
+ /* Autodetect image format. */
+ if (!pszFormat)
+ {
+ /* Don't pass error interface, as that would triggers error messages
+ * because some backends fail to open the image. */
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Format autodetect failed: %Rrc\n", rc);
+ }
+
+ PVDISK pVD = NULL;
+ rc = VDCreate(pVDIfs, enmType, &pVD);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot create the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open in info mode to be able to open diff images without their parent. */
+ rc = VDOpen(pVD, pszFormat, pszFilename, VD_OPEN_FLAGS_INFO, NULL);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot open the virtual disk image \"%s\": %Rrf (%Rrc)\n",
+ pszFilename, rc, rc);
+
+ RTUUID oldImageUuid;
+ rc = VDGetUuid(pVD, VD_LAST_IMAGE, &oldImageUuid);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot get UUID of virtual disk image \"%s\": %Rrc\n",
+ pszFilename, rc);
+
+ RTPrintf("Old image UUID: %RTuuid\n", &oldImageUuid);
+
+ RTUUID oldParentUuid;
+ rc = VDGetParentUuid(pVD, VD_LAST_IMAGE, &oldParentUuid);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot get parent UUID of virtual disk image \"%s\": %Rrc\n",
+ pszFilename, rc);
+
+ RTPrintf("Old parent UUID: %RTuuid\n", &oldParentUuid);
+
+ if (fSetImageUuid)
+ {
+ RTPrintf("New image UUID: %RTuuid\n", &imageUuid);
+ rc = VDSetUuid(pVD, VD_LAST_IMAGE, &imageUuid);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot set UUID of virtual disk image \"%s\": %Rrf (%Rrc)\n",
+ pszFilename, rc, rc);
+ }
+
+ if (fSetParentUuid)
+ {
+ RTPrintf("New parent UUID: %RTuuid\n", &parentUuid);
+ rc = VDSetParentUuid(pVD, VD_LAST_IMAGE, &parentUuid);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot set parent UUID of virtual disk image \"%s\": %Rrf (%Rrc)\n",
+ pszFilename, rc, rc);
+ }
+
+ VDDestroy(pVD);
+
+ if (pszFormat)
+ {
+ RTStrFree(pszFormat);
+ pszFormat = NULL;
+ }
+
+ return 0;
+}
+
+
+static int handleGeometry(HandlerArg *a)
+{
+ const char *pszFilename = NULL;
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ uint16_t cCylinders = 0;
+ uint8_t cHeads = 0;
+ uint8_t cSectors = 0;
+ bool fCylinders = false;
+ bool fHeads = false;
+ bool fSectors = false;
+ int rc;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--format", 'o', RTGETOPT_REQ_STRING },
+ { "--clearchs", 'C', RTGETOPT_REQ_NOTHING },
+ { "--cylinders", 'c', RTGETOPT_REQ_UINT16 },
+ { "--heads", 'e', RTGETOPT_REQ_UINT8 },
+ { "--sectors", 's', RTGETOPT_REQ_UINT8 }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+ case 'o': // --format
+ pszFormat = RTStrDup(ValueUnion.psz);
+ break;
+ case 'C': // --clearchs
+ cCylinders = 0;
+ cHeads = 0;
+ cSectors = 0;
+ fCylinders = true;
+ fHeads = true;
+ fSectors = true;
+ break;
+ case 'c': // --cylinders
+ cCylinders = ValueUnion.u16;
+ fCylinders = true;
+ break;
+ case 'e': // --heads
+ cHeads = ValueUnion.u8;
+ fHeads = true;
+ break;
+ case 's': // --sectors
+ cSectors = ValueUnion.u8;
+ fSectors = true;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* Autodetect image format. */
+ if (!pszFormat)
+ {
+ /* Don't pass error interface, as that would triggers error messages
+ * because some backends fail to open the image. */
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Format autodetect failed: %Rrc\n", rc);
+ }
+
+ PVDISK pVD = NULL;
+ rc = VDCreate(pVDIfs, enmType, &pVD);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot create the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open in info mode to be able to open diff images without their parent. */
+ rc = VDOpen(pVD, pszFormat, pszFilename, VD_OPEN_FLAGS_INFO, NULL);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot open the virtual disk image \"%s\": %Rrf (%Rrc)\n",
+ pszFilename, rc, rc);
+
+ VDGEOMETRY oldLCHSGeometry;
+ rc = VDGetLCHSGeometry(pVD, VD_LAST_IMAGE, &oldLCHSGeometry);
+ if (rc == VERR_VD_GEOMETRY_NOT_SET)
+ {
+ memset(&oldLCHSGeometry, 0, sizeof(oldLCHSGeometry));
+ rc = VINF_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot get LCHS geometry of virtual disk image \"%s\": %Rrc\n",
+ pszFilename, rc);
+
+ VDGEOMETRY newLCHSGeometry = oldLCHSGeometry;
+ if (fCylinders)
+ newLCHSGeometry.cCylinders = cCylinders;
+ if (fHeads)
+ newLCHSGeometry.cHeads = cHeads;
+ if (fSectors)
+ newLCHSGeometry.cSectors = cSectors;
+
+ if (fCylinders || fHeads || fSectors)
+ {
+ RTPrintf("Old image LCHS: %u/%u/%u\n", oldLCHSGeometry.cCylinders, oldLCHSGeometry.cHeads, oldLCHSGeometry.cSectors);
+ RTPrintf("New image LCHS: %u/%u/%u\n", newLCHSGeometry.cCylinders, newLCHSGeometry.cHeads, newLCHSGeometry.cSectors);
+
+ rc = VDSetLCHSGeometry(pVD, VD_LAST_IMAGE, &newLCHSGeometry);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot set LCHS geometry of virtual disk image \"%s\": %Rrf (%Rrc)\n",
+ pszFilename, rc, rc);
+ }
+ else
+ RTPrintf("Current image LCHS: %u/%u/%u\n", oldLCHSGeometry.cCylinders, oldLCHSGeometry.cHeads, oldLCHSGeometry.cSectors);
+
+
+ VDDestroy(pVD);
+
+ if (pszFormat)
+ {
+ RTStrFree(pszFormat);
+ pszFormat = NULL;
+ }
+
+ return 0;
+}
+
+
+typedef struct FILEIOSTATE
+{
+ RTFILE file;
+ /** Size of file. */
+ uint64_t cb;
+ /** Offset in the file. */
+ uint64_t off;
+ /** Offset where the buffer contents start. UINT64_MAX=buffer invalid. */
+ uint64_t offBuffer;
+ /** Size of valid data in the buffer. */
+ uint32_t cbBuffer;
+ /** Buffer for efficient I/O */
+ uint8_t abBuffer[16 *_1M];
+} FILEIOSTATE, *PFILEIOSTATE;
+
+static DECLCALLBACK(int) convInOpen(void *pvUser, const char *pszLocation, uint32_t fOpen, PFNVDCOMPLETED pfnCompleted,
+ void **ppStorage)
+{
+ RT_NOREF2(pvUser, pszLocation);
+
+ /* Validate input. */
+ AssertPtrReturn(ppStorage, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnCompleted, VERR_INVALID_PARAMETER);
+ AssertReturn((fOpen & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ, VERR_INVALID_PARAMETER);
+ RTFILE file;
+ int rc = RTFileFromNative(&file, RTFILE_NATIVE_STDIN);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* No need to clear the buffer, the data will be read from disk. */
+ PFILEIOSTATE pFS = (PFILEIOSTATE)RTMemAlloc(sizeof(FILEIOSTATE));
+ if (!pFS)
+ return VERR_NO_MEMORY;
+
+ pFS->file = file;
+ pFS->cb = 0;
+ pFS->off = 0;
+ pFS->offBuffer = UINT64_MAX;
+ pFS->cbBuffer = 0;
+
+ *ppStorage = pFS;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convInClose(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+
+ RTMemFree(pFS);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convInDelete(void *pvUser, const char *pcszFilename)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInMove(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
+{
+ NOREF(pvUser);
+ NOREF(pcszSrc);
+ NOREF(pcszDst);
+ NOREF(fMove);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInGetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pcbFreeSpace, VERR_INVALID_POINTER);
+ *pcbFreeSpace = 0;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convInGetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pModificationTime, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInGetSize(void *pvUser, void *pStorage, uint64_t *pcbSize)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInSetSize(void *pvUser, void *pStorage, uint64_t cbSize)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(cbSize);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInRead(void *pvUser, void *pStorage, uint64_t uOffset,
+ void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+ AssertReturn(uOffset >= pFS->off, VERR_INVALID_PARAMETER);
+ int rc;
+
+ /* Fill buffer if it is empty. */
+ if (pFS->offBuffer == UINT64_MAX)
+ {
+ /* Repeat reading until buffer is full or EOF. */
+ size_t cbRead;
+ size_t cbSumRead = 0;
+ uint8_t *pbTmp = (uint8_t *)&pFS->abBuffer[0];
+ size_t cbTmp = sizeof(pFS->abBuffer);
+ do
+ {
+ rc = RTFileRead(pFS->file, pbTmp, cbTmp, &cbRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ pbTmp += cbRead;
+ cbTmp -= cbRead;
+ cbSumRead += cbRead;
+ } while (cbTmp && cbRead);
+
+ pFS->offBuffer = 0;
+ pFS->cbBuffer = (uint32_t)cbSumRead;
+ if (!cbSumRead && !pcbRead) /* Caller can't handle partial reads. */
+ return VERR_EOF;
+ }
+
+ /* Read several blocks and assemble the result if necessary */
+ size_t cbTotalRead = 0;
+ do
+ {
+ /* Skip over areas no one wants to read. */
+ while (uOffset > pFS->offBuffer + pFS->cbBuffer - 1)
+ {
+ if (pFS->cbBuffer < sizeof(pFS->abBuffer))
+ {
+ if (pcbRead)
+ *pcbRead = cbTotalRead;
+ return VERR_EOF;
+ }
+
+ /* Repeat reading until buffer is full or EOF. */
+ size_t cbRead;
+ size_t cbSumRead = 0;
+ uint8_t *pbTmp = (uint8_t *)&pFS->abBuffer[0];
+ size_t cbTmp = sizeof(pFS->abBuffer);
+ do
+ {
+ rc = RTFileRead(pFS->file, pbTmp, cbTmp, &cbRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ pbTmp += cbRead;
+ cbTmp -= cbRead;
+ cbSumRead += cbRead;
+ } while (cbTmp && cbRead);
+
+ pFS->offBuffer += pFS->cbBuffer;
+ pFS->cbBuffer = (uint32_t)cbSumRead;
+ }
+
+ uint32_t cbThisRead = (uint32_t)RT_MIN(cbBuffer,
+ pFS->cbBuffer - uOffset % sizeof(pFS->abBuffer));
+ memcpy(pvBuffer, &pFS->abBuffer[uOffset % sizeof(pFS->abBuffer)],
+ cbThisRead);
+ uOffset += cbThisRead;
+ pvBuffer = (uint8_t *)pvBuffer + cbThisRead;
+ cbBuffer -= cbThisRead;
+ cbTotalRead += cbThisRead;
+ if (!cbTotalRead && !pcbRead) /* Caller can't handle partial reads. */
+ return VERR_EOF;
+ } while (cbBuffer > 0);
+
+ if (pcbRead)
+ *pcbRead = cbTotalRead;
+
+ pFS->off = uOffset;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convInWrite(void *pvUser, void *pStorage, uint64_t uOffset, const void *pvBuffer, size_t cbBuffer,
+ size_t *pcbWritten)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(uOffset);
+ NOREF(cbBuffer);
+ NOREF(pcbWritten);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInFlush(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convStdOutOpen(void *pvUser, const char *pszLocation, uint32_t fOpen, PFNVDCOMPLETED pfnCompleted,
+ void **ppStorage)
+{
+ RT_NOREF2(pvUser, pszLocation);
+
+ /* Validate input. */
+ AssertPtrReturn(ppStorage, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnCompleted, VERR_INVALID_PARAMETER);
+ AssertReturn((fOpen & RTFILE_O_ACCESS_MASK) == RTFILE_O_WRITE, VERR_INVALID_PARAMETER);
+ RTFILE file;
+ int rc = RTFileFromNative(&file, RTFILE_NATIVE_STDOUT);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Must clear buffer, so that skipped over data is initialized properly. */
+ PFILEIOSTATE pFS = (PFILEIOSTATE)RTMemAllocZ(sizeof(FILEIOSTATE));
+ if (!pFS)
+ return VERR_NO_MEMORY;
+
+ pFS->file = file;
+ pFS->cb = 0;
+ pFS->off = 0;
+ pFS->offBuffer = 0;
+ pFS->cbBuffer = sizeof(FILEIOSTATE);
+
+ *ppStorage = pFS;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convStdOutClose(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+ int rc = VINF_SUCCESS;
+
+ /* Flush any remaining buffer contents. */
+ if (pFS->cbBuffer)
+ rc = RTFileWrite(pFS->file, &pFS->abBuffer[0], pFS->cbBuffer, NULL);
+ if ( RT_SUCCESS(rc)
+ && pFS->cb > pFS->off)
+ {
+ /* Write zeros if the set file size is not met. */
+ uint64_t cbLeft = pFS->cb - pFS->off;
+ RT_ZERO(pFS->abBuffer);
+
+ while (cbLeft)
+ {
+ size_t cbThisWrite = RT_MIN(cbLeft, sizeof(pFS->abBuffer));
+ rc = RTFileWrite(pFS->file, &pFS->abBuffer[0],
+ cbThisWrite, NULL);
+ cbLeft -= cbThisWrite;
+ }
+ }
+
+ RTMemFree(pFS);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) convStdOutDelete(void *pvUser, const char *pcszFilename)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutMove(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
+{
+ NOREF(pvUser);
+ NOREF(pcszSrc);
+ NOREF(pcszDst);
+ NOREF(fMove);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutGetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pcbFreeSpace, VERR_INVALID_POINTER);
+ *pcbFreeSpace = INT64_MAX;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convStdOutGetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pModificationTime, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutGetSize(void *pvUser, void *pStorage, uint64_t *pcbSize)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutSetSize(void *pvUser, void *pStorage, uint64_t cbSize)
+{
+ RT_NOREF2(pvUser, cbSize);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutRead(void *pvUser, void *pStorage, uint64_t uOffset, void *pvBuffer, size_t cbBuffer,
+ size_t *pcbRead)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(uOffset);
+ NOREF(cbBuffer);
+ NOREF(pcbRead);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutWrite(void *pvUser, void *pStorage, uint64_t uOffset, const void *pvBuffer, size_t cbBuffer,
+ size_t *pcbWritten)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+ AssertReturn(uOffset >= pFS->off, VERR_INVALID_PARAMETER);
+ int rc;
+
+ /* Write the data to the buffer, flushing as required. */
+ size_t cbTotalWritten = 0;
+ do
+ {
+ /* Flush the buffer if we need a new one. */
+ while (uOffset > pFS->offBuffer + sizeof(pFS->abBuffer) - 1)
+ {
+ rc = RTFileWrite(pFS->file, &pFS->abBuffer[0],
+ sizeof(pFS->abBuffer), NULL);
+ RT_ZERO(pFS->abBuffer);
+ pFS->offBuffer += sizeof(pFS->abBuffer);
+ pFS->cbBuffer = 0;
+ }
+
+ uint32_t cbThisWrite = (uint32_t)RT_MIN(cbBuffer,
+ sizeof(pFS->abBuffer) - uOffset % sizeof(pFS->abBuffer));
+ memcpy(&pFS->abBuffer[uOffset % sizeof(pFS->abBuffer)], pvBuffer,
+ cbThisWrite);
+ uOffset += cbThisWrite;
+ pvBuffer = (uint8_t *)pvBuffer + cbThisWrite;
+ cbBuffer -= cbThisWrite;
+ cbTotalWritten += cbThisWrite;
+ } while (cbBuffer > 0);
+
+ if (pcbWritten)
+ *pcbWritten = cbTotalWritten;
+
+ pFS->cbBuffer = uOffset % sizeof(pFS->abBuffer);
+ if (!pFS->cbBuffer)
+ pFS->cbBuffer = sizeof(pFS->abBuffer);
+ pFS->off = uOffset;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convStdOutFlush(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convFileOutOpen(void *pvUser, const char *pszLocation, uint32_t fOpen, PFNVDCOMPLETED pfnCompleted,
+ void **ppStorage)
+{
+ RT_NOREF1(pvUser);
+
+ /* Validate input. */
+ AssertPtrReturn(ppStorage, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnCompleted, VERR_INVALID_PARAMETER);
+ AssertReturn((fOpen & RTFILE_O_ACCESS_MASK) == RTFILE_O_WRITE, VERR_INVALID_PARAMETER);
+ RTFILE file;
+ int rc = RTFileOpen(&file, pszLocation, fOpen);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Must clear buffer, so that skipped over data is initialized properly. */
+ PFILEIOSTATE pFS = (PFILEIOSTATE)RTMemAllocZ(sizeof(FILEIOSTATE));
+ if (!pFS)
+ return VERR_NO_MEMORY;
+
+ pFS->file = file;
+ pFS->cb = 0;
+ pFS->off = 0;
+ pFS->offBuffer = 0;
+ pFS->cbBuffer = sizeof(FILEIOSTATE);
+
+ *ppStorage = pFS;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convFileOutClose(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+ int rc = VINF_SUCCESS;
+
+ /* Flush any remaining buffer contents. */
+ if (pFS->cbBuffer)
+ rc = RTFileWriteAt(pFS->file, pFS->offBuffer, &pFS->abBuffer[0], pFS->cbBuffer, NULL);
+ RTFileClose(pFS->file);
+
+ RTMemFree(pFS);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) convFileOutDelete(void *pvUser, const char *pcszFilename)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convFileOutMove(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
+{
+ NOREF(pvUser);
+ NOREF(pcszSrc);
+ NOREF(pcszDst);
+ NOREF(fMove);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convFileOutGetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pcbFreeSpace, VERR_INVALID_POINTER);
+ *pcbFreeSpace = INT64_MAX;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convFileOutGetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pModificationTime, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convFileOutGetSize(void *pvUser, void *pStorage, uint64_t *pcbSize)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convFileOutSetSize(void *pvUser, void *pStorage, uint64_t cbSize)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+
+ int rc = RTFileSetSize(pFS->file, cbSize);
+ if (RT_SUCCESS(rc))
+ pFS->cb = cbSize;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convFileOutRead(void *pvUser, void *pStorage, uint64_t uOffset, void *pvBuffer, size_t cbBuffer,
+ size_t *pcbRead)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(uOffset);
+ NOREF(cbBuffer);
+ NOREF(pcbRead);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convFileOutWrite(void *pvUser, void *pStorage, uint64_t uOffset, const void *pvBuffer, size_t cbBuffer,
+ size_t *pcbWritten)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+ AssertReturn(uOffset >= pFS->off, VERR_INVALID_PARAMETER);
+ int rc;
+
+ /* Write the data to the buffer, flushing as required. */
+ size_t cbTotalWritten = 0;
+ do
+ {
+ /* Flush the buffer if we need a new one. */
+ while (uOffset > pFS->offBuffer + sizeof(pFS->abBuffer) - 1)
+ {
+ if (!ASMMemIsZero(pFS->abBuffer, sizeof(pFS->abBuffer)))
+ rc = RTFileWriteAt(pFS->file, pFS->offBuffer,
+ &pFS->abBuffer[0],
+ sizeof(pFS->abBuffer), NULL);
+ RT_ZERO(pFS->abBuffer);
+ pFS->offBuffer += sizeof(pFS->abBuffer);
+ pFS->cbBuffer = 0;
+ }
+
+ uint32_t cbThisWrite = (uint32_t)RT_MIN(cbBuffer,
+ sizeof(pFS->abBuffer) - uOffset % sizeof(pFS->abBuffer));
+ memcpy(&pFS->abBuffer[uOffset % sizeof(pFS->abBuffer)], pvBuffer,
+ cbThisWrite);
+ uOffset += cbThisWrite;
+ pvBuffer = (uint8_t *)pvBuffer + cbThisWrite;
+ cbBuffer -= cbThisWrite;
+ cbTotalWritten += cbThisWrite;
+ } while (cbBuffer > 0);
+
+ if (pcbWritten)
+ *pcbWritten = cbTotalWritten;
+
+ pFS->cbBuffer = uOffset % sizeof(pFS->abBuffer);
+ if (!pFS->cbBuffer)
+ pFS->cbBuffer = sizeof(pFS->abBuffer);
+ pFS->off = uOffset;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convFileOutFlush(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ return VINF_SUCCESS;
+}
+
+static int handleConvert(HandlerArg *a)
+{
+ const char *pszSrcFilename = NULL;
+ const char *pszDstFilename = NULL;
+ bool fStdIn = false;
+ bool fStdOut = false;
+ bool fCreateSparse = false;
+ const char *pszSrcFormat = NULL;
+ VDTYPE enmSrcType = VDTYPE_HDD;
+ const char *pszDstFormat = NULL;
+ const char *pszVariant = NULL;
+ PVDISK pSrcDisk = NULL;
+ PVDISK pDstDisk = NULL;
+ unsigned uImageFlags = VD_IMAGE_FLAGS_NONE;
+ PVDINTERFACE pIfsImageInput = NULL;
+ PVDINTERFACE pIfsImageOutput = NULL;
+ VDINTERFACEIO IfsInputIO;
+ VDINTERFACEIO IfsOutputIO;
+ int rc = VINF_SUCCESS;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--srcfilename", 'i', RTGETOPT_REQ_STRING },
+ { "--dstfilename", 'o', RTGETOPT_REQ_STRING },
+ { "--stdin", 'p', RTGETOPT_REQ_NOTHING },
+ { "--stdout", 'P', RTGETOPT_REQ_NOTHING },
+ { "--srcformat", 's', RTGETOPT_REQ_STRING },
+ { "--dstformat", 'd', RTGETOPT_REQ_STRING },
+ { "--variant", 'v', RTGETOPT_REQ_STRING },
+ { "--create-sparse", 'c', RTGETOPT_REQ_NOTHING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'i': // --srcfilename
+ pszSrcFilename = ValueUnion.psz;
+ break;
+ case 'o': // --dstfilename
+ pszDstFilename = ValueUnion.psz;
+ break;
+ case 'p': // --stdin
+ fStdIn = true;
+ break;
+ case 'P': // --stdout
+ fStdOut = true;
+ break;
+ case 's': // --srcformat
+ pszSrcFormat = ValueUnion.psz;
+ break;
+ case 'd': // --dstformat
+ pszDstFormat = ValueUnion.psz;
+ break;
+ case 'v': // --variant
+ pszVariant = ValueUnion.psz;
+ break;
+ case 'c': // --create-sparse
+ fCreateSparse = true;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters and handle dummies/defaults. */
+ if (fStdIn && !pszSrcFormat)
+ return errorSyntax("Mandatory --srcformat option missing\n");
+ if (!pszDstFormat)
+ pszDstFormat = "VDI";
+ if (fStdIn && !pszSrcFilename)
+ {
+ /* Complete dummy, will be just passed to various calls to fulfill
+ * the "must be non-NULL" requirement, and is completely ignored
+ * otherwise. It shown in the stderr message below. */
+ pszSrcFilename = "stdin";
+ }
+ if (fStdOut && !pszDstFilename)
+ {
+ /* Will be stored in the destination image if it is a streamOptimized
+ * VMDK, but it isn't really relevant - use it for "branding". */
+ if (!RTStrICmp(pszDstFormat, "VMDK"))
+ pszDstFilename = "VirtualBoxStream.vmdk";
+ else
+ pszDstFilename = "stdout";
+ }
+ if (!pszSrcFilename)
+ return errorSyntax("Mandatory --srcfilename option missing\n");
+ if (!pszDstFilename)
+ return errorSyntax("Mandatory --dstfilename option missing\n");
+
+ if (fStdIn)
+ {
+ IfsInputIO.pfnOpen = convInOpen;
+ IfsInputIO.pfnClose = convInClose;
+ IfsInputIO.pfnDelete = convInDelete;
+ IfsInputIO.pfnMove = convInMove;
+ IfsInputIO.pfnGetFreeSpace = convInGetFreeSpace;
+ IfsInputIO.pfnGetModificationTime = convInGetModificationTime;
+ IfsInputIO.pfnGetSize = convInGetSize;
+ IfsInputIO.pfnSetSize = convInSetSize;
+ IfsInputIO.pfnReadSync = convInRead;
+ IfsInputIO.pfnWriteSync = convInWrite;
+ IfsInputIO.pfnFlushSync = convInFlush;
+ VDInterfaceAdd(&IfsInputIO.Core, "stdin", VDINTERFACETYPE_IO,
+ NULL, sizeof(VDINTERFACEIO), &pIfsImageInput);
+ }
+ if (fStdOut)
+ {
+ IfsOutputIO.pfnOpen = convStdOutOpen;
+ IfsOutputIO.pfnClose = convStdOutClose;
+ IfsOutputIO.pfnDelete = convStdOutDelete;
+ IfsOutputIO.pfnMove = convStdOutMove;
+ IfsOutputIO.pfnGetFreeSpace = convStdOutGetFreeSpace;
+ IfsOutputIO.pfnGetModificationTime = convStdOutGetModificationTime;
+ IfsOutputIO.pfnGetSize = convStdOutGetSize;
+ IfsOutputIO.pfnSetSize = convStdOutSetSize;
+ IfsOutputIO.pfnReadSync = convStdOutRead;
+ IfsOutputIO.pfnWriteSync = convStdOutWrite;
+ IfsOutputIO.pfnFlushSync = convStdOutFlush;
+ VDInterfaceAdd(&IfsOutputIO.Core, "stdout", VDINTERFACETYPE_IO,
+ NULL, sizeof(VDINTERFACEIO), &pIfsImageOutput);
+ }
+ else if (fCreateSparse)
+ {
+ IfsOutputIO.pfnOpen = convFileOutOpen;
+ IfsOutputIO.pfnClose = convFileOutClose;
+ IfsOutputIO.pfnDelete = convFileOutDelete;
+ IfsOutputIO.pfnMove = convFileOutMove;
+ IfsOutputIO.pfnGetFreeSpace = convFileOutGetFreeSpace;
+ IfsOutputIO.pfnGetModificationTime = convFileOutGetModificationTime;
+ IfsOutputIO.pfnGetSize = convFileOutGetSize;
+ IfsOutputIO.pfnSetSize = convFileOutSetSize;
+ IfsOutputIO.pfnReadSync = convFileOutRead;
+ IfsOutputIO.pfnWriteSync = convFileOutWrite;
+ IfsOutputIO.pfnFlushSync = convFileOutFlush;
+ VDInterfaceAdd(&IfsOutputIO.Core, "fileout", VDINTERFACETYPE_IO,
+ NULL, sizeof(VDINTERFACEIO), &pIfsImageOutput);
+ }
+
+ /* check the variant parameter */
+ if (pszVariant)
+ {
+ char *psz = (char*)pszVariant;
+ 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(pszVariant, "standard", len))
+ uImageFlags |= VD_IMAGE_FLAGS_NONE;
+ else if (!RTStrNICmp(pszVariant, "fixed", len))
+ uImageFlags |= VD_IMAGE_FLAGS_FIXED;
+ else if (!RTStrNICmp(pszVariant, "split2g", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_SPLIT_2G;
+ else if (!RTStrNICmp(pszVariant, "stream", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED;
+ else if (!RTStrNICmp(pszVariant, "esx", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_ESX;
+ else
+ return errorSyntax("Invalid --variant option\n");
+ }
+ if (pszComma)
+ psz += len + 1;
+ else
+ psz += len;
+ }
+ }
+
+ do
+ {
+ /* try to determine input format if not specified */
+ if (!pszSrcFormat)
+ {
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ rc = VDGetFormat(NULL, NULL, pszSrcFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ {
+ errorSyntax("No file format specified, please specify format: %Rrc\n", rc);
+ break;
+ }
+ pszSrcFormat = pszFormat;
+ enmSrcType = enmType;
+ }
+
+ rc = VDCreate(pVDIfs, enmSrcType, &pSrcDisk);
+ if (RT_FAILURE(rc))
+ {
+ errorRuntime("Error while creating source disk container: %Rrf (%Rrc)\n", rc, rc);
+ break;
+ }
+
+ rc = VDOpen(pSrcDisk, pszSrcFormat, pszSrcFilename,
+ VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_SEQUENTIAL,
+ pIfsImageInput);
+ if (RT_FAILURE(rc))
+ {
+ errorRuntime("Error while opening source image: %Rrf (%Rrc)\n", rc, rc);
+ break;
+ }
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pDstDisk);
+ if (RT_FAILURE(rc))
+ {
+ errorRuntime("Error while creating the destination disk container: %Rrf (%Rrc)\n", rc, rc);
+ break;
+ }
+
+ uint64_t cbSize = VDGetSize(pSrcDisk, VD_LAST_IMAGE);
+ RTStrmPrintf(g_pStdErr, "Converting image \"%s\" with size %RU64 bytes (%RU64MB)...\n", pszSrcFilename, cbSize, (cbSize + _1M - 1) / _1M);
+
+ /* Create the output image */
+ rc = VDCopy(pSrcDisk, VD_LAST_IMAGE, pDstDisk, pszDstFormat,
+ pszDstFilename, false, 0, uImageFlags, NULL,
+ VD_OPEN_FLAGS_NORMAL | VD_OPEN_FLAGS_SEQUENTIAL, NULL,
+ pIfsImageOutput, NULL);
+ if (RT_FAILURE(rc))
+ {
+ errorRuntime("Error while copying the image: %Rrf (%Rrc)\n", rc, rc);
+ break;
+ }
+
+ }
+ while (0);
+
+ if (pDstDisk)
+ VDDestroy(pDstDisk);
+ if (pSrcDisk)
+ VDDestroy(pSrcDisk);
+
+ return RT_SUCCESS(rc) ? 0 : 1;
+}
+
+
+static int handleInfo(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+ const char *pszFilename = NULL;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* just try it */
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Format autodetect failed: %Rrc\n", rc);
+
+ rc = VDCreate(pVDIfs, enmType, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open the image */
+ rc = VDOpen(pDisk, pszFormat, pszFilename, VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_READONLY, NULL);
+ RTStrFree(pszFormat);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while opening the image: %Rrf (%Rrc)\n", rc, rc);
+
+ VDDumpImages(pDisk);
+
+ VDDestroy(pDisk);
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vboximgQueryBlockStatus(void *pvUser, uint64_t off,
+ uint64_t cb, bool *pfAllocated)
+{
+ RTVFS hVfs = (RTVFS)pvUser;
+ return RTVfsQueryRangeState(hVfs, off, cb, pfAllocated);
+}
+
+
+static DECLCALLBACK(int) vboximgQueryRangeUse(void *pvUser, uint64_t off, uint64_t cb,
+ bool *pfUsed)
+{
+ RTDVM hVolMgr = (RTDVM)pvUser;
+ return RTDvmMapQueryBlockStatus(hVolMgr, off, cb, pfUsed);
+}
+
+
+typedef struct VBOXIMGVFS
+{
+ /** Pointer to the next VFS handle. */
+ struct VBOXIMGVFS *pNext;
+ /** VFS handle. */
+ RTVFS hVfs;
+} VBOXIMGVFS, *PVBOXIMGVFS;
+
+static int handleCompact(HandlerArg *a)
+{
+ PVDISK pDisk = NULL;
+ VDINTERFACEQUERYRANGEUSE VDIfQueryRangeUse;
+ PVDINTERFACE pIfsCompact = NULL;
+ RTDVM hDvm = NIL_RTDVM;
+ PVBOXIMGVFS pVBoxImgVfsHead = NULL;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--filesystemaware", 'a', RTGETOPT_REQ_NOTHING },
+ { "--file-system-aware", 'a', RTGETOPT_REQ_NOTHING },
+ };
+
+ const char *pszFilename = NULL;
+ bool fFilesystemAware = false;
+ bool fVerbose = true;
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ case 'a':
+ fFilesystemAware = true;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* just try it */
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ int rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Format autodetect failed: %Rrc\n", rc);
+
+ rc = VDCreate(pVDIfs, enmType, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open the image */
+ rc = VDOpen(pDisk, pszFormat, pszFilename, VD_OPEN_FLAGS_NORMAL, NULL);
+ RTStrFree(pszFormat);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while opening the image: %Rrf (%Rrc)\n", rc, rc);
+
+ /*
+ * If --file-system-aware, we first ask the disk volume manager (DVM) to
+ * find the volumes on the disk.
+ */
+ if ( RT_SUCCESS(rc)
+ && fFilesystemAware)
+ {
+ RTVFSFILE hVfsDisk;
+ rc = VDCreateVfsFileFromDisk(pDisk, 0 /*fFlags*/, &hVfsDisk);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDvmCreate(&hDvm, hVfsDisk, 512 /*cbSector*/, 0 /*fFlags*/);
+ RTVfsFileRelease(hVfsDisk);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDvmMapOpen(hDvm);
+ if ( RT_SUCCESS(rc)
+ && RTDvmMapGetValidVolumes(hDvm) > 0)
+ {
+ /*
+ * Enumerate the volumes: Try finding a file system interpreter and
+ * set the block query status callback to work with the FS.
+ */
+ uint32_t iVol = 0;
+ RTDVMVOLUME hVol;
+ rc = RTDvmMapQueryFirstVolume(hDvm, &hVol);
+ AssertRC(rc);
+
+ while (RT_SUCCESS(rc))
+ {
+ if (fVerbose)
+ {
+ char *pszVolName;
+ rc = RTDvmVolumeQueryName(hVol, &pszVolName);
+ if (RT_FAILURE(rc))
+ pszVolName = NULL;
+ RTMsgInfo("Vol%u: %Rhcb %s%s%s\n", iVol, RTDvmVolumeGetSize(hVol),
+ RTDvmVolumeTypeGetDescr(RTDvmVolumeGetType(hVol)),
+ pszVolName ? " " : "", pszVolName ? pszVolName : "");
+ RTStrFree(pszVolName);
+ }
+
+ RTVFSFILE hVfsFile;
+ rc = RTDvmVolumeCreateVfsFile(hVol, RTFILE_O_READWRITE, &hVfsFile);
+ if (RT_FAILURE(rc))
+ {
+ errorRuntime("RTDvmVolumeCreateVfsFile failed: %Rrc\n");
+ break;
+ }
+
+ /* Try to detect the filesystem in this volume. */
+ RTERRINFOSTATIC ErrInfo;
+ RTVFS hVfs;
+ rc = RTVfsMountVol(hVfsFile, RTVFSMNT_F_READ_ONLY | RTVFSMNT_F_FOR_RANGE_IN_USE, &hVfs,
+ RTErrInfoInitStatic(&ErrInfo));
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXIMGVFS pVBoxImgVfs = (PVBOXIMGVFS)RTMemAllocZ(sizeof(VBOXIMGVFS));
+ if (!pVBoxImgVfs)
+ {
+ RTVfsRelease(hVfs);
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pVBoxImgVfs->hVfs = hVfs;
+ pVBoxImgVfs->pNext = pVBoxImgVfsHead;
+ pVBoxImgVfsHead = pVBoxImgVfs;
+ RTDvmVolumeSetQueryBlockStatusCallback(hVol, vboximgQueryBlockStatus, hVfs);
+ }
+ else if (rc != VERR_NOT_SUPPORTED)
+ {
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ errorRuntime("RTVfsMountVol failed: %s\n", ErrInfo.Core.pszMsg);
+ break;
+ }
+ else if (fVerbose && RTErrInfoIsSet(&ErrInfo.Core))
+ RTMsgInfo("Unsupported file system: %s", ErrInfo.Core.pszMsg);
+
+ /*
+ * Advance. (Releasing hVol here is fine since RTDvmVolumeCreateVfsFile
+ * retained a reference and the hVfs a reference of it again.)
+ */
+ RTDVMVOLUME hVolNext = NIL_RTDVMVOLUME;
+ if (RT_SUCCESS(rc))
+ rc = RTDvmMapQueryNextVolume(hDvm, hVol, &hVolNext);
+ RTDvmVolumeRelease(hVol);
+ hVol = hVolNext;
+ iVol++;
+ }
+
+ if (rc == VERR_DVM_MAP_NO_VOLUME)
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ VDIfQueryRangeUse.pfnQueryRangeUse = vboximgQueryRangeUse;
+ VDInterfaceAdd(&VDIfQueryRangeUse.Core, "QueryRangeUse", VDINTERFACETYPE_QUERYRANGEUSE,
+ hDvm, sizeof(VDINTERFACEQUERYRANGEUSE), &pIfsCompact);
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ RTPrintf("There are no partitions in the volume map\n");
+ else if (rc == VERR_NOT_FOUND)
+ {
+ RTPrintf("No known volume format on disk found\n");
+ rc = VINF_SUCCESS;
+ }
+ else
+ errorRuntime("Error while opening the volume manager: %Rrf (%Rrc)\n", rc, rc);
+ }
+ else
+ errorRuntime("Error creating the volume manager: %Rrf (%Rrc)\n", rc, rc);
+ }
+ else
+ errorRuntime("Error while creating VFS interface for the disk: %Rrf (%Rrc)\n", rc, rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = VDCompact(pDisk, 0, pIfsCompact);
+ if (RT_FAILURE(rc))
+ errorRuntime("Error while compacting image: %Rrf (%Rrc)\n", rc, rc);
+ }
+
+ while (pVBoxImgVfsHead)
+ {
+ PVBOXIMGVFS pVBoxImgVfsFree = pVBoxImgVfsHead;
+
+ pVBoxImgVfsHead = pVBoxImgVfsHead->pNext;
+ RTVfsRelease(pVBoxImgVfsFree->hVfs);
+ RTMemFree(pVBoxImgVfsFree);
+ }
+
+ if (hDvm)
+ RTDvmRelease(hDvm);
+
+ VDDestroy(pDisk);
+
+ return rc;
+}
+
+
+static int handleCreateCache(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+ const char *pszFilename = NULL;
+ uint64_t cbSize = 0;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--size", 's', RTGETOPT_REQ_UINT64 }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ case 's': // --size
+ cbSize = ValueUnion.u64;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ if (!cbSize)
+ return errorSyntax("Mandatory --size option missing\n");
+
+ /* just try it */
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ rc = VDCreateCache(pDisk, "VCI", pszFilename, cbSize, VD_IMAGE_FLAGS_DEFAULT,
+ NULL, NULL, VD_OPEN_FLAGS_NORMAL, NULL, NULL);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk cache: %Rrf (%Rrc)\n", rc, rc);
+
+ VDDestroy(pDisk);
+
+ return rc;
+}
+
+static DECLCALLBACK(bool) vdIfCfgCreateBaseAreKeysValid(void *pvUser, const char *pszzValid)
+{
+ RT_NOREF2(pvUser, pszzValid);
+ return VINF_SUCCESS; /** @todo Implement. */
+}
+
+static DECLCALLBACK(int) vdIfCfgCreateBaseQuerySize(void *pvUser, const char *pszName, size_t *pcbValue)
+{
+ AssertPtrReturn(pcbValue, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pvUser, VERR_GENERAL_FAILURE);
+
+ if (RTStrCmp(pszName, "DataAlignment"))
+ return VERR_CFGM_VALUE_NOT_FOUND;
+
+ *pcbValue = strlen((const char *)pvUser) + 1 /* include terminator */;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vdIfCfgCreateBaseQuery(void *pvUser, const char *pszName, char *pszValue, size_t cchValue)
+{
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pvUser, VERR_GENERAL_FAILURE);
+
+ if (RTStrCmp(pszName, "DataAlignment"))
+ return VERR_CFGM_VALUE_NOT_FOUND;
+
+ if (strlen((const char *)pvUser) >= cchValue)
+ return VERR_CFGM_NOT_ENOUGH_SPACE;
+
+ memcpy(pszValue, pvUser, strlen((const char *)pvUser) + 1);
+
+ return VINF_SUCCESS;
+
+}
+
+static int handleCreateBase(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+ const char *pszFilename = NULL;
+ const char *pszBackend = "VDI";
+ const char *pszVariant = NULL;
+ unsigned uImageFlags = VD_IMAGE_FLAGS_NONE;
+ uint64_t cbSize = 0;
+ const char *pszDataAlignment = NULL;
+ VDGEOMETRY LCHSGeometry, PCHSGeometry;
+ PVDINTERFACE pVDIfsOperation = NULL;
+ VDINTERFACECONFIG vdIfCfg;
+
+ memset(&LCHSGeometry, 0, sizeof(LCHSGeometry));
+ memset(&PCHSGeometry, 0, sizeof(PCHSGeometry));
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--size", 's', RTGETOPT_REQ_UINT64 },
+ { "--format", 'b', RTGETOPT_REQ_STRING },
+ { "--variant", 'v', RTGETOPT_REQ_STRING },
+ { "--dataalignment", 'a', RTGETOPT_REQ_STRING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ case 's': // --size
+ cbSize = ValueUnion.u64;
+ break;
+
+ case 'b': // --format
+ pszBackend = ValueUnion.psz;
+ break;
+
+ case 'v': // --variant
+ pszVariant = ValueUnion.psz;
+ break;
+
+ case 'a': // --dataalignment
+ pszDataAlignment = ValueUnion.psz;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ if (!cbSize)
+ return errorSyntax("Mandatory --size option missing\n");
+
+ if (pszVariant)
+ {
+ rc = parseDiskVariant(pszVariant, &uImageFlags);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Invalid variant %s given\n", pszVariant);
+ }
+
+ /* Setup the config interface if required. */
+ if (pszDataAlignment)
+ {
+ vdIfCfg.pfnAreKeysValid = vdIfCfgCreateBaseAreKeysValid;
+ vdIfCfg.pfnQuerySize = vdIfCfgCreateBaseQuerySize;
+ vdIfCfg.pfnQuery = vdIfCfgCreateBaseQuery;
+ VDInterfaceAdd(&vdIfCfg.Core, "Config", VDINTERFACETYPE_CONFIG, (void *)pszDataAlignment,
+ sizeof(vdIfCfg), &pVDIfsOperation);
+ }
+
+ /* just try it */
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ rc = VDCreateBase(pDisk, pszBackend, pszFilename, cbSize, uImageFlags,
+ NULL, &PCHSGeometry, &LCHSGeometry, NULL, VD_OPEN_FLAGS_NORMAL,
+ NULL, pVDIfsOperation);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk: %Rrf (%Rrc)\n", rc, rc);
+
+ VDDestroy(pDisk);
+
+ return rc;
+}
+
+
+static int handleRepair(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ const char *pszFilename = NULL;
+ char *pszBackend = NULL;
+ const char *pszFormat = NULL;
+ bool fDryRun = false;
+ VDTYPE enmType = VDTYPE_HDD;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
+ { "--format", 'b', RTGETOPT_REQ_STRING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ case 'd': // --dry-run
+ fDryRun = true;
+ break;
+
+ case 'b': // --format
+ pszFormat = ValueUnion.psz;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* just try it */
+ if (!pszFormat)
+ {
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszBackend, &enmType);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Format autodetect failed: %Rrc\n", rc);
+ pszFormat = pszBackend;
+ }
+
+ rc = VDRepair(pVDIfs, NULL, pszFilename, pszFormat, fDryRun ? VD_REPAIR_DRY_RUN : 0);
+ if (RT_FAILURE(rc))
+ rc = errorRuntime("Error while repairing the virtual disk: %Rrf (%Rrc)\n", rc, rc);
+
+ if (pszBackend)
+ RTStrFree(pszBackend);
+ return rc;
+}
+
+
+static int handleClearComment(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+ const char *pszFilename = NULL;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* just try it */
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Format autodetect failed: %Rrc\n", rc);
+
+ rc = VDCreate(pVDIfs, enmType, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open the image */
+ rc = VDOpen(pDisk, pszFormat, pszFilename, VD_OPEN_FLAGS_INFO, NULL);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while opening the image: %Rrf (%Rrc)\n", rc, rc);
+
+ VDSetComment(pDisk, 0, NULL);
+
+ VDDestroy(pDisk);
+ return rc;
+}
+
+
+static int handleCreateFloppy(HandlerArg *a)
+{
+ const char *pszFilename = NULL;
+ uint64_t cbFloppy = 1474560;
+ uint16_t cbSector = 0;
+ uint8_t cHeads = 0;
+ uint8_t cSectorsPerCluster = 0;
+ uint8_t cSectorsPerTrack = 0;
+ uint16_t cRootDirEntries = 0;
+ uint8_t bMedia = 0;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--sectors-per-cluster", 'c', RTGETOPT_REQ_UINT8 },
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--heads", 'h', RTGETOPT_REQ_UINT8 },
+ { "--media-byte", 'm', RTGETOPT_REQ_UINT8 },
+ { "--root-dir-entries", 'r', RTGETOPT_REQ_UINT16 },
+ { "--size", 's', RTGETOPT_REQ_UINT64 },
+ { "--sector-size", 'S', RTGETOPT_REQ_UINT16 },
+ { "--sectors-per-track", 't', RTGETOPT_REQ_UINT8 },
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'c': cSectorsPerCluster = ValueUnion.u8; break;
+ case 'f': pszFilename = ValueUnion.psz; break;
+ case 'h': cHeads = ValueUnion.u8; break;
+ case 'm': bMedia = ValueUnion.u8; break;
+ case 'r': cRootDirEntries = ValueUnion.u16; break;
+ case 's': cbFloppy = ValueUnion.u64; break;
+ case 'S': cbSector = ValueUnion.u16; break;
+ case 't': cSectorsPerTrack = ValueUnion.u8; break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /*
+ * Do the job.
+ */
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsChainOpenFile(pszFilename,
+ RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_ALL
+ | (0770 << RTFILE_O_CREATE_MODE_SHIFT),
+ &hVfsFile, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsFatVolFormat(hVfsFile, 0, cbFloppy, RTFSFATVOL_FMT_F_FULL, cbSector, cSectorsPerCluster, RTFSFATTYPE_INVALID,
+ cHeads, cSectorsPerTrack, bMedia, 0 /*cHiddenSectors*/, cRootDirEntries,
+ RTErrInfoInitStatic(&ErrInfo));
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ errorRuntime("Error %Rrc formatting floppy '%s': %s", rc, pszFilename, ErrInfo.Core.pszMsg);
+ else
+ errorRuntime("Error formatting floppy '%s': %Rrc", pszFilename, rc);
+ }
+ else
+ RTVfsChainMsgError("RTVfsChainOpenFile", pszFilename, rc, offError, &ErrInfo.Core);
+ return RTEXITCODE_FAILURE;
+}
+
+
+static int handleCreateIso(HandlerArg *a)
+{
+ return RTFsIsoMakerCmd(a->argc + 1, a->argv - 1);
+}
+
+
+static int handleClearResize(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+ const char *pszFilename = NULL;
+ uint64_t cbNew = 0;
+ VDGEOMETRY LCHSGeometry, PCHSGeometry;
+
+ memset(&LCHSGeometry, 0, sizeof(LCHSGeometry));
+ memset(&PCHSGeometry, 0, sizeof(PCHSGeometry));
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--size", 's', RTGETOPT_REQ_UINT64 }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ case 's': // --size
+ cbNew = ValueUnion.u64;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ if (!cbNew)
+ return errorSyntax("Mandatory --size option missing or invalid\n");
+
+ /* just try it */
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Format autodetect failed: %Rrc\n", rc);
+
+ rc = VDCreate(pVDIfs, enmType, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open the image */
+ rc = VDOpen(pDisk, pszFormat, pszFilename, VD_OPEN_FLAGS_NORMAL, NULL);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while opening the image: %Rrf (%Rrc)\n", rc, rc);
+
+ rc = VDResize(pDisk, cbNew, &PCHSGeometry, &LCHSGeometry, NULL);
+ if (RT_FAILURE(rc))
+ rc = errorRuntime("Error while resizing the virtual disk: %Rrf (%Rrc)\n", rc, rc);
+
+ VDDestroy(pDisk);
+ return rc;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int exitcode = 0;
+
+ int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_STANDALONE_APP);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ g_pszProgName = RTPathFilename(argv[0]);
+
+ bool fShowLogo = false;
+ int iCmd = 1;
+ int iCmdArg;
+
+ /* global options */
+ for (int i = 1; i < argc || argc <= iCmd; i++)
+ {
+ if ( argc <= iCmd
+ || !strcmp(argv[i], "help")
+ || !strcmp(argv[i], "-?")
+ || !strcmp(argv[i], "-h")
+ || !strcmp(argv[i], "-help")
+ || !strcmp(argv[i], "--help"))
+ {
+ showLogo(g_pStdOut);
+ printUsage(g_pStdOut);
+ return 0;
+ }
+
+ if ( !strcmp(argv[i], "-v")
+ || !strcmp(argv[i], "-version")
+ || !strcmp(argv[i], "-Version")
+ || !strcmp(argv[i], "--version"))
+ {
+ /* Print version number, and do nothing else. */
+ RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
+ return 0;
+ }
+
+ if ( !strcmp(argv[i], "--nologo")
+ || !strcmp(argv[i], "-nologo")
+ || !strcmp(argv[i], "-q"))
+ {
+ /* suppress the logo */
+ fShowLogo = false;
+ iCmd++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ iCmdArg = iCmd + 1;
+
+ if (fShowLogo)
+ showLogo(g_pStdOut);
+
+ /* initialize the VD backend with dummy handlers */
+ VDINTERFACEERROR vdInterfaceError;
+ vdInterfaceError.pfnError = handleVDError;
+ vdInterfaceError.pfnMessage = handleVDMessage;
+
+ rc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+
+ rc = VDInit();
+ if (RT_FAILURE(rc))
+ {
+ errorSyntax("Initializing backends failed! rc=%Rrc\n", rc);
+ return 1;
+ }
+
+ /*
+ * All registered command handlers
+ */
+ static const struct
+ {
+ const char *command;
+ int (*handler)(HandlerArg *a);
+ } s_commandHandlers[] =
+ {
+ { "setuuid", handleSetUUID },
+ { "geometry", handleGeometry },
+ { "convert", handleConvert },
+ { "info", handleInfo },
+ { "compact", handleCompact },
+ { "createcache", handleCreateCache },
+ { "createbase", handleCreateBase },
+ { "createfloppy", handleCreateFloppy },
+ { "createiso", handleCreateIso },
+ { "repair", handleRepair },
+ { "clearcomment", handleClearComment },
+ { "resize", handleClearResize },
+ { NULL, NULL }
+ };
+
+ HandlerArg handlerArg = { 0, NULL };
+ int commandIndex;
+ for (commandIndex = 0; s_commandHandlers[commandIndex].command != NULL; commandIndex++)
+ {
+ if (!strcmp(s_commandHandlers[commandIndex].command, argv[iCmd]))
+ {
+ handlerArg.argc = argc - iCmdArg;
+ handlerArg.argv = &argv[iCmdArg];
+
+ exitcode = s_commandHandlers[commandIndex].handler(&handlerArg);
+ break;
+ }
+ }
+ if (!s_commandHandlers[commandIndex].command)
+ {
+ errorSyntax("Invalid command '%s'", argv[iCmd]);
+ return 1;
+ }
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ {
+ errorSyntax("Unloading backends failed! rc=%Rrc\n", rc);
+ return 1;
+ }
+
+ return exitcode;
+}
+
+/* dummy stub for RuntimeR3 */
+#ifndef RT_OS_WINDOWS
+RTDECL(bool) RTAssertShouldPanic(void)
+{
+ return true;
+}
+#endif
diff --git a/src/VBox/Storage/testcase/vbox-img.rc b/src/VBox/Storage/testcase/vbox-img.rc
new file mode 100644
index 00000000..9f50be00
--- /dev/null
+++ b/src/VBox/Storage/testcase/vbox-img.rc
@@ -0,0 +1,61 @@
+/* $Id: vbox-img.rc $ */
+/** @file
+ * vbox-img - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Virtual Disk Utility\0"
+ VALUE "InternalName", "vbox-img\0"
+ VALUE "OriginalFilename", "vbox-img.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/Storage/testcase/vdkeystoremgr.cpp b/src/VBox/Storage/testcase/vdkeystoremgr.cpp
new file mode 100644
index 00000000..3e45a25a
--- /dev/null
+++ b/src/VBox/Storage/testcase/vdkeystoremgr.cpp
@@ -0,0 +1,288 @@
+/* $Id: vdkeystoremgr.cpp $ */
+/** @file
+ * Keystore utility for debugging.
+ */
+
+/*
+ * Copyright (C) 2016-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/vd.h>
+#include <iprt/errcore.h>
+#include <VBox/version.h>
+#include <iprt/initterm.h>
+#include <iprt/base64.h>
+#include <iprt/buildconfig.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/stream.h>
+#include <iprt/message.h>
+#include <iprt/getopt.h>
+#include <iprt/assert.h>
+
+#include "../VDKeyStore.h"
+
+/** command handler argument */
+struct HandlerArg
+{
+ int argc;
+ char **argv;
+};
+
+static const char *g_pszProgName = "";
+static void printUsage(PRTSTREAM pStrm)
+{
+ RTStrmPrintf(pStrm,
+ "Usage: %s\n"
+ " create --password <password>\n"
+ " --cipher <cipher>\n"
+ " --dek <dek in base64>\n"
+ "\n"
+ " dump --keystore <keystore data in base64>\n"
+ " [--password <password to decrypt the DEK inside]\n",
+ g_pszProgName);
+}
+
+static void showLogo(PRTSTREAM pStrm)
+{
+ static bool s_fShown; /* show only once */
+
+ if (!s_fShown)
+ {
+ RTStrmPrintf(pStrm, VBOX_PRODUCT " VD Keystore Mgr " VBOX_VERSION_STRING "\n"
+ "Copyright (C) 2016-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+ s_fShown = true;
+ }
+}
+
+/**
+ * Print a usage synopsis and the syntax error message.
+ */
+static int errorSyntax(const char *pszFormat, ...)
+{
+ va_list args;
+ showLogo(g_pStdErr); // show logo even if suppressed
+ va_start(args, pszFormat);
+ RTStrmPrintf(g_pStdErr, "\nSyntax error: %N\n", pszFormat, &args);
+ va_end(args);
+ printUsage(g_pStdErr);
+ return 1;
+}
+
+static int errorRuntime(const char *pszFormat, ...)
+{
+ va_list args;
+
+ va_start(args, pszFormat);
+ RTMsgErrorV(pszFormat, args);
+ va_end(args);
+ return 1;
+}
+
+static DECLCALLBACK(int) handleCreate(HandlerArg *pArgs)
+{
+ const char *pszPassword = NULL;
+ const char *pszCipher = NULL;
+ const char *pszDek = NULL;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--password", 'p', RTGETOPT_REQ_STRING },
+ { "--cipher" , 'c', RTGETOPT_REQ_STRING },
+ { "--dek", 'd', RTGETOPT_REQ_STRING }
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'p': // --password
+ pszPassword = ValueUnion.psz;
+ break;
+ case 'c': // --cipher
+ pszCipher = ValueUnion.psz;
+ break;
+ case 'd': // --dek
+ pszDek = ValueUnion.psz;
+ break;
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszPassword)
+ return errorSyntax("Mandatory --password option missing\n");
+ if (!pszCipher)
+ return errorSyntax("Mandatory --cipher option missing\n");
+ if (!pszDek)
+ return errorSyntax("Mandatory --dek option missing\n");
+
+ /* Get the size of the decoded DEK. */
+ ssize_t cbDekDec = RTBase64DecodedSize(pszDek, NULL);
+ if (cbDekDec == -1)
+ return errorRuntime("The encoding of the base64 DEK is bad\n");
+
+ uint8_t *pbDek = (uint8_t *)RTMemAllocZ(cbDekDec);
+ size_t cbDek = cbDekDec;
+ if (!pbDek)
+ return errorRuntime("Failed to allocate memory for the DEK\n");
+
+ int rc = RTBase64Decode(pszDek, pbDek, cbDek, &cbDek, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszKeyStoreEnc = NULL;
+ rc = vdKeyStoreCreate(pszPassword, pbDek, cbDek, pszCipher, &pszKeyStoreEnc);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Successfully created keystore\n"
+ "Keystore (base64): \n"
+ "%s\n", pszKeyStoreEnc);
+ RTMemFree(pszKeyStoreEnc);
+ }
+ else
+ errorRuntime("Failed to create keystore with %Rrc\n", rc);
+ }
+ else
+ errorRuntime("Failed to decode the DEK with %Rrc\n", rc);
+
+ RTMemFree(pbDek);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static DECLCALLBACK(int) handleDump(HandlerArg *pArgs)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int main(int argc, char *argv[])
+{
+ int exitcode = 0;
+
+ int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_STANDALONE_APP);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ g_pszProgName = RTPathFilename(argv[0]);
+
+ bool fShowLogo = false;
+ int iCmd = 1;
+ int iCmdArg;
+
+ /* global options */
+ for (int i = 1; i < argc || argc <= iCmd; i++)
+ {
+ if ( argc <= iCmd
+ || !strcmp(argv[i], "help")
+ || !strcmp(argv[i], "-?")
+ || !strcmp(argv[i], "-h")
+ || !strcmp(argv[i], "-help")
+ || !strcmp(argv[i], "--help"))
+ {
+ showLogo(g_pStdOut);
+ printUsage(g_pStdOut);
+ return 0;
+ }
+
+ if ( !strcmp(argv[i], "-v")
+ || !strcmp(argv[i], "-version")
+ || !strcmp(argv[i], "-Version")
+ || !strcmp(argv[i], "--version"))
+ {
+ /* Print version number, and do nothing else. */
+ RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
+ return 0;
+ }
+
+ if ( !strcmp(argv[i], "--nologo")
+ || !strcmp(argv[i], "-nologo")
+ || !strcmp(argv[i], "-q"))
+ {
+ /* suppress the logo */
+ fShowLogo = false;
+ iCmd++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ iCmdArg = iCmd + 1;
+
+ if (fShowLogo)
+ showLogo(g_pStdOut);
+
+ /*
+ * All registered command handlers
+ */
+ static const struct
+ {
+ const char *command;
+ DECLR3CALLBACKMEMBER(int, handler, (HandlerArg *a));
+ } s_commandHandlers[] =
+ {
+ { "create", handleCreate },
+ { "dump", handleDump },
+ { NULL, NULL }
+ };
+
+ HandlerArg handlerArg = { 0, NULL };
+ int commandIndex;
+ for (commandIndex = 0; s_commandHandlers[commandIndex].command != NULL; commandIndex++)
+ {
+ if (!strcmp(s_commandHandlers[commandIndex].command, argv[iCmd]))
+ {
+ handlerArg.argc = argc - iCmdArg;
+ handlerArg.argv = &argv[iCmdArg];
+
+ exitcode = s_commandHandlers[commandIndex].handler(&handlerArg);
+ break;
+ }
+ }
+ if (!s_commandHandlers[commandIndex].command)
+ {
+ errorSyntax("Invalid command '%s'", argv[iCmd]);
+ return 1;
+ }
+
+ return exitcode;
+}
+
+/* dummy stub for RuntimeR3 */
+#ifndef RT_OS_WINDOWS
+RTDECL(bool) RTAssertShouldPanic(void)
+{
+ return true;
+}
+#endif
diff --git a/src/VBox/Storage/testcase/vdkeystoremgr.rc b/src/VBox/Storage/testcase/vdkeystoremgr.rc
new file mode 100644
index 00000000..fbc8d902
--- /dev/null
+++ b/src/VBox/Storage/testcase/vdkeystoremgr.rc
@@ -0,0 +1,61 @@
+/* $Id: vdkeystoremgr.rc $ */
+/** @file
+ * vdkeystoremgr - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2016-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox VD Keystore utility\0"
+ VALUE "InternalName", "vdkeystoremgr\0"
+ VALUE "OriginalFilename", "vdkeystoremgr.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