diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Storage/testcase | |
parent | Initial commit. (diff) | |
download | virtualbox-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')
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 |