summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/ValidationKit/utils/usb/UsbTestService.cpp')
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestService.cpp1658
1 files changed, 1658 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp
new file mode 100644
index 00000000..0912ff51
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp
@@ -0,0 +1,1658 @@
+/* $Id: UsbTestService.cpp $ */
+/** @file
+ * UsbTestService - Remote USB test configuration and execution server.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* 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.
+ *
+ * @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 *)RTMemReallocZ(papClients, cClientsMax * sizeof(PUTSCLIENT), (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: 157380 $\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;
+}
+