summaryrefslogtreecommitdiffstats
path: root/src/VBox/Storage/testcase
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
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')
-rw-r--r--src/VBox/Storage/testcase/BuiltinTests.h57
-rw-r--r--src/VBox/Storage/testcase/Makefile.kmk236
-rw-r--r--src/VBox/Storage/testcase/VDDefs.h52
-rw-r--r--src/VBox/Storage/testcase/VDIoBackend.cpp250
-rw-r--r--src/VBox/Storage/testcase/VDIoBackend.h104
-rw-r--r--src/VBox/Storage/testcase/VDIoBackendMem.cpp263
-rw-r--r--src/VBox/Storage/testcase/VDIoBackendMem.h92
-rw-r--r--src/VBox/Storage/testcase/VDIoRnd.cpp118
-rw-r--r--src/VBox/Storage/testcase/VDIoRnd.h69
-rw-r--r--src/VBox/Storage/testcase/VDMemDisk.cpp418
-rw-r--r--src/VBox/Storage/testcase/VDMemDisk.h141
-rw-r--r--src/VBox/Storage/testcase/VDScript.cpp3006
-rw-r--r--src/VBox/Storage/testcase/VDScript.h235
-rw-r--r--src/VBox/Storage/testcase/VDScriptAst.cpp366
-rw-r--r--src/VBox/Storage/testcase/VDScriptAst.h594
-rw-r--r--src/VBox/Storage/testcase/VDScriptChecker.cpp44
-rw-r--r--src/VBox/Storage/testcase/VDScriptInternal.h121
-rw-r--r--src/VBox/Storage/testcase/VDScriptInterp.cpp1071
-rw-r--r--src/VBox/Storage/testcase/VDScriptStack.h152
-rw-r--r--src/VBox/Storage/testcase/tstVD-2.cpp272
-rw-r--r--src/VBox/Storage/testcase/tstVD.cpp1080
-rw-r--r--src/VBox/Storage/testcase/tstVDCompact.vd102
-rw-r--r--src/VBox/Storage/testcase/tstVDCopy.vd100
-rw-r--r--src/VBox/Storage/testcase/tstVDDiscard.vd69
-rw-r--r--src/VBox/Storage/testcase/tstVDFill.cpp244
-rw-r--r--src/VBox/Storage/testcase/tstVDIo.cpp3019
-rw-r--r--src/VBox/Storage/testcase/tstVDIo.vd73
-rw-r--r--src/VBox/Storage/testcase/tstVDMultBackends.vd70
-rw-r--r--src/VBox/Storage/testcase/tstVDResize.vd79
-rw-r--r--src/VBox/Storage/testcase/tstVDShareable.vd66
-rw-r--r--src/VBox/Storage/testcase/tstVDSnap.cpp482
-rw-r--r--src/VBox/Storage/testcase/vbox-img.cpp2186
-rw-r--r--src/VBox/Storage/testcase/vbox-img.rc61
-rw-r--r--src/VBox/Storage/testcase/vdkeystoremgr.cpp288
-rw-r--r--src/VBox/Storage/testcase/vdkeystoremgr.rc61
35 files changed, 15641 insertions, 0 deletions
diff --git a/src/VBox/Storage/testcase/BuiltinTests.h b/src/VBox/Storage/testcase/BuiltinTests.h
new file mode 100644
index 00000000..f3a68cbb
--- /dev/null
+++ b/src/VBox/Storage/testcase/BuiltinTests.h
@@ -0,0 +1,57 @@
+/** @file
+ *
+ * tstVDIo testing utility - builtin tests.
+ */
+
+/*
+ * Copyright (C) 2014-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_BuiltinTests_h
+#define VBOX_INCLUDED_SRC_testcase_BuiltinTests_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/**
+ * Builtin Tests (in generated BuiltinTests.cpp)
+ */
+typedef struct TSTVDIOTESTENTRY
+{
+ /** Test name. */
+ const char *pszName;
+ /** Pointer to the raw bytes. */
+ const unsigned char *pch;
+ /** Number of bytes. */
+ unsigned cb;
+} TSTVDIOTESTENTRY;
+/** Pointer to a trust anchor table entry. */
+typedef TSTVDIOTESTENTRY const *PCTSTVDIOTESTENTRY;
+
+/** Macro for simplifying generating the trust anchor tables. */
+#define TSTVDIOTESTENTRY_GEN(a_szName, a_abTest) { #a_szName, &a_abTest[0], sizeof(a_abTest) }
+
+/** All tests we know. */
+extern TSTVDIOTESTENTRY const g_aVDIoTests[];
+/** Number of entries in g_aVDIoTests. */
+extern unsigned const g_cVDIoTests;
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_BuiltinTests_h */
diff --git a/src/VBox/Storage/testcase/Makefile.kmk b/src/VBox/Storage/testcase/Makefile.kmk
new file mode 100644
index 00000000..a3d229ae
--- /dev/null
+++ b/src/VBox/Storage/testcase/Makefile.kmk
@@ -0,0 +1,236 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the storage device & driver testcases.
+#
+
+#
+# Copyright (C) 2006-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
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Basic testcases for the VD code.
+#
+ifdef VBOX_WITH_TESTCASES
+ PROGRAMS += tstVD tstVD-2 tstVDSnap tstVDFill
+
+ tstVD_TEMPLATE = VBoxR3TstExe
+ tstVD_SOURCES = tstVD.cpp
+ tstVD_LIBS = $(LIB_DDU)
+
+ tstVD-2_TEMPLATE = VBoxR3TstExe
+ tstVD-2_SOURCES = tstVD-2.cpp
+ tstVD-2_LIBS = $(LIB_DDU)
+
+ tstVDFill_TEMPLATE = VBoxR3TstExe
+ tstVDFill_SOURCES = tstVDFill.cpp
+ tstVDFill_LIBS = $(LIB_DDU)
+
+ PROGRAMS += tstVDIo
+
+ #
+ # VD I/O test scripts to built in -> .cpp
+ #
+ TSTVDIO_BUILTIN_TESTS_FILE = $(tstVDIo_0_OUTDIR)/BuiltinTests.cpp
+ TSTVDIO_BUILTIN_TESTS := \
+ tstVDIo=tstVDIo.vd \
+ tstVDResize=tstVDResize.vd \
+ tstVDCompact=tstVDCompact.vd \
+ tstVDCopy=tstVDCopy.vd \
+ tstVDDiscard=tstVDDiscard.vd \
+ tstVDShareable=tstVDShareable.vd
+ TSTVDIO_BUILTIN_TEST_NAMES := $(foreach test,$(TSTVDIO_BUILTIN_TESTS),$(firstword $(subst =,$(SPACE) ,$(test))))
+ TSTVDIO_PATH_TESTS := $(PATH_SUB_CURRENT)
+
+ # 1=name, 2=filter
+ TSTVDIO_GEN_TEST_MACRO = 'TSTVDIOTESTENTRY const g_a$(1)[] =' '{' \
+ $(foreach testnm,$(filter $(2),$(TSTVDIO_BUILTIN_TEST_NAMES)), ' TSTVDIOTESTENTRY_GEN($(testnm), g_ab$(testnm)),') \
+ '};' 'unsigned const g_c$(1) = RT_ELEMENTS(g_a$(1));' '' ''
+
+ $$(TSTVDIO_BUILTIN_TESTS_FILE): $(MAKEFILE_CURRENT) \
+ $(foreach test,$(TSTVDIO_BUILTIN_TESTS),$(TSTVDIO_PATH_TESTS)/$(lastword $(subst =,$(SPACE) ,$(test)))) \
+ $(VBOX_BIN2C) \
+ | $$(dir $$@)
+ $(QUIET)$(RM) -f -- $@ $@.vd
+ $(QUIET)$(APPEND) -n "$@" \
+ '' \
+ '#include "BuiltinTests.h"' \
+ ''
+ $(foreach test,$(TSTVDIO_BUILTIN_TESTS), $(NLTAB)$(VBOX_BIN2C) -ascii --append --no-size \
+ "$(firstword $(subst =,$(SP) ,$(test)))" \
+ "$(TSTVDIO_PATH_TESTS)/$(lastword $(subst =,$(SP) ,$(test)))" \
+ "$@")
+
+ # Generate test lists.
+ $(QUIET)$(APPEND) -n "$@" '' \
+ $(call TSTVDIO_GEN_TEST_MACRO,VDIoTests,%) \
+
+ tstVDIo_TEMPLATE = VBoxR3TstExe
+ tstVDIo_INCS := $(PATH_SUB_CURRENT)
+
+ ifdef VBOX_TSTVDIO_WITH_LOG_REPLAY
+ tstVDIo_DEFS += VBOX_TSTVDIO_WITH_LOG_REPLAY
+ endif
+
+ tstVDIo_SOURCES = \
+ tstVDIo.cpp \
+ VDIoBackend.cpp \
+ VDIoBackendMem.cpp \
+ VDMemDisk.cpp \
+ VDIoRnd.cpp \
+ VDScript.cpp \
+ VDScriptAst.cpp \
+ VDScriptChecker.cpp \
+ VDScriptInterp.cpp \
+ $(TSTVDIO_BUILTIN_TESTS_FILE)
+ tstVDIo_LIBS = \
+ $(LIB_DDU)
+ tstVDIo_CLEAN = \
+ $(TSTVDIO_BUILTIN_TESTS_FILE)
+ tstVDSnap_TEMPLATE = VBoxR3TstExe
+ tstVDSnap_LIBS = $(LIB_DDU)
+ tstVDSnap_SOURCES = tstVDSnap.cpp
+endif
+
+if defined(VBOX_WITH_TESTCASES) || defined(VBOX_WITH_VBOX_IMG)
+ PROGRAMS += vbox-img
+
+ #
+ # vbox-img - static because it might be used as a standalone tool.
+ #
+ ifneq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),solaris.sparc32 solaris.sparc64)
+ vbox-img_TEMPLATE := VBoxR3Exe
+ vbox-img_DEFS := IN_VBOXDDU IN_VBOXDDU_STATIC VBOX_HDD_NO_DYNAMIC_BACKENDS
+ else
+ vbox-img_TEMPLATE := VBoxR3Static
+ vbox-img_DEFS := IN_VBOXDDU IN_VBOXDDU_STATIC VBOX_HDD_NO_DYNAMIC_BACKENDS
+ endif
+ vbox-img_INCS := \
+ ../../Main/include
+ vbox-img_SOURCES := \
+ vbox-img.cpp \
+ ../VD.cpp \
+ ../VDPlugin.cpp \
+ ../VDVfs.cpp \
+ ../VDI.cpp \
+ ../VMDK.cpp \
+ ../VHD.cpp \
+ ../DMG.cpp \
+ ../Parallels.cpp \
+ ../ISCSI.cpp \
+ ../RAW.cpp \
+ ../QED.cpp \
+ ../QCOW.cpp \
+ ../VHDX.cpp \
+ ../CUE.cpp \
+ ../VISO.cpp \
+ ../VCICache.cpp \
+ ../VDIfVfs.cpp
+ vbox-img_SOURCES.win = \
+ vbox-img.rc
+ ifeq ($(vbox-img_TEMPLATE),VBoxR3Exe)
+ vbox-img_LIBS = \
+ $(LIB_RUNTIME)
+ ifeq ($(KBUILD_TARGET),freebsd)
+ vbox-img_LIBS += geom bsdxml sbuf
+ else ifeq ($(KBUILD_TARGET),solaris)
+ vbox-img_LIBS += kstat efi
+ endif
+
+ else
+ vbox-img_LIBS = \
+ $(VBOX_LIB_RUNTIME_STATIC)
+ if1of ($(KBUILD_TARGET), os2 win)
+ vbox-img_LIBS += \
+ $(SDK_VBoxLzf_STATIC_LIBS) \
+ $(SDK_VBoxZlibStatic_LIBS)
+ else
+ vbox-img_LIBS += \
+ $(SDK_VBoxLzf_LIBS) \
+ $(SDK_VBoxZlib_LIBS)
+ endif
+ ifeq ($(KBUILD_TARGET),linux)
+ ifdef SDK_VBoxLibXml2_LIBS
+ vbox-img_LIBS += xml2
+ endif
+ else ifeq ($(KBUILD_TARGET),freebsd)
+ vbox-img_LIBS += iconv geom bsdxml sbuf
+ ifdef SDK_VBoxLibXml2_LIBS
+ vbox-img_LIBS += xml2 lzma
+ endif
+ else ifeq ($(KBUILD_TARGET),darwin)
+ vbox-img_LIBS += iconv
+ else ifeq ($(KBUILD_TARGET),win)
+ vbox-img_SDKS.win = VBoxNtDll
+ else ifeq ($(KBUILD_TARGET),solaris)
+ vbox-img_LIBS += kstat efi
+ ifdef SDK_VBoxLibXml2_LIBS
+ vbox-img_LIBS += xml2
+ endif
+ endif
+ endif
+
+endif
+
+if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_PLUGIN_CRYPT) \
+ && defined(VBOX_WITH_EXTPACK_PUEL) && defined(VBOX_WITH_EXTPACK_PUEL_BUILD) \
+ && defined(VBOX_WITH_VDKEYSTOREMGR)
+ PROGRAMS += vdkeystoremgr
+
+ #
+ # vdkeystoremgr - static because it might be used as a standalone tool.
+ #
+ vdkeystoremgr_TEMPLATE = VBoxR3Static
+ vdkeystoremgr_DEFS += IN_VBOXDDU IN_VBOXDDU_STATIC VBOX_HDD_NO_DYNAMIC_BACKENDS
+ vdkeystoremgr_SOURCES = \
+ vdkeystoremgr.cpp \
+ ../VDKeyStore.cpp
+ vdkeystoremgr_SOURCES.win = \
+ vdkeystoremgr_SOURCES.rc
+ vdkeystoremgr_LIBS = \
+ $(VBOX_LIB_RUNTIME_STATIC) \
+ $(PATH_STAGE_LIB)/SUPR3$(VBOX_SUFF_LIB)
+ if1of ($(KBUILD_TARGET), os2 win)
+ vdkeystoremgr_LIBS += \
+ $(SDK_VBoxLzf_STATIC_LIBS) \
+ $(SDK_VBoxZlibStatic_LIBS)
+ else
+ vdkeystoremgr_LIBS += \
+ $(SDK_VBoxLzf_LIBS) \
+ $(SDK_VBoxZlib_LIBS)
+ endif
+ ifeq ($(KBUILD_TARGET),linux)
+ ifdef SDK_VBoxLibXml2_LIBS
+ vdkeystoremgr_LIBS += xml2
+ endif
+ else if1of ($(KBUILD_TARGET), darwin freebsd)
+ vdkeystoremgr_LIBS += iconv
+ else ifeq ($(KBUILD_TARGET),win)
+ vdkeystoremgr_SDKS.win = VBoxNtDll
+ else ifeq ($(KBUILD_TARGET),solaris)
+ vdkeystoremgr_LIBS += kstat
+ endif
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Storage/testcase/VDDefs.h b/src/VBox/Storage/testcase/VDDefs.h
new file mode 100644
index 00000000..42badf96
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDDefs.h
@@ -0,0 +1,52 @@
+/** $Id: VDDefs.h $ */
+/** @file
+ *
+ * VBox HDD container test utility, common definitions.
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDDefs_h
+#define VBOX_INCLUDED_SRC_testcase_VDDefs_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/sg.h>
+
+/**
+ * I/O transfer direction.
+ */
+typedef enum VDIOTXDIR
+{
+ /** Read. */
+ VDIOTXDIR_READ = 0,
+ /** Write. */
+ VDIOTXDIR_WRITE,
+ /** Flush. */
+ VDIOTXDIR_FLUSH,
+ /** Invalid. */
+ VDIOTXDIR_INVALID
+} VDIOTXDIR;
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDDefs_h */
diff --git a/src/VBox/Storage/testcase/VDIoBackend.cpp b/src/VBox/Storage/testcase/VDIoBackend.cpp
new file mode 100644
index 00000000..8358c06a
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoBackend.cpp
@@ -0,0 +1,250 @@
+/* $Id: VDIoBackend.cpp $ */
+/** @file
+ * VBox HDD container test utility, I/O backend API
+ */
+
+/*
+ * Copyright (C) 2013-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 /** @todo Log group */
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+
+#include "VDIoBackend.h"
+#include "VDMemDisk.h"
+#include "VDIoBackendMem.h"
+
+typedef struct VDIOBACKEND
+{
+ /** Memory I/O backend handle. */
+ PVDIOBACKENDMEM pIoMem;
+ /** Users of the memory backend. */
+ volatile uint32_t cRefsIoMem;
+ /** Users of the file backend. */
+ volatile uint32_t cRefsFile;
+} VDIOBACKEND;
+
+typedef struct VDIOSTORAGE
+{
+ /** Pointer to the I/O backend parent. */
+ PVDIOBACKEND pIoBackend;
+ /** Completion callback. */
+ PFNVDIOCOMPLETE pfnComplete;
+ /** Flag whether this storage is backed by a file or memory.disk. */
+ bool fMemory;
+ /** Type dependent data. */
+ union
+ {
+ /** Memory disk handle. */
+ PVDMEMDISK pMemDisk;
+ /** file handle. */
+ RTFILE hFile;
+ } u;
+} VDIOSTORAGE;
+
+
+int VDIoBackendCreate(PPVDIOBACKEND ppIoBackend)
+{
+ int rc = VINF_SUCCESS;
+ PVDIOBACKEND pIoBackend;
+
+ pIoBackend = (PVDIOBACKEND)RTMemAllocZ(sizeof(VDIOBACKEND));
+ if (pIoBackend)
+ *ppIoBackend = pIoBackend;
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+void VDIoBackendDestroy(PVDIOBACKEND pIoBackend)
+{
+ if (pIoBackend->pIoMem)
+ VDIoBackendMemDestroy(pIoBackend->pIoMem);
+ RTMemFree(pIoBackend);
+}
+
+int VDIoBackendStorageCreate(PVDIOBACKEND pIoBackend, const char *pszBackend,
+ const char *pszName, PFNVDIOCOMPLETE pfnComplete,
+ PPVDIOSTORAGE ppIoStorage)
+{
+ int rc = VINF_SUCCESS;
+ PVDIOSTORAGE pIoStorage = (PVDIOSTORAGE)RTMemAllocZ(sizeof(VDIOSTORAGE));
+
+ if (pIoStorage)
+ {
+ pIoStorage->pIoBackend = pIoBackend;
+ pIoStorage->pfnComplete = pfnComplete;
+ if (!strcmp(pszBackend, "memory"))
+ {
+ pIoStorage->fMemory = true;
+ rc = VDMemDiskCreate(&pIoStorage->u.pMemDisk, 0 /* Growing */);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pIoBackend->cRefsIoMem);
+ if ( cRefs == 1
+ && !pIoBackend->pIoMem)
+ {
+ rc = VDIoBackendMemCreate(&pIoBackend->pIoMem);
+ if (RT_FAILURE(rc))
+ VDMemDiskDestroy(pIoStorage->u.pMemDisk);
+ }
+ }
+ }
+ else if (!strcmp(pszBackend, "file"))
+ {
+ /* Create file. */
+ rc = RTFileOpen(&pIoStorage->u.hFile, pszName,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_ASYNC_IO | RTFILE_O_NO_CACHE | RTFILE_O_DENY_NONE);
+
+ if (RT_FAILURE(rc))
+ ASMAtomicDecU32(&pIoBackend->cRefsFile);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pIoStorage);
+ else
+ *ppIoStorage = pIoStorage;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+void VDIoBackendStorageDestroy(PVDIOSTORAGE pIoStorage)
+{
+ if (pIoStorage->fMemory)
+ {
+ VDMemDiskDestroy(pIoStorage->u.pMemDisk);
+ ASMAtomicDecU32(&pIoStorage->pIoBackend->cRefsIoMem);
+ }
+ else
+ {
+ RTFileClose(pIoStorage->u.hFile);
+ ASMAtomicDecU32(&pIoStorage->pIoBackend->cRefsFile);
+ }
+ RTMemFree(pIoStorage);
+}
+
+int VDIoBackendTransfer(PVDIOSTORAGE pIoStorage, VDIOTXDIR enmTxDir, uint64_t off,
+ size_t cbTransfer, PRTSGBUF pSgBuf, void *pvUser, bool fSync)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pIoStorage->fMemory)
+ {
+ if (!fSync)
+ {
+ rc = VDIoBackendMemTransfer(pIoStorage->pIoBackend->pIoMem, pIoStorage->u.pMemDisk,
+ enmTxDir, off, cbTransfer, pSgBuf, pIoStorage->pfnComplete,
+ pvUser);
+ }
+ else
+ {
+ switch (enmTxDir)
+ {
+ case VDIOTXDIR_READ:
+ rc = VDMemDiskRead(pIoStorage->u.pMemDisk, off, cbTransfer, pSgBuf);
+ break;
+ case VDIOTXDIR_WRITE:
+ rc = VDMemDiskWrite(pIoStorage->u.pMemDisk, off, cbTransfer, pSgBuf);
+ break;
+ case VDIOTXDIR_FLUSH:
+ break;
+ default:
+ AssertMsgFailed(("Invalid transfer type %d\n", enmTxDir));
+ }
+ }
+ }
+ else
+ {
+ if (!fSync)
+ rc = VERR_NOT_IMPLEMENTED;
+ else
+ {
+ switch (enmTxDir)
+ {
+ case VDIOTXDIR_READ:
+ rc = RTFileSgReadAt(pIoStorage->u.hFile, off, pSgBuf, cbTransfer, NULL);
+ break;
+ case VDIOTXDIR_WRITE:
+ rc = RTFileSgWriteAt(pIoStorage->u.hFile, off, pSgBuf, cbTransfer, NULL);
+ break;
+ case VDIOTXDIR_FLUSH:
+ rc = RTFileFlush(pIoStorage->u.hFile);
+ break;
+ default:
+ AssertMsgFailed(("Invalid transfer type %d\n", enmTxDir));
+ }
+ }
+ }
+
+ return rc;
+}
+
+int VDIoBackendStorageSetSize(PVDIOSTORAGE pIoStorage, uint64_t cbSize)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pIoStorage->fMemory)
+ {
+ rc = VDMemDiskSetSize(pIoStorage->u.pMemDisk, cbSize);
+ }
+ else
+ rc = RTFileSetSize(pIoStorage->u.hFile, cbSize);
+
+ return rc;
+}
+
+int VDIoBackendStorageGetSize(PVDIOSTORAGE pIoStorage, uint64_t *pcbSize)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pIoStorage->fMemory)
+ {
+ rc = VDMemDiskGetSize(pIoStorage->u.pMemDisk, pcbSize);
+ }
+ else
+ rc = RTFileQuerySize(pIoStorage->u.hFile, pcbSize);
+
+ return rc;
+}
+
+DECLHIDDEN(int) VDIoBackendDumpToFile(PVDIOSTORAGE pIoStorage, const char *pszPath)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pIoStorage->fMemory)
+ rc = VDMemDiskWriteToFile(pIoStorage->u.pMemDisk, pszPath);
+ else
+ rc = VERR_NOT_IMPLEMENTED;
+
+ return rc;
+}
+
diff --git a/src/VBox/Storage/testcase/VDIoBackend.h b/src/VBox/Storage/testcase/VDIoBackend.h
new file mode 100644
index 00000000..9775e49c
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoBackend.h
@@ -0,0 +1,104 @@
+/** $Id: VDIoBackend.h $ */
+/** @file
+ *
+ * VBox HDD container test utility, async I/O backend
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDIoBackend_h
+#define VBOX_INCLUDED_SRC_testcase_VDIoBackend_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/sg.h>
+
+#include "VDDefs.h"
+
+/** I/O backend handle. */
+typedef struct VDIOBACKEND *PVDIOBACKEND;
+/** Pointer to a I/O backend handle. */
+typedef PVDIOBACKEND *PPVDIOBACKEND;
+
+/** Storage handle. */
+typedef struct VDIOSTORAGE *PVDIOSTORAGE;
+/** Pointer to a storage handle. */
+typedef PVDIOSTORAGE *PPVDIOSTORAGE;
+
+/**
+ * Completion handler.
+ *
+ * @returns IPRT status code.
+ * @param pvUser Opaque user data.
+ * @param rcReq Completion code for the request.
+ */
+typedef DECLCALLBACKTYPE(int, FNVDIOCOMPLETE,(void *pvUser, int rcReq));
+/** Pointer to a completion handler. */
+typedef FNVDIOCOMPLETE *PFNVDIOCOMPLETE;
+
+/**
+ * Creates a new memory I/O backend.
+ *
+ * @returns IPRT status code.
+ *
+ * @param ppIoBackend Where to store the handle on success.
+ */
+int VDIoBackendCreate(PPVDIOBACKEND ppIoBackend);
+
+/**
+ * Destroys a memory I/O backend.
+ *
+ * @param pIoBackend The backend to destroy.
+ */
+void VDIoBackendDestroy(PVDIOBACKEND pIoBackend);
+
+int VDIoBackendStorageCreate(PVDIOBACKEND pIoBackend, const char *pszBackend,
+ const char *pszName, PFNVDIOCOMPLETE pfnComplete,
+ PPVDIOSTORAGE ppIoStorage);
+
+void VDIoBackendStorageDestroy(PVDIOSTORAGE pIoStorage);
+
+int VDIoBackendStorageSetSize(PVDIOSTORAGE pIoStorage, uint64_t cbSize);
+
+int VDIoBackendStorageGetSize(PVDIOSTORAGE pIoStorage, uint64_t *pcbSize);
+
+DECLHIDDEN(int) VDIoBackendDumpToFile(PVDIOSTORAGE pIoStorage, const char *pszPath);
+
+/**
+ * Enqueues a new I/O request.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pIoStorage Storage handle.
+ * @param enmTxDir The transfer direction.
+ * @param off Start offset of the transfer.
+ * @param cbTransfer Size of the transfer.
+ * @param pSgBuf S/G buffer to use.
+ * @param pvUser Opaque user data.
+ * @param fSync Flag whether to wait for the operation to complete.
+ */
+int VDIoBackendTransfer(PVDIOSTORAGE pIoStorage, VDIOTXDIR enmTxDir, uint64_t off,
+ size_t cbTransfer, PRTSGBUF pSgBuf, void *pvUser, bool fSync);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDIoBackend_h */
diff --git a/src/VBox/Storage/testcase/VDIoBackendMem.cpp b/src/VBox/Storage/testcase/VDIoBackendMem.cpp
new file mode 100644
index 00000000..8255965c
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoBackendMem.cpp
@@ -0,0 +1,263 @@
+/* $Id: VDIoBackendMem.cpp $ */
+/** @file
+ * VBox HDD container test utility, async I/O memory backend
+ */
+
+/*
+ * Copyright (C) 2011-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 /** @todo Log group */
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/thread.h>
+#include <iprt/circbuf.h>
+#include <iprt/semaphore.h>
+
+#include "VDMemDisk.h"
+#include "VDIoBackendMem.h"
+
+#define VDMEMIOBACKEND_REQS 1024
+
+/**
+ * Memory I/O request.
+ */
+typedef struct VDIOBACKENDREQ
+{
+ /** I/O request direction. */
+ VDIOTXDIR enmTxDir;
+ /** Memory disk handle. */
+ PVDMEMDISK pMemDisk;
+ /** Start offset. */
+ uint64_t off;
+ /** Size of the transfer. */
+ size_t cbTransfer;
+ /** Completion handler to call. */
+ PFNVDIOCOMPLETE pfnComplete;
+ /** Opaque user data. */
+ void *pvUser;
+ /** S/G buffer. */
+ RTSGBUF SgBuf;
+ /** Segment array - variable size. */
+ RTSGSEG aSegs[1];
+} VDIOBACKENDREQ, *PVDIOBACKENDREQ;
+
+typedef PVDIOBACKENDREQ *PPVDIOBACKENDREQ;
+
+/**
+ * I/O memory backend
+ */
+typedef struct VDIOBACKENDMEM
+{
+ /** Thread handle for the backend. */
+ RTTHREAD hThreadIo;
+ /** Circular buffer used for submitting requests. */
+ PRTCIRCBUF pRequestRing;
+ /** Size of the buffer in request items. */
+ unsigned cReqsRing;
+ /** Event semaphore the thread waits on for more work. */
+ RTSEMEVENT EventSem;
+ /** Flag whether the server should be still running. */
+ volatile bool fRunning;
+ /** Number of requests waiting in the request buffer. */
+ volatile uint32_t cReqsWaiting;
+} VDIOBACKENDMEM;
+
+static DECLCALLBACK(int) vdIoBackendMemThread(RTTHREAD hThread, void *pvUser);
+
+/**
+ * Pokes the I/O thread that something interesting happened.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pIoBackend The backend to poke.
+ */
+static int vdIoBackendMemThreadPoke(PVDIOBACKENDMEM pIoBackend)
+{
+ return RTSemEventSignal(pIoBackend->EventSem);
+}
+
+int VDIoBackendMemCreate(PPVDIOBACKENDMEM ppIoBackend)
+{
+ int rc = VINF_SUCCESS;
+ PVDIOBACKENDMEM pIoBackend = NULL;
+
+ pIoBackend = (PVDIOBACKENDMEM)RTMemAllocZ(sizeof(VDIOBACKENDMEM));
+ if (pIoBackend)
+ {
+ rc = RTCircBufCreate(&pIoBackend->pRequestRing, VDMEMIOBACKEND_REQS * sizeof(PVDIOBACKENDREQ));
+ if (RT_SUCCESS(rc))
+ {
+ pIoBackend->cReqsRing = VDMEMIOBACKEND_REQS * sizeof(VDIOBACKENDREQ);
+ pIoBackend->fRunning = true;
+
+ rc = RTSemEventCreate(&pIoBackend->EventSem);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadCreate(&pIoBackend->hThreadIo, vdIoBackendMemThread, pIoBackend, 0, RTTHREADTYPE_IO,
+ RTTHREADFLAGS_WAITABLE, "MemIo");
+ if (RT_SUCCESS(rc))
+ {
+ *ppIoBackend = pIoBackend;
+
+ LogFlowFunc(("returns success\n"));
+ return VINF_SUCCESS;
+ }
+ RTSemEventDestroy(pIoBackend->EventSem);
+ }
+
+ RTCircBufDestroy(pIoBackend->pRequestRing);
+ }
+
+ RTMemFree(pIoBackend);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+int VDIoBackendMemDestroy(PVDIOBACKENDMEM pIoBackend)
+{
+ ASMAtomicXchgBool(&pIoBackend->fRunning, false);
+ vdIoBackendMemThreadPoke(pIoBackend);
+
+ RTThreadWait(pIoBackend->hThreadIo, RT_INDEFINITE_WAIT, NULL);
+ RTSemEventDestroy(pIoBackend->EventSem);
+ RTCircBufDestroy(pIoBackend->pRequestRing);
+ RTMemFree(pIoBackend);
+
+ return VINF_SUCCESS;
+}
+
+int VDIoBackendMemTransfer(PVDIOBACKENDMEM pIoBackend, PVDMEMDISK pMemDisk,
+ VDIOTXDIR enmTxDir, uint64_t off, size_t cbTransfer,
+ PRTSGBUF pSgBuf, PFNVDIOCOMPLETE pfnComplete, void *pvUser)
+{
+ PVDIOBACKENDREQ pReq = NULL;
+ PPVDIOBACKENDREQ ppReq = NULL;
+ size_t cbData;
+ unsigned cSegs = 0;
+
+ LogFlowFunc(("Queuing request\n"));
+
+ if (enmTxDir != VDIOTXDIR_FLUSH)
+ RTSgBufSegArrayCreate(pSgBuf, NULL, &cSegs, cbTransfer);
+
+ pReq = (PVDIOBACKENDREQ)RTMemAlloc(RT_UOFFSETOF_DYN(VDIOBACKENDREQ, aSegs[cSegs]));
+ if (!pReq)
+ return VERR_NO_MEMORY;
+
+ RTCircBufAcquireWriteBlock(pIoBackend->pRequestRing, sizeof(PVDIOBACKENDREQ), (void **)&ppReq, &cbData);
+ if (!ppReq)
+ {
+ RTMemFree(pReq);
+ return VERR_NO_MEMORY;
+ }
+
+ Assert(cbData == sizeof(PVDIOBACKENDREQ));
+ pReq->enmTxDir = enmTxDir;
+ pReq->cbTransfer = cbTransfer;
+ pReq->off = off;
+ pReq->pMemDisk = pMemDisk;
+ pReq->pfnComplete = pfnComplete;
+ pReq->pvUser = pvUser;
+ if (enmTxDir != VDIOTXDIR_FLUSH)
+ {
+ RTSgBufSegArrayCreate(pSgBuf, &pReq->aSegs[0], &cSegs, cbTransfer);
+ RTSgBufInit(&pReq->SgBuf, pReq->aSegs, cSegs);
+ }
+
+ *ppReq = pReq;
+ RTCircBufReleaseWriteBlock(pIoBackend->pRequestRing, sizeof(PVDIOBACKENDREQ));
+ uint32_t cReqsWaiting = ASMAtomicIncU32(&pIoBackend->cReqsWaiting);
+ if (cReqsWaiting == 1)
+ vdIoBackendMemThreadPoke(pIoBackend);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * I/O thread for the memory backend.
+ *
+ * @returns IPRT status code.
+ *
+ * @param hThread The thread handle.
+ * @param pvUser Opaque user data.
+ */
+static DECLCALLBACK(int) vdIoBackendMemThread(RTTHREAD hThread, void *pvUser)
+{
+ PVDIOBACKENDMEM pIoBackend = (PVDIOBACKENDMEM)pvUser;
+ RT_NOREF1(hThread);
+
+ while (pIoBackend->fRunning)
+ {
+ int rc = RTSemEventWait(pIoBackend->EventSem, RT_INDEFINITE_WAIT);
+ if (RT_FAILURE(rc) || !pIoBackend->fRunning)
+ break;
+
+ PVDIOBACKENDREQ pReq;
+ PPVDIOBACKENDREQ ppReq;
+ size_t cbData;
+ uint32_t cReqsWaiting = ASMAtomicXchgU32(&pIoBackend->cReqsWaiting, 0);
+
+ while (cReqsWaiting)
+ {
+ int rcReq = VINF_SUCCESS;
+
+ /* Do we have another request? */
+ RTCircBufAcquireReadBlock(pIoBackend->pRequestRing, sizeof(PVDIOBACKENDREQ), (void **)&ppReq, &cbData);
+ Assert(!ppReq || cbData == sizeof(PVDIOBACKENDREQ));
+ RTCircBufReleaseReadBlock(pIoBackend->pRequestRing, cbData);
+
+ pReq = *ppReq;
+ cReqsWaiting--;
+
+ LogFlowFunc(("Processing request\n"));
+ switch (pReq->enmTxDir)
+ {
+ case VDIOTXDIR_READ:
+ {
+ rcReq = VDMemDiskRead(pReq->pMemDisk, pReq->off, pReq->cbTransfer, &pReq->SgBuf);
+ break;
+ }
+ case VDIOTXDIR_WRITE:
+ {
+ rcReq = VDMemDiskWrite(pReq->pMemDisk, pReq->off, pReq->cbTransfer, &pReq->SgBuf);
+ break;
+ }
+ case VDIOTXDIR_FLUSH:
+ break;
+ default:
+ AssertMsgFailed(("Invalid TX direction!\n"));
+ }
+
+ /* Notify completion. */
+ pReq->pfnComplete(pReq->pvUser, rcReq);
+ RTMemFree(pReq);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Storage/testcase/VDIoBackendMem.h b/src/VBox/Storage/testcase/VDIoBackendMem.h
new file mode 100644
index 00000000..04e6f600
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoBackendMem.h
@@ -0,0 +1,92 @@
+/** $Id: VDIoBackendMem.h $ */
+/** @file
+ *
+ * VBox HDD container test utility, async I/O memory backend
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDIoBackendMem_h
+#define VBOX_INCLUDED_SRC_testcase_VDIoBackendMem_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/sg.h>
+
+#include "VDDefs.h"
+
+/** Memory backend handle. */
+typedef struct VDIOBACKENDMEM *PVDIOBACKENDMEM;
+/** Pointer to a memory backend handle. */
+typedef PVDIOBACKENDMEM *PPVDIOBACKENDMEM;
+
+/**
+ * Completion handler.
+ *
+ * @returns IPRT status code.
+ * @param pvUser Opaque user data.
+ * @param rcReq Completion code for the request.
+ */
+typedef DECLCALLBACKTYPE(int, FNVDIOCOMPLETE,(void *pvUser, int rcReq));
+/** Pointer to a completion handler. */
+typedef FNVDIOCOMPLETE *PFNVDIOCOMPLETE;
+
+/**
+ * Creates a new memory I/O backend.
+ *
+ * @returns IPRT status code.
+ *
+ * @param ppIoBackend Where to store the handle on success.
+ */
+int VDIoBackendMemCreate(PPVDIOBACKENDMEM ppIoBackend);
+
+/**
+ * Destroys a memory I/O backend.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pIoBackend The backend to destroy.
+ */
+int VDIoBackendMemDestroy(PVDIOBACKENDMEM pIoBackend);
+
+/**
+ * Enqueues a new I/O request.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pIoBackend The backend which should handle the
+ * transfer.
+ * @param pMemDisk The memory disk the request is for.
+ * @param enmTxDir The transfer direction.
+ * @param off Start offset of the transfer.
+ * @param cbTransfer Size of the transfer.
+ * @param pSgBuf S/G buffer to use.
+ * @param pfnComplete Completion handler to call.
+ * @param pvUser Opaque user data.
+ */
+int VDIoBackendMemTransfer(PVDIOBACKENDMEM pIoBackend, PVDMEMDISK pMemDisk,
+ VDIOTXDIR enmTxDir, uint64_t off, size_t cbTransfer,
+ PRTSGBUF pSgBuf, PFNVDIOCOMPLETE pfnComplete, void *pvUser);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDIoBackendMem_h */
diff --git a/src/VBox/Storage/testcase/VDIoRnd.cpp b/src/VBox/Storage/testcase/VDIoRnd.cpp
new file mode 100644
index 00000000..70fae79b
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoRnd.cpp
@@ -0,0 +1,118 @@
+/* $Id: VDIoRnd.cpp $ */
+/** @file
+ * VBox HDD container test utility - I/O data generator.
+ */
+
+/*
+ * Copyright (C) 2011-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 <iprt/log.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/assert.h>
+
+#include "VDIoRnd.h"
+
+/**
+ * I/O random data generator instance data.
+ */
+typedef struct VDIORND
+{
+ /** Pointer to the buffer holding the random data. */
+ uint8_t *pbPattern;
+ /** Size of the buffer. */
+ size_t cbPattern;
+ /** RNG */
+ RTRAND hRand;
+} VDIORND;
+
+int VDIoRndCreate(PPVDIORND ppIoRnd, size_t cbPattern, uint64_t uSeed)
+{
+ PVDIORND pIoRnd = NULL;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(ppIoRnd, VERR_INVALID_POINTER);
+
+ pIoRnd = (PVDIORND)RTMemAllocZ(sizeof(VDIORND));
+ if (pIoRnd)
+ pIoRnd->pbPattern = (uint8_t *)RTMemPageAllocZ(cbPattern);
+
+ if ( pIoRnd
+ && pIoRnd->pbPattern)
+ {
+ pIoRnd->cbPattern = cbPattern;
+
+ rc = RTRandAdvCreateParkMiller(&pIoRnd->hRand);
+ if (RT_SUCCESS(rc))
+ {
+ RTRandAdvSeed(pIoRnd->hRand, uSeed);
+ RTRandAdvBytes(pIoRnd->hRand, pIoRnd->pbPattern, cbPattern);
+ }
+ else
+ {
+ RTMemPageFree(pIoRnd->pbPattern, cbPattern);
+ RTMemFree(pIoRnd);
+ }
+ }
+ else
+ {
+ if (pIoRnd)
+ RTMemFree(pIoRnd);
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppIoRnd = pIoRnd;
+
+ return rc;
+}
+
+void VDIoRndDestroy(PVDIORND pIoRnd)
+{
+ AssertPtrReturnVoid(pIoRnd);
+
+ RTRandAdvDestroy(pIoRnd->hRand);
+ RTMemPageFree(pIoRnd->pbPattern, pIoRnd->cbPattern);
+ RTMemFree(pIoRnd);
+}
+
+int VDIoRndGetBuffer(PVDIORND pIoRnd, void **ppv, size_t cb)
+{
+ AssertPtrReturn(pIoRnd, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppv, VERR_INVALID_POINTER);
+ AssertReturn(cb > 0, VERR_INVALID_PARAMETER);
+
+ if (cb > pIoRnd->cbPattern - 512)
+ return VERR_INVALID_PARAMETER;
+
+ *ppv = pIoRnd->pbPattern + RT_ALIGN_64(RTRandAdvU64Ex(pIoRnd->hRand, 0, pIoRnd->cbPattern - cb - 512), 512);
+ return VINF_SUCCESS;
+}
+
+
+uint32_t VDIoRndGetU32Ex(PVDIORND pIoRnd, uint32_t uMin, uint32_t uMax)
+{
+ return RTRandAdvU32Ex(pIoRnd->hRand, uMin, uMax);
+}
+
diff --git a/src/VBox/Storage/testcase/VDIoRnd.h b/src/VBox/Storage/testcase/VDIoRnd.h
new file mode 100644
index 00000000..a648d6a9
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDIoRnd.h
@@ -0,0 +1,69 @@
+/** @file
+ *
+ * VBox HDD container test utility - I/O data generator.
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDIoRnd_h
+#define VBOX_INCLUDED_SRC_testcase_VDIoRnd_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** Pointer to the I/O random number generator. */
+typedef struct VDIORND *PVDIORND;
+/** Pointer to a I/O random number generator pointer. */
+typedef PVDIORND *PPVDIORND;
+
+/**
+ * Creates a I/O RNG.
+ *
+ * @returns VBox status code.
+ *
+ * @param ppIoRnd Where to store the handle on success.
+ * @param cbPattern Size of the test pattern to create.
+ * @param uSeed Seed for the RNG.
+ */
+int VDIoRndCreate(PPVDIORND ppIoRnd, size_t cbPattern, uint64_t uSeed);
+
+/**
+ * Destroys the I/O RNG.
+ *
+ * @param pIoRnd I/O RNG handle.
+ */
+void VDIoRndDestroy(PVDIORND pIoRnd);
+
+/**
+ * Returns a pointer filled with random data of the given size.
+ *
+ * @returns VBox status code.
+ *
+ * @param pIoRnd I/O RNG handle.
+ * @param ppv Where to store the pointer on success.
+ * @param cb Size of the buffer.
+ */
+int VDIoRndGetBuffer(PVDIORND pIoRnd, void **ppv, size_t cb);
+
+uint32_t VDIoRndGetU32Ex(PVDIORND pIoRnd, uint32_t uMin, uint32_t uMax);
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDIoRnd_h */
diff --git a/src/VBox/Storage/testcase/VDMemDisk.cpp b/src/VBox/Storage/testcase/VDMemDisk.cpp
new file mode 100644
index 00000000..16156a5e
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDMemDisk.cpp
@@ -0,0 +1,418 @@
+/* $Id: VDMemDisk.cpp $ */
+/** @file
+ * VBox HDD container test utility, memory disk/file.
+ */
+
+/*
+ * Copyright (C) 2011-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 /** @todo Log group */
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/mem.h>
+#include <iprt/file.h>
+
+#include "VDMemDisk.h"
+
+/**
+ * Memory disk/file.
+ */
+typedef struct VDMEMDISK
+{
+ /** Current size of the disk. */
+ uint64_t cbDisk;
+ /** Flag whether the disk can grow. */
+ bool fGrowable;
+ /** Pointer to the AVL tree holding the segments. */
+ PAVLRU64TREE pTreeSegments;
+} VDMEMDISK;
+
+/**
+ * A disk segment.
+ */
+typedef struct VDMEMDISKSEG
+{
+ /** AVL tree core. */
+ AVLRU64NODECORE Core;
+ /** Pointer to the data. */
+ void *pvSeg;
+} VDMEMDISKSEG, *PVDMEMDISKSEG;
+
+
+int VDMemDiskCreate(PPVDMEMDISK ppMemDisk, uint64_t cbSize)
+{
+ AssertPtrReturn(ppMemDisk, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ PVDMEMDISK pMemDisk = (PVDMEMDISK)RTMemAllocZ(sizeof(VDMEMDISK));
+ if (pMemDisk)
+ {
+ pMemDisk->fGrowable = cbSize ? false : true;
+ pMemDisk->cbDisk = cbSize;
+ pMemDisk->pTreeSegments = (PAVLRU64TREE)RTMemAllocZ(sizeof(AVLRU64TREE));
+ if (pMemDisk->pTreeSegments)
+ *ppMemDisk = pMemDisk;
+ else
+ {
+ RTMemFree(pMemDisk);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdMemDiskDestroy(PAVLRU64NODECORE pNode, void *pvUser)
+{
+ RT_NOREF1(pvUser);
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)pNode;
+ RTMemFree(pSeg->pvSeg);
+ RTMemFree(pSeg);
+ return VINF_SUCCESS;
+}
+
+void VDMemDiskDestroy(PVDMEMDISK pMemDisk)
+{
+ AssertPtrReturnVoid(pMemDisk);
+
+ RTAvlrU64Destroy(pMemDisk->pTreeSegments, vdMemDiskDestroy, NULL);
+ RTMemFree(pMemDisk->pTreeSegments);
+ RTMemFree(pMemDisk);
+}
+
+int VDMemDiskWrite(PVDMEMDISK pMemDisk, uint64_t off, size_t cbWrite, PRTSGBUF pSgBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pMemDisk=%#p off=%llu cbWrite=%zu pSgBuf=%#p\n",
+ pMemDisk, off, cbWrite, pSgBuf));
+
+ AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSgBuf, VERR_INVALID_POINTER);
+
+ /* Check for a write beyond the end of a disk. */
+ if ( !pMemDisk->fGrowable
+ && (off + cbWrite) > pMemDisk->cbDisk)
+ return VERR_INVALID_PARAMETER;
+
+ /* Update the segments */
+ size_t cbLeft = cbWrite;
+ uint64_t offCurr = off;
+
+ while ( cbLeft
+ && RT_SUCCESS(rc))
+ {
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64RangeGet(pMemDisk->pTreeSegments, offCurr);
+ size_t cbRange = 0;
+ unsigned offSeg = 0;
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, offCurr, true);
+ if ( !pSeg
+ || offCurr + cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+
+ /* Create new segment */
+ pSeg = (PVDMEMDISKSEG)RTMemAllocZ(sizeof(VDMEMDISKSEG));
+ if (pSeg)
+ {
+ pSeg->Core.Key = offCurr;
+ pSeg->Core.KeyLast = offCurr + cbRange - 1;
+ pSeg->pvSeg = RTMemAllocZ(cbRange);
+
+ if (!pSeg->pvSeg)
+ {
+ RTMemFree(pSeg);
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ bool fInserted = RTAvlrU64Insert(pMemDisk->pTreeSegments, &pSeg->Core);
+ AssertMsg(fInserted, ("Bug!\n")); NOREF(fInserted);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ AssertPtr(pSeg);
+ size_t cbCopied = RTSgBufCopyToBuf(pSgBuf, (uint8_t *)pSeg->pvSeg + offSeg, cbRange);
+ Assert(cbCopied == cbRange); NOREF(cbCopied);
+ }
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ /* Update size of the disk. */
+ if ( RT_SUCCESS(rc)
+ && pMemDisk->fGrowable
+ && (off + cbWrite) > pMemDisk->cbDisk)
+ {
+ pMemDisk->cbDisk = off + cbWrite;
+ }
+
+ return rc;
+}
+
+
+int VDMemDiskRead(PVDMEMDISK pMemDisk, uint64_t off, size_t cbRead, PRTSGBUF pSgBuf)
+{
+ LogFlowFunc(("pMemDisk=%#p off=%llu cbRead=%zu pSgBuf=%#p\n",
+ pMemDisk, off, cbRead, pSgBuf));
+
+ AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSgBuf, VERR_INVALID_POINTER);
+
+ /* Check for a read beyond the end of a disk. */
+ if ((off + cbRead) > pMemDisk->cbDisk)
+ return VERR_INVALID_PARAMETER;
+
+ /* Compare read data */
+ size_t cbLeft = cbRead;
+ uint64_t offCurr = off;
+
+ while (cbLeft)
+ {
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64RangeGet(pMemDisk->pTreeSegments, offCurr);
+ size_t cbRange = 0;
+ unsigned offSeg = 0;
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, offCurr, true);
+ if ( !pSeg
+ || offCurr + cbLeft <= pSeg->Core.Key)
+ {
+ /* No data in the tree for this read. Fill with 0. */
+ cbRange = cbLeft;
+ }
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+
+ RTSgBufSet(pSgBuf, 0, cbRange);
+ }
+ else
+ {
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+
+ RTSgBufCopyFromBuf(pSgBuf, (uint8_t *)pSeg->pvSeg + offSeg, cbRange);
+ }
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ return VINF_SUCCESS;
+}
+
+int VDMemDiskSetSize(PVDMEMDISK pMemDisk, uint64_t cbSize)
+{
+ AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER);
+
+ if (!pMemDisk->fGrowable)
+ return VERR_NOT_SUPPORTED;
+
+ if (pMemDisk->cbDisk <= cbSize)
+ {
+ /* Increase. */
+ pMemDisk->cbDisk = cbSize;
+ }
+ else
+ {
+ /* We have to delete all parts beyond the new end. */
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64Get(pMemDisk->pTreeSegments, cbSize);
+ if (pSeg)
+ {
+ RTAvlrU64Remove(pMemDisk->pTreeSegments, pSeg->Core.Key);
+ if (pSeg->Core.Key < cbSize)
+ {
+ /* Cut off the part which is not in the file anymore. */
+ pSeg->pvSeg = RTMemRealloc(pSeg->pvSeg, pSeg->Core.KeyLast - cbSize + 1);
+ pSeg->Core.KeyLast = cbSize - pSeg->Core.Key - 1;
+
+ bool fInserted = RTAvlrU64Insert(pMemDisk->pTreeSegments, &pSeg->Core);
+ AssertMsg(fInserted, ("Bug!\n")); NOREF(fInserted);
+ }
+ else
+ {
+ /* Free the whole block. */
+ RTMemFree(pSeg->pvSeg);
+ RTMemFree(pSeg);
+ }
+ }
+
+ /* Kill all blocks coming after. */
+ do
+ {
+ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, cbSize, true);
+ if (pSeg)
+ {
+ RTAvlrU64Remove(pMemDisk->pTreeSegments, pSeg->Core.Key);
+ RTMemFree(pSeg->pvSeg);
+ pSeg->pvSeg = NULL;
+ RTMemFree(pSeg);
+ }
+ else
+ break;
+ } while (true);
+
+ pMemDisk->cbDisk = cbSize;
+ }
+
+ return VINF_SUCCESS;
+}
+
+int VDMemDiskGetSize(PVDMEMDISK pMemDisk, uint64_t *pcbSize)
+{
+ AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+
+ *pcbSize = pMemDisk->cbDisk;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes a segment to the given file.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pNode The disk segment to write to the file.
+ * @param pvParam Opaque user data containing the pointer to
+ * the file handle.
+ */
+static DECLCALLBACK(int) vdMemDiskSegmentWriteToFile(PAVLRU64NODECORE pNode, void *pvParam)
+{
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)pNode;
+ RTFILE hFile = *(PRTFILE)pvParam;
+
+ return RTFileWriteAt(hFile, pSeg->Core.Key, pSeg->pvSeg, pSeg->Core.KeyLast - pSeg->Core.Key + 1, NULL);
+}
+
+int VDMemDiskWriteToFile(PVDMEMDISK pMemDisk, const char *pcszFilename)
+{
+ int rc = VINF_SUCCESS;
+ RTFILE hFile = NIL_RTFILE;
+
+ LogFlowFunc(("pMemDisk=%#p pcszFilename=%s\n", pMemDisk, pcszFilename));
+ AssertPtrReturn(pMemDisk, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszFilename, VERR_INVALID_POINTER);
+
+ rc = RTFileOpen(&hFile, pcszFilename, RTFILE_O_DENY_NONE | RTFILE_O_CREATE | RTFILE_O_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAvlrU64DoWithAll(pMemDisk->pTreeSegments, true, vdMemDiskSegmentWriteToFile, &hFile);
+
+ RTFileClose(hFile);
+ if (RT_FAILURE(rc))
+ RTFileDelete(pcszFilename);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+int VDMemDiskReadFromFile(PVDMEMDISK pMemDisk, const char *pcszFilename)
+{
+ RT_NOREF2(pMemDisk, pcszFilename);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int VDMemDiskCmp(PVDMEMDISK pMemDisk, uint64_t off, size_t cbCmp, PRTSGBUF pSgBuf)
+{
+ LogFlowFunc(("pMemDisk=%#p off=%llx cbCmp=%u pSgBuf=%#p\n",
+ pMemDisk, off, cbCmp, pSgBuf));
+
+ /* Compare data */
+ size_t cbLeft = cbCmp;
+ uint64_t offCurr = off;
+
+ while (cbLeft)
+ {
+ PVDMEMDISKSEG pSeg = (PVDMEMDISKSEG)RTAvlrU64Get(pMemDisk->pTreeSegments, offCurr);
+ size_t cbRange = 0;
+ bool fCmp = false;
+ unsigned offSeg = 0;
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PVDMEMDISKSEG)RTAvlrU64GetBestFit(pMemDisk->pTreeSegments, offCurr, true);
+ if (!pSeg)
+ {
+ /* No data in the tree for this read. Assume everything is ok. */
+ cbRange = cbLeft;
+ }
+ else if (offCurr + cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+ }
+ else
+ {
+ fCmp = true;
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+ }
+
+ if (fCmp)
+ {
+ RTSGSEG Seg;
+ RTSGBUF SgBufCmp;
+ size_t cbOff = 0;
+ int rc = 0;
+
+ Seg.cbSeg = cbRange;
+ Seg.pvSeg = (uint8_t *)pSeg->pvSeg + offSeg;
+
+ RTSgBufInit(&SgBufCmp, &Seg, 1);
+ rc = RTSgBufCmpEx(pSgBuf, &SgBufCmp, cbRange, &cbOff, true);
+ if (rc)
+ return rc;
+ }
+ else
+ RTSgBufAdvance(pSgBuf, cbRange);
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ return 0;
+}
+
diff --git a/src/VBox/Storage/testcase/VDMemDisk.h b/src/VBox/Storage/testcase/VDMemDisk.h
new file mode 100644
index 00000000..cae29133
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDMemDisk.h
@@ -0,0 +1,141 @@
+/** @file
+ *
+ * VBox HDD container test utility, memory disk/file.
+ */
+
+/*
+ * 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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDMemDisk_h
+#define VBOX_INCLUDED_SRC_testcase_VDMemDisk_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/sg.h>
+
+/** Handle to the a memory disk. */
+typedef struct VDMEMDISK *PVDMEMDISK;
+/** Pointer to a memory disk handle. */
+typedef PVDMEMDISK *PPVDMEMDISK;
+
+/**
+ * Creates a new memory disk with the given size.
+ *
+ * @returns VBOX status code.
+ *
+ * @param ppMemDisk Where to store the memory disk handle.
+ * @param cbSize Size of the disk if it is fixed.
+ * If 0 the disk grows when it is written to
+ * and the size can be changed with
+ * VDMemDiskSetSize().
+ */
+int VDMemDiskCreate(PPVDMEMDISK ppMemDisk, uint64_t cbSize);
+
+/**
+ * Destroys a memory disk.
+ *
+ * @param pMemDisk The memory disk to destroy.
+ */
+void VDMemDiskDestroy(PVDMEMDISK pMemDisk);
+
+/**
+ * Writes the specified amount of data from the S/G buffer at
+ * the given offset.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param off Where to start writing to.
+ * @param cbWrite How many bytes to write.
+ * @param pSgBuf The S/G buffer to write from.
+ */
+int VDMemDiskWrite(PVDMEMDISK pMemDisk, uint64_t off, size_t cbWrite, PRTSGBUF pSgBuf);
+
+/**
+ * Reads the specified amount of data into the S/G buffer
+ * starting from the given offset.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param off Where to start reading from.
+ * @param cbRead The amount of bytes to read.
+ * @param pSgBuf The S/G buffer to read into.
+ */
+int VDMemDiskRead(PVDMEMDISK pMemDisk, uint64_t off, size_t cbRead, PRTSGBUF pSgBuf);
+
+/**
+ * Sets the size of the memory disk.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param cbSize The new size to set.
+ */
+int VDMemDiskSetSize(PVDMEMDISK pMemDisk, uint64_t cbSize);
+
+/**
+ * Gets the current size of the memory disk.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param pcbSize Where to store the size of the memory
+ * disk.
+ */
+int VDMemDiskGetSize(PVDMEMDISK pMemDisk, uint64_t *pcbSize);
+
+/**
+ * Dumps the memory disk to a file.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param pcszFilename Where to dump the content.
+ */
+int VDMemDiskWriteToFile(PVDMEMDISK pMemDisk, const char *pcszFilename);
+
+/**
+ * Reads the content of a file into the given memory disk.
+ * All data stored in the memory disk will be overwritten.
+ *
+ * @returns VBox status code.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param pcszFilename The file to load from.
+ */
+int VDMemDiskReadFromFile(PVDMEMDISK pMemDisk, const char *pcszFilename);
+
+/**
+ * Compares the given range of the memory disk with a provided S/G buffer.
+ *
+ * @returns whatever memcmp returns.
+ *
+ * @param pMemDisk The memory disk handle.
+ * @param off Where to start comparing.
+ * @param cbCmp How many bytes to compare.
+ * @param pSgBuf The S/G buffer to compare with.
+ */
+int VDMemDiskCmp(PVDMEMDISK pMemDisk, uint64_t off, size_t cbCmp, PRTSGBUF pSgBuf);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDMemDisk_h */
diff --git a/src/VBox/Storage/testcase/VDScript.cpp b/src/VBox/Storage/testcase/VDScript.cpp
new file mode 100644
index 00000000..0ff3e8a5
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScript.cpp
@@ -0,0 +1,3006 @@
+/* $Id: VDScript.cpp $ */
+/** @file
+ * VBox HDD container test utility - scripting engine.
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+/** @page pg_vd_script VDScript - Simple scripting language for VD I/O testing.
+ *
+ * This component implements a very simple scripting language to make testing the VD
+ * library more flexible and testcases faster to implement without the need to recompile
+ * everything after it changed.
+ * The language is a small subset of the C language. It doesn't support unions, structs,
+ * global variables, typedefed types or pointers (yet). It also adds a boolean and a string type.
+ * Strings are immutable and only to print messages from the script.
+ * There are also not the default types like int or unsigned because theire ranges are architecture
+ * dependent. Instead VDScript uses uint8_t, int8_t, ... as primitive types.
+ *
+ * Why inventing a completely new language?
+ *
+ * Well it is not a completely new language to start with, it is a subset of C and the
+ * language can be extended later on to reach the full C language later on.
+ * Second, there is no static typed scripting language I like which could be implemented
+ * and finally because I can ;)
+ * The code implementing the scripting engine is designed to be easily incorporated into other
+ * code. Could be used as a scripting language for the VBox debugger for example or in the scm
+ * tool to automatically rewrite C code using the AST VDSCript generates...
+ *
+ * The syntax of VDSCript is derived from the C syntax. The syntax of C in BNF was taken
+ * from: http://www.csci.csusb.edu/dick/samples/c.syntax.html
+ * and: http://slps.github.com/zoo/c/iso-9899-tc3.html
+ * and: http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf
+ */
+
+#define LOGGROUP LOGGROUP_DEFAULT
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+
+#include "VDScriptAst.h"
+#include "VDScriptInternal.h"
+
+/**
+ * VD script token class.
+ */
+typedef enum VDTOKENCLASS
+{
+ /** Invalid. */
+ VDTOKENCLASS_INVALID = 0,
+ /** Identifier class. */
+ VDTOKENCLASS_IDENTIFIER,
+ /** Numerical constant. */
+ VDTOKENCLASS_NUMCONST,
+ /** String constant. */
+ VDTOKENCLASS_STRINGCONST,
+ /** Operators */
+ VDTOKENCLASS_OPERATORS,
+ /** Reserved keyword */
+ VDTOKENCLASS_KEYWORD,
+ /** Punctuator */
+ VDTOKENCLASS_PUNCTUATOR,
+ /** End of stream */
+ VDTOKENCLASS_EOS,
+ /** 32bit hack. */
+ VDTOKENCLASS_32BIT_HACK = 0x7fffffff
+} VDTOKENCLASS;
+/** Pointer to a token class. */
+typedef VDTOKENCLASS *PVDTOKENCLASS;
+
+/**
+ * Keyword types.
+ */
+typedef enum VDSCRIPTTOKENKEYWORD
+{
+ VDSCRIPTTOKENKEYWORD_INVALID = 0,
+ VDSCRIPTTOKENKEYWORD_CONTINUE,
+ VDSCRIPTTOKENKEYWORD_REGISTER,
+ VDSCRIPTTOKENKEYWORD_RESTRICT,
+ VDSCRIPTTOKENKEYWORD_VOLATILE,
+ VDSCRIPTTOKENKEYWORD_TYPEDEF,
+ VDSCRIPTTOKENKEYWORD_DEFAULT,
+ VDSCRIPTTOKENKEYWORD_EXTERN,
+ VDSCRIPTTOKENKEYWORD_STATIC,
+ VDSCRIPTTOKENKEYWORD_RETURN,
+ VDSCRIPTTOKENKEYWORD_SWITCH,
+ VDSCRIPTTOKENKEYWORD_STRUCT,
+ VDSCRIPTTOKENKEYWORD_WHILE,
+ VDSCRIPTTOKENKEYWORD_BREAK,
+ VDSCRIPTTOKENKEYWORD_CONST,
+ VDSCRIPTTOKENKEYWORD_FALSE,
+ VDSCRIPTTOKENKEYWORD_TRUE,
+ VDSCRIPTTOKENKEYWORD_ELSE,
+ VDSCRIPTTOKENKEYWORD_CASE,
+ VDSCRIPTTOKENKEYWORD_AUTO,
+ VDSCRIPTTOKENKEYWORD_FOR,
+ VDSCRIPTTOKENKEYWORD_IF,
+ VDSCRIPTTOKENKEYWORD_DO,
+ VDSCRIPTTOKENKEYWORD_32BIT_HACK = 0x7fffffff
+} VDSCRIPTTOKENKEYWORD;
+/** Pointer to a keyword type. */
+typedef VDSCRIPTTOKENKEYWORD *PVDSCRIPTTOKENKEYWORD;
+
+/**
+ * VD script token.
+ */
+typedef struct VDSCRIPTTOKEN
+{
+ /** Token class. */
+ VDTOKENCLASS enmClass;
+ /** Token position in the source buffer. */
+ VDSRCPOS Pos;
+ /** Data based on the token class. */
+ union
+ {
+ /** Identifier. */
+ struct
+ {
+ /** Pointer to the start of the identifier. */
+ const char *pszIde;
+ /** Number of characters for the identifier excluding the null terminator. */
+ size_t cchIde;
+ } Ide;
+ /** Numerical constant. */
+ struct
+ {
+ uint64_t u64;
+ } NumConst;
+ /** String constant */
+ struct
+ {
+ /** Pointer to the start of the string constant. */
+ const char *pszString;
+ /** Number of characters of the string, including the null terminator. */
+ size_t cchString;
+ } StringConst;
+ /** Operator */
+ struct
+ {
+ /** The operator string. */
+ char aszOp[4]; /** Maximum of 3 for >>= + null terminator. */
+ } Operator;
+ /** Keyword. */
+ struct
+ {
+ /** The keyword type. */
+ VDSCRIPTTOKENKEYWORD enmKeyword;
+ } Keyword;
+ /** Punctuator. */
+ struct
+ {
+ /** The punctuator in question. */
+ char chPunctuator;
+ } Punctuator;
+ } Class;
+} VDSCRIPTTOKEN;
+/** Pointer to a script token. */
+typedef VDSCRIPTTOKEN *PVDSCRIPTTOKEN;
+/** Pointer to a const script token. */
+typedef const VDSCRIPTTOKEN *PCVDSCRIPTTOKEN;
+
+/**
+ * Tokenizer state.
+ */
+typedef struct VDTOKENIZER
+{
+ /** Char buffer to read from. */
+ const char *pszInput;
+ /** Current position ininput buffer. */
+ VDSRCPOS Pos;
+ /** Token 1. */
+ VDSCRIPTTOKEN Token1;
+ /** Token 2. */
+ VDSCRIPTTOKEN Token2;
+ /** Pointer to the current active token. */
+ PVDSCRIPTTOKEN pTokenCurr;
+ /** The next token in the input stream (used for peeking). */
+ PVDSCRIPTTOKEN pTokenNext;
+} VDTOKENIZER;
+
+/**
+ * Operators entry.
+ */
+typedef struct VDSCRIPTOP
+{
+ /** Operator string. */
+ const char *pszOp;
+ /** Size of the operator in characters without zero terminator. */
+ size_t cchOp;
+} VDSCRIPTOP;
+/** Pointer to a script operator. */
+typedef VDSCRIPTOP *PVDSCRIPTOP;
+
+/**
+ * Known operators array, sort from higest character count to lowest.
+ */
+static VDSCRIPTOP g_aScriptOps[] =
+{
+ {">>=", 3},
+ {"<<=", 3},
+ {"+=", 2},
+ {"-=", 2},
+ {"/=", 2},
+ {"%=", 2},
+ {"&=", 2},
+ {"|=", 2},
+ {"^=", 2},
+ {"&&", 2},
+ {"||", 2},
+ {"<<", 2},
+ {">>", 2},
+ {"++", 2},
+ {"--", 2},
+ {"==", 2},
+ {"!=", 2},
+ {">=", 2},
+ {"<=", 2},
+ {"->", 2},
+ {"=", 1},
+ {"+", 1},
+ {"-", 1},
+ {"*", 1},
+ {"/", 1},
+ {"%", 1},
+ {"|", 1},
+ {"&", 1},
+ {"^", 1},
+ {"<", 1},
+ {">", 1},
+ {"!", 1},
+ {"~", 1},
+ {".", 1}
+};
+
+/**
+ * Known punctuators.
+ */
+static VDSCRIPTOP g_aScriptPunctuators[] =
+{
+ {"(", 1},
+ {")", 1},
+ {"{", 1},
+ {"}", 1},
+ {",", 1},
+ {";", 1},
+};
+
+/**
+ * Keyword entry.
+ */
+typedef struct VDSCRIPTKEYWORD
+{
+ /** Keyword string. */
+ const char *pszKeyword;
+ /** Size of the string in characters without zero terminator. */
+ size_t cchKeyword;
+ /** Keyword type. */
+ VDSCRIPTTOKENKEYWORD enmKeyword;
+} VDSCRIPTKEYWORD;
+/** */
+typedef VDSCRIPTKEYWORD *PVDSCRIPTKEYWORD;
+
+/**
+ * Known keywords.
+ */
+static VDSCRIPTKEYWORD g_aKeywords[] =
+{
+ {RT_STR_TUPLE("continue"), VDSCRIPTTOKENKEYWORD_CONTINUE},
+ {RT_STR_TUPLE("register"), VDSCRIPTTOKENKEYWORD_REGISTER},
+ {RT_STR_TUPLE("restrict"), VDSCRIPTTOKENKEYWORD_RESTRICT},
+ {RT_STR_TUPLE("volatile"), VDSCRIPTTOKENKEYWORD_VOLATILE},
+ {RT_STR_TUPLE("typedef"), VDSCRIPTTOKENKEYWORD_TYPEDEF},
+ {RT_STR_TUPLE("default"), VDSCRIPTTOKENKEYWORD_DEFAULT},
+ {RT_STR_TUPLE("extern"), VDSCRIPTTOKENKEYWORD_EXTERN},
+ {RT_STR_TUPLE("static"), VDSCRIPTTOKENKEYWORD_STATIC},
+ {RT_STR_TUPLE("return"), VDSCRIPTTOKENKEYWORD_RETURN},
+ {RT_STR_TUPLE("switch"), VDSCRIPTTOKENKEYWORD_SWITCH},
+ {RT_STR_TUPLE("struct"), VDSCRIPTTOKENKEYWORD_STRUCT},
+ {RT_STR_TUPLE("while"), VDSCRIPTTOKENKEYWORD_WHILE},
+ {RT_STR_TUPLE("break"), VDSCRIPTTOKENKEYWORD_BREAK},
+ {RT_STR_TUPLE("const"), VDSCRIPTTOKENKEYWORD_CONST},
+ {RT_STR_TUPLE("false"), VDSCRIPTTOKENKEYWORD_FALSE},
+ {RT_STR_TUPLE("true"), VDSCRIPTTOKENKEYWORD_TRUE},
+ {RT_STR_TUPLE("else"), VDSCRIPTTOKENKEYWORD_ELSE},
+ {RT_STR_TUPLE("case"), VDSCRIPTTOKENKEYWORD_CASE},
+ {RT_STR_TUPLE("auto"), VDSCRIPTTOKENKEYWORD_AUTO},
+ {RT_STR_TUPLE("for"), VDSCRIPTTOKENKEYWORD_FOR},
+ {RT_STR_TUPLE("if"), VDSCRIPTTOKENKEYWORD_IF},
+ {RT_STR_TUPLE("do"), VDSCRIPTTOKENKEYWORD_DO}
+};
+
+static int vdScriptParseCompoundStatement(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSTMT *ppAstNodeCompound);
+static int vdScriptParseStatement(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSTMT *ppAstNodeStmt);
+static int vdScriptParseExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr);
+static int vdScriptParseAssignmentExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr);
+static int vdScriptParseCastExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr);
+#if 0 /* unused */
+static int vdScriptParseConstExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr);
+#endif
+
+/**
+ * Returns whether the tokenizer reached the end of the stream.
+ *
+ * @returns true if the tokenizer reached the end of stream marker
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(bool) vdScriptTokenizerIsEos(PVDTOKENIZER pTokenizer)
+{
+ return *pTokenizer->pszInput == '\0';
+}
+
+/**
+ * Skip one character in the input stream.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) vdScriptTokenizerSkipCh(PVDTOKENIZER pTokenizer)
+{
+ pTokenizer->pszInput++;
+ pTokenizer->Pos.iChStart++;
+ pTokenizer->Pos.iChEnd++;
+}
+
+/**
+ * Returns the next char in the input buffer without advancing it.
+ *
+ * @returns Next character in the input buffer.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(char) vdScriptTokenizerPeekCh(PVDTOKENIZER pTokenizer)
+{
+ return vdScriptTokenizerIsEos(pTokenizer)
+ ? '\0'
+ : *(pTokenizer->pszInput + 1);
+}
+
+/**
+ * Returns the next character in the input buffer advancing the internal
+ * position.
+ *
+ * @returns Next character in the stream.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(char) vdScriptTokenizerGetCh(PVDTOKENIZER pTokenizer)
+{
+ char ch;
+
+ if (vdScriptTokenizerIsEos(pTokenizer))
+ ch = '\0';
+ else
+ ch = *pTokenizer->pszInput;
+
+ return ch;
+}
+
+/**
+ * Sets a new line for the tokenizer.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) vdScriptTokenizerNewLine(PVDTOKENIZER pTokenizer, unsigned cSkip)
+{
+ pTokenizer->pszInput += cSkip;
+ pTokenizer->Pos.iLine++;
+ pTokenizer->Pos.iChStart = 1;
+ pTokenizer->Pos.iChEnd = 1;
+}
+
+/**
+ * Checks whether the current position in the input stream is a new line
+ * and skips it.
+ *
+ * @returns Flag whether there was a new line at the current position
+ * in the input buffer.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(bool) vdScriptTokenizerIsSkipNewLine(PVDTOKENIZER pTokenizer)
+{
+ bool fNewline = true;
+
+ if ( vdScriptTokenizerGetCh(pTokenizer) == '\r'
+ && vdScriptTokenizerPeekCh(pTokenizer) == '\n')
+ vdScriptTokenizerNewLine(pTokenizer, 2);
+ else if (vdScriptTokenizerGetCh(pTokenizer) == '\n')
+ vdScriptTokenizerNewLine(pTokenizer, 1);
+ else
+ fNewline = false;
+
+ return fNewline;
+}
+
+/**
+ * Skips a multi line comment.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) vdScriptTokenizerSkipComment(PVDTOKENIZER pTokenizer)
+{
+ while ( !vdScriptTokenizerIsEos(pTokenizer)
+ && ( vdScriptTokenizerGetCh(pTokenizer) != '*'
+ || vdScriptTokenizerPeekCh(pTokenizer) != '/'))
+ {
+ if (!vdScriptTokenizerIsSkipNewLine(pTokenizer))
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+
+ if (!vdScriptTokenizerIsEos(pTokenizer))
+ vdScriptTokenizerSkipCh(pTokenizer);
+ if (!vdScriptTokenizerIsEos(pTokenizer))
+ vdScriptTokenizerSkipCh(pTokenizer);
+}
+
+/**
+ * Skip all whitespace starting from the current input buffer position.
+ * Skips all present comments too.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) vdScriptTokenizerSkipWhitespace(PVDTOKENIZER pTokenizer)
+{
+ while (!vdScriptTokenizerIsEos(pTokenizer))
+ {
+ while ( vdScriptTokenizerGetCh(pTokenizer) == ' '
+ || vdScriptTokenizerGetCh(pTokenizer) == '\t')
+ vdScriptTokenizerSkipCh(pTokenizer);
+
+ if ( !vdScriptTokenizerIsEos(pTokenizer)
+ && !vdScriptTokenizerIsSkipNewLine(pTokenizer))
+ {
+ if ( vdScriptTokenizerGetCh(pTokenizer) == '/'
+ && vdScriptTokenizerPeekCh(pTokenizer) == '*')
+ {
+ vdScriptTokenizerSkipCh(pTokenizer);
+ vdScriptTokenizerSkipCh(pTokenizer);
+ vdScriptTokenizerSkipComment(pTokenizer);
+ }
+ else
+ break; /* Skipped everything, next is some real content. */
+ }
+ }
+}
+
+/**
+ * Get an identifier token from the tokenizer.
+ *
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void vdScriptTokenizerGetIdeOrKeyword(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ char ch;
+ unsigned cchIde = 0;
+ bool fIsKeyword = false;
+ const char *pszIde = pTokenizer->pszInput;
+
+ pToken->Pos = pTokenizer->Pos;
+
+ Assert(RT_C_IS_ALPHA(*pszIde) || *pszIde == '_' );
+
+ do
+ {
+ cchIde++;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ ch = vdScriptTokenizerGetCh(pTokenizer);
+ }
+ while (RT_C_IS_ALNUM(ch) || ch == '_');
+
+ /* Check whether we got an identifier or an reserved keyword. */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aKeywords); i++)
+ {
+ if (!RTStrNCmp(g_aKeywords[i].pszKeyword, pszIde, g_aKeywords[i].cchKeyword))
+ {
+ fIsKeyword = true;
+ pToken->enmClass = VDTOKENCLASS_KEYWORD;
+ pToken->Class.Keyword.enmKeyword = g_aKeywords[i].enmKeyword;
+ break;
+ }
+ }
+
+ if (!fIsKeyword)
+ {
+ pToken->enmClass = VDTOKENCLASS_IDENTIFIER;
+ pToken->Class.Ide.pszIde = pszIde;
+ pToken->Class.Ide.cchIde = cchIde;
+ }
+ pToken->Pos.iChEnd += cchIde;
+}
+
+/**
+ * Get a numerical constant from the tokenizer.
+ *
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void vdScriptTokenizerGetNumberConst(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ char *pszNext = NULL;
+
+ Assert(RT_C_IS_DIGIT(vdScriptTokenizerGetCh(pTokenizer)));
+
+ /* Let RTStrToUInt64Ex() do all the work, looks C compliant :). */
+ pToken->enmClass = VDTOKENCLASS_NUMCONST;
+ int rc = RTStrToUInt64Ex(pTokenizer->pszInput, &pszNext, 0, &pToken->Class.NumConst.u64);
+ Assert(RT_SUCCESS(rc) || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES); NOREF(rc);
+ /** @todo Handle number to big, throw a warning */
+
+ unsigned cchNumber = pszNext - pTokenizer->pszInput;
+ for (unsigned i = 0; i < cchNumber; i++)
+ vdScriptTokenizerSkipCh(pTokenizer);
+
+ /* Check for a supported suffix, supported are K|M|G. */
+ if (vdScriptTokenizerGetCh(pTokenizer) == 'K')
+ {
+ pToken->Class.NumConst.u64 *= _1K;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+ else if (vdScriptTokenizerGetCh(pTokenizer) == 'M')
+ {
+ pToken->Class.NumConst.u64 *= _1M;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+ else if (vdScriptTokenizerGetCh(pTokenizer) == 'G')
+ {
+ pToken->Class.NumConst.u64 *= _1G;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+ else if (vdScriptTokenizerGetCh(pTokenizer) == 'T')
+ {
+ pToken->Class.NumConst.u64 *= _1T;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+}
+
+/**
+ * Parses a string constant.
+ *
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ *
+ * @remarks No escape sequences allowed at this time.
+ */
+static void vdScriptTokenizerGetStringConst(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ unsigned cchStr = 0;
+
+ Assert(vdScriptTokenizerGetCh(pTokenizer) == '\"');
+ vdScriptTokenizerSkipCh(pTokenizer); /* Skip " */
+
+ pToken->enmClass = VDTOKENCLASS_STRINGCONST;
+ pToken->Pos = pTokenizer->Pos;
+ pToken->Class.StringConst.pszString = pTokenizer->pszInput;
+
+ while (vdScriptTokenizerGetCh(pTokenizer) != '\"')
+ {
+ cchStr++;
+ vdScriptTokenizerSkipCh(pTokenizer);
+ }
+
+ vdScriptTokenizerSkipCh(pTokenizer); /* Skip closing " */
+
+ pToken->Class.StringConst.cchString = cchStr;
+ pToken->Pos.iChEnd += cchStr;
+}
+
+/**
+ * Get the end of stream token.
+ *
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void vdScriptTokenizerGetEos(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ Assert(vdScriptTokenizerGetCh(pTokenizer) == '\0');
+
+ pToken->enmClass = VDTOKENCLASS_EOS;
+ pToken->Pos = pTokenizer->Pos;
+}
+
+/**
+ * Get operator or punctuator token.
+ *
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static void vdScriptTokenizerGetOperatorOrPunctuator(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ bool fOpFound = false;
+
+ pToken->enmClass = VDTOKENCLASS_INVALID;
+ pToken->Pos = pTokenizer->Pos;
+
+ /*
+ * Use table based approach here, not the fastest solution but enough for our purpose
+ * for now.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aScriptOps); i++)
+ {
+ if (!RTStrNCmp(g_aScriptOps[i].pszOp, pTokenizer->pszInput, g_aScriptOps[i].cchOp))
+ {
+ memset(pToken->Class.Operator.aszOp, 0, sizeof(pToken->Class.Operator.aszOp));
+
+ int rc = RTStrCopy(pToken->Class.Operator.aszOp, sizeof(pToken->Class.Operator.aszOp), g_aScriptOps[i].pszOp);
+ AssertRC(rc);
+
+ pToken->enmClass = VDTOKENCLASS_OPERATORS;
+ pToken->Pos.iChEnd += (unsigned)g_aScriptOps[i].cchOp;
+
+ /** @todo Make this prettier. */
+ for (unsigned j = 0; j < g_aScriptOps[i].cchOp; j++)
+ vdScriptTokenizerSkipCh(pTokenizer);
+ fOpFound = true;
+ break;
+ }
+ }
+
+ if (!fOpFound)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aScriptPunctuators); i++)
+ {
+ if (!RTStrNCmp(g_aScriptPunctuators[i].pszOp, pTokenizer->pszInput, g_aScriptPunctuators[i].cchOp))
+ {
+ pToken->Pos.iChEnd += (unsigned)g_aScriptPunctuators[i].cchOp;
+ pToken->enmClass = VDTOKENCLASS_PUNCTUATOR;
+ pToken->Class.Punctuator.chPunctuator = *g_aScriptPunctuators[i].pszOp;
+
+ vdScriptTokenizerSkipCh(pTokenizer);
+ fOpFound = true;
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Read the next token from the tokenizer stream.
+ *
+ * @param pTokenizer The tokenizer to read from.
+ * @param pToken Uninitialized token to fill the token data into.
+ */
+static void vdScriptTokenizerReadNextToken(PVDTOKENIZER pTokenizer, PVDSCRIPTTOKEN pToken)
+{
+ /* Skip all eventually existing whitespace, newlines and comments first. */
+ vdScriptTokenizerSkipWhitespace(pTokenizer);
+
+ char ch = vdScriptTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_ALPHA(ch) || ch == '_')
+ vdScriptTokenizerGetIdeOrKeyword(pTokenizer, pToken);
+ else if (RT_C_IS_DIGIT(ch))
+ vdScriptTokenizerGetNumberConst(pTokenizer, pToken);
+ else if (ch == '\"')
+ vdScriptTokenizerGetStringConst(pTokenizer, pToken);
+ else if (ch == '\0')
+ vdScriptTokenizerGetEos(pTokenizer, pToken);
+ else
+ vdScriptTokenizerGetOperatorOrPunctuator(pTokenizer, pToken);
+}
+
+/**
+ * Create a new tokenizer.
+ *
+ * @returns Pointer to the new tokenizer state on success.
+ * NULL if out of memory.
+ * @param pszInput The input to create the tokenizer for.
+ */
+static PVDTOKENIZER vdScriptTokenizerCreate(const char *pszInput)
+{
+ PVDTOKENIZER pTokenizer = (PVDTOKENIZER)RTMemAllocZ(sizeof(VDTOKENIZER));
+ if (pTokenizer)
+ {
+ pTokenizer->pszInput = pszInput;
+ pTokenizer->Pos.iLine = 1;
+ pTokenizer->Pos.iChStart = 1;
+ pTokenizer->Pos.iChEnd = 1;
+ pTokenizer->pTokenCurr = &pTokenizer->Token1;
+ pTokenizer->pTokenNext = &pTokenizer->Token2;
+ /* Fill the tokenizer with two first tokens. */
+ vdScriptTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenCurr);
+ vdScriptTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
+ }
+
+ return pTokenizer;
+}
+
+#if 0 /** @todo unused */
+/**
+ * Destroys a given tokenizer state.
+ *
+ * @param pTokenizer The tokenizer to destroy.
+ */
+static void vdScriptTokenizerDestroy(PVDTOKENIZER pTokenizer)
+{
+ RTMemFree(pTokenizer);
+}
+#endif
+
+/**
+ * Get the current token in the input stream.
+ *
+ * @returns Pointer to the next token in the stream.
+ * @param pTokenizer The tokenizer to destroy.
+ */
+DECLINLINE(PCVDSCRIPTTOKEN) vdScriptTokenizerGetToken(PVDTOKENIZER pTokenizer)
+{
+ return pTokenizer->pTokenCurr;
+}
+
+/**
+ * Get the class of the current token.
+ *
+ * @returns Class of the current token.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(VDTOKENCLASS) vdScriptTokenizerGetTokenClass(PVDTOKENIZER pTokenizer)
+{
+ return pTokenizer->pTokenCurr->enmClass;
+}
+
+/**
+ * Returns the token class of the next token in the stream.
+ *
+ * @returns Token class of the next token.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(VDTOKENCLASS) vdScriptTokenizerPeekNextClass(PVDTOKENIZER pTokenizer)
+{
+ return pTokenizer->pTokenNext->enmClass;
+}
+
+/**
+ * Consume the current token advancing to the next in the stream.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+static void vdScriptTokenizerConsume(PVDTOKENIZER pTokenizer)
+{
+ PVDSCRIPTTOKEN pTokenTmp = pTokenizer->pTokenCurr;
+
+ /* Switch next token to current token and read in the next token. */
+ pTokenizer->pTokenCurr = pTokenizer->pTokenNext;
+ pTokenizer->pTokenNext = pTokenTmp;
+ vdScriptTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
+}
+
+/**
+ * Check whether the next token in the input stream is a punctuator and matches the given
+ * character.
+ *
+ * @returns true if the token matched.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param chCheck The punctuator to check against.
+ */
+static bool vdScriptTokenizerIsPunctuatorEqual(PVDTOKENIZER pTokenizer, char chCheck)
+{
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pTokenizer);
+
+ if ( pToken->enmClass == VDTOKENCLASS_PUNCTUATOR
+ && pToken->Class.Punctuator.chPunctuator == chCheck)
+ return true;
+
+ return false;
+}
+
+/**
+ * Check whether the next token in the input stream is a punctuator and matches the given
+ * character and skips it.
+ *
+ * @returns true if the token matched and was skipped.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param chCheck The punctuator to check against.
+ */
+static bool vdScriptTokenizerSkipIfIsPunctuatorEqual(PVDTOKENIZER pTokenizer, char chCheck)
+{
+ bool fEqual = vdScriptTokenizerIsPunctuatorEqual(pTokenizer, chCheck);
+ if (fEqual)
+ vdScriptTokenizerConsume(pTokenizer);
+
+ return fEqual;
+}
+
+/**
+ * Check whether the next token in the input stream is a keyword and matches the given
+ * keyword.
+ *
+ * @returns true if the token matched.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param enmKey The keyword to check against.
+ */
+static bool vdScriptTokenizerIsKeywordEqual(PVDTOKENIZER pTokenizer, VDSCRIPTTOKENKEYWORD enmKeyword)
+{
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pTokenizer);
+
+ if ( pToken->enmClass == VDTOKENCLASS_KEYWORD
+ && pToken->Class.Keyword.enmKeyword == enmKeyword)
+ return true;
+
+ return false;
+}
+
+/**
+ * Check whether the next token in the input stream is a keyword and matches the given
+ * keyword and skips it.
+ *
+ * @returns true if the token matched and was skipped.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param enmKey The keyword to check against.
+ */
+static bool vdScriptTokenizerSkipIfIsKeywordEqual(PVDTOKENIZER pTokenizer, VDSCRIPTTOKENKEYWORD enmKeyword)
+{
+ bool fEqual = vdScriptTokenizerIsKeywordEqual(pTokenizer, enmKeyword);
+ if (fEqual)
+ vdScriptTokenizerConsume(pTokenizer);
+
+ return fEqual;
+}
+
+/**
+ * Check whether the next token in the input stream is a keyword and matches the given
+ * keyword.
+ *
+ * @returns true if the token matched.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param pszOp The operation to check against.
+ */
+static bool vdScriptTokenizerIsOperatorEqual(PVDTOKENIZER pTokenizer, const char *pszOp)
+{
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pTokenizer);
+
+ if ( pToken->enmClass == VDTOKENCLASS_OPERATORS
+ && !RTStrCmp(pToken->Class.Operator.aszOp, pszOp))
+ return true;
+
+ return false;
+}
+
+/**
+ * Check whether the next token in the input stream is an operator and matches the given
+ * keyword and skips it.
+ *
+ * @returns true if the token matched and was skipped.
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param pszOp The operation to check against.
+ */
+static bool vdScriptTokenizerSkipIfIsOperatorEqual(PVDTOKENIZER pTokenizer, const char *pszOp)
+{
+ bool fEqual = vdScriptTokenizerIsOperatorEqual(pTokenizer, pszOp);
+ if (fEqual)
+ vdScriptTokenizerConsume(pTokenizer);
+
+ return fEqual;
+}
+
+/**
+ * Record an error while parsing.
+ *
+ * @returns VBox status code passed.
+ */
+static int vdScriptParserError(PVDSCRIPTCTXINT pThis, int rc, RT_SRC_POS_DECL, const char *pszFmt, ...)
+{
+ RT_NOREF1(pThis); RT_SRC_POS_NOREF();
+ va_list va;
+ va_start(va, pszFmt);
+ RTPrintfV(pszFmt, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Puts the next identifier AST node on the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeIde Where to store the identifier AST node on success.
+ */
+static int vdScriptParseIde(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTIDE *ppAstNodeIde)
+{
+ int rc = VINF_SUCCESS;
+ PCVDSCRIPTTOKEN pToken;
+
+ LogFlowFunc(("pThis=%p ppAstNodeIde=%p\n", pThis, ppAstNodeIde));
+
+ pToken = vdScriptTokenizerGetToken(pThis->pTokenizer);
+ if (pToken->enmClass != VDTOKENCLASS_IDENTIFIER)
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected identifer got...\n");
+ else
+ {
+ /* Create new AST node and push onto stack. */
+ PVDSCRIPTASTIDE pAstNodeIde = vdScriptAstNodeIdeAlloc(pToken->Class.Ide.cchIde);
+ if (pAstNodeIde)
+ {
+ rc = RTStrCopyEx(pAstNodeIde->aszIde, pToken->Class.Ide.cchIde + 1, pToken->Class.Ide.pszIde, pToken->Class.Ide.cchIde);
+ AssertRC(rc);
+ pAstNodeIde->cchIde = (unsigned)pToken->Class.Ide.cchIde;
+
+ *ppAstNodeIde = pAstNodeIde;
+ vdScriptTokenizerConsume(pThis->pTokenizer);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating identifier AST node\n");
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a primary expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the primary expression on success.
+ */
+static int vdScriptParsePrimaryExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ rc = vdScriptParseExpression(pThis, ppAstNodeExpr);
+ if (RT_SUCCESS(rc)
+ && !vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ }
+ else
+ {
+ PVDSCRIPTASTEXPR pExpr = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExpr)
+ {
+ if (vdScriptTokenizerGetTokenClass(pThis->pTokenizer) == VDTOKENCLASS_IDENTIFIER)
+ {
+ PVDSCRIPTASTIDE pIde = NULL;
+ rc = vdScriptParseIde(pThis, &pIde);
+ if (RT_SUCCESS(rc))
+ {
+ pExpr->enmType = VDSCRIPTEXPRTYPE_PRIMARY_IDENTIFIER;
+ pExpr->pIde = pIde;
+ }
+ }
+ else if (vdScriptTokenizerGetTokenClass(pThis->pTokenizer) == VDTOKENCLASS_NUMCONST)
+ {
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pThis->pTokenizer);
+ pExpr->enmType = VDSCRIPTEXPRTYPE_PRIMARY_NUMCONST;
+ pExpr->u64 = pToken->Class.NumConst.u64;
+ vdScriptTokenizerConsume(pThis->pTokenizer);
+ }
+ else if (vdScriptTokenizerGetTokenClass(pThis->pTokenizer) == VDTOKENCLASS_STRINGCONST)
+ {
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pThis->pTokenizer);
+ pExpr->enmType = VDSCRIPTEXPRTYPE_PRIMARY_STRINGCONST;
+ pExpr->pszStr = RTStrDupN(pToken->Class.StringConst.pszString, pToken->Class.StringConst.cchString);
+ vdScriptTokenizerConsume(pThis->pTokenizer);
+
+ if (!pExpr->pszStr)
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating string\n");
+ }
+ else if (vdScriptTokenizerGetTokenClass(pThis->pTokenizer) == VDTOKENCLASS_KEYWORD)
+ {
+ PCVDSCRIPTTOKEN pToken = vdScriptTokenizerGetToken(pThis->pTokenizer);
+ pExpr->enmType = VDSCRIPTEXPRTYPE_PRIMARY_BOOLEAN;
+
+ if (pToken->Class.Keyword.enmKeyword == VDSCRIPTTOKENKEYWORD_TRUE)
+ pExpr->f = true;
+ else if (pToken->Class.Keyword.enmKeyword == VDSCRIPTTOKENKEYWORD_FALSE)
+ pExpr->f = false;
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Unexpected keyword, expected true or false\n");
+ vdScriptTokenizerConsume(pThis->pTokenizer);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\" | identifier | constant | string, got ...\n");
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pExpr->Core);
+ else
+ *ppAstNodeExpr = pExpr;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse an argument list for a function call.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pFnCall The function call AST node.
+ */
+static int vdScriptParseFnCallArgumentList(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR pFnCall)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p pFnCall=%p\n", pThis, pFnCall));
+
+ rc = vdScriptParseAssignmentExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ RTListAppend(&pFnCall->FnCall.ListArgs, &pExpr->Core.ListNode);
+ while (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ','))
+ {
+ rc = vdScriptParseAssignmentExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pFnCall->FnCall.ListArgs, &pExpr->Core.ListNode);
+ else
+ break;
+ }
+ if ( RT_SUCCESS(rc)
+ && !vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a postfix expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * postfix-expression:
+ * primary-expression
+ * postfix-expression ( argument-expression )
+ * postfix-expression ++
+ * postfix-expression --
+ * postfix-expression . identifier
+ * postfix-expression -> identifier
+ * @note: Not supported so far are:
+ * ( type-name ) { initializer-list }
+ * ( type-name ) { initializer-list , }
+ */
+static int vdScriptParsePostfixExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParsePrimaryExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ while (true)
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "++"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_POSTFIX_INCREMENT;
+ pExprNew->pExpr = pExpr;
+ pExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "--"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_POSTFIX_DECREMENT;
+ pExprNew->pExpr = pExpr;
+ pExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "->"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ PVDSCRIPTASTIDE pIde = NULL;
+ rc = vdScriptParseIde(pThis, &pIde);
+ if (RT_SUCCESS(rc))
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_POSTFIX_DEREFERENCE;
+ pExprNew->Deref.pIde = pIde;
+ pExprNew->Deref.pExpr = pExpr;
+ pExpr = pExprNew;
+ }
+ else
+ vdScriptAstNodeFree(&pExprNew->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "."))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ PVDSCRIPTASTIDE pIde = NULL;
+ rc = vdScriptParseIde(pThis, &pIde);
+ if (RT_SUCCESS(rc))
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_POSTFIX_DOT;
+ pExprNew->Deref.pIde = pIde;
+ pExprNew->Deref.pExpr = pExpr;
+ pExpr = pExprNew;
+ }
+ else
+ vdScriptAstNodeFree(&pExprNew->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_POSTFIX_FNCALL;
+ RTListInit(&pExprNew->FnCall.ListArgs);
+ if (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ rc = vdScriptParseFnCallArgumentList(pThis, pExprNew);
+ pExprNew->FnCall.pFnIde = pExpr;
+ pExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse an unary expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * unary-expression:
+ * postfix-expression
+ * ++ unary-expression
+ * -- unary-expression
+ * + cast-expression
+ * - cast-expression
+ * ~ cast-expression
+ * ! cast-expression
+ * & cast-expression
+ * * cast-expression
+ */
+static int vdScriptParseUnaryExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+ PVDSCRIPTASTEXPR pExprTop = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ /** @todo Think about a more beautiful way of parsing this. */
+ while (true)
+ {
+ bool fQuit = false;
+ bool fCastExprFollows = false;
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ VDSCRIPTEXPRTYPE enmType = VDSCRIPTEXPRTYPE_INVALID;
+
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "++"))
+ enmType = VDSCRIPTEXPRTYPE_UNARY_INCREMENT;
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "--"))
+ enmType = VDSCRIPTEXPRTYPE_UNARY_DECREMENT;
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "+"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_POSSIGN;
+ fCastExprFollows = true;
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "-"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_NEGSIGN;
+ fCastExprFollows = true;
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "~"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_INVERT;
+ fCastExprFollows = true;
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "!"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_NEGATE;
+ fCastExprFollows = true;
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "&"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_REFERENCE;
+ fCastExprFollows = true;
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "*"))
+ {
+ enmType = VDSCRIPTEXPRTYPE_UNARY_DEREFERENCE;
+ fCastExprFollows = true;
+ }
+
+ if (enmType != VDSCRIPTEXPRTYPE_INVALID)
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = enmType;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+
+ if ( RT_SUCCESS(rc)
+ && fCastExprFollows)
+ {
+ PVDSCRIPTASTEXPR pCastExpr = NULL;
+
+ rc = vdScriptParseCastExpression(pThis, &pCastExpr);
+ if (RT_SUCCESS(rc))
+ pExprNew->pExpr = pCastExpr;
+ else
+ vdScriptAstNodeFree(&pExprNew->Core);
+ fQuit = true;
+ }
+ }
+ else
+ {
+ /* Must be a postfix expression. */
+ rc = vdScriptParsePostfixExpression(pThis, &pExprNew);
+ fQuit = true;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (!pExprTop)
+ {
+ pExprTop = pExprNew;
+ pExpr = pExprNew;
+ }
+ else
+ {
+ pExpr->pExpr = pExprNew;
+ pExpr = pExprNew;
+ }
+ if (fQuit)
+ break;
+ }
+ else
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExprTop;
+ else if (pExprTop)
+ vdScriptAstNodeFree(&pExprTop->Core);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+#if 0 /* unused */
+/**
+ * Parse a storage class specifier.
+ *
+ * @param pThis The script context.
+ * @param penmStorageClass Where to return the parsed storage classe.
+ * Contains VDSCRIPTASTSTORAGECLASS_INVALID if no
+ * valid storage class specifier was found.
+ *
+ * @note Syntax:
+ * typedef
+ * extern
+ * static
+ * auto
+ * register
+ */
+static void vdScriptParseStorageClassSpecifier(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSTORAGECLASS penmStorageClass)
+{
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_INVALID;
+
+ if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_TYPEDEF))
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_TYPEDEF;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_EXTERN))
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_EXTERN;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_STATIC))
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_STATIC;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_AUTO))
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_AUTO;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_REGISTER))
+ *penmStorageClass = VDSCRIPTASTSTORAGECLASS_REGISTER;
+}
+#endif /* unused */
+
+#if 0 /* unused */
+/**
+ * Parse a type qualifier.
+ *
+ * @param pThis The script context.
+ * @param penmTypeQualifier Where to return the parsed type qualifier.
+ * Contains VDSCRIPTASTTYPEQUALIFIER_INVALID if no
+ * valid type qualifier was found.
+ *
+ * @note Syntax:
+ * const
+ * restrict
+ * volatile
+ */
+static void vdScriptParseTypeQualifier(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTTYPEQUALIFIER penmTypeQualifier)
+{
+ *penmTypeQualifier = VDSCRIPTASTTYPEQUALIFIER_INVALID;
+
+ if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_CONST))
+ *penmTypeQualifier = VDSCRIPTASTTYPEQUALIFIER_CONST;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_RESTRICT))
+ *penmTypeQualifier = VDSCRIPTASTTYPEQUALIFIER_RESTRICT;
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_VOLATILE))
+ *penmTypeQualifier = VDSCRIPTASTTYPEQUALIFIER_VOLATILE;
+}
+#endif /* unused */
+
+#if 0
+/**
+ * Parse a struct or union specifier.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstTypeSpec Where to store the type specifier AST node on success.
+ * @param enmTypeSpecifier The type specifier to identify whete this is a struct or a union.
+ */
+static int vdScriptParseStructOrUnionSpecifier(PVDSCRIPTCTXINT pThis, , enmTypeSpecifier)
+{
+ int rc = VINF_SUCCESS;
+
+ return rc;
+}
+
+/**
+ * Parse a type specifier.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstTypeSpec Where to store the type specifier AST node on success.
+ *
+ * @note Syntax:
+ * struct-or-union-specifier
+ * enum-specifier
+ * typedef-name (identifier: includes void, bool, uint8_t, int8_t, ... for basic integer types)
+ */
+static int vdScriptParseTypeSpecifier(PVDSCRIPTCTXINT pThis, )
+{
+ int rc = VINF_SUCCESS;
+
+ if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_STRUCT))
+ rc = vdScriptParseStructOrUnionSpecifier(pThis, , VDSCRIPTASTTYPESPECIFIER_STRUCT);
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_UNION))
+ rc = vdScriptParseStructOrUnionSpecifier(pThis, , VDSCRIPTASTTYPESPECIFIER_UNION);
+ else
+ {
+ PVDSCRIPTASTIDE pIde = NULL;
+
+ rc = vdScriptParseIde(pThis, &pIde);
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsgFailed(("TODO\n")); /* Parse identifier. */
+ }
+ }
+
+ return rc;
+}
+#endif
+
+/**
+ * Parse a cast expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * cast-expression:
+ * unary-expression
+ * ( type-name ) cast-expression
+ */
+static int vdScriptParseCastExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+#if 0
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTTYPE pTypeName = NULL;
+ rc = vdScriptParseTypeName(pThis, &pTypeName);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTEXPR pExpr = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExpr)
+ {
+ pExpr->enmType = VDSCRIPTEXPRTYPE_CAST;
+ rc = vdScriptParseCastExpression(pThis, &pExpr->Cast.pExpr); /** @todo Kill recursion. */
+ if (RT_SUCCESS(rc))
+ pExpr->Cast.pTypeName = pTypeName;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pTypeName->Core);
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ }
+ else
+#endif
+ rc = vdScriptParseUnaryExpression(pThis, ppAstNodeExpr);
+
+ return rc;
+}
+
+/**
+ * Parse a multiplicative expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * multiplicative-expression:
+ * cast-expression
+ * multiplicative-expression * cast-expression
+ * multiplicative-expression / cast-expression
+ * multiplicative-expression % cast-expression
+ */
+static int vdScriptParseMultiplicativeExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseCastExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ while (RT_SUCCESS(rc))
+ {
+ VDSCRIPTEXPRTYPE enmType = VDSCRIPTEXPRTYPE_INVALID;
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "*"))
+ enmType = VDSCRIPTEXPRTYPE_MULTIPLICATION;
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "/"))
+ enmType = VDSCRIPTEXPRTYPE_DIVISION;
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "%"))
+ enmType = VDSCRIPTEXPRTYPE_MODULUS;
+ else
+ break;
+
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = enmType;
+ else
+ {
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ break;
+ }
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseCastExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a additive expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * additive-expression:
+ * multiplicative-expression
+ * additive-expression + multiplicative-expression
+ * additive-expression - multiplicative-expression
+ */
+static int vdScriptParseAdditiveExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseMultiplicativeExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "+"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ADDITION;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "-"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_SUBTRACTION;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseMultiplicativeExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a shift expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * shift-expression:
+ * additive-expression
+ * shift-expression << additive-expression
+ * shift-expression >> additive-expression
+ */
+static int vdScriptParseShiftExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseAdditiveExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "<<"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LSL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, ">>"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LSR;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseAdditiveExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a relational expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * relational-expression:
+ * shift-expression
+ * relational-expression < shift-expression
+ * relational-expression > shift-expression
+ * relational-expression >= shift-expression
+ * relational-expression <= shift-expression
+ */
+static int vdScriptParseRelationalExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseShiftExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "<"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LOWER;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, ">"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_HIGHER;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, ">="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_HIGHEREQUAL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "<="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LOWEREQUAL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseShiftExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a equality expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * equality-expression:
+ * relational-expression
+ * equality-expression == relational-expression
+ * equality-expression != relational-expression
+ */
+static int vdScriptParseEqualityExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseRelationalExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "=="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_EQUAL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "!="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_NOTEQUAL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseRelationalExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a bitwise and expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * and-expression:
+ * equality-expression
+ * and-expression & equality-expression
+ */
+static int vdScriptParseBitwiseAndExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseEqualityExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "&"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_EQUAL;
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseEqualityExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a bitwise xor expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * xor-expression:
+ * and-expression
+ * xor-expression ^ equality-expression
+ */
+static int vdScriptParseBitwiseXorExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseBitwiseAndExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "^"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_BITWISE_XOR;
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseBitwiseAndExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a bitwise or expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * or-expression:
+ * xor-expression
+ * or-expression | xor-expression
+ */
+static int vdScriptParseBitwiseOrExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseBitwiseXorExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "|"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_BITWISE_OR;
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseBitwiseXorExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a logical and expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * logical-and-expression:
+ * or-expression
+ * logical-and-expression | or-expression
+ */
+static int vdScriptParseLogicalAndExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseBitwiseOrExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "&&"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LOGICAL_AND;
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseBitwiseOrExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a logical or expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * logical-or-expression:
+ * logical-and-expression
+ * logical-or-expression | logical-and-expression
+ */
+static int vdScriptParseLogicalOrExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseLogicalAndExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "||"))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ {
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_LOGICAL_OR;
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseLogicalAndExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse a conditional expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note: VDScript doesn't support logical-or-expression ? expression : conditional-expression
+ * so a conditional expression is equal to a logical-or-expression.
+ */
+static int vdScriptParseCondExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ return vdScriptParseLogicalOrExpression(pThis, ppAstNodeExpr);
+}
+
+#if 0 /* unused */
+/**
+ * Parse a constant expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * constant-expression:
+ * conditional-expression
+ */
+static int vdScriptParseConstExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ return vdScriptParseCondExpression(pThis, ppAstNodeExpr);
+}
+#endif
+
+/**
+ * Parse an assignment expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * assignment-expression:
+ * conditional-expression
+ * unary-expression assignment-operator assignment-expression
+ */
+static int vdScriptParseAssignmentExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pExpr;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseLogicalOrExpression(pThis, &pExpr);
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTEXPR pExprNew = NULL;
+ while (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "*="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_MULT;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "/="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_DIV;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "%="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_MOD;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "+="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_ADD;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "-="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_SUB;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "<<="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_LSL;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, ">>="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_LSR;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "&="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_AND;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "^="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_XOR;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsOperatorEqual(pThis->pTokenizer, "|="))
+ {
+ pExprNew = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pExprNew)
+ pExprNew->enmType = VDSCRIPTEXPRTYPE_ASSIGN_OR;
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else
+ break;
+
+ pExprNew->BinaryOp.pLeftExpr = pExpr;
+ pExpr = pExprNew;
+ rc = vdScriptParseLogicalOrExpression(pThis, &pExprNew);
+ if (RT_SUCCESS(rc))
+ pExpr->BinaryOp.pRightExpr = pExprNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pExpr;
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse an expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeExpr Where to store the expression AST node on success.
+ *
+ * @note Syntax:
+ * expression:
+ * assignment-expression
+ * expression , assignment-expression
+ */
+static int vdScriptParseExpression(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTEXPR *ppAstNodeExpr)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTEXPR pAssignExpr = NULL;
+
+ LogFlowFunc(("pThis=%p ppAstNodeExpr=%p\n", pThis, ppAstNodeExpr));
+
+ rc = vdScriptParseAssignmentExpression(pThis, &pAssignExpr);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ','))
+ {
+ PVDSCRIPTASTEXPR pListAssignExpr = (PVDSCRIPTASTEXPR)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_EXPRESSION);
+ if (pListAssignExpr)
+ {
+ pListAssignExpr->enmType = VDSCRIPTEXPRTYPE_ASSIGNMENT_LIST;
+ RTListInit(&pListAssignExpr->ListExpr);
+ RTListAppend(&pListAssignExpr->ListExpr, &pAssignExpr->Core.ListNode);
+ do
+ {
+ rc = vdScriptParseAssignmentExpression(pThis, &pAssignExpr);
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pListAssignExpr->ListExpr, &pAssignExpr->Core.ListNode);
+ } while ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ','));
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pListAssignExpr->Core);
+ else
+ *ppAstNodeExpr = pListAssignExpr;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating expression AST node\n");
+ }
+ else if (RT_SUCCESS(rc))
+ *ppAstNodeExpr = pAssignExpr;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parse an if statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pAstNodeIf Uninitialized if AST node.
+ *
+ * @note The caller skipped the "if" token already.
+ */
+static int vdScriptParseIf(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTIF pAstNodeIf)
+{
+ int rc = VINF_SUCCESS;
+
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTEXPR pCondExpr = NULL;
+ rc = vdScriptParseExpression(pThis, &pCondExpr);
+ if (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTSTMT pStmt = NULL;
+ PVDSCRIPTASTSTMT pElseStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pStmt);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_ELSE))
+ rc = vdScriptParseStatement(pThis, &pElseStmt);
+
+ if (RT_SUCCESS(rc))
+ {
+ pAstNodeIf->pCond = pCondExpr;
+ pAstNodeIf->pTrueStmt = pStmt;
+ pAstNodeIf->pElseStmt = pElseStmt;
+ }
+ else if (pStmt)
+ vdScriptAstNodeFree(&pStmt->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pCondExpr->Core);
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\", got ...\n");
+
+ return rc;
+}
+
+/**
+ * Parse a switch statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pAstNodeSwitch Uninitialized switch AST node.
+ *
+ * @note The caller skipped the "switch" token already.
+ */
+static int vdScriptParseSwitch(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSWITCH pAstNodeSwitch)
+{
+ int rc = VINF_SUCCESS;
+
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ rc = vdScriptParseExpression(pThis, &pExpr);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTSTMT pStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pStmt);
+ if (RT_SUCCESS(rc))
+ {
+ pAstNodeSwitch->pCond = pExpr;
+ pAstNodeSwitch->pStmt = pStmt;
+ }
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\", got ...\n");
+
+ return rc;
+}
+
+/**
+ * Parse a while or do ... while statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pAstNodeWhile Uninitialized while AST node.
+ *
+ * @note The caller skipped the "while" or "do" token already.
+ */
+static int vdScriptParseWhile(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTWHILE pAstNodeWhile, bool fDoWhile)
+{
+ int rc = VINF_SUCCESS;
+
+ pAstNodeWhile->fDoWhile = fDoWhile;
+
+ if (fDoWhile)
+ {
+ PVDSCRIPTASTSTMT pStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pStmt);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_WHILE))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ rc = vdScriptParseExpression(pThis, &pExpr);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ {
+ pAstNodeWhile->pCond = pExpr;
+ pAstNodeWhile->pStmt = pStmt;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\", got ...\n");
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"while\", got ...\n");
+
+ if ( RT_FAILURE(rc)
+ && pStmt)
+ vdScriptAstNodeFree(&pStmt->Core);
+ }
+ else
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTEXPR pExpr = NULL;
+
+ rc = vdScriptParseExpression(pThis, &pExpr);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTSTMT pStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pStmt);
+ if (RT_SUCCESS(rc))
+ {
+ pAstNodeWhile->pCond = pExpr;
+ pAstNodeWhile->pStmt = pStmt;
+ }
+ else
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \")\", got ...\n");
+ vdScriptAstNodeFree(&pExpr->Core);
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\", got ...\n");
+ }
+
+ return rc;
+}
+
+/**
+ * Parse a for statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pAstNodeFor Uninitialized for AST node.
+ *
+ * @note The caller skipped the "for" token already.
+ */
+static int vdScriptParseFor(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTFOR pAstNodeFor)
+{
+ int rc = VINF_SUCCESS;
+
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTEXPR pExprStart = NULL;
+ PVDSCRIPTASTEXPR pExprCond = NULL;
+ PVDSCRIPTASTEXPR pExpr3 = NULL;
+
+ rc = vdScriptParseExpression(pThis, &pExprStart);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ {
+ rc = vdScriptParseExpression(pThis, &pExprCond);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ {
+ rc = vdScriptParseExpression(pThis, &pExpr3);
+ if ( RT_SUCCESS(rc)
+ && vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTSTMT pStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pStmt);
+ if (RT_SUCCESS(rc))
+ {
+ pAstNodeFor->pExprStart = pExprStart;
+ pAstNodeFor->pExprCond = pExprCond;
+ pAstNodeFor->pExpr3 = pExpr3;
+ pAstNodeFor->pStmt = pStmt;
+ }
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+ else if (RT_SUCCESS(rc))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+
+ if (RT_FAILURE(rc))
+ {
+ if (pExprStart)
+ vdScriptAstNodeFree(&pExprStart->Core);
+ if (pExprCond)
+ vdScriptAstNodeFree(&pExprCond->Core);
+ if (pExpr3)
+ vdScriptAstNodeFree(&pExpr3->Core);
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\", got ...\n");
+
+ return rc;
+}
+
+/**
+ * Parse a declaration.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeDecl Where to store the declaration AST node on success.
+ */
+static int vdScriptParseDeclaration(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTDECL *ppAstNodeDecl)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+ RT_NOREF2(pThis, ppAstNodeDecl);
+ return rc;
+}
+
+/**
+ * Parse a statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeStmt Where to store the statement AST node on success.
+ */
+static int vdScriptParseStatement(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSTMT *ppAstNodeStmt)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Shortcut for a new compound statement. */
+ if (vdScriptTokenizerIsPunctuatorEqual(pThis->pTokenizer, '{'))
+ rc = vdScriptParseCompoundStatement(pThis, ppAstNodeStmt);
+ else
+ {
+ PVDSCRIPTASTSTMT pAstNodeStmt = (PVDSCRIPTASTSTMT)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_STATEMENT);
+
+ if (pAstNodeStmt)
+ {
+
+ if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_DEFAULT))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ':'))
+ {
+ PVDSCRIPTASTSTMT pAstNodeStmtDef = NULL;
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_DEFAULT;
+ rc = vdScriptParseStatement(pThis, &pAstNodeStmtDef);
+ if (RT_SUCCESS(rc))
+ pAstNodeStmt->pStmt = pAstNodeStmtDef;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \":\", got ...\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_CASE))
+ {
+ PVDSCRIPTASTEXPR pAstNodeExpr = NULL;
+ rc = vdScriptParseCondExpression(pThis, &pAstNodeExpr);
+ if (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ':'))
+ {
+ PVDSCRIPTASTSTMT pAstNodeCaseStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pAstNodeCaseStmt);
+ if (RT_SUCCESS(rc))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_CASE;
+ pAstNodeStmt->Case.pExpr = pAstNodeExpr;
+ pAstNodeStmt->Case.pStmt = pAstNodeCaseStmt;
+ }
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \":\", got ...\n");
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pAstNodeExpr->Core);
+ }
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_IF))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_IF;
+ rc = vdScriptParseIf(pThis, &pAstNodeStmt->If);
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_SWITCH))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_SWITCH;
+ rc = vdScriptParseSwitch(pThis, &pAstNodeStmt->Switch);
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_WHILE))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_WHILE;
+ rc = vdScriptParseWhile(pThis, &pAstNodeStmt->While, false /* fDoWhile */);
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_DO))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_WHILE;
+ rc = vdScriptParseWhile(pThis, &pAstNodeStmt->While, true /* fDoWhile */);
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_FOR))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_FOR;
+ rc = vdScriptParseFor(pThis, &pAstNodeStmt->For);
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_CONTINUE))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_CONTINUE;
+ if (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_BREAK))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_BREAK;
+ if (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+ else if (vdScriptTokenizerSkipIfIsKeywordEqual(pThis->pTokenizer, VDSCRIPTTOKENKEYWORD_RETURN))
+ {
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_RETURN;
+ if (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ {
+ rc = vdScriptParseExpression(pThis, &pAstNodeStmt->pExpr);
+ if ( RT_SUCCESS(rc)
+ && !vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+ else
+ pAstNodeStmt->pExpr = NULL; /* No expression for return. */
+ }
+ else
+ {
+ /* Must be an expression. */
+ pAstNodeStmt->enmStmtType = VDSCRIPTSTMTTYPE_EXPRESSION;
+ rc = vdScriptParseExpression(pThis, &pAstNodeStmt->pExpr);
+ if ( RT_SUCCESS(rc)
+ && !vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ';'))
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \";\", got ...\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeStmt = pAstNodeStmt;
+ else
+ vdScriptAstNodeFree(&pAstNodeStmt->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory creating statement node\n");
+ }
+
+ return rc;
+}
+
+/**
+ * Parses a compound statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param ppAstNodeCompound Where to store the compound AST node on success.
+ */
+static int vdScriptParseCompoundStatement(PVDSCRIPTCTXINT pThis, PVDSCRIPTASTSTMT *ppAstNodeCompound)
+{
+ int rc = VINF_SUCCESS;
+
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '{'))
+ {
+ PVDSCRIPTASTSTMT pAstNodeCompound = (PVDSCRIPTASTSTMT)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_STATEMENT);
+ if (pAstNodeCompound)
+ {
+ pAstNodeCompound->enmStmtType = VDSCRIPTSTMTTYPE_COMPOUND;
+ RTListInit(&pAstNodeCompound->Compound.ListDecls);
+ RTListInit(&pAstNodeCompound->Compound.ListStmts);
+ while (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '}'))
+ {
+ /*
+ * Check whether we have a declaration or a statement.
+ * For now we assume that 2 identifier tokens specify a declaration
+ * (type + variable name). Having two consecutive identifers is not possible
+ * for a statement.
+ */
+ if ( vdScriptTokenizerGetTokenClass(pThis->pTokenizer) == VDTOKENCLASS_IDENTIFIER
+ && vdScriptTokenizerPeekNextClass(pThis->pTokenizer) == VDTOKENCLASS_IDENTIFIER)
+ {
+ PVDSCRIPTASTDECL pAstNodeDecl = NULL;
+ rc = vdScriptParseDeclaration(pThis, &pAstNodeDecl);
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pAstNodeCompound->Compound.ListDecls, &pAstNodeDecl->Core.ListNode);
+ }
+ else
+ {
+ PVDSCRIPTASTSTMT pAstNodeStmt = NULL;
+ rc = vdScriptParseStatement(pThis, &pAstNodeStmt);
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pAstNodeCompound->Compound.ListStmts, &pAstNodeStmt->Core.ListNode);
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAstNodeCompound = pAstNodeCompound;
+ else
+ vdScriptAstNodeFree(&pAstNodeCompound->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory creating compound statement node\n");
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"{\" got...\n");
+
+
+ return rc;
+}
+
+/**
+ * Parses a function definition from the given tokenizer.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ */
+static int vdScriptParseAddFnDef(PVDSCRIPTCTXINT pThis)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTASTIDE pRetType = NULL;
+ PVDSCRIPTASTIDE pFnIde = NULL;
+
+ LogFlowFunc(("pThis=%p\n", pThis));
+
+ /* Put return type on the stack. */
+ rc = vdScriptParseIde(pThis, &pRetType);
+ if (RT_SUCCESS(rc))
+ {
+ /* Function name */
+ rc = vdScriptParseIde(pThis, &pFnIde);
+ if (RT_SUCCESS(rc))
+ {
+ if (vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, '('))
+ {
+ PVDSCRIPTASTFN pAstNodeFn = (PVDSCRIPTASTFN)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_FUNCTION);
+
+ if (pAstNodeFn)
+ {
+ pAstNodeFn->pFnIde = pFnIde;
+ pAstNodeFn->pRetType = pRetType;
+ RTListInit(&pAstNodeFn->ListArgs);
+
+ pFnIde = NULL;
+ pRetType = NULL;
+
+ /* Parse parameter list, create empty parameter list AST node and put it on the stack. */
+ while (!vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ PVDSCRIPTASTIDE pArgType = NULL;
+ PVDSCRIPTASTIDE pArgIde = NULL;
+ /* Parse two identifiers, first one is the type, second the name. */
+ rc = vdScriptParseIde(pThis, &pArgType);
+ if (RT_SUCCESS(rc))
+ rc = vdScriptParseIde(pThis, &pArgIde);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTFNARG pAstNodeFnArg = (PVDSCRIPTASTFNARG)vdScriptAstNodeAlloc(VDSCRIPTASTCLASS_FUNCTIONARG);
+ if (pAstNodeFnArg)
+ {
+ pAstNodeFnArg->pArgIde = pArgIde;
+ pAstNodeFnArg->pType = pArgType;
+ RTListAppend(&pAstNodeFn->ListArgs, &pAstNodeFnArg->Core.ListNode);
+ pAstNodeFn->cArgs++;
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating function argument AST node\n");
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pArgType)
+ vdScriptAstNodeFree(&pArgType->Core);
+ if (pArgIde)
+ vdScriptAstNodeFree(&pArgIde->Core);
+ }
+
+ if ( !vdScriptTokenizerSkipIfIsPunctuatorEqual(pThis->pTokenizer, ',')
+ && !vdScriptTokenizerIsPunctuatorEqual(pThis->pTokenizer, ')'))
+ {
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \",\" or \")\" got...\n");
+ break;
+ }
+ }
+
+ /* Parse the compound or statement block now. */
+ if (RT_SUCCESS(rc))
+ {
+ PVDSCRIPTASTSTMT pAstCompound = NULL;
+
+ rc = vdScriptParseCompoundStatement(pThis, &pAstCompound);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Link compound statement block to function AST node and add it to the
+ * list of functions.
+ */
+ pAstNodeFn->pCompoundStmts = pAstCompound;
+ RTListAppend(&pThis->ListAst, &pAstNodeFn->Core.ListNode);
+
+ PVDSCRIPTFN pFn = (PVDSCRIPTFN)RTMemAllocZ(sizeof(VDSCRIPTFN));
+ if (pFn)
+ {
+ pFn->Core.pszString = pAstNodeFn->pFnIde->aszIde;
+ pFn->Core.cchString = strlen(pFn->Core.pszString);
+ pFn->fExternal = false;
+ pFn->Type.Internal.pAstFn = pAstNodeFn;
+ /** @todo Parameters. */
+ RTStrSpaceInsert(&pThis->hStrSpaceFn, &pFn->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory allocating memory for function\n");
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ vdScriptAstNodeFree(&pAstNodeFn->Core);
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Parser: Out of memory allocating function AST node\n");
+ }
+ else
+ rc = vdScriptParserError(pThis, VERR_INVALID_PARAMETER, RT_SRC_POS, "Parser: Expected \"(\" got...\n");
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pRetType)
+ vdScriptAstNodeFree(&pRetType->Core);
+ if (pFnIde)
+ vdScriptAstNodeFree(&pFnIde->Core);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Parses the script from the given tokenizer.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ */
+static int vdScriptParseFromTokenizer(PVDSCRIPTCTXINT pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%p\n", pThis));
+
+ /* This is a very very simple LL(1) parser, don't expect much from it for now :). */
+ while ( RT_SUCCESS(rc)
+ && !vdScriptTokenizerIsEos(pThis->pTokenizer))
+ rc = vdScriptParseAddFnDef(pThis);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+DECLHIDDEN(int) VDScriptCtxCreate(PVDSCRIPTCTX phScriptCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("phScriptCtx=%p\n", phScriptCtx));
+
+ AssertPtrReturn(phScriptCtx, VERR_INVALID_POINTER);
+
+ PVDSCRIPTCTXINT pThis = (PVDSCRIPTCTXINT)RTMemAllocZ(sizeof(VDSCRIPTCTXINT));
+ if (pThis)
+ {
+ pThis->hStrSpaceFn = NULL;
+ RTListInit(&pThis->ListAst);
+ *phScriptCtx = pThis;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdScriptCtxDestroyFnSpace(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ NOREF(pvUser);
+
+ /*
+ * Just free the whole structure, the AST for internal functions will be
+ * destroyed later.
+ */
+ RTMemFree(pStr);
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(void) VDScriptCtxDestroy(VDSCRIPTCTX hScriptCtx)
+{
+ PVDSCRIPTCTXINT pThis = hScriptCtx;
+
+ AssertPtrReturnVoid(pThis);
+
+ LogFlowFunc(("hScriptCtx=%p\n", pThis));
+
+ RTStrSpaceDestroy(&pThis->hStrSpaceFn, vdScriptCtxDestroyFnSpace, NULL);
+
+ /* Go through list of function ASTs and destroy them. */
+ PVDSCRIPTASTCORE pIter;
+ PVDSCRIPTASTCORE pIterNext;
+ RTListForEachSafe(&pThis->ListAst, pIter, pIterNext, VDSCRIPTASTCORE, ListNode)
+ {
+ RTListNodeRemove(&pIter->ListNode);
+ RTListInit(&pIter->ListNode);
+ vdScriptAstNodeFree(pIter);
+ }
+
+ RTMemFree(pThis);
+}
+
+DECLHIDDEN(int) VDScriptCtxCallbacksRegister(VDSCRIPTCTX hScriptCtx, PCVDSCRIPTCALLBACK paCallbacks,
+ unsigned cCallbacks, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTCTXINT pThis = hScriptCtx;
+
+ LogFlowFunc(("hScriptCtx=%p paCallbacks=%p cCallbacks=%u pvUser=%p\n",
+ pThis, paCallbacks, cCallbacks, pvUser));
+
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(paCallbacks, VERR_INVALID_POINTER);
+ AssertReturn(cCallbacks > 0, VERR_INVALID_PARAMETER);
+
+ /** @todo Unregister already registered callbacks in case of an error. */
+ do
+ {
+ PVDSCRIPTFN pFn = NULL;
+
+ if (RTStrSpaceGet(&pThis->hStrSpaceFn, paCallbacks->pszFnName))
+ {
+ rc = VERR_DUPLICATE;
+ break;
+ }
+
+ pFn = (PVDSCRIPTFN)RTMemAllocZ(RT_UOFFSETOF_DYN(VDSCRIPTFN, aenmArgTypes[paCallbacks->cArgs]));
+ if (!pFn)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /** @todo Validate argument and returns types. */
+ pFn->Core.pszString = paCallbacks->pszFnName;
+ pFn->Core.cchString = strlen(pFn->Core.pszString);
+ pFn->fExternal = true;
+ pFn->Type.External.pfnCallback = paCallbacks->pfnCallback;
+ pFn->Type.External.pvUser = pvUser;
+ pFn->enmTypeRetn = paCallbacks->enmTypeReturn;
+ pFn->cArgs = paCallbacks->cArgs;
+
+ for (unsigned i = 0; i < paCallbacks->cArgs; i++)
+ pFn->aenmArgTypes[i] = paCallbacks->paArgs[i];
+
+ RTStrSpaceInsert(&pThis->hStrSpaceFn, &pFn->Core);
+ cCallbacks--;
+ paCallbacks++;
+ }
+ while (cCallbacks);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+DECLHIDDEN(int) VDScriptCtxLoadScript(VDSCRIPTCTX hScriptCtx, const char *pszScript)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTCTXINT pThis = hScriptCtx;
+
+ LogFlowFunc(("hScriptCtx=%p pszScript=%p\n", pThis, pszScript));
+
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszScript, VERR_INVALID_POINTER);
+
+ PVDTOKENIZER pTokenizer = vdScriptTokenizerCreate(pszScript);
+ if (pTokenizer)
+ {
+ pThis->pTokenizer = pTokenizer;
+ rc = vdScriptParseFromTokenizer(pThis);
+ pThis->pTokenizer = NULL;
+ RTMemFree(pTokenizer);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+DECLHIDDEN(int) VDScriptCtxCallFn(VDSCRIPTCTX hScriptCtx, const char *pszFnCall,
+ PVDSCRIPTARG paArgs, unsigned cArgs)
+{
+ PVDSCRIPTCTXINT pThis = hScriptCtx;
+ VDSCRIPTARG Ret;
+ return vdScriptCtxInterprete(pThis, pszFnCall, paArgs, cArgs, &Ret);
+}
diff --git a/src/VBox/Storage/testcase/VDScript.h b/src/VBox/Storage/testcase/VDScript.h
new file mode 100644
index 00000000..d6611b32
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScript.h
@@ -0,0 +1,235 @@
+/** @file
+ *
+ * VBox HDD container test utility - scripting engine.
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDScript_h
+#define VBOX_INCLUDED_SRC_testcase_VDScript_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** Handle to the scripting context. */
+typedef struct VDSCRIPTCTXINT *VDSCRIPTCTX;
+/** Pointer to a scripting context handle. */
+typedef VDSCRIPTCTX *PVDSCRIPTCTX;
+
+/**
+ * Supprted primitive types in the scripting engine.
+ */
+typedef enum VDSCRIPTTYPE
+{
+ /** Invalid type, do not use. */
+ VDSCRIPTTYPE_INVALID = 0,
+ /** void type, used for no return value of methods. */
+ VDSCRIPTTYPE_VOID,
+ /** unsigned 8bit integer. */
+ VDSCRIPTTYPE_UINT8,
+ VDSCRIPTTYPE_INT8,
+ VDSCRIPTTYPE_UINT16,
+ VDSCRIPTTYPE_INT16,
+ VDSCRIPTTYPE_UINT32,
+ VDSCRIPTTYPE_INT32,
+ VDSCRIPTTYPE_UINT64,
+ VDSCRIPTTYPE_INT64,
+ VDSCRIPTTYPE_STRING,
+ VDSCRIPTTYPE_BOOL,
+ VDSCRIPTTYPE_POINTER,
+ /** As usual, the 32bit blowup hack. */
+ VDSCRIPTTYPE_32BIT_HACK = 0x7fffffff
+} VDSCRIPTTYPE;
+/** Pointer to a type. */
+typedef VDSCRIPTTYPE *PVDSCRIPTTYPE;
+/** Pointer to a const type. */
+typedef const VDSCRIPTTYPE *PCVDSCRIPTTYPE;
+
+/**
+ * Script argument.
+ */
+typedef struct VDSCRIPTARG
+{
+ /** Type of the argument. */
+ VDSCRIPTTYPE enmType;
+ /** Value */
+ union
+ {
+ uint8_t u8;
+ int8_t i8;
+ uint16_t u16;
+ int16_t i16;
+ uint32_t u32;
+ int32_t i32;
+ uint64_t u64;
+ int64_t i64;
+ const char *psz;
+ bool f;
+ void *p;
+ };
+} VDSCRIPTARG;
+/** Pointer to an argument. */
+typedef VDSCRIPTARG *PVDSCRIPTARG;
+
+/** Script callback. */
+typedef DECLCALLBACKTYPE(int, FNVDSCRIPTCALLBACK,(PVDSCRIPTARG paScriptArgs, void *pvUser));
+/** Pointer to a script callback. */
+typedef FNVDSCRIPTCALLBACK *PFNVDSCRIPTCALLBACK;
+
+/**
+ * Callback registration structure.
+ */
+typedef struct VDSCRIPTCALLBACK
+{
+ /** The function name. */
+ const char *pszFnName;
+ /** The return type of the function. */
+ VDSCRIPTTYPE enmTypeReturn;
+ /** Pointer to the array of argument types. */
+ PCVDSCRIPTTYPE paArgs;
+ /** Number of arguments this method takes. */
+ unsigned cArgs;
+ /** The callback handler. */
+ PFNVDSCRIPTCALLBACK pfnCallback;
+} VDSCRIPTCALLBACK;
+/** Pointer to a callback register entry. */
+typedef VDSCRIPTCALLBACK *PVDSCRIPTCALLBACK;
+/** Pointer to a const callback register entry. */
+typedef const VDSCRIPTCALLBACK *PCVDSCRIPTCALLBACK;
+
+/**
+ * @{
+ */
+/** The address space stays assigned to a variable
+ * even if the pointer is casted to another type.
+ */
+#define VDSCRIPT_AS_FLAGS_TRANSITIVE RT_BIT(0)
+/** @} */
+
+/**
+ * Address space read callback
+ *
+ * @returns VBox status code.
+ * @param pvUser Opaque user data given on registration.
+ * @param Address The address to read from, address is stored in the member for
+ * base type given on registration.
+ * @param pvBuf Where to store the read bits.
+ * @param cbRead How much to read.
+ */
+typedef DECLCALLBACKTYPE(int, FNVDSCRIPTASREAD,(void *pvUser, VDSCRIPTARG Address, void *pvBuf, size_t cbRead));
+/** Pointer to a read callback. */
+typedef FNVDSCRIPTASREAD *PFNVDSCRIPTASREAD;
+
+/**
+ * Address space write callback
+ *
+ * @returns VBox status code.
+ * @param pvUser Opaque user data given on registration.
+ * @param Address The address to write to, address is stored in the member for
+ * base type given on registration.
+ * @param pvBuf Data to write.
+ * @param cbWrite How much to write.
+ */
+typedef DECLCALLBACKTYPE(int, FNVDSCRIPTASWRITE,(void *pvUser, VDSCRIPTARG Address, const void *pvBuf, size_t cbWrite));
+/** Pointer to a write callback. */
+typedef FNVDSCRIPTASWRITE *PFNVDSCRIPTASWRITE;
+
+/**
+ * Create a new scripting context.
+ *
+ * @returns VBox status code.
+ * @param phScriptCtx Where to store the scripting context on success.
+ */
+DECLHIDDEN(int) VDScriptCtxCreate(PVDSCRIPTCTX phScriptCtx);
+
+/**
+ * Destroys the given scripting context.
+ *
+ * @param hScriptCtx The script context to destroy.
+ */
+DECLHIDDEN(void) VDScriptCtxDestroy(VDSCRIPTCTX hScriptCtx);
+
+/**
+ * Register callbacks for the scripting context.
+ *
+ * @returns VBox status code.
+ * @param hScriptCtx The script context handle.
+ * @param paCallbacks Pointer to the callbacks to register.
+ * @param cCallbacks Number of callbacks in the array.
+ * @param pvUser Opaque user data to pass on the callback invocation.
+ */
+DECLHIDDEN(int) VDScriptCtxCallbacksRegister(VDSCRIPTCTX hScriptCtx, PCVDSCRIPTCALLBACK paCallbacks,
+ unsigned cCallbacks, void *pvUser);
+
+/**
+ * Load a given script into the context.
+ *
+ * @returns VBox status code.
+ * @param hScriptCtx The script context handle.
+ * @param pszScript Pointer to the char buffer containing the script.
+ */
+DECLHIDDEN(int) VDScriptCtxLoadScript(VDSCRIPTCTX hScriptCtx, const char *pszScript);
+
+/**
+ * Execute a given method in the script context.
+ *
+ * @returns VBox status code.
+ * @param hScriptCtx The script context handle.
+ * @param pszFnCall The method to call.
+ * @param paArgs Pointer to arguments to pass.
+ * @param cArgs Number of arguments.
+ */
+DECLHIDDEN(int) VDScriptCtxCallFn(VDSCRIPTCTX hScriptCtx, const char *pszFnCall,
+ PVDSCRIPTARG paArgs, unsigned cArgs);
+
+/**
+ * Registers a new address space provider.
+ *
+ * @returns VBox status code.
+ * @param hScriptCtx The script context handle.
+ * @param pszType The type string.
+ * @param enmBaseType The base integer type to use for the address space.
+ * Bool and String are not supported of course.
+ * @param pfnRead The read callback for the registered address space.
+ * @param pfnWrite The write callback for the registered address space.
+ * @param pvUser Opaque user data to pass to the read and write callbacks.
+ * @param fFlags Flags, see VDSCRIPT_AS_FLAGS_*.
+ *
+ * @note This will automatically register a new type with the identifier given in pszType
+ * used for the pointer. Every variable with this type is automatically treated as a pointer.
+ *
+ * @note If the transitive flag is set the address space stays assigned even if the pointer value
+ * is casted to another pointer type.
+ * In the following example the pointer pStruct will use the registered address space for RTGCPHYS
+ * and dereferencing the pointer causes the read/write callbacks to be triggered.
+ *
+ * ...
+ * Struct *pStruct = (Struct *)(RTGCPHYS)0x12345678;
+ * pStruct->count++;
+ * ...
+ */
+DECLHIDDEN(int) VDScriptCtxAsRegister(VDSCRIPTCTX hScriptCtx, const char *pszType, VDSCRIPTTYPE enmBaseType,
+ PFNVDSCRIPTASREAD pfnRead, PFNVDSCRIPTASWRITE pfnWrite, void *pvUser,
+ uint32_t fFlags);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDScript_h */
diff --git a/src/VBox/Storage/testcase/VDScriptAst.cpp b/src/VBox/Storage/testcase/VDScriptAst.cpp
new file mode 100644
index 00000000..c7fc01f6
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptAst.cpp
@@ -0,0 +1,366 @@
+/* $Id: VDScriptAst.cpp $ */
+/** @file
+ * VBox HDD container test utility - scripting engine AST node related functions.
+ */
+
+/*
+ * Copyright (C) 2013-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 <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+
+#include "VDScriptAst.h"
+
+/**
+ * Put all child nodes of the given expression AST node onto the given to free list.
+ *
+ * @param pList The free list to append everything to.
+ * @param pAstNode The expression node to free.
+ */
+static void vdScriptAstNodeExpressionPutOnFreeList(PRTLISTANCHOR pList, PVDSCRIPTASTCORE pAstNode)
+{
+ AssertMsgReturnVoid(pAstNode->enmClass == VDSCRIPTASTCLASS_EXPRESSION,
+ ("Given AST node is not a statement\n"));
+
+ PVDSCRIPTASTEXPR pExpr = (PVDSCRIPTASTEXPR)pAstNode;
+ switch (pExpr->enmType)
+ {
+ case VDSCRIPTEXPRTYPE_PRIMARY_NUMCONST:
+ case VDSCRIPTEXPRTYPE_PRIMARY_BOOLEAN:
+ break;
+ case VDSCRIPTEXPRTYPE_PRIMARY_STRINGCONST:
+ RTStrFree((char *)pExpr->pszStr);
+ break;
+ case VDSCRIPTEXPRTYPE_PRIMARY_IDENTIFIER:
+ {
+ RTListAppend(pList, &pExpr->pIde->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_ASSIGNMENT_LIST:
+ {
+ while (!RTListIsEmpty(&pExpr->ListExpr))
+ {
+ PVDSCRIPTASTCORE pNode = RTListGetFirst(&pExpr->ListExpr, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pNode->ListNode);
+ RTListAppend(pList, &pNode->ListNode);
+ }
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_POSTFIX_FNCALL:
+ {
+ RTListAppend(pList, &pExpr->FnCall.pFnIde->Core.ListNode);
+ while (!RTListIsEmpty(&pExpr->FnCall.ListArgs))
+ {
+ PVDSCRIPTASTCORE pNode = RTListGetFirst(&pExpr->FnCall.ListArgs, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pNode->ListNode);
+ RTListAppend(pList, &pNode->ListNode);
+ }
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_POSTFIX_DEREFERENCE:
+ case VDSCRIPTEXPRTYPE_POSTFIX_DOT:
+ {
+ RTListAppend(pList, &pExpr->Deref.pIde->Core.ListNode);
+ RTListAppend(pList, &pExpr->Deref.pExpr->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_POSTFIX_INCREMENT:
+ case VDSCRIPTEXPRTYPE_POSTFIX_DECREMENT:
+ case VDSCRIPTEXPRTYPE_UNARY_INCREMENT:
+ case VDSCRIPTEXPRTYPE_UNARY_DECREMENT:
+ case VDSCRIPTEXPRTYPE_UNARY_POSSIGN:
+ case VDSCRIPTEXPRTYPE_UNARY_NEGSIGN:
+ case VDSCRIPTEXPRTYPE_UNARY_INVERT:
+ case VDSCRIPTEXPRTYPE_UNARY_NEGATE:
+ case VDSCRIPTEXPRTYPE_UNARY_REFERENCE:
+ case VDSCRIPTEXPRTYPE_UNARY_DEREFERENCE:
+ {
+ RTListAppend(pList, &pExpr->pExpr->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_MULTIPLICATION:
+ case VDSCRIPTEXPRTYPE_DIVISION:
+ case VDSCRIPTEXPRTYPE_MODULUS:
+ case VDSCRIPTEXPRTYPE_ADDITION:
+ case VDSCRIPTEXPRTYPE_SUBTRACTION:
+ case VDSCRIPTEXPRTYPE_LSR:
+ case VDSCRIPTEXPRTYPE_LSL:
+ case VDSCRIPTEXPRTYPE_LOWER:
+ case VDSCRIPTEXPRTYPE_HIGHER:
+ case VDSCRIPTEXPRTYPE_LOWEREQUAL:
+ case VDSCRIPTEXPRTYPE_HIGHEREQUAL:
+ case VDSCRIPTEXPRTYPE_EQUAL:
+ case VDSCRIPTEXPRTYPE_NOTEQUAL:
+ case VDSCRIPTEXPRTYPE_BITWISE_AND:
+ case VDSCRIPTEXPRTYPE_BITWISE_XOR:
+ case VDSCRIPTEXPRTYPE_BITWISE_OR:
+ case VDSCRIPTEXPRTYPE_LOGICAL_AND:
+ case VDSCRIPTEXPRTYPE_LOGICAL_OR:
+ case VDSCRIPTEXPRTYPE_ASSIGN:
+ case VDSCRIPTEXPRTYPE_ASSIGN_MULT:
+ case VDSCRIPTEXPRTYPE_ASSIGN_DIV:
+ case VDSCRIPTEXPRTYPE_ASSIGN_MOD:
+ case VDSCRIPTEXPRTYPE_ASSIGN_ADD:
+ case VDSCRIPTEXPRTYPE_ASSIGN_SUB:
+ case VDSCRIPTEXPRTYPE_ASSIGN_LSL:
+ case VDSCRIPTEXPRTYPE_ASSIGN_LSR:
+ case VDSCRIPTEXPRTYPE_ASSIGN_AND:
+ case VDSCRIPTEXPRTYPE_ASSIGN_XOR:
+ case VDSCRIPTEXPRTYPE_ASSIGN_OR:
+ {
+ RTListAppend(pList, &pExpr->BinaryOp.pLeftExpr->Core.ListNode);
+ RTListAppend(pList, &pExpr->BinaryOp.pRightExpr->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_INVALID:
+ default:
+ AssertMsgFailedReturnVoid(("Invalid AST node expression type %d\n",
+ pExpr->enmType));
+ }
+}
+
+/**
+ * Free a given statement AST node and put everything on the given to free list.
+ *
+ * @param pList The free list to append everything to.
+ * @param pAstNode The statement node to free.
+ */
+static void vdScriptAstNodeStatmentPutOnFreeList(PRTLISTANCHOR pList, PVDSCRIPTASTCORE pAstNode)
+{
+ AssertMsgReturnVoid(pAstNode->enmClass == VDSCRIPTASTCLASS_STATEMENT,
+ ("Given AST node is not a statement\n"));
+
+ PVDSCRIPTASTSTMT pStmt = (PVDSCRIPTASTSTMT)pAstNode;
+ switch (pStmt->enmStmtType)
+ {
+ case VDSCRIPTSTMTTYPE_COMPOUND:
+ {
+ /* Put all declarations on the to free list. */
+ while (!RTListIsEmpty(&pStmt->Compound.ListDecls))
+ {
+ PVDSCRIPTASTCORE pNode = RTListGetFirst(&pStmt->Compound.ListDecls, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pNode->ListNode);
+ RTListAppend(pList, &pNode->ListNode);
+ }
+
+ /* Put all statements on the to free list. */
+ while (!RTListIsEmpty(&pStmt->Compound.ListStmts))
+ {
+ PVDSCRIPTASTCORE pNode = RTListGetFirst(&pStmt->Compound.ListStmts, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pNode->ListNode);
+ RTListAppend(pList, &pNode->ListNode);
+ }
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_EXPRESSION:
+ {
+ if (pStmt->pExpr)
+ RTListAppend(pList, &pStmt->pExpr->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_IF:
+ {
+ RTListAppend(pList, &pStmt->If.pCond->Core.ListNode);
+ RTListAppend(pList, &pStmt->If.pTrueStmt->Core.ListNode);
+ if (pStmt->If.pElseStmt)
+ RTListAppend(pList, &pStmt->If.pElseStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_SWITCH:
+ {
+ RTListAppend(pList, &pStmt->Switch.pCond->Core.ListNode);
+ RTListAppend(pList, &pStmt->Switch.pStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_WHILE:
+ {
+ RTListAppend(pList, &pStmt->While.pCond->Core.ListNode);
+ RTListAppend(pList, &pStmt->While.pStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_FOR:
+ {
+ RTListAppend(pList, &pStmt->For.pExprStart->Core.ListNode);
+ RTListAppend(pList, &pStmt->For.pExprCond->Core.ListNode);
+ RTListAppend(pList, &pStmt->For.pExpr3->Core.ListNode);
+ RTListAppend(pList, &pStmt->For.pStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_RETURN:
+ {
+ if (pStmt->pExpr)
+ RTListAppend(pList, &pStmt->pExpr->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_CASE:
+ {
+ RTListAppend(pList, &pStmt->Case.pExpr->Core.ListNode);
+ RTListAppend(pList, &pStmt->Case.pStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_DEFAULT:
+ {
+ RTListAppend(pList, &pStmt->Case.pStmt->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_CONTINUE:
+ case VDSCRIPTSTMTTYPE_BREAK:
+ break;
+ case VDSCRIPTSTMTTYPE_INVALID:
+ default:
+ AssertMsgFailedReturnVoid(("Invalid AST node statement type %d\n",
+ pStmt->enmStmtType));
+ }
+}
+
+DECLHIDDEN(void) vdScriptAstNodeFree(PVDSCRIPTASTCORE pAstNode)
+{
+ RTLISTANCHOR ListFree;
+
+ /*
+ * The node is not allowed to be part of a list because we need it
+ * for the nodes to free list.
+ */
+ Assert(RTListIsEmpty(&pAstNode->ListNode));
+ RTListInit(&ListFree);
+ RTListAppend(&ListFree, &pAstNode->ListNode);
+
+ do
+ {
+ pAstNode = RTListGetFirst(&ListFree, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pAstNode->ListNode);
+
+ switch (pAstNode->enmClass)
+ {
+ case VDSCRIPTASTCLASS_FUNCTION:
+ {
+ PVDSCRIPTASTFN pFn = (PVDSCRIPTASTFN)pAstNode;
+
+ if (pFn->pRetType)
+ RTListAppend(&ListFree, &pFn->pRetType->Core.ListNode);
+ if (pFn->pFnIde)
+ RTListAppend(&ListFree, &pFn->pFnIde->Core.ListNode);
+
+ /* Put argument list on the to free list. */
+ while (!RTListIsEmpty(&pFn->ListArgs))
+ {
+ PVDSCRIPTASTCORE pArg = RTListGetFirst(&pFn->ListArgs, VDSCRIPTASTCORE, ListNode);
+ RTListNodeRemove(&pArg->ListNode);
+ RTListAppend(&ListFree, &pArg->ListNode);
+ }
+
+ /* Put compound statement onto the list. */
+ RTListAppend(&ListFree, &pFn->pCompoundStmts->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTASTCLASS_FUNCTIONARG:
+ {
+ PVDSCRIPTASTFNARG pAstNodeArg = (PVDSCRIPTASTFNARG)pAstNode;
+ if (pAstNodeArg->pType)
+ RTListAppend(&ListFree, &pAstNodeArg->pType->Core.ListNode);
+ if (pAstNodeArg->pArgIde)
+ RTListAppend(&ListFree, &pAstNodeArg->pArgIde->Core.ListNode);
+ break;
+ }
+ case VDSCRIPTASTCLASS_IDENTIFIER:
+ break;
+ case VDSCRIPTASTCLASS_DECLARATION:
+ case VDSCRIPTASTCLASS_TYPENAME:
+ {
+ AssertMsgFailed(("TODO\n"));
+ break;
+ }
+ case VDSCRIPTASTCLASS_STATEMENT:
+ {
+ vdScriptAstNodeStatmentPutOnFreeList(&ListFree, pAstNode);
+ break;
+ }
+ case VDSCRIPTASTCLASS_EXPRESSION:
+ {
+ vdScriptAstNodeExpressionPutOnFreeList(&ListFree, pAstNode);
+ break;
+ }
+ case VDSCRIPTASTCLASS_INVALID:
+ default:
+ AssertMsgFailedReturnVoid(("Invalid AST node class given %d\n", pAstNode->enmClass));
+ }
+
+ RTMemFree(pAstNode);
+ } while (!RTListIsEmpty(&ListFree));
+
+}
+
+DECLHIDDEN(PVDSCRIPTASTCORE) vdScriptAstNodeAlloc(VDSCRIPTASTCLASS enmClass)
+{
+ size_t cbAlloc = 0;
+
+ switch (enmClass)
+ {
+ case VDSCRIPTASTCLASS_FUNCTION:
+ cbAlloc = sizeof(VDSCRIPTASTFN);
+ break;
+ case VDSCRIPTASTCLASS_FUNCTIONARG:
+ cbAlloc = sizeof(VDSCRIPTASTFNARG);
+ break;
+ case VDSCRIPTASTCLASS_DECLARATION:
+ cbAlloc = sizeof(VDSCRIPTASTDECL);
+ break;
+ case VDSCRIPTASTCLASS_STATEMENT:
+ cbAlloc = sizeof(VDSCRIPTASTSTMT);
+ break;
+ case VDSCRIPTASTCLASS_EXPRESSION:
+ cbAlloc = sizeof(VDSCRIPTASTEXPR);
+ break;
+ case VDSCRIPTASTCLASS_TYPENAME:
+ cbAlloc = sizeof(VDSCRIPTASTTYPENAME);
+ break;
+ case VDSCRIPTASTCLASS_IDENTIFIER:
+ case VDSCRIPTASTCLASS_INVALID:
+ default:
+ AssertMsgFailedReturn(("Invalid AST node class given %d\n", enmClass), NULL);
+ }
+
+ PVDSCRIPTASTCORE pAstNode = (PVDSCRIPTASTCORE)RTMemAllocZ(cbAlloc);
+ if (pAstNode)
+ {
+ pAstNode->enmClass = enmClass;
+ RTListInit(&pAstNode->ListNode);
+ }
+
+ return pAstNode;
+}
+
+DECLHIDDEN(PVDSCRIPTASTIDE) vdScriptAstNodeIdeAlloc(size_t cchIde)
+{
+ PVDSCRIPTASTIDE pAstNode = (PVDSCRIPTASTIDE)RTMemAllocZ(RT_UOFFSETOF_DYN(VDSCRIPTASTIDE, aszIde[cchIde + 1]));
+ if (pAstNode)
+ {
+ pAstNode->Core.enmClass = VDSCRIPTASTCLASS_IDENTIFIER;
+ RTListInit(&pAstNode->Core.ListNode);
+ }
+
+ return pAstNode;
+}
diff --git a/src/VBox/Storage/testcase/VDScriptAst.h b/src/VBox/Storage/testcase/VDScriptAst.h
new file mode 100644
index 00000000..5b58a36d
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptAst.h
@@ -0,0 +1,594 @@
+/** @file
+ * VBox HDD container test utility - scripting engine, AST related structures.
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDScriptAst_h
+#define VBOX_INCLUDED_SRC_testcase_VDScriptAst_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/list.h>
+
+/**
+ * Position information.
+ */
+typedef struct VDSRCPOS
+{
+ /** Line in the source. */
+ unsigned iLine;
+ /** Current start character .*/
+ unsigned iChStart;
+ /** Current end character. */
+ unsigned iChEnd;
+} VDSRCPOS;
+/** Pointer to a source position. */
+typedef struct VDSRCPOS *PVDSRCPOS;
+
+/**
+ * AST node classes.
+ */
+typedef enum VDSCRIPTASTCLASS
+{
+ /** Invalid. */
+ VDSCRIPTASTCLASS_INVALID = 0,
+ /** Function node. */
+ VDSCRIPTASTCLASS_FUNCTION,
+ /** Function argument. */
+ VDSCRIPTASTCLASS_FUNCTIONARG,
+ /** Identifier node. */
+ VDSCRIPTASTCLASS_IDENTIFIER,
+ /** Declaration node. */
+ VDSCRIPTASTCLASS_DECLARATION,
+ /** Statement node. */
+ VDSCRIPTASTCLASS_STATEMENT,
+ /** Expression node. */
+ VDSCRIPTASTCLASS_EXPRESSION,
+ /** Type name node. */
+ VDSCRIPTASTCLASS_TYPENAME,
+ /** Type specifier node. */
+ VDSCRIPTASTCLASS_TYPESPECIFIER,
+ /** 32bit blowup. */
+ VDSCRIPTASTCLASS_32BIT_HACK = 0x7fffffff
+} VDSCRIPTASTCLASS;
+/** Pointer to an AST node class. */
+typedef VDSCRIPTASTCLASS *PVDSCRIPTASTCLASS;
+
+/**
+ * Core AST structure.
+ */
+typedef struct VDSCRIPTASTCORE
+{
+ /** The node class, used for verification. */
+ VDSCRIPTASTCLASS enmClass;
+ /** List which might be used. */
+ RTLISTNODE ListNode;
+ /** Position in the source file of this node. */
+ VDSRCPOS Pos;
+} VDSCRIPTASTCORE;
+/** Pointer to an AST core structure. */
+typedef VDSCRIPTASTCORE *PVDSCRIPTASTCORE;
+
+/** Pointer to an statement node - forward declaration. */
+typedef struct VDSCRIPTASTSTMT *PVDSCRIPTASTSTMT;
+/** Pointer to an expression node - forward declaration. */
+typedef struct VDSCRIPTASTEXPR *PVDSCRIPTASTEXPR;
+
+/**
+ * AST identifier node.
+ */
+typedef struct VDSCRIPTASTIDE
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Number of characters in the identifier, excluding the zero terminator. */
+ unsigned cchIde;
+ /** Identifier, variable size. */
+ char aszIde[1];
+} VDSCRIPTASTIDE;
+/** Pointer to an identifer node. */
+typedef VDSCRIPTASTIDE *PVDSCRIPTASTIDE;
+
+/**
+ * Type specifier.
+ */
+typedef enum VDSCRIPTASTTYPESPECIFIER
+{
+ /** Invalid type specifier. */
+ VDSCRIPTASTTYPESPECIFIER_INVALID = 0,
+ /** Union type specifier. */
+ VDSCRIPTASTTYPESPECIFIER_UNION,
+ /** Struct type specifier. */
+ VDSCRIPTASTTYPESPECIFIER_STRUCT,
+ /** Identifier of a typedefed type. */
+ VDSCRIPTASTTYPESPECIFIER_IDE,
+ /** 32bit hack. */
+ VDSCRIPTASTTYPESPECIFIER_32BIT_HACK = 0x7fffffff
+} VDSCRIPTASTTYPESPECIFIER;
+/** Pointer to a typespecifier. */
+typedef VDSCRIPTASTTYPESPECIFIER *PVDSCRIPTASTTYPESPECIFIER;
+
+/**
+ * AST type specifier.
+ */
+typedef struct VDSCRIPTASTTYPESPEC
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Specifier type. */
+ VDSCRIPTASTTYPESPECIFIER enmType;
+ /** Type dependent data .*/
+ union
+ {
+ /** Pointer to an identifier for typedefed types. */
+ PVDSCRIPTASTIDE pIde;
+ /** struct or union specifier. */
+ struct
+ {
+ /** Pointer to the identifier, optional. */
+ PVDSCRIPTASTIDE pIde;
+ /** Declaration list - VDSCRIPTAST. */
+ RTLISTANCHOR ListDecl;
+ } StructUnion;
+ };
+} VDSCRIPTASTTYPESPEC;
+/** Pointer to an AST type specifier. */
+typedef VDSCRIPTASTTYPESPEC *PVDSCRIPTASTTYPESPEC;
+
+/**
+ * Storage clase specifier.
+ */
+typedef enum VDSCRIPTASTSTORAGECLASS
+{
+ /** Invalid storage class sepcifier. */
+ VDSCRIPTASTSTORAGECLASS_INVALID = 0,
+ /** A typedef type. */
+ VDSCRIPTASTSTORAGECLASS_TYPEDEF,
+ /** An external declared object. */
+ VDSCRIPTASTSTORAGECLASS_EXTERN,
+ /** A static declared object. */
+ VDSCRIPTASTSTORAGECLASS_STATIC,
+ /** Auto object. */
+ VDSCRIPTASTSTORAGECLASS_AUTO,
+ /** Object should be stored in a register. */
+ VDSCRIPTASTSTORAGECLASS_REGISTER,
+ /** 32bit hack. */
+ VDSCRIPTASTSTORAGECLASS_32BIT_HACK = 0x7fffffff
+} VDSCRIPTASTSTORAGECLASS;
+/** Pointer to a storage class. */
+typedef VDSCRIPTASTSTORAGECLASS *PVDSCRIPTASTSTORAGECLASS;
+
+/**
+ * Type qualifier.
+ */
+typedef enum VDSCRIPTASTTYPEQUALIFIER
+{
+ /** Invalid type qualifier. */
+ VDSCRIPTASTTYPEQUALIFIER_INVALID = 0,
+ /** Const type qualifier. */
+ VDSCRIPTASTTYPEQUALIFIER_CONST,
+ /** Restrict type qualifier. */
+ VDSCRIPTASTTYPEQUALIFIER_RESTRICT,
+ /** Volatile type qualifier. */
+ VDSCRIPTASTTYPEQUALIFIER_VOLATILE,
+ /** 32bit hack. */
+ VDSCRIPTASTTYPEQUALIFIER_32BIT_HACK = 0x7fffffff
+} VDSCRIPTASTTYPEQUALIFIER;
+/** Pointer to a type qualifier. */
+typedef VDSCRIPTASTTYPEQUALIFIER *PVDSCRIPTASTTYPEQUALIFIER;
+
+/**
+ * AST type name node.
+ */
+typedef struct VDSCRIPTASTTYPENAME
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+} VDSCRIPTASTTYPENAME;
+/** Pointer to a type name node. */
+typedef VDSCRIPTASTTYPENAME *PVDSCRIPTASTTYPENAME;
+
+/**
+ * AST declaration node.
+ */
+typedef struct VDSCRIPTASTDECL
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** @todo */
+} VDSCRIPTASTDECL;
+/** Pointer to an declaration node. */
+typedef VDSCRIPTASTDECL *PVDSCRIPTASTDECL;
+
+/**
+ * Expression types.
+ */
+typedef enum VDSCRIPTEXPRTYPE
+{
+ /** Invalid. */
+ VDSCRIPTEXPRTYPE_INVALID = 0,
+ /** Numerical constant. */
+ VDSCRIPTEXPRTYPE_PRIMARY_NUMCONST,
+ /** String constant. */
+ VDSCRIPTEXPRTYPE_PRIMARY_STRINGCONST,
+ /** Boolean constant. */
+ VDSCRIPTEXPRTYPE_PRIMARY_BOOLEAN,
+ /** Identifier. */
+ VDSCRIPTEXPRTYPE_PRIMARY_IDENTIFIER,
+ /** List of assignment expressions as in a = b = c = ... . */
+ VDSCRIPTEXPRTYPE_ASSIGNMENT_LIST,
+ /** Postfix increment expression. */
+ VDSCRIPTEXPRTYPE_POSTFIX_INCREMENT,
+ /** Postfix decrement expression. */
+ VDSCRIPTEXPRTYPE_POSTFIX_DECREMENT,
+ /** Postfix function call expression. */
+ VDSCRIPTEXPRTYPE_POSTFIX_FNCALL,
+ /** Postfix dereference expression. */
+ VDSCRIPTEXPRTYPE_POSTFIX_DEREFERENCE,
+ /** Dot operator (@todo: Is there a better name for it?). */
+ VDSCRIPTEXPRTYPE_POSTFIX_DOT,
+ /** Unary increment expression. */
+ VDSCRIPTEXPRTYPE_UNARY_INCREMENT,
+ /** Unary decrement expression. */
+ VDSCRIPTEXPRTYPE_UNARY_DECREMENT,
+ /** Unary positive sign expression. */
+ VDSCRIPTEXPRTYPE_UNARY_POSSIGN,
+ /** Unary negtive sign expression. */
+ VDSCRIPTEXPRTYPE_UNARY_NEGSIGN,
+ /** Unary invert expression. */
+ VDSCRIPTEXPRTYPE_UNARY_INVERT,
+ /** Unary negate expression. */
+ VDSCRIPTEXPRTYPE_UNARY_NEGATE,
+ /** Unary reference expression. */
+ VDSCRIPTEXPRTYPE_UNARY_REFERENCE,
+ /** Unary dereference expression. */
+ VDSCRIPTEXPRTYPE_UNARY_DEREFERENCE,
+ /** Cast expression. */
+ VDSCRIPTEXPRTYPE_CAST,
+ /** Multiplicative expression. */
+ VDSCRIPTEXPRTYPE_MULTIPLICATION,
+ /** Division expression. */
+ VDSCRIPTEXPRTYPE_DIVISION,
+ /** Modulus expression. */
+ VDSCRIPTEXPRTYPE_MODULUS,
+ /** Addition expression. */
+ VDSCRIPTEXPRTYPE_ADDITION,
+ /** Subtraction expression. */
+ VDSCRIPTEXPRTYPE_SUBTRACTION,
+ /** Logical shift right. */
+ VDSCRIPTEXPRTYPE_LSR,
+ /** Logical shift left. */
+ VDSCRIPTEXPRTYPE_LSL,
+ /** Lower than expression */
+ VDSCRIPTEXPRTYPE_LOWER,
+ /** Higher than expression */
+ VDSCRIPTEXPRTYPE_HIGHER,
+ /** Lower or equal than expression */
+ VDSCRIPTEXPRTYPE_LOWEREQUAL,
+ /** Higher or equal than expression */
+ VDSCRIPTEXPRTYPE_HIGHEREQUAL,
+ /** Equals expression */
+ VDSCRIPTEXPRTYPE_EQUAL,
+ /** Not equal expression */
+ VDSCRIPTEXPRTYPE_NOTEQUAL,
+ /** Bitwise and expression */
+ VDSCRIPTEXPRTYPE_BITWISE_AND,
+ /** Bitwise xor expression */
+ VDSCRIPTEXPRTYPE_BITWISE_XOR,
+ /** Bitwise or expression */
+ VDSCRIPTEXPRTYPE_BITWISE_OR,
+ /** Logical and expression */
+ VDSCRIPTEXPRTYPE_LOGICAL_AND,
+ /** Logical or expression */
+ VDSCRIPTEXPRTYPE_LOGICAL_OR,
+ /** Assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN,
+ /** Multiplicative assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_MULT,
+ /** Division assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_DIV,
+ /** Modulus assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_MOD,
+ /** Additive assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_ADD,
+ /** Subtractive assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_SUB,
+ /** Bitwise left shift assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_LSL,
+ /** Bitwise right shift assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_LSR,
+ /** Bitwise and assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_AND,
+ /** Bitwise xor assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_XOR,
+ /** Bitwise or assign expression */
+ VDSCRIPTEXPRTYPE_ASSIGN_OR,
+ /** 32bit hack. */
+ VDSCRIPTEXPRTYPE_32BIT_HACK = 0x7fffffff
+} VDSCRIPTEXPRTYPE;
+/** Pointer to an expression type. */
+typedef VDSCRIPTEXPRTYPE *PVDSCRIPTEXPRTYPE;
+
+/**
+ * AST expression node.
+ */
+typedef struct VDSCRIPTASTEXPR
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Expression type. */
+ VDSCRIPTEXPRTYPE enmType;
+ /** Expression type dependent data. */
+ union
+ {
+ /** Numerical constant. */
+ uint64_t u64;
+ /** Primary identifier. */
+ PVDSCRIPTASTIDE pIde;
+ /** String literal */
+ const char *pszStr;
+ /** Boolean constant. */
+ bool f;
+ /** List of expressions - VDSCRIPTASTEXPR. */
+ RTLISTANCHOR ListExpr;
+ /** Pointer to another expression. */
+ PVDSCRIPTASTEXPR pExpr;
+ /** Function call expression. */
+ struct
+ {
+ /** Other postfix expression used as the identifier for the function. */
+ PVDSCRIPTASTEXPR pFnIde;
+ /** Argument list if existing. */
+ RTLISTANCHOR ListArgs;
+ } FnCall;
+ /** Binary operation. */
+ struct
+ {
+ /** Left operator. */
+ PVDSCRIPTASTEXPR pLeftExpr;
+ /** Right operator. */
+ PVDSCRIPTASTEXPR pRightExpr;
+ } BinaryOp;
+ /** Dereference or dot operation. */
+ struct
+ {
+ /** The identifier to access. */
+ PVDSCRIPTASTIDE pIde;
+ /** Postfix expression coming after this. */
+ PVDSCRIPTASTEXPR pExpr;
+ } Deref;
+ /** Cast expression. */
+ struct
+ {
+ /** Type name. */
+ PVDSCRIPTASTTYPENAME pTypeName;
+ /** Following cast expression. */
+ PVDSCRIPTASTEXPR pExpr;
+ } Cast;
+ };
+} VDSCRIPTASTEXPR;
+
+/**
+ * AST if node.
+ */
+typedef struct VDSCRIPTASTIF
+{
+ /** Conditional expression. */
+ PVDSCRIPTASTEXPR pCond;
+ /** The true branch */
+ PVDSCRIPTASTSTMT pTrueStmt;
+ /** The else branch, can be NULL if no else branch. */
+ PVDSCRIPTASTSTMT pElseStmt;
+} VDSCRIPTASTIF;
+/** Pointer to an expression node. */
+typedef VDSCRIPTASTIF *PVDSCRIPTASTIF;
+
+/**
+ * AST switch node.
+ */
+typedef struct VDSCRIPTASTSWITCH
+{
+ /** Conditional expression. */
+ PVDSCRIPTASTEXPR pCond;
+ /** The statement to follow. */
+ PVDSCRIPTASTSTMT pStmt;
+} VDSCRIPTASTSWITCH;
+/** Pointer to an expression node. */
+typedef VDSCRIPTASTSWITCH *PVDSCRIPTASTSWITCH;
+
+/**
+ * AST while or do ... while node.
+ */
+typedef struct VDSCRIPTASTWHILE
+{
+ /** Flag whether this is a do while loop. */
+ bool fDoWhile;
+ /** Conditional expression. */
+ PVDSCRIPTASTEXPR pCond;
+ /** The statement to follow. */
+ PVDSCRIPTASTSTMT pStmt;
+} VDSCRIPTASTWHILE;
+/** Pointer to an expression node. */
+typedef VDSCRIPTASTWHILE *PVDSCRIPTASTWHILE;
+
+/**
+ * AST for node.
+ */
+typedef struct VDSCRIPTASTFOR
+{
+ /** Initializer expression. */
+ PVDSCRIPTASTEXPR pExprStart;
+ /** The exit condition. */
+ PVDSCRIPTASTEXPR pExprCond;
+ /** The third expression (normally used to increase/decrease loop variable). */
+ PVDSCRIPTASTEXPR pExpr3;
+ /** The for loop body. */
+ PVDSCRIPTASTSTMT pStmt;
+} VDSCRIPTASTFOR;
+/** Pointer to an expression node. */
+typedef VDSCRIPTASTFOR *PVDSCRIPTASTFOR;
+
+/**
+ * Statement types.
+ */
+typedef enum VDSCRIPTSTMTTYPE
+{
+ /** Invalid. */
+ VDSCRIPTSTMTTYPE_INVALID = 0,
+ /** Compound statement. */
+ VDSCRIPTSTMTTYPE_COMPOUND,
+ /** Expression statement. */
+ VDSCRIPTSTMTTYPE_EXPRESSION,
+ /** if statement. */
+ VDSCRIPTSTMTTYPE_IF,
+ /** switch statement. */
+ VDSCRIPTSTMTTYPE_SWITCH,
+ /** while statement. */
+ VDSCRIPTSTMTTYPE_WHILE,
+ /** for statement. */
+ VDSCRIPTSTMTTYPE_FOR,
+ /** continue statement. */
+ VDSCRIPTSTMTTYPE_CONTINUE,
+ /** break statement. */
+ VDSCRIPTSTMTTYPE_BREAK,
+ /** return statement. */
+ VDSCRIPTSTMTTYPE_RETURN,
+ /** case statement. */
+ VDSCRIPTSTMTTYPE_CASE,
+ /** default statement. */
+ VDSCRIPTSTMTTYPE_DEFAULT,
+ /** 32bit hack. */
+ VDSCRIPTSTMTTYPE_32BIT_HACK = 0x7fffffff
+} VDSCRIPTSTMTTYPE;
+/** Pointer to a statement type. */
+typedef VDSCRIPTSTMTTYPE *PVDSCRIPTSTMTTYPE;
+
+/**
+ * AST statement node.
+ */
+typedef struct VDSCRIPTASTSTMT
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Statement type */
+ VDSCRIPTSTMTTYPE enmStmtType;
+ /** Statement type dependent data. */
+ union
+ {
+ /** Compound statement. */
+ struct
+ {
+ /** List of declarations - VDSCRIPTASTDECL. */
+ RTLISTANCHOR ListDecls;
+ /** List of statements - VDSCRIPTASTSTMT. */
+ RTLISTANCHOR ListStmts;
+ } Compound;
+ /** case, default statement. */
+ struct
+ {
+ /** Pointer to the expression. */
+ PVDSCRIPTASTEXPR pExpr;
+ /** Pointer to the statement. */
+ PVDSCRIPTASTSTMT pStmt;
+ } Case;
+ /** "if" statement. */
+ VDSCRIPTASTIF If;
+ /** "switch" statement. */
+ VDSCRIPTASTSWITCH Switch;
+ /** "while" or "do ... while" loop. */
+ VDSCRIPTASTWHILE While;
+ /** "for" loop. */
+ VDSCRIPTASTFOR For;
+ /** Pointer to another statement. */
+ PVDSCRIPTASTSTMT pStmt;
+ /** Expression statement. */
+ PVDSCRIPTASTEXPR pExpr;
+ };
+} VDSCRIPTASTSTMT;
+
+/**
+ * AST node for one function argument.
+ */
+typedef struct VDSCRIPTASTFNARG
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Identifier describing the type of the argument. */
+ PVDSCRIPTASTIDE pType;
+ /** The name of the argument. */
+ PVDSCRIPTASTIDE pArgIde;
+} VDSCRIPTASTFNARG;
+/** Pointer to a AST function argument node. */
+typedef VDSCRIPTASTFNARG *PVDSCRIPTASTFNARG;
+
+/**
+ * AST node describing a function.
+ */
+typedef struct VDSCRIPTASTFN
+{
+ /** Core structure. */
+ VDSCRIPTASTCORE Core;
+ /** Identifier describing the return type. */
+ PVDSCRIPTASTIDE pRetType;
+ /** Name of the function. */
+ PVDSCRIPTASTIDE pFnIde;
+ /** Number of arguments in the list. */
+ unsigned cArgs;
+ /** Argument list - VDSCRIPTASTFNARG. */
+ RTLISTANCHOR ListArgs;
+ /** Compound statement node. */
+ PVDSCRIPTASTSTMT pCompoundStmts;
+} VDSCRIPTASTFN;
+/** Pointer to a function AST node. */
+typedef VDSCRIPTASTFN *PVDSCRIPTASTFN;
+
+/**
+ * Free the given AST node and all subsequent nodes pointed to
+ * by the given node.
+ *
+ * @param pAstNode The AST node to free.
+ */
+DECLHIDDEN(void) vdScriptAstNodeFree(PVDSCRIPTASTCORE pAstNode);
+
+/**
+ * Allocate a non variable in size AST node of the given class.
+ *
+ * @returns Pointer to the allocated node.
+ * NULL if out of memory.
+ * @param enmClass The class of the AST node.
+ */
+DECLHIDDEN(PVDSCRIPTASTCORE) vdScriptAstNodeAlloc(VDSCRIPTASTCLASS enmClass);
+
+/**
+ * Allocate a IDE node which can hold the given number of characters.
+ *
+ * @returns Pointer to the allocated node.
+ * NULL if out of memory.
+ * @param cchIde Number of characters which can be stored in the node.
+ */
+DECLHIDDEN(PVDSCRIPTASTIDE) vdScriptAstNodeIdeAlloc(size_t cchIde);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDScriptAst_h */
+
diff --git a/src/VBox/Storage/testcase/VDScriptChecker.cpp b/src/VBox/Storage/testcase/VDScriptChecker.cpp
new file mode 100644
index 00000000..92e6d919
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptChecker.cpp
@@ -0,0 +1,44 @@
+/* $Id: VDScriptChecker.cpp $ */
+/** @file
+ * VBox HDD container test utility - scripting engine, type and context checker.
+ */
+
+/*
+ * Copyright (C) 2013-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 <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+
+#include <VBox/log.h>
+
+#include "VDScriptAst.h"
+#include "VDScriptInternal.h"
+
+
+DECLHIDDEN(int) vdScriptCtxCheck(PVDSCRIPTCTXINT pThis)
+{
+ RT_NOREF1(pThis);
+ return VERR_NOT_IMPLEMENTED;
+}
diff --git a/src/VBox/Storage/testcase/VDScriptInternal.h b/src/VBox/Storage/testcase/VDScriptInternal.h
new file mode 100644
index 00000000..fe92ac45
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptInternal.h
@@ -0,0 +1,121 @@
+/** @file
+ *
+ * VBox HDD container test utility - scripting engine, internal script structures.
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDScriptInternal_h
+#define VBOX_INCLUDED_SRC_testcase_VDScriptInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/list.h>
+#include <iprt/string.h>
+
+#include "VDScript.h"
+
+/**
+ * Script function which can be called.
+ */
+typedef struct VDSCRIPTFN
+{
+ /** String space core. */
+ RTSTRSPACECORE Core;
+ /** Flag whether function is defined in the source or was
+ * registered from the outside. */
+ bool fExternal;
+ /** Flag dependent data. */
+ union
+ {
+ /** Data for functions defined in the source. */
+ struct
+ {
+ /** Pointer to the AST defining the function. */
+ PVDSCRIPTASTFN pAstFn;
+ } Internal;
+ /** Data for external defined functions. */
+ struct
+ {
+ /** Callback function. */
+ PFNVDSCRIPTCALLBACK pfnCallback;
+ /** Opaque user data. */
+ void *pvUser;
+ } External;
+ } Type;
+ /** Return type of the function. */
+ VDSCRIPTTYPE enmTypeRetn;
+ /** Number of arguments the function takes. */
+ unsigned cArgs;
+ /** Variable sized array of argument types. */
+ VDSCRIPTTYPE aenmArgTypes[1];
+} VDSCRIPTFN;
+/** Pointer to a script function registration structure. */
+typedef VDSCRIPTFN *PVDSCRIPTFN;
+
+/** Pointer to a tokenize state. */
+typedef struct VDTOKENIZER *PVDTOKENIZER;
+
+/**
+ * Script context.
+ */
+typedef struct VDSCRIPTCTXINT
+{
+ /** String space of external registered and source defined functions. */
+ RTSTRSPACE hStrSpaceFn;
+ /** List of ASTs for functions - VDSCRIPTASTFN. */
+ RTLISTANCHOR ListAst;
+ /** Pointer to the current tokenizer state. */
+ PVDTOKENIZER pTokenizer;
+} VDSCRIPTCTXINT;
+/** Pointer to a script context. */
+typedef VDSCRIPTCTXINT *PVDSCRIPTCTXINT;
+
+/**
+ * Check the context for type correctness.
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ */
+DECLHIDDEN(int) vdScriptCtxCheck(PVDSCRIPTCTXINT pThis);
+
+/**
+ * Interprete a given function AST. The executed functions
+ * must be type correct, otherwise the behavior is undefined
+ * (Will assert in debug builds).
+ *
+ * @returns VBox status code.
+ * @param pThis The script context.
+ * @param pszFn The function name to interprete.
+ * @param paArgs Arguments to pass to the function.
+ * @param cArgs Number of arguments.
+ * @param pRet Where to store the return value on success.
+ *
+ * @note: The AST is not modified in any way during the interpretation process.
+ */
+DECLHIDDEN(int) vdScriptCtxInterprete(PVDSCRIPTCTXINT pThis, const char *pszFn,
+ PVDSCRIPTARG paArgs, unsigned cArgs,
+ PVDSCRIPTARG pRet);
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDScriptInternal_h */
diff --git a/src/VBox/Storage/testcase/VDScriptInterp.cpp b/src/VBox/Storage/testcase/VDScriptInterp.cpp
new file mode 100644
index 00000000..8122c683
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptInterp.cpp
@@ -0,0 +1,1071 @@
+/* $Id: VDScriptInterp.cpp $ */
+/** @file
+ * VBox HDD container test utility - scripting engine, interpreter.
+ */
+
+/*
+ * Copyright (C) 2013-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 <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+
+#include "VDScriptAst.h"
+#include "VDScriptStack.h"
+#include "VDScriptInternal.h"
+
+/**
+ * Interpreter variable.
+ */
+typedef struct VDSCRIPTINTERPVAR
+{
+ /** String space core. */
+ RTSTRSPACECORE Core;
+ /** Value. */
+ VDSCRIPTARG Value;
+} VDSCRIPTINTERPVAR;
+/** Pointer to an interpreter variable. */
+typedef VDSCRIPTINTERPVAR *PVDSCRIPTINTERPVAR;
+
+/**
+ * Block scope.
+ */
+typedef struct VDSCRIPTINTERPSCOPE
+{
+ /** Pointer to the enclosing scope if available. */
+ struct VDSCRIPTINTERPSCOPE *pParent;
+ /** String space of declared variables in this scope. */
+ RTSTRSPACE hStrSpaceVar;
+} VDSCRIPTINTERPSCOPE;
+/** Pointer to a scope block. */
+typedef VDSCRIPTINTERPSCOPE *PVDSCRIPTINTERPSCOPE;
+
+/**
+ * Function call.
+ */
+typedef struct VDSCRIPTINTERPFNCALL
+{
+ /** Pointer to the caller of this function. */
+ struct VDSCRIPTINTERPFNCALL *pCaller;
+ /** Root scope of this function. */
+ VDSCRIPTINTERPSCOPE ScopeRoot;
+ /** Current scope in this function. */
+ PVDSCRIPTINTERPSCOPE pScopeCurr;
+} VDSCRIPTINTERPFNCALL;
+/** Pointer to a function call. */
+typedef VDSCRIPTINTERPFNCALL *PVDSCRIPTINTERPFNCALL;
+
+/**
+ * Interpreter context.
+ */
+typedef struct VDSCRIPTINTERPCTX
+{
+ /** Pointer to the script context. */
+ PVDSCRIPTCTXINT pScriptCtx;
+ /** Current function call entry. */
+ PVDSCRIPTINTERPFNCALL pFnCallCurr;
+ /** Stack of calculated values. */
+ VDSCRIPTSTACK StackValues;
+ /** Evaluation control stack. */
+ VDSCRIPTSTACK StackCtrl;
+} VDSCRIPTINTERPCTX;
+/** Pointer to an interpreter context. */
+typedef VDSCRIPTINTERPCTX *PVDSCRIPTINTERPCTX;
+
+/**
+ * Interpreter control type.
+ */
+typedef enum VDSCRIPTINTERPCTRLTYPE
+{
+ VDSCRIPTINTERPCTRLTYPE_INVALID = 0,
+ /** Function call to evaluate now, all values are computed
+ * and are stored on the value stack.
+ */
+ VDSCRIPTINTERPCTRLTYPE_FN_CALL,
+ /** Cleanup the function call, deleting the scope and restoring the previous one. */
+ VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP,
+ /** If statement to evaluate now, the guard is on the stack. */
+ VDSCRIPTINTERPCTRLTYPE_IF,
+ /** While or for statement. */
+ VDSCRIPTINTERPCTRLTYPE_LOOP,
+ /** switch statement. */
+ VDSCRIPTINTERPCTRLTYPE_SWITCH,
+ /** Compound statement. */
+ VDSCRIPTINTERPCTRLTYPE_COMPOUND,
+ /** 32bit blowup. */
+ VDSCRIPTINTERPCTRLTYPE_32BIT_HACK = 0x7fffffff
+} VDSCRIPTINTERPCTRLTYPE;
+/** Pointer to a control type. */
+typedef VDSCRIPTINTERPCTRLTYPE *PVDSCRIPTINTERPCTRLTYPE;
+
+/**
+ * Interpreter stack control entry.
+ */
+typedef struct VDSCRIPTINTERPCTRL
+{
+ /** Flag whether this entry points to an AST node to evaluate. */
+ bool fEvalAst;
+ /** Flag dependent data. */
+ union
+ {
+ /** Pointer to the AST node to interprete. */
+ PVDSCRIPTASTCORE pAstNode;
+ /** Interpreter control structure. */
+ struct
+ {
+ /** Type of control. */
+ VDSCRIPTINTERPCTRLTYPE enmCtrlType;
+ /** Function call data. */
+ struct
+ {
+ /** Function to call. */
+ PVDSCRIPTFN pFn;
+ } FnCall;
+ /** Compound statement. */
+ struct
+ {
+ /** The compound statement node. */
+ PVDSCRIPTASTSTMT pStmtCompound;
+ /** Current statement evaluated. */
+ PVDSCRIPTASTSTMT pStmtCurr;
+ } Compound;
+ /** Pointer to an AST node. */
+ PVDSCRIPTASTCORE pAstNode;
+ } Ctrl;
+ };
+} VDSCRIPTINTERPCTRL;
+/** Pointer to an exec stack control entry. */
+typedef VDSCRIPTINTERPCTRL *PVDSCRIPTINTERPCTRL;
+
+/**
+ * Record an error while interpreting.
+ *
+ * @returns VBox status code passed.
+ * @param pThis The interpreter context.
+ * @param rc The status code to record.
+ * @param RT_SRC_POS Position in the source code.
+ * @param pszFmt Format string.
+ */
+static int vdScriptInterpreterError(PVDSCRIPTINTERPCTX pThis, int rc, RT_SRC_POS_DECL, const char *pszFmt, ...)
+{
+ RT_NOREF1(pThis); RT_SRC_POS_NOREF();
+ va_list va;
+ va_start(va, pszFmt);
+ RTPrintfV(pszFmt, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Pops the topmost value from the value stack.
+ *
+ * @param pThis The interpreter context.
+ * @param pVal Where to store the value.
+ */
+DECLINLINE(void) vdScriptInterpreterPopValue(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTARG pVal)
+{
+ PVDSCRIPTARG pValStack = (PVDSCRIPTARG)vdScriptStackGetUsed(&pThis->StackValues);
+ if (!pValStack)
+ {
+ RT_ZERO(*pVal);
+ AssertPtrReturnVoid(pValStack);
+ }
+
+ *pVal = *pValStack;
+ vdScriptStackPop(&pThis->StackValues);
+}
+
+/**
+ * Pushes a given value onto the value stack.
+ */
+DECLINLINE(int) vdScriptInterpreterPushValue(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTARG pVal)
+{
+ PVDSCRIPTARG pValStack = (PVDSCRIPTARG)vdScriptStackGetUnused(&pThis->StackValues);
+ if (!pValStack)
+ return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory pushing a value on the value stack");
+
+ *pValStack = *pVal;
+ vdScriptStackPush(&pThis->StackValues);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Pushes an AST node onto the control stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param enmCtrlType The control entry type.
+ */
+DECLINLINE(int) vdScriptInterpreterPushAstEntry(PVDSCRIPTINTERPCTX pThis,
+ PVDSCRIPTASTCORE pAstNode)
+{
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = true;
+ pCtrl->pAstNode = pAstNode;
+ vdScriptStackPush(&pThis->StackCtrl);
+ return VINF_SUCCESS;
+ }
+
+ return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+}
+
+/**
+ * Pushes a control entry without additional data onto the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param enmCtrlType The control entry type.
+ */
+DECLINLINE(int) vdScriptInterpreterPushNonDataCtrlEntry(PVDSCRIPTINTERPCTX pThis,
+ VDSCRIPTINTERPCTRLTYPE enmCtrlType)
+{
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = false;
+ pCtrl->Ctrl.enmCtrlType = enmCtrlType;
+ vdScriptStackPush(&pThis->StackCtrl);
+ return VINF_SUCCESS;
+ }
+
+ return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+}
+
+/**
+ * Pushes a compound statement control entry onto the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pStmtFirst The first statement of the compound statement
+ */
+DECLINLINE(int) vdScriptInterpreterPushCompoundCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
+{
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = false;
+ pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_COMPOUND;
+ pCtrl->Ctrl.Compound.pStmtCompound = pStmt;
+ pCtrl->Ctrl.Compound.pStmtCurr = RTListGetFirst(&pStmt->Compound.ListStmts, VDSCRIPTASTSTMT, Core.ListNode);
+ vdScriptStackPush(&pThis->StackCtrl);
+ return VINF_SUCCESS;
+ }
+
+ return vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+}
+
+/**
+ * Pushes a while statement control entry onto the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pStmt The while statement.
+ */
+DECLINLINE(int) vdScriptInterpreterPushWhileCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = false;
+ pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_LOOP;
+ pCtrl->Ctrl.pAstNode = &pStmt->Core;
+ vdScriptStackPush(&pThis->StackCtrl);
+
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->While.pCond->Core);
+ if ( RT_SUCCESS(rc)
+ && pStmt->While.fDoWhile)
+ {
+ /* Push the statement to execute for do ... while loops because they run at least once. */
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->While.pStmt->Core);
+ if (RT_FAILURE(rc))
+ {
+ /* Cleanup while control statement and AST node. */
+ vdScriptStackPop(&pThis->StackCtrl);
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+ }
+ else if (RT_FAILURE(rc))
+ vdScriptStackPop(&pThis->StackCtrl); /* Cleanup the while control statement. */
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+
+ return rc;
+}
+
+/**
+ * Pushes an if statement control entry onto the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pStmt The if statement.
+ */
+static int vdScriptInterpreterPushIfCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = false;
+ pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_IF;
+ pCtrl->Ctrl.pAstNode = &pStmt->Core;
+ vdScriptStackPush(&pThis->StackCtrl);
+
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->If.pCond->Core);
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+
+ return rc;
+}
+
+/**
+ * Pushes a for statement control entry onto the stack.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pStmt The while statement.
+ */
+DECLINLINE(int) vdScriptInterpreterPushForCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrl)
+ {
+ pCtrl->fEvalAst = false;
+ pCtrl->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_LOOP;
+ pCtrl->Ctrl.pAstNode = &pStmt->Core;
+ vdScriptStackPush(&pThis->StackCtrl);
+
+ /* Push the conditional first and the initializer .*/
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->For.pExprCond->Core);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->For.pExprStart->Core);
+ if (RT_FAILURE(rc))
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory adding an entry on the control stack");
+
+ return rc;
+}
+
+/**
+ * Destroy variable string space callback.
+ */
+static DECLCALLBACK(int) vdScriptInterpreterVarSpaceDestroy(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ RT_NOREF1(pvUser);
+ RTMemFree(pStr);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Setsup a new scope in the current function call.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ */
+static int vdScriptInterpreterScopeCreate(PVDSCRIPTINTERPCTX pThis)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTINTERPSCOPE pScope = (PVDSCRIPTINTERPSCOPE)RTMemAllocZ(sizeof(VDSCRIPTINTERPSCOPE));
+ if (pScope)
+ {
+ pScope->pParent = pThis->pFnCallCurr->pScopeCurr;
+ pScope->hStrSpaceVar = NULL;
+ pThis->pFnCallCurr->pScopeCurr = pScope;
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory allocating new scope");
+
+ return rc;
+}
+
+/**
+ * Destroys the current scope.
+ *
+ * @param pThis The interpreter context.
+ */
+static void vdScriptInterpreterScopeDestroyCurr(PVDSCRIPTINTERPCTX pThis)
+{
+ AssertMsgReturnVoid(pThis->pFnCallCurr->pScopeCurr != &pThis->pFnCallCurr->ScopeRoot,
+ ("Current scope is root scope of function call\n"));
+
+ PVDSCRIPTINTERPSCOPE pScope = pThis->pFnCallCurr->pScopeCurr;
+ pThis->pFnCallCurr->pScopeCurr = pScope->pParent;
+ RTStrSpaceDestroy(&pScope->hStrSpaceVar, vdScriptInterpreterVarSpaceDestroy, NULL);
+ RTMemFree(pScope);
+}
+
+/**
+ * Get the content of the given variable identifier from the current or parent scope.
+ */
+static PVDSCRIPTINTERPVAR vdScriptInterpreterGetVar(PVDSCRIPTINTERPCTX pThis, const char *pszVar)
+{
+ PVDSCRIPTINTERPSCOPE pScopeCurr = pThis->pFnCallCurr->pScopeCurr;
+ PVDSCRIPTINTERPVAR pVar = NULL;
+
+ while ( !pVar
+ && pScopeCurr)
+ {
+ pVar = (PVDSCRIPTINTERPVAR)RTStrSpaceGet(&pScopeCurr->hStrSpaceVar, pszVar);
+ if (pVar)
+ break;
+ pScopeCurr = pScopeCurr->pParent;
+ }
+
+
+ return pVar;
+}
+
+/**
+ * Evaluate an expression.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pExpr The expression to evaluate.
+ */
+static int vdScriptInterpreterEvaluateExpression(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTEXPR pExpr)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (pExpr->enmType)
+ {
+ case VDSCRIPTEXPRTYPE_PRIMARY_NUMCONST:
+ {
+ /* Push the numerical constant on the value stack. */
+ VDSCRIPTARG NumConst;
+ NumConst.enmType = VDSCRIPTTYPE_UINT64;
+ NumConst.u64 = pExpr->u64;
+ rc = vdScriptInterpreterPushValue(pThis, &NumConst);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_PRIMARY_STRINGCONST:
+ {
+ /* Push the string literal on the value stack. */
+ VDSCRIPTARG StringConst;
+ StringConst.enmType = VDSCRIPTTYPE_STRING;
+ StringConst.psz = pExpr->pszStr;
+ rc = vdScriptInterpreterPushValue(pThis, &StringConst);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_PRIMARY_BOOLEAN:
+ {
+ VDSCRIPTARG BoolConst;
+ BoolConst.enmType = VDSCRIPTTYPE_BOOL;
+ BoolConst.f = pExpr->f;
+ rc = vdScriptInterpreterPushValue(pThis, &BoolConst);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_PRIMARY_IDENTIFIER:
+ {
+ /* Look it up and push the value onto the value stack. */
+ PVDSCRIPTINTERPVAR pVar = vdScriptInterpreterGetVar(pThis, pExpr->pIde->aszIde);
+
+ AssertPtrReturn(pVar, VERR_IPE_UNINITIALIZED_STATUS);
+ rc = vdScriptInterpreterPushValue(pThis, &pVar->Value);
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_POSTFIX_INCREMENT:
+ case VDSCRIPTEXPRTYPE_POSTFIX_DECREMENT:
+ AssertMsgFailed(("TODO\n"));
+ RT_FALL_THRU();
+ case VDSCRIPTEXPRTYPE_POSTFIX_FNCALL:
+ {
+ PVDSCRIPTFN pFn = (PVDSCRIPTFN)RTStrSpaceGet(&pThis->pScriptCtx->hStrSpaceFn, pExpr->FnCall.pFnIde->pIde->aszIde);
+ if (pFn)
+ {
+ /* Push a function call control entry on the stack. */
+ PVDSCRIPTINTERPCTRL pCtrlFn = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUnused(&pThis->StackCtrl);
+ if (pCtrlFn)
+ {
+ pCtrlFn->fEvalAst = false;
+ pCtrlFn->Ctrl.enmCtrlType = VDSCRIPTINTERPCTRLTYPE_FN_CALL;
+ pCtrlFn->Ctrl.FnCall.pFn = pFn;
+ vdScriptStackPush(&pThis->StackCtrl);
+
+ /* Push parameter expressions on the stack. */
+ PVDSCRIPTASTEXPR pArg = RTListGetFirst(&pExpr->FnCall.ListArgs, VDSCRIPTASTEXPR, Core.ListNode);
+ while (pArg)
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pArg->Core);
+ if (RT_FAILURE(rc))
+ break;
+ pArg = RTListGetNext(&pExpr->FnCall.ListArgs, pArg, VDSCRIPTASTEXPR, Core.ListNode);
+ }
+ }
+ }
+ else
+ AssertMsgFailed(("Invalid program given, unknown function: %s\n", pExpr->FnCall.pFnIde->pIde->aszIde));
+ break;
+ }
+ case VDSCRIPTEXPRTYPE_UNARY_INCREMENT:
+ case VDSCRIPTEXPRTYPE_UNARY_DECREMENT:
+ case VDSCRIPTEXPRTYPE_UNARY_POSSIGN:
+ case VDSCRIPTEXPRTYPE_UNARY_NEGSIGN:
+ case VDSCRIPTEXPRTYPE_UNARY_INVERT:
+ case VDSCRIPTEXPRTYPE_UNARY_NEGATE:
+ case VDSCRIPTEXPRTYPE_MULTIPLICATION:
+ case VDSCRIPTEXPRTYPE_DIVISION:
+ case VDSCRIPTEXPRTYPE_MODULUS:
+ case VDSCRIPTEXPRTYPE_ADDITION:
+ case VDSCRIPTEXPRTYPE_SUBTRACTION:
+ case VDSCRIPTEXPRTYPE_LSR:
+ case VDSCRIPTEXPRTYPE_LSL:
+ case VDSCRIPTEXPRTYPE_LOWER:
+ case VDSCRIPTEXPRTYPE_HIGHER:
+ case VDSCRIPTEXPRTYPE_LOWEREQUAL:
+ case VDSCRIPTEXPRTYPE_HIGHEREQUAL:
+ case VDSCRIPTEXPRTYPE_EQUAL:
+ case VDSCRIPTEXPRTYPE_NOTEQUAL:
+ case VDSCRIPTEXPRTYPE_BITWISE_AND:
+ case VDSCRIPTEXPRTYPE_BITWISE_XOR:
+ case VDSCRIPTEXPRTYPE_BITWISE_OR:
+ case VDSCRIPTEXPRTYPE_LOGICAL_AND:
+ case VDSCRIPTEXPRTYPE_LOGICAL_OR:
+ case VDSCRIPTEXPRTYPE_ASSIGN:
+ case VDSCRIPTEXPRTYPE_ASSIGN_MULT:
+ case VDSCRIPTEXPRTYPE_ASSIGN_DIV:
+ case VDSCRIPTEXPRTYPE_ASSIGN_MOD:
+ case VDSCRIPTEXPRTYPE_ASSIGN_ADD:
+ case VDSCRIPTEXPRTYPE_ASSIGN_SUB:
+ case VDSCRIPTEXPRTYPE_ASSIGN_LSL:
+ case VDSCRIPTEXPRTYPE_ASSIGN_LSR:
+ case VDSCRIPTEXPRTYPE_ASSIGN_AND:
+ case VDSCRIPTEXPRTYPE_ASSIGN_XOR:
+ case VDSCRIPTEXPRTYPE_ASSIGN_OR:
+ case VDSCRIPTEXPRTYPE_ASSIGNMENT_LIST:
+ AssertMsgFailed(("TODO\n"));
+ RT_FALL_THRU();
+ default:
+ AssertMsgFailed(("Invalid expression type: %d\n", pExpr->enmType));
+ }
+ return rc;
+}
+
+/**
+ * Evaluate a statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pStmt The statement to evaluate.
+ */
+static int vdScriptInterpreterEvaluateStatement(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTSTMT pStmt)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (pStmt->enmStmtType)
+ {
+ case VDSCRIPTSTMTTYPE_COMPOUND:
+ {
+ /* Setup new scope. */
+ rc = vdScriptInterpreterScopeCreate(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Declarations */
+ rc = vdScriptInterpreterPushCompoundCtrlEntry(pThis, pStmt);
+ }
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_EXPRESSION:
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pStmt->pExpr->Core);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_IF:
+ {
+ rc = vdScriptInterpreterPushIfCtrlEntry(pThis, pStmt);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_SWITCH:
+ AssertMsgFailed(("TODO\n"));
+ break;
+ case VDSCRIPTSTMTTYPE_WHILE:
+ {
+ rc = vdScriptInterpreterPushWhileCtrlEntry(pThis, pStmt);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_RETURN:
+ {
+ /* Walk up the control stack until we reach a function cleanup entry. */
+ PVDSCRIPTINTERPCTRL pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ while ( pCtrl
+ && ( pCtrl->fEvalAst
+ || pCtrl->Ctrl.enmCtrlType != VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP))
+ {
+ /* Cleanup up any compound statement scope. */
+ if ( !pCtrl->fEvalAst
+ && pCtrl->Ctrl.enmCtrlType == VDSCRIPTINTERPCTRLTYPE_COMPOUND)
+ vdScriptInterpreterScopeDestroyCurr(pThis);
+
+ vdScriptStackPop(&pThis->StackCtrl);
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ }
+ AssertMsg(RT_VALID_PTR(pCtrl), ("Incorrect program, return outside of function\n"));
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_FOR:
+ {
+ rc = vdScriptInterpreterPushForCtrlEntry(pThis, pStmt);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_CONTINUE:
+ {
+ /* Remove everything up to a loop control entry. */
+ PVDSCRIPTINTERPCTRL pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ while ( pCtrl
+ && ( pCtrl->fEvalAst
+ || pCtrl->Ctrl.enmCtrlType != VDSCRIPTINTERPCTRLTYPE_LOOP))
+ {
+ /* Cleanup up any compound statement scope. */
+ if ( !pCtrl->fEvalAst
+ && pCtrl->Ctrl.enmCtrlType == VDSCRIPTINTERPCTRLTYPE_COMPOUND)
+ vdScriptInterpreterScopeDestroyCurr(pThis);
+
+ vdScriptStackPop(&pThis->StackCtrl);
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ }
+ AssertMsg(RT_VALID_PTR(pCtrl), ("Incorrect program, continue outside of loop\n"));
+
+ /* Put the conditionals for while and for loops onto the control stack again. */
+ PVDSCRIPTASTSTMT pLoopStmt = (PVDSCRIPTASTSTMT)pCtrl->Ctrl.pAstNode;
+
+ AssertMsg( pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_WHILE
+ || pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_FOR,
+ ("Invalid statement type, must be for or while loop\n"));
+
+ if (pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_FOR)
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pExprCond->Core);
+ else if (!pLoopStmt->While.fDoWhile)
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->While.pCond->Core);
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_BREAK:
+ {
+ /* Remove everything including the loop control statement. */
+ PVDSCRIPTINTERPCTRL pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ while ( pCtrl
+ && ( pCtrl->fEvalAst
+ || pCtrl->Ctrl.enmCtrlType != VDSCRIPTINTERPCTRLTYPE_LOOP))
+ {
+ /* Cleanup up any compound statement scope. */
+ if ( !pCtrl->fEvalAst
+ && pCtrl->Ctrl.enmCtrlType == VDSCRIPTINTERPCTRLTYPE_COMPOUND)
+ vdScriptInterpreterScopeDestroyCurr(pThis);
+
+ vdScriptStackPop(&pThis->StackCtrl);
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ }
+ AssertMsg(RT_VALID_PTR(pCtrl), ("Incorrect program, break outside of loop\n"));
+ vdScriptStackPop(&pThis->StackCtrl); /* Remove loop control statement. */
+ break;
+ }
+ case VDSCRIPTSTMTTYPE_CASE:
+ case VDSCRIPTSTMTTYPE_DEFAULT:
+ AssertMsgFailed(("TODO\n"));
+ break;
+ default:
+ AssertMsgFailed(("Invalid statement type: %d\n", pStmt->enmStmtType));
+ }
+
+ return rc;
+}
+
+/**
+ * Evaluates the given AST node.
+ *
+ * @returns VBox statuse code.
+ * @param pThis The interpreter context.
+ * @param pAstNode The AST node to interpret.
+ */
+static int vdScriptInterpreterEvaluateAst(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTASTCORE pAstNode)
+{
+ int rc = VERR_NOT_IMPLEMENTED;
+
+ switch (pAstNode->enmClass)
+ {
+ case VDSCRIPTASTCLASS_DECLARATION:
+ {
+ AssertMsgFailed(("TODO\n"));
+ break;
+ }
+ case VDSCRIPTASTCLASS_STATEMENT:
+ {
+ rc = vdScriptInterpreterEvaluateStatement(pThis, (PVDSCRIPTASTSTMT)pAstNode);
+ break;
+ }
+ case VDSCRIPTASTCLASS_EXPRESSION:
+ {
+ rc = vdScriptInterpreterEvaluateExpression(pThis, (PVDSCRIPTASTEXPR)pAstNode);
+ break;
+ }
+ /* These should never ever appear here. */
+ case VDSCRIPTASTCLASS_IDENTIFIER:
+ case VDSCRIPTASTCLASS_FUNCTION:
+ case VDSCRIPTASTCLASS_FUNCTIONARG:
+ case VDSCRIPTASTCLASS_INVALID:
+ default:
+ AssertMsgFailed(("Invalid AST node class: %d\n", pAstNode->enmClass));
+ }
+
+ return rc;
+}
+
+/**
+ * Evaluate a function call.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pFn The function execute.
+ */
+static int vdScriptInterpreterFnCall(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTFN pFn)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!pFn->fExternal)
+ {
+ PVDSCRIPTASTFN pAstFn = pFn->Type.Internal.pAstFn;
+
+ /* Add function call cleanup marker on the stack first. */
+ rc = vdScriptInterpreterPushNonDataCtrlEntry(pThis, VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create function call frame and set it up. */
+ PVDSCRIPTINTERPFNCALL pFnCall = (PVDSCRIPTINTERPFNCALL)RTMemAllocZ(sizeof(VDSCRIPTINTERPFNCALL));
+ if (pFnCall)
+ {
+ pFnCall->pCaller = pThis->pFnCallCurr;
+ pFnCall->ScopeRoot.pParent = NULL;
+ pFnCall->ScopeRoot.hStrSpaceVar = NULL;
+ pFnCall->pScopeCurr = &pFnCall->ScopeRoot;
+
+ /* Add the variables, remember order. The first variable in the argument has the value at the top of the value stack. */
+ PVDSCRIPTASTFNARG pArg = RTListGetFirst(&pAstFn->ListArgs, VDSCRIPTASTFNARG, Core.ListNode);
+ for (unsigned i = 0; i < pAstFn->cArgs; i++)
+ {
+ PVDSCRIPTINTERPVAR pVar = (PVDSCRIPTINTERPVAR)RTMemAllocZ(sizeof(VDSCRIPTINTERPVAR));
+ if (pVar)
+ {
+ pVar->Core.pszString = pArg->pArgIde->aszIde;
+ pVar->Core.cchString = pArg->pArgIde->cchIde;
+ vdScriptInterpreterPopValue(pThis, &pVar->Value);
+ bool fInserted = RTStrSpaceInsert(&pFnCall->ScopeRoot.hStrSpaceVar, &pVar->Core);
+ Assert(fInserted); RT_NOREF_PV(fInserted);
+ }
+ else
+ {
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory creating a variable");
+ break;
+ }
+ pArg = RTListGetNext(&pAstFn->ListArgs, pArg, VDSCRIPTASTFNARG, Core.ListNode);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Push compount statement on the control stack and make the newly created
+ * call frame the current one.
+ */
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pAstFn->pCompoundStmts->Core);
+ if (RT_SUCCESS(rc))
+ pThis->pFnCallCurr = pFnCall;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTStrSpaceDestroy(&pFnCall->ScopeRoot.hStrSpaceVar, vdScriptInterpreterVarSpaceDestroy, NULL);
+ RTMemFree(pFnCall);
+ }
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS, "Out of memory creating a call frame");
+ }
+ }
+ else
+ {
+ /* External function call, build the argument list. */
+ if (pFn->cArgs)
+ {
+ PVDSCRIPTARG paArgs = (PVDSCRIPTARG)RTMemAllocZ(pFn->cArgs * sizeof(VDSCRIPTARG));
+ if (paArgs)
+ {
+ for (unsigned i = 0; i < pFn->cArgs; i++)
+ vdScriptInterpreterPopValue(pThis, &paArgs[i]);
+
+ rc = pFn->Type.External.pfnCallback(paArgs, pFn->Type.External.pvUser);
+ RTMemFree(paArgs);
+ }
+ else
+ rc = vdScriptInterpreterError(pThis, VERR_NO_MEMORY, RT_SRC_POS,
+ "Out of memory creating argument array for external function call");
+ }
+ else
+ rc = pFn->Type.External.pfnCallback(NULL, pFn->Type.External.pvUser);
+ }
+
+ return rc;
+}
+
+/**
+ * Evaluate interpreter control statement.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ * @param pCtrl The control entry to evaluate.
+ */
+static int vdScriptInterpreterEvaluateCtrlEntry(PVDSCRIPTINTERPCTX pThis, PVDSCRIPTINTERPCTRL pCtrl)
+{
+ int rc = VINF_SUCCESS;
+
+ Assert(!pCtrl->fEvalAst);
+ switch (pCtrl->Ctrl.enmCtrlType)
+ {
+ case VDSCRIPTINTERPCTRLTYPE_FN_CALL:
+ {
+ PVDSCRIPTFN pFn = pCtrl->Ctrl.FnCall.pFn;
+
+ vdScriptStackPop(&pThis->StackCtrl);
+ rc = vdScriptInterpreterFnCall(pThis, pFn);
+ break;
+ }
+ case VDSCRIPTINTERPCTRLTYPE_FN_CALL_CLEANUP:
+ {
+ vdScriptStackPop(&pThis->StackCtrl);
+
+ /* Delete function call entry. */
+ AssertPtr(pThis->pFnCallCurr);
+ PVDSCRIPTINTERPFNCALL pFnCallFree = pThis->pFnCallCurr;
+
+ pThis->pFnCallCurr = pFnCallFree->pCaller;
+ Assert(pFnCallFree->pScopeCurr == &pFnCallFree->ScopeRoot);
+ RTStrSpaceDestroy(&pFnCallFree->ScopeRoot.hStrSpaceVar, vdScriptInterpreterVarSpaceDestroy, NULL);
+ RTMemFree(pFnCallFree);
+ break;
+ }
+ case VDSCRIPTINTERPCTRLTYPE_COMPOUND:
+ {
+ if (!pCtrl->Ctrl.Compound.pStmtCurr)
+ {
+ /* Evaluated last statement, cleanup and remove the control statement from the stack. */
+ vdScriptInterpreterScopeDestroyCurr(pThis);
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+ else
+ {
+ /* Push the current statement onto the control stack and move on. */
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pCtrl->Ctrl.Compound.pStmtCurr->Core);
+ if (RT_SUCCESS(rc))
+ {
+ pCtrl->Ctrl.Compound.pStmtCurr = RTListGetNext(&pCtrl->Ctrl.Compound.pStmtCompound->Compound.ListStmts,
+ pCtrl->Ctrl.Compound.pStmtCurr, VDSCRIPTASTSTMT, Core.ListNode);
+ }
+ }
+ break;
+ }
+ case VDSCRIPTINTERPCTRLTYPE_LOOP:
+ {
+ PVDSCRIPTASTSTMT pLoopStmt = (PVDSCRIPTASTSTMT)pCtrl->Ctrl.pAstNode;
+
+ /* Check whether the condition passed. */
+ VDSCRIPTARG Cond;
+ vdScriptInterpreterPopValue(pThis, &Cond);
+ AssertMsg(Cond.enmType == VDSCRIPTTYPE_BOOL,
+ ("Value on stack is not of boolean type\n"));
+
+ if (Cond.f)
+ {
+ /* Execute the loop another round. */
+ if (pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_WHILE)
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->While.pCond->Core);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->While.pStmt->Core);
+ if (RT_FAILURE(rc))
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+ }
+ else
+ {
+ AssertMsg(pLoopStmt->enmStmtType == VDSCRIPTSTMTTYPE_FOR,
+ ("Not a for statement\n"));
+
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pExprCond->Core);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pExpr3->Core);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pLoopStmt->For.pStmt->Core);
+ if (RT_FAILURE(rc))
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+
+ if (RT_FAILURE(rc))
+ vdScriptStackPop(&pThis->StackCtrl);
+ }
+ }
+ }
+ else
+ vdScriptStackPop(&pThis->StackCtrl); /* Remove loop control statement. */
+ break;
+ }
+ case VDSCRIPTINTERPCTRLTYPE_IF:
+ {
+ PVDSCRIPTASTSTMT pIfStmt = (PVDSCRIPTASTSTMT)pCtrl->Ctrl.pAstNode;
+
+ vdScriptStackPop(&pThis->StackCtrl); /* Remove if control statement. */
+
+ /* Check whether the condition passed. */
+ VDSCRIPTARG Cond;
+ vdScriptInterpreterPopValue(pThis, &Cond);
+ AssertMsg(Cond.enmType == VDSCRIPTTYPE_BOOL,
+ ("Value on stack is not of boolean type\n"));
+
+ if (Cond.f)
+ {
+ /* Execute the true branch. */
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pIfStmt->If.pTrueStmt->Core);
+ }
+ else if (pIfStmt->If.pElseStmt)
+ rc = vdScriptInterpreterPushAstEntry(pThis, &pIfStmt->If.pElseStmt->Core);
+
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid evaluation control type on the stack: %d\n",
+ pCtrl->Ctrl.enmCtrlType));
+ }
+
+ return rc;
+}
+
+/**
+ * The interpreter evaluation core loop.
+ *
+ * @returns VBox status code.
+ * @param pThis The interpreter context.
+ */
+static int vdScriptInterpreterEvaluate(PVDSCRIPTINTERPCTX pThis)
+{
+ int rc = VINF_SUCCESS;
+ PVDSCRIPTINTERPCTRL pCtrl = NULL;
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ while (pCtrl)
+ {
+ if (pCtrl->fEvalAst)
+ {
+ PVDSCRIPTASTCORE pAstNode = pCtrl->pAstNode;
+ vdScriptStackPop(&pThis->StackCtrl);
+
+ rc = vdScriptInterpreterEvaluateAst(pThis, pAstNode);
+ }
+ else
+ rc = vdScriptInterpreterEvaluateCtrlEntry(pThis, pCtrl);
+
+ pCtrl = (PVDSCRIPTINTERPCTRL)vdScriptStackGetUsed(&pThis->StackCtrl);
+ }
+
+ return rc;
+}
+
+DECLHIDDEN(int) vdScriptCtxInterprete(PVDSCRIPTCTXINT pThis, const char *pszFn,
+ PVDSCRIPTARG paArgs, unsigned cArgs,
+ PVDSCRIPTARG pRet)
+{
+ RT_NOREF1(pRet);
+ int rc = VINF_SUCCESS;
+ VDSCRIPTINTERPCTX InterpCtx;
+ PVDSCRIPTFN pFn = NULL;
+
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFn, VERR_INVALID_POINTER);
+ AssertReturn( (!cArgs && !paArgs)
+ || (cArgs && paArgs), VERR_INVALID_PARAMETER);
+
+ InterpCtx.pScriptCtx = pThis;
+ InterpCtx.pFnCallCurr = NULL;
+ vdScriptStackInit(&InterpCtx.StackValues, sizeof(VDSCRIPTARG));
+ vdScriptStackInit(&InterpCtx.StackCtrl, sizeof(VDSCRIPTINTERPCTRL));
+
+ pFn = (PVDSCRIPTFN)RTStrSpaceGet(&pThis->hStrSpaceFn, pszFn);
+ if (pFn)
+ {
+ if (cArgs == pFn->cArgs)
+ {
+ /* Push the arguments onto the stack. */
+ /** @todo Check expected and given argument types. */
+ for (unsigned i = 0; i < cArgs; i++)
+ {
+ PVDSCRIPTARG pArg = (PVDSCRIPTARG)vdScriptStackGetUnused(&InterpCtx.StackValues);
+ *pArg = paArgs[i];
+ vdScriptStackPush(&InterpCtx.StackValues);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Setup function call frame and parameters. */
+ rc = vdScriptInterpreterFnCall(&InterpCtx, pFn);
+ if (RT_SUCCESS(rc))
+ {
+ /* Run the interpreter. */
+ rc = vdScriptInterpreterEvaluate(&InterpCtx);
+ vdScriptStackDestroy(&InterpCtx.StackValues);
+ vdScriptStackDestroy(&InterpCtx.StackCtrl);
+ }
+ }
+ }
+ else
+ rc = vdScriptInterpreterError(&InterpCtx, VERR_INVALID_PARAMETER, RT_SRC_POS, "Invalid number of parameters, expected %d got %d", pFn->cArgs, cArgs);
+ }
+ else
+ rc = vdScriptInterpreterError(&InterpCtx, VERR_NOT_FOUND, RT_SRC_POS, "Function with identifier \"%s\" not found", pszFn);
+
+
+ return rc;
+}
diff --git a/src/VBox/Storage/testcase/VDScriptStack.h b/src/VBox/Storage/testcase/VDScriptStack.h
new file mode 100644
index 00000000..1d39591e
--- /dev/null
+++ b/src/VBox/Storage/testcase/VDScriptStack.h
@@ -0,0 +1,152 @@
+/** @file
+ *
+ * VBox HDD container test utility - scripting engine, internal stack implementation.
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_testcase_VDScriptStack_h
+#define VBOX_INCLUDED_SRC_testcase_VDScriptStack_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/list.h>
+#include <iprt/string.h>
+
+#include "VDScript.h"
+
+/**
+ * Stack structure.
+ */
+typedef struct VDSCRIPTSTACK
+{
+ /** Size of one stack element. */
+ size_t cbStackEntry;
+ /** Stack memory. */
+ void *pvStack;
+ /** Number of elements on the stack. */
+ unsigned cOnStack;
+ /** Maximum number of elements the stack can hold. */
+ unsigned cOnStackMax;
+} VDSCRIPTSTACK;
+/** Pointer to a stack. */
+typedef VDSCRIPTSTACK *PVDSCRIPTSTACK;
+
+/**
+ * Init the stack structure.
+ *
+ * @param pStack The stack to initialize.
+ * @param cbStackEntry The size of one stack entry.
+ */
+DECLINLINE(void) vdScriptStackInit(PVDSCRIPTSTACK pStack, size_t cbStackEntry)
+{
+ pStack->cbStackEntry = cbStackEntry;
+ pStack->pvStack = NULL;
+ pStack->cOnStack = 0;
+ pStack->cOnStackMax = 0;
+}
+
+/**
+ * Destroys the given stack freeing all memory.
+ *
+ * @param pStack The stack to destroy.
+ */
+DECLINLINE(void) vdScriptStackDestroy(PVDSCRIPTSTACK pStack)
+{
+ if (pStack->pvStack)
+ RTMemFree(pStack->pvStack);
+ pStack->cbStackEntry = 0;
+ pStack->pvStack = NULL;
+ pStack->cOnStack = 0;
+ pStack->cOnStackMax = 0;
+}
+
+/**
+ * Gets the topmost unused stack entry.
+ *
+ * @returns Pointer to the first unused entry.
+ * NULL if there is no room left and increasing the stack failed.
+ * @param pStack The stack.
+ */
+DECLINLINE(void *)vdScriptStackGetUnused(PVDSCRIPTSTACK pStack)
+{
+ void *pvElem = NULL;
+
+ if (pStack->cOnStack >= pStack->cOnStackMax)
+ {
+ unsigned cOnStackMaxNew = pStack->cOnStackMax + 10;
+ void *pvStackNew = NULL;
+
+ /* Try to increase stack space. */
+ pvStackNew = RTMemRealloc(pStack->pvStack, cOnStackMaxNew * pStack->cbStackEntry);
+ if (pvStackNew)
+ {
+ pStack->pvStack = pvStackNew;
+ pStack->cOnStackMax = cOnStackMaxNew;
+ }
+
+ }
+
+ if (pStack->cOnStack < pStack->cOnStackMax)
+ pvElem = (char *)pStack->pvStack + pStack->cOnStack * pStack->cbStackEntry;
+
+ return pvElem;
+}
+
+/**
+ * Gets the topmost used entry on the stack.
+ *
+ * @returns Pointer to the first used entry
+ * or NULL if the stack is empty.
+ * @param pStack The stack.
+ */
+DECLINLINE(void *)vdScriptStackGetUsed(PVDSCRIPTSTACK pStack)
+{
+ if (!pStack->cOnStack)
+ return NULL;
+ else
+ return (char *)pStack->pvStack + (pStack->cOnStack - 1) * pStack->cbStackEntry;
+}
+
+/**
+ * Increases the used element count for the given stack.
+ *
+ * @param pStack The stack.
+ */
+DECLINLINE(void) vdScriptStackPush(PVDSCRIPTSTACK pStack)
+{
+ pStack->cOnStack++;
+}
+
+/**
+ * Decreases the used element count for the given stack.
+ *
+ * @param pStack The stack.
+ */
+DECLINLINE(void) vdScriptStackPop(PVDSCRIPTSTACK pStack)
+{
+ pStack->cOnStack--;
+}
+
+#endif /* !VBOX_INCLUDED_SRC_testcase_VDScriptStack_h */
diff --git a/src/VBox/Storage/testcase/tstVD-2.cpp b/src/VBox/Storage/testcase/tstVD-2.cpp
new file mode 100644
index 00000000..34223714
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVD-2.cpp
@@ -0,0 +1,272 @@
+/** @file
+ *
+ * Simple VBox HDD container test utility. Only fast tests.
+ */
+
+/*
+ * Copyright (C) 2006-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/err.h>
+#include <VBox/vd.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/initterm.h>
+#include <iprt/rand.h>
+#include "stdio.h"
+#include "stdlib.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The error count. */
+unsigned g_cErrors = 0;
+
+static struct KeyValuePair {
+ const char *key;
+ const char *value;
+} aCfgNode[] = {
+ { "TargetName", "test" },
+ { "LUN", "1" },
+ { "TargetAddress", "address" },
+ { NULL, NULL }
+};
+
+static DECLCALLBACK(bool) tstAreKeysValid(void *pvUser, const char *pszzValid)
+{
+ RT_NOREF2(pvUser, pszzValid);
+ return true;
+}
+
+static const char *tstGetValueByKey(const char *pszKey)
+{
+ for (int i = 0; aCfgNode[i].key; i++)
+ if (!strcmp(aCfgNode[i].key, pszKey))
+ return aCfgNode[i].value;
+ return NULL;
+}
+
+static DECLCALLBACK(int) tstQuerySize(void *pvUser, const char *pszName, size_t *pcbValue)
+{
+ RT_NOREF1(pvUser);
+ const char *pszValue = tstGetValueByKey(pszName);
+ if (!pszValue)
+ return VERR_CFGM_VALUE_NOT_FOUND;
+ *pcbValue = strlen(pszValue) + 1;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstQuery(void *pvUser, const char *pszName, char *pszValue, size_t cchValue)
+{
+ RT_NOREF1(pvUser);
+ const char *pszTmp = tstGetValueByKey(pszName);
+ if (!pszValue)
+ return VERR_CFGM_VALUE_NOT_FOUND;
+ size_t cchTmp = strlen(pszTmp) + 1;
+ if (cchValue < cchTmp)
+ return VERR_CFGM_NOT_ENOUGH_SPACE;
+ memcpy(pszValue, pszTmp, cchTmp);
+ return VINF_SUCCESS;
+}
+
+static const char *tstVDDeviceType(VDTYPE enmType)
+{
+ switch (enmType)
+ {
+ case VDTYPE_HDD:
+ return "HardDisk";
+ case VDTYPE_OPTICAL_DISC:
+ return "OpticalDisc";
+ case VDTYPE_FLOPPY:
+ return "Floppy";
+ default:
+ return "Unknown";
+ }
+}
+
+static int tstVDBackendInfo(void)
+{
+ int rc;
+#define MAX_BACKENDS 100
+ VDBACKENDINFO aVDInfo[MAX_BACKENDS];
+ unsigned cEntries;
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ } while (0)
+
+ rc = VDBackendInfo(MAX_BACKENDS, aVDInfo, &cEntries);
+ CHECK("VDBackendInfo()");
+
+ for (unsigned i=0; i < cEntries; i++)
+ {
+ RTPrintf("Backend %u: name=%s capabilities=%#06x extensions=",
+ i, aVDInfo[i].pszBackend, aVDInfo[i].uBackendCaps);
+ if (aVDInfo[i].paFileExtensions)
+ {
+ PCVDFILEEXTENSION pa = aVDInfo[i].paFileExtensions;
+ while (pa->pszExtension != NULL)
+ {
+ if (pa != aVDInfo[i].paFileExtensions)
+ RTPrintf(",");
+ RTPrintf("%s (%s)", pa->pszExtension, tstVDDeviceType(pa->enmType));
+ pa++;
+ }
+ if (pa == aVDInfo[i].paFileExtensions)
+ RTPrintf("<EMPTY>");
+ }
+ else
+ RTPrintf("<NONE>");
+ RTPrintf(" config=");
+ if (aVDInfo[i].paConfigInfo)
+ {
+ PCVDCONFIGINFO pa = aVDInfo[i].paConfigInfo;
+ while (pa->pszKey != NULL)
+ {
+ if (pa != aVDInfo[i].paConfigInfo)
+ RTPrintf(",");
+ RTPrintf("(key=%s type=", pa->pszKey);
+ switch (pa->enmValueType)
+ {
+ case VDCFGVALUETYPE_INTEGER:
+ RTPrintf("integer");
+ break;
+ case VDCFGVALUETYPE_STRING:
+ RTPrintf("string");
+ break;
+ case VDCFGVALUETYPE_BYTES:
+ RTPrintf("bytes");
+ break;
+ default:
+ RTPrintf("INVALID!");
+ }
+ RTPrintf(" default=");
+ if (pa->pszDefaultValue)
+ RTPrintf("%s", pa->pszDefaultValue);
+ else
+ RTPrintf("<NONE>");
+ RTPrintf(" flags=");
+ if (!pa->uKeyFlags)
+ RTPrintf("none");
+ unsigned cFlags = 0;
+ if (pa->uKeyFlags & VD_CFGKEY_MANDATORY)
+ {
+ if (cFlags)
+ RTPrintf(",");
+ RTPrintf("mandatory");
+ cFlags++;
+ }
+ if (pa->uKeyFlags & VD_CFGKEY_EXPERT)
+ {
+ if (cFlags)
+ RTPrintf(",");
+ RTPrintf("expert");
+ cFlags++;
+ }
+ RTPrintf(")");
+ pa++;
+ }
+ if (pa == aVDInfo[i].paConfigInfo)
+ RTPrintf("<EMPTY>");
+ }
+ else
+ RTPrintf("<NONE>");
+ RTPrintf("\n");
+
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACECONFIG ic;
+
+ ic.pfnAreKeysValid = tstAreKeysValid;
+ ic.pfnQuerySize = tstQuerySize;
+ ic.pfnQuery = tstQuery;
+
+ rc = VDInterfaceAdd(&ic.Core, "tstVD-2_Config", VDINTERFACETYPE_CONFIG,
+ NULL, sizeof(VDINTERFACECONFIG), &pVDIfs);
+ AssertRC(rc);
+
+ char *pszLocation, *pszName;
+ rc = aVDInfo[i].pfnComposeLocation(pVDIfs, &pszLocation);
+ CHECK("pfnComposeLocation()");
+ if (pszLocation)
+ {
+ RTMemFree(pszLocation);
+ if (aVDInfo[i].uBackendCaps & VD_CAP_FILE)
+ {
+ RTPrintf("Non-NULL location returned for file-based backend!\n");
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ rc = aVDInfo[i].pfnComposeName(pVDIfs, &pszName);
+ CHECK("pfnComposeName()");
+ if (pszName)
+ {
+ RTMemFree(pszName);
+ if (aVDInfo[i].uBackendCaps & VD_CAP_FILE)
+ {
+ RTPrintf("Non-NULL name returned for file-based backend!\n");
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ }
+
+#undef CHECK
+ return 0;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int rc;
+
+ RTR3InitExe(argc, &argv, 0);
+ RTPrintf("tstVD-2: TESTING...\n");
+
+ rc = tstVDBackendInfo();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD-2: getting backend info test failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVD-2: unloading backends failed! rc=%Rrc\n", rc);
+ g_cErrors++;
+ }
+ /*
+ * Summary
+ */
+ if (!g_cErrors)
+ RTPrintf("tstVD-2: SUCCESS\n");
+ else
+ RTPrintf("tstVD-2: FAILURE - %d errors\n", g_cErrors);
+
+ return !!g_cErrors;
+}
+
diff --git a/src/VBox/Storage/testcase/tstVD.cpp b/src/VBox/Storage/testcase/tstVD.cpp
new file mode 100644
index 00000000..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;
+}
+
diff --git a/src/VBox/Storage/testcase/tstVDCompact.vd b/src/VBox/Storage/testcase/tstVDCompact.vd
new file mode 100644
index 00000000..0d71af35
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDCompact.vd
@@ -0,0 +1,102 @@
+/* $Id: tstVDCompact.vd $ */
+/**
+ * Storage: Testcase for compacting disks.
+ */
+
+/*
+ * 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
+ */
+
+void tstCompact(string strMsg, string strBackend)
+{
+ print(strMsg);
+
+ /* Create disk containers, read verification is on. */
+ createdisk("disk", true);
+ create("disk", "base", "tstCompact.disk", "dynamic", strBackend, 200M, false, false);
+
+ /* Fill the disk with random data. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 100, "none");
+ /* Read the data to verify it once. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 0, "none");
+ /* Fill a part with 0's. */
+ io("disk", false, 1, "seq", 64K, 100M, 150M, 50M, 100, "zero");
+
+ /* Now compact. */
+ compact("disk", 0);
+ /* Read again to verify that the content hasn't changed. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 0, "none");
+ /* Fill everything with 0. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 100, "zero");
+ /* Now compact again. */
+ compact("disk", 0);
+ /* Read again to verify that the content hasn't changed. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 0, "none");
+
+ close("disk", "single", true);
+ destroydisk("disk");
+}
+
+void tstSnapshotCompact(string strMsg, string strBackend)
+{
+ print(strMsg);
+
+ /* Create disk containers, read verification is on. */
+ createdisk("disk", true);
+ create("disk", "base", "tstCompact.disk", "dynamic", strBackend, 200M, false, false);
+
+ /* Fill the disk with random data. */
+ io("disk", false, 1, "seq", 64K, 0, 100M, 100M, 100, "none");
+
+ create("test", "diff", "tst2.disk", "dynamic", strBackend, 200M, false /* fIgnoreFlush */, true /* fHonorSame */);
+
+ io("disk", false, 1, "seq", 64K, 100M, 200M, 100M, 100, "none");
+ io("disk", false, 1, "seq", 64K, 100M, 150M, 50M, 100, "zero");
+
+ create("disk", "diff", "tst3.disk", "dynamic", strBackend, 200M, false /* fIgnoreFlush */, true /* fHonorSame */);
+ merge("disk", 1, 2);
+
+ compact("disk", 1);
+
+ close("disk", "single", true);
+ close("disk", "single", true);
+ close("disk", "single", true);
+ destroydisk("disk");
+}
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes. */
+ iorngcreate(10M, "manual", 1234567890);
+
+ /* Create zero pattern */
+ iopatterncreatefromnumber("zero", 1M, 0);
+
+ tstCompact("Testing VDI", "VDI");
+ tstCompact("Testing VHD", "VHD");
+
+ tstSnapshotCompact("Testing Snapshot VDI", "VDI");
+ tstSnapshotCompact("Testing Snapshot VHD", "VHD");
+
+ /* Destroy RNG and pattern */
+ iopatterndestroy("zero");
+ iorngdestroy();
+}
diff --git a/src/VBox/Storage/testcase/tstVDCopy.vd b/src/VBox/Storage/testcase/tstVDCopy.vd
new file mode 100644
index 00000000..efddb802
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDCopy.vd
@@ -0,0 +1,100 @@
+/* $Id: tstVDCopy.vd $ */
+/**
+ * Storage: Testcase for VDCopy with snapshots and optimizations.
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes. */
+ iorngcreate(10M, "manual", 1234567890);
+
+ /* Create source disk and fill data. */
+ print("Creating Source Disk");
+ createdisk("source", false);
+ create("source", "base", "source_base.vdi", "dynamic", "VDI", 1G, false, false);
+ io("source", false, 1, "rnd", 64K, 0, 512M, 256M, 100, "none");
+
+ print("Creating first diff");
+ create("source", "diff", "source_diff1.vdi", "dynamic", "VDI", 1G, false, false);
+ io("source", false, 1, "rnd", 64K, 512M, 1G, 256M, 50, "none");
+
+ print("Creating second diff");
+ create("source", "diff", "source_diff2.vdi", "dynamic", "VDI", 1G, false, false);
+ io("source", false, 1, "rnd", 1M, 0, 1G, 45M, 100, "none");
+
+ print("Creating third diff");
+ create("source", "diff", "source_diff3.vdi", "dynamic", "VDI", 1G, false, false);
+ io("source", false, 1, "rnd", 1M, 0, 1G, 45M, 100, "none");
+
+ print("Creating fourth diff");
+ create("source", "diff", "source_diff4.vdi", "dynamic", "VDI", 1G, false, false);
+ io("source", false, 1, "rnd", 1M, 0, 1G, 45M, 100, "none");
+
+ print("Creating destination disk");
+ createdisk("dest", false);
+
+ print("Copying base image");
+ copy("source", "dest", 0, "VDI", "dest_base.vdi", false, 0, 0xffffffff, 0xffffffff); /* Image content unknown */
+
+ print("Copying first diff optimized");
+ copy("source", "dest", 1, "VDI", "dest_diff1.vdi", false, 0, 0, 0);
+
+ print("Copying other diffs optimized");
+ copy("source", "dest", 2, "VDI", "dest_diff2.vdi", false, 0, 1, 1);
+ copy("source", "dest", 3, "VDI", "dest_diff3.vdi", false, 0, 2, 2);
+ copy("source", "dest", 4, "VDI", "dest_diff4.vdi", false, 0, 3, 3);
+
+ print("Comparing disks");
+ comparedisks("source", "dest");
+
+ printfilesize("source", 0);
+ printfilesize("source", 1);
+ printfilesize("source", 2);
+ printfilesize("source", 3);
+ printfilesize("source", 4);
+
+ printfilesize("dest", 0);
+ printfilesize("dest", 1);
+ printfilesize("dest", 2);
+ printfilesize("dest", 3);
+ printfilesize("dest", 4);
+
+ print("Cleaning up");
+ close("dest", "single", true);
+ close("dest", "single", true);
+ close("dest", "single", true);
+ close("dest", "single", true);
+ close("dest", "single", true);
+
+ close("source", "single", true);
+ close("source", "single", true);
+ close("source", "single", true);
+ close("source", "single", true);
+ close("source", "single", true);
+ destroydisk("source");
+ destroydisk("dest");
+
+ iorngdestroy();
+}
diff --git a/src/VBox/Storage/testcase/tstVDDiscard.vd b/src/VBox/Storage/testcase/tstVDDiscard.vd
new file mode 100644
index 00000000..e3d67424
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDDiscard.vd
@@ -0,0 +1,69 @@
+/* $Id: tstVDDiscard.vd $ */
+/**
+ * Storage: Testcase for discarding data in a disk.
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes. */
+ iorngcreate(10M, "manual", 1234567890);
+
+ print("Testing VDI");
+
+ /* Create disk containers, read verification is on. */
+ createdisk("disk", true /* fVerify */);
+ /* Create the disk. */
+ create("disk", "base", "tstCompact.vdi", "dynamic", "VDI", 2G, false /* fIgnoreFlush */, false);
+ /* Fill the disk with random data */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 100, "none");
+ /* Read the data to verify it once. */
+ io("disk", false, 1, "seq", 64K, 0, 200M, 200M, 0, "none");
+ close("disk", "single", false);
+
+ open("disk", "tstCompact.vdi", "VDI", true, false, false, true, false, false);
+ printfilesize("disk", 0);
+ discard("disk", true, "6,0M,512K,1M,512K,2M,512K,3M,512K,4M,512K,5M,512K");
+ discard("disk", true, "6,6M,512K,7M,512K,8M,512K,9M,512K,10M,512K,11M,512K");
+ discard("disk", true, "1,512K,512K");
+ discard("disk", false, "1,1024K,64K");
+ printfilesize("disk", 0);
+
+ print("Discard whole block");
+ discard("disk", true, "1,20M,1M");
+ printfilesize("disk", 0);
+
+ print("Split Discard");
+ discard("disk", true, "1,21M,512K");
+ printfilesize("disk", 0);
+ discard("disk", true, "1,22016K,512K");
+ printfilesize("disk", 0);
+
+ /* Cleanup */
+ close("disk", "single", true);
+ destroydisk("disk");
+
+ /* Destroy RNG and pattern */
+ iorngdestroy();
+}
diff --git a/src/VBox/Storage/testcase/tstVDFill.cpp b/src/VBox/Storage/testcase/tstVDFill.cpp
new file mode 100644
index 00000000..6c91e694
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDFill.cpp
@@ -0,0 +1,244 @@
+/** @file
+ *
+ * Test utility to fill a given image with random data up to a certain size (sequentially).
+ */
+
+/*
+ * Copyright (C) 2016-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/getopt.h>
+#include <iprt/rand.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The error count. */
+unsigned g_cErrors = 0;
+/** Global RNG state. */
+RTRAND g_hRand;
+
+#define TSTVDFILL_TEST_PATTERN_SIZE _1M
+
+static DECLCALLBACK(void) tstVDError(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va)
+{
+ RT_NOREF1(pvUser);
+ g_cErrors++;
+ RTPrintf("tstVDFill: Error %Rrc at %s:%u (%s): ", rc, RT_SRC_POS_ARGS);
+ RTPrintfV(pszFormat, va);
+ RTPrintf("\n");
+}
+
+static DECLCALLBACK(int) tstVDMessage(void *pvUser, const char *pszFormat, va_list va)
+{
+ RT_NOREF1(pvUser);
+ RTPrintf("tstVDFill: ");
+ RTPrintfV(pszFormat, va);
+ return VINF_SUCCESS;
+}
+
+static int tstFill(const char *pszFilename, const char *pszFormat, bool fStreamOptimized, uint64_t cbDisk, uint64_t cbFill)
+{
+ int rc;
+ PVDISK pVD = NULL;
+ VDGEOMETRY PCHS = { 0, 0, 0 };
+ VDGEOMETRY LCHS = { 0, 0, 0 };
+ PVDINTERFACE pVDIfs = NULL;
+ VDINTERFACEERROR VDIfError;
+
+ /** Buffer storing the random test pattern. */
+ uint8_t *pbTestPattern = NULL;
+
+ /* Create the virtual disk test data */
+ pbTestPattern = (uint8_t *)RTMemAlloc(TSTVDFILL_TEST_PATTERN_SIZE);
+
+ RTRandAdvBytes(g_hRand, pbTestPattern, TSTVDFILL_TEST_PATTERN_SIZE);
+
+ RTPrintf("Disk size is %llu bytes\n", cbDisk);
+
+ /* Create error interface. */
+ /* Create error interface. */
+ VDIfError.pfnError = tstVDError;
+ VDIfError.pfnMessage = tstVDMessage;
+
+ rc = VDInterfaceAdd(&VDIfError.Core, "tstVD_Error", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+ AssertRC(rc);
+
+#define CHECK(str) \
+ do \
+ { \
+ RTPrintf("%s rc=%Rrc\n", str, rc); \
+ if (RT_FAILURE(rc)) \
+ { \
+ if (pbTestPattern) \
+ RTMemFree(pbTestPattern); \
+ VDDestroy(pVD); \
+ g_cErrors++; \
+ return rc; \
+ } \
+ } while (0)
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pVD);
+ CHECK("VDCreate()");
+
+ rc = VDCreateBase(pVD, pszFormat, pszFilename, cbDisk,
+ fStreamOptimized ? VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED : VD_IMAGE_FLAGS_NONE,
+ "Test image", &PCHS, &LCHS, NULL, VD_OPEN_FLAGS_NORMAL,
+ NULL, NULL);
+ CHECK("VDCreateBase()");
+
+ uint64_t uOff = 0;
+ uint64_t cbGb = 0;
+ while ( uOff < cbFill
+ && RT_SUCCESS(rc))
+ {
+ size_t cbThisWrite = RT_MIN(TSTVDFILL_TEST_PATTERN_SIZE, cbFill - uOff);
+ rc = VDWrite(pVD, uOff, pbTestPattern, cbThisWrite);
+ if (RT_SUCCESS(rc))
+ {
+ uOff += cbThisWrite;
+ cbGb += cbThisWrite;
+ /* Print a message for every GB we wrote. */
+ if (cbGb >= _1G)
+ {
+ RTStrmPrintf(g_pStdErr, "Wrote %llu bytes\n", uOff);
+ cbGb = 0;
+ }
+ }
+ }
+
+ VDDestroy(pVD);
+ if (pbTestPattern)
+ RTMemFree(pbTestPattern);
+
+#undef CHECK
+ return rc;
+}
+
+/**
+ * Shows help message.
+ */
+static void printUsage(void)
+{
+ RTPrintf("Usage:\n"
+ "--disk-size <size in MB> Size of the disk\n"
+ "--fill-size <size in MB> How much to fill\n"
+ "--filename <filename> Filename of the image\n"
+ "--format <VDI|VMDK|...> Format to use\n"
+ "--streamoptimized Use the stream optimized format\n"
+ "--help Show this text\n");
+}
+
+static const RTGETOPTDEF g_aOptions[] =
+{
+ { "--disk-size", 's', RTGETOPT_REQ_UINT64 },
+ { "--fill-size", 'f', RTGETOPT_REQ_UINT64 },
+ { "--filename", 'p', RTGETOPT_REQ_STRING },
+ { "--format", 't', RTGETOPT_REQ_STRING },
+ { "--streamoptimized", 'r', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING }
+};
+
+int main(int argc, char *argv[])
+{
+ RTR3InitExe(argc, &argv, 0);
+ int rc;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ char c;
+ uint64_t cbDisk = 0;
+ uint64_t cbFill = 0;
+ const char *pszFilename = NULL;
+ const char *pszFormat = NULL;
+ bool fStreamOptimized = false;
+
+ rc = VDInit();
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ RTGetOptInit(&GetState, argc, argv, g_aOptions,
+ RT_ELEMENTS(g_aOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
+
+ while ( RT_SUCCESS(rc)
+ && (c = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (c)
+ {
+ case 's':
+ cbDisk = ValueUnion.u64 * _1M;
+ break;
+ case 'f':
+ cbFill = ValueUnion.u64 * _1M;
+ break;
+ case 'p':
+ pszFilename = ValueUnion.psz;
+ break;
+ case 't':
+ pszFormat = ValueUnion.psz;
+ break;
+ case 'r':
+ fStreamOptimized = true;
+ break;
+ case 'h':
+ default:
+ printUsage();
+ break;
+ }
+ }
+
+ if (!cbDisk || !cbFill || !pszFilename || !pszFormat)
+ {
+ RTPrintf("tstVDFill: Arguments missing!\n");
+ return 1;
+ }
+
+ rc = RTRandAdvCreateParkMiller(&g_hRand);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstVDFill: Creating RNG failed rc=%Rrc\n", rc);
+ return 1;
+ }
+
+ RTRandAdvSeed(g_hRand, 0x12345678);
+
+ rc = tstFill(pszFilename, pszFormat, fStreamOptimized, cbDisk, cbFill);
+ if (RT_FAILURE(rc))
+ RTPrintf("tstVDFill: Filling disk failed! rc=%Rrc\n", rc);
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ RTPrintf("tstVDFill: unloading backends failed! rc=%Rrc\n", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Storage/testcase/tstVDIo.cpp b/src/VBox/Storage/testcase/tstVDIo.cpp
new file mode 100644
index 00000000..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;
+}
diff --git a/src/VBox/Storage/testcase/tstVDIo.vd b/src/VBox/Storage/testcase/tstVDIo.vd
new file mode 100644
index 00000000..78058fad
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDIo.vd
@@ -0,0 +1,73 @@
+/* $Id: tstVDIo.vd $ */
+/**
+ * Storage: Simple I/O testing for most backends.
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+
+void tstIo(string strMessage, string strBackend)
+{
+ print(strMessage);
+ createdisk("test", true /* fVerify */);
+ create("test", "base", "tst.disk", "dynamic", strBackend, 200M, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "seq", 64K, 0, 200M, 200M, 100, "none");
+ io("test", false, 1, "seq", 64K, 0, 200M, 200M, 100, "none");
+ io("test", true, 32, "seq", 64K, 0, 200M, 200M, 0, "none");
+ io("test", false, 1, "seq", 64K, 0, 200M, 200M, 0, "none");
+ create("test", "diff", "tst2.disk", "dynamic", strBackend, 200M, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 200M, 200M, 50, "none");
+ io("test", false, 1, "rnd", 64K, 0, 200M, 200M, 50, "none");
+ create("test", "diff", "tst3.disk", "dynamic", strBackend, 200M, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 200M, 200M, 50, "none");
+ io("test", false, 1, "rnd", 64K, 0, 200M, 200M, 50, "none");
+ close("test", "single", true /* fDelete */);
+ close("test", "single", true /* fDelete */);
+ close("test", "single", true /* fDelete */);
+ destroydisk("test");
+}
+
+void tstIoUnaligned(string strMessage, string strBackend)
+{
+ print(strMessage);
+ createdisk("test", true);
+ create("test", "base", "tst.disk", "dynamic", strBackend, 2G, false);
+ io("test", false, 1, "seq", 512, 3584, 4096, 512, 100, "none");
+ io("test", false, 1, "seq", 512, 3584, 4096, 512, 0, "none");
+ destroydisk("test");
+}
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes */
+ iorngcreate(10M, "manual", 1234567890);
+
+ tstIo("Testing VDI", "VDI");
+ tstIo("Testing VMDK", "VMDK");
+ tstIo("Testing VHD", "VHD");
+ tstIo("Testing Parallels", "Parallels");
+ tstIo("Testing QED", "QED");
+ tstIo("Testing QCOW", "QCOW");
+
+ iorngdestroy();
+}
+
diff --git a/src/VBox/Storage/testcase/tstVDMultBackends.vd b/src/VBox/Storage/testcase/tstVDMultBackends.vd
new file mode 100644
index 00000000..51625193
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDMultBackends.vd
@@ -0,0 +1,70 @@
+/* $Id: tstVDMultBackends.vd $ */
+/**
+ * Storage: Simple I/O test with different backends in one chain.
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+
+void tstIo(string strMessage, string strBackend)
+{
+ print(strMessage);
+ createdisk("test", true /* fVerify */);
+ create("test", "base", "tst.disk", "dynamic", strBackend, 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "seq", 64K, 0, 2G, 200M, 100, "none");
+ io("test", false, 1, "seq", 64K, 0, 2G, 200M, 100, "none");
+ io("test", true, 32, "seq", 64K, 0, 2G, 200M, 0, "none");
+ io("test", false, 1, "seq", 64K, 0, 2G, 200M, 0, "none");
+ create("test", "diff", "tst2.disk", "dynamic", "VMDK", 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ io("test", false, 1, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ create("test", "diff", "tst3.disk", "dynamic", "VMDK", 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ io("test", false, 1, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ create("test", "diff", "tst4.disk", "dynamic", "VMDK", 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 0, "none");
+
+ create("test", "diff", "tst5.disk", "dynamic", "VMDK", 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 0, "none");
+
+ create("test", "diff", "tst6.disk", "dynamic", "VMDK", 2G, false /* fIgnoreFlush */, false);
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 50, "none");
+ io("test", true, 32, "rnd", 64K, 0, 2G, 200M, 0, "none");
+
+ close("test", "single", true /* fDelete */);
+ close("test", "single", true /* fDelete */);
+ close("test", "single", true /* fDelete */);
+ destroydisk("test");
+}
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes */
+ iorngcreate(10M, "manual", 1234567890);
+
+ tstIo("Testing VDI", "VDI");
+
+ iorngdestroy();
+}
+
diff --git a/src/VBox/Storage/testcase/tstVDResize.vd b/src/VBox/Storage/testcase/tstVDResize.vd
new file mode 100644
index 00000000..4e564860
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDResize.vd
@@ -0,0 +1,79 @@
+/* $Id: tstVDResize.vd $ */
+/**
+ * Storage: Resize testing for VDI.
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes. */
+ iorngcreate(10M, "manual", 1234567890);
+
+ print("Testing VDI");
+ createdisk("test", true);
+ create("test", "base", "tst.vdi", "dynamic", "VDI", 1T, false, false);
+ io("test", false, 1, "seq", 64K, 255G, 257G, 2G, 100, "none");
+ resize("test", 1331200M);
+ io("test", false, 1, "seq", 64K, 255G, 257G, 2G, 0, "none");
+ close("test", "single", true /* fDelete */);
+ destroydisk("test");
+
+ print("Testing VMDK Monolithic Flat");
+ createdisk("test-vmdk-mflat", true);
+ create("test-vmdk-mflat", "base", "test-vmdk-mflat.vmdk", "Fixed", "VMDK", 4G, false, false);
+ io("test-vmdk-mflat", false, 1, "seq", 64K, 1G, 2G, 1G, 100, "none");
+ resize("test-vmdk-mflat", 6000M);
+ io("test-vmdk-mflat", false, 1, "seq", 64K, 4G, 5G, 1G, 100, "none");
+ close("test-vmdk-mflat", "single", true /* fDelete */);
+ destroydisk("test-vmdk-mflat");
+
+ print("Testing VMDK Split Flat");
+ createdisk("test-vmdk-sflat", true);
+ create("test-vmdk-sflat", "base", "test-vmdk-sflat.vmdk", "vmdk-fixed-split", "VMDK", 4G, false, false);
+ io("test-vmdk-sflat", false, 1, "seq", 64K, 1G, 2G, 1G, 100, "none");
+ resize("test-vmdk-sflat", 6000M);
+ io("test-vmdk-sflat", false, 1, "seq", 64K, 4G, 5G, 1G, 100, "none");
+ close("test-vmdk-sflat", "single", true /* fDelete */);
+ destroydisk("test-vmdk-sflat");
+
+ print("Testing VMDK Sparse");
+ createdisk("test-vmdk-sparse", true);
+ create("test-vmdk-sparse", "base", "test-vmdk-sparse.vmdk", "Dynamic", "VMDK", 4G, false, false);
+ io("test-vmdk-sparse", false, 1, "seq", 64K, 1G, 2G, 1G, 100, "none");
+ resize("test-vmdk-sparse", 6000M);
+ io("test-vmdk-sparse", false, 1, "seq", 64K, 4G, 5G, 1G, 100, "none");
+ close("test-vmdk-sparse", "single", true /* fDelete */);
+ destroydisk("test-vmdk-sparse");
+
+ print("Testing VMDK Sparse Split");
+ createdisk("test-vmdk-sparse-split", true);
+ create("test-vmdk-sparse-split", "base", "test-vmdk-sparse-split.vmdk", "vmdk-dynamic-split", "VMDK", 4G, false, false);
+ io("test-vmdk-sparse-split", false, 1, "seq", 64K, 1G, 2G, 1G, 100, "none");
+ resize("test-vmdk-sparse-split", 6000M);
+ io("test-vmdk-sparse-split", false, 1, "seq", 64K, 4G, 5G, 1G, 100, "none");
+ close("test-vmdk-sparse-split", "single", true /* fDelete */);
+ destroydisk("test-vmdk-sparse-split");
+
+ iorngdestroy();
+}
diff --git a/src/VBox/Storage/testcase/tstVDShareable.vd b/src/VBox/Storage/testcase/tstVDShareable.vd
new file mode 100644
index 00000000..d82959d7
--- /dev/null
+++ b/src/VBox/Storage/testcase/tstVDShareable.vd
@@ -0,0 +1,66 @@
+/* $Id: tstVDShareable.vd $ */
+/**
+ * Storage: Testcase for shareable disks.
+ */
+
+/*
+ * 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
+ */
+
+void main()
+{
+ /* Init I/O RNG for generating random data for writes. */
+ iorngcreate(10M, "manual", 1234567890);
+
+ /* Create disk containers. */
+ createdisk("shared1", false);
+ createdisk("shared2", false);
+
+ /* Create the disk and close it. */
+ create("shared1", "base", "tstShared.vdi", "fixed", "VDI", 20M, false, false);
+ close("shared1", "all", false);
+
+ /* Open the disk with sharing enabled. */
+ open("shared1", "tstShared.vdi", "VDI", true /* fAsync */, true /* fShareable */, false, false, false, false);
+ open("shared2", "tstShared.vdi", "VDI", true /* fAsync */, true /* fShareable */, false, false, false, false);
+
+ /* Write to one disk and verify that the other disk can see the content. */
+ io("shared1", true, 32, "seq", 64K, 0, 20M, 20M, 100, "none");
+ comparedisks("shared1", "shared2");
+
+ /* Write to the second disk and verify that the first can see the content. */
+ io("shared2", true, 64, "seq", 8K, 0, 20M, 20M, 50, "none");
+ comparedisks("shared1", "shared2");
+
+ /* Close but don't delete yet. */
+ close("shared1", "all", false);
+ close("shared2", "all", false);
+
+ /* Open and delete. */
+ open("shared1", "tstShared.vdi", "VDI", false /* fAsync */, false /* fShareable */, false, false, false, false);
+ close("shared1", "single", true);
+
+ /* Cleanup */
+ destroydisk("shared1");
+ destroydisk("shared2");
+ iorngdestroy();
+}
+
diff --git a/src/VBox/Storage/testcase/tstVDSnap.cpp b/src/VBox/Storage/testcase/tstVDSnap.cpp
new file mode 100644
index 00000000..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;
+}
+
diff --git a/src/VBox/Storage/testcase/vbox-img.cpp b/src/VBox/Storage/testcase/vbox-img.cpp
new file mode 100644
index 00000000..071a670d
--- /dev/null
+++ b/src/VBox/Storage/testcase/vbox-img.cpp
@@ -0,0 +1,2186 @@
+/* $Id: vbox-img.cpp $ */
+/** @file
+ * Standalone image manipulation tool
+ */
+
+/*
+ * Copyright (C) 2010-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 <VBox/err.h>
+#include <VBox/version.h>
+#include <iprt/initterm.h>
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/fsvfs.h>
+#include <iprt/fsisomaker.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/stream.h>
+#include <iprt/message.h>
+#include <iprt/getopt.h>
+#include <iprt/assert.h>
+#include <iprt/dvm.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const char *g_pszProgName = "";
+
+
+
+static void printUsage(PRTSTREAM pStrm)
+{
+ RTStrmPrintf(pStrm,
+ "Usage: %s\n"
+ " setuuid --filename <filename>\n"
+ " [--format VDI|VMDK|VHD|...]\n"
+ " [--uuid <uuid>]\n"
+ " [--parentuuid <uuid>]\n"
+ " [--zeroparentuuid]\n"
+ "\n"
+ " geometry --filename <filename>\n"
+ " [--format VDI|VMDK|VHD|...]\n"
+ " [--clearchs]\n"
+ " [--cylinders <number>]\n"
+ " [--heads <number>]\n"
+ " [--sectors <number>]\n"
+ "\n"
+ " convert --srcfilename <filename>\n"
+ " --dstfilename <filename>\n"
+ " [--stdin]|[--stdout]\n"
+ " [--srcformat VDI|VMDK|VHD|RAW|..]\n"
+ " [--dstformat VDI|VMDK|VHD|RAW|..]\n"
+ " [--variant Standard,Fixed,Split2G,Stream,ESX]\n"
+ "\n"
+ " info --filename <filename>\n"
+ "\n"
+ " compact --filename <filename>\n"
+ " [--filesystemaware]\n"
+ "\n"
+ " createcache --filename <filename>\n"
+ " --size <cache size>\n"
+ "\n"
+ " createbase --filename <filename>\n"
+ " --size <size in bytes>\n"
+ " [--format VDI|VMDK|VHD] (default: VDI)\n"
+ " [--variant Standard,Fixed,Split2G,Stream,ESX]\n"
+ " [--dataalignment <alignment in bytes>]\n"
+ "\n"
+ " createfloppy --filename <filename>\n"
+ " [--size <size in bytes>]\n"
+ " [--root-dir-entries <value>]\n"
+ " [--sector-size <bytes>]\n"
+ " [--heads <value>]\n"
+ " [--sectors-per-track <count>]\n"
+ " [--media-byte <byte>]\n"
+ "\n"
+ " createiso [too-many-options]\n"
+ "\n"
+ " repair --filename <filename>\n"
+ " [--dry-run]\n"
+ " [--format VDI|VMDK|VHD] (default: autodetect)\n"
+ "\n"
+ " clearcomment --filename <filename>\n"
+ "\n"
+ " resize --filename <filename>\n"
+ " --size <new size>\n",
+ g_pszProgName);
+}
+
+static void showLogo(PRTSTREAM pStrm)
+{
+ static bool s_fShown; /* show only once */
+
+ if (!s_fShown)
+ {
+ RTStrmPrintf(pStrm, VBOX_PRODUCT " Disk Utility " VBOX_VERSION_STRING "\n"
+ "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+ s_fShown = true;
+ }
+}
+
+/** command handler argument */
+struct HandlerArg
+{
+ int argc;
+ char **argv;
+};
+
+static PVDINTERFACE pVDIfs;
+
+static DECLCALLBACK(void) handleVDError(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va)
+{
+ RT_NOREF2(pvUser, rc);
+ RT_SRC_POS_NOREF();
+ RTMsgErrorV(pszFormat, va);
+}
+
+static DECLCALLBACK(int) handleVDMessage(void *pvUser, const char *pszFormat, va_list va)
+{
+ NOREF(pvUser);
+ RTPrintfV(pszFormat, va);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Print a usage synopsis and the syntax error message.
+ */
+static int errorSyntax(const char *pszFormat, ...)
+{
+ va_list args;
+ showLogo(g_pStdErr); // show logo even if suppressed
+ va_start(args, pszFormat);
+ RTStrmPrintf(g_pStdErr, "\nSyntax error: %N\n", pszFormat, &args);
+ va_end(args);
+ printUsage(g_pStdErr);
+ return 1;
+}
+
+static int errorRuntime(const char *pszFormat, ...)
+{
+ va_list args;
+
+ va_start(args, pszFormat);
+ RTMsgErrorV(pszFormat, args);
+ va_end(args);
+ return 1;
+}
+
+static int parseDiskVariant(const char *psz, unsigned *puImageFlags)
+{
+ int rc = VINF_SUCCESS;
+ unsigned uImageFlags = *puImageFlags;
+
+ while (psz && *psz && RT_SUCCESS(rc))
+ {
+ size_t len;
+ const char *pszComma = strchr(psz, ',');
+ if (pszComma)
+ len = pszComma - psz;
+ else
+ len = strlen(psz);
+ if (len > 0)
+ {
+ /*
+ * Parsing is intentionally inconsistent: "standard" resets the
+ * variant, whereas the other flags are cumulative.
+ */
+ if (!RTStrNICmp(psz, "standard", len))
+ uImageFlags = VD_IMAGE_FLAGS_NONE;
+ else if ( !RTStrNICmp(psz, "fixed", len)
+ || !RTStrNICmp(psz, "static", len))
+ uImageFlags |= VD_IMAGE_FLAGS_FIXED;
+ else if (!RTStrNICmp(psz, "Diff", len))
+ uImageFlags |= VD_IMAGE_FLAGS_DIFF;
+ else if (!RTStrNICmp(psz, "split2g", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_SPLIT_2G;
+ else if ( !RTStrNICmp(psz, "stream", len)
+ || !RTStrNICmp(psz, "streamoptimized", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED;
+ else if (!RTStrNICmp(psz, "esx", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_ESX;
+ else
+ rc = VERR_PARSE_ERROR;
+ }
+ if (pszComma)
+ psz += len + 1;
+ else
+ psz += len;
+ }
+
+ if (RT_SUCCESS(rc))
+ *puImageFlags = uImageFlags;
+ return rc;
+}
+
+
+static int handleSetUUID(HandlerArg *a)
+{
+ const char *pszFilename = NULL;
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ RTUUID imageUuid;
+ RTUUID parentUuid;
+ bool fSetImageUuid = false;
+ bool fSetParentUuid = false;
+ RTUuidClear(&imageUuid);
+ RTUuidClear(&parentUuid);
+ int rc;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--format", 'o', RTGETOPT_REQ_STRING },
+ { "--uuid", 'u', RTGETOPT_REQ_UUID },
+ { "--parentuuid", 'p', RTGETOPT_REQ_UUID },
+ { "--zeroparentuuid", 'P', RTGETOPT_REQ_NOTHING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+ case 'o': // --format
+ pszFormat = RTStrDup(ValueUnion.psz);
+ break;
+ case 'u': // --uuid
+ imageUuid = ValueUnion.Uuid;
+ fSetImageUuid = true;
+ break;
+ case 'p': // --parentuuid
+ parentUuid = ValueUnion.Uuid;
+ fSetParentUuid = true;
+ break;
+ case 'P': // --zeroparentuuid
+ RTUuidClear(&parentUuid);
+ fSetParentUuid = true;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* Check for consistency of optional parameters. */
+ if (fSetImageUuid && RTUuidIsNull(&imageUuid))
+ return errorSyntax("Invalid parameter to --uuid option\n");
+
+ /* Autodetect image format. */
+ if (!pszFormat)
+ {
+ /* Don't pass error interface, as that would triggers error messages
+ * because some backends fail to open the image. */
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Format autodetect failed: %Rrc\n", rc);
+ }
+
+ PVDISK pVD = NULL;
+ rc = VDCreate(pVDIfs, enmType, &pVD);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot create the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open in info mode to be able to open diff images without their parent. */
+ rc = VDOpen(pVD, pszFormat, pszFilename, VD_OPEN_FLAGS_INFO, NULL);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot open the virtual disk image \"%s\": %Rrf (%Rrc)\n",
+ pszFilename, rc, rc);
+
+ RTUUID oldImageUuid;
+ rc = VDGetUuid(pVD, VD_LAST_IMAGE, &oldImageUuid);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot get UUID of virtual disk image \"%s\": %Rrc\n",
+ pszFilename, rc);
+
+ RTPrintf("Old image UUID: %RTuuid\n", &oldImageUuid);
+
+ RTUUID oldParentUuid;
+ rc = VDGetParentUuid(pVD, VD_LAST_IMAGE, &oldParentUuid);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot get parent UUID of virtual disk image \"%s\": %Rrc\n",
+ pszFilename, rc);
+
+ RTPrintf("Old parent UUID: %RTuuid\n", &oldParentUuid);
+
+ if (fSetImageUuid)
+ {
+ RTPrintf("New image UUID: %RTuuid\n", &imageUuid);
+ rc = VDSetUuid(pVD, VD_LAST_IMAGE, &imageUuid);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot set UUID of virtual disk image \"%s\": %Rrf (%Rrc)\n",
+ pszFilename, rc, rc);
+ }
+
+ if (fSetParentUuid)
+ {
+ RTPrintf("New parent UUID: %RTuuid\n", &parentUuid);
+ rc = VDSetParentUuid(pVD, VD_LAST_IMAGE, &parentUuid);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot set parent UUID of virtual disk image \"%s\": %Rrf (%Rrc)\n",
+ pszFilename, rc, rc);
+ }
+
+ VDDestroy(pVD);
+
+ if (pszFormat)
+ {
+ RTStrFree(pszFormat);
+ pszFormat = NULL;
+ }
+
+ return 0;
+}
+
+
+static int handleGeometry(HandlerArg *a)
+{
+ const char *pszFilename = NULL;
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ uint16_t cCylinders = 0;
+ uint8_t cHeads = 0;
+ uint8_t cSectors = 0;
+ bool fCylinders = false;
+ bool fHeads = false;
+ bool fSectors = false;
+ int rc;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--format", 'o', RTGETOPT_REQ_STRING },
+ { "--clearchs", 'C', RTGETOPT_REQ_NOTHING },
+ { "--cylinders", 'c', RTGETOPT_REQ_UINT16 },
+ { "--heads", 'e', RTGETOPT_REQ_UINT8 },
+ { "--sectors", 's', RTGETOPT_REQ_UINT8 }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+ case 'o': // --format
+ pszFormat = RTStrDup(ValueUnion.psz);
+ break;
+ case 'C': // --clearchs
+ cCylinders = 0;
+ cHeads = 0;
+ cSectors = 0;
+ fCylinders = true;
+ fHeads = true;
+ fSectors = true;
+ break;
+ case 'c': // --cylinders
+ cCylinders = ValueUnion.u16;
+ fCylinders = true;
+ break;
+ case 'e': // --heads
+ cHeads = ValueUnion.u8;
+ fHeads = true;
+ break;
+ case 's': // --sectors
+ cSectors = ValueUnion.u8;
+ fSectors = true;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* Autodetect image format. */
+ if (!pszFormat)
+ {
+ /* Don't pass error interface, as that would triggers error messages
+ * because some backends fail to open the image. */
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Format autodetect failed: %Rrc\n", rc);
+ }
+
+ PVDISK pVD = NULL;
+ rc = VDCreate(pVDIfs, enmType, &pVD);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot create the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open in info mode to be able to open diff images without their parent. */
+ rc = VDOpen(pVD, pszFormat, pszFilename, VD_OPEN_FLAGS_INFO, NULL);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot open the virtual disk image \"%s\": %Rrf (%Rrc)\n",
+ pszFilename, rc, rc);
+
+ VDGEOMETRY oldLCHSGeometry;
+ rc = VDGetLCHSGeometry(pVD, VD_LAST_IMAGE, &oldLCHSGeometry);
+ if (rc == VERR_VD_GEOMETRY_NOT_SET)
+ {
+ memset(&oldLCHSGeometry, 0, sizeof(oldLCHSGeometry));
+ rc = VINF_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot get LCHS geometry of virtual disk image \"%s\": %Rrc\n",
+ pszFilename, rc);
+
+ VDGEOMETRY newLCHSGeometry = oldLCHSGeometry;
+ if (fCylinders)
+ newLCHSGeometry.cCylinders = cCylinders;
+ if (fHeads)
+ newLCHSGeometry.cHeads = cHeads;
+ if (fSectors)
+ newLCHSGeometry.cSectors = cSectors;
+
+ if (fCylinders || fHeads || fSectors)
+ {
+ RTPrintf("Old image LCHS: %u/%u/%u\n", oldLCHSGeometry.cCylinders, oldLCHSGeometry.cHeads, oldLCHSGeometry.cSectors);
+ RTPrintf("New image LCHS: %u/%u/%u\n", newLCHSGeometry.cCylinders, newLCHSGeometry.cHeads, newLCHSGeometry.cSectors);
+
+ rc = VDSetLCHSGeometry(pVD, VD_LAST_IMAGE, &newLCHSGeometry);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Cannot set LCHS geometry of virtual disk image \"%s\": %Rrf (%Rrc)\n",
+ pszFilename, rc, rc);
+ }
+ else
+ RTPrintf("Current image LCHS: %u/%u/%u\n", oldLCHSGeometry.cCylinders, oldLCHSGeometry.cHeads, oldLCHSGeometry.cSectors);
+
+
+ VDDestroy(pVD);
+
+ if (pszFormat)
+ {
+ RTStrFree(pszFormat);
+ pszFormat = NULL;
+ }
+
+ return 0;
+}
+
+
+typedef struct FILEIOSTATE
+{
+ RTFILE file;
+ /** Size of file. */
+ uint64_t cb;
+ /** Offset in the file. */
+ uint64_t off;
+ /** Offset where the buffer contents start. UINT64_MAX=buffer invalid. */
+ uint64_t offBuffer;
+ /** Size of valid data in the buffer. */
+ uint32_t cbBuffer;
+ /** Buffer for efficient I/O */
+ uint8_t abBuffer[16 *_1M];
+} FILEIOSTATE, *PFILEIOSTATE;
+
+static DECLCALLBACK(int) convInOpen(void *pvUser, const char *pszLocation, uint32_t fOpen, PFNVDCOMPLETED pfnCompleted,
+ void **ppStorage)
+{
+ RT_NOREF2(pvUser, pszLocation);
+
+ /* Validate input. */
+ AssertPtrReturn(ppStorage, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnCompleted, VERR_INVALID_PARAMETER);
+ AssertReturn((fOpen & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ, VERR_INVALID_PARAMETER);
+ RTFILE file;
+ int rc = RTFileFromNative(&file, RTFILE_NATIVE_STDIN);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* No need to clear the buffer, the data will be read from disk. */
+ PFILEIOSTATE pFS = (PFILEIOSTATE)RTMemAlloc(sizeof(FILEIOSTATE));
+ if (!pFS)
+ return VERR_NO_MEMORY;
+
+ pFS->file = file;
+ pFS->cb = 0;
+ pFS->off = 0;
+ pFS->offBuffer = UINT64_MAX;
+ pFS->cbBuffer = 0;
+
+ *ppStorage = pFS;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convInClose(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+
+ RTMemFree(pFS);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convInDelete(void *pvUser, const char *pcszFilename)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInMove(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
+{
+ NOREF(pvUser);
+ NOREF(pcszSrc);
+ NOREF(pcszDst);
+ NOREF(fMove);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInGetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pcbFreeSpace, VERR_INVALID_POINTER);
+ *pcbFreeSpace = 0;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convInGetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pModificationTime, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInGetSize(void *pvUser, void *pStorage, uint64_t *pcbSize)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInSetSize(void *pvUser, void *pStorage, uint64_t cbSize)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(cbSize);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInRead(void *pvUser, void *pStorage, uint64_t uOffset,
+ void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+ AssertReturn(uOffset >= pFS->off, VERR_INVALID_PARAMETER);
+ int rc;
+
+ /* Fill buffer if it is empty. */
+ if (pFS->offBuffer == UINT64_MAX)
+ {
+ /* Repeat reading until buffer is full or EOF. */
+ size_t cbRead;
+ size_t cbSumRead = 0;
+ uint8_t *pbTmp = (uint8_t *)&pFS->abBuffer[0];
+ size_t cbTmp = sizeof(pFS->abBuffer);
+ do
+ {
+ rc = RTFileRead(pFS->file, pbTmp, cbTmp, &cbRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ pbTmp += cbRead;
+ cbTmp -= cbRead;
+ cbSumRead += cbRead;
+ } while (cbTmp && cbRead);
+
+ pFS->offBuffer = 0;
+ pFS->cbBuffer = (uint32_t)cbSumRead;
+ if (!cbSumRead && !pcbRead) /* Caller can't handle partial reads. */
+ return VERR_EOF;
+ }
+
+ /* Read several blocks and assemble the result if necessary */
+ size_t cbTotalRead = 0;
+ do
+ {
+ /* Skip over areas no one wants to read. */
+ while (uOffset > pFS->offBuffer + pFS->cbBuffer - 1)
+ {
+ if (pFS->cbBuffer < sizeof(pFS->abBuffer))
+ {
+ if (pcbRead)
+ *pcbRead = cbTotalRead;
+ return VERR_EOF;
+ }
+
+ /* Repeat reading until buffer is full or EOF. */
+ size_t cbRead;
+ size_t cbSumRead = 0;
+ uint8_t *pbTmp = (uint8_t *)&pFS->abBuffer[0];
+ size_t cbTmp = sizeof(pFS->abBuffer);
+ do
+ {
+ rc = RTFileRead(pFS->file, pbTmp, cbTmp, &cbRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ pbTmp += cbRead;
+ cbTmp -= cbRead;
+ cbSumRead += cbRead;
+ } while (cbTmp && cbRead);
+
+ pFS->offBuffer += pFS->cbBuffer;
+ pFS->cbBuffer = (uint32_t)cbSumRead;
+ }
+
+ uint32_t cbThisRead = (uint32_t)RT_MIN(cbBuffer,
+ pFS->cbBuffer - uOffset % sizeof(pFS->abBuffer));
+ memcpy(pvBuffer, &pFS->abBuffer[uOffset % sizeof(pFS->abBuffer)],
+ cbThisRead);
+ uOffset += cbThisRead;
+ pvBuffer = (uint8_t *)pvBuffer + cbThisRead;
+ cbBuffer -= cbThisRead;
+ cbTotalRead += cbThisRead;
+ if (!cbTotalRead && !pcbRead) /* Caller can't handle partial reads. */
+ return VERR_EOF;
+ } while (cbBuffer > 0);
+
+ if (pcbRead)
+ *pcbRead = cbTotalRead;
+
+ pFS->off = uOffset;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convInWrite(void *pvUser, void *pStorage, uint64_t uOffset, const void *pvBuffer, size_t cbBuffer,
+ size_t *pcbWritten)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(uOffset);
+ NOREF(cbBuffer);
+ NOREF(pcbWritten);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convInFlush(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convStdOutOpen(void *pvUser, const char *pszLocation, uint32_t fOpen, PFNVDCOMPLETED pfnCompleted,
+ void **ppStorage)
+{
+ RT_NOREF2(pvUser, pszLocation);
+
+ /* Validate input. */
+ AssertPtrReturn(ppStorage, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnCompleted, VERR_INVALID_PARAMETER);
+ AssertReturn((fOpen & RTFILE_O_ACCESS_MASK) == RTFILE_O_WRITE, VERR_INVALID_PARAMETER);
+ RTFILE file;
+ int rc = RTFileFromNative(&file, RTFILE_NATIVE_STDOUT);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Must clear buffer, so that skipped over data is initialized properly. */
+ PFILEIOSTATE pFS = (PFILEIOSTATE)RTMemAllocZ(sizeof(FILEIOSTATE));
+ if (!pFS)
+ return VERR_NO_MEMORY;
+
+ pFS->file = file;
+ pFS->cb = 0;
+ pFS->off = 0;
+ pFS->offBuffer = 0;
+ pFS->cbBuffer = sizeof(FILEIOSTATE);
+
+ *ppStorage = pFS;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convStdOutClose(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+ int rc = VINF_SUCCESS;
+
+ /* Flush any remaining buffer contents. */
+ if (pFS->cbBuffer)
+ rc = RTFileWrite(pFS->file, &pFS->abBuffer[0], pFS->cbBuffer, NULL);
+ if ( RT_SUCCESS(rc)
+ && pFS->cb > pFS->off)
+ {
+ /* Write zeros if the set file size is not met. */
+ uint64_t cbLeft = pFS->cb - pFS->off;
+ RT_ZERO(pFS->abBuffer);
+
+ while (cbLeft)
+ {
+ size_t cbThisWrite = RT_MIN(cbLeft, sizeof(pFS->abBuffer));
+ rc = RTFileWrite(pFS->file, &pFS->abBuffer[0],
+ cbThisWrite, NULL);
+ cbLeft -= cbThisWrite;
+ }
+ }
+
+ RTMemFree(pFS);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) convStdOutDelete(void *pvUser, const char *pcszFilename)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutMove(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
+{
+ NOREF(pvUser);
+ NOREF(pcszSrc);
+ NOREF(pcszDst);
+ NOREF(fMove);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutGetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pcbFreeSpace, VERR_INVALID_POINTER);
+ *pcbFreeSpace = INT64_MAX;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convStdOutGetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pModificationTime, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutGetSize(void *pvUser, void *pStorage, uint64_t *pcbSize)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutSetSize(void *pvUser, void *pStorage, uint64_t cbSize)
+{
+ RT_NOREF2(pvUser, cbSize);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutRead(void *pvUser, void *pStorage, uint64_t uOffset, void *pvBuffer, size_t cbBuffer,
+ size_t *pcbRead)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(uOffset);
+ NOREF(cbBuffer);
+ NOREF(pcbRead);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convStdOutWrite(void *pvUser, void *pStorage, uint64_t uOffset, const void *pvBuffer, size_t cbBuffer,
+ size_t *pcbWritten)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+ AssertReturn(uOffset >= pFS->off, VERR_INVALID_PARAMETER);
+ int rc;
+
+ /* Write the data to the buffer, flushing as required. */
+ size_t cbTotalWritten = 0;
+ do
+ {
+ /* Flush the buffer if we need a new one. */
+ while (uOffset > pFS->offBuffer + sizeof(pFS->abBuffer) - 1)
+ {
+ rc = RTFileWrite(pFS->file, &pFS->abBuffer[0],
+ sizeof(pFS->abBuffer), NULL);
+ RT_ZERO(pFS->abBuffer);
+ pFS->offBuffer += sizeof(pFS->abBuffer);
+ pFS->cbBuffer = 0;
+ }
+
+ uint32_t cbThisWrite = (uint32_t)RT_MIN(cbBuffer,
+ sizeof(pFS->abBuffer) - uOffset % sizeof(pFS->abBuffer));
+ memcpy(&pFS->abBuffer[uOffset % sizeof(pFS->abBuffer)], pvBuffer,
+ cbThisWrite);
+ uOffset += cbThisWrite;
+ pvBuffer = (uint8_t *)pvBuffer + cbThisWrite;
+ cbBuffer -= cbThisWrite;
+ cbTotalWritten += cbThisWrite;
+ } while (cbBuffer > 0);
+
+ if (pcbWritten)
+ *pcbWritten = cbTotalWritten;
+
+ pFS->cbBuffer = uOffset % sizeof(pFS->abBuffer);
+ if (!pFS->cbBuffer)
+ pFS->cbBuffer = sizeof(pFS->abBuffer);
+ pFS->off = uOffset;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convStdOutFlush(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convFileOutOpen(void *pvUser, const char *pszLocation, uint32_t fOpen, PFNVDCOMPLETED pfnCompleted,
+ void **ppStorage)
+{
+ RT_NOREF1(pvUser);
+
+ /* Validate input. */
+ AssertPtrReturn(ppStorage, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnCompleted, VERR_INVALID_PARAMETER);
+ AssertReturn((fOpen & RTFILE_O_ACCESS_MASK) == RTFILE_O_WRITE, VERR_INVALID_PARAMETER);
+ RTFILE file;
+ int rc = RTFileOpen(&file, pszLocation, fOpen);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Must clear buffer, so that skipped over data is initialized properly. */
+ PFILEIOSTATE pFS = (PFILEIOSTATE)RTMemAllocZ(sizeof(FILEIOSTATE));
+ if (!pFS)
+ return VERR_NO_MEMORY;
+
+ pFS->file = file;
+ pFS->cb = 0;
+ pFS->off = 0;
+ pFS->offBuffer = 0;
+ pFS->cbBuffer = sizeof(FILEIOSTATE);
+
+ *ppStorage = pFS;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convFileOutClose(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+ int rc = VINF_SUCCESS;
+
+ /* Flush any remaining buffer contents. */
+ if (pFS->cbBuffer)
+ rc = RTFileWriteAt(pFS->file, pFS->offBuffer, &pFS->abBuffer[0], pFS->cbBuffer, NULL);
+ RTFileClose(pFS->file);
+
+ RTMemFree(pFS);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) convFileOutDelete(void *pvUser, const char *pcszFilename)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convFileOutMove(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
+{
+ NOREF(pvUser);
+ NOREF(pcszSrc);
+ NOREF(pcszDst);
+ NOREF(fMove);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convFileOutGetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pcbFreeSpace, VERR_INVALID_POINTER);
+ *pcbFreeSpace = INT64_MAX;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convFileOutGetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pModificationTime, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convFileOutGetSize(void *pvUser, void *pStorage, uint64_t *pcbSize)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convFileOutSetSize(void *pvUser, void *pStorage, uint64_t cbSize)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+
+ int rc = RTFileSetSize(pFS->file, cbSize);
+ if (RT_SUCCESS(rc))
+ pFS->cb = cbSize;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convFileOutRead(void *pvUser, void *pStorage, uint64_t uOffset, void *pvBuffer, size_t cbBuffer,
+ size_t *pcbRead)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(uOffset);
+ NOREF(cbBuffer);
+ NOREF(pcbRead);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+static DECLCALLBACK(int) convFileOutWrite(void *pvUser, void *pStorage, uint64_t uOffset, const void *pvBuffer, size_t cbBuffer,
+ size_t *pcbWritten)
+{
+ NOREF(pvUser);
+ AssertPtrReturn(pStorage, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ PFILEIOSTATE pFS = (PFILEIOSTATE)pStorage;
+ AssertReturn(uOffset >= pFS->off, VERR_INVALID_PARAMETER);
+ int rc;
+
+ /* Write the data to the buffer, flushing as required. */
+ size_t cbTotalWritten = 0;
+ do
+ {
+ /* Flush the buffer if we need a new one. */
+ while (uOffset > pFS->offBuffer + sizeof(pFS->abBuffer) - 1)
+ {
+ if (!ASMMemIsZero(pFS->abBuffer, sizeof(pFS->abBuffer)))
+ rc = RTFileWriteAt(pFS->file, pFS->offBuffer,
+ &pFS->abBuffer[0],
+ sizeof(pFS->abBuffer), NULL);
+ RT_ZERO(pFS->abBuffer);
+ pFS->offBuffer += sizeof(pFS->abBuffer);
+ pFS->cbBuffer = 0;
+ }
+
+ uint32_t cbThisWrite = (uint32_t)RT_MIN(cbBuffer,
+ sizeof(pFS->abBuffer) - uOffset % sizeof(pFS->abBuffer));
+ memcpy(&pFS->abBuffer[uOffset % sizeof(pFS->abBuffer)], pvBuffer,
+ cbThisWrite);
+ uOffset += cbThisWrite;
+ pvBuffer = (uint8_t *)pvBuffer + cbThisWrite;
+ cbBuffer -= cbThisWrite;
+ cbTotalWritten += cbThisWrite;
+ } while (cbBuffer > 0);
+
+ if (pcbWritten)
+ *pcbWritten = cbTotalWritten;
+
+ pFS->cbBuffer = uOffset % sizeof(pFS->abBuffer);
+ if (!pFS->cbBuffer)
+ pFS->cbBuffer = sizeof(pFS->abBuffer);
+ pFS->off = uOffset;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) convFileOutFlush(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ return VINF_SUCCESS;
+}
+
+static int handleConvert(HandlerArg *a)
+{
+ const char *pszSrcFilename = NULL;
+ const char *pszDstFilename = NULL;
+ bool fStdIn = false;
+ bool fStdOut = false;
+ bool fCreateSparse = false;
+ const char *pszSrcFormat = NULL;
+ VDTYPE enmSrcType = VDTYPE_HDD;
+ const char *pszDstFormat = NULL;
+ const char *pszVariant = NULL;
+ PVDISK pSrcDisk = NULL;
+ PVDISK pDstDisk = NULL;
+ unsigned uImageFlags = VD_IMAGE_FLAGS_NONE;
+ PVDINTERFACE pIfsImageInput = NULL;
+ PVDINTERFACE pIfsImageOutput = NULL;
+ VDINTERFACEIO IfsInputIO;
+ VDINTERFACEIO IfsOutputIO;
+ int rc = VINF_SUCCESS;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--srcfilename", 'i', RTGETOPT_REQ_STRING },
+ { "--dstfilename", 'o', RTGETOPT_REQ_STRING },
+ { "--stdin", 'p', RTGETOPT_REQ_NOTHING },
+ { "--stdout", 'P', RTGETOPT_REQ_NOTHING },
+ { "--srcformat", 's', RTGETOPT_REQ_STRING },
+ { "--dstformat", 'd', RTGETOPT_REQ_STRING },
+ { "--variant", 'v', RTGETOPT_REQ_STRING },
+ { "--create-sparse", 'c', RTGETOPT_REQ_NOTHING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'i': // --srcfilename
+ pszSrcFilename = ValueUnion.psz;
+ break;
+ case 'o': // --dstfilename
+ pszDstFilename = ValueUnion.psz;
+ break;
+ case 'p': // --stdin
+ fStdIn = true;
+ break;
+ case 'P': // --stdout
+ fStdOut = true;
+ break;
+ case 's': // --srcformat
+ pszSrcFormat = ValueUnion.psz;
+ break;
+ case 'd': // --dstformat
+ pszDstFormat = ValueUnion.psz;
+ break;
+ case 'v': // --variant
+ pszVariant = ValueUnion.psz;
+ break;
+ case 'c': // --create-sparse
+ fCreateSparse = true;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters and handle dummies/defaults. */
+ if (fStdIn && !pszSrcFormat)
+ return errorSyntax("Mandatory --srcformat option missing\n");
+ if (!pszDstFormat)
+ pszDstFormat = "VDI";
+ if (fStdIn && !pszSrcFilename)
+ {
+ /* Complete dummy, will be just passed to various calls to fulfill
+ * the "must be non-NULL" requirement, and is completely ignored
+ * otherwise. It shown in the stderr message below. */
+ pszSrcFilename = "stdin";
+ }
+ if (fStdOut && !pszDstFilename)
+ {
+ /* Will be stored in the destination image if it is a streamOptimized
+ * VMDK, but it isn't really relevant - use it for "branding". */
+ if (!RTStrICmp(pszDstFormat, "VMDK"))
+ pszDstFilename = "VirtualBoxStream.vmdk";
+ else
+ pszDstFilename = "stdout";
+ }
+ if (!pszSrcFilename)
+ return errorSyntax("Mandatory --srcfilename option missing\n");
+ if (!pszDstFilename)
+ return errorSyntax("Mandatory --dstfilename option missing\n");
+
+ if (fStdIn)
+ {
+ IfsInputIO.pfnOpen = convInOpen;
+ IfsInputIO.pfnClose = convInClose;
+ IfsInputIO.pfnDelete = convInDelete;
+ IfsInputIO.pfnMove = convInMove;
+ IfsInputIO.pfnGetFreeSpace = convInGetFreeSpace;
+ IfsInputIO.pfnGetModificationTime = convInGetModificationTime;
+ IfsInputIO.pfnGetSize = convInGetSize;
+ IfsInputIO.pfnSetSize = convInSetSize;
+ IfsInputIO.pfnReadSync = convInRead;
+ IfsInputIO.pfnWriteSync = convInWrite;
+ IfsInputIO.pfnFlushSync = convInFlush;
+ VDInterfaceAdd(&IfsInputIO.Core, "stdin", VDINTERFACETYPE_IO,
+ NULL, sizeof(VDINTERFACEIO), &pIfsImageInput);
+ }
+ if (fStdOut)
+ {
+ IfsOutputIO.pfnOpen = convStdOutOpen;
+ IfsOutputIO.pfnClose = convStdOutClose;
+ IfsOutputIO.pfnDelete = convStdOutDelete;
+ IfsOutputIO.pfnMove = convStdOutMove;
+ IfsOutputIO.pfnGetFreeSpace = convStdOutGetFreeSpace;
+ IfsOutputIO.pfnGetModificationTime = convStdOutGetModificationTime;
+ IfsOutputIO.pfnGetSize = convStdOutGetSize;
+ IfsOutputIO.pfnSetSize = convStdOutSetSize;
+ IfsOutputIO.pfnReadSync = convStdOutRead;
+ IfsOutputIO.pfnWriteSync = convStdOutWrite;
+ IfsOutputIO.pfnFlushSync = convStdOutFlush;
+ VDInterfaceAdd(&IfsOutputIO.Core, "stdout", VDINTERFACETYPE_IO,
+ NULL, sizeof(VDINTERFACEIO), &pIfsImageOutput);
+ }
+ else if (fCreateSparse)
+ {
+ IfsOutputIO.pfnOpen = convFileOutOpen;
+ IfsOutputIO.pfnClose = convFileOutClose;
+ IfsOutputIO.pfnDelete = convFileOutDelete;
+ IfsOutputIO.pfnMove = convFileOutMove;
+ IfsOutputIO.pfnGetFreeSpace = convFileOutGetFreeSpace;
+ IfsOutputIO.pfnGetModificationTime = convFileOutGetModificationTime;
+ IfsOutputIO.pfnGetSize = convFileOutGetSize;
+ IfsOutputIO.pfnSetSize = convFileOutSetSize;
+ IfsOutputIO.pfnReadSync = convFileOutRead;
+ IfsOutputIO.pfnWriteSync = convFileOutWrite;
+ IfsOutputIO.pfnFlushSync = convFileOutFlush;
+ VDInterfaceAdd(&IfsOutputIO.Core, "fileout", VDINTERFACETYPE_IO,
+ NULL, sizeof(VDINTERFACEIO), &pIfsImageOutput);
+ }
+
+ /* check the variant parameter */
+ if (pszVariant)
+ {
+ char *psz = (char*)pszVariant;
+ while (psz && *psz && RT_SUCCESS(rc))
+ {
+ size_t len;
+ const char *pszComma = strchr(psz, ',');
+ if (pszComma)
+ len = pszComma - psz;
+ else
+ len = strlen(psz);
+ if (len > 0)
+ {
+ if (!RTStrNICmp(pszVariant, "standard", len))
+ uImageFlags |= VD_IMAGE_FLAGS_NONE;
+ else if (!RTStrNICmp(pszVariant, "fixed", len))
+ uImageFlags |= VD_IMAGE_FLAGS_FIXED;
+ else if (!RTStrNICmp(pszVariant, "split2g", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_SPLIT_2G;
+ else if (!RTStrNICmp(pszVariant, "stream", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED;
+ else if (!RTStrNICmp(pszVariant, "esx", len))
+ uImageFlags |= VD_VMDK_IMAGE_FLAGS_ESX;
+ else
+ return errorSyntax("Invalid --variant option\n");
+ }
+ if (pszComma)
+ psz += len + 1;
+ else
+ psz += len;
+ }
+ }
+
+ do
+ {
+ /* try to determine input format if not specified */
+ if (!pszSrcFormat)
+ {
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ rc = VDGetFormat(NULL, NULL, pszSrcFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ {
+ errorSyntax("No file format specified, please specify format: %Rrc\n", rc);
+ break;
+ }
+ pszSrcFormat = pszFormat;
+ enmSrcType = enmType;
+ }
+
+ rc = VDCreate(pVDIfs, enmSrcType, &pSrcDisk);
+ if (RT_FAILURE(rc))
+ {
+ errorRuntime("Error while creating source disk container: %Rrf (%Rrc)\n", rc, rc);
+ break;
+ }
+
+ rc = VDOpen(pSrcDisk, pszSrcFormat, pszSrcFilename,
+ VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_SEQUENTIAL,
+ pIfsImageInput);
+ if (RT_FAILURE(rc))
+ {
+ errorRuntime("Error while opening source image: %Rrf (%Rrc)\n", rc, rc);
+ break;
+ }
+
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pDstDisk);
+ if (RT_FAILURE(rc))
+ {
+ errorRuntime("Error while creating the destination disk container: %Rrf (%Rrc)\n", rc, rc);
+ break;
+ }
+
+ uint64_t cbSize = VDGetSize(pSrcDisk, VD_LAST_IMAGE);
+ RTStrmPrintf(g_pStdErr, "Converting image \"%s\" with size %RU64 bytes (%RU64MB)...\n", pszSrcFilename, cbSize, (cbSize + _1M - 1) / _1M);
+
+ /* Create the output image */
+ rc = VDCopy(pSrcDisk, VD_LAST_IMAGE, pDstDisk, pszDstFormat,
+ pszDstFilename, false, 0, uImageFlags, NULL,
+ VD_OPEN_FLAGS_NORMAL | VD_OPEN_FLAGS_SEQUENTIAL, NULL,
+ pIfsImageOutput, NULL);
+ if (RT_FAILURE(rc))
+ {
+ errorRuntime("Error while copying the image: %Rrf (%Rrc)\n", rc, rc);
+ break;
+ }
+
+ }
+ while (0);
+
+ if (pDstDisk)
+ VDDestroy(pDstDisk);
+ if (pSrcDisk)
+ VDDestroy(pSrcDisk);
+
+ return RT_SUCCESS(rc) ? 0 : 1;
+}
+
+
+static int handleInfo(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+ const char *pszFilename = NULL;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* just try it */
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Format autodetect failed: %Rrc\n", rc);
+
+ rc = VDCreate(pVDIfs, enmType, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open the image */
+ rc = VDOpen(pDisk, pszFormat, pszFilename, VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_READONLY, NULL);
+ RTStrFree(pszFormat);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while opening the image: %Rrf (%Rrc)\n", rc, rc);
+
+ VDDumpImages(pDisk);
+
+ VDDestroy(pDisk);
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vboximgQueryBlockStatus(void *pvUser, uint64_t off,
+ uint64_t cb, bool *pfAllocated)
+{
+ RTVFS hVfs = (RTVFS)pvUser;
+ return RTVfsQueryRangeState(hVfs, off, cb, pfAllocated);
+}
+
+
+static DECLCALLBACK(int) vboximgQueryRangeUse(void *pvUser, uint64_t off, uint64_t cb,
+ bool *pfUsed)
+{
+ RTDVM hVolMgr = (RTDVM)pvUser;
+ return RTDvmMapQueryBlockStatus(hVolMgr, off, cb, pfUsed);
+}
+
+
+typedef struct VBOXIMGVFS
+{
+ /** Pointer to the next VFS handle. */
+ struct VBOXIMGVFS *pNext;
+ /** VFS handle. */
+ RTVFS hVfs;
+} VBOXIMGVFS, *PVBOXIMGVFS;
+
+static int handleCompact(HandlerArg *a)
+{
+ PVDISK pDisk = NULL;
+ VDINTERFACEQUERYRANGEUSE VDIfQueryRangeUse;
+ PVDINTERFACE pIfsCompact = NULL;
+ RTDVM hDvm = NIL_RTDVM;
+ PVBOXIMGVFS pVBoxImgVfsHead = NULL;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--filesystemaware", 'a', RTGETOPT_REQ_NOTHING },
+ { "--file-system-aware", 'a', RTGETOPT_REQ_NOTHING },
+ };
+
+ const char *pszFilename = NULL;
+ bool fFilesystemAware = false;
+ bool fVerbose = true;
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ case 'a':
+ fFilesystemAware = true;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* just try it */
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ int rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Format autodetect failed: %Rrc\n", rc);
+
+ rc = VDCreate(pVDIfs, enmType, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open the image */
+ rc = VDOpen(pDisk, pszFormat, pszFilename, VD_OPEN_FLAGS_NORMAL, NULL);
+ RTStrFree(pszFormat);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while opening the image: %Rrf (%Rrc)\n", rc, rc);
+
+ /*
+ * If --file-system-aware, we first ask the disk volume manager (DVM) to
+ * find the volumes on the disk.
+ */
+ if ( RT_SUCCESS(rc)
+ && fFilesystemAware)
+ {
+ RTVFSFILE hVfsDisk;
+ rc = VDCreateVfsFileFromDisk(pDisk, 0 /*fFlags*/, &hVfsDisk);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDvmCreate(&hDvm, hVfsDisk, 512 /*cbSector*/, 0 /*fFlags*/);
+ RTVfsFileRelease(hVfsDisk);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDvmMapOpen(hDvm);
+ if ( RT_SUCCESS(rc)
+ && RTDvmMapGetValidVolumes(hDvm) > 0)
+ {
+ /*
+ * Enumerate the volumes: Try finding a file system interpreter and
+ * set the block query status callback to work with the FS.
+ */
+ uint32_t iVol = 0;
+ RTDVMVOLUME hVol;
+ rc = RTDvmMapQueryFirstVolume(hDvm, &hVol);
+ AssertRC(rc);
+
+ while (RT_SUCCESS(rc))
+ {
+ if (fVerbose)
+ {
+ char *pszVolName;
+ rc = RTDvmVolumeQueryName(hVol, &pszVolName);
+ if (RT_FAILURE(rc))
+ pszVolName = NULL;
+ RTMsgInfo("Vol%u: %Rhcb %s%s%s\n", iVol, RTDvmVolumeGetSize(hVol),
+ RTDvmVolumeTypeGetDescr(RTDvmVolumeGetType(hVol)),
+ pszVolName ? " " : "", pszVolName ? pszVolName : "");
+ RTStrFree(pszVolName);
+ }
+
+ RTVFSFILE hVfsFile;
+ rc = RTDvmVolumeCreateVfsFile(hVol, RTFILE_O_READWRITE, &hVfsFile);
+ if (RT_FAILURE(rc))
+ {
+ errorRuntime("RTDvmVolumeCreateVfsFile failed: %Rrc\n");
+ break;
+ }
+
+ /* Try to detect the filesystem in this volume. */
+ RTERRINFOSTATIC ErrInfo;
+ RTVFS hVfs;
+ rc = RTVfsMountVol(hVfsFile, RTVFSMNT_F_READ_ONLY | RTVFSMNT_F_FOR_RANGE_IN_USE, &hVfs,
+ RTErrInfoInitStatic(&ErrInfo));
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXIMGVFS pVBoxImgVfs = (PVBOXIMGVFS)RTMemAllocZ(sizeof(VBOXIMGVFS));
+ if (!pVBoxImgVfs)
+ {
+ RTVfsRelease(hVfs);
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ pVBoxImgVfs->hVfs = hVfs;
+ pVBoxImgVfs->pNext = pVBoxImgVfsHead;
+ pVBoxImgVfsHead = pVBoxImgVfs;
+ RTDvmVolumeSetQueryBlockStatusCallback(hVol, vboximgQueryBlockStatus, hVfs);
+ }
+ else if (rc != VERR_NOT_SUPPORTED)
+ {
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ errorRuntime("RTVfsMountVol failed: %s\n", ErrInfo.Core.pszMsg);
+ break;
+ }
+ else if (fVerbose && RTErrInfoIsSet(&ErrInfo.Core))
+ RTMsgInfo("Unsupported file system: %s", ErrInfo.Core.pszMsg);
+
+ /*
+ * Advance. (Releasing hVol here is fine since RTDvmVolumeCreateVfsFile
+ * retained a reference and the hVfs a reference of it again.)
+ */
+ RTDVMVOLUME hVolNext = NIL_RTDVMVOLUME;
+ if (RT_SUCCESS(rc))
+ rc = RTDvmMapQueryNextVolume(hDvm, hVol, &hVolNext);
+ RTDvmVolumeRelease(hVol);
+ hVol = hVolNext;
+ iVol++;
+ }
+
+ if (rc == VERR_DVM_MAP_NO_VOLUME)
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ VDIfQueryRangeUse.pfnQueryRangeUse = vboximgQueryRangeUse;
+ VDInterfaceAdd(&VDIfQueryRangeUse.Core, "QueryRangeUse", VDINTERFACETYPE_QUERYRANGEUSE,
+ hDvm, sizeof(VDINTERFACEQUERYRANGEUSE), &pIfsCompact);
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ RTPrintf("There are no partitions in the volume map\n");
+ else if (rc == VERR_NOT_FOUND)
+ {
+ RTPrintf("No known volume format on disk found\n");
+ rc = VINF_SUCCESS;
+ }
+ else
+ errorRuntime("Error while opening the volume manager: %Rrf (%Rrc)\n", rc, rc);
+ }
+ else
+ errorRuntime("Error creating the volume manager: %Rrf (%Rrc)\n", rc, rc);
+ }
+ else
+ errorRuntime("Error while creating VFS interface for the disk: %Rrf (%Rrc)\n", rc, rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = VDCompact(pDisk, 0, pIfsCompact);
+ if (RT_FAILURE(rc))
+ errorRuntime("Error while compacting image: %Rrf (%Rrc)\n", rc, rc);
+ }
+
+ while (pVBoxImgVfsHead)
+ {
+ PVBOXIMGVFS pVBoxImgVfsFree = pVBoxImgVfsHead;
+
+ pVBoxImgVfsHead = pVBoxImgVfsHead->pNext;
+ RTVfsRelease(pVBoxImgVfsFree->hVfs);
+ RTMemFree(pVBoxImgVfsFree);
+ }
+
+ if (hDvm)
+ RTDvmRelease(hDvm);
+
+ VDDestroy(pDisk);
+
+ return rc;
+}
+
+
+static int handleCreateCache(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+ const char *pszFilename = NULL;
+ uint64_t cbSize = 0;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--size", 's', RTGETOPT_REQ_UINT64 }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ case 's': // --size
+ cbSize = ValueUnion.u64;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ if (!cbSize)
+ return errorSyntax("Mandatory --size option missing\n");
+
+ /* just try it */
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ rc = VDCreateCache(pDisk, "VCI", pszFilename, cbSize, VD_IMAGE_FLAGS_DEFAULT,
+ NULL, NULL, VD_OPEN_FLAGS_NORMAL, NULL, NULL);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk cache: %Rrf (%Rrc)\n", rc, rc);
+
+ VDDestroy(pDisk);
+
+ return rc;
+}
+
+static DECLCALLBACK(bool) vdIfCfgCreateBaseAreKeysValid(void *pvUser, const char *pszzValid)
+{
+ RT_NOREF2(pvUser, pszzValid);
+ return VINF_SUCCESS; /** @todo Implement. */
+}
+
+static DECLCALLBACK(int) vdIfCfgCreateBaseQuerySize(void *pvUser, const char *pszName, size_t *pcbValue)
+{
+ AssertPtrReturn(pcbValue, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pvUser, VERR_GENERAL_FAILURE);
+
+ if (RTStrCmp(pszName, "DataAlignment"))
+ return VERR_CFGM_VALUE_NOT_FOUND;
+
+ *pcbValue = strlen((const char *)pvUser) + 1 /* include terminator */;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vdIfCfgCreateBaseQuery(void *pvUser, const char *pszName, char *pszValue, size_t cchValue)
+{
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pvUser, VERR_GENERAL_FAILURE);
+
+ if (RTStrCmp(pszName, "DataAlignment"))
+ return VERR_CFGM_VALUE_NOT_FOUND;
+
+ if (strlen((const char *)pvUser) >= cchValue)
+ return VERR_CFGM_NOT_ENOUGH_SPACE;
+
+ memcpy(pszValue, pvUser, strlen((const char *)pvUser) + 1);
+
+ return VINF_SUCCESS;
+
+}
+
+static int handleCreateBase(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+ const char *pszFilename = NULL;
+ const char *pszBackend = "VDI";
+ const char *pszVariant = NULL;
+ unsigned uImageFlags = VD_IMAGE_FLAGS_NONE;
+ uint64_t cbSize = 0;
+ const char *pszDataAlignment = NULL;
+ VDGEOMETRY LCHSGeometry, PCHSGeometry;
+ PVDINTERFACE pVDIfsOperation = NULL;
+ VDINTERFACECONFIG vdIfCfg;
+
+ memset(&LCHSGeometry, 0, sizeof(LCHSGeometry));
+ memset(&PCHSGeometry, 0, sizeof(PCHSGeometry));
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--size", 's', RTGETOPT_REQ_UINT64 },
+ { "--format", 'b', RTGETOPT_REQ_STRING },
+ { "--variant", 'v', RTGETOPT_REQ_STRING },
+ { "--dataalignment", 'a', RTGETOPT_REQ_STRING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ case 's': // --size
+ cbSize = ValueUnion.u64;
+ break;
+
+ case 'b': // --format
+ pszBackend = ValueUnion.psz;
+ break;
+
+ case 'v': // --variant
+ pszVariant = ValueUnion.psz;
+ break;
+
+ case 'a': // --dataalignment
+ pszDataAlignment = ValueUnion.psz;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ if (!cbSize)
+ return errorSyntax("Mandatory --size option missing\n");
+
+ if (pszVariant)
+ {
+ rc = parseDiskVariant(pszVariant, &uImageFlags);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Invalid variant %s given\n", pszVariant);
+ }
+
+ /* Setup the config interface if required. */
+ if (pszDataAlignment)
+ {
+ vdIfCfg.pfnAreKeysValid = vdIfCfgCreateBaseAreKeysValid;
+ vdIfCfg.pfnQuerySize = vdIfCfgCreateBaseQuerySize;
+ vdIfCfg.pfnQuery = vdIfCfgCreateBaseQuery;
+ VDInterfaceAdd(&vdIfCfg.Core, "Config", VDINTERFACETYPE_CONFIG, (void *)pszDataAlignment,
+ sizeof(vdIfCfg), &pVDIfsOperation);
+ }
+
+ /* just try it */
+ rc = VDCreate(pVDIfs, VDTYPE_HDD, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ rc = VDCreateBase(pDisk, pszBackend, pszFilename, cbSize, uImageFlags,
+ NULL, &PCHSGeometry, &LCHSGeometry, NULL, VD_OPEN_FLAGS_NORMAL,
+ NULL, pVDIfsOperation);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk: %Rrf (%Rrc)\n", rc, rc);
+
+ VDDestroy(pDisk);
+
+ return rc;
+}
+
+
+static int handleRepair(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ const char *pszFilename = NULL;
+ char *pszBackend = NULL;
+ const char *pszFormat = NULL;
+ bool fDryRun = false;
+ VDTYPE enmType = VDTYPE_HDD;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
+ { "--format", 'b', RTGETOPT_REQ_STRING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ case 'd': // --dry-run
+ fDryRun = true;
+ break;
+
+ case 'b': // --format
+ pszFormat = ValueUnion.psz;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* just try it */
+ if (!pszFormat)
+ {
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszBackend, &enmType);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Format autodetect failed: %Rrc\n", rc);
+ pszFormat = pszBackend;
+ }
+
+ rc = VDRepair(pVDIfs, NULL, pszFilename, pszFormat, fDryRun ? VD_REPAIR_DRY_RUN : 0);
+ if (RT_FAILURE(rc))
+ rc = errorRuntime("Error while repairing the virtual disk: %Rrf (%Rrc)\n", rc, rc);
+
+ if (pszBackend)
+ RTStrFree(pszBackend);
+ return rc;
+}
+
+
+static int handleClearComment(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+ const char *pszFilename = NULL;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /* just try it */
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Format autodetect failed: %Rrc\n", rc);
+
+ rc = VDCreate(pVDIfs, enmType, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open the image */
+ rc = VDOpen(pDisk, pszFormat, pszFilename, VD_OPEN_FLAGS_INFO, NULL);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while opening the image: %Rrf (%Rrc)\n", rc, rc);
+
+ VDSetComment(pDisk, 0, NULL);
+
+ VDDestroy(pDisk);
+ return rc;
+}
+
+
+static int handleCreateFloppy(HandlerArg *a)
+{
+ const char *pszFilename = NULL;
+ uint64_t cbFloppy = 1474560;
+ uint16_t cbSector = 0;
+ uint8_t cHeads = 0;
+ uint8_t cSectorsPerCluster = 0;
+ uint8_t cSectorsPerTrack = 0;
+ uint16_t cRootDirEntries = 0;
+ uint8_t bMedia = 0;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--sectors-per-cluster", 'c', RTGETOPT_REQ_UINT8 },
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--heads", 'h', RTGETOPT_REQ_UINT8 },
+ { "--media-byte", 'm', RTGETOPT_REQ_UINT8 },
+ { "--root-dir-entries", 'r', RTGETOPT_REQ_UINT16 },
+ { "--size", 's', RTGETOPT_REQ_UINT64 },
+ { "--sector-size", 'S', RTGETOPT_REQ_UINT16 },
+ { "--sectors-per-track", 't', RTGETOPT_REQ_UINT8 },
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'c': cSectorsPerCluster = ValueUnion.u8; break;
+ case 'f': pszFilename = ValueUnion.psz; break;
+ case 'h': cHeads = ValueUnion.u8; break;
+ case 'm': bMedia = ValueUnion.u8; break;
+ case 'r': cRootDirEntries = ValueUnion.u16; break;
+ case 's': cbFloppy = ValueUnion.u64; break;
+ case 'S': cbSector = ValueUnion.u16; break;
+ case 't': cSectorsPerTrack = ValueUnion.u8; break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ /*
+ * Do the job.
+ */
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsChainOpenFile(pszFilename,
+ RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_ALL
+ | (0770 << RTFILE_O_CREATE_MODE_SHIFT),
+ &hVfsFile, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsFatVolFormat(hVfsFile, 0, cbFloppy, RTFSFATVOL_FMT_F_FULL, cbSector, cSectorsPerCluster, RTFSFATTYPE_INVALID,
+ cHeads, cSectorsPerTrack, bMedia, 0 /*cHiddenSectors*/, cRootDirEntries,
+ RTErrInfoInitStatic(&ErrInfo));
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ errorRuntime("Error %Rrc formatting floppy '%s': %s", rc, pszFilename, ErrInfo.Core.pszMsg);
+ else
+ errorRuntime("Error formatting floppy '%s': %Rrc", pszFilename, rc);
+ }
+ else
+ RTVfsChainMsgError("RTVfsChainOpenFile", pszFilename, rc, offError, &ErrInfo.Core);
+ return RTEXITCODE_FAILURE;
+}
+
+
+static int handleCreateIso(HandlerArg *a)
+{
+ return RTFsIsoMakerCmd(a->argc + 1, a->argv - 1);
+}
+
+
+static int handleClearResize(HandlerArg *a)
+{
+ int rc = VINF_SUCCESS;
+ PVDISK pDisk = NULL;
+ const char *pszFilename = NULL;
+ uint64_t cbNew = 0;
+ VDGEOMETRY LCHSGeometry, PCHSGeometry;
+
+ memset(&LCHSGeometry, 0, sizeof(LCHSGeometry));
+ memset(&PCHSGeometry, 0, sizeof(PCHSGeometry));
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--filename", 'f', RTGETOPT_REQ_STRING },
+ { "--size", 's', RTGETOPT_REQ_UINT64 }
+ };
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'f': // --filename
+ pszFilename = ValueUnion.psz;
+ break;
+
+ case 's': // --size
+ cbNew = ValueUnion.u64;
+ break;
+
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszFilename)
+ return errorSyntax("Mandatory --filename option missing\n");
+
+ if (!cbNew)
+ return errorSyntax("Mandatory --size option missing or invalid\n");
+
+ /* just try it */
+ char *pszFormat = NULL;
+ VDTYPE enmType = VDTYPE_INVALID;
+ rc = VDGetFormat(NULL, NULL, pszFilename, VDTYPE_INVALID, &pszFormat, &enmType);
+ if (RT_FAILURE(rc))
+ return errorSyntax("Format autodetect failed: %Rrc\n", rc);
+
+ rc = VDCreate(pVDIfs, enmType, &pDisk);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while creating the virtual disk container: %Rrf (%Rrc)\n", rc, rc);
+
+ /* Open the image */
+ rc = VDOpen(pDisk, pszFormat, pszFilename, VD_OPEN_FLAGS_NORMAL, NULL);
+ if (RT_FAILURE(rc))
+ return errorRuntime("Error while opening the image: %Rrf (%Rrc)\n", rc, rc);
+
+ rc = VDResize(pDisk, cbNew, &PCHSGeometry, &LCHSGeometry, NULL);
+ if (RT_FAILURE(rc))
+ rc = errorRuntime("Error while resizing the virtual disk: %Rrf (%Rrc)\n", rc, rc);
+
+ VDDestroy(pDisk);
+ return rc;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int exitcode = 0;
+
+ int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_STANDALONE_APP);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ g_pszProgName = RTPathFilename(argv[0]);
+
+ bool fShowLogo = false;
+ int iCmd = 1;
+ int iCmdArg;
+
+ /* global options */
+ for (int i = 1; i < argc || argc <= iCmd; i++)
+ {
+ if ( argc <= iCmd
+ || !strcmp(argv[i], "help")
+ || !strcmp(argv[i], "-?")
+ || !strcmp(argv[i], "-h")
+ || !strcmp(argv[i], "-help")
+ || !strcmp(argv[i], "--help"))
+ {
+ showLogo(g_pStdOut);
+ printUsage(g_pStdOut);
+ return 0;
+ }
+
+ if ( !strcmp(argv[i], "-v")
+ || !strcmp(argv[i], "-version")
+ || !strcmp(argv[i], "-Version")
+ || !strcmp(argv[i], "--version"))
+ {
+ /* Print version number, and do nothing else. */
+ RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
+ return 0;
+ }
+
+ if ( !strcmp(argv[i], "--nologo")
+ || !strcmp(argv[i], "-nologo")
+ || !strcmp(argv[i], "-q"))
+ {
+ /* suppress the logo */
+ fShowLogo = false;
+ iCmd++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ iCmdArg = iCmd + 1;
+
+ if (fShowLogo)
+ showLogo(g_pStdOut);
+
+ /* initialize the VD backend with dummy handlers */
+ VDINTERFACEERROR vdInterfaceError;
+ vdInterfaceError.pfnError = handleVDError;
+ vdInterfaceError.pfnMessage = handleVDMessage;
+
+ rc = VDInterfaceAdd(&vdInterfaceError.Core, "VBoxManage_IError", VDINTERFACETYPE_ERROR,
+ NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
+
+ rc = VDInit();
+ if (RT_FAILURE(rc))
+ {
+ errorSyntax("Initializing backends failed! rc=%Rrc\n", rc);
+ return 1;
+ }
+
+ /*
+ * All registered command handlers
+ */
+ static const struct
+ {
+ const char *command;
+ int (*handler)(HandlerArg *a);
+ } s_commandHandlers[] =
+ {
+ { "setuuid", handleSetUUID },
+ { "geometry", handleGeometry },
+ { "convert", handleConvert },
+ { "info", handleInfo },
+ { "compact", handleCompact },
+ { "createcache", handleCreateCache },
+ { "createbase", handleCreateBase },
+ { "createfloppy", handleCreateFloppy },
+ { "createiso", handleCreateIso },
+ { "repair", handleRepair },
+ { "clearcomment", handleClearComment },
+ { "resize", handleClearResize },
+ { NULL, NULL }
+ };
+
+ HandlerArg handlerArg = { 0, NULL };
+ int commandIndex;
+ for (commandIndex = 0; s_commandHandlers[commandIndex].command != NULL; commandIndex++)
+ {
+ if (!strcmp(s_commandHandlers[commandIndex].command, argv[iCmd]))
+ {
+ handlerArg.argc = argc - iCmdArg;
+ handlerArg.argv = &argv[iCmdArg];
+
+ exitcode = s_commandHandlers[commandIndex].handler(&handlerArg);
+ break;
+ }
+ }
+ if (!s_commandHandlers[commandIndex].command)
+ {
+ errorSyntax("Invalid command '%s'", argv[iCmd]);
+ return 1;
+ }
+
+ rc = VDShutdown();
+ if (RT_FAILURE(rc))
+ {
+ errorSyntax("Unloading backends failed! rc=%Rrc\n", rc);
+ return 1;
+ }
+
+ return exitcode;
+}
+
+/* dummy stub for RuntimeR3 */
+#ifndef RT_OS_WINDOWS
+RTDECL(bool) RTAssertShouldPanic(void)
+{
+ return true;
+}
+#endif
diff --git a/src/VBox/Storage/testcase/vbox-img.rc b/src/VBox/Storage/testcase/vbox-img.rc
new file mode 100644
index 00000000..32a577ed
--- /dev/null
+++ b/src/VBox/Storage/testcase/vbox-img.rc
@@ -0,0 +1,61 @@
+/* $Id: vbox-img.rc $ */
+/** @file
+ * vbox-img - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-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 <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Virtual Disk Utility\0"
+ VALUE "InternalName", "vbox-img\0"
+ VALUE "OriginalFilename", "vbox-img.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Storage/testcase/vdkeystoremgr.cpp b/src/VBox/Storage/testcase/vdkeystoremgr.cpp
new file mode 100644
index 00000000..2f871604
--- /dev/null
+++ b/src/VBox/Storage/testcase/vdkeystoremgr.cpp
@@ -0,0 +1,288 @@
+/* $Id: vdkeystoremgr.cpp $ */
+/** @file
+ * Keystore utility for debugging.
+ */
+
+/*
+ * Copyright (C) 2016-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/version.h>
+#include <iprt/initterm.h>
+#include <iprt/base64.h>
+#include <iprt/buildconfig.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/stream.h>
+#include <iprt/message.h>
+#include <iprt/getopt.h>
+#include <iprt/assert.h>
+
+#include "../VDKeyStore.h"
+
+/** command handler argument */
+struct HandlerArg
+{
+ int argc;
+ char **argv;
+};
+
+static const char *g_pszProgName = "";
+static void printUsage(PRTSTREAM pStrm)
+{
+ RTStrmPrintf(pStrm,
+ "Usage: %s\n"
+ " create --password <password>\n"
+ " --cipher <cipher>\n"
+ " --dek <dek in base64>\n"
+ "\n"
+ " dump --keystore <keystore data in base64>\n"
+ " [--password <password to decrypt the DEK inside]\n",
+ g_pszProgName);
+}
+
+static void showLogo(PRTSTREAM pStrm)
+{
+ static bool s_fShown; /* show only once */
+
+ if (!s_fShown)
+ {
+ RTStrmPrintf(pStrm, VBOX_PRODUCT " VD Keystore Mgr " VBOX_VERSION_STRING "\n"
+ "Copyright (C) 2016-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+ s_fShown = true;
+ }
+}
+
+/**
+ * Print a usage synopsis and the syntax error message.
+ */
+static int errorSyntax(const char *pszFormat, ...)
+{
+ va_list args;
+ showLogo(g_pStdErr); // show logo even if suppressed
+ va_start(args, pszFormat);
+ RTStrmPrintf(g_pStdErr, "\nSyntax error: %N\n", pszFormat, &args);
+ va_end(args);
+ printUsage(g_pStdErr);
+ return 1;
+}
+
+static int errorRuntime(const char *pszFormat, ...)
+{
+ va_list args;
+
+ va_start(args, pszFormat);
+ RTMsgErrorV(pszFormat, args);
+ va_end(args);
+ return 1;
+}
+
+static DECLCALLBACK(int) handleCreate(HandlerArg *pArgs)
+{
+ const char *pszPassword = NULL;
+ const char *pszCipher = NULL;
+ const char *pszDek = NULL;
+
+ /* Parse the command line. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--password", 'p', RTGETOPT_REQ_STRING },
+ { "--cipher" , 'c', RTGETOPT_REQ_STRING },
+ { "--dek", 'd', RTGETOPT_REQ_STRING }
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'p': // --password
+ pszPassword = ValueUnion.psz;
+ break;
+ case 'c': // --cipher
+ pszCipher = ValueUnion.psz;
+ break;
+ case 'd': // --dek
+ pszDek = ValueUnion.psz;
+ break;
+ default:
+ ch = RTGetOptPrintError(ch, &ValueUnion);
+ printUsage(g_pStdErr);
+ return ch;
+ }
+ }
+
+ /* Check for mandatory parameters. */
+ if (!pszPassword)
+ return errorSyntax("Mandatory --password option missing\n");
+ if (!pszCipher)
+ return errorSyntax("Mandatory --cipher option missing\n");
+ if (!pszDek)
+ return errorSyntax("Mandatory --dek option missing\n");
+
+ /* Get the size of the decoded DEK. */
+ ssize_t cbDekDec = RTBase64DecodedSize(pszDek, NULL);
+ if (cbDekDec == -1)
+ return errorRuntime("The encoding of the base64 DEK is bad\n");
+
+ uint8_t *pbDek = (uint8_t *)RTMemAllocZ(cbDekDec);
+ size_t cbDek = cbDekDec;
+ if (!pbDek)
+ return errorRuntime("Failed to allocate memory for the DEK\n");
+
+ int rc = RTBase64Decode(pszDek, pbDek, cbDek, &cbDek, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszKeyStoreEnc = NULL;
+ rc = vdKeyStoreCreate(pszPassword, pbDek, cbDek, pszCipher, &pszKeyStoreEnc);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Successfully created keystore\n"
+ "Keystore (base64): \n"
+ "%s\n", pszKeyStoreEnc);
+ RTMemFree(pszKeyStoreEnc);
+ }
+ else
+ errorRuntime("Failed to create keystore with %Rrc\n", rc);
+ }
+ else
+ errorRuntime("Failed to decode the DEK with %Rrc\n", rc);
+
+ RTMemFree(pbDek);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static DECLCALLBACK(int) handleDump(HandlerArg *pArgs)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int main(int argc, char *argv[])
+{
+ int exitcode = 0;
+
+ int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_STANDALONE_APP);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ g_pszProgName = RTPathFilename(argv[0]);
+
+ bool fShowLogo = false;
+ int iCmd = 1;
+ int iCmdArg;
+
+ /* global options */
+ for (int i = 1; i < argc || argc <= iCmd; i++)
+ {
+ if ( argc <= iCmd
+ || !strcmp(argv[i], "help")
+ || !strcmp(argv[i], "-?")
+ || !strcmp(argv[i], "-h")
+ || !strcmp(argv[i], "-help")
+ || !strcmp(argv[i], "--help"))
+ {
+ showLogo(g_pStdOut);
+ printUsage(g_pStdOut);
+ return 0;
+ }
+
+ if ( !strcmp(argv[i], "-v")
+ || !strcmp(argv[i], "-version")
+ || !strcmp(argv[i], "-Version")
+ || !strcmp(argv[i], "--version"))
+ {
+ /* Print version number, and do nothing else. */
+ RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
+ return 0;
+ }
+
+ if ( !strcmp(argv[i], "--nologo")
+ || !strcmp(argv[i], "-nologo")
+ || !strcmp(argv[i], "-q"))
+ {
+ /* suppress the logo */
+ fShowLogo = false;
+ iCmd++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ iCmdArg = iCmd + 1;
+
+ if (fShowLogo)
+ showLogo(g_pStdOut);
+
+ /*
+ * All registered command handlers
+ */
+ static const struct
+ {
+ const char *command;
+ DECLR3CALLBACKMEMBER(int, handler, (HandlerArg *a));
+ } s_commandHandlers[] =
+ {
+ { "create", handleCreate },
+ { "dump", handleDump },
+ { NULL, NULL }
+ };
+
+ HandlerArg handlerArg = { 0, NULL };
+ int commandIndex;
+ for (commandIndex = 0; s_commandHandlers[commandIndex].command != NULL; commandIndex++)
+ {
+ if (!strcmp(s_commandHandlers[commandIndex].command, argv[iCmd]))
+ {
+ handlerArg.argc = argc - iCmdArg;
+ handlerArg.argv = &argv[iCmdArg];
+
+ exitcode = s_commandHandlers[commandIndex].handler(&handlerArg);
+ break;
+ }
+ }
+ if (!s_commandHandlers[commandIndex].command)
+ {
+ errorSyntax("Invalid command '%s'", argv[iCmd]);
+ return 1;
+ }
+
+ return exitcode;
+}
+
+/* dummy stub for RuntimeR3 */
+#ifndef RT_OS_WINDOWS
+RTDECL(bool) RTAssertShouldPanic(void)
+{
+ return true;
+}
+#endif
diff --git a/src/VBox/Storage/testcase/vdkeystoremgr.rc b/src/VBox/Storage/testcase/vdkeystoremgr.rc
new file mode 100644
index 00000000..7fca5e9b
--- /dev/null
+++ b/src/VBox/Storage/testcase/vdkeystoremgr.rc
@@ -0,0 +1,61 @@
+/* $Id: vdkeystoremgr.rc $ */
+/** @file
+ * vdkeystoremgr - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2016-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 <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox VD Keystore utility\0"
+ VALUE "InternalName", "vdkeystoremgr\0"
+ VALUE "OriginalFilename", "vdkeystoremgr.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END