/* $Id: USBIdDatabaseGenerator.cpp 59733 2016-02-19 01:42:18Z vboxsync $ */ /** @file * USB device vendor and product ID database - generator. */ /* * Copyright (C) 2015-2016 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #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 /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** For verbose output. */ static bool g_fVerbose = false; /** Output prefix for informational output. */ #define INFO_PREF "USBIdDatabaseGenerator: Info: " using namespace std; static const char * const header = "/** @file\n" " * USB device vendor and product ID database - Autogenerated from \n" " */\n" "\n" "/*\n" " * Copyright (C) 2015-2016 Oracle Corporation\n" " *\n" " * This file is part of VirtualBox Open Source Edition(OSE), as\n" " * available from http ://www.virtualbox.org. This file is free software;\n" " * you can redistribute it and / or modify it under the terms of the GNU\n" " * General Public License(GPL) as published by the Free Software\n" " * Foundation, in version 2 as it comes in the \"COPYING\" file of the\n" " * VirtualBox OSE distribution.VirtualBox OSE is distributed in the\n" " * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n" " */" "\n" "\n" "#include \"USBIdDatabase.h\"\n" "\n"; static const char * const product_header = "/**\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"; const char * const product_part2 = "};\n" "\n" "\nconst RTBLDPROGSTRREF USBIdDatabase::s_aProductNames[] =\n" "{\n"; const char * const product_footer = "};\n" "\n" "const size_t USBIdDatabase::s_cProducts = RT_ELEMENTS(USBIdDatabase::s_aProducts);\n"; const char * const vendor_header = "\nUSBIDDBVENDOR const USBIdDatabase::s_aVendors[] =\n" "{\n"; const char * const vendor_part2 = "};\n" "\n" "\nconst RTBLDPROGSTRREF USBIdDatabase::s_aVendorNames[] =\n" "{\n"; const char * const vendor_footer = "};\n" "\n" "const size_t USBIdDatabase::s_cVendors = RT_ELEMENTS(USBIdDatabase::s_aVendors);\n"; const char * const start_block = "# Vendors, devices and interfaces. Please keep sorted."; const char * const end_block = "# List of known device classes, subclasses and protocols"; /********************************************************************************************************************************* * 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; }; namespace State { typedef int Value; enum { lookForStartBlock, lookForEndBlock, finished }; } typedef vector ProductsSet; typedef vector VendorsSet; /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ ProductsSet g_products; 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. */ int ParseAlias(const string& src, size_t& id, string& desc) { unsigned int i = 0; if (sscanf(src.c_str(), "%x", &i) != 1) return ERROR_IN_PARSE_LINE; /* skip the number and following whitespace. */ size_t offNext = src.find_first_of(" \t", 1); offNext = src.find_first_not_of(" \t", offNext); if (offNext != string::npos) { size_t cchLength = src.length() - offNext; if (cchLength <= USB_ID_DATABASE_MAX_STRING) { id = i; desc = src.substr(offNext); /* Check the string encoding. */ int rc = RTStrValidateEncoding(desc.c_str()); if (RT_SUCCESS(rc)) { g_cbRawStrings += desc.length() + 1; return RTEXITCODE_SUCCESS; } RTMsgError("Invalid encoding: '%s' (rc=%Rrc)", desc.c_str(), rc); } else RTMsgError("String to long: %zu", cchLength); } else RTMsgError("Error parsing '%s'", src.c_str()); return ERROR_IN_PARSE_LINE; } bool IsCommentOrEmptyLine(const string& str) { size_t index = str.find_first_not_of(" \t");// skip left spaces return index == string::npos || str[index] == '#'; } bool getline(PRTSTREAM instream, string& resString) { const size_t szBuf = 4096; char buf[szBuf] = { 0 }; int rc = RTStrmGetLine(instream, buf, szBuf); if (RT_SUCCESS(rc)) { resString = buf; return true; } if (rc != VERR_EOF) RTMsgWarning("RTStrmGetLine failed: %Rrc", rc); return false; } int ParseUsbIds(PRTSTREAM instream) { State::Value state = State::lookForStartBlock; string line; int res = 0; VendorRecord vendor = { 0, 0, 0, "" }; while (state != State::finished && getline(instream, line)) { switch (state) { case State::lookForStartBlock: { if (line.find(start_block) != string::npos) state = State::lookForEndBlock; break; } case State::lookForEndBlock: { if (line.find(end_block) != string::npos) state = State::finished; else { if (!IsCommentOrEmptyLine(line)) { if (line[0] == '\t') { // Parse Product line // first line should be vendor if (vendor.vendorID == 0) return RTMsgErrorExit((RTEXITCODE)ERROR_WRONG_FILE_FORMAT, "Wrong file format. Product before vendor: '%s'", line.c_str()); ProductRecord product = { 0, vendor.vendorID, 0, "" }; if (ParseAlias(line.substr(1), product.productID, product.str) != 0) return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE, "Error in parsing product line: '", line.c_str()); product.key = RT_MAKE_U32(product.productID, product.vendorID); Assert(product.vendorID == vendor.vendorID); g_products.push_back(product); } else { // Parse vendor line if (ParseAlias(line, vendor.vendorID, vendor.str) != 0) return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE, "Error in parsing vendor line: '", line.c_str()); g_vendors.push_back(vendor); } } } break; } } } if (state == State::lookForStartBlock) return RTMsgErrorExit((RTEXITCODE)ERROR_WRONG_FILE_FORMAT, "wrong format of input file. Start line is not found."); return 0; } 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); 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. */ 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 cout << INFO_PREF "Total " << dec << cbActual << " bytes"; if (cbActual < cbRaw) cout << " saving " << dec << ((cbRaw - cbActual) * 100 / cbRaw) << "% (" << (cbRaw - cbActual) << " bytes)"; else cout << " wasting " << dec << (cbActual - cbRaw) << " bytes"; cout << "; old version " << cbOldRaw << " bytes + relocs (" << ((cbOldRaw - cbActual) * 100 / cbOldRaw) << "% save)." << endl; /* * 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); fputs(header, pOut); BldProgStrTab_WriteStringTable(&StrTab, pOut, "", "USBIdDatabase::s_", "StrTab"); fputs(product_header, pOut); for (ProductsSet::iterator itp = g_products.begin(); itp != g_products.end(); ++itp) fprintf(pOut, " { 0x%04x },\n", itp->productID); fputs(product_part2, pOut); for (ProductsSet::iterator itp = g_products.begin(); itp != g_products.end(); ++itp) fprintf(pOut, "{ 0x%06x, 0x%02x },\n", itp->StrRef.offStrTab, itp->StrRef.cchString); fputs(product_footer, pOut); fputs(vendor_header, pOut); for (VendorsSet::iterator itv = g_vendors.begin(); itv != g_vendors.end(); ++itv) fprintf(pOut, " { 0x%04x, 0x%04x, 0x%04x },\n", itv->vendorID, itv->iProduct, itv->cProducts); fputs(vendor_part2, pOut); for (VendorsSet::iterator itv = g_vendors.begin(); itv != g_vendors.end(); ++itv) fprintf(pOut, "{ 0x%06x, 0x%02x },\n", itv->StrRef.offStrTab, itv->StrRef.cchString); fputs(vendor_footer, pOut); 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; }