diff options
Diffstat (limited to 'src/VBox/Storage/testcase/tstVD.cpp')
-rw-r--r-- | src/VBox/Storage/testcase/tstVD.cpp | 1080 |
1 files changed, 1080 insertions, 0 deletions
diff --git a/src/VBox/Storage/testcase/tstVD.cpp b/src/VBox/Storage/testcase/tstVD.cpp new file mode 100644 index 00000000..460c6859 --- /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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <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; +} + |