/* $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 . * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 \n" " Where to load the config from\n" " --cdrom \n" " Where the CD/DVD-ROM will be mounted.\n" " Default: %s\n" " --scratch \n" " Where to put scratch files.\n" " Default: %s \n" , pszArgv0, g_szDefCdRomPath, g_szDefScratchPath); RTStrmPrintf(pStrm, " --transport \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; }