summaryrefslogtreecommitdiffstats
path: root/src/VBox/Storage/testcase/tstVDIo.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Storage/testcase/tstVDIo.cpp
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Storage/testcase/tstVDIo.cpp')
-rw-r--r--src/VBox/Storage/testcase/tstVDIo.cpp3019
1 files changed, 3019 insertions, 0 deletions
diff --git a/src/VBox/Storage/testcase/tstVDIo.cpp b/src/VBox/Storage/testcase/tstVDIo.cpp
new file mode 100644
index 00000000..2964a6b9
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDIo.cpp
@@ -0,0 +1,3019 @@
+/* $Id: tstVDIo.cpp $ */
+/** @file
+ * VBox HDD container test utility - I/O replay.
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+
+#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.
+ *
+ * @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.
+ *
+ * @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.
+ */
+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;
+}