diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/ValidationKit/utils | |
parent | Initial commit. (diff) | |
download | virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/ValidationKit/utils')
72 files changed, 27328 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/utils/Makefile.kmk b/src/VBox/ValidationKit/utils/Makefile.kmk new file mode 100644 index 00000000..d68f5674 --- /dev/null +++ b/src/VBox/ValidationKit/utils/Makefile.kmk @@ -0,0 +1,69 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Utilities. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Include sub-makefiles. +# +include $(PATH_SUB_CURRENT)/TestExecServ/Makefile.kmk +include $(PATH_SUB_CURRENT)/cpu/Makefile.kmk +include $(PATH_SUB_CURRENT)/fs/Makefile.kmk +include $(PATH_SUB_CURRENT)/misc/Makefile.kmk +include $(PATH_SUB_CURRENT)/network/Makefile.kmk +ifneq ($(KBUILD_TARGET),os2) + include $(PATH_SUB_CURRENT)/serial/Makefile.kmk +endif +ifeq ($(KBUILD_TARGET),win) + include $(PATH_SUB_CURRENT)/nt/Makefile.kmk +endif +ifeq ($(KBUILD_TARGET),linux) + include $(PATH_SUB_CURRENT)/usb/Makefile.kmk +endif + +ifdef VBOX_WITH_AUDIO_VALIDATIONKIT + include $(PATH_SUB_CURRENT)/audio/Makefile.kmk +endif + +# +# On OS/2 the binaries requires the libc DLLs +# (no official static linking support). +# +INSTALLS.os2 += ValidationKitOs2LibC +ValidationKitOs2LibC_TEMPLATE = VBoxValidationKitR3 +ValidationKitOs2LibC_SOURCES = \ + $(KBUILD_BIN_PATH)/libc06.dll \ + $(KBUILD_BIN_PATH)/libc061.dll \ + $(KBUILD_BIN_PATH)/libc062.dll \ + $(KBUILD_BIN_PATH)/libc063.dll \ + $(KBUILD_BIN_PATH)/libc064.dll \ + $(KBUILD_BIN_PATH)/libc065.dll \ + $(KBUILD_BIN_PATH)/libc066.dll + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/Makefile.kmk b/src/VBox/ValidationKit/utils/TestExecServ/Makefile.kmk new file mode 100644 index 00000000..7584e91c --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/Makefile.kmk @@ -0,0 +1,71 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - The Basic Remote Execution Service. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +PROGRAMS += TestExecService +TestExecService_TEMPLATE = VBoxValidationKitR3 +TestExecService_DEFS = \ + KBUILD_TARGET=\"$(KBUILD_TARGET)\" \ + KBUILD_TARGET_ARCH=\"$(KBUILD_TARGET_ARCH)\" +TestExecService_SOURCES = \ + TestExecService.cpp \ + TestExecServiceTcp.cpp + +ifn1of ($(KBUILD_TARGET), os2) + TestExecService_SOURCES += \ + TestExecServiceSerial.cpp +endif + +INSTALLS += TestExecServiceFiles +TestExecServiceFiles_TEMPLATE = VBoxValidationKitR3 +TestExecServiceFiles_INST = $(INST_VALIDATIONKIT) +TestExecServiceFiles_SOURCES := \ + vboxtxs-readme.txt + +TestExecServiceFiles_EXEC_SOURCES.linux := \ + $(PATH_SUB_CURRENT)/linux/vboxtxs.sh=>linux/vboxtxs \ + $(PATH_SUB_CURRENT)/linux/vboxtxs.service=>linux/vboxtxs.service \ + $(PATH_SUB_CURRENT)/linux/vboxtxs-nat.sh=>linux/vboxtxs-nat + +TestExecServiceFiles_SOURCES.solaris := \ + $(PATH_SUB_CURRENT)/solaris/vboxtxs.xml=>solaris/vboxtxs.xml \ + $(PATH_SUB_CURRENT)/solaris/vboxtxs-sol10.xml=>solaris/vboxtxs-sol10.xml +TestExecServiceFiles_EXEC_SOURCES.solaris := \ + $(PATH_SUB_CURRENT)/solaris/vboxtxs.sh=>solaris/vboxtxs.sh + +TestExecServiceFiles_SOURCES.win := \ + $(PATH_SUB_CURRENT)/win/vboxtxs.reg=>win/vboxtxs.reg \ + $(PATH_SUB_CURRENT)/win/vboxtxs-nat.reg=>win/vboxtxs-nat.reg +TestExecServiceFiles_EXEC_SOURCES.win := \ + $(PATH_SUB_CURRENT)/win/vboxtxs.cmd=>win/vboxtxs.cmd \ + $(PATH_SUB_CURRENT)/win/vboxtxs-nat.cmd=>win/vboxtxs-nat.cmd + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/TestExecService.cpp b/src/VBox/ValidationKit/utils/TestExecServ/TestExecService.cpp new file mode 100644 index 00000000..d9c341c5 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/TestExecService.cpp @@ -0,0 +1,3540 @@ +/* $Id: TestExecService.cpp $ */ +/** @file + * TestExecServ - Basic Remote Execution Service. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/cdrom.h> +#include <iprt/critsect.h> +#include <iprt/crc.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/initterm.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/uuid.h> +#include <iprt/zip.h> + +#include "TestExecServiceInternal.h" + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Handle IDs used by txsDoExec for the poll set. + */ +typedef enum TXSEXECHNDID +{ + TXSEXECHNDID_STDIN = 0, + TXSEXECHNDID_STDOUT, + TXSEXECHNDID_STDERR, + TXSEXECHNDID_TESTPIPE, + TXSEXECHNDID_STDIN_WRITABLE, + TXSEXECHNDID_TRANSPORT, + TXSEXECHNDID_THREAD +} TXSEXECHNDID; + + +/** + * For buffering process input supplied by the client. + */ +typedef struct TXSEXECSTDINBUF +{ + /** The mount of buffered data. */ + size_t cb; + /** The current data offset. */ + size_t off; + /** The data buffer. */ + char *pch; + /** The amount of allocated buffer space. */ + size_t cbAllocated; + /** Send further input into the bit bucket (stdin is dead). */ + bool fBitBucket; + /** The CRC-32 for standard input (received part). */ + uint32_t uCrc32; +} TXSEXECSTDINBUF; +/** Pointer to a standard input buffer. */ +typedef TXSEXECSTDINBUF *PTXSEXECSTDINBUF; + +/** + * TXS child process info. + */ +typedef struct TXSEXEC +{ + PCTXSPKTHDR pPktHdr; + RTMSINTERVAL cMsTimeout; + int rcReplySend; + + RTPOLLSET hPollSet; + RTPIPE hStdInW; + RTPIPE hStdOutR; + RTPIPE hStdErrR; + RTPIPE hTestPipeR; + RTPIPE hWakeUpPipeR; + RTTHREAD hThreadWaiter; + + /** @name For the setup phase + * @{ */ + struct StdPipe + { + RTHANDLE hChild; + PRTHANDLE phChild; + } StdIn, + StdOut, + StdErr; + RTPIPE hTestPipeW; + RTENV hEnv; + /** @} */ + + /** For serializating some access. */ + RTCRITSECT CritSect; + /** @name Members protected by the critical section. + * @{ */ + RTPROCESS hProcess; + /** The process status. Only valid when fProcessAlive is cleared. */ + RTPROCSTATUS ProcessStatus; + /** Set when the process is alive, clear when dead. */ + bool volatile fProcessAlive; + /** The end of the pipe that hThreadWaiter writes to. */ + RTPIPE hWakeUpPipeW; + /** @} */ +} TXSEXEC; +/** Pointer to a the TXS child process info. */ +typedef TXSEXEC *PTXSEXEC; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Transport layers. + */ +static const PCTXSTRANSPORT g_apTransports[] = +{ + &g_TcpTransport, +#ifndef RT_OS_OS2 + &g_SerialTransport, +#endif + //&g_FileSysTransport, + //&g_GuestPropTransport, + //&g_TestDevTransport, +}; + +/** The select transport layer. */ +static PCTXSTRANSPORT g_pTransport; +/** The scratch path. */ +static char g_szScratchPath[RTPATH_MAX]; +/** The default scratch path. */ +static char g_szDefScratchPath[RTPATH_MAX]; +/** The CD/DVD-ROM path. */ +static char g_szCdRomPath[RTPATH_MAX]; +/** The default CD/DVD-ROM path. */ +static char g_szDefCdRomPath[RTPATH_MAX]; +/** The operating system short name. */ +static char g_szOsShortName[16]; +/** The CPU architecture short name. */ +static char g_szArchShortName[16]; +/** The combined "OS.arch" name. */ +static char g_szOsDotArchShortName[32]; +/** The combined "OS/arch" name. */ +static char g_szOsSlashArchShortName[32]; +/** The executable suffix. */ +static char g_szExeSuff[8]; +/** The shell script suffix. */ +static char g_szScriptSuff[8]; +/** UUID identifying this TXS instance. This can be used to see if TXS + * has been restarted or not. */ +static RTUUID g_InstanceUuid; +/** Whether to display the output of the child process or not. */ +static bool g_fDisplayOutput = true; +/** Whether to terminate or not. + * @todo implement signals and stuff. */ +static bool volatile g_fTerminate = false; +/** Verbosity level. */ +uint32_t g_cVerbose = 1; + + +/** + * Calculates the checksum value, zero any padding space and send the packet. + * + * @returns IPRT status code. + * @param pPkt The packet to send. Must point to a correctly + * aligned buffer. + */ +static int txsSendPkt(PTXSPKTHDR pPkt) +{ + Assert(pPkt->cb >= sizeof(*pPkt)); + pPkt->uCrc32 = RTCrc32(pPkt->achOpcode, pPkt->cb - RT_UOFFSETOF(TXSPKTHDR, achOpcode)); + if (pPkt->cb != RT_ALIGN_32(pPkt->cb, TXSPKT_ALIGNMENT)) + memset((uint8_t *)pPkt + pPkt->cb, '\0', RT_ALIGN_32(pPkt->cb, TXSPKT_ALIGNMENT) - pPkt->cb); + + Log(("txsSendPkt: cb=%#x opcode=%.8s\n", pPkt->cb, pPkt->achOpcode)); + Log2(("%.*Rhxd\n", RT_MIN(pPkt->cb, 256), pPkt)); + int rc = g_pTransport->pfnSendPkt(pPkt); + while (RT_UNLIKELY(rc == VERR_INTERRUPTED) && !g_fTerminate) + rc = g_pTransport->pfnSendPkt(pPkt); + if (RT_FAILURE(rc)) + Log(("txsSendPkt: rc=%Rrc\n", rc)); + + return rc; +} + +/** + * Sends a babble reply and disconnects the client (if applicable). + * + * @param pszOpcode The BABBLE opcode. + */ +static void txsReplyBabble(const char *pszOpcode) +{ + TXSPKTHDR Reply; + Reply.cb = sizeof(Reply); + Reply.uCrc32 = 0; + memcpy(Reply.achOpcode, pszOpcode, sizeof(Reply.achOpcode)); + + g_pTransport->pfnBabble(&Reply, 20*1000); +} + +/** + * Receive and validate a packet. + * + * Will send bable responses to malformed packets that results in a error status + * code. + * + * @returns IPRT status code. + * @param ppPktHdr Where to return the packet on success. Free + * with RTMemFree. + * @param fAutoRetryOnFailure Whether to retry on error. + */ +static int txsRecvPkt(PPTXSPKTHDR ppPktHdr, bool fAutoRetryOnFailure) +{ + for (;;) + { + PTXSPKTHDR pPktHdr; + int rc = g_pTransport->pfnRecvPkt(&pPktHdr); + if (RT_SUCCESS(rc)) + { + /* validate the packet. */ + if ( pPktHdr->cb >= sizeof(TXSPKTHDR) + && pPktHdr->cb < TXSPKT_MAX_SIZE) + { + Log2(("txsRecvPkt: pPktHdr=%p cb=%#x crc32=%#x opcode=%.8s\n" + "%.*Rhxd\n", + pPktHdr, pPktHdr->cb, pPktHdr->uCrc32, pPktHdr->achOpcode, RT_MIN(pPktHdr->cb, 256), pPktHdr)); + uint32_t uCrc32Calc = pPktHdr->uCrc32 != 0 + ? RTCrc32(&pPktHdr->achOpcode[0], pPktHdr->cb - RT_UOFFSETOF(TXSPKTHDR, achOpcode)) + : 0; + if (pPktHdr->uCrc32 == uCrc32Calc) + { + AssertCompileMemberSize(TXSPKTHDR, achOpcode, 8); + if ( RT_C_IS_UPPER(pPktHdr->achOpcode[0]) + && RT_C_IS_UPPER(pPktHdr->achOpcode[1]) + && (RT_C_IS_UPPER(pPktHdr->achOpcode[2]) || pPktHdr->achOpcode[2] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[3]) || pPktHdr->achOpcode[3] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[4]) || pPktHdr->achOpcode[4] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[5]) || pPktHdr->achOpcode[5] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[6]) || pPktHdr->achOpcode[6] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[7]) || pPktHdr->achOpcode[7] == ' ') + ) + { + Log(("txsRecvPkt: cb=%#x opcode=%.8s\n", pPktHdr->cb, pPktHdr->achOpcode)); + *ppPktHdr = pPktHdr; + return rc; + } + + rc = VERR_IO_BAD_COMMAND; + } + else + { + Log(("txsRecvPkt: cb=%#x opcode=%.8s crc32=%#x actual=%#x\n", + pPktHdr->cb, pPktHdr->achOpcode, pPktHdr->uCrc32, uCrc32Calc)); + rc = VERR_IO_CRC; + } + } + else + rc = VERR_IO_BAD_LENGTH; + + /* Send babble reply and disconnect the client if the transport is + connection oriented. */ + if (rc == VERR_IO_BAD_LENGTH) + txsReplyBabble("BABBLE L"); + else if (rc == VERR_IO_CRC) + txsReplyBabble("BABBLE C"); + else if (rc == VERR_IO_BAD_COMMAND) + txsReplyBabble("BABBLE O"); + else + txsReplyBabble("BABBLE "); + RTMemFree(pPktHdr); + } + + /* Try again or return failure? */ + if ( g_fTerminate + || rc != VERR_INTERRUPTED + || !fAutoRetryOnFailure + ) + { + Log(("txsRecvPkt: rc=%Rrc\n", rc)); + return rc; + } + } +} + +/** + * Make a simple reply, only status opcode. + * + * @returns IPRT status code of the send. + * @param pReply The reply packet. + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param cbExtra Bytes in addition to the header. + */ +static int txsReplyInternal(PTXSPKTHDR pReply, const char *pszOpcode, size_t cbExtra) +{ + /* copy the opcode, don't be too strict in case of a padding screw up. */ + size_t cchOpcode = strlen(pszOpcode); + if (RT_LIKELY(cchOpcode == sizeof(pReply->achOpcode))) + memcpy(pReply->achOpcode, pszOpcode, sizeof(pReply->achOpcode)); + else + { + Assert(cchOpcode == sizeof(pReply->achOpcode)); + while (cchOpcode > 0 && pszOpcode[cchOpcode - 1] == ' ') + cchOpcode--; + AssertMsgReturn(cchOpcode < sizeof(pReply->achOpcode), ("%d/'%.8s'\n", cchOpcode, pszOpcode), VERR_INTERNAL_ERROR_4); + memcpy(pReply->achOpcode, pszOpcode, cchOpcode); + memset(&pReply->achOpcode[cchOpcode], ' ', sizeof(pReply->achOpcode) - cchOpcode); + } + + pReply->cb = (uint32_t)sizeof(TXSPKTHDR) + (uint32_t)cbExtra; + pReply->uCrc32 = 0; + + return txsSendPkt(pReply); +} + +/** + * Make a simple reply, only status opcode. + * + * @returns IPRT status code of the send. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + */ +static int txsReplySimple(PCTXSPKTHDR pPktHdr, const char *pszOpcode) +{ + TXSPKTHDR Pkt; + NOREF(pPktHdr); + return txsReplyInternal(&Pkt, pszOpcode, 0); +} + +/** + * Acknowledges a packet with success. + * + * @returns IPRT status code of the send. + * @param pPktHdr The original packet (for future use). + */ +static int txsReplyAck(PCTXSPKTHDR pPktHdr) +{ + return txsReplySimple(pPktHdr, "ACK "); +} + +/** + * Replies with a failure. + * + * @returns IPRT status code of the send. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param pszDetailFmt Longer description of the problem (format + * string). + * @param va Format arguments. + */ +static int txsReplyFailureV(PCTXSPKTHDR pPktHdr, const char *pszOpcode, const char *pszDetailFmt, va_list va) +{ + NOREF(pPktHdr); + union + { + TXSPKTHDR Hdr; + char ach[256]; + } uPkt; + + size_t cchDetail = RTStrPrintfV(&uPkt.ach[sizeof(TXSPKTHDR)], + sizeof(uPkt) - sizeof(TXSPKTHDR), + pszDetailFmt, va); + return txsReplyInternal(&uPkt.Hdr, pszOpcode, cchDetail + 1); +} + +/** + * Replies with a failure. + * + * @returns IPRT status code of the send. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param pszDetailFmt Longer description of the problem (format + * string). + * @param ... Format arguments. + */ +static int txsReplyFailure(PCTXSPKTHDR pPktHdr, const char *pszOpcode, const char *pszDetailFmt, ...) +{ + va_list va; + va_start(va, pszDetailFmt); + int rc = txsReplyFailureV(pPktHdr, pszOpcode, pszDetailFmt, va); + va_end(va); + return rc; +} + +/** + * Replies according to the return code. + * + * @returns IPRT status code of the send. + * @param pPktHdr The packet to reply to. + * @param rcOperation The status code to report. + * @param pszOperationFmt The operation that failed. Typically giving the + * function call with important arguments. + * @param ... Arguments to the format string. + */ +static int txsReplyRC(PCTXSPKTHDR pPktHdr, int rcOperation, const char *pszOperationFmt, ...) +{ + if (RT_SUCCESS(rcOperation)) + return txsReplyAck(pPktHdr); + + char szOperation[128]; + va_list va; + va_start(va, pszOperationFmt); + RTStrPrintfV(szOperation, sizeof(szOperation), pszOperationFmt, va); + va_end(va); + + return txsReplyFailure(pPktHdr, "FAILED ", "%s failed with rc=%Rrc (opcode '%.8s')", + szOperation, rcOperation, pPktHdr->achOpcode); +} + +/** + * Signal a bad packet minum size. + * + * @returns IPRT status code of the send. + * @param pPktHdr The packet to reply to. + * @param cbMin The minimum size. + */ +static int txsReplyBadMinSize(PCTXSPKTHDR pPktHdr, size_t cbMin) +{ + return txsReplyFailure(pPktHdr, "BAD SIZE", "Expected at least %zu bytes, got %u (opcode '%.8s')", + cbMin, pPktHdr->cb, pPktHdr->achOpcode); +} + +/** + * Signal a bad packet exact size. + * + * @returns IPRT status code of the send. + * @param pPktHdr The packet to reply to. + * @param cb The wanted size. + */ +static int txsReplyBadSize(PCTXSPKTHDR pPktHdr, size_t cb) +{ + return txsReplyFailure(pPktHdr, "BAD SIZE", "Expected at %zu bytes, got %u (opcode '%.8s')", + cb, pPktHdr->cb, pPktHdr->achOpcode); +} + +/** + * Deals with a command that isn't implemented yet. + * @returns IPRT status code of the send. + * @param pPktHdr The packet which opcode isn't implemented. + */ +static int txsReplyNotImplemented(PCTXSPKTHDR pPktHdr) +{ + return txsReplyFailure(pPktHdr, "NOT IMPL", "Opcode '%.8s' is not implemented", pPktHdr->achOpcode); +} + +/** + * Deals with a unknown command. + * @returns IPRT status code of the send. + * @param pPktHdr The packet to reply to. + */ +static int txsReplyUnknown(PCTXSPKTHDR pPktHdr) +{ + return txsReplyFailure(pPktHdr, "UNKNOWN ", "Opcode '%.8s' is not known", pPktHdr->achOpcode); +} + +/** + * Replaces a variable with its value. + * + * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY. + * @param ppszNew In/Out. + * @param pcchNew In/Out. (Messed up on failure.) + * @param offVar Variable offset. + * @param cchVar Variable length. + * @param pszValue The value. + * @param cchValue Value length. + */ +static int txsReplaceStringVariable(char **ppszNew, size_t *pcchNew, size_t offVar, size_t cchVar, + const char *pszValue, size_t cchValue) +{ + size_t const cchAfter = *pcchNew - offVar - cchVar; + if (cchVar < cchValue) + { + *pcchNew += cchValue - cchVar; + int rc = RTStrRealloc(ppszNew, *pcchNew + 1); + if (RT_FAILURE(rc)) + return rc; + } + + char *pszNew = *ppszNew; + memmove(&pszNew[offVar + cchValue], &pszNew[offVar + cchVar], cchAfter + 1); + memcpy(&pszNew[offVar], pszValue, cchValue); + return VINF_SUCCESS; +} + +/** + * Replace the variables found in the source string, returning a new string that + * lives on the string heap. + * + * @returns Boolean success indicator. Will reply to the client with all the + * gory detail on failure. + * @param pPktHdr The packet the string relates to. For replying + * on error. + * @param pszSrc The source string. + * @param ppszNew Where to return the new string. + * @param prcSend Where to return the status code of the send on + * failure. + */ +static int txsReplaceStringVariables(PCTXSPKTHDR pPktHdr, const char *pszSrc, char **ppszNew, int *prcSend) +{ + /* Lazy approach that employs memmove. */ + size_t cchNew = strlen(pszSrc); + char *pszNew = RTStrDup(pszSrc); + char *pszDollar = pszNew; + while ((pszDollar = strchr(pszDollar, '$')) != NULL) + { + if (pszDollar[1] == '{') + { + const char *pszEnd = strchr(&pszDollar[2], '}'); + if (pszEnd) + { +#define IF_VARIABLE_DO(pszDollar, szVarExpr, pszValue) \ + if ( cchVar == sizeof(szVarExpr) - 1 \ + && !memcmp(pszDollar, szVarExpr, sizeof(szVarExpr) - 1) ) \ + { \ + size_t const cchValue = strlen(pszValue); \ + rc = txsReplaceStringVariable(&pszNew, &cchNew, offDollar, \ + sizeof(szVarExpr) - 1, pszValue, cchValue); \ + offDollar += cchValue; \ + } + int rc; + size_t const cchVar = pszEnd - pszDollar + 1; /* includes "${}" */ + size_t offDollar = pszDollar - pszNew; + IF_VARIABLE_DO(pszDollar, "${CDROM}", g_szCdRomPath) + else IF_VARIABLE_DO(pszDollar, "${SCRATCH}", g_szScratchPath) + else IF_VARIABLE_DO(pszDollar, "${ARCH}", g_szArchShortName) + else IF_VARIABLE_DO(pszDollar, "${OS}", g_szOsShortName) + else IF_VARIABLE_DO(pszDollar, "${OS.ARCH}", g_szOsDotArchShortName) + else IF_VARIABLE_DO(pszDollar, "${OS/ARCH}", g_szOsSlashArchShortName) + else IF_VARIABLE_DO(pszDollar, "${EXESUFF}", g_szExeSuff) + else IF_VARIABLE_DO(pszDollar, "${SCRIPTSUFF}", g_szScriptSuff) + else + { + RTStrFree(pszNew); + *prcSend = txsReplyFailure(pPktHdr, "UNKN VAR", "Unknown variable '%.*s' encountered in '%s'", + cchVar, pszDollar, pszSrc); + *ppszNew = NULL; + return false; + } + pszDollar = &pszNew[offDollar]; + + if (RT_FAILURE(rc)) + { + RTStrFree(pszNew); + *prcSend = txsReplyRC(pPktHdr, rc, "RTStrRealloc"); + *ppszNew = NULL; + return false; + } +#undef IF_VARIABLE_DO + } + } + } + + *ppszNew = pszNew; + *prcSend = VINF_SUCCESS; + return true; +} + +/** + * Checks if the string is valid and returns the expanded version. + * + * @returns true if valid, false if invalid. + * @param pPktHdr The packet being unpacked. + * @param pszArgName The argument name. + * @param psz Pointer to the string within pPktHdr. + * @param ppszExp Where to return the expanded string. Must be + * freed by calling RTStrFree(). + * @param ppszNext Where to return the pointer to the next field. + * If NULL, then we assume this string is at the + * end of the packet and will make sure it has the + * advertised length. + * @param prcSend Where to return the status code of the send on + * failure. + */ +static bool txsIsStringValid(PCTXSPKTHDR pPktHdr, const char *pszArgName, const char *psz, + char **ppszExp, const char **ppszNext, int *prcSend) +{ + *ppszExp = NULL; + if (ppszNext) + *ppszNext = NULL; + + size_t const off = psz - (const char *)pPktHdr; + if (pPktHdr->cb <= off) + { + *prcSend = txsReplyFailure(pPktHdr, "STR MISS", "Missing string argument '%s' in '%.8s'", + pszArgName, pPktHdr->achOpcode); + return false; + } + + size_t const cchMax = pPktHdr->cb - off; + const char *pszEnd = RTStrEnd(psz, cchMax); + if (!pszEnd) + { + *prcSend = txsReplyFailure(pPktHdr, "STR TERM", "The string argument '%s' in '%.8s' is unterminated", + pszArgName, pPktHdr->achOpcode); + return false; + } + + if (!ppszNext && (size_t)(pszEnd - psz) != cchMax - 1) + { + *prcSend = txsReplyFailure(pPktHdr, "STR SHRT", "The string argument '%s' in '%.8s' is shorter than advertised", + pszArgName, pPktHdr->achOpcode); + return false; + } + + if (!txsReplaceStringVariables(pPktHdr, psz, ppszExp, prcSend)) + return false; + if (ppszNext) + *ppszNext = pszEnd + 1; + return true; +} + +/** + * Validates a packet with a single string after the header. + * + * @returns true if valid, false if invalid. + * @param pPktHdr The packet. + * @param pszArgName The argument name. + * @param ppszExp Where to return the string pointer. Variables + * will be replaced and it must therefore be freed + * by calling RTStrFree(). + * @param prcSend Where to return the status code of the send on + * failure. + */ +static bool txsIsStringPktValid(PCTXSPKTHDR pPktHdr, const char *pszArgName, char **ppszExp, int *prcSend) +{ + if (pPktHdr->cb < sizeof(TXSPKTHDR) + 2) + { + *ppszExp = NULL; + *prcSend = txsReplyBadMinSize(pPktHdr, sizeof(TXSPKTHDR) + 2); + return false; + } + + return txsIsStringValid(pPktHdr, pszArgName, (const char *)(pPktHdr + 1), ppszExp, NULL, prcSend); +} + +/** + * Checks if the two opcodes match. + * + * @returns true on match, false on mismatch. + * @param pPktHdr The packet header. + * @param pszOpcode2 The opcode we're comparing with. Does not have + * to be the whole 8 chars long. + */ +DECLINLINE(bool) txsIsSameOpcode(PCTXSPKTHDR pPktHdr, const char *pszOpcode2) +{ + if (pPktHdr->achOpcode[0] != pszOpcode2[0]) + return false; + if (pPktHdr->achOpcode[1] != pszOpcode2[1]) + return false; + + unsigned i = 2; + while ( i < RT_SIZEOFMEMB(TXSPKTHDR, achOpcode) + && pszOpcode2[i] != '\0') + { + if (pPktHdr->achOpcode[i] != pszOpcode2[i]) + break; + i++; + } + + if ( i < RT_SIZEOFMEMB(TXSPKTHDR, achOpcode) + && pszOpcode2[i] == '\0') + { + while ( i < RT_SIZEOFMEMB(TXSPKTHDR, achOpcode) + && pPktHdr->achOpcode[i] == ' ') + i++; + } + + return i == RT_SIZEOFMEMB(TXSPKTHDR, achOpcode); +} + +/** + * Used by txsDoGetFile to wait for a reply ACK from the client. + * + * @returns VINF_SUCCESS on ACK, VERR_GENERAL_FAILURE on NACK, + * VERR_NET_NOT_CONNECTED on unknown response (sending a bable reply), + * or whatever txsRecvPkt returns. + * @param pPktHdr The original packet (for future use). + */ +static int txsWaitForAck(PCTXSPKTHDR pPktHdr) +{ + NOREF(pPktHdr); + /** @todo timeout? */ + PTXSPKTHDR pReply; + int rc = txsRecvPkt(&pReply, false /*fAutoRetryOnFailure*/); + if (RT_SUCCESS(rc)) + { + if (txsIsSameOpcode(pReply, "ACK")) + rc = VINF_SUCCESS; + else if (txsIsSameOpcode(pReply, "NACK")) + rc = VERR_GENERAL_FAILURE; + else + { + txsReplyBabble("BABBLE "); + rc = VERR_NET_NOT_CONNECTED; + } + RTMemFree(pReply); + } + return rc; +} + +/** + * Unpacks a tar file. + * + * @returns IPRT status code from send. + * @param pPktHdr The unpack file packet. + */ +static int txsDoUnpackFile(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszFile = NULL; + char *pszDirectory = NULL; + + /* Packet cursor. */ + const char *pch = (const char *)(pPktHdr + 1); + + if (txsIsStringValid(pPktHdr, "file", pch, &pszFile, &pch, &rc)) + { + if (txsIsStringValid(pPktHdr, "directory", pch, &pszDirectory, &pch, &rc)) + { + char *pszSuff = RTPathSuffix(pszFile); + + const char *apszArgs[7]; + unsigned cArgs = 0; + + apszArgs[cArgs++] = "RTTar"; + apszArgs[cArgs++] = "--extract"; + + apszArgs[cArgs++] = "--file"; + apszArgs[cArgs++] = pszFile; + + apszArgs[cArgs++] = "--directory"; + apszArgs[cArgs++] = pszDirectory; + + if ( pszSuff + && ( !RTStrICmp(pszSuff, ".gz") + || !RTStrICmp(pszSuff, ".tgz"))) + apszArgs[cArgs++] = "--gunzip"; + + RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs); + if (rcExit != RTEXITCODE_SUCCESS) + rc = VERR_GENERAL_FAILURE; /** @todo proper return code. */ + else + rc = VINF_SUCCESS; + + rc = txsReplyRC(pPktHdr, rc, "RTZipTarCmd(\"%s\",\"%s\")", + pszFile, pszDirectory); + + RTStrFree(pszDirectory); + } + RTStrFree(pszFile); + } + + return rc; +} + +/** + * Downloads a file to the client. + * + * The transfer sends a stream of DATA packets (0 or more) and ends it all with + * a ACK packet. If an error occurs, a FAILURE packet is sent and the transfer + * aborted. + * + * @returns IPRT status code from send. + * @param pPktHdr The get file packet. + */ +static int txsDoGetFile(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "file", &pszPath, &rc)) + return rc; + + RTFILE hFile; + rc = RTFileOpen(&hFile, pszPath, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN); + if (RT_SUCCESS(rc)) + { + uint32_t uMyCrc32 = RTCrc32Start(); + for (;;) + { + struct + { + TXSPKTHDR Hdr; + uint32_t uCrc32; + char ab[_64K]; + char abPadding[TXSPKT_ALIGNMENT]; + } Pkt; + size_t cbRead; + rc = RTFileRead(hFile, &Pkt.ab[0], _64K, &cbRead); + if (RT_FAILURE(rc) || cbRead == 0) + { + if (rc == VERR_EOF || (RT_SUCCESS(rc) && cbRead == 0)) + { + Pkt.uCrc32 = RTCrc32Finish(uMyCrc32); + rc = txsReplyInternal(&Pkt.Hdr, "DATA EOF", sizeof(uint32_t)); + if (RT_SUCCESS(rc)) + rc = txsWaitForAck(&Pkt.Hdr); + } + else + rc = txsReplyRC(pPktHdr, rc, "RTFileRead"); + break; + } + + uMyCrc32 = RTCrc32Process(uMyCrc32, &Pkt.ab[0], cbRead); + Pkt.uCrc32 = RTCrc32Finish(uMyCrc32); + rc = txsReplyInternal(&Pkt.Hdr, "DATA ", cbRead + sizeof(uint32_t)); + if (RT_FAILURE(rc)) + break; + rc = txsWaitForAck(&Pkt.Hdr); + if (RT_FAILURE(rc)) + break; + } + + RTFileClose(hFile); + } + else + rc = txsReplyRC(pPktHdr, rc, "RTFileOpen(,\"%s\",)", pszPath); + + RTStrFree(pszPath); + return rc; +} + +/** + * Uploads a file from the client. + * + * The transfer sends a stream of DATA packets (0 or more) and ends it all with + * a DATA EOF packet. We ACK each of these, so that if a write error occurs we + * can abort the transfer straight away. + * + * @returns IPRT status code from send. + * @param pPktHdr The put file packet. + */ +static int txsDoPutFile(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "file", &pszPath, &rc)) + return rc; + + RTFILE hFile; + rc = RTFileOpen(&hFile, pszPath, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE); + if (RT_SUCCESS(rc)) + { + bool fSuccess = false; + rc = txsReplyAck(pPktHdr); + if (RT_SUCCESS(rc)) + { + /* + * Read client command packets and process them. + */ + uint32_t uMyCrc32 = RTCrc32Start(); + for (;;) + { + PTXSPKTHDR pDataPktHdr; + rc = txsRecvPkt(&pDataPktHdr, false /*fAutoRetryOnFailure*/); + if (RT_FAILURE(rc)) + break; + + if (txsIsSameOpcode(pDataPktHdr, "DATA")) + { + size_t const cbMin = sizeof(TXSPKTHDR) + sizeof(uint32_t); + if (pDataPktHdr->cb >= cbMin) + { + size_t cbData = pDataPktHdr->cb - cbMin; + const void *pvData = (const char *)pDataPktHdr + cbMin; + uint32_t uCrc32 = *(uint32_t const *)(pDataPktHdr + 1); + + uMyCrc32 = RTCrc32Process(uMyCrc32, pvData, cbData); + if (RTCrc32Finish(uMyCrc32) == uCrc32) + { + rc = RTFileWrite(hFile, pvData, cbData, NULL); + if (RT_SUCCESS(rc)) + { + rc = txsReplyAck(pDataPktHdr); + RTMemFree(pDataPktHdr); + continue; + } + + rc = txsReplyRC(pDataPktHdr, rc, "RTFileWrite"); + } + else + rc = txsReplyFailure(pDataPktHdr, "BAD DCRC", "mycrc=%#x your=%#x", uMyCrc32, uCrc32); + } + else + rc = txsReplyBadMinSize(pPktHdr, cbMin); + } + else if (txsIsSameOpcode(pDataPktHdr, "DATA EOF")) + { + if (pDataPktHdr->cb == sizeof(TXSPKTHDR) + sizeof(uint32_t)) + { + uint32_t uCrc32 = *(uint32_t const *)(pDataPktHdr + 1); + if (RTCrc32Finish(uMyCrc32) == uCrc32) + { + rc = txsReplyAck(pDataPktHdr); + fSuccess = RT_SUCCESS(rc); + } + else + rc = txsReplyFailure(pDataPktHdr, "BAD DCRC", "mycrc=%#x your=%#x", uMyCrc32, uCrc32); + } + else + rc = txsReplyAck(pDataPktHdr); + } + else if (txsIsSameOpcode(pDataPktHdr, "ABORT")) + rc = txsReplyAck(pDataPktHdr); + else + rc = txsReplyFailure(pDataPktHdr, "UNKNOWN ", "Opcode '%.8s' is not known or not recognized during PUT FILE", pDataPktHdr->achOpcode); + RTMemFree(pDataPktHdr); + break; + } + } + + RTFileClose(hFile); + + /* + * Delete the file on failure. + */ + if (!fSuccess) + RTFileDelete(pszPath); + } + else + rc = txsReplyRC(pPktHdr, rc, "RTFileOpen(,\"%s\",)", pszPath); + + RTStrFree(pszPath); + return rc; +} + +/** + * List the entries in the specified directory. + * + * @returns IPRT status code from send. + * @param pPktHdr The list packet. + */ +static int txsDoList(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "dir", &pszPath, &rc)) + return rc; + + rc = txsReplyNotImplemented(pPktHdr); + + RTStrFree(pszPath); + return rc; +} + + +/** + * Get info about a file system object, following all but the symbolic links + * except in the final path component. + * + * @returns IPRT status code from send. + * @param pPktHdr The lstat packet. + */ +static int txsDoLStat(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "path", &pszPath, &rc)) + return rc; + + RTFSOBJINFO Info; + rc = RTPathQueryInfoEx(pszPath, &Info, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + /** @todo figure out how to format the return buffer here. */ + rc = txsReplyNotImplemented(pPktHdr); + else + rc = txsReplyRC(pPktHdr, rc, "RTPathQueryInfoEx(\"%s\",,UNIX,ON_LINK)", pszPath); + + RTStrFree(pszPath); + return rc; +} + +/** + * Get info about a file system object, following all symbolic links. + * + * @returns IPRT status code from send. + * @param pPktHdr The stat packet. + */ +static int txsDoStat(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "path", &pszPath, &rc)) + return rc; + + RTFSOBJINFO Info; + rc = RTPathQueryInfoEx(pszPath, &Info, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(rc)) + /** @todo figure out how to format the return buffer here. */ + rc = txsReplyNotImplemented(pPktHdr); + else + rc = txsReplyRC(pPktHdr, rc, "RTPathQueryInfoEx(\"%s\",,UNIX,FOLLOW_LINK)", pszPath); + + RTStrFree(pszPath); + return rc; +} + +/** + * Checks if the specified path is a symbolic link. + * + * @returns IPRT status code from send. + * @param pPktHdr The issymlnk packet. + */ +static int txsDoIsSymlnk(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "symlink", &pszPath, &rc)) + return rc; + + RTFSOBJINFO Info; + rc = RTPathQueryInfoEx(pszPath, &Info, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc) && RTFS_IS_SYMLINK(Info.Attr.fMode)) + rc = txsReplySimple(pPktHdr, "TRUE "); + else + rc = txsReplySimple(pPktHdr, "FALSE "); + + RTStrFree(pszPath); + return rc; +} + +/** + * Checks if the specified path is a file or not. + * + * If the final path element is a symbolic link to a file, we'll return + * FALSE. + * + * @returns IPRT status code from send. + * @param pPktHdr The isfile packet. + */ +static int txsDoIsFile(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "dir", &pszPath, &rc)) + return rc; + + RTFSOBJINFO Info; + rc = RTPathQueryInfoEx(pszPath, &Info, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc) && RTFS_IS_FILE(Info.Attr.fMode)) + rc = txsReplySimple(pPktHdr, "TRUE "); + else + rc = txsReplySimple(pPktHdr, "FALSE "); + + RTStrFree(pszPath); + return rc; +} + +/** + * Checks if the specified path is a directory or not. + * + * If the final path element is a symbolic link to a directory, we'll return + * FALSE. + * + * @returns IPRT status code from send. + * @param pPktHdr The isdir packet. + */ +static int txsDoIsDir(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "dir", &pszPath, &rc)) + return rc; + + RTFSOBJINFO Info; + rc = RTPathQueryInfoEx(pszPath, &Info, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(Info.Attr.fMode)) + rc = txsReplySimple(pPktHdr, "TRUE "); + else + rc = txsReplySimple(pPktHdr, "FALSE "); + + RTStrFree(pszPath); + return rc; +} + +/** + * Changes the group of a file, directory of symbolic link. + * + * @returns IPRT status code from send. + * @param pPktHdr The chmod packet. + */ +static int txsDoChGrp(PCTXSPKTHDR pPktHdr) +{ + return txsReplyNotImplemented(pPktHdr); +} + +/** + * Changes the owner of a file, directory of symbolic link. + * + * @returns IPRT status code from send. + * @param pPktHdr The chmod packet. + */ +static int txsDoChOwn(PCTXSPKTHDR pPktHdr) +{ + return txsReplyNotImplemented(pPktHdr); +} + +/** + * Changes the mode of a file or directory. + * + * @returns IPRT status code from send. + * @param pPktHdr The chmod packet. + */ +static int txsDoChMod(PCTXSPKTHDR pPktHdr) +{ + return txsReplyNotImplemented(pPktHdr); +} + +/** + * Removes a directory tree. + * + * @returns IPRT status code from send. + * @param pPktHdr The rmtree packet. + */ +static int txsDoRmTree(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "dir", &pszPath, &rc)) + return rc; + + rc = RTDirRemoveRecursive(pszPath, 0 /*fFlags*/); + + rc = txsReplyRC(pPktHdr, rc, "RTDirRemoveRecusive(\"%s\",0)", pszPath); + RTStrFree(pszPath); + return rc; +} + +/** + * Removes a symbolic link. + * + * @returns IPRT status code from send. + * @param pPktHdr The rmsymlink packet. + */ +static int txsDoRmSymlnk(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "symlink", &pszPath, &rc)) + return rc; + + rc = VERR_NOT_IMPLEMENTED; /// @todo RTSymlinkDelete(pszPath); + + rc = txsReplyRC(pPktHdr, rc, "RTSymlinkDelete(\"%s\")", pszPath); + RTStrFree(pszPath); + return rc; +} + +/** + * Removes a file. + * + * @returns IPRT status code from send. + * @param pPktHdr The rmfile packet. + */ +static int txsDoRmFile(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "file", &pszPath, &rc)) + return rc; + + rc = RTFileDelete(pszPath); + + rc = txsReplyRC(pPktHdr, rc, "RTFileDelete(\"%s\")", pszPath); + RTStrFree(pszPath); + return rc; +} + +/** + * Removes a directory. + * + * @returns IPRT status code from send. + * @param pPktHdr The rmdir packet. + */ +static int txsDoRmDir(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "dir", &pszPath, &rc)) + return rc; + + rc = RTDirRemove(pszPath); + + rc = txsReplyRC(pPktHdr, rc, "RTDirRemove(\"%s\")", pszPath); + RTStrFree(pszPath); + return rc; +} + +/** + * Creates a symbolic link. + * + * @returns IPRT status code from send. + * @param pPktHdr The mksymlnk packet. + */ +static int txsDoMkSymlnk(PCTXSPKTHDR pPktHdr) +{ + return txsReplyNotImplemented(pPktHdr); +} + +/** + * Creates a directory and all its parents. + * + * @returns IPRT status code from send. + * @param pPktHdr The mkdir -p packet. + */ +static int txsDoMkDrPath(PCTXSPKTHDR pPktHdr) +{ + /* The same format as the MKDIR command. */ + if (pPktHdr->cb < sizeof(TXSPKTHDR) + sizeof(RTFMODE) + 2) + return txsReplyBadMinSize(pPktHdr, sizeof(TXSPKTHDR) + sizeof(RTFMODE) + 2); + + int rc; + char *pszPath; + if (!txsIsStringValid(pPktHdr, "dir", (const char *)(pPktHdr + 1) + sizeof(RTFMODE), &pszPath, NULL, &rc)) + return rc; + + RTFMODE fMode = *(RTFMODE const *)(pPktHdr + 1); + rc = RTDirCreateFullPath(pszPath, fMode); + + rc = txsReplyRC(pPktHdr, rc, "RTDirCreateFullPath(\"%s\", %#x)", pszPath, fMode); + RTStrFree(pszPath); + return rc; +} + +/** + * Creates a directory. + * + * @returns IPRT status code from send. + * @param pPktHdr The mkdir packet. + */ +static int txsDoMkDir(PCTXSPKTHDR pPktHdr) +{ + /* After the packet header follows a mode mask and the remainder of + the packet is the zero terminated directory name. */ + size_t const cbMin = sizeof(TXSPKTHDR) + sizeof(RTFMODE) + 2; + if (pPktHdr->cb < cbMin) + return txsReplyBadMinSize(pPktHdr, cbMin); + + int rc; + char *pszPath; + if (!txsIsStringValid(pPktHdr, "dir", (const char *)(pPktHdr + 1) + sizeof(RTFMODE), &pszPath, NULL, &rc)) + return rc; + + RTFMODE fMode = *(RTFMODE const *)(pPktHdr + 1); + rc = RTDirCreate(pszPath, fMode, 0); + + rc = txsReplyRC(pPktHdr, rc, "RTDirCreate(\"%s\", %#x)", pszPath, fMode); + RTStrFree(pszPath); + return rc; +} + +/** + * Cleans up the scratch area. + * + * @returns IPRT status code from send. + * @param pPktHdr The shutdown packet. + */ +static int txsDoCleanup(PCTXSPKTHDR pPktHdr) +{ + int rc = RTDirRemoveRecursive(g_szScratchPath, RTDIRRMREC_F_CONTENT_ONLY); + return txsReplyRC(pPktHdr, rc, "RTDirRemoveRecursive(\"%s\", CONTENT_ONLY)", g_szScratchPath); +} + +/** + * Ejects the specified DVD/CD drive. + * + * @returns IPRT status code from send. + * @param pPktHdr The eject packet. + */ +static int txsDoCdEject(PCTXSPKTHDR pPktHdr) +{ + /* After the packet header follows a uint32_t ordinal. */ + size_t const cbExpected = sizeof(TXSPKTHDR) + sizeof(uint32_t); + if (pPktHdr->cb != cbExpected) + return txsReplyBadSize(pPktHdr, cbExpected); + uint32_t iOrdinal = *(uint32_t const *)(pPktHdr + 1); + + RTCDROM hCdrom; + int rc = RTCdromOpenByOrdinal(iOrdinal, RTCDROM_O_CONTROL, &hCdrom); + if (RT_FAILURE(rc)) + return txsReplyRC(pPktHdr, rc, "RTCdromOpenByOrdinal(%u, RTCDROM_O_CONTROL, )", iOrdinal); + rc = RTCdromEject(hCdrom, true /*fForce*/); + RTCdromRelease(hCdrom); + + return txsReplyRC(pPktHdr, rc, "RTCdromEject(ord=%u, fForce=true)", iOrdinal); +} + +/** + * Common worker for txsDoShutdown and txsDoReboot. + * + * @returns IPRT status code from send. + * @param pPktHdr The reboot packet. + * @param fAction Which action to take. + */ +static int txsCommonShutdownReboot(PCTXSPKTHDR pPktHdr, uint32_t fAction) +{ + /* + * We ACK the reboot & shutdown before actually performing them, then we + * terminate the transport layer. + * + * This is to make sure the client isn't stuck with a dead connection. The + * transport layer termination also make sure we won't accept new + * connections in case the client is too eager to reconnect to a rebooted + * test victim. On the down side, we cannot easily report RTSystemShutdown + * failures failures this way. But the client can kind of figure it out by + * reconnecting and seeing that our UUID was unchanged. + */ + int rc; + if (pPktHdr->cb != sizeof(TXSPKTHDR)) + return txsReplyBadSize(pPktHdr, sizeof(TXSPKTHDR)); + g_pTransport->pfnNotifyReboot(); + rc = txsReplyAck(pPktHdr); + RTThreadSleep(2560); /* fudge factor */ + g_pTransport->pfnTerm(); + + /* + * Do the job, if it fails we'll restart the transport layer. + */ +#if 0 + rc = VINF_SUCCESS; +#else + rc = RTSystemShutdown(0 /*cMsDelay*/, + fAction | RTSYSTEM_SHUTDOWN_PLANNED | RTSYSTEM_SHUTDOWN_FORCE, + "Test Execution Service"); +#endif + if (RT_SUCCESS(rc)) + { + RTMsgInfo(fAction == RTSYSTEM_SHUTDOWN_REBOOT ? "Rebooting...\n" : "Shutting down...\n"); + g_fTerminate = true; + } + else + { + RTMsgError("RTSystemShutdown w/ fAction=%#x failed: %Rrc", fAction, rc); + + int rc2 = g_pTransport->pfnInit(); + if (RT_FAILURE(rc2)) + { + g_fTerminate = true; + rc = rc2; + } + } + return rc; +} + +/** + * Shuts down the machine, powering it off if possible. + * + * @returns IPRT status code from send. + * @param pPktHdr The shutdown packet. + */ +static int txsDoShutdown(PCTXSPKTHDR pPktHdr) +{ + return txsCommonShutdownReboot(pPktHdr, RTSYSTEM_SHUTDOWN_POWER_OFF_HALT); +} + +/** + * Reboots the machine. + * + * @returns IPRT status code from send. + * @param pPktHdr The reboot packet. + */ +static int txsDoReboot(PCTXSPKTHDR pPktHdr) +{ + return txsCommonShutdownReboot(pPktHdr, RTSYSTEM_SHUTDOWN_REBOOT); +} + +/** + * Verifies and acknowledges a "UUID" request. + * + * @returns IPRT status code. + * @param pPktHdr The howdy packet. + */ +static int txsDoUuid(PCTXSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(TXSPKTHDR)) + return txsReplyBadSize(pPktHdr, sizeof(TXSPKTHDR)); + + struct + { + TXSPKTHDR Hdr; + char szUuid[RTUUID_STR_LENGTH]; + char abPadding[TXSPKT_ALIGNMENT]; + } Pkt; + + int rc = RTUuidToStr(&g_InstanceUuid, Pkt.szUuid, sizeof(Pkt.szUuid)); + if (RT_FAILURE(rc)) + return txsReplyRC(pPktHdr, rc, "RTUuidToStr"); + return txsReplyInternal(&Pkt.Hdr, "ACK UUID", strlen(Pkt.szUuid) + 1); +} + +/** + * Verifies and acknowledges a "BYE" request. + * + * @returns IPRT status code. + * @param pPktHdr The howdy packet. + */ +static int txsDoBye(PCTXSPKTHDR pPktHdr) +{ + int rc; + if (pPktHdr->cb == sizeof(TXSPKTHDR)) + rc = txsReplyAck(pPktHdr); + else + rc = txsReplyBadSize(pPktHdr, sizeof(TXSPKTHDR)); + g_pTransport->pfnNotifyBye(); + return rc; +} + +/** + * Verifies and acknowledges a "HOWDY" request. + * + * @returns IPRT status code. + * @param pPktHdr The howdy packet. + */ +static int txsDoHowdy(PCTXSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(TXSPKTHDR)) + return txsReplyBadSize(pPktHdr, sizeof(TXSPKTHDR)); + int rc = txsReplyAck(pPktHdr); + if (RT_SUCCESS(rc)) + { + g_pTransport->pfnNotifyHowdy(); + RTDirRemoveRecursive(g_szScratchPath, RTDIRRMREC_F_CONTENT_ONLY); + } + return rc; +} + +/** + * Replies according to the return code. + * + * @returns rcOperation and pTxsExec->rcReplySend. + * @param pTxsExec The TXSEXEC instance. + * @param rcOperation The status code to report. + * @param pszOperationFmt The operation that failed. Typically giving the + * function call with important arguments. + * @param ... Arguments to the format string. + */ +static int txsExecReplyRC(PTXSEXEC pTxsExec, int rcOperation, const char *pszOperationFmt, ...) +{ + AssertStmt(RT_FAILURE_NP(rcOperation), rcOperation = VERR_IPE_UNEXPECTED_INFO_STATUS); + + char szOperation[128]; + va_list va; + va_start(va, pszOperationFmt); + RTStrPrintfV(szOperation, sizeof(szOperation), pszOperationFmt, va); + va_end(va); + + pTxsExec->rcReplySend = txsReplyFailure(pTxsExec->pPktHdr, "FAILED ", + "%s failed with rc=%Rrc (opcode '%.8s')", + szOperation, rcOperation, pTxsExec->pPktHdr->achOpcode); + return rcOperation; +} + + +/** + * Sends the process exit status reply to the TXS client. + * + * @returns IPRT status code of the send. + * @param pTxsExec The TXSEXEC instance. + * @param fProcessAlive Whether the process is still alive (against our + * will). + * @param fProcessTimedOut Whether the process timed out. + * @param MsProcessKilled When the process was killed, UINT64_MAX if not. + */ +static int txsExecSendExitStatus(PTXSEXEC pTxsExec, bool fProcessAlive, bool fProcessTimedOut, uint64_t MsProcessKilled) +{ + int rc; + if ( fProcessTimedOut && !fProcessAlive && MsProcessKilled != UINT64_MAX) + { + rc = txsReplySimple(pTxsExec->pPktHdr, "PROC TOK"); + if (g_fDisplayOutput) + RTPrintf("txs: Process timed out and was killed\n"); + } + else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX) + { + rc = txsReplySimple(pTxsExec->pPktHdr, "PROC TOA"); + if (g_fDisplayOutput) + RTPrintf("txs: Process timed out and was not killed successfully\n"); + } + else if (g_fTerminate && (fProcessAlive || MsProcessKilled != UINT64_MAX)) + rc = txsReplySimple(pTxsExec->pPktHdr, "PROC DWN"); + else if (fProcessAlive) + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC DOO", "Doofus! process is alive when it should not"); + AssertFailed(); + } + else if (MsProcessKilled != UINT64_MAX) + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC DOO", "Doofus! process has been killed when it should not"); + AssertFailed(); + } + else if ( pTxsExec->ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL + && pTxsExec->ProcessStatus.iStatus == 0) + { + rc = txsReplySimple(pTxsExec->pPktHdr, "PROC OK "); + if (g_fDisplayOutput) + RTPrintf("txs: Process exited with status: 0\n"); + } + else if (pTxsExec->ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL) + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC NOK", "%d", pTxsExec->ProcessStatus.iStatus); + if (g_fDisplayOutput) + RTPrintf("txs: Process exited with status: %d\n", pTxsExec->ProcessStatus.iStatus); + } + else if (pTxsExec->ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL) + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC SIG", "%d", pTxsExec->ProcessStatus.iStatus); + if (g_fDisplayOutput) + RTPrintf("txs: Process exited with status: signal %d\n", pTxsExec->ProcessStatus.iStatus); + } + else if (pTxsExec->ProcessStatus.enmReason == RTPROCEXITREASON_ABEND) + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC ABD", ""); + if (g_fDisplayOutput) + RTPrintf("txs: Process exited with status: abend\n"); + } + else + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC DOO", "enmReason=%d iStatus=%d", + pTxsExec->ProcessStatus.enmReason, pTxsExec->ProcessStatus.iStatus); + AssertMsgFailed(("enmReason=%d iStatus=%d", pTxsExec->ProcessStatus.enmReason, pTxsExec->ProcessStatus.iStatus)); + } + return rc; +} + +/** + * Handle pending output data or error on standard out, standard error or the + * test pipe. + * + * @returns IPRT status code from client send. + * @param hPollSet The polling set. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phPipeR The pipe handle. + * @param puCrc32 The current CRC-32 of the stream. (In/Out) + * @param enmHndId The handle ID. + * @param pszOpcode The opcode for the data upload. + * + * @todo Put the last 4 parameters into a struct! + */ +static int txsDoExecHlpHandleOutputEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phPipeR, + uint32_t *puCrc32, TXSEXECHNDID enmHndId, const char *pszOpcode) +{ + Log(("txsDoExecHlpHandleOutputEvent: %s fPollEvt=%#x\n", pszOpcode, fPollEvt)); + + /* + * Try drain the pipe before acting on any errors. + */ + int rc = VINF_SUCCESS; + struct + { + TXSPKTHDR Hdr; + uint32_t uCrc32; + char abBuf[_64K]; + char abPadding[TXSPKT_ALIGNMENT]; + } Pkt; + size_t cbRead; + int rc2 = RTPipeRead(*phPipeR, Pkt.abBuf, sizeof(Pkt.abBuf), &cbRead); + if (RT_SUCCESS(rc2) && cbRead) + { + Log(("Crc32=%#x ", *puCrc32)); + *puCrc32 = RTCrc32Process(*puCrc32, Pkt.abBuf, cbRead); + Log(("cbRead=%#x Crc32=%#x \n", cbRead, *puCrc32)); + Pkt.uCrc32 = RTCrc32Finish(*puCrc32); + if (g_fDisplayOutput) + { + if (enmHndId == TXSEXECHNDID_STDOUT) + RTStrmPrintf(g_pStdErr, "%.*s", cbRead, Pkt.abBuf); + else if (enmHndId == TXSEXECHNDID_STDERR) + RTStrmPrintf(g_pStdErr, "%.*s", cbRead, Pkt.abBuf); + } + + rc = txsReplyInternal(&Pkt.Hdr, pszOpcode, cbRead + sizeof(uint32_t)); + + /* Make sure we go another poll round in case there was too much data + for the buffer to hold. */ + fPollEvt &= RTPOLL_EVT_ERROR; + } + else if (RT_FAILURE(rc2)) + { + fPollEvt |= RTPOLL_EVT_ERROR; + AssertMsg(rc2 == VERR_BROKEN_PIPE, ("%Rrc\n", rc)); + } + + /* + * If an error was raised signalled, + */ + if (fPollEvt & RTPOLL_EVT_ERROR) + { + rc2 = RTPollSetRemove(hPollSet, enmHndId); + AssertRC(rc2); + + rc2 = RTPipeClose(*phPipeR); + AssertRC(rc2); + *phPipeR = NIL_RTPIPE; + } + return rc; +} + +/** + * Try write some more data to the standard input of the child. + * + * @returns IPRT status code. + * @param pStdInBuf The standard input buffer. + * @param hStdInW The standard input pipe. + */ +static int txsDoExecHlpWriteStdIn(PTXSEXECSTDINBUF pStdInBuf, RTPIPE hStdInW) +{ + size_t cbToWrite = pStdInBuf->cb - pStdInBuf->off; + size_t cbWritten; + int rc = RTPipeWrite(hStdInW, &pStdInBuf->pch[pStdInBuf->off], cbToWrite, &cbWritten); + if (RT_SUCCESS(rc)) + { + Assert(cbWritten == cbToWrite); + pStdInBuf->off += cbWritten; + } + return rc; +} + +/** + * Handle an error event on standard input. + * + * @param hPollSet The polling set. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phStdInW The standard input pipe handle. + * @param pStdInBuf The standard input buffer. + */ +static void txsDoExecHlpHandleStdInErrorEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW, + PTXSEXECSTDINBUF pStdInBuf) +{ + NOREF(fPollEvt); + int rc2; + if (pStdInBuf->off < pStdInBuf->cb) + { + rc2 = RTPollSetRemove(hPollSet, TXSEXECHNDID_STDIN_WRITABLE); + AssertRC(rc2); + } + + rc2 = RTPollSetRemove(hPollSet, TXSEXECHNDID_STDIN); + AssertRC(rc2); + + rc2 = RTPipeClose(*phStdInW); + AssertRC(rc2); + *phStdInW = NIL_RTPIPE; + + RTMemFree(pStdInBuf->pch); + pStdInBuf->pch = NULL; + pStdInBuf->off = 0; + pStdInBuf->cb = 0; + pStdInBuf->cbAllocated = 0; + pStdInBuf->fBitBucket = true; +} + +/** + * Handle an event indicating we can write to the standard input pipe of the + * child process. + * + * @param hPollSet The polling set. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phStdInW The standard input pipe. + * @param pStdInBuf The standard input buffer. + */ +static void txsDoExecHlpHandleStdInWritableEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW, + PTXSEXECSTDINBUF pStdInBuf) +{ + int rc; + if (!(fPollEvt & RTPOLL_EVT_ERROR)) + { + rc = txsDoExecHlpWriteStdIn(pStdInBuf, *phStdInW); + if (RT_FAILURE(rc) && rc != VERR_BAD_PIPE) + { + /** @todo do we need to do something about this error condition? */ + AssertRC(rc); + } + + if (pStdInBuf->off < pStdInBuf->cb) + { + rc = RTPollSetRemove(hPollSet, TXSEXECHNDID_STDIN_WRITABLE); + AssertRC(rc); + } + } + else + txsDoExecHlpHandleStdInErrorEvent(hPollSet, fPollEvt, phStdInW, pStdInBuf); +} + +/** + * Handle a transport event or successful pfnPollIn() call. + * + * @returns IPRT status code from client send. + * @retval VINF_EOF indicates ABORT command. + * + * @param hPollSet The polling set. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param idPollHnd The handle ID. + * @param phStdInW The standard input pipe. + * @param pStdInBuf The standard input buffer. + */ +static int txsDoExecHlpHandleTransportEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, uint32_t idPollHnd, + PRTPIPE phStdInW, PTXSEXECSTDINBUF pStdInBuf) +{ + /* ASSUMES the transport layer will detect or clear any error condition. */ + NOREF(fPollEvt); NOREF(idPollHnd); + Log(("txsDoExecHlpHandleTransportEvent\n")); + /** @todo Use a callback for this case? */ + + /* + * Read client command packet and process it. + */ + /** @todo Sometimes this hangs on windows because there isn't any data pending. + * We probably get woken up at the wrong time or in the wrong way, i.e. RTPoll() + * is busted for sockets. + * + * Temporary workaround: Poll for input before trying to read it. */ + if (!g_pTransport->pfnPollIn()) + { + Log(("Bad transport event\n")); + RTThreadYield(); + return VINF_SUCCESS; + } + PTXSPKTHDR pPktHdr; + int rc = txsRecvPkt(&pPktHdr, false /*fAutoRetryOnFailure*/); + if (RT_FAILURE(rc)) + return rc; + Log(("Bad transport event\n")); + + /* + * The most common thing here would be a STDIN request with data + * for the child process. + */ + if (txsIsSameOpcode(pPktHdr, "STDIN")) + { + if ( !pStdInBuf->fBitBucket + && pPktHdr->cb >= sizeof(TXSPKTHDR) + sizeof(uint32_t)) + { + uint32_t uCrc32 = *(uint32_t *)(pPktHdr + 1); + const char *pch = (const char *)(pPktHdr + 1) + sizeof(uint32_t); + size_t cb = pPktHdr->cb - sizeof(TXSPKTHDR) - sizeof(uint32_t); + + /* Check the CRC */ + pStdInBuf->uCrc32 = RTCrc32Process(pStdInBuf->uCrc32, pch, cb); + if (RTCrc32Finish(pStdInBuf->uCrc32) == uCrc32) + { + + /* Rewind the buffer if it's empty. */ + size_t cbInBuf = pStdInBuf->cb - pStdInBuf->off; + bool const fAddToSet = cbInBuf == 0; + if (fAddToSet) + pStdInBuf->cb = pStdInBuf->off = 0; + + /* Try and see if we can simply append the data. */ + if (cb + pStdInBuf->cb <= pStdInBuf->cbAllocated) + { + memcpy(&pStdInBuf->pch[pStdInBuf->cb], pch, cb); + pStdInBuf->cb += cb; + rc = txsReplyAck(pPktHdr); + } + else + { + /* Try write a bit or two before we move+realloc the buffer. */ + if (cbInBuf > 0) + txsDoExecHlpWriteStdIn(pStdInBuf, *phStdInW); + + /* Move any buffered data to the front. */ + cbInBuf = pStdInBuf->cb - pStdInBuf->off; + if (cbInBuf == 0) + pStdInBuf->cb = pStdInBuf->off = 0; + else + { + memmove(pStdInBuf->pch, &pStdInBuf->pch[pStdInBuf->off], cbInBuf); + pStdInBuf->cb = cbInBuf; + pStdInBuf->off = 0; + } + + /* Do we need to grow the buffer? */ + if (cb + pStdInBuf->cb > pStdInBuf->cbAllocated) + { + size_t cbAlloc = pStdInBuf->cb + cb; + cbAlloc = RT_ALIGN_Z(cbAlloc, _64K); + void *pvNew = RTMemRealloc(pStdInBuf->pch, cbAlloc); + if (pvNew) + { + pStdInBuf->pch = (char *)pvNew; + pStdInBuf->cbAllocated = cbAlloc; + } + } + + /* Finally, copy the data. */ + if (cb + pStdInBuf->cb <= pStdInBuf->cbAllocated) + { + memcpy(&pStdInBuf->pch[pStdInBuf->cb], pch, cb); + pStdInBuf->cb += cb; + rc = txsReplyAck(pPktHdr); + } + else + rc = txsReplySimple(pPktHdr, "STDINMEM"); + } + + /* + * Flush the buffered data and add/remove the standard input + * handle from the set. + */ + txsDoExecHlpWriteStdIn(pStdInBuf, *phStdInW); + if (fAddToSet && pStdInBuf->off < pStdInBuf->cb) + { + int rc2 = RTPollSetAddPipe(hPollSet, *phStdInW, RTPOLL_EVT_WRITE, TXSEXECHNDID_STDIN_WRITABLE); + AssertRC(rc2); + } + else if (!fAddToSet && pStdInBuf->off >= pStdInBuf->cb) + { + int rc2 = RTPollSetRemove(hPollSet, TXSEXECHNDID_STDIN_WRITABLE); + AssertRC(rc2); + } + } + else + rc = txsReplyFailure(pPktHdr, "STDINCRC", "Invalid CRC checksum expected %#x got %#x", + pStdInBuf->uCrc32, uCrc32); + } + else if (pPktHdr->cb < sizeof(TXSPKTHDR) + sizeof(uint32_t)) + rc = txsReplySimple(pPktHdr, "STDINBAD"); + else + rc = txsReplySimple(pPktHdr, "STDINIGN"); + } + /* + * Marks the end of the stream for stdin. + */ + else if (txsIsSameOpcode(pPktHdr, "STDINEOS")) + { + if (RT_LIKELY(pPktHdr->cb == sizeof(TXSPKTHDR))) + { + /* Close the pipe. */ + txsDoExecHlpHandleStdInErrorEvent(hPollSet, fPollEvt, phStdInW, pStdInBuf); + rc = txsReplyAck(pPktHdr); + } + else + rc = txsReplySimple(pPktHdr, "STDINBAD"); + } + /* + * The only other two requests are connection oriented and we return a error + * code so that we unwind the whole EXEC shebang and start afresh. + */ + else if (txsIsSameOpcode(pPktHdr, "BYE")) + { + rc = txsDoBye(pPktHdr); + if (RT_SUCCESS(rc)) + rc = VERR_NET_NOT_CONNECTED; + } + else if (txsIsSameOpcode(pPktHdr, "HOWDY")) + { + rc = txsDoHowdy(pPktHdr); + if (RT_SUCCESS(rc)) + rc = VERR_NET_NOT_CONNECTED; + } + else if (txsIsSameOpcode(pPktHdr, "ABORT")) + { + rc = txsReplyAck(pPktHdr); + if (RT_SUCCESS(rc)) + rc = VINF_EOF; /* this is but ugly! */ + } + else + rc = txsReplyFailure(pPktHdr, "UNKNOWN ", "Opcode '%.8s' is not known or not recognized during EXEC", pPktHdr->achOpcode); + + RTMemFree(pPktHdr); + return rc; +} + +/** + * Handles the output and input of the process, waits for it finish up. + * + * @returns IPRT status code from reply send. + * @param pTxsExec The TXSEXEC instance. + */ +static int txsDoExecHlp2(PTXSEXEC pTxsExec) +{ + int rc; /* client send. */ + int rc2; + TXSEXECSTDINBUF StdInBuf = { 0, 0, NULL, 0, pTxsExec->hStdInW == NIL_RTPIPE, RTCrc32Start() }; + uint32_t uStdOutCrc32 = RTCrc32Start(); + uint32_t uStdErrCrc32 = uStdOutCrc32; + uint32_t uTestPipeCrc32 = uStdOutCrc32; + uint64_t const MsStart = RTTimeMilliTS(); + bool fProcessTimedOut = false; + uint64_t MsProcessKilled = UINT64_MAX; + RTMSINTERVAL const cMsPollBase = g_pTransport->pfnPollSetAdd || pTxsExec->hStdInW == NIL_RTPIPE + ? 5000 : 100; + RTMSINTERVAL cMsPollCur = 0; + + /* + * Before entering the loop, tell the client that we've started the guest + * and that it's now OK to send input to the process. (This is not the + * final ACK, so the packet header is NULL ... kind of bogus.) + */ + rc = txsReplyAck(NULL); + + /* + * Process input, output, the test pipe and client requests. + */ + while ( RT_SUCCESS(rc) + && RT_UNLIKELY(!g_fTerminate)) + { + /* + * Wait/Process all pending events. + */ + uint32_t idPollHnd; + uint32_t fPollEvt; + Log3(("Calling RTPollNoResume(,%u,)...\n", cMsPollCur)); + rc2 = RTPollNoResume(pTxsExec->hPollSet, cMsPollCur, &fPollEvt, &idPollHnd); + Log3(("RTPollNoResume -> fPollEvt=%#x idPollHnd=%u\n", fPollEvt, idPollHnd)); + if (g_fTerminate) + continue; + cMsPollCur = 0; /* no rest until we've checked everything. */ + + if (RT_SUCCESS(rc2)) + { + switch (idPollHnd) + { + case TXSEXECHNDID_STDOUT: + rc = txsDoExecHlpHandleOutputEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hStdOutR, &uStdOutCrc32, + TXSEXECHNDID_STDOUT, "STDOUT "); + break; + + case TXSEXECHNDID_STDERR: + rc = txsDoExecHlpHandleOutputEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hStdErrR, &uStdErrCrc32, + TXSEXECHNDID_STDERR, "STDERR "); + break; + + case TXSEXECHNDID_TESTPIPE: + rc = txsDoExecHlpHandleOutputEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hTestPipeR, &uTestPipeCrc32, + TXSEXECHNDID_TESTPIPE, "TESTPIPE"); + break; + + case TXSEXECHNDID_STDIN: + txsDoExecHlpHandleStdInErrorEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hStdInW, &StdInBuf); + break; + + case TXSEXECHNDID_STDIN_WRITABLE: + txsDoExecHlpHandleStdInWritableEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hStdInW, &StdInBuf); + break; + + case TXSEXECHNDID_THREAD: + rc2 = RTPollSetRemove(pTxsExec->hPollSet, TXSEXECHNDID_THREAD); AssertRC(rc2); + break; + + default: + rc = txsDoExecHlpHandleTransportEvent(pTxsExec->hPollSet, fPollEvt, idPollHnd, &pTxsExec->hStdInW, + &StdInBuf); + break; + } + if (RT_FAILURE(rc) || rc == VINF_EOF) + break; /* abort command, or client dead or something */ + continue; + } + + /* + * Check for incoming data. + */ + if (g_pTransport->pfnPollIn()) + { + rc = txsDoExecHlpHandleTransportEvent(pTxsExec->hPollSet, 0, UINT32_MAX, &pTxsExec->hStdInW, &StdInBuf); + if (RT_FAILURE(rc) || rc == VINF_EOF) + break; /* abort command, or client dead or something */ + continue; + } + + /* + * If the process has terminated, we're should head out. + */ + if (!ASMAtomicReadBool(&pTxsExec->fProcessAlive)) + break; + + /* + * Check for timed out, killing the process. + */ + uint32_t cMilliesLeft = RT_INDEFINITE_WAIT; + if (pTxsExec->cMsTimeout != RT_INDEFINITE_WAIT) + { + uint64_t u64Now = RTTimeMilliTS(); + uint64_t cMsElapsed = u64Now - MsStart; + if (cMsElapsed >= pTxsExec->cMsTimeout) + { + fProcessTimedOut = true; + if ( MsProcessKilled == UINT64_MAX + || u64Now - MsProcessKilled > 1000) + { + if (u64Now - MsProcessKilled > 20*60*1000) + break; /* give up after 20 mins */ + RTCritSectEnter(&pTxsExec->CritSect); + if (pTxsExec->fProcessAlive) + RTProcTerminate(pTxsExec->hProcess); + RTCritSectLeave(&pTxsExec->CritSect); + MsProcessKilled = u64Now; + continue; + } + cMilliesLeft = 10000; + } + else + cMilliesLeft = pTxsExec->cMsTimeout - (uint32_t)cMsElapsed; + } + + /* Reset the polling interval since we've done all pending work. */ + cMsPollCur = cMilliesLeft >= cMsPollBase ? cMsPollBase : cMilliesLeft; + } + + /* + * At this point we should hopefully only have to wait 0 ms on the thread + * to release the handle... But if for instance the process refuses to die, + * we'll have to try kill it again. Bothersome. + */ + for (size_t i = 0; i < 22; i++) + { + rc2 = RTThreadWait(pTxsExec->hThreadWaiter, 500, NULL); + if (RT_SUCCESS(rc)) + { + pTxsExec->hThreadWaiter = NIL_RTTHREAD; + Assert(!pTxsExec->fProcessAlive); + break; + } + if (i == 0 || i == 10 || i == 15 || i == 18 || i > 20) + { + RTCritSectEnter(&pTxsExec->CritSect); + if (pTxsExec->fProcessAlive) + RTProcTerminate(pTxsExec->hProcess); + RTCritSectLeave(&pTxsExec->CritSect); + } + } + + /* + * If we don't have a client problem (RT_FAILURE(rc) we'll reply to the + * clients exec packet now. + */ + if (RT_SUCCESS(rc)) + rc = txsExecSendExitStatus(pTxsExec, pTxsExec->fProcessAlive, fProcessTimedOut, MsProcessKilled); + + RTMemFree(StdInBuf.pch); + return rc; +} + +/** + * Creates a poll set for the pipes and let the transport layer add stuff to it + * as well. + * + * @returns IPRT status code, reply to client made on error. + * @param pTxsExec The TXSEXEC instance. + */ +static int txsExecSetupPollSet(PTXSEXEC pTxsExec) +{ + int rc = RTPollSetCreate(&pTxsExec->hPollSet); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetCreate"); + + rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hStdInW, RTPOLL_EVT_ERROR, TXSEXECHNDID_STDIN); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetAddPipe/stdin"); + + rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + TXSEXECHNDID_STDOUT); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetAddPipe/stdout"); + + rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + TXSEXECHNDID_STDERR); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetAddPipe/stderr"); + + rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hTestPipeR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + TXSEXECHNDID_TESTPIPE); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetAddPipe/test"); + + rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hWakeUpPipeR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + TXSEXECHNDID_THREAD); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetAddPipe/wakeup"); + + if (g_pTransport->pfnPollSetAdd) + { + rc = g_pTransport->pfnPollSetAdd(pTxsExec->hPollSet, TXSEXECHNDID_TRANSPORT); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "%s->pfnPollSetAdd/stdin", g_pTransport->szName); + } + + return VINF_SUCCESS; +} + +/** + * Thread that calls RTProcWait and signals the main thread when it returns. + * + * The thread is created before the process is started and is waiting for a user + * signal from the main thread before it calls RTProcWait. + * + * @returns VINF_SUCCESS (ignored). + * @param hThreadSelf The thread handle. + * @param pvUser The TXEEXEC structure. + */ +static DECLCALLBACK(int) txsExecWaitThreadProc(RTTHREAD hThreadSelf, void *pvUser) +{ + PTXSEXEC pTxsExec = (PTXSEXEC)pvUser; + + /* Wait for the go ahead... */ + int rc = RTThreadUserWait(hThreadSelf, RT_INDEFINITE_WAIT); AssertRC(rc); + + RTCritSectEnter(&pTxsExec->CritSect); + for (;;) + { + RTCritSectLeave(&pTxsExec->CritSect); + rc = RTProcWaitNoResume(pTxsExec->hProcess, RTPROCWAIT_FLAGS_BLOCK, &pTxsExec->ProcessStatus); + RTCritSectEnter(&pTxsExec->CritSect); + + /* If the pipe is NIL, the destructor wants us to get lost ASAP. */ + if (pTxsExec->hWakeUpPipeW == NIL_RTPIPE) + break; + + if (RT_FAILURE(rc)) + { + rc = RTProcWait(pTxsExec->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &pTxsExec->ProcessStatus); + if (rc == VERR_PROCESS_RUNNING) + continue; + + if (RT_FAILURE(rc)) + { + AssertRC(rc); + pTxsExec->ProcessStatus.iStatus = rc; + pTxsExec->ProcessStatus.enmReason = RTPROCEXITREASON_ABEND; + } + } + + /* The process finished, signal the main thread over the pipe. */ + ASMAtomicWriteBool(&pTxsExec->fProcessAlive, false); + size_t cbIgnored; + RTPipeWrite(pTxsExec->hWakeUpPipeW, "done", 4, &cbIgnored); + RTPipeClose(pTxsExec->hWakeUpPipeW); + pTxsExec->hWakeUpPipeW = NIL_RTPIPE; + break; + } + RTCritSectLeave(&pTxsExec->CritSect); + + return VINF_SUCCESS; +} + +/** + * Sets up the thread that waits for the process to complete. + * + * @returns IPRT status code, reply to client made on error. + * @param pTxsExec The TXSEXEC instance. + */ +static int txsExecSetupThread(PTXSEXEC pTxsExec) +{ + int rc = RTPipeCreate(&pTxsExec->hWakeUpPipeR, &pTxsExec->hWakeUpPipeW, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + { + pTxsExec->hWakeUpPipeR = pTxsExec->hWakeUpPipeW = NIL_RTPIPE; + return txsExecReplyRC(pTxsExec, rc, "RTPipeCreate/wait"); + } + + rc = RTThreadCreate(&pTxsExec->hThreadWaiter, txsExecWaitThreadProc, + pTxsExec, 0 /*cbStack */, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "TxsProcW"); + if (RT_FAILURE(rc)) + { + pTxsExec->hThreadWaiter = NIL_RTTHREAD; + return txsExecReplyRC(pTxsExec, rc, "RTThreadCreate"); + } + + return VINF_SUCCESS; +} + +/** + * Sets up the test pipe. + * + * @returns IPRT status code, reply to client made on error. + * @param pTxsExec The TXSEXEC instance. + * @param pszTestPipe How to set up the test pipe. + */ +static int txsExecSetupTestPipe(PTXSEXEC pTxsExec, const char *pszTestPipe) +{ + if (strcmp(pszTestPipe, "|")) + return VINF_SUCCESS; + + int rc = RTPipeCreate(&pTxsExec->hTestPipeR, &pTxsExec->hTestPipeW, RTPIPE_C_INHERIT_WRITE); + if (RT_FAILURE(rc)) + { + pTxsExec->hTestPipeR = pTxsExec->hTestPipeW = NIL_RTPIPE; + return txsExecReplyRC(pTxsExec, rc, "RTPipeCreate/test/%s", pszTestPipe); + } + + char szVal[64]; + RTStrPrintf(szVal, sizeof(szVal), "%#llx", (uint64_t)RTPipeToNative(pTxsExec->hTestPipeW)); + rc = RTEnvSetEx(pTxsExec->hEnv, "IPRT_TEST_PIPE", szVal); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTEnvSetEx/test/%s", pszTestPipe); + + return VINF_SUCCESS; +} + +/** + * Sets up the redirection / pipe / nothing for one of the standard handles. + * + * @returns IPRT status code, reply to client made on error. + * @param pTxsExec The TXSEXEC instance. + * @param pszHowTo How to set up this standard handle. + * @param pszStdWhat For what to setup redirection (stdin/stdout/stderr). + * @param fd Which standard handle it is (0 == stdin, 1 == + * stdout, 2 == stderr). + * @param ph The generic handle that @a pph may be set + * pointing to. Always set. + * @param pph Pointer to the RTProcCreateExec argument. + * Always set. + * @param phPipe Where to return the end of the pipe that we + * should service. Always set. + */ +static int txsExecSetupRedir(PTXSEXEC pTxsExec, const char *pszHowTo, const char *pszStdWhat, int fd, PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe) +{ + ph->enmType = RTHANDLETYPE_PIPE; + ph->u.hPipe = NIL_RTPIPE; + *pph = NULL; + *phPipe = NIL_RTPIPE; + + int rc; + if (!strcmp(pszHowTo, "|")) + { + /* + * Setup a pipe for forwarding to/from the client. + */ + if (fd == 0) + rc = RTPipeCreate(&ph->u.hPipe, phPipe, RTPIPE_C_INHERIT_READ); + else + rc = RTPipeCreate(phPipe, &ph->u.hPipe, RTPIPE_C_INHERIT_WRITE); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPipeCreate/%s/%s", pszStdWhat, pszHowTo); + ph->enmType = RTHANDLETYPE_PIPE; + *pph = ph; + } + else if (!strcmp(pszHowTo, "/dev/null")) + { + /* + * Redirect to/from /dev/null. + */ + RTFILE hFile; + rc = RTFileOpenBitBucket(&hFile, fd == 0 ? RTFILE_O_READ : RTFILE_O_WRITE); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTFileOpenBitBucket/%s/%s", pszStdWhat, pszHowTo); + + ph->enmType = RTHANDLETYPE_FILE; + ph->u.hFile = hFile; + *pph = ph; + } + else if (*pszHowTo) + { + /* + * Redirect to/from file. + */ + uint32_t fFlags; + if (fd == 0) + fFlags = RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN; + else + { + if (pszHowTo[0] != '>' || pszHowTo[1] != '>') + fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE; + else + { + /* append */ + pszHowTo += 2; + fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND; + } + } + + RTFILE hFile; + rc = RTFileOpen(&hFile, pszHowTo, fFlags); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTFileOpen/%s/%s", pszStdWhat, pszHowTo); + + ph->enmType = RTHANDLETYPE_FILE; + ph->u.hFile = hFile; + *pph = ph; + } + else + /* same as parent (us) */ + rc = VINF_SUCCESS; + return rc; +} + +/** + * Create the environment. + * + * @returns IPRT status code, reply to client made on error. + * @param pTxsExec The TXSEXEC instance. + * @param cEnvVars The number of environment variables. + * @param papszEnv The environment variables (var=value). + */ +static int txsExecSetupEnv(PTXSEXEC pTxsExec, uint32_t cEnvVars, const char * const *papszEnv) +{ + /* + * Create the environment. + */ + int rc = RTEnvClone(&pTxsExec->hEnv, RTENV_DEFAULT); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTEnvClone"); + + for (size_t i = 0; i < cEnvVars; i++) + { + rc = RTEnvPutEx(pTxsExec->hEnv, papszEnv[i]); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTEnvPutEx(,'%s')", papszEnv[i]); + } + return VINF_SUCCESS; +} + +/** + * Deletes the TXSEXEC structure and frees the memory backing it. + * + * @param pTxsExec The structure to destroy. + */ +static void txsExecDestroy(PTXSEXEC pTxsExec) +{ + int rc2; + + rc2 = RTEnvDestroy(pTxsExec->hEnv); AssertRC(rc2); + pTxsExec->hEnv = NIL_RTENV; + rc2 = RTPipeClose(pTxsExec->hTestPipeW); AssertRC(rc2); + pTxsExec->hTestPipeW = NIL_RTPIPE; + rc2 = RTHandleClose(pTxsExec->StdErr.phChild); AssertRC(rc2); + pTxsExec->StdErr.phChild = NULL; + rc2 = RTHandleClose(pTxsExec->StdOut.phChild); AssertRC(rc2); + pTxsExec->StdOut.phChild = NULL; + rc2 = RTHandleClose(pTxsExec->StdIn.phChild); AssertRC(rc2); + pTxsExec->StdIn.phChild = NULL; + + rc2 = RTPipeClose(pTxsExec->hTestPipeR); AssertRC(rc2); + pTxsExec->hTestPipeR = NIL_RTPIPE; + rc2 = RTPipeClose(pTxsExec->hStdErrR); AssertRC(rc2); + pTxsExec->hStdErrR = NIL_RTPIPE; + rc2 = RTPipeClose(pTxsExec->hStdOutR); AssertRC(rc2); + pTxsExec->hStdOutR = NIL_RTPIPE; + rc2 = RTPipeClose(pTxsExec->hStdInW); AssertRC(rc2); + pTxsExec->hStdInW = NIL_RTPIPE; + + RTPollSetDestroy(pTxsExec->hPollSet); + pTxsExec->hPollSet = NIL_RTPOLLSET; + + /* + * If the process is still running we're in a bit of a fix... Try kill it, + * although that's potentially racing process termination and reusage of + * the pid. + */ + RTCritSectEnter(&pTxsExec->CritSect); + + RTPipeClose(pTxsExec->hWakeUpPipeW); + pTxsExec->hWakeUpPipeW = NIL_RTPIPE; + RTPipeClose(pTxsExec->hWakeUpPipeR); + pTxsExec->hWakeUpPipeR = NIL_RTPIPE; + + if ( pTxsExec->hProcess != NIL_RTPROCESS + && pTxsExec->fProcessAlive) + RTProcTerminate(pTxsExec->hProcess); + + RTCritSectLeave(&pTxsExec->CritSect); + + int rcThread = VINF_SUCCESS; + if (pTxsExec->hThreadWaiter != NIL_RTTHREAD) + rcThread = RTThreadWait(pTxsExec->hThreadWaiter, 5000, NULL); + if (RT_SUCCESS(rcThread)) + { + pTxsExec->hThreadWaiter = NIL_RTTHREAD; + RTCritSectDelete(&pTxsExec->CritSect); + RTMemFree(pTxsExec); + } + /* else: leak it or RTThreadWait may cause heap corruption later. */ +} + +/** + * Initializes the TXSEXEC structure. + * + * @returns VINF_SUCCESS and non-NULL *ppTxsExec on success, reply send status + * and *ppTxsExec set to NULL on failure. + * @param pPktHdr The exec packet. + * @param cMsTimeout The time parameter. + * @param ppTxsExec Where to return the structure. + */ +static int txsExecCreate(PCTXSPKTHDR pPktHdr, RTMSINTERVAL cMsTimeout, PTXSEXEC *ppTxsExec) +{ + *ppTxsExec = NULL; + + /* + * Allocate the basic resources. + */ + PTXSEXEC pTxsExec = (PTXSEXEC)RTMemAlloc(sizeof(*pTxsExec)); + if (!pTxsExec) + return txsReplyRC(pPktHdr, VERR_NO_MEMORY, "RTMemAlloc(%zu)", sizeof(*pTxsExec)); + int rc = RTCritSectInit(&pTxsExec->CritSect); + if (RT_FAILURE(rc)) + { + RTMemFree(pTxsExec); + return txsReplyRC(pPktHdr, rc, "RTCritSectInit"); + } + + /* + * Initialize the member to NIL values. + */ + pTxsExec->pPktHdr = pPktHdr; + pTxsExec->cMsTimeout = cMsTimeout; + pTxsExec->rcReplySend = VINF_SUCCESS; + + pTxsExec->hPollSet = NIL_RTPOLLSET; + pTxsExec->hStdInW = NIL_RTPIPE; + pTxsExec->hStdOutR = NIL_RTPIPE; + pTxsExec->hStdErrR = NIL_RTPIPE; + pTxsExec->hTestPipeR = NIL_RTPIPE; + pTxsExec->hWakeUpPipeR = NIL_RTPIPE; + pTxsExec->hThreadWaiter = NIL_RTTHREAD; + + pTxsExec->StdIn.phChild = NULL; + pTxsExec->StdOut.phChild = NULL; + pTxsExec->StdErr.phChild = NULL; + pTxsExec->hTestPipeW = NIL_RTPIPE; + pTxsExec->hEnv = NIL_RTENV; + + pTxsExec->hProcess = NIL_RTPROCESS; + pTxsExec->ProcessStatus.iStatus = 254; + pTxsExec->ProcessStatus.enmReason = RTPROCEXITREASON_ABEND; + pTxsExec->fProcessAlive = false; + pTxsExec->hWakeUpPipeW = NIL_RTPIPE; + + *ppTxsExec = pTxsExec; + return VINF_SUCCESS; +} + +/** + * txsDoExec helper that takes over when txsDoExec has expanded the packet. + * + * @returns IPRT status code from send. + * @param pPktHdr The exec packet. + * @param fFlags Flags, reserved for future use. + * @param pszExecName The executable name. + * @param cArgs The argument count. + * @param papszArgs The arguments. + * @param cEnvVars The environment variable count. + * @param papszEnv The environment variables. + * @param pszStdIn How to deal with standard in. + * @param pszStdOut How to deal with standard out. + * @param pszStdErr How to deal with standard err. + * @param pszTestPipe How to deal with the test pipe. + * @param pszUsername The user to run the program as. + * @param cMillies The process time limit in milliseconds. + */ +static int txsDoExecHlp(PCTXSPKTHDR pPktHdr, uint32_t fFlags, const char *pszExecName, + uint32_t cArgs, const char * const *papszArgs, + uint32_t cEnvVars, const char * const *papszEnv, + const char *pszStdIn, const char *pszStdOut, const char *pszStdErr, const char *pszTestPipe, + const char *pszUsername, RTMSINTERVAL cMillies) +{ + int rc2; + RT_NOREF_PV(fFlags); + + /* + * Input validation, filter out things we don't yet support.. + */ + Assert(!fFlags); + if (!*pszExecName) + return txsReplyFailure(pPktHdr, "STR ZERO", "Executable name is empty"); + if (!*pszStdIn) + return txsReplyFailure(pPktHdr, "STR ZERO", "The stdin howto is empty"); + if (!*pszStdOut) + return txsReplyFailure(pPktHdr, "STR ZERO", "The stdout howto is empty"); + if (!*pszStdErr) + return txsReplyFailure(pPktHdr, "STR ZERO", "The stderr howto is empty"); + if (!*pszTestPipe) + return txsReplyFailure(pPktHdr, "STR ZERO", "The testpipe howto is empty"); + if (strcmp(pszTestPipe, "|") && strcmp(pszTestPipe, "/dev/null")) + return txsReplyFailure(pPktHdr, "BAD TSTP", "Only \"|\" and \"/dev/null\" are allowed as testpipe howtos ('%s')", + pszTestPipe); + if (*pszUsername) + return txsReplyFailure(pPktHdr, "NOT IMPL", "Executing as a specific user is not implemented ('%s')", pszUsername); + + /* + * Prepare for process launch. + */ + PTXSEXEC pTxsExec; + int rc = txsExecCreate(pPktHdr, cMillies, &pTxsExec); + if (pTxsExec == NULL) + return rc; + rc = txsExecSetupEnv(pTxsExec, cEnvVars, papszEnv); + if (RT_SUCCESS(rc)) + rc = txsExecSetupRedir(pTxsExec, pszStdIn, "StdIn", 0, &pTxsExec->StdIn.hChild, &pTxsExec->StdIn.phChild, &pTxsExec->hStdInW); + if (RT_SUCCESS(rc)) + rc = txsExecSetupRedir(pTxsExec, pszStdOut, "StdOut", 1, &pTxsExec->StdOut.hChild, &pTxsExec->StdOut.phChild, &pTxsExec->hStdOutR); + if (RT_SUCCESS(rc)) + rc = txsExecSetupRedir(pTxsExec, pszStdErr, "StdErr", 2, &pTxsExec->StdErr.hChild, &pTxsExec->StdErr.phChild, &pTxsExec->hStdErrR); + if (RT_SUCCESS(rc)) + rc = txsExecSetupTestPipe(pTxsExec, pszTestPipe); + if (RT_SUCCESS(rc)) + rc = txsExecSetupThread(pTxsExec); + if (RT_SUCCESS(rc)) + rc = txsExecSetupPollSet(pTxsExec); + if (RT_SUCCESS(rc)) + { + /* + * Create the process. + */ + if (g_fDisplayOutput) + { + RTPrintf("txs: Executing \"%s\": ", pszExecName); + for (uint32_t i = 0; i < cArgs; i++) + RTPrintf(" \"%s\"", papszArgs[i]); + RTPrintf("\n"); + } + rc = RTProcCreateEx(pszExecName, papszArgs, pTxsExec->hEnv, 0 /*fFlags*/, + pTxsExec->StdIn.phChild, pTxsExec->StdOut.phChild, pTxsExec->StdErr.phChild, + *pszUsername ? pszUsername : NULL, NULL, + &pTxsExec->hProcess); + if (RT_SUCCESS(rc)) + { + ASMAtomicWriteBool(&pTxsExec->fProcessAlive, true); + rc2 = RTThreadUserSignal(pTxsExec->hThreadWaiter); AssertRC(rc2); + + /* + * Close the child ends of any pipes and redirected files. + */ + rc2 = RTHandleClose(pTxsExec->StdIn.phChild); AssertRC(rc2); + pTxsExec->StdIn.phChild = NULL; + rc2 = RTHandleClose(pTxsExec->StdOut.phChild); AssertRC(rc2); + pTxsExec->StdOut.phChild = NULL; + rc2 = RTHandleClose(pTxsExec->StdErr.phChild); AssertRC(rc2); + pTxsExec->StdErr.phChild = NULL; + rc2 = RTPipeClose(pTxsExec->hTestPipeW); AssertRC(rc2); + pTxsExec->hTestPipeW = NIL_RTPIPE; + + /* + * Let another worker function funnel output and input to the + * client as well as the process exit code. + */ + rc = txsDoExecHlp2(pTxsExec); + } + else + rc = txsReplyFailure(pPktHdr, "FAILED ", "Executing process \"%s\" failed with %Rrc", + pszExecName, rc); + } + else + rc = pTxsExec->rcReplySend; + txsExecDestroy(pTxsExec); + return rc; +} + +/** + * Execute a program. + * + * @returns IPRT status code from send. + * @param pPktHdr The exec packet. + */ +static int txsDoExec(PCTXSPKTHDR pPktHdr) +{ + /* + * This packet has a lot of parameters, most of which are zero terminated + * strings. The strings used in items 7 thru 10 are either file names, + * "/dev/null" or a pipe char (|). + * + * Packet content: + * 1. Flags reserved for future use (32-bit unsigned). + * 2. The executable name (string). + * 3. The argument count given as a 32-bit unsigned integer. + * 4. The arguments strings. + * 5. The number of environment strings (32-bit unsigned). + * 6. The environment strings (var=val) to apply the environment. + * 7. What to do about standard in (string). + * 8. What to do about standard out (string). + * 9. What to do about standard err (string). + * 10. What to do about the test pipe (string). + * 11. The name of the user to run the program as (string). Empty string + * means running it as the current user. + * 12. Process time limit in milliseconds (32-bit unsigned). Max == no limit. + */ + size_t const cbMin = sizeof(TXSPKTHDR) + + sizeof(uint32_t) /* flags */ + 2 + + sizeof(uint32_t) /* argc */ + 2 /* argv */ + + sizeof(uint32_t) + 0 /* environ */ + + 4 * 1 + + sizeof(uint32_t) /* timeout */; + if (pPktHdr->cb < cbMin) + return txsReplyBadMinSize(pPktHdr, cbMin); + + /* unpack the packet */ + char const *pchEnd = (char const *)pPktHdr + pPktHdr->cb; + char const *pch = (char const *)(pPktHdr + 1); /* cursor */ + + /* 1. flags */ + uint32_t const fFlags = *(uint32_t const *)pch; + pch += sizeof(uint32_t); + if (fFlags != 0) + return txsReplyFailure(pPktHdr, "BAD FLAG", "Invalid EXEC flags %#x, expected 0", fFlags); + + /* 2. exec name */ + int rc; + char *pszExecName = NULL; + if (!txsIsStringValid(pPktHdr, "execname", pch, &pszExecName, &pch, &rc)) + return rc; + + /* 3. argc */ + uint32_t const cArgs = (size_t)(pchEnd - pch) > sizeof(uint32_t) ? *(uint32_t const *)pch : 0xff; + pch += sizeof(uint32_t); + if (cArgs * 1 >= (size_t)(pchEnd - pch)) + rc = txsReplyFailure(pPktHdr, "BAD ARGC", "Bad or missing argument count (%#x)", cArgs); + else if (cArgs > 128) + rc = txsReplyFailure(pPktHdr, "BAD ARGC", "Too many arguments (%#x)", cArgs); + else + { + char **papszArgs = (char **)RTMemTmpAllocZ(sizeof(char *) * (cArgs + 1)); + if (papszArgs) + { + /* 4. argv */ + bool fOk = true; + for (size_t i = 0; i < cArgs && fOk; i++) + { + fOk = txsIsStringValid(pPktHdr, "argvN", pch, &papszArgs[i], &pch, &rc); + if (!fOk) + break; + } + if (fOk) + { + /* 5. cEnvVars */ + uint32_t const cEnvVars = (size_t)(pchEnd - pch) > sizeof(uint32_t) ? *(uint32_t const *)pch : 0xfff; + pch += sizeof(uint32_t); + if (cEnvVars * 1 >= (size_t)(pchEnd - pch)) + rc = txsReplyFailure(pPktHdr, "BAD ENVC", "Bad or missing environment variable count (%#x)", cEnvVars); + else if (cEnvVars > 256) + rc = txsReplyFailure(pPktHdr, "BAD ENVC", "Too many environment variables (%#x)", cEnvVars); + else + { + char **papszEnv = (char **)RTMemTmpAllocZ(sizeof(char *) * (cEnvVars + 1)); + if (papszEnv) + { + /* 6. environ */ + for (size_t i = 0; i < cEnvVars && fOk; i++) + { + fOk = txsIsStringValid(pPktHdr, "envN", pch, &papszEnv[i], &pch, &rc); + if (!fOk) /* Bail out on error. */ + break; + } + if (fOk) + { + /* 7. stdout */ + char *pszStdIn; + if (txsIsStringValid(pPktHdr, "stdin", pch, &pszStdIn, &pch, &rc)) + { + /* 8. stdout */ + char *pszStdOut; + if (txsIsStringValid(pPktHdr, "stdout", pch, &pszStdOut, &pch, &rc)) + { + /* 9. stderr */ + char *pszStdErr; + if (txsIsStringValid(pPktHdr, "stderr", pch, &pszStdErr, &pch, &rc)) + { + /* 10. testpipe */ + char *pszTestPipe; + if (txsIsStringValid(pPktHdr, "testpipe", pch, &pszTestPipe, &pch, &rc)) + { + /* 11. username */ + char *pszUsername; + if (txsIsStringValid(pPktHdr, "username", pch, &pszUsername, &pch, &rc)) + { + /** @todo No password value? */ + + /* 12. time limit */ + uint32_t const cMillies = (size_t)(pchEnd - pch) >= sizeof(uint32_t) + ? *(uint32_t const *)pch + : 0; + if ((size_t)(pchEnd - pch) > sizeof(uint32_t)) + rc = txsReplyFailure(pPktHdr, "BAD END ", "Timeout argument not at end of packet (%#x)", (size_t)(pchEnd - pch)); + else if ((size_t)(pchEnd - pch) < sizeof(uint32_t)) + rc = txsReplyFailure(pPktHdr, "BAD NOTO", "No timeout argument"); + else if (cMillies < 1000) + rc = txsReplyFailure(pPktHdr, "BAD TO ", "Timeout is less than a second (%#x)", cMillies); + else + { + pch += sizeof(uint32_t); + + /* + * Time to employ a helper here before we go way beyond + * the right margin... + */ + rc = txsDoExecHlp(pPktHdr, fFlags, pszExecName, + cArgs, papszArgs, + cEnvVars, papszEnv, + pszStdIn, pszStdOut, pszStdErr, pszTestPipe, + pszUsername, + cMillies == UINT32_MAX ? RT_INDEFINITE_WAIT : cMillies); + } + RTStrFree(pszUsername); + } + RTStrFree(pszTestPipe); + } + RTStrFree(pszStdErr); + } + RTStrFree(pszStdOut); + } + RTStrFree(pszStdIn); + } + } + for (size_t i = 0; i < cEnvVars; i++) + RTStrFree(papszEnv[i]); + RTMemTmpFree(papszEnv); + } + else + rc = txsReplyFailure(pPktHdr, "NO MEM ", "Failed to allocate %zu bytes environ", sizeof(char *) * (cEnvVars + 1)); + } + } + for (size_t i = 0; i < cArgs; i++) + RTStrFree(papszArgs[i]); + RTMemTmpFree(papszArgs); + } + else + rc = txsReplyFailure(pPktHdr, "NO MEM ", "Failed to allocate %zu bytes for argv", sizeof(char *) * (cArgs + 1)); + } + RTStrFree(pszExecName); + + return rc; +} + +/** + * The main loop. + * + * @returns exit code. + */ +static RTEXITCODE txsMainLoop(void) +{ + if (g_cVerbose > 0) + RTMsgInfo("txsMainLoop: start...\n"); + RTEXITCODE enmExitCode = RTEXITCODE_SUCCESS; + while (!g_fTerminate) + { + /* + * Read client command packet and process it. + */ + PTXSPKTHDR pPktHdr; + int rc = txsRecvPkt(&pPktHdr, true /*fAutoRetryOnFailure*/); + if (RT_FAILURE(rc)) + continue; + if (g_cVerbose > 0) + RTMsgInfo("txsMainLoop: CMD: %.8s...", pPktHdr->achOpcode); + + /* + * Do a string switch on the opcode bit. + */ + /* Connection: */ + if ( txsIsSameOpcode(pPktHdr, "HOWDY ")) + rc = txsDoHowdy(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "BYE ")) + rc = txsDoBye(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "UUID ")) + rc = txsDoUuid(pPktHdr); + /* Process: */ + else if (txsIsSameOpcode(pPktHdr, "EXEC ")) + rc = txsDoExec(pPktHdr); + /* Admin: */ + else if (txsIsSameOpcode(pPktHdr, "REBOOT ")) + rc = txsDoReboot(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "SHUTDOWN")) + rc = txsDoShutdown(pPktHdr); + /* CD/DVD control: */ + else if (txsIsSameOpcode(pPktHdr, "CD EJECT")) + rc = txsDoCdEject(pPktHdr); + /* File system: */ + else if (txsIsSameOpcode(pPktHdr, "CLEANUP ")) + rc = txsDoCleanup(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "MKDIR ")) + rc = txsDoMkDir(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "MKDRPATH")) + rc = txsDoMkDrPath(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "MKSYMLNK")) + rc = txsDoMkSymlnk(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "RMDIR ")) + rc = txsDoRmDir(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "RMFILE ")) + rc = txsDoRmFile(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "RMSYMLNK")) + rc = txsDoRmSymlnk(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "RMTREE ")) + rc = txsDoRmTree(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "CHMOD ")) + rc = txsDoChMod(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "CHOWN ")) + rc = txsDoChOwn(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "CHGRP ")) + rc = txsDoChGrp(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "ISDIR ")) + rc = txsDoIsDir(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "ISFILE ")) + rc = txsDoIsFile(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "ISSYMLNK")) + rc = txsDoIsSymlnk(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "STAT ")) + rc = txsDoStat(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "LSTAT ")) + rc = txsDoLStat(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "LIST ")) + rc = txsDoList(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "PUT FILE")) + rc = txsDoPutFile(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "GET FILE")) + rc = txsDoGetFile(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "UNPKFILE")) + rc = txsDoUnpackFile(pPktHdr); + /* Misc: */ + else + rc = txsReplyUnknown(pPktHdr); + + if (g_cVerbose > 0) + RTMsgInfo("txsMainLoop: CMD: %.8s -> %Rrc", pPktHdr->achOpcode, rc); + RTMemFree(pPktHdr); + } + + if (g_cVerbose > 0) + RTMsgInfo("txsMainLoop: end\n"); + return enmExitCode; +} + + +/** + * Finalizes the scratch directory, making sure it exists. + * + * @returns exit code. + */ +static RTEXITCODE txsFinalizeScratch(void) +{ + RTPathStripTrailingSlash(g_szScratchPath); + char *pszFilename = RTPathFilename(g_szScratchPath); + if (!pszFilename) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "cannot use root for scratch (%s)\n", g_szScratchPath); + + int rc; + if (strchr(pszFilename, 'X')) + { + char ch = *pszFilename; + rc = RTDirCreateFullPath(g_szScratchPath, 0700); + *pszFilename = ch; + if (RT_SUCCESS(rc)) + rc = RTDirCreateTemp(g_szScratchPath, 0700); + } + else + { + if (RTDirExists(g_szScratchPath)) + rc = VINF_SUCCESS; + else + rc = RTDirCreateFullPath(g_szScratchPath, 0700); + } + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to create scratch directory: %Rrc (%s)\n", rc, g_szScratchPath); + return RTEXITCODE_SUCCESS; +} + +/** + * Attempts to complete an upgrade by updating the original and relaunching + * ourselves from there again. + * + * On failure, we'll continue running as the temporary copy. + * + * @returns Exit code. Exit if this is non-zero or @a *pfExit is set. + * @param argc The number of arguments. + * @param argv The argument vector. + * @param pfExit For indicating exit when the exit code is zero. + * @param pszUpgrading The upgraded image path. + */ +static RTEXITCODE txsAutoUpdateStage2(int argc, char **argv, bool *pfExit, const char *pszUpgrading) +{ + if (g_cVerbose > 0) + RTMsgInfo("Auto update stage 2..."); + + /* + * Copy the current executable onto the original. + * Note that we're racing the original program on some platforms, thus the + * 60 sec sleep mess. + */ + char szUpgradePath[RTPATH_MAX]; + if (!RTProcGetExecutablePath(szUpgradePath, sizeof(szUpgradePath))) + { + RTMsgError("RTProcGetExecutablePath failed (step 2)\n"); + return RTEXITCODE_SUCCESS; + } + void *pvUpgrade; + size_t cbUpgrade; + int rc = RTFileReadAll(szUpgradePath, &pvUpgrade, &cbUpgrade); + if (RT_FAILURE(rc)) + { + RTMsgError("RTFileReadAllEx(\"%s\"): %Rrc (step 2)\n", szUpgradePath, rc); + return RTEXITCODE_SUCCESS; + } + + uint64_t StartMilliTS = RTTimeMilliTS(); + RTFILE hFile; + rc = RTFileOpen(&hFile, pszUpgrading, + RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE + | (0755 << RTFILE_O_CREATE_MODE_SHIFT)); + while ( RT_FAILURE(rc) + && RTTimeMilliTS() - StartMilliTS < 60000) + { + RTThreadSleep(1000); + rc = RTFileOpen(&hFile, pszUpgrading, + RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE + | (0755 << RTFILE_O_CREATE_MODE_SHIFT)); + } + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, pvUpgrade, cbUpgrade, NULL); + RTFileClose(hFile); + if (RT_SUCCESS(rc)) + { + /* + * Relaunch the service with the original name, foricbly barring + * further upgrade cycles in case of bugs (and simplifying the code). + */ + const char **papszArgs = (const char **)RTMemAlloc((argc + 1 + 1) * sizeof(char **)); + if (papszArgs) + { + papszArgs[0] = pszUpgrading; + for (int i = 1; i < argc; i++) + papszArgs[i] = argv[i]; + papszArgs[argc] = "--no-auto-upgrade"; + papszArgs[argc + 1] = NULL; + + RTMsgInfo("Launching upgraded image: \"%s\"\n", pszUpgrading); + RTPROCESS hProc; + rc = RTProcCreate(pszUpgrading, papszArgs, RTENV_DEFAULT, 0 /*fFlags*/, &hProc); + if (RT_SUCCESS(rc)) + *pfExit = true; + else + RTMsgError("RTProcCreate(\"%s\"): %Rrc (upgrade stage 2)\n", pszUpgrading, rc); + RTMemFree(papszArgs); + } + else + RTMsgError("RTMemAlloc failed during upgrade attempt (stage 2)\n"); + } + else + RTMsgError("RTFileWrite(%s,,%zu): %Rrc (step 2) - BAD\n", pszUpgrading, cbUpgrade, rc); + } + else + RTMsgError("RTFileOpen(,%s,): %Rrc\n", pszUpgrading, rc); + RTFileReadAllFree(pvUpgrade, cbUpgrade); + return RTEXITCODE_SUCCESS; +} + +/** + * Checks for an upgrade and respawns if there is. + * + * @returns Exit code. Exit if this is non-zero or @a *pfExit is set. + * @param argc The number of arguments. + * @param argv The argument vector. + * @param cSecsCdWait Number of seconds to wait on the CD. + * @param pfExit For indicating exit when the exit code is zero. + */ +static RTEXITCODE txsAutoUpdateStage1(int argc, char **argv, uint32_t cSecsCdWait, bool *pfExit) +{ + if (g_cVerbose > 1) + RTMsgInfo("Auto update stage 1..."); + + /* + * Figure names of the current service image and the potential upgrade. + */ + char szOrgPath[RTPATH_MAX]; + if (!RTProcGetExecutablePath(szOrgPath, sizeof(szOrgPath))) + { + RTMsgError("RTProcGetExecutablePath failed\n"); + return RTEXITCODE_SUCCESS; + } + + char szUpgradePath[RTPATH_MAX]; + int rc = RTPathJoin(szUpgradePath, sizeof(szUpgradePath), g_szCdRomPath, g_szOsSlashArchShortName); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szUpgradePath, sizeof(szUpgradePath), RTPathFilename(szOrgPath)); + if (RT_FAILURE(rc)) + { + RTMsgError("Failed to construct path to potential service upgrade: %Rrc\n", rc); + return RTEXITCODE_SUCCESS; + } + + /* + * Query information about the two images and read the entire potential source file. + * Because the CD may take a little time to be mounted when the system boots, we + * need to do some fudging here. + */ + uint64_t nsStart = RTTimeNanoTS(); + RTFSOBJINFO UpgradeInfo; + for (;;) + { + rc = RTPathQueryInfo(szUpgradePath, &UpgradeInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + break; + if ( rc != VERR_FILE_NOT_FOUND + && rc != VERR_PATH_NOT_FOUND + && rc != VERR_MEDIA_NOT_PRESENT + && rc != VERR_MEDIA_NOT_RECOGNIZED) + { + RTMsgError("RTPathQueryInfo(\"%s\"): %Rrc (upgrade)\n", szUpgradePath, rc); + return RTEXITCODE_SUCCESS; + } + uint64_t cNsElapsed = RTTimeNanoTS() - nsStart; + if (cNsElapsed >= cSecsCdWait * RT_NS_1SEC_64) + { + if (g_cVerbose > 0) + RTMsgInfo("Auto update: Giving up waiting for media."); + return RTEXITCODE_SUCCESS; + } + RTThreadSleep(500); + } + + RTFSOBJINFO OrgInfo; + rc = RTPathQueryInfo(szOrgPath, &OrgInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + { + RTMsgError("RTPathQueryInfo(\"%s\"): %Rrc (old)\n", szOrgPath, rc); + return RTEXITCODE_SUCCESS; + } + + void *pvUpgrade; + size_t cbUpgrade; + rc = RTFileReadAllEx(szUpgradePath, 0, UpgradeInfo.cbObject, RTFILE_RDALL_O_DENY_NONE, &pvUpgrade, &cbUpgrade); + if (RT_FAILURE(rc)) + { + RTMsgError("RTPathQueryInfo(\"%s\"): %Rrc (old)\n", szOrgPath, rc); + return RTEXITCODE_SUCCESS; + } + + /* + * Compare and see if we've got a different service image or not. + */ + if (OrgInfo.cbObject == UpgradeInfo.cbObject) + { + /* must compare bytes. */ + void *pvOrg; + size_t cbOrg; + rc = RTFileReadAllEx(szOrgPath, 0, OrgInfo.cbObject, RTFILE_RDALL_O_DENY_NONE, &pvOrg, &cbOrg); + if (RT_FAILURE(rc)) + { + RTMsgError("RTFileReadAllEx(\"%s\"): %Rrc\n", szOrgPath, rc); + RTFileReadAllFree(pvUpgrade, cbUpgrade); + return RTEXITCODE_SUCCESS; + } + bool fSame = !memcmp(pvUpgrade, pvOrg, OrgInfo.cbObject); + RTFileReadAllFree(pvOrg, cbOrg); + if (fSame) + { + RTFileReadAllFree(pvUpgrade, cbUpgrade); + if (g_cVerbose > 0) + RTMsgInfo("Auto update: Not necessary."); + return RTEXITCODE_SUCCESS; + } + } + + /* + * Should upgrade. Start by creating an executable copy of the update + * image in the scratch area. + */ + RTEXITCODE rcExit = txsFinalizeScratch(); + if (rcExit == RTEXITCODE_SUCCESS) + { + char szTmpPath[RTPATH_MAX]; + rc = RTPathJoin(szTmpPath, sizeof(szTmpPath), g_szScratchPath, RTPathFilename(szOrgPath)); + if (RT_SUCCESS(rc)) + { + RTFileDelete(szTmpPath); /* shouldn't hurt. */ + + RTFILE hFile; + rc = RTFileOpen(&hFile, szTmpPath, + RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE + | (0755 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, pvUpgrade, UpgradeInfo.cbObject, NULL); + RTFileClose(hFile); + if (RT_SUCCESS(rc)) + { + /* + * Try execute the new image and quit if it works. + */ + const char **papszArgs = (const char **)RTMemAlloc((argc + 2 + 1) * sizeof(char **)); + if (papszArgs) + { + papszArgs[0] = szTmpPath; + for (int i = 1; i < argc; i++) + papszArgs[i] = argv[i]; + papszArgs[argc] = "--upgrading"; + papszArgs[argc + 1] = szOrgPath; + papszArgs[argc + 2] = NULL; + + RTMsgInfo("Launching intermediate automatic upgrade stage: \"%s\"\n", szTmpPath); + RTPROCESS hProc; + rc = RTProcCreate(szTmpPath, papszArgs, RTENV_DEFAULT, 0 /*fFlags*/, &hProc); + if (RT_SUCCESS(rc)) + *pfExit = true; + else + RTMsgError("RTProcCreate(\"%s\"): %Rrc (upgrade stage 1)\n", szTmpPath, rc); + RTMemFree(papszArgs); + } + else + RTMsgError("RTMemAlloc failed during upgrade attempt (stage)\n"); + } + else + RTMsgError("RTFileWrite(%s,,%zu): %Rrc\n", szTmpPath, UpgradeInfo.cbObject, rc); + } + else + RTMsgError("RTFileOpen(,%s,): %Rrc\n", szTmpPath, rc); + } + else + RTMsgError("Failed to construct path to temporary upgrade image: %Rrc\n", rc); + } + + RTFileReadAllFree(pvUpgrade, cbUpgrade); + return rcExit; +} + +/** + * Determines the default configuration. + */ +static void txsSetDefaults(void) +{ + /* + * OS and ARCH. + */ + AssertCompile(sizeof(KBUILD_TARGET) <= sizeof(g_szOsShortName)); + strcpy(g_szOsShortName, KBUILD_TARGET); + + AssertCompile(sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szArchShortName)); + strcpy(g_szArchShortName, KBUILD_TARGET_ARCH); + + AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsDotArchShortName)); + strcpy(g_szOsDotArchShortName, KBUILD_TARGET); + g_szOsDotArchShortName[sizeof(KBUILD_TARGET) - 1] = '.'; + strcpy(&g_szOsDotArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH); + + AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsSlashArchShortName)); + strcpy(g_szOsSlashArchShortName, KBUILD_TARGET); + g_szOsSlashArchShortName[sizeof(KBUILD_TARGET) - 1] = '/'; + strcpy(&g_szOsSlashArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH); + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + strcpy(g_szExeSuff, ".exe"); + strcpy(g_szScriptSuff, ".cmd"); +#else + strcpy(g_szExeSuff, ""); + strcpy(g_szScriptSuff, ".sh"); +#endif + + /* + * The CD/DVD-ROM location. + */ + /** @todo do a better job here :-) */ +#ifdef RT_OS_WINDOWS + strcpy(g_szDefCdRomPath, "D:/"); +#elif defined(RT_OS_OS2) + strcpy(g_szDefCdRomPath, "D:/"); +#else + if (RTDirExists("/media")) + strcpy(g_szDefCdRomPath, "/media/cdrom"); + else + strcpy(g_szDefCdRomPath, "/mnt/cdrom"); +#endif + strcpy(g_szCdRomPath, g_szDefCdRomPath); + + /* + * Temporary directory. + */ + int rc = RTPathTemp(g_szDefScratchPath, sizeof(g_szDefScratchPath)); + if (RT_SUCCESS(rc)) +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) || defined(RT_OS_DOS) + rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "txs-XXXX.tmp"); +#else + rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "txs-XXXXXXXXX.tmp"); +#endif + if (RT_FAILURE(rc)) + { + RTMsgError("RTPathTemp/Append failed when constructing scratch path: %Rrc\n", rc); + strcpy(g_szDefScratchPath, "/tmp/txs-XXXX.tmp"); + } + strcpy(g_szScratchPath, g_szDefScratchPath); + + /* + * The default transporter is the first one. + */ + g_pTransport = g_apTransports[0]; +} + +/** + * Prints the usage. + * + * @param pStrm Where to print it. + * @param pszArgv0 The program name (argv[0]). + */ +static void txsUsage(PRTSTREAM pStrm, const char *pszArgv0) +{ + RTStrmPrintf(pStrm, + "Usage: %Rbn [options]\n" + "\n" + "Options:\n" + " --cdrom <path>\n" + " Where the CD/DVD-ROM will be mounted.\n" + " Default: %s\n" + " --scratch <path>\n" + " Where to put scratch files.\n" + " Default: %s \n" + , + pszArgv0, + g_szDefCdRomPath, + g_szDefScratchPath); + RTStrmPrintf(pStrm, + " --transport <name>\n" + " Use the specified transport layer, one of the following:\n"); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + RTStrmPrintf(pStrm, " %s - %s\n", g_apTransports[i]->szName, g_apTransports[i]->pszDesc); + RTStrmPrintf(pStrm, " Default: %s\n", g_pTransport->szName); + RTStrmPrintf(pStrm, + " --auto-upgrade, --no-auto-upgrade\n" + " To enable or disable the automatic upgrade mechanism where any different\n" + " version found on the CD-ROM on startup will replace the initial copy.\n" + " Default: --auto-upgrade\n" + " --wait-cdrom <secs>\n" + " Number of seconds to wait for the CD-ROM to be mounted before giving up\n" + " on automatic upgrading.\n" + " Default: --wait-cdrom 1; solaris: --wait-cdrom 8\n" + " --upgrading <org-path>\n" + " Internal use only.\n"); + RTStrmPrintf(pStrm, + " --display-output, --no-display-output\n" + " Display the output and the result of all child processes.\n"); + RTStrmPrintf(pStrm, + " --foreground\n" + " Don't daemonize, run in the foreground.\n"); + RTStrmPrintf(pStrm, + " --help, -h, -?\n" + " Display this message and exit.\n" + " --version, -V\n" + " Display the version and exit.\n"); + + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (g_apTransports[i]->cOpts) + { + RTStrmPrintf(pStrm, + "\n" + "Options for %s:\n", g_apTransports[i]->szName); + g_apTransports[i]->pfnUsage(g_pStdOut); + } +} + +/** + * Parses the arguments. + * + * @returns Exit code. Exit if this is non-zero or @a *pfExit is set. + * @param argc The number of arguments. + * @param argv The argument vector. + * @param pfExit For indicating exit when the exit code is zero. + */ +static RTEXITCODE txsParseArgv(int argc, char **argv, bool *pfExit) +{ + *pfExit = false; + + /* + * Storage for locally handled options. + */ + bool fAutoUpgrade = true; + bool fDaemonize = true; + bool fDaemonized = false; + const char *pszUpgrading = NULL; +#ifdef RT_OS_SOLARIS + uint32_t cSecsCdWait = 8; +#else + uint32_t cSecsCdWait = 1; +#endif + + /* + * Combine the base and transport layer option arrays. + */ + static const RTGETOPTDEF s_aBaseOptions[] = + { + { "--transport", 't', RTGETOPT_REQ_STRING }, + { "--cdrom", 'c', RTGETOPT_REQ_STRING }, + { "--wait-cdrom", 'w', RTGETOPT_REQ_UINT32 }, + { "--scratch", 's', RTGETOPT_REQ_STRING }, + { "--auto-upgrade", 'a', RTGETOPT_REQ_NOTHING }, + { "--no-auto-upgrade", 'A', RTGETOPT_REQ_NOTHING }, + { "--upgrading", 'U', RTGETOPT_REQ_STRING }, + { "--display-output", 'd', RTGETOPT_REQ_NOTHING }, + { "--no-display-output",'D', RTGETOPT_REQ_NOTHING }, + { "--foreground", 'f', RTGETOPT_REQ_NOTHING }, + { "--daemonized", 'Z', RTGETOPT_REQ_NOTHING }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + + size_t cOptions = RT_ELEMENTS(s_aBaseOptions); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + cOptions += g_apTransports[i]->cOpts; + + PRTGETOPTDEF paOptions = (PRTGETOPTDEF)alloca(cOptions * sizeof(RTGETOPTDEF)); + if (!paOptions) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "alloca failed\n"); + + memcpy(paOptions, s_aBaseOptions, sizeof(s_aBaseOptions)); + cOptions = RT_ELEMENTS(s_aBaseOptions); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + { + memcpy(&paOptions[cOptions], g_apTransports[i]->paOpts, g_apTransports[i]->cOpts * sizeof(RTGETOPTDEF)); + cOptions += g_apTransports[i]->cOpts; + } + + /* + * Parse the arguments. + */ + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, paOptions, cOptions, 1, 0 /* fFlags */); + AssertRC(rc); + + int ch; + RTGETOPTUNION Val; + while ((ch = RTGetOpt(&GetState, &Val))) + { + switch (ch) + { + case 'a': + fAutoUpgrade = true; + break; + + case 'A': + fAutoUpgrade = false; + break; + + case 'c': + rc = RTStrCopy(g_szCdRomPath, sizeof(g_szCdRomPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "CD/DVD-ROM is path too long (%Rrc)\n", rc); + break; + + case 'd': + g_fDisplayOutput = true; + break; + + case 'D': + g_fDisplayOutput = false; + break; + + case 'f': + fDaemonize = false; + break; + + case 'h': + txsUsage(g_pStdOut, argv[0]); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 's': + rc = RTStrCopy(g_szScratchPath, sizeof(g_szScratchPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "scratch path is too long (%Rrc)\n", rc); + break; + + case 't': + { + PCTXSTRANSPORT pTransport = NULL; + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (!strcmp(g_apTransports[i]->szName, Val.psz)) + { + pTransport = g_apTransports[i]; + break; + } + if (!pTransport) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown transport layer name '%s'\n", Val.psz); + g_pTransport = pTransport; + break; + } + + case 'U': + pszUpgrading = Val.psz; + break; + + case 'w': + cSecsCdWait = Val.u32; + break; + + case 'q': + g_cVerbose = 0; + break; + + case 'v': + g_cVerbose++; + break; + + case 'V': + RTPrintf("$Revision: 127855 $\n"); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 'Z': + fDaemonized = true; + fDaemonize = false; + break; + + default: + { + rc = VERR_TRY_AGAIN; + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (g_apTransports[i]->cOpts) + { + rc = g_apTransports[i]->pfnOption(ch, &Val); + if (RT_SUCCESS(rc)) + break; + if (rc != VERR_TRY_AGAIN) + { + *pfExit = true; + return RTEXITCODE_SYNTAX; + } + } + if (rc == VERR_TRY_AGAIN) + { + *pfExit = true; + return RTGetOptPrintError(ch, &Val); + } + break; + } + } + } + + /* + * Handle automatic upgrading of the service. + */ + if (fAutoUpgrade && !*pfExit) + { + RTEXITCODE rcExit; + if (pszUpgrading) + rcExit = txsAutoUpdateStage2(argc, argv, pfExit, pszUpgrading); + else + rcExit = txsAutoUpdateStage1(argc, argv, cSecsCdWait, pfExit); + if ( *pfExit + || rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + /* + * Daemonize ourselves if asked to. + */ + if (fDaemonize && !*pfExit) + { + if (g_cVerbose > 0) + RTMsgInfo("Daemonizing..."); + rc = RTProcDaemonize(argv, "--daemonized"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize: %Rrc\n", rc); + *pfExit = true; + } + + return RTEXITCODE_SUCCESS; +} + + +int main(int argc, char **argv) +{ + /* + * Initialize the runtime. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Determine defaults and parse the arguments. + */ + txsSetDefaults(); + bool fExit; + RTEXITCODE rcExit = txsParseArgv(argc, argv, &fExit); + if (rcExit != RTEXITCODE_SUCCESS || fExit) + return rcExit; + + /* + * Generate a UUID for this TXS instance. + */ + rc = RTUuidCreate(&g_InstanceUuid); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTUuidCreate failed: %Rrc", rc); + if (g_cVerbose > 0) + RTMsgInfo("Instance UUID: %RTuuid", &g_InstanceUuid); + + /* + * Finalize the scratch directory and initialize the transport layer. + */ + rcExit = txsFinalizeScratch(); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + rc = g_pTransport->pfnInit(); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* + * Ok, start working + */ + rcExit = txsMainLoop(); + + /* + * Cleanup. + */ + g_pTransport->pfnTerm(); + + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceInternal.h b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceInternal.h new file mode 100644 index 00000000..4476fb81 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceInternal.h @@ -0,0 +1,222 @@ +/* $Id: TestExecServiceInternal.h $ */ +/** @file + * TestExecServ - Basic Remote Execution Service, Internal Header. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef VBOX_INCLUDED_SRC_TestExecServ_TestExecServiceInternal_h +#define VBOX_INCLUDED_SRC_TestExecServ_TestExecServiceInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/getopt.h> +#include <iprt/stream.h> + +RT_C_DECLS_BEGIN + +/** + * Packet header. + */ +typedef struct TXSPKTHDR +{ + /** The unpadded packet length. This include this header. */ + uint32_t cb; + /** The CRC-32 for the packet starting from the opcode field. 0 if the packet + * hasn't been CRCed. */ + uint32_t uCrc32; + /** Packet opcode, an unterminated ASCII string. */ + uint8_t achOpcode[8]; +} TXSPKTHDR; +AssertCompileSize(TXSPKTHDR, 16); +/** Pointer to a packet header. */ +typedef TXSPKTHDR *PTXSPKTHDR; +/** Pointer to a packet header. */ +typedef TXSPKTHDR const *PCTXSPKTHDR; +/** Pointer to a packet header pointer. */ +typedef PTXSPKTHDR *PPTXSPKTHDR; + +/** Packet alignment. */ +#define TXSPKT_ALIGNMENT 16 +/** Max packet size. */ +#define TXSPKT_MAX_SIZE _256K + + +/** + * Transport layer descriptor. + */ +typedef struct TXSTRANSPORT +{ + /** The name. */ + char szName[16]; + /** The description. */ + const char *pszDesc; + /** Pointer to an array of options. */ + PCRTGETOPTDEF paOpts; + /** The number of options in the array. */ + size_t cOpts; + + /** + * Print the usage information for this transport layer. + * + * @param pStream The stream to print the usage info to. + * + * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0. + */ + DECLR3CALLBACKMEMBER(void, pfnUsage,(PRTSTREAM pStream)); + + /** + * Handle an option. + * + * When encountering an options that is not part of the base options, we'll call + * this method for each transport layer until one handles it. + * + * @retval VINF_SUCCESS if handled. + * @retval VERR_TRY_AGAIN if not handled. + * @retval VERR_INVALID_PARAMETER if we should exit with a non-zero status. + * + * @param ch The short option value. + * @param pVal Pointer to the value union. + * + * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0. + */ + DECLR3CALLBACKMEMBER(int, pfnOption,(int ch, PCRTGETOPTUNION pVal)); + + /** + * Initializes the transport layer. + * + * @returns IPRT status code. On errors, the transport layer shall call + * RTMsgError to display the error details to the user. + */ + DECLR3CALLBACKMEMBER(int, pfnInit,(void)); + + /** + * Terminate the transport layer, closing and freeing resources. + * + * On errors, the transport layer shall call RTMsgError to display the error + * details to the user. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm,(void)); + + /** + * Polls for incoming packets. + * + * @returns true if there are pending packets, false if there isn't. + */ + DECLR3CALLBACKMEMBER(bool, pfnPollIn,(void)); + + /** + * Adds any pollable handles to the poll set. + * + * This is optional and layers that doesn't have anything that can be polled + * shall set this method pointer to NULL to indicate that pfnPollIn must be used + * instead. + * + * @returns IPRT status code. + * @param hPollSet The poll set to add them to. + * @param idStart The handle ID to start at. + */ + DECLR3CALLBACKMEMBER(int, pfnPollSetAdd,(RTPOLLSET hPollSet, uint32_t idStart)); + + /** + * Receives an incoming packet. + * + * This will block until the data becomes available or we're interrupted by a + * signal or something. + * + * @returns IPRT status code. On error conditions other than VERR_INTERRUPTED, + * the current operation will be aborted when applicable. When + * interrupted, the transport layer will store the data until the next + * receive call. + * + * @param ppPktHdr Where to return the pointer to the packet we've + * read. This is allocated from the heap using + * RTMemAlloc (w/ TXSPKT_ALIGNMENT) and must be + * free by calling RTMemFree. + */ + DECLR3CALLBACKMEMBER(int, pfnRecvPkt,(PPTXSPKTHDR ppPktHdr)); + + /** + * Sends an outgoing packet. + * + * This will block until the data has been written. + * + * @returns IPRT status code. + * @retval VERR_INTERRUPTED if interrupted before anything was sent. + * + * @param pPktHdr The packet to send. The size is given by + * aligning the size in the header by + * TXSPKT_ALIGNMENT. + */ + DECLR3CALLBACKMEMBER(int, pfnSendPkt,(PCTXSPKTHDR pPktHdr)); + + /** + * Sends a babble packet and disconnects the client (if applicable). + * + * @param pPktHdr The packet to send. The size is given by + * aligning the size in the header by + * TXSPKT_ALIGNMENT. + * @param cMsSendTimeout The send timeout measured in milliseconds. + */ + DECLR3CALLBACKMEMBER(void, pfnBabble,(PCTXSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout)); + + /** + * Notification about a client HOWDY. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyHowdy,(void)); + + /** + * Notification about a client BYE. + * + * For connection oriented transport layers, it would be good to disconnect the + * client at this point. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyBye,(void)); + + /** + * Notification about a REBOOT or SHUTDOWN. + * + * For connection oriented transport layers, stop listening for and + * accepting at this point. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyReboot,(void)); + + /** Non-zero end marker. */ + uint32_t u32EndMarker; +} TXSTRANSPORT; +/** Pointer to a const transport layer descriptor. */ +typedef const struct TXSTRANSPORT *PCTXSTRANSPORT; + + +extern TXSTRANSPORT const g_TcpTransport; +extern TXSTRANSPORT const g_SerialTransport; +extern TXSTRANSPORT const g_FileSysTransport; +extern TXSTRANSPORT const g_GuestPropTransport; +extern TXSTRANSPORT const g_TestDevTransport; + +extern uint32_t g_cVerbose; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_TestExecServ_TestExecServiceInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceSerial.cpp b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceSerial.cpp new file mode 100644 index 00000000..a659f5d4 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceSerial.cpp @@ -0,0 +1,407 @@ +/* $Id: TestExecServiceSerial.cpp $ */ +/** @file + * TestExecServ - Basic Remote Execution Service, Serial port Transport Layer. + */ + +/* + * Copyright (C) 2018-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/string.h> +#include <iprt/serialport.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "TestExecServiceInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The default baud rate port. */ +#define TXS_SERIAL_DEF_BAUDRATE 115200 +/** The default serial device to use. */ +#if defined(RT_OS_LINUX) +# define TXS_SERIAL_DEF_DEVICE "/dev/ttyS0" +#elif defined(RT_OS_WINDOWS) +# define TXS_SERIAL_DEF_DEVICE "COM1" +#elif defined(RT_OS_SOLARIS) +# define TXS_SERIAL_DEF_DEVICE "<todo>" +#elif defined(RT_OS_FREEBSD) +# define TXS_SERIAL_DEF_DEVICE "<todo>" +#elif defined(RT_OS_DARWIN) +# define TXS_SERIAL_DEF_DEVICE "<todo>" +#else +# error "Port me" +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name Serial Parameters + * @{ */ +/** The addresses to bind to. Empty string means any. */ +static uint32_t g_uSerialBaudRate = TXS_SERIAL_DEF_BAUDRATE; +/** The serial port device to use. */ +static char g_szSerialDevice[256] = TXS_SERIAL_DEF_DEVICE; +/** @} */ + +/** The serial port handle. */ +static RTSERIALPORT g_hSerialPort = NIL_RTSERIALPORT; +/** The size of the stashed data. */ +static size_t g_cbSerialStashed = 0; +/** The size of the stashed data allocation. */ +static size_t g_cbSerialStashedAlloced = 0; +/** The stashed data. */ +static uint8_t *g_pbSerialStashed = NULL; + + + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyReboot} + */ +static DECLCALLBACK(void) txsSerialNotifyReboot(void) +{ + /* nothing to do here */ +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyBye} + */ +static DECLCALLBACK(void) txsSerialNotifyBye(void) +{ + /* nothing to do here */ +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyHowdy} + */ +static DECLCALLBACK(void) txsSerialNotifyHowdy(void) +{ + /* nothing to do here */ +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnBabble} + */ +static DECLCALLBACK(void) txsSerialBabble(PCTXSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout) +{ + Assert(g_hSerialPort != NIL_RTSERIALPORT); + + /* + * Try send the babble reply. + */ + NOREF(cMsSendTimeout); /** @todo implement the timeout here; non-blocking write + select-on-write. */ + int rc; + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, TXSPKT_ALIGNMENT); + do rc = RTSerialPortWrite(g_hSerialPort, pPktHdr, cbToSend, NULL); + while (rc == VERR_INTERRUPTED); + + /* + * Disconnect the client. + */ + Log(("txsSerialBabble: RTSerialPortWrite rc=%Rrc\n", rc)); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnSendPkt} + */ +static DECLCALLBACK(int) txsSerialSendPkt(PCTXSPKTHDR pPktHdr) +{ + Assert(g_hSerialPort != NIL_RTSERIALPORT); + Assert(pPktHdr->cb >= sizeof(TXSPKTHDR)); + + /* + * Write it. + */ + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, TXSPKT_ALIGNMENT); + int rc = RTSerialPortWrite(g_hSerialPort, pPktHdr, cbToSend, NULL); + if ( RT_FAILURE(rc) + && rc != VERR_INTERRUPTED) + { + /* assume fatal connection error. */ + Log(("RTSerialPortWrite -> %Rrc\n", rc)); + } + + return rc; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnRecvPkt} + */ +static DECLCALLBACK(int) txsSerialRecvPkt(PPTXSPKTHDR ppPktHdr) +{ + Assert(g_hSerialPort != NIL_RTSERIALPORT); + + int rc = VINF_SUCCESS; + *ppPktHdr = NULL; + + /* + * Read state. + */ + size_t offData = 0; + size_t cbData = 0; + size_t cbDataAlloced; + uint8_t *pbData = NULL; + + /* + * Any stashed data? + */ + if (g_cbSerialStashedAlloced) + { + offData = g_cbSerialStashed; + cbDataAlloced = g_cbSerialStashedAlloced; + pbData = g_pbSerialStashed; + + g_cbSerialStashed = 0; + g_cbSerialStashedAlloced = 0; + g_pbSerialStashed = NULL; + } + else + { + cbDataAlloced = RT_ALIGN_Z(64, TXSPKT_ALIGNMENT); + pbData = (uint8_t *)RTMemAlloc(cbDataAlloced); + if (!pbData) + return VERR_NO_MEMORY; + } + + /* + * Read and valid the length. + */ + while (offData < sizeof(uint32_t)) + { + size_t cbRead = sizeof(uint32_t) - offData; + rc = RTSerialPortRead(g_hSerialPort, pbData + offData, cbRead, NULL); + if (RT_FAILURE(rc)) + break; + offData += cbRead; + } + if (RT_SUCCESS(rc)) + { + ASMCompilerBarrier(); /* paranoia^3 */ + cbData = *(uint32_t volatile *)pbData; + if (cbData >= sizeof(TXSPKTHDR) && cbData <= TXSPKT_MAX_SIZE) + { + /* + * Align the length and reallocate the return packet it necessary. + */ + cbData = RT_ALIGN_Z(cbData, TXSPKT_ALIGNMENT); + if (cbData > cbDataAlloced) + { + void *pvNew = RTMemRealloc(pbData, cbData); + if (pvNew) + { + pbData = (uint8_t *)pvNew; + cbDataAlloced = cbData; + } + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + /* + * Read the remainder of the data. + */ + while (offData < cbData) + { + size_t cbRead = cbData - offData; + rc = RTSerialPortRead(g_hSerialPort, pbData + offData, cbRead, NULL); + if (RT_FAILURE(rc)) + break; + offData += cbRead; + } + } + } + else + rc = VERR_NET_PROTOCOL_ERROR; + } + if (RT_SUCCESS(rc)) + *ppPktHdr = (PTXSPKTHDR)pbData; + else + { + /* + * Deal with errors. + */ + if (rc == VERR_INTERRUPTED) + { + /* stash it away for the next call. */ + g_cbSerialStashed = cbData; + g_cbSerialStashedAlloced = cbDataAlloced; + g_pbSerialStashed = pbData; + } + else + { + RTMemFree(pbData); + + /* assume fatal connection error. */ + Log(("txsSerialRecvPkt: RTSerialPortRead -> %Rrc\n", rc)); + } + } + + return rc; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnPollIn} + */ +static DECLCALLBACK(bool) txsSerialPollIn(void) +{ + Assert(g_hSerialPort != NIL_RTSERIALPORT); + + uint32_t fEvtsRecv = 0; + int rc = RTSerialPortEvtPoll(g_hSerialPort, RTSERIALPORT_EVT_F_DATA_RX, + &fEvtsRecv, 0/*cMillies*/); + return RT_SUCCESS(rc); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnTerm} + */ +static DECLCALLBACK(void) txsSerialTerm(void) +{ + if (g_hSerialPort != NIL_RTSERIALPORT) + RTSerialPortClose(g_hSerialPort); + + /* Clean up stashing. */ + if (g_pbSerialStashed) + RTMemFree(g_pbSerialStashed); + g_pbSerialStashed = NULL; + g_cbSerialStashed = 0; + g_cbSerialStashedAlloced = 0; + + Log(("txsSerialTerm: done\n")); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnInit} + */ +static DECLCALLBACK(int) txsSerialInit(void) +{ + uint32_t fOpenFlags = RTSERIALPORT_OPEN_F_READ | RTSERIALPORT_OPEN_F_WRITE; + int rc = RTSerialPortOpen(&g_hSerialPort, &g_szSerialDevice[0], fOpenFlags); + if (RT_SUCCESS(rc)) + { + RTSERIALPORTCFG SerPortCfg; + + SerPortCfg.uBaudRate = g_uSerialBaudRate; + SerPortCfg.enmParity = RTSERIALPORTPARITY_NONE; + SerPortCfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS; + SerPortCfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE; + rc = RTSerialPortCfgSet(g_hSerialPort, &SerPortCfg, NULL); + if (RT_FAILURE(rc)) + { + RTMsgError("RTSerialPortCfgSet() failed: %Rrc\n", rc); + RTSerialPortClose(g_hSerialPort); + g_hSerialPort = NIL_RTSERIALPORT; + } + } + else + RTMsgError("RTSerialPortOpen(, %s, %#x) failed: %Rrc\n", + g_szSerialDevice, fOpenFlags, rc); + + return rc; +} + +/** Options */ +enum TXSSERIALOPT +{ + TXSSERIALOPT_BAUDRATE = 1000, + TXSSERIALOPT_DEVICE +}; + +/** + * @interface_method_impl{TXSTRANSPORT,pfnOption} + */ +static DECLCALLBACK(int) txsSerialOption(int ch, PCRTGETOPTUNION pVal) +{ + int rc; + + switch (ch) + { + case TXSSERIALOPT_DEVICE: + rc = RTStrCopy(g_szSerialDevice, sizeof(g_szSerialDevice), pVal->psz); + if (RT_FAILURE(rc)) + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "Serial port device path is too long (%Rrc)", rc); + if (!g_szSerialDevice[0]) + strcpy(g_szSerialDevice, TXS_SERIAL_DEF_DEVICE); + return VINF_SUCCESS; + case TXSSERIALOPT_BAUDRATE: + g_uSerialBaudRate = pVal->u32 == 0 ? TXS_SERIAL_DEF_BAUDRATE : pVal->u32; + return VINF_SUCCESS; + } + return VERR_TRY_AGAIN; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnUsage} + */ +DECLCALLBACK(void) txsSerialUsage(PRTSTREAM pStream) +{ + RTStrmPrintf(pStream, + " --serial-device <device>\n" + " Selects the serial port to use.\n" + " Default: %s\n" + " --serial-baudrate <baudrate>\n" + " Selects the baudrate to set the serial port to.\n" + " Default: %u\n" + , TXS_SERIAL_DEF_DEVICE, TXS_SERIAL_DEF_BAUDRATE); +} + +/** Command line options for the serial transport layer. */ +static const RTGETOPTDEF g_SerialOpts[] = +{ + { "--serial-device", TXSSERIALOPT_DEVICE, RTGETOPT_REQ_STRING }, + { "--serial-baudrate", TXSSERIALOPT_BAUDRATE, RTGETOPT_REQ_UINT32 } +}; + +/** Serial port transport layer. */ +const TXSTRANSPORT g_SerialTransport = +{ + /* .szName = */ "serial", + /* .pszDesc = */ "Serial", + /* .cOpts = */ &g_SerialOpts[0], + /* .paOpts = */ RT_ELEMENTS(g_SerialOpts), + /* .pfnUsage = */ txsSerialUsage, + /* .pfnOption = */ txsSerialOption, + /* .pfnInit = */ txsSerialInit, + /* .pfnTerm = */ txsSerialTerm, + /* .pfnPollIn = */ txsSerialPollIn, + /* .pfnPollSetAdd = */ NULL, + /* .pfnRecvPkt = */ txsSerialRecvPkt, + /* .pfnSendPkt = */ txsSerialSendPkt, + /* .pfnBabble = */ txsSerialBabble, + /* .pfnNotifyHowdy = */ txsSerialNotifyHowdy, + /* .pfnNotifyBye = */ txsSerialNotifyBye, + /* .pfnNotifyReboot = */ txsSerialNotifyReboot, + /* .u32EndMarker = */ UINT32_C(0x12345678) +}; + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceTcp.cpp b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceTcp.cpp new file mode 100644 index 00000000..b975daf5 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceTcp.cpp @@ -0,0 +1,832 @@ +/* $Id: TestExecServiceTcp.cpp $ */ +/** @file + * TestExecServ - Basic Remote Execution Service, TCP/IP Transport Layer. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/poll.h> +#include <iprt/string.h> +#include <iprt/tcp.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "TestExecServiceInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The default server port. */ +#define TXS_TCP_DEF_BIND_PORT 5042 +/** The default client port. */ +#define TXS_TCP_DEF_CONNECT_PORT 5048 + +/** The default server bind address. */ +#define TXS_TCP_DEF_BIND_ADDRESS "" +/** The default client connect address (i.e. of the host server). */ +#define TXS_TCP_DEF_CONNECT_ADDRESS "10.0.2.2" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name TCP Parameters + * @{ */ +static enum { TXSTCPMODE_BOTH, TXSTCPMODE_CLIENT, TXSTCPMODE_SERVER } + g_enmTcpMode = TXSTCPMODE_BOTH; + +/** The addresses to bind to. Empty string means any. */ +static char g_szTcpBindAddr[256] = TXS_TCP_DEF_BIND_ADDRESS; +/** The TCP port to listen to. */ +static uint32_t g_uTcpBindPort = TXS_TCP_DEF_BIND_PORT; +/** The addresses to connect to if fRevesedSetupMode is @c true. */ +static char g_szTcpConnectAddr[256] = TXS_TCP_DEF_CONNECT_ADDRESS; +/** The TCP port to listen to. */ +static uint32_t g_uTcpConnectPort = TXS_TCP_DEF_CONNECT_PORT; +/** @} */ + +/** Critical section for serializing access to the next few variables. */ +static RTCRITSECT g_TcpCritSect; +/** Pointer to the TCP server instance. */ +static PRTTCPSERVER g_pTcpServer = NULL; +/** Thread calling RTTcpServerListen2. */ +static RTTHREAD g_hThreadTcpServer = NIL_RTTHREAD; +/** Thread calling RTTcpClientConnect. */ +static RTTHREAD g_hThreadTcpConnect = NIL_RTTHREAD; +/** The main thread handle (for signalling). */ +static RTTHREAD g_hThreadMain = NIL_RTTHREAD; +/** Stop connecting attempts when set. */ +static bool g_fTcpStopConnecting = false; +/** Connect cancel cookie. */ +static PRTTCPCLIENTCONNECTCANCEL volatile g_pTcpConnectCancelCookie = NULL; + +/** Socket of the current client. */ +static RTSOCKET g_hTcpClient = NIL_RTSOCKET; +/** Indicates whether g_hTcpClient comes from the server or from a client + * connect (relevant when closing it). */ +static bool g_fTcpClientFromServer = false; +/** The size of the stashed data. */ +static size_t g_cbTcpStashed = 0; +/** The size of the stashed data allocation. */ +static size_t g_cbTcpStashedAlloced = 0; +/** The stashed data. */ +static uint8_t *g_pbTcpStashed = NULL; + + + +/** + * Disconnects the current client. + */ +static void txsTcpDisconnectClient(void) +{ + int rc; + if (g_fTcpClientFromServer) + rc = RTTcpServerDisconnectClient2(g_hTcpClient); + else + rc = RTTcpClientClose(g_hTcpClient); + AssertRCSuccess(rc); + g_hTcpClient = NIL_RTSOCKET; +} + +/** + * Sets the current client socket in a safe manner. + * + * @returns NIL_RTSOCKET if consumed, other wise hTcpClient. + * @param hTcpClient The client socket. + */ +static RTSOCKET txsTcpSetClient(RTSOCKET hTcpClient) +{ + RTCritSectEnter(&g_TcpCritSect); + if ( g_hTcpClient == NIL_RTSOCKET + && !g_fTcpStopConnecting + && g_hThreadMain != NIL_RTTHREAD + ) + { + g_fTcpClientFromServer = true; + g_hTcpClient = hTcpClient; + int rc = RTThreadUserSignal(g_hThreadMain); AssertRC(rc); + hTcpClient = NIL_RTSOCKET; + } + RTCritSectLeave(&g_TcpCritSect); + return hTcpClient; +} + +/** + * Server mode connection thread. + * + * @returns iprt status code. + * @param hSelf Thread handle. Ignored. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int) txsTcpServerConnectThread(RTTHREAD hSelf, void *pvUser) +{ + RTSOCKET hTcpClient; + int rc = RTTcpServerListen2(g_pTcpServer, &hTcpClient); + Log(("txsTcpConnectServerThread: RTTcpServerListen2 -> %Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + hTcpClient = txsTcpSetClient(hTcpClient); + RTTcpServerDisconnectClient2(hTcpClient); + } + + RT_NOREF2(hSelf, pvUser); + return rc; +} + +/** + * Checks if it's a fatal RTTcpClientConnect return code. + * + * @returns true / false. + * @param rc The IPRT status code. + */ +static bool txsTcpIsFatalClientConnectStatus(int rc) +{ + return rc != VERR_NET_UNREACHABLE + && rc != VERR_NET_HOST_DOWN + && rc != VERR_NET_HOST_UNREACHABLE + && rc != VERR_NET_CONNECTION_REFUSED + && rc != VERR_TIMEOUT + && rc != VERR_NET_CONNECTION_TIMED_OUT; +} + +/** + * Client mode connection thread. + * + * @returns iprt status code. + * @param hSelf Thread handle. Use to sleep on. The main thread will + * signal it to speed up thread shutdown. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int) txsTcpClientConnectThread(RTTHREAD hSelf, void *pvUser) +{ + RT_NOREF1(pvUser); + + for (;;) + { + /* Stop? */ + RTCritSectEnter(&g_TcpCritSect); + bool fStop = g_fTcpStopConnecting; + RTCritSectLeave(&g_TcpCritSect); + if (fStop) + return VINF_SUCCESS; + + /* Try connect. */ /** @todo make cancelable! */ + RTSOCKET hTcpClient; + Log2(("Calling RTTcpClientConnect(%s, %u,)...\n", g_szTcpConnectAddr, g_uTcpConnectPort)); + int rc = RTTcpClientConnectEx(g_szTcpConnectAddr, g_uTcpConnectPort, &hTcpClient, + RT_SOCKETCONNECT_DEFAULT_WAIT, &g_pTcpConnectCancelCookie); + Log(("txsTcpRecvPkt: RTTcpClientConnect -> %Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + hTcpClient = txsTcpSetClient(hTcpClient); + RTTcpClientCloseEx(hTcpClient, true /* fGracefulShutdown*/); + break; + } + + if (txsTcpIsFatalClientConnectStatus(rc)) + return rc; + + /* Delay a wee bit before retrying. */ + RTThreadUserWait(hSelf, 1536); + } + return VINF_SUCCESS; +} + +/** + * Wait on the threads to complete. + * + * @returns Thread status (if collected), otherwise VINF_SUCCESS. + * @param cMillies The period to wait on each thread. + */ +static int txsTcpConnectWaitOnThreads(RTMSINTERVAL cMillies) +{ + int rcRet = VINF_SUCCESS; + + if (g_hThreadTcpConnect != NIL_RTTHREAD) + { + int rcThread; + int rc2 = RTThreadWait(g_hThreadTcpConnect, cMillies, &rcThread); + if (RT_SUCCESS(rc2)) + { + g_hThreadTcpConnect = NIL_RTTHREAD; + rcRet = rcThread; + } + } + + if (g_hThreadTcpServer != NIL_RTTHREAD) + { + int rcThread; + int rc2 = RTThreadWait(g_hThreadTcpServer, cMillies, &rcThread); + if (RT_SUCCESS(rc2)) + { + g_hThreadTcpServer = NIL_RTTHREAD; + if (RT_SUCCESS(rc2)) + rcRet = rcThread; + } + } + return rcRet; +} + +/** + * Connects to the peer. + * + * @returns VBox status code. Updates g_hTcpClient and g_fTcpClientFromServer on + * success + */ +static int txsTcpConnect(void) +{ + int rc; + if (g_enmTcpMode == TXSTCPMODE_SERVER) + { + g_fTcpClientFromServer = true; + rc = RTTcpServerListen2(g_pTcpServer, &g_hTcpClient); + Log(("txsTcpRecvPkt: RTTcpServerListen2 -> %Rrc\n", rc)); + } + else if (g_enmTcpMode == TXSTCPMODE_CLIENT) + { + g_fTcpClientFromServer = false; + for (;;) + { + Log2(("Calling RTTcpClientConnect(%s, %u,)...\n", g_szTcpConnectAddr, g_uTcpConnectPort)); + rc = RTTcpClientConnect(g_szTcpConnectAddr, g_uTcpConnectPort, &g_hTcpClient); + Log(("txsTcpRecvPkt: RTTcpClientConnect -> %Rrc\n", rc)); + if (RT_SUCCESS(rc) || txsTcpIsFatalClientConnectStatus(rc)) + break; + + /* Delay a wee bit before retrying. */ + RTThreadSleep(1536); + } + } + else + { + Assert(g_enmTcpMode == TXSTCPMODE_BOTH); + RTTHREAD hSelf = RTThreadSelf(); + + /* + * Create client threads. + */ + RTCritSectEnter(&g_TcpCritSect); + RTThreadUserReset(hSelf); + g_hThreadMain = hSelf; + g_fTcpStopConnecting = false; + RTCritSectLeave(&g_TcpCritSect); + + txsTcpConnectWaitOnThreads(32); + + rc = VINF_SUCCESS; + if (g_hThreadTcpConnect == NIL_RTTHREAD) + { + g_pTcpConnectCancelCookie = NULL; + rc = RTThreadCreate(&g_hThreadTcpConnect, txsTcpClientConnectThread, NULL, 0, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "tcpconn"); + } + if (g_hThreadTcpServer == NIL_RTTHREAD && RT_SUCCESS(rc)) + rc = RTThreadCreate(&g_hThreadTcpServer, txsTcpServerConnectThread, NULL, 0, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "tcpserv"); + + RTCritSectEnter(&g_TcpCritSect); + + /* + * Wait for connection to be established. + */ + while ( RT_SUCCESS(rc) + && g_hTcpClient == NIL_RTSOCKET) + { + RTCritSectLeave(&g_TcpCritSect); + RTThreadUserWait(hSelf, 1536); + rc = txsTcpConnectWaitOnThreads(0); + RTCritSectEnter(&g_TcpCritSect); + } + + /* + * Cancel the threads. + */ + g_hThreadMain = NIL_RTTHREAD; + g_fTcpStopConnecting = true; + + RTCritSectLeave(&g_TcpCritSect); + RTTcpClientCancelConnect(&g_pTcpConnectCancelCookie); + } + + AssertMsg(RT_SUCCESS(rc) ? g_hTcpClient != NIL_RTSOCKET : g_hTcpClient == NIL_RTSOCKET, ("%Rrc %p\n", rc, g_hTcpClient)); + g_cbTcpStashed = 0; + return rc; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyReboot} + */ +static DECLCALLBACK(void) txsTcpNotifyReboot(void) +{ + Log(("txsTcpNotifyReboot: RTTcpServerDestroy(%p)\n", g_pTcpServer)); + if (g_pTcpServer) + { + int rc = RTTcpServerDestroy(g_pTcpServer); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDestroy failed in txsTcpNotifyReboot: %Rrc", rc); + g_pTcpServer = NULL; + } +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyBye} + */ +static DECLCALLBACK(void) txsTcpNotifyBye(void) +{ + Log(("txsTcpNotifyBye: txsTcpDisconnectClient %RTsock\n", g_hTcpClient)); + txsTcpDisconnectClient(); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyHowdy} + */ +static DECLCALLBACK(void) txsTcpNotifyHowdy(void) +{ + /* nothing to do here */ +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnBabble} + */ +static DECLCALLBACK(void) txsTcpBabble(PCTXSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout) +{ + /* + * Quietly ignore already disconnected client. + */ + RTSOCKET hTcpClient = g_hTcpClient; + if (hTcpClient == NIL_RTSOCKET) + return; + + /* + * Try send the babble reply. + */ + NOREF(cMsSendTimeout); /** @todo implement the timeout here; non-blocking write + select-on-write. */ + int rc; + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, TXSPKT_ALIGNMENT); + do rc = RTTcpWrite(hTcpClient, pPktHdr, cbToSend); + while (rc == VERR_INTERRUPTED); + + /* + * Disconnect the client. + */ + Log(("txsTcpBabble: txsTcpDisconnectClient(%RTsock) (RTTcpWrite rc=%Rrc)\n", g_hTcpClient, rc)); + txsTcpDisconnectClient(); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnSendPkt} + */ +static DECLCALLBACK(int) txsTcpSendPkt(PCTXSPKTHDR pPktHdr) +{ + Assert(pPktHdr->cb >= sizeof(TXSPKTHDR)); + + /* + * Fail if no client connection. + */ + RTSOCKET hTcpClient = g_hTcpClient; + if (hTcpClient == NIL_RTSOCKET) + return VERR_NET_NOT_CONNECTED; + + /* + * Write it. + */ + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, TXSPKT_ALIGNMENT); + int rc = RTTcpWrite(hTcpClient, pPktHdr, cbToSend); + if ( RT_FAILURE(rc) + && rc != VERR_INTERRUPTED) + { + /* assume fatal connection error. */ + Log(("RTTcpWrite -> %Rrc -> txsTcpDisconnectClient(%RTsock)\n", rc, g_hTcpClient)); + txsTcpDisconnectClient(); + } + + return rc; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnRecvPkt} + */ +static DECLCALLBACK(int) txsTcpRecvPkt(PPTXSPKTHDR ppPktHdr) +{ + int rc = VINF_SUCCESS; + *ppPktHdr = NULL; + + /* + * Do we have to wait for a client to connect? + */ + RTSOCKET hTcpClient = g_hTcpClient; + if (hTcpClient == NIL_RTSOCKET) + { + rc = txsTcpConnect(); + if (RT_FAILURE(rc)) + return rc; + hTcpClient = g_hTcpClient; Assert(hTcpClient != NIL_RTSOCKET); + } + + /* + * Read state. + */ + size_t offData = 0; + size_t cbData = 0; + size_t cbDataAlloced; + uint8_t *pbData = NULL; + + /* + * Any stashed data? + */ + if (g_cbTcpStashedAlloced) + { + offData = g_cbTcpStashed; + cbDataAlloced = g_cbTcpStashedAlloced; + pbData = g_pbTcpStashed; + + g_cbTcpStashed = 0; + g_cbTcpStashedAlloced = 0; + g_pbTcpStashed = NULL; + } + else + { + cbDataAlloced = RT_ALIGN_Z(64, TXSPKT_ALIGNMENT); + pbData = (uint8_t *)RTMemAlloc(cbDataAlloced); + if (!pbData) + return VERR_NO_MEMORY; + } + + /* + * Read and valid the length. + */ + while (offData < sizeof(uint32_t)) + { + size_t cbRead; + rc = RTTcpRead(hTcpClient, pbData + offData, sizeof(uint32_t) - offData, &cbRead); + if (RT_FAILURE(rc)) + break; + if (cbRead == 0) + { + Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#1)\n", rc)); + rc = VERR_NET_NOT_CONNECTED; + break; + } + offData += cbRead; + } + if (RT_SUCCESS(rc)) + { + ASMCompilerBarrier(); /* paranoia^3 */ + cbData = *(uint32_t volatile *)pbData; + if (cbData >= sizeof(TXSPKTHDR) && cbData <= TXSPKT_MAX_SIZE) + { + /* + * Align the length and reallocate the return packet it necessary. + */ + cbData = RT_ALIGN_Z(cbData, TXSPKT_ALIGNMENT); + if (cbData > cbDataAlloced) + { + void *pvNew = RTMemRealloc(pbData, cbData); + if (pvNew) + { + pbData = (uint8_t *)pvNew; + cbDataAlloced = cbData; + } + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + /* + * Read the remainder of the data. + */ + while (offData < cbData) + { + size_t cbRead; + rc = RTTcpRead(hTcpClient, pbData + offData, cbData - offData, &cbRead); + if (RT_FAILURE(rc)) + break; + if (cbRead == 0) + { + Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#2)\n", rc)); + rc = VERR_NET_NOT_CONNECTED; + break; + } + offData += cbRead; + } + } + } + else + rc = VERR_NET_PROTOCOL_ERROR; + } + if (RT_SUCCESS(rc)) + *ppPktHdr = (PTXSPKTHDR)pbData; + else + { + /* + * Deal with errors. + */ + if (rc == VERR_INTERRUPTED) + { + /* stash it away for the next call. */ + g_cbTcpStashed = cbData; + g_cbTcpStashedAlloced = cbDataAlloced; + g_pbTcpStashed = pbData; + } + else + { + RTMemFree(pbData); + + /* assume fatal connection error. */ + Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc -> txsTcpDisconnectClient(%RTsock)\n", rc, g_hTcpClient)); + txsTcpDisconnectClient(); + } + } + + return rc; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnPollSetAdd} + */ +static DECLCALLBACK(int) txsTcpPollSetAdd(RTPOLLSET hPollSet, uint32_t idStart) +{ + return RTPollSetAddSocket(hPollSet, g_hTcpClient, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, idStart); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnPollIn} + */ +static DECLCALLBACK(bool) txsTcpPollIn(void) +{ + RTSOCKET hTcpClient = g_hTcpClient; + if (hTcpClient == NIL_RTSOCKET) + return false; + int rc = RTTcpSelectOne(hTcpClient, 0/*cMillies*/); + return RT_SUCCESS(rc); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnTerm} + */ +static DECLCALLBACK(void) txsTcpTerm(void) +{ + /* Signal thread */ + if (RTCritSectIsInitialized(&g_TcpCritSect)) + { + RTCritSectEnter(&g_TcpCritSect); + g_fTcpStopConnecting = true; + RTCritSectLeave(&g_TcpCritSect); + } + + if (g_hThreadTcpConnect != NIL_RTTHREAD) + { + RTThreadUserSignal(g_hThreadTcpConnect); + RTTcpClientCancelConnect(&g_pTcpConnectCancelCookie); + } + + /* Shut down the server (will wake up thread). */ + if (g_pTcpServer) + { + Log(("txsTcpTerm: Destroying server...\n")); + int rc = RTTcpServerDestroy(g_pTcpServer); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDestroy failed in txsTcpTerm: %Rrc", rc); + g_pTcpServer = NULL; + } + + /* Shut down client */ + if (g_hTcpClient != NIL_RTSOCKET) + { + if (g_fTcpClientFromServer) + { + Log(("txsTcpTerm: Disconnecting client...\n")); + int rc = RTTcpServerDisconnectClient2(g_hTcpClient); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDisconnectClient2(%RTsock) failed in txsTcpTerm: %Rrc", g_hTcpClient, rc); + } + else + { + int rc = RTTcpClientClose(g_hTcpClient); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpClientClose(%RTsock) failed in txsTcpTerm: %Rrc", g_hTcpClient, rc); + } + g_hTcpClient = NIL_RTSOCKET; + } + + /* Clean up stashing. */ + RTMemFree(g_pbTcpStashed); + g_pbTcpStashed = NULL; + g_cbTcpStashed = 0; + g_cbTcpStashedAlloced = 0; + + /* Wait for the thread (they should've had some time to quit by now). */ + txsTcpConnectWaitOnThreads(15000); + + /* Finally, clean up the critical section. */ + if (RTCritSectIsInitialized(&g_TcpCritSect)) + RTCritSectDelete(&g_TcpCritSect); + + Log(("txsTcpTerm: done\n")); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnInit} + */ +static DECLCALLBACK(int) txsTcpInit(void) +{ + int rc = RTCritSectInit(&g_TcpCritSect); + if (RT_SUCCESS(rc) && g_enmTcpMode != TXSTCPMODE_CLIENT) + { + rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NET_DOWN) + { + RTMsgInfo("RTTcpServerCreateEx(%s, %u,) failed: %Rrc, retrying for 20 seconds...\n", + g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc); + uint64_t StartMs = RTTimeMilliTS(); + do + { + RTThreadSleep(1000); + rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer); + } while ( rc == VERR_NET_DOWN + && RTTimeMilliTS() - StartMs < 20000); + if (RT_SUCCESS(rc)) + RTMsgInfo("RTTcpServerCreateEx succceeded.\n"); + } + if (RT_FAILURE(rc)) + { + g_pTcpServer = NULL; + RTCritSectDelete(&g_TcpCritSect); + RTMsgError("RTTcpServerCreateEx(%s, %u,) failed: %Rrc\n", + g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc); + } + } + } + + return rc; +} + +/** Options */ +enum TXSTCPOPT +{ + TXSTCPOPT_MODE = 1000, + TXSTCPOPT_BIND_ADDRESS, + TXSTCPOPT_BIND_PORT, + TXSTCPOPT_CONNECT_ADDRESS, + TXSTCPOPT_CONNECT_PORT, + + /* legacy: */ + TXSTCPOPT_LEGACY_PORT, + TXSTCPOPT_LEGACY_CONNECT +}; + +/** + * @interface_method_impl{TXSTRANSPORT,pfnOption} + */ +static DECLCALLBACK(int) txsTcpOption(int ch, PCRTGETOPTUNION pVal) +{ + int rc; + + switch (ch) + { + case TXSTCPOPT_MODE: + if (!strcmp(pVal->psz, "both")) + g_enmTcpMode = TXSTCPMODE_BOTH; + else if (!strcmp(pVal->psz, "client")) + g_enmTcpMode = TXSTCPMODE_CLIENT; + else if (!strcmp(pVal->psz, "server")) + g_enmTcpMode = TXSTCPMODE_SERVER; + else + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid TCP mode: '%s'\n", pVal->psz); + return VINF_SUCCESS; + + case TXSTCPOPT_BIND_ADDRESS: + rc = RTStrCopy(g_szTcpBindAddr, sizeof(g_szTcpBindAddr), pVal->psz); + if (RT_FAILURE(rc)) + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP bind address is too long (%Rrc)", rc); + return VINF_SUCCESS; + + case TXSTCPOPT_BIND_PORT: + g_uTcpBindPort = pVal->u16 == 0 ? TXS_TCP_DEF_BIND_PORT : pVal->u16; + return VINF_SUCCESS; + + case TXSTCPOPT_LEGACY_CONNECT: + g_enmTcpMode = TXSTCPMODE_CLIENT; + RT_FALL_THRU(); + case TXSTCPOPT_CONNECT_ADDRESS: + rc = RTStrCopy(g_szTcpConnectAddr, sizeof(g_szTcpConnectAddr), pVal->psz); + if (RT_FAILURE(rc)) + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP connect address is too long (%Rrc)", rc); + if (!g_szTcpConnectAddr[0]) + strcpy(g_szTcpConnectAddr, TXS_TCP_DEF_CONNECT_ADDRESS); + return VINF_SUCCESS; + + case TXSTCPOPT_CONNECT_PORT: + g_uTcpConnectPort = pVal->u16 == 0 ? TXS_TCP_DEF_CONNECT_PORT : pVal->u16; + return VINF_SUCCESS; + + case TXSTCPOPT_LEGACY_PORT: + if (pVal->u16 == 0) + { + g_uTcpBindPort = TXS_TCP_DEF_BIND_PORT; + g_uTcpConnectPort = TXS_TCP_DEF_CONNECT_PORT; + } + else + { + g_uTcpBindPort = pVal->u16; + g_uTcpConnectPort = pVal->u16; + } + return VINF_SUCCESS; + } + return VERR_TRY_AGAIN; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnUsage} + */ +DECLCALLBACK(void) txsTcpUsage(PRTSTREAM pStream) +{ + RTStrmPrintf(pStream, + " --tcp-mode <both|client|server>\n" + " Selects the mode of operation.\n" + " Default: both\n" + " --tcp-bind-address <address>\n" + " The address(es) to listen to TCP connection on. Empty string\n" + " means any address, this is the default.\n" + " --tcp-bind-port <port>\n" + " The port to listen to TCP connections on.\n" + " Default: %u\n" + " --tcp-connect-address <address>\n" + " The address of the server to try connect to in client mode.\n" + " Default: " TXS_TCP_DEF_CONNECT_ADDRESS "\n" + " --tcp-connect-port <port>\n" + " The port on the server to connect to in client mode.\n" + " Default: %u\n" + , TXS_TCP_DEF_BIND_PORT, TXS_TCP_DEF_CONNECT_PORT); +} + +/** Command line options for the TCP/IP transport layer. */ +static const RTGETOPTDEF g_TcpOpts[] = +{ + { "--tcp-mode", TXSTCPOPT_MODE, RTGETOPT_REQ_STRING }, + { "--tcp-bind-address", TXSTCPOPT_BIND_ADDRESS, RTGETOPT_REQ_STRING }, + { "--tcp-bind-port", TXSTCPOPT_BIND_PORT, RTGETOPT_REQ_UINT16 }, + { "--tcp-connect-address", TXSTCPOPT_CONNECT_ADDRESS, RTGETOPT_REQ_STRING }, + { "--tcp-connect-port", TXSTCPOPT_CONNECT_PORT, RTGETOPT_REQ_UINT16 }, + + /* legacy */ + { "--tcp-port", TXSTCPOPT_LEGACY_PORT, RTGETOPT_REQ_UINT16 }, + { "--tcp-connect", TXSTCPOPT_LEGACY_CONNECT, RTGETOPT_REQ_STRING }, +}; + +/** TCP/IP transport layer. */ +const TXSTRANSPORT g_TcpTransport = +{ + /* .szName = */ "tcp", + /* .pszDesc = */ "TCP/IP", + /* .cOpts = */ &g_TcpOpts[0], + /* .paOpts = */ RT_ELEMENTS(g_TcpOpts), + /* .pfnUsage = */ txsTcpUsage, + /* .pfnOption = */ txsTcpOption, + /* .pfnInit = */ txsTcpInit, + /* .pfnTerm = */ txsTcpTerm, + /* .pfnPollIn = */ txsTcpPollIn, + /* .pfnPollSetAdd = */ txsTcpPollSetAdd, + /* .pfnRecvPkt = */ txsTcpRecvPkt, + /* .pfnSendPkt = */ txsTcpSendPkt, + /* .pfnBabble = */ txsTcpBabble, + /* .pfnNotifyHowdy = */ txsTcpNotifyHowdy, + /* .pfnNotifyBye = */ txsTcpNotifyBye, + /* .pfnNotifyReboot = */ txsTcpNotifyReboot, + /* .u32EndMarker = */ UINT32_C(0x12345678) +}; + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-nat.sh b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-nat.sh new file mode 100755 index 00000000..7c082f27 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-nat.sh @@ -0,0 +1,164 @@ +#!/bin/sh +## @file +# VirtualBox Test Execution Service Init Script for NATted setups. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +# chkconfig: 35 35 65 +# description: VirtualBox Test Execution Service +# +### BEGIN INIT INFO +# Provides: vboxtxs +# Required-Start: $network +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Description: VirtualBox Test Execution Service +### END INIT INFO + +PATH=$PATH:/bin:/sbin:/usr/sbin +SCRIPTNAME=vboxtxs-nat.sh + +CDROM_PATH=/media/cdrom +SCRATCH_PATH=/tmp/vboxtxs-scratch + +PIDFILE="/var/run/vboxtxs" + +# Preamble for Gentoo +if [ "`which $0`" = "/sbin/rc" ]; then + shift +fi + +begin_msg() +{ + test -n "${2}" && echo "${SCRIPTNAME}: ${1}." + logger -t "${SCRIPTNAME}" "${1}." +} + +succ_msg() +{ + logger -t "${SCRIPTNAME}" "${1}." +} + +fail_msg() +{ + echo "${SCRIPTNAME}: failed: ${1}." >&2 + logger -t "${SCRIPTNAME}" "failed: ${1}." +} + +killproc() { + kp_binary="${1##*/}" + pkill "${kp_binary}" || return 0 + sleep 1 + pkill "${kp_binary}" || return 0 + sleep 1 + pkill -9 "${kp_binary}" + return 0 +} + +case "`uname -m`" in + AMD64|amd64|X86_64|x86_64) + binary=/opt/validationkit/linux/amd64/TestExecService + ;; + + i386|x86|i486|i586|i686|i787) + binary=/opt/validationkit/linux/x86/TestExecService + ;; + + *) + binary=/opt/validationkit/linux/x86/TestExecService + ;; +esac + +fixAndTestBinary() { + chmod a+x "$binary" 2> /dev/null > /dev/null + test -x "$binary" || { + echo "Cannot run $binary" + exit 1 + } +} + +start() { + if ! test -f $PIDFILE; then + begin_msg "Starting VirtualBox Test Execution Service" console + fixAndTestBinary + mount /dev/cdrom "${CDROM_PATH}" 2> /dev/null > /dev/null + $binary --auto-upgrade --scratch="${SCRATCH_PATH}" --cdrom="${CDROM_PATH}" \ + --no-display-output --tcp-connect 10.0.2.2 > /dev/null + RETVAL=$? + test $RETVAL -eq 0 && sleep 2 && echo `pidof TestExecService` > $PIDFILE + if ! test -s "${PIDFILE}"; then + RETVAL=5 + fi + if test $RETVAL -eq 0; then + succ_msg "VirtualBox Test Execution service started" + else + fail_msg "VirtualBox Test Execution service failed to start" + fi + fi + return $RETVAL +} + +stop() { + if test -f $PIDFILE; then + begin_msg "Stopping VirtualBox Test Execution Service" console + killproc $binary + fi +} + +restart() { + stop && start +} + +status() { + echo -n "Checking for vboxtxs" + if [ -f $PIDFILE ]; then + echo " ...running" + else + echo " ...not running" + fi +} + +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + restart + ;; +status) + status + ;; +setup) + ;; +cleanup) + ;; +*) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $RETVAL diff --git a/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.desktop b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.desktop new file mode 100644 index 00000000..95e7ddcb --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Application +Encoding=UTF-8 +Name=vboxtxs +Exec=sudo sh -c "/opt/validationkit/linux/vboxtxs-runvm start" + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.sh b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.sh new file mode 100755 index 00000000..67261eae --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.sh @@ -0,0 +1,198 @@ +#!/bin/sh +## @file +# VirtualBox Test Execution Service Init Script. +# + +# +# Copyright (C) 2018-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +# chkconfig: 35 35 65 +# description: VirtualBox Test Execution Service +# +### BEGIN INIT INFO +# Provides: vboxtxs-runvm +# Required-Start: $ALL +# Required-Stop: +# Default-Start: 5 +# Default-Stop: 0 1 6 +# Description: VirtualBox Test Execution Service +### END INIT INFO + +PATH=$PATH:/bin:/sbin:/usr/sbin +SCRIPTNAME=vboxtxs-runvm.sh + +CDROM_PATH=/media/cdrom +SCRATCH_PATH=/tmp/vboxtxs-scratch +SMOKEOUTPUT_PATH=/tmp/vboxtxs-smoketestoutput +DEVKMSG_PATH=/dev/kmsg + +PIDFILE="/var/run/vboxtxs" + +export TESTBOX_PATH_RESOURCES="/home/vbox/testrsrc" +SMOKETEST_SCRIPT="/opt/validationkit/tests/smoketests/tdSmokeTest1.py" +PYTHON_BINARY="python" + +# Preamble for Gentoo +if [ "`which $0`" = "/sbin/rc" ]; then + shift +fi + +kernlog_msg() { + test -n "$2" && echo "${SCRIPTNAME}: ${1}" + echo "${SCRIPTNAME}: ${1}" > $DEVKMSG_PATH +} + +dumpfile_to_kernlog() { + if test -f "$1"; then + kernlog_msg "---------------------- DUMP BEGIN ----------------------" + cat "$1" | while read LINE + do + kernlog_msg "${LINE}" + done + kernlog_msg "---------------------- DUMP END ------------------------" + rm -f "$1" + else + kernlog_msg "${1}: file not found" console + fi +} + +killproc() +{ + kp_binary="${1##*/}" + pkill "${kp_binary}" || return 0 + sleep 1 + pkill "${kp_binary}" || return 0 + sleep 1 + pkill -9 "${kp_binary}" + return 0 +} + +case "`uname -m`" in + AMD64|amd64|X86_64|x86_64) + binary=/opt/validationkit/linux/amd64/TestExecService + ;; + + i386|x86|i486|i586|i686|i787) + binary=/opt/validationkit/linux/x86/TestExecService + ;; + + *) + binary=/opt/validationkit/linux/x86/TestExecService + ;; +esac + +fixAndTestBinary() { + chmod a+x "$binary" 2> /dev/null > /dev/null + test -x "$binary" || { + echo "Cannot run $binary" + exit 1 + } +} + +testRsrcPath() { + test -d "$TESTBOX_PATH_RESOURCES" || { + echo "TESTBOX_PATH_RESOURCES directory not found" + exit 1 + } +} + +start() { + if ! test -f $PIDFILE; then + kernlog_msg "Starting Nested Smoke Test" console + fixAndTestBinary + testRsrcPath + $PYTHON_BINARY $SMOKETEST_SCRIPT -v -v -d --vbox-session-type gui --nic-attachment nat --quick all 1> "${SMOKEOUTPUT_PATH}" 2>&1 + RETVAL=$? + dumpfile_to_kernlog "${SMOKEOUTPUT_PATH}" + sync + sleep 15 + if test $RETVAL -eq 0; then + kernlog_msg "Nested Smoke Test done; Starting Test Execution service" console + mkdir -p "${CDROM_PATH}" + mount -o ro /dev/cdrom "${CDROM_PATH}" 2> /dev/null > /dev/null + $binary --auto-upgrade --scratch="${SCRATCH_PATH}" --cdrom="${CDROM_PATH}" --no-display-output > /dev/null + RETVAL=$? + test $RETVAL -eq 0 && sleep 3 && echo `pidof TestExecService` > $PIDFILE + if ! test -s "${PIDFILE}"; then + RETVAL=5 + fi + if test $RETVAL -eq 0; then + kernlog_msg "Test Execution service started" console + else + kernlog_msg "Test Execution service failed to start" console + RETVAL=6 + fi + else + kernlog_msg "Smoke Test failed! error code ${RETVAL}" console + RETVAL=7 + fi + else + kernlog_msg "Starting Nested Smoke Test failed! Pidfile ${PIDFILE} exists" console + RETVAL=9 + fi + return $RETVAL +} + +stop() { + if test -f $PIDFILE; then + kernlog_msg "Stopping Test Execution Service" + killproc $binary + rm -f $PIDFILE + fi +} + +restart() { + stop && start +} + +status() { + echo -n "Checking for vboxtxs" + if [ -f $PIDFILE ]; then + echo " ...running" + else + echo " ...not running" + fi +} + +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + restart + ;; +status) + status + ;; +setup) + ;; +cleanup) + ;; +*) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $RETVAL + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.service b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.service new file mode 100644 index 00000000..b61426e9 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.service @@ -0,0 +1,18 @@ +[Unit] +Description=VirtualBox Test Execution Service +SourcePath=/opt/validationkit/linux/vboxtxs + +[Service] +Type=forking +Restart=no +TimeoutSec=5min +IgnoreSIGPIPE=no +KillMode=process +GuessMainPID=no +RemainAfterExit=yes +ExecStart=/opt/validationkit/linux/vboxtxs start +ExecStop=/opt/validationkit/linux/vboxtxs stop + +[Install] +WantedBy=multi-user.target + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.sh b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.sh new file mode 100755 index 00000000..0e79494e --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.sh @@ -0,0 +1,163 @@ +#!/bin/sh +## @file +# VirtualBox Test Execution Service Init Script. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +# chkconfig: 35 35 65 +# description: VirtualBox Test Execution Service +# +### BEGIN INIT INFO +# Provides: vboxtxs +# Required-Start: $network +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Description: VirtualBox Test Execution Service +### END INIT INFO + +PATH=$PATH:/bin:/sbin:/usr/sbin +SCRIPTNAME=vboxtxs.sh + +CDROM_PATH=/media/cdrom +SCRATCH_PATH=/tmp/vboxtxs-scratch + +PIDFILE="/var/run/vboxtxs" + +# Preamble for Gentoo +if [ "`which $0`" = "/sbin/rc" ]; then + shift +fi + +begin_msg() +{ + test -n "${2}" && echo "${SCRIPTNAME}: ${1}." + logger -t "${SCRIPTNAME}" "${1}." +} + +succ_msg() +{ + logger -t "${SCRIPTNAME}" "${1}." +} + +fail_msg() +{ + echo "${SCRIPTNAME}: failed: ${1}." >&2 + logger -t "${SCRIPTNAME}" "failed: ${1}." +} + +killproc() { + kp_binary="${1##*/}" + pkill "${kp_binary}" || return 0 + sleep 1 + pkill "${kp_binary}" || return 0 + sleep 1 + pkill -9 "${kp_binary}" + return 0 +} + +case "`uname -m`" in + AMD64|amd64|X86_64|x86_64) + binary=/opt/validationkit/linux/amd64/TestExecService + ;; + + i386|x86|i486|i586|i686|i787) + binary=/opt/validationkit/linux/x86/TestExecService + ;; + + *) + binary=/opt/validationkit/linux/x86/TestExecService + ;; +esac + +fixAndTestBinary() { + chmod a+x "$binary" 2> /dev/null > /dev/null + test -x "$binary" || { + echo "Cannot run $binary" + exit 1 + } +} + +start() { + if ! test -f $PIDFILE; then + begin_msg "Starting VirtualBox Test Execution Service" console + fixAndTestBinary + mount /dev/cdrom "${CDROM_PATH}" 2> /dev/null > /dev/null + $binary --auto-upgrade --scratch="${SCRATCH_PATH}" --cdrom="${CDROM_PATH}" --no-display-output > /dev/null + RETVAL=$? + test $RETVAL -eq 0 && sleep 2 && echo `pidof TestExecService` > $PIDFILE + if ! test -s "${PIDFILE}"; then + RETVAL=5 + fi + if test $RETVAL -eq 0; then + succ_msg "VirtualBox Test Execution service started" + else + fail_msg "VirtualBox Test Execution service failed to start" + fi + fi + return $RETVAL +} + +stop() { + if test -f $PIDFILE; then + begin_msg "Stopping VirtualBox Test Execution Service" console + killproc $binary + fi +} + +restart() { + stop && start +} + +status() { + echo -n "Checking for vboxtxs" + if [ -f $PIDFILE ]; then + echo " ...running" + else + echo " ...not running" + fi +} + +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + restart + ;; +status) + status + ;; +setup) + ;; +cleanup) + ;; +*) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $RETVAL diff --git a/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs-sol10.xml b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs-sol10.xml new file mode 100644 index 00000000..a41e23fa --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs-sol10.xml @@ -0,0 +1,73 @@ +<?xml version='1.0'?> +<!-- + Solaris SMF service manifest for the VirtualBox Test eXecution Service. + $Id: vboxtxs-sol10.xml $ + + Copyright (C) 2010-2017 Oracle Corporation + + This file is part of VirtualBox Open Source Edition (OSE), as + available from http://www.virtualbox.org. This file is free software; + you can redistribute it and/or modify it under the terms of the GNU + General Public License (GPL) as published by the Free Software + Foundation, in version 2 as it comes in the "COPYING" file of the + VirtualBox OSE distribution. VirtualBox OSE is distributed in the + hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + + The contents of this file may alternatively be used under the terms + of the Common Development and Distribution License Version 1.0 + (CDDL) only, as it comes in the "COPYING.CDDL" file of the + VirtualBox OSE distribution, in which case the provisions of the + CDDL are applicable instead of those of the GPL. + + You may elect to license modified versions of this file under the + terms and conditions of either the GPL or the CDDL or both. +--> +<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> + +<service_bundle type='manifest' name='export'> +<service name='system/virtualbox/vboxtxs' type='service' version='1'> + + <create_default_instance enabled='false' /> + <single_instance/> + + <!-- Wait for the network to start up --> + <dependency name='milestone-network' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/network:default' /> + </dependency> + + <!-- Wait for devices to be initialized as we depend on vboxguest (PCI) --> + <dependency name='milestone-devices' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/devices:default' /> + </dependency> + + <!-- We wish to be started as late as possible... so go crazy with deps. --> + <dependency name='multi-user' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user:default' /> + </dependency> + <dependency name='multi-user-server' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user-server:default' /> + </dependency> + <dependency name='filesystem-local' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/local:default' /> + </dependency> + <dependency name='filesystem-autofs' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/autofs:default' /> + </dependency> + <dependency name='filesystem-volfs' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/volfs:default' /> + </dependency> + + <!-- start + stop methods --> + <exec_method type='method' name='start' exec='/opt/VBoxTest/testsuite/solaris/vboxtxs.sh' timeout_seconds='30' /> + <exec_method type='method' name='stop' exec=':kill' timeout_seconds='60' /> + + <!-- Description --> + <template> + <common_name> + <loctext xml:lang='C'>VirtualBox Test eXecution Service</loctext> + </common_name> + </template> +</service> + +</service_bundle> + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.sh b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.sh new file mode 100755 index 00000000..cb095362 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.sh @@ -0,0 +1,54 @@ +#!/bin/sh +## @file +# VirtualBox Test Execution Service Architecture Wrapper for Solaris. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +# 1. Change directory to the script directory (usually /opt/VBoxTest/). +set -x +MY_DIR=`dirname "$0"` +cd "${MY_DIR}" + +# 2. Determine the architecture. +MY_ARCH=`isainfo -k` +case "${MY_ARCH}" in + amd64) + MY_ARCH=amd64 + ;; + i386) + MY_ARCH=x86 + ;; + *) + echo "vboxtxs.sh: Unsupported architecture '${MY_ARCH}' returned by isainfo -k." >&2 + exit 2; + ;; +esac + +# 3. Exec the service. +exec "./${MY_ARCH}/TestExecService" \ + --cdrom="/cdrom/cdrom0/" \ + --scratch="/var/tmp/VBoxTest/" \ + --no-display-output \ + $* +exit 3; + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.xml b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.xml new file mode 100644 index 00000000..c6cb530b --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.xml @@ -0,0 +1,73 @@ +<?xml version='1.0'?> +<!-- + Solaris SMF service manifest for the VirtualBox Test eXecution Service. + $Id: vboxtxs.xml $ + + Copyright (C) 2010-2017 Oracle Corporation + + This file is part of VirtualBox Open Source Edition (OSE), as + available from http://www.virtualbox.org. This file is free software; + you can redistribute it and/or modify it under the terms of the GNU + General Public License (GPL) as published by the Free Software + Foundation, in version 2 as it comes in the "COPYING" file of the + VirtualBox OSE distribution. VirtualBox OSE is distributed in the + hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + + The contents of this file may alternatively be used under the terms + of the Common Development and Distribution License Version 1.0 + (CDDL) only, as it comes in the "COPYING.CDDL" file of the + VirtualBox OSE distribution, in which case the provisions of the + CDDL are applicable instead of those of the GPL. + + You may elect to license modified versions of this file under the + terms and conditions of either the GPL or the CDDL or both. +--> +<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> + +<service_bundle type='manifest' name='export'> +<service name='system/virtualbox/vboxtxs' type='service' version='1'> + + <create_default_instance enabled='false' /> + <single_instance/> + + <!-- Wait for the network to start up --> + <dependency name='milestone-network' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/network:default' /> + </dependency> + + <!-- Wait for devices to be initialized as we depend on vboxguest (PCI) --> + <dependency name='milestone-devices' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/devices:default' /> + </dependency> + + <!-- We wish to be started as late as possible... so go crazy with deps. --> + <dependency name='multi-user' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user:default' /> + </dependency> + <dependency name='multi-user-server' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user-server:default' /> + </dependency> + <dependency name='filesystem-local' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/local:default' /> + </dependency> + <dependency name='filesystem-autofs' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/autofs:default' /> + </dependency> + <dependency name='filesystem-rmvolmgr' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/rmvolmgr:default' /> + </dependency> + + <!-- start + stop methods --> + <exec_method type='method' name='start' exec='/opt/VBoxTest/testsuite/solaris/vboxtxs.sh' timeout_seconds='30' /> + <exec_method type='method' name='stop' exec=':kill' timeout_seconds='60' /> + + <!-- Description --> + <template> + <common_name> + <loctext xml:lang='C'>VirtualBox Test eXecution Service</loctext> + </common_name> + </template> +</service> + +</service_bundle> + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-readme.txt b/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-readme.txt new file mode 100644 index 00000000..06debea8 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-readme.txt @@ -0,0 +1,124 @@ +$Id: vboxtxs-readme.txt $ + + +VirtualBox Test eXecution Service +================================= + +This readme briefly describes how to install the Test eXecution Service (TXS) +on the various systems. + +There are currently two transport options for the TXS: + + - The default is to use it in TCP server mode, i.e. the test script needs + to know the guest's IP and therefore requires guest additions to be + installed as well. (Please use the latest stable additions compatible with + the VBox host versions you intend to test.) + + - The alternative is for NATted setups where TXS will act like a TCP client + and try connect to the test script on the host. Since this require that + TXS knows which IP to connect to, it's only really possible in a NATted + setup where we know the host IP is 10.0.2.2. + +Since r85596 TXS operates in both modes by default so the nat version of +the init scripts is not required anymore. Instead the other type can be installed +for both cases. + +Linux Installation +------------------ + +1. cd /root +2. scp/download VBoxValidationKit*.zip there. +3. unzip VBoxValidationKit*.zip +4. chmod -R u+w,a+x /opt/validationkit/ +5. cd /etc/init.d/ +6a. init.rc: Link up the right init script (see connection type above): + nat) ln -s ../../opt/validationkit/linux/vboxtxs-nat ./vboxtxs + other) ln -s ../../opt/validationkit/linux/vboxtxs ./vboxtxs +6b. systemd: Link/copy up the vboxtxs.system to [/usr]/lib/systemd/. +7a. init.rc: Add vboxtxs to runlevels 2, 3, 5 and any other that makes sense + on the distro. There is usually some command for doing this... +7b: systemd: Enable the vboxtxs service. +8. Check the cdrom location in vboxtxs and fix it so it's correct, make sure + to update in svn as well. +9. reboot / done. + + +OS/2 Installation +-------------------- + +1. Start an "OS/2 Window" ("OS/2 System" -> "Command Prompts") +2. md C:\Apps +3. cd C:\Apps +4. Mount the validationkit iso. +5. copy D:\os2\x86\* C:\Apps +5. copy D:\os2\x86\libc*.dll C:\OS2\DLL\ +6. Open C:\startup.cmd in an editor (tedit.exe for instance or e.exe). +7. Add the line "start /C C:\Apps\TestExecService.exe --foreground" at the top of the file. +8. reboot / done +9. Do test. + + +Solaris Installation +-------------------- + +1. Start the guest and open a root console. +2. mkdir -p /opt/VBoxTest +3. cd /opt/VBoxTest +4. scp/download VBoxValidationKit*.zip there. +5. unzip VBoxValidationKit*.zip +6. chmod -R u+w,a+x /opt/VBoxTest/ +7. Import the right service setup depending on the Solaris version: + <= 10u9) /usr/sbin/svccfg import /opt/VBoxTest/validationkit/solaris/vboxtxs-sol10.xml + >= 11.0) /usr/sbin/svccfg import /opt/VBoxTest/validationkit/solaris/vboxtxs.xml +8. /usr/sbin/svcadm enable svc:/system/virtualbox/vboxtxs +9. reboot / done. + +To remove the service before repeating steps 7 & 8: +1. /usr/sbin/svcadm disable -s svc:/system/virtualbox/vboxtxs:default +2. /usr/sbin/svccfg delete svc:/system/virtualbox/vboxtxs:default + +Note. To configure dhcp for more a new interface the files +/etc/hostname.<if#X> and /etc/dhcp.<ifnm#> have to exist. If you want the VM +to work with any network card you throw at it, create /etc/*.pcn[01] and +/etc/*.e1000g[012] as Solaris will remember it has seen the other variants +before and use a different instance number (or something to that effect). + + +Windows Installation +-------------------- + +1. Log on as Administrator. +2. Set password to 'password'. +3. Start CMD.EXE or equivalent. +4. md C:\Apps +5. cd C:\Apps +6. Mount the validationkit iso. +7. copy D:\win\* C:\Apps +8. copy D:\win\<x86 or amd64>\* C:\Apps +9. Import the right service setup (see connection type above): + nat) start C:\Apps\vboxtxs-nat.reg + other) start C:\Apps\vboxtxs.reg +11. reboot / done +12. Do test. + +NT 3.1 and 3.x tricks: +- Make sure the file system is NTFS. Observed issues converting 2GB partitions, + more success with smaller. +- For NT3.1 PCNET drivers can be found on the net. No DHCP, so NAT only with + IP 10.0.2.15, 10.0.2.2 as gateway, and 10.0.2.3 as DNS with --natdnsproxy1 on. +- On NT3.1 you need to add SystemDrive=C: to the environment. +- Need to perform registry edits manually. +- Use startup folder instead of non-exising Windows/Run key. + + +Testing the setup +----------------- + +1. Make sure the validationkit.iso is inserted. +2. Boot / reboot the guest. +3. Depending on the TXS transport options: + nat) python testdriver/tst-txsclient.py --reversed-setup + other) python testdriver/tst-txsclient.py --hostname <guest-ip> + + + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-runvm-readme.txt b/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-runvm-readme.txt new file mode 100644 index 00000000..f8745391 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-runvm-readme.txt @@ -0,0 +1,54 @@ +$Id: vboxtxs-runvm-readme.txt $ + + +VirtualBox Test eXecution Service +================================= + +This readme briefly describes how to install the Test eXecution Service (TXS) +for nested hardware-virtualization smoke testing on the various systems. + +The basic idea is to execute one smoke test within the VM and then launch +the regular TXS service in the VM to report success or failure to the host. + +Linux Installation +------------------ + +1. scp/download latest release build of VirtualBox and install it in the VM. +2. scp/download the required smoke test VDI from remote test-resource to + /home/vbox/testrsrc/3.0/tcp/win2k3ent-acpi.vdi +3. cd /root +3. scp/download VBoxValidationKit*.zip there. +5. unzip VBoxValidationKit*.zip +6. chmod -R u+w,a+x /opt/validationkit/ +7a. Gnome: Copy /opt/validationkit/linux/vboxtxs-runvm.desktop to /etc/xdg/autostart +7b. KDE/Others: TODO: Document other desktop managers +8. Add the vbox user to sudo group using: + sudo usermod -a -G sudo vbox +9. Ensure no password is required for vbox when using sudo: + sudo echo "vbox ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/username +10. Check the cdrom location and /dev/kmsg equivalent of your linux distro + in /opt/validationkit/linux/vboxtxs-runvm and fix it so it's correct +11. Reboot / done. + +TODO: Document other OSes as we add them. + +Note: vboxtxs-runvm uses a GUI session to launch the nested-VM for better +visibility when troubleshooting the nested smoke test. + +If this causes problems try troubleshooting the XAUTHORITY and DISPLAY +environment variables in vboxtxs-runvm.service. It might differ depending +on the display manager of the particular linux distro. + + + +Testing the setup +----------------- + +1. Make sure the validationkit.iso is inserted. +2. Boot / reboot the guest. +3. To test the connection - Depending on the TXS transport options: + nat) python testdriver/tst-txsclient.py --reversed-setup + other) python testdriver/tst-txsclient.py --hostname <guest-ip> +4. To test the smoke test: + python tests/smoketests/tdSmokeTest1.py -v -v -d --vbox-session-type gui --test-vms <guest-name> + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.cmd b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.cmd new file mode 100644 index 00000000..7d134338 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.cmd @@ -0,0 +1,31 @@ +@REM REM @file
+@REM VirtualBox Test Execution Service Init Script for NATted VMs.
+@REM
+
+@REM
+REM
+REM Copyright (C) 2006-2019 Oracle Corporation
+REM
+REM This file is part of VirtualBox Open Source Edition (OSE), as
+REM available from http://www.virtualbox.org. This file is free software;
+REM you can redistribute it and/or modify it under the terms of the GNU
+REM General Public License (GPL) as published by the Free Software
+REM Foundation, in version 2 as it comes in the "COPYING" file of the
+REM VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+REM hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+REM
+REM The contents of this file may alternatively be used under the terms
+REM of the Common Development and Distribution License Version 1.0
+REM (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+REM VirtualBox OSE distribution, in which case the provisions of the
+REM CDDL are applicable instead of those of the GPL.
+REM
+REM You may elect to license modified versions of this file under the
+REM terms and conditions of either the GPL or the CDDL or both.
+REM
+
+%SystemDrive%\Apps\TestExecService.exe --foreground --display-output ^
+--cdrom D:\ --scratch C:\Temp\vboxtest --auto-upgrade ^
+--tcp-connect 10.0.2.2
+pause
+
diff --git a/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.reg b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.reg new file mode 100644 index 00000000..1102b6ec --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.reg @@ -0,0 +1,12 @@ +REGEDIT4
+
+[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]
+"PowerdownAfterShutdown"="1"
+"AutoAdminLogon"="1"
+"ForceAutoLogon"="1"
+"DefaultUserName"="Administrator"
+"DefaultPassword"="password"
+
+[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
+"NTConfiguration"="c:\\Apps\\vboxtxs-nat.cmd"
+
diff --git a/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.cmd b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.cmd new file mode 100644 index 00000000..de03e5e5 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.cmd @@ -0,0 +1,30 @@ +@REM REM @file
+@REM VirtualBox Test Execution Service Init Script.
+@REM
+
+@REM
+REM
+REM Copyright (C) 2006-2019 Oracle Corporation
+REM
+REM This file is part of VirtualBox Open Source Edition (OSE), as
+REM available from http://www.virtualbox.org. This file is free software;
+REM you can redistribute it and/or modify it under the terms of the GNU
+REM General Public License (GPL) as published by the Free Software
+REM Foundation, in version 2 as it comes in the "COPYING" file of the
+REM VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+REM hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+REM
+REM The contents of this file may alternatively be used under the terms
+REM of the Common Development and Distribution License Version 1.0
+REM (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+REM VirtualBox OSE distribution, in which case the provisions of the
+REM CDDL are applicable instead of those of the GPL.
+REM
+REM You may elect to license modified versions of this file under the
+REM terms and conditions of either the GPL or the CDDL or both.
+REM
+
+%SystemDrive%\Apps\TestExecService.exe --foreground --display-output ^
+--cdrom D:\ --scratch C:\Temp\vboxtest --auto-upgrade
+pause
+
diff --git a/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.reg b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.reg new file mode 100644 index 00000000..3253bd76 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.reg @@ -0,0 +1,12 @@ +REGEDIT4
+
+[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]
+"PowerdownAfterShutdown"="1"
+"AutoAdminLogon"="1"
+"ForceAutoLogon"="1"
+"DefaultUserName"="Administrator"
+"DefaultPassword"="password"
+
+[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
+"NTConfiguration"="c:\\Apps\\vboxtxs.cmd"
+
diff --git a/src/VBox/ValidationKit/utils/audio/Makefile.kmk b/src/VBox/ValidationKit/utils/audio/Makefile.kmk new file mode 100644 index 00000000..f236b7dd --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/Makefile.kmk @@ -0,0 +1,38 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Audio Utilities. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Utility to play sine wave to Default Audio Device. +# +PROGRAMS.win += ntPlayToneWaveX +ntPlayToneWaveX_TEMPLATE = VBoxValidationKitR3 +ntPlayToneWaveX_SOURCES = ntPlayToneWaveX.cpp + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/audio/ntPlayToneWaveX.cpp b/src/VBox/ValidationKit/utils/audio/ntPlayToneWaveX.cpp new file mode 100644 index 00000000..c713eb33 --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/ntPlayToneWaveX.cpp @@ -0,0 +1,216 @@ +/* $Id: ntPlayToneWaveX.cpp $ */ +/** @file + * ???? + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> + +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/errcore.h> + +#define _USE_MATH_DEFINES +#include <math.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +uint32_t g_cSamplesPerSec = 44100; +uint32_t g_cSamplesPerPeriod = 100; // 441.0Hz for 44.1kHz +uint32_t g_cSamplesInBuffer = 4096; +double g_rdSecDuration = 5.0; + +uint32_t g_cbSample; // assuming 16-bit stereo (for now) + +HWAVEOUT g_hWaveOut; +HANDLE g_hWavEvent; + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + static const RTGETOPTDEF s_aOptions[] = + { + { "--samples-per-sec", 's', RTGETOPT_REQ_UINT32 }, + { "--period-in-samples", 'p', RTGETOPT_REQ_UINT32 }, + { "--bufsize-in-samples", 'b', RTGETOPT_REQ_UINT32 }, + { "--total-duration-in-secs", 'd', RTGETOPT_REQ_UINT32 } + }; + + RTGETOPTSTATE State; + RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + RTGETOPTUNION ValueUnion; + int chOpt; + while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0) + { + switch (chOpt) + { + case 's': g_cSamplesPerSec = ValueUnion.u32; break; + case 'p': g_cSamplesPerPeriod = ValueUnion.u32; break; + case 'b': g_cSamplesInBuffer = ValueUnion.u32; break; + case 'd': g_rdSecDuration = ValueUnion.u32; break; + case 'h': + RTPrintf("usage: ntPlayToneWaveX.exe\n" + "[-s|--samples-per-sec]\n" + "[-p|--period-in-samples]\n" + "[-b|--bufsize-in-samples]\n" + "[-d|--total-duration-in-secs]\n" + "\n" + "Plays sine tone using ancient waveX API\n"); + return 0; + + default: + return RTGetOptPrintError(chOpt, &ValueUnion); + } + } + + + WAVEFORMATEX waveFormatEx = { 0 }; + MMRESULT mmresult; + + waveFormatEx.wFormatTag = WAVE_FORMAT_PCM; + waveFormatEx.nChannels = 2; + waveFormatEx.nSamplesPerSec = g_cSamplesPerSec; + waveFormatEx.wBitsPerSample = 16; + waveFormatEx.nBlockAlign = g_cbSample = waveFormatEx.nChannels * waveFormatEx.wBitsPerSample / 8; + waveFormatEx.nAvgBytesPerSec = waveFormatEx.nBlockAlign * waveFormatEx.nSamplesPerSec; + waveFormatEx.cbSize = 0; + + g_hWavEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + mmresult = waveOutOpen(&g_hWaveOut, WAVE_MAPPER, &waveFormatEx, (DWORD_PTR)g_hWavEvent, NULL, CALLBACK_EVENT); + + if (mmresult != MMSYSERR_NOERROR) + { + RTMsgError("waveOutOpen failed with 0x%X\n", mmresult); + return -1; + } + + + uint32_t ui32SamplesToPlayTotal = (uint32_t)(g_rdSecDuration * g_cSamplesPerSec); + uint32_t ui32SamplesToPlay = ui32SamplesToPlayTotal; + uint32_t ui32SamplesPlayed = 0; + uint32_t ui32SamplesForWavBuf; + + WAVEHDR waveHdr1 = {0}, waveHdr2 = {0}, *pWaveHdr, *pWaveHdrPlaying, *pWaveHdrWaiting; + uint32_t i, k; + DWORD res; + + int16_t *i16Samples1 = (int16_t *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, g_cSamplesInBuffer * g_cbSample); + int16_t *i16Samples2 = (int16_t *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, g_cSamplesInBuffer * g_cbSample); + + k = 0; // This is discrete time really!!! + + for (i = 0; i < g_cSamplesInBuffer; i++, k++) + { + i16Samples1[2 * i] = (uint16_t)(10000.0 * sin(2.0 * M_PI * k / g_cSamplesPerPeriod)); + i16Samples1[2 * i + 1] = i16Samples1[2 * i]; + } + + ui32SamplesForWavBuf = min(ui32SamplesToPlay, g_cSamplesInBuffer); + + waveHdr1.lpData = (LPSTR)i16Samples1; + waveHdr1.dwBufferLength = ui32SamplesForWavBuf * g_cbSample; + waveHdr1.dwFlags = 0; + waveHdr1.dwLoops = 0; + + ui32SamplesToPlay -= ui32SamplesForWavBuf; + ui32SamplesPlayed += ui32SamplesForWavBuf; + + pWaveHdrPlaying = &waveHdr1; + + mmresult = waveOutPrepareHeader(g_hWaveOut, pWaveHdrPlaying, sizeof(WAVEHDR)); + mmresult = waveOutWrite(g_hWaveOut, pWaveHdrPlaying, sizeof(WAVEHDR)); + //RTMsgInfo("waveOutWrite completes with %d\n", mmresult); + + res = WaitForSingleObject(g_hWavEvent, INFINITE); + //RTMsgInfo("WaitForSingleObject completes with %d\n\n", res); + + waveHdr2.lpData = (LPSTR)i16Samples2; + waveHdr2.dwBufferLength = 0; + waveHdr2.dwFlags = 0; + waveHdr2.dwLoops = 0; + + pWaveHdrWaiting = &waveHdr2; + + while (ui32SamplesToPlay > 0) + { + int16_t *i16Samples = (int16_t *)pWaveHdrWaiting->lpData; + + for (i = 0; i < g_cSamplesInBuffer; i++, k++) + { + i16Samples[2 * i] = (uint16_t)(10000.0 * sin(2.0 * M_PI * k / g_cSamplesPerPeriod)); + i16Samples[2 * i + 1] = i16Samples[2 * i]; + } + + ui32SamplesForWavBuf = min(ui32SamplesToPlay, g_cSamplesInBuffer); + + pWaveHdrWaiting->dwBufferLength = ui32SamplesForWavBuf * g_cbSample; + pWaveHdrWaiting->dwFlags = 0; + pWaveHdrWaiting->dwLoops = 0; + + + ui32SamplesToPlay -= ui32SamplesForWavBuf; + ui32SamplesPlayed += ui32SamplesForWavBuf; + + mmresult = waveOutPrepareHeader(g_hWaveOut, pWaveHdrWaiting, sizeof(WAVEHDR)); + mmresult = waveOutWrite(g_hWaveOut, pWaveHdrWaiting, sizeof(WAVEHDR)); + //RTMsgInfo("waveOutWrite completes with %d\n", mmresult); + + res = WaitForSingleObject(g_hWavEvent, INFINITE); + //RTMsgInfo("WaitForSingleObject completes with %d\n\n", res); + + mmresult = waveOutUnprepareHeader(g_hWaveOut, pWaveHdrPlaying, sizeof(WAVEHDR)); + //RTMsgInfo("waveOutUnprepareHeader completes with %d\n", mmresult); + + pWaveHdr = pWaveHdrWaiting; + pWaveHdrWaiting = pWaveHdrPlaying; + pWaveHdrPlaying = pWaveHdr; + } + + while (mmresult = waveOutUnprepareHeader(g_hWaveOut, pWaveHdrPlaying, sizeof(WAVEHDR))) + { + //Expecting WAVERR_STILLPLAYING + //RTMsgInfo("waveOutUnprepareHeader failed with 0x%X\n", mmresult); + Sleep(100); + } + + if (mmresult == MMSYSERR_NOERROR) + { + waveOutClose(g_hWaveOut); + } + + HeapFree(GetProcessHeap(), 0, i16Samples1); + HeapFree(GetProcessHeap(), 0, i16Samples2); +} + diff --git a/src/VBox/ValidationKit/utils/cpu/Makefile.kmk b/src/VBox/ValidationKit/utils/cpu/Makefile.kmk new file mode 100644 index 00000000..ed1d2fc6 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/Makefile.kmk @@ -0,0 +1,66 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - CPU Test Utilities. +# + +# +# Copyright (C) 2009-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +PROGRAMS += xmmsaving +xmmsaving_TEMPLATE = VBoxValidationKitR3 +xmmsaving_SOURCES = xmmsaving.cpp xmmsaving-asm.asm + +PROGRAMS += exceptionsR3 +exceptionsR3_TEMPLATE = VBoxValidationKitR3 +exceptionsR3_SOURCES = exceptionsR3.cpp exceptionsR3-asm.asm + +PROGRAMS += cpu-numa +cpu-numa_TEMPLATE = VBoxValidationKitR3 +cpu-numa_SOURCES = cpu-numa.cpp + +PROGRAMS += cpu-alloc-all-mem +cpu-alloc-all-mem_TEMPLATE = VBoxValidationKitR3 +cpu-alloc-all-mem_SOURCES = cpu-alloc-all-mem.cpp + +ifneq ($(KBUILD_HOST),os2) +PROGRAMS += cidet-app +endif +cidet-app_TEMPLATE = VBoxValidationKitR3 +cidet-app_SOURCES = \ + cidet-app.cpp \ + cidet-appA.asm \ + cidet-core.cpp \ + cidet-instr-1.cpp +cidet-app_DEFS = IN_DIS +cidet-app_DEFS.linux = CIDET_REDUCED_CTX +cidet-app_LIBS = $(PATH_STAGE_LIB)/DisasmR3Static$(VBOX_SUFF_LIB) +cidet-app_VBOX_IMPORT_CHECKER.win.x86 = $(NO_SUCH_VARIABLE) # doesn't work on NT4 yet. + +PROGRAMS += rdtsc +rdtsc_TEMPLATE = VBoxValidationKitR3 +rdtsc_SOURCES = rdtsc.cpp rdtsc-asm.asm + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet-app.cpp b/src/VBox/ValidationKit/utils/cpu/cidet-app.cpp new file mode 100644 index 00000000..8a40eb67 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet-app.cpp @@ -0,0 +1,1366 @@ +/* $Id: cidet-app.cpp $ */ +/** @file + * CPU Instruction Decoding & Execution Tests - Ring-3 Driver Application. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "cidet.h" + +#include <iprt/asm-amd64-x86.h> +#include <iprt/buildconfig.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/rand.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> + +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#else +# define USE_SIGNALS +# include <signal.h> +# include <unistd.h> +# include <sys/ucontext.h> +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def CIDET_LEAVE_GS_ALONE + * Leave GS alone on 64-bit darwin (gs is 0, no ldt or gdt entry to load that'll + * restore the lower 32-bits of the base when saving and restoring the register). + */ +#if (defined(RT_OS_DARWIN) && defined(RT_ARCH_AMD64)) || defined(DOXYGEN_RUNNING) +# define CIDET_LEAVE_GS_ALONE +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * CIDET driver app buffer. + */ +typedef struct CIDETAPPBUF +{ + /** The buffer size. */ + size_t cb; + /** The normal allocation. + * There is a fence page before this as well as at pbNormal+cb. */ + uint8_t *pbNormal; + /** The low memory allocation (32-bit addressable if 64-bit host, 16-bit + * addressable if 32-bit host). */ + uint8_t *pbLow; + /** Set if we're using the normal buffer, clear if it's the low one. */ + bool fUsingNormal : 1; + /** Set if the buffer is armed, clear if mostly accessible. */ + bool fArmed : 1; + /** Set if this is a code buffer. */ + bool fIsCode : 1; + /** The memory protection for the pages (RTMEM_PROT_XXX). */ + uint8_t fDefaultProt : 3; + /** The memory protection for the last page (RTMEM_PROT_XXX). */ + uint8_t fLastPageProt : 3; + /** The buffer index. */ + uint16_t idxCfg; +} CIDETAPPBUF; +/** Pointer to a CIDET driver app buffer. */ +typedef CIDETAPPBUF *PCIDETAPPBUF; + +/** Number of code buffers. */ +#define CIDETAPP_CODE_BUF_COUNT 1 +/** Number of data buffers. */ +#define CIDETAPP_DATA_BUF_COUNT 1 + + +/** + * CIDET driver app instance. + */ +typedef struct CIDETAPP +{ + /** The core structure. */ + CIDETCORE Core; + /** The execute return context. */ + CIDETCPUCTX ExecuteCtx; + /** Code buffers (runs parallel to g_aCodeBufCfgs). */ + CIDETAPPBUF aCodeBuffers[CIDETAPP_CODE_BUF_COUNT]; + /** Data buffers (runs parallel to g_aDataBufCfgs). */ + CIDETAPPBUF aDataBuffers[CIDETAPP_DATA_BUF_COUNT]; + + /** The lowest stack address. */ + uint8_t *pbStackLow; + /** The end of the stack allocation (highest address). */ + uint8_t *pbStackEnd; + /** Stack size (= pbStackEnd - pbStackLow). */ + uint32_t cbStack; + /** Whether we're currently using the 'lock int3' to deal with tricky stack. */ + bool fUsingLockedInt3; +} CIDETAPP; +/** Pointer to a CIDET driver app instance. */ +typedef CIDETAPP *PCIDETAPP; +/** Pointer to a pointer to a CIDET driver app instance. */ +typedef PCIDETAPP *PPCIDETAPP; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The test instance handle. */ +static RTTEST g_hTest; +/** Points to the instance data while executing, NULL if not executing or if + * we've already handled the first exception while executing. */ +static PCIDETAPP volatile g_pExecutingThis; +#ifdef USE_SIGNALS +/** The default signal mask. */ +static sigset_t g_ProcSigMask; +/** The alternative signal stack. */ +static stack_t g_AltStack; +#endif + + +/** Code buffer configurations (parallel to CIDETAPP::aCodeBuffers). */ +static CIDETBUFCFG g_aCodeBufCfgs[CIDETAPP_CODE_BUF_COUNT] = +{ + { + "Normal", + CIDETBUF_PROT_RWX | CIDETBUF_DPL_3 | CIDETBUF_DPL_SAME | CIDETBUF_SEG_ER | CIDETBUF_KIND_CODE, + }, +}; + +/** Data buffer configurations (parallel to CIDETAPP::aDataBuffers). */ +static CIDETBUFCFG g_aDataBufCfgs[CIDETAPP_DATA_BUF_COUNT] = +{ + { + "Normal", + CIDETBUF_PROT_RWX | CIDETBUF_DPL_3 | CIDETBUF_DPL_SAME | CIDETBUF_SEG_RW | CIDETBUF_KIND_DATA, + }, +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +DECLASM(void) CidetAppSaveAndRestoreCtx(void); +DECLASM(void) CidetAppRestoreCtx(PCCIDETCPUCTX pRestoreCtx); +DECLASM(void) CidetAppExecute(PCIDETCPUCTX pSaveCtx, PCCIDETCPUCTX pRestoreCtx); + + +/* + * + * + * Exception and signal handling. + * Exception and signal handling. + * Exception and signal handling. + * + * + */ + +#ifdef RT_OS_WINDOWS +static int CidetAppXcptFilter(EXCEPTION_POINTERS *pXcptPtrs) +{ + /* + * Grab the this point. We expect at most one signal. + */ + PCIDETAPP pThis = g_pExecutingThis; + g_pExecutingThis = NULL; + if (pThis == NULL) + { + /* we're up the infamous creek... */ + for (;;) ExitProcess(2); + } + + /* + * Gather CPU state information from the context structure. + */ + CONTEXT *pSrcCtx = pXcptPtrs->ContextRecord; +# ifdef RT_ARCH_AMD64 + if ( (pSrcCtx->ContextFlags & (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)) + != (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)) + __debugbreak(); + pThis->Core.ActualCtx.rip = pSrcCtx->Rip; + pThis->Core.ActualCtx.rfl = pSrcCtx->EFlags; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xAX] = pSrcCtx->Rax; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xCX] = pSrcCtx->Rcx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDX] = pSrcCtx->Rdx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBX] = pSrcCtx->Rbx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSP] = pSrcCtx->Rsp; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBP] = pSrcCtx->Rbp; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSI] = pSrcCtx->Rsi; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDI] = pSrcCtx->Rdi; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x8] = pSrcCtx->R8; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x9] = pSrcCtx->R9; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x10] = pSrcCtx->R10; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x11] = pSrcCtx->R11; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x12] = pSrcCtx->R12; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x13] = pSrcCtx->R13; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x14] = pSrcCtx->R14; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x15] = pSrcCtx->R15; + pThis->Core.ActualCtx.aSRegs[X86_SREG_ES] = pSrcCtx->SegEs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_CS] = pSrcCtx->SegCs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_SS] = pSrcCtx->SegSs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_DS] = pSrcCtx->SegDs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] = pSrcCtx->SegFs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] = pSrcCtx->SegGs; + if (pSrcCtx->ContextFlags & CONTEXT_FLOATING_POINT) + { + /* ... */ + } + if (pSrcCtx->ContextFlags & CONTEXT_DEBUG_REGISTERS) + { + /* ... */ + } + +# elif defined(RT_ARCH_X86) + if ( (pSrcCtx->ContextFlags & (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)) + != (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)) + __debugbreak(); + pThis->Core.ActualCtx.rip = pSrcCtx->Eip; + pThis->Core.ActualCtx.rfl = pSrcCtx->EFlags; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xAX] = pSrcCtx->Eax; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xCX] = pSrcCtx->Ecx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDX] = pSrcCtx->Edx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBX] = pSrcCtx->Ebx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSP] = pSrcCtx->Esp; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBP] = pSrcCtx->Ebp; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSI] = pSrcCtx->Esi; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDI] = pSrcCtx->Edi; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x8] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x9] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x10] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x11] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x12] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x13] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x14] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x15] = 0; + pThis->Core.ActualCtx.aSRegs[X86_SREG_ES] = pSrcCtx->SegEs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_CS] = pSrcCtx->SegCs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_SS] = pSrcCtx->SegSs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_DS] = pSrcCtx->SegDs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] = pSrcCtx->SegFs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] = pSrcCtx->SegGs; + if (pSrcCtx->ContextFlags & CONTEXT_FLOATING_POINT) + { + /* ... */ + } + if (pSrcCtx->ContextFlags & CONTEXT_EXTENDED_REGISTERS) + { + /* ... */ + } + if (pSrcCtx->ContextFlags & CONTEXT_DEBUG_REGISTERS) + { + /* ... */ + } +# else +# error "Not supported" +# endif + + /* + * Add/Adjust CPU state information according to the exception code. + */ + pThis->Core.ActualCtx.uErr = UINT64_MAX; + switch (pXcptPtrs->ExceptionRecord->ExceptionCode) + { + case EXCEPTION_INT_DIVIDE_BY_ZERO: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_DE; + break; + case EXCEPTION_SINGLE_STEP: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_DB; + break; + case EXCEPTION_BREAKPOINT: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_BP; + break; + case EXCEPTION_INT_OVERFLOW: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_OF; + break; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_BR; + break; + case EXCEPTION_ILLEGAL_INSTRUCTION: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_UD; + break; + + case EXCEPTION_PRIV_INSTRUCTION: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_GP; + pThis->Core.ActualCtx.uErr = 0; + break; + + case EXCEPTION_ACCESS_VIOLATION: + { + pThis->Core.ActualCtx.uXcpt = X86_XCPT_PF; + pThis->Core.ActualCtx.cr2 = pXcptPtrs->ExceptionRecord->ExceptionInformation[1]; + pThis->Core.ActualCtx.uErr = 0; + if (pXcptPtrs->ExceptionRecord->ExceptionInformation[0] == EXCEPTION_WRITE_FAULT) + pThis->Core.ActualCtx.uErr = X86_TRAP_PF_RW; + else if (pXcptPtrs->ExceptionRecord->ExceptionInformation[0] == EXCEPTION_EXECUTE_FAULT) + pThis->Core.ActualCtx.uErr = X86_TRAP_PF_ID; + else if (pXcptPtrs->ExceptionRecord->ExceptionInformation[0] != EXCEPTION_READ_FAULT) + AssertFatalFailed(); + + MEMORY_BASIC_INFORMATION MemInfo = {0}; + if (VirtualQuery((PVOID)pXcptPtrs->ExceptionRecord->ExceptionInformation[1], &MemInfo, sizeof(MemInfo)) > 0) + switch (MemInfo.Protect & 0xff) + { + case PAGE_NOACCESS: + break; + case PAGE_READONLY: + case PAGE_READWRITE: + case PAGE_WRITECOPY: + case PAGE_EXECUTE: + case PAGE_EXECUTE_READ: + case PAGE_EXECUTE_READWRITE: + case PAGE_EXECUTE_WRITECOPY: + pThis->Core.ActualCtx.uErr |= X86_TRAP_PF_P; + break; + default: + AssertFatalFailed(); + } + break; + } + + case EXCEPTION_FLT_DENORMAL_OPERAND: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INEXACT_RESULT: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_FLT_OVERFLOW: + case EXCEPTION_FLT_STACK_CHECK: + case EXCEPTION_FLT_UNDERFLOW: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_MF; + break; + + case EXCEPTION_DATATYPE_MISALIGNMENT: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_AC; + break; + + default: + pThis->Core.ActualCtx.uXcpt = pXcptPtrs->ExceptionRecord->ExceptionCode; + break; + } + + /* + * Our own personal long jump implementation. + */ + CidetAppRestoreCtx(&pThis->ExecuteCtx); + + /* Won't return...*/ + return EXCEPTION_EXECUTE_HANDLER; +} + + +/** + * Vectored exception handler. + * + * @returns Long jumps or terminates the process. + * @param pXcptPtrs The exception record. + */ +static LONG CALLBACK CidetAppVectoredXcptHandler(EXCEPTION_POINTERS *pXcptPtrs) +{ + RTStrmPrintf(g_pStdErr, "CidetAppVectoredXcptHandler!\n"); + CidetAppXcptFilter(pXcptPtrs); + + /* won't get here. */ + return EXCEPTION_CONTINUE_SEARCH; +} + + +/** + * Unhandled exception filter. + * + * @returns Long jumps or terminates the process. + * @param pXcptPtrs The exception record. + */ +static LONG CALLBACK CidetAppUnhandledXcptFilter(EXCEPTION_POINTERS *pXcptPtrs) +{ + RTStrmPrintf(g_pStdErr, "CidetAppUnhandledXcptFilter!\n"); + CidetAppXcptFilter(pXcptPtrs); + + /* won't get here. */ + return EXCEPTION_CONTINUE_SEARCH; +} + + +#elif defined(USE_SIGNALS) +/** + * Signal handler. + */ +static void CidetAppSigHandler(int iSignal, siginfo_t *pSigInfo, void *pvCtx) +{ +# if 1 + if ( !g_pExecutingThis + || !g_pExecutingThis->fUsingLockedInt3 + || iSignal != SIGILL) + { + RTStrmPrintf(g_pStdErr, "signal %d pSigInfo=%p pvCtx=%p", iSignal, pSigInfo, pvCtx); + if (pSigInfo) + RTStrmPrintf(g_pStdErr, " si_addr=%p si_code=%#x sival_ptr=%p sival_int=%d", + pSigInfo->si_addr, pSigInfo->si_code, pSigInfo->si_value.sival_ptr, pSigInfo->si_value.sival_int); + RTStrmPrintf(g_pStdErr, "\n"); + } +# endif + + /* + * Grab the this point. We expect at most one signal. + */ + PCIDETAPP pThis = g_pExecutingThis; + g_pExecutingThis = NULL; + if (pThis == NULL) + { + /* we're up the infamous creek... */ + RTStrmPrintf(g_pStdErr, "Creek time!\n"); + for (;;) _exit(2); + } + + /* + * Gather all the CPU state information available. + */ +# ifdef RT_OS_LINUX + ucontext_t const *pCtx = (ucontext_t const *)pvCtx; +# ifdef RT_ARCH_AMD64 + + pThis->Core.ActualCtx.aGRegs[X86_GREG_xAX] = pCtx->uc_mcontext.gregs[REG_RAX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xCX] = pCtx->uc_mcontext.gregs[REG_RCX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDX] = pCtx->uc_mcontext.gregs[REG_RDX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBX] = pCtx->uc_mcontext.gregs[REG_RBX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSP] = pCtx->uc_mcontext.gregs[REG_RSP]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBP] = pCtx->uc_mcontext.gregs[REG_RBP]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSI] = pCtx->uc_mcontext.gregs[REG_RSI]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDI] = pCtx->uc_mcontext.gregs[REG_RDI]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x8 ] = pCtx->uc_mcontext.gregs[REG_R8]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x9 ] = pCtx->uc_mcontext.gregs[REG_R9]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x10] = pCtx->uc_mcontext.gregs[REG_R10]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x11] = pCtx->uc_mcontext.gregs[REG_R11]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x12] = pCtx->uc_mcontext.gregs[REG_R12]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x13] = pCtx->uc_mcontext.gregs[REG_R13]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x14] = pCtx->uc_mcontext.gregs[REG_R14]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x15] = pCtx->uc_mcontext.gregs[REG_R15]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_CS] = RT_LO_U16((uint32_t)pCtx->uc_mcontext.gregs[REG_CSGSFS]); + pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] = RT_HI_U16((uint32_t)pCtx->uc_mcontext.gregs[REG_CSGSFS]); + pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] = (uint16_t)RT_HI_U32(pCtx->uc_mcontext.gregs[REG_CSGSFS]); + pThis->Core.ActualCtx.aSRegs[X86_SREG_DS] = ASMGetDS(); + pThis->Core.ActualCtx.aSRegs[X86_SREG_ES] = ASMGetES(); + pThis->Core.ActualCtx.aSRegs[X86_SREG_SS] = ASMGetSS(); + pThis->Core.ActualCtx.rip = pCtx->uc_mcontext.gregs[REG_RIP]; + pThis->Core.ActualCtx.rfl = pCtx->uc_mcontext.gregs[REG_EFL]; + pThis->Core.ActualCtx.cr2 = pCtx->uc_mcontext.gregs[REG_CR2]; + pThis->Core.ActualCtx.uXcpt = pCtx->uc_mcontext.gregs[REG_TRAPNO]; + pThis->Core.ActualCtx.uErr = pCtx->uc_mcontext.gregs[REG_ERR]; + + /* Fudge the FS and GS registers as setup_sigcontext returns 0. */ + if (pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] == 0) + pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] = pThis->Core.ExpectedCtx.aSRegs[X86_SREG_FS]; + if (pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] == 0) + pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] = pThis->Core.ExpectedCtx.aSRegs[X86_SREG_GS]; + +# elif defined(RT_ARCH_X86) + pThis->Core.ActualCtx.aGRegs[X86_GREG_xAX] = pCtx->uc_mcontext.gregs[REG_EAX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xCX] = pCtx->uc_mcontext.gregs[REG_ECX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDX] = pCtx->uc_mcontext.gregs[REG_EDX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBX] = pCtx->uc_mcontext.gregs[REG_EBX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSP] = pCtx->uc_mcontext.gregs[REG_ESP]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBP] = pCtx->uc_mcontext.gregs[REG_EBP]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSI] = pCtx->uc_mcontext.gregs[REG_ESI]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDI] = pCtx->uc_mcontext.gregs[REG_EDI]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_CS] = pCtx->uc_mcontext.gregs[REG_CS]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_DS] = pCtx->uc_mcontext.gregs[REG_DS]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_ES] = pCtx->uc_mcontext.gregs[REG_ES]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] = pCtx->uc_mcontext.gregs[REG_FS]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] = pCtx->uc_mcontext.gregs[REG_GS]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_SS] = pCtx->uc_mcontext.gregs[REG_SS]; + pThis->Core.ActualCtx.rip = pCtx->uc_mcontext.gregs[REG_EIP]; + pThis->Core.ActualCtx.rfl = pCtx->uc_mcontext.gregs[REG_EFL]; + pThis->Core.ActualCtx.cr2 = pCtx->uc_mcontext.cr2; + pThis->Core.ActualCtx.uXcpt = pCtx->uc_mcontext.gregs[REG_TRAPNO]; + pThis->Core.ActualCtx.uErr = pCtx->uc_mcontext.gregs[REG_ERR]; + +# else +# error "Unsupported arch." +# endif + + /* Adjust uErr. */ + switch (pThis->Core.ActualCtx.uXcpt) + { + case X86_XCPT_TS: + case X86_XCPT_NP: + case X86_XCPT_SS: + case X86_XCPT_GP: + case X86_XCPT_PF: + case X86_XCPT_AC: + case X86_XCPT_DF: + break; + default: + pThis->Core.ActualCtx.uErr = UINT64_MAX; + break; + } + +# if 0 + /* Fudge the resume flag (it's probably always set here). */ + if ( (pThis->Core.ActualCtx.rfl & X86_EFL_RF) + && !(pThis->Core.ExpectedCtx.rfl & X86_EFL_RF)) + pThis->Core.ActualCtx.rfl &= ~X86_EFL_RF; +# endif + +# else + /** @todo */ +# endif + + + /* + * Check for the 'lock int3' instruction used for tricky stacks. + */ + if ( pThis->fUsingLockedInt3 + && pThis->Core.ActualCtx.uXcpt == X86_XCPT_UD + && pThis->Core.ActualCtx.rip == pThis->Core.CodeBuf.uEffBufAddr - pThis->Core.CodeBuf.offSegBase + + pThis->Core.CodeBuf.offActive + pThis->Core.CodeBuf.cbActive ) + { + pThis->Core.ActualCtx.uXcpt = UINT32_MAX; + Assert(pThis->Core.ActualCtx.uErr == UINT64_MAX); + pThis->Core.ActualCtx.rfl &= ~X86_EFL_RF; + } + + /* + * Jump back to CidetAppCbExecute. + */ + CidetAppRestoreCtx(&pThis->ExecuteCtx); +} +#endif + + + +/* + * + * Buffer handling + * Buffer handling + * Buffer handling + * + * + */ + +static int cidetAppAllocateAndConfigureOneBuffer(PCIDETAPP pThis, PCIDETAPPBUF pBuf, uint16_t idxBuf, bool fIsCode, + uint32_t fFlags) +{ + RT_NOREF_PV(pThis); + static uint8_t const s_afBufProtToDefaultMemProt[] = + { + /* [0] = */ RTMEM_PROT_NONE, + /* [1] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC, + /* [2] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE, + /* [3] = */ RTMEM_PROT_READ | RTMEM_PROT_EXEC, + /* [4] = */ RTMEM_PROT_READ, + /* [5] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC, + /* [6] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC, + /* [7] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC, + /* [8] = */ RTMEM_PROT_NONE, + /* [9] = */ RTMEM_PROT_NONE, + /* [10] = */ RTMEM_PROT_NONE, + /* [11] = */ RTMEM_PROT_NONE, + /* [12] = */ RTMEM_PROT_NONE, + /* [13] = */ RTMEM_PROT_NONE, + /* [14] = */ RTMEM_PROT_NONE, + /* [15] = */ RTMEM_PROT_NONE, + }; + static uint8_t const s_afBufProtToLastPageMemProt[] = + { + /* [0] = */ RTMEM_PROT_NONE, + /* [1] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC, + /* [2] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE, + /* [3] = */ RTMEM_PROT_READ | RTMEM_PROT_EXEC, + /* [4] = */ RTMEM_PROT_READ, + /* [5] = */ RTMEM_PROT_NONE, + /* [6] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE, + /* [7] = */ RTMEM_PROT_READ, + /* [8] = */ RTMEM_PROT_NONE, + /* [9] = */ RTMEM_PROT_NONE, + /* [10] = */ RTMEM_PROT_NONE, + /* [11] = */ RTMEM_PROT_NONE, + /* [12] = */ RTMEM_PROT_NONE, + /* [13] = */ RTMEM_PROT_NONE, + /* [14] = */ RTMEM_PROT_NONE, + /* [15] = */ RTMEM_PROT_NONE, + }; + + int rc; + Assert(CIDETBUF_IS_CODE(fFlags) == fIsCode); + pBuf->fIsCode = fIsCode; + pBuf->idxCfg = idxBuf; + pBuf->fUsingNormal = true; + pBuf->fDefaultProt = s_afBufProtToDefaultMemProt[fFlags & CIDETBUF_PROT_MASK]; + pBuf->fLastPageProt = s_afBufProtToLastPageMemProt[fFlags & CIDETBUF_PROT_MASK]; + if (pBuf->fDefaultProt != RTMEM_PROT_NONE) + { + /* + * Allocate a 3 page buffer plus two fence pages. + */ + pBuf->cb = fIsCode ? CIDET_CODE_BUF_SIZE : CIDET_DATA_BUF_SIZE; + pBuf->pbNormal = (uint8_t *)RTMemPageAlloc(PAGE_SIZE + pBuf->cb + PAGE_SIZE); + if (pBuf->pbNormal) + { + memset(pBuf->pbNormal, 0x55, PAGE_SIZE); + memset(pBuf->pbNormal + PAGE_SIZE, 0xcc, pBuf->cb); + memset(pBuf->pbNormal + PAGE_SIZE + pBuf->cb, 0x77, PAGE_SIZE); + + /* Set up fence pages. */ + rc = RTMemProtect(pBuf->pbNormal, PAGE_SIZE, RTMEM_PROT_NONE); /* fence */ + if (RT_SUCCESS(rc)) + rc = RTMemProtect(pBuf->pbNormal + PAGE_SIZE + pBuf->cb, PAGE_SIZE, RTMEM_PROT_NONE); /* fence */ + pBuf->pbNormal += PAGE_SIZE; + + /* Default protection + read + write. */ + if (RT_SUCCESS(rc)) + rc = RTMemProtect(pBuf->pbNormal, pBuf->cb, pBuf->fDefaultProt | RTMEM_PROT_READ | RTMEM_PROT_WRITE); + + /* + * Allocate a low memory buffer or LDT if necessary. + */ + if ( RT_SUCCESS(rc) + && (uintptr_t)pBuf->pbNormal + pBuf->cb > RT_BIT_64(sizeof(uintptr_t) / 2 * 8)) + { + /** @todo Buffers for the other addressing mode. */ + pBuf->pbLow = NULL; + } + else + pBuf->pbLow = pBuf->pbNormal; + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + } + else + rc = RTTestIFailedRc(VERR_NO_PAGE_MEMORY, "Error allocating three pages."); + } + else + rc = RTTestIFailedRc(VERR_NO_PAGE_MEMORY, "Unsupported buffer config: fFlags=%#x, idxBuf=%u", fFlags, idxBuf); + return rc; +} + + +static void CidetAppDeleteBuffer(PCIDETAPPBUF pBuf) +{ + RTMemProtect(pBuf->pbNormal - PAGE_SIZE, PAGE_SIZE + pBuf->cb + PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + RTMemPageFree(pBuf->pbNormal - PAGE_SIZE, PAGE_SIZE + pBuf->cb + PAGE_SIZE); + if (pBuf->pbLow != pBuf->pbNormal && pBuf->pbLow) + { + RTMemProtect(pBuf->pbLow, pBuf->cb, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + RTMemFreeEx(pBuf->pbLow, pBuf->cb); + } +} + + +static bool CidetAppArmBuf(PCIDETAPP pThis, PCIDETAPPBUF pAppBuf) +{ + RT_NOREF_PV(pThis); + uint8_t *pbUsingBuf = (pAppBuf->fUsingNormal ? pAppBuf->pbNormal : pAppBuf->pbLow); + if (pAppBuf->fLastPageProt == pAppBuf->fDefaultProt) + { + if ((pAppBuf->fDefaultProt & (RTMEM_PROT_READ | RTMEM_PROT_WRITE)) != (RTMEM_PROT_READ | RTMEM_PROT_WRITE)) + RTTESTI_CHECK_RC_RET(RTMemProtect(pbUsingBuf, pAppBuf->cb, pAppBuf->fDefaultProt), VINF_SUCCESS, false); + } + else + { + if ((pAppBuf->fDefaultProt & (RTMEM_PROT_READ | RTMEM_PROT_WRITE)) != (RTMEM_PROT_READ | RTMEM_PROT_WRITE)) + RTTESTI_CHECK_RC_RET(RTMemProtect(pbUsingBuf, pAppBuf->cb - PAGE_SIZE, pAppBuf->fDefaultProt), VINF_SUCCESS, false); + RTTESTI_CHECK_RC_RET(RTMemProtect(pbUsingBuf + pAppBuf->cb - PAGE_SIZE, PAGE_SIZE, pAppBuf->fLastPageProt), + VINF_SUCCESS, false); + } + pAppBuf->fArmed = true; + return true; +} + + +static bool CidetAppDearmBuf(PCIDETAPP pThis, PCIDETAPPBUF pAppBuf) +{ + RT_NOREF_PV(pThis); + uint8_t *pbUsingBuf = (pAppBuf->fUsingNormal ? pAppBuf->pbNormal : pAppBuf->pbLow); + int rc = RTMemProtect(pbUsingBuf, pAppBuf->cb, pAppBuf->fDefaultProt | RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTMemProtect failed on %s buf #%u: %Rrc", pAppBuf->fIsCode ? "code" : "data", pAppBuf->idxCfg, rc); + return false; + } + pAppBuf->fArmed = false; + return true; +} + + +/** + * @interface_method_impl{CIDETCORE,pfnReInitDataBuf} + */ +static DECLCALLBACK(bool) CidetAppCbReInitDataBuf(PCIDETCORE pThis, PCIDETBUF pBuf) +{ + PCIDETAPP pThisApp = (PCIDETAPP)pThis; + PCIDETAPPBUF pAppBuf = &pThisApp->aDataBuffers[pBuf->idxCfg]; + Assert(CIDETBUF_IS_DATA(pBuf->pCfg->fFlags)); + + /* + * De-arm the buffer. + */ + if (pAppBuf->fArmed) + if (RT_UNLIKELY(!CidetAppDearmBuf(pThisApp, pAppBuf))) + return false; + + /* + * Check the allocation requirements. + */ + if (RT_UNLIKELY((size_t)pBuf->off + pBuf->cb > pAppBuf->cb)) + { + RTTestIFailed("Buffer too small; off=%#x cb=%#x pAppBuf->cb=%#x (%s)", + pBuf->off, pBuf->cb, pAppBuf->cb, pBuf->pCfg->pszName); + return false; + } + + /* + * Do we need to use the low buffer? Check that we have one, if we need it. + */ + bool fUseNormal = pThis->cbAddrMode == ARCH_BITS / 8; + if (!fUseNormal && !pAppBuf->pbLow) + return false; + + /* + * Update the state. + */ + pAppBuf->fUsingNormal = fUseNormal; + + pBuf->offActive = pBuf->off; + pBuf->cbActive = pBuf->cb; + pBuf->cbPrologue = 0; + pBuf->cbEpilogue = 0; + pBuf->uSeg = UINT32_MAX; + pBuf->cbActiveSegLimit = UINT64_MAX; + pBuf->uSegBase = 0; + if (fUseNormal) + pBuf->uEffBufAddr = (uintptr_t)pAppBuf->pbNormal; + else + pBuf->uEffBufAddr = (uintptr_t)pAppBuf->pbLow; + + return true; +} + + +/** + * @interface_method_impl{CIDETCORE,pfnSetupDataBuf} + */ +static DECLCALLBACK(bool) CidetAppCbSetupDataBuf(PCIDETCORE pThis, PCIDETBUF pBuf, void const *pvSrc) +{ + PCIDETAPP pThisApp = (PCIDETAPP)pThis; + PCIDETAPPBUF pAppBuf = &pThisApp->aDataBuffers[pBuf->idxCfg]; + Assert(CIDETBUF_IS_DATA(pBuf->pCfg->fFlags)); + Assert(!pAppBuf->fArmed); + + + /* + * Copy over the data. + */ + uint8_t *pbUsingBuf = (pAppBuf->fUsingNormal ? pAppBuf->pbNormal : pAppBuf->pbLow); + memcpy(pbUsingBuf + pBuf->offActive, pvSrc, pBuf->cbActive); + + /* + * Arm the buffer. + */ + return CidetAppArmBuf(pThisApp, pAppBuf); +} + + +/** + * @interface_method_impl{CIDETCORE,pfnIsBufEqual} + */ +static DECLCALLBACK(bool) CidetAppCbIsBufEqual(PCIDETCORE pThis, struct CIDETBUF *pBuf, void const *pvExpected) +{ + PCIDETAPP pThisApp = (PCIDETAPP)pThis; + PCIDETAPPBUF pAppBuf = CIDETBUF_IS_CODE(pBuf->pCfg->fFlags) + ? &pThisApp->aCodeBuffers[pBuf->idxCfg] + : &pThisApp->aDataBuffers[pBuf->idxCfg]; + + /* + * Disarm the buffer if we can't read it all. + */ + if ( pAppBuf->fArmed + && ( !(pAppBuf->fLastPageProt & RTMEM_PROT_READ) + || !(pAppBuf->fDefaultProt & RTMEM_PROT_READ)) ) + if (RT_UNLIKELY(!CidetAppDearmBuf(pThisApp, pAppBuf))) + return false; + + /* + * Do the comparing. + */ + uint8_t *pbUsingBuf = (pAppBuf->fUsingNormal ? pAppBuf->pbNormal : pAppBuf->pbLow); + if (memcmp(pbUsingBuf + pBuf->offActive, pvExpected, pBuf->cbActive) != 0) + { + /** @todo RTMEM_PROT_NONE may kill content on some hosts... */ + return false; + } + + /** @todo check padding. */ + return true; +} + + +/* + * + * Code buffer, prologue, epilogue, and execution. + * Code buffer, prologue, epilogue, and execution. + * Code buffer, prologue, epilogue, and execution. + * + * + */ + + +/** + * @interface_method_impl{CIDETCORE,pfnReInitCodeBuf} + */ +static DECLCALLBACK(bool) CidetAppCbReInitCodeBuf(PCIDETCORE pThis, PCIDETBUF pBuf) +{ + PCIDETAPP pThisApp = (PCIDETAPP)pThis; + PCIDETAPPBUF pAppBuf = &pThisApp->aCodeBuffers[pBuf->idxCfg]; + Assert(CIDETBUF_IS_CODE(pBuf->pCfg->fFlags)); + Assert(pAppBuf->fUsingNormal); + + /* + * De-arm the buffer. + */ + if (pAppBuf->fArmed) + if (RT_UNLIKELY(!CidetAppDearmBuf(pThisApp, pAppBuf))) + return false; + + /* + * Determin the prologue and epilogue sizes. + */ + uint16_t cbPrologue = 0; + uint16_t cbEpilogue = ARCH_BITS == 64 ? 0x56 : 0x4e; + if (pThis->InCtx.fTrickyStack) + cbEpilogue = 16; + + /* + * Check the allocation requirements. + */ + if (RT_UNLIKELY( cbPrologue > pBuf->off + || (size_t)pBuf->off + pBuf->cb + cbEpilogue > pAppBuf->cb)) + { + RTTestIFailed("Buffer too small; off=%#x cb=%#x cbPro=%#x cbEpi=%#x pAppBuf->cb=%#x (%s)", + pBuf->off, pBuf->cb, cbPrologue, cbEpilogue, pAppBuf->cb, pBuf->pCfg->pszName); + return false; + } + + /* + * Update the state. + */ + pAppBuf->fUsingNormal = true; + + pBuf->cbActive = pBuf->cb; + pBuf->offActive = pBuf->off; + pBuf->cbPrologue = cbPrologue; + pBuf->cbEpilogue = cbEpilogue; + pBuf->uSeg = UINT32_MAX; + pBuf->cbActiveSegLimit = UINT64_MAX; + pBuf->uSegBase = 0; + pBuf->uEffBufAddr = (uintptr_t)pAppBuf->pbNormal; + + return true; +} + + +/** + * @interface_method_impl{CIDETCORE,pfnSetupCodeBuf} + */ +static DECLCALLBACK(bool) CidetAppCbSetupCodeBuf(PCIDETCORE pThis, PCIDETBUF pBuf, void const *pvInstr) +{ + PCIDETAPP pThisApp = (PCIDETAPP)pThis; + PCIDETAPPBUF pAppBuf =&pThisApp->aCodeBuffers[pBuf->idxCfg]; + Assert(CIDETBUF_IS_CODE(pBuf->pCfg->fFlags)); + Assert(pAppBuf->fUsingNormal); + Assert(!pAppBuf->fArmed); + + /* + * Emit prologue code. + */ + uint8_t *pbDst = pAppBuf->pbNormal + pBuf->offActive - pBuf->cbPrologue; + + /* + * Copy over the code. + */ + Assert(pbDst == &pAppBuf->pbNormal[pBuf->offActive]); + memcpy(pbDst, pvInstr, pBuf->cbActive); + pbDst += pBuf->cbActive; + + /* + * Emit epilogue code. + */ + if (!pThis->InCtx.fTrickyStack) + { + /* + * The stack is reasonably good, do minimal work. + * + * Note! Ideally, we would just fill in 16 int3s here and check that + * we hit the first right one. However, if we wish to run this + * code with IEM, we better skip unnecessary trips to ring-0. + */ + uint8_t * const pbStartEpilogue = pbDst; + + /* jmp $+6 */ + *pbDst++ = 0xeb; + *pbDst++ = 0x06; /* This is a push es, so if the decoder is one off, we'll hit the int 3 below. */ + + /* Six int3s for trapping incorrectly decoded instructions. */ + *pbDst++ = 0xcc; + *pbDst++ = 0xcc; + *pbDst++ = 0xcc; + *pbDst++ = 0xcc; + *pbDst++ = 0xcc; + *pbDst++ = 0xcc; + + /* push rip / call $+0 */ + *pbDst++ = 0xe8; + *pbDst++ = 0x00; + *pbDst++ = 0x00; + *pbDst++ = 0x00; + *pbDst++ = 0x00; + uint8_t offRipAdjust = (uint8_t)(uintptr_t)(pbStartEpilogue - pbDst); + + /* push xCX */ + *pbDst++ = 0x51; + + /* mov xCX, [xSP + xCB] */ + *pbDst++ = 0x48; + *pbDst++ = 0x8b; + *pbDst++ = 0x4c; + *pbDst++ = 0x24; + *pbDst++ = sizeof(uintptr_t); + + /* lea xCX, [xCX - 24] */ + *pbDst++ = 0x48; + *pbDst++ = 0x8d; + *pbDst++ = 0x49; + *pbDst++ = offRipAdjust; + + /* mov xCX, [xSP + xCB] */ + *pbDst++ = 0x48; + *pbDst++ = 0x89; + *pbDst++ = 0x4c; + *pbDst++ = 0x24; + *pbDst++ = sizeof(uintptr_t); + + /* mov xCX, &pThis->ActualCtx */ +#ifdef RT_ARCH_AMD64 + *pbDst++ = 0x48; +#endif + *pbDst++ = 0xb9; + *(uintptr_t *)pbDst = (uintptr_t)&pThisApp->Core.ActualCtx; + pbDst += sizeof(uintptr_t); + + /* pop [ss:rcx + ActualCtx.aGRegs[X86_GREG_xCX]] */ + *pbDst++ = 0x36; + *pbDst++ = 0x8f; + *pbDst++ = 0x41; + *pbDst++ = RT_UOFFSETOF(CIDETCPUCTX, aGRegs[X86_GREG_xCX]); + Assert(RT_UOFFSETOF(CIDETCPUCTX, aGRegs[X86_GREG_xCX]) < 0x7f); + + /* mov [ss:rcx + ActualCtx.aGRegs[X86_GREG_xDX]], rdx */ + *pbDst++ = 0x36; +#ifdef RT_ARCH_AMD64 + *pbDst++ = 0x48; +#endif + *pbDst++ = 0x89; + *pbDst++ = 0x51; + *pbDst++ = RT_UOFFSETOF(CIDETCPUCTX, aGRegs[X86_GREG_xDX]); + Assert(RT_UOFFSETOF(CIDETCPUCTX, aGRegs[X86_GREG_xDX]) < 0x7f); + + /* mov [ss:rcx + ActualCtx.aSRegs[X86_GREG_DS]], ds */ + *pbDst++ = 0x36; + *pbDst++ = 0x8c; + *pbDst++ = 0x99; + *(uint32_t *)pbDst = RT_UOFFSETOF(CIDETCPUCTX, aSRegs[X86_SREG_DS]); + pbDst += sizeof(uint32_t); + + /* mov edx, 0XXYYh */ + *pbDst++ = 0xba; + *(uint32_t *)pbDst = pThisApp->Core.InTemplateCtx.aSRegs[X86_SREG_DS]; + pbDst += sizeof(uint32_t); + + /* mov ds, dx */ + *pbDst++ = 0x8e; + *pbDst++ = 0xda; + + /* mov xDX, &pThisApp->ExecuteCtx */ +#ifdef RT_ARCH_AMD64 + *pbDst++ = 0x48; +#endif + *pbDst++ = 0xba; + *(uintptr_t *)pbDst = (uintptr_t)&pThisApp->ExecuteCtx; + pbDst += sizeof(uintptr_t); + +#ifdef RT_ARCH_AMD64 + /* jmp [cs:$ wrt rip] */ + *pbDst++ = 0xff; + *pbDst++ = 0x25; + *(uint32_t *)pbDst = 0; + pbDst += sizeof(uint32_t); +#else + /* jmp NAME(CidetAppSaveAndRestoreCtx) */ + *pbDst++ = 0xb9; +#endif + *(uintptr_t *)pbDst = (uintptr_t)CidetAppSaveAndRestoreCtx; + pbDst += sizeof(uintptr_t); + + /* int3 */ + *pbDst++ = 0xcc; + + pThisApp->fUsingLockedInt3 = false; + + } + else + { + /* + * Tricky stack, so just make it raise #UD after a successful run. + */ + *pbDst++ = 0xf0; /* lock prefix */ + memset(pbDst, 0xcc, 15); /* int3 */ + pbDst += 15; + + pThisApp->fUsingLockedInt3 = true; + } + + AssertMsg(pbDst == &pAppBuf->pbNormal[pBuf->offActive + pBuf->cb + pBuf->cbEpilogue], + ("cbEpilogue=%#x, actual %#x\n", pBuf->cbEpilogue, pbDst - &pAppBuf->pbNormal[pBuf->offActive + pBuf->cb])); + + /* + * Arm the buffer. + */ + return CidetAppArmBuf(pThisApp, pAppBuf); +} + + +/** + * @interface_method_impl{CIDETCORE,pfnExecute} + */ +static DECLCALLBACK(bool) CidetAppCbExecute(PCIDETCORE pThis) +{ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_DARWIN) + /* Skip tricky stack because windows cannot dispatch exception if RSP/ESP is bad. */ + if (pThis->InCtx.fTrickyStack) + return false; +#endif + + g_pExecutingThis = (PCIDETAPP)pThis; +#ifdef RT_OS_WINDOWS + __try + { + CidetAppExecute(&((PCIDETAPP)pThis)->ExecuteCtx, &pThis->InCtx); + } + __except (CidetAppXcptFilter(GetExceptionInformation())) + { + /* Won't end up here... */ + } + g_pExecutingThis = NULL; +#else + CidetAppExecute(&((PCIDETAPP)pThis)->ExecuteCtx, &pThis->InCtx); + if (g_pExecutingThis) + g_pExecutingThis = NULL; + else + { + RTTESTI_CHECK_RC(sigprocmask(SIG_SETMASK, &g_ProcSigMask, NULL), 0); + RTTESTI_CHECK_RC(sigaltstack(&g_AltStack, NULL), 0); + } +#endif + + return true; +} + + + + +/* + * + * + * CIDET Application. + * CIDET Application. + * CIDET Application. + * + * + */ + + +/** + * @interface_method_impl{CIDETCORE,pfnFailure} + */ +static DECLCALLBACK(void) CidetAppCbFailureV(PCIDETCORE pThis, const char *pszFormat, va_list va) +{ + RT_NOREF_PV(pThis); + RTTestIFailedV(pszFormat, va); +} + + +static int cidetAppAllocateAndConfigureBuffers(PCIDETAPP pThis) +{ + /* + * Code buffers. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCodeBuffers); i++) + { + int rc = cidetAppAllocateAndConfigureOneBuffer(pThis, &pThis->aCodeBuffers[i], i, true /*fCode*/, + g_aCodeBufCfgs[i].fFlags); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Data buffers. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aDataBuffers); i++) + { + int rc = cidetAppAllocateAndConfigureOneBuffer(pThis, &pThis->aDataBuffers[i], i, false /*fCode*/, + g_aDataBufCfgs[i].fFlags); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Stack. + */ + pThis->cbStack = _32K; + pThis->pbStackLow = (uint8_t *)RTMemPageAlloc(pThis->cbStack); + if (!pThis->pbStackLow) + { + RTTestIFailed("Failed to allocate %u bytes for stack\n", pThis->cbStack); + return false; + } + pThis->pbStackEnd = pThis->pbStackLow + pThis->cbStack; + + return true; +} + + +static int CidetAppCreate(PPCIDETAPP ppThis) +{ + *ppThis = NULL; + + PCIDETAPP pThis = (PCIDETAPP)RTMemAlloc(sizeof(*pThis)); + if (!pThis) + return RTTestIFailedRc(VERR_NO_MEMORY, "Error allocating %zu bytes.", sizeof(*pThis)); + + /* Create a random source. */ + RTRAND hRand; + int rc = RTRandAdvCreateParkMiller(&hRand); + if (RT_SUCCESS(rc)) + { + uint64_t uSeed = ASMReadTSC(); + rc = RTRandAdvSeed(hRand, uSeed); + if (RT_SUCCESS(rc)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "Random seed %#llx\n", uSeed); + + /* Initialize the CIDET structure. */ + rc = CidetCoreInit(&pThis->Core, hRand); + if (RT_SUCCESS(rc)) + { + pThis->Core.pfnReInitDataBuf = CidetAppCbReInitDataBuf; + pThis->Core.pfnSetupDataBuf = CidetAppCbSetupDataBuf; + pThis->Core.pfnIsBufEqual = CidetAppCbIsBufEqual; + pThis->Core.pfnReInitCodeBuf = CidetAppCbReInitCodeBuf; + pThis->Core.pfnSetupCodeBuf = CidetAppCbSetupCodeBuf; + pThis->Core.pfnExecute = CidetAppCbExecute; + pThis->Core.pfnFailure = CidetAppCbFailureV; + + pThis->Core.paCodeBufConfigs = g_aCodeBufCfgs; + pThis->Core.cCodeBufConfigs = CIDETAPP_CODE_BUF_COUNT; + pThis->Core.paDataBufConfigs = g_aDataBufCfgs; + pThis->Core.cDataBufConfigs = CIDETAPP_DATA_BUF_COUNT; + + rc = cidetAppAllocateAndConfigureBuffers(pThis); + if (RT_SUCCESS(rc)) + { + rc = CidetCoreSetTargetMode(&pThis->Core, ARCH_BITS == 32 ? CIDETMODE_PP_32 : CIDETMODE_LM_64); + if (RT_SUCCESS(rc)) + { + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_CS] = ASMGetCS(); + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_DS] = ASMGetDS(); + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_ES] = ASMGetES(); + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_FS] = ASMGetFS(); + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_GS] = ASMGetGS(); + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_SS] = ASMGetSS(); + pThis->Core.InTemplateCtx.aGRegs[X86_GREG_xSP] = (uintptr_t)pThis->pbStackEnd - 64; + + pThis->Core.fTestCfg |= CIDET_TESTCFG_SEG_PRF_CS; + pThis->Core.fTestCfg |= CIDET_TESTCFG_SEG_PRF_DS; + pThis->Core.fTestCfg |= CIDET_TESTCFG_SEG_PRF_ES; +#if !defined(RT_OS_WINDOWS) + pThis->Core.fTestCfg |= CIDET_TESTCFG_SEG_PRF_FS; +#endif +#if !defined(CIDET_LEAVE_GS_ALONE) + pThis->Core.fTestCfg |= CIDET_TESTCFG_SEG_PRF_GS; +#endif + + *ppThis = pThis; + return VINF_SUCCESS; + } + rc = RTTestIFailedRc(rc, "Error setting target mode: %Rrc", rc); + } + CidetCoreDelete(&pThis->Core); + } + else + { + rc = RTTestIFailedRc(rc, "CidetCoreInit failed: %Rrc", rc); + RTRandAdvDestroy(hRand); + } + } + else + rc = RTTestIFailedRc(rc, "RTRandAdvCreate failed: %Rrc", rc); + RTMemFree(pThis); + return rc; +} + + +static void CidetAppDestroy(PCIDETAPP pThis) +{ + CidetCoreDelete(&pThis->Core); + + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCodeBuffers); i++) + CidetAppDeleteBuffer(&pThis->aCodeBuffers[i]); + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aDataBuffers); i++) + CidetAppDeleteBuffer(&pThis->aDataBuffers[i]); + RTMemPageFree(pThis->pbStackLow, pThis->cbStack); + + RTMemFree(pThis); +} + + +static void CidetAppTestBunch(PCIDETAPP pThis, PCCIDETINSTR paInstructions, uint32_t cInstructions, const char *pszBunchName) +{ + for (uint32_t iInstr = 0; iInstr < cInstructions; iInstr++) + { + RTTestSubF(g_hTest, "%s - %s", pszBunchName, paInstructions[iInstr].pszMnemonic); + CidetCoreTestInstruction(&pThis->Core, &paInstructions[iInstr]); + } +} + + +int main(int argc, char **argv) +{ + /* + * Initialize the runtime. + */ + RTEXITCODE rcExit = RTTestInitExAndCreate(argc, &argv, 0, "cidet-app", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* + * Parse arguments. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--noop", 'n', RTGETOPT_REQ_NOTHING }, + }; + + int chOpt; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((chOpt = RTGetOpt(&GetState, &ValueUnion))) + { + switch (chOpt) + { + case 'n': + break; + + case 'h': + RTPrintf("usage: %s\n", argv[0]); + return RTEXITCODE_SUCCESS; + + case 'V': + RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision()); + return RTEXITCODE_SUCCESS; + + default: + return RTGetOptPrintError(chOpt, &ValueUnion); + } + } + +#ifdef USE_SIGNALS + /* + * Set up signal handlers with alternate stack. + */ + /* Get the default signal mask. */ + RTTESTI_CHECK_RC_RET(sigprocmask(SIG_BLOCK, NULL, &g_ProcSigMask), 0, RTEXITCODE_FAILURE); + + /* Alternative stack so we can play with esp/rsp. */ + RT_ZERO(g_AltStack); + g_AltStack.ss_flags = 0; +# ifdef SIGSTKSZ + g_AltStack.ss_size = RT_MAX(SIGSTKSZ, _128K); +# else + g_AltStack.ss_size = _128K; +# endif +#ifdef RT_OS_FREEBSD + g_AltStack.ss_sp = (char *)RTMemPageAlloc(g_AltStack.ss_size); +#else + g_AltStack.ss_sp = RTMemPageAlloc(g_AltStack.ss_size); +#endif + RTTESTI_CHECK_RET(g_AltStack.ss_sp != NULL, RTEXITCODE_FAILURE); + RTTESTI_CHECK_RC_RET(sigaltstack(&g_AltStack, NULL), 0, RTEXITCODE_FAILURE); + + /* Default signal action config. */ + struct sigaction Act; + RT_ZERO(Act); + Act.sa_sigaction = CidetAppSigHandler; + Act.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigfillset(&Act.sa_mask); + + /* Hook the signals we might need. */ + sigaction(SIGILL, &Act, NULL); + sigaction(SIGTRAP, &Act, NULL); +# ifdef SIGEMT + sigaction(SIGEMT, &Act, NULL); +# endif + sigaction(SIGFPE, &Act, NULL); + sigaction(SIGBUS, &Act, NULL); + sigaction(SIGSEGV, &Act, NULL); + +#elif defined(RT_OS_WINDOWS) + /* + * Register vectored exception handler and override the default unhandled + * exception filter, just to be on the safe side... + */ + RTTESTI_CHECK(AddVectoredExceptionHandler(1 /* first */, CidetAppVectoredXcptHandler) != NULL); + SetUnhandledExceptionFilter(CidetAppUnhandledXcptFilter); +#endif + + /* + * Do the work. + */ + RTTestBanner(g_hTest); + + PCIDETAPP pThis; + int rc = CidetAppCreate(&pThis); + if (RT_SUCCESS(rc)) + { + CidetAppTestBunch(pThis, g_aCidetInstructions1, g_cCidetInstructions1, "First Bunch"); + + CidetAppDestroy(pThis); + } + + return RTTestSummaryAndDestroy(g_hTest); +} + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet-appA.asm b/src/VBox/ValidationKit/utils/cpu/cidet-appA.asm new file mode 100644 index 00000000..1f1e8bd5 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet-appA.asm @@ -0,0 +1,309 @@ +; $Id: cidet-appA.asm $ +;; @file +; CPU Instruction Decoding & Execution Tests - Ring-3 Driver Application, Assembly Code. +; + +; +; Copyright (C) 2009-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "cidet.mac" + + +;******************************************************************************* +;* Global Variables * +;******************************************************************************* +%ifdef RT_ARCH_X86 +;; Used by CidetAppSaveAndRestoreCtx when we have a tricky target stack. +g_uTargetEip dd 0 +g_uTargetCs dw 0 +%endif + + +;; +; Leave GS alone on 64-bit darwin (gs is 0, no ldt or gdt entry to load that'll +; restore the lower 32-bits of the base when saving and restoring the register). +%ifdef RT_OS_DARWIN + %ifdef RT_ARCH_AMD64 + %define CIDET_LEAVE_GS_ALONE + %endif +%endif + + + +BEGINCODE + +;; +; ASSUMES that it's called and the EIP/RIP is found on the stack. +; +; @param pSaveCtx ds:xCX The context to save; DS, xDX and xCX have +; already been saved by the caller. +; @param pRestoreCtx ds:xDX The context to restore. +; +BEGINPROC CidetAppSaveAndRestoreCtx + ; + ; Save the stack pointer and program counter first so we can later + ; bypass this step if we need to. + ; + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8], xAX ; need scratch register. + lea xAX, [xSP + xCB] + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xSP * 8], xAX + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_SS * 2], ss + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_CS * 2], cs + mov xAX, [xSP] + mov [xCX + CIDETCPUCTX.rip], xAX + jmp CidetAppSaveAndRestoreCtx_1 + +GLOBALNAME CidetAppSaveAndRestoreCtx_NoSsSpCsIp + mov [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8], xAX +CidetAppSaveAndRestoreCtx_1: + + ; Flags. +%ifdef RT_ARCH_AMD64 + pushfq +%else + pushfd +%endif + pop xAX + mov [xCX + CIDETCPUCTX.rfl], xAX + + ; Segment registers. + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_ES * 2], es + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_FS * 2], fs + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_GS * 2], gs + + ; Remaining GPRs. + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xBX * 8], xBX + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xBP * 8], xBP + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xSI * 8], xSI + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xDI * 8], xDI +%ifdef RT_ARCH_AMD64 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x8 * 8], r8 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x9 * 8], r9 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x10 * 8], r10 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x11 * 8], r11 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x12 * 8], r12 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x13 * 8], r13 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x14 * 8], r14 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x15 * 8], r15 + xor eax, eax + mov [xCX + CIDETCPUCTX.cr2], rax + %ifndef CIDET_REDUCED_CTX + mov [xCX + CIDETCPUCTX.cr0], rax + mov [xCX + CIDETCPUCTX.cr3], rax + mov [xCX + CIDETCPUCTX.cr4], rax + mov [xCX + CIDETCPUCTX.cr8], rax + mov [xCX + CIDETCPUCTX.dr0], rax + mov [xCX + CIDETCPUCTX.dr1], rax + mov [xCX + CIDETCPUCTX.dr2], rax + mov [xCX + CIDETCPUCTX.dr3], rax + mov [xCX + CIDETCPUCTX.dr6], rax + mov [xCX + CIDETCPUCTX.dr7], rax + mov [xCX + CIDETCPUCTX.tr], ax + mov [xCX + CIDETCPUCTX.ldtr], ax + %endif +%else + xor eax, eax + mov [xCX + CIDETCPUCTX.rfl + 4], eax + mov [xCX + CIDETCPUCTX.rip + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xCX * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xDX * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xBX * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xSP * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xBP * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xSI * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xDI * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x8 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x8 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x9 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x9 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x10 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x10 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x11 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x11 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x12 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x12 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x13 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x13 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x14 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x14 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x15 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x15 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.cr2 ], eax + mov [xCX + CIDETCPUCTX.cr2 + 4], eax + %ifndef CIDET_REDUCED_CTX + mov [xCX + CIDETCPUCTX.cr0 ], eax + mov [xCX + CIDETCPUCTX.cr0 + 4], eax + mov [xCX + CIDETCPUCTX.cr3 ], eax + mov [xCX + CIDETCPUCTX.cr3 + 4], eax + mov [xCX + CIDETCPUCTX.cr4 ], eax + mov [xCX + CIDETCPUCTX.cr4 + 4], eax + mov [xCX + CIDETCPUCTX.cr8 ], eax + mov [xCX + CIDETCPUCTX.cr8 + 4], eax + mov [xCX + CIDETCPUCTX.dr0 ], eax + mov [xCX + CIDETCPUCTX.dr0 + 4], eax + mov [xCX + CIDETCPUCTX.dr1 ], eax + mov [xCX + CIDETCPUCTX.dr1 + 4], eax + mov [xCX + CIDETCPUCTX.dr2 ], eax + mov [xCX + CIDETCPUCTX.dr2 + 4], eax + mov [xCX + CIDETCPUCTX.dr3 ], eax + mov [xCX + CIDETCPUCTX.dr3 + 4], eax + mov [xCX + CIDETCPUCTX.dr6 ], eax + mov [xCX + CIDETCPUCTX.dr6 + 4], eax + mov [xCX + CIDETCPUCTX.dr7 ], eax + mov [xCX + CIDETCPUCTX.dr7 + 4], eax + mov [xCX + CIDETCPUCTX.tr], ax + mov [xCX + CIDETCPUCTX.ldtr], ax + %endif +%endif + dec xAX + mov [xCX + CIDETCPUCTX.uErr], xAX +%ifdef RT_ARCH_X86 + mov [xCX + CIDETCPUCTX.uErr + 4], eax +%endif + mov [xCX + CIDETCPUCTX.uXcpt], eax + + ; + ; Restore the other state (pointer in xDX). + ; +NAME(CidetAppSaveAndRestoreCtx_Restore): + + ; Restore ES, FS, and GS. + mov es, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_ES * 2] + mov fs, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_FS * 2] +%ifndef CIDET_LEAVE_GS_ALONE + mov gs, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_GS * 2] +%endif + + ; Restore most GPRs (except xCX, xAX and xSP). + mov xCX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xCX * 8] + mov xBX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xBX * 8] + mov xBP, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xBP * 8] + mov xSI, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xSI * 8] + mov xDI, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xDI * 8] +%ifdef RT_ARCH_AMD64 + mov r8, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x8 * 8] + mov r9, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x9 * 8] + mov r10, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x10 * 8] + mov r11, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x11 * 8] + mov r12, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x12 * 8] + mov r13, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x13 * 8] + mov r14, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x14 * 8] + mov r15, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x15 * 8] +%endif + +%ifdef RT_ARCH_AMD64 + ; Create an iret frame which restores SS:RSP, RFLAGS, and CS:RIP. + movzx eax, word [xDX + CIDETCPUCTX.aSRegs + X86_SREG_SS * 2] + push xAX + push qword [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xSP * 8] + push qword [xDX + CIDETCPUCTX.rfl] + movzx eax, word [xDX + CIDETCPUCTX.aSRegs + X86_SREG_CS * 2] + push xAX + push qword [xDX + CIDETCPUCTX.rip] + + ; Restore DS, xAX and xDX then do the iret. + mov ds, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_DS * 2] + mov xAX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8] + mov xDX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xDX * 8] + iretq +%else + ; In 32-bit mode iret doesn't restore CS:ESP for us, so we have to + ; make a choice whether the SS:ESP is more important than EFLAGS. + cmp byte [xDX + CIDETCPUCTX.fTrickyStack], 0 + jne .tricky_stack + + mov ss, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_SS * 2] + mov xSP, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xSP * 8] + + push dword [xDX + CIDETCPUCTX.rfl] ; iret frame + movzx eax, word [xDX + CIDETCPUCTX.aSRegs + X86_SREG_CS * 2] ; iret frame + push xAX ; iret frame + push dword [xDX + CIDETCPUCTX.rip] ; iret frame + + mov xAX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8] + mov ds, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_DS * 2] + mov xDX, [cs:xDX + CIDETCPUCTX.aGRegs + X86_GREG_xDX * 8] + iretd + +.tricky_stack: + mov xAX, [xDX + CIDETCPUCTX.rip] + mov [g_uTargetEip], xAX + mov ax, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_CS * 2] + mov [g_uTargetCs], ax + push dword [xDX + CIDETCPUCTX.rfl] + popfd + mov ss, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_SS * 2] + mov xSP, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xSP * 8] + mov xAX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8] + mov ds, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_DS * 2] + mov xDX, [cs:xDX + CIDETCPUCTX.aGRegs + X86_GREG_xDX * 8] + jmp far [cs:g_uTargetEip] +%endif +ENDPROC CidetAppSaveAndRestoreCtx + + +;; +; C callable version of CidetAppSaveAndRestoreCtx more or less. +; +; @param pSaveCtx x86:esp+4 gcc:rdi msc:rcx +; @param pRestoreCtx x86:esp+8 gcc:rsi msc:rdx +BEGINPROC CidetAppExecute +%ifdef RT_ARCH_X86 + mov ecx, [esp + 4] + mov edx, [esp + 8] +%elifdef ASM_CALL64_GCC + mov rcx, rdi + mov rdx, rsi +%elifndef ASM_CALL64_MSC + %error "unsupport arch." +%endif + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_DS * 2], ds + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xDX * 8], xDX + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xCX * 8], xCX + jmp NAME(CidetAppSaveAndRestoreCtx) +ENDPROC CidetAppExecute + + +;; +; C callable restore function. +; +; @param pRestoreCtx x86:esp+4 gcc:rdi msc:rcx +BEGINPROC CidetAppRestoreCtx +%ifdef RT_ARCH_X86 + mov edx, [esp + 4] +%elifdef ASM_CALL64_GCC + mov rdx, rdi +%elifdef ASM_CALL64_MSC + mov rdx, rcx +%else + %error "unsupport arch." +%endif + mov ds, [cs:xDX + CIDETCPUCTX.aSRegs + X86_SREG_DS * 2] + jmp NAME(CidetAppSaveAndRestoreCtx_Restore) +ENDPROC CidetAppRestoreCtx + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet-core.cpp b/src/VBox/ValidationKit/utils/cpu/cidet-core.cpp new file mode 100644 index 00000000..4d7bde87 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet-core.cpp @@ -0,0 +1,2358 @@ +/* $Id: cidet-core.cpp $ */ +/** @file + * CPU Instruction Decoding & Execution Tests - Simple Instructions. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define CIDET_INSTR_TEST_OP_FLAG(a_pInstr, a_fFlag) \ + ( ((a_pInstr)->afOperands[0] & (a_fFlag)) \ + || ((a_pInstr)->afOperands[1] & (a_fFlag)) \ + || ( (a_pInstr)->cOperands > 2 \ + && ( ((a_pInstr)->afOperands[2] & (a_fFlag)) \ + || ((a_pInstr)->afOperands[3] & (a_fFlag)) ) ) ) + +#define CIDET_INSTR_TEST_OP_MASK_VALUE(a_pInstr, a_fMask, a_fValue) \ + ( ((a_pInstr)->afOperands[0] & (a_fMask)) == (a_fValue) \ + || ((a_pInstr)->afOperands[1] & (a_fMask)) == (a_fValue) \ + || ( (a_pInstr)->cOperands > 2 \ + && ( ((a_pInstr)->afOperands[2] & (a_fMask)) == (a_fValue) \ + || ((a_pInstr)->afOperands[3] & (a_fMask)) == (a_fValue) ) ) ) + +/** @def CIDET_DPRINTF + * Debug printf. */ +#if 1 //def DEBUG_bird +# define CIDET_DPRINTF(a) do { RTPrintf a; } while (0) +# define CIDET_DPRINTF_ENABLED +#else +# define CIDET_DPRINTF(a) do { } while (0) +#endif + +/** @def CIDET_DEBUG_DISAS + * Enables instruction disassembly. */ +#if defined(DOXYGEN_RUNNING) +# define CIDET_DEBUG_DISAS 1 +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "cidet.h" + +#include <iprt/assert.h> +#include <iprt/rand.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/errcore.h> +#if defined(CIDET_DPRINTF_ENABLED) || defined(CIDET_DEBUG_DISAS) +# include <VBox/dis.h> +# include <iprt/stream.h> +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** For translating CIDET_OF_Z_XXX values (after shifting). */ +uint16_t const g_acbCidetOfSizes[] = +{ + /* [CIDET_OF_Z_NONE] = */ 0, + /* [CIDET_OF_Z_BYTE] = */ 1, + /* [CIDET_OF_Z_WORD] = */ 2, + /* [CIDET_OF_Z_DWORD] = */ 4, + /* [CIDET_OF_Z_QWORD] = */ 8, + /* [CIDET_OF_Z_TBYTE] = */ 10, + /* [CIDET_OF_Z_OWORD] = */ 16, + /* [CIDET_OF_Z_YWORD] = */ 32, + /* [CIDET_OF_Z_ZWORD] = */ 64, + /* [CIDET_OF_Z_VAR_WDQ] = */ UINT16_MAX, + /* [0xa] = */ 0, + /* [0xb] = */ 0, + /* [0xc] = */ 0, + /* [0xd] = */ 0, + /* [0xe] = */ 0, + /* [CIDET_OF_Z_SPECIAL] = */ UINT16_MAX - 1, +}; + + +/** Converts operand sizes in bytes to 64-bit masks. */ +static const uint64_t g_au64ByteSizeToMask[] = +{ + UINT64_C(0x0000000000000000), + UINT64_C(0x00000000000000ff), + UINT64_C(0x000000000000ffff), + UINT64_C(0x0000000000ffffff), + UINT64_C(0x00000000ffffffff), + UINT64_C(0x000000ffffffffff), + UINT64_C(0x0000ffffffffffff), + UINT64_C(0x00ffffffffffffff), + UINT64_C(0xffffffffffffffff), +}; + +/** Converts operand sizes in bytes to 64-bit signed max values. */ +static const int64_t g_ai64ByteSizeToMax[] = +{ + INT64_C(0x0000000000000000), + INT64_C(0x000000000000007f), + INT64_C(0x0000000000007fff), + INT64_C(0x00000000007fffff), + INT64_C(0x000000007fffffff), + INT64_C(0x0000007fffffffff), + INT64_C(0x00007fffffffffff), + INT64_C(0x007fffffffffffff), + INT64_C(0x7fffffffffffffff), +}; + + +bool CidetInstrHasMrmMemOperand(PCCIDETINSTR pInstr) +{ + return CIDET_INSTR_TEST_OP_FLAG(pInstr, CIDET_OF_M_RM_ONLY_M); +} + + +bool CidetInstrHasMrmRegOperand(PCCIDETINSTR pInstr) +{ + return CIDET_INSTR_TEST_OP_FLAG(pInstr, CIDET_OF_M_RM_ONLY_R); +} + + +bool CidetInstrRespondsToOperandSizePrefixes(PCCIDETINSTR pInstr) +{ + return CIDET_INSTR_TEST_OP_MASK_VALUE(pInstr, CIDET_OF_Z_MASK, CIDET_OF_Z_VAR_WDQ); +} + + + + +int CidetCoreInit(PCIDETCORE pThis, RTRAND hRand) +{ + AssertPtr(pThis); + AssertPtr(hRand); + + RT_ZERO(*pThis); + pThis->u32Magic = CIDETCORE_MAGIC; + pThis->hRand = hRand; + return VINF_SUCCESS; +} + + +void CidetCoreDelete(PCIDETCORE pThis) +{ + AssertPtr(pThis); Assert(pThis->u32Magic == CIDETCORE_MAGIC); + + RTRandAdvDestroy(pThis->hRand); + RT_ZERO(*pThis); +} + + +/** + * Report a test failure via CIDET::pfnFailure + * + * @returns false + * @param pThis Pointer to the core structure. + * @param pszFormat Format string containing failure details. + * @param va Arguments referenced in @a pszFormat. + */ +int CidetCoreSetErrorV(PCIDETCORE pThis, const char *pszFormat, va_list va) +{ + pThis->pfnFailure(pThis, pszFormat, va); + return false; +} + + +/** + * Report a test failure via CIDET::pfnFailure + * + * @returns false + * @param pThis Pointer to the core structure. + * @param pszFormat Format string containing failure details. + * @param ... Arguments referenced in @a pszFormat. + */ +bool CidetCoreSetError(PCIDETCORE pThis, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + CidetCoreSetErrorV(pThis, pszFormat, va); + va_end(va); + return false; +} + + +/** + * Get a signed random number, with a given number of significant bytes. + * + * @returns Random number. + * @param pThis Pointer to the core structure. + * @param cbSignificant The number of significant bytes. + */ +int64_t CidetCoreGetRandS64(PCIDETCORE pThis, uint8_t cbSignificant) +{ + int64_t iVal = RTRandAdvS64(pThis->hRand); + switch (cbSignificant) + { + case 1: return (int8_t)iVal; + case 2: return (int16_t)iVal; + case 4: return (int32_t)iVal; + case 8: return iVal; + default: + AssertReleaseFailed(); + return iVal; + } +} + + +/** + * Get an unsigned random number, with a given number of significant bytes. + * + * @returns Random number. + * @param pThis Pointer to the core structure. + * @param cbSignificant The number of significant bytes. + */ +uint64_t CidetCoreGetRandU64(PCIDETCORE pThis, uint8_t cbSignificant) +{ + Assert(cbSignificant == 1 || cbSignificant == 2 || cbSignificant == 4 || cbSignificant == 8); + + uint64_t uVal = RTRandAdvU64(pThis->hRand); + uVal &= g_au64ByteSizeToMask[cbSignificant]; + + return uVal; +} + + + +void CidetCoreInitializeCtxTemplate(PCIDETCORE pThis) +{ + pThis->InTemplateCtx.rip = UINT64_MAX; + pThis->InTemplateCtx.rfl = X86_EFL_1 | X86_EFL_ID | X86_EFL_IF; + + unsigned i = RT_ELEMENTS(pThis->InTemplateCtx.aGRegs); + if (CIDETMODE_IS_LM(pThis->bMode)) + while (i-- > 0) + pThis->InTemplateCtx.aGRegs[i] = UINT64_C(0x3fefcc00daba005d) + | ((uint64_t)i << 32) + | ((uint32_t)i << 8); + else + while (i-- > 0) + pThis->InTemplateCtx.aGRegs[i] = UINT64_C(0xfada009b) + | ((uint32_t)i << 12) + | ((uint32_t)i << 8); + i = RT_ELEMENTS(pThis->InTemplateCtx.aSRegs); + while (i-- > 0) + pThis->InTemplateCtx.aSRegs[i] = 0; /* Front end sets these afterwards. */ + pThis->InTemplateCtx.cr2 = 0; +#ifndef CIDET_REDUCED_CTX + pThis->InTemplateCtx.tr = 0; + pThis->InTemplateCtx.ldtr = 0; + pThis->InTemplateCtx.cr0 = 0; + pThis->InTemplateCtx.cr3 = 0; + pThis->InTemplateCtx.cr4 = 0; + pThis->InTemplateCtx.cr8 = 0; +#endif + pThis->InTemplateCtx.fIgnoredRFlags = 0; + pThis->InTemplateCtx.uXcpt = UINT32_MAX; + pThis->InTemplateCtx.uErr = UINT64_MAX; + pThis->InTemplateCtx.fTrickyStack = false; +} + + +/** + * Sets the target mode. + * + * Caller must set up default selector values after calling this function. + * + * @returns VBox status code. + * @param pThis Pointer to the core structure. + * @param bMode The new mode. + */ +int CidetCoreSetTargetMode(PCIDETCORE pThis, uint8_t bMode) +{ + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); AssertReturn(pThis->u32Magic == CIDETCORE_MAGIC, VERR_INVALID_HANDLE); + switch (bMode) + { + //case CIDETMODE_RM: + //case CIDETMODE_PE_16: + //case CIDETMODE_PE_32: + //case CIDETMODE_PE_V86: + //case CIDETMODE_PP_16: + case CIDETMODE_PP_32: + //case CIDETMODE_PP_V86: + //case CIDETMODE_PAE_16: + case CIDETMODE_PAE_32: + //case CIDETMODE_PAE_V86: + //case CIDETMODE_LM_S16: + //case CIDETMODE_LM_32: + case CIDETMODE_LM_64: + break; + default: + return VERR_NOT_IMPLEMENTED; + } + pThis->bMode = bMode; + CidetCoreInitializeCtxTemplate(pThis); + return VINF_SUCCESS; +} + + +bool CidetCoreIsEncodingCompatibleWithInstruction(PCIDETCORE pThis) +{ + RT_NOREF_PV(pThis); + return true; +} + + +/** + * Selects the next address size mode. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + */ +static bool cidetCoreSetupNextBaseEncoding_AddressSize(PCIDETCORE pThis) +{ + if (pThis->fAddrSizePrf) + { + /* + * Reset to default. + */ + pThis->cbAddrMode = CIDETMODE_GET_BYTE_COUNT(pThis->bMode); + pThis->fAddrSizePrf = false; + } + else + { + /* + * The other addressing size. + */ + if (CIDETMODE_IS_64BIT(pThis->bMode)) + pThis->cbAddrMode = 4; + else if (CIDETMODE_IS_32BIT(pThis->bMode)) + pThis->cbAddrMode = 2; + else + { + AssertRelease(CIDETMODE_IS_16BIT(pThis->bMode)); + pThis->cbAddrMode = 2; + } + pThis->fAddrSizePrf = true; + } + return pThis->fAddrSizePrf; +} + + +/** + * Selects the first REG encoding. + * + * @param pThis The core state structure. + */ +static void cidetCoreSetupFirstBaseEncoding_MrmReg(PCIDETCORE pThis) +{ + pThis->aOperands[pThis->idxMrmRegOp].iReg = 0; + pThis->aOperands[pThis->idxMrmRegOp].fIsMem = false; + pThis->aOperands[pThis->idxMrmRegOp].fIsRipRelative = false; + pThis->aOperands[pThis->idxMrmRegOp].fIsHighByteRegister = false; + pThis->aOperands[pThis->idxMrmRegOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRegOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRegOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRegOp].uMemScale = 1; + pThis->aOperands[pThis->idxMrmRegOp].iEffSeg = UINT8_MAX; + pThis->bModRm &= ~X86_MODRM_REG_MASK; + pThis->fRexR = false; +} + + +/** + * Selects the next REG (ModR/M) encoding. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_MrmReg(PCIDETCORE pThis, uint8_t iReg) +{ + Assert(pThis->idxMrmRegOp < RT_ELEMENTS(pThis->aOperands) && !pThis->aOperands[pThis->idxMrmRegOp].fIsMem); + Assert(iReg < 16); + + /* + * Clear the collision flags here because of the byte register kludge. + */ + pThis->fHasRegCollisionDirect = false; + pThis->fHasRegCollisionMemBase = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasRegCollisionMem = false; + + /* + * Clear the REX prefix and high byte register tracking too. ASSUMES MrmReg is after MrmRmMod. + */ + Assert(!pThis->fNoRexPrefixMrmRm); + Assert(!pThis->fHasHighByteRegInMrmRm); + pThis->fNoRexPrefixMrmReg = false; + pThis->fNoRexPrefix = false; + pThis->fHasHighByteRegInMrmReg = false; + pThis->aOperands[pThis->idxMrmRegOp].fIsHighByteRegister = false; + + /* + * Special kludge for ah, ch, dh, bh, spl, bpl, sil, and dil. + * Needs extra care in 64-bit mode and special collision detection code. + */ + CIDET_DPRINTF(("aOperands[%u].cb=%u fGpr=%u iReg=%d fRex=%d fRexW=%u fRexX=%u fRexB=%u fRexR=%d\n", + pThis->idxMrmRegOp, pThis->aOperands[pThis->idxMrmRegOp].cb, CIDET_OF_K_IS_GPR(pThis->fMrmRegOp), iReg, + pThis->fRex, pThis->fRexW, pThis->fRexX, pThis->fRexB, pThis->fRexR)); + if ( pThis->aOperands[pThis->idxMrmRegOp].cb == 1 + && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) + && iReg >= 3 + && ( iReg <= 6 + || (CIDETMODE_IS_64BIT(pThis->bMode) && iReg == 7 && !pThis->fRex)) ) + + { + if (!pThis->fRex && iReg >= 4 && CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix) + { + /* The AMD64 low variants: spl, bpl, sil and dil. */ + pThis->fRex = true; + pThis->fHasStackRegInMrmReg = iReg == X86_GREG_xSP; + + /* Check for collisions. */ + if (pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands)) + { + Assert(!pThis->fHasHighByteRegInMrmRm); + if (!pThis->aOperands[pThis->idxMrmRmOp].fIsMem) + pThis->fHasRegCollisionDirect = CIDET_OF_K_IS_GPR(pThis->fMrmRmOp) + && iReg == pThis->aOperands[pThis->idxMrmRmOp].iReg; + else + { + Assert(!pThis->fUsesVexIndexRegs || pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg == UINT8_MAX); + + pThis->fHasRegCollisionMemBase = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg; + pThis->fHasRegCollisionMemIndex = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + } + } + else + { + /* Next register: ah, ch, dh and bh. */ + iReg++; + pThis->aOperands[pThis->idxMrmRegOp].iReg = iReg; + pThis->bModRm &= ~X86_MODRM_REG_MASK; + pThis->bModRm |= (iReg & X86_MODRM_REG_SMASK) << X86_MODRM_REG_SHIFT; + pThis->fRex = false; + pThis->fRexR = false; + pThis->fNoRexPrefixMrmReg = true; + pThis->fNoRexPrefix = true; + pThis->fHasHighByteRegInMrmReg = true; + pThis->fHasStackRegInMrmReg = false; + pThis->aOperands[pThis->idxMrmRegOp].fIsHighByteRegister = true; + Assert(!pThis->fRexW); Assert(!pThis->fRexX); Assert(!pThis->fRexB); + + /* Check for collisions. */ + if (pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands)) + { + if (!pThis->aOperands[pThis->idxMrmRmOp].fIsMem) + pThis->fHasRegCollisionDirect = CIDET_OF_K_IS_GPR(pThis->fMrmRmOp) + && ( ( pThis->aOperands[pThis->idxMrmRmOp].cb == 1 + && iReg == pThis->aOperands[pThis->idxMrmRmOp].iReg + && pThis->fHasHighByteRegInMrmRm) + || ( pThis->aOperands[pThis->idxMrmRmOp].cb > 1 + && iReg - 4 == pThis->aOperands[pThis->idxMrmRmOp].iReg)); + else + { + Assert(!pThis->fUsesVexIndexRegs || pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg == UINT8_MAX); + + pThis->fHasRegCollisionMemBase = iReg - 4 == pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg; + pThis->fHasRegCollisionMemIndex = iReg - 4 == pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + } + } + return true; + } + + Assert(!pThis->fRex || (iReg == 7 && CIDETMODE_IS_64BIT(pThis->bMode))); + pThis->fRex = false; + + /* + * Next register. + */ + iReg = (iReg + 1) & (CIDETMODE_IS_64BIT(pThis->bMode) ? 15 : 7); + + pThis->aOperands[pThis->idxMrmRegOp].iReg = iReg; + pThis->bModRm &= ~X86_MODRM_REG_MASK; + pThis->bModRm |= (iReg & X86_MODRM_REG_SMASK) << X86_MODRM_REG_SHIFT; + pThis->fRexR = iReg >= 8; + pThis->fHasStackRegInMrmReg = iReg == X86_GREG_xSP && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + + /* + * Register collision detection. + */ + if (pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands)) + { + if (!pThis->aOperands[pThis->idxMrmRmOp].fIsMem) + pThis->fHasRegCollisionDirect = iReg == pThis->aOperands[pThis->idxMrmRmOp].iReg + && CIDET_OF_K_IS_SAME(pThis->fMrmRmOp, pThis->fMrmRegOp); + else if (CIDET_OF_K_IS_GPR(pThis->fMrmRegOp)) + { + Assert(!pThis->fUsesVexIndexRegs || pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg == UINT8_MAX); + pThis->fHasRegCollisionMemBase = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg; + pThis->fHasRegCollisionMemIndex = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + } + Assert(!pThis->fSib); + + return iReg != 0; +} + + +/** + * Selects the next MOD & R/M encoding, 16-bit addressing variant. + * + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static void cidetCoreSetupFirstBaseEncoding_MrmRmMod_16bit(PCIDETCORE pThis, uint8_t iReg) +{ + if (CidetInstrHasMrmRegOperand(pThis->pCurInstr)) + { + pThis->aOperands[pThis->idxMrmRmOp].iReg = 0; + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = false; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->aOperands[pThis->idxMrmRmOp].iEffSeg = UINT8_MAX; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->bModRm |= 3 << X86_MODRM_MOD_SHIFT; + pThis->fRexB = false; + pThis->fRexX = false; + pThis->fHasMemoryOperand = false; + pThis->fHasRegCollisionDirect = iReg == 0 + && CIDET_OF_K_IS_SAME(pThis->fMrmRmOp, pThis->fMrmRegOp); + pThis->fHasRegCollisionMem = false; + pThis->fHasRegCollisionMemBase = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasStackRegInMrmRmBase = false; + } + else + { + Assert(CidetInstrHasMrmMemOperand(pThis->pCurInstr)); + pThis->aOperands[pThis->idxMrmRmOp].iReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = true; + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = false; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xSI; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->aOperands[pThis->idxMrmRmOp].iEffSeg = UINT8_MAX; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->fRexB = false; + pThis->fRexX = false; + pThis->fHasMemoryOperand = true; + pThis->fHasRegCollisionDirect = false; + iReg -= pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasRegCollisionMemBase = iReg == X86_GREG_xBX && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + pThis->fHasRegCollisionMemIndex = iReg == X86_GREG_xSI && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + pThis->fHasStackRegInMrmRmBase = false; + } +} + + +/** + * Selects the next MOD & R/M encoding, 16-bit addressing variant. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_MrmRmMod_16bit(PCIDETCORE pThis, uint8_t iReg) +{ + AssertRelease(!pThis->fRexB); + AssertRelease(!pThis->fRexX); + uint8_t iRm = pThis->bModRm & X86_MODRM_RM_MASK; + uint8_t iMod = (pThis->bModRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK; + if (iMod == 3) + { + /* + * Register access mode. + */ + Assert(pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands) && !pThis->aOperands[pThis->idxMrmRmOp].fIsMem); + Assert(!pThis->fHasMemoryOperand); + Assert(!pThis->fHasRegCollisionMem); + Assert(!pThis->fHasRegCollisionMemBase); + Assert(!pThis->fHasRegCollisionMemIndex); + if (iRm < 7) + { + iRm++; + pThis->aOperands[pThis->idxMrmRmOp].iReg = iRm; + pThis->bModRm &= ~X86_MODRM_RM_MASK; + pThis->bModRm |= iRm; + pThis->fHasRegCollisionDirect = iRm == iReg + && CIDET_OF_K_IS_SAME(pThis->fMrmRmOp, pThis->fMrmRegOp); + pThis->fHasStackRegInMrmRmBase = iRm == X86_GREG_xSP && CIDET_OF_K_IS_GPR(pThis->fMrmRmOp); + return true; + } + + /* If no memory modes, we're done. */ + if (!CidetInstrHasMrmMemOperand(pThis->pCurInstr)) + { + cidetCoreSetupFirstBaseEncoding_MrmRmMod_16bit(pThis, iReg); + return false; + } + + /* Next mode: 16-bit memory addressing without displacement. */ + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = true; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + iMod = 0; + } + else + { + /* + * Memory access mode. + */ + Assert(pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands) && pThis->aOperands[pThis->idxMrmRmOp].fIsMem); + Assert(pThis->fHasMemoryOperand); + if (iRm < 7) + { + iRm++; + switch (iRm) + { + case 1: + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xDI; + break; + case 2: + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBP; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xSI; + break; + case 3: + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBP; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xDI; + break; + case 4: + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xSI; + break; + case 5: + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xDI; + break; + case 6: + if (iMod == 0) + { + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 2; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + } + else + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBP; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + break; + case 7: + if (iMod == 0) + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + break; + default: AssertReleaseFailed(); + } + pThis->bModRm &= ~X86_MODRM_RM_MASK; + pThis->bModRm |= iRm; + if (CIDET_OF_K_IS_GPR(pThis->fMrmRegOp)) + { + iReg -= pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasRegCollisionMemBase = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg; + pThis->fHasRegCollisionMemIndex = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + return true; + } + + /* Last mode? */ + if (iMod >= 2) + { + cidetCoreSetupFirstBaseEncoding_MrmRmMod_16bit(pThis, iReg); + return false; + } + + /* Next memory addressing mode (if any). */ + iMod++; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp++; + } + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xSI; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->bModRm |= iMod << X86_MODRM_MOD_SHIFT; + pThis->fHasMemoryOperand = true; + pThis->fHasRegCollisionDirect = false; + pThis->fHasStackRegInMrmRmBase = false; + if (CIDET_OF_K_IS_GPR(pThis->fMrmRmOp)) + { + iReg -= pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasRegCollisionMemBase = iReg == X86_GREG_xBX; + pThis->fHasRegCollisionMemIndex = iReg == X86_GREG_xSI; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + return true; +} + + +/** + * Selects the first MOD & R/M encoding, 32-bit and 64-bit addressing variant. + * + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + * @param f64Bit Set if 64-bit, clear if 32-bit. + */ +static void cidetCoreSetupFirstBaseEncoding_MrmRmMod_32bit64bit(PCIDETCORE pThis, uint8_t iReg, bool f64Bit) +{ + RT_NOREF_PV(f64Bit); + if (CidetInstrHasMrmRegOperand(pThis->pCurInstr)) + { + pThis->aOperands[pThis->idxMrmRmOp].iReg = 0; + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = false; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->aOperands[pThis->idxMrmRmOp].iEffSeg = UINT8_MAX; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->bModRm |= 3 << X86_MODRM_MOD_SHIFT; + pThis->fRexB = false; + pThis->fRexX = false; + pThis->fHasMemoryOperand = false; + pThis->fHasRegCollisionDirect = iReg == 0 + && CIDET_OF_K_IS_SAME(pThis->fMrmRmOp, pThis->fMrmRegOp); + pThis->fHasRegCollisionMem = false; + pThis->fHasRegCollisionMemBase = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasStackRegInMrmRmBase = false; + } + else + { + Assert(CidetInstrHasMrmMemOperand(pThis->pCurInstr)); + pThis->aOperands[pThis->idxMrmRmOp].iReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = true; + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = false; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->aOperands[pThis->idxMrmRmOp].iEffSeg = UINT8_MAX; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->fRexB = false; + pThis->fRexX = false; + pThis->fHasMemoryOperand = true; + pThis->fHasRegCollisionDirect = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasRegCollisionMemBase = iReg == pThis->fHasHighByteRegInMrmReg * 4 && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase; + pThis->fHasStackRegInMrmRmBase = false; + } +} + + +/** + * Selects the next MOD & R/M encoding, 32-bit and 64-bit addressing variant. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + * @param f64Bit Set if 64-bit, clear if 32-bit. + */ +static bool cidetCoreSetupNextBaseEncoding_MrmRmMod_32bit64bit(PCIDETCORE pThis, uint8_t iReg, bool f64Bit) +{ + AssertRelease(!pThis->fRexX || CIDETMODE_IS_64BIT(pThis->bMode)); + AssertRelease(!pThis->fRexB || CIDETMODE_IS_64BIT(pThis->bMode)); + uint8_t iRm = (pThis->bModRm & X86_MODRM_RM_MASK) + pThis->fRexB * 8; + uint8_t iMod = (pThis->bModRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK; + if (iMod == 3) + { + /* + * Register access mode. + */ + Assert(pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands) && !pThis->aOperands[pThis->idxMrmRmOp].fIsMem); + Assert(!pThis->fHasMemoryOperand); + Assert(!pThis->fHasRegCollisionMem); + Assert(!pThis->fHasRegCollisionMemBase); + Assert(!pThis->fHasRegCollisionMemIndex); + + if (CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fRexX && !pThis->fNoRexPrefix) /* should be ignored. */ + { + pThis->fRexX = true; + return true; + } + + /* Reset the byte register kludges variables. */ + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = false; + pThis->fHasHighByteRegInMrmRm = false; + pThis->fNoRexPrefixMrmRm = false; + pThis->fNoRexPrefix = pThis->fNoRexPrefixMrmReg; + + if (iRm < (CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix ? 15 : 7)) + { + /* + * Byte register kludge. + */ + if ( pThis->aOperands[pThis->idxMrmRmOp].cb == 1 + && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) + && iRm >= 3 + && ( iRm <= 6 + || (iRm == 7 && CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fRexX) ) ) + { + if (!pThis->fRexX && iRm >= 4 && CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix) + { + /* The AMD64 low variants: spl, bpl, sil and dil. (Using fRexX here as REG covers fRex.) */ + pThis->fRexX = true; + pThis->fHasRegCollisionDirect = CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) + && iRm == iReg - pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasStackRegInMrmRmBase = iRm == X86_GREG_xSP && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + } + else + { + /* Next register: ah, ch, dh and bh. */ + iRm++; + pThis->aOperands[pThis->idxMrmRmOp].iReg = iRm; + pThis->bModRm &= ~X86_MODRM_RM_MASK; + pThis->bModRm |= iRm & X86_MODRM_RM_MASK; + pThis->fRexB = false; + pThis->fRexX = false; + if (!pThis->fRexR && !pThis->fRexW && !pThis->fRex) + { + pThis->fNoRexPrefixMrmRm = true; + pThis->fNoRexPrefix = true; + pThis->fHasHighByteRegInMrmRm = true; + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = true; + pThis->fHasRegCollisionDirect = CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) + && iRm - 4 == iReg - pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasStackRegInMrmRmBase = false; + + } + else + { + /* Can't do the high stuff, so do the spl, bpl, sil and dil variation instead. + Note! We don't set the RexX yet since the base register or operand width holds it down. */ + pThis->fHasRegCollisionDirect = CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) + && iRm == iReg - pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasStackRegInMrmRmBase = iRm == X86_GREG_xSP && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + } + } + } + /* + * Normal register. + */ + else + { + iRm++; + pThis->aOperands[pThis->idxMrmRmOp].iReg = iRm; + pThis->bModRm &= ~X86_MODRM_RM_MASK; + pThis->bModRm |= iRm & X86_MODRM_RM_MASK; + pThis->fRexB = iRm >= 8; + pThis->fRexX = false; + pThis->fHasRegCollisionDirect = iRm == iReg && CIDET_OF_K_IS_SAME(pThis->fMrmRmOp, pThis->fMrmRegOp); + pThis->fHasStackRegInMrmRmBase = iRm == X86_GREG_xSP && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + } + return true; + } + + /* If no memory modes, we're done. */ + if (!CidetInstrHasMrmMemOperand(pThis->pCurInstr)) + { + cidetCoreSetupFirstBaseEncoding_MrmRmMod_32bit64bit(pThis, iReg, f64Bit); + return false; + } + + /* Next mode: 32-bit/64-bit memory addressing without displacement. */ + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = true; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + iMod = 0; + } + else + { + /* + * Memory access mode. + */ + Assert(pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands) && pThis->aOperands[pThis->idxMrmRmOp].fIsMem); + Assert(pThis->fHasMemoryOperand); + Assert(!pThis->fHasStackRegInMrmRmBase); + if (iRm < (CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix ? 15 : 7)) + { + iRm++; + if (iRm == 12) + iRm++; /* Leave REX.B=1 to the next-sib-base function. */ + if (iRm == 4) + { + /* SIB */ + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = 0; + pThis->fSib = true; + pThis->bSib = 0; + } + else if ((iRm & 7) == 5 && iMod == 0) + { + /* Absolute or wrt rip addressing. */ + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = CIDETMODE_IS_64BIT(pThis->bMode); + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 4; + } + else + { + if ((iRm & 7) == 6 && iMod == 0) + { + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = false; + } + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = iRm; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + } + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->bModRm &= ~X86_MODRM_RM_MASK; + pThis->bModRm |= iRm & X86_MODRM_RM_MASK; + pThis->fRexB = iRm >= 8; + pThis->fRexX = false; + if (CIDET_OF_K_IS_GPR(pThis->fMrmRegOp)) + { + iReg -= pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasRegCollisionMemBase = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg; + pThis->fHasRegCollisionMemIndex = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + return true; + } + + /* Last mode? */ + if (iMod >= 2) + { + cidetCoreSetupFirstBaseEncoding_MrmRmMod_32bit64bit(pThis, iReg, f64Bit); + return false; + } + + /* Next memory addressing mode (if any). */ + iMod++; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = iMod == 1 ? 1 : 4; + } + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->bModRm |= iMod << X86_MODRM_MOD_SHIFT; + pThis->fRexB = false; + pThis->fRexX = false; + pThis->fHasMemoryOperand = true; + pThis->fHasRegCollisionDirect = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasRegCollisionMemBase = iReg == pThis->fHasHighByteRegInMrmReg * 4 + && CIDET_OF_K_IS_GPR(pThis->fMrmRmOp); + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase; + pThis->fHasStackRegInMrmRmBase = false; + return true; +} + + +/** + * Selects the next MOD & R/M encoding. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_MrmRmMod(PCIDETCORE pThis, uint8_t iReg) +{ + if (pThis->cbAddrMode == 2) + return cidetCoreSetupNextBaseEncoding_MrmRmMod_16bit(pThis, iReg); + if (pThis->cbAddrMode == 4) + return cidetCoreSetupNextBaseEncoding_MrmRmMod_32bit64bit(pThis, iReg, false); + if (pThis->cbAddrMode == 8) + return cidetCoreSetupNextBaseEncoding_MrmRmMod_32bit64bit(pThis, iReg, true); + AssertReleaseFailedReturn(false); +} + + + +/** + * Selects the next SIB base register (/ encoding). + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_SibBase(PCIDETCORE pThis, uint8_t iReg) +{ + AssertRelease(!pThis->fRexB || CIDETMODE_IS_64BIT(pThis->bMode)); + + uint8_t iBase = (pThis->bSib & X86_SIB_BASE_MASK) + pThis->fRexB * 8; + iBase = (iBase + 1) & (CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix ? 15 : 7); + + if ((iBase & 7) == 5 && (pThis->bModRm & X86_MODRM_MOD_MASK) == 0) + { + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 4; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + } + else + { + if ((iBase & 7) == 6 && (pThis->bModRm & X86_MODRM_MOD_MASK) == 0) + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = iBase; + } + pThis->bSib &= ~X86_SIB_BASE_MASK; + pThis->bSib |= iBase & X86_SIB_BASE_MASK; + pThis->fRexB = iBase >= 8; + pThis->fHasRegCollisionMemBase = pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg + == iReg - pThis->fHasHighByteRegInMrmReg * 4 + && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + pThis->fHasStackRegInMrmRmBase = iBase == X86_GREG_xSP; + + return iBase != 0; +} + + +/** + * Selects the next SIB index register (/ encoding). + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_SibIndex(PCIDETCORE pThis, uint8_t iReg) +{ + AssertRelease(!pThis->fRexX || CIDETMODE_IS_64BIT(pThis->bMode)); + Assert(pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands) && pThis->aOperands[pThis->idxMrmRmOp].fIsMem); + + uint8_t iIndex = ((pThis->bSib >> X86_SIB_INDEX_SHIFT) & X86_SIB_INDEX_SMASK) + pThis->fRexX * 8; + iIndex = (iIndex + 1) & (CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix ? 15 : 7); + + if (iIndex == 4 && !pThis->fUsesVexIndexRegs) + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + else + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = iIndex; + pThis->bSib &= ~X86_SIB_INDEX_MASK; + pThis->bSib |= (iIndex & X86_SIB_INDEX_SMASK) << X86_SIB_INDEX_SHIFT; + pThis->fRexX = iIndex >= 8; + pThis->fHasRegCollisionMemIndex = pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg + == iReg - pThis->fHasHighByteRegInMrmReg * 4 + && ( !pThis->fUsesVexIndexRegs + ? CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) : CIDET_OF_K_IS_VRX(pThis->fMrmRegOp) ); + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + + return iIndex != 0; +} + + +/** + * Selects the next SIB scale. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_SibScale(PCIDETCORE pThis, uint8_t iReg) +{ + RT_NOREF_PV(iReg); + switch ((pThis->bSib >> X86_SIB_SCALE_SHIFT) & X86_SIB_SCALE_SMASK) + { + case 0: + pThis->bSib |= 1 << X86_SIB_SCALE_SHIFT; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 2; + return true; + case 1: + pThis->bSib &= ~X86_SIB_SCALE_MASK; + pThis->bSib |= 2 << X86_SIB_SCALE_SHIFT; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 4; + return true; + case 2: + pThis->bSib |= 3 << X86_SIB_SCALE_SHIFT; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 8; + return true; + case 3: + pThis->bSib &= ~X86_SIB_SCALE_MASK; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + return false; + + default: AssertReleaseFailedReturn(false); + } +} + + +/** + * Selects the next segment prefix. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + */ +static bool cidetCoreSetupNextBaseEncoding_SegmentPrefix(PCIDETCORE pThis) +{ + if ( pThis->fHasMemoryOperand + && (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_MASK)) + { + switch (pThis->uSegPrf) + { + case X86_SREG_COUNT: + pThis->uSegPrf = X86_SREG_ES; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_ES) + return true; + RT_FALL_THRU(); + case X86_SREG_ES: + pThis->uSegPrf = X86_SREG_CS; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_CS) + return true; + RT_FALL_THRU(); + case X86_SREG_CS: + pThis->uSegPrf = X86_SREG_SS; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_SS) + return true; + RT_FALL_THRU(); + case X86_SREG_SS: + pThis->uSegPrf = X86_SREG_DS; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_DS) + return true; + RT_FALL_THRU(); + case X86_SREG_DS: + pThis->uSegPrf = X86_SREG_FS; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_FS) + return true; + RT_FALL_THRU(); + case X86_SREG_FS: + pThis->uSegPrf = X86_SREG_GS; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_GS) + return true; + RT_FALL_THRU(); + case X86_SREG_GS: + break; + default: AssertReleaseFailedBreak(); + } + pThis->uSegPrf = X86_SREG_COUNT; + } + return false; +} + + +/** + * Updates the variable sized operands. + * + * @param pThis The core state structure. + */ +static void cidetCoreUpdateOperandSizes(PCIDETCORE pThis) +{ + uint8_t iOp = pThis->cOperands; + while (iOp-- > 0) + pThis->aOperands[iOp].cb = (uint8_t)CidetCoreGetOperandSize(pThis, iOp); +} + + +/** + * Selects the next operand size. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + */ +static bool cidetCoreSetupNextBaseEncoding_OperandSize(PCIDETCORE pThis) +{ + if (CidetInstrRespondsToOperandSizePrefixes(pThis->pCurInstr)) + { + if (CIDETMODE_IS_64BIT(pThis->bMode)) + { + switch (pThis->fOpSizePrf + pThis->fRexW * 2) + { + case 0: + pThis->fOpSizePrf = true; + cidetCoreUpdateOperandSizes(pThis); + return true; + case 1: + pThis->fOpSizePrf = false; + if (pThis->fNoRexPrefix) + break; + pThis->fRexW = true; + cidetCoreUpdateOperandSizes(pThis); + return true; + case 2: + pThis->fOpSizePrf = true; /* check that it's ignored. */ + cidetCoreUpdateOperandSizes(pThis); + return true; + default: AssertReleaseFailed(); + case 3: + break; + } + } + else + { + if (!pThis->fOpSizePrf) + { + pThis->fOpSizePrf = true; + cidetCoreUpdateOperandSizes(pThis); + return true; + } + } + pThis->fRexW = false; + pThis->fOpSizePrf = false; + cidetCoreUpdateOperandSizes(pThis); + } + return false; +} + + +bool CidetCoreSetupNextBaseEncoding(PCIDETCORE pThis) +{ + if (pThis->fUsesModRm) + { + /* + * The wheels are lined up as follows: + * 1. Address size prefix. + * 2. MODRM.MOD + * 3. MODRM.REG + REX.R + * 4. MODRM.R/M + REX.B + * 5. SIB - MODRM.R/M == 4 && MODRM.MOD != 3: + * 5a) SIB.BASE + REX.B + * 5b) SIB.INDEX + REX.X + * 5c) SIB.SCALE + * 6. Segment prefix overrides if applicable and supported (memory). + * 7. Operand size prefix and REX.W if applicable. + */ + if (cidetCoreSetupNextBaseEncoding_OperandSize(pThis)) + return true; + if (cidetCoreSetupNextBaseEncoding_SegmentPrefix(pThis)) + return true; + + /* The ModR/M register value for collision detection. */ + uint8_t iReg = ((pThis->bModRm >> X86_MODRM_REG_SHIFT) & X86_MODRM_REG_SMASK) + pThis->fRexR * 8; + + if (pThis->fSib) + { + AssertRelease(pThis->fHasMemoryOperand); + if (cidetCoreSetupNextBaseEncoding_SibScale(pThis, iReg)) + return true; + if (cidetCoreSetupNextBaseEncoding_SibIndex(pThis, iReg)) + return true; + if (cidetCoreSetupNextBaseEncoding_SibBase(pThis, iReg)) + return true; + Assert(pThis->bSib == 0); + pThis->fSib = false; + } + + if (cidetCoreSetupNextBaseEncoding_MrmRmMod(pThis, iReg)) + return true; + if (cidetCoreSetupNextBaseEncoding_MrmReg(pThis, iReg)) + return true; + if (cidetCoreSetupNextBaseEncoding_AddressSize(pThis)) + return true; + } + else + AssertFailedReturn(false); + return false; +} + + +bool CidetCoreSetupFirstBaseEncoding(PCIDETCORE pThis) +{ + /* + * Reset all the knobs and wheels. + */ + pThis->fSib = false; + pThis->uSegPrf = X86_SREG_COUNT; + pThis->fAddrSizePrf = false; + pThis->fOpSizePrf = false; + pThis->fRexW = false; + pThis->fRexR = false; + pThis->fRexX = false; + pThis->fRexB = false; + pThis->fRex = false; + pThis->bModRm = 0; + pThis->bSib = 0; + + /* Indicators. */ + pThis->cbAddrMode = CIDETMODE_GET_BYTE_COUNT(pThis->bMode); + pThis->fHasMemoryOperand = false; + pThis->fHasRegCollisionMem = false; + pThis->fHasRegCollisionMemBase = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasStackRegInMrmRmBase = false; + + /* + * Now, drill down on the instruction encoding. + */ + if (pThis->pCurInstr->fFlags & CIDET_IF_MODRM) + { + Assert(pThis->fUsesModRm == true); + cidetCoreSetupFirstBaseEncoding_MrmReg(pThis); + if (pThis->cbAddrMode == 2) + cidetCoreSetupFirstBaseEncoding_MrmRmMod_16bit(pThis, 0); + else if (pThis->cbAddrMode == 4) + cidetCoreSetupFirstBaseEncoding_MrmRmMod_32bit64bit(pThis, 0, false); + else if (pThis->cbAddrMode == 8) + cidetCoreSetupFirstBaseEncoding_MrmRmMod_32bit64bit(pThis, 0, true); + else + AssertReleaseFailedReturn(false); + } + else + AssertFailedReturn(false); + return true; +} + + +/** + * The next memory operand configuration. + * + * @returns true if new one to test, false if we've reached end already. + * @param pThis The core state structure. + */ +bool CidetCoreSetupNextMemoryOperandConfig(PCIDETCORE pThis) +{ + RT_NOREF_PV(pThis); + return false; +} + + +/** + * Sets up the first memory operand configuration and counts memory operands. + * + * @returns true on success, false if no data buffers configured or failure. + * @param pThis The core state structure. + */ +bool CidetCoreSetupFirstMemoryOperandConfig(PCIDETCORE pThis) +{ + pThis->cMemoryOperands = 0; + PCIDETBUF pDataBuf = &pThis->DataBuf; + uint8_t idxOp = pThis->cOperands; + while (idxOp-- > 0) + if (!pThis->aOperands[idxOp].fIsMem) + pThis->aOperands[idxOp].pDataBuf = NULL; + else + { + if (RT_UNLIKELY(!pThis->cDataBufConfigs)) + return false; + + pDataBuf->idxCfg = 0; + pDataBuf->pCfg = &pThis->paDataBufConfigs[0]; + pDataBuf->off = 0; + pDataBuf->cb = pThis->aOperands[idxOp].cb; + pDataBuf->cbSegLimit = UINT16_MAX; + pDataBuf->offSegBase = 0; + pDataBuf->fActive = false; + pDataBuf->idxOp = idxOp; + pDataBuf->fXcptAfterInstruction = false; + pDataBuf->enmExpectXcpt = kCidetExpectXcpt_None; + pThis->aOperands[idxOp].pDataBuf = pDataBuf; + pThis->cMemoryOperands++; + pDataBuf++; + } + + /** @todo implement more than one memory operand. */ + AssertReleaseReturn(pThis->cMemoryOperands <= 1, false); + return true; +} + + +/** + * The next code buffer configuration. + * + * @returns true if new one to test, false if we've reached end already. + * @param pThis The core state structure. + */ +bool CidetCoreSetupNextCodeBufferConfig(PCIDETCORE pThis) +{ + RT_NOREF_PV(pThis); + return false; +} + + +/** + * Sets up the first code buffer configuration. + * + * @returns true on success, false if no data buffers configured or failure. + * @param pThis The core state structure. + */ +bool CidetCoreSetupFirstCodeBufferConfig(PCIDETCORE pThis) +{ + Assert(pThis->cCodeBufConfigs > 0); + Assert(CIDETBUF_IS_CODE(pThis->paCodeBufConfigs[0].fFlags)); + pThis->CodeBuf.idxCfg = 0; + pThis->CodeBuf.pCfg = &pThis->paCodeBufConfigs[0]; + pThis->CodeBuf.off = 0; + pThis->CodeBuf.cb = 0x1000; + pThis->CodeBuf.cbSegLimit = UINT16_MAX; + pThis->CodeBuf.offSegBase = 0; + pThis->CodeBuf.fActive = true; + pThis->CodeBuf.idxOp = 7; + pThis->CodeBuf.fXcptAfterInstruction = false; + pThis->CodeBuf.enmExpectXcpt = kCidetExpectXcpt_None; + return true; +} + + +/** + * Gets the (encoded) size of the given operand in the current context. + * + * @returns Size in bytes. + * @param pThis The core state structure (for context). + * @param iOp The operand index. + */ +uint32_t CidetCoreGetOperandSize(PCIDETCORE pThis, uint8_t iOp) +{ + Assert(iOp < RT_ELEMENTS(pThis->aOperands)); + uint32_t cbOp = g_acbCidetOfSizes[(pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) >> CIDET_OF_Z_SHIFT]; + if (cbOp == UINT16_MAX) + { + Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_VAR_WDQ); + if (CIDETMODE_IS_64BIT(pThis->bMode)) + { + if (pThis->fRexW) + cbOp = 8; + else if (!pThis->fOpSizePrf) + cbOp = 4; + else + cbOp = 2; + } + else if (CIDETMODE_IS_32BIT(pThis->bMode)) + cbOp = !pThis->fOpSizePrf ? 4 : 2; + else + { + Assert(CIDETMODE_IS_16BIT(pThis->bMode)); + cbOp = !pThis->fOpSizePrf ? 2 : 4; + } + return cbOp; + } + + if (cbOp == UINT16_MAX - 1) + { + Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_SPECIAL); + AssertReleaseFailedReturn(0); + } + + if (cbOp) + { +#ifdef VBOX_STRICT + switch (cbOp) + { + case 1: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_BYTE); break; + case 2: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_WORD); break; + case 4: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_DWORD); break; + case 8: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_QWORD); break; + case 10: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_TBYTE); break; + case 16: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_OWORD); break; + case 32: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_YWORD); break; + case 64: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_ZWORD); break; + default: AssertFailed(); + } +#endif + return cbOp; + } + AssertReleaseFailedReturn(0); +} + + +bool CideCoreSetInstruction(PCIDETCORE pThis, PCCIDETINSTR pInstr) +{ + AssertReleaseMsgReturn(RT_VALID_PTR(pInstr), ("%p\n", pInstr), false); + + pThis->pCurInstr = pInstr; + + /* + * Extract info from the instruction descriptor. + */ + pThis->fUsesModRm = false; + pThis->fUsesVexIndexRegs = false; + pThis->idxMrmRegOp = 7; + pThis->idxMrmRmOp = 7; + pThis->fMrmRegOp = 0; + pThis->fMrmRmOp = 0; + pThis->fInstrFlags = pInstr->fFlags; + pThis->cOperands = pInstr->cOperands; + if (pInstr->fFlags & CIDET_IF_MODRM) + { + pThis->fUsesModRm = true; + for (uint8_t iOp = 0; iOp < pInstr->cOperands; iOp++) + if (pInstr->afOperands[iOp] & CIDET_OF_M_REG) + { + pThis->idxMrmRegOp = iOp; + pThis->fMrmRegOp = pInstr->afOperands[iOp]; + } + else if (pInstr->afOperands[iOp] & CIDET_OF_M_RM) + { + pThis->idxMrmRmOp = iOp; + pThis->fMrmRmOp = pInstr->afOperands[iOp]; + } + } + else + AssertFailedReturn(false); + + uint8_t iOp; + for (iOp = 0; iOp < pInstr->cOperands; iOp++) + { + pThis->aOperands[iOp].fFlags = pInstr->afOperands[iOp]; + pThis->aOperands[iOp].iReg = UINT8_MAX; + pThis->aOperands[iOp].cb = (uint8_t)CidetCoreGetOperandSize(pThis, iOp); + pThis->aOperands[iOp].fIsImmediate = (pInstr->afOperands[iOp] & CIDET_OF_K_MASK) == CIDET_OF_K_IMM; + pThis->aOperands[iOp].fIsMem = (pInstr->afOperands[iOp] & CIDET_OF_K_MASK) == CIDET_OF_K_MEM; + pThis->aOperands[iOp].fIsRipRelative = false; + pThis->aOperands[iOp].cbMemDisp = 0; + pThis->aOperands[iOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[iOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[iOp].uMemScale = 1; + pThis->aOperands[iOp].iEffSeg = UINT8_MAX; + pThis->aOperands[iOp].offSeg = UINT64_MAX; + pThis->aOperands[iOp].uEffAddr = UINT64_MAX; + pThis->aOperands[iOp].uImmDispValue = UINT64_MAX; + pThis->aOperands[iOp].uMemBaseRegValue = UINT64_MAX; + pThis->aOperands[iOp].uMemIndexRegValue = UINT64_MAX; + pThis->aOperands[iOp].In.pv = NULL; + pThis->aOperands[iOp].Expected.pv = NULL; + pThis->aOperands[iOp].pDataBuf = NULL; + } + + for (; iOp < RT_ELEMENTS(pThis->aOperands); iOp++) + { + pThis->aOperands[iOp].fFlags = 0; + pThis->aOperands[iOp].iReg = UINT8_MAX; + pThis->aOperands[iOp].cb = 0; + pThis->aOperands[iOp].fIsImmediate = false; + pThis->aOperands[iOp].fIsMem = false; + pThis->aOperands[iOp].fIsRipRelative = false; + pThis->aOperands[iOp].cbMemDisp = 0; + pThis->aOperands[iOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[iOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[iOp].uMemScale = 1; + pThis->aOperands[iOp].iEffSeg = UINT8_MAX; + pThis->aOperands[iOp].offSeg = UINT64_MAX; + pThis->aOperands[iOp].uEffAddr = UINT64_MAX; + pThis->aOperands[iOp].uImmDispValue = UINT64_MAX; + pThis->aOperands[iOp].uMemBaseRegValue = UINT64_MAX; + pThis->aOperands[iOp].uMemIndexRegValue = UINT64_MAX; + pThis->aOperands[iOp].In.pv = NULL; + pThis->aOperands[iOp].Expected.pv = NULL; + pThis->aOperands[iOp].pDataBuf = NULL; + } + + /* + * Reset various things. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aiInOut); i++) + pThis->aiInOut[i] = 0; + + return true; +} + + +bool CidetCoreSetupInOut(PCIDETCORE pThis) +{ + /* + * Enumerate the operands. + */ + uint8_t *pbBuf = &pThis->abBuf[0]; + pbBuf = RT_ALIGN_PT(pbBuf, 16, uint8_t *); + + uint8_t idxOp = pThis->cOperands; + while (idxOp-- > 0) + { + if (pThis->aOperands[idxOp].fIsMem) + { + /* + * Memory operand. + */ + Assert(pThis->aOperands[idxOp].fIsMem); + + /* Set the In & Expected members to point to temporary buffer space. */ + pThis->aOperands[idxOp].Expected.pu8 = pbBuf; + pbBuf += pThis->aOperands[idxOp].cb; + pbBuf = RT_ALIGN_PT(pbBuf, 16, uint8_t *); + + pThis->aOperands[idxOp].In.pu8 = pbBuf; + pbBuf += pThis->aOperands[idxOp].cb; + pbBuf = RT_ALIGN_PT(pbBuf, 16, uint8_t *); + + /* Initialize the buffer we're gonna use. */ + pThis->aOperands[idxOp].iEffSeg = pThis->uSegPrf != X86_SREG_COUNT + ? pThis->uSegPrf + : !(pThis->aOperands[idxOp].fFlags & CIDET_OF_ALWAYS_SEG_ES) ? X86_SREG_DS + : X86_SREG_ES; + + PCIDETBUF pDataBuf = pThis->aOperands[idxOp].pDataBuf; + AssertReleaseReturn(pDataBuf, false); + Assert(pDataBuf->cb == pThis->aOperands[idxOp].cb); + Assert(pDataBuf->idxOp == idxOp); + if (!pThis->pfnReInitDataBuf(pThis, pDataBuf)) + { + pThis->cSkippedReInitDataBuf++; + return false; + } + pDataBuf->fActive = true; + + /* Calc buffer related operand members. */ + pThis->aOperands[idxOp].uEffAddr = pDataBuf->uEffBufAddr + pDataBuf->off; + uint64_t offSeg = pThis->aOperands[idxOp].uEffAddr - pDataBuf->uSegBase; + pThis->aOperands[idxOp].offSeg = offSeg; + AssertRelease(offSeg <= g_au64ByteSizeToMask[pThis->cbAddrMode]); + + /* + * Select register and displacement values for the buffer addressing (works on offSeg). + */ + uint8_t const iMemIndexReg = pThis->aOperands[idxOp].iMemIndexReg; + uint8_t const iMemBaseReg = pThis->aOperands[idxOp].iMemBaseReg; + if (pThis->aOperands[idxOp].fIsRipRelative) + { + /* rip relative. */ + pThis->aOperands[idxOp].uImmDispValue = offSeg - (pThis->InCtx.rip + pThis->cbInstr); + Assert(pThis->aOperands[idxOp].cbMemDisp == 4); + if ( (int64_t)pThis->aOperands[idxOp].uImmDispValue > INT32_MAX + || (int64_t)pThis->aOperands[idxOp].uImmDispValue < INT32_MIN) + { + pThis->cSkippedDataBufWrtRip++; + return false; + } + } + else if (iMemBaseReg != UINT8_MAX) + { + if ( iMemBaseReg != iMemIndexReg + || pThis->fUsesVexIndexRegs) + { + /* [base] or [base + disp] or [base + index * scale] or [base + index * scale + disp] */ + if (pThis->aOperands[idxOp].cbMemDisp > 0) + { + pThis->aOperands[idxOp].uImmDispValue = CidetCoreGetRandS64(pThis, pThis->aOperands[idxOp].cbMemDisp); + offSeg -= (int64_t)pThis->aOperands[idxOp].uImmDispValue; + } + + if (iMemIndexReg != UINT8_MAX) + { + pThis->aOperands[idxOp].uMemIndexRegValue = CidetCoreGetRandU64(pThis, pThis->cbAddrMode); + offSeg -= pThis->aOperands[idxOp].uMemIndexRegValue * pThis->aOperands[idxOp].uMemScale; + } + + pThis->aOperands[idxOp].uMemBaseRegValue = offSeg & g_au64ByteSizeToMask[pThis->cbAddrMode]; + } + else + { + /* base == index; [base + index * scale] or [base * (scale + 1)]. */ + uint8_t const uEffScale = pThis->aOperands[idxOp].uMemScale + 1; + if (pThis->aOperands[idxOp].cbMemDisp > 0) + { + pThis->aOperands[idxOp].uImmDispValue = CidetCoreGetRandS64(pThis, pThis->aOperands[idxOp].cbMemDisp); + offSeg -= (int64_t)pThis->aOperands[idxOp].uImmDispValue; + offSeg &= g_au64ByteSizeToMask[pThis->cbAddrMode]; + uint8_t uRemainder = offSeg % uEffScale; + if (uRemainder != 0) + { + Assert(pThis->aOperands[idxOp].cbMemDisp < 8); + Assert( (int64_t)pThis->aOperands[idxOp].uImmDispValue + <= g_ai64ByteSizeToMax[pThis->aOperands[idxOp].cbMemDisp]); + pThis->aOperands[idxOp].uImmDispValue = (int64_t)pThis->aOperands[idxOp].uImmDispValue + + uRemainder; + offSeg -= uRemainder; + if ( (int64_t)pThis->aOperands[idxOp].uImmDispValue + > g_ai64ByteSizeToMax[pThis->aOperands[idxOp].cbMemDisp]) + { + pThis->aOperands[idxOp].uImmDispValue -= uEffScale; + offSeg += uEffScale; + } + Assert(offSeg % uEffScale == 0); + } + } + else + { + offSeg &= g_au64ByteSizeToMask[pThis->cbAddrMode]; + if (offSeg % uEffScale != 0) + { + pThis->cSkippedSameBaseIndexRemainder++; + return false; + } + } + offSeg /= uEffScale; + pThis->aOperands[idxOp].uMemBaseRegValue = pThis->aOperands[idxOp].uMemIndexRegValue = offSeg; + } + } + else if (iMemIndexReg != UINT8_MAX) + { + /* [index * scale] or [index * scale + disp] */ + if (pThis->aOperands[idxOp].cbMemDisp > 0) + { + pThis->aOperands[idxOp].uImmDispValue = CidetCoreGetRandS64(pThis, pThis->aOperands[idxOp].cbMemDisp); + offSeg -= (int64_t)pThis->aOperands[idxOp].uImmDispValue; + pThis->aOperands[idxOp].uImmDispValue += offSeg & (RT_BIT_64(pThis->aOperands[idxOp].uMemScale) - 1); + offSeg &= ~(RT_BIT_64(pThis->aOperands[idxOp].uMemScale) - 1); + } + else if (offSeg & (RT_BIT_64(pThis->aOperands[idxOp].uMemScale) - 1)) + { + pThis->cSkippedOnlyIndexRemainder++; + return false; + } + + pThis->aOperands[idxOp].uMemIndexRegValue = offSeg / pThis->aOperands[idxOp].uMemScale; + Assert((offSeg % pThis->aOperands[idxOp].uMemScale) == 0); + AssertRelease(!pThis->fUsesVexIndexRegs); /** @todo implement VEX indexing */ + } + else + { + /* [disp] */ + Assert( pThis->aOperands[idxOp].cbMemDisp == 8 + || pThis->aOperands[idxOp].cbMemDisp == 4 + || pThis->aOperands[idxOp].cbMemDisp == 2 + || pThis->aOperands[idxOp].cbMemDisp == 1); + if ( pThis->aOperands[idxOp].cbMemDisp == 4 + ? (int64_t)offSeg != (int32_t)offSeg + : pThis->aOperands[idxOp].cbMemDisp == 2 + ? (int64_t)offSeg != (int16_t)offSeg + : pThis->aOperands[idxOp].cbMemDisp == 1 + ? (int64_t)offSeg != (int8_t)offSeg + : false /* 8 */) + { + pThis->cSkippedDirectAddressingOverflow++; + return false; + } + pThis->aOperands[idxOp].uImmDispValue = offSeg; + } + + /* + * Modify the input and expected output contexts with the base and + * index register values. To simplify verification and the work + * here, we update the uMemBaseRegValue and uMemIndexRegValue + * members to reflect the whole register. + */ + if (iMemBaseReg != UINT8_MAX) + { + if (pThis->cbAddrMode == 4) + { + pThis->aOperands[idxOp].uMemBaseRegValue &= UINT32_MAX; + pThis->aOperands[idxOp].uMemBaseRegValue |= pThis->InCtx.aGRegs[iMemBaseReg] & UINT64_C(0xffffffff00000000); + } + else if (pThis->cbAddrMode == 2) + { + pThis->aOperands[idxOp].uMemBaseRegValue &= UINT16_MAX; + pThis->aOperands[idxOp].uMemBaseRegValue |= pThis->InCtx.aGRegs[iMemBaseReg] & UINT64_C(0xffffffffffff0000); + } + pThis->InCtx.aGRegs[iMemBaseReg] = pThis->aOperands[idxOp].uMemBaseRegValue; + pThis->ExpectedCtx.aGRegs[iMemBaseReg] = pThis->aOperands[idxOp].uMemBaseRegValue; + } + + if (iMemIndexReg != UINT8_MAX) + { + if (pThis->cbAddrMode == 4) + { + pThis->aOperands[idxOp].uMemIndexRegValue &= UINT32_MAX; + pThis->aOperands[idxOp].uMemIndexRegValue |= pThis->InCtx.aGRegs[iMemIndexReg] & UINT64_C(0xffffffff00000000); + } + else if (pThis->cbAddrMode == 2) + { + pThis->aOperands[idxOp].uMemIndexRegValue &= UINT16_MAX; + pThis->aOperands[idxOp].uMemIndexRegValue |= pThis->InCtx.aGRegs[iMemIndexReg] & UINT64_C(0xffffffffffff0000); + } + pThis->InCtx.aGRegs[iMemIndexReg] = pThis->aOperands[idxOp].uMemIndexRegValue; + pThis->ExpectedCtx.aGRegs[iMemIndexReg] = pThis->aOperands[idxOp].uMemIndexRegValue; + } + } + else + { + /* + * Non-memory, so clear the memory related members. + */ + Assert(!pThis->aOperands[idxOp].fIsMem); + pThis->aOperands[idxOp].iEffSeg = UINT8_MAX; + pThis->aOperands[idxOp].offSeg = UINT64_MAX; + pThis->aOperands[idxOp].uEffAddr = UINT64_MAX; + pThis->aOperands[idxOp].pDataBuf = NULL; + + switch (pThis->aOperands[idxOp].fFlags & CIDET_OF_K_MASK) + { + case CIDET_OF_K_GPR: + if (!pThis->aOperands[idxOp].fIsHighByteRegister) + { + pThis->aOperands[idxOp].In.pv = &pThis->InCtx.aGRegs[pThis->aOperands[idxOp].iReg]; + pThis->aOperands[idxOp].Expected.pv = &pThis->ExpectedCtx.aGRegs[pThis->aOperands[idxOp].iReg]; + } + else + { + pThis->aOperands[idxOp].In.pv = &pThis->InCtx.aGRegs[pThis->aOperands[idxOp].iReg - 4]; + pThis->aOperands[idxOp].In.pu8++; + pThis->aOperands[idxOp].Expected.pv = &pThis->ExpectedCtx.aGRegs[pThis->aOperands[idxOp].iReg - 4]; + pThis->aOperands[idxOp].Expected.pu8++; + } + break; + + case CIDET_OF_K_IMM: + pThis->aOperands[idxOp].In.pv = NULL; + pThis->aOperands[idxOp].Expected.pv = NULL; + break; + + case CIDET_OF_K_SREG: + if (pThis->aOperands[idxOp].iReg < RT_ELEMENTS(pThis->InCtx.aSRegs)) + { + pThis->aOperands[idxOp].In.pv = &pThis->InCtx.aSRegs[pThis->aOperands[idxOp].iReg]; + pThis->aOperands[idxOp].Expected.pv = &pThis->ExpectedCtx.aSRegs[pThis->aOperands[idxOp].iReg]; + } + else + { + pThis->aOperands[idxOp].In.pv = NULL; + pThis->aOperands[idxOp].Expected.pv = NULL; + } + break; + + case CIDET_OF_K_CR: + case CIDET_OF_K_SSE: + case CIDET_OF_K_AVX: + case CIDET_OF_K_AVX512: + case CIDET_OF_K_FPU: + case CIDET_OF_K_MMX: + case CIDET_OF_K_AVXFUTURE: + case CIDET_OF_K_SPECIAL: + case CIDET_OF_K_TEST: + /** @todo Implement testing these registers. */ + case CIDET_OF_K_NONE: + default: + AssertReleaseFailedReturn(false); + } + } + } + AssertRelease((uintptr_t)pbBuf - (uintptr_t)&pThis->abBuf[0] <= sizeof(pThis->abBuf)); + + /* + * Call instruction specific setup function (for operand values and flags). + */ + int rc = pThis->pCurInstr->pfnSetupInOut(pThis, false /*fInvalid*/); + if (RT_FAILURE(rc)) + { + pThis->cSkippedSetupInOut++; + return false; + } + + /* + * Do the 2nd set of the memory operand preparations. + */ + if (pThis->fHasMemoryOperand) + { + idxOp = pThis->cOperands; + while (idxOp-- > 0) + if (pThis->aOperands[idxOp].fIsMem) + { + Assert(pThis->aOperands[idxOp].pDataBuf); + if (!pThis->pfnSetupDataBuf(pThis, pThis->aOperands[idxOp].pDataBuf, pThis->aOperands[idxOp].In.pv)) + { + pThis->cSkippedSetupDataBuf++; + return false; + } + + Assert( pThis->aOperands[idxOp].iMemBaseReg == UINT8_MAX + || pThis->InCtx.aGRegs[pThis->aOperands[idxOp].iMemBaseReg] == pThis->aOperands[idxOp].uMemBaseRegValue); + Assert( pThis->aOperands[idxOp].iMemIndexReg == UINT8_MAX + || ( !pThis->fUsesVexIndexRegs + ? pThis->InCtx.aGRegs[pThis->aOperands[idxOp].iMemIndexReg] + == pThis->aOperands[idxOp].uMemIndexRegValue + : false /** @todo VEX indexing */)); + } + } + + return true; +} + + +/** + * Figures the instruction length. + * + * This is a duplicate of CidetCoreAssemble() with the buffer updates removed. + * + * @returns true and pThis->cbInstr on success, false on failure. + * @param pThis The core state structure (for context). + */ +bool CidetCoreAssembleLength(PCIDETCORE pThis) +{ + uint8_t off = 0; + + /* + * Prefixes. + */ + if (1) + { + if (pThis->fAddrSizePrf) + off++; + if (pThis->fOpSizePrf) + off++; + } + else + { + /** @todo prefix list. */ + } + + /* + * Prefixes that must come right before the opcode. + */ + /** @todo VEX and EVEX. */ + if (pThis->fVex) + { + /** @todo VEX and EVEX. */ + } + else if (pThis->fEvex) + { + /** @todo VEX and EVEX. */ + } + else + { + if (pThis->fRexB || pThis->fRexX || pThis->fRexR || pThis->fRexW || pThis->fRex) + off++; + } + + /* + * The opcode. + */ + //uint8_t const *pbOpcode = pThis->pCurInstr->abOpcode; + switch (pThis->pCurInstr->cbOpcode) + { + case 3: off++; RT_FALL_THRU(); + case 2: off++; RT_FALL_THRU(); + case 1: off++; + break; + default: + AssertReleaseFailedReturn(false); + } + + /* + * Mod R/M + */ + if (pThis->fUsesModRm) + { + off++; + if (pThis->fSib) + off++; + if (pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands)) + { + //uint64_t uDispValue = pThis->aOperands[pThis->idxMrmRmOp].uImmDispValue; + switch (pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp) + { + case 0: break; + case 8: + case 7: + case 6: + case 5: + case 4: + case 3: + case 2: + case 1: + break; + default: AssertReleaseFailedReturn(false); + } + off += pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp; + } + } + + /* + * Immediates. + */ + uint8_t iOp = pThis->cOperands; + while (iOp-- > 0) + if ((pThis->aOperands[iOp].fFlags & CIDET_OF_K_MASK) == CIDET_OF_K_IMM) + { + //uint64_t uImmValue = pThis->aOperands[iOp].uImmDispValue; + switch (pThis->aOperands[iOp].cb) + { + case 8: + case 7: + case 6: + case 5: + case 4: + case 3: + case 2: + case 1: + break; + default: AssertReleaseFailedReturn(false); + } + off += pThis->aOperands[iOp].cb; + } + + pThis->cbInstr = off; + return true; +} + + +/** + * Assembles the instruction. + * + * This is a duplicate of CidetCoreAssembleLength() with buffer writes. + * + * @returns true and pThis->cbInstr and pThis->abInstr on success, false on + * failure. + * @param pThis The core state structure (for context). + */ +bool CidetCoreAssemble(PCIDETCORE pThis) +{ + uint8_t off = 0; + + /* + * Prefixes. + */ + if (1) + { + if (pThis->fAddrSizePrf) + pThis->abInstr[off++] = 0x67; + if (pThis->fOpSizePrf) + pThis->abInstr[off++] = 0x66; + } + else + { + /** @todo prefix list. */ + } + + /* + * Prefixes that must come right before the opcode. + */ + /** @todo VEX and EVEX. */ + if (pThis->fVex) + { + /** @todo VEX and EVEX. */ + } + else if (pThis->fEvex) + { + /** @todo VEX and EVEX. */ + } + else + { + if (pThis->fRexB || pThis->fRexX || pThis->fRexR || pThis->fRexW || pThis->fRex) + pThis->abInstr[off++] = 0x40 | (pThis->fRexB * 1) | (pThis->fRexX * 2) | (pThis->fRexR * 4) | (pThis->fRexW * 8); + } + + /* + * The opcode. + */ + uint8_t const *pbOpcode = pThis->pCurInstr->abOpcode; + switch (pThis->pCurInstr->cbOpcode) + { + case 3: pThis->abInstr[off++] = *pbOpcode++; RT_FALL_THRU(); + case 2: pThis->abInstr[off++] = *pbOpcode++; RT_FALL_THRU(); + case 1: pThis->abInstr[off++] = *pbOpcode++; + break; + default: + AssertReleaseFailedReturn(false); + } + + /* + * Mod R/M + */ + if (pThis->fUsesModRm) + { + pThis->abInstr[off++] = pThis->bModRm; + if (pThis->fSib) + pThis->abInstr[off++] = pThis->bSib; + if (pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands)) + { + uint64_t uDispValue = pThis->aOperands[pThis->idxMrmRmOp].uImmDispValue; + switch (pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp) + { + case 0: break; + case 8: pThis->abInstr[off + 3] = (uDispValue >> 56) & UINT8_C(0xff); RT_FALL_THRU(); + case 7: pThis->abInstr[off + 3] = (uDispValue >> 48) & UINT8_C(0xff); RT_FALL_THRU(); + case 6: pThis->abInstr[off + 3] = (uDispValue >> 40) & UINT8_C(0xff); RT_FALL_THRU(); + case 5: pThis->abInstr[off + 3] = (uDispValue >> 32) & UINT8_C(0xff); RT_FALL_THRU(); + case 4: pThis->abInstr[off + 3] = (uDispValue >> 24) & UINT8_C(0xff); RT_FALL_THRU(); + case 3: pThis->abInstr[off + 2] = (uDispValue >> 16) & UINT8_C(0xff); RT_FALL_THRU(); + case 2: pThis->abInstr[off + 1] = (uDispValue >> 8) & UINT8_C(0xff); RT_FALL_THRU(); + case 1: pThis->abInstr[off] = uDispValue & UINT8_C(0xff); + break; + default: AssertReleaseFailedReturn(false); + } + off += pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp; + } + } + + /* + * Immediates. + */ + uint8_t iOp = pThis->cOperands; + while (iOp-- > 0) + if ((pThis->aOperands[iOp].fFlags & CIDET_OF_K_MASK) == CIDET_OF_K_IMM) + { + uint64_t uImmValue = pThis->aOperands[iOp].uImmDispValue; + switch (pThis->aOperands[iOp].cb) + { + case 8: pThis->abInstr[off + 3] = (uImmValue >> 56) & UINT8_C(0xff); RT_FALL_THRU(); + case 7: pThis->abInstr[off + 3] = (uImmValue >> 48) & UINT8_C(0xff); RT_FALL_THRU(); + case 6: pThis->abInstr[off + 3] = (uImmValue >> 40) & UINT8_C(0xff); RT_FALL_THRU(); + case 5: pThis->abInstr[off + 3] = (uImmValue >> 32) & UINT8_C(0xff); RT_FALL_THRU(); + case 4: pThis->abInstr[off + 3] = (uImmValue >> 24) & UINT8_C(0xff); RT_FALL_THRU(); + case 3: pThis->abInstr[off + 2] = (uImmValue >> 16) & UINT8_C(0xff); RT_FALL_THRU(); + case 2: pThis->abInstr[off + 1] = (uImmValue >> 8) & UINT8_C(0xff); RT_FALL_THRU(); + case 1: pThis->abInstr[off] = uImmValue & UINT8_C(0xff); + break; + default: AssertReleaseFailedReturn(false); + } + off += pThis->aOperands[iOp].cb; + } + + pThis->cbInstr = off; + return true; +} + + +bool CidetCoreReInitCodeBuf(PCIDETCORE pThis) +{ + /* + * Re-initialize the buffer. Requires instruction length and positioning. + */ + if (CidetCoreAssembleLength(pThis)) + { + pThis->CodeBuf.cb = pThis->cbInstr; + pThis->CodeBuf.off = CIDET_CODE_BUF_SIZE - PAGE_SIZE - pThis->cbInstr; + if (pThis->pfnReInitCodeBuf(pThis, &pThis->CodeBuf)) + { + pThis->CodeBuf.fActive = true; + + /* + * Update the RIP and CS values in the input and expected contexts. + */ + pThis->InCtx.rip = pThis->CodeBuf.uEffBufAddr + pThis->CodeBuf.offActive - pThis->CodeBuf.uSegBase; + pThis->ExpectedCtx.rip = pThis->InCtx.rip + pThis->cbInstr; /** @todo account for expected traps. */ + if (pThis->CodeBuf.uSeg != UINT32_MAX) + { + pThis->InCtx.aSRegs[X86_SREG_CS] = pThis->CodeBuf.uSeg; + pThis->ExpectedCtx.aSRegs[X86_SREG_CS] = pThis->CodeBuf.uSeg; + } + return true; + } + else + pThis->cSkippedReInitCodeBuf++; + } + else + pThis->cSkippedAssemble++; + return false; +} + + +#ifdef CIDET_DEBUG_DISAS +/** + * @callback_method_impl{FNDISREADBYTES} + */ +static DECLCALLBACK(int) cidetCoreDisReadBytes(PDISSTATE pDis, uint8_t offInstr, uint8_t cbMinRead, uint8_t cbMaxRead) +{ + PCIDETCORE pThis = (PCIDETCORE)pDis->pvUser; + memcpy(&pDis->abInstr[offInstr], &pThis->abInstr[offInstr], cbMaxRead); + pDis->cbCachedInstr = offInstr + cbMaxRead; + return VINF_SUCCESS; +} +#endif + + +bool CidetCoreSetupCodeBuf(PCIDETCORE pThis, unsigned iSubTest) +{ + if (CidetCoreAssemble(pThis)) + { + //CIDET_DPRINTF(("%04u: %.*Rhxs\n", i, pThis->cbInstr, pThis->abInstr)); +#ifdef CIDET_DEBUG_DISAS + DISCPUSTATE Dis; + char szInstr[80] = {0}; + uint32_t cbInstr; + int rcDis = DISInstrToStrEx(pThis->InCtx.rip, + CIDETMODE_IS_64BIT(pThis->bMode) ? DISCPUMODE_64BIT + : CIDETMODE_IS_32BIT(pThis->bMode) ? DISCPUMODE_32BIT : DISCPUMODE_16BIT, + cidetCoreDisReadBytes, + pThis, + DISOPTYPE_ALL, + &Dis, + &cbInstr, + szInstr, sizeof(szInstr)); + CIDET_DPRINTF(("%04u: %s", iSubTest, szInstr)); + Assert(cbInstr == pThis->cbInstr); +#else + RT_NOREF_PV(iSubTest); +#endif + if (pThis->pfnSetupCodeBuf(pThis, &pThis->CodeBuf, pThis->abInstr)) + { + return true; + } + pThis->cSkippedSetupCodeBuf++; + } + else + pThis->cSkippedAssemble++; + return false; +} + + +/** + * Compares the output with the output expectations. + * + * @returns true if ok, false if not (calls pfnFailure too). + * @param pThis The core state structure. + */ +bool CidetCoreCheckResults(PCIDETCORE pThis) +{ + if (memcmp(&pThis->ActualCtx, &pThis->ExpectedCtx, CIDETCPUCTX_COMPARE_SIZE) == 0) + return true; + + unsigned cDiffs = 0; +#define IF_FIELD_DIFFERS_SET_ERROR(a_Field, a_Fmt) \ + if (pThis->ActualCtx.a_Field != pThis->ExpectedCtx.a_Field) \ + { \ + CidetCoreSetError(pThis, #a_Field " differs: got %#llx expected %#llx", \ + pThis->ActualCtx.a_Field, pThis->ExpectedCtx.a_Field); \ + cDiffs++; \ + } else do { } while (0) + + IF_FIELD_DIFFERS_SET_ERROR(rip, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(rfl, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xAX], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xBX], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xCX], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xDX], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xSP], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xBP], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xSI], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xDI], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x8], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x9], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x9], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x10], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x11], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x12], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x13], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x14], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x15], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_CS], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_SS], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_DS], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_ES], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_FS], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_GS], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(uXcpt, "%#04x"); + IF_FIELD_DIFFERS_SET_ERROR(uErr, "%#04llx"); + IF_FIELD_DIFFERS_SET_ERROR(cr2, "%#010llx"); +#ifndef CIDET_REDUCED_CTX + IF_FIELD_DIFFERS_SET_ERROR(tr, "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(ldtr, "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(cr0, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(cr3, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(cr4, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(cr8, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr0, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr1, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr2, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr3, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr6, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr7, "%#010llx"); +#endif + +AssertMsgFailed(("cDiffs=%d\n", cDiffs)); + Assert(cDiffs > 0); + return cDiffs == 0; +} + + +bool CidetCoreTest_Basic(PCIDETCORE pThis) +{ + /* + * Iterate all encodings. + */ + if (!CidetCoreSetupFirstBaseEncoding(pThis)) + return CidetCoreSetError(pThis, "CidetCoreSetupFirstBaseEncoding failed"); + unsigned cExecuted = 0; + unsigned cSkipped = 0; + do + { + /* + * Iterate data buffer configurations (one iteration if none). + */ + if (CidetCoreSetupFirstMemoryOperandConfig(pThis)) + { + do + { + /* + * Iterate code buffer configurations. + */ + if (!CidetCoreSetupFirstCodeBufferConfig(pThis)) + return CidetCoreSetError(pThis, "CidetCoreSetupFirstMemoryOperandConfig failed"); + do + { + /* + * Set up inputs and expected outputs, then emit the test code. + */ + pThis->InCtx = pThis->InTemplateCtx; + pThis->InCtx.fTrickyStack = pThis->fHasStackRegInMrmRmBase || pThis->fHasStackRegInMrmReg; + pThis->ExpectedCtx = pThis->InCtx; + if ( CidetCoreReInitCodeBuf(pThis) + && CidetCoreSetupInOut(pThis) + && CidetCoreSetupCodeBuf(pThis, cSkipped + cExecuted) + ) + { + if (pThis->pfnExecute(pThis)) + { + cExecuted++; + + /* + * Check the result against our expectations. + */ + CidetCoreCheckResults(pThis); + /** @todo check result. */ + + } + else + cSkipped++; + } + else + cSkipped++; + } while (CidetCoreSetupNextCodeBufferConfig(pThis)); + } while (CidetCoreSetupNextMemoryOperandConfig(pThis)); + } + else + cSkipped++; + } while (CidetCoreSetupNextBaseEncoding(pThis)); + + CIDET_DPRINTF(("CidetCoreTest_Basic: cExecuted=%u cSkipped=%u\n" + " cSkippedSetupInOut =%u\n" + " cSkippedReInitDataBuf =%u\n" + " cSkippedSetupDataBuf =%u\n" + " cSkippedDataBufWrtRip =%u\n" + " cSkippedAssemble =%u\n" + " cSkippedReInitCodeBuf =%u\n" + " cSkippedSetupCodeBuf =%u\n" + " cSkippedSameBaseIndexRemainder =%u\n" + " cSkippedOnlyIndexRemainder =%u\n" + " cSkippedDirectAddressingOverflow =%u\n" + , + cExecuted, cSkipped, + pThis->cSkippedSetupInOut, + pThis->cSkippedReInitDataBuf, + pThis->cSkippedSetupDataBuf, + pThis->cSkippedDataBufWrtRip, + pThis->cSkippedAssemble, + pThis->cSkippedReInitCodeBuf, + pThis->cSkippedSetupCodeBuf, + pThis->cSkippedSameBaseIndexRemainder, + pThis->cSkippedOnlyIndexRemainder, + pThis->cSkippedDirectAddressingOverflow + )); + + return true; +} + + +bool CidetCoreTestInstruction(PCIDETCORE pThis, PCCIDETINSTR pInstr) +{ + AssertReleaseMsgReturn(RT_VALID_PTR(pThis), ("%p\n", pThis), false); + AssertReleaseReturn(pThis->u32Magic == CIDETCORE_MAGIC, false); + AssertReleaseReturn(pThis->cCodeBufConfigs > 0, false); + + if (!CideCoreSetInstruction(pThis, pInstr)) + return CidetCoreSetError(pThis, "CideCoreSetInstruction failed"); + + bool fResult = CidetCoreTest_Basic(pThis); + + return fResult; +} + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet-instr-1.cpp b/src/VBox/ValidationKit/utils/cpu/cidet-instr-1.cpp new file mode 100644 index 00000000..a40e8d0f --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet-instr-1.cpp @@ -0,0 +1,287 @@ +/* $Id: cidet-instr-1.cpp $ */ +/** @file + * CPU Instruction Decoding & Execution Tests - First bunch of instructions. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "cidet.h" +#include <VBox/err.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* + * Shorter defines for the EFLAGS to save table space. + */ +#undef CF +#undef PF +#undef AF +#undef ZF +#undef SF +#undef OF + +#define CF X86_EFL_CF +#define PF X86_EFL_PF +#define AF X86_EFL_AF +#define ZF X86_EFL_ZF +#define SF X86_EFL_SF +#define OF X86_EFL_OF + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct CIDET2IN1OUTWITHFLAGSU8ENTRY +{ + uint8_t uIn1; + uint8_t uIn2; + uint16_t fEFlagsIn; + uint8_t uOut; + uint16_t fEFlagsOut; +} CIDET2IN1OUTWITHFLAGSU8ENTRY; +typedef CIDET2IN1OUTWITHFLAGSU8ENTRY const *PCCIDET2IN1OUTWITHFLAGSU8ENTRY; + +typedef struct CIDET2IN1OUTWITHFLAGSU16ENTRY +{ + uint16_t uIn1; + uint16_t uIn2; + uint16_t fEFlagsIn; + uint16_t uOut; + uint16_t fEFlagsOut; +} CIDET2IN1OUTWITHFLAGSU16ENTRY; +typedef CIDET2IN1OUTWITHFLAGSU16ENTRY const *PCCIDET2IN1OUTWITHFLAGSU16ENTRY; + +typedef struct CIDET2IN1OUTWITHFLAGSU32ENTRY +{ + uint32_t uIn1; + uint32_t uIn2; + uint16_t fEFlagsIn; + uint32_t uOut; + uint16_t fEFlagsOut; +} CIDET2IN1OUTWITHFLAGSU32ENTRY; +typedef CIDET2IN1OUTWITHFLAGSU32ENTRY const *PCCIDET2IN1OUTWITHFLAGSU32ENTRY; + +typedef struct CIDET2IN1OUTWITHFLAGSU64ENTRY +{ + uint64_t uIn1; + uint64_t uIn2; + uint16_t fEFlagsIn; + uint64_t uOut; + uint16_t fEFlagsOut; +} CIDET2IN1OUTWITHFLAGSU64ENTRY; +typedef CIDET2IN1OUTWITHFLAGSU64ENTRY const *PCCIDET2IN1OUTWITHFLAGSU64ENTRY; + +typedef struct CIDET2IN1OUTWITHFLAGS +{ + PCCIDET2IN1OUTWITHFLAGSU8ENTRY pa8Entries; + PCCIDET2IN1OUTWITHFLAGSU16ENTRY pa16Entries; + PCCIDET2IN1OUTWITHFLAGSU32ENTRY pa32Entries; + PCCIDET2IN1OUTWITHFLAGSU64ENTRY pa64Entries; + uint16_t c8Entries; + uint16_t c16Entries; + uint16_t c32Entries; + uint16_t c64Entries; + uint32_t fRelevantEFlags; +} CIDET2IN1OUTWITHFLAGS; + +#define CIDET2IN1OUTWITHFLAGS_INITIALIZER(a_fRelevantEFlags) \ + { \ + &s_a8Results[0], &s_a16Results[0], &s_a32Results[0], &s_a64Results[0], \ + RT_ELEMENTS(s_a8Results), RT_ELEMENTS(s_a16Results), RT_ELEMENTS(s_a32Results), RT_ELEMENTS(s_a64Results), \ + (a_fRelevantEFlags) \ + } + + +/** + * Generic worker for a FNCIDETSETUPINOUT function with two GPR/MEM registers, + * storing result in the first and flags. + * + * @returns See FNCIDETSETUPINOUT. + * @param pThis The core CIDET state structure. The InCtx + * and ExpectedCtx members will be modified. + * @param fInvalid When set, get the next invalid operands that will + * cause exceptions/faults. + * @param pResults The result collection. + */ +static int CidetGenericIn2Out1WithFlags(PCIDETCORE pThis, bool fInvalid, CIDET2IN1OUTWITHFLAGS const *pResults) +{ + int rc; + + Assert(pThis->idxMrmRegOp < 2); + Assert(pThis->idxMrmRmOp < 2); + Assert(pThis->idxMrmRmOp != pThis->idxMrmRegOp); + AssertCompile(RT_ELEMENTS(pThis->aiInOut) >= 4); + + if (!fInvalid) + { + if ( !pThis->fHasRegCollisionDirect + && !pThis->fHasRegCollisionMem) + { + pThis->InCtx.rfl &= ~(uint64_t)pResults->fRelevantEFlags; + pThis->ExpectedCtx.rfl &= ~(uint64_t)pResults->fRelevantEFlags; + switch (pThis->aOperands[0].cb) + { + case 1: + { + uint16_t idx = ++pThis->aiInOut[0] % pResults->c8Entries; + PCCIDET2IN1OUTWITHFLAGSU8ENTRY pEntry = &pResults->pa8Entries[idx]; + rc = idx ? VINF_SUCCESS : VINF_EOF; + + *pThis->aOperands[0].In.pu8 = pEntry->uIn1; + *pThis->aOperands[1].In.pu8 = pEntry->uIn2; + pThis->InCtx.rfl |= pEntry->fEFlagsIn; + + *pThis->aOperands[0].Expected.pu8 = pEntry->uOut; + *pThis->aOperands[1].Expected.pu8 = pEntry->uIn2; + pThis->ExpectedCtx.rfl |= pEntry->fEFlagsOut; + break; + } + + case 2: + { + uint16_t idx = ++pThis->aiInOut[1] % pResults->c16Entries; + PCCIDET2IN1OUTWITHFLAGSU16ENTRY pEntry = &pResults->pa16Entries[idx]; + rc = idx ? VINF_SUCCESS : VINF_EOF; + + *pThis->aOperands[0].In.pu16 = pEntry->uIn1; + *pThis->aOperands[1].In.pu16 = pEntry->uIn2; + pThis->InCtx.rfl |= pEntry->fEFlagsIn; + + *pThis->aOperands[0].Expected.pu16 = pEntry->uOut; + *pThis->aOperands[1].Expected.pu16 = pEntry->uIn2; + pThis->ExpectedCtx.rfl |= pEntry->fEFlagsOut; + break; + } + + case 4: + { + uint16_t idx = ++pThis->aiInOut[2] % pResults->c32Entries; + PCCIDET2IN1OUTWITHFLAGSU32ENTRY pEntry = &pResults->pa32Entries[idx]; + rc = idx ? VINF_SUCCESS : VINF_EOF; + + *pThis->aOperands[0].In.pu32 = pEntry->uIn1; + *pThis->aOperands[1].In.pu32 = pEntry->uIn2; + pThis->InCtx.rfl |= pEntry->fEFlagsIn; + + *pThis->aOperands[0].Expected.pu32 = pEntry->uOut; + if (!pThis->aOperands[0].fIsMem) + pThis->aOperands[0].Expected.pu32[1] = 0; + *pThis->aOperands[1].Expected.pu32 = pEntry->uIn2; + pThis->ExpectedCtx.rfl |= pEntry->fEFlagsOut; + break; + } + + case 8: + { + uint16_t idx = ++pThis->aiInOut[3] % pResults->c64Entries; + PCCIDET2IN1OUTWITHFLAGSU64ENTRY pEntry = &pResults->pa64Entries[idx]; + rc = idx ? VINF_SUCCESS : VINF_EOF; + + *pThis->aOperands[0].In.pu64 = pEntry->uIn1; + *pThis->aOperands[1].In.pu64 = pEntry->uIn2; + pThis->InCtx.rfl |= pEntry->fEFlagsIn; + + *pThis->aOperands[0].Expected.pu64 = pEntry->uOut; + *pThis->aOperands[1].Expected.pu64 = pEntry->uIn2; + pThis->ExpectedCtx.rfl |= pEntry->fEFlagsOut; + break; + } + + default: + AssertFailed(); + rc = VERR_INTERNAL_ERROR_3; + } + } + else + rc = VERR_NOT_SUPPORTED; + } + else + rc = VERR_NO_DATA; + return rc; +} + + +static DECLCALLBACK(int) cidetInOutAdd(PCIDETCORE pThis, bool fInvalid) +{ + static const CIDET2IN1OUTWITHFLAGSU8ENTRY s_a8Results[] = + { + { UINT8_C(0x00), UINT8_C(0x00), 0, UINT8_C(0x00), ZF | PF }, + { UINT8_C(0xff), UINT8_C(0x01), 0, UINT8_C(0x00), CF | ZF | AF | PF }, + { UINT8_C(0x7f), UINT8_C(0x80), 0, UINT8_C(0xff), SF | PF }, + { UINT8_C(0x01), UINT8_C(0x01), 0, UINT8_C(0x02), 0 }, + }; + static const CIDET2IN1OUTWITHFLAGSU16ENTRY s_a16Results[] = + { + { UINT16_C(0x0000), UINT16_C(0x0000), 0, UINT16_C(0x0000), ZF | PF }, + { UINT16_C(0xfefd), UINT16_C(0x0103), 0, UINT16_C(0x0000), CF | ZF | AF | PF }, + { UINT16_C(0x8e7d), UINT16_C(0x7182), 0, UINT16_C(0xffff), SF | PF }, + { UINT16_C(0x0001), UINT16_C(0x0001), 0, UINT16_C(0x0002), 0 }, + }; + static const CIDET2IN1OUTWITHFLAGSU32ENTRY s_a32Results[] = + { + { UINT32_C(0x00000000), UINT32_C(0x00000000), 0, UINT32_C(0x00000000), ZF | PF }, + { UINT32_C(0xfefdfcfb), UINT32_C(0x01020305), 0, UINT32_C(0x00000000), CF | ZF | AF | PF }, + { UINT32_C(0x8efdfcfb), UINT32_C(0x71020304), 0, UINT32_C(0xffffffff), SF | PF }, + { UINT32_C(0x00000001), UINT32_C(0x00000001), 0, UINT32_C(0x00000002), 0 }, + }; + static const CIDET2IN1OUTWITHFLAGSU64ENTRY s_a64Results[] = + { + { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000), 0, UINT64_C(0x0000000000000000), ZF | PF }, + { UINT64_C(0xfefdfcfbfaf9f8f7), UINT64_C(0x0102030405060709), 0, UINT64_C(0x0000000000000000), CF | ZF | AF | PF }, + { UINT64_C(0x7efdfcfbfaf9f8f7), UINT64_C(0x8102030405060708), 0, UINT64_C(0xffffffffffffffff), SF | PF }, + { UINT64_C(0x0000000000000001), UINT64_C(0x0000000000000001), 0, UINT64_C(0x0000000000000002), 0 }, + }; + static const CIDET2IN1OUTWITHFLAGS s_Results = CIDET2IN1OUTWITHFLAGS_INITIALIZER(CF | PF | AF | SF | OF); + return CidetGenericIn2Out1WithFlags(pThis, fInvalid, &s_Results); +} + + +/** First bunch of instructions. */ +const CIDETINSTR g_aCidetInstructions1[] = +{ +#if 1 + { + "add Eb,Gb", cidetInOutAdd, 1, {0x00, 0, 0}, 0, 2, + { CIDET_OF_K_GPR | CIDET_OF_Z_BYTE | CIDET_OF_M_RM | CIDET_OF_A_RW, + CIDET_OF_K_GPR | CIDET_OF_Z_BYTE | CIDET_OF_M_REG | CIDET_OF_A_R, + 0, 0 }, CIDET_IF_MODRM + }, +#endif +#if 1 + { + "add Ev,Gv", cidetInOutAdd, 1, {0x01, 0, 0}, 0, 2, + { CIDET_OF_K_GPR | CIDET_OF_Z_VAR_WDQ | CIDET_OF_M_RM | CIDET_OF_A_RW, + CIDET_OF_K_GPR | CIDET_OF_Z_VAR_WDQ | CIDET_OF_M_REG | CIDET_OF_A_R, + 0, 0 }, CIDET_IF_MODRM + }, +#endif +}; +/** Number of instruction in the g_aInstructions1 array. */ +const uint32_t g_cCidetInstructions1 = RT_ELEMENTS(g_aCidetInstructions1); + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet.h b/src/VBox/ValidationKit/utils/cpu/cidet.h new file mode 100644 index 00000000..a3667c73 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet.h @@ -0,0 +1,1082 @@ +/* $Id: cidet.h $ */ +/** @file + * CPU Instruction Decoding & Execution Tests - C/C++ Header. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef VBOX_INCLUDED_SRC_cpu_cidet_h +#define VBOX_INCLUDED_SRC_cpu_cidet_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> +#include <iprt/x86.h> + + +/** @name CIDET - Operand flags. + * @{ */ +#define CIDET_OF_FIXED_MASK UINT32_C(0x0000001f) /**< Fixed register/whatever mask. */ + +#define CIDET_OF_Z_SHIFT 8 /**< Size shift. */ +#define CIDET_OF_Z_MASK UINT32_C(0x00000f00) /**< Size mask. */ +#define CIDET_OF_Z_NONE UINT32_C(0x00000000) /**< Unused zero value. */ +#define CIDET_OF_Z_BYTE UINT32_C(0x00000100) /**< Byte size. */ +#define CIDET_OF_Z_WORD UINT32_C(0x00000200) /**< Word (2 bytes) size. */ +#define CIDET_OF_Z_DWORD UINT32_C(0x00000300) /**< Double word (4 bytes) size. */ +#define CIDET_OF_Z_QWORD UINT32_C(0x00000400) /**< Quad word (8 bytes) size. */ +#define CIDET_OF_Z_TBYTE UINT32_C(0x00000500) /**< Ten byte (10 bytes) size - aka TWORD. */ +#define CIDET_OF_Z_OWORD UINT32_C(0x00000600) /**< Octa word (16 bytes) size - aka DQWORD. */ +#define CIDET_OF_Z_YWORD UINT32_C(0x00000700) /**< Yxx sized, i.e. 32 bytes. */ +#define CIDET_OF_Z_ZWORD UINT32_C(0x00000800) /**< Zxx sized, i.e. 64 bytes. */ +#define CIDET_OF_Z_VAR_WDQ UINT32_C(0x00000900) /**< Variable size depending on size prefix (2, 4, or 8 bytes). */ +#define CIDET_OF_Z_SPECIAL UINT32_C(0x00000f00) /**< Special size, see instruction flags or smth. */ + +#define CIDET_OF_K_MASK UINT32_C(0x0000f000) /**< Kind of operand. */ +#define CIDET_OF_K_NONE UINT32_C(0x00000000) /**< Unused zero value. */ +#define CIDET_OF_K_GPR UINT32_C(0x00001000) /**< General purpose register. Includes memory when used with CIDET_OF_M_RM. */ +#define CIDET_OF_K_SREG UINT32_C(0x00002000) /**< Segment register. */ +#define CIDET_OF_K_CR UINT32_C(0x00003000) /**< Control register. */ +#define CIDET_OF_K_SSE UINT32_C(0x00004000) /**< SSE register. */ +#define CIDET_OF_K_AVX UINT32_C(0x00005000) /**< AVX register. */ +#define CIDET_OF_K_AVX512 UINT32_C(0x00006000) /**< AVX-512 register. */ +#define CIDET_OF_K_AVXFUTURE UINT32_C(0x00007000) /**< Reserved for future AVX register set. */ +#define CIDET_OF_K_VRX_TST_MASK UINT32_C(0x0000c000) /**< Used for testing for VRX register kind, see CIDET_OF_K_IS_VRX. */ +#define CIDET_OF_K_VRX_TST_RES UINT32_C(0x00004000) /**< Used for testing for VRX register kind, see CIDET_OF_K_IS_VRX. */ +#define CIDET_OF_K_FPU UINT32_C(0x00008000) /**< FPU register. */ +#define CIDET_OF_K_MMX UINT32_C(0x00009000) /**< MMX register. */ +#define CIDET_OF_K_TEST UINT32_C(0x0000a000) /**< Test register. */ +#define CIDET_OF_K_IMM UINT32_C(0x0000d000) /**< Immediate. */ +#define CIDET_OF_K_MEM UINT32_C(0x0000e000) /**< Memory. */ +#define CIDET_OF_K_SPECIAL UINT32_C(0x0000f000) /**< Special. */ +/** Check if @a a_fOp is a general purpose register. */ +#define CIDET_OF_K_IS_GPR(a_fOp) ( ((a_fOp) & CIDET_OF_K_MASK) == CIDET_OF_K_GPR ) +/** Check if @a a_fOp is a XMM (SSE), YMM (AVX), ZMM (AVX-512) or similar register. */ +#define CIDET_OF_K_IS_VRX(a_fOp) ( ((a_fOp) & CIDET_OF_K_VRX_TST_MASK) == CIDET_OF_K_VRX_TST_RES ) +/** Check if @a a_fOp1 and @a a_fOp2 specify the same kind of register, + * treating SSE, AVX, AVX-512 and AVX-future as the same kind and ignoring the + * special register kind. */ +#define CIDET_OF_K_IS_SAME(a_fOp1, a_fOp2) \ + ( ((a_fOp1) & CIDET_OF_K_MASK) == ((a_fOp2) & CIDET_OF_K_MASK) \ + ? ((a_fOp1) & CIDET_OF_K_MASK) != CIDET_OF_K_SPECIAL \ + : (CIDET_OF_K_IS_VRX(a_fOp1) && CIDET_OF_K_IS_VRX(a_fOp2)) ) + +#define CIDET_OF_M_RM_ONLY_R UINT32_C(0x00010000) +#define CIDET_OF_M_RM_ONLY_M UINT32_C(0x00020000) +#define CIDET_OF_M_RM (CIDET_OF_M_RM_ONLY_R | CIDET_OF_M_RM_ONLY_M) +#define CIDET_OF_M_REG UINT32_C(0x00040000) + +#define CIDET_OF_A_R UINT32_C(0x00080000) /**< Read access. */ +#define CIDET_OF_A_W UINT32_C(0x00100000) /**< Write access. */ +#define CIDET_OF_A_RW UINT32_C(0x00180000) /**< Read & write access. */ + +/** The operand defaults to 64-bit width in 64-bit mode, making 32-bit width + * inaccessible. */ +#define CIDET_OF_DEFAULT_64BIT UINT32_C(0x40000000) +/** Operand always uses the ES segment for memory accesses. */ +#define CIDET_OF_ALWAYS_SEG_ES UINT32_C(0x80000000) +/** @} */ + + +/** @name CIDET - Instruction flags. + * @{ */ +#define CIDET_IF_MODRM RT_BIT_64(0) /**< ModR/M encoded. */ +#define CIDET_IF_PRIVILEGED RT_BIT_64(1) /**< Privileged. */ +/** @} */ + + +/** + * Callback function for setting up the input and expected output CPU contexts. + * + * @returns VBox status code. + * @retval VINF_EOF when static test data wraps (first entry is returned). + * @retval VERR_NO_DATA if @a fInvalid is set and there are no invalid operand + * values for this instruction. + * @retval VERR_NOT_SUPPORTED if something in the setup prevents us from + * comming up with working set of inputs and outputs. + * + * @param pThis The core CIDET state structure. The InCtx + * and ExpectedCtx members will be modified. + * @param fInvalid When set, get the next invalid operands that will + * cause exceptions/faults. + */ +typedef DECLCALLBACK(int) FNCIDETSETUPINOUT(struct CIDETCORE *pThis, bool fInvalid); +/** Pointer to a FNCIDETSETUPINOUT function. */ +typedef FNCIDETSETUPINOUT *PFNCIDETSETUPINOUT; + + +/** + * Instruction test descriptor. + */ +typedef struct CIDETINSTR +{ + /** The mnemonic (kind of). */ + const char *pszMnemonic; + /** Setup input and outputs. */ + PFNCIDETSETUPINOUT pfnSetupInOut; + /** Number of opcode bytes. */ + uint8_t cbOpcode; + /** Opcode byte(s). */ + uint8_t abOpcode[3]; + /** Mandatory prefix (zero if not applicable). */ + uint8_t bMandatoryPrefix; + /** Number of operands. */ + uint8_t cOperands; + /** Operand flags. */ + uint32_t afOperands[4]; + /** Flags. */ + uint64_t fFlags; +} CIDETINSTR; +/** Pointer to an instruction test descriptor. */ +typedef CIDETINSTR const *PCCIDETINSTR; + + +/** + * CPU Context with a few extra bits for expectations and results. + */ +typedef struct CIDETCPUCTX +{ + uint64_t rip; + uint64_t rfl; + uint64_t aGRegs[16]; + uint16_t aSRegs[6]; + +#ifndef CIDET_REDUCED_CTX + uint16_t tr; + uint16_t ldtr; + uint64_t cr0; +#else + uint16_t au16Padding[2]; +#endif + uint64_t cr2; +#ifndef CIDET_REDUCED_CTX + uint64_t cr3; + uint64_t cr4; + uint64_t cr8; + uint64_t dr0; + uint64_t dr1; + uint64_t dr2; + uint64_t dr3; + uint64_t dr6; + uint64_t dr7; +#endif + + uint64_t uErr; /**< Exception error code. UINT64_MAX if not applicable. (Not for input context.) */ + uint32_t uXcpt; /**< Exception number. UINT32_MAX if no exception. (Not for input context.) */ + + uint32_t fIgnoredRFlags; /**< Only for expected result. */ + bool fTrickyStack; /**< Set if the stack might be bad. May come at the cost of accurate flags (32-bit). */ +} CIDETCPUCTX; +typedef CIDETCPUCTX *PCIDETCPUCTX; +typedef CIDETCPUCTX const *PCCIDETCPUCTX; + +/** Number of bytes of CIDETCPUCTX that can be compared quickly using memcmp. + * Anything following these bytes are not relevant to the compare. */ +#define CIDETCPUCTX_COMPARE_SIZE RT_UOFFSETOF(CIDETCPUCTX, fIgnoredRFlags) + + +/** @name CPU mode + bits + environment. + * @{ */ +#define CIDETMODE_BIT_MASK UINT8_C(0x0e) /**< The instruction bit count. Results in byte size when masked. */ +#define CIDETMODE_BIT_16 UINT8_C(0x02) /**< 16-bit instructions. */ +#define CIDETMODE_BIT_32 UINT8_C(0x04) /**< 32-bit instructions. */ +#define CIDETMODE_BIT_64 UINT8_C(0x08) /**< 64-bit instructions. */ +#define CIDETMODE_MODE_MASK UINT8_C(0x70) /**< CPU mode mask. */ +#define CIDETMODE_MODE_RM UINT8_C(0x00) /**< Real mode. */ +#define CIDETMODE_MODE_PE UINT8_C(0x10) /**< Protected mode without paging. */ +#define CIDETMODE_MODE_PP UINT8_C(0x20) /**< Paged protected mode. */ +#define CIDETMODE_MODE_PAE UINT8_C(0x30) /**< PAE protected mode (paged). */ +#define CIDETMODE_MODE_LM UINT8_C(0x40) /**< Long mode (paged). */ +#define CIDETMODE_ENV_MASK UINT8_C(0x81) /**< Execution environment. */ +#define CIDETMODE_ENV_NORMAL UINT8_C(0x01) /**< Normal environment. */ +#define CIDETMODE_ENV_V86 UINT8_C(0x80) /**< V8086 environment. */ +#define CIDETMODE_RM (CIDETMODE_MODE_RM | CIDETMODE_BIT_16 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PE_16 (CIDETMODE_MODE_PE | CIDETMODE_BIT_16 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PE_32 (CIDETMODE_MODE_PE | CIDETMODE_BIT_32 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PE_V86 (CIDETMODE_MODE_PE | CIDETMODE_BIT_16 | CIDETMODE_ENV_V86) +#define CIDETMODE_PP_16 (CIDETMODE_MODE_PP | CIDETMODE_BIT_16 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PP_32 (CIDETMODE_MODE_PP | CIDETMODE_BIT_32 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PP_V86 (CIDETMODE_MODE_PP | CIDETMODE_BIT_16 | CIDETMODE_ENV_V86) +#define CIDETMODE_PAE_16 (CIDETMODE_MODE_PAE | CIDETMODE_BIT_16 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PAE_32 (CIDETMODE_MODE_PAE | CIDETMODE_BIT_32 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PAE_V86 (CIDETMODE_MODE_PAE | CIDETMODE_BIT_16 | CIDETMODE_ENV_V86) +#define CIDETMODE_LM_16 (CIDETMODE_MODE_LM | CIDETMODE_BIT_16 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_LM_32 (CIDETMODE_MODE_LM | CIDETMODE_BIT_32 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_LM_64 (CIDETMODE_MODE_LM | CIDETMODE_BIT_64 | CIDETMODE_ENV_NORMAL) +/** Test if @a a_bMode is a 16-bit mode. */ +#define CIDETMODE_IS_16BIT(a_bMode) ( ((a_bMode) & CIDETMODE_BIT_MASK) == CIDETMODE_BIT_16 ) +/** Test if @a a_bMode is a 32-bit mode. */ +#define CIDETMODE_IS_32BIT(a_bMode) ( ((a_bMode) & CIDETMODE_BIT_MASK) == CIDETMODE_BIT_32 ) +/** Test if @a a_bMode is a 64-bit mode. */ +#define CIDETMODE_IS_64BIT(a_bMode) ( ((a_bMode) & CIDETMODE_BIT_MASK) == CIDETMODE_BIT_64 ) +/** Get the instruction bit count. */ +#define CIDETMODE_GET_BIT_COUNT(a_bMode) ( CIDETMODE_GET_BYTE_COUNT(a_bMode) * 8 ) +/** Get the instruction byte count. */ +#define CIDETMODE_GET_BYTE_COUNT(a_bMode) ( (a_bMode) & CIDETMODE_BIT_MASK ) +/** Test if @a a_bMode long mode. */ +#define CIDETMODE_IS_LM(a_bMode) ( ((a_bMode) & CIDETMODE_MODE_MASK) == CIDETMODE_MODE_LM ) +/** Test if @a a_bMode some kind of protected mode. */ +#define CIDETMODE_IS_PROT(a_bMode) ( ((a_bMode) & CIDETMODE_MODE_MASK) >= CIDETMODE_MODE_PE ) + +/** @} */ + + +/** @name Test Configuration Flags. + * @{ */ +#define CIDET_TESTCFG_SEG_PRF_CS UINT64_C(0x0000000000000001) +#define CIDET_TESTCFG_SEG_PRF_SS UINT64_C(0x0000000000000002) +#define CIDET_TESTCFG_SEG_PRF_DS UINT64_C(0x0000000000000004) +#define CIDET_TESTCFG_SEG_PRF_ES UINT64_C(0x0000000000000008) +#define CIDET_TESTCFG_SEG_PRF_FS UINT64_C(0x0000000000000010) +#define CIDET_TESTCFG_SEG_PRF_GS UINT64_C(0x0000000000000020) +#define CIDET_TESTCFG_SEG_PRF_MASK UINT64_C(0x000000000000003f) +/** @} */ + +/** */ +typedef enum CIDETREG +{ + kCidetReg_Gpr_Invalid = 0, + + kCidetReg_Gpr_al, + kCidetReg_Gpr_cl, + kCidetReg_Gpr_dl, + kCidetReg_Gpr_bl, + kCidetReg_Gpr_spl, + kCidetReg_Gpr_bpl, + kCidetReg_Gpr_sil, + kCidetReg_Gpr_dil, + kCidetReg_Gpr_r8b, + kCidetReg_Gpr_r9b, + kCidetReg_Gpr_r10b, + kCidetReg_Gpr_r11b, + kCidetReg_Gpr_r12b, + kCidetReg_Gpr_r13b, + kCidetReg_Gpr_r14b, + kCidetReg_Gpr_r15b, + kCidetReg_Gpr_ah, + kCidetReg_Gpr_ch, + kCidetReg_Gpr_dh, + kCidetReg_Gpr_bh, +#define kCidetReg_Gpr_Byte_First kCidetReg_Gpr_al +#define kCidetReg_Gpr_Byte_First_Upper kCidetReg_Gpr_ah +#define kCidetReg_Gpr_Byte_Last kCidetReg_Gpr_bh + + kCidetReg_Gpr_ax, + kCidetReg_Gpr_cx, + kCidetReg_Gpr_dx, + kCidetReg_Gpr_bx, + kCidetReg_Gpr_sp, + kCidetReg_Gpr_bp, + kCidetReg_Gpr_si, + kCidetReg_Gpr_di, + kCidetReg_Gpr_r8w, + kCidetReg_Gpr_r9w, + kCidetReg_Gpr_r10w, + kCidetReg_Gpr_r11w, + kCidetReg_Gpr_r12w, + kCidetReg_Gpr_r13w, + kCidetReg_Gpr_r14w, + kCidetReg_Gpr_r15w, +#define kCidetReg_Gpr_Word_First kCidetReg_Gpr_ax +#define kCidetReg_Gpr_Word_Last kCidetReg_Gpr_r15w + + kCidetReg_Gpr_eax, + kCidetReg_Gpr_ecx, + kCidetReg_Gpr_edx, + kCidetReg_Gpr_ebx, + kCidetReg_Gpr_esp, + kCidetReg_Gpr_ebp, + kCidetReg_Gpr_esi, + kCidetReg_Gpr_edi, + kCidetReg_Gpr_r8d, + kCidetReg_Gpr_r9d, + kCidetReg_Gpr_r10d, + kCidetReg_Gpr_r11d, + kCidetReg_Gpr_r12d, + kCidetReg_Gpr_r13d, + kCidetReg_Gpr_r14d, + kCidetReg_Gpr_r15d, +#define kCidetReg_Gpr_DWord_First kCidetReg_Gpr_eax +#define kCidetReg_Gpr_DWord_Last kCidetReg_Gpr_r15d + + kCidetReg_Gpr_rax, + kCidetReg_Gpr_rcx, + kCidetReg_Gpr_rdx, + kCidetReg_Gpr_rbx, + kCidetReg_Gpr_rsp, + kCidetReg_Gpr_rbp, + kCidetReg_Gpr_rsi, + kCidetReg_Gpr_rdi, + kCidetReg_Gpr_r8, + kCidetReg_Gpr_r9, + kCidetReg_Gpr_r10, + kCidetReg_Gpr_r11, + kCidetReg_Gpr_r12, + kCidetReg_Gpr_r13, + kCidetReg_Gpr_r14, + kCidetReg_Gpr_r15, +#define kCidetReg_Gpr_QWord_First kCidetReg_Gpr_rax +#define kCidetReg_Gpr_QWord_Last kCidetReg_Gpr_r15 + + kCidetReg_Seg_es, + kCidetReg_Seg_cs, + kCidetReg_Seg_ss, + kCidetReg_Seg_ds, + kCidetReg_Seg_fs, + kCidetReg_Seg_gs, + kCidetReg_Seg_Inv6, + kCidetReg_Seg_Inv7, +#define kCidetReg_Seg_First kCidetReg_Seg_es +#define kCidetReg_Seg_Last kCidetReg_Seg_gs +#define kCidetReg_Seg_Last_Inv kCidetReg_Seg_Inv7 + + kCidetReg_Misc_ip, + kCidetReg_Misc_eip, + kCidetReg_Misc_rip, + kCidetReg_Misc_flags, + kCidetReg_Misc_eflags, + kCidetReg_Misc_rflags, + kCidetReg_Misc_tr, + kCidetReg_Misc_ldtr, + kCidetReg_Misc_gdtr, + kCidetReg_Misc_idtr, + + kCidetReg_Ctrl_cr0, + kCidetReg_Ctrl_cr1, + kCidetReg_Ctrl_cr2, + kCidetReg_Ctrl_cr3, + kCidetReg_Ctrl_cr4, + kCidetReg_Ctrl_cr5, + kCidetReg_Ctrl_cr6, + kCidetReg_Ctrl_cr7, + kCidetReg_Ctrl_cr8, + kCidetReg_Ctrl_cr9, + kCidetReg_Ctrl_cr10, + kCidetReg_Ctrl_cr11, + kCidetReg_Ctrl_cr12, + kCidetReg_Ctrl_cr13, + kCidetReg_Ctrl_cr14, + kCidetReg_Ctrl_cr15, +#define kCidetReg_Ctrl_First kCidetReg_Ctrl_cr0 +#define kCidetReg_Ctrl_Last kCidetReg_Ctrl_cr15 +#define CIDETREG_CTRL_IS_VALID(a_iReg) ( (a_iReg) == kCidetReg_Ctrl_cr0 \ + && (a_iReg) == kCidetReg_Ctrl_cr2 \ + && (a_iReg) == kCidetReg_Ctrl_cr3 \ + && (a_iReg) == kCidetReg_Ctrl_cr4 \ + && (a_iReg) == kCidetReg_Ctrl_cr8 ) + + kCidetReg_Dbg_dr0, + kCidetReg_Dbg_dr1, + kCidetReg_Dbg_dr2, + kCidetReg_Dbg_dr3, + kCidetReg_Dbg_dr4, + kCidetReg_Dbg_dr5, + kCidetReg_Dbg_dr6, + kCidetReg_Dbg_dr7, + kCidetReg_Dbg_dr8, + kCidetReg_Dbg_dr9, + kCidetReg_Dbg_dr10, + kCidetReg_Dbg_dr11, + kCidetReg_Dbg_dr12, + kCidetReg_Dbg_dr13, + kCidetReg_Dbg_dr14, + kCidetReg_Dbg_dr15, +#define kCidetReg_Dbg_First kCidetReg_Dbg_dr0 +#define kCidetReg_Dbg_Last kCidetReg_Dbg_dr15 +#define CIDETREG_DBG_IS_VALID(a_iReg) ((a_iReg) < kCidetReg_Dbg_dr8 && (a_iReg) >= kCidetReg_Dbg_First) + + kCidetReg_Test_tr0, + kCidetReg_Test_tr1, + kCidetReg_Test_tr2, + kCidetReg_Test_tr3, + kCidetReg_Test_tr4, + kCidetReg_Test_tr5, + kCidetReg_Test_tr6, + kCidetReg_Test_tr7, + kCidetReg_Test_tr8, + kCidetReg_Test_tr9, + kCidetReg_Test_tr10, + kCidetReg_Test_tr11, + kCidetReg_Test_tr12, + kCidetReg_Test_tr13, + kCidetReg_Test_tr14, + kCidetReg_Test_tr15, +#define kCidetReg_Test_First kCidetReg_Test_tr0 +#define kCidetReg_Test_Last kCidetReg_Test_tr15 + + kCidetReg_Fpu_st0, + kCidetReg_Fpu_st1, + kCidetReg_Fpu_st2, + kCidetReg_Fpu_st3, + kCidetReg_Fpu_st4, + kCidetReg_Fpu_st5, + kCidetReg_Fpu_st6, + kCidetReg_Fpu_st7, +#define kCidetReg_Fpu_First kCidetReg_Mmx_st0 +#define kCidetReg_Fpu_Last kCidetReg_Mmx_st7 + + kCidetReg_FpuMisc_cs, + kCidetReg_FpuMisc_ip, + kCidetReg_FpuMisc_ds, + kCidetReg_FpuMisc_dp, + kCidetReg_FpuMisc_fop, + kCidetReg_FpuMisc_ftw, + kCidetReg_FpuMisc_fsw, + kCidetReg_FpuMisc_fcw, + kCidetReg_FpuMisc_mxcsr_mask, + kCidetReg_FpuMisc_mxcsr, + + kCidetReg_Mmx_mm0, + kCidetReg_Mmx_mm1, + kCidetReg_Mmx_mm2, + kCidetReg_Mmx_mm3, + kCidetReg_Mmx_mm4, + kCidetReg_Mmx_mm5, + kCidetReg_Mmx_mm6, + kCidetReg_Mmx_mm7, +#define kCidetReg_Mmx_First kCidetReg_Mmx_mm0 +#define kCidetReg_Mmx_Last kCidetReg_Mmx_mm7 + + kCidetReg_Sse_xmm0, + kCidetReg_Sse_xmm1, + kCidetReg_Sse_xmm2, + kCidetReg_Sse_xmm3, + kCidetReg_Sse_xmm4, + kCidetReg_Sse_xmm5, + kCidetReg_Sse_xmm6, + kCidetReg_Sse_xmm7, + kCidetReg_Sse_xmm8, + kCidetReg_Sse_xmm9, + kCidetReg_Sse_xmm10, + kCidetReg_Sse_xmm11, + kCidetReg_Sse_xmm12, + kCidetReg_Sse_xmm13, + kCidetReg_Sse_xmm14, + kCidetReg_Sse_xmm15, + kCidetReg_Sse_xmm16, + kCidetReg_Sse_xmm17, + kCidetReg_Sse_xmm18, + kCidetReg_Sse_xmm19, + kCidetReg_Sse_xmm20, + kCidetReg_Sse_xmm21, + kCidetReg_Sse_xmm22, + kCidetReg_Sse_xmm23, + kCidetReg_Sse_xmm24, + kCidetReg_Sse_xmm25, + kCidetReg_Sse_xmm26, + kCidetReg_Sse_xmm27, + kCidetReg_Sse_xmm28, + kCidetReg_Sse_xmm29, + kCidetReg_Sse_xmm30, + kCidetReg_Sse_xmm31, +#define kCidetReg_Sse_First kCidetReg_Mmx_Xmm0 +#define kCidetReg_Sse_Last kCidetReg_Mmx_Xmm15 +#define kCidetReg_Sse_Last_Avx512 kCidetReg_Mmx_Xmm31 + + kCidetReg_Avx_Ymm0, + kCidetReg_Avx_Ymm1, + kCidetReg_Avx_Ymm2, + kCidetReg_Avx_Ymm3, + kCidetReg_Avx_Ymm4, + kCidetReg_Avx_Ymm5, + kCidetReg_Avx_Ymm6, + kCidetReg_Avx_Ymm7, + kCidetReg_Avx_Ymm8, + kCidetReg_Avx_Ymm9, + kCidetReg_Avx_Ymm10, + kCidetReg_Avx_Ymm11, + kCidetReg_Avx_Ymm12, + kCidetReg_Avx_Ymm13, + kCidetReg_Avx_Ymm14, + kCidetReg_Avx_Ymm15, + kCidetReg_Avx_Ymm16, + kCidetReg_Avx_Ymm17, + kCidetReg_Avx_Ymm18, + kCidetReg_Avx_Ymm19, + kCidetReg_Avx_Ymm20, + kCidetReg_Avx_Ymm21, + kCidetReg_Avx_Ymm22, + kCidetReg_Avx_Ymm23, + kCidetReg_Avx_Ymm24, + kCidetReg_Avx_Ymm25, + kCidetReg_Avx_Ymm26, + kCidetReg_Avx_Ymm27, + kCidetReg_Avx_Ymm28, + kCidetReg_Avx_Ymm29, + kCidetReg_Avx_Ymm30, + kCidetReg_Avx_Ymm31, +#define kCidetReg_Avx_First kCidetReg_Avx_Ymm0 +#define kCidetReg_Avx_Last kCidetReg_Avx_Ymm15 +#define kCidetReg_Avx_Last_Avx512 kCidetReg_Avx_Ymm31 + + kCidetReg_Avx512_Zmm0, + kCidetReg_Avx512_Zmm1, + kCidetReg_Avx512_Zmm2, + kCidetReg_Avx512_Zmm3, + kCidetReg_Avx512_Zmm4, + kCidetReg_Avx512_Zmm5, + kCidetReg_Avx512_Zmm6, + kCidetReg_Avx512_Zmm7, + kCidetReg_Avx512_Zmm8, + kCidetReg_Avx512_Zmm9, + kCidetReg_Avx512_Zmm10, + kCidetReg_Avx512_Zmm11, + kCidetReg_Avx512_Zmm12, + kCidetReg_Avx512_Zmm13, + kCidetReg_Avx512_Zmm14, + kCidetReg_Avx512_Zmm15, + kCidetReg_Avx512_Zmm16, + kCidetReg_Avx512_Zmm17, + kCidetReg_Avx512_Zmm18, + kCidetReg_Avx512_Zmm19, + kCidetReg_Avx512_Zmm20, + kCidetReg_Avx512_Zmm21, + kCidetReg_Avx512_Zmm22, + kCidetReg_Avx512_Zmm23, + kCidetReg_Avx512_Zmm24, + kCidetReg_Avx512_Zmm25, + kCidetReg_Avx512_Zmm26, + kCidetReg_Avx512_Zmm27, + kCidetReg_Avx512_Zmm28, + kCidetReg_Avx512_Zmm29, + kCidetReg_Avx512_Zmm30, + kCidetReg_Avx512_Zmm31, +#define kCidetReg_Avx512_First kCidetReg_Avx512_Zmm0 +#define kCidetReg_Avx512_Last kCidetReg_Avx512_Zmm31 + + kCidetReg_End +} CIDETREG; + + +/** @name CIDETBUF_XXX - buffer flags. + * @{ */ +#define CIDETBUF_PROT_MASK UINT32_C(0x0000000f) /**< Page protection mask. */ +#define CIDETBUF_PROT_RWX UINT32_C(0x00000001) /**< Read + write + execute. */ +#define CIDETBUF_PROT_RWNX UINT32_C(0x00000002) /**< Read + write + no execute. */ +#define CIDETBUF_PROT_RX UINT32_C(0x00000003) /**< Read + execute. */ +#define CIDETBUF_PROT_RNX UINT32_C(0x00000004) /**< Read + no execute. */ +#define CIDETBUF_PROT_RWX_1NP UINT32_C(0x00000005) /**< Read + write + execute; 1 page not present. */ +#define CIDETBUF_PROT_RWX_1RWNX UINT32_C(0x00000006) /**< Read + write + execute; 1 page read + write + no execute. */ +#define CIDETBUF_PROT_RWX_1RNX UINT32_C(0x00000007) /**< Read + write + execute; 1 page read + no execute. */ +#define CIDETBUF_PROT_RWX_1RWXS UINT32_C(0x00000008) /**< Read + write + execute; 1 page read + execute + supervisor. */ + +#define CIDETBUF_LOC_MASK UINT32_C(0x000000f0) /**< Location mask. */ +/** Buffer located at top and start of the 32-bit address space. */ +#define CIDETBUF_LOC_32BIT_WRAP UINT32_C(0x00000010) +/** Buffer located at the low canonical boundrary (AMD64). */ +#define CIDETBUF_LOC_CANON_LO UINT32_C(0x00000020) +/** Buffer located at the high canonical boundrary (AMD64). */ +#define CIDETBUF_LOC_CANON_HI UINT32_C(0x00000030) + +/** Segment protection mask. */ +#define CIDETBUF_SEG_MASK UINT32_C(0x00000f00) +#define CIDETBUF_SEG_EO UINT32_C(0x00000100) /**< Execute only */ +#define CIDETBUF_SEG_ER UINT32_C(0x00000200) /**< Execute + read */ +#define CIDETBUF_SEG_EO_CONF UINT32_C(0x00000300) /**< Execute only + conforming. */ +#define CIDETBUF_SEG_ER_CONF UINT32_C(0x00000400) /**< Execute + read + conforming. */ +#define CIDETBUF_SEG_RO UINT32_C(0x00000500) /**< Read only. */ +#define CIDETBUF_SEG_RW UINT32_C(0x00000600) /**< Read + write. */ +#define CIDETBUF_SEG_RO_DOWN UINT32_C(0x00000700) /**< Read only + expand down. */ +#define CIDETBUF_SEG_RW_DOWN UINT32_C(0x00000800) /**< Read + write + expand down. */ + +#define CIDETBUF_DPL_MASK UINT32_C(0x00003000) /**< DPL mask. */ +#define CIDETBUF_DPL_0 UINT32_C(0x00000000) /**< DPL=0. */ +#define CIDETBUF_DPL_1 UINT32_C(0x00001000) /**< DPL=1. */ +#define CIDETBUF_DPL_2 UINT32_C(0x00002000) /**< DPL=2. */ +#define CIDETBUF_DPL_3 UINT32_C(0x00003000) /**< DPL=3. */ +#define CIDETBUF_DPL_SAME UINT32_C(0x00004000) /**< Same DPL as the execution environment. */ + +#define CIDETBUF_SEG_LIMIT_BASE_CAP UINT32_C(0x00008000) /**< Capability to change segment limit and base. */ + +#define CIDETBUF_KIND_DATA UINT32_C(0x00000000) /**< Data buffer. */ +#define CIDETBUF_KIND_CODE UINT32_C(0x80000000) /**< Code buffer. */ +/** Checks if @a a_fFlags describes a code buffer. */ +#define CIDETBUF_IS_CODE(a_fFlags) (((a_fFlags) & CIDETBUF_KIND_CODE) != 0) +/** Checks if @a a_fFlags describes a data buffer. */ +#define CIDETBUF_IS_DATA(a_fFlags) (((a_fFlags) & CIDETBUF_KIND_CODE) == 0) +/** @} */ + +/** Code buffer size. (At least two pages.) */ +#define CIDET_CODE_BUF_SIZE (PAGE_SIZE * 2) +/** Data buffer size. (At least two pages.) */ +#define CIDET_DATA_BUF_SIZE (PAGE_SIZE * 3) + + +/** + * Detailed expected exception. + * + * This is used to internally in the core to calculate the expected exception + * considering all the things that may cause exceptions. + */ +typedef enum CIDETEXPECTXCPT +{ + kCidetExpectXcpt_Invalid = 0, + /** No exception expected. */ + kCidetExpectXcpt_None, + + /** Page not present. */ + kCidetExpectXcpt_PageNotPresent, + /** Write access to a non-writable page. */ + kCidetExpectXcpt_PageNotWritable, + /** Executable access to a non-executable page. */ + kCidetExpectXcpt_PageNotExecutable, + /** Access to supervisor page from user mode code. */ + kCidetExpectXcpt_PagePrivileged, +#define kCidetExpectXcpt_First_PageFault kCidetExpectXcpt_PageNotPresent +#define kCidetExpectXcpt_Last_PageFault kCidetExpectXcpt_PagePrivileged + + /** Read or write access to an execute only segment. */ + kCidetExpectXcpt_SegExecuteOnly, + /** Write to a read only or execute+read segment. */ + kCidetExpectXcpt_SegNotWritable, + /** Exceeded the limit of a non-stack access. */ + kCidetExpectXcpt_SegExceededLimit, + /** Non-canonical address via any segment other than the stack. */ + kCidetExpectXcpt_AddrNotCanonical, + /** Misaligned 16 or 32 byte SSE or AVX operand. */ + kCidetExpectXcpt_MisalignedSseAvx, + /** Privileged instruction. */ + kCidetExpectXcpt_PrivilegedInstruction, +#define kCidetExpectXcpt_First_GeneralProtectionFault kCidetExpectXcpt_SegExecuteOnly +#define kCidetExpectXcpt_Last_GeneralProtectionFault kCidetExpectXcpt_PrivilegedInstruction + + /** Exceeded the limit of a stack access. */ + kCidetExpectXcpt_StackExceededLimit, + /** Non-canonical stack address. */ + kCidetExpectXcpt_StackAddrNotCanonical, +#define kCidetExpectXcpt_First_StackFault kCidetExpectXcpt_StackExceededLimit +#define kCidetExpectXcpt_Last_StackFault kCidetExpectXcpt_StackAddrNotCanonical + + /** Misaligned memory operand (and alignment checking is in effect) if AC is + * enabled (executing in ring-3). */ + kCidetExpectXcpt_MisalignedIfAcEnabled, + /** Misaligned 16 byte memory operand resulting in \#AC if ring-3 and + * enable, otherwise \#GP(0). */ + kCidetExpectXcpt_Misaligned16ByteAcEnabledOrGp, +#define kCidetExpectXcpt_First_AlignmentCheckFault kCidetExpectXcpt_MisalignedIfAcEnabled +#define kCidetExpectXcpt_Last_AlignmentCheckFault kCidetExpectXcpt_Misaligned16ByteAcEnabledOrGp + + kCidetExpectXcpt_End +} CIDETEXPECTXCPT; + + +/** + * Buffer configuration. + */ +typedef struct CIDETBUFCFG +{ + /** The name of this buffer configuration. */ + const char *pszName; + /** The buffer flags (CIDETBUF_XXX) */ + uint32_t fFlags; +} CIDETBUFCFG; +/** Pointer to a constant buffer configuration. */ +typedef CIDETBUFCFG const *PCCIDETBUFCFG; + + +/** + * CIDET buffer for code or data. + * + * ASSUMES page aligned buffers. + */ +typedef struct CIDETBUF +{ + /** @name Owned & modified by the front end. + * @{ */ + /** Effective buffer address. */ + uint64_t uEffBufAddr; + /** The segment base address. */ + uint64_t uSegBase; + /** The active segment limit (see also cbSegLimit). UINT64_MAX if flat. */ + uint64_t cbActiveSegLimit; + /** This specifies the selector to use if a non-flat segment limit or special + * segment flags was requested via pfnSetupBuf. UINT32_MAX if any segment is + * selector works. */ + uint32_t uSeg; + /** The off value at the last pfnReinitBuf call. */ + uint16_t offActive; + /** The cb value at the last pfnReinitBuf call. */ + uint16_t cbActive; + /** Prologue (or front fence) size. */ + uint16_t cbPrologue; + /** Epilogue (or tail fence) size. */ + uint16_t cbEpilogue; + /** @} */ + + /** @name Set by the core before pfnReinitBuf call. + * @{ */ + /** Pointer to the buffer config. */ + PCCIDETBUFCFG pCfg; + /** The configuration index. */ + uint32_t idxCfg; + /** The offset into the buffer of the data / code. */ + uint16_t off; + /** The number of bytes of data / code. */ + uint16_t cb; + /** The segment limit relative to the start of the buffer (last byte included + * in count). UINT16_MAX if maximum segment size should be used. */ + uint16_t cbSegLimit; + /** Desired segment base offset. + * This is for checking where the alignment checks are performed. */ + uint8_t offSegBase; + + /** Set if this buffer is actively being used. */ + bool fActive : 1; + /** The operand index (if data), 7 if not active. */ + uint8_t idxOp : 3; + /** Code: Set if the expected exception is supposed to occur on the + * following insturction, not the instruction unter test. */ + bool fXcptAfterInstruction : 1; + /** Set if the instruction will read from the buffer. */ + bool fRead : 1; + /** Set if the instruction will write to the buffer. */ + bool fWrite : 1; + /** The expected exception. */ + CIDETEXPECTXCPT enmExpectXcpt; + /** @} */ +} CIDETBUF; +/** Pointer to a CIDET buffer for code or data. */ +typedef CIDETBUF *PCIDETBUF; + + +/** + * CPU Instruction Decoding & Execution Testing (CIDET) state. + */ +typedef struct CIDETCORE +{ + /** Magic number (CIDETCORE_MAGIC). */ + uint32_t u32Magic; + + /** The target CPU mode / environment. */ + uint8_t bMode; + /** The target ring. */ + uint8_t iRing; + /** Unused padding bytes. */ + uint8_t abPadding1[2]; + + /** Test configuration. */ + uint64_t fTestCfg; + + /** Code buffer configurations to test. + * The first buffer must be a normal buffer that does not cause any problems. */ + PCCIDETBUFCFG paCodeBufConfigs; + /** The number of code buffer configurations to test (pafCodeBufConfigs). */ + uint32_t cCodeBufConfigs; + /** The number of data buffer configurations to test (pafDataBufConfigs). */ + uint32_t cDataBufConfigs; + /** Data buffer configurations to test. + * The first buffer must be a normal buffer that does not cause any problems. */ + PCCIDETBUFCFG paDataBufConfigs; + + /** The instruction currently under testing. */ + PCCIDETINSTR pCurInstr; + + /** Primary data buffer. */ + CIDETBUF DataBuf; + /** Secondary data buffer. */ + CIDETBUF DataBuf2; + + /** Handle to the random number source. */ + RTRAND hRand; + + /** + * Re-initializes one of the data buffers. + * + * @returns true on succes, false if the request cannot be satisfied. + * @param pThis The core state. + * @param pBuf Pointer to the buffer structure. + */ + DECLCALLBACKMEMBER(bool, pfnReInitDataBuf)(struct CIDETCORE *pThis, PCIDETBUF pBuf); + + /** + * Copies bytes into the data buffer and sets it up for execution. + * + * @returns true on succes, false if the request cannot be satisfied. + * @param pThis The core state. + * @param pBuf Pointer to the buffer structure. + * @param pvSrc The source bytes (size and destination offset + * given in pfnReinitBuf call). + */ + DECLCALLBACKMEMBER(bool, pfnSetupDataBuf)(struct CIDETCORE *pThis, PCIDETBUF pBuf, void const *pvSrc); + + /** + * Compares buffer content after test execution. + * + * This also checks any fill bytes in the buffer that the front end may + * have put up. The front end will double buffer the content of supposedly + * inaccessible pages as well as non-existing pages to simplify things for + * the core code. + * + * @returns true if equal, false if not. + * @param pThis The core state. + * @param pBuf Pointer to the buffer structure. + * @param pvExpected Pointer to the expected source bytes (size and + * buffer offset given in pfnReinitBuf call). + */ + DECLCALLBACKMEMBER(bool, pfnIsBufEqual)(struct CIDETCORE *pThis, struct CIDETBUF *pBuf, void const *pvExpected); + + /** + * Re-initializes the code buffer. + * + * @returns true on succes, false if the request cannot be satisfied. + * @param pThis The core state. + * @param pBuf Pointer to the CodeBuf member. The off and cb + * members represent what the core wants to + * execute. + */ + DECLCALLBACKMEMBER(bool, pfnReInitCodeBuf)(struct CIDETCORE *pThis, PCIDETBUF pBuf); + + /** + * Emit code into the code buffer, making everything ready for pfnExecute. + * + * @returns VBox status code. + * @param pThis Pointer to the core structure. + * @param pBuf Pointer to the CodeBuf member. + * @param pvInstr Pointer to the encoded instruction bytes. + */ + DECLCALLBACKMEMBER(bool, pfnSetupCodeBuf)(struct CIDETCORE *pThis, PCIDETBUF pBuf, void const *pvInstr); + + /** + * Executes the code indicated by InCtx, returning the result in ActualCtx. + * + * @returns true if execute, false if skipped. + * @param pThis Pointer to the core structure. + */ + DECLCALLBACKMEMBER(bool, pfnExecute)(struct CIDETCORE *pThis); + + /** + * Report a test failure. + * + * @param pThis Pointer to the core structure. + * @param pszFormat Format string containing failure details. + * @param va Arguments referenced in @a pszFormat. + */ + DECLCALLBACKMEMBER(void, pfnFailure)(struct CIDETCORE *pThis, const char *pszFormat, va_list va); + + /** Array of indexes for use by FNCIDETSETUPINOUT. + * Reset when changing instruction or switching between valid and invalid + * inputs. */ + uint32_t aiInOut[4]; + + /** @name Copyied and extracted instruction information. + * @{ */ + /** The flags (CIDET_OF_XXX) for the MODRM.REG operand, 0 if not applicable. */ + uint32_t fMrmRegOp; + /** The flags (CIDET_OF_XXX) for the MODRM.RM operand, 0 if not applicable. */ + uint32_t fMrmRmOp; + /** Instruction flags (CIDETINSTR::fFlags). */ + uint64_t fInstrFlags; + /** Number of operands (CIDETINSTR::cOperands). */ + uint8_t cOperands; + /** Number of memory operands (set by CidetCoreSetupFirstMemoryOperandConfig). */ + uint8_t cMemoryOperands : 3; + /** Set if we're working on a MOD R/M byte. */ + bool fUsesModRm : 1; + /** The index of the MODRM.REG operand, 7 if not applicable. */ + uint8_t idxMrmRegOp : 3; + /** The index of the MODRM.RM operand, 7 if not applicable. */ + uint8_t idxMrmRmOp : 3; + /** Set if the SIB byte uses VEX registers for indexing. */ + bool fUsesVexIndexRegs : 1; + /** @} */ + + /** @name Basic encoding knobs, wheels and indicators. + * @{ */ + /** Set if we're working on a SIB byte. */ + bool fSib : 1; + /** Required segment prefix (X86_SREG_XXX), X86_SREG_COUNT if not. */ + uint8_t uSegPrf : 3; + /** The address size prefix. */ + bool fAddrSizePrf : 1; + /** The operand size prefix. */ + bool fOpSizePrf : 1; + /** The REX.W prefix value. */ + bool fRexW : 1; + /** The REX.R prefix value. */ + bool fRexR : 1; + /** The REX.X prefix value. */ + bool fRexX : 1; + /** The REX.B prefix value. */ + bool fRexB : 1; + /** Set if a REX prefix is required with or without flags (for byte regs). */ + bool fRex : 1; + /** Use VEX encoding. */ + bool fVex : 1; + /** Use EVEX encoding. */ + bool fEvex : 1; + /** Indicator: Effective addressing mode in bytes (2, 4, 8). */ + uint8_t cbAddrMode : 4; + /** Indicator: Set if there is an operand accessing memory. */ + bool fHasMemoryOperand : 1; + /** Indicator: Set if a register is used in two or more operands, and one of + * them being for addressing. */ + bool fHasRegCollisionMem : 1; + /** Indicator: Helper indicator for tracking SIB.BASE collision. */ + bool fHasRegCollisionMemBase : 1; + /** Indicator: Helper indicator for tracking SIB.INDEX collision. */ + bool fHasRegCollisionMemIndex : 1; + /** Indicator: Set if a register is used directly in more than one operand. */ + bool fHasRegCollisionDirect : 1; + + /** Indicator: Set if MODRM.REG is the stack register. */ + bool fHasStackRegInMrmReg : 1; + /** Indicator: Set if MODRM.RM or SIB.BASE is the stack register. */ + bool fHasStackRegInMrmRmBase: 1; + + /** Indicator: High byte-register specified by MODRM.REG. */ + bool fHasHighByteRegInMrmReg : 1; + /** Indicator: High byte-register specified by MODRM.RM. */ + bool fHasHighByteRegInMrmRm : 1; + /** Indicator: Set if REX prefixes are incompatible with the byte-register + * specified by MODRM.REG. */ + bool fNoRexPrefixMrmReg : 1; + /** Indicator: Set if REX prefixes are incompatible with the byte-register + * specified by MODRM.RM. */ + bool fNoRexPrefixMrmRm : 1; + /** Indicator: fNoRexPrefixMrmReg || fNoRexPrefixMrmMr. */ + bool fNoRexPrefix : 1; + /** The MOD R/M byte we're working on (if fUsesModRm is set). */ + uint8_t bModRm; + /** The SIB/VSIB byte we're working on (if fSib is set). */ + uint8_t bSib; + /** @} */ + + /** The effective instruction address. (See InCtx.rip and InCtx.cs for the + * rest of the instruction addressing stuff.) */ + uint64_t uInstrEffAddr; + + /** Operand information, mainly for the FNCIDETSETUPINOUT and similar. */ + struct + { + /** The operand flags copied from (CIDETINSTR::afOperands). */ + uint32_t fFlags; + /** The encoded register number, if register, UINT8_MAX if not. */ + uint8_t iReg; + /** The actual operand size (encoded). */ + uint8_t cb; + /** Set if immediate value. */ + bool fIsImmediate : 1; + /** Set if memory access. */ + bool fIsMem : 1; + /** Set if addressing is relative to RIP. */ + bool fIsRipRelative : 1; + /** Set if it's a high byte register. */ + bool fIsHighByteRegister : 1; + /** Size of the disposition, 0 if none. */ + uint8_t cbMemDisp; + /** Base register, UINT8_MAX if not applicable. */ + uint8_t iMemBaseReg; + /** Index register, UINT8_MAX if not applicable. */ + uint8_t iMemIndexReg; + /** Index register, 1 if not applicable. */ + uint8_t uMemScale; + /** Effective segment register, UINT8_MAX if not memory access. */ + uint8_t iEffSeg; + /** Segment offset if memory access. Undefined if not memory access. */ + uint64_t offSeg; + /** The effective address if memory access. */ + uint64_t uEffAddr; + /** Immediate or displacement value. */ + uint64_t uImmDispValue; + /** Base register value, undefined if irrelevant. */ + uint64_t uMemBaseRegValue; + /** Index register value, undefined if irrelevant. */ + uint64_t uMemIndexRegValue; + /** Points to where the input data for this operand should be placed, + * when possible. In the fIsMem = true case, it either points directly + * to the input buffer or to a temporary one. While in the other case, + * it'll point into InCtx when possible. */ + RTPTRUNION In; + /** Points to where the expected output data for this operand should be + * stored, when possible. In the fIsMem = false case, it'll point into + * ExpectedCtx when possible. */ + RTPTRUNION Expected; + /** Pointer to the data buffer for this operand. */ + PCIDETBUF pDataBuf; + } aOperands[4]; + + /** Buffer where we assemble the instruction. */ + uint8_t abInstr[45]; + /** The size of the instruction in abInstr. */ + uint8_t cbInstr; + /** Offset of the instruction into the buffer. */ + uint16_t offInstr; + /** Current code buffer. */ + CIDETBUF CodeBuf; + + /** The input context. Initalized by driver and FNCIDETSETUPINOUT. */ + CIDETCPUCTX InCtx; + /** The expected output context. */ + CIDETCPUCTX ExpectedCtx; + /** The actual output context. */ + CIDETCPUCTX ActualCtx; + /** Template input context, initialized when setting the mode. */ + CIDETCPUCTX InTemplateCtx; + + /** Input and expected output temporary memory buffers. */ + uint8_t abBuf[0x2000]; + + + /** Number of skipped tests because of pfnSetupInOut failures. */ + uint32_t cSkippedSetupInOut; + /** Number of skipped tests because of pfnReInitDataBuf failures. */ + uint32_t cSkippedReInitDataBuf; + /** Number of skipped tests because of pfnSetupDataBuf failures. */ + uint32_t cSkippedSetupDataBuf; + /** Number of skipped tests because RIP relative addressing constraints. */ + uint32_t cSkippedDataBufWrtRip; + /** Number of skipped tests because of assemble failures. */ + uint32_t cSkippedAssemble; + /** Number of skipped tests because of pfnReInitCodeBuf failures. */ + uint32_t cSkippedReInitCodeBuf; + /** Number of skipped tests because of pfnSetupCodeBuf failures. */ + uint32_t cSkippedSetupCodeBuf; + /** Number of skipped tests because the base and index registers are the same + * one and there was a remainder when trying to point to the data buffer. */ + uint32_t cSkippedSameBaseIndexRemainder; + /** Number of skipped tests because index-only addressing left a remainder. */ + uint32_t cSkippedOnlyIndexRemainder; + /** Number of skipped tests because of direct addressing overflowed. */ + uint32_t cSkippedDirectAddressingOverflow; + + +} CIDETCORE; +/** Pointer to the CIDET core state. */ +typedef CIDETCORE *PCIDETCORE; + +/** Magic number for CIDETCORE (Lee Konitz). */ +#define CIDETCORE_MAGIC UINT32_C(0x19271013) + + +int CidetCoreInit(PCIDETCORE pThis, RTRAND hRand); +void CidetCoreDelete(PCIDETCORE pThis); +int CidetCoreSetTargetMode(PCIDETCORE pThis, uint8_t bMode); +uint32_t CidetCoreGetOperandSize(PCIDETCORE pThis, uint8_t iOp); +bool CidetCoreTestInstruction(PCIDETCORE pThis, PCCIDETINSTR pInstr); + + +extern const CIDETINSTR g_aCidetInstructions1[]; +extern const uint32_t g_cCidetInstructions1; + +#endif /* !VBOX_INCLUDED_SRC_cpu_cidet_h */ + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet.mac b/src/VBox/ValidationKit/utils/cpu/cidet.mac new file mode 100644 index 00000000..1324c75b --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet.mac @@ -0,0 +1,65 @@ +; $Id: cidet.mac $ ; +;; @file +; CPU Instruction Decoding & Execution Tests - Assembly Header. +; + +; +; Copyright (C) 2014-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; + + +%ifndef ___cidet_mac___ +%define ___cidet_mac___ + +struc CIDETCPUCTX + .rip resq 1 + .rfl resq 1 + .aGRegs resq 16 + .aSRegs resw 6 + +%ifndef CIDET_REDUCED_CTX + .tr resw 1 + .ldtr resw 1 + .cr0 resq 1 +%else + .au16Padding resw 2 +%endif + .cr2 resq 1 +%ifndef CIDET_REDUCED_CTX + .cr3 resq 1 + .cr4 resq 1 + .cr8 resq 1 + .dr0 resq 1 + .dr1 resq 1 + .dr2 resq 1 + .dr3 resq 1 + .dr6 resq 1 + .dr7 resq 1 +%endif + + .uErr resq 1 + .uXcpt resd 1 + + .fIgnoredRFlags resd 1 + .fTrickyStack resb 1 +endstruc + +%endif + diff --git a/src/VBox/ValidationKit/utils/cpu/cpu-alloc-all-mem.cpp b/src/VBox/ValidationKit/utils/cpu/cpu-alloc-all-mem.cpp new file mode 100644 index 00000000..644d2dfb --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cpu-alloc-all-mem.cpp @@ -0,0 +1,213 @@ +/* $Id: cpu-alloc-all-mem.cpp $ */ +/** @file + * Allocate all memory we can get and then quit. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/test.h> + +#include <iprt/asm.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/time.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct TSTALLOC +{ + /** The page sequence number. */ + size_t iPageSeq; + /** The allocation sequence number. */ + size_t iAllocSeq; + /** The allocation size. */ + size_t cb; + /** Pointer to the ourselves (paranoid). */ + void *pv; + /** Linked list node. */ + RTLISTNODE Node; + +} TSTALLOC; +typedef TSTALLOC *PTSTALLOC; + + +static bool checkList(PRTLISTNODE pHead) +{ + size_t iPageSeq = 0; + size_t iAllocSeq = 0; + PTSTALLOC pCur; + RTListForEach(pHead, pCur, TSTALLOC, Node) + { + RTTESTI_CHECK_RET(pCur->iAllocSeq == iAllocSeq, false); + RTTESTI_CHECK_RET(pCur->pv == pCur, false); + + size_t const *pu = (size_t const *)pCur; + size_t const *puEnd = pu + pCur->cb / sizeof(size_t); + while (pu != puEnd) + { + RTTESTI_CHECK_RET(*pu == iPageSeq, false); + iPageSeq++; + pu += PAGE_SIZE / sizeof(size_t); + } + iAllocSeq++; + } + return true; +} + + +static void doTest(RTTEST hTest) +{ + RTTestSub(hTest, "Allocate all memory"); + + RTLISTANCHOR AllocHead; + PTSTALLOC pCur; + uint64_t cNsElapsed = 0; + size_t cbPrint = 0; + uint64_t uPrintTS = 0; + size_t cbTotal = 0; +#if ARCH_BITS == 64 + size_t const cbOneStart = 64 * _1M; + size_t const cbOneMin = 4 * _1M; +#else + size_t const cbOneStart = 16 * _1M; + size_t const cbOneMin = 4 * _1M; +#endif + size_t cbOne = cbOneStart; + size_t cAllocs = 0; + uint32_t iPageSeq = 0; + RTListInit(&AllocHead); + + for (;;) + { + /* + * Allocate a chunk and make sure all the pages are there. + */ + uint64_t const uStartTS = RTTimeNanoTS(); + pCur = (PTSTALLOC)RTMemPageAlloc(cbOne); + if (pCur) + { + size_t *pu = (size_t *)pCur; + size_t *puEnd = pu + cbOne / sizeof(size_t); + while (pu != puEnd) + { + *pu = iPageSeq++; + pu += PAGE_SIZE / sizeof(size_t); + } + uint64_t const uEndTS = RTTimeNanoTS(); + uint64_t const cNsThis = uEndTS - uStartTS; + + /* + * Update the statistics. + */ + cNsElapsed += cNsThis; + cbTotal += cbOne; + cAllocs++; + + /* + * Link the allocation. + */ + pCur->iAllocSeq = cAllocs - 1; + pCur->pv = pCur; + pCur->cb = cbOne; + RTListAppend(&AllocHead, &pCur->Node); + + /* + * Print progress info? + */ + if ( uEndTS - uPrintTS >= RT_NS_1SEC_64*10 +#if ARCH_BITS == 64 + || cbTotal - cbPrint >= _4G +#else + || cbTotal - cbPrint >= _2G +#endif + ) + { + cbPrint = cbTotal; + uPrintTS = uEndTS; + + uint32_t cMBPerSec = (uint32_t)(cbTotal / ((double)cNsElapsed / RT_NS_1SEC) / _1M); + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%'zu bytes in %'llu ns - %'u MB/s\n", + cbTotal, cNsElapsed, cMBPerSec); + RTTESTI_CHECK_RETV(checkList(&AllocHead)); + } + } + else + { + /* + * Try again with a smaller request. + */ + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Failed to allocate %'zu bytes (after %'zu bytes)\n", cbOne, cbTotal); + if (cbOne <= cbOneMin) + break; + cbOne = cbOneMin; + } + } + + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Verifying...\n"); + RTTESTI_CHECK_RETV(checkList(&AllocHead)); + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "... detected no corruption.\n"); + + /* + * Free up some memory before displaying the results. + */ + size_t i = 0; + PTSTALLOC pPrev; + RTListForEachReverseSafe(&AllocHead, pCur, pPrev, TSTALLOC, Node) + { + RTMemPageFree(pCur->pv, pCur->cb); + if (++i > 32) + break; + } + + RTTestValue(hTest, "amount", cbTotal, RTTESTUNIT_BYTES); + RTTestValue(hTest, "time", cNsElapsed, RTTESTUNIT_NS); + uint32_t cMBPerSec = (uint32_t)(cbTotal / ((double)cNsElapsed / RT_NS_1SEC) / _1M); + RTTestValue(hTest, "speed", cMBPerSec, RTTESTUNIT_MEGABYTES_PER_SEC); + RTTestSubDone(hTest); +} + + +int main(int argc, char **argv) +{ + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate("memallocall", &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + NOREF(argv); + if (argc == 1) + doTest(hTest); + else + RTTestFailed(hTest, "This test takes no arguments!"); + + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/ValidationKit/utils/cpu/cpu-numa.cpp b/src/VBox/ValidationKit/utils/cpu/cpu-numa.cpp new file mode 100644 index 00000000..ed83c34c --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cpu-numa.cpp @@ -0,0 +1,195 @@ +/* $Id: cpu-numa.cpp $ */ +/** @file + * numa - NUMA / memory benchmark. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/test.h> + +#include <iprt/asm.h> +//#if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64) +//# include <iprt/asm-amd64-x86.h> +//#endif +#include <iprt/mem.h> +#include <iprt/mp.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The number of threads to skip when testing. */ +static uint32_t g_cThreadsToSkip = 1; + +/** + * Gets the next online CPU. + * + * @returns Next CPU index or RTCPUSET_MAX_CPUS. + * @param iCurCpu The current CPU (index). + */ +static int getNextCpu(unsigned iCurCpu) +{ + /* Skip to the next chip. */ + iCurCpu = (iCurCpu / g_cThreadsToSkip) * g_cThreadsToSkip; + iCurCpu += g_cThreadsToSkip; + + /* Skip offline cpus. */ + while ( iCurCpu < RTCPUSET_MAX_CPUS + && !RTMpIsCpuOnline(iCurCpu) ) + iCurCpu++; + + /* Make sure we're within bounds (in case of bad input). */ + if (iCurCpu > RTCPUSET_MAX_CPUS) + iCurCpu = RTCPUSET_MAX_CPUS; + return iCurCpu; +} + + +static void doTest(RTTEST hTest) +{ + NOREF(hTest); + uint32_t iAllocCpu = 0; + while (iAllocCpu < RTCPUSET_MAX_CPUS) + { + const uint32_t cbTestSet = _1M * 32; + const uint32_t cIterations = 384; + + /* + * Change CPU and allocate a chunk of memory. + */ + RTTESTI_CHECK_RC_OK_RETV(RTThreadSetAffinityToCpu(RTMpCpuIdFromSetIndex(iAllocCpu))); + + void *pvTest = RTMemPageAlloc(cbTestSet); /* may be leaked, who cares */ + RTTESTI_CHECK_RETV(pvTest != NULL); + memset(pvTest, 0xef, cbTestSet); + + /* + * Do the tests. + */ + uint32_t iAccessCpu = 0; + while (iAccessCpu < RTCPUSET_MAX_CPUS) + { + RTTESTI_CHECK_RC_OK_RETV(RTThreadSetAffinityToCpu(RTMpCpuIdFromSetIndex(iAccessCpu))); + + /* + * The write test. + */ + RTTimeNanoTS(); RTThreadYield(); + uint64_t u64StartTS = RTTimeNanoTS(); + for (uint32_t i = 0; i < cIterations; i++) + { + ASMCompilerBarrier(); /* paranoia */ + memset(pvTest, i, cbTestSet); + } + uint64_t const cNsElapsedWrite = RTTimeNanoTS() - u64StartTS; + uint64_t cMBPerSec = (uint64_t)( ((uint64_t)cIterations * cbTestSet) /* bytes */ + / ((long double)cNsElapsedWrite / RT_NS_1SEC_64) /* seconds */ + / _1M /* MB */ ); + RTTestIValueF(cMBPerSec, RTTESTUNIT_MEGABYTES_PER_SEC, "cpu%02u-mem%02u-write", iAllocCpu, iAccessCpu); + + /* + * The read test. + */ + memset(pvTest, 0, cbTestSet); + RTTimeNanoTS(); RTThreadYield(); + u64StartTS = RTTimeNanoTS(); + for (uint32_t i = 0; i < cIterations; i++) + { +#if 1 + size_t register u = 0; + size_t volatile *puCur = (size_t volatile *)pvTest; + size_t volatile *puEnd = puCur + cbTestSet / sizeof(size_t); + while (puCur != puEnd) + u += *puCur++; +#else + ASMCompilerBarrier(); /* paranoia */ + void *pvFound = memchr(pvTest, (i & 127) + 1, cbTestSet); + RTTESTI_CHECK(pvFound == NULL); +#endif + } + uint64_t const cNsElapsedRead = RTTimeNanoTS() - u64StartTS; + cMBPerSec = (uint64_t)( ((uint64_t)cIterations * cbTestSet) /* bytes */ + / ((long double)cNsElapsedRead / RT_NS_1SEC_64) /* seconds */ + / _1M /* MB */ ); + RTTestIValueF(cMBPerSec, RTTESTUNIT_MEGABYTES_PER_SEC, "cpu%02u-mem%02u-read", iAllocCpu, iAccessCpu); + + /* + * The read/write test. + */ + RTTimeNanoTS(); RTThreadYield(); + u64StartTS = RTTimeNanoTS(); + for (uint32_t i = 0; i < cIterations; i++) + { + ASMCompilerBarrier(); /* paranoia */ + memcpy(pvTest, (uint8_t *)pvTest + cbTestSet / 2, cbTestSet / 2); + } + uint64_t const cNsElapsedRW = RTTimeNanoTS() - u64StartTS; + cMBPerSec = (uint64_t)( ((uint64_t)cIterations * cbTestSet) /* bytes */ + / ((long double)cNsElapsedRW / RT_NS_1SEC_64) /* seconds */ + / _1M /* MB */ ); + RTTestIValueF(cMBPerSec, RTTESTUNIT_MEGABYTES_PER_SEC, "cpu%02u-mem%02u-read-write", iAllocCpu, iAccessCpu); + + /* + * Total time. + */ + RTTestIValueF(cNsElapsedRead + cNsElapsedWrite + cNsElapsedRW, RTTESTUNIT_NS, + "cpu%02u-mem%02u-time", iAllocCpu, iAccessCpu); + + /* advance */ + iAccessCpu = getNextCpu(iAccessCpu); + } + + /* + * Clean up and advance to the next CPU. + */ + RTMemPageFree(pvTest, cbTestSet); + iAllocCpu = getNextCpu(iAllocCpu); + } +} + + +int main(int argc, char **argv) +{ + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate("numa-1", &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + +#if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64) + /** @todo figure basic topology. */ +#endif + if (argc == 2) + g_cThreadsToSkip = RTStrToUInt8(argv[1]); + + doTest(hTest); + + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/ValidationKit/utils/cpu/exceptionsR3-asm.asm b/src/VBox/ValidationKit/utils/cpu/exceptionsR3-asm.asm new file mode 100644 index 00000000..7c698d72 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/exceptionsR3-asm.asm @@ -0,0 +1,150 @@ +; $Id: exceptionsR3-asm.asm $ +;; @file +; exceptionsR3-asm.asm - assembly helpers. +; + +; +; Copyright (C) 2009-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" + + +;******************************************************************************* +;* Defined Constants And Macros * +;******************************************************************************* +%ifdef RT_ARCH_AMD64 + %define TST_XCPT_MAGIC 0123456789abcdef0h +%else + %define TST_XCPT_MAGIC 012345678h +%endif + +%macro tstXcptAsmProlog 0 + push xBP + push xDI + push xSI + push xBX + %ifdef RT_ARCH_X86 + push gs + push fs + push es + push ds + %endif + %ifdef RT_ARCH_AMD64 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + %endif + + mov xAX, TST_XCPT_MAGIC + mov xBX, xAX + mov xCX, xAX + mov xDX, xAX + mov xDI, xAX + mov xSI, xAX + mov xBP, xAX + %ifdef RT_ARCH_AMD64 + mov r8, xAX + mov r9, xAX + mov r10, xAX + mov r11, xAX + mov r12, xAX + mov r13, xAX + mov r14, xAX + mov r15, xAX + %endif +%endmacro + +%macro tstXcptAsmEpilog 0 + %ifdef RT_ARCH_AMD64 + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + %endif + %ifdef RT_ARCH_X86 + pop ds + pop es + pop fs + pop gs + %endif + pop xBX + pop xSI + pop xDI + pop xBP +%endmacro + + +BEGINCODE + +;; +BEGINPROC tstXcptAsmNullPtrRead +; tstXcptAsmProlog + xor eax, eax +GLOBALNAME tstXcptAsmNullPtrRead_PC + mov al, [xAX] +; tstXcptAsmEpilog + ret +ENDPROC tstXcptAsmNullPtrRead + + +;; +BEGINPROC tstXcptAsmNullPtrWrite + tstXcptAsmProlog + xor eax, eax +GLOBALNAME tstXcptAsmNullPtrWrite_PC + mov [xAX], al + tstXcptAsmEpilog + ret +ENDPROC tstXcptAsmNullPtrWrite + + +;; +BEGINPROC tstXcptAsmSysCall + tstXcptAsmProlog +GLOBALNAME tstXcptAsmSysCall_PC + syscall + tstXcptAsmEpilog + ret +ENDPROC tstXcptAsmSysCall + + +;; +BEGINPROC tstXcptAsmSysEnter + tstXcptAsmProlog +GLOBALNAME tstXcptAsmSysEnter_PC +%ifdef RT_ARCH_AMD64 + db 00fh, 034h ; test this on 64-bit, yasm complains... +%else + sysenter +%endif + tstXcptAsmEpilog + ret +ENDPROC tstXcptAsmSysEnter + diff --git a/src/VBox/ValidationKit/utils/cpu/exceptionsR3.cpp b/src/VBox/ValidationKit/utils/cpu/exceptionsR3.cpp new file mode 100644 index 00000000..4cd4f36c --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/exceptionsR3.cpp @@ -0,0 +1,262 @@ +/* $Id: exceptionsR3.cpp $ */ +/** @file + * exceptionsR3 - Tests various ring-3 CPU exceptions. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#include <iprt/ctype.h> +#include <iprt/getopt.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> +#include <iprt/x86.h> + +#include <setjmp.h> + +#ifndef RT_OS_WINDOWS +# define USE_SIGNALS +# include <signal.h> +# include <stdlib.h> +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Executes a simple test. */ +#define TST_XCPT(Trapper, iTrap, uErr) \ + do \ + { \ + RTTestISub(#Trapper); \ + tstXcptReset(); \ + if (!setjmp(g_JmpBuf)) \ + { \ + tstXcptAsm##Trapper(); \ + RTTestIFailed("%s didn't trap (line no %u)", #Trapper, __LINE__); \ + } \ + else if ( (iTrap) != tstXcptCurTrap() \ + || (uErr) != tstXcptCurErr() ) \ + RTTestIFailed("%s trapped with %#x/%#x, expected %#x/%#x (line no %u)", \ + #Trapper, tstXcptCurTrap(), tstXcptCurErr(), (iTrap), (uErr), __LINE__); \ + else \ + RTTestISubDone(); \ + } while (0) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Where to longjmp to when getting a signal/exception. */ +jmp_buf g_JmpBuf; +#ifdef USE_SIGNALS +/** Pending signal. + * -1 if no signal is pending. */ +int32_t volatile g_iSignal; +/** Pending signal info. */ +siginfo_t volatile g_SigInfo; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +DECLASM(void) tstXcptAsmNullPtrRead(void); +DECLASM(void) tstXcptAsmNullPtrWrite(void); +DECLASM(void) tstXcptAsmSysEnter(void); +DECLASM(void) tstXcptAsmSysCall(void); + + + +#ifdef USE_SIGNALS +/** + * Generic signal handler. + */ +static void tstXcptSigHandler(int iSignal, siginfo_t *pSigInfo, void *pvCtx) +{ +#if 1 + RTStrmPrintf(g_pStdErr, "signal %d pSigInfo=%p pvCtx=%p", iSignal, pSigInfo, pvCtx); + if (pSigInfo) + RTStrmPrintf(g_pStdErr, " si_addr=%p si_code=%#x sival_ptr=%p sival_int=%d", + pSigInfo->si_addr, pSigInfo->si_code, pSigInfo->si_value.sival_ptr, pSigInfo->si_value.sival_int); + RTStrmPrintf(g_pStdErr, "\n"); +#endif + if (g_iSignal == -1) + { + g_iSignal = iSignal; + if (pSigInfo) + memcpy((void *)&g_SigInfo, pSigInfo, sizeof(g_SigInfo)); + longjmp(g_JmpBuf, 1); + } + else + { + /* we're up the infamous creek... */ + _Exit(2); + } +} + +#elif defined(RT_OS_WINDOWS) +/** @todo */ +//# error "PORTME" + +#else +# error "PORTME" +#endif + + +/** Reset the current exception state and get ready for a new trap. */ +static void tstXcptReset(void) +{ +#ifdef USE_SIGNALS + g_iSignal = -1; + memset((void *)&g_SigInfo, 0, sizeof(g_SigInfo)); +#endif +} + + + +/** Get the current intel trap number. Returns -1 if none. */ +static int tstXcptCurTrap(void) +{ +#ifdef USE_SIGNALS + /** @todo this is just a quick sketch. */ + switch (g_iSignal) + { + case SIGBUS: +# ifdef RT_OS_DARWIN + if (g_SigInfo.si_code == 2 /*KERN_PROTECTION_FAILURE*/) + return X86_XCPT_PF; +# endif + return X86_XCPT_GP; + + case SIGSEGV: + return X86_XCPT_GP; + } +#endif + return -1; +} + + +/** Get the exception error code if applicable. */ +static uint32_t tstXcptCurErr(void) +{ +#ifdef USE_SIGNALS + /** @todo this is just a quick sketch. */ + switch (g_iSignal) + { + case SIGBUS: +# ifdef RT_OS_DARWIN + if (g_SigInfo.si_code == 2 /*KERN_PROTECTION_FAILURE*/) + return 0; +# endif + break; + + case SIGSEGV: + break; + } +#endif + return UINT32_MAX; +} + + +int main(int argc, char **argv) +{ + /* + * Prolog. + */ + RTTEST hTest; + int rc = RTTestInitAndCreate("exceptionsR3", &hTest); + if (rc) + return rc; + + /* + * Parse options. + */ + bool volatile fRawMode = false; + static const RTGETOPTDEF s_aOptions[] = + { + { "--raw-mode", 'r', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTUNION ValUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((rc = RTGetOpt(&GetState, &ValUnion))) + { + switch (rc) + { + case 'r': + fRawMode = true; + break; + + default: + return RTGetOptPrintError(rc, &ValUnion); + } + } + + /* + * Test setup. + */ +#ifdef USE_SIGNALS + struct sigaction Act; + RT_ZERO(Act); + Act.sa_sigaction = tstXcptSigHandler; + Act.sa_flags = SA_SIGINFO; + sigfillset(&Act.sa_mask); + + sigaction(SIGILL, &Act, NULL); + sigaction(SIGTRAP, &Act, NULL); +# ifdef SIGEMT + sigaction(SIGEMT, &Act, NULL); +# endif + sigaction(SIGFPE, &Act, NULL); + sigaction(SIGBUS, &Act, NULL); + sigaction(SIGSEGV, &Act, NULL); + +#else + /** @todo Implement this using structured exception handling on Windows and + * OS/2. */ +#endif + + /* + * The tests. + */ + RTTestBanner(hTest); + TST_XCPT(NullPtrRead, X86_XCPT_PF, 0); + TST_XCPT(NullPtrWrite, X86_XCPT_PF, 0); + if (fRawMode) + { + TST_XCPT(SysEnter, X86_XCPT_GP, 0); + TST_XCPT(SysCall, X86_XCPT_UD, 0); + } + + /* + * Epilog. + */ + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/ValidationKit/utils/cpu/rdtsc-asm.asm b/src/VBox/ValidationKit/utils/cpu/rdtsc-asm.asm new file mode 100644 index 00000000..b7ccdc7f --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/rdtsc-asm.asm @@ -0,0 +1,152 @@ +; $Id: rdtsc-asm.asm $ +;; @file +; RDTSC test, assembly code +; + +; +; Copyright (C) 2009-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +BEGINDATA +;; +; Where DoTscReads() returns the rdtsc values. +; +; @note The results are 32-bit value pairs in x86 mode and 64-bit pairs in +; AMD64 mode. +GLOBALNAME g_aRdTscResults +%ifdef RT_ARCH_AMD64 + dq 0, 0 + dq 0, 0 ; first value stored + dq 0, 0 + dq 0, 0 + dq 0, 0 + dq 0, 0 + dq 0, 0 +%else + dq 0, 0 + dd 0, 0 ; first value stored + dd 0, 0 + dd 0, 0 +%endif + + +BEGINCODE + +;; Takes no arguments, returns number of values read into g_aRdTscResults. +BEGINPROC DoTscReads + push xBP + mov xBP, xSP +%ifdef RT_ARCH_AMD64 + mov rax, 0feedfacecafebabeh + mov rdx, 0cafebabefeedfaceh + mov r8, 0deadbeef0deadbeefh + mov r9, 0deadbeef0deadbeefh + mov r10, 0deadbeef0deadbeefh + mov r11, 0deadbeef0deadbeefh + push rbx + push r12 + push r13 + push r14 + push r15 + + ; Read 6x TSC into registers. + rdtsc + mov r8, rax + mov r9, rdx + rdtsc + mov r10, rax + mov r11, rdx + rdtsc + mov r12, rax + mov r13, rdx + rdtsc + mov r14, rax + mov r15, rdx + rdtsc + mov rbx, rax + mov rcx, rdx + rdtsc + + ; Store the values (64-bit). + mov [NAME(g_aRdTscResults) + 10h xWrtRIP], r8 + mov [NAME(g_aRdTscResults) + 18h xWrtRIP], r9 + mov [NAME(g_aRdTscResults) + 20h xWrtRIP], r10 + mov [NAME(g_aRdTscResults) + 28h xWrtRIP], r11 + mov [NAME(g_aRdTscResults) + 30h xWrtRIP], r12 + mov [NAME(g_aRdTscResults) + 38h xWrtRIP], r13 + mov [NAME(g_aRdTscResults) + 40h xWrtRIP], r14 + mov [NAME(g_aRdTscResults) + 48h xWrtRIP], r15 + mov [NAME(g_aRdTscResults) + 50h xWrtRIP], rbx + mov [NAME(g_aRdTscResults) + 58h xWrtRIP], rcx + mov [NAME(g_aRdTscResults) + 60h xWrtRIP], rax + mov [NAME(g_aRdTscResults) + 68h xWrtRIP], rdx + + pop r15 + pop r14 + pop r13 + pop r12 + pop rbx + + mov eax, 6 +%else + mov eax, 0feedfaceh + mov edx, 0cafebabeh + push esi + push edi + push ebx + + ; Read 3x TSC into registers. + rdtsc + mov ebx, eax + mov ecx, edx + rdtsc + mov esi, eax + mov edi, edx + rdtsc + + ; Store values. + mov [NAME(g_aRdTscResults) + 08h], ebx + mov [NAME(g_aRdTscResults) + 0ch], ecx + mov [NAME(g_aRdTscResults) + 10h], esi + mov [NAME(g_aRdTscResults) + 14h], edi + mov [NAME(g_aRdTscResults) + 18h], eax + mov [NAME(g_aRdTscResults) + 1ch], edx + + pop ebx + pop edi + pop esi + + mov eax, 3 +%endif + leave + ret +ENDPROC DoTscReads + diff --git a/src/VBox/ValidationKit/utils/cpu/rdtsc.cpp b/src/VBox/ValidationKit/utils/cpu/rdtsc.cpp new file mode 100644 index 00000000..80095096 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/rdtsc.cpp @@ -0,0 +1,291 @@ +/* $Id: rdtsc.cpp $ */ +/** @file + * rdtsc - Test if three consecutive rdtsc instructions return different values. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/types.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct RDTSCRESULT +{ + RTCCUINTREG uLow, uHigh; +} RDTSCRESULT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +extern "C" RDTSCRESULT g_aRdTscResults[]; /* rdtsc-asm.asm */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** + * Does 3 (32-bit) or 6 (64-bit) fast TSC reads and stores the result + * in g_aRdTscResults, starting with the 2nd entry. + * + * Starting the result storing at g_aRdTscResults[1] make it easy to do the + * comparisons in a loop. + * + * @returns Number of results read into g_aRdTscResults[1] and onwards. + */ +DECLASM(uint32_t) DoTscReads(void); + + + + +int main(int argc, char **argv) +{ + + /* + * Tunables. + */ + uint64_t offJumpThreshold = _4G * 2; + unsigned cMaxLoops = 10000000; + unsigned cStatusEvery = 2000000; + unsigned cMinSeconds = 0; + + for (int i = 1; i < argc; i++) + { + const char *psz = argv[i]; + if (*psz == '-') + { + psz++; + char chOpt; + while ((chOpt = *psz++) != '\0') + { + /* Option value. */ + const char *pszValue = NULL; + unsigned long uValue = 0; + switch (chOpt) + { + case 'l': + case 's': + case 'm': + if (*psz == '\0') + { + if (i + 1 >= argc) + { + printf("syntax error: The %c option requires a value\n", chOpt); + return RTEXITCODE_SYNTAX; + } + pszValue = argv[++i]; + } + else + pszValue = psz + (*psz == ':' || *psz == '='); + switch (chOpt) + { + case 'l': + case 's': + case 'm': + { + char *pszNext = NULL; + uValue = strtoul(pszValue, &pszNext, 0); + if (pszNext && *pszNext != '\0') + { + if (*pszNext == 'M'&& pszNext[1] == '\0') + uValue *= _1M; + else if (*pszNext == 'K' && pszNext[1] == '\0') + uValue *= _1K; + else if (*pszNext == 'G' && pszNext[1] == '\0') + uValue *= _1G; + else + { + printf("syntax error: Bad value format for option %c: %s\n", chOpt, pszValue); + return RTEXITCODE_SYNTAX; + } + } + break; + } + } + break; + } + + /* handle the option. */ + switch (chOpt) + { + case 'l': + cMaxLoops = uValue; + break; + + case 'm': + cMinSeconds = uValue; + break; + + case 's': + cStatusEvery = uValue; + break; + + case 'h': + case '?': + printf("usage: rdtsc [-l <loops>] [-s <loops-between-status>]\n" + " [-m <minimum-seconds-to-run>]\n"); + return RTEXITCODE_SUCCESS; + + default: + printf("syntax error: Unknown option %c (argument %d)\n", chOpt, i); + return RTEXITCODE_SYNTAX; + } + } + } + else + { + printf("synatx error: argument %d (%s): not an option\n", i, psz); + return RTEXITCODE_SYNTAX; + } + } + + /* + * Do the job. + */ + time_t uSecStart; + time(&uSecStart); + unsigned cOuterLoops = 0; + unsigned cLoopsToNextStatus = cStatusEvery; + unsigned cRdTscInstructions = 0; + unsigned cBackwards = 0; + unsigned cSame = 0; + unsigned cBadValues = 0; + unsigned cJumps = 0; + uint64_t offMaxJump = 0; + uint64_t offMinIncr = UINT64_MAX; + uint64_t offMaxIncr = 0; + + g_aRdTscResults[0] = g_aRdTscResults[DoTscReads() - 1]; + + for (;;) + { + for (unsigned iLoop = 0; iLoop < cMaxLoops; iLoop++) + { + uint32_t const cResults = DoTscReads(); + cRdTscInstructions += cResults; + + for (uint32_t i = 0; i < cResults; i++) + { + uint64_t uPrev = RT_MAKE_U64((uint32_t)g_aRdTscResults[i ].uLow, (uint32_t)g_aRdTscResults[i ].uHigh); + uint64_t uCur = RT_MAKE_U64((uint32_t)g_aRdTscResults[i + 1].uLow, (uint32_t)g_aRdTscResults[i + 1].uHigh); + if (RT_LIKELY(uCur != uPrev)) + { + int64_t offDelta = uCur - uPrev; + if (RT_LIKELY(offDelta >= 0)) + { + if (RT_LIKELY((uint64_t)offDelta < offJumpThreshold)) + { + if ((uint64_t)offDelta < offMinIncr) + offMinIncr = offDelta; + if ((uint64_t)offDelta > offMaxIncr && i != 0) + offMaxIncr = offDelta; + } + else + { + cJumps++; + if ((uint64_t)offDelta > offMaxJump) + offMaxJump = offDelta; + printf("%u/%u: Jump: %#010x`%08x -> %#010x`%08x\n", cOuterLoops, iLoop, + (unsigned)g_aRdTscResults[i].uHigh, (unsigned)g_aRdTscResults[i].uLow, + (unsigned)g_aRdTscResults[i + 1].uHigh, (unsigned)g_aRdTscResults[i + 1].uLow); + } + } + else + { + cBackwards++; + printf("%u/%u: Back: %#010x`%08x -> %#010x`%08x\n", cOuterLoops, iLoop, + (unsigned)g_aRdTscResults[i].uHigh, (unsigned)g_aRdTscResults[i].uLow, + (unsigned)g_aRdTscResults[i + 1].uHigh, (unsigned)g_aRdTscResults[i + 1].uLow); + } + } + else + { + cSame++; + printf("%u/%u: Same: %#010x`%08x -> %#010x`%08x\n", cOuterLoops, iLoop, + (unsigned)g_aRdTscResults[i].uHigh, (unsigned)g_aRdTscResults[i].uLow, + (unsigned)g_aRdTscResults[i + 1].uHigh, (unsigned)g_aRdTscResults[i + 1].uLow); + } +#if ARCH_BITS == 64 + if ((g_aRdTscResults[i + 1].uLow >> 32) || (g_aRdTscResults[i + 1].uHigh >> 32)) + cBadValues++; +#endif + } + + /* Copy the last value for the next iteration. */ + g_aRdTscResults[0] = g_aRdTscResults[cResults]; + + /* Display status. */ + if (RT_LIKELY(--cLoopsToNextStatus > 0)) + { /* likely */ } + else + { + cLoopsToNextStatus = cStatusEvery; + printf("%u/%u: %#010x`%08x\n", cOuterLoops, iLoop, + (unsigned)g_aRdTscResults[cResults].uHigh, (unsigned)g_aRdTscResults[cResults].uLow); + } + } + + /* + * Check minimum number of seconds. + */ + cOuterLoops++; + if (!cMinSeconds) + break; + time_t uSecNow; + if ( time(&uSecNow) == (time_t)-1 + || uSecNow == (time_t)-1 + || uSecStart == (time_t)-1 + || uSecNow - uSecStart >= (time_t)cMinSeconds) + break; + } + + /* + * Summary. + */ + if (cBackwards == 0 && cSame == 0 && cJumps == 0 && cBadValues == 0) + { + printf("rdtsc: Success (%u RDTSC over %u*%u loops, deltas: %#x`%08x..%#x`%08x)\n", + cRdTscInstructions, cOuterLoops, cMaxLoops, + (unsigned)(offMinIncr >> 32), (unsigned)offMinIncr, (unsigned)(offMaxIncr >> 32), (unsigned)offMaxIncr); + return RTEXITCODE_SUCCESS; + } + printf("RDTSC instructions: %u\n", cRdTscInstructions); + printf("Loops: %u * %u => %u\n", cMaxLoops, cOuterLoops, cOuterLoops * cMaxLoops); + printf("Backwards: %u\n", cBackwards); + printf("Jumps: %u\n", cJumps); + printf("Max jumps: %#010x`%08x\n", (unsigned)(offMaxJump >> 32), (unsigned)offMaxJump); + printf("Same value: %u\n", cSame); + printf("Bad values: %u\n", cBadValues); + printf("Min increment: %#010x`%08x\n", (unsigned)(offMinIncr >> 32), (unsigned)offMinIncr); + printf("Max increment: %#010x`%08x\n", (unsigned)(offMaxIncr >> 32), (unsigned)offMaxIncr); + return RTEXITCODE_FAILURE; +} + diff --git a/src/VBox/ValidationKit/utils/cpu/xmmsaving-asm.asm b/src/VBox/ValidationKit/utils/cpu/xmmsaving-asm.asm new file mode 100644 index 00000000..54cb2c05 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/xmmsaving-asm.asm @@ -0,0 +1,152 @@ +; $Id: xmmsaving-asm.asm $ +;; @file +; xmmsaving - assembly helpers. +; + +; +; Copyright (C) 2009-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; + + +%include "iprt/asmdefs.mac" +%include "VBox/vmm/stam.mac" + + +BEGINCODE + + +;; +; DECLASM(int) XmmSavingTestLoadSet(const MYXMMREGSET *pSet, const MYXMMREGSET *pPrevSet, PRTUINT128U pBadVal); +; +; @returns 0 on success, 1-based register number on failure. +; @param pSet The new set. +; @param pPrevSet The previous set. Can be NULL. +; @param pBadVal Where to store the actual register value on failure. +; +BEGINPROC XmmSavingTestLoadSet + push xBP + mov xBP, xSP + sub xSP, 32 ; Space for storing an XMM register (in TEST_REG). + and xSP, ~31 ; Align it. + + ; Unify register/arguments. +%ifdef ASM_CALL64_GCC + mov r8, rdx ; pBadVal + mov xCX, rdi ; pSet + mov xDX, rsi ; pPrevSet +%endif +%ifdef RT_ARCH_X86 + mov xCX, [ebp + 8] ; pSet + mov xDX, [ebp + 12] ; pPrevSet +%endif + + test xDX, xDX + jz near .just_load + + ; Check that the old set is still correct. +%macro TEST_REG 1, + movdqa [xSP], xmm %+ %1 + mov xAX, [xDX + %1 * 8] + cmp [xSP], xAX + jne %%bad + mov xAX, [xDX + %1 * 8 + xCB] + cmp [xSP + xCB], xAX +%ifdef RT_ARCH_X86 + jne %%bad + mov xAX, [xDX + %1 * 8 + xCB*2] + cmp [xSP + xCB*2], xAX + jne %%bad + mov xAX, [xDX + %1 * 8 + xCB*3] + cmp [xSP + xCB*3], xAX +%endif + je %%next +%%bad: + mov eax, %1 + 1 + jmp .return_copy_badval +%%next: +%endmacro + + TEST_REG 0 + TEST_REG 1 + TEST_REG 2 + TEST_REG 3 + TEST_REG 4 + TEST_REG 5 + TEST_REG 6 + TEST_REG 7 +%ifdef RT_ARCH_AMD64 + TEST_REG 8 + TEST_REG 9 + TEST_REG 10 + TEST_REG 11 + TEST_REG 12 + TEST_REG 13 + TEST_REG 14 + TEST_REG 15 +%endif + + ; Load the new state. +.just_load: + movdqu xmm0, [xCX + 0*8] + movdqu xmm1, [xCX + 1*8] + movdqu xmm2, [xCX + 2*8] + movdqu xmm3, [xCX + 3*8] + movdqu xmm4, [xCX + 4*8] + movdqu xmm5, [xCX + 5*8] + movdqu xmm6, [xCX + 6*8] + movdqu xmm7, [xCX + 7*8] +%ifdef RT_ARCH_AMD64 + movdqu xmm8, [xCX + 8*8] + movdqu xmm9, [xCX + 9*8] + movdqu xmm10, [xCX + 10*8] + movdqu xmm11, [xCX + 11*8] + movdqu xmm12, [xCX + 12*8] + movdqu xmm13, [xCX + 13*8] + movdqu xmm14, [xCX + 14*8] + movdqu xmm15, [xCX + 15*8] +%endif + xor eax, eax + jmp .return + +.return_copy_badval: + ; don't touch eax here. +%ifdef RT_ARCH_X86 + mov edx, [ebp + 16] + mov ecx, [esp] + mov [edx ], ecx + mov ecx, [esp + 4] + mov [edx + 4], ecx + mov ecx, [esp + 8] + mov [edx + 8], ecx + mov ecx, [esp + 12] + mov [edx + 12], ecx +%else + mov rdx, [rsp] + mov rcx, [rsp + 8] + mov [r8], rdx + mov [r8 + 8], rcx +%endif + jmp .return + +.return: + leave + ret +ENDPROC XmmSavingTestLoadSet + diff --git a/src/VBox/ValidationKit/utils/cpu/xmmsaving.cpp b/src/VBox/ValidationKit/utils/cpu/xmmsaving.cpp new file mode 100644 index 00000000..ff543556 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/xmmsaving.cpp @@ -0,0 +1,120 @@ +/* $Id: xmmsaving.cpp $ */ +/** @file + * xmmsaving - Test that all XMM register state is handled correctly and + * not corrupted the VMM. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/test.h> +#include <iprt/x86.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct MYXMMREGSET +{ + RTUINT128U aRegs[16]; +} MYXMMREGSET; + + +DECLASM(int) XmmSavingTestLoadSet(const MYXMMREGSET *pSet, const MYXMMREGSET *pPrevSet, PRTUINT128U pBadVal); + + +static void XmmSavingTest(void) +{ + RTTestISub("xmm saving and restoring"); + + /* Create the test sets. */ + static MYXMMREGSET s_aSets[256]; + for (unsigned s = 0; s < RT_ELEMENTS(s_aSets); s++) + { + for (unsigned r = 0; r < RT_ELEMENTS(s_aSets[s].aRegs); r++) + { + unsigned x = (s << 4) | r; + s_aSets[s].aRegs[r].au32[0] = x | UINT32_C(0x12345000); + s_aSets[s].aRegs[r].au32[1] = (x << 8) | UINT32_C(0x88700011); + s_aSets[s].aRegs[r].au32[2] = (x << 16) | UINT32_C(0xe000dcba); + s_aSets[s].aRegs[r].au32[3] = (x << 20) | UINT32_C(0x00087654); + } + } + + /* Do the actual testing. */ + const MYXMMREGSET *pPrev2 = NULL; + const MYXMMREGSET *pPrev = NULL; + for (int i = 0; i < 1000000; i++) + { + if ((i % 50000) == 0) + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "."); + pPrev = pPrev2 = NULL; /* May be trashed by the above call. */ + } + for (unsigned s = 0; s < RT_ELEMENTS(s_aSets); s++) + { + RTUINT128U BadVal; + const MYXMMREGSET *pSet = &s_aSets[s]; + int r = XmmSavingTestLoadSet(pSet, pPrev, &BadVal); + if (r-- != 0) + { + RTTestIFailed("i=%d s=%d r=%d", i, s, r); + RTTestIFailureDetails("XMM%-2d = %08x,%08x,%08x,%08x\n", + r, + BadVal.au32[0], + BadVal.au32[1], + BadVal.au32[2], + BadVal.au32[3]); + RTTestIFailureDetails("Expected %08x,%08x,%08x,%08x\n", + pPrev->aRegs[r].au32[0], + pPrev->aRegs[r].au32[1], + pPrev->aRegs[r].au32[2], + pPrev->aRegs[r].au32[3]); + if (pPrev2) + RTTestIFailureDetails("PrevPrev %08x,%08x,%08x,%08x\n", + pPrev2->aRegs[r].au32[0], + pPrev2->aRegs[r].au32[1], + pPrev2->aRegs[r].au32[2], + pPrev2->aRegs[r].au32[3]); + return; + } + pPrev2 = pPrev; + pPrev = pSet; + } + } + RTTestISubDone(); +} + + +int main() +{ + RTTEST hTest; + int rc = RTTestInitAndCreate("xmmsaving", &hTest); + if (rc) + return rc; + XmmSavingTest(); + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/ValidationKit/utils/dos/DosSleep.c b/src/VBox/ValidationKit/utils/dos/DosSleep.c new file mode 100644 index 00000000..5f5e78d0 --- /dev/null +++ b/src/VBox/ValidationKit/utils/dos/DosSleep.c @@ -0,0 +1,57 @@ +/* $Id: DosSleep.c $ */ +/** @file + * 16-bit DOS sleep program. + * + * Build: wcl -I%WATCOM%\h\win -l=dos -k4096 -fm -W4 DosSleep.c + */ + +/* + * Copyright (C) 2018-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + + + +int main(int argc, char **argv) +{ + int iExit; + + if (argc == 2) + { + int cSeconds = atoi(argv[1]); + sleep(cSeconds); + iExit = 0; + } + else + { + fprintf(stderr, "syntax error: only expected a number of seconds\n"); + iExit = 4; + } + + return iExit; +} + diff --git a/src/VBox/ValidationKit/utils/dos/DosVmOff.asm b/src/VBox/ValidationKit/utils/dos/DosVmOff.asm new file mode 100644 index 00000000..f35cf0ff --- /dev/null +++ b/src/VBox/ValidationKit/utils/dos/DosVmOff.asm @@ -0,0 +1,69 @@ +; $Id: DosVmOff.asm $ +;; @file +; 16-bit DOS COM program that powers off the VM. +; +; Build: yasm -f bin -i../../../../../include/ DosVmOff.asm -o DosVmOff.com +; + +; +; Copyright (C) 2018-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; + + + +%include "VBox/bios.mac" + + org 100h + +segment text +main: +%if 0 + ; Setup stack. + mov ax, stack + mov ss, ax + mov sp, top_of_stack +%endif + + ; Do the shutdown thing. + mov ax, cs + mov ds, ax + + mov bl, 64 + mov dx, VBOX_BIOS_SHUTDOWN_PORT + mov ax, VBOX_BIOS_OLD_SHUTDOWN_PORT +.retry: + mov cx, 8 + mov si, .s_szShutdown + rep outsb + xchg ax, dx ; alternate between the new (VBox) and old (Bochs) ports. + dec bl + jnz .retry + + + ; Probably not a VBox VM, exit the program with errorlevel 1. +.whatever: + mov ax, 04c01h + int 021h + hlt + jmp .whatever + +.s_szShutdown: + db 'Shutdown', 0 + diff --git a/src/VBox/ValidationKit/utils/dos/WinExit.asm b/src/VBox/ValidationKit/utils/dos/WinExit.asm new file mode 100644 index 00000000..b747891f --- /dev/null +++ b/src/VBox/ValidationKit/utils/dos/WinExit.asm @@ -0,0 +1,85 @@ +; $Id: WinExit.asm $ +;; @file +; 16-bit windows program that exits windows. +; +; Build: wcl -I%WATCOM%\h\win -l=windows -k4096 -fm WinExit.asm +; + +; +; Copyright (C) 2018-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; + + + +;.stack 4096 +STACK segment para stack 'STACK' +STACK ends + + +extrn INITTASK:FAR +extrn INITAPP:FAR +extrn EXITWINDOWS:FAR +extrn WAITEVENT:FAR + +_TEXT segment word public 'CODE' +start: + push bp + mov bp, sp + + ; + ; Initialize the windows app. + ; + call INITTASK + + xor ax, ax + push ax + call WAITEVENT + + push di ; hInstance + push di + call INITAPP + + ; + ; Do what we're here for, exitting windows. + ; + xor ax, ax + xor cx, cx + xor dx, dx + push ax + push ax + push ax + push ax + call EXITWINDOWS + + ; + ; Exit via DOS interrupt. + ; + xor al, al + mov ah,04cH + int 021h + + mov sp, bp + pop bp + ret + +_TEXT ends + +end start + diff --git a/src/VBox/ValidationKit/utils/fs/FsPerf.cpp b/src/VBox/ValidationKit/utils/fs/FsPerf.cpp new file mode 100644 index 00000000..5e0d88ff --- /dev/null +++ b/src/VBox/ValidationKit/utils/fs/FsPerf.cpp @@ -0,0 +1,2659 @@ +/* $Id: FsPerf.cpp $ */ +/** @file + * FsPerf - File System (Shared Folders) Performance Benchmark. + */ + +/* + * Copyright (C) 2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/test.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/zero.h> + +#ifdef RT_OS_WINDOWS +# include <iprt/nt/nt-and-windows.h> +#else +# include <errno.h> +# include <unistd.h> +# include <sys/fcntl.h> +# include <sys/mman.h> +# include <sys/types.h> +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** + * Macro for profiling @a a_fnCall (typically forced inline) for about @a a_cNsTarget ns. + * + * Always does an even number of iterations. + */ +#define PROFILE_FN(a_fnCall, a_cNsTarget, a_szDesc) \ + do { \ + /* Estimate how many iterations we need to fill up the given timeslot: */ \ + fsPerfYield(); \ + uint64_t nsStart = RTTimeNanoTS(); \ + uint64_t ns; \ + do \ + ns = RTTimeNanoTS(); \ + while (ns == nsStart); \ + nsStart = ns; \ + \ + uint64_t iIteration = 0; \ + do \ + { \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + iIteration++; \ + ns = RTTimeNanoTS() - nsStart; \ + } while (ns < RT_NS_10MS || (iIteration & 1)); \ + ns /= iIteration; \ + if (ns > g_nsPerNanoTSCall + 32) \ + ns -= g_nsPerNanoTSCall; \ + \ + uint64_t cIterations = (a_cNsTarget) / ns; \ + if (cIterations <= 1) \ + cIterations = 2; \ + else if (cIterations & 1) \ + cIterations++; \ + \ + /* Do the actual profiling: */ \ + fsPerfYield(); \ + iIteration = 0; \ + nsStart = RTTimeNanoTS(); \ + for (; iIteration < cIterations; iIteration++) \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + ns = RTTimeNanoTS() - nsStart; \ + RTTestIValueF(ns / cIterations, RTTESTUNIT_NS_PER_OCCURRENCE, a_szDesc); \ + if (g_fShowDuration) \ + RTTestIValueF(ns, RTTESTUNIT_NS, "%s duration", a_szDesc); \ + } while (0) + + +/** + * Macro for profiling an operation on each file in the manytree directory tree. + * + * Always does an even number of tree iterations. + */ +#define PROFILE_MANYTREE_FN(a_szPath, a_fnCall, a_cEstimationIterations, a_cNsTarget, a_szDesc) \ + do { \ + if (!g_fManyFiles) \ + break; \ + \ + /* Estimate how many iterations we need to fill up the given timeslot: */ \ + fsPerfYield(); \ + uint64_t nsStart = RTTimeNanoTS(); \ + uint64_t ns; \ + do \ + ns = RTTimeNanoTS(); \ + while (ns == nsStart); \ + nsStart = ns; \ + \ + PFSPERFNAMEENTRY pCur; \ + uint64_t iIteration = 0; \ + do \ + { \ + RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \ + { \ + memcpy(a_szPath, pCur->szName, pCur->cchName); \ + for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \ + { \ + RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + } \ + } \ + iIteration++; \ + ns = RTTimeNanoTS() - nsStart; \ + } while (ns < RT_NS_10MS || (iIteration & 1)); \ + ns /= iIteration; \ + if (ns > g_nsPerNanoTSCall + 32) \ + ns -= g_nsPerNanoTSCall; \ + \ + uint32_t cIterations = (a_cNsTarget) / ns; \ + if (cIterations <= 1) \ + cIterations = 2; \ + else if (cIterations & 1) \ + cIterations++; \ + \ + /* Do the actual profiling: */ \ + fsPerfYield(); \ + uint32_t cCalls = 0; \ + nsStart = RTTimeNanoTS(); \ + for (iIteration = 0; iIteration < cIterations; iIteration++) \ + { \ + RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \ + { \ + memcpy(a_szPath, pCur->szName, pCur->cchName); \ + for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \ + { \ + RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + cCalls++; \ + } \ + } \ + } \ + ns = RTTimeNanoTS() - nsStart; \ + RTTestIValueF(ns / cCalls, RTTESTUNIT_NS_PER_OCCURRENCE, a_szDesc); \ + if (g_fShowDuration) \ + RTTestIValueF(ns, RTTESTUNIT_NS, "%s duration", a_szDesc); \ + } while (0) + + +/** + * Execute a_fnCall for each file in the manytree. + */ +#define DO_MANYTREE_FN(a_szPath, a_fnCall) \ + do { \ + PFSPERFNAMEENTRY pCur; \ + RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \ + { \ + memcpy(a_szPath, pCur->szName, pCur->cchName); \ + for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \ + { \ + RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \ + a_fnCall; \ + } \ + } \ + } while (0) + + +/** @def FSPERF_VERR_PATH_NOT_FOUND + * Hides the fact that we only get VERR_PATH_NOT_FOUND on non-unix systems. */ +#if defined(RT_OS_WINDOWS) //|| defined(RT_OS_OS2) - using posix APIs IIRC, so lost in translation. +# define FSPERF_VERR_PATH_NOT_FOUND VERR_PATH_NOT_FOUND +#else +# define FSPERF_VERR_PATH_NOT_FOUND VERR_FILE_NOT_FOUND +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct FSPERFNAMEENTRY +{ + RTLISTNODE Entry; + uint16_t cchName; + char szName[RT_FLEXIBLE_ARRAY]; +} FSPERFNAMEENTRY; +typedef FSPERFNAMEENTRY *PFSPERFNAMEENTRY; + + +enum +{ + kCmdOpt_First = 128, + + kCmdOpt_ManyFiles = kCmdOpt_First, + kCmdOpt_NoManyFiles, + kCmdOpt_Open, + kCmdOpt_NoOpen, + kCmdOpt_FStat, + kCmdOpt_NoFStat, + kCmdOpt_FChMod, + kCmdOpt_NoFChMod, + kCmdOpt_FUtimes, + kCmdOpt_NoFUtimes, + kCmdOpt_Stat, + kCmdOpt_NoStat, + kCmdOpt_ChMod, + kCmdOpt_NoChMod, + kCmdOpt_Utimes, + kCmdOpt_NoUtimes, + kCmdOpt_Rename, + kCmdOpt_NoRename, + kCmdOpt_DirEnum, + kCmdOpt_NoDirEnum, + kCmdOpt_MkRmDir, + kCmdOpt_NoMkRmDir, + kCmdOpt_StatVfs, + kCmdOpt_NoStatVfs, + kCmdOpt_Rm, + kCmdOpt_NoRm, + kCmdOpt_ChSize, + kCmdOpt_NoChSize, + kCmdOpt_Read, + kCmdOpt_NoRead, + kCmdOpt_Write, + kCmdOpt_NoWrite, + kCmdOpt_Seek, + kCmdOpt_NoSeek, + kCmdOpt_FSync, + kCmdOpt_NoFSync, + kCmdOpt_MMap, + kCmdOpt_NoMMap, + + kCmdOpt_End +}; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + { "--dir", 'd', RTGETOPT_REQ_STRING }, + { "--seconds", 's', RTGETOPT_REQ_UINT32 }, + { "--milliseconds", 'm', RTGETOPT_REQ_UINT64 }, + + { "--enable-all", 'e', RTGETOPT_REQ_NOTHING }, + { "--disable-all", 'z', RTGETOPT_REQ_NOTHING }, + + { "--many-files", kCmdOpt_ManyFiles, RTGETOPT_REQ_NOTHING }, + { "--no-many-files", kCmdOpt_NoManyFiles, RTGETOPT_REQ_NOTHING }, + + { "--open", kCmdOpt_Open, RTGETOPT_REQ_NOTHING }, + { "--no-open", kCmdOpt_NoOpen, RTGETOPT_REQ_NOTHING }, + { "--fstat", kCmdOpt_FStat, RTGETOPT_REQ_NOTHING }, + { "--no-fstat", kCmdOpt_NoFStat, RTGETOPT_REQ_NOTHING }, + { "--fchmod", kCmdOpt_FChMod, RTGETOPT_REQ_NOTHING }, + { "--no-fchmod", kCmdOpt_NoFChMod, RTGETOPT_REQ_NOTHING }, + { "--futimes", kCmdOpt_FUtimes, RTGETOPT_REQ_NOTHING }, + { "--no-futimes", kCmdOpt_NoFUtimes, RTGETOPT_REQ_NOTHING }, + { "--stat", kCmdOpt_Stat, RTGETOPT_REQ_NOTHING }, + { "--no-stat", kCmdOpt_NoStat, RTGETOPT_REQ_NOTHING }, + { "--chmod", kCmdOpt_ChMod, RTGETOPT_REQ_NOTHING }, + { "--no-chmod", kCmdOpt_NoChMod, RTGETOPT_REQ_NOTHING }, + { "--utimes", kCmdOpt_Utimes, RTGETOPT_REQ_NOTHING }, + { "--no-utimes", kCmdOpt_NoUtimes, RTGETOPT_REQ_NOTHING }, + { "--rename", kCmdOpt_Rename, RTGETOPT_REQ_NOTHING }, + { "--no-rename", kCmdOpt_NoRename, RTGETOPT_REQ_NOTHING }, + { "--dir-enum", kCmdOpt_DirEnum, RTGETOPT_REQ_NOTHING }, + { "--no-dir-enum", kCmdOpt_NoDirEnum, RTGETOPT_REQ_NOTHING }, + { "--mk-rm-dir", kCmdOpt_MkRmDir, RTGETOPT_REQ_NOTHING }, + { "--no-mk-rm-dir", kCmdOpt_NoMkRmDir, RTGETOPT_REQ_NOTHING }, + { "--stat-vfs", kCmdOpt_StatVfs, RTGETOPT_REQ_NOTHING }, + { "--no-stat-vfs", kCmdOpt_NoStatVfs, RTGETOPT_REQ_NOTHING }, + { "--rm", kCmdOpt_Rm, RTGETOPT_REQ_NOTHING }, + { "--no-rm", kCmdOpt_NoRm, RTGETOPT_REQ_NOTHING }, + { "--chsize", kCmdOpt_ChSize, RTGETOPT_REQ_NOTHING }, + { "--no-chsize", kCmdOpt_NoChSize, RTGETOPT_REQ_NOTHING }, + { "--read", kCmdOpt_Read, RTGETOPT_REQ_NOTHING }, + { "--no-read", kCmdOpt_NoRead, RTGETOPT_REQ_NOTHING }, + { "--write", kCmdOpt_Write, RTGETOPT_REQ_NOTHING }, + { "--no-write", kCmdOpt_NoWrite, RTGETOPT_REQ_NOTHING }, + { "--seek", kCmdOpt_Seek, RTGETOPT_REQ_NOTHING }, + { "--no-seek", kCmdOpt_NoSeek, RTGETOPT_REQ_NOTHING }, + { "--fsync", kCmdOpt_FSync, RTGETOPT_REQ_NOTHING }, + { "--no-fsync", kCmdOpt_NoFSync, RTGETOPT_REQ_NOTHING }, + { "--mmap", kCmdOpt_MMap, RTGETOPT_REQ_NOTHING }, + { "--no-mmap", kCmdOpt_NoMMap, RTGETOPT_REQ_NOTHING }, + + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--version", 'V', RTGETOPT_REQ_NOTHING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */ +}; + +/** The test handle. */ +static RTTEST g_hTest; +/** The number of nanoseconds a RTTimeNanoTS call takes. + * This is used for adjusting loop count estimates. */ +static uint64_t g_nsPerNanoTSCall = 1; +/** Whether or not to display the duration of each profile run. + * This is chiefly for verify the estimate phase. */ +static bool g_fShowDuration = true; +/** Verbosity level. */ +static uint32_t g_uVerbosity = 0; + +/** @name Selected subtest + * @{ */ +static bool g_fManyFiles = true; +static bool g_fOpen = true; +static bool g_fFStat = true; +static bool g_fFChMod = true; +static bool g_fFUtimes = true; +static bool g_fStat = true; +static bool g_fChMod = true; +static bool g_fUtimes = true; +static bool g_fRename = true; +static bool g_fDirEnum = true; +static bool g_fMkRmDir = true; +static bool g_fStatVfs = true; +static bool g_fRm = true; +static bool g_fChSize = true; +static bool g_fRead = true; +static bool g_fWrite = true; +static bool g_fSeek = true; +static bool g_fFSync = true; +static bool g_fMMap = true; +/** @} */ + +/** The length of each test run. */ +static uint64_t g_nsTestRun = RT_NS_1SEC_64 * 10; + +/** For the 'manyfiles' subdir. */ +static uint32_t g_cManyFiles = 10000; + +/** Number of files in the 'manytree' directory tree. */ +static uint32_t g_cManyTreeFiles = 640 + 16*640 /*10880*/; +/** Number of files per directory in the 'manytree' construct. */ +static uint32_t g_cManyTreeFilesPerDir = 640; +/* Number of subdirs per directory in the 'manytree' construct. */ +static uint32_t g_cManyTreeSubdirsPerDir = 16; +/** The depth of the 'manytree' directory tree. */ +static uint32_t g_cManyTreeDepth = 1; +/** List of directories in the many tree, creation order. */ +static RTLISTANCHOR g_ManyTreeHead; + +/** Number of configured I/O block sizes. */ +static uint32_t g_cIoBlocks = 8; +/** Configured I/O block sizes. */ +static uint32_t g_acbIoBlocks[16] = { 1, 512, 4096, 16384, 65536, _1M, _32M, _128M }; +/** The desired size of the test file we use for I/O. */ +static uint64_t g_cbIoFile = _512M; + +/** The length of g_szDir. */ +static size_t g_cchDir; +/** The length of g_szEmptyDir. */ +static size_t g_cchEmptyDir; +/** The length of g_szDeepDir. */ +static size_t g_cchDeepDir; + +/** The test directory (absolute). This will always have a trailing slash. */ +static char g_szDir[RTPATH_MAX]; +/** The empty test directory (absolute). This will always have a trailing slash. */ +static char g_szEmptyDir[RTPATH_MAX]; +/** The deep test directory (absolute). This will always have a trailing slash. */ +static char g_szDeepDir[RTPATH_MAX]; + + +/** + * Yield the CPU and stuff before starting a test run. + */ +DECLINLINE(void) fsPerfYield(void) +{ + RTThreadYield(); + RTThreadYield(); +} + + +/** + * Profiles the RTTimeNanoTS call, setting g_nsPerNanoTSCall. + */ +static void fsPerfNanoTS(void) +{ + fsPerfYield(); + + /* Make sure we start off on a changing timestamp on platforms will low time resoultion. */ + uint64_t nsStart = RTTimeNanoTS(); + uint64_t ns; + do + ns = RTTimeNanoTS(); + while (ns == nsStart); + nsStart = ns; + + /* Call it for 10 ms. */ + uint32_t i = 0; + do + { + i++; + ns = RTTimeNanoTS(); + } + while (ns - nsStart < RT_NS_10MS); + + g_nsPerNanoTSCall = (ns - nsStart) / i; +} + + +/** + * Construct a path relative to the base test directory. + * + * @returns g_szDir. + * @param pszAppend What to append. + * @param cchAppend How much to append. + */ +DECLINLINE(char *) InDir(const char *pszAppend, size_t cchAppend) +{ + Assert(g_szDir[g_cchDir - 1] == RTPATH_SLASH); + memcpy(&g_szDir[g_cchDir], pszAppend, cchAppend); + g_szDir[g_cchDir + cchAppend] = '\0'; + return &g_szDir[0]; +} + + +/** + * Construct a path relative to the empty directory. + * + * @returns g_szEmptyDir. + * @param pszAppend What to append. + * @param cchAppend How much to append. + */ +DECLINLINE(char *) InEmptyDir(const char *pszAppend, size_t cchAppend) +{ + Assert(g_szEmptyDir[g_cchEmptyDir - 1] == RTPATH_SLASH); + memcpy(&g_szEmptyDir[g_cchEmptyDir], pszAppend, cchAppend); + g_szEmptyDir[g_cchEmptyDir + cchAppend] = '\0'; + return &g_szEmptyDir[0]; +} + + +/** + * Construct a path relative to the deep test directory. + * + * @returns g_szDeepDir. + * @param pszAppend What to append. + * @param cchAppend How much to append. + */ +DECLINLINE(char *) InDeepDir(const char *pszAppend, size_t cchAppend) +{ + Assert(g_szDeepDir[g_cchDeepDir - 1] == RTPATH_SLASH); + memcpy(&g_szDeepDir[g_cchDeepDir], pszAppend, cchAppend); + g_szDeepDir[g_cchDeepDir + cchAppend] = '\0'; + return &g_szDeepDir[0]; +} + + +/** + * Prepares the test area. + * @returns VBox status code. + */ +static int fsPrepTestArea(void) +{ + /* The empty subdir and associated globals: */ + static char s_szEmpty[] = "empty"; + memcpy(g_szEmptyDir, g_szDir, g_cchDir); + memcpy(&g_szEmptyDir[g_cchDir], s_szEmpty, sizeof(s_szEmpty)); + g_cchEmptyDir = g_cchDir + sizeof(s_szEmpty) - 1; + RTTESTI_CHECK_RC_RET(RTDirCreate(g_szEmptyDir, 0755, 0), VINF_SUCCESS, rcCheck); + g_szEmptyDir[g_cchEmptyDir++] = RTPATH_SLASH; + g_szEmptyDir[g_cchEmptyDir] = '\0'; + RTTestIPrintf(RTTESTLVL_ALWAYS, "Empty dir: %s\n", g_szEmptyDir); + + /* Deep directory: */ + memcpy(g_szDeepDir, g_szDir, g_cchDir); + g_cchDeepDir = g_cchDir; + do + { + static char const s_szSub[] = "d" RTPATH_SLASH_STR; + memcpy(&g_szDeepDir[g_cchDeepDir], s_szSub, sizeof(s_szSub)); + g_cchDeepDir += sizeof(s_szSub) - 1; + RTTESTI_CHECK_RC_RET( RTDirCreate(g_szDeepDir, 0755, 0), VINF_SUCCESS, rcCheck); + } while (g_cchDeepDir < 176); + RTTestIPrintf(RTTESTLVL_ALWAYS, "Deep dir: %s\n", g_szDeepDir); + + /* Create known file in both deep and shallow dirs: */ + RTFILE hKnownFile; + RTTESTI_CHECK_RC_RET(RTFileOpen(&hKnownFile, InDir(RT_STR_TUPLE("known-file")), + RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), + VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC_RET(RTFileClose(hKnownFile), VINF_SUCCESS, rcCheck); + + RTTESTI_CHECK_RC_RET(RTFileOpen(&hKnownFile, InDeepDir(RT_STR_TUPLE("known-file")), + RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), + VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC_RET(RTFileClose(hKnownFile), VINF_SUCCESS, rcCheck); + + return VINF_SUCCESS; +} + + +/** + * Create a name list entry. + * @returns Pointer to the entry, NULL if out of memory. + * @param pchName The name. + * @param cchName The name length. + */ +PFSPERFNAMEENTRY fsPerfCreateNameEntry(const char *pchName, size_t cchName) +{ + PFSPERFNAMEENTRY pEntry = (PFSPERFNAMEENTRY)RTMemAllocVar(RT_UOFFSETOF_DYN(FSPERFNAMEENTRY, szName[cchName + 1])); + if (pEntry) + { + RTListInit(&pEntry->Entry); + pEntry->cchName = (uint16_t)cchName; + memcpy(pEntry->szName, pchName, cchName); + pEntry->szName[cchName] = '\0'; + } + return pEntry; +} + + +static int fsPerfManyTreeRecursiveDirCreator(size_t cchDir, uint32_t iDepth) +{ + PFSPERFNAMEENTRY pEntry = fsPerfCreateNameEntry(g_szDir, cchDir); + RTTESTI_CHECK_RET(pEntry, VERR_NO_MEMORY); + RTListAppend(&g_ManyTreeHead, &pEntry->Entry); + + RTTESTI_CHECK_RC_RET(RTDirCreate(g_szDir, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL), + VINF_SUCCESS, rcCheck); + + if (iDepth < g_cManyTreeDepth) + for (uint32_t i = 0; i < g_cManyTreeSubdirsPerDir; i++) + { + size_t cchSubDir = RTStrPrintf(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, "d%02u" RTPATH_SLASH_STR, i); + RTTESTI_CHECK_RC_RET(fsPerfManyTreeRecursiveDirCreator(cchDir + cchSubDir, iDepth + 1), VINF_SUCCESS, rcCheck); + } + + return VINF_SUCCESS; +} + + +void fsPerfManyFiles(void) +{ + RTTestISub("manyfiles"); + + /* + * Create a sub-directory with like 10000 files in it. + * + * This does push the directory organization of the underlying file system, + * which is something we might not want to profile with shared folders. It + * is however useful for directory enumeration. + */ + RTTESTI_CHECK_RC_RETV(RTDirCreate(InDir(RT_STR_TUPLE("manyfiles")), 0755, + RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL), + VINF_SUCCESS); + + size_t offFilename = strlen(g_szDir); + g_szDir[offFilename++] = RTPATH_SLASH; + + fsPerfYield(); + RTFILE hFile; + uint64_t const nsStart = RTTimeNanoTS(); + for (uint32_t i = 0; i < g_cManyFiles; i++) + { + RTStrFormatU32(&g_szDir[offFilename], sizeof(g_szDir) - offFilename, i, 10, 5, 5, RTSTR_F_ZEROPAD); + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile, g_szDir, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS); + } + uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart; + RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Creating %u empty files in single directory", g_cManyFiles); + RTTestIValueF(cNsElapsed / g_cManyFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Create empty file (single dir)"); + + /* + * Create a bunch of directories with exacly 32 files in each, hoping to + * avoid any directory organization artifacts. + */ + /* Create the directories first, building a list of them for simplifying iteration: */ + RTListInit(&g_ManyTreeHead); + InDir(RT_STR_TUPLE("manytree" RTPATH_SLASH_STR)); + RTTESTI_CHECK_RC_RETV(fsPerfManyTreeRecursiveDirCreator(strlen(g_szDir), 0), VINF_SUCCESS); + + /* Create the zero byte files: */ + fsPerfYield(); + uint64_t const nsStart2 = RTTimeNanoTS(); + uint32_t cFiles = 0; + PFSPERFNAMEENTRY pCur; + RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) + { + char szPath[RTPATH_MAX]; + memcpy(szPath, pCur->szName, pCur->cchName); + for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) + { + RTStrFormatU32(&szPath[pCur->cchName], sizeof(szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile, szPath, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS); + cFiles++; + } + } + uint64_t const cNsElapsed2 = RTTimeNanoTS() - nsStart2; + RTTestIValueF(cNsElapsed2, RTTESTUNIT_NS, "Creating %u empty files in tree", cFiles); + RTTestIValueF(cNsElapsed2 / cFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Create empty file (tree)"); + RTTESTI_CHECK(g_cManyTreeFiles == cFiles); +} + + +DECL_FORCE_INLINE(int) fsPerfOpenExistingOnceReadonly(const char *pszFile) +{ + RTFILE hFile; + RTTESTI_CHECK_RC_RET(RTFileOpen(&hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS); + return VINF_SUCCESS; +} + + +DECL_FORCE_INLINE(int) fsPerfOpenExistingOnceWriteonly(const char *pszFile) +{ + RTFILE hFile; + RTTESTI_CHECK_RC_RET(RTFileOpen(&hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS); + return VINF_SUCCESS; +} + + +void fsPerfOpen(void) +{ + RTTestISub("open"); + + /* Opening non-existing files. */ + RTFILE hFile; + RTTESTI_CHECK_RC(RTFileOpen(&hFile, InEmptyDir(RT_STR_TUPLE("no-such-file")), + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileOpen(&hFile, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileOpen(&hFile, InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VERR_PATH_NOT_FOUND); + + /* + * Create file1 and then try exclusivly creating it again. + * Then profile opening it for reading. + */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file1")), + RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileOpen(&hFile, g_szDir, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VERR_ALREADY_EXISTS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + PROFILE_FN(fsPerfOpenExistingOnceReadonly(g_szDir), g_nsTestRun, "RTFileOpen/Close/Readonly"); + PROFILE_FN(fsPerfOpenExistingOnceWriteonly(g_szDir), g_nsTestRun, "RTFileOpen/Close/Writeonly"); + + /* + * Profile opening in the deep directory too. + */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file1")), + RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + PROFILE_FN(fsPerfOpenExistingOnceReadonly(g_szDeepDir), g_nsTestRun, "RTFileOpen/Close/deep/readonly"); + PROFILE_FN(fsPerfOpenExistingOnceWriteonly(g_szDeepDir), g_nsTestRun, "RTFileOpen/Close/deep/writeonly"); + + /* Manytree: */ + char szPath[RTPATH_MAX]; + PROFILE_MANYTREE_FN(szPath, fsPerfOpenExistingOnceReadonly(szPath), 1, g_nsTestRun, "RTFileOpen/Close/manytree/readonly"); +} + + +void fsPerfFStat(void) +{ + RTTestISub("fstat"); + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file2")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTFSOBJINFO ObjInfo = {0}; + PROFILE_FN(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_NOTHING), g_nsTestRun, "RTFileQueryInfo/NOTHING"); + PROFILE_FN(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_UNIX), g_nsTestRun, "RTFileQueryInfo/UNIX"); + + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); +} + + +void fsPerfFChMod(void) +{ + RTTestISub("fchmod"); + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file4")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTFSOBJINFO ObjInfo = {0}; + RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS); + RTFMODE const fEvenMode = (ObjInfo.Attr.fMode & ~RTFS_UNIX_ALL_ACCESS_PERMS) | RTFS_DOS_READONLY | 0400; + RTFMODE const fOddMode = (ObjInfo.Attr.fMode & ~(RTFS_UNIX_ALL_ACCESS_PERMS | RTFS_DOS_READONLY)) | 0640; + PROFILE_FN(RTFileSetMode(hFile1, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTFileSetMode"); + + RTFileSetMode(hFile1, ObjInfo.Attr.fMode); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); +} + + +void fsPerfFUtimes(void) +{ + RTTestISub("futimes"); + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file5")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTIMESPEC Time1; + RTTimeNow(&Time1); + RTTIMESPEC Time2 = Time1; + RTTimeSpecSubSeconds(&Time2, 3636); + + RTFSOBJINFO ObjInfo0 = {0}; + RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo0, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS); + + /* Modify modification time: */ + RTTESTI_CHECK_RC(RTFileSetTimes(hFile1, NULL, &Time2, NULL, NULL), VINF_SUCCESS); + RTFSOBJINFO ObjInfo1 = {0}; + RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo1, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS); + RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo1.ModificationTime) >> 2) == (RTTimeSpecGetSeconds(&Time2) >> 2)); + char sz1[RTTIME_STR_LEN], sz2[RTTIME_STR_LEN]; /* Div by 1000 here for posix impl. using timeval. */ + RTTESTI_CHECK_MSG(RTTimeSpecGetNano(&ObjInfo1.AccessTime) / 1000 == RTTimeSpecGetNano(&ObjInfo0.AccessTime) / 1000, + ("%s, expected %s", RTTimeSpecToString(&ObjInfo1.AccessTime, sz1, sizeof(sz1)), + RTTimeSpecToString(&ObjInfo0.AccessTime, sz2, sizeof(sz2)))); + + /* Modify access time: */ + RTTESTI_CHECK_RC(RTFileSetTimes(hFile1, &Time1, NULL, NULL, NULL), VINF_SUCCESS); + RTFSOBJINFO ObjInfo2 = {0}; + RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo2, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS); + RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo2.AccessTime) >> 2) == (RTTimeSpecGetSeconds(&Time1) >> 2)); + RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo2.ModificationTime) / 1000 == RTTimeSpecGetNano(&ObjInfo1.ModificationTime) / 1000); + + /* Benchmark it: */ + PROFILE_FN(RTFileSetTimes(hFile1, NULL, iIteration & 1 ? &Time1 : &Time2, NULL, NULL), g_nsTestRun, "RTFileSetTimes"); + + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); +} + + +void fsPerfStat(void) +{ + RTTestISub("stat"); + RTFSOBJINFO ObjInfo; + + /* Non-existing files. */ + RTTESTI_CHECK_RC(RTPathQueryInfoEx(InEmptyDir(RT_STR_TUPLE("no-such-file")), + &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathQueryInfoEx(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), + &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathQueryInfoEx(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), + &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VERR_PATH_NOT_FOUND); + + /* Shallow: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file3")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + PROFILE_FN(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), g_nsTestRun, + "RTPathQueryInfoEx/NOTHING"); + PROFILE_FN(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), g_nsTestRun, + "RTPathQueryInfoEx/UNIX"); + + + /* Deep: */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file3")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + PROFILE_FN(RTPathQueryInfoEx(g_szDeepDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), g_nsTestRun, + "RTPathQueryInfoEx/deep/NOTHING"); + PROFILE_FN(RTPathQueryInfoEx(g_szDeepDir, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), g_nsTestRun, + "RTPathQueryInfoEx/deep/UNIX"); + + /* Manytree: */ + char szPath[RTPATH_MAX]; + PROFILE_MANYTREE_FN(szPath, RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), + 1, g_nsTestRun, "RTPathQueryInfoEx/manytree/NOTHING"); + PROFILE_MANYTREE_FN(szPath, RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), + 1, g_nsTestRun, "RTPathQueryInfoEx/manytree/UNIX"); +} + + +void fsPerfChmod(void) +{ + RTTestISub("chmod"); + + /* Non-existing files. */ + RTTESTI_CHECK_RC(RTPathSetMode(InEmptyDir(RT_STR_TUPLE("no-such-file")), 0665), + VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathSetMode(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), 0665), + FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathSetMode(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), 0665), VERR_PATH_NOT_FOUND); + + /* Shallow: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file14")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + RTFSOBJINFO ObjInfo; + RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS); + RTFMODE const fEvenMode = (ObjInfo.Attr.fMode & ~RTFS_UNIX_ALL_ACCESS_PERMS) | RTFS_DOS_READONLY | 0400; + RTFMODE const fOddMode = (ObjInfo.Attr.fMode & ~(RTFS_UNIX_ALL_ACCESS_PERMS | RTFS_DOS_READONLY)) | 0640; + PROFILE_FN(RTPathSetMode(g_szDir, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTPathSetMode"); + RTPathSetMode(g_szDir, ObjInfo.Attr.fMode); + + /* Deep: */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file14")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + PROFILE_FN(RTPathSetMode(g_szDeepDir, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTPathSetMode/deep"); + RTPathSetMode(g_szDeepDir, ObjInfo.Attr.fMode); + + /* Manytree: */ + char szPath[RTPATH_MAX]; + PROFILE_MANYTREE_FN(szPath, RTPathSetMode(szPath, iIteration & 1 ? fOddMode : fEvenMode), 1, g_nsTestRun, + "RTPathSetMode/manytree"); + DO_MANYTREE_FN(szPath, RTPathSetMode(szPath, ObjInfo.Attr.fMode)); +} + + +void fsPerfUtimes(void) +{ + RTTestISub("utimes"); + + RTTIMESPEC Time1; + RTTimeNow(&Time1); + RTTIMESPEC Time2 = Time1; + RTTimeSpecSubSeconds(&Time2, 3636); + + /* Non-existing files. */ + RTTESTI_CHECK_RC(RTPathSetTimesEx(InEmptyDir(RT_STR_TUPLE("no-such-file")), NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK), + VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathSetTimesEx(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), + NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK), + FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathSetTimesEx(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), + NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK), + VERR_PATH_NOT_FOUND); + + /* Shallow: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file15")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + RTFSOBJINFO ObjInfo0 = {0}; + RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo0, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS); + + /* Modify modification time: */ + RTTESTI_CHECK_RC(RTPathSetTimesEx(g_szDir, NULL, &Time2, NULL, NULL, RTPATH_F_ON_LINK), VINF_SUCCESS); + RTFSOBJINFO ObjInfo1; + RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo1, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS); + RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo1.ModificationTime) >> 2) == (RTTimeSpecGetSeconds(&Time2) >> 2)); + RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo1.AccessTime) / 1000 == RTTimeSpecGetNano(&ObjInfo0.AccessTime) / 1000 /* posix timeval */); + + /* Modify access time: */ + RTTESTI_CHECK_RC(RTPathSetTimesEx(g_szDir, &Time1, NULL, NULL, NULL, RTPATH_F_ON_LINK), VINF_SUCCESS); + RTFSOBJINFO ObjInfo2 = {0}; + RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo2, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS); + RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo2.AccessTime) >> 2) == (RTTimeSpecGetSeconds(&Time1) >> 2)); + RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo2.ModificationTime) / 1000 == RTTimeSpecGetNano(&ObjInfo1.ModificationTime) / 1000 /* posix timeval */); + + /* Profile shallow: */ + PROFILE_FN(RTPathSetTimesEx(g_szDir, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1, + NULL, NULL, RTPATH_F_ON_LINK), + g_nsTestRun, "RTPathSetTimesEx"); + + /* Deep: */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file15")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + PROFILE_FN(RTPathSetTimesEx(g_szDeepDir, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1, + NULL, NULL, RTPATH_F_ON_LINK), + g_nsTestRun, "RTPathSetTimesEx/deep"); + + /* Manytree: */ + char szPath[RTPATH_MAX]; + PROFILE_MANYTREE_FN(szPath, RTPathSetTimesEx(szPath, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1, + NULL, NULL, RTPATH_F_ON_LINK), + 1, g_nsTestRun, "RTPathSetTimesEx/manytree"); +} + + +DECL_FORCE_INLINE(int) fsPerfRenameMany(const char *pszFile, uint32_t iIteration) +{ + char szRenamed[RTPATH_MAX]; + strcat(strcpy(szRenamed, pszFile), "-renamed"); + if (!(iIteration & 1)) + return RTPathRename(pszFile, szRenamed, 0); + return RTPathRename(szRenamed, pszFile, 0); +} + + +void fsPerfRename(void) +{ + RTTestISub("rename"); + char szPath[RTPATH_MAX]; + + /* Non-existing files. */ + strcpy(szPath, InEmptyDir(RT_STR_TUPLE("other-no-such-file"))); + RTTESTI_CHECK_RC(RTPathRename(InEmptyDir(RT_STR_TUPLE("no-such-file")), szPath, 0), VERR_FILE_NOT_FOUND); + strcpy(szPath, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "other-no-such-file"))); + RTTESTI_CHECK_RC(RTPathRename(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), szPath, 0), + FSPERF_VERR_PATH_NOT_FOUND); + strcpy(szPath, InEmptyDir(RT_STR_TUPLE("other-no-such-file"))); + RTTESTI_CHECK_RC(RTPathRename(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), szPath, 0), VERR_PATH_NOT_FOUND); + + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file16")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + strcat(strcpy(szPath, g_szDir), "-no-such-dir" RTPATH_SLASH_STR "file16"); + RTTESTI_CHECK_RC(RTPathRename(szPath, g_szDir, 0), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathRename(g_szDir, szPath, 0), FSPERF_VERR_PATH_NOT_FOUND); + + /* Shallow: */ + strcat(strcpy(szPath, g_szDir), "-other"); + PROFILE_FN(RTPathRename(iIteration & 1 ? szPath : g_szDir, iIteration & 1 ? g_szDir : szPath, 0), g_nsTestRun, "RTPathRename"); + + /* Deep: */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file15")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + strcat(strcpy(szPath, g_szDeepDir), "-other"); + PROFILE_FN(RTPathRename(iIteration & 1 ? szPath : g_szDeepDir, iIteration & 1 ? g_szDeepDir : szPath, 0), + g_nsTestRun, "RTPathRename/deep"); + + /* Manytree: */ + PROFILE_MANYTREE_FN(szPath, fsPerfRenameMany(szPath, iIteration), 2, g_nsTestRun, "RTPathRename/manytree"); +} + + +DECL_FORCE_INLINE(int) fsPerfEnumEmpty(void) +{ + RTDIR hDir; + g_szEmptyDir[g_cchEmptyDir] = '\0'; + RTTESTI_CHECK_RC_RET(RTDirOpen(&hDir, g_szEmptyDir), VINF_SUCCESS, rcCheck); + + RTDIRENTRY Entry; + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES); + + RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS); + return VINF_SUCCESS; +} + + +DECL_FORCE_INLINE(int) fsPerfEnumManyFiles(void) +{ + RTDIR hDir; + RTTESTI_CHECK_RC_RET(RTDirOpen(&hDir, InDir(RT_STR_TUPLE("manyfiles"))), VINF_SUCCESS, rcCheck); + uint32_t cLeft = g_cManyFiles + 2; + for (;;) + { + RTDIRENTRY Entry; + if (cLeft > 0) + RTTESTI_CHECK_RC_BREAK(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS); + else + { + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES); + break; + } + cLeft--; + } + RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS); + return VINF_SUCCESS; +} + + +void vsPerfDirEnum(void) +{ + RTTestISub("dir enum"); + RTDIR hDir; + + /* Non-existing files. */ + RTTESTI_CHECK_RC(RTDirOpen(&hDir, InEmptyDir(RT_STR_TUPLE("no-such-file"))), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirOpen(&hDir, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirOpen(&hDir, InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND); + + /* + * The empty directory. + */ + g_szEmptyDir[g_cchEmptyDir] = '\0'; + RTTESTI_CHECK_RC_RETV(RTDirOpen(&hDir, g_szEmptyDir), VINF_SUCCESS); + + uint32_t fDots = 0; + RTDIRENTRY Entry; + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS); + RTTESTI_CHECK(RTDirEntryIsStdDotLink(&Entry)); + fDots |= RT_BIT_32(Entry.cbName - 1); + + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS); + RTTESTI_CHECK(RTDirEntryIsStdDotLink(&Entry)); + fDots |= RT_BIT_32(Entry.cbName - 1); + RTTESTI_CHECK(fDots == 3); + + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES); + + RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS); + + /* + * The directory with many files in it. + */ + if (g_fManyFiles) + { + fDots = 0; + uint32_t const cBitmap = RT_ALIGN_32(g_cManyFiles, 64); + void *pvBitmap = alloca(cBitmap / 8); + RT_BZERO(pvBitmap, cBitmap / 8); + for (uint32_t i = g_cManyFiles; i < cBitmap; i++) + ASMBitSet(pvBitmap, i); + + uint32_t cFiles = 0; + RTTESTI_CHECK_RC_RETV(RTDirOpen(&hDir, InDir(RT_STR_TUPLE("manyfiles"))), VINF_SUCCESS); + for (;;) + { + int rc = RTDirRead(hDir, &Entry, NULL); + if (rc == VINF_SUCCESS) + { + if (Entry.szName[0] == '.') + { + if (Entry.szName[1] == '.') + { + RTTESTI_CHECK(!(fDots & 2)); + fDots |= 2; + } + else + { + RTTESTI_CHECK(Entry.szName[1] == '\0'); + RTTESTI_CHECK(!(fDots & 1)); + fDots |= 1; + } + } + else + { + uint32_t iFile = UINT32_MAX; + RTTESTI_CHECK_RC(RTStrToUInt32Full(Entry.szName, 10, &iFile), VINF_SUCCESS); + if ( iFile < g_cManyFiles + && !ASMBitTest(pvBitmap, iFile)) + { + ASMBitSet(pvBitmap, iFile); + cFiles++; + } + else + RTTestFailed(g_hTest, "line %u: iFile=%u g_cManyFiles=%u\n", __LINE__, iFile, g_cManyFiles); + } + } + else if (rc == VERR_NO_MORE_FILES) + break; + else + { + RTTestFailed(g_hTest, "RTDirRead failed enumerating manyfiles: %Rrc\n", rc); + RTDirClose(hDir); + return; + } + } + RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS); + RTTESTI_CHECK(fDots == 3); + RTTESTI_CHECK(cFiles == g_cManyFiles); + RTTESTI_CHECK(ASMMemIsAllU8(pvBitmap, cBitmap / 8, 0xff)); + } + + /* + * Profile. + */ + PROFILE_FN(fsPerfEnumEmpty(),g_nsTestRun, "RTDirOpen/Read/Close empty"); + if (g_fManyFiles) + PROFILE_FN(fsPerfEnumManyFiles(), g_nsTestRun, "RTDirOpen/Read/Close manyfiles"); +} + + +void fsPerfMkRmDir(void) +{ + RTTestISub("mkdir/rmdir"); + + /* Non-existing directories: */ + RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir"))), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), 0755, 0), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), 0755, 0), VERR_PATH_NOT_FOUND); + + /** @todo check what happens if non-final path component isn't a directory. unix + * should return ENOTDIR and IPRT translates that to VERR_PATH_NOT_FOUND. + * Curious what happens on windows. */ + + /* Already existing directories and files: */ + RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE(".")), 0755, 0), VERR_ALREADY_EXISTS); + RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE("..")), 0755, 0), VERR_ALREADY_EXISTS); + + /* Remove directory with subdirectories: */ +#if defined(RT_OS_WINDOWS) + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("."))), VERR_DIR_NOT_EMPTY); +#else + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("."))), VERR_INVALID_PARAMETER); /* EINVAL for '.' */ +#endif +#if defined(RT_OS_WINDOWS) + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE(".."))), VERR_SHARING_VIOLATION); /* weird */ +#else + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE(".."))), VERR_DIR_NOT_EMPTY); +#endif + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE(""))), VERR_DIR_NOT_EMPTY); + + /* Create a directory and remove it: */ + RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("subdir-1")), 0755, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRemove(g_szDir), VINF_SUCCESS); + + /* Create a file and try remove it or create a directory with the same name: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file18")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRemove(g_szDir), VERR_NOT_A_DIRECTORY); + RTTESTI_CHECK_RC(RTDirCreate(g_szDir, 0755, 0), VERR_ALREADY_EXISTS); + RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("file18" RTPATH_SLASH_STR "subdir")), 0755, 0), VERR_PATH_NOT_FOUND); + + /* + * Profile alternately creating and removing a bunch of directories. + */ + RTTESTI_CHECK_RC_RETV(RTDirCreate(InDir(RT_STR_TUPLE("subdir-2")), 0755, 0), VINF_SUCCESS); + size_t cchDir = strlen(g_szDir); + g_szDir[cchDir++] = RTPATH_SLASH; + g_szDir[cchDir++] = 's'; + + uint32_t cCreated = 0; + uint64_t nsCreate = 0; + uint64_t nsRemove = 0; + for (;;) + { + /* Create a bunch: */ + uint64_t nsStart = RTTimeNanoTS(); + for (uint32_t i = 0; i < 998; i++) + { + RTStrFormatU32(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, i, 10, 3, 3, RTSTR_F_ZEROPAD); + RTTESTI_CHECK_RC_RETV(RTDirCreate(g_szDir, 0755, 0), VINF_SUCCESS); + } + nsCreate += RTTimeNanoTS() - nsStart; + cCreated += 998; + + /* Remove the bunch: */ + nsStart = RTTimeNanoTS(); + for (uint32_t i = 0; i < 998; i++) + { + RTStrFormatU32(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, i, 10, 3, 3, RTSTR_F_ZEROPAD); + RTTESTI_CHECK_RC_RETV(RTDirRemove(g_szDir), VINF_SUCCESS); + } + nsRemove = RTTimeNanoTS() - nsStart; + + /* Check if we got time for another round: */ + if ( ( nsRemove >= g_nsTestRun + && nsCreate >= g_nsTestRun) + || nsCreate + nsRemove >= g_nsTestRun * 3) + break; + } + RTTestIValue("RTDirCreate", nsCreate / cCreated, RTTESTUNIT_NS_PER_OCCURRENCE); + RTTestIValue("RTDirRemove", nsRemove / cCreated, RTTESTUNIT_NS_PER_OCCURRENCE); +} + + +void fsPerfStatVfs(void) +{ + RTTestISub("statvfs"); + + g_szEmptyDir[g_cchEmptyDir] = '\0'; + RTFOFF cbTotal; + RTFOFF cbFree; + uint32_t cbBlock; + uint32_t cbSector; + RTTESTI_CHECK_RC(RTFsQuerySizes(g_szEmptyDir, &cbTotal, &cbFree, &cbBlock, &cbSector), VINF_SUCCESS); + + uint32_t uSerial; + RTTESTI_CHECK_RC(RTFsQuerySerial(g_szEmptyDir, &uSerial), VINF_SUCCESS); + + RTFSPROPERTIES Props; + RTTESTI_CHECK_RC(RTFsQueryProperties(g_szEmptyDir, &Props), VINF_SUCCESS); + + RTFSTYPE enmType; + RTTESTI_CHECK_RC(RTFsQueryType(g_szEmptyDir, &enmType), VINF_SUCCESS); + +} + + +void fsPerfRm(void) +{ + RTTestISub("rm"); + + /* Non-existing files. */ + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-file"))), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND); + + /* Directories: */ +#if defined(RT_OS_WINDOWS) + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_ACCESS_DENIED); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VERR_ACCESS_DENIED); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_ACCESS_DENIED); +#else + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_IS_A_DIRECTORY); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VERR_IS_A_DIRECTORY); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_IS_A_DIRECTORY); +#endif + + /* Shallow: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file19")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VERR_FILE_NOT_FOUND); + + if (g_fManyFiles) + { + /* + * Profile the deletion of the manyfiles content. + */ + { + InDir(RT_STR_TUPLE("manyfiles" RTPATH_SLASH_STR)); + size_t const offFilename = strlen(g_szDir); + fsPerfYield(); + uint64_t const nsStart = RTTimeNanoTS(); + for (uint32_t i = 0; i < g_cManyFiles; i++) + { + RTStrFormatU32(&g_szDir[offFilename], sizeof(g_szDir) - offFilename, i, 10, 5, 5, RTSTR_F_ZEROPAD); + RTTESTI_CHECK_RC_RETV(RTFileDelete(g_szDir), VINF_SUCCESS); + } + uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart; + RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Deleted %u empty files from a single directory", g_cManyFiles); + RTTestIValueF(cNsElapsed / g_cManyFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Delete file (single dir)"); + } + + /* + * Ditto for the manytree. + */ + { + char szPath[RTPATH_MAX]; + uint64_t const nsStart = RTTimeNanoTS(); + DO_MANYTREE_FN(szPath, RTTESTI_CHECK_RC_RETV(RTFileDelete(szPath), VINF_SUCCESS)); + uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart; + RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Deleted %u empty files in tree", g_cManyTreeFiles); + RTTestIValueF(cNsElapsed / g_cManyTreeFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Delete file (tree)"); + } + } +} + + +void fsPerfChSize(void) +{ + RTTestISub("chsize"); + + /* + * We need some free space to perform this test. + */ + g_szDir[g_cchDir] = '\0'; + RTFOFF cbFree = 0; + RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS); + if (cbFree < _1M) + { + RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 1MB", cbFree); + return; + } + + /* + * Create a file and play around with it's size. + * We let the current file position follow the end position as we make changes. + */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file20")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS); + uint64_t cbFile = UINT64_MAX; + RTTESTI_CHECK_RC(RTFileGetSize(hFile1, &cbFile), VINF_SUCCESS); + RTTESTI_CHECK(cbFile == 0); + + uint8_t abBuf[4096]; + static uint64_t const s_acbChanges[] = + { + 1023, 1024, 1024, 1025, 8192, 11111, _1M, _8M, _8M, + _4M, _2M + 1, _1M - 1, 65537, 65536, 32768, 8000, 7999, 7998, 1024, 1, 0 + }; + uint64_t cbOld = 0; + for (unsigned i = 0; i < RT_ELEMENTS(s_acbChanges); i++) + { + uint64_t cbNew = s_acbChanges[i]; + if (cbNew + _64K >= (uint64_t)cbFree) + continue; + + RTTESTI_CHECK_RC(RTFileSetSize(hFile1, cbNew), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileGetSize(hFile1, &cbFile), VINF_SUCCESS); + RTTESTI_CHECK_MSG(cbFile == cbNew, ("cbFile=%#RX64 cbNew=%#RX64\n", cbFile, cbNew)); + + if (cbNew > cbOld) + { + /* Check that the extension is all zeroed: */ + uint64_t cbLeft = cbNew - cbOld; + while (cbLeft > 0) + { + memset(abBuf, 0xff, sizeof(abBuf)); + size_t cbToRead = sizeof(abBuf); + if (cbToRead > cbLeft) + cbToRead = (size_t)cbLeft; + RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, cbToRead, NULL), VINF_SUCCESS); + RTTESTI_CHECK(ASMMemIsZero(abBuf, cbToRead)); + cbLeft -= cbToRead; + } + } + else + { + /* Check that reading fails with EOF because current position is now beyond the end: */ + RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, 1, NULL), VERR_EOF); + + /* Keep current position at the end of the file: */ + RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbNew, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + } + cbOld = cbNew; + } + + /* + * Profile just the file setting operation itself, keeping the changes within + * an allocation unit to avoid needing to adjust the actual (host) FS allocation. + * ASSUMES allocation unit >= 512 and power of two. + */ + RTTESTI_CHECK_RC(RTFileSetSize(hFile1, _64K), VINF_SUCCESS); + PROFILE_FN(RTFileSetSize(hFile1, _64K - (iIteration & 255) - 128), g_nsTestRun, "RTFileSetSize/noalloc"); + + RTTESTI_CHECK_RC(RTFileSetSize(hFile1, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS); +} + + +int fsPerfIoPrepFile(RTFILE hFile1, uint64_t cbFile, uint8_t **ppbFree) +{ + /* + * Seek to the end - 4K and write the last 4K. + * This should have the effect of filling the whole file with zeros. + */ + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, cbFile - _4K, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, g_abRTZero4K, _4K, NULL), VINF_SUCCESS, rcCheck); + + /* + * Check that the space we searched across actually is zero filled. + */ + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + size_t cbBuf = _1M; + uint8_t *pbBuf = *ppbFree = (uint8_t *)RTMemAlloc(cbBuf); + RTTESTI_CHECK_RET(pbBuf != NULL, VERR_NO_MEMORY); + uint64_t cbLeft = cbFile; + while (cbLeft > 0) + { + size_t cbToRead = cbBuf; + if (cbToRead > cbLeft) + cbToRead = (size_t)cbLeft; + pbBuf[cbToRead] = 0xff; + + RTTESTI_CHECK_RC_RET(RTFileRead(hFile1, pbBuf, cbToRead, NULL), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RET(ASMMemIsZero(pbBuf, cbToRead), VERR_MISMATCH); + + cbLeft -= cbToRead; + } + + /* + * Fill the file with 0xf6 and insert offset markers with 1KB intervals. + */ + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + memset(pbBuf, 0xf6, cbBuf); + cbLeft = cbFile; + uint64_t off = 0; + while (cbLeft > 0) + { + Assert(!(off & (_1K - 1))); + Assert(!(cbBuf & (_1K - 1))); + for (size_t offBuf = 0; offBuf < cbBuf; offBuf += _1K, off += _1K) + *(uint64_t *)&pbBuf[offBuf] = off; + + size_t cbToWrite = cbBuf; + if (cbToWrite > cbLeft) + cbToWrite = (size_t)cbLeft; + + RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBuf, cbToWrite, NULL), VINF_SUCCESS, rcCheck); + + cbLeft -= cbToWrite; + } + + return VINF_SUCCESS; +} + + +/** + * Checks the content read from the file fsPerfIoPrepFile() prepared. + */ +bool fsPerfCheckReadBuf(unsigned uLineNo, uint64_t off, uint8_t const *pbBuf, size_t cbBuf, uint8_t bFiller = 0xf6) +{ + uint32_t cMismatches = 0; + size_t offBuf = 0; + uint32_t offBlock = (uint32_t)(off & (_1K - 1)); + while (offBuf < cbBuf) + { + /* + * Check the offset marker: + */ + if (offBlock < sizeof(uint64_t)) + { + RTUINT64U uMarker; + uMarker.u = off + offBuf - offBlock; + unsigned offMarker = offBlock & (sizeof(uint64_t) - 1); + while (offMarker < sizeof(uint64_t) && offBuf < cbBuf) + { + if (uMarker.au8[offMarker] != pbBuf[offBuf]) + { + RTTestIFailed("%u: Mismatch at buffer/file offset %#zx/%#RX64: %#x, expected %#x", + uLineNo, offBuf, off + offBuf, pbBuf[offBuf], uMarker.au8[offMarker]); + if (cMismatches++ > 32) + return false; + } + offMarker++; + offBuf++; + } + offBlock = sizeof(uint64_t); + } + + /* + * Check the filling: + */ + size_t cbFilling = RT_MIN(_1K - offBlock, cbBuf - offBuf); + if ( cbFilling == 0 + || ASMMemIsAllU8(&pbBuf[offBuf], cbFilling, bFiller)) + offBuf += cbFilling; + else + { + /* Some mismatch, locate it/them: */ + while (cbFilling > 0 && offBuf < cbBuf) + { + if (pbBuf[offBuf] != bFiller) + { + RTTestIFailed("%u: Mismatch at buffer/file offset %#zx/%#RX64: %#x, expected %#04x", + uLineNo, offBuf, off + offBuf, pbBuf[offBuf], bFiller); + if (cMismatches++ > 32) + return false; + } + offBuf++; + cbFilling--; + } + } + offBlock = 0; + } + return cMismatches == 0; +} + + +/** + * Sets up write buffer with offset markers and fillers. + */ +void fsPerfFillWriteBuf(uint64_t off, uint8_t *pbBuf, size_t cbBuf, uint8_t bFiller = 0xf6) +{ + uint32_t offBlock = (uint32_t)(off & (_1K - 1)); + while (cbBuf > 0) + { + /* The marker. */ + if (offBlock < sizeof(uint64_t)) + { + RTUINT64U uMarker; + uMarker.u = off + offBlock; + if (cbBuf > sizeof(uMarker) - offBlock) + { + memcpy(pbBuf, &uMarker.au8[offBlock], sizeof(uMarker) - offBlock); + pbBuf += sizeof(uMarker) - offBlock; + cbBuf -= sizeof(uMarker) - offBlock; + off += sizeof(uMarker) - offBlock; + } + else + { + memcpy(pbBuf, &uMarker.au8[offBlock], cbBuf); + return; + } + offBlock = sizeof(uint64_t); + } + + /* Do the filling. */ + size_t cbFilling = RT_MIN(_1K - offBlock, cbBuf); + memset(pbBuf, bFiller, cbFilling); + pbBuf += cbFilling; + cbBuf -= cbFilling; + off += cbFilling; + + offBlock = 0; + } +} + + + +void fsPerfIoSeek(RTFILE hFile1, uint64_t cbFile) +{ + /* + * Do a bunch of search tests, most which are random. + */ + struct + { + int rc; + uint32_t uMethod; + int64_t offSeek; + uint64_t offActual; + + } aSeeks[9 + 64] = + { + { VINF_SUCCESS, RTFILE_SEEK_BEGIN, 0, 0 }, + { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 0, 0 }, + { VINF_SUCCESS, RTFILE_SEEK_END, 0, cbFile }, + { VINF_SUCCESS, RTFILE_SEEK_CURRENT, -4096, cbFile - 4096 }, + { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 4096 - (int64_t)cbFile, 0 }, + { VINF_SUCCESS, RTFILE_SEEK_END, -(int64_t)cbFile/2, cbFile / 2 + (cbFile & 1) }, + { VINF_SUCCESS, RTFILE_SEEK_CURRENT, -(int64_t)cbFile/2, 0 }, +#if defined(RT_OS_WINDOWS) + { VERR_NEGATIVE_SEEK, RTFILE_SEEK_CURRENT, -1, 0 }, +#else + { VERR_INVALID_PARAMETER, RTFILE_SEEK_CURRENT, -1, 0 }, +#endif + { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 0, 0 }, + }; + + uint64_t offActual = 0; + for (unsigned i = 9; i < RT_ELEMENTS(aSeeks); i++) + { + switch (RTRandU32Ex(RTFILE_SEEK_BEGIN, RTFILE_SEEK_END)) + { + default: AssertFailedBreak(); + case RTFILE_SEEK_BEGIN: + aSeeks[i].uMethod = RTFILE_SEEK_BEGIN; + aSeeks[i].rc = VINF_SUCCESS; + aSeeks[i].offSeek = RTRandU64Ex(0, cbFile + cbFile / 8); + aSeeks[i].offActual = offActual = aSeeks[i].offSeek; + break; + + case RTFILE_SEEK_CURRENT: + aSeeks[i].uMethod = RTFILE_SEEK_CURRENT; + aSeeks[i].rc = VINF_SUCCESS; + aSeeks[i].offSeek = (int64_t)RTRandU64Ex(0, cbFile + cbFile / 8) - (int64_t)offActual; + aSeeks[i].offActual = offActual += aSeeks[i].offSeek; + break; + + case RTFILE_SEEK_END: + aSeeks[i].uMethod = RTFILE_SEEK_END; + aSeeks[i].rc = VINF_SUCCESS; + aSeeks[i].offSeek = -(int64_t)RTRandU64Ex(0, cbFile); + aSeeks[i].offActual = offActual = cbFile + aSeeks[i].offSeek; + break; + } + } + + for (unsigned iDoReadCheck = 0; iDoReadCheck < 2; iDoReadCheck++) + { + for (uint32_t i = 0; i < RT_ELEMENTS(aSeeks); i++) + { + offActual = UINT64_MAX; + int rc = RTFileSeek(hFile1, aSeeks[i].offSeek, aSeeks[i].uMethod, &offActual); + if (rc != aSeeks[i].rc) + RTTestIFailed("Seek #%u: Expected %Rrc, got %Rrc", i, aSeeks[i].rc, rc); + if (RT_SUCCESS(rc) && offActual != aSeeks[i].offActual) + RTTestIFailed("Seek #%u: offActual %#RX64, expected %#RX64", i, offActual, aSeeks[i].offActual); + if (RT_SUCCESS(rc)) + { + uint64_t offTell = RTFileTell(hFile1); + if (offTell != offActual) + RTTestIFailed("Seek #%u: offActual %#RX64, RTFileTell %#RX64", i, offActual, offTell); + } + + if (RT_SUCCESS(rc) && offActual + _2K <= cbFile && iDoReadCheck) + { + uint8_t abBuf[_2K]; + RTTESTI_CHECK_RC(rc = RTFileRead(hFile1, abBuf, sizeof(abBuf), NULL), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + size_t offMarker = (size_t)(RT_ALIGN_64(offActual, _1K) - offActual); + uint64_t uMarker = *(uint64_t *)&abBuf[offMarker]; /** @todo potentially unaligned access */ + if (uMarker != offActual + offMarker) + RTTestIFailed("Seek #%u: Invalid marker value (@ %#RX64): %#RX64, expected %#RX64", + i, offActual, uMarker, offActual + offMarker); + + RTTESTI_CHECK_RC(RTFileSeek(hFile1, -(int64_t)sizeof(abBuf), RTFILE_SEEK_CURRENT, NULL), VINF_SUCCESS); + } + } + } + } + + + /* + * Profile seeking relative to the beginning of the file and relative + * to the end. The latter might be more expensive in a SF context. + */ + PROFILE_FN(RTFileSeek(hFile1, iIteration < cbFile ? iIteration : iIteration % cbFile, RTFILE_SEEK_BEGIN, NULL), + g_nsTestRun, "RTFileSeek/BEGIN"); + PROFILE_FN(RTFileSeek(hFile1, iIteration < cbFile ? -(int64_t)iIteration : -(int64_t)(iIteration % cbFile), RTFILE_SEEK_END, NULL), + g_nsTestRun, "RTFileSeek/END"); + +} + + +/** For fsPerfIoRead and fsPerfIoWrite. */ +#define PROFILE_IO_FN(a_szOperation, a_fnCall) \ + do \ + { \ + RTTESTI_CHECK_RC_RETV(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); \ + uint64_t offActual = 0; \ + uint32_t cSeeks = 0; \ + \ + /* Estimate how many iterations we need to fill up the given timeslot: */ \ + fsPerfYield(); \ + uint64_t nsStart = RTTimeNanoTS(); \ + uint64_t ns; \ + do \ + ns = RTTimeNanoTS(); \ + while (ns == nsStart); \ + nsStart = ns; \ + \ + uint64_t iIteration = 0; \ + do \ + { \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + iIteration++; \ + ns = RTTimeNanoTS() - nsStart; \ + } while (ns < RT_NS_10MS); \ + ns /= iIteration; \ + if (ns > g_nsPerNanoTSCall + 32) \ + ns -= g_nsPerNanoTSCall; \ + uint64_t cIterations = g_nsTestRun / ns; \ + \ + /* Do the actual profiling: */ \ + cSeeks = 0; \ + iIteration = 0; \ + fsPerfYield(); \ + nsStart = RTTimeNanoTS(); \ + for (uint32_t iAdjust = 0; iAdjust < 4; iAdjust++) \ + { \ + for (; iIteration < cIterations; iIteration++)\ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + ns = RTTimeNanoTS() - nsStart;\ + if (ns >= g_nsTestRun - (g_nsTestRun / 10)) \ + break; \ + cIterations += cIterations / 4; \ + if (cIterations & 1) \ + cIterations++; \ + nsStart += g_nsPerNanoTSCall; \ + } \ + RTTestIValueF(ns / iIteration, \ + RTTESTUNIT_NS_PER_OCCURRENCE, a_szOperation "/seq/%RU32 latency", cbBlock); \ + RTTestIValueF((uint64_t)iIteration * cbBlock / ((double)ns / RT_NS_1SEC), \ + RTTESTUNIT_BYTES_PER_SEC, a_szOperation "/seq/%RU32 throughput", cbBlock); \ + RTTestIValueF(iIteration, \ + RTTESTUNIT_CALLS, a_szOperation "/seq/%RU32 calls", cbBlock); \ + RTTestIValueF((uint64_t)iIteration * cbBlock, \ + RTTESTUNIT_BYTES, a_szOperation "/seq/%RU32 bytes", cbBlock); \ + RTTestIValueF(cSeeks, \ + RTTESTUNIT_OCCURRENCES, a_szOperation "/seq/%RU32 seeks", cbBlock); \ + if (g_fShowDuration) \ + RTTestIValueF(ns, RTTESTUNIT_NS, a_szOperation "/seq/%RU32 duration", cbBlock); \ + } while (0) + + +/** + * One RTFileRead profiling iteration. + */ +DECL_FORCE_INLINE(int) fsPerfIoReadWorker(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock, uint8_t *pbBlock, + uint64_t *poffActual, uint32_t *pcSeeks) +{ + /* Do we need to seek back to the start? */ + if (*poffActual + cbBlock <= cbFile) + { /* likely */ } + else + { + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + *pcSeeks += 1; + *poffActual = 0; + } + + size_t cbActuallyRead = 0; + RTTESTI_CHECK_RC_RET(RTFileRead(hFile1, pbBlock, cbBlock, &cbActuallyRead), VINF_SUCCESS, rcCheck); + if (cbActuallyRead == cbBlock) + { + *poffActual += cbActuallyRead; + return VINF_SUCCESS; + } + RTTestIFailed("RTFileRead at %#RX64 returned just %#x bytes, expected %#x", *poffActual, cbActuallyRead, cbBlock); + *poffActual += cbActuallyRead; + return VERR_READ_ERROR; +} + + +void fsPerfIoReadBlockSize(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock) +{ + RTTestISubF("IO - Sequential read %RU32", cbBlock); + + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBlock); + if (pbBuf) + { + memset(pbBuf, 0xf7, cbBlock); + PROFILE_IO_FN("RTFileRead", fsPerfIoReadWorker(hFile1, cbFile, cbBlock, pbBuf, &offActual, &cSeeks)); + RTMemPageFree(pbBuf, cbBlock); + } + else + RTTestSkipped(g_hTest, "insufficient (virtual) memory available"); +} + + +void fsPerfRead(RTFILE hFile1, RTFILE hFileNoCache, uint64_t cbFile) +{ + RTTestISubF("IO - RTFileRead"); + + /* + * Allocate a big buffer we can play around with. Min size is 1MB. + */ + size_t cbBuf = cbFile < _64M ? (size_t)cbFile : _64M; + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf); + while (!pbBuf) + { + cbBuf /= 2; + RTTESTI_CHECK_RETV(cbBuf >= _1M); + pbBuf = (uint8_t *)RTMemPageAlloc(_32M); + } + +#if 1 + /* + * Start at the beginning and read the full buffer in random small chunks, thereby + * checking that unaligned buffer addresses, size and file offsets work fine. + */ + struct + { + uint64_t offFile; + uint32_t cbMax; + } aRuns[] = { { 0, 127 }, { cbFile - cbBuf, UINT32_MAX }, { 0, UINT32_MAX -1 }}; + for (uint32_t i = 0; i < RT_ELEMENTS(aRuns); i++) + { + memset(pbBuf, 0x55, cbBuf); + RTTESTI_CHECK_RC(RTFileSeek(hFile1, aRuns[i].offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + for (size_t offBuf = 0; offBuf < cbBuf; ) + { + uint32_t const cbLeft = (uint32_t)(cbBuf - offBuf); + uint32_t const cbToRead = aRuns[i].cbMax < UINT32_MAX / 2 ? RTRandU32Ex(1, RT_MIN(aRuns[i].cbMax, cbLeft)) + : aRuns[i].cbMax == UINT32_MAX ? RTRandU32Ex(RT_MAX(cbLeft / 4, 1), cbLeft) + : RTRandU32Ex(cbLeft >= _8K ? _8K : 1, RT_MIN(_1M, cbLeft)); + size_t cbActual = 0; + RTTESTI_CHECK_RC(RTFileRead(hFile1, &pbBuf[offBuf], cbToRead, &cbActual), VINF_SUCCESS); + if (cbActual == cbToRead) + offBuf += cbActual; + else + { + RTTestIFailed("Attempting to read %#x bytes at %#zx, only got %#x bytes back! (cbLeft=%#x cbBuf=%#zx)\n", + cbToRead, offBuf, cbActual, cbLeft, cbBuf); + if (cbActual) + offBuf += cbActual; + else + pbBuf[offBuf++] = 0x11; + } + } + fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf); + } +#endif + + /* + * Test reading beyond the end of the file. + */ + size_t const acbMax[] = { cbBuf, _64K, _16K, _4K, 256 }; + uint32_t const aoffFromEos[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 32, 63, 64, 127, 128, 255, 254, 256, 1023, 1024, 2048, + 4092, 4093, 4094, 4095, 4096, 4097, 4098, 4099, 4100, 8192, 16384, 32767, 32768, 32769, 65535, 65536, _1M - 1 + }; + for (unsigned iMax = 0; iMax < RT_ELEMENTS(acbMax); iMax++) + { + size_t const cbMaxRead = acbMax[iMax]; + for (uint32_t iOffFromEos = 0; iOffFromEos < RT_ELEMENTS(aoffFromEos); iOffFromEos++) + { + uint32_t off = aoffFromEos[iOffFromEos]; + if (off >= cbMaxRead) + continue; + RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + size_t cbActual = ~(size_t)0; + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, &cbActual), VINF_SUCCESS); + RTTESTI_CHECK(cbActual == off); + + RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + cbActual = ~(size_t)0; + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, off, &cbActual), VINF_SUCCESS); + RTTESTI_CHECK_MSG(cbActual == off, ("%#zx vs %#zx", cbActual, off)); + + cbActual = ~(size_t)0; + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, 1, &cbActual), VINF_SUCCESS); + RTTESTI_CHECK(cbActual == 0); + + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, NULL), VERR_EOF); + } + } + + /* + * Test reading beyond end of the file. + */ + for (unsigned iMax = 0; iMax < RT_ELEMENTS(acbMax); iMax++) + { + size_t const cbMaxRead = acbMax[iMax]; + for (uint32_t off = 0; off < 256; off++) + { + RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile + off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + size_t cbActual = ~(size_t)0; + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, &cbActual), VINF_SUCCESS); + RTTESTI_CHECK(cbActual == 0); + + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, NULL), VERR_EOF); + } + } + + /* + * Do uncached access, must be page aligned. + */ + uint32_t cbPage = PAGE_SIZE; + memset(pbBuf, 0x66, cbBuf); + RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + for (size_t offBuf = 0; offBuf < cbBuf; ) + { + uint32_t const cPagesLeft = (uint32_t)((cbBuf - offBuf) / cbPage); + uint32_t const cPagesToRead = RTRandU32Ex(1, cPagesLeft); + size_t const cbToRead = cPagesToRead * (size_t)cbPage; + size_t cbActual = 0; + RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, &pbBuf[offBuf], cbToRead, &cbActual), VINF_SUCCESS); + if (cbActual == cbToRead) + offBuf += cbActual; + else + { + RTTestIFailed("Attempting to read %#zx bytes at %#zx, only got %#x bytes back!\n", cbToRead, offBuf, cbActual); + if (cbActual) + offBuf += cbActual; + else + { + memset(&pbBuf[offBuf], 0x11, cbPage); + offBuf += cbPage; + } + } + } + fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf); + + /* + * Check reading zero bytes at the end of the file. + * Requires native call because RTFileWrite doesn't call kernel on zero byte reads. + */ + RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS); +#ifdef RT_OS_WINDOWS + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 0, NULL, NULL); + RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt)); + RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS); + RTTESTI_CHECK(Ios.Information == 0); + + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 1, NULL, NULL); + RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x", rcNt)); + RTTESTI_CHECK(Ios.Status == STATUS_END_OF_FILE); + RTTESTI_CHECK(Ios.Information == 0); +#else + ssize_t cbRead = read((int)RTFileToNative(hFile1), pbBuf, 0); + RTTESTI_CHECK(cbRead == 0); +#endif + + /* + * Other OS specific stuff. + */ +#ifdef RT_OS_WINDOWS + /* Check that reading at an offset modifies the position: */ + RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS); + RTTESTI_CHECK(RTFileTell(hFile1) == cbFile); + + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + LARGE_INTEGER offNt; + offNt.QuadPart = cbFile / 2; + rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, _4K, &offNt, NULL); + RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt)); + RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS); + RTTESTI_CHECK(Ios.Information == _4K); + RTTESTI_CHECK(RTFileTell(hFile1) == cbFile / 2 + _4K); + fsPerfCheckReadBuf(__LINE__, cbFile / 2, pbBuf, _4K); +#endif + + RTMemPageFree(pbBuf, cbBuf); +} + + +/** + * One RTFileWrite profiling iteration. + */ +DECL_FORCE_INLINE(int) fsPerfIoWriteWorker(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock, uint8_t *pbBlock, + uint64_t *poffActual, uint32_t *pcSeeks) +{ + /* Do we need to seek back to the start? */ + if (*poffActual + cbBlock <= cbFile) + { /* likely */ } + else + { + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + *pcSeeks += 1; + *poffActual = 0; + } + + size_t cbActuallyWritten = 0; + RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBlock, cbBlock, &cbActuallyWritten), VINF_SUCCESS, rcCheck); + if (cbActuallyWritten == cbBlock) + { + *poffActual += cbActuallyWritten; + return VINF_SUCCESS; + } + RTTestIFailed("RTFileWrite at %#RX64 returned just %#x bytes, expected %#x", *poffActual, cbActuallyWritten, cbBlock); + *poffActual += cbActuallyWritten; + return VERR_WRITE_ERROR; +} + + +void fsPerfIoWriteBlockSize(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock) +{ + RTTestISubF("IO - Sequential write %RU32", cbBlock); + + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBlock); + if (pbBuf) + { + memset(pbBuf, 0xf7, cbBlock); + PROFILE_IO_FN("RTFileWrite", fsPerfIoWriteWorker(hFile1, cbFile, cbBlock, pbBuf, &offActual, &cSeeks)); + RTMemPageFree(pbBuf, cbBlock); + } + else + RTTestSkipped(g_hTest, "insufficient (virtual) memory available"); +} + + +void fsPerfWrite(RTFILE hFile1, RTFILE hFileNoCache, RTFILE hFileWriteThru, uint64_t cbFile) +{ + RTTestISubF("IO - RTFileWrite"); + + /* + * Allocate a big buffer we can play around with. Min size is 1MB. + */ + size_t cbBuf = cbFile < _64M ? (size_t)cbFile : _64M; + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf); + while (!pbBuf) + { + cbBuf /= 2; + RTTESTI_CHECK_RETV(cbBuf >= _1M); + pbBuf = (uint8_t *)RTMemPageAlloc(_32M); + } + + /* + * Start at the beginning and write out the full buffer in random small chunks, thereby + * checking that unaligned buffer addresses, size and file offsets work fine. + */ + struct + { + uint64_t offFile; + uint32_t cbMax; + } aRuns[] = { { 0, 127 }, { cbFile - cbBuf, UINT32_MAX }, { 0, UINT32_MAX -1 }}; + uint8_t bFiller = 0x88; + for (uint32_t i = 0; i < RT_ELEMENTS(aRuns); i++, bFiller) + { + fsPerfFillWriteBuf(aRuns[i].offFile, pbBuf, cbBuf, bFiller); + fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf, bFiller); + + RTTESTI_CHECK_RC(RTFileSeek(hFile1, aRuns[i].offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + for (size_t offBuf = 0; offBuf < cbBuf; ) + { + uint32_t const cbLeft = (uint32_t)(cbBuf - offBuf); + uint32_t const cbToWrite = aRuns[i].cbMax < UINT32_MAX / 2 ? RTRandU32Ex(1, aRuns[i].cbMax) + : aRuns[i].cbMax == UINT32_MAX ? RTRandU32Ex(RT_MAX(cbLeft / 4, 1), cbLeft) + : RTRandU32Ex(cbLeft >= _8K ? _8K : 1, RT_MIN(_1M, cbLeft)); + size_t cbActual = 0; + RTTESTI_CHECK_RC(RTFileWrite(hFile1, &pbBuf[offBuf], cbToWrite, &cbActual), VINF_SUCCESS); + if (cbActual == cbToWrite) + offBuf += cbActual; + else + { + RTTestIFailed("Attempting to write %#x bytes at %#zx, only got %#x written!\n", cbToWrite, offBuf, cbActual); + if (cbActual) + offBuf += cbActual; + else + pbBuf[offBuf++] = 0x11; + } + } + + RTTESTI_CHECK_RC(RTFileReadAt(hFile1, aRuns[i].offFile, pbBuf, cbBuf, NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf, bFiller); + } + + + /* + * Do uncached and write-thru accesses, must be page aligned. + */ + RTFILE ahFiles[2] = { hFileWriteThru, hFileNoCache }; + for (unsigned iFile = 0; iFile < RT_ELEMENTS(ahFiles); iFile++, bFiller++) + { + fsPerfFillWriteBuf(0, pbBuf, cbBuf, bFiller); + fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf, bFiller); + RTTESTI_CHECK_RC(RTFileSeek(ahFiles[iFile], 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + + uint32_t cbPage = PAGE_SIZE; + for (size_t offBuf = 0; offBuf < cbBuf; ) + { + uint32_t const cPagesLeft = (uint32_t)((cbBuf - offBuf) / cbPage); + uint32_t const cPagesToWrite = RTRandU32Ex(1, cPagesLeft); + size_t const cbToWrite = cPagesToWrite * (size_t)cbPage; + size_t cbActual = 0; + RTTESTI_CHECK_RC(RTFileWrite(ahFiles[iFile], &pbBuf[offBuf], cbToWrite, &cbActual), VINF_SUCCESS); + if (cbActual == cbToWrite) + { + RTTESTI_CHECK_RC(RTFileReadAt(hFile1, offBuf, pbBuf, cbToWrite, NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, offBuf, pbBuf, cbToWrite, bFiller); + offBuf += cbActual; + } + else + { + RTTestIFailed("Attempting to read %#zx bytes at %#zx, only got %#x written!\n", cbToWrite, offBuf, cbActual); + if (cbActual) + offBuf += cbActual; + else + { + memset(&pbBuf[offBuf], 0x11, cbPage); + offBuf += cbPage; + } + } + } + + RTTESTI_CHECK_RC(RTFileReadAt(ahFiles[iFile], 0, pbBuf, cbBuf, NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf, bFiller); + } + + /* + * Check the behavior of writing zero bytes to the file _4K from the end + * using native API. In the olden days zero sized write have been known + * to be used to truncate a file. + */ + RTTESTI_CHECK_RC(RTFileSeek(hFile1, -_4K, RTFILE_SEEK_END, NULL), VINF_SUCCESS); +#ifdef RT_OS_WINDOWS + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtWriteFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 0, NULL, NULL); + RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt)); + RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS); + RTTESTI_CHECK(Ios.Information == 0); +#else + ssize_t cbWritten = write((int)RTFileToNative(hFile1), pbBuf, 0); + RTTESTI_CHECK(cbWritten == 0); +#endif + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, _4K, NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, cbFile - _4K, pbBuf, _4K, pbBuf[0x8]); + + /* + * Other OS specific stuff. + */ +#ifdef RT_OS_WINDOWS + /* Check that reading at an offset modifies the position: */ + RTTESTI_CHECK_RC(RTFileReadAt(hFile1, cbFile / 2, pbBuf, _4K, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS); + RTTESTI_CHECK(RTFileTell(hFile1) == cbFile); + + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + LARGE_INTEGER offNt; + offNt.QuadPart = cbFile / 2; + rcNt = NtWriteFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, _4K, &offNt, NULL); + RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt)); + RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS); + RTTESTI_CHECK(Ios.Information == _4K); + RTTESTI_CHECK(RTFileTell(hFile1) == cbFile / 2 + _4K); +#endif + + RT_NOREF(hFileNoCache, hFileWriteThru); + RTMemPageFree(pbBuf, cbBuf); +} + + +/** + * Worker for testing RTFileFlush. + */ +DECL_FORCE_INLINE(int) fsPerfFSyncWorker(RTFILE hFile1, uint64_t cbFile, uint8_t *pbBuf, size_t cbBuf, uint64_t *poffFile) +{ + if (*poffFile + cbBuf <= cbFile) + { /* likely */ } + else + { + RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + *poffFile = 0; + } + + RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBuf, cbBuf, NULL), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC_RET(RTFileFlush(hFile1), VINF_SUCCESS, rcCheck); + + *poffFile += cbBuf; + return VINF_SUCCESS; +} + + +void fsPerfFSync(RTFILE hFile1, uint64_t cbFile) +{ + RTTestISub("fsync"); + + RTTESTI_CHECK_RC(RTFileFlush(hFile1), VINF_SUCCESS); + + PROFILE_FN(RTFileFlush(hFile1), g_nsTestRun, "RTFileFlush"); + + size_t cbBuf = PAGE_SIZE; + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf); + RTTESTI_CHECK_RETV(pbBuf != NULL); + memset(pbBuf, 0xf4, cbBuf); + + RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + uint64_t offFile = 0; + PROFILE_FN(fsPerfFSyncWorker(hFile1, cbFile, pbBuf, cbBuf, &offFile), g_nsTestRun, "RTFileWrite[Page]/RTFileFlush"); + + RTMemPageFree(pbBuf, cbBuf); +} + + +void fsPerfMMap(RTFILE hFile1, RTFILE hFileNoCache, uint64_t cbFile) +{ + RTTestISub("mmap"); +#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) + static const char * const s_apszStates[] = { "readonly", "writecopy", "readwrite" }; + enum { kMMap_ReadOnly = 0, kMMap_WriteCopy, kMMap_ReadWrite, kMMap_End }; + for (int enmState = kMMap_ReadOnly; enmState < kMMap_End; enmState++) + { + /* + * Do the mapping. + */ + size_t cbMapping = (size_t)cbFile; + if (cbMapping != cbFile) + cbMapping = _256M; + uint8_t *pbMapping; + +# ifdef RT_OS_WINDOWS + HANDLE hSection; + for (;; cbMapping /= 2) + { + hSection = CreateFileMapping((HANDLE)RTFileToNative(hFile1), NULL, + enmState == kMMap_ReadOnly ? PAGE_READONLY + : enmState == kMMap_WriteCopy ? PAGE_WRITECOPY : PAGE_READWRITE, + (uint32_t)((uint64_t)cbMapping >> 32), (uint32_t)cbMapping, NULL); + DWORD dwErr1 = GetLastError(); + DWORD dwErr2 = 0; + if (hSection != NULL) + { + pbMapping = (uint8_t *)MapViewOfFile(hSection, + enmState == kMMap_ReadOnly ? FILE_MAP_READ + : enmState == kMMap_WriteCopy ? FILE_MAP_COPY + : FILE_MAP_WRITE, + 0, 0, cbMapping); + if (pbMapping) + break; + dwErr2 = GetLastError(); + CloseHandle(hSection); + } + if (cbMapping <= _2M) + { + RTTestIFailed("%u: CreateFileMapping or MapViewOfFile failed: %u, %u", enmState, dwErr1, dwErr2); + return; + } + } +# else + for (;; cbMapping /= 2) + { + pbMapping = (uint8_t *)mmap(NULL, cbMapping, + enmState == kMMap_ReadOnly ? PROT_READ : PROT_READ | PROT_WRITE, + enmState == kMMap_WriteCopy ? MAP_PRIVATE : MAP_SHARED, + (int)RTFileToNative(hFile1), 0); + if ((void *)pbMapping != MAP_FAILED) + break; + RTTESTI_CHECK_MSG_RETV(cbMapping > _2M, ("errno=%d", errno)); + } +# endif + + /* + * Time page-ins just for fun. + */ + size_t const cPages = cbMapping >> PAGE_SHIFT; + size_t uDummy = 0; + uint64_t ns = RTTimeNanoTS(); + for (size_t iPage = 0; iPage < cPages; iPage++) + uDummy += ASMAtomicReadU8(&pbMapping[iPage << PAGE_SHIFT]); + ns = RTTimeNanoTS() - ns; + RTTestIValueF(ns / cPages, RTTESTUNIT_NS_PER_OCCURRENCE, "page-in %s", s_apszStates[enmState]); + + /* Check the content. */ + fsPerfCheckReadBuf(__LINE__, 0, pbMapping, cbMapping); + + if (enmState != kMMap_ReadOnly) + { + /* Write stuff to the first two megabytes. In the COW case, we'll detect + corruption of shared data during content checking of the RW iterations. */ + fsPerfFillWriteBuf(0, pbMapping, _2M, 0xf7); + if (enmState == kMMap_ReadWrite) + { + /* For RW we can try read back from the file handle and check if we get + a match there first. */ + uint8_t abBuf[_4K]; + for (uint32_t off = 0; off < _2M; off += sizeof(abBuf)) + { + RTTESTI_CHECK_RC(RTFileReadAt(hFile1, off, abBuf, sizeof(abBuf), NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, off, abBuf, sizeof(abBuf), 0xf7); + } +# ifdef RT_OS_WINDOWS + RTTESTI_CHECK(FlushViewOfFile(pbMapping, _2M)); +# else + RTTESTI_CHECK(msync(pbMapping, _2M, MS_SYNC) == 0); +# endif + + /* + * Time modifying and flushing a few different number of pages. + */ + static size_t const s_acbFlush[] = { PAGE_SIZE, PAGE_SIZE * 2, PAGE_SIZE * 3, PAGE_SIZE * 8, PAGE_SIZE * 16, _2M }; + for (unsigned iFlushSize = 0 ; iFlushSize < RT_ELEMENTS(s_acbFlush); iFlushSize++) + { + size_t const cbFlush = s_acbFlush[iFlushSize]; + if (cbFlush > cbMapping) + continue; + +# if defined(RT_OS_LINUX) + size_t const cFlushes = RT_MIN(cbMapping / cbFlush, 2048); +# else + size_t const cFlushes = cbMapping / cbFlush; +# endif + uint8_t *pbCur = pbMapping; + ns = RTTimeNanoTS(); + for (size_t iFlush = 0; iFlush < cFlushes; iFlush++, pbCur += cbFlush) + { + for (size_t offFlush = 0; offFlush < cbFlush; offFlush += PAGE_SIZE) + *(size_t volatile *)&pbCur[offFlush + 8] = cbFlush; +# ifdef RT_OS_WINDOWS + RTTESTI_CHECK(FlushViewOfFile(pbCur, cbFlush)); +# else + RTTESTI_CHECK(msync(pbCur, cbFlush, MS_SYNC) == 0); +# endif + } + ns = RTTimeNanoTS() - ns; + RTTestIValueF(ns / cFlushes, RTTESTUNIT_NS_PER_OCCURRENCE, "touch/flush/%zu", cbFlush); + + /* + * Check that all the changes made it thru to the file: + */ + size_t cbBuf = _2M; + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(_2M); + if (!pbBuf) + { + cbBuf = _4K; + pbBuf = (uint8_t *)RTMemPageAlloc(_2M); + } + if (pbBuf) + { + RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + size_t const cbToCheck = cFlushes * cbFlush; + unsigned cErrors = 0; + for (size_t offBuf = 0; cErrors < 32 && offBuf < cbToCheck; offBuf += cbBuf) + { + size_t cbToRead = RT_MIN(cbBuf, cbToCheck - offBuf); + RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, pbBuf, cbToRead, NULL), VINF_SUCCESS); + + for (size_t offFlush = 0; offFlush < cbToRead; offFlush += PAGE_SIZE) + if (*(size_t volatile *)&pbBuf[offFlush + 8] != cbFlush) + { + RTTestIFailed("Flush issue at offset #%zx: %#zx, expected %#zx (cbFlush=%#zx)", + offBuf, *(size_t volatile *)&pbBuf[offFlush + 8], cbFlush, cbFlush); + if (++cErrors > 32) + break; + } + } + RTMemPageFree(pbBuf, cbBuf); + } + } + } + } + + /* + * Unmap it. + */ +# ifdef RT_OS_WINDOWS + RTTESTI_CHECK(UnmapViewOfFile(pbMapping)); + RTTESTI_CHECK(CloseHandle(hSection)); +# else + RTTESTI_CHECK(munmap(pbMapping, cbMapping) == 0); +# endif + } + + RT_NOREF(hFileNoCache); +#else + RTTestSkipped(g_hTest, "not supported/implemented"); + RT_NOREF(hFile1, hFileNoCache, cbFile); +#endif +} + + +/** + * This does the read, write and seek tests. + */ +void fsPerfIo(void) +{ + RTTestISub("I/O"); + + /* + * Determin the size of the test file. + */ + g_szDir[g_cchDir] = '\0'; + RTFOFF cbFree = 0; + RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS); + uint64_t cbFile = g_cbIoFile; + if (cbFile + _16M < (uint64_t)cbFree) + cbFile = RT_ALIGN_64(cbFile, _64K); + else + { + if (cbFree < _32M) + { + RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 32MB", cbFree); + return; + } + cbFile = cbFree - (cbFree > _128M ? _64M : _16M); + cbFile = RT_ALIGN_64(cbFile, _64K); + RTTestIPrintf(RTTESTLVL_ALWAYS, "Adjusted file size to %'RU64 bytes, due to %'RU64 bytes free.\n", cbFile, cbFree); + } + if (cbFile < _64K) + { + RTTestSkipped(g_hTest, "Specified test file size too small: %'RU64 bytes, requires >= 64KB", cbFile); + return; + } + + /* + * Create a cbFile sized test file. + */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file21")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS); + RTFILE hFileNoCache; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileNoCache, g_szDir, + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_NO_CACHE), + VINF_SUCCESS); + RTFILE hFileWriteThru; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileWriteThru, g_szDir, + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_WRITE_THROUGH), + VINF_SUCCESS); + + uint8_t *pbFree = NULL; + int rc = fsPerfIoPrepFile(hFile1, cbFile, &pbFree); + RTMemFree(pbFree); + if (RT_SUCCESS(rc)) + { + /* + * Do the testing & profiling. + */ + if (g_fSeek) + fsPerfIoSeek(hFile1, cbFile); + if (g_fRead) + { + fsPerfRead(hFile1, hFileNoCache, cbFile); + for (unsigned i = 0; i < g_cIoBlocks; i++) + fsPerfIoReadBlockSize(hFile1, cbFile, g_acbIoBlocks[i]); + } + if (g_fMMap) + fsPerfMMap(hFile1, hFileNoCache, cbFile); + if (g_fWrite) + { + /* This is destructive to the file content. */ + fsPerfWrite(hFile1, hFileNoCache, hFileWriteThru, cbFile); + for (unsigned i = 0; i < g_cIoBlocks; i++) + fsPerfIoWriteBlockSize(hFile1, cbFile, g_acbIoBlocks[i]); + } + if (g_fFSync) + fsPerfFSync(hFile1, cbFile); + } + + RTTESTI_CHECK_RC(RTFileSetSize(hFile1, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFileNoCache), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFileWriteThru), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS); +} + + +static void Usage(PRTSTREAM pStrm) +{ + char szExec[RTPATH_MAX]; + RTStrmPrintf(pStrm, "usage: %s <-d <testdir>> [options]\n", + RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec)))); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "options: \n"); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + char szHelp[80]; + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'd': pszHelp = "The directory to use for testing. Default: CWD/fstestdir"; break; + case 'e': pszHelp = "Enables all tests. Default: -e"; break; + case 'z': pszHelp = "Disables all tests. Default: -e"; break; + case 's': pszHelp = "Set benchmark duration in seconds. Default: 10 sec"; break; + case 'm': pszHelp = "Set benchmark duration in milliseconds. Default: 10000 ms"; break; + case 'v': pszHelp = "More verbose execution."; break; + case 'q': pszHelp = "Quiet execution."; break; + case 'h': pszHelp = "Displays this help and exit"; break; + case 'V': pszHelp = "Displays the program revision"; break; + default: + if (g_aCmdOptions[i].iShort >= kCmdOpt_First) + { + if (RTStrStartsWith(g_aCmdOptions[i].pszLong, "--no-")) + RTStrPrintf(szHelp, sizeof(szHelp), "Disables the '%s' test.", g_aCmdOptions[i].pszLong + 5); + else + RTStrPrintf(szHelp, sizeof(szHelp), "Enables the '%s' test.", g_aCmdOptions[i].pszLong + 2); + pszHelp = szHelp; + } + else + pszHelp = "Option undocumented"; + break; + } + if ((unsigned)g_aCmdOptions[i].iShort < 127U) + { + char szOpt[64]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-20s%s\n", szOpt, pszHelp); + } + else + RTStrmPrintf(pStrm, " %-20s%s\n", g_aCmdOptions[i].pszLong, pszHelp); + } +} + + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT and globals. + */ + int rc = RTTestInitAndCreate("FsPerf", &g_hTest); + if (rc) + return rc; + RTListInit(&g_ManyTreeHead); + + /* + * Default values. + */ + rc = RTPathGetCurrent(g_szDir, sizeof(g_szDir) / 2); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(g_szDir, sizeof(g_szDir) / 2, "fstestdir-"); + if (RT_SUCCESS(rc)) + { + g_cchDir = strlen(g_szDir); + g_cchDir += RTStrPrintf(&g_szDir[g_cchDir], sizeof(g_szDir) - g_cchDir, "%u" RTPATH_SLASH_STR, RTProcSelf()); + } + else + { + RTTestFailed(g_hTest, "RTPathGetCurrent (or RTPathAppend) failed: %Rrc\n", rc); + return RTTestSummaryAndDestroy(g_hTest); + } + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'd': + rc = RTPathAbs(ValueUnion.psz, g_szDir, sizeof(g_szDir) / 2); + if (RT_SUCCESS(rc)) + break; + RTTestFailed(g_hTest, "RTPathAbs(%s) failed: %Rrc\n", ValueUnion.psz, rc); + return RTTestSummaryAndDestroy(g_hTest); + + case 's': + if (ValueUnion.u32 == 0) + g_nsTestRun = RT_NS_1SEC_64 * 10; + else + g_nsTestRun = ValueUnion.u32 * RT_NS_1SEC_64; + break; + + case 'm': + if (ValueUnion.u64 == 0) + g_nsTestRun = RT_NS_1SEC_64 * 10; + else + g_nsTestRun = ValueUnion.u64 * RT_NS_1MS; + break; + + case 'e': + g_fManyFiles = true; + g_fOpen = true; + g_fFStat = true; + g_fFChMod = true; + g_fFUtimes = true; + g_fStat = true; + g_fChMod = true; + g_fUtimes = true; + g_fRename = true; + g_fDirEnum = true; + g_fMkRmDir = true; + g_fStatVfs = true; + g_fRm = true; + g_fChSize = true; + g_fRead = true; + g_fWrite = true; + g_fSeek = true; + g_fFSync = true; + g_fMMap = true; + break; + + case 'z': + g_fManyFiles = false; + g_fOpen = false; + g_fFStat = false; + g_fFChMod = false; + g_fFUtimes = false; + g_fStat = false; + g_fChMod = false; + g_fUtimes = false; + g_fRename = false; + g_fDirEnum = false; + g_fMkRmDir = false; + g_fStatVfs = false; + g_fRm = false; + g_fChSize = false; + g_fRead = false; + g_fWrite = false; + g_fSeek = false; + g_fFSync = false; + g_fMMap = false; + break; + +#define CASE_OPT(a_Stem) \ + case RT_CONCAT(kCmdOpt_,a_Stem): RT_CONCAT(g_f,a_Stem) = true; break; \ + case RT_CONCAT(kCmdOpt_No,a_Stem): RT_CONCAT(g_f,a_Stem) = false; break + CASE_OPT(ManyFiles); + CASE_OPT(Open); + CASE_OPT(FStat); + CASE_OPT(FChMod); + CASE_OPT(FUtimes); + CASE_OPT(Stat); + CASE_OPT(ChMod); + CASE_OPT(Utimes); + CASE_OPT(Rename); + CASE_OPT(DirEnum); + CASE_OPT(MkRmDir); + CASE_OPT(StatVfs); + CASE_OPT(Rm); + CASE_OPT(ChSize); + CASE_OPT(Read); + CASE_OPT(Write); + CASE_OPT(Seek); + CASE_OPT(FSync); + CASE_OPT(MMap); +#undef CASE_OPT + + case 'q': + g_uVerbosity = 0; + break; + + case 'v': + g_uVerbosity++; + break; + + case 'h': + Usage(g_pStdOut); + return RTEXITCODE_SUCCESS; + + case 'V': + { + char szRev[] = "$Revision: 128290 $"; + szRev[RT_ELEMENTS(szRev) - 2] = '\0'; + RTPrintf(RTStrStrip(strchr(szRev, ':') + 1)); + return RTEXITCODE_SUCCESS; + } + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Create the test directory with an 'empty' subdirectory under it, + * execute the tests, and remove directory when done. + */ + RTTestBanner(g_hTest); + if (!RTPathExists(g_szDir)) + { + /* The base dir: */ + rc = RTDirCreate(g_szDir, 0755, + RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); + if (RT_SUCCESS(rc)) + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "Test dir: %s\n", g_szDir); + rc = fsPrepTestArea(); + if (RT_SUCCESS(rc)) + { + /* Profile RTTimeNanoTS(). */ + fsPerfNanoTS(); + + /* Do tests: */ + if (g_fManyFiles) + fsPerfManyFiles(); + if (g_fOpen) + fsPerfOpen(); + if (g_fFStat) + fsPerfFStat(); + if (g_fFChMod) + fsPerfFChMod(); + if (g_fFUtimes) + fsPerfFUtimes(); + if (g_fStat) + fsPerfStat(); + if (g_fChMod) + fsPerfChmod(); + if (g_fUtimes) + fsPerfUtimes(); + if (g_fRename) + fsPerfRename(); + if (g_fDirEnum) + vsPerfDirEnum(); + if (g_fMkRmDir) + fsPerfMkRmDir(); + if (g_fStatVfs) + fsPerfStatVfs(); + if (g_fRm || g_fManyFiles) + fsPerfRm(); /* deletes manyfiles and manytree */ + if (g_fChSize) + fsPerfChSize(); + if (g_fRead || g_fWrite || g_fSeek || g_fFSync || g_fMMap) + fsPerfIo(); + } + + /* Cleanup: */ + g_szDir[g_cchDir] = '\0'; + rc = RTDirRemoveRecursive(g_szDir, RTDIRRMREC_F_CONTENT_AND_DIR); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "RTDirRemoveRecursive(%s,) -> %Rrc\n", g_szDir, rc); + } + else + RTTestFailed(g_hTest, "RTDirCreate(%s) -> %Rrc\n", g_szDir, rc); + } + else + RTTestFailed(g_hTest, "Test directory already exists: %s\n", g_szDir); + + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/ValidationKit/utils/fs/Makefile.kmk b/src/VBox/ValidationKit/utils/fs/Makefile.kmk new file mode 100644 index 00000000..a51e8c54 --- /dev/null +++ b/src/VBox/ValidationKit/utils/fs/Makefile.kmk @@ -0,0 +1,39 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - File system tests. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# File System Performance (think shared folders). +# +PROGRAMS += FsPerf +FsPerf_TEMPLATE = VBoxValidationKitR3 +FsPerf_SOURCES = FsPerf.cpp + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/misc/Makefile.kmk b/src/VBox/ValidationKit/utils/misc/Makefile.kmk new file mode 100644 index 00000000..5d90b09c --- /dev/null +++ b/src/VBox/ValidationKit/utils/misc/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Miscellaneous Utilites. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +PROGRAMS += LoadGenerator +LoadGenerator_TEMPLATE = VBoxValidationKitR3Host +LoadGenerator_SOURCES = loadgenerator.cpp + +SYSMODS += loadgeneratorR0 +loadgeneratorR0_TEMPLATE = VBoxValidationKitR0 +loadgeneratorR0_SOURCES = loadgeneratorR0.cpp + +PROGRAMS += vts_rm +vts_rm_TEMPLATE = VBoxValidationKitR3 +vts_rm_SOURCES = vts_rm.cpp + +PROGRAMS += vts_tar +vts_tar_TEMPLATE = VBoxValidationKitR3 +vts_tar_SDKS = VBOX_ZLIB_STATIC +vts_tar_SOURCES = vts_tar.cpp + +PROGRAMS += vts_shutdown +vts_shutdown_TEMPLATE = VBoxValidationKitR3 +vts_shutdown_SOURCES = ../../../Runtime/tools/RTShutdown.cpp + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/misc/loadgenerator.cpp b/src/VBox/ValidationKit/utils/misc/loadgenerator.cpp new file mode 100644 index 00000000..d6ce5cb1 --- /dev/null +++ b/src/VBox/ValidationKit/utils/misc/loadgenerator.cpp @@ -0,0 +1,381 @@ +/* $Id: loadgenerator.cpp $ */ +/** @file + * Load Generator. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/errcore.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/initterm.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/mp.h> +#include <iprt/asm.h> +#include <iprt/getopt.h> +#include <VBox/sup.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Whether the threads should quit or not. */ +static bool volatile g_fQuit = false; +static const char *g_pszProgramName = NULL; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int Error(const char *pszFormat, ...); + + +static void LoadGenSpin(uint64_t cNanoSeconds) +{ + const uint64_t u64StartTS = RTTimeNanoTS(); + do + { + for (uint32_t volatile i = 0; i < 10240 && !g_fQuit; i++) + i++; + } while (RTTimeNanoTS() - u64StartTS < cNanoSeconds && !g_fQuit); +} + + +static DECLCALLBACK(int) LoadGenSpinThreadFunction(RTTHREAD hThreadSelf, void *pvUser) +{ + NOREF(hThreadSelf); + LoadGenSpin(*(uint64_t *)pvUser); + return VINF_SUCCESS; +} + + +static int LoadGenIpiInit(void) +{ + /* + * Try make sure the support library is initialized... + */ + SUPR3Init(NULL); + + /* + * Load the module. + */ + char szPath[RTPATH_MAX]; + int rc = RTPathAppPrivateArchTop(szPath, sizeof(szPath) - sizeof("/loadgenerator.r0")); + if (RT_SUCCESS(rc)) + { + strcat(szPath, "/loadgeneratorR0.r0"); + void *pvImageBase; + rc = SUPR3LoadServiceModule(szPath, "loadgeneratorR0", "LoadGenR0ServiceReqHandler", &pvImageBase); + if (RT_SUCCESS(rc)) + { + /* done */ + } + else + Error("SUPR3LoadServiceModule(%s): %Rrc\n", szPath, rc); + } + else + Error("RTPathAppPrivateArch: %Rrc\n", rc); + return rc; +} + + +static void LoadGenIpi(uint64_t cNanoSeconds) +{ + const uint64_t u64StartTS = RTTimeNanoTS(); + do + { + int rc = SUPR3CallR0Service("loadgeneratorR0", sizeof("loadgeneratorR0") - 1, + 0 /* uOperation */, 1 /* cIpis */, NULL /* pReqHdr */); + if (RT_FAILURE(rc)) + { + Error("SUPR3CallR0Service: %Rrc\n", rc); + break; + } + } while (RTTimeNanoTS() - u64StartTS < cNanoSeconds && !g_fQuit); +} + + +static DECLCALLBACK(int) LoadGenIpiThreadFunction(RTTHREAD hThreadSelf, void *pvUser) +{ + LoadGenIpi(*(uint64_t *)pvUser); + NOREF(hThreadSelf); + return VINF_SUCCESS; +} + + +static int Error(const char *pszFormat, ...) +{ + va_list va; + RTStrmPrintf(g_pStdErr, "%s: error: ", g_pszProgramName); + va_start(va, pszFormat); + RTStrmPrintfV(g_pStdErr, pszFormat, va); + va_end(va); + return 1; +} + + +static int SyntaxError(const char *pszFormat, ...) +{ + va_list va; + RTStrmPrintf(g_pStdErr, "%s: syntax error: ", g_pszProgramName); + va_start(va, pszFormat); + RTStrmPrintfV(g_pStdErr, pszFormat, va); + va_end(va); + return 1; +} + + +int main(int argc, char **argv) +{ + static const struct LOADGENTYPE + { + const char *pszName; + int (*pfnInit)(void); + PFNRTTHREAD pfnThread; + } s_aLoadTypes[] = + { + { "spin", NULL, LoadGenSpinThreadFunction }, + { "ipi", LoadGenIpiInit, LoadGenIpiThreadFunction }, + }; + unsigned iLoadType = 0; + static RTTHREAD s_aThreads[256]; + int rc; + uint32_t cThreads = 1; + bool fScaleByCpus = false; + RTTHREADTYPE enmThreadType = RTTHREADTYPE_DEFAULT; + RTPROCPRIORITY enmProcPriority = RTPROCPRIORITY_DEFAULT; + uint64_t cNanoSeconds = UINT64_MAX; + + RTR3InitExe(argc, &argv, 0); + + /* + * Set program name. + */ + g_pszProgramName = RTPathFilename(argv[0]); + + /* + * Parse arguments. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--number-of-threads", 'n', RTGETOPT_REQ_UINT32 }, + { "--timeout", 't', RTGETOPT_REQ_STRING }, + { "--thread-type", 'p', RTGETOPT_REQ_STRING }, + { "--scale-by-cpus", 'c', RTGETOPT_REQ_NOTHING }, + { "--load", 'l', RTGETOPT_REQ_STRING }, + }; + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'n': + cThreads = ValueUnion.u64; + if (cThreads == 0 || cThreads > RT_ELEMENTS(s_aThreads)) + return SyntaxError("Requested number of threads, %RU32, is out of range (1..%d).\n", + cThreads, RT_ELEMENTS(s_aThreads) - 1); + break; + + case 't': + { + char *psz; + rc = RTStrToUInt64Ex(ValueUnion.psz, &psz, 0, &cNanoSeconds); + if (RT_FAILURE(rc)) + return SyntaxError("Failed reading the alleged number '%s' (option '%s', rc=%Rrc).\n", + ValueUnion.psz, rc); + while (*psz == ' ' || *psz == '\t') + psz++; + if (*psz) + { + uint64_t u64Factor = 1; + if (!strcmp(psz, "ns")) + u64Factor = 1; + else if (!strcmp(psz, "ms")) + u64Factor = 1000; + else if (!strcmp(psz, "s")) + u64Factor = 1000000000; + else if (!strcmp(psz, "m")) + u64Factor = UINT64_C(60000000000); + else if (!strcmp(psz, "h")) + u64Factor = UINT64_C(3600000000000); + else + return SyntaxError("Unknown time suffix '%s'\n", psz); + uint64_t u64 = cNanoSeconds * u64Factor; + if (u64 < cNanoSeconds || (u64 < u64Factor && u64)) + return SyntaxError("Time representation overflowed! (%RU64 * %RU64)\n", + psz, cNanoSeconds, u64Factor); + cNanoSeconds = u64; + } + break; + } + + case 'p': + { + enmProcPriority = RTPROCPRIORITY_NORMAL; + + uint32_t u32 = RTTHREADTYPE_INVALID; + char *psz; + rc = RTStrToUInt32Ex(ValueUnion.psz, &psz, 0, &u32); + if (RT_FAILURE(rc) || *psz) + { + if (!strcmp(ValueUnion.psz, "default")) + { + enmProcPriority = RTPROCPRIORITY_DEFAULT; + enmThreadType = RTTHREADTYPE_DEFAULT; + } + else if (!strcmp(ValueUnion.psz, "idle")) + { + enmProcPriority = RTPROCPRIORITY_LOW; + enmThreadType = RTTHREADTYPE_INFREQUENT_POLLER; + } + else if (!strcmp(ValueUnion.psz, "high")) + { + enmProcPriority = RTPROCPRIORITY_HIGH; + enmThreadType = RTTHREADTYPE_IO; + } + else + return SyntaxError("can't grok thread type '%s'\n", + ValueUnion.psz); + } + else + { + enmThreadType = (RTTHREADTYPE)u32; + if (enmThreadType <= RTTHREADTYPE_INVALID || enmThreadType >= RTTHREADTYPE_END) + return SyntaxError("thread type '%d' is out of range (%d..%d)\n", + ValueUnion.psz, RTTHREADTYPE_INVALID + 1, RTTHREADTYPE_END - 1); + } + break; + } + + case 'c': + fScaleByCpus = true; + break; + + case 'l': + { + for (unsigned i = 0; i < RT_ELEMENTS(s_aLoadTypes); i++) + if (!strcmp(s_aLoadTypes[i].pszName, ValueUnion.psz)) + { + ValueUnion.psz = NULL; + iLoadType = i; + break; + } + if (ValueUnion.psz) + return SyntaxError("Unknown load type '%s'.\n", ValueUnion.psz); + break; + } + + case 'h': + RTStrmPrintf(g_pStdOut, + "Usage: %s [-p|--thread-type <type>] [-t|--timeout <sec|xxx[h|m|s|ms|ns]>] \\\n" + " %*s [-n|--number-of-threads <threads>] [-l|--load <loadtype>]\n" + "\n" + "Load types: spin, ipi.\n" + , + g_pszProgramName, strlen(g_pszProgramName), ""); + return 1; + + case 'V': + RTPrintf("$Revision: 127855 $\n"); + return 0; + + case VINF_GETOPT_NOT_OPTION: + return SyntaxError("Unknown argument #%d: '%s'\n", GetState.iNext-1, ValueUnion.psz); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + /* + * Scale thread count by host cpu count. + */ + if (fScaleByCpus) + { + const unsigned cCpus = RTMpGetOnlineCount(); + if (cCpus * cThreads > RT_ELEMENTS(s_aThreads)) + return SyntaxError("Requested number of threads, %RU32, is out of range (1..%d) when scaled by %d.\n", + cThreads, RT_ELEMENTS(s_aThreads) - 1, cCpus); + cThreads *= cCpus; + } + + /* + * Modify process and thread priority? (ignore failure) + */ + if (enmProcPriority != RTPROCPRIORITY_DEFAULT) + RTProcSetPriority(enmProcPriority); + if (enmThreadType != RTTHREADTYPE_DEFAULT) + RTThreadSetType(RTThreadSelf(), enmThreadType); + + /* + * Load type specific init. + */ + if (s_aLoadTypes[iLoadType].pfnInit) + { + rc = s_aLoadTypes[iLoadType].pfnInit(); + if (RT_FAILURE(rc)) + return 1; + } + + + /* + * Start threads. + */ + for (unsigned i = 1; i < cThreads; i++) + { + s_aThreads[i] = NIL_RTTHREAD; + rc = RTThreadCreate(&s_aThreads[i], s_aLoadTypes[iLoadType].pfnThread, + &cNanoSeconds, 128*1024, enmThreadType, RTTHREADFLAGS_WAITABLE, "spinner"); + if (RT_FAILURE(rc)) + { + ASMAtomicXchgBool(&g_fQuit, true); + RTStrmPrintf(g_pStdErr, "%s: failed to create thread #%d, rc=%Rrc\n", g_pszProgramName, i, rc); + while (i-- > 1) + RTThreadWait(s_aThreads[i], 1500, NULL); + return 1; + } + } + + /* our selves */ + s_aLoadTypes[iLoadType].pfnThread(RTThreadSelf(), &cNanoSeconds); + + /* + * Wait for threads. + */ + ASMAtomicXchgBool(&g_fQuit, true); + for (unsigned i = 1; i < cThreads; i++) + RTThreadWait(s_aThreads[i], 1500, NULL); + + return 0; +} + diff --git a/src/VBox/ValidationKit/utils/misc/loadgeneratorR0.cpp b/src/VBox/ValidationKit/utils/misc/loadgeneratorR0.cpp new file mode 100644 index 00000000..b45638af --- /dev/null +++ b/src/VBox/ValidationKit/utils/misc/loadgeneratorR0.cpp @@ -0,0 +1,91 @@ +/* $Id: loadgeneratorR0.cpp $ */ +/** @file + * Load Generator, Ring-0 Service. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/mp.h> +#include <VBox/sup.h> +#include <iprt/errcore.h> + + + + +/** + * Worker for loadgenR0Ipi. + */ +static DECLCALLBACK(void) loadgenR0IpiWorker(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + NOREF(idCpu); + NOREF(pvUser1); + NOREF(pvUser2); +} + + +/** + * Generate broadcast inter processor interrupts (IPI), aka cross calls. + * + * @returns VBox status code. + * @param cIpis The number of IPIs to do. + */ +static int loadgenR0Ipi(uint64_t cIpis) +{ + if (cIpis > _1G || !cIpis) + return VERR_INVALID_PARAMETER; + + while (cIpis-- > 0) + { + int rc = RTMpOnAll(loadgenR0IpiWorker, NULL, NULL); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +/** + * Service request handler entry point. + * + * @copydoc FNSUPR0SERVICEREQHANDLER + */ +extern "C" DECLEXPORT(int) LoadGenR0ServiceReqHandler(PSUPDRVSESSION pSession, uint32_t uOperation, + uint64_t u64Arg, PSUPR0SERVICEREQHDR pReqHdr) + +{ + switch (uOperation) + { + case 0: + if (pReqHdr) + return VERR_INVALID_PARAMETER; + return loadgenR0Ipi(u64Arg); + + default: + NOREF(pSession); + return VERR_NOT_SUPPORTED; + } +} + diff --git a/src/VBox/ValidationKit/utils/misc/vts_rm.cpp b/src/VBox/ValidationKit/utils/misc/vts_rm.cpp new file mode 100644 index 00000000..646905fa --- /dev/null +++ b/src/VBox/ValidationKit/utils/misc/vts_rm.cpp @@ -0,0 +1,44 @@ +/* $Id: vts_rm.cpp $ */ +/** @file + * VirtualBox Validation Kit - rm like utility. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/path.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/message.h> + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + return RTPathRmCmd(argc, argv); +} + diff --git a/src/VBox/ValidationKit/utils/misc/vts_tar.cpp b/src/VBox/ValidationKit/utils/misc/vts_tar.cpp new file mode 100644 index 00000000..bcc8c3c7 --- /dev/null +++ b/src/VBox/ValidationKit/utils/misc/vts_tar.cpp @@ -0,0 +1,44 @@ +/* $Id: vts_tar.cpp $ */ +/** @file + * VirtualBox Validation Kit - tar like utility. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/zip.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/message.h> + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + return RTZipTarCmd(argc, argv); +} + diff --git a/src/VBox/ValidationKit/utils/network/Makefile.kmk b/src/VBox/ValidationKit/utils/network/Makefile.kmk new file mode 100644 index 00000000..0b89b643 --- /dev/null +++ b/src/VBox/ValidationKit/utils/network/Makefile.kmk @@ -0,0 +1,39 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Network tests. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Network Performance Benchmark. +# +PROGRAMS += NetPerf +NetPerf_TEMPLATE = VBoxValidationKitR3 +NetPerf_SOURCES = NetPerf.cpp + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/network/NetPerf.cpp b/src/VBox/ValidationKit/utils/network/NetPerf.cpp new file mode 100644 index 00000000..182f8b47 --- /dev/null +++ b/src/VBox/ValidationKit/utils/network/NetPerf.cpp @@ -0,0 +1,1954 @@ +/* $Id: NetPerf.cpp $ */ +/** @file + * NetPerf - Network Performance Benchmark. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/tcp.h> +#include <iprt/thread.h> +#include <iprt/test.h> +#include <iprt/time.h> +#include <iprt/timer.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Default TCP port (update help text if you change this) */ +#define NETPERF_DEFAULT_PORT 5002 + +/** Default TCP packet size (bytes) */ +#define NETPERF_DEFAULT_PKT_SIZE_THROUGHPUT 8192 +/** Default TCP packet size (bytes) */ +#define NETPERF_DEFAULT_PKT_SIZE_LATENCY 1024 +/** Maximum packet size possible (bytes). */ +#define NETPERF_MAX_PKT_SIZE _1M +/** Minimum packet size possible (bytes). */ +#define NETPERF_MIN_PKT_SIZE sizeof(NETPERFHDR) + +/** Default timeout in (seconds) */ +#define NETPERF_DEFAULT_TIMEOUT 10 +/** Maximum timeout possible (seconds). */ +#define NETPERF_MAX_TIMEOUT 3600 /* 1h */ +/** Minimum timeout possible (seconds). */ +#define NETPERF_MIN_TIMEOUT 1 + +/** The default warmup time (ms). */ +#define NETPERF_DEFAULT_WARMUP 1000 /* 1s */ +/** The maxium warmup time (ms). */ +#define NETPERF_MAX_WARMUP 60000 /* 60s */ +/** The minimum warmup time (ms). */ +#define NETPERF_MIN_WARMUP 1000 /* 1s */ + +/** The default cool down time (ms). */ +#define NETPERF_DEFAULT_COOL_DOWN 1000 /* 1s */ +/** The maxium cool down time (ms). */ +#define NETPERF_MAX_COOL_DOWN 60000 /* 60s */ +/** The minimum cool down time (ms). */ +#define NETPERF_MIN_COOL_DOWN 1000 /* 1s */ + +/** The length of the length prefix used when submitting parameters and + * results. */ +#define NETPERF_LEN_PREFIX 4 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef enum NETPERFPROTO +{ + NETPERFPROTO_INVALID = 0, + NETPERFPROTO_TCP + //NETPERFPROTO_UDP +} NETPERFPROTO; + +/** + * What kind of test we're performing. + */ +typedef enum NETPERFMODE +{ + NETPERFMODE_INVALID = 0, + /** Latency of a symmetric packet exchange. */ + NETPERFMODE_LATENCY, + /** Separate throughput measurements for each direction. */ + NETPERFMODE_THROUGHPUT, + /** Transmit throughput. */ + NETPERFMODE_THROUGHPUT_XMIT, + /** Transmit throughput. */ + NETPERFMODE_THROUGHPUT_RECV +} NETPERFMODE; + +/** + * Statistics. + */ +typedef struct NETPERFSTATS +{ + uint64_t cTx; + uint64_t cRx; + uint64_t cEchos; + uint64_t cErrors; + uint64_t cNsElapsed; +} NETPERFSTATS; + +/** + * Settings & a little bit of state. + */ +typedef struct NETPERFPARAMS +{ + /** @name Static settings + * @{ */ + /** The TCP port number. */ + uint32_t uPort; + /** Client: Use server statistcs. */ + bool fServerStats; + /** Server: Quit after the first client. */ + bool fSingleClient; + /** @} */ + + /** @name Dynamic settings + * @{ */ + /** Disable send packet coalescing. */ + bool fNoDelay; + /** Detect broken payloads. */ + bool fCheckData; + /** The test mode. */ + NETPERFMODE enmMode; + /** The number of seconds to run each of the test steps. */ + uint32_t cSecTimeout; + /** Number of millisecond to spend warning up before testing. */ + uint32_t cMsWarmup; + /** Number of millisecond to spend cooling down after the testing. */ + uint32_t cMsCoolDown; + /** The packet size. */ + uint32_t cbPacket; + /** @} */ + + /** @name State + * @{ */ + RTSOCKET hSocket; + /** @} */ +} NETPERFPARAMS; + +/** + * Packet header used in tests. + * + * Need to indicate when we've timed out and it's time to reverse the roles or + * stop testing. + */ +typedef struct NETPERFHDR +{ + /** Magic value (little endian). */ + uint32_t u32Magic; + /** State value. */ + uint32_t u32State; + /** Sequence number (little endian). */ + uint32_t u32Seq; + /** Reserved, must be zero. */ + uint32_t u32Reserved; +} NETPERFHDR; + +/** Magic value for NETPERFHDR::u32Magic. */ +#define NETPERFHDR_MAGIC UINT32_C(0xfeedf00d) + +/** @name Packet State (NETPERF::u32Magic) + * @{ */ +/** Warm up. */ +#define NETPERFHDR_WARMUP UINT32_C(0x0c0ffe01) +/** The clock is running. */ +#define NETPERFHDR_TESTING UINT32_C(0x0c0ffe02) +/** Stop the clock but continue the package flow. */ +#define NETPERFHDR_COOL_DOWN UINT32_C(0x0c0ffe03) +/** Done, stop the clock if not done already and reply with results. */ +#define NETPERFHDR_DONE UINT32_C(0x0c0ffe04) +/** @} */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Connection start/identifier to make sure other end is NetPerf. */ +static const char g_ConnectStart[] = "yo! waaazzzzzaaaaup dude?"; +/** Start of parameters proposal made by the client. */ +static const char g_szStartParams[] = "deal?"; +/** All okay to start test */ +static const char g_szAck[] = "okay!"; +/** Negative. */ +static const char g_szNegative[] = "nope!"; +AssertCompile(sizeof(g_szAck) == sizeof(g_szNegative)); +/** Start of statistics. */ +static const char g_szStartStats[] = "dude, stats"; + +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + { "--server", 's', RTGETOPT_REQ_NOTHING }, + { "--client", 'c', RTGETOPT_REQ_STRING }, + { "--interval", 'i', RTGETOPT_REQ_UINT32 }, + { "--port", 'p', RTGETOPT_REQ_UINT32 }, + { "--len", 'l', RTGETOPT_REQ_UINT32 }, + { "--nodelay", 'N', RTGETOPT_REQ_NOTHING }, + { "--mode", 'm', RTGETOPT_REQ_STRING }, + { "--warmup", 'w', RTGETOPT_REQ_UINT32 }, + { "--cool-down", 'W', RTGETOPT_REQ_UINT32 }, + { "--server-stats", 'S', RTGETOPT_REQ_NOTHING }, + { "--single-client", '1', RTGETOPT_REQ_NOTHING }, + { "--daemonize", 'd', RTGETOPT_REQ_NOTHING }, + { "--daemonized", 'D', RTGETOPT_REQ_NOTHING }, + { "--check-data", 'C', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */ +}; + +/** The test handle. */ +static RTTEST g_hTest; +/** Verbosity level. */ +static uint32_t g_uVerbosity = 0; + + + +static void Usage(PRTSTREAM pStrm) +{ + char szExec[RTPATH_MAX]; + RTStrmPrintf(pStrm, "usage: %s <-s|-c <host>> [options]\n", + RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec)))); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "options: \n"); + + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'h': + pszHelp = "Displays this help and exit"; + break; + case 's': + pszHelp = "Run in server mode, waiting for clients (default)"; + break; + case 'c': + pszHelp = "Run in client mode, connecting to <host>"; + break; + case 'i': + pszHelp = "Interval in seconds to run the test (default " RT_XSTR(NETPERF_DEFAULT_TIMEOUT) " s)"; + break; + case 'p': + pszHelp = "Server port to listen/connect to (default " RT_XSTR(NETPERF_DEFAULT_PORT) ")"; + break; + case 'l': + pszHelp = "Packet size in bytes (defaults to " RT_XSTR(NETPERF_DEFAULT_PKT_SIZE_LATENCY) + " for latency and " RT_XSTR(NETPERF_DEFAULT_PKT_SIZE_THROUGHPUT) " for throughput)"; + break; + case 'm': + pszHelp = "Test mode: latency (default), throughput, throughput-xmit or throughput-recv"; + break; + case 'N': + pszHelp = "Set TCP no delay, disabling Nagle's algorithm"; + break; + case 'S': + pszHelp = "Report server stats, ignored if server"; + break; + case '1': + pszHelp = "Stop the server after the first client"; + break; + case 'd': + pszHelp = "Daemonize if server, ignored if client"; + break; + case 'D': + continue; /* internal */ + case 'w': + pszHelp = "Warmup time, in milliseconds (default " RT_XSTR(NETPERF_DEFAULT_WARMUP) " ms)"; + break; + case 'W': + pszHelp = "Cool down time, in milliseconds (default " RT_XSTR(NETPERF_DEFAULT_COOL_DOWN) " ms)"; + break; + case 'C': + pszHelp = "Check payload data at the receiving end"; + break; + case 'v': + pszHelp = "Verbose execution."; + break; + default: + pszHelp = "Option undocumented"; + break; + } + char szOpt[256]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-20s%s\n", szOpt, pszHelp); + } +} + +/** + * Timer callback employed to set the stop indicator. + * + * This is used both by the client and server side. + * + * @param hTimer The timer, ignored. + * @param pvUser Pointer to the stop variable. + * @param iTick The tick, ignored. + */ +static DECLCALLBACK(void) netperfStopTimerCallback(RTTIMERLR hTimer, void *pvUser, uint64_t iTick) +{ + bool volatile *pfStop = (bool volatile *)pvUser; + if (g_uVerbosity > 0) + RTPrintf("Time's Up!\n"); + ASMAtomicWriteBool(pfStop, true); + NOREF(hTimer); NOREF(iTick); +} + +/** + * Sends a statistics packet to our peer. + * + * @returns IPRT status code. + * @param pStats The stats to send. + * @param hSocket The TCP socket to send them to. + */ +static int netperfSendStats(NETPERFSTATS const *pStats, RTSOCKET hSocket) +{ + char szBuf[256 + NETPERF_LEN_PREFIX]; + size_t cch = RTStrPrintf(&szBuf[NETPERF_LEN_PREFIX], sizeof(szBuf) - NETPERF_LEN_PREFIX, + "%s:%llu:%llu:%llu:%llu:%llu", + g_szStartStats, + pStats->cTx, + pStats->cRx, + pStats->cEchos, + pStats->cErrors, + pStats->cNsElapsed); + + RTStrPrintf(szBuf, NETPERF_LEN_PREFIX + 1, "%0*u", NETPERF_LEN_PREFIX, cch); + szBuf[NETPERF_LEN_PREFIX] = g_szStartStats[0]; + Assert(strlen(szBuf) == cch + NETPERF_LEN_PREFIX); + + int rc = RTTcpWrite(hSocket, szBuf, cch + NETPERF_LEN_PREFIX); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "stats: Failed to send stats: %Rrc\n", rc); + + /* + * Wait for ACK. + */ + rc = RTTcpRead(hSocket, szBuf, sizeof(g_szAck) - 1, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "stats: failed to write stats: %Rrc\n", rc); + szBuf[sizeof(g_szAck) - 1] = '\0'; + if (!strcmp(szBuf, g_szNegative)) + return RTTestIFailedRc(rc, "stats: client failed to parse them\n"); + if (strcmp(szBuf, g_szAck)) + return RTTestIFailedRc(rc, "stats: got '%s' in instead of ack/nack\n", szBuf); + + return VINF_SUCCESS; +} + +/** + * Receives a statistics packet from our peer. + * + * @returns IPRT status code. Error signalled. + * @param pStats Where to receive the stats. + * @param hSocket The TCP socket to recevie them from. + */ +static int netperfRecvStats(NETPERFSTATS *pStats, RTSOCKET hSocket) +{ + /* + * Read the stats message. + */ + /* the length prefix */ + char szBuf[256 + NETPERF_LEN_PREFIX]; + int rc = RTTcpRead(hSocket, szBuf, NETPERF_LEN_PREFIX, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "stats: failed to read stats prefix: %Rrc\n", rc); + + szBuf[NETPERF_LEN_PREFIX] = '\0'; + uint32_t cch; + rc = RTStrToUInt32Full(szBuf, 10, &cch); + if (rc != VINF_SUCCESS) + return RTTestIFailedRc(RT_SUCCESS(rc) ? -rc : rc, "stats: bad stat length prefix: '%s' - %Rrc\n", szBuf, rc); + if (cch >= sizeof(szBuf)) + return RTTestIFailedRc(VERR_BUFFER_OVERFLOW, "stats: too large: %u bytes\n", cch); + + /* the actual message */ + rc = RTTcpRead(hSocket, szBuf, cch, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "failed to read stats: %Rrc\n", rc); + szBuf[cch] = '\0'; + + /* + * Validate the message header. + */ + if ( strncmp(szBuf, g_szStartStats, sizeof(g_szStartStats) - 1) + || szBuf[sizeof(g_szStartStats) - 1] != ':') + return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "stats: invalid packet start: '%s'\n", szBuf); + char *pszCur = &szBuf[sizeof(g_szStartStats)]; + + /* + * Parse it. + */ + static const char * const s_apszNames[] = + { + "cTx", "cRx", "cEchos", "cErrors", "cNsElapsed" + }; + uint64_t *apu64[RT_ELEMENTS(s_apszNames)] = + { + &pStats->cTx, + &pStats->cRx, + &pStats->cEchos, + &pStats->cErrors, + &pStats->cNsElapsed + }; + + for (unsigned i = 0; i < RT_ELEMENTS(apu64); i++) + { + if (!pszCur) + return RTTestIFailedRc(VERR_PARSE_ERROR, "stats: missing %s\n", s_apszNames[i]); + + char *pszNext = strchr(pszCur, ':'); + if (pszNext) + *pszNext++ = '\0'; + rc = RTStrToUInt64Full(pszCur, 10, apu64[i]); + if (rc != VINF_SUCCESS) + return RTTestIFailedRc(RT_SUCCESS(rc) ? -rc : rc, "stats: bad value for %s: '%s' - %Rrc\n", + s_apszNames[i], pszCur, rc); + + pszCur = pszNext; + } + + if (pszCur) + return RTTestIFailedRc(VERR_PARSE_ERROR, "stats: Unparsed data: '%s'\n", pszCur); + + /* + * Send ACK. + */ + rc = RTTcpWrite(hSocket, g_szAck, sizeof(g_szAck) - 1); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "stats: failed to write ack: %Rrc\n", rc); + + return VINF_SUCCESS; +} + +/** + * TCP Throughput: Print the statistics. + * + * @param pSendStats Send stats. + * @param pRecvStats Receive stats. + * @param cbPacket Packet size. + */ +static void netperfPrintThroughputStats(NETPERFSTATS const *pSendStats, NETPERFSTATS const *pRecvStats, uint32_t cbPacket) +{ + RTTestIValue("Packet size", cbPacket, RTTESTUNIT_BYTES); + + if (pSendStats) + { + double rdSecElapsed = (double)pSendStats->cNsElapsed / 1000000000.0; + RTTestIValue("Sends", pSendStats->cTx, RTTESTUNIT_PACKETS); + RTTestIValue("Send Interval", pSendStats->cNsElapsed, RTTESTUNIT_NS); + RTTestIValue("Send Throughput", (uint64_t)(cbPacket * pSendStats->cTx / rdSecElapsed), RTTESTUNIT_BYTES_PER_SEC); + RTTestIValue("Send Rate", (uint64_t)(pSendStats->cTx / rdSecElapsed), RTTESTUNIT_PACKETS_PER_SEC); + RTTestIValue("Send Latency", (uint64_t)(rdSecElapsed / pSendStats->cTx * 1000000000.0), RTTESTUNIT_NS_PER_PACKET); + } + + if (pRecvStats) + { + double rdSecElapsed = (double)pRecvStats->cNsElapsed / 1000000000.0; + RTTestIValue("Receives", pRecvStats->cRx, RTTESTUNIT_PACKETS); + RTTestIValue("Receive Interval", pRecvStats->cNsElapsed, RTTESTUNIT_NS); + RTTestIValue("Receive Throughput", (uint64_t)(cbPacket * pRecvStats->cRx / rdSecElapsed), RTTESTUNIT_BYTES_PER_SEC); + RTTestIValue("Receive Rate", (uint64_t)(pRecvStats->cRx / rdSecElapsed), RTTESTUNIT_PACKETS_PER_SEC); + RTTestIValue("Receive Latency", (uint64_t)(rdSecElapsed / pRecvStats->cRx * 1000000000.0), RTTESTUNIT_NS_PER_PACKET); + } +} + +/** + * TCP Throughput: Send data to the other party. + * + * @returns IPRT status code. + * @param pParams The TCP parameters block. + * @param pBuf The buffer we're using when sending. + * @param pSendStats Where to return the statistics. + */ +static int netperfTCPThroughputSend(NETPERFPARAMS const *pParams, NETPERFHDR *pBuf, NETPERFSTATS *pSendStats) +{ + RT_ZERO(*pSendStats); + + /* + * Create the timer + */ + RTTIMERLR hTimer; + bool volatile fStop = false; + int rc = RTTimerLRCreateEx(&hTimer, 0 /* nsec */, RTTIMER_FLAGS_CPU_ANY, netperfStopTimerCallback, (void *)&fStop); + if (RT_SUCCESS(rc)) + { + uint32_t u32Seq = 0; + + RT_BZERO(pBuf, pParams->cbPacket); + pBuf->u32Magic = RT_H2LE_U32_C(NETPERFHDR_MAGIC); + pBuf->u32State = 0; + pBuf->u32Seq = 0; + pBuf->u32Reserved = 0; + + /* + * Warm up. + */ + if (g_uVerbosity > 0) + RTPrintf("Warmup...\n"); + pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_WARMUP); + rc = RTTimerLRStart(hTimer, pParams->cMsWarmup * UINT64_C(1000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + while (!fStop) + { + u32Seq++; + pBuf->u32Seq = RT_H2LE_U32(u32Seq); + rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/warmup: %Rrc\n", rc); + break; + } + } + } + else + RTTestIFailed("RTTimerLRStart/warmup: %Rrc\n", rc); + + /* + * The real thing. + */ + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTPrintf("The real thing...\n"); + pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_TESTING); + fStop = false; + rc = RTTimerLRStart(hTimer, pParams->cSecTimeout * UINT64_C(1000000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + uint64_t u64StartTS = RTTimeNanoTS(); + while (!fStop) + { + u32Seq++; + pBuf->u32Seq = RT_H2LE_U32(u32Seq); + rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/testing: %Rrc\n", rc); + break; + } + pSendStats->cTx++; + } + pSendStats->cNsElapsed = RTTimeNanoTS() - u64StartTS; + } + else + RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc); + } + + /* + * Cool down. + */ + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTPrintf("Cool down...\n"); + pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN); + fStop = false; + rc = RTTimerLRStart(hTimer, pParams->cMsCoolDown * UINT64_C(1000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + while (!fStop) + { + u32Seq++; + pBuf->u32Seq = RT_H2LE_U32(u32Seq); + rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/cool down: %Rrc\n", rc); + break; + } + } + } + else + RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc); + } + + /* + * Send DONE packet. + */ + if (g_uVerbosity > 0) + RTPrintf("Done\n"); + if (RT_SUCCESS(rc)) + { + u32Seq++; + pBuf->u32Seq = RT_H2LE_U32(u32Seq); + pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_DONE); + rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket); + if (RT_FAILURE(rc)) + RTTestIFailed("RTTcpWrite/done: %Rrc\n", rc); + } + + RTTimerLRDestroy(hTimer); + } + else + RTTestIFailed("Failed to create timer object: %Rrc\n", rc); + return rc; +} + + +/** + * TCP Throughput: Receive data from the other party. + * + * @returns IPRT status code. + * @param pParams The TCP parameters block. + * @param pBuf The buffer we're using when sending. + * @param pStats Where to return the statistics. + */ +static int netperfTCPThroughputRecv(NETPERFPARAMS const *pParams, NETPERFHDR *pBuf, NETPERFSTATS *pStats) +{ + RT_ZERO(*pStats); + + int rc; + uint32_t u32Seq = 0; + uint64_t cRx = 0; + uint64_t u64StartTS = 0; + uint32_t uState = RT_H2LE_U32_C(NETPERFHDR_WARMUP); + + for (;;) + { + rc = RTTcpRead(pParams->hSocket, pBuf, pParams->cbPacket, NULL); + if (RT_FAILURE(rc)) + { + pStats->cErrors++; + RTTestIFailed("RTTcpRead failed: %Rrc\n", rc); + break; + } + if (RT_UNLIKELY( pBuf->u32Magic != RT_H2LE_U32_C(NETPERFHDR_MAGIC) + || pBuf->u32Reserved != 0)) + { + pStats->cErrors++; + RTTestIFailed("Invalid magic or reserved field value: %#x %#x\n", RT_H2LE_U32(pBuf->u32Magic), RT_H2LE_U32(pBuf->u32Reserved)); + rc = VERR_INVALID_MAGIC; + break; + } + + u32Seq += 1; + if (RT_UNLIKELY(pBuf->u32Seq != RT_H2LE_U32(u32Seq))) + { + pStats->cErrors++; + RTTestIFailed("Out of sequence: got %#x, expected %#x\n", RT_H2LE_U32(pBuf->u32Seq), u32Seq); + rc = VERR_WRONG_ORDER; + break; + } + + if (pParams->fCheckData && uState == RT_H2LE_U32_C(NETPERFHDR_TESTING)) + { + unsigned i = sizeof(NETPERFHDR); + for (;i < pParams->cbPacket; ++i) + if (((unsigned char *)pBuf)[i]) + break; + if (i != pParams->cbPacket) + { + pStats->cErrors++; + RTTestIFailed("Broken payload: at %#x got %#x, expected %#x\n", i, ((unsigned char *)pBuf)[i], 0); + rc = VERR_NOT_EQUAL; + break; + } + } + if (RT_LIKELY(pBuf->u32State == uState)) + cRx++; + /* + * Validate and act on switch state. + */ + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_WARMUP) + && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_TESTING)) + { + cRx = 0; + u64StartTS = RTTimeNanoTS(); + uState = pBuf->u32State; + } + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_TESTING) + && ( pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN) + || pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE)) ) + { + pStats->cNsElapsed = RTTimeNanoTS() - u64StartTS; + pStats->cRx = cRx + 1; + uState = pBuf->u32State; + if (uState == RT_H2LE_U32_C(NETPERFHDR_DONE)) + break; + } + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN) + && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE)) + { + uState = pBuf->u32State; + break; + } + else + { + pStats->cErrors++; + RTTestIFailed("Protocol error: invalid state transition %#x -> %#x\n", + RT_LE2H_U32(uState), RT_LE2H_U32(pBuf->u32State)); + rc = VERR_INVALID_MAGIC; + break; + } + } + + AssertReturn(uState == RT_H2LE_U32_C(NETPERFHDR_DONE) || RT_FAILURE(rc), VERR_INVALID_STATE); + return rc; +} + + +/** + * Prints the statistics for the latency test. + * + * @param pStats The statistics. + * @param cbPacket The packet size in bytes. + */ +static void netperfPrintLatencyStats(NETPERFSTATS const *pStats, uint32_t cbPacket) +{ + double rdSecElapsed = (double)pStats->cNsElapsed / 1000000000.0; + RTTestIValue("Transmitted", pStats->cTx, RTTESTUNIT_PACKETS); + RTTestIValue("Successful echos", pStats->cEchos, RTTESTUNIT_PACKETS); + RTTestIValue("Errors", pStats->cErrors, RTTESTUNIT_PACKETS); + RTTestIValue("Interval", pStats->cNsElapsed, RTTESTUNIT_NS); + RTTestIValue("Packet size", cbPacket, RTTESTUNIT_BYTES); + RTTestIValue("Average rate", (uint64_t)(pStats->cEchos / rdSecElapsed), RTTESTUNIT_PACKETS_PER_SEC); + RTTestIValue("Average throughput", (uint64_t)(cbPacket * pStats->cEchos / rdSecElapsed), RTTESTUNIT_BYTES_PER_SEC); + RTTestIValue("Average latency", (uint64_t)(rdSecElapsed / pStats->cEchos * 1000000000.0), RTTESTUNIT_NS_PER_ROUND_TRIP); + RTTestISubDone(); +} + + +/** + * NETPERFMODE -> string. + * + * @returns readonly string. + * @param enmMode The mode. + */ +static const char *netperfModeToString(NETPERFMODE enmMode) +{ + switch (enmMode) + { + case NETPERFMODE_LATENCY: return "latency"; + case NETPERFMODE_THROUGHPUT: return "throughput"; + case NETPERFMODE_THROUGHPUT_XMIT: return "throughput-xmit"; + case NETPERFMODE_THROUGHPUT_RECV: return "throughput-recv"; + default: AssertFailed(); return "internal-error"; + } +} + +/** + * String -> NETPERFMODE. + * + * @returns The corresponding NETPERFMODE, NETPERFMODE_INVALID on failure. + * @param pszMode The mode string. + */ +static NETPERFMODE netperfModeFromString(const char *pszMode) +{ + if (!strcmp(pszMode, "latency")) + return NETPERFMODE_LATENCY; + if ( !strcmp(pszMode, "throughput") + || !strcmp(pszMode, "thruput") ) + return NETPERFMODE_THROUGHPUT; + if ( !strcmp(pszMode, "throughput-xmit") + || !strcmp(pszMode, "thruput-xmit") + || !strcmp(pszMode, "xmit") ) + return NETPERFMODE_THROUGHPUT_XMIT; + if ( !strcmp(pszMode, "throughput-recv") + || !strcmp(pszMode, "thruput-recv") + || !strcmp(pszMode, "recv") ) + return NETPERFMODE_THROUGHPUT_RECV; + return NETPERFMODE_INVALID; +} + + + + + +/** + * TCP Server: Throughput test. + * + * @returns IPRT status code. + * @param pParams The parameters to use for this test. + */ +static int netperfTCPServerDoThroughput(NETPERFPARAMS const *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Receive first, then Send. The reverse of the client. + */ + NETPERFSTATS RecvStats; + int rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats); + if (RT_SUCCESS(rc)) + { + rc = netperfSendStats(&RecvStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS SendStats; + rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats); + if (RT_SUCCESS(rc)) + { + rc = netperfSendStats(&SendStats, pParams->hSocket); + netperfPrintThroughputStats(&SendStats, &RecvStats, pParams->cbPacket); + } + } + } + + return rc; +} + +/** + * TCP Server: Throughput xmit test (receive from client). + * + * @returns IPRT status code. + * @param pParams The parameters to use for this test. + */ +static int netperfTCPServerDoThroughputXmit(NETPERFPARAMS const *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Receive the transmitted data (reverse of client). + */ + NETPERFSTATS RecvStats; + int rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats); + if (RT_SUCCESS(rc)) + { + rc = netperfSendStats(&RecvStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + netperfPrintThroughputStats(NULL, &RecvStats, pParams->cbPacket); + } + + return rc; +} + +/** + * TCP Server: Throughput recv test (transmit to client). + * + * @returns IPRT status code. + * @param pParams The parameters to use for this test. + */ +static int netperfTCPServerDoThroughputRecv(NETPERFPARAMS const *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Send data to the client (reverse of client). + */ + NETPERFSTATS SendStats; + int rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats); + if (RT_SUCCESS(rc)) + { + rc = netperfSendStats(&SendStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + netperfPrintThroughputStats(&SendStats, NULL, pParams->cbPacket); + } + + return rc; +} + +/** + * TCP Server: Latency test. + * + * @returns IPRT status code. + * @param pParams The parameters to use for this test. + */ +static int netperfTCPServerDoLatency(NETPERFPARAMS const *pParams) +{ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Failed to allocated packet buffer of %u bytes.\n", pParams->cbPacket); + + /* + * Ping pong with client. + */ + int rc; + uint32_t uState = RT_H2LE_U32_C(NETPERFHDR_WARMUP); + uint32_t u32Seq = 0; + uint64_t cTx = 0; + uint64_t cRx = 0; + uint64_t u64StartTS = 0; + NETPERFSTATS Stats; + RT_ZERO(Stats); + for (;;) + { + rc = RTTcpRead(pParams->hSocket, pBuf, pParams->cbPacket, NULL); + if (RT_FAILURE(rc)) + { + RTTestIFailed("Failed to read data from client: %Rrc\n", rc); + break; + } + + /* + * Validate the packet + */ + if (RT_UNLIKELY( pBuf->u32Magic != RT_H2LE_U32_C(NETPERFHDR_MAGIC) + || pBuf->u32Reserved != 0)) + { + RTTestIFailed("Invalid magic or reserved field value: %#x %#x\n", RT_H2LE_U32(pBuf->u32Magic), RT_H2LE_U32(pBuf->u32Reserved)); + rc = VERR_INVALID_MAGIC; + break; + } + + u32Seq += 1; + if (RT_UNLIKELY(pBuf->u32Seq != RT_H2LE_U32(u32Seq))) + { + RTTestIFailed("Out of sequence: got %#x, expected %#x\n", RT_H2LE_U32(pBuf->u32Seq), u32Seq); + rc = VERR_WRONG_ORDER; + break; + } + + /* + * Count the packet if the state remains unchanged. + */ + if (RT_LIKELY(pBuf->u32State == uState)) + cRx++; + /* + * Validate and act on the state transition. + */ + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_WARMUP) + && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_TESTING)) + { + cRx = cTx = 0; + u64StartTS = RTTimeNanoTS(); + uState = pBuf->u32State; + } + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_TESTING) + && ( pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN) + || pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE)) ) + { + Stats.cNsElapsed = RTTimeNanoTS() - u64StartTS; + Stats.cEchos = cTx; + Stats.cTx = cTx; + Stats.cRx = cRx; + uState = pBuf->u32State; + if (uState == RT_H2LE_U32_C(NETPERFHDR_DONE)) + break; + } + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN) + && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE)) + { + uState = pBuf->u32State; + break; + } + else + { + RTTestIFailed("Protocol error: invalid state transition %#x -> %#x\n", + RT_LE2H_U32(uState), RT_LE2H_U32(pBuf->u32State)); + break; + } + + /* + * Write same data back to client. + */ + rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("Failed to write data to client: %Rrc\n", rc); + break; + } + + cTx++; + } + + /* + * Send stats to client and print them. + */ + if (uState == RT_H2LE_U32_C(NETPERFHDR_DONE)) + netperfSendStats(&Stats, pParams->hSocket); + + if ( uState == RT_H2LE_U32_C(NETPERFHDR_DONE) + || uState == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN)) + netperfPrintLatencyStats(&Stats, pParams->cbPacket); + + RTMemFree(pBuf); + return rc; +} + +/** + * Parses the parameters the client has sent us. + * + * @returns IPRT status code. Message has been shown on failure. + * @param pParams The parameter structure to store the parameters + * in. + * @param pszParams The parameter string sent by the client. + */ +static int netperfTCPServerParseParams(NETPERFPARAMS *pParams, char *pszParams) +{ + /* + * Set defaults for the dynamic settings. + */ + pParams->fNoDelay = false; + pParams->enmMode = NETPERFMODE_LATENCY; + pParams->cSecTimeout = NETPERF_DEFAULT_WARMUP; + pParams->cMsCoolDown = NETPERF_DEFAULT_COOL_DOWN; + pParams->cMsWarmup = NETPERF_DEFAULT_WARMUP; + pParams->cbPacket = NETPERF_DEFAULT_PKT_SIZE_LATENCY; + + /* + * Parse the client parameters. + */ + /* first arg: transport type. [mandatory] */ + char *pszCur = strchr(pszParams, ':'); + if (!pszCur) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: No colon\n"); + char *pszNext = strchr(++pszCur, ':'); + if (pszNext) + *pszNext++ = '\0'; + if (strcmp(pszCur, "TCP")) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: Invalid transport type: \"%s\"\n", pszCur); + pszCur = pszNext; + + /* second arg: mode. [mandatory] */ + if (!pszCur) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: Missing test mode\n"); + pszNext = strchr(pszCur, ':'); + if (pszNext) + *pszNext++ = '\0'; + pParams->enmMode = netperfModeFromString(pszCur); + if (pParams->enmMode == NETPERFMODE_INVALID) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: Invalid test mode: \"%s\"\n", pszCur); + pszCur = pszNext; + + /* + * The remainder are uint32_t or bool. + */ + struct + { + bool fBool; + bool fMandatory; + void *pvValue; + uint32_t uMin; + uint32_t uMax; + const char *pszName; + } aElements[] = + { + { false, true, &pParams->cSecTimeout, NETPERF_MIN_TIMEOUT, NETPERF_MAX_TIMEOUT, "timeout" }, + { false, true, &pParams->cbPacket, NETPERF_MIN_PKT_SIZE, NETPERF_MAX_PKT_SIZE, "packet size" }, + { false, true, &pParams->cMsWarmup, NETPERF_MIN_WARMUP, NETPERF_MAX_WARMUP, "warmup period" }, + { false, true, &pParams->cMsCoolDown, NETPERF_MIN_COOL_DOWN, NETPERF_MAX_COOL_DOWN, "cool down period" }, + { true, true, &pParams->fNoDelay, false, true, "no delay" }, + }; + + for (unsigned i = 0; i < RT_ELEMENTS(aElements); i++) + { + if (!pszCur) + return aElements[i].fMandatory + ? RTTestIFailedRc(VERR_PARSE_ERROR, "client params: missing %s\n", aElements[i].pszName) + : VINF_SUCCESS; + + pszNext = strchr(pszCur, ':'); + if (pszNext) + *pszNext++ = '\0'; + uint32_t u32; + int rc = RTStrToUInt32Full(pszCur, 10, &u32); + if (rc != VINF_SUCCESS) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: bad %s value \"%s\": %Rrc\n", + aElements[i].pszName, pszCur, rc); + + if ( u32 < aElements[i].uMin + || u32 > aElements[i].uMax) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: %s %u s is out of range (%u..%u)\n", + aElements[i].pszName, u32, aElements[i].uMin, aElements[i].uMax); + if (aElements[i].fBool) + *(bool *)aElements[i].pvValue = u32 ? true : false; + else + *(uint32_t *)aElements[i].pvValue = u32; + + pszCur = pszNext; + } + + /* Fail if too many elements. */ + if (pszCur) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: too many elements: \"%s\"\n", + pszCur); + return VINF_SUCCESS; +} + + +/** + * TCP server callback that handles one client connection. + * + * @returns IPRT status code. VERR_TCP_SERVER_STOP is special. + * @param hSocket The client socket. + * @param pvUser Our parameters. + */ +static DECLCALLBACK(int) netperfTCPServerWorker(RTSOCKET hSocket, void *pvUser) +{ + NETPERFPARAMS *pParams = (NETPERFPARAMS *)pvUser; + AssertReturn(pParams, VERR_INVALID_POINTER); + + pParams->hSocket = hSocket; + + RTNETADDR Addr; + int rc = RTTcpGetPeerAddress(hSocket, &Addr); + if (RT_SUCCESS(rc)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "Client connected from %RTnaddr\n", &Addr); + else + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "Failed to get client details: %Rrc\n", rc); + Addr.enmType = RTNETADDRTYPE_INVALID; + } + + /* + * Greet the other dude. + */ + rc = RTTcpWrite(hSocket, g_ConnectStart, sizeof(g_ConnectStart) - 1); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to send connection start Id: %Rrc\n", rc); + + /* + * Read connection parameters. + */ + char szBuf[256]; + rc = RTTcpRead(hSocket, szBuf, NETPERF_LEN_PREFIX, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to read connection parameters: %Rrc\n", rc); + szBuf[NETPERF_LEN_PREFIX] = '\0'; + uint32_t cchParams; + rc = RTStrToUInt32Full(szBuf, 10, &cchParams); + if (rc != VINF_SUCCESS) + return RTTestIFailedRc(RT_SUCCESS(rc) ? VERR_INTERNAL_ERROR : rc, + "Failed to read connection parameters: %Rrc\n", rc); + if (cchParams >= sizeof(szBuf)) + return RTTestIFailedRc(VERR_TOO_MUCH_DATA, "parameter packet is too big (%u bytes)\n", cchParams); + rc = RTTcpRead(hSocket, szBuf, cchParams, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to read connection parameters: %Rrc\n", rc); + szBuf[cchParams] = '\0'; + + if (strncmp(szBuf, g_szStartParams, sizeof(g_szStartParams) - 1)) + return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "Invalid connection parameters '%s'\n", szBuf); + + /* + * Parse the parameters and signal whether we've got a deal or not. + */ + rc = netperfTCPServerParseParams(pParams, szBuf); + if (RT_FAILURE(rc)) + { + int rc2 = RTTcpWrite(hSocket, g_szNegative, sizeof(g_szNegative) - 1); + if (RT_FAILURE(rc2)) + RTTestIFailed("Failed to send negative ack: %Rrc\n", rc2); + return rc; + } + + if (Addr.enmType != RTNETADDRTYPE_INVALID) + RTTestISubF("%RTnaddr - %s, %u s, %u bytes", &Addr, + netperfModeToString(pParams->enmMode), pParams->cSecTimeout, pParams->cbPacket); + else + RTTestISubF("Unknown - %s, %u s, %u bytes", + netperfModeToString(pParams->enmMode), pParams->cSecTimeout, pParams->cbPacket); + + rc = RTTcpSetSendCoalescing(hSocket, !pParams->fNoDelay); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to apply no-delay option (%RTbool): %Rrc\n", pParams->fNoDelay, rc); + + rc = RTTcpWrite(hSocket, g_szAck, sizeof(g_szAck) - 1); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to send start test commend to client: %Rrc\n", rc); + + /* + * Take action according to our mode. + */ + switch (pParams->enmMode) + { + case NETPERFMODE_LATENCY: + rc = netperfTCPServerDoLatency(pParams); + break; + + case NETPERFMODE_THROUGHPUT: + rc = netperfTCPServerDoThroughput(pParams); + break; + + case NETPERFMODE_THROUGHPUT_XMIT: + rc = netperfTCPServerDoThroughputXmit(pParams); + break; + + case NETPERFMODE_THROUGHPUT_RECV: + rc = netperfTCPServerDoThroughputRecv(pParams); + break; + + case NETPERFMODE_INVALID: + rc = VERR_INTERNAL_ERROR; + break; + + /* no default! */ + } + if (rc == VERR_NO_MEMORY) + return VERR_TCP_SERVER_STOP; + + /* + * Wait for other clients or quit. + */ + if (pParams->fSingleClient) + return VERR_TCP_SERVER_STOP; + return VINF_SUCCESS; +} + + +/** + * TCP server. + * + * @returns IPRT status code. + * @param pParams The TCP parameter block. + */ +static int netperfTCPServer(NETPERFPARAMS *pParams) +{ + /* + * Spawn the TCP server thread & listen. + */ + PRTTCPSERVER pServer; + int rc = RTTcpServerCreateEx(NULL, pParams->uPort, &pServer); + if (RT_SUCCESS(rc)) + { + RTPrintf("Server listening on TCP port %d\n", pParams->uPort); + rc = RTTcpServerListen(pServer, netperfTCPServerWorker, pParams); + RTTcpServerDestroy(pServer); + } + else + RTPrintf("Failed to create TCP server thread: %Rrc\n", rc); + + return rc; +} + +/** + * The server part. + * + * @returns Exit code. + * @param enmProto The protocol. + * @param pParams The parameter block. + */ +static RTEXITCODE netperfServer(NETPERFPROTO enmProto, NETPERFPARAMS *pParams) +{ + + switch (enmProto) + { + case NETPERFPROTO_TCP: + { + int rc = netperfTCPServer(pParams); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } + + default: + RTTestIFailed("Protocol not supported.\n"); + return RTEXITCODE_FAILURE; + } +} + + + + + +/** + * TCP client: Do the throughput test. + * + * @returns IPRT status code + * @param pParams The parameters. + */ +static int netperfTCPClientDoThroughput(NETPERFPARAMS *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Send first, then Receive. + */ + NETPERFSTATS SendStats; + int rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS SrvSendStats; + rc = netperfRecvStats(&SrvSendStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS RecvStats; + rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS SrvRecvStats; + rc = netperfRecvStats(&SrvRecvStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + { + if (pParams->fServerStats) + netperfPrintThroughputStats(&SrvSendStats, &SrvRecvStats, pParams->cbPacket); + else + netperfPrintThroughputStats(&SendStats, &RecvStats, pParams->cbPacket); + } + } + } + } + + RTTestISubDone(); + return rc; +} + +/** + * TCP client: Do the throughput xmit test. + * + * @returns IPRT status code + * @param pParams The parameters. + */ +static int netperfTCPClientDoThroughputXmit(NETPERFPARAMS *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Do the job. + */ + NETPERFSTATS SendStats; + int rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS SrvSendStats; + rc = netperfRecvStats(&SrvSendStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + { + if (pParams->fServerStats) + netperfPrintThroughputStats(&SrvSendStats, NULL, pParams->cbPacket); + else + netperfPrintThroughputStats(&SendStats, NULL, pParams->cbPacket); + } + } + + RTTestISubDone(); + return rc; +} + +/** + * TCP client: Do the throughput recv test. + * + * @returns IPRT status code + * @param pParams The parameters. + */ +static int netperfTCPClientDoThroughputRecv(NETPERFPARAMS *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Do the job. + */ + NETPERFSTATS RecvStats; + int rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS SrvRecvStats; + rc = netperfRecvStats(&SrvRecvStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + { + if (pParams->fServerStats) + netperfPrintThroughputStats(NULL, &SrvRecvStats, pParams->cbPacket); + else + netperfPrintThroughputStats(NULL, &RecvStats, pParams->cbPacket); + } + } + + RTTestISubDone(); + return rc; +} + +/** + * TCP client: Do the latency test. + * + * @returns IPRT status code + * @param pParams The parameters. + */ +static int netperfTCPClientDoLatency(NETPERFPARAMS *pParams) +{ + /* + * Generate a selection of packages before we start, after all we're not + * benchmarking the random number generator, are we. :-) + */ + void *pvReadBuf = RTMemAllocZ(pParams->cbPacket); + if (!pvReadBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + size_t i; + NETPERFHDR *apPackets[256]; + for (i = 0; i < RT_ELEMENTS(apPackets); i++) + { + apPackets[i] = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!apPackets[i]) + { + while (i-- > 0) + RTMemFree(apPackets[i]); + RTMemFree(pvReadBuf); + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + } + RTRandBytes(apPackets[i], pParams->cbPacket); + apPackets[i]->u32Magic = RT_H2LE_U32_C(NETPERFHDR_MAGIC); + apPackets[i]->u32State = 0; + apPackets[i]->u32Seq = 0; + apPackets[i]->u32Reserved = 0; + } + + /* + * Create & start a timer to eventually disconnect. + */ + bool volatile fStop = false; + RTTIMERLR hTimer; + int rc = RTTimerLRCreateEx(&hTimer, 0 /* nsec */, RTTIMER_FLAGS_CPU_ANY, netperfStopTimerCallback, (void *)&fStop); + if (RT_SUCCESS(rc)) + { + uint32_t u32Seq = 0; + NETPERFSTATS Stats; + RT_ZERO(Stats); + + /* + * Warm up. + */ + if (g_uVerbosity > 0) + RTPrintf("Warmup...\n"); + rc = RTTimerLRStart(hTimer, pParams->cMsWarmup * UINT64_C(1000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + while (!fStop) + { + NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)]; + u32Seq++; + pPacket->u32Seq = RT_H2LE_U32(u32Seq); + pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_WARMUP); + rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/warmup: %Rrc\n", rc); + break; + } + rc = RTTcpRead(pParams->hSocket, pvReadBuf, pParams->cbPacket, NULL); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpRead/warmup: %Rrc\n", rc); + break; + } + } + } + else + RTTestIFailed("RTTimerLRStart/warmup: %Rrc\n", rc); + + /* + * The real thing. + */ + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTPrintf("The real thing...\n"); + fStop = false; + rc = RTTimerLRStart(hTimer, pParams->cSecTimeout * UINT64_C(1000000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + uint64_t u64StartTS = RTTimeNanoTS(); + while (!fStop) + { + NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)]; + u32Seq++; + pPacket->u32Seq = RT_H2LE_U32(u32Seq); + pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_TESTING); + rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/testing: %Rrc\n", rc); + break; + } + Stats.cTx++; + + rc = RTTcpRead(pParams->hSocket, pvReadBuf, pParams->cbPacket, NULL); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpRead/testing: %Rrc\n", rc); + break; + } + Stats.cRx++; + + if (!memcmp(pvReadBuf, pPacket, pParams->cbPacket)) + Stats.cEchos++; + else + Stats.cErrors++; + } + Stats.cNsElapsed = RTTimeNanoTS() - u64StartTS; + } + else + RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc); + } + + /* + * Cool down. + */ + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTPrintf("Cool down...\n"); + fStop = false; + rc = RTTimerLRStart(hTimer, pParams->cMsCoolDown * UINT64_C(1000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + while (!fStop) + { + NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)]; + u32Seq++; + pPacket->u32Seq = RT_H2LE_U32(u32Seq); + pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN); + rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/warmup: %Rrc\n", rc); + break; + } + rc = RTTcpRead(pParams->hSocket, pvReadBuf, pParams->cbPacket, NULL); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpRead/warmup: %Rrc\n", rc); + break; + } + } + } + else + RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc); + } + + /* + * Send DONE packet. + */ + if (g_uVerbosity > 0) + RTPrintf("Done\n"); + if (RT_SUCCESS(rc)) + { + u32Seq++; + NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)]; + pPacket->u32Seq = RT_H2LE_U32(u32Seq); + pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_DONE); + rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket); + if (RT_FAILURE(rc)) + RTTestIFailed("RTTcpWrite/done: %Rrc\n", rc); + } + + + /* + * Get and print stats. + */ + NETPERFSTATS SrvStats; + if (RT_SUCCESS(rc)) + { + rc = netperfRecvStats(&SrvStats, pParams->hSocket); + if (RT_SUCCESS(rc) && pParams->fServerStats) + netperfPrintLatencyStats(&SrvStats, pParams->cbPacket); + else if (!pParams->fServerStats) + netperfPrintLatencyStats(&Stats, pParams->cbPacket); + } + + /* clean up*/ + RTTimerLRDestroy(hTimer); + } + else + RTTestIFailed("Failed to create timer object: %Rrc\n", rc); + for (i = 0; i < RT_ELEMENTS(apPackets); i++) + RTMemFree(apPackets[i]); + + RTMemFree(pvReadBuf); + + return rc; +} + +/** + * TCP client test driver. + * + * @returns IPRT status code + * @param pszServer The server name. + * @param pParams The parameter structure. + */ +static int netperfTCPClient(const char *pszServer, NETPERFPARAMS *pParams) +{ + AssertReturn(pParams, VERR_INVALID_POINTER); + RTTestISubF("TCP - %u s, %u bytes%s", pParams->cSecTimeout, + pParams->cbPacket, pParams->fNoDelay ? ", no delay" : ""); + + RTSOCKET hSocket = NIL_RTSOCKET; + int rc = RTTcpClientConnect(pszServer, pParams->uPort, &hSocket); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to connect to %s on port %u: %Rrc\n", pszServer, pParams->uPort, rc); + pParams->hSocket = hSocket; + + /* + * Disable send coalescing (no-delay). + */ + if (pParams->fNoDelay) + { + rc = RTTcpSetSendCoalescing(hSocket, false /*fEnable*/); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to set no-delay option: %Rrc\n", rc); + } + + /* + * Verify the super secret Start Connect Id to start the connection. + */ + char szBuf[256 + NETPERF_LEN_PREFIX]; + RT_ZERO(szBuf); + rc = RTTcpRead(hSocket, szBuf, sizeof(g_ConnectStart) - 1, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to read connection initializer: %Rrc\n", rc); + + if (strcmp(szBuf, g_ConnectStart)) + return RTTestIFailedRc(VERR_INVALID_MAGIC, "Invalid connection initializer '%s'\n", szBuf); + + /* + * Send all the dynamic parameters to the server. + * (If the server is newer than the client, it will select default for any + * missing parameters.) + */ + size_t cchParams = RTStrPrintf(&szBuf[NETPERF_LEN_PREFIX], sizeof(szBuf) - NETPERF_LEN_PREFIX, + "%s:%s:%s:%u:%u:%u:%u:%u", + g_szStartParams, + "TCP", + netperfModeToString(pParams->enmMode), + pParams->cSecTimeout, + pParams->cbPacket, + pParams->cMsWarmup, + pParams->cMsCoolDown, + pParams->fNoDelay); + RTStrPrintf(szBuf, NETPERF_LEN_PREFIX + 1, "%0*u", NETPERF_LEN_PREFIX, cchParams); + szBuf[NETPERF_LEN_PREFIX] = g_szStartParams[0]; + Assert(strlen(szBuf) == NETPERF_LEN_PREFIX + cchParams); + rc = RTTcpWrite(hSocket, szBuf, NETPERF_LEN_PREFIX + cchParams); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to send connection parameters: %Rrc\n", rc); + + /* + * Wait for acknowledgment. + */ + rc = RTTcpRead(hSocket, szBuf, sizeof(g_szAck) - 1, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to send parameters: %Rrc\n", rc); + szBuf[sizeof(g_szAck) - 1] = '\0'; + + if (!strcmp(szBuf, g_szNegative)) + return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "Server failed to accept packet size of %u bytes.\n", pParams->cbPacket); + if (strcmp(szBuf, g_szAck)) + return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "Invalid response from server '%s'\n", szBuf); + + /* + * Take action according to our mode. + */ + switch (pParams->enmMode) + { + case NETPERFMODE_LATENCY: + RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the latency test for %u seconds.\n", + pszServer, pParams->uPort, pParams->cSecTimeout); + rc = netperfTCPClientDoLatency(pParams); + break; + + case NETPERFMODE_THROUGHPUT: + RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the throughput test for %u seconds in each direction.\n", + pszServer, pParams->uPort, pParams->cSecTimeout); + rc = netperfTCPClientDoThroughput(pParams); + break; + + case NETPERFMODE_THROUGHPUT_XMIT: + RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the throughput-xmit test for %u seconds.\n", + pszServer, pParams->uPort, pParams->cSecTimeout); + rc = netperfTCPClientDoThroughputXmit(pParams); + break; + + case NETPERFMODE_THROUGHPUT_RECV: + RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the throughput-recv test for %u seconds.\n", + pszServer, pParams->uPort, pParams->cSecTimeout); + rc = netperfTCPClientDoThroughputRecv(pParams); + break; + + case NETPERFMODE_INVALID: + rc = VERR_INTERNAL_ERROR; + break; + + /* no default! */ + } + return rc; +} + +/** + * The client part. + * + * @returns Exit code. + * @param enmProto The protocol. + * @param pszServer The server name. + * @param pvUser The parameter block as opaque user data. + */ +static RTEXITCODE netperfClient(NETPERFPROTO enmProto, const char *pszServer, void *pvUser) +{ + switch (enmProto) + { + case NETPERFPROTO_TCP: + { + NETPERFPARAMS *pParams = (NETPERFPARAMS *)pvUser; + int rc = netperfTCPClient(pszServer, pParams); + if (pParams->hSocket != NIL_RTSOCKET) + { + RTTcpClientClose(pParams->hSocket); + pParams->hSocket = NIL_RTSOCKET; + } + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } + + default: + RTTestIFailed("Protocol not supported.\n"); + return RTEXITCODE_FAILURE; + } +} + + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT and globals. + */ + int rc = RTTestInitAndCreate("NetPerf", &g_hTest); + if (rc) + return rc; + + /* + * Special case. + */ + if (argc < 2) + { + RTTestFailed(g_hTest, "No arguments given."); + return RTTestSummaryAndDestroy(g_hTest); + } + + /* + * Default values. + */ + NETPERFPROTO enmProtocol = NETPERFPROTO_TCP; + bool fServer = true; + bool fDaemonize = false; + bool fDaemonized = false; + bool fPacketSizeSet = false; + const char *pszServerAddress= NULL; + + NETPERFPARAMS Params; + Params.uPort = NETPERF_DEFAULT_PORT; + Params.fServerStats = false; + Params.fSingleClient = false; + + Params.fNoDelay = false; + Params.fCheckData = false; + Params.enmMode = NETPERFMODE_LATENCY; + Params.cSecTimeout = NETPERF_DEFAULT_TIMEOUT; + Params.cMsWarmup = NETPERF_DEFAULT_WARMUP; + Params.cMsCoolDown = NETPERF_DEFAULT_COOL_DOWN; + Params.cbPacket = NETPERF_DEFAULT_PKT_SIZE_LATENCY; + + Params.hSocket = NIL_RTSOCKET; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion))) + { + switch (rc) + { + case 's': + fServer = true; + break; + + case 'c': + fServer = false; + pszServerAddress = ValueUnion.psz; + break; + + case 'd': + fDaemonize = true; + break; + + case 'D': + fDaemonized = true; + break; + + case 'i': + Params.cSecTimeout = ValueUnion.u32; + if ( Params.cSecTimeout < NETPERF_MIN_TIMEOUT + || Params.cSecTimeout > NETPERF_MAX_TIMEOUT) + { + RTTestFailed(g_hTest, "Invalid interval %u s, valid range: %u-%u\n", + Params.cbPacket, NETPERF_MIN_TIMEOUT, NETPERF_MAX_TIMEOUT); + return RTTestSummaryAndDestroy(g_hTest); + } + break; + + case 'l': + Params.cbPacket = ValueUnion.u32; + if ( Params.cbPacket < NETPERF_MIN_PKT_SIZE + || Params.cbPacket > NETPERF_MAX_PKT_SIZE) + { + RTTestFailed(g_hTest, "Invalid packet size %u bytes, valid range: %u-%u\n", + Params.cbPacket, NETPERF_MIN_PKT_SIZE, NETPERF_MAX_PKT_SIZE); + return RTTestSummaryAndDestroy(g_hTest); + } + fPacketSizeSet = true; + break; + + case 'm': + Params.enmMode = netperfModeFromString(ValueUnion.psz); + if (Params.enmMode == NETPERFMODE_INVALID) + { + RTTestFailed(g_hTest, "Invalid test mode: \"%s\"\n", ValueUnion.psz); + return RTTestSummaryAndDestroy(g_hTest); + } + if (!fPacketSizeSet) + switch (Params.enmMode) + { + case NETPERFMODE_LATENCY: + Params.cbPacket = NETPERF_DEFAULT_PKT_SIZE_LATENCY; + break; + case NETPERFMODE_THROUGHPUT: + case NETPERFMODE_THROUGHPUT_XMIT: + case NETPERFMODE_THROUGHPUT_RECV: + Params.cbPacket = NETPERF_DEFAULT_PKT_SIZE_THROUGHPUT; + break; + case NETPERFMODE_INVALID: + break; + /* no default! */ + } + break; + + case 'p': + Params.uPort = ValueUnion.u32; + break; + + case 'N': + Params.fNoDelay = true; + break; + + case 'S': + Params.fServerStats = true; + break; + + case '1': + Params.fSingleClient = true; + break; + + case 'v': + g_uVerbosity++; + break; + + case 'h': + Usage(g_pStdOut); + return RTEXITCODE_SUCCESS; + + case 'V': + RTPrintf("$Revision: 128238 $\n"); + return RTEXITCODE_SUCCESS; + + case 'w': + Params.cMsWarmup = ValueUnion.u32; + if ( Params.cMsWarmup < NETPERF_MIN_WARMUP + || Params.cMsWarmup > NETPERF_MAX_WARMUP) + { + RTTestFailed(g_hTest, "invalid warmup time %u ms, valid range: %u-%u\n", + Params.cMsWarmup, NETPERF_MIN_WARMUP, NETPERF_MAX_WARMUP); + return RTTestSummaryAndDestroy(g_hTest); + } + break; + + case 'W': + Params.cMsCoolDown = ValueUnion.u32; + if ( Params.cMsCoolDown < NETPERF_MIN_COOL_DOWN + || Params.cMsCoolDown > NETPERF_MAX_COOL_DOWN) + { + RTTestFailed(g_hTest, "invalid cool down time %u ms, valid range: %u-%u\n", + Params.cMsCoolDown, NETPERF_MIN_COOL_DOWN, NETPERF_MAX_COOL_DOWN); + return RTTestSummaryAndDestroy(g_hTest); + } + break; + + case 'C': + Params.fCheckData = true; + break; + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Handle the server process daemoniziation. + */ + if (fDaemonize && !fDaemonized && fServer) + { + rc = RTProcDaemonize(argv, "--daemonized"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize failed: %Rrc\n", rc); + return RTEXITCODE_SUCCESS; + } + + /* + * Get down to business. + */ + RTTestBanner(g_hTest); + if (fServer) + rc = netperfServer(enmProtocol, &Params); + else if (pszServerAddress) + rc = netperfClient(enmProtocol, pszServerAddress, &Params); + else + RTTestFailed(g_hTest, "missing server address to connect to\n"); + + RTEXITCODE rc2 = RTTestSummaryAndDestroy(g_hTest); + return rc2 != RTEXITCODE_FAILURE ? (RTEXITCODE)rc2 : rc; +} + diff --git a/src/VBox/ValidationKit/utils/nt/Makefile.kmk b/src/VBox/ValidationKit/utils/nt/Makefile.kmk new file mode 100644 index 00000000..d6d0f4cc --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/Makefile.kmk @@ -0,0 +1,53 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Windows NT Specific Utilities. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Set Clock Frequency Utility. +# +PROGRAMS.win += ntSetFreq +ntSetFreq_TEMPLATE = VBoxValidationKitR3 +ntSetFreq_SOURCES = ntsetfreq.cpp +ntSetFreq_VBOX_IMPORT_CHECKER.win.x86 = nt350 + +# +# Test coherency among NT time sources. +# +PROGRAMS.win += ntTimeSources +ntTimeSources_TEMPLATE = VBoxValidationKitR3 +ntTimeSources_SOURCES = nttimesources.cpp + +# +# Test NtFlushVirtualMemory. +# +PROGRAMS.win += ntFlushVirtualMemory +ntFlushVirtualMemory_TEMPLATE = VBoxValidationKitR3 +ntFlushVirtualMemory_SOURCES = ntFlushVirtualMemory.cpp + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/nt/ntFlushVirtualMemory.cpp b/src/VBox/ValidationKit/utils/nt/ntFlushVirtualMemory.cpp new file mode 100644 index 00000000..fb69f1bc --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/ntFlushVirtualMemory.cpp @@ -0,0 +1,488 @@ +/* $Id: ntFlushVirtualMemory.cpp $ */ +/** @file + * Memory mapped files testcase - NT. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt.h> + +#include <iprt/alloca.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/err.h> +#include <iprt/x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Create page signature. */ +#define MAKE_PAGE_SIGNATURE(a_iPage) ((a_iPage) | UINT32_C(0x42000000) ) + +/** Number history entries on the page. */ +#define NUM_ROUND_HISTORY 16 + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** How chatty we should be. */ +static uint32_t g_cVerbosity = 0; + + +/** + * Checks if the on-disk file matches our expectations. + * + * @returns IPRT status code, fully bitched. + * @param pszFilename The name of the file. + * @param pu32BufChk Buffer to read the file into + * @param pu32BufOrg Expected file content. + * @param cbBuf The buffer size. + * @param iRound The update round. + */ +static int CheckFile(const char *pszFilename, uint32_t *pu32BufChk, uint32_t const *pu32BufOrg, size_t cbBuf, uint32_t iRound) +{ + /* + * Open and read the file into memory. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFilename, + GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*pfnApc*/, NULL /*pvApcCtx*/, + &Ios, pu32BufChk, (ULONG)cbBuf, NULL /*poffFile*/, NULL /*pvKey*/); + if (NT_SUCCESS(rcNt) || Ios.Information != cbBuf) + { + /* + * See if the content of the file matches our expectations. + */ + if (memcmp(pu32BufChk, pu32BufOrg, cbBuf) == 0) + { /* matches - likely */ } + else + { + RTMsgError("Round %u: Buffer mismatch!\n", iRound); + + /* Try figure where the differences are. */ + size_t const cPages = cbBuf / X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(pu32BufOrg[0]); + for (uint32_t iPage = 0; iPage < cPages; iPage++) + for (uint32_t iItem = 0; iItem < cItemsPerPage; iItem++) + { + uint32_t uValue = pu32BufChk[iPage * cItemsPerPage + iItem]; + uint32_t uExpected = pu32BufOrg[iPage * cItemsPerPage + iItem]; + if (uValue != uExpected) + RTMsgError("Round %u: page #%u, index #%u: %#x, expected %#x\n", + iRound, iPage, iItem, uValue, uExpected); + } + + rc = VERR_MISMATCH; + } + } + else if (NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtReadFile return %zu bytes intead of %zu!\n", iRound, Ios.Information, cbBuf); + rc = VERR_READ_ERROR; + } + else + { + RTMsgError("Round %u: NtReadFile(%#x) failed: %#x (%#x)\n", iRound, cbBuf, rcNt, Ios.Status); + rc = RTErrConvertFromNtStatus(rcNt); + } + + /* + * Close the file and return. + */ + rcNt = NtClose(hFile); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtCloseFile() failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + RTMsgError("Round %u: RTNtPathOpen() failed: %Rrc\n", iRound, rc); + return rc; +} + + +/** + * Manually checks whether the buffer matches up to our expectations. + * + * @returns IPRT status code, fully bitched. + * @param pu32Buf The buffer/mapping to check. + * @param cbBuf The buffer size. + * @param iRound The update round. + * @param cFlushesLeft Number of flushes left in the round. + */ +static int CheckBuffer(uint32_t const *pu32Buf, size_t cbBuf, uint32_t iRound, uint32_t cFlushesLeft) +{ + size_t const cPages = cbBuf / X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(pu32Buf[0]); + size_t const offPage = iRound & (NUM_ROUND_HISTORY - 1); + uint32_t const uValue = iRound | (cFlushesLeft << 20); +//RTPrintf("debug: CheckBuffer: %p %u/%u\n", pu32Buf, iRound, cFlushesLeft); + + for (uint32_t iPage = 0; iPage < cPages; iPage++) + { + uint32_t uActual = pu32Buf[iPage * cItemsPerPage + offPage]; + if (uActual != uValue) + { + RTMsgError("Round %u/%u: page #%u: last entry is corrupted: %#x, expected %#x\n", + iRound, cFlushesLeft, iPage, uActual, uValue); + return VERR_MISMATCH; + } + + uActual = pu32Buf[iPage * cItemsPerPage + cItemsPerPage - 1]; + if (uActual != MAKE_PAGE_SIGNATURE(iPage)) + { + RTMsgError("Round %u/%u: page #%u magic corrupted: %#x, expected %#x\n", + iRound, cFlushesLeft, iPage, uActual, MAKE_PAGE_SIGNATURE(iPage)); + return VERR_INVALID_MAGIC; + } + } + + /* + * Check previous rounds. + */ + for (uint32_t cRoundsAgo = 1; cRoundsAgo < NUM_ROUND_HISTORY - 1 && cRoundsAgo <= iRound; cRoundsAgo++) + { + uint32_t iOldRound = iRound - cRoundsAgo; + size_t const offOldPage = iOldRound & (NUM_ROUND_HISTORY - 1); + for (uint32_t iPage = 0; iPage < cPages; iPage++) + { + uint32_t uActual = pu32Buf[iPage * cItemsPerPage + offOldPage]; + if (uActual != iOldRound) + { + RTMsgError("Round %u/%u: page #%u: entry from %u rounds ago is corrupted: %#x, expected %#x\n", + iRound, cFlushesLeft, iPage, cRoundsAgo, uActual, uValue); + return VERR_MISMATCH; + } + } + } + + return VINF_SUCCESS; +} + + +/** + * Updates the buffer. + * + * @param pu32Buf The buffer/mapping to update. + * @param cbBuf The buffer size. + * @param iRound The update round. + * @param cFlushesLeft Number of flushes left in this round. + */ +static void UpdateBuffer(uint32_t *pu32Buf, size_t cbBuf, uint32_t iRound, uint32_t cFlushesLeft) +{ + size_t const cPages = cbBuf / X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(pu32Buf[0]); + size_t const offPage = iRound & (NUM_ROUND_HISTORY - 1); + uint32_t const uValue = iRound | (cFlushesLeft << 20); +//RTPrintf("debug: UpdateBuffer: %p %u/%u\n", pu32Buf, iRound, cFlushesLeft); + + for (uint32_t iPage = 0; iPage < cPages; iPage++) + pu32Buf[iPage * cItemsPerPage + offPage] = uValue; +} + + + +/** + * Modifies the file via memory mapping. + * + * @returns IPRT status code, fully bitched. + * @param pszFilename The file we're using as a test bed. + * @param pu32BufOrg The sane copy of the file that gets updated in + * parallel. + * @param cbBuf The size of the file and bufer. + * @param iRound The current round number. + * @param fCheckFirst Whether to read from the mapping the mapping + * before dirtying it the first time around. + * @param fCheckAfterFlush Whether to read from the mapping the mapping + * before dirtying it after a flush. + * @param cFlushes How many times we modify the mapping and flush + * it before one final modification and unmapping. + * @param fLargePages Whether to use large pages. + */ +static int MakeModifications(const char *pszFilename, uint32_t *pu32BufOrg, size_t cbBuf, uint32_t iRound, + bool fCheckFirst, bool fCheckAfterFlush, uint32_t cFlushes, bool fLargePages) +{ + + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + int rc = RTNtPathOpen(pszFilename, + GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + HANDLE hSection; + NTSTATUS rcNt = NtCreateSection(&hSection, + SECTION_ALL_ACCESS, + NULL, /*pObjAttrs*/ + NULL, /*pcbMax*/ + PAGE_READWRITE, + SEC_COMMIT, + hFile); + NtClose(hFile); + if (NT_SUCCESS(rcNt)) + { + PVOID pvMapping = NULL; + SIZE_T cbMapping = 0; + rcNt = NtMapViewOfSection(hSection, NtCurrentProcess(), + &pvMapping, + 0, /* ZeroBits */ + 0, /* CommitSize */ + NULL, /* SectionOffset */ + &cbMapping, + ViewUnmap, + fLargePages ? MEM_LARGE_PAGES : 0, + PAGE_READWRITE); + if (NT_SUCCESS(rcNt)) + { + /* + * Make the modifications. + */ + if (g_cVerbosity >= 2) + RTPrintf("debug: pvMapping=%p LB %#x\n", pvMapping, cbBuf); + + for (uint32_t iInner = 0;; iInner++) + { + if (iInner ? fCheckAfterFlush : fCheckFirst) + { + if (iInner == 0) + rc = CheckBuffer((uint32_t *)pvMapping, cbBuf, iRound - 1, 0); + else + rc = CheckBuffer((uint32_t *)pvMapping, cbBuf, iRound, cFlushes - iInner + 1); + if (RT_FAILURE(rc)) + { + RTMsgError("Round %u/%u: NtUnmapViewOfSection failed: %#x\n", iRound, rcNt); + break; + } + } + + UpdateBuffer((uint32_t *)pvMapping, cbBuf, iRound, cFlushes - iInner); + UpdateBuffer(pu32BufOrg, cbBuf, iRound, cFlushes - iInner); + + if (iInner >= cFlushes) + break; + + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + SIZE_T cbBuf2 = cbBuf; + PVOID pvMapping2 = pvMapping; + rcNt = NtFlushVirtualMemory(NtCurrentProcess(), &pvMapping2, &cbBuf2, &Ios); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtFlushVirtualMemory failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + break; + } + } + + /* + * Cleanup. + */ + rcNt = NtUnmapViewOfSection(NtCurrentProcess(), pvMapping); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtUnmapViewOfSection failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + { + RTMsgError("Round %u: NtMapViewOfSection failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + + rcNt = NtClose(hSection); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtClose(hSection) failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + { + RTMsgError("Round %u: NtCreateSection failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + RTMsgError("Round %u: Error opening file '%s' for memory mapping: %Rrc\n", iRound, pszFilename, rc); + return rc; +} + + +int main(int argc, char **argv) +{ + /* + * Init IPRT. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Parse arguments. + */ + const char *pszFilename = NULL; + uint32_t cRounds = 4096; + uint32_t cPages = 128; + bool fLargePages = false; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--rounds", 'r', RTGETOPT_REQ_UINT32 }, + { "--pages", 'p', RTGETOPT_REQ_UINT32 }, + { "--filename", 'f', RTGETOPT_REQ_STRING }, + { "--large-pages", 'l', RTGETOPT_REQ_NOTHING }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTSTATE State; + RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + RTGETOPTUNION ValueUnion; + int chOpt; + while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0) + { + switch (chOpt) + { + case 'r': cRounds = ValueUnion.u32; break; + case 'p': cPages = ValueUnion.u32; break; + case 'f': pszFilename = ValueUnion.psz; break; + case 'l': fLargePages = true; break; + case 'q': g_cVerbosity = 0; break; + case 'v': g_cVerbosity += 1; break; + case 'h': + RTPrintf("usage: ntFlushVirtualMemory [-c <times>] [-p <pages>] [-l|--large-pages] [-f <filename>]\n" + "\n" + "Aims at testing memory mapped files on NT w/ NtFlushVirtualMemory / FlushViewOfFile.\n"); + return 0; + + default: + return RTGetOptPrintError(chOpt, &ValueUnion); + } + } + + /* + * Allocate buffers and initialize the original with page numbers. + * + * We keep a original copy that gets updated in parallel to the memory + * mapping, allowing for simple file initialization and memcpy checking. + * + * The second buffer is for reading the file from disk and check. + */ + size_t const cbBuf = cPages * X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(uint32_t); + uint32_t *pu32BufOrg = (uint32_t *)RTMemPageAllocZ(cbBuf); + uint32_t *pu32BufChk = (uint32_t *)RTMemPageAllocZ(cbBuf); + if (pu32BufOrg == NULL || pu32BufChk == NULL) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate two %zu sized buffers!\n", cbBuf); + + for (uint32_t iPage = 0; iPage < cPages; iPage++) + pu32BufOrg[iPage * cItemsPerPage + cItemsPerPage - 1] = MAKE_PAGE_SIGNATURE(iPage); + + rc = CheckBuffer(pu32BufOrg, cbBuf, 0, 0); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Internal error: CheckBuffer failed on virgin buffer: %Rrc\n", rc); + + /* + * Open the file and write out the orignal one. + */ + RTFILE hFile; + if (!pszFilename) + { + char *pszBuf = (char *)alloca(RTPATH_MAX); + rc = RTFileOpenTemp(&hFile, pszBuf, RTPATH_MAX, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary file: %Rrc\n", rc); + pszFilename = pszBuf; + } + else + { + rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open '%s': %Rrc\n", rc); + } + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + rc = RTFileWrite(hFile, pu32BufOrg, cbBuf, NULL); + if (RT_SUCCESS(rc)) + { + RTFileClose(hFile); + + /* + * Do the rounds. We count from 1 here to make verifying the previous round simpler. + */ + for (uint32_t iRound = 1; iRound <= cRounds; iRound++) + { + rc = MakeModifications(pszFilename, pu32BufOrg, cbBuf, iRound, + ((iRound >> 5) & 1) == 1, ((iRound >> 5) & 3) == 3, (iRound >> 3) & 31, fLargePages); + if (RT_SUCCESS(rc)) + { + rc = CheckBuffer(pu32BufOrg, cbBuf, iRound, 0); + if (RT_SUCCESS(rc)) + { + rc = CheckFile(pszFilename, pu32BufChk, pu32BufOrg, cbBuf, iRound); + if (RT_SUCCESS(rc)) + continue; + } + } + break; + } + } + else + { + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error writing initial %zu bytes to '%s': %Rrc\n", cbBuf, rc); + RTFileClose(hFile); + } + RTFileDelete(pszFilename); + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/nt/ntsetfreq.cpp b/src/VBox/ValidationKit/utils/nt/ntsetfreq.cpp new file mode 100644 index 00000000..4c04eafc --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/ntsetfreq.cpp @@ -0,0 +1,151 @@ +/* $Id: ntsetfreq.cpp $ */ +/** @file + * Set the NT timer frequency. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt.h> + +#include <iprt/initterm.h> +#include <iprt/getopt.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/errcore.h> + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Parse arguments. + */ + bool fVerbose = true; + uint32_t u32NewRes = 0; + uint32_t cSecsSleep = UINT32_MAX; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--resolution", 'r', RTGETOPT_REQ_UINT32 }, + { "--sleep", 's', RTGETOPT_REQ_UINT32 }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((rc = RTGetOpt(&GetState, &ValueUnion))) + { + switch (rc) + { + case 'r': + u32NewRes = ValueUnion.u32; + if (u32NewRes > 16*10000 /* 16 ms */ || u32NewRes < 1000 /* 100 microsec */) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, + "syntax error: the new timer resolution (%RU32) is out of range\n", + u32NewRes); + break; + + case 's': + cSecsSleep = ValueUnion.u32; + break; + + case 'q': + fVerbose = false; + break; + + case 'v': + fVerbose = true; + break; + + case 'h': + RTPrintf("Usage: ntsetfreq [-q|--quiet] [-v|--verbose] [-r|--resolution <100ns>] [-s|--sleep <1s>]\n"); + return RTEXITCODE_SUCCESS; + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + + /* + * Query the current resolution. + */ + ULONG Cur = UINT32_MAX; + ULONG Min = UINT32_MAX; + ULONG Max = UINT32_MAX; + NTSTATUS rcNt = STATUS_SUCCESS; + if (fVerbose || !u32NewRes) + { + rcNt = NtQueryTimerResolution(&Min, &Max, &Cur); + if (NT_SUCCESS(rcNt)) + RTMsgInfo("cur: %u (%u.%02u Hz) min: %u (%u.%02u Hz) max: %u (%u.%02u Hz)\n", + Cur, 10000000 / Cur, (10000000 / (Cur * 100)) % 100, + Min, 10000000 / Min, (10000000 / (Min * 100)) % 100, + Max, 10000000 / Max, (10000000 / (Max * 100)) % 100); + else + RTMsgError("NTQueryTimerResolution failed with status %#x\n", rcNt); + } + + if (u32NewRes) + { + rcNt = NtSetTimerResolution(u32NewRes, TRUE, &Cur); + if (!NT_SUCCESS(rcNt)) + RTMsgError("NTSetTimerResolution(%RU32,,) failed with status %#x\n", u32NewRes, rcNt); + else if (fVerbose) + { + Cur = Min = Max = UINT32_MAX; + rcNt = NtQueryTimerResolution(&Min, &Max, &Cur); + if (NT_SUCCESS(rcNt)) + RTMsgInfo("new: %u (%u.%02u Hz) requested %RU32 (%u.%02u Hz)\n", + Cur, 10000000 / Cur, (10000000 / (Cur * 100)) % 100, + u32NewRes, 10000000 / u32NewRes, (10000000 / (u32NewRes * 100)) % 100); + else + RTMsgError("NTSetTimerResolution succeeded but the NTQueryTimerResolution call failed with status %#x (ignored)\n", + rcNt); + rcNt = STATUS_SUCCESS; + } + } + + if (u32NewRes && NT_SUCCESS(rcNt)) + { + if (cSecsSleep == UINT32_MAX) + for (;;) + RTThreadSleep(RT_INDEFINITE_WAIT); + else + while (cSecsSleep-- > 0) + RTThreadSleep(1000); + } + + return NT_SUCCESS(rcNt) ? 0 : 1; +} + diff --git a/src/VBox/ValidationKit/utils/nt/nttimesources.cpp b/src/VBox/ValidationKit/utils/nt/nttimesources.cpp new file mode 100644 index 00000000..31e1e2b9 --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/nttimesources.cpp @@ -0,0 +1,236 @@ +/* $Id: nttimesources.cpp $ */ +/** @file + * Check the various time sources on Windows NT. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> + +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/test.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct _MY_KSYSTEM_TIME +{ + ULONG LowPart; + LONG High1Time; + LONG High2Time; +} MY_KSYSTEM_TIME; + +typedef struct _MY_KUSER_SHARED_DATA +{ + ULONG TickCountLowDeprecated; + ULONG TickCountMultiplier; + volatile MY_KSYSTEM_TIME InterruptTime; + volatile MY_KSYSTEM_TIME SystemTime; + volatile MY_KSYSTEM_TIME TimeZoneBias; + /* The rest is not relevant. */ +} MY_KUSER_SHARED_DATA; + +/** The fixed pointer to the user shared data. */ +#define MY_USER_SHARED_DATA ((MY_KUSER_SHARED_DATA *)0x7ffe0000) + +/** Spins until GetTickCount() changes. */ +static void SpinUntilTick(void) +{ + /* spin till GetTickCount changes. */ + DWORD dwMsTick = GetTickCount(); + while (GetTickCount() == dwMsTick) + /* nothing */; +} + +/** Delay function that tries to return right after GetTickCount changed. */ +static void DelayMillies(DWORD dwMsStart, DWORD cMillies) +{ + /* Delay cMillies - 1. */ + Sleep(cMillies - 1); + while (GetTickCount() - dwMsStart < cMillies - 1U) + Sleep(1); + + SpinUntilTick(); +} + + +int main(int argc, char **argv) +{ + RT_NOREF1(argv); + + /* + * Init, create a test instance and "parse" arguments. + */ + RTTEST hTest; + int rc = RTTestInitAndCreate("nttimesources", &hTest); + if (rc) + return rc; + if (argc > 1) + { + RTTestFailed(hTest, "Syntax error! no arguments expected"); + return RTTestSummaryAndDestroy(hTest); + } + + /* + * Guess MHz using GetTickCount. + */ + RTTestSub(hTest, "Guess MHz"); + DWORD dwTickStart, dwTickEnd, cMsTicks; + uint64_t u64TscStart, u64TscEnd, cTscTicks; + + /* get a good start time. */ + SpinUntilTick(); + do + { + dwTickStart = GetTickCount(); + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscStart = ASMReadTSC(); + ASMCompilerBarrier(); + } while (GetTickCount() != dwTickStart); + + /* delay a good while. */ + DelayMillies(dwTickStart, 256); + + /* get a good end time. */ + do + { + dwTickEnd = GetTickCount(); + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscEnd = ASMReadTSC(); + ASMCompilerBarrier(); + } while (GetTickCount() != dwTickEnd); + cMsTicks = dwTickEnd - dwTickStart; + cTscTicks = u64TscEnd - u64TscStart; + + /* Calc an approximate TSC frequency: + cTscTicks / uTscHz = cMsTicks / 1000 + 1 / uTscHz = (cMsTicks / 1000) / cTscTicks + uTscHz = cTscTicks / (cMsTicks / 1000) */ + uint64_t u64TscHz = (long double)cTscTicks / ((long double)cMsTicks / 1000.0); + if ( u64TscHz > _1M*3 + && u64TscHz < _1T) + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "u64TscHz=%'llu", u64TscHz); + else + { + RTTestFailed(hTest, "u64TscHz=%'llu - out of range", u64TscHz); + u64TscHz = 0; + } + + + /* + * Pit GetTickCount, InterruptTime, Performance Counters and TSC against each other. + */ + LARGE_INTEGER PrfHz; + LARGE_INTEGER PrfStart, PrfEnd, cPrfTicks; + LARGE_INTEGER IntStart, IntEnd, cIntTicks; + for (uint32_t i = 0; i < 7; i++) + { + RTTestSubF(hTest, "The whole bunch - pass #%u", i + 1); + + if (!QueryPerformanceFrequency(&PrfHz)) + { + RTTestFailed(hTest, "QueryPerformanceFrequency failed (%u)", GetLastError()); + return RTTestSummaryAndDestroy(hTest); + } + + /* get a good start time. */ + SpinUntilTick(); + do + { + IntStart.HighPart = MY_USER_SHARED_DATA->InterruptTime.High1Time; + IntStart.LowPart = MY_USER_SHARED_DATA->InterruptTime.LowPart; + dwTickStart = GetTickCount(); + if (!QueryPerformanceCounter(&PrfStart)) + { + RTTestFailed(hTest, "QueryPerformanceCounter failed (%u)", GetLastError()); + return RTTestSummaryAndDestroy(hTest); + } + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscStart = ASMReadTSC(); + ASMCompilerBarrier(); + } while ( MY_USER_SHARED_DATA->InterruptTime.High2Time != IntStart.HighPart + || MY_USER_SHARED_DATA->InterruptTime.LowPart != IntStart.LowPart + || GetTickCount() != dwTickStart); + + /* delay a good while. */ + DelayMillies(dwTickStart, 256); + + /* get a good end time. */ + do + { + IntEnd.HighPart = MY_USER_SHARED_DATA->InterruptTime.High1Time; + IntEnd.LowPart = MY_USER_SHARED_DATA->InterruptTime.LowPart; + dwTickEnd = GetTickCount(); + if (!QueryPerformanceCounter(&PrfEnd)) + { + RTTestFailed(hTest, "QueryPerformanceCounter failed (%u)", GetLastError()); + return RTTestSummaryAndDestroy(hTest); + } + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscEnd = ASMReadTSC(); + ASMCompilerBarrier(); + } while ( MY_USER_SHARED_DATA->InterruptTime.High2Time != IntEnd.HighPart + || MY_USER_SHARED_DATA->InterruptTime.LowPart != IntEnd.LowPart + || GetTickCount() != dwTickEnd); + + cMsTicks = dwTickEnd - dwTickStart; + cTscTicks = u64TscEnd - u64TscStart; + cIntTicks.QuadPart = IntEnd.QuadPart - IntStart.QuadPart; + cPrfTicks.QuadPart = PrfEnd.QuadPart - PrfStart.QuadPart; + + /* Recalc to micro seconds. */ + uint64_t u64MicroSecMs = (uint64_t)cMsTicks * 1000; + uint64_t u64MicroSecTsc = u64TscHz ? (long double)cTscTicks / u64TscHz * 1000000 : u64MicroSecMs; + uint64_t u64MicroSecInt = cIntTicks.QuadPart / 10; /* 100ns units*/ + uint64_t u64MicroSecPrf = (long double)cPrfTicks.QuadPart / PrfHz.QuadPart * 1000000; + + /* check how much they differ using the millisecond tick count as the standard candle. */ + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - GetTickCount\n", u64MicroSecMs, 0); + + int64_t off = u64MicroSecTsc - u64MicroSecMs; + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - TSC\n", u64MicroSecTsc, off); + RTTEST_CHECK(hTest, RT_ABS(off) < 50000 /*us*/); /* some extra uncertainty with TSC. */ + + off = u64MicroSecInt - u64MicroSecMs; + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - InterruptTime\n", u64MicroSecInt, off); + RTTEST_CHECK(hTest, RT_ABS(off) < 25000 /*us*/); + + off = u64MicroSecPrf - u64MicroSecMs; + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - QueryPerformanceCounter\n", u64MicroSecPrf, off); + RTTEST_CHECK(hTest, RT_ABS(off) < 25000 /*us*/); + } + + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/ValidationKit/utils/serial/Makefile.kmk b/src/VBox/ValidationKit/utils/serial/Makefile.kmk new file mode 100644 index 00000000..0e69f13f --- /dev/null +++ b/src/VBox/ValidationKit/utils/serial/Makefile.kmk @@ -0,0 +1,39 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Serial port tests. +# + +# +# Copyright (C) 2017-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Serial port testing utility. +# +PROGRAMS += SerialTest +SerialTest_TEMPLATE = VBoxValidationKitR3 +SerialTest_SOURCES = SerialTest.cpp + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/serial/SerialTest.cpp b/src/VBox/ValidationKit/utils/serial/SerialTest.cpp new file mode 100644 index 00000000..979f5ed7 --- /dev/null +++ b/src/VBox/ValidationKit/utils/serial/SerialTest.cpp @@ -0,0 +1,969 @@ +/* $Id: SerialTest.cpp $ */ +/** @file + * SerialTest - Serial port testing utility. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/errcore.h> +#include <iprt/getopt.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/serialport.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Number of times to toggle the status lines during the test. */ +#define SERIALTEST_STS_LINE_TOGGLE_COUNT 100 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + + +/** + * Serial test mode. + */ +typedef enum SERIALTESTMODE +{ + /** Invalid mode. */ + SERIALTESTMODE_INVALID = 0, + /** Serial port is looped back to itself */ + SERIALTESTMODE_LOOPBACK, + /** A secondary serial port is used with a null modem cable in between. */ + SERIALTESTMODE_SECONDARY, + /** The serial port is connected externally over which we have no control. */ + SERIALTESTMODE_EXTERNAL, + /** Usual 32bit hack. */ + SERIALTESTMODE_32BIT_HACK = 0x7fffffff +} SERIALTESTMODE; +/** Pointer to a serial test mode. */ +typedef SERIALTESTMODE *PSERIALTESTMDOE; + +/** Pointer to the serial test data instance. */ +typedef struct SERIALTEST *PSERIALTEST; + +/** + * Test callback function. + * + * @returns IPRT status code. + * @param pSerialTest The serial test instance data. + */ +typedef DECLCALLBACK(int) FNSERIALTESTRUN(PSERIALTEST pSerialTest); +/** Pointer to the serial test callback. */ +typedef FNSERIALTESTRUN *PFNSERIALTESTRUN; + + +/** + * The serial test instance data. + */ +typedef struct SERIALTEST +{ + /** The assigned test handle. */ + RTTEST hTest; + /** The assigned serial port. */ + RTSERIALPORT hSerialPort; + /** The currently active config. */ + PCRTSERIALPORTCFG pSerialCfg; +} SERIALTEST; + + +/** + * Test descriptor. + */ +typedef struct SERIALTESTDESC +{ + /** Test ID. */ + const char *pszId; + /** Test description. */ + const char *pszDesc; + /** Test run callback. */ + PFNSERIALTESTRUN pfnRun; +} SERIALTESTDESC; +/** Pointer to a test descriptor. */ +typedef SERIALTESTDESC *PSERIALTESTDESC; +/** Pointer to a constant test descriptor. */ +typedef const SERIALTESTDESC *PCSERIALTESTDESC; + + +/** + * TX/RX buffer containing a simple counter. + */ +typedef struct SERIALTESTTXRXBUFCNT +{ + /** The current counter value. */ + uint32_t iCnt; + /** Number of bytes left to receive/transmit. */ + size_t cbTxRxLeft; + /** The offset into the buffer to receive to/send from. */ + size_t offBuf; + /** Maximum size to send/receive before processing is needed again. */ + size_t cbTxRxMax; + /** The data buffer. */ + uint8_t abBuf[_1K]; +} SERIALTESTTXRXBUFCNT; +/** Pointer to a TX/RX buffer. */ +typedef SERIALTESTTXRXBUFCNT *PSERIALTESTTXRXBUFCNT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + + +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + {"--device", 'd', RTGETOPT_REQ_STRING }, + {"--baudrate", 'b', RTGETOPT_REQ_UINT32 }, + {"--parity", 'p', RTGETOPT_REQ_STRING }, + {"--databits", 'c', RTGETOPT_REQ_UINT32 }, + {"--stopbits", 's', RTGETOPT_REQ_STRING }, + {"--mode", 'm', RTGETOPT_REQ_STRING }, + {"--secondarydevice", 'l', RTGETOPT_REQ_STRING }, + {"--tests", 't', RTGETOPT_REQ_STRING }, + {"--txbytes", 'x', RTGETOPT_REQ_UINT32 }, + {"--verbose", 'v', RTGETOPT_REQ_NOTHING}, + {"--help", 'h', RTGETOPT_REQ_NOTHING} +}; + + +static DECLCALLBACK(int) serialTestRunReadWrite(PSERIALTEST pSerialTest); +static DECLCALLBACK(int) serialTestRunWrite(PSERIALTEST pSerialTest); +static DECLCALLBACK(int) serialTestRunStsLines(PSERIALTEST pSerialTest); + +/** Implemented tests. */ +static const SERIALTESTDESC g_aSerialTests[] = +{ + {"readwrite", "Simple Read/Write test on the same serial port", serialTestRunReadWrite }, + {"write", "Simple write test (verification done somewhere else)", serialTestRunWrite }, + {"stslines", "Testing the status line setting and receiving", serialTestRunStsLines } +}; + +/** Verbosity value. */ +static unsigned g_cVerbosity = 0; +/** The test handle. */ +static RTTEST g_hTest = NIL_RTTEST; +/** The serial test mode. */ +static SERIALTESTMODE g_enmMode = SERIALTESTMODE_LOOPBACK; +/** Random number generator. */ +static RTRAND g_hRand = NIL_RTRAND; +/** The serial port handle. */ +static RTSERIALPORT g_hSerialPort = NIL_RTSERIALPORT; +/** The loopback serial port handle if configured. */ +static RTSERIALPORT g_hSerialPortSecondary = NIL_RTSERIALPORT; +/** Number of bytes to transmit for read/write tests. */ +static size_t g_cbTx = _1M; +/** The config used. */ +static RTSERIALPORTCFG g_SerialPortCfg = +{ + /* uBaudRate */ + 115200, + /* enmParity */ + RTSERIALPORTPARITY_NONE, + /* enmDataBitCount */ + RTSERIALPORTDATABITS_8BITS, + /* enmStopBitCount */ + RTSERIALPORTSTOPBITS_ONE +}; + + +/** + * Initializes a TX buffer. + * + * @returns nothing. + * @param pSerBuf The serial buffer to initialize. + * @param cbTx Maximum number of bytes to transmit. + */ +static void serialTestTxBufInit(PSERIALTESTTXRXBUFCNT pSerBuf, size_t cbTx) +{ + pSerBuf->iCnt = 0; + pSerBuf->offBuf = 0; + pSerBuf->cbTxRxMax = 0; + pSerBuf->cbTxRxLeft = cbTx; + RT_ZERO(pSerBuf->abBuf); +} + + +/** + * Initializes a RX buffer. + * + * @returns nothing. + * @param pSerBuf The serial buffer to initialize. + * @param cbRx Maximum number of bytes to receive. + */ +static void serialTestRxBufInit(PSERIALTESTTXRXBUFCNT pSerBuf, size_t cbRx) +{ + pSerBuf->iCnt = 0; + pSerBuf->offBuf = 0; + pSerBuf->cbTxRxMax = sizeof(pSerBuf->abBuf); + pSerBuf->cbTxRxLeft = cbRx; + RT_ZERO(pSerBuf->abBuf); +} + + +/** + * Prepares the given TX buffer with data for sending it out. + * + * @returns nothing. + * @param pSerBuf The TX buffer pointer. + */ +static void serialTestTxBufPrepare(PSERIALTESTTXRXBUFCNT pSerBuf) +{ + /* Move the data to the front to make room at the end to fill. */ + if (pSerBuf->offBuf) + { + memmove(&pSerBuf->abBuf[0], &pSerBuf->abBuf[pSerBuf->offBuf], sizeof(pSerBuf->abBuf) - pSerBuf->offBuf); + pSerBuf->offBuf = 0; + } + + /* Fill up with data. */ + uint32_t offData = 0; + while (pSerBuf->cbTxRxMax + sizeof(uint32_t) <= sizeof(pSerBuf->abBuf)) + { + pSerBuf->iCnt++; + *(uint32_t *)&pSerBuf->abBuf[pSerBuf->offBuf + offData] = pSerBuf->iCnt; + pSerBuf->cbTxRxMax += sizeof(uint32_t); + offData += sizeof(uint32_t); + } +} + + +/** + * Sends a new batch of data from the TX buffer preapring new data if required. + * + * @returns IPRT status code. + * @param hSerialPort The serial port handle to send the data to. + * @param pSerBuf The TX buffer pointer. + */ +static int serialTestTxBufSend(RTSERIALPORT hSerialPort, PSERIALTESTTXRXBUFCNT pSerBuf) +{ + int rc = VINF_SUCCESS; + + if (pSerBuf->cbTxRxLeft) + { + if (!pSerBuf->cbTxRxMax) + serialTestTxBufPrepare(pSerBuf); + + size_t cbToWrite = RT_MIN(pSerBuf->cbTxRxMax, pSerBuf->cbTxRxLeft); + size_t cbWritten = 0; + rc = RTSerialPortWriteNB(hSerialPort, &pSerBuf->abBuf[pSerBuf->offBuf], cbToWrite, &cbWritten); + if (RT_SUCCESS(rc)) + { + pSerBuf->cbTxRxMax -= cbWritten; + pSerBuf->offBuf += cbWritten; + pSerBuf->cbTxRxLeft -= cbWritten; + } + } + + return rc; +} + + +/** + * Receives dat from the given serial port into the supplied RX buffer and does some validity checking. + * + * @returns IPRT status code. + * @param hSerialPort The serial port handle to receive data from. + * @param pSerBuf The RX buffer pointer. + */ +static int serialTestRxBufRecv(RTSERIALPORT hSerialPort, PSERIALTESTTXRXBUFCNT pSerBuf) +{ + int rc = VINF_SUCCESS; + + if (pSerBuf->cbTxRxLeft) + { + size_t cbToRead = RT_MIN(pSerBuf->cbTxRxMax, pSerBuf->cbTxRxLeft); + size_t cbRead = 0; + rc = RTSerialPortReadNB(hSerialPort, &pSerBuf->abBuf[pSerBuf->offBuf], cbToRead, &cbRead); + if (RT_SUCCESS(rc)) + { + pSerBuf->offBuf += cbRead; + pSerBuf->cbTxRxMax -= cbRead; + pSerBuf->cbTxRxLeft -= cbRead; + } + } + + return rc; +} + + +/** + * Verifies the data in the given RX buffer for correct transmission. + * + * @returns Flag whether verification failed. + * @param hTest The test handle to report errors to. + * @param pSerBuf The RX buffer pointer. + * @param iCntTx The current TX counter value the RX buffer should never get ahead of. + */ +static bool serialTestRxBufVerify(RTTEST hTest, PSERIALTESTTXRXBUFCNT pSerBuf, uint32_t iCntTx) +{ + uint32_t offRx = 0; + bool fFailed = false; + + while (offRx + sizeof(uint32_t) < pSerBuf->offBuf) + { + uint32_t u32Val = *(uint32_t *)&pSerBuf->abBuf[offRx]; + offRx += sizeof(uint32_t); + + if (RT_UNLIKELY(u32Val != ++pSerBuf->iCnt)) + { + fFailed = true; + if (g_cVerbosity > 0) + RTTestFailed(hTest, "Data corruption/loss detected, expected counter value %u got %u\n", + pSerBuf->iCnt, u32Val); + } + } + + if (RT_UNLIKELY(pSerBuf->iCnt > iCntTx)) + { + fFailed = true; + RTTestFailed(hTest, "Overtook the send buffer, expected maximum counter value %u got %u\n", + iCntTx, pSerBuf->iCnt); + } + + /* Remove processed data from the buffer and move the rest to the front. */ + if (offRx) + { + memmove(&pSerBuf->abBuf[0], &pSerBuf->abBuf[offRx], sizeof(pSerBuf->abBuf) - offRx); + pSerBuf->offBuf -= offRx; + pSerBuf->cbTxRxMax += offRx; + } + + return fFailed; +} + + +DECLINLINE(bool) serialTestRndTrue(void) +{ + return RTRandAdvU32Ex(g_hRand, 0, 1) == 1; +} + +/** + * Runs a simple read/write test. + * + * @returns IPRT status code. + * @param pSerialTest The serial test configuration. + */ +static DECLCALLBACK(int) serialTestRunReadWrite(PSERIALTEST pSerialTest) +{ + uint64_t tsStart = RTTimeNanoTS(); + bool fFailed = false; + SERIALTESTTXRXBUFCNT SerBufTx; + SERIALTESTTXRXBUFCNT SerBufRx; + + serialTestTxBufInit(&SerBufTx, g_cbTx); + serialTestRxBufInit(&SerBufRx, g_cbTx); + + int rc = serialTestTxBufSend(pSerialTest->hSerialPort, &SerBufTx); + while ( RT_SUCCESS(rc) + && ( SerBufTx.cbTxRxLeft + || SerBufRx.cbTxRxLeft)) + { + uint32_t fEvts = 0; + uint32_t fEvtsQuery = 0; + if (SerBufTx.cbTxRxLeft) + fEvtsQuery |= RTSERIALPORT_EVT_F_DATA_TX; + if (SerBufRx.cbTxRxLeft) + fEvtsQuery |= RTSERIALPORT_EVT_F_DATA_RX; + + rc = RTSerialPortEvtPoll(pSerialTest->hSerialPort, fEvtsQuery, &fEvts, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + + if (fEvts & RTSERIALPORT_EVT_F_DATA_RX) + { + rc = serialTestRxBufRecv(pSerialTest->hSerialPort, &SerBufRx); + if (RT_FAILURE(rc)) + break; + + bool fRes = serialTestRxBufVerify(pSerialTest->hTest, &SerBufRx, SerBufTx.iCnt); + if (fRes && !fFailed) + { + fFailed = true; + RTTestFailed(pSerialTest->hTest, "Data corruption/loss detected\n"); + } + } + if ( RT_SUCCESS(rc) + && (fEvts & RTSERIALPORT_EVT_F_DATA_TX)) + rc = serialTestTxBufSend(pSerialTest->hSerialPort, &SerBufTx); + } + + uint64_t tsRuntime = RTTimeNanoTS() - tsStart; + size_t cNsPerByte = tsRuntime / g_cbTx; + uint64_t cbBytesPerSec = RT_NS_1SEC / cNsPerByte; + RTTestValue(pSerialTest->hTest, "Throughput", cbBytesPerSec, RTTESTUNIT_BYTES_PER_SEC); + + return rc; +} + + +/** + * Runs a simple write test without doing any verification. + * + * @returns IPRT status code. + * @param pSerialTest The serial test configuration. + */ +static DECLCALLBACK(int) serialTestRunWrite(PSERIALTEST pSerialTest) +{ + uint64_t tsStart = RTTimeNanoTS(); + SERIALTESTTXRXBUFCNT SerBufTx; + + serialTestTxBufInit(&SerBufTx, g_cbTx); + + int rc = serialTestTxBufSend(pSerialTest->hSerialPort, &SerBufTx); + while ( RT_SUCCESS(rc) + && SerBufTx.cbTxRxLeft) + { + uint32_t fEvts = 0; + + rc = RTSerialPortEvtPoll(pSerialTest->hSerialPort, RTSERIALPORT_EVT_F_DATA_TX, &fEvts, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + + if (fEvts & RTSERIALPORT_EVT_F_DATA_TX) + rc = serialTestTxBufSend(pSerialTest->hSerialPort, &SerBufTx); + } + + uint64_t tsRuntime = RTTimeNanoTS() - tsStart; + size_t cNsPerByte = tsRuntime / g_cbTx; + uint64_t cbBytesPerSec = RT_NS_1SEC / cNsPerByte; + RTTestValue(pSerialTest->hTest, "Throughput", cbBytesPerSec, RTTESTUNIT_BYTES_PER_SEC); + + return rc; +} + + +/** + * Tests setting status lines and getting notified about status line changes. + * + * @returns IPRT status code. + * @param pSerialTest The serial test configuration. + */ +static DECLCALLBACK(int) serialTestRunStsLines(PSERIALTEST pSerialTest) +{ + int rc = VINF_SUCCESS; + + if (g_enmMode == SERIALTESTMODE_LOOPBACK) + { + uint32_t fStsLinesQueriedOld = 0; + + rc = RTSerialPortChgStatusLines(pSerialTest->hSerialPort, + RTSERIALPORT_CHG_STS_LINES_F_RTS | RTSERIALPORT_CHG_STS_LINES_F_DTR, + 0); + if (RT_SUCCESS(rc)) + { + rc = RTSerialPortQueryStatusLines(pSerialTest->hSerialPort, &fStsLinesQueriedOld); + if (RT_SUCCESS(rc)) + { + /* Everything should be clear at this stage. */ + if (!fStsLinesQueriedOld) + { + uint32_t fStsLinesSetOld = 0; + + for (uint32_t i = 0; i < SERIALTEST_STS_LINE_TOGGLE_COUNT; i++) + { + uint32_t fStsLinesSet = 0; + uint32_t fStsLinesClear = 0; + + /* Change RTS? */ + if (serialTestRndTrue()) + { + /* Clear, if set previously otherwise set it. */ + if (fStsLinesSetOld & RTSERIALPORT_CHG_STS_LINES_F_RTS) + fStsLinesClear |= RTSERIALPORT_CHG_STS_LINES_F_RTS; + else + fStsLinesSet |= RTSERIALPORT_CHG_STS_LINES_F_RTS; + } + + /* Change DTR? */ + if (serialTestRndTrue()) + { + /* Clear, if set previously otherwise set it. */ + if (fStsLinesSetOld & RTSERIALPORT_CHG_STS_LINES_F_DTR) + fStsLinesClear |= RTSERIALPORT_CHG_STS_LINES_F_DTR; + else + fStsLinesSet |= RTSERIALPORT_CHG_STS_LINES_F_DTR; + } + + rc = RTSerialPortChgStatusLines(pSerialTest->hSerialPort, fStsLinesClear, fStsLinesSet); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Changing status lines failed with %Rrc on iteration %u (fSet=%#x fClear=%#x)\n", + rc, i, fStsLinesSet, fStsLinesClear); + break; + } + + /* Wait for status line monitor event. */ + uint32_t fEvtsRecv = 0; + rc = RTSerialPortEvtPoll(pSerialTest->hSerialPort, RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED, + &fEvtsRecv, RT_MS_1SEC); + if ( RT_FAILURE(rc) + && (rc != VERR_TIMEOUT && !fStsLinesSet && !fStsLinesClear)) + { + RTTestFailed(g_hTest, "Waiting for status line change failed with %Rrc on iteration %u\n", + rc, i); + break; + } + + uint32_t fStsLinesQueried = 0; + rc = RTSerialPortQueryStatusLines(pSerialTest->hSerialPort, &fStsLinesQueried); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Querying status lines failed with %Rrc on iteration %u\n", + rc, i); + break; + } + + /* Compare expected and real result. */ + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_DSR) + != (fStsLinesQueriedOld & RTSERIALPORT_STS_LINE_DSR)) + { + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_DSR) + && !(fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + RTTestFailed(g_hTest, "DSR line got set when it shouldn't be on iteration %u\n", i); + else if ( !(fStsLinesQueried & RTSERIALPORT_STS_LINE_DSR) + && !(fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + RTTestFailed(g_hTest, "DSR line got cleared when it shouldn't be on iteration %u\n", i); + } + else if ( (fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_DTR) + || (fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + RTTestFailed(g_hTest, "DSR line didn't change when it should have on iteration %u\n", i); + + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_DCD) + != (fStsLinesQueriedOld & RTSERIALPORT_STS_LINE_DCD)) + { + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_DCD) + && !(fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + RTTestFailed(g_hTest, "DCD line got set when it shouldn't be on iteration %u\n", i); + else if ( !(fStsLinesQueried & RTSERIALPORT_STS_LINE_DCD) + && !(fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + RTTestFailed(g_hTest, "DCD line got cleared when it shouldn't be on iteration %u\n", i); + } + else if ( (fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_DTR) + || (fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + RTTestFailed(g_hTest, "DCD line didn't change when it should have on iteration %u\n", i); + + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_CTS) + != (fStsLinesQueriedOld & RTSERIALPORT_STS_LINE_CTS)) + { + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_CTS) + && !(fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_RTS)) + RTTestFailed(g_hTest, "CTS line got set when it shouldn't be on iteration %u\n", i); + else if ( !(fStsLinesQueried & RTSERIALPORT_STS_LINE_CTS) + && !(fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_RTS)) + RTTestFailed(g_hTest, "CTS line got cleared when it shouldn't be on iteration %u\n", i); + } + else if ( (fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_RTS) + || (fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_RTS)) + RTTestFailed(g_hTest, "CTS line didn't change when it should have on iteration %u\n", i); + + if (RTTestErrorCount(g_hTest) > 0) + break; + + fStsLinesSetOld |= fStsLinesSet; + fStsLinesSetOld &= ~fStsLinesClear; + fStsLinesQueriedOld = fStsLinesQueried; + } + } + else + RTTestFailed(g_hTest, "Status lines active which should be clear (%#x, but expected %#x)\n", + fStsLinesQueriedOld, 0); + } + else + RTTestFailed(g_hTest, "Querying status lines failed with %Rrc\n", rc); + } + else + RTTestFailed(g_hTest, "Clearing status lines failed with %Rrc\n", rc); + } + else + rc = VERR_NOT_IMPLEMENTED; + + return rc; +} + + +/** + * Returns an array of test descriptors get from the given string. + * + * @returns Pointer to the array of test descriptors. + * @param pszTests The string containing the tests separated with ':'. + */ +static PSERIALTESTDESC serialTestSelectFromCmdLine(const char *pszTests) +{ + size_t cTests = 1; + + const char *pszNext = strchr(pszTests, ':'); + while (pszNext) + { + pszNext++; + cTests++; + pszNext = strchr(pszNext, ':'); + } + + PSERIALTESTDESC paTests = (PSERIALTESTDESC)RTMemAllocZ((cTests + 1) * sizeof(SERIALTESTDESC)); + if (RT_LIKELY(paTests)) + { + uint32_t iTest = 0; + + pszNext = strchr(pszTests, ':'); + while (pszNext) + { + bool fFound = false; + + pszNext++; /* Skip : character. */ + + for (unsigned i = 0; i < RT_ELEMENTS(g_aSerialTests); i++) + { + if (!RTStrNICmp(pszTests, g_aSerialTests[i].pszId, pszNext - pszTests - 1)) + { + memcpy(&paTests[iTest], &g_aSerialTests[i], sizeof(SERIALTESTDESC)); + fFound = true; + break; + } + } + + if (RT_UNLIKELY(!fFound)) + { + RTPrintf("Testcase \"%.*s\" not known\n", pszNext - pszTests - 1, pszTests); + RTMemFree(paTests); + return NULL; + } + + pszTests = pszNext; + pszNext = strchr(pszTests, ':'); + } + + /* Fill last descriptor. */ + bool fFound = false; + for (unsigned i = 0; i < RT_ELEMENTS(g_aSerialTests); i++) + { + if (!RTStrICmp(pszTests, g_aSerialTests[i].pszId)) + { + memcpy(&paTests[iTest], &g_aSerialTests[i], sizeof(SERIALTESTDESC)); + fFound = true; + break; + } + } + + if (RT_UNLIKELY(!fFound)) + { + RTPrintf("Testcase \"%s\" not known\n", pszTests); + RTMemFree(paTests); + paTests = NULL; + } + } + else + RTPrintf("Failed to allocate test descriptors for %u selected tests\n", cTests); + + return paTests; +} + + +/** + * Shows tool usage text. + */ +static void serialTestUsage(PRTSTREAM pStrm) +{ + char szExec[RTPATH_MAX]; + RTStrmPrintf(pStrm, "usage: %s [options]\n", + RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec)))); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "options: \n"); + + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'h': + pszHelp = "Displays this help and exit"; + break; + case 'd': + pszHelp = "Use the specified serial port device"; + break; + case 'b': + pszHelp = "Use the given baudrate"; + break; + case 'p': + pszHelp = "Use the given parity, valid modes are: none, even, odd, mark, space"; + break; + case 'c': + pszHelp = "Use the given data bitcount, valid are: 5, 6, 7, 8"; + break; + case 's': + pszHelp = "Use the given stop bitcount, valid are: 1, 1.5, 2"; + break; + case 'm': + pszHelp = "Mode of the serial port, valid are: loopback, secondary, external"; + break; + case 'l': + pszHelp = "Use the given serial port device as the secondary device"; + break; + case 't': + pszHelp = "The tests to run separated by ':'"; + break; + case 'x': + pszHelp = "Number of bytes to transmit during read/write tests"; + break; + default: + pszHelp = "Option undocumented"; + break; + } + char szOpt[256]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-30s%s\n", szOpt, pszHelp); + } +} + + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT and globals. + */ + int rc = RTTestInitAndCreate("SerialTest", &g_hTest); + if (rc) + return rc; + + /* + * Default values. + */ + const char *pszDevice = NULL; + const char *pszDeviceSecondary = NULL; + PSERIALTESTDESC paTests = NULL; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion))) + { + switch (rc) + { + case 'h': + serialTestUsage(g_pStdOut); + return RTEXITCODE_SUCCESS; + case 'v': + g_cVerbosity++; + break; + case 'd': + pszDevice = ValueUnion.psz; + break; + case 'l': + pszDeviceSecondary = ValueUnion.psz; + break; + case 'b': + g_SerialPortCfg.uBaudRate = ValueUnion.u32; + break; + case 'p': + if (!RTStrICmp(ValueUnion.psz, "none")) + g_SerialPortCfg.enmParity = RTSERIALPORTPARITY_NONE; + else if (!RTStrICmp(ValueUnion.psz, "even")) + g_SerialPortCfg.enmParity = RTSERIALPORTPARITY_EVEN; + else if (!RTStrICmp(ValueUnion.psz, "odd")) + g_SerialPortCfg.enmParity = RTSERIALPORTPARITY_ODD; + else if (!RTStrICmp(ValueUnion.psz, "mark")) + g_SerialPortCfg.enmParity = RTSERIALPORTPARITY_MARK; + else if (!RTStrICmp(ValueUnion.psz, "space")) + g_SerialPortCfg.enmParity = RTSERIALPORTPARITY_SPACE; + else + { + RTPrintf("Unknown parity \"%s\" given\n", ValueUnion.psz); + return RTEXITCODE_FAILURE; + } + break; + case 'c': + if (ValueUnion.u32 == 5) + g_SerialPortCfg.enmDataBitCount = RTSERIALPORTDATABITS_5BITS; + else if (ValueUnion.u32 == 6) + g_SerialPortCfg.enmDataBitCount = RTSERIALPORTDATABITS_6BITS; + else if (ValueUnion.u32 == 7) + g_SerialPortCfg.enmDataBitCount = RTSERIALPORTDATABITS_7BITS; + else if (ValueUnion.u32 == 8) + g_SerialPortCfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS; + else + { + RTPrintf("Unknown data bitcount \"%u\" given\n", ValueUnion.u32); + return RTEXITCODE_FAILURE; + } + break; + case 's': + if (!RTStrICmp(ValueUnion.psz, "1")) + g_SerialPortCfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE; + else if (!RTStrICmp(ValueUnion.psz, "1.5")) + g_SerialPortCfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONEPOINTFIVE; + else if (!RTStrICmp(ValueUnion.psz, "2")) + g_SerialPortCfg.enmStopBitCount = RTSERIALPORTSTOPBITS_TWO; + else + { + RTPrintf("Unknown stop bitcount \"%s\" given\n", ValueUnion.psz); + return RTEXITCODE_FAILURE; + } + break; + case 'm': + if (!RTStrICmp(ValueUnion.psz, "loopback")) + g_enmMode = SERIALTESTMODE_LOOPBACK; + else if (!RTStrICmp(ValueUnion.psz, "secondary")) + g_enmMode = SERIALTESTMODE_SECONDARY; + else if (!RTStrICmp(ValueUnion.psz, "external")) + g_enmMode = SERIALTESTMODE_EXTERNAL; + else + { + RTPrintf("Unknown serial test mode \"%s\" given\n", ValueUnion.psz); + return RTEXITCODE_FAILURE; + } + break; + case 't': + paTests = serialTestSelectFromCmdLine(ValueUnion.psz); + if (!paTests) + return RTEXITCODE_FAILURE; + break; + case 'x': + g_cbTx = ValueUnion.u32; + break; + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + if (g_enmMode == SERIALTESTMODE_SECONDARY && !pszDeviceSecondary) + { + RTPrintf("Mode set to secondary device but no secondary device given\n"); + return RTEXITCODE_FAILURE; + } + + if (!paTests) + { + /* Select all. */ + paTests = (PSERIALTESTDESC)RTMemAllocZ((RT_ELEMENTS(g_aSerialTests) + 1) * sizeof(SERIALTESTDESC)); + if (RT_UNLIKELY(!paTests)) + { + RTPrintf("Failed to allocate memory for test descriptors\n"); + return RTEXITCODE_FAILURE; + } + memcpy(paTests, &g_aSerialTests[0], RT_ELEMENTS(g_aSerialTests) * sizeof(SERIALTESTDESC)); + } + + rc = RTRandAdvCreateParkMiller(&g_hRand); + if (RT_FAILURE(rc)) + { + RTPrintf("Failed to create random number generator: %Rrc\n", rc); + return RTEXITCODE_FAILURE; + } + + rc = RTRandAdvSeed(g_hRand, UINT64_C(0x123456789abcdef)); + AssertRC(rc); + + /* + * Start testing. + */ + RTTestBanner(g_hTest); + + if (pszDevice) + { + uint32_t fFlags = RTSERIALPORT_OPEN_F_READ + | RTSERIALPORT_OPEN_F_WRITE + | RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING; + + RTTestSub(g_hTest, "Opening device"); + rc = RTSerialPortOpen(&g_hSerialPort, pszDevice, fFlags); + if (RT_SUCCESS(rc)) + { + if (g_enmMode == SERIALTESTMODE_SECONDARY) + { + RTTestSub(g_hTest, "Opening secondary device"); + rc = RTSerialPortOpen(&g_hSerialPortSecondary, pszDeviceSecondary, fFlags); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Opening secondary device \"%s\" failed with %Rrc\n", pszDevice, rc); + } + + if (RT_SUCCESS(rc)) + { + RTTestSub(g_hTest, "Setting serial port configuration"); + + rc = RTSerialPortCfgSet(g_hSerialPort, &g_SerialPortCfg ,NULL); + if (RT_SUCCESS(rc)) + { + if (g_enmMode == SERIALTESTMODE_SECONDARY) + { + RTTestSub(g_hTest, "Setting serial port configuration for secondary device"); + rc = RTSerialPortCfgSet(g_hSerialPortSecondary, &g_SerialPortCfg, NULL); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Setting configuration of secondary device \"%s\" failed with %Rrc\n", pszDevice, rc); + } + + if (RT_SUCCESS(rc)) + { + SERIALTEST Test; + PSERIALTESTDESC pTest = &paTests[0]; + + Test.hTest = g_hTest; + Test.hSerialPort = g_hSerialPort; + Test.pSerialCfg = &g_SerialPortCfg; + + while (pTest->pszId) + { + RTTestSub(g_hTest, pTest->pszDesc); + rc = pTest->pfnRun(&Test); + if ( RT_FAILURE(rc) + || RTTestErrorCount(g_hTest) > 0) + RTTestFailed(g_hTest, "Running test \"%s\" failed (%Rrc, cErrors=%u)\n", + pTest->pszId, rc, RTTestErrorCount(g_hTest)); + + RTTestSubDone(g_hTest); + pTest++; + } + } + } + else + RTTestFailed(g_hTest, "Setting configuration of device \"%s\" failed with %Rrc\n", pszDevice, rc); + + RTSerialPortClose(g_hSerialPort); + } + } + else + RTTestFailed(g_hTest, "Opening device \"%s\" failed with %Rrc\n", pszDevice, rc); + } + else + RTTestFailed(g_hTest, "No device given on command line\n"); + + RTRandAdvDestroy(g_hRand); + RTMemFree(paTests); + RTEXITCODE rcExit = RTTestSummaryAndDestroy(g_hTest); + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/usb/Makefile.kmk b/src/VBox/ValidationKit/utils/usb/Makefile.kmk new file mode 100644 index 00000000..24f3c927 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/Makefile.kmk @@ -0,0 +1,58 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - USB test helpers. +# + +# +# Copyright (C) 2014-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# USB Linux test frontend. +# +ifeq ($(KBUILD_TARGET),linux) + PROGRAMS += UsbTest + UsbTest_TEMPLATE = VBoxValidationKitR3 + UsbTest_SOURCES = UsbTest.cpp +endif + +PROGRAMS += UsbTestService +UsbTestService_TEMPLATE = VBoxValidationKitR3 +UsbTestService_DEFS = \ + KBUILD_TARGET=\"$(KBUILD_TARGET)\" \ + KBUILD_TARGET_ARCH=\"$(KBUILD_TARGET_ARCH)\" +UsbTestService_SOURCES = \ + UsbTestService.cpp \ + UsbTestServiceGadgetCfg.cpp \ + UsbTestServiceGadgetClassTest.cpp \ + UsbTestServiceGadgetHost.cpp \ + UsbTestServiceGadgetHostUsbIp.cpp \ + UsbTestServiceGadget.cpp \ + UsbTestServiceProtocol.cpp \ + UsbTestServiceTcp.cpp +UsbTestService_SOURCES.linux = \ + UsbTestServicePlatform-linux.cpp + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTest.cpp b/src/VBox/ValidationKit/utils/usb/UsbTest.cpp new file mode 100644 index 00000000..bc13e1e0 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTest.cpp @@ -0,0 +1,658 @@ +/* $Id: UsbTest.cpp $ */ +/** @file + * UsbTest - User frontend for the Linux usbtest USB test and benchmarking module. + * Integrates with our test framework for nice outputs. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> + +#include <iprt/linux/sysfs.h> + +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <sys/ioctl.h> +#include <linux/usbdevice_fs.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * USB test request data. + * There is no public header with this information so we define it ourself here. + */ +typedef struct USBTESTPARMS +{ + /** Specifies the test to run. */ + uint32_t idxTest; + /** How many iterations the test should be executed. */ + uint32_t cIterations; + /** Size of the data packets. */ + uint32_t cbData; + /** Size of */ + uint32_t cbVariation; + /** Length of the S/G list for the test. */ + uint32_t cSgLength; + /** Returned time data after completing the test. */ + struct timeval TimeTest; +} USBTESTPARAMS; +/** Pointer to a test parameter structure. */ +typedef USBTESTPARAMS *PUSBTESTPARAMS; + +/** + * USB device descriptor. Used to search for the test device based + * on the vendor and product id. + */ +#pragma pack(1) +typedef struct USBDEVDESC +{ + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} USBDEVDESC; +#pragma pack() + +#define USBTEST_REQUEST _IOWR('U', 100, USBTESTPARMS) + +/** + * Callback to set up the test parameters for a specific test. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if setting the parameters up succeeded. Any other error code + * otherwise indicating the kind of error. + * @param idxTest The test index. + * @param pszTest Test name. + * @param pParams The USB test parameters to set up. + */ +typedef DECLCALLBACK(int) FNUSBTESTPARAMSSETUP(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams); +/** Pointer to a USB test parameters setup callback. */ +typedef FNUSBTESTPARAMSSETUP *PFNUSBTESTPARAMSSETUP; + +/** + * USB test descriptor. + */ +typedef struct USBTESTDESC +{ + /** (Sort of) Descriptive test name. */ + const char *pszName; + /** Flag whether the test is excluded. */ + bool fExcluded; + /** The parameter setup callback. */ + PFNUSBTESTPARAMSSETUP pfnParamsSetup; +} USBTESTDESC; +/** Pointer a USB test descriptor. */ +typedef USBTESTDESC *PUSBTESTDESC; + +/** + * USB speed values. + */ +typedef enum USBTESTSPEED +{ + USBTESTSPEED_ANY = 0, + USBTESTSPEED_UNKNOWN, + USBTESTSPEED_LOW, + USBTESTSPEED_FULL, + USBTESTSPEED_HIGH, + USBTESTSPEED_SUPER +} USBTESTSPEED; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Some forward method declarations. */ +static DECLCALLBACK(int) usbTestParamsSetupReadWrite(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams); +static DECLCALLBACK(int) usbTestParamsSetupControlWrites(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams); + +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + {"--device", 'd', RTGETOPT_REQ_STRING }, + {"--help", 'h', RTGETOPT_REQ_NOTHING}, + {"--exclude", 'e', RTGETOPT_REQ_UINT32}, + {"--exclude-all", 'a', RTGETOPT_REQ_NOTHING}, + {"--include", 'i', RTGETOPT_REQ_UINT32}, + {"--expected-speed", 's', RTGETOPT_REQ_STRING } +}; + +static USBTESTDESC g_aTests[] = +{ + /* pszTest fExcluded pfnParamsSetup */ + {"NOP", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk write", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk read", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk write variabe size", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk read variabe size", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk write", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk read", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk write variabe size", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk read variabe size", false, usbTestParamsSetupReadWrite}, + {"Chapter 9 Control Test", false, usbTestParamsSetupReadWrite}, + {"Queued control messaging", false, usbTestParamsSetupReadWrite}, + {"Unlink reads", false, usbTestParamsSetupReadWrite}, + {"Unlink writes", false, usbTestParamsSetupReadWrite}, + {"Set/Clear halts", false, usbTestParamsSetupReadWrite}, + {"Control writes", false, usbTestParamsSetupControlWrites}, + {"Isochronous write", false, usbTestParamsSetupReadWrite}, + {"Isochronous read", false, usbTestParamsSetupReadWrite}, + {"Bulk write unaligned (DMA)", false, usbTestParamsSetupReadWrite}, + {"Bulk read unaligned (DMA)", false, usbTestParamsSetupReadWrite}, + {"Bulk write unaligned (no DMA)", false, usbTestParamsSetupReadWrite}, + {"Bulk read unaligned (no DMA)", false, usbTestParamsSetupReadWrite}, + {"Control writes unaligned", false, usbTestParamsSetupControlWrites}, + {"Isochronous write unaligned", false, usbTestParamsSetupReadWrite}, + {"Isochronous read unaligned", false, usbTestParamsSetupReadWrite}, + {"Unlink queued Bulk", false, usbTestParamsSetupReadWrite} +}; + +/** The test handle. */ +static RTTEST g_hTest; +/** The expected device speed. */ +static USBTESTSPEED g_enmSpeed = USBTESTSPEED_ANY; + +/** + * Setup callback for basic read/write (bulk, isochronous) tests. + * + * @copydoc FNUSBTESTPARAMSSETUP + */ +static DECLCALLBACK(int) usbTestParamsSetupReadWrite(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams) +{ + NOREF(idxTest); + NOREF(pszTest); + + pParams->cIterations = 1000; + pParams->cbData = 512; + pParams->cbVariation = 512; + pParams->cSgLength = 32; + + return VINF_SUCCESS; +} + +/** + * Setup callback for the control writes test. + * + * @copydoc FNUSBTESTPARAMSSETUP + */ +static DECLCALLBACK(int) usbTestParamsSetupControlWrites(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams) +{ + NOREF(idxTest); + NOREF(pszTest); + + pParams->cIterations = 1000; + pParams->cbData = 512; + /* + * Must be smaller than cbData or the parameter check in the usbtest module fails, + * no idea yet why it must be this. + */ + pParams->cbVariation = 256; + pParams->cSgLength = 32; + + return VINF_SUCCESS; +} + +/** + * Shows tool usage text. + */ +static void usbTestUsage(PRTSTREAM pStrm) +{ + char szExec[RTPATH_MAX]; + RTStrmPrintf(pStrm, "usage: %s [options]\n", + RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec)))); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "options: \n"); + + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'h': + pszHelp = "Displays this help and exit"; + break; + case 'd': + pszHelp = "Use the specified test device"; + break; + case 'e': + pszHelp = "Exclude the given test id from the list"; + break; + case 'a': + pszHelp = "Exclude all tests from the list (useful to enable single tests later with --include)"; + break; + case 'i': + pszHelp = "Include the given test id in the list"; + break; + case 's': + pszHelp = "The device speed to expect"; + break; + default: + pszHelp = "Option undocumented"; + break; + } + char szOpt[256]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-30s%s\n", szOpt, pszHelp); + } +} + +/** + * Searches for a USB test device and returns the bus and device ID and the device speed. + */ +static int usbTestDeviceQueryBusAndDevId(uint16_t *pu16BusId, uint16_t *pu16DevId, USBTESTSPEED *penmSpeed) +{ + bool fFound = false; + +#define USBTEST_USB_DEV_SYSFS "/sys/bus/usb/devices/" + + RTDIR hDirUsb = NULL; + int rc = RTDirOpen(&hDirUsb, USBTEST_USB_DEV_SYSFS); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirUsbBus; + rc = RTDirRead(hDirUsb, &DirUsbBus, NULL); + if ( RT_SUCCESS(rc) + && RTStrNCmp(DirUsbBus.szName, "usb", 3) + && RTLinuxSysFsExists(USBTEST_USB_DEV_SYSFS "%s/idVendor", DirUsbBus.szName)) + { + int64_t idVendor = 0; + int64_t idProduct = 0; + int64_t iBusId = 0; + int64_t iDevId = 0; + char aszSpeed[20]; + + rc = RTLinuxSysFsReadIntFile(16, &idVendor, USBTEST_USB_DEV_SYSFS "%s/idVendor", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(16, &idProduct, USBTEST_USB_DEV_SYSFS "%s/idProduct", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(16, &iBusId, USBTEST_USB_DEV_SYSFS "%s/busnum", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(16, &iDevId, USBTEST_USB_DEV_SYSFS "%s/devnum", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadStrFile(&aszSpeed[0], sizeof(aszSpeed), NULL, USBTEST_USB_DEV_SYSFS "%s/speed", DirUsbBus.szName); + + if ( RT_SUCCESS(rc) + && idVendor == 0x0525 + && idProduct == 0xa4a0) + { + if (penmSpeed) + { + /* Parse the speed. */ + if (!RTStrCmp(&aszSpeed[0], "1.5")) + *penmSpeed = USBTESTSPEED_LOW; + else if (!RTStrCmp(&aszSpeed[0], "12")) + *penmSpeed = USBTESTSPEED_FULL; + else if (!RTStrCmp(&aszSpeed[0], "480")) + *penmSpeed = USBTESTSPEED_HIGH; + else if ( !RTStrCmp(&aszSpeed[0], "5000") + || !RTStrCmp(&aszSpeed[0], "10000")) + *penmSpeed = USBTESTSPEED_SUPER; + else + *penmSpeed = USBTESTSPEED_UNKNOWN; + } + + if (pu16BusId) + *pu16BusId = (uint16_t)iBusId; + if (pu16DevId) + *pu16DevId = (uint16_t)iDevId; + fFound = true; + break; + } + } + else if (rc != VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + } while ( RT_SUCCESS(rc) + && !fFound); + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + RTDirClose(hDirUsb); + } + + if (RT_SUCCESS(rc) && !fFound) + rc = VERR_NOT_FOUND; + + return rc; +} + +/** + * Search for a USB test device and return the device path. + * + * @returns Path to the USB test device or NULL if none was found. + */ +static char *usbTestFindDevice(void) +{ + /* + * Very crude and quick way to search for the correct test device. + * Assumption is that the path looks like /dev/bus/usb/%3d/%3d. + */ + char *pszDevPath = NULL; + + RTDIR hDirUsb = NULL; + int rc = RTDirOpen(&hDirUsb, "/dev/bus/usb"); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirUsbBus; + rc = RTDirRead(hDirUsb, &DirUsbBus, NULL); + if (RT_SUCCESS(rc)) + { + char aszPath[RTPATH_MAX + 1]; + RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), "/dev/bus/usb/%s", DirUsbBus.szName); + + RTDIR hDirUsbBus = NULL; + rc = RTDirOpen(&hDirUsbBus, &aszPath[0]); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirUsbDev; + rc = RTDirRead(hDirUsbBus, &DirUsbDev, NULL); + if (RT_SUCCESS(rc)) + { + char aszPathDev[RTPATH_MAX + 1]; + RTStrPrintf(&aszPathDev[0], RT_ELEMENTS(aszPathDev), "/dev/bus/usb/%s/%s", + DirUsbBus.szName, DirUsbDev.szName); + + RTFILE hFileDev; + rc = RTFileOpen(&hFileDev, aszPathDev, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + USBDEVDESC DevDesc; + + rc = RTFileRead(hFileDev, &DevDesc, sizeof(DevDesc), NULL); + RTFileClose(hFileDev); + + if ( RT_SUCCESS(rc) + && DevDesc.idVendor == 0x0525 + && DevDesc.idProduct == 0xa4a0) + pszDevPath = RTStrDup(aszPathDev); + } + + rc = VINF_SUCCESS; + } + else if (rc != VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + } while ( RT_SUCCESS(rc) + && !pszDevPath); + + rc = VINF_SUCCESS; + RTDirClose(hDirUsbBus); + } + } + else if (rc != VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + } while ( RT_SUCCESS(rc) + && !pszDevPath); + + RTDirClose(hDirUsb); + } + + return pszDevPath; +} + +static int usbTestIoctl(int iDevFd, int iInterface, PUSBTESTPARAMS pParams) +{ + struct usbdevfs_ioctl IoCtlData; + + IoCtlData.ifno = iInterface; + IoCtlData.ioctl_code = (int)USBTEST_REQUEST; + IoCtlData.data = pParams; + return ioctl(iDevFd, USBDEVFS_IOCTL, &IoCtlData); +} + +/** + * Test execution worker. + * + * @returns nothing. + * @param pszDevice The device to use for testing. + */ +static void usbTestExec(const char *pszDevice) +{ + int iDevFd; + + RTTestSub(g_hTest, "Opening device"); + iDevFd = open(pszDevice, O_RDWR); + if (iDevFd != -1) + { + USBTESTPARAMS Params; + + RTTestPassed(g_hTest, "Opening device successful\n"); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) + { + RTTestSub(g_hTest, g_aTests[i].pszName); + + if (g_aTests[i].fExcluded) + { + RTTestSkipped(g_hTest, "Excluded from list"); + continue; + } + + int rc = g_aTests[i].pfnParamsSetup(i, g_aTests[i].pszName, &Params); + if (RT_SUCCESS(rc)) + { + Params.idxTest = i; + + /* Assume the test interface has the number 0 for now. */ + int rcPosix = usbTestIoctl(iDevFd, 0, &Params); + if (rcPosix < 0 && errno == EOPNOTSUPP) + { + RTTestSkipped(g_hTest, "Not supported"); + continue; + } + + if (rcPosix < 0) + { + /* + * The error status code of the unlink testcase is + * offset by 2000 for the sync and 1000 for the sync code path + * (see drivers/usb/misc/usbtest.c in the Linux kernel sources). + * + * Adjust to the actual status code so converting doesn't assert. + */ + int iTmpErrno = errno; + if (iTmpErrno >= 2000) + iTmpErrno -= 2000; + else if (iTmpErrno >= 1000) + iTmpErrno -= 1000; + RTTestFailed(g_hTest, "Test failed with %Rrc\n", RTErrConvertFromErrno(iTmpErrno)); + } + else + { + uint64_t u64Ns = Params.TimeTest.tv_sec * RT_NS_1SEC + Params.TimeTest.tv_usec * RT_NS_1US; + RTTestValue(g_hTest, "Runtime", u64Ns, RTTESTUNIT_NS); + } + } + else + RTTestFailed(g_hTest, "Setting up test parameters failed with %Rrc\n", rc); + RTTestSubDone(g_hTest); + } + + close(iDevFd); + } + else + RTTestFailed(g_hTest, "Opening device failed with %Rrc\n", RTErrConvertFromErrno(errno)); + +} + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT and globals. + */ + int rc = RTTestInitAndCreate("UsbTest", &g_hTest); + if (rc) + return rc; + + /* + * Default values. + */ + const char *pszDevice = NULL; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion))) + { + switch (rc) + { + case 'h': + usbTestUsage(g_pStdOut); + return RTEXITCODE_SUCCESS; + case 'd': + pszDevice = ValueUnion.psz; + break; + case 's': + if (!RTStrICmp(ValueUnion.psz, "Low")) + g_enmSpeed = USBTESTSPEED_LOW; + else if (!RTStrICmp(ValueUnion.psz, "Full")) + g_enmSpeed = USBTESTSPEED_FULL; + else if (!RTStrICmp(ValueUnion.psz, "High")) + g_enmSpeed = USBTESTSPEED_HIGH; + else if (!RTStrICmp(ValueUnion.psz, "Super")) + g_enmSpeed = USBTESTSPEED_SUPER; + else + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid speed passed to --expected-speed\n"); + RTTestErrorInc(g_hTest); + return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion); + } + break; + case 'e': + if (ValueUnion.u32 < RT_ELEMENTS(g_aTests)) + g_aTests[ValueUnion.u32].fExcluded = true; + else + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid test number passed to --exclude\n"); + RTTestErrorInc(g_hTest); + return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion); + } + break; + case 'a': + for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) + g_aTests[i].fExcluded = true; + break; + case 'i': + if (ValueUnion.u32 < RT_ELEMENTS(g_aTests)) + g_aTests[ValueUnion.u32].fExcluded = false; + else + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid test number passed to --include\n"); + RTTestErrorInc(g_hTest); + return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion); + } + break; + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Start testing. + */ + RTTestBanner(g_hTest); + + /* Find the first test device if none was given. */ + if (!pszDevice) + { + RTTestSub(g_hTest, "Detecting device"); + pszDevice = usbTestFindDevice(); + if (!pszDevice) + RTTestFailed(g_hTest, "Failed to find suitable device\n"); + + RTTestSubDone(g_hTest); + } + + if (pszDevice) + { + /* First check that the requested speed matches. */ + if (g_enmSpeed != USBTESTSPEED_ANY) + { + RTTestSub(g_hTest, "Checking correct device speed"); + + USBTESTSPEED enmSpeed = USBTESTSPEED_UNKNOWN; + rc = usbTestDeviceQueryBusAndDevId(NULL, NULL, &enmSpeed); + if (RT_SUCCESS(rc)) + { + if (enmSpeed == g_enmSpeed) + RTTestPassed(g_hTest, "Reported device speed matches requested speed\n"); + else + RTTestFailed(g_hTest, "Reported device speed doesn'match requested speed (%u vs %u)\n", + enmSpeed, g_enmSpeed); + } + else + RTTestFailed(g_hTest, "Failed to query device speed with rc=%Rrc\n", rc); + + RTTestSubDone(g_hTest); + } + usbTestExec(pszDevice); + } + + RTEXITCODE rcExit = RTTestSummaryAndDestroy(g_hTest); + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp new file mode 100644 index 00000000..dc77f2d4 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp @@ -0,0 +1,1649 @@ +/* $Id: UsbTestService.cpp $ */ +/** @file + * UsbTestService - Remote USB test configuration and execution server. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/crc.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/initterm.h> +#include <iprt/json.h> +#include <iprt/list.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> + +#include "UsbTestServiceInternal.h" +#include "UsbTestServiceGadget.h" +#include "UsbTestServicePlatform.h" + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +#define UTS_USBIP_PORT_FIRST 3240 +#define UTS_USBIP_PORT_LAST 3340 + +/** + * UTS client state. + */ +typedef enum UTSCLIENTSTATE +{ + /** Invalid client state. */ + UTSCLIENTSTATE_INVALID = 0, + /** Client is initialising, only the HOWDY and BYE packets are allowed. */ + UTSCLIENTSTATE_INITIALISING, + /** Client is in fully cuntional state and ready to process all requests. */ + UTSCLIENTSTATE_READY, + /** Client is destroying. */ + UTSCLIENTSTATE_DESTROYING, + /** 32bit hack. */ + UTSCLIENTSTATE_32BIT_HACK = 0x7fffffff +} UTSCLIENTSTATE; + +/** + * UTS client instance. + */ +typedef struct UTSCLIENT +{ + /** List node for new clients. */ + RTLISTNODE NdLst; + /** The current client state. */ + UTSCLIENTSTATE enmState; + /** Transport backend specific data. */ + PUTSTRANSPORTCLIENT pTransportClient; + /** Client hostname. */ + char *pszHostname; + /** Gadget host handle. */ + UTSGADGETHOST hGadgetHost; + /** Handle fo the current configured gadget. */ + UTSGADGET hGadget; +} UTSCLIENT; +/** Pointer to a UTS client instance. */ +typedef UTSCLIENT *PUTSCLIENT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Transport layers. + */ +static const PCUTSTRANSPORT g_apTransports[] = +{ + &g_TcpTransport, + //&g_SerialTransport, + //&g_FileSysTransport, + //&g_GuestPropTransport, + //&g_TestDevTransport, +}; + +/** The select transport layer. */ +static PCUTSTRANSPORT g_pTransport; +/** The config path. */ +static char g_szCfgPath[RTPATH_MAX]; +/** The scratch path. */ +static char g_szScratchPath[RTPATH_MAX]; +/** The default scratch path. */ +static char g_szDefScratchPath[RTPATH_MAX]; +/** The CD/DVD-ROM path. */ +static char g_szCdRomPath[RTPATH_MAX]; +/** The default CD/DVD-ROM path. */ +static char g_szDefCdRomPath[RTPATH_MAX]; +/** The operating system short name. */ +static char g_szOsShortName[16]; +/** The CPU architecture short name. */ +static char g_szArchShortName[16]; +/** The combined "OS.arch" name. */ +static char g_szOsDotArchShortName[32]; +/** The combined "OS/arch" name. */ +static char g_szOsSlashArchShortName[32]; +/** The executable suffix. */ +static char g_szExeSuff[8]; +/** The shell script suffix. */ +static char g_szScriptSuff[8]; +/** Whether to display the output of the child process or not. */ +static bool g_fDisplayOutput = true; +/** Whether to terminate or not. + * @todo implement signals and stuff. */ +static bool volatile g_fTerminate = false; +/** Configuration AST. */ +static RTJSONVAL g_hCfgJson = NIL_RTJSONVAL; +/** Pipe for communicating with the serving thread about new clients. - read end */ +static RTPIPE g_hPipeR; +/** Pipe for communicating with the serving thread about new clients. - write end */ +static RTPIPE g_hPipeW; +/** Thread serving connected clients. */ +static RTTHREAD g_hThreadServing; +/** Critical section protecting the list of new clients. */ +static RTCRITSECT g_CritSectClients; +/** List of new clients waiting to be picked up by the client worker thread. */ +static RTLISTANCHOR g_LstClientsNew; +/** First USB/IP port we can use. */ +static uint16_t g_uUsbIpPortFirst = UTS_USBIP_PORT_FIRST; +/** Last USB/IP port we can use. */ +static uint16_t g_uUsbIpPortLast = UTS_USBIP_PORT_LAST; +/** Next free port. */ +static uint16_t g_uUsbIpPortNext = UTS_USBIP_PORT_FIRST; + + + +/** + * Returns the string represenation of the given state. + */ +static const char *utsClientStateStringify(UTSCLIENTSTATE enmState) +{ + switch (enmState) + { + case UTSCLIENTSTATE_INVALID: + return "INVALID"; + case UTSCLIENTSTATE_INITIALISING: + return "INITIALISING"; + case UTSCLIENTSTATE_READY: + return "READY"; + case UTSCLIENTSTATE_DESTROYING: + return "DESTROYING"; + case UTSCLIENTSTATE_32BIT_HACK: + default: + break; + } + + AssertMsgFailed(("Unknown state %#x\n", enmState)); + return "UNKNOWN"; +} + +/** + * Calculates the checksum value, zero any padding space and send the packet. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPkt The packet to send. Must point to a correctly + * aligned buffer. + */ +static int utsSendPkt(PUTSCLIENT pClient, PUTSPKTHDR pPkt) +{ + Assert(pPkt->cb >= sizeof(*pPkt)); + pPkt->uCrc32 = RTCrc32(pPkt->achOpcode, pPkt->cb - RT_UOFFSETOF(UTSPKTHDR, achOpcode)); + if (pPkt->cb != RT_ALIGN_32(pPkt->cb, UTSPKT_ALIGNMENT)) + memset((uint8_t *)pPkt + pPkt->cb, '\0', RT_ALIGN_32(pPkt->cb, UTSPKT_ALIGNMENT) - pPkt->cb); + + Log(("utsSendPkt: cb=%#x opcode=%.8s\n", pPkt->cb, pPkt->achOpcode)); + Log2(("%.*Rhxd\n", RT_MIN(pPkt->cb, 256), pPkt)); + int rc = g_pTransport->pfnSendPkt(pClient->pTransportClient, pPkt); + while (RT_UNLIKELY(rc == VERR_INTERRUPTED) && !g_fTerminate) + rc = g_pTransport->pfnSendPkt(pClient->pTransportClient, pPkt); + if (RT_FAILURE(rc)) + Log(("utsSendPkt: rc=%Rrc\n", rc)); + + return rc; +} + +/** + * Sends a babble reply and disconnects the client (if applicable). + * + * @param pClient The UTS client structure. + * @param pszOpcode The BABBLE opcode. + */ +static void utsReplyBabble(PUTSCLIENT pClient, const char *pszOpcode) +{ + UTSPKTHDR Reply; + Reply.cb = sizeof(Reply); + Reply.uCrc32 = 0; + memcpy(Reply.achOpcode, pszOpcode, sizeof(Reply.achOpcode)); + + g_pTransport->pfnBabble(pClient->pTransportClient, &Reply, 20*1000); +} + +/** + * Receive and validate a packet. + * + * Will send bable responses to malformed packets that results in a error status + * code. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param ppPktHdr Where to return the packet on success. Free + * with RTMemFree. + * @param fAutoRetryOnFailure Whether to retry on error. + */ +static int utsRecvPkt(PUTSCLIENT pClient, PPUTSPKTHDR ppPktHdr, bool fAutoRetryOnFailure) +{ + for (;;) + { + PUTSPKTHDR pPktHdr; + int rc = g_pTransport->pfnRecvPkt(pClient->pTransportClient, &pPktHdr); + if (RT_SUCCESS(rc)) + { + /* validate the packet. */ + if ( pPktHdr->cb >= sizeof(UTSPKTHDR) + && pPktHdr->cb < UTSPKT_MAX_SIZE) + { + Log2(("utsRecvPkt: pPktHdr=%p cb=%#x crc32=%#x opcode=%.8s\n" + "%.*Rhxd\n", + pPktHdr, pPktHdr->cb, pPktHdr->uCrc32, pPktHdr->achOpcode, RT_MIN(pPktHdr->cb, 256), pPktHdr)); + uint32_t uCrc32Calc = pPktHdr->uCrc32 != 0 + ? RTCrc32(&pPktHdr->achOpcode[0], pPktHdr->cb - RT_UOFFSETOF(UTSPKTHDR, achOpcode)) + : 0; + if (pPktHdr->uCrc32 == uCrc32Calc) + { + AssertCompileMemberSize(UTSPKTHDR, achOpcode, 8); + if ( RT_C_IS_UPPER(pPktHdr->achOpcode[0]) + && RT_C_IS_UPPER(pPktHdr->achOpcode[1]) + && (RT_C_IS_UPPER(pPktHdr->achOpcode[2]) || pPktHdr->achOpcode[2] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[3]) || pPktHdr->achOpcode[3] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[4]) || pPktHdr->achOpcode[4] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[5]) || pPktHdr->achOpcode[5] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[6]) || pPktHdr->achOpcode[6] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[7]) || pPktHdr->achOpcode[7] == ' ') + ) + { + Log(("utsRecvPkt: cb=%#x opcode=%.8s\n", pPktHdr->cb, pPktHdr->achOpcode)); + *ppPktHdr = pPktHdr; + return rc; + } + + rc = VERR_IO_BAD_COMMAND; + } + else + { + Log(("utsRecvPkt: cb=%#x opcode=%.8s crc32=%#x actual=%#x\n", + pPktHdr->cb, pPktHdr->achOpcode, pPktHdr->uCrc32, uCrc32Calc)); + rc = VERR_IO_CRC; + } + } + else + rc = VERR_IO_BAD_LENGTH; + + /* Send babble reply and disconnect the client if the transport is + connection oriented. */ + if (rc == VERR_IO_BAD_LENGTH) + utsReplyBabble(pClient, "BABBLE L"); + else if (rc == VERR_IO_CRC) + utsReplyBabble(pClient, "BABBLE C"); + else if (rc == VERR_IO_BAD_COMMAND) + utsReplyBabble(pClient, "BABBLE O"); + else + utsReplyBabble(pClient, "BABBLE "); + RTMemFree(pPktHdr); + } + + /* Try again or return failure? */ + if ( g_fTerminate + || rc != VERR_INTERRUPTED + || !fAutoRetryOnFailure + ) + { + Log(("utsRecvPkt: rc=%Rrc\n", rc)); + return rc; + } + } +} + +/** + * Make a simple reply, only status opcode. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pReply The reply packet. + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param cbExtra Bytes in addition to the header. + */ +static int utsReplyInternal(PUTSCLIENT pClient, PUTSPKTSTS pReply, const char *pszOpcode, size_t cbExtra) +{ + /* copy the opcode, don't be too strict in case of a padding screw up. */ + size_t cchOpcode = strlen(pszOpcode); + if (RT_LIKELY(cchOpcode == sizeof(pReply->Hdr.achOpcode))) + memcpy(pReply->Hdr.achOpcode, pszOpcode, sizeof(pReply->Hdr.achOpcode)); + else + { + Assert(cchOpcode == sizeof(pReply->Hdr.achOpcode)); + while (cchOpcode > 0 && pszOpcode[cchOpcode - 1] == ' ') + cchOpcode--; + AssertMsgReturn(cchOpcode < sizeof(pReply->Hdr.achOpcode), ("%d/'%.8s'\n", cchOpcode, pszOpcode), VERR_INTERNAL_ERROR_4); + memcpy(pReply->Hdr.achOpcode, pszOpcode, cchOpcode); + memset(&pReply->Hdr.achOpcode[cchOpcode], ' ', sizeof(pReply->Hdr.achOpcode) - cchOpcode); + } + + pReply->Hdr.cb = (uint32_t)sizeof(UTSPKTSTS) + (uint32_t)cbExtra; + pReply->Hdr.uCrc32 = 0; + + return utsSendPkt(pClient, &pReply->Hdr); +} + +/** + * Make a simple reply, only status opcode. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + */ +static int utsReplySimple(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode) +{ + UTSPKTSTS Pkt; + + RT_ZERO(Pkt); + Pkt.rcReq = VINF_SUCCESS; + Pkt.cchStsMsg = 0; + NOREF(pPktHdr); + return utsReplyInternal(pClient, &Pkt, pszOpcode, 0); +} + +/** + * Acknowledges a packet with success. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + */ +static int utsReplyAck(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplySimple(pClient, pPktHdr, "ACK "); +} + +/** + * Replies with a failure. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + * @param rcReq Status code. + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param rcReq The status code of the request. + * @param pszDetailFmt Longer description of the problem (format string). + * @param va Format arguments. + */ +static int utsReplyFailureV(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode, int rcReq, const char *pszDetailFmt, va_list va) +{ + NOREF(pPktHdr); + union + { + UTSPKTSTS Hdr; + char ach[256]; + } uPkt; + + RT_ZERO(uPkt); + size_t cchDetail = RTStrPrintfV(&uPkt.ach[sizeof(UTSPKTSTS)], + sizeof(uPkt) - sizeof(UTSPKTSTS), + pszDetailFmt, va); + uPkt.Hdr.rcReq = rcReq; + uPkt.Hdr.cchStsMsg = cchDetail; + return utsReplyInternal(pClient, &uPkt.Hdr, pszOpcode, cchDetail + 1); +} + +/** + * Replies with a failure. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param rcReq Status code. + * @param pszDetailFmt Longer description of the problem (format string). + * @param ... Format arguments. + */ +static int utsReplyFailure(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode, int rcReq, const char *pszDetailFmt, ...) +{ + va_list va; + va_start(va, pszDetailFmt); + int rc = utsReplyFailureV(pClient, pPktHdr, pszOpcode, rcReq, pszDetailFmt, va); + va_end(va); + return rc; +} + +/** + * Replies according to the return code. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + * @param rcOperation The status code to report. + * @param pszOperationFmt The operation that failed. Typically giving the + * function call with important arguments. + * @param ... Arguments to the format string. + */ +static int utsReplyRC(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, int rcOperation, const char *pszOperationFmt, ...) +{ + if (RT_SUCCESS(rcOperation)) + return utsReplyAck(pClient, pPktHdr); + + char szOperation[128]; + va_list va; + va_start(va, pszOperationFmt); + RTStrPrintfV(szOperation, sizeof(szOperation), pszOperationFmt, va); + va_end(va); + + return utsReplyFailure(pClient, pPktHdr, "FAILED ", rcOperation, "%s failed with rc=%Rrc (opcode '%.8s')", + szOperation, rcOperation, pPktHdr->achOpcode); +} + +#if 0 /* unused */ +/** + * Signal a bad packet minum size. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + * @param cbMin The minimum size. + */ +static int utsReplyBadMinSize(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, size_t cbMin) +{ + return utsReplyFailure(pClient, pPktHdr, "BAD SIZE", VERR_INVALID_PARAMETER, "Expected at least %zu bytes, got %u (opcode '%.8s')", + cbMin, pPktHdr->cb, pPktHdr->achOpcode); +} +#endif + +/** + * Signal a bad packet exact size. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + * @param cb The wanted size. + */ +static int utsReplyBadSize(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, size_t cb) +{ + return utsReplyFailure(pClient, pPktHdr, "BAD SIZE", VERR_INVALID_PARAMETER, "Expected at %zu bytes, got %u (opcode '%.8s')", + cb, pPktHdr->cb, pPktHdr->achOpcode); +} + +#if 0 /* unused */ +/** + * Deals with a command that isn't implemented yet. + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet which opcode isn't implemented. + */ +static int utsReplyNotImplemented(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "NOT IMPL", VERR_NOT_IMPLEMENTED, "Opcode '%.8s' is not implemented", pPktHdr->achOpcode); +} +#endif + +/** + * Deals with a unknown command. + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + */ +static int utsReplyUnknown(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "UNKNOWN ", VERR_NOT_FOUND, "Opcode '%.8s' is not known", pPktHdr->achOpcode); +} + +/** + * Deals with a command which contains an unterminated string. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet containing the unterminated string. + */ +static int utsReplyBadStrTermination(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "BAD TERM", VERR_INVALID_PARAMETER, "Opcode '%.8s' contains an unterminated string", pPktHdr->achOpcode); +} + +/** + * Deals with a command sent in an invalid client state. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet containing the unterminated string. + */ +static int utsReplyInvalidState(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "INVSTATE", VERR_INVALID_STATE, "Opcode '%.8s' is not supported at client state '%s", + pPktHdr->achOpcode, utsClientStateStringify(pClient->enmState)); +} + +/** + * Parses an unsigned integer from the given value string. + * + * @returns IPRT status code. + * @retval VERR_OUT_OF_RANGE if the parsed value exceeds the given maximum. + * @param pszVal The value string. + * @param uMax The maximum value. + * @param pu64 Where to store the parsed number on success. + */ +static int utsDoGadgetCreateCfgParseUInt(const char *pszVal, uint64_t uMax, uint64_t *pu64) +{ + int rc = RTStrToUInt64Ex(pszVal, NULL, 0, pu64); + if (RT_SUCCESS(rc)) + { + if (*pu64 > uMax) + rc = VERR_OUT_OF_RANGE; + } + + return rc; +} + +/** + * Parses a signed integer from the given value string. + * + * @returns IPRT status code. + * @retval VERR_OUT_OF_RANGE if the parsed value exceeds the given range. + * @param pszVal The value string. + * @param iMin The minimum value. + * @param iMax The maximum value. + * @param pi64 Where to store the parsed number on success. + */ +static int utsDoGadgetCreateCfgParseInt(const char *pszVal, int64_t iMin, int64_t iMax, int64_t *pi64) +{ + int rc = RTStrToInt64Ex(pszVal, NULL, 0, pi64); + if (RT_SUCCESS(rc)) + { + if ( *pi64 < iMin + || *pi64 > iMax) + rc = VERR_OUT_OF_RANGE; + } + + return rc; +} + +/** + * Parses the given config item and fills in the value according to the given type. + * + * @returns IPRT status code. + * @param pCfgItem The config item to parse. + * @param u32Type The config type. + * @param pszVal The value encoded as a string. + */ +static int utsDoGadgetCreateCfgParseItem(PUTSGADGETCFGITEM pCfgItem, uint32_t u32Type, + const char *pszVal) +{ + int rc = VINF_SUCCESS; + + switch (u32Type) + { + case UTSPKT_GDGT_CFG_ITEM_TYPE_BOOLEAN: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_BOOLEAN; + if ( RTStrICmp(pszVal, "enabled") + || RTStrICmp(pszVal, "1") + || RTStrICmp(pszVal, "true")) + pCfgItem->Val.u.f = true; + else if ( RTStrICmp(pszVal, "disabled") + || RTStrICmp(pszVal, "0") + || RTStrICmp(pszVal, "false")) + pCfgItem->Val.u.f = false; + else + rc = VERR_INVALID_PARAMETER; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_STRING: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_STRING; + pCfgItem->Val.u.psz = RTStrDup(pszVal); + if (!pCfgItem->Val.u.psz) + rc = VERR_NO_STR_MEMORY; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT8: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT8; + + uint64_t u64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT8_MAX, &u64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.u8 = (uint8_t)u64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT16: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT16; + + uint64_t u64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT16_MAX, &u64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.u16 = (uint16_t)u64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT32: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT32; + + uint64_t u64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT32_MAX, &u64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.u32 = (uint32_t)u64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT64: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT64_MAX, &pCfgItem->Val.u.u64); + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT8: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT8; + + int64_t i64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT8_MIN, INT8_MAX, &i64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.i8 = (int8_t)i64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT16: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT16; + + int64_t i64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT16_MIN, INT16_MAX, &i64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.i16 = (int16_t)i64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT32: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT32; + + int64_t i64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT32_MIN, INT32_MAX, &i64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.i32 = (int32_t)i64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT64: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT64_MIN, INT64_MAX, &pCfgItem->Val.u.i64); + break; + } + default: + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +/** + * Creates the configuration from the given GADGET CREATE packet. + * + * @returns IPRT status code. + * @param pCfgItem The first config item header in the request packet. + * @param cCfgItems Number of config items in the packet to parse. + * @param cbPkt Number of bytes left in the packet for the config data. + * @param paCfg The array of configuration items to fill. + */ +static int utsDoGadgetCreateFillCfg(PUTSPKTREQGDGTCTORCFGITEM pCfgItem, unsigned cCfgItems, + size_t cbPkt, PUTSGADGETCFGITEM paCfg) +{ + int rc = VINF_SUCCESS; + unsigned idxCfg = 0; + + while ( RT_SUCCESS(rc) + && cCfgItems + && cbPkt) + { + if (cbPkt >= sizeof(UTSPKTREQGDGTCTORCFGITEM)) + { + cbPkt -= sizeof(UTSPKTREQGDGTCTORCFGITEM); + if (pCfgItem->u32KeySize + pCfgItem->u32ValSize >= cbPkt) + { + const char *pszKey = (const char *)(pCfgItem + 1); + const char *pszVal = pszKey + pCfgItem->u32KeySize; + + /* Validate termination. */ + if ( *(pszKey + pCfgItem->u32KeySize - 1) != '\0' + || *(pszVal + pCfgItem->u32ValSize - 1) != '\0') + rc = VERR_INVALID_PARAMETER; + else + { + paCfg[idxCfg].pszKey = RTStrDup(pszKey); + + rc = utsDoGadgetCreateCfgParseItem(&paCfg[idxCfg], pCfgItem->u32Type, pszVal); + if (RT_SUCCESS(rc)) + { + cbPkt -= pCfgItem->u32KeySize + pCfgItem->u32ValSize; + cCfgItems--; + idxCfg++; + pCfgItem = (PUTSPKTREQGDGTCTORCFGITEM)(pszVal + pCfgItem->u32ValSize); + } + } + } + else + rc = VERR_INVALID_PARAMETER; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +/** + * Verifies and acknowledges a "BYE" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The howdy packet. + */ +static int utsDoBye(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + int rc; + if (pPktHdr->cb == sizeof(UTSPKTHDR)) + rc = utsReplyAck(pClient, pPktHdr); + else + rc = utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTHDR)); + return rc; +} + +/** + * Verifies and acknowledges a "HOWDY" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The howdy packet. + */ +static int utsDoHowdy(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + int rc = VINF_SUCCESS; + + if (pPktHdr->cb != sizeof(UTSPKTREQHOWDY)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQHOWDY)); + + if (pClient->enmState != UTSCLIENTSTATE_INITIALISING) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQHOWDY pReq = (PUTSPKTREQHOWDY)pPktHdr; + + if (pReq->uVersion != UTS_PROTOCOL_VS) + return utsReplyRC(pClient, pPktHdr, VERR_VERSION_MISMATCH, "The given version %#x is not supported", pReq->uVersion); + + /* Verify hostname string. */ + if (pReq->cchHostname >= sizeof(pReq->achHostname)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(pReq->achHostname) - 1); + + if (pReq->achHostname[pReq->cchHostname] != '\0') + return utsReplyBadStrTermination(pClient, pPktHdr); + + /* Extract string. */ + pClient->pszHostname = RTStrDup(&pReq->achHostname[0]); + if (!pClient->pszHostname) + return utsReplyRC(pClient, pPktHdr, VERR_NO_MEMORY, "Failed to allocate memory for the hostname string"); + + if (pReq->fUsbConn & UTSPKT_HOWDY_CONN_F_PHYSICAL) + return utsReplyRC(pClient, pPktHdr, VERR_NOT_SUPPORTED, "Physical connections are not yet supported"); + + if (pReq->fUsbConn & UTSPKT_HOWDY_CONN_F_USBIP) + { + /* Set up the USB/IP server, find an unused port we can start the server on. */ + UTSGADGETCFGITEM aCfg[2]; + + uint16_t uPort = g_uUsbIpPortNext; + + if (g_uUsbIpPortNext == g_uUsbIpPortLast) + g_uUsbIpPortNext = g_uUsbIpPortFirst; + else + g_uUsbIpPortNext++; + + aCfg[0].pszKey = "UsbIp/Port"; + aCfg[0].Val.enmType = UTSGADGETCFGTYPE_UINT16; + aCfg[0].Val.u.u16 = uPort; + aCfg[1].pszKey = NULL; + + rc = utsGadgetHostCreate(UTSGADGETHOSTTYPE_USBIP, &aCfg[0], &pClient->hGadgetHost); + if (RT_SUCCESS(rc)) + { + /* Send the reply with the configured USB/IP port. */ + UTSPKTREPHOWDY Rep; + + RT_ZERO(Rep); + + Rep.uVersion = UTS_PROTOCOL_VS; + Rep.fUsbConn = UTSPKT_HOWDY_CONN_F_USBIP; + Rep.uUsbIpPort = uPort; + Rep.cUsbIpDevices = 1; + Rep.cPhysicalDevices = 0; + + rc = utsReplyInternal(pClient, &Rep.Sts, "ACK ", sizeof(Rep) - sizeof(UTSPKTSTS)); + if (RT_SUCCESS(rc)) + { + g_pTransport->pfnNotifyHowdy(pClient->pTransportClient); + pClient->enmState = UTSCLIENTSTATE_READY; + RTDirRemoveRecursive(g_szScratchPath, RTDIRRMREC_F_CONTENT_ONLY); + } + } + else + return utsReplyRC(pClient, pPktHdr, rc, "Creating the USB/IP gadget host failed"); + } + else + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "No access method requested"); + + return rc; +} + +/** + * Verifies and processes a "GADGET CREATE" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget create packet. + */ +static int utsDoGadgetCreate(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + int rc = VINF_SUCCESS; + + if (pPktHdr->cb < sizeof(UTSPKTREQGDGTCTOR)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTCTOR)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTCTOR pReq = (PUTSPKTREQGDGTCTOR)pPktHdr; + + if (pReq->u32GdgtType != UTSPKT_GDGT_CREATE_TYPE_TEST) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "The given gadget type is not supported"); + + if (pReq->u32GdgtAccess != UTSPKT_GDGT_CREATE_ACCESS_USBIP) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "The given gadget access method is not supported"); + + PUTSGADGETCFGITEM paCfg = NULL; + if (pReq->u32CfgItems > 0) + { + paCfg = (PUTSGADGETCFGITEM)RTMemAllocZ((pReq->u32CfgItems + 1) * sizeof(UTSGADGETCFGITEM)); + if (RT_UNLIKELY(!paCfg)) + return utsReplyRC(pClient, pPktHdr, VERR_NO_MEMORY, "Failed to allocate memory for configration items"); + + rc = utsDoGadgetCreateFillCfg((PUTSPKTREQGDGTCTORCFGITEM)(pReq + 1), pReq->u32CfgItems, + pPktHdr->cb - sizeof(UTSPKTREQGDGTCTOR), paCfg); + if (RT_FAILURE(rc)) + { + RTMemFree(paCfg); + return utsReplyRC(pClient, pPktHdr, rc, "Failed to parse configuration"); + } + } + + rc = utsGadgetCreate(pClient->hGadgetHost, UTSGADGETCLASS_TEST, paCfg, &pClient->hGadget); + if (RT_SUCCESS(rc)) + { + UTSPKTREPGDGTCTOR Rep; + RT_ZERO(Rep); + + Rep.idGadget = 0; + Rep.u32BusId = utsGadgetGetBusId(pClient->hGadget); + Rep.u32DevId = utsGadgetGetDevId(pClient->hGadget); + rc = utsReplyInternal(pClient, &Rep.Sts, "ACK ", sizeof(Rep) - sizeof(UTSPKTSTS)); + } + else + rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to create gadget with %Rrc\n", rc); + + return rc; +} + +/** + * Verifies and processes a "GADGET DESTROY" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget destroy packet. + */ +static int utsDoGadgetDestroy(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(UTSPKTREQGDGTDTOR)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTDTOR)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTDTOR pReq = (PUTSPKTREQGDGTDTOR)pPktHdr; + + if (pReq->idGadget != 0) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid"); + if (pClient->hGadget == NIL_UTSGADGET) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up"); + + utsGadgetRelease(pClient->hGadget); + pClient->hGadget = NIL_UTSGADGET; + + return utsReplyAck(pClient, pPktHdr); +} + +/** + * Verifies and processes a "GADGET CONNECT" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget connect packet. + */ +static int utsDoGadgetConnect(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(UTSPKTREQGDGTCNCT)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTCNCT)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTCNCT pReq = (PUTSPKTREQGDGTCNCT)pPktHdr; + + if (pReq->idGadget != 0) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid"); + if (pClient->hGadget == NIL_UTSGADGET) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up"); + + int rc = utsGadgetConnect(pClient->hGadget); + if (RT_SUCCESS(rc)) + rc = utsReplyAck(pClient, pPktHdr); + else + rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to connect the gadget"); + + return rc; +} + +/** + * Verifies and processes a "GADGET DISCONNECT" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget disconnect packet. + */ +static int utsDoGadgetDisconnect(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(UTSPKTREQGDGTDCNT)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTDCNT)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTDCNT pReq = (PUTSPKTREQGDGTDCNT)pPktHdr; + + if (pReq->idGadget != 0) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid"); + if (pClient->hGadget == NIL_UTSGADGET) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up"); + + int rc = utsGadgetDisconnect(pClient->hGadget); + if (RT_SUCCESS(rc)) + rc = utsReplyAck(pClient, pPktHdr); + else + rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to disconnect the gadget"); + + return rc; +} + +/** + * Main request processing routine for each client. + * + * @returns IPRT status code. + * @param pClient The UTS client structure sending the request. + */ +static int utsClientReqProcess(PUTSCLIENT pClient) +{ + /* + * Read client command packet and process it. + */ + PUTSPKTHDR pPktHdr = NULL; + int rc = utsRecvPkt(pClient, &pPktHdr, true /*fAutoRetryOnFailure*/); + if (RT_FAILURE(rc)) + return rc; + + /* + * Do a string switch on the opcode bit. + */ + /* Connection: */ + if ( utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_HOWDY)) + rc = utsDoHowdy(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_BYE)) + rc = utsDoBye(pClient, pPktHdr); + /* Gadget API. */ + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_CREATE)) + rc = utsDoGadgetCreate(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_DESTROY)) + rc = utsDoGadgetDestroy(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_CONNECT)) + rc = utsDoGadgetConnect(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_DISCONNECT)) + rc = utsDoGadgetDisconnect(pClient, pPktHdr); + /* Misc: */ + else + rc = utsReplyUnknown(pClient, pPktHdr); + + RTMemFree(pPktHdr); + + return rc; +} + +/** + * Destroys a client instance. + * + * @returns nothing. + * @param pClient The UTS client structure. + */ +static void utsClientDestroy(PUTSCLIENT pClient) +{ + if (pClient->pszHostname) + RTStrFree(pClient->pszHostname); + if (pClient->hGadget != NIL_UTSGADGET) + utsGadgetRelease(pClient->hGadget); + if (pClient->hGadgetHost != NIL_UTSGADGETHOST) + utsGadgetHostRelease(pClient->hGadgetHost); + RTMemFree(pClient); +} + +/** + * The main thread worker serving the clients. + */ +static DECLCALLBACK(int) utsClientWorker(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF2(hThread, pvUser); + unsigned cClientsMax = 0; + unsigned cClientsCur = 0; + PUTSCLIENT *papClients = NULL; + RTPOLLSET hPollSet; + + int rc = RTPollSetCreate(&hPollSet); + if (RT_FAILURE(rc)) + return rc; + + /* Add the pipe to the poll set. */ + rc = RTPollSetAddPipe(hPollSet, g_hPipeR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 0); + if (RT_SUCCESS(rc)) + { + while (!g_fTerminate) + { + uint32_t fEvts; + uint32_t uId; + rc = RTPoll(hPollSet, RT_INDEFINITE_WAIT, &fEvts, &uId); + if (RT_SUCCESS(rc)) + { + if (uId == 0) + { + if (fEvts & RTPOLL_EVT_ERROR) + break; + + /* We got woken up because of a new client. */ + Assert(fEvts & RTPOLL_EVT_READ); + + uint8_t bRead; + size_t cbRead = 0; + rc = RTPipeRead(g_hPipeR, &bRead, 1, &cbRead); + AssertRC(rc); + + RTCritSectEnter(&g_CritSectClients); + /* Walk the list and add all new clients. */ + PUTSCLIENT pIt, pItNext; + RTListForEachSafe(&g_LstClientsNew, pIt, pItNext, UTSCLIENT, NdLst) + { + RTListNodeRemove(&pIt->NdLst); + Assert(cClientsCur <= cClientsMax); + if (cClientsCur == cClientsMax) + { + /* Realloc to accommodate for the new clients. */ + PUTSCLIENT *papClientsNew = (PUTSCLIENT *)RTMemRealloc(papClients, (cClientsMax + 10) * sizeof(PUTSCLIENT)); + if (RT_LIKELY(papClientsNew)) + { + cClientsMax += 10; + papClients = papClientsNew; + } + } + + if (cClientsCur < cClientsMax) + { + /* Find a free slot in the client array. */ + unsigned idxSlt = 0; + while ( idxSlt < cClientsMax + && papClients[idxSlt] != NULL) + idxSlt++; + + rc = g_pTransport->pfnPollSetAdd(hPollSet, pIt->pTransportClient, idxSlt + 1); + if (RT_SUCCESS(rc)) + { + cClientsCur++; + papClients[idxSlt] = pIt; + } + else + { + g_pTransport->pfnNotifyBye(pIt->pTransportClient); + utsClientDestroy(pIt); + } + } + else + { + g_pTransport->pfnNotifyBye(pIt->pTransportClient); + utsClientDestroy(pIt); + } + } + RTCritSectLeave(&g_CritSectClients); + } + else + { + /* Client sends a request, pick the right client and process it. */ + PUTSCLIENT pClient = papClients[uId - 1]; + AssertPtr(pClient); + if (fEvts & RTPOLL_EVT_READ) + rc = utsClientReqProcess(pClient); + + if ( (fEvts & RTPOLL_EVT_ERROR) + || RT_FAILURE(rc)) + { + /* Close connection and remove client from array. */ + rc = g_pTransport->pfnPollSetRemove(hPollSet, pClient->pTransportClient, uId); + AssertRC(rc); + + g_pTransport->pfnNotifyBye(pClient->pTransportClient); + papClients[uId - 1] = NULL; + cClientsCur--; + utsClientDestroy(pClient); + } + } + } + } + } + + RTPollSetDestroy(hPollSet); + + return rc; +} + +/** + * The main loop. + * + * @returns exit code. + */ +static RTEXITCODE utsMainLoop(void) +{ + RTEXITCODE enmExitCode = RTEXITCODE_SUCCESS; + while (!g_fTerminate) + { + /* + * Wait for new connection and spin off a new thread + * for every new client. + */ + PUTSTRANSPORTCLIENT pTransportClient; + int rc = g_pTransport->pfnWaitForConnect(&pTransportClient); + if (RT_FAILURE(rc)) + continue; + + /* + * New connection, create new client structure and spin of + * the request handling thread. + */ + PUTSCLIENT pClient = (PUTSCLIENT)RTMemAllocZ(sizeof(UTSCLIENT)); + if (RT_LIKELY(pClient)) + { + pClient->enmState = UTSCLIENTSTATE_INITIALISING; + pClient->pTransportClient = pTransportClient; + pClient->pszHostname = NULL; + pClient->hGadgetHost = NIL_UTSGADGETHOST; + pClient->hGadget = NIL_UTSGADGET; + + /* Add client to the new list and inform the worker thread. */ + RTCritSectEnter(&g_CritSectClients); + RTListAppend(&g_LstClientsNew, &pClient->NdLst); + RTCritSectLeave(&g_CritSectClients); + + size_t cbWritten = 0; + rc = RTPipeWrite(g_hPipeW, "", 1, &cbWritten); + if (RT_FAILURE(rc)) + RTMsgError("Failed to inform worker thread of a new client"); + } + else + { + RTMsgError("Creating new client structure failed with out of memory error\n"); + g_pTransport->pfnNotifyBye(pTransportClient); + } + + + } + + return enmExitCode; +} + +/** + * Initializes the global UTS state. + * + * @returns IPRT status code. + */ +static int utsInit(void) +{ + int rc = VINF_SUCCESS; + PRTERRINFO pErrInfo = NULL; + + RTListInit(&g_LstClientsNew); + + rc = RTJsonParseFromFile(&g_hCfgJson, g_szCfgPath, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = utsPlatformInit(); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&g_CritSectClients); + if (RT_SUCCESS(rc)) + { + rc = RTPipeCreate(&g_hPipeR, &g_hPipeW, 0); + if (RT_SUCCESS(rc)) + { + /* Spin off the thread serving connections. */ + rc = RTThreadCreate(&g_hThreadServing, utsClientWorker, NULL, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, + "USBTSTSRV"); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + else + RTMsgError("Creating the client worker thread failed with %Rrc\n", rc); + + RTPipeClose(g_hPipeR); + RTPipeClose(g_hPipeW); + } + else + RTMsgError("Creating communications pipe failed with %Rrc\n", rc); + + RTCritSectDelete(&g_CritSectClients); + } + else + RTMsgError("Creating global critical section failed with %Rrc\n", rc); + + RTJsonValueRelease(g_hCfgJson); + } + else + RTMsgError("Initializing the platform failed with %Rrc\n", rc); + } + else + { + if (RTErrInfoIsSet(pErrInfo)) + { + RTMsgError("Failed to parse config with detailed error: %s (%Rrc)\n", + pErrInfo->pszMsg, pErrInfo->rc); + RTErrInfoFree(pErrInfo); + } + else + RTMsgError("Failed to parse config with unknown error (%Rrc)\n", rc); + } + + return rc; +} + +/** + * Determines the default configuration. + */ +static void utsSetDefaults(void) +{ + /* + * OS and ARCH. + */ + AssertCompile(sizeof(KBUILD_TARGET) <= sizeof(g_szOsShortName)); + strcpy(g_szOsShortName, KBUILD_TARGET); + + AssertCompile(sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szArchShortName)); + strcpy(g_szArchShortName, KBUILD_TARGET_ARCH); + + AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsDotArchShortName)); + strcpy(g_szOsDotArchShortName, KBUILD_TARGET); + g_szOsDotArchShortName[sizeof(KBUILD_TARGET) - 1] = '.'; + strcpy(&g_szOsDotArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH); + + AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsSlashArchShortName)); + strcpy(g_szOsSlashArchShortName, KBUILD_TARGET); + g_szOsSlashArchShortName[sizeof(KBUILD_TARGET) - 1] = '/'; + strcpy(&g_szOsSlashArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH); + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + strcpy(g_szExeSuff, ".exe"); + strcpy(g_szScriptSuff, ".cmd"); +#else + strcpy(g_szExeSuff, ""); + strcpy(g_szScriptSuff, ".sh"); +#endif + + /* + * The CD/DVD-ROM location. + */ + /** @todo do a better job here :-) */ +#ifdef RT_OS_WINDOWS + strcpy(g_szDefCdRomPath, "D:/"); +#elif defined(RT_OS_OS2) + strcpy(g_szDefCdRomPath, "D:/"); +#else + if (RTDirExists("/media")) + strcpy(g_szDefCdRomPath, "/media/cdrom"); + else + strcpy(g_szDefCdRomPath, "/mnt/cdrom"); +#endif + strcpy(g_szCdRomPath, g_szDefCdRomPath); + + /* + * Temporary directory. + */ + int rc = RTPathTemp(g_szDefScratchPath, sizeof(g_szDefScratchPath)); + if (RT_SUCCESS(rc)) +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) || defined(RT_OS_DOS) + rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "uts-XXXX.tmp"); +#else + rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "uts-XXXXXXXXX.tmp"); +#endif + if (RT_FAILURE(rc)) + { + RTMsgError("RTPathTemp/Append failed when constructing scratch path: %Rrc\n", rc); + strcpy(g_szDefScratchPath, "/tmp/uts-XXXX.tmp"); + } + strcpy(g_szScratchPath, g_szDefScratchPath); + + /* + * Config file location. + */ + /** @todo Improve */ +#if !defined(RT_OS_WINDOWS) + strcpy(g_szCfgPath, "/etc/uts.conf"); +#else + strcpy(g_szCfgPath, ""); +#endif + + /* + * The default transporter is the first one. + */ + g_pTransport = g_apTransports[0]; +} + +/** + * Prints the usage. + * + * @param pStrm Where to print it. + * @param pszArgv0 The program name (argv[0]). + */ +static void utsUsage(PRTSTREAM pStrm, const char *pszArgv0) +{ + RTStrmPrintf(pStrm, + "Usage: %Rbn [options]\n" + "\n" + "Options:\n" + " --config <path>\n" + " Where to load the config from\n" + " --cdrom <path>\n" + " Where the CD/DVD-ROM will be mounted.\n" + " Default: %s\n" + " --scratch <path>\n" + " Where to put scratch files.\n" + " Default: %s \n" + , + pszArgv0, + g_szDefCdRomPath, + g_szDefScratchPath); + RTStrmPrintf(pStrm, + " --transport <name>\n" + " Use the specified transport layer, one of the following:\n"); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + RTStrmPrintf(pStrm, " %s - %s\n", g_apTransports[i]->szName, g_apTransports[i]->pszDesc); + RTStrmPrintf(pStrm, " Default: %s\n", g_pTransport->szName); + RTStrmPrintf(pStrm, + " --display-output, --no-display-output\n" + " Display the output and the result of all child processes.\n"); + RTStrmPrintf(pStrm, + " --foreground\n" + " Don't daemonize, run in the foreground.\n"); + RTStrmPrintf(pStrm, + " --help, -h, -?\n" + " Display this message and exit.\n" + " --version, -V\n" + " Display the version and exit.\n"); + + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (g_apTransports[i]->cOpts) + { + RTStrmPrintf(pStrm, + "\n" + "Options for %s:\n", g_apTransports[i]->szName); + g_apTransports[i]->pfnUsage(g_pStdOut); + } +} + +/** + * Parses the arguments. + * + * @returns Exit code. Exit if this is non-zero or @a *pfExit is set. + * @param argc The number of arguments. + * @param argv The argument vector. + * @param pfExit For indicating exit when the exit code is zero. + */ +static RTEXITCODE utsParseArgv(int argc, char **argv, bool *pfExit) +{ + *pfExit = false; + + /* + * Storage for locally handled options. + */ + bool fDaemonize = true; + bool fDaemonized = false; + + /* + * Combine the base and transport layer option arrays. + */ + static const RTGETOPTDEF s_aBaseOptions[] = + { + { "--config", 'C', RTGETOPT_REQ_STRING }, + { "--transport", 't', RTGETOPT_REQ_STRING }, + { "--cdrom", 'c', RTGETOPT_REQ_STRING }, + { "--scratch", 's', RTGETOPT_REQ_STRING }, + { "--display-output", 'd', RTGETOPT_REQ_NOTHING }, + { "--no-display-output",'D', RTGETOPT_REQ_NOTHING }, + { "--foreground", 'f', RTGETOPT_REQ_NOTHING }, + { "--daemonized", 'Z', RTGETOPT_REQ_NOTHING }, + }; + + size_t cOptions = RT_ELEMENTS(s_aBaseOptions); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + cOptions += g_apTransports[i]->cOpts; + + PRTGETOPTDEF paOptions = (PRTGETOPTDEF)alloca(cOptions * sizeof(RTGETOPTDEF)); + if (!paOptions) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "alloca failed\n"); + + memcpy(paOptions, s_aBaseOptions, sizeof(s_aBaseOptions)); + cOptions = RT_ELEMENTS(s_aBaseOptions); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + { + memcpy(&paOptions[cOptions], g_apTransports[i]->paOpts, g_apTransports[i]->cOpts * sizeof(RTGETOPTDEF)); + cOptions += g_apTransports[i]->cOpts; + } + + /* + * Parse the arguments. + */ + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, paOptions, cOptions, 1, 0 /* fFlags */); + AssertRC(rc); + + int ch; + RTGETOPTUNION Val; + while ((ch = RTGetOpt(&GetState, &Val))) + { + switch (ch) + { + case 'C': + rc = RTStrCopy(g_szCfgPath, sizeof(g_szCfgPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Config file path is path too long (%Rrc)\n", rc); + break; + + case 'c': + rc = RTStrCopy(g_szCdRomPath, sizeof(g_szCdRomPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "CD/DVD-ROM is path too long (%Rrc)\n", rc); + break; + + case 'd': + g_fDisplayOutput = true; + break; + + case 'D': + g_fDisplayOutput = false; + break; + + case 'f': + fDaemonize = false; + break; + + case 'h': + utsUsage(g_pStdOut, argv[0]); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 's': + rc = RTStrCopy(g_szScratchPath, sizeof(g_szScratchPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "scratch path is too long (%Rrc)\n", rc); + break; + + case 't': + { + PCUTSTRANSPORT pTransport = NULL; + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (!strcmp(g_apTransports[i]->szName, Val.psz)) + { + pTransport = g_apTransports[i]; + break; + } + if (!pTransport) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown transport layer name '%s'\n", Val.psz); + g_pTransport = pTransport; + break; + } + + case 'V': + RTPrintf("$Revision: 127855 $\n"); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 'Z': + fDaemonized = true; + fDaemonize = false; + break; + + default: + { + rc = VERR_TRY_AGAIN; + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (g_apTransports[i]->cOpts) + { + rc = g_apTransports[i]->pfnOption(ch, &Val); + if (RT_SUCCESS(rc)) + break; + if (rc != VERR_TRY_AGAIN) + { + *pfExit = true; + return RTEXITCODE_SYNTAX; + } + } + if (rc == VERR_TRY_AGAIN) + { + *pfExit = true; + return RTGetOptPrintError(ch, &Val); + } + break; + } + } + } + + /* + * Daemonize ourselves if asked to. + */ + if (fDaemonize && !*pfExit) + { + rc = RTProcDaemonize(argv, "--daemonized"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize: %Rrc\n", rc); + *pfExit = true; + } + + return RTEXITCODE_SUCCESS; +} + + +int main(int argc, char **argv) +{ + /* + * Initialize the runtime. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Determine defaults and parse the arguments. + */ + utsSetDefaults(); + bool fExit; + RTEXITCODE rcExit = utsParseArgv(argc, argv, &fExit); + if (rcExit != RTEXITCODE_SUCCESS || fExit) + return rcExit; + + /* + * Initialize global state. + */ + rc = utsInit(); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* + * Initialize the transport layer. + */ + rc = g_pTransport->pfnInit(); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* + * Ok, start working + */ + rcExit = utsMainLoop(); + + /* + * Cleanup. + */ + g_pTransport->pfnTerm(); + + utsPlatformTerm(); + + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp new file mode 100644 index 00000000..8c090173 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp @@ -0,0 +1,202 @@ +/* $Id: UsbTestServiceGadget.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget host API. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "UsbTestServiceGadgetInternal.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETINT +{ + /** Reference counter. */ + volatile uint32_t cRefs; + /** Pointer to the gadget class callback table. */ + PCUTSGADGETCLASSIF pClassIf; + /** The gadget host handle. */ + UTSGADGETHOST hGadgetHost; + /** Class specific instance data - variable in size. */ + uint8_t abClassInst[1]; +} UTSGADGETINT; +/** Pointer to the internal gadget host instance data. */ +typedef UTSGADGETINT *PUTSGADGETINT; + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ + +/** Known gadget host interfaces. */ +static const PCUTSGADGETCLASSIF g_apUtsGadgetClass[] = +{ + &g_UtsGadgetClassTest +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Destroys a gadget instance. + * + * @returns nothing. + * @param pThis The gadget instance. + */ +static void utsGadgetDestroy(PUTSGADGETINT pThis) +{ + pThis->pClassIf->pfnTerm((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); + RTMemFree(pThis); +} + + +DECLHIDDEN(int) utsGadgetCreate(UTSGADGETHOST hGadgetHost, UTSGADGETCLASS enmClass, + PCUTSGADGETCFGITEM paCfg, PUTSGADET phGadget) +{ + int rc = VINF_SUCCESS; + PCUTSGADGETCLASSIF pClassIf = NULL; + + /* Get the interface. */ + for (unsigned i = 0; i < RT_ELEMENTS(g_apUtsGadgetClass); i++) + { + if (g_apUtsGadgetClass[i]->enmClass == enmClass) + { + pClassIf = g_apUtsGadgetClass[i]; + break; + } + } + + if (RT_LIKELY(pClassIf)) + { + PUTSGADGETINT pThis = (PUTSGADGETINT)RTMemAllocZ(RT_UOFFSETOF_DYN(UTSGADGETINT, abClassInst[pClassIf->cbClass])); + if (RT_LIKELY(pThis)) + { + pThis->cRefs = 1; + pThis->hGadgetHost = hGadgetHost; + pThis->pClassIf = pClassIf; + rc = pClassIf->pfnInit((PUTSGADGETCLASSINT)&pThis->abClassInst[0], paCfg); + if (RT_SUCCESS(rc)) + { + /* Connect the gadget to the host. */ + rc = utsGadgetHostGadgetConnect(pThis->hGadgetHost, pThis); + if (RT_SUCCESS(rc)) + *phGadget = pThis; + } + else + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + + +DECLHIDDEN(uint32_t) utsGadgetRetain(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, 0); + + return ASMAtomicIncU32(&pThis->cRefs); +} + + +DECLHIDDEN(uint32_t) utsGadgetRelease(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, 0); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + utsGadgetDestroy(pThis); + + return cRefs; +} + + +DECLHIDDEN(uint32_t) utsGadgetGetBusId(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return pThis->pClassIf->pfnGetBusId((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); +} + + +DECLHIDDEN(uint32_t) utsGadgetGetDevId(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return 1; /** @todo Current assumption which is true on Linux with dummy_hcd. */ +} + + +DECLHIDDEN(int) utsGadgetConnect(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + int rc = pThis->pClassIf->pfnConnect((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); + if (RT_SUCCESS(rc)) + rc = utsGadgetHostGadgetConnect(pThis->hGadgetHost, hGadget); + + return rc; +} + + +DECLHIDDEN(int) utsGadgetDisconnect(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + int rc = utsGadgetHostGadgetDisconnect(pThis->hGadgetHost, hGadget); + if (RT_SUCCESS(rc)) + rc = pThis->pClassIf->pfnDisconnect((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); + + return rc; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h new file mode 100644 index 00000000..15d04aed --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h @@ -0,0 +1,536 @@ +/* $Id: UsbTestServiceGadget.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Gadget API. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +RT_C_DECLS_BEGIN + +/** Opaque gadget host handle. */ +typedef struct UTSGADGETHOSTINT *UTSGADGETHOST; +/** Pointer to a gadget host handle. */ +typedef UTSGADGETHOST *PUTSGADGETHOST; + +/** NIL gadget host handle. */ +#define NIL_UTSGADGETHOST ((UTSGADGETHOST)0) + +/** Opaque USB gadget handle. */ +typedef struct UTSGADGETINT *UTSGADGET; +/** Pointer to a USB gadget handle. */ +typedef UTSGADGET *PUTSGADET; + +/** NIL gadget handle. */ +#define NIL_UTSGADGET ((UTSGADGET)0) + +/** + * Gadget/Gadget host configuration item type. + */ +typedef enum UTSGADGETCFGTYPE +{ + /** Don't use! */ + UTSGADGETCFGTYPE_INVALID = 0, + /** Boolean type. */ + UTSGADGETCFGTYPE_BOOLEAN, + /** UTF-8 string. */ + UTSGADGETCFGTYPE_STRING, + /** Unsigned 8bit integer. */ + UTSGADGETCFGTYPE_UINT8, + /** Unsigned 16bit integer. */ + UTSGADGETCFGTYPE_UINT16, + /** Unsigned 32bit integer. */ + UTSGADGETCFGTYPE_UINT32, + /** Unsigned 64bit integer. */ + UTSGADGETCFGTYPE_UINT64, + /** Signed 8bit integer. */ + UTSGADGETCFGTYPE_INT8, + /** Signed 16bit integer. */ + UTSGADGETCFGTYPE_INT16, + /** Signed 32bit integer. */ + UTSGADGETCFGTYPE_INT32, + /** Signed 64bit integer. */ + UTSGADGETCFGTYPE_INT64, + /** 32bit hack. */ + UTSGADGETCFGTYPE_32BIT_HACK = 0x7fffffff +} UTSGADGETCFGTYPE; + +/** + * Gadget configuration value. + */ +typedef struct UTSGADGETCFGVAL +{ + /** Value type */ + UTSGADGETCFGTYPE enmType; + /** Value based on the type. */ + union + { + bool f; + const char *psz; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + } u; +} UTSGADGETCFGVAL; +/** Pointer to a gadget configuration value. */ +typedef UTSGADGETCFGVAL *PUTSGADGETCFGVAL; +/** Pointer to a const gadget configuration value. */ +typedef const UTSGADGETCFGVAL *PCUTSGADGETCFGVAL; + +/** + * Gadget configuration item. + */ +typedef struct UTSGADGETCFGITEM +{ + /** Item key. */ + const char *pszKey; + /** Item value. */ + UTSGADGETCFGVAL Val; +} UTSGADGETCFGITEM; +/** Pointer to a gadget configuration item. */ +typedef UTSGADGETCFGITEM *PUTSGADGETCFGITEM; +/** Pointer to a const gadget configuration item. */ +typedef const UTSGADGETCFGITEM *PCUTSGADGETCFGITEM; + +/** + * Type for the gadget host. + */ +typedef enum UTSGADGETHOSTTYPE +{ + /** Invalid type, don't use. */ + UTSGADGETHOSTTYPE_INVALID = 0, + /** USB/IP host, gadgets are exported using a USB/IP server. */ + UTSGADGETHOSTTYPE_USBIP, + /** Physical connection using a device or OTG port. */ + UTSGADGETHOSTTYPE_PHYSICAL, + /** 32bit hack. */ + UTSGADGETHOSTTYPE_32BIT_HACK = 0x7fffffff +} UTSGADGETHOSTTYPE; + +/** + * USB gadget class. + */ +typedef enum UTSGADGETCLASS +{ + /** Invalid class, don't use. */ + UTSGADGETCLASS_INVALID = 0, + /** Special test device class. */ + UTSGADGETCLASS_TEST, + /** MSD device. */ + UTSGADGETCLASS_MSD, + /** 32bit hack. */ + UTSGADGETCLASS_32BIT_HACK = 0x7fffffff +} UTSGADGETCLASS; + +/** + * Queries the value of a given boolean key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pf Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryBool(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf); + +/** + * Queries the value of a given boolean key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pf Where to store the value on success. + * @param fDef The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryBoolDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf, bool fDef); + +/** + * Queries the string value of a given key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param ppszVal Where to store the pointer to the string on success, + * must be freed with RTStrFree(). + */ +DECLHIDDEN(int) utsGadgetCfgQueryString(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal); + +/** + * Queries the string value of a given key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param ppszVal Where to store the pointer to the string on success, + * must be freed with RTStrFree(). + * @param pszDef The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryStringDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal, const char *pszDef); + +/** + * Queries the value of a given uint8_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu8 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8); + +/** + * Queries the value of a given uint8_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu8 Where to store the value on success. + * @param u8Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8, uint8_t u8Def); + +/** + * Queries the value of a given uint16_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu16 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16); + +/** + * Queries the value of a given uint16_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu16 Where to store the value on success. + * @param u16Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16, uint16_t u16Def); + +/** + * Queries the value of a given uint32_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu32 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32); + +/** + * Queries the value of a given uint32_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu32 Where to store the value on success. + * @param u32Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32, uint32_t u32Def); + +/** + * Queries the value of a given uint64_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu64 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64); + +/** + * Queries the value of a given uint64_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu64 Where to store the value on success. + * @param u64Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64, uint64_t u64Def); + +/** + * Queries the value of a given int8_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi8 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8); + +/** + * Queries the value of a given int8_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi8 Where to store the value on success. + * @param i8Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8, uint8_t i8Def); + +/** + * Queries the value of a given int16_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi16 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16); + +/** + * Queries the value of a given int16_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi16 Where to store the value on success. + * @param i16Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16, uint16_t i16Def); + +/** + * Queries the value of a given int32_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi32 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32); + +/** + * Queries the value of a given int32_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi32 Where to store the value on success. + * @param i32Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32, uint32_t i32Def); + +/** + * Queries the value of a given int64_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi64 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64); + +/** + * Queries the value of a given int64_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi64 Where to store the value on success. + * @param i64Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64, uint64_t i64Def); + +/** + * Creates a new USB gadget host. + * + * @returns IPRT status code. + * @param enmType The host type. + * @param paCfg Additional configuration parameters - optional. + * The array must be terminated with a NULL entry. + * @param phGadgetHost Where to store the handle to the gadget host on success. + */ +DECLHIDDEN(int) utsGadgetHostCreate(UTSGADGETHOSTTYPE enmType, PCUTSGADGETCFGITEM paCfg, + PUTSGADGETHOST phGadgetHost); + +/** + * Retains the given gadget host handle. + * + * @returns New reference count. + * @param hGadgetHost The gadget host handle to retain. + */ +DECLHIDDEN(uint32_t) utsGadgetHostRetain(UTSGADGETHOST hGadgetHost); + +/** + * Releases the given gadget host handle, destroying it if the reference + * count reaches 0. + * + * @returns New reference count. + * @param hGadgetHost The gadget host handle to release. + */ +DECLHIDDEN(uint32_t) utsGadgetHostRelease(UTSGADGETHOST hGadgetHost); + +/** + * Returns the current config of the given gadget host. + * + * @returns Pointer to a constant array of configuration items for the given gadget host. + * @param hGadgetHost The gadget host handle. + */ +DECLHIDDEN(PCUTSGADGETCFGITEM) utsGadgetHostGetCfg(UTSGADGETHOST hGadgetHost); + +/** + * Connects the given gadget to the host. + * + * @returns IPRT status code. + * @param hGadgetHost The gadget host handle. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(int) utsGadgetHostGadgetConnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget); + +/** + * Disconnects the given gadget from the host. + * + * @returns IPRT status code. + * @param hGadgetHost The gadget host handle. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(int) utsGadgetHostGadgetDisconnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget); + +/** + * Creates a new USB gadget based the class. + * + * @returns IPRT status code. + * @param hGadgetHost The gadget host the gadget is part of. + * @param enmClass The gadget class. + * @param paCfg Array of optional configuration items for the gadget. + * @param phGadget Where to store the gadget handle on success. + */ +DECLHIDDEN(int) utsGadgetCreate(UTSGADGETHOST hGadgetHost, UTSGADGETCLASS enmClass, + PCUTSGADGETCFGITEM paCfg, PUTSGADET phGadget); + +/** + * Retains the given gadget handle. + * + * @returns New reference count. + * @param hGadget The gadget handle to retain. + */ +DECLHIDDEN(uint32_t) utsGadgetRetain(UTSGADGET hGadget); + +/** + * Releases the given gadget handle, destroying it if the reference + * count reaches 0. + * + * @returns New reference count. + * @param hGadget The gadget handle to destroy. + */ +DECLHIDDEN(uint32_t) utsGadgetRelease(UTSGADGET hGadget); + +/** + * Returns the current config of the given gadget. + * + * @returns Pointer to a constant array of configuration items for the given gadget. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(PCUTSGADGETCFGITEM) utsGadgetGetCfg(UTSGADGET hGadget); + +/** + * Returns the path of the given gadget from which it can be accessed. + * + * @returns Access path. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(const char *) utsGadgetGetAccessPath(UTSGADGET hGadget); + +/** + * Returns the bus ID the gadget is on. + * + * @returns Bus ID of the gadget. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(uint32_t) utsGadgetGetBusId(UTSGADGET hGadget); + +/** + * Returns the device ID of the gagdet. + * + * @returns Device ID of the gadget. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(uint32_t) utsGadgetGetDevId(UTSGADGET hGadget); + +/** + * Mark the gadget as connected to the host. Depending + * on the host type it will be appear as physically attached + * or will appear in the exported USB device list. + * + * @returns IPRT status code. + * @param hGadget The gadget handle to connect. + */ +DECLHIDDEN(int) utsGadgetConnect(UTSGADGET hGadget); + +/** + * Mark the gadget as disconnected from the host. + * + * @returns IPRT status code. + * @param hGadget The gadget handle to disconnect. + */ +DECLHIDDEN(int) utsGadgetDisconnect(UTSGADGET hGadget); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp new file mode 100644 index 00000000..0f123f2e --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp @@ -0,0 +1,452 @@ +/* $Id: UsbTestServiceGadgetCfg.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget Cfg API. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "UsbTestServiceGadget.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Returns the gadget configuration item matching the given key. + * + * @returns Pointer to the configuration item on success or NULL if not found. + * @param paCfg The configuration item array. + * @param pszKey The key to look for. + */ +static PCUTSGADGETCFGITEM utsGadgetCfgGetItemFromKey(PCUTSGADGETCFGITEM paCfg, const char *pszKey) +{ + while ( paCfg + && paCfg->pszKey) + { + if (!RTStrCmp(paCfg->pszKey, pszKey)) + return paCfg; + + paCfg++; + } + return NULL; +} + + + +DECLHIDDEN(int) utsGadgetCfgQueryBool(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_BOOLEAN) + { + *pf = pCfgItem->Val.u.f; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryBoolDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf, bool fDef) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_BOOLEAN) + { + *pf = pCfgItem ? pCfgItem->Val.u.f : fDef; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryString(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_STRING) + { + *ppszVal = RTStrDup(pCfgItem->Val.u.psz); + if (*ppszVal) + rc = VINF_SUCCESS; + else + rc = VERR_NO_STR_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryStringDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal, const char *pszDef) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_STRING) + { + *ppszVal = RTStrDup(pCfgItem ? pCfgItem->Val.u.psz : pszDef); + if (*ppszVal) + rc = VINF_SUCCESS; + else + rc = VERR_NO_STR_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT8) + { + *pu8 = pCfgItem->Val.u.u8; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8, uint8_t u8Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT8) + { + *pu8 = pCfgItem ? pCfgItem->Val.u.u8 : u8Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT16) + { + *pu16 = pCfgItem->Val.u.u16; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16, uint16_t u16Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT16) + { + *pu16 = pCfgItem ? pCfgItem->Val.u.u16 : u16Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT32) + { + *pu32 = pCfgItem->Val.u.u32; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32, uint32_t u32Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT32) + { + *pu32 = pCfgItem ? pCfgItem->Val.u.u32 : u32Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT64) + { + *pu64 = pCfgItem->Val.u.u64; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64, uint64_t u64Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT64) + { + *pu64 = pCfgItem ? pCfgItem->Val.u.u64 : u64Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT8) + { + *pi8 = pCfgItem->Val.u.i8; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8, uint8_t i8Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT8) + { + *pi8 = pCfgItem ? pCfgItem->Val.u.i8 : i8Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT16) + { + *pi16 = pCfgItem->Val.u.i16; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16, uint16_t i16Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT16) + { + *pi16 = pCfgItem ? pCfgItem->Val.u.i16 : i16Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT32) + { + *pi32 = pCfgItem->Val.u.i32; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32, uint32_t i32Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT32) + { + *pi32 = pCfgItem ? pCfgItem->Val.u.i32 : i32Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT64) + { + *pi64 = pCfgItem->Val.u.i64; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64, uint64_t i64Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT64) + { + *pi64 = pCfgItem ? pCfgItem->Val.u.i64 : i64Def; + rc = VINF_SUCCESS; + } + + return rc; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp new file mode 100644 index 00000000..9426d9ca --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp @@ -0,0 +1,461 @@ +/* $Id: UsbTestServiceGadgetClassTest.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget class + * for the test device. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/symlink.h> +#include <iprt/thread.h> + +#include <iprt/linux/sysfs.h> + +#include "UsbTestServiceGadgetInternal.h" +#include "UsbTestServicePlatform.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** Default configfs mount point. */ +#define UTS_GADGET_CLASS_CONFIGFS_MNT_DEF "/sys/kernel/config/usb_gadget" +/** Gadget template name */ +#define UTS_GADGET_TEMPLATE_NAME "gadget_test" + +/** Default vendor ID which is recognized by the usbtest driver. */ +#define UTS_GADGET_TEST_VENDOR_ID_DEF UINT16_C(0x0525) +/** Default product ID which is recognized by the usbtest driver. */ +#define UTS_GADGET_TEST_PRODUCT_ID_DEF UINT16_C(0xa4a0) +/** Default device class. */ +#define UTS_GADGET_TEST_DEVICE_CLASS_DEF UINT8_C(0xff) +/** Default serial number string. */ +#define UTS_GADGET_TEST_SERIALNUMBER_DEF "0123456789" +/** Default manufacturer string. */ +#define UTS_GADGET_TEST_MANUFACTURER_DEF "Oracle Inc." +/** Default product string. */ +#define UTS_GADGET_TEST_PRODUCT_DEF "USB test device" + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETCLASSINT +{ + /** Gadget template path. */ + char *pszGadgetPath; + /** The UDC this gadget is connected to. */ + char *pszUdc; + /** Bus identifier for the used UDC. */ + uint32_t uBusId; + /** Device identifier. */ + uint32_t uDevId; +} UTSGADGETCLASSINT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Number of already created gadgets, used for the template name. */ +static volatile uint32_t g_cGadgets = 0; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Creates a new directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param va The arguments. + */ +static int utsGadgetClassTestDirCreateV(const char *pszFormat, va_list va) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + + size_t cbStr = RTStrPrintfV(&aszPath[0], sizeof(aszPath), pszFormat, va); + if (cbStr <= sizeof(aszPath) - 1) + rc = RTDirCreateFullPath(aszPath, 0700); + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Creates a new directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param ... The arguments. + */ +static int utsGadgetClassTestDirCreate(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = utsGadgetClassTestDirCreateV(pszFormat, va); + va_end(va); + return rc; +} + + +/** + * Removes a directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param va The arguments. + */ +static int utsGadgetClassTestDirRemoveV(const char *pszFormat, va_list va) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + + size_t cbStr = RTStrPrintfV(&aszPath[0], sizeof(aszPath), pszFormat, va); + if (cbStr <= sizeof(aszPath) - 1) + rc = RTDirRemove(aszPath); + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Removes a directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param ... The arguments. + */ +static int utsGadgetClassTestDirRemove(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = utsGadgetClassTestDirRemoveV(pszFormat, va); + va_end(va); + return rc; +} + + +/** + * Links the given function to the given config. + * + * @returns IPRT status code. + * @param pClass The gadget class instance data. + * @param pszFunc The function to link. + * @param pszCfg The configuration which the function will be part of. + */ +static int utsGadgetClassTestLinkFuncToCfg(PUTSGADGETCLASSINT pClass, const char *pszFunc, const char *pszCfg) +{ + int rc = VINF_SUCCESS; + char aszPathFunc[RTPATH_MAX + 1]; + char aszPathCfg[RTPATH_MAX + 1]; + + size_t cbStr = RTStrPrintf(&aszPathFunc[0], sizeof(aszPathFunc), "%s/functions/%s", + pClass->pszGadgetPath, pszFunc); + if (cbStr <= sizeof(aszPathFunc) - 1) + { + cbStr = RTStrPrintf(&aszPathCfg[0], sizeof(aszPathCfg), "%s/configs/%s/%s", + pClass->pszGadgetPath, pszCfg, pszFunc); + if (cbStr <= sizeof(aszPathCfg) - 1) + rc = RTSymlinkCreate(&aszPathCfg[0], &aszPathFunc[0], RTSYMLINKTYPE_DIR, 0); + else + rc = VERR_BUFFER_OVERFLOW; + } + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Unlinks the given function from the given configuration. + * + * @returns IPRT status code. + * @param pClass The gadget class instance data. + * @param pszFunc The function to unlink. + * @param pszCfg The configuration which the function is currently part of. + */ +static int utsGadgetClassTestUnlinkFuncFromCfg(PUTSGADGETCLASSINT pClass, const char *pszFunc, const char *pszCfg) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + size_t cbStr = RTStrPrintf(&aszPath[0], sizeof(aszPath), "%s/configs/%s/%s", + pClass->pszGadgetPath, pszCfg, pszFunc); + if (cbStr <= sizeof(aszPath) - 1) + rc = RTSymlinkDelete(&aszPath[0], 0); + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Cleans up any leftover configurations from the gadget class. + * + * @returns nothing. + * @param pClass The gadget class instance data. + */ +static void utsGadgetClassTestCleanup(PUTSGADGETCLASSINT pClass) +{ + /* Unbind the gadget from the currently assigned UDC first. */ + int rc = RTLinuxSysFsWriteStrFile("", 0, NULL, "%s/UDC", pClass->pszGadgetPath); + AssertRC(rc); + + /* Delete the symlinks, ignore any errors. */ + utsGadgetClassTestUnlinkFuncFromCfg(pClass, "Loopback.0", "c.2"); + utsGadgetClassTestUnlinkFuncFromCfg(pClass, "SourceSink.0", "c.1"); + + /* Delete configuration strings and then the configuration directories. */ + utsGadgetClassTestDirRemove("%s/configs/c.2/strings/0x409", pClass->pszGadgetPath); + utsGadgetClassTestDirRemove("%s/configs/c.1/strings/0x409", pClass->pszGadgetPath); + + utsGadgetClassTestDirRemove("%s/configs/c.2", pClass->pszGadgetPath); + utsGadgetClassTestDirRemove("%s/configs/c.1", pClass->pszGadgetPath); + + /* Delete the functions. */ + utsGadgetClassTestDirRemove("%s/functions/Loopback.0", pClass->pszGadgetPath); + utsGadgetClassTestDirRemove("%s/functions/SourceSink.0", pClass->pszGadgetPath); + + /* Delete the english strings. */ + utsGadgetClassTestDirRemove("%s/strings/0x409", pClass->pszGadgetPath); + + /* Finally delete the gadget template. */ + utsGadgetClassTestDirRemove(pClass->pszGadgetPath); + + /* Release the UDC. */ + if (pClass->pszUdc) + { + rc = utsPlatformLnxReleaseUDC(pClass->pszUdc); + AssertRC(rc); + RTStrFree(pClass->pszUdc); + } +} + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnInit} + */ +static DECLCALLBACK(int) utsGadgetClassTestInit(PUTSGADGETCLASSINT pClass, PCUTSGADGETCFGITEM paCfg) +{ + int rc = VINF_SUCCESS; + + if (RTLinuxSysFsExists(UTS_GADGET_CLASS_CONFIGFS_MNT_DEF)) + { + /* Create the gadget template */ + unsigned idx = ASMAtomicIncU32(&g_cGadgets); + + int rcStr = RTStrAPrintf(&pClass->pszGadgetPath, "%s/%s%u", UTS_GADGET_CLASS_CONFIGFS_MNT_DEF, + UTS_GADGET_TEMPLATE_NAME, idx); + if (rcStr == -1) + return VERR_NO_STR_MEMORY; + + rc = utsGadgetClassTestDirCreate(pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + { + uint16_t idVendor = 0; + uint16_t idProduct = 0; + uint8_t bDeviceClass = 0; + char *pszSerial = NULL; + char *pszManufacturer = NULL; + char *pszProduct = NULL; + bool fSuperSpeed = false; + + /* Get basic device config. */ + rc = utsGadgetCfgQueryU16Def(paCfg, "Gadget/idVendor", &idVendor, UTS_GADGET_TEST_VENDOR_ID_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryU16Def(paCfg, "Gadget/idProduct", &idProduct, UTS_GADGET_TEST_PRODUCT_ID_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryU8Def(paCfg, "Gadget/bDeviceClass", &bDeviceClass, UTS_GADGET_TEST_DEVICE_CLASS_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/SerialNumber", &pszSerial, UTS_GADGET_TEST_SERIALNUMBER_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/Manufacturer", &pszManufacturer, UTS_GADGET_TEST_MANUFACTURER_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/Product", &pszProduct, UTS_GADGET_TEST_PRODUCT_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryBoolDef(paCfg, "Gadget/SuperSpeed", &fSuperSpeed, false); + + if (RT_SUCCESS(rc)) + { + /* Write basic attributes. */ + rc = RTLinuxSysFsWriteU16File(16, idVendor, "%s/idVendor", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteU16File(16, idProduct, "%s/idProduct", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteU16File(16, bDeviceClass, "%s/bDeviceClass", pClass->pszGadgetPath); + + /* Create english language strings. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/strings/0x409", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pszSerial, 0, NULL, "%s/strings/0x409/serialnumber", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pszManufacturer, 0, NULL, "%s/strings/0x409/manufacturer", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pszProduct, 0, NULL, "%s/strings/0x409/product", pClass->pszGadgetPath); + + /* Create the gadget functions. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/functions/SourceSink.0", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/functions/Loopback.0", pClass->pszGadgetPath); + + /* Create the device configs. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.1", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.2", pClass->pszGadgetPath); + + /* Write configuration strings. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.1/strings/0x409", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.2/strings/0x409", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile("source and sink data", 0, NULL, "%s/configs/c.1/strings/0x409/configuration", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile("loop input to output", 0, NULL, "%s/configs/c.2/strings/0x409/configuration", pClass->pszGadgetPath); + + /* Link the functions into the configurations. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestLinkFuncToCfg(pClass, "SourceSink.0", "c.1"); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestLinkFuncToCfg(pClass, "Loopback.0", "c.2"); + + /* Finally enable the gadget by attaching it to a UDC. */ + if (RT_SUCCESS(rc)) + { + pClass->pszUdc = NULL; + + rc = utsPlatformLnxAcquireUDC(fSuperSpeed, &pClass->pszUdc, &pClass->uBusId); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pClass->pszUdc, 0, NULL, "%s/UDC", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + RTThreadSleep(500); /* Fudge: Sleep a bit to give the device a chance to appear on the host so binding succeeds. */ + } + } + + if (pszSerial) + RTStrFree(pszSerial); + if (pszManufacturer) + RTStrFree(pszManufacturer); + if (pszProduct) + RTStrFree(pszProduct); + } + } + else + rc = VERR_NOT_FOUND; + + if (RT_FAILURE(rc)) + utsGadgetClassTestCleanup(pClass); + + return rc; +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnTerm} + */ +static DECLCALLBACK(void) utsGadgetClassTestTerm(PUTSGADGETCLASSINT pClass) +{ + utsGadgetClassTestCleanup(pClass); + + if (pClass->pszGadgetPath) + RTStrFree(pClass->pszGadgetPath); +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnGetBusId} + */ +static DECLCALLBACK(uint32_t) utsGadgetClassTestGetBusId(PUTSGADGETCLASSINT pClass) +{ + return pClass->uBusId; +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnConnect} + */ +static DECLCALLBACK(int) utsGadgetClassTestConnect(PUTSGADGETCLASSINT pClass) +{ + int rc = RTLinuxSysFsWriteStrFile("connect", 0, NULL, "/sys/class/udc/%s/soft_connect", pClass->pszUdc); + if (RT_SUCCESS(rc)) + RTThreadSleep(500); /* Fudge: Sleep a bit to give the device a chance to appear on the host so binding succeeds. */ + + return rc; +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnDisconnect} + */ +static DECLCALLBACK(int) utsGadgetClassTestDisconnect(PUTSGADGETCLASSINT pClass) +{ + return RTLinuxSysFsWriteStrFile("disconnect", 0, NULL, "/sys/class/udc/%s/soft_connect", pClass->pszUdc);} + + + +/** + * The gadget host interface callback table. + */ +const UTSGADGETCLASSIF g_UtsGadgetClassTest = +{ + /** enmType */ + UTSGADGETCLASS_TEST, + /** pszDesc */ + "UTS test device gadget class", + /** cbIf */ + sizeof(UTSGADGETCLASSINT), + /** pfnInit */ + utsGadgetClassTestInit, + /** pfnTerm */ + utsGadgetClassTestTerm, + /** pfnGetBusId */ + utsGadgetClassTestGetBusId, + /** pfnConnect */ + utsGadgetClassTestConnect, + /** pfnDisconnect. */ + utsGadgetClassTestDisconnect +}; + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp new file mode 100644 index 00000000..7e5c21db --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp @@ -0,0 +1,170 @@ +/* $Id: UsbTestServiceGadgetHost.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget host API. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "UsbTestServiceGadget.h" +#include "UsbTestServiceGadgetHostInternal.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETHOSTINT +{ + /** Reference counter. */ + volatile uint32_t cRefs; + /** Pointer to the gadget host callback table. */ + PCUTSGADGETHOSTIF pHstIf; + /** Interface specific instance data - variable in size. */ + uint8_t abIfInst[1]; +} UTSGADGETHOSTINT; +/** Pointer to the internal gadget host instance data. */ +typedef UTSGADGETHOSTINT *PUTSGADGETHOSTINT; + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ + +/** Known gadget host interfaces. */ +static const PCUTSGADGETHOSTIF g_apUtsGadgetHostIf[] = +{ + &g_UtsGadgetHostIfUsbIp, +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Destroys a gadget host instance. + * + * @returns nothing. + * @param pThis The gadget host instance. + */ +static void utsGadgetHostDestroy(PUTSGADGETHOSTINT pThis) +{ + /** @todo Remove all gadgets. */ + pThis->pHstIf->pfnTerm((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0]); + RTMemFree(pThis); +} + + +DECLHIDDEN(int) utsGadgetHostCreate(UTSGADGETHOSTTYPE enmType, PCUTSGADGETCFGITEM paCfg, + PUTSGADGETHOST phGadgetHost) +{ + int rc = VINF_SUCCESS; + PCUTSGADGETHOSTIF pIf = NULL; + + /* Get the interface. */ + for (unsigned i = 0; i < RT_ELEMENTS(g_apUtsGadgetHostIf); i++) + { + if (g_apUtsGadgetHostIf[i]->enmType == enmType) + { + pIf = g_apUtsGadgetHostIf[i]; + break; + } + } + + if (RT_LIKELY(pIf)) + { + PUTSGADGETHOSTINT pThis = (PUTSGADGETHOSTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(UTSGADGETHOSTINT, abIfInst[pIf->cbIf])); + if (RT_LIKELY(pThis)) + { + pThis->cRefs = 1; + pThis->pHstIf = pIf; + rc = pIf->pfnInit((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], paCfg); + if (RT_SUCCESS(rc)) + *phGadgetHost = pThis; + else + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + + +DECLHIDDEN(uint32_t) utsGadgetHostRetain(UTSGADGETHOST hGadgetHost) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, 0); + + return ASMAtomicIncU32(&pThis->cRefs); +} + + +DECLHIDDEN(uint32_t) utsGadgetHostRelease(UTSGADGETHOST hGadgetHost) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, 0); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + utsGadgetHostDestroy(pThis); + + return cRefs; +} + + +DECLHIDDEN(int) utsGadgetHostGadgetConnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return pThis->pHstIf->pfnGadgetConnect((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], hGadget); +} + + +DECLHIDDEN(int) utsGadgetHostGadgetDisconnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return pThis->pHstIf->pfnGadgetDisconnect((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], hGadget); +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h new file mode 100644 index 00000000..c1849018 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h @@ -0,0 +1,123 @@ +/* $Id: UsbTestServiceGadgetHostInternal.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Gadget API. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +#include "UsbTestServiceGadget.h" + +RT_C_DECLS_BEGIN + +/** Pointer to an opaque type dependent gadget host instance data. */ +typedef struct UTSGADGETHOSTTYPEINT *PUTSGADGETHOSTTYPEINT; +/** Pointer to a gadget host instance pointer. */ +typedef PUTSGADGETHOSTTYPEINT *PPUTSGADETHOSTTYPEINT; + +/** + * Gadget host interface. + */ +typedef struct UTSGADGETHOSTIF +{ + /** The gadget host type implemented. */ + UTSGADGETHOSTTYPE enmType; + /** Description. */ + const char *pszDesc; + /** Size of the interface specific instance data. */ + size_t cbIf; + + /** + * Initializes the gadget host interface. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param paCfg The configuration of the interface. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (PUTSGADGETHOSTTYPEINT pIf, PCUTSGADGETCFGITEM paCfg)); + + /** + * Terminates the gadget host interface. + * + * @returns nothing. + * @param pIf The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm, (PUTSGADGETHOSTTYPEINT pIf)); + + /** + * Adds the given gadget to the host interface. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to add. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetAdd, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + + /** + * Removes the given gadget from the host interface. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to remove. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetRemove, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + + /** + * Connects the given gadget to the host interface so it appears as connected to the client + * of the gadget host. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to add. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetConnect, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + + /** + * Disconnects the given gadget from the host interface so it appears as disconnected to the client + * of the gadget host. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to add. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetDisconnect, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + +} UTSGADGETHOSTIF; +/** Pointer to a gadget host callback table. */ +typedef UTSGADGETHOSTIF *PUTSGADGETHOSTIF; +/** Pointer to a const gadget host callback table. */ +typedef const struct UTSGADGETHOSTIF *PCUTSGADGETHOSTIF; + +extern UTSGADGETHOSTIF const g_UtsGadgetHostIfUsbIp; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp new file mode 100644 index 00000000..5a35acaa --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp @@ -0,0 +1,253 @@ +/* $Id: UsbTestServiceGadgetHostUsbIp.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget host interface + * for USB/IP. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "UsbTestServiceGadgetHostInternal.h" +#include "UsbTestServicePlatform.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETHOSTTYPEINT +{ + /** Handle to the USB/IP daemon process. */ + RTPROCESS hProcUsbIp; +} UTSGADGETHOSTTYPEINT; + +/** Default port of the USB/IP server. */ +#define UTS_GADGET_HOST_USBIP_PORT_DEF 3240 + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Worker for binding/unbinding the given gadget from the USB/IP server. + * + * @returns IPRT status code. + * @param pThis The gadget host instance. + * @param hGadget The gadget handle. + * @param fBind Flag whether to do a bind or unbind. + */ +static int usbGadgetHostUsbIpBindUnbind(PUTSGADGETHOSTTYPEINT pThis, UTSGADGET hGadget, bool fBind) +{ + RT_NOREF1(pThis); + uint32_t uBusId, uDevId; + char aszBus[32]; + + uBusId = utsGadgetGetBusId(hGadget); + uDevId = utsGadgetGetDevId(hGadget); + + /* Create the busid argument string. */ + size_t cbRet = RTStrPrintf(&aszBus[0], RT_ELEMENTS(aszBus), "%u-%u", uBusId, uDevId); + if (cbRet == RT_ELEMENTS(aszBus)) + return VERR_BUFFER_OVERFLOW; + + /* Bind to the USB/IP server. */ + RTPROCESS hProcUsbIp = NIL_RTPROCESS; + const char *apszArgv[5]; + + apszArgv[0] = "usbip"; + apszArgv[1] = fBind ? "bind" : "unbind"; + apszArgv[2] = "-b"; + apszArgv[3] = &aszBus[0]; + apszArgv[4] = NULL; + + int rc = RTProcCreate("usbip", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcUsbIp); + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(hProcUsbIp, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); + if (RT_SUCCESS(rc)) + { + /* Evaluate the process status. */ + if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL + || ProcSts.iStatus != 0) + rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */ + } + } + + return rc; +} + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnInit} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpInit(PUTSGADGETHOSTTYPEINT pIf, PCUTSGADGETCFGITEM paCfg) +{ + int rc = VINF_SUCCESS; + uint16_t uPort = 0; + + pIf->hProcUsbIp = NIL_RTPROCESS; + + rc = utsGadgetCfgQueryU16Def(paCfg, "UsbIp/Port", &uPort, UTS_GADGET_HOST_USBIP_PORT_DEF); + if (RT_SUCCESS(rc)) + { + /* Make sure the kernel drivers are loaded. */ + rc = utsPlatformModuleLoad("usbip-core", NULL, 0); + if (RT_SUCCESS(rc)) + { + rc = utsPlatformModuleLoad("usbip-host", NULL, 0); + if (RT_SUCCESS(rc)) + { + char aszPort[10]; + char aszPidFile[64]; + const char *apszArgv[6]; + + RTStrPrintf(aszPort, RT_ELEMENTS(aszPort), "%u", uPort); + RTStrPrintf(aszPidFile, RT_ELEMENTS(aszPidFile), "/var/run/usbipd-%u.pid", uPort); + /* Start the USB/IP server process. */ + apszArgv[0] = "usbipd"; + apszArgv[1] = "--tcp-port"; + apszArgv[2] = aszPort; + apszArgv[3] = "--pid"; + apszArgv[4] = aszPidFile; + apszArgv[5] = NULL; + rc = RTProcCreate("usbipd", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &pIf->hProcUsbIp); + if (RT_SUCCESS(rc)) + { + /* Wait for a bit to make sure the server started up successfully. */ + uint64_t tsStart = RTTimeMilliTS(); + do + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(pIf->hProcUsbIp, RTPROCWAIT_FLAGS_NOBLOCK, &ProcSts); + if (rc != VERR_PROCESS_RUNNING) + { + rc = VERR_INVALID_HANDLE; + break; + } + RTThreadSleep(1); + rc = VINF_SUCCESS; + } while (RTTimeMilliTS() - tsStart < 2 * 1000); /* 2 seconds. */ + } + } + } + } + + return rc; +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnTerm} + */ +static DECLCALLBACK(void) utsGadgetHostUsbIpTerm(PUTSGADGETHOSTTYPEINT pIf) +{ + /* Kill the process and wait for it to terminate. */ + RTProcTerminate(pIf->hProcUsbIp); + + RTPROCSTATUS ProcSts; + RTProcWait(pIf->hProcUsbIp, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetAdd} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetAdd(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + /* Nothing to do so far. */ + RT_NOREF2(pIf, hGadget); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetRemove} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetRemove(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + /* Nothing to do so far. */ + RT_NOREF2(pIf, hGadget); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetConnect} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetConnect(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + return usbGadgetHostUsbIpBindUnbind(pIf, hGadget, true /* fBind */); +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetDisconnect} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetDisconnect(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + return usbGadgetHostUsbIpBindUnbind(pIf, hGadget, false /* fBind */); +} + + + +/** + * The gadget host interface callback table. + */ +const UTSGADGETHOSTIF g_UtsGadgetHostIfUsbIp = +{ + /** enmType */ + UTSGADGETHOSTTYPE_USBIP, + /** pszDesc */ + "UTS USB/IP gadget host", + /** cbIf */ + sizeof(UTSGADGETHOSTTYPEINT), + /** pfnInit */ + utsGadgetHostUsbIpInit, + /** pfnTerm */ + utsGadgetHostUsbIpTerm, + /** pfnGadgetAdd */ + utsGadgetHostUsbIpGadgetAdd, + /** pfnGadgetRemove */ + utsGadgetHostUsbIpGadgetRemove, + /** pfnGadgetConnect */ + utsGadgetHostUsbIpGadgetConnect, + /** pfnGadgetDisconnect */ + utsGadgetHostUsbIpGadgetDisconnect +}; diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h new file mode 100644 index 00000000..5540de71 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h @@ -0,0 +1,109 @@ +/* $Id: UsbTestServiceGadgetInternal.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Interal gadget interfaces. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +#include "UsbTestServiceGadget.h" + +RT_C_DECLS_BEGIN + +/** Pointer to an opaque type dependent gadget host instance data. */ +typedef struct UTSGADGETCLASSINT *PUTSGADGETCLASSINT; +/** Pointer to a gadget host instance pointer. */ +typedef PUTSGADGETCLASSINT *PPUTSGADGETCLASSINT; + +/** + * Gadget class interface. + */ +typedef struct UTSGADGETCLASSIF +{ + /** The gadget class type implemented. */ + UTSGADGETCLASS enmClass; + /** Description. */ + const char *pszDesc; + /** Size of the class specific instance data. */ + size_t cbClass; + + /** + * Initializes the gadget class instance. + * + * @returns IPRT status code. + * @param pClass The interface specific instance data. + * @param paCfg The configuration of the interface. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (PUTSGADGETCLASSINT pClass, PCUTSGADGETCFGITEM paCfg)); + + /** + * Terminates the gadget class instance. + * + * @returns nothing. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm, (PUTSGADGETCLASSINT pClass)); + + /** + * Returns the bus ID of the class instance. + * + * @returns Bus ID. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(uint32_t, pfnGetBusId, (PUTSGADGETCLASSINT pClass)); + + /** + * Connects the gadget. + * + * @returns IPRT status code. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(int, pfnConnect, (PUTSGADGETCLASSINT pClass)); + + /** + * Disconnect the gadget. + * + * @returns IPRT status code. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(int, pfnDisconnect, (PUTSGADGETCLASSINT pClass)); + +} UTSGADGETCLASSIF; +/** Pointer to a gadget class callback table. */ +typedef UTSGADGETCLASSIF *PUTSGADGETCLASSIF; +/** Pointer to a const gadget host callback table. */ +typedef const struct UTSGADGETCLASSIF *PCUTSGADGETCLASSIF; + +extern UTSGADGETCLASSIF const g_UtsGadgetClassTest; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h new file mode 100644 index 00000000..f636e2c4 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h @@ -0,0 +1,216 @@ +/* $Id: UsbTestServiceInternal.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Internal Header. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/getopt.h> +#include <iprt/stream.h> + +#include "UsbTestServiceProtocol.h" + +RT_C_DECLS_BEGIN + +/** Opaque UTS transport layer specific client data. */ +typedef struct UTSTRANSPORTCLIENT *PUTSTRANSPORTCLIENT; +typedef PUTSTRANSPORTCLIENT *PPUTSTRANSPORTCLIENT; + +/** + * Transport layer descriptor. + */ +typedef struct UTSTRANSPORT +{ + /** The name. */ + char szName[16]; + /** The description. */ + const char *pszDesc; + /** Pointer to an array of options. */ + PCRTGETOPTDEF paOpts; + /** The number of options in the array. */ + size_t cOpts; + + /** + * Print the usage information for this transport layer. + * + * @param pStream The stream to print the usage info to. + * + * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0. + */ + DECLR3CALLBACKMEMBER(void, pfnUsage, (PRTSTREAM pStream)); + + /** + * Handle an option. + * + * When encountering an options that is not part of the base options, we'll call + * this method for each transport layer until one handles it. + * + * @retval VINF_SUCCESS if handled. + * @retval VERR_TRY_AGAIN if not handled. + * @retval VERR_INVALID_PARAMETER if we should exit with a non-zero status. + * + * @param ch The short option value. + * @param pVal Pointer to the value union. + * + * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0. + */ + DECLR3CALLBACKMEMBER(int, pfnOption, (int ch, PCRTGETOPTUNION pVal)); + + /** + * Initializes the transport layer. + * + * @returns IPRT status code. On errors, the transport layer shall call + * RTMsgError to display the error details to the user. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (void)); + + /** + * Terminate the transport layer, closing and freeing resources. + * + * On errors, the transport layer shall call RTMsgError to display the error + * details to the user. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm, (void)); + + /** + * Waits for a new client to connect and returns the client specific data on + * success. + */ + DECLR3CALLBACKMEMBER(int, pfnWaitForConnect, (PPUTSTRANSPORTCLIENT ppClientNew)); + + /** + * Polls for incoming packets. + * + * @returns true if there are pending packets, false if there isn't. + * @param pClient The client to poll for data. + */ + DECLR3CALLBACKMEMBER(bool, pfnPollIn, (PUTSTRANSPORTCLIENT pClient)); + + /** + * Adds any pollable handles to the poll set. + * + * @returns IPRT status code. + * @param hPollSet The poll set to add them to. + * @param pClient The transport client structure. + * @param idStart The handle ID to start at. + */ + DECLR3CALLBACKMEMBER(int, pfnPollSetAdd, (RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart)); + + /** + * Removes the given client frmo the given pollset. + * + * @returns IPRT status code. + * @param hPollSet The poll set to remove from. + * @param pClient The transport client structure. + * @param idStart The handle ID to remove. + */ + DECLR3CALLBACKMEMBER(int, pfnPollSetRemove, (RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart)); + + /** + * Receives an incoming packet. + * + * This will block until the data becomes available or we're interrupted by a + * signal or something. + * + * @returns IPRT status code. On error conditions other than VERR_INTERRUPTED, + * the current operation will be aborted when applicable. When + * interrupted, the transport layer will store the data until the next + * receive call. + * + * @param pClient The transport client structure. + * @param ppPktHdr Where to return the pointer to the packet we've + * read. This is allocated from the heap using + * RTMemAlloc (w/ UTSPKT_ALIGNMENT) and must be + * free by calling RTMemFree. + */ + DECLR3CALLBACKMEMBER(int, pfnRecvPkt, (PUTSTRANSPORTCLIENT pClient, PPUTSPKTHDR ppPktHdr)); + + /** + * Sends an outgoing packet. + * + * This will block until the data has been written. + * + * @returns IPRT status code. + * @retval VERR_INTERRUPTED if interrupted before anything was sent. + * + * @param pClient The transport client structure. + * @param pPktHdr The packet to send. The size is given by + * aligning the size in the header by + * UTSPKT_ALIGNMENT. + */ + DECLR3CALLBACKMEMBER(int, pfnSendPkt, (PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr)); + + /** + * Sends a babble packet and disconnects the client (if applicable). + * + * @param pClient The transport client structure. + * @param pPktHdr The packet to send. The size is given by + * aligning the size in the header by + * UTSPKT_ALIGNMENT. + * @param cMsSendTimeout The send timeout measured in milliseconds. + */ + DECLR3CALLBACKMEMBER(void, pfnBabble, (PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout)); + + /** + * Notification about a client HOWDY. + * + * @param pClient The transport client structure. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyHowdy, (PUTSTRANSPORTCLIENT pClient)); + + /** + * Notification about a client BYE. + * + * For connection oriented transport layers, it would be good to disconnect the + * client at this point. + * + * @param pClient The transport client structure. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyBye, (PUTSTRANSPORTCLIENT pClient)); + + /** + * Notification about a REBOOT or SHUTDOWN. + * + * For connection oriented transport layers, stop listening for and + * accepting at this point. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyReboot, (void)); + + /** Non-zero end marker. */ + uint32_t u32EndMarker; +} UTSTRANSPORT; +/** Pointer to a const transport layer descriptor. */ +typedef const struct UTSTRANSPORT *PCUTSTRANSPORT; + + +extern UTSTRANSPORT const g_TcpTransport; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp new file mode 100644 index 00000000..e276d186 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp @@ -0,0 +1,438 @@ +/* $Id: UsbTestServicePlatform-linux.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Platform + * specific helpers - Linux version. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/string.h> + +#include <iprt/linux/sysfs.h> + +#include "UsbTestServicePlatform.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** Where the dummy_hcd.* and dummy_udc.* entries are stored. */ +#define UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/sys/devices/platform" + +/** + * A USB bus provided by the dummy HCD. + */ +typedef struct UTSPLATFORMLNXDUMMYHCDBUS +{ + /** The bus ID on the host the dummy HCD is serving. */ + uint32_t uBusId; + /** Flag whether this is a super speed bus. */ + bool fSuperSpeed; +} UTSPLATFORMLNXDUMMYHCDBUS; +/** Pointer to a Dummy HCD bus. */ +typedef UTSPLATFORMLNXDUMMYHCDBUS *PUTSPLATFORMLNXDUMMYHCDBUS; + +/** + * A dummy UDC descriptor. + */ +typedef struct UTSPLATFORMLNXDUMMYHCD +{ + /** Index of the dummy hcd entry. */ + uint32_t idxDummyHcd; + /** Name for the dummy HCD. */ + const char *pszHcdName; + /** Name for the accompanying dummy HCD. */ + const char *pszUdcName; + /** Flag whether this HCD is free for use. */ + bool fAvailable; + /** Flag whether this HCD contains a super speed capable bus. */ + bool fSuperSpeed; + /** Number of busses this HCD instance serves. */ + unsigned cBusses; + /** Bus structures the HCD serves.*/ + PUTSPLATFORMLNXDUMMYHCDBUS paBusses; +} UTSPLATFORMLNXDUMMYHCD; +/** Pointer to a dummy HCD entry. */ +typedef UTSPLATFORMLNXDUMMYHCD *PUTSPLATFORMLNXDUMMYHCD; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Array of dummy HCD entries. */ +static PUTSPLATFORMLNXDUMMYHCD g_paDummyHcd = NULL; +/** Number of Dummy hCD entries in the array. */ +static unsigned g_cDummyHcd = 0; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Queries the assigned busses for the given dummy HCD instance. + * + * @returns IPRT status code. + * @param pHcd The dummy HCD bus instance. + * @param pszHcdName The base HCD name. + */ +static int utsPlatformLnxDummyHcdQueryBusses(PUTSPLATFORMLNXDUMMYHCD pHcd, const char *pszHcdName) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + unsigned idxBusCur = 0; + unsigned idxBusMax = 0; + + size_t cchPath = RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.%u/usb*", + pszHcdName, pHcd->idxDummyHcd); + if (cchPath == RT_ELEMENTS(aszPath)) + return VERR_BUFFER_OVERFLOW; + + RTDIR hDir = NULL; + rc = RTDirOpenFiltered(&hDir, aszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirFolderContent; + rc = RTDirRead(hDir, &DirFolderContent, NULL); + if (RT_SUCCESS(rc)) + { + uint32_t uBusId = 0; + + /* Extract the bus number - it is after "usb", i.e. "usb9" indicates a bus ID of 9. */ + rc = RTStrToUInt32Ex(&DirFolderContent.szName[3], NULL, 10, &uBusId); + if (RT_SUCCESS(rc)) + { + /* Check whether this is a super speed bus. */ + int64_t iSpeed = 0; + bool fSuperSpeed = false; + rc = RTLinuxSysFsReadIntFile(10, &iSpeed, UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.%u/%s/speed", + pszHcdName, pHcd->idxDummyHcd, DirFolderContent.szName); + if ( RT_SUCCESS(rc) + && (iSpeed == 5000 || iSpeed == 10000)) + { + fSuperSpeed = true; + pHcd->fSuperSpeed = true; + } + + /* Add to array of available busses for this HCD. */ + if (idxBusCur == idxBusMax) + { + size_t cbNew = (idxBusMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCDBUS); + PUTSPLATFORMLNXDUMMYHCDBUS pNew = (PUTSPLATFORMLNXDUMMYHCDBUS)RTMemRealloc(pHcd->paBusses, cbNew); + if (pNew) + { + idxBusMax += 10; + pHcd->paBusses = pNew; + } + } + + if (idxBusCur < idxBusMax) + { + pHcd->paBusses[idxBusCur].uBusId = uBusId; + pHcd->paBusses[idxBusCur].fSuperSpeed = fSuperSpeed; + idxBusCur++; + } + else + rc = VERR_NO_MEMORY; + } + } + } while (RT_SUCCESS(rc)); + + pHcd->cBusses = idxBusCur; + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + RTDirClose(hDir); + } + + return rc; +} + + +/** + * Scans all available HCDs with the given name. + * + * @returns IPRT status code. + * @param pszHcdName The base HCD name. + * @param pszUdcName The base UDC name. + */ +static int utsPlatformLnxHcdScanByName(const char *pszHcdName, const char *pszUdcName) +{ + char aszPath[RTPATH_MAX + 1]; + size_t cchPath = RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), + UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.*", pszHcdName); + if (cchPath == RT_ELEMENTS(aszPath)) + return VERR_BUFFER_OVERFLOW; + + /* Enumerate the available HCD and their bus numbers. */ + RTDIR hDir = NULL; + int rc = RTDirOpenFiltered(&hDir, aszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + unsigned idxHcdCur = g_cDummyHcd; + unsigned idxHcdMax = g_cDummyHcd; + + do + { + RTDIRENTRY DirFolderContent; + rc = RTDirRead(hDir, &DirFolderContent, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Get the HCD index and assigned bus number form the sysfs entries, + * Any error here is silently ignored and results in the HCD not being + * added to the list of available controllers. + */ + const char *pszIdx = RTStrStr(DirFolderContent.szName, "."); + if (pszIdx) + { + /* Skip the separator and convert number to index. */ + pszIdx++; + + uint32_t idxHcd = 0; + rc = RTStrToUInt32Ex(pszIdx, NULL, 10, &idxHcd); + if (RT_SUCCESS(rc)) + { + /* Add to array of available HCDs. */ + if (idxHcdCur == idxHcdMax) + { + size_t cbNew = (idxHcdMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCD); + PUTSPLATFORMLNXDUMMYHCD pNew = (PUTSPLATFORMLNXDUMMYHCD)RTMemRealloc(g_paDummyHcd, cbNew); + if (pNew) + { + idxHcdMax += 10; + g_paDummyHcd = pNew; + } + } + + if (idxHcdCur < idxHcdMax) + { + g_paDummyHcd[idxHcdCur].idxDummyHcd = idxHcd; + g_paDummyHcd[idxHcdCur].pszHcdName = pszHcdName; + g_paDummyHcd[idxHcdCur].pszUdcName = pszUdcName; + g_paDummyHcd[idxHcdCur].fAvailable = true; + g_paDummyHcd[idxHcdCur].fSuperSpeed = false; + g_paDummyHcd[idxHcdCur].cBusses = 0; + g_paDummyHcd[idxHcdCur].paBusses = NULL; + rc = utsPlatformLnxDummyHcdQueryBusses(&g_paDummyHcd[idxHcdCur], pszHcdName); + if (RT_SUCCESS(rc)) + idxHcdCur++; + } + else + rc = VERR_NO_MEMORY; + } + } + } + } while (RT_SUCCESS(rc)); + + g_cDummyHcd = idxHcdCur; + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + RTDirClose(hDir); + } + + return rc; +} + +DECLHIDDEN(int) utsPlatformInit(void) +{ + /* Load the modules required for setting up USB/IP testing. */ + int rc = utsPlatformModuleLoad("libcomposite", NULL, 0); + if (RT_SUCCESS(rc)) + { + const char *apszArg[] = { "num=20" }; /** @todo Make configurable from config. */ + rc = utsPlatformModuleLoad("dummy_hcd", &apszArg[0], RT_ELEMENTS(apszArg)); + if (RT_SUCCESS(rc)) + rc = utsPlatformModuleLoad("dummy_hcd_ss", &apszArg[0], RT_ELEMENTS(apszArg)); + if (RT_SUCCESS(rc)) + rc = utsPlatformLnxHcdScanByName("dummy_hcd", "dummy_udc"); + if (RT_SUCCESS(rc)) + rc = utsPlatformLnxHcdScanByName("dummy_hcd_ss", "dummy_udc_ss"); + } + + return rc; +} + + +DECLHIDDEN(void) utsPlatformTerm(void) +{ + /* Unload dummy HCD. */ + utsPlatformModuleUnload("dummy_hcd"); + utsPlatformModuleUnload("dummy_hcd_ss"); + + RTMemFree(g_paDummyHcd); +} + + +DECLHIDDEN(int) utsPlatformModuleLoad(const char *pszModule, const char **papszArgv, + unsigned cArgv) +{ + RTPROCESS hProcModprobe = NIL_RTPROCESS; + const char **papszArgs = (const char **)RTMemAllocZ((3 + cArgv) * sizeof(const char *)); + if (RT_UNLIKELY(!papszArgs)) + return VERR_NO_MEMORY; + + papszArgs[0] = "modprobe"; + papszArgs[1] = pszModule; + + unsigned idx; + for (idx = 0; idx < cArgv; idx++) + papszArgs[2+idx] = papszArgv[idx]; + papszArgs[2+idx] = NULL; + + int rc = RTProcCreate("modprobe", papszArgs, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcModprobe); + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(hProcModprobe, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); + if (RT_SUCCESS(rc)) + { + /* Evaluate the process status. */ + if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL + || ProcSts.iStatus != 0) + rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */ + } + } + + RTMemFree(papszArgs); + return rc; +} + + +DECLHIDDEN(int) utsPlatformModuleUnload(const char *pszModule) +{ + RTPROCESS hProcModprobe = NIL_RTPROCESS; + const char *apszArgv[3]; + + apszArgv[0] = "rmmod"; + apszArgv[1] = pszModule; + apszArgv[2] = NULL; + + int rc = RTProcCreate("rmmod", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcModprobe); + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(hProcModprobe, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); + if (RT_SUCCESS(rc)) + { + /* Evaluate the process status. */ + if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL + || ProcSts.iStatus != 0) + rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */ + } + } + + return rc; +} + + +DECLHIDDEN(int) utsPlatformLnxAcquireUDC(bool fSuperSpeed, char **ppszUdc, uint32_t *puBusId) +{ + int rc = VERR_NOT_FOUND; + + for (unsigned i = 0; i < g_cDummyHcd; i++) + { + PUTSPLATFORMLNXDUMMYHCD pHcd = &g_paDummyHcd[i]; + + /* + * We can't use a super speed capable UDC for gadgets with lower speeds + * because they hardcode the maximum speed to SuperSpeed most of the time + * which will make it unusable for lower speeds. + */ + if ( pHcd->fAvailable + && pHcd->fSuperSpeed == fSuperSpeed) + { + /* Check all assigned busses for a speed match. */ + for (unsigned idxBus = 0; idxBus < pHcd->cBusses; idxBus++) + { + if (pHcd->paBusses[idxBus].fSuperSpeed == fSuperSpeed) + { + rc = VINF_SUCCESS; + int cbRet = RTStrAPrintf(ppszUdc, "%s.%u", pHcd->pszUdcName, pHcd->idxDummyHcd); + if (cbRet == -1) + rc = VERR_NO_STR_MEMORY; + *puBusId = pHcd->paBusses[idxBus].uBusId; + pHcd->fAvailable = false; + break; + } + } + + if (rc != VERR_NOT_FOUND) + break; + } + } + + return rc; +} + + +DECLHIDDEN(int) utsPlatformLnxReleaseUDC(const char *pszUdc) +{ + int rc = VERR_INVALID_PARAMETER; + const char *pszIdx = RTStrStr(pszUdc, "."); + if (pszIdx) + { + size_t cchUdcName = pszIdx - pszUdc; + pszIdx++; + uint32_t idxHcd = 0; + rc = RTStrToUInt32Ex(pszIdx, NULL, 10, &idxHcd); + if (RT_SUCCESS(rc)) + { + rc = VERR_NOT_FOUND; + + for (unsigned i = 0; i < g_cDummyHcd; i++) + { + if ( g_paDummyHcd[i].idxDummyHcd == idxHcd + && !RTStrNCmp(g_paDummyHcd[i].pszUdcName, pszUdc, cchUdcName)) + { + AssertReturn(!g_paDummyHcd[i].fAvailable, VERR_INVALID_PARAMETER); + g_paDummyHcd[i].fAvailable = true; + rc = VINF_SUCCESS; + break; + } + } + } + } + + return rc; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h new file mode 100644 index 00000000..15154f41 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h @@ -0,0 +1,95 @@ +/* $Id: UsbTestServicePlatform.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Platform specific helpers. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +RT_C_DECLS_BEGIN + +/** + * Initializes the platform specific structures for UTS. + * + * @returns IPRT status code. + */ +DECLHIDDEN(int) utsPlatformInit(void); + +/** + * Frees all platform specific structures for UTS. + */ +DECLHIDDEN(void) utsPlatformTerm(void); + +/** + * Loads the specified kernel module on the platform. + * + * @returns IPRT status code. + * @param pszModule The module to load. + * @param papszArgv Array of arguments to pass to the module. + * @param cArgv Number of argument array entries. + */ +DECLHIDDEN(int) utsPlatformModuleLoad(const char *pszModule, const char **papszArgv, + unsigned cArgv); + +/** + * Unloads the specified kernel module on the platform. + * + * @returns IPRT status code. + * @param pszModule The module to unload. + */ +DECLHIDDEN(int) utsPlatformModuleUnload(const char *pszModule); + +#ifdef RT_OS_LINUX + +/** + * Acquires a free UDC to attach a gadget to. + * + * @returns IPRT status code. + * @param fSuperSpeed Flag whether a super speed bus is required. + * @param ppszUdc Where to store the pointer to the name of the UDC on success. + * Free with RTStrFree(). + * @param puBusId Where to store the bus ID the UDC is attached to on the host side. + */ +DECLHIDDEN(int) utsPlatformLnxAcquireUDC(bool fSuperSpeed, char **ppszUdc, uint32_t *puBusId); + +/** + * Releases the given UDC for other use. + * + * @returns IPRT status code. + * @param pszUdc The UDC to release. + */ +DECLHIDDEN(int) utsPlatformLnxReleaseUDC(const char *pszUdc); + +#endif + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp new file mode 100644 index 00000000..ef25fd30 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp @@ -0,0 +1,114 @@ +/* $Id: UsbTestServiceProtocol.cpp $ */ +/** @file + * UsbTestService - Remote USB test configuration and execution server, Protocol helpers. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/asm.h> +#include <iprt/cdefs.h> + +#include "UsbTestServiceProtocol.h" + + + +/** + * Converts a UTS packet header from host to network byte order. + * + * @returns nothing. + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolPktHdrH2N(PUTSPKTHDR pPktHdr) +{ + pPktHdr->cb = RT_H2N_U32(pPktHdr->cb); + pPktHdr->uCrc32 = RT_H2N_U32(pPktHdr->uCrc32); +} + + +/** + * Converts a UTS packet header from network to host byte order. + * + * @returns nothing. + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolPktHdrN2H(PUTSPKTHDR pPktHdr) +{ + pPktHdr->cb = RT_N2H_U32(pPktHdr->cb); + pPktHdr->uCrc32 = RT_N2H_U32(pPktHdr->uCrc32); +} + + +/** + * Converts a UTS status header from host to network byte order. + * + * @returns nothing. + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolStsHdrH2N(PUTSPKTSTS pPktHdr) +{ + utsProtocolPktHdrH2N(&pPktHdr->Hdr); + pPktHdr->rcReq = RT_H2N_U32(pPktHdr->rcReq); + pPktHdr->cchStsMsg = RT_H2N_U32(pPktHdr->cchStsMsg); +} + + +/** + * Converts a UTS status header from network to host byte order. + * + * @returns nothing. + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolStsHdrN2H(PUTSPKTSTS pPktHdr) +{ + utsProtocolPktHdrN2H(&pPktHdr->Hdr); + pPktHdr->rcReq = RT_N2H_U32(pPktHdr->rcReq); + pPktHdr->cchStsMsg = RT_N2H_U32(pPktHdr->cchStsMsg); +} + + +DECLHIDDEN(void) utsProtocolReqH2N(PUTSPKTHDR pPktHdr) +{ + utsProtocolPktHdrH2N(pPktHdr); +} + + +DECLHIDDEN(void) utsProtocolReqN2H(PUTSPKTHDR pPktHdr) +{ + RT_NOREF1(pPktHdr); +} + + +DECLHIDDEN(void) utsProtocolRepH2N(PUTSPKTSTS pPktHdr) +{ + RT_NOREF1(pPktHdr); +} + + +DECLHIDDEN(void) utsProtocolRepN2H(PUTSPKTSTS pPktHdr) +{ + RT_NOREF1(pPktHdr); +} diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h new file mode 100644 index 00000000..98c543fa --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h @@ -0,0 +1,367 @@ +/* $Id: UsbTestServiceProtocol.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Protocol Header. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> + +RT_C_DECLS_BEGIN + +/** + * Common Packet header (for requests and replies). + */ +typedef struct UTSPKTHDR +{ + /** The unpadded packet length. This include this header. */ + uint32_t cb; + /** The CRC-32 for the packet starting from the opcode field. 0 if the packet + * hasn't been CRCed. */ + uint32_t uCrc32; + /** Packet opcode, an unterminated ASCII string. */ + uint8_t achOpcode[8]; +} UTSPKTHDR; +AssertCompileSize(UTSPKTHDR, 16); +/** Pointer to a packet header. */ +typedef UTSPKTHDR *PUTSPKTHDR; +/** Pointer to a packet header. */ +typedef UTSPKTHDR const *PCUTSPKTHDR; +/** Pointer to a packet header pointer. */ +typedef PUTSPKTHDR *PPUTSPKTHDR; + +/** Packet alignment. */ +#define UTSPKT_ALIGNMENT 16 +/** Max packet size. */ +#define UTSPKT_MAX_SIZE _256K + +/** + * Status packet. + */ +typedef struct UTSPKTSTS +{ + /** Embedded common packet header. */ + UTSPKTHDR Hdr; + /** The IPRT status code of the request. */ + int32_t rcReq; + /** Size of the optional status message following this structure - + * only for errors. */ + uint32_t cchStsMsg; + /** Padding - reserved. */ + uint8_t au8Padding[8]; +} UTSPKTSTS; +AssertCompileSizeAlignment(UTSPKTSTS, UTSPKT_ALIGNMENT); +/** Pointer to a status packet header. */ +typedef UTSPKTSTS *PUTSPKTSTS; + +#define UTSPKT_OPCODE_HOWDY "HOWDY " + +/** 32bit protocol version consisting of a 16bit major and 16bit minor part. */ +#define UTS_PROTOCOL_VS (UTS_PROTOCOL_VS_MAJOR | UTS_PROTOCOL_VS_MINOR) +/** The major version part of the protocol version. */ +#define UTS_PROTOCOL_VS_MAJOR (1 << 16) +/** The minor version part of the protocol version. */ +#define UTS_PROTOCOL_VS_MINOR (0) + +/** + * The HOWDY request structure. + */ +typedef struct UTSPKTREQHOWDY +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Version of the protocol the client wants to use. */ + uint32_t uVersion; + /** Mask of USB device connections the client wants to use. */ + uint32_t fUsbConn; + /** The number of characters for the hostname. */ + uint32_t cchHostname; + /** The client host name as terminated ASCII string. */ + char achHostname[68]; +} UTSPKTREQHOWDY; +AssertCompileSizeAlignment(UTSPKTREQHOWDY, UTSPKT_ALIGNMENT); +/** Pointer to a HOWDY request structure. */ +typedef UTSPKTREQHOWDY *PUTSPKTREQHOWDY; + +/** + * The HOWDY reply structure. + */ +typedef struct UTSPKTREPHOWDY +{ + /** Status packet. */ + UTSPKTSTS Sts; + /** Version to use for the established connection. */ + uint32_t uVersion; + /** Mask of supported USB device connections for this connection. */ + uint32_t fUsbConn; + /** Port number the USB/IP server is listening on if + * the client requested USB/IP support and the server can + * deliver it. */ + uint32_t uUsbIpPort; + /** Maximum number of devices supported over USB/IP + * at the same time. */ + uint32_t cUsbIpDevices; + /** Maximum number of physical devices supported for this client + * if a physical connection is present. */ + uint32_t cPhysicalDevices; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREPHOWDY; +AssertCompileSizeAlignment(UTSPKTREPHOWDY, UTSPKT_ALIGNMENT); +/** Pointer to a HOWDY reply structure. */ +typedef UTSPKTREPHOWDY *PUTSPKTREPHOWDY; + +/** Connections over USB/IP are supported. */ +#define UTSPKT_HOWDY_CONN_F_USBIP RT_BIT_32(0) +/** The server has a physical connection available to the client + * which can be used for testing. */ +#define UTSPKT_HOWDY_CONN_F_PHYSICAL RT_BIT_32(1) + + +#define UTSPKT_OPCODE_BYE "BYE " + +/* No additional structures for BYE. */ + +#define UTSPKT_OPCODE_GADGET_CREATE "GDGTCRT " + +/** + * The GADGET CREATE request structure. + */ +typedef struct UTSPKTREQGDGTCTOR +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget type. */ + uint32_t u32GdgtType; + /** Access methods. */ + uint32_t u32GdgtAccess; + /** Number of config items - following this structure. */ + uint32_t u32CfgItems; + /** Reserved. */ + uint32_t u32Rsvd0; +} UTSPKTREQGDGTCTOR; +AssertCompileSizeAlignment(UTSPKTREQGDGTCTOR, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CREATE structure. */ +typedef UTSPKTREQGDGTCTOR *PUTSPKTREQGDGTCTOR; + +/** Gadget type - Test device. */ +#define UTSPKT_GDGT_CREATE_TYPE_TEST UINT32_C(0x1) + +/** Gadget acess method - USB/IP. */ +#define UTSPKT_GDGT_CREATE_ACCESS_USBIP UINT32_C(0x1) + +/** + * Configuration item. + */ +typedef struct UTSPKTREQGDGTCTORCFGITEM +{ + /** Size of the key incuding termination in bytes. */ + uint32_t u32KeySize; + /** Item type. */ + uint32_t u32Type; + /** Size of the value string including termination in bytes. */ + uint32_t u32ValSize; + /** Reserved. */ + uint32_t u32Rsvd0; +} UTSPKTREQGDGTCTORCFGITEM; +AssertCompileSizeAlignment(UTSPKTREQGDGTCTORCFGITEM, UTSPKT_ALIGNMENT); +/** Pointer to a configuration item. */ +typedef UTSPKTREQGDGTCTORCFGITEM *PUTSPKTREQGDGTCTORCFGITEM; + +/** Boolean configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_BOOLEAN UINT32_C(1) +/** String configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_STRING UINT32_C(2) +/** Unsigned 8-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT8 UINT32_C(3) +/** Unsigned 16-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT16 UINT32_C(4) +/** Unsigned 32-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT32 UINT32_C(5) +/** Unsigned 64-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT64 UINT32_C(6) +/** Signed 8-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT8 UINT32_C(7) +/** Signed 16-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT16 UINT32_C(8) +/** Signed 32-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT32 UINT32_C(9) +/** Signed 64-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT64 UINT32_C(10) + +/** + * The GADGET CREATE reply structure. + */ +typedef struct UTSPKTREPGDGTCTOR +{ + /** Status packet. */ + UTSPKTSTS Sts; + /** The gadget ID on success. */ + uint32_t idGadget; + /** Bus ID the gadget is attached to */ + uint32_t u32BusId; + /** Device ID of the gadget on the bus. */ + uint32_t u32DevId; + /** Padding - reserved. */ + uint8_t au8Padding[4]; +} UTSPKTREPGDGTCTOR; +AssertCompileSizeAlignment(UTSPKTREPGDGTCTOR, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CREATE structure. */ +typedef UTSPKTREPGDGTCTOR *PUTSPKTREPGDGTCTOR; + + +#define UTSPKT_OPCODE_GADGET_DESTROY "GDGTDTOR" + +/** + * The GADGET DESTROY request structure. + */ +typedef struct UTSPKTREQGDGTDTOR +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget ID as returned from the GADGET CREATE request on success. */ + uint32_t idGadget; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREQGDGTDTOR; +AssertCompileSizeAlignment(UTSPKTREQGDGTDTOR, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET DESTROY structure. */ +typedef UTSPKTREQGDGTDTOR *PUTSPKTREQGDGTDTOR; + +/* No additional structure for the reply (just standard STATUS packet). */ + +#define UTSPKT_OPCODE_GADGET_CONNECT "GDGTCNCT" + +/** + * The GADGET CONNECT request structure. + */ +typedef struct UTSPKTREQGDGTCNCT +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget ID as returned from the GADGET CREATE request on success. */ + uint32_t idGadget; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREQGDGTCNCT; +AssertCompileSizeAlignment(UTSPKTREQGDGTCNCT, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CONNECT request structure. */ +typedef UTSPKTREQGDGTCNCT *PUTSPKTREQGDGTCNCT; + +/* No additional structure for the reply (just standard STATUS packet). */ + +#define UTSPKT_OPCODE_GADGET_DISCONNECT "GDGTDCNT" + +/** + * The GADGET DISCONNECT request structure. + */ +typedef struct UTSPKTREQGDGTDCNT +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget ID as returned from the GADGET CREATE request on success. */ + uint32_t idGadget; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREQGDGTDCNT; +AssertCompileSizeAlignment(UTSPKTREQGDGTDCNT, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CONNECT request structure. */ +typedef UTSPKTREQGDGTDCNT *PUTSPKTREQGDGTDCNT; + +/* No additional structure for the reply (just standard STATUS packet). */ + +/** + * Checks if the two opcodes match. + * + * @returns true on match, false on mismatch. + * @param pPktHdr The packet header. + * @param pszOpcode2 The opcode we're comparing with. Does not have + * to be the whole 8 chars long. + */ +DECLINLINE(bool) utsIsSameOpcode(PCUTSPKTHDR pPktHdr, const char *pszOpcode2) +{ + if (pPktHdr->achOpcode[0] != pszOpcode2[0]) + return false; + if (pPktHdr->achOpcode[1] != pszOpcode2[1]) + return false; + + unsigned i = 2; + while ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode) + && pszOpcode2[i] != '\0') + { + if (pPktHdr->achOpcode[i] != pszOpcode2[i]) + break; + i++; + } + + if ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode) + && pszOpcode2[i] == '\0') + { + while ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode) + && pPktHdr->achOpcode[i] == ' ') + i++; + } + + return i == RT_SIZEOFMEMB(UTSPKTHDR, achOpcode); +} + +/** + * Converts a UTS request packet from host to network byte ordering. + * + * @returns nothing. + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolReqH2N(PUTSPKTHDR pPktHdr); + +/** + * Converts a UTS request packet from network to host byte ordering. + * + * @returns nothing. + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolReqN2H(PUTSPKTHDR pPktHdr); + +/** + * Converts a UTS reply packet from host to network byte ordering. + * + * @returns nothing. + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolRepH2N(PUTSPKTHDR pPktHdr); + +/** + * Converts a UTS reply packet from network to host byte ordering. + * + * @returns nothing. + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolRepN2H(PUTSPKTHDR pPktHdr); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h */ diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp new file mode 100644 index 00000000..5793c6c2 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp @@ -0,0 +1,502 @@ +/* $Id: UsbTestServiceTcp.cpp $ */ +/** @file + * UsbTestService - Remote USB test configuration and execution server, TCP/IP Transport Layer. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/poll.h> +#include <iprt/string.h> +#include <iprt/tcp.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "UsbTestServiceInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The default server port. */ +#define UTS_TCP_DEF_BIND_PORT 6042 +/** The default server bind address. */ +#define UTS_TCP_DEF_BIND_ADDRESS "" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * TCP specific client data. + */ +typedef struct UTSTRANSPORTCLIENT +{ + /** Socket of the current client. */ + RTSOCKET hTcpClient; + /** The size of the stashed data. */ + size_t cbTcpStashed; + /** The size of the stashed data allocation. */ + size_t cbTcpStashedAlloced; + /** The stashed data. */ + uint8_t *pbTcpStashed; +} UTSTRANSPORTCLIENT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name TCP Parameters + * @{ */ +/** The addresses to bind to. Empty string means any. */ +static char g_szTcpBindAddr[256] = UTS_TCP_DEF_BIND_ADDRESS; +/** The TCP port to listen to. */ +static uint32_t g_uTcpBindPort = UTS_TCP_DEF_BIND_PORT; +/** @} */ + +/** Pointer to the TCP server instance. */ +static PRTTCPSERVER g_pTcpServer = NULL; +#if 0 /* unused */ +/** Stop connecting attempts when set. */ +static bool g_fTcpStopConnecting = false; +#endif + + + +/** + * Disconnects the current client and frees all stashed data. + */ +static void utsTcpDisconnectClient(PUTSTRANSPORTCLIENT pClient) +{ + if (pClient->hTcpClient != NIL_RTSOCKET) + { + int rc = RTTcpServerDisconnectClient2(pClient->hTcpClient); + pClient->hTcpClient = NIL_RTSOCKET; + AssertRCSuccess(rc); + } + + if (pClient->pbTcpStashed) + { + RTMemFree(pClient->pbTcpStashed); + pClient->pbTcpStashed = NULL; + } +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnWaitForConnect} + */ +static DECLCALLBACK(int) utsTcpWaitForConnect(PPUTSTRANSPORTCLIENT ppClientNew) +{ + int rc; + RTSOCKET hClientNew; + + rc = RTTcpServerListen2(g_pTcpServer, &hClientNew); + Log(("utsTcpWaitForConnect: RTTcpServerListen2 -> %Rrc\n", rc)); + + if (RT_SUCCESS(rc)) + { + PUTSTRANSPORTCLIENT pClient = (PUTSTRANSPORTCLIENT)RTMemAllocZ(sizeof(UTSTRANSPORTCLIENT)); + if (RT_LIKELY(pClient)) + { + pClient->hTcpClient = hClientNew; + pClient->cbTcpStashed = 0; + pClient->cbTcpStashedAlloced = 0; + pClient->pbTcpStashed = NULL; + *ppClientNew = pClient; + } + else + { + RTTcpServerDisconnectClient2(hClientNew); + rc = VERR_NO_MEMORY; + } + } + + return rc; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnNotifyReboot} + */ +static DECLCALLBACK(void) utsTcpNotifyReboot(void) +{ + Log(("utsTcpNotifyReboot: RTTcpServerDestroy(%p)\n", g_pTcpServer)); + if (g_pTcpServer) + { + int rc = RTTcpServerDestroy(g_pTcpServer); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDestroy failed in utsTcpNotifyReboot: %Rrc", rc); + g_pTcpServer = NULL; + } +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnNotifyBye} + */ +static DECLCALLBACK(void) utsTcpNotifyBye(PUTSTRANSPORTCLIENT pClient) +{ + Log(("utsTcpNotifyBye: utsTcpDisconnectClient %RTsock\n", pClient->hTcpClient)); + utsTcpDisconnectClient(pClient); + RTMemFree(pClient); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnNotifyHowdy} + */ +static DECLCALLBACK(void) utsTcpNotifyHowdy(PUTSTRANSPORTCLIENT pClient) +{ + /* nothing to do here */ + RT_NOREF1(pClient); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnBabble} + */ +static DECLCALLBACK(void) utsTcpBabble(PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout) +{ + /* + * Try send the babble reply. + */ + NOREF(cMsSendTimeout); /** @todo implement the timeout here; non-blocking write + select-on-write. */ + int rc; + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, UTSPKT_ALIGNMENT); + do rc = RTTcpWrite(pClient->hTcpClient, pPktHdr, cbToSend); + while (rc == VERR_INTERRUPTED); + + /* + * Disconnect the client. + */ + Log(("utsTcpBabble: utsTcpDisconnectClient(%RTsock) (RTTcpWrite rc=%Rrc)\n", pClient->hTcpClient, rc)); + utsTcpDisconnectClient(pClient); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnSendPkt} + */ +static DECLCALLBACK(int) utsTcpSendPkt(PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + Assert(pPktHdr->cb >= sizeof(UTSPKTHDR)); + + /* + * Write it. + */ + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, UTSPKT_ALIGNMENT); + int rc = RTTcpWrite(pClient->hTcpClient, pPktHdr, cbToSend); + if ( RT_FAILURE(rc) + && rc != VERR_INTERRUPTED) + { + /* assume fatal connection error. */ + Log(("RTTcpWrite -> %Rrc -> utsTcpDisconnectClient(%RTsock)\n", rc, pClient->hTcpClient)); + utsTcpDisconnectClient(pClient); + } + + return rc; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnRecvPkt} + */ +static DECLCALLBACK(int) utsTcpRecvPkt(PUTSTRANSPORTCLIENT pClient, PPUTSPKTHDR ppPktHdr) +{ + int rc = VINF_SUCCESS; + *ppPktHdr = NULL; + + /* + * Read state. + */ + size_t offData = 0; + size_t cbData = 0; + size_t cbDataAlloced; + uint8_t *pbData = NULL; + + /* + * Any stashed data? + */ + if (pClient->cbTcpStashedAlloced) + { + offData = pClient->cbTcpStashed; + cbDataAlloced = pClient->cbTcpStashedAlloced; + pbData = pClient->pbTcpStashed; + + pClient->cbTcpStashed = 0; + pClient->cbTcpStashedAlloced = 0; + pClient->pbTcpStashed = NULL; + } + else + { + cbDataAlloced = RT_ALIGN_Z(64, UTSPKT_ALIGNMENT); + pbData = (uint8_t *)RTMemAlloc(cbDataAlloced); + if (!pbData) + return VERR_NO_MEMORY; + } + + /* + * Read and valid the length. + */ + while (offData < sizeof(uint32_t)) + { + size_t cbRead; + rc = RTTcpRead(pClient->hTcpClient, pbData + offData, sizeof(uint32_t) - offData, &cbRead); + if (RT_FAILURE(rc)) + break; + if (cbRead == 0) + { + Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#1)\n", rc)); + rc = VERR_NET_NOT_CONNECTED; + break; + } + offData += cbRead; + } + if (RT_SUCCESS(rc)) + { + ASMCompilerBarrier(); /* paranoia^3 */ + cbData = *(uint32_t volatile *)pbData; + if (cbData >= sizeof(UTSPKTHDR) && cbData <= UTSPKT_MAX_SIZE) + { + /* + * Align the length and reallocate the return packet it necessary. + */ + cbData = RT_ALIGN_Z(cbData, UTSPKT_ALIGNMENT); + if (cbData > cbDataAlloced) + { + void *pvNew = RTMemRealloc(pbData, cbData); + if (pvNew) + { + pbData = (uint8_t *)pvNew; + cbDataAlloced = cbData; + } + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + /* + * Read the remainder of the data. + */ + while (offData < cbData) + { + size_t cbRead; + rc = RTTcpRead(pClient->hTcpClient, pbData + offData, cbData - offData, &cbRead); + if (RT_FAILURE(rc)) + break; + if (cbRead == 0) + { + Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#2)\n", rc)); + rc = VERR_NET_NOT_CONNECTED; + break; + } + offData += cbRead; + } + } + } + else + rc = VERR_NET_PROTOCOL_ERROR; + } + if (RT_SUCCESS(rc)) + *ppPktHdr = (PUTSPKTHDR)pbData; + else + { + /* + * Deal with errors. + */ + if (rc == VERR_INTERRUPTED) + { + /* stash it away for the next call. */ + pClient->cbTcpStashed = cbData; + pClient->cbTcpStashedAlloced = cbDataAlloced; + pClient->pbTcpStashed = pbData; + } + else + { + RTMemFree(pbData); + + /* assume fatal connection error. */ + Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc -> utsTcpDisconnectClient(%RTsock)\n", rc, pClient->hTcpClient)); + utsTcpDisconnectClient(pClient); + } + } + + return rc; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnPollSetAdd} + */ +static DECLCALLBACK(int) utsTcpPollSetAdd(RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart) +{ + return RTPollSetAddSocket(hPollSet, pClient->hTcpClient, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, idStart); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnPollSetRemove} + */ +static DECLCALLBACK(int) utsTcpPollSetRemove(RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart) +{ + RT_NOREF1(pClient); + return RTPollSetRemove(hPollSet, idStart); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnPollIn} + */ +static DECLCALLBACK(bool) utsTcpPollIn(PUTSTRANSPORTCLIENT pClient) +{ + int rc = RTTcpSelectOne(pClient->hTcpClient, 0/*cMillies*/); + return RT_SUCCESS(rc); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnTerm} + */ +static DECLCALLBACK(void) utsTcpTerm(void) +{ + /* Shut down the server (will wake up thread). */ + if (g_pTcpServer) + { + Log(("utsTcpTerm: Destroying server...\n")); + int rc = RTTcpServerDestroy(g_pTcpServer); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDestroy failed in utsTcpTerm: %Rrc", rc); + g_pTcpServer = NULL; + } + + Log(("utsTcpTerm: done\n")); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnInit} + */ +static DECLCALLBACK(int) utsTcpInit(void) +{ + int rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NET_DOWN) + { + RTMsgInfo("RTTcpServerCreateEx(%s, %u,) failed: %Rrc, retrying for 20 seconds...\n", + g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc); + uint64_t StartMs = RTTimeMilliTS(); + do + { + RTThreadSleep(1000); + rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer); + } while ( rc == VERR_NET_DOWN + && RTTimeMilliTS() - StartMs < 20000); + if (RT_SUCCESS(rc)) + RTMsgInfo("RTTcpServerCreateEx succceeded.\n"); + } + if (RT_FAILURE(rc)) + { + g_pTcpServer = NULL; + RTMsgError("RTTcpServerCreateEx(%s, %u,) failed: %Rrc\n", + g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc); + } + } + + return rc; +} + +/** Options */ +enum UTSTCPOPT +{ + UTSTCPOPT_BIND_ADDRESS = 1000, + UTSTCPOPT_BIND_PORT +}; + +/** + * @interface_method_impl{UTSTRANSPORT,pfnOption} + */ +static DECLCALLBACK(int) utsTcpOption(int ch, PCRTGETOPTUNION pVal) +{ + int rc; + + switch (ch) + { + case UTSTCPOPT_BIND_ADDRESS: + rc = RTStrCopy(g_szTcpBindAddr, sizeof(g_szTcpBindAddr), pVal->psz); + if (RT_FAILURE(rc)) + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP bind address is too long (%Rrc)", rc); + return VINF_SUCCESS; + + case UTSTCPOPT_BIND_PORT: + g_uTcpBindPort = pVal->u16 == 0 ? UTS_TCP_DEF_BIND_PORT : pVal->u16; + return VINF_SUCCESS; + } + return VERR_TRY_AGAIN; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnUsage} + */ +DECLCALLBACK(void) utsTcpUsage(PRTSTREAM pStream) +{ + RTStrmPrintf(pStream, + " --tcp-bind-address <address>\n" + " The address(es) to listen to TCP connection on. Empty string\n" + " means any address, this is the default.\n" + " --tcp-bind-port <port>\n" + " The port to listen to TCP connections on.\n" + " Default: %u\n" + , UTS_TCP_DEF_BIND_PORT); +} + +/** Command line options for the TCP/IP transport layer. */ +static const RTGETOPTDEF g_TcpOpts[] = +{ + { "--tcp-bind-address", UTSTCPOPT_BIND_ADDRESS, RTGETOPT_REQ_STRING }, + { "--tcp-bind-port", UTSTCPOPT_BIND_PORT, RTGETOPT_REQ_UINT16 } +}; + +/** TCP/IP transport layer. */ +const UTSTRANSPORT g_TcpTransport = +{ + /* .szName = */ "tcp", + /* .pszDesc = */ "TCP/IP", + /* .cOpts = */ &g_TcpOpts[0], + /* .paOpts = */ RT_ELEMENTS(g_TcpOpts), + /* .pfnUsage = */ utsTcpUsage, + /* .pfnOption = */ utsTcpOption, + /* .pfnInit = */ utsTcpInit, + /* .pfnTerm = */ utsTcpTerm, + /* .pfnWaitForConnect = */ utsTcpWaitForConnect, + /* .pfnPollIn = */ utsTcpPollIn, + /* .pfnPollSetAdd = */ utsTcpPollSetAdd, + /* .pfnPollSetRemove = */ utsTcpPollSetRemove, + /* .pfnRecvPkt = */ utsTcpRecvPkt, + /* .pfnSendPkt = */ utsTcpSendPkt, + /* .pfnBabble = */ utsTcpBabble, + /* .pfnNotifyHowdy = */ utsTcpNotifyHowdy, + /* .pfnNotifyBye = */ utsTcpNotifyBye, + /* .pfnNotifyReboot = */ utsTcpNotifyReboot, + /* .u32EndMarker = */ UINT32_C(0x12345678) +}; |