VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp@ 106378

Last change on this file since 106378 was 106065, checked in by vboxsync, 2 months ago

Manual copyright year updates.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.9 KB
Line 
1/* $Id: USBIdDatabaseGenerator.cpp 106065 2024-09-16 21:42:41Z vboxsync $ */
2/** @file
3 * USB device vendor and product ID database - generator.
4 */
5
6/*
7 * Copyright (C) 2015-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <stdio.h>
33
34#include <algorithm>
35#include <map>
36#include <iprt/sanitized/string>
37#include <vector>
38
39#include <VBox/version.h>
40
41#include <iprt/err.h>
42#include <iprt/initterm.h>
43#include <iprt/message.h>
44#include <iprt/string.h>
45#include <iprt/stream.h>
46
47#include "../include/USBIdDatabase.h"
48
49
50/*
51 * Include the string table generator.
52 */
53#define BLDPROG_STRTAB_MAX_STRLEN (USB_ID_DATABASE_MAX_STRING - 1)
54#ifdef USB_ID_DATABASE_WITH_COMPRESSION
55# define BLDPROG_STRTAB_WITH_COMPRESSION
56#else
57# undef BLDPROG_STRTAB_WITH_COMPRESSION
58#endif
59#define BLDPROG_STRTAB_WITH_CAMEL_WORDS
60#undef BLDPROG_STRTAB_PURE_ASCII
61#include <iprt/bldprog-strtab-template.cpp.h>
62
63
64
65/*********************************************************************************************************************************
66* Global Variables *
67*********************************************************************************************************************************/
68/** For verbose output. */
69static bool g_fVerbose = false;
70
71
72/*********************************************************************************************************************************
73* Defined Constants And Macros *
74*********************************************************************************************************************************/
75// error codes (complements RTEXITCODE_XXX).
76#define ERROR_OPEN_FILE (12)
77#define ERROR_IN_PARSE_LINE (13)
78#define ERROR_DUPLICATE_ENTRY (14)
79#define ERROR_WRONG_FILE_FORMAT (15)
80#define ERROR_TOO_MANY_PRODUCTS (16)
81
82
83/*********************************************************************************************************************************
84* Structures and Typedefs *
85*********************************************************************************************************************************/
86struct VendorRecord
87{
88 size_t vendorID;
89 size_t iProduct;
90 size_t cProducts;
91 std::string str;
92 BLDPROGSTRING StrRef;
93};
94
95struct ProductRecord
96{
97 size_t key;
98 size_t vendorID;
99 size_t productID;
100 std::string str;
101 BLDPROGSTRING StrRef;
102};
103
104typedef std::vector<ProductRecord> ProductsSet;
105typedef std::vector<VendorRecord> VendorsSet;
106
107
108/*********************************************************************************************************************************
109* Global Variables *
110*********************************************************************************************************************************/
111static ProductsSet g_products;
112static VendorsSet g_vendors;
113
114/** The size of all the raw strings, including terminators. */
115static size_t g_cbRawStrings = 0;
116
117
118
119bool operator < (const ProductRecord& lh, const ProductRecord& rh)
120{
121 return lh.key < rh.key;
122}
123
124bool operator < (const VendorRecord& lh, const VendorRecord& rh)
125{
126 return lh.vendorID < rh.vendorID;
127}
128
129bool operator == (const ProductRecord& lh, const ProductRecord& rh)
130{
131 return lh.key == rh.key;
132}
133
134bool operator == (const VendorRecord& lh, const VendorRecord& rh)
135{
136 return lh.vendorID == rh.vendorID;
137}
138
139
140/*
141 * Input file parsing.
142 */
143static int ParseAlias(char *pszLine, size_t& id, std::string& desc)
144{
145 /* First there's a hexadeciman number. */
146 uint32_t uVal;
147 char *pszNext;
148 int vrc = RTStrToUInt32Ex(pszLine, &pszNext, 16, &uVal);
149 if ( vrc == VWRN_TRAILING_CHARS
150 || vrc == VWRN_TRAILING_SPACES
151 || vrc == VINF_SUCCESS)
152 {
153 /* Skip the whipespace following it and at the end of the line. */
154 pszNext = RTStrStripL(pszNext);
155 if (*pszNext != '\0')
156 {
157 vrc = RTStrValidateEncoding(pszNext);
158 if (RT_SUCCESS(vrc))
159 {
160 size_t cchDesc = strlen(pszNext);
161 if (cchDesc <= USB_ID_DATABASE_MAX_STRING)
162 {
163 id = uVal;
164 desc = pszNext;
165 g_cbRawStrings += cchDesc + 1;
166 return RTEXITCODE_SUCCESS;
167 }
168 RTMsgError("String to long: %zu", cchDesc);
169 }
170 else
171 RTMsgError("Invalid encoding: '%s' (vrc=%Rrc)", pszNext, vrc);
172 }
173 else
174 RTMsgError("Error parsing '%s'", pszLine);
175 }
176 else
177 RTMsgError("Error converting number at the start of '%s': %Rrc", pszLine, vrc);
178 return ERROR_IN_PARSE_LINE;
179}
180
181static int ParseUsbIds(PRTSTREAM pInStrm, const char *pszFile)
182{
183 /*
184 * State data.
185 */
186 VendorRecord vendor = { 0, 0, 0, "" };
187
188 /*
189 * Process the file line-by-line.
190 *
191 * The generic format is that we have top level entries (vendors) starting
192 * in position 0 with sub entries starting after one or more, depending on
193 * the level, tab characters.
194 *
195 * Specifically, the list of vendors and their products will always start
196 * with a vendor line followed by indented products. The first character
197 * on the vendor line is a hex digit (four in total) that makes up the
198 * vendor ID. The product lines equally starts with a 4 digit hex ID value.
199 *
200 * Other lists are assumed to have first lines that doesn't start with any
201 * lower case hex digit.
202 */
203 uint32_t iLine = 0;;
204 for (;;)
205 {
206 char szLine[_4K];
207 int vrc = RTStrmGetLine(pInStrm, szLine, sizeof(szLine));
208 if (RT_SUCCESS(vrc))
209 {
210 iLine++;
211
212 /* Check for vendor line. */
213 char chType = szLine[0];
214 if ( RT_C_IS_XDIGIT(chType)
215 && RT_C_IS_SPACE(szLine[4])
216 && RT_C_IS_XDIGIT(szLine[1])
217 && RT_C_IS_XDIGIT(szLine[2])
218 && RT_C_IS_XDIGIT(szLine[3]) )
219 {
220 if (ParseAlias(szLine, vendor.vendorID, vendor.str) == 0)
221 g_vendors.push_back(vendor);
222 else
223 return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE,
224 "%s(%d): Error in parsing vendor line: '%s'", pszFile, iLine, szLine);
225 }
226 /* Check for product line. */
227 else if (szLine[0] == '\t' && vendor.vendorID != 0)
228 {
229 ProductRecord product = { 0, vendor.vendorID, 0, "" };
230 if (ParseAlias(&szLine[1], product.productID, product.str) == 0)
231 {
232 product.key = RT_MAKE_U32(product.productID, product.vendorID);
233 Assert(product.vendorID == vendor.vendorID);
234 g_products.push_back(product);
235 }
236 else
237 return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE, "Error in parsing product line: '%s'", szLine);
238 }
239 /* If not a blank or comment line, it is some other kind of data.
240 So, make sure the vendor ID is cleared so we don't try process
241 the sub-items of in some other list as products. */
242 else if ( chType != '#'
243 && chType != '\0'
244 && *RTStrStripL(szLine) != '\0')
245 vendor.vendorID = 0;
246 }
247 else if (vrc == VERR_EOF)
248 return RTEXITCODE_SUCCESS;
249 else
250 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTStrmGetLine failed: %Rrc", vrc);
251 }
252}
253
254static void WriteSourceFile(FILE *pOut, const char *argv0, PBLDPROGSTRTAB pStrTab)
255{
256 fprintf(pOut,
257 "/** @file\n"
258 " * USB device vendor and product ID database - Autogenerated by %s\n"
259 " */\n"
260 "\n"
261 "/*\n"
262 " * Copyright (C) 2015-" VBOX_C_YEAR " Oracle and/or its affiliates.\n"
263 " *\n"
264 " * This file is part of VirtualBox base platform packages, as\n"
265 " * available from https://www.virtualbox.org.\n"
266 " *\n"
267 " * This program is free software; you can redistribute it and/or\n"
268 " * modify it under the terms of the GNU General Public License\n"
269 " * as published by the Free Software Foundation, in version 3 of the\n"
270 " * License.\n"
271 " *\n"
272 " * This program is distributed in the hope that it will be useful, but\n"
273 " * WITHOUT ANY WARRANTY; without even the implied warranty of\n"
274 " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
275 " * General Public License for more details.\n"
276 " *\n"
277 " * You should have received a copy of the GNU General Public License\n"
278 " * along with this program; if not, see <https://www.gnu.org/licenses>.\n"
279 " *\n"
280 " * SPDX-License-Identifier: GPL-3.0-only\n"
281 " */"
282 "\n"
283 "\n"
284 "#include \"USBIdDatabase.h\"\n"
285 "\n",
286 argv0);
287
288 BldProgStrTab_WriteStringTable(pStrTab, pOut, "", "USBIdDatabase::s_", "StrTab");
289
290 fputs("/**\n"
291 " * USB devices aliases array.\n"
292 " * Format: VendorId, ProductId, Vendor Name, Product Name\n"
293 " * The source of the list is http://www.linux-usb.org/usb.ids\n"
294 " */\n"
295 "USBIDDBPROD const USBIdDatabase::s_aProducts[] =\n"
296 "{\n", pOut);
297 for (ProductsSet::iterator itp = g_products.begin(); itp != g_products.end(); ++itp)
298 fprintf(pOut, " { 0x%04x },\n", (unsigned)itp->productID);
299 fputs("};\n"
300 "\n"
301 "\n"
302 "const RTBLDPROGSTRREF USBIdDatabase::s_aProductNames[] =\n"
303 "{\n", pOut);
304 for (ProductsSet::iterator itp = g_products.begin(); itp != g_products.end(); ++itp)
305 fprintf(pOut, "{ 0x%06x, 0x%02x },\n", itp->StrRef.offStrTab, (unsigned)itp->StrRef.cchString);
306 fputs("};\n"
307 "\n"
308 "const size_t USBIdDatabase::s_cProducts = RT_ELEMENTS(USBIdDatabase::s_aProducts);\n"
309 "\n", pOut);
310
311 fputs("USBIDDBVENDOR const USBIdDatabase::s_aVendors[] =\n"
312 "{\n", pOut);
313 for (VendorsSet::iterator itv = g_vendors.begin(); itv != g_vendors.end(); ++itv)
314 fprintf(pOut, " { 0x%04x, 0x%04x, 0x%04x },\n", (unsigned)itv->vendorID, (unsigned)itv->iProduct, (unsigned)itv->cProducts);
315 fputs("};\n"
316 "\n"
317 "\n"
318 "const RTBLDPROGSTRREF USBIdDatabase::s_aVendorNames[] =\n"
319 "{\n", pOut);
320 for (VendorsSet::iterator itv = g_vendors.begin(); itv != g_vendors.end(); ++itv)
321 fprintf(pOut, "{ 0x%06x, 0x%02x },\n", itv->StrRef.offStrTab, (unsigned)itv->StrRef.cchString);
322 fputs("};\n"
323 "\n"
324 "const size_t USBIdDatabase::s_cVendors = RT_ELEMENTS(USBIdDatabase::s_aVendors);\n"
325 "\n", pOut);
326}
327
328static int usage(FILE *pOut, const char *argv0)
329{
330 fprintf(pOut, "Usage: %s [linux.org usb list file] [custom usb list file] [-o output file]\n", argv0);
331 return RTEXITCODE_SYNTAX;
332}
333
334
335int main(int argc, char *argv[])
336{
337 /*
338 * Initialize IPRT and convert argv to UTF-8.
339 */
340 int vrc = RTR3InitExe(argc, &argv, 0);
341 if (RT_FAILURE(vrc))
342 return RTMsgInitFailure(vrc);
343
344 /*
345 * Parse arguments and read input files.
346 */
347 if (argc < 4)
348 {
349 usage(stderr, argv[0]);
350 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Insufficient arguments.");
351 }
352 g_products.reserve(20000);
353 g_vendors.reserve(3500);
354
355 const char *pszOutFile = NULL;
356 for (int i = 1; i < argc; i++)
357 {
358 if (strcmp(argv[i], "-o") == 0)
359 {
360 pszOutFile = argv[++i];
361 continue;
362 }
363 if ( strcmp(argv[i], "-h") == 0
364 || strcmp(argv[i], "-?") == 0
365 || strcmp(argv[i], "--help") == 0)
366 {
367 usage(stdout, argv[0]);
368 return RTEXITCODE_SUCCESS;
369 }
370
371 PRTSTREAM pInStrm;
372 vrc = RTStrmOpen(argv[i], "r", &pInStrm);
373 if (RT_FAILURE(vrc))
374 return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE,
375 "Failed to open file '%s' for reading: %Rrc", argv[i], vrc);
376
377 vrc = ParseUsbIds(pInStrm, argv[i]);
378 RTStrmClose(pInStrm);
379 if (vrc != 0)
380 {
381 RTMsgError("Failed parsing USB devices file '%s'", argv[i]);
382 return vrc;
383 }
384 }
385
386 /*
387 * Due to USBIDDBVENDOR::iProduct, there is currently a max of 64KB products.
388 * (Not a problem as we've only have less that 54K products currently.)
389 */
390 if (g_products.size() > _64K)
391 return RTMsgErrorExit((RTEXITCODE)ERROR_TOO_MANY_PRODUCTS,
392 "More than 64K products is not supported: %u products", g_products.size());
393
394 /*
395 * Sort the IDs and fill in the iProduct and cProduct members.
396 */
397 sort(g_products.begin(), g_products.end());
398 sort(g_vendors.begin(), g_vendors.end());
399
400 size_t iProduct = 0;
401 for (size_t iVendor = 0; iVendor < g_vendors.size(); iVendor++)
402 {
403 size_t const idVendor = g_vendors[iVendor].vendorID;
404 g_vendors[iVendor].iProduct = iProduct;
405 if ( iProduct < g_products.size()
406 && g_products[iProduct].vendorID <= idVendor)
407 {
408 if (g_products[iProduct].vendorID == idVendor)
409 do
410 iProduct++;
411 while ( iProduct < g_products.size()
412 && g_products[iProduct].vendorID == idVendor);
413 else
414 return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE, "product without vendor after sorting. impossible!");
415 }
416 g_vendors[iVendor].cProducts = iProduct - g_vendors[iVendor].iProduct;
417 }
418
419 /*
420 * Verify that all IDs are unique.
421 */
422 ProductsSet::iterator ita = adjacent_find(g_products.begin(), g_products.end());
423 if (ita != g_products.end())
424 return RTMsgErrorExit((RTEXITCODE)ERROR_DUPLICATE_ENTRY, "Duplicate alias detected: idProduct=%#06x", ita->productID);
425
426 /*
427 * Build the string table.
428 * Do string compression and create the string table.
429 */
430 BLDPROGSTRTAB StrTab;
431 if (!BldProgStrTab_Init(&StrTab, g_products.size() + g_vendors.size()))
432 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory!");
433
434 for (ProductsSet::iterator it = g_products.begin(); it != g_products.end(); ++it)
435 {
436 it->StrRef.pszString = (char *)it->str.c_str();
437 BldProgStrTab_AddString(&StrTab, &it->StrRef);
438 }
439 for (VendorsSet::iterator it = g_vendors.begin(); it != g_vendors.end(); ++it)
440 {
441 it->StrRef.pszString = (char *)it->str.c_str();
442 BldProgStrTab_AddString(&StrTab, &it->StrRef);
443 }
444
445 if (!BldProgStrTab_CompileIt(&StrTab, g_fVerbose))
446 return RTMsgErrorExit(RTEXITCODE_FAILURE, "BldProgStrTab_CompileIt failed!\n");
447
448 /*
449 * Print stats. Making a little extra effort to get it all on one line.
450 */
451 size_t const cbVendorEntry = sizeof(USBIdDatabase::s_aVendors[0]) + sizeof(USBIdDatabase::s_aVendorNames[0]);
452 size_t const cbProductEntry = sizeof(USBIdDatabase::s_aProducts[0]) + sizeof(USBIdDatabase::s_aProductNames[0]);
453
454 size_t cbOldRaw = (g_products.size() + g_vendors.size()) * sizeof(const char *) * 2 + g_cbRawStrings;
455 size_t cbRaw = g_vendors.size() * cbVendorEntry + g_products.size() * cbProductEntry + g_cbRawStrings;
456 size_t cbActual = g_vendors.size() * cbVendorEntry + g_products.size() * cbProductEntry + StrTab.cchStrTab;
457#ifdef USB_ID_DATABASE_WITH_COMPRESSION
458 cbActual += sizeof(StrTab.aCompDict);
459#endif
460
461 char szMsg1[32];
462 RTStrPrintf(szMsg1, sizeof(szMsg1),"Total %zu bytes", cbActual);
463 char szMsg2[64];
464 RTStrPrintf(szMsg2, sizeof(szMsg2)," old version %zu bytes + relocs (%zu%% save)",
465 cbOldRaw, (cbOldRaw - cbActual) * 100 / cbOldRaw);
466 if (cbActual < cbRaw)
467 RTMsgInfo("%s - saving %zu%% (%zu bytes);%s", szMsg1, (cbRaw - cbActual) * 100 / cbRaw, cbRaw - cbActual, szMsg2);
468 else
469 RTMsgInfo("%s - wasting %zu bytes;%s", szMsg1, cbActual - cbRaw, szMsg2);
470
471 /*
472 * Produce the source file.
473 */
474 if (!pszOutFile)
475 return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Output file is not specified.");
476
477 FILE *pOut = fopen(pszOutFile, "w");
478 if (!pOut)
479 return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Error opening '%s' for writing", pszOutFile);
480
481 WriteSourceFile(pOut, argv[0], &StrTab);
482
483 if (ferror(pOut))
484 return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Error writing '%s'!", pszOutFile);
485 if (fclose(pOut) != 0)
486 return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Error closing '%s'!", pszOutFile);
487
488 return RTEXITCODE_SUCCESS;
489}
490
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette