summaryrefslogtreecommitdiffstats
path: root/src/VBox/Storage/testcase/tstVDSnap.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Storage/testcase/tstVDSnap.cpp')
-rw-r--r--src/VBox/Storage/testcase/tstVDSnap.cpp482
1 files changed, 482 insertions, 0 deletions
diff --git a/src/VBox/Storage/testcase/tstVDSnap.cpp b/src/VBox/Storage/testcase/tstVDSnap.cpp
new file mode 100644
index 00000000..8dd4548a
--- /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-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
+ */
+
+#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;
+}
+