From 2bebebc9e0444ec53afd7f1f37aa80ff6b95f5f7 Mon Sep 17 00:00:00 2001
From: Alexandru Ardelean <ardeleanalex@gmail.com>
Date: Tue, 19 May 2026 17:52:47 +0300
Subject: [PATCH] udev: implement hwdb USB ID lookup from usb.ids (#80)

Replace the four stub udev_hwdb_*() functions with a working
implementation that looks up vendor and product names from a
usb.ids database file (e.g. provided by the hwdata package).

udev_hwdb_get_properties_list_entry() parses the file for USB
modalias strings of the form "usb:vVVVVpPPPP" (product lookup)
or "usb:vVVVV" (vendor lookup), and populates the entry list with
ID_MODEL_FROM_DATABASE or ID_VENDOR_FROM_DATABASE properties.

Lines are read via POSIX getline() so the buffer grows as needed
on long usb.ids entries.

The path to the usb.ids file is set at build time via the
USB_IDS_PATH Makefile variable, which defaults to
${SHAREDIR}/hwdata/usb.ids.
---
 Makefile |   3 ++
 udev.c   | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 144 insertions(+), 7 deletions(-)

--- a/Makefile
+++ b/Makefile
@@ -3,9 +3,12 @@
 
 PREFIX = /usr/local
 LIBDIR = ${PREFIX}/lib
+SHAREDIR = ${PREFIX}/share
 INCLUDEDIR = ${PREFIX}/include
 PKGCONFIGDIR = ${LIBDIR}/pkgconfig
+USB_IDS_PATH = ${SHAREDIR}/hwdata/usb.ids
 XCFLAGS = ${CPPFLAGS} ${CFLAGS} -std=c99 -fPIC -D_XOPEN_SOURCE=700 \
+		  -DUSB_IDS_PATH=\"${USB_IDS_PATH}\" \
 		  -Wall -Wextra -Wpedantic -Wmissing-prototypes -Wstrict-prototypes \
 		  -Wno-unused-parameter
 XLDFLAGS = ${LDFLAGS} -shared -Wl,-soname,libudev.so.1
--- a/udev.c
+++ b/udev.c
@@ -15,9 +15,13 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <stdio.h>
+#include <stdint.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include "udev.h"
+#include "udev_list.h"
 
 struct udev {
     int refcount;
@@ -76,22 +80,152 @@ int udev_get_log_priority(struct udev *u
     return 0;
 }
 
-/* XXX NOT IMPLEMENTED */ struct udev_hwdb *udev_hwdb_new(struct udev *udev)
+struct udev_hwdb {
+    struct udev_list_entry head;
+    int refcount;
+};
+
+static int usb_ids_lookup_vendor(uint16_t vendorid, char *buf, size_t buflen)
 {
-    return NULL;
+    char *line = NULL, *name;
+    size_t cap = 0, len;
+    unsigned int id;
+    FILE *f;
+
+    f = fopen(USB_IDS_PATH, "r");
+    if (!f) {
+        return 0;
+    }
+    while (getline(&line, &cap, f) != -1) {
+        if (line[0] == '#' || line[0] == '\t' || line[0] == '\n') {
+            continue;
+        }
+        if (sscanf(line, "%04x", &id) != 1 || id != vendorid) {
+            continue;
+        }
+        name = strchr(line, ' ');
+        if (name) {
+            while (*name == ' ') {
+                name++;
+            }
+            len = strlen(name);
+            while (len > 0 && (name[len - 1] == '\n' || name[len - 1] == '\r')) {
+                name[--len] = '\0';
+            }
+            snprintf(buf, buflen, "%s", name);
+        }
+        break;
+    }
+    free(line);
+    fclose(f);
+    return buf[0] != '\0';
+}
+
+static int usb_ids_lookup_product(uint16_t vendorid, uint16_t productid, char *buf, size_t buflen)
+{
+    char *line = NULL, *name;
+    size_t cap = 0, len;
+    int in_vendor = 0;
+    unsigned int id;
+    FILE *f;
+
+    f = fopen(USB_IDS_PATH, "r");
+    if (!f) {
+        return 0;
+    }
+    while (getline(&line, &cap, f) != -1) {
+        if (line[0] == '#' || line[0] == '\n') {
+            continue;
+        }
+        if (in_vendor) {
+            if (line[0] != '\t') {
+                break;
+            }
+            if (line[1] == '\t') {
+                continue;
+            }
+            if (sscanf(line + 1, "%04x", &id) != 1 || id != productid) {
+                continue;
+            }
+            name = strchr(line + 1, ' ');
+            if (name) {
+                while (*name == ' ') {
+                    name++;
+                }
+                len = strlen(name);
+                while (len > 0 && (name[len - 1] == '\n' || name[len - 1] == '\r')) {
+                    name[--len] = '\0';
+                }
+                snprintf(buf, buflen, "%s", name);
+            }
+            break;
+        }
+        if (line[0] == '\t') {
+            continue;
+        }
+        if (sscanf(line, "%04x", &id) != 1 || id != vendorid) {
+            continue;
+        }
+        in_vendor = 1;
+    }
+    free(line);
+    fclose(f);
+    return buf[0] != '\0';
 }
 
-/* XXX NOT IMPLEMENTED */ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb)
+struct udev_hwdb *udev_hwdb_new(struct udev *udev)
 {
-    return NULL;
+    struct udev_hwdb *hwdb;
+
+    hwdb = calloc(1, sizeof(*hwdb));
+    if (!hwdb) {
+        return NULL;
+    }
+    hwdb->refcount = 1;
+    return hwdb;
 }
 
-/* XXX NOT IMPLEMENTED */ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb)
+struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb)
 {
-    return NULL;
+    if (!hwdb) {
+        return NULL;
+    }
+    hwdb->refcount++;
+    return hwdb;
 }
 
-/* XXX NOT IMPLEMENTED */ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags)
+struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb)
 {
+    if (!hwdb) {
+        return NULL;
+    }
+    if (--hwdb->refcount > 0) {
+        return NULL;
+    }
+    udev_list_entry_free_all(&hwdb->head);
+    free(hwdb);
     return NULL;
 }
+
+struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags)
+{
+    unsigned int vendorid, productid;
+    char name[64];
+
+    if (!hwdb || !modalias) {
+        return NULL;
+    }
+    udev_list_entry_free_all(&hwdb->head);
+    hwdb->head.next = NULL;
+    name[0] = '\0';
+    if (sscanf(modalias, "usb:v%4xp%4x", &vendorid, &productid) == 2) {
+        if (usb_ids_lookup_product((uint16_t)vendorid, (uint16_t)productid, name, sizeof(name))) {
+            udev_list_entry_add(&hwdb->head, "ID_MODEL_FROM_DATABASE", name, 0);
+        }
+    } else if (sscanf(modalias, "usb:v%4x", &vendorid) == 1) {
+        if (usb_ids_lookup_vendor((uint16_t)vendorid, name, sizeof(name))) {
+            udev_list_entry_add(&hwdb->head, "ID_VENDOR_FROM_DATABASE", name, 0);
+        }
+    }
+    return hwdb->head.next;
+}
