summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp')
-rw-r--r--src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp488
1 files changed, 488 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp b/src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp
new file mode 100644
index 00000000..8e4edb0b
--- /dev/null
+++ b/src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp
@@ -0,0 +1,488 @@
+/* $Id: USBIdDatabaseGenerator.cpp $ */
+/** @file
+ * USB device vendor and product ID database - generator.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdio.h>
+
+#include <algorithm>
+#include <map>
+#include <iprt/sanitized/string>
+#include <vector>
+
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+
+#include "../include/USBIdDatabase.h"
+
+
+/*
+ * Include the string table generator.
+ */
+#define BLDPROG_STRTAB_MAX_STRLEN (USB_ID_DATABASE_MAX_STRING - 1)
+#ifdef USB_ID_DATABASE_WITH_COMPRESSION
+# define BLDPROG_STRTAB_WITH_COMPRESSION
+#else
+# undef BLDPROG_STRTAB_WITH_COMPRESSION
+#endif
+#define BLDPROG_STRTAB_WITH_CAMEL_WORDS
+#undef BLDPROG_STRTAB_PURE_ASCII
+#include <iprt/bldprog-strtab-template.cpp.h>
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** For verbose output. */
+static bool g_fVerbose = false;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+// error codes (complements RTEXITCODE_XXX).
+#define ERROR_OPEN_FILE (12)
+#define ERROR_IN_PARSE_LINE (13)
+#define ERROR_DUPLICATE_ENTRY (14)
+#define ERROR_WRONG_FILE_FORMAT (15)
+#define ERROR_TOO_MANY_PRODUCTS (16)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct VendorRecord
+{
+ size_t vendorID;
+ size_t iProduct;
+ size_t cProducts;
+ std::string str;
+ BLDPROGSTRING StrRef;
+};
+
+struct ProductRecord
+{
+ size_t key;
+ size_t vendorID;
+ size_t productID;
+ std::string str;
+ BLDPROGSTRING StrRef;
+};
+
+typedef std::vector<ProductRecord> ProductsSet;
+typedef std::vector<VendorRecord> VendorsSet;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static ProductsSet g_products;
+static VendorsSet g_vendors;
+
+/** The size of all the raw strings, including terminators. */
+static size_t g_cbRawStrings = 0;
+
+
+
+bool operator < (const ProductRecord& lh, const ProductRecord& rh)
+{
+ return lh.key < rh.key;
+}
+
+bool operator < (const VendorRecord& lh, const VendorRecord& rh)
+{
+ return lh.vendorID < rh.vendorID;
+}
+
+bool operator == (const ProductRecord& lh, const ProductRecord& rh)
+{
+ return lh.key == rh.key;
+}
+
+bool operator == (const VendorRecord& lh, const VendorRecord& rh)
+{
+ return lh.vendorID == rh.vendorID;
+}
+
+
+/*
+ * Input file parsing.
+ */
+static int ParseAlias(char *pszLine, size_t& id, std::string& desc)
+{
+ /* First there's a hexadeciman number. */
+ uint32_t uVal;
+ char *pszNext;
+ int rc = RTStrToUInt32Ex(pszLine, &pszNext, 16, &uVal);
+ if ( rc == VWRN_TRAILING_CHARS
+ || rc == VWRN_TRAILING_SPACES
+ || rc == VINF_SUCCESS)
+ {
+ /* Skip the whipespace following it and at the end of the line. */
+ pszNext = RTStrStripL(pszNext);
+ if (*pszNext != '\0')
+ {
+ rc = RTStrValidateEncoding(pszNext);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchDesc = strlen(pszNext);
+ if (cchDesc <= USB_ID_DATABASE_MAX_STRING)
+ {
+ id = uVal;
+ desc = pszNext;
+ g_cbRawStrings += cchDesc + 1;
+ return RTEXITCODE_SUCCESS;
+ }
+ RTMsgError("String to long: %zu", cchDesc);
+ }
+ else
+ RTMsgError("Invalid encoding: '%s' (rc=%Rrc)", pszNext, rc);
+ }
+ else
+ RTMsgError("Error parsing '%s'", pszLine);
+ }
+ else
+ RTMsgError("Error converting number at the start of '%s': %Rrc", pszLine, rc);
+ return ERROR_IN_PARSE_LINE;
+}
+
+static int ParseUsbIds(PRTSTREAM pInStrm, const char *pszFile)
+{
+ /*
+ * State data.
+ */
+ VendorRecord vendor = { 0, 0, 0, "" };
+
+ /*
+ * Process the file line-by-line.
+ *
+ * The generic format is that we have top level entries (vendors) starting
+ * in position 0 with sub entries starting after one or more, depending on
+ * the level, tab characters.
+ *
+ * Specifically, the list of vendors and their products will always start
+ * with a vendor line followed by indented products. The first character
+ * on the vendor line is a hex digit (four in total) that makes up the
+ * vendor ID. The product lines equally starts with a 4 digit hex ID value.
+ *
+ * Other lists are assumed to have first lines that doesn't start with any
+ * lower case hex digit.
+ */
+ uint32_t iLine = 0;;
+ for (;;)
+ {
+ char szLine[_4K];
+ int rc = RTStrmGetLine(pInStrm, szLine, sizeof(szLine));
+ if (RT_SUCCESS(rc))
+ {
+ iLine++;
+
+ /* Check for vendor line. */
+ char chType = szLine[0];
+ if ( RT_C_IS_XDIGIT(chType)
+ && RT_C_IS_SPACE(szLine[4])
+ && RT_C_IS_XDIGIT(szLine[1])
+ && RT_C_IS_XDIGIT(szLine[2])
+ && RT_C_IS_XDIGIT(szLine[3]) )
+ {
+ if (ParseAlias(szLine, vendor.vendorID, vendor.str) == 0)
+ g_vendors.push_back(vendor);
+ else
+ return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE,
+ "%s(%d): Error in parsing vendor line: '%s'", pszFile, iLine, szLine);
+ }
+ /* Check for product line. */
+ else if (szLine[0] == '\t' && vendor.vendorID != 0)
+ {
+ ProductRecord product = { 0, vendor.vendorID, 0, "" };
+ if (ParseAlias(&szLine[1], product.productID, product.str) == 0)
+ {
+ product.key = RT_MAKE_U32(product.productID, product.vendorID);
+ Assert(product.vendorID == vendor.vendorID);
+ g_products.push_back(product);
+ }
+ else
+ return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE, "Error in parsing product line: '%s'", szLine);
+ }
+ /* If not a blank or comment line, it is some other kind of data.
+ So, make sure the vendor ID is cleared so we don't try process
+ the sub-items of in some other list as products. */
+ else if ( chType != '#'
+ && chType != '\0'
+ && *RTStrStripL(szLine) != '\0')
+ vendor.vendorID = 0;
+ }
+ else if (rc == VERR_EOF)
+ return RTEXITCODE_SUCCESS;
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTStrmGetLine failed: %Rrc", rc);
+ }
+}
+
+static void WriteSourceFile(FILE *pOut, const char *argv0, PBLDPROGSTRTAB pStrTab)
+{
+ fprintf(pOut,
+ "/** @file\n"
+ " * USB device vendor and product ID database - Autogenerated by %s\n"
+ " */\n"
+ "\n"
+ "/*\n"
+ " * Copyright (C) 2015-2022 Oracle and/or its affiliates.\n"
+ " *\n"
+ " * This file is part of VirtualBox base platform packages, as\n"
+ " * available from https://www.virtualbox.org.\n"
+ " *\n"
+ " * This program is free software; you can redistribute it and/or\n"
+ " * modify it under the terms of the GNU General Public License\n"
+ " * as published by the Free Software Foundation, in version 3 of the\n"
+ " * License.\n"
+ " *\n"
+ " * This program is distributed in the hope that it will be useful, but\n"
+ " * WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
+ " * General Public License for more details.\n"
+ " *\n"
+ " * You should have received a copy of the GNU General Public License\n"
+ " * along with this program; if not, see <https://www.gnu.org/licenses>.\n"
+ " *\n"
+ " * SPDX-License-Identifier: GPL-3.0-only\n"
+ " */"
+ "\n"
+ "\n"
+ "#include \"USBIdDatabase.h\"\n"
+ "\n",
+ argv0);
+
+ BldProgStrTab_WriteStringTable(pStrTab, pOut, "", "USBIdDatabase::s_", "StrTab");
+
+ fputs("/**\n"
+ " * USB devices aliases array.\n"
+ " * Format: VendorId, ProductId, Vendor Name, Product Name\n"
+ " * The source of the list is http://www.linux-usb.org/usb.ids\n"
+ " */\n"
+ "USBIDDBPROD const USBIdDatabase::s_aProducts[] =\n"
+ "{\n", pOut);
+ for (ProductsSet::iterator itp = g_products.begin(); itp != g_products.end(); ++itp)
+ fprintf(pOut, " { 0x%04x },\n", (unsigned)itp->productID);
+ fputs("};\n"
+ "\n"
+ "\n"
+ "const RTBLDPROGSTRREF USBIdDatabase::s_aProductNames[] =\n"
+ "{\n", pOut);
+ for (ProductsSet::iterator itp = g_products.begin(); itp != g_products.end(); ++itp)
+ fprintf(pOut, "{ 0x%06x, 0x%02x },\n", itp->StrRef.offStrTab, (unsigned)itp->StrRef.cchString);
+ fputs("};\n"
+ "\n"
+ "const size_t USBIdDatabase::s_cProducts = RT_ELEMENTS(USBIdDatabase::s_aProducts);\n"
+ "\n", pOut);
+
+ fputs("USBIDDBVENDOR const USBIdDatabase::s_aVendors[] =\n"
+ "{\n", pOut);
+ for (VendorsSet::iterator itv = g_vendors.begin(); itv != g_vendors.end(); ++itv)
+ fprintf(pOut, " { 0x%04x, 0x%04x, 0x%04x },\n", (unsigned)itv->vendorID, (unsigned)itv->iProduct, (unsigned)itv->cProducts);
+ fputs("};\n"
+ "\n"
+ "\n"
+ "const RTBLDPROGSTRREF USBIdDatabase::s_aVendorNames[] =\n"
+ "{\n", pOut);
+ for (VendorsSet::iterator itv = g_vendors.begin(); itv != g_vendors.end(); ++itv)
+ fprintf(pOut, "{ 0x%06x, 0x%02x },\n", itv->StrRef.offStrTab, (unsigned)itv->StrRef.cchString);
+ fputs("};\n"
+ "\n"
+ "const size_t USBIdDatabase::s_cVendors = RT_ELEMENTS(USBIdDatabase::s_aVendors);\n"
+ "\n", pOut);
+}
+
+static int usage(FILE *pOut, const char *argv0)
+{
+ fprintf(pOut, "Usage: %s [linux.org usb list file] [custom usb list file] [-o output file]\n", argv0);
+ return RTEXITCODE_SYNTAX;
+}
+
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Initialize IPRT and convert argv to UTF-8.
+ */
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse arguments and read input files.
+ */
+ if (argc < 4)
+ {
+ usage(stderr, argv[0]);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Insufficient arguments.");
+ }
+ g_products.reserve(20000);
+ g_vendors.reserve(3500);
+
+ const char *pszOutFile = NULL;
+ for (int i = 1; i < argc; i++)
+ {
+ if (strcmp(argv[i], "-o") == 0)
+ {
+ pszOutFile = argv[++i];
+ continue;
+ }
+ if ( strcmp(argv[i], "-h") == 0
+ || strcmp(argv[i], "-?") == 0
+ || strcmp(argv[i], "--help") == 0)
+ {
+ usage(stdout, argv[0]);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ PRTSTREAM pInStrm;
+ rc = RTStrmOpen(argv[i], "r", &pInStrm);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE,
+ "Failed to open file '%s' for reading: %Rrc", argv[i], rc);
+
+ rc = ParseUsbIds(pInStrm, argv[i]);
+ RTStrmClose(pInStrm);
+ if (rc != 0)
+ {
+ RTMsgError("Failed parsing USB devices file '%s'", argv[i]);
+ return rc;
+ }
+ }
+
+ /*
+ * Due to USBIDDBVENDOR::iProduct, there is currently a max of 64KB products.
+ * (Not a problem as we've only have less that 54K products currently.)
+ */
+ if (g_products.size() > _64K)
+ return RTMsgErrorExit((RTEXITCODE)ERROR_TOO_MANY_PRODUCTS,
+ "More than 64K products is not supported: %u products", g_products.size());
+
+ /*
+ * Sort the IDs and fill in the iProduct and cProduct members.
+ */
+ sort(g_products.begin(), g_products.end());
+ sort(g_vendors.begin(), g_vendors.end());
+
+ size_t iProduct = 0;
+ for (size_t iVendor = 0; iVendor < g_vendors.size(); iVendor++)
+ {
+ size_t const idVendor = g_vendors[iVendor].vendorID;
+ g_vendors[iVendor].iProduct = iProduct;
+ if ( iProduct < g_products.size()
+ && g_products[iProduct].vendorID <= idVendor)
+ {
+ if (g_products[iProduct].vendorID == idVendor)
+ do
+ iProduct++;
+ while ( iProduct < g_products.size()
+ && g_products[iProduct].vendorID == idVendor);
+ else
+ return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE, "product without vendor after sorting. impossible!");
+ }
+ g_vendors[iVendor].cProducts = iProduct - g_vendors[iVendor].iProduct;
+ }
+
+ /*
+ * Verify that all IDs are unique.
+ */
+ ProductsSet::iterator ita = adjacent_find(g_products.begin(), g_products.end());
+ if (ita != g_products.end())
+ return RTMsgErrorExit((RTEXITCODE)ERROR_DUPLICATE_ENTRY, "Duplicate alias detected: idProduct=%#06x", ita->productID);
+
+ /*
+ * Build the string table.
+ * Do string compression and create the string table.
+ */
+ BLDPROGSTRTAB StrTab;
+ if (!BldProgStrTab_Init(&StrTab, g_products.size() + g_vendors.size()))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory!");
+
+ for (ProductsSet::iterator it = g_products.begin(); it != g_products.end(); ++it)
+ {
+ it->StrRef.pszString = (char *)it->str.c_str();
+ BldProgStrTab_AddString(&StrTab, &it->StrRef);
+ }
+ for (VendorsSet::iterator it = g_vendors.begin(); it != g_vendors.end(); ++it)
+ {
+ it->StrRef.pszString = (char *)it->str.c_str();
+ BldProgStrTab_AddString(&StrTab, &it->StrRef);
+ }
+
+ if (!BldProgStrTab_CompileIt(&StrTab, g_fVerbose))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "BldProgStrTab_CompileIt failed!\n");
+
+ /*
+ * Print stats. Making a little extra effort to get it all on one line.
+ */
+ size_t const cbVendorEntry = sizeof(USBIdDatabase::s_aVendors[0]) + sizeof(USBIdDatabase::s_aVendorNames[0]);
+ size_t const cbProductEntry = sizeof(USBIdDatabase::s_aProducts[0]) + sizeof(USBIdDatabase::s_aProductNames[0]);
+
+ size_t cbOldRaw = (g_products.size() + g_vendors.size()) * sizeof(const char *) * 2 + g_cbRawStrings;
+ size_t cbRaw = g_vendors.size() * cbVendorEntry + g_products.size() * cbProductEntry + g_cbRawStrings;
+ size_t cbActual = g_vendors.size() * cbVendorEntry + g_products.size() * cbProductEntry + StrTab.cchStrTab;
+#ifdef USB_ID_DATABASE_WITH_COMPRESSION
+ cbActual += sizeof(StrTab.aCompDict);
+#endif
+
+ char szMsg1[32];
+ RTStrPrintf(szMsg1, sizeof(szMsg1),"Total %zu bytes", cbActual);
+ char szMsg2[64];
+ RTStrPrintf(szMsg2, sizeof(szMsg2)," old version %zu bytes + relocs (%zu%% save)",
+ cbOldRaw, (cbOldRaw - cbActual) * 100 / cbOldRaw);
+ if (cbActual < cbRaw)
+ RTMsgInfo("%s - saving %zu%% (%zu bytes);%s", szMsg1, (cbRaw - cbActual) * 100 / cbRaw, cbRaw - cbActual, szMsg2);
+ else
+ RTMsgInfo("%s - wasting %zu bytes;%s", szMsg1, cbActual - cbRaw, szMsg2);
+
+ /*
+ * Produce the source file.
+ */
+ if (!pszOutFile)
+ return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Output file is not specified.");
+
+ FILE *pOut = fopen(pszOutFile, "w");
+ if (!pOut)
+ return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Error opening '%s' for writing", pszOutFile);
+
+ WriteSourceFile(pOut, argv[0], &StrTab);
+
+ if (ferror(pOut))
+ return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Error writing '%s'!", pszOutFile);
+ if (fclose(pOut) != 0)
+ return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Error closing '%s'!", pszOutFile);
+
+ return RTEXITCODE_SUCCESS;
+}
+