diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Storage | |
parent | Initial commit. (diff) | |
download | virtualbox-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')
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, + ¶llelsHeader, 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, ¶llelsHeader, + 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 |