SDL: udev: Fix O(n^2) device walking issue (closes #9092) (53ade)

From 53ade19430632612d7b17e3c9fe457b5cb3473bb Mon Sep 17 00:00:00 2001
From: Tyson Whitehead <[EMAIL REDACTED]>
Date: Sat, 23 Mar 2024 14:10:28 -0400
Subject: [PATCH] udev: Fix O(n^2) device walking issue (closes #9092)

I believe there was a O(n^2) device walking issues on startup

- MaybeAddDevice gets called for every device at startup
- MaybeAddDevice calls IsJoystick
- IsJoystick calls SDL_UDEV_GetProductInfo
- SDL_UDEV_GetProductInfo calls udev_enumerate_scan_devices
- udev_enumerate_scan_devices walks all the devices

Prior to commit 3b1e0e1 this was mostly masked as IsJoystick only
called SDL_UDEV_GetProductInfo when a JSIOCGNAME ioctl was
successful. This fixes the O(n^2) behaviour by directly getting
the device via udev_device_new_from_devnum (based on type, major,
and minor number) instead of enumerating everything via
udev_enumerate_scan_devices and matching on name.
---
 src/core/linux/SDL_udev.c | 79 +++++++++++++++++++--------------------
 1 file changed, 39 insertions(+), 40 deletions(-)

diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c
index b8205027d2790..fb99213825672 100644
--- a/src/core/linux/SDL_udev.c
+++ b/src/core/linux/SDL_udev.c
@@ -31,6 +31,7 @@
 #ifdef SDL_USE_LIBUDEV
 
 #include <linux/input.h>
+#include <sys/stat.h>
 
 #include "SDL_evdev_capabilities.h"
 #include "../unix/SDL_poll.h"
@@ -223,61 +224,59 @@ int SDL_UDEV_Scan(void)
 
 SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class)
 {
-    struct udev_enumerate *enumerate = NULL;
-    struct udev_list_entry *devs = NULL;
-    struct udev_list_entry *item = NULL;
-    SDL_bool found = SDL_FALSE;
+    struct stat statbuf;
+    char type;
+    struct udev_device *dev;
+    const char* val;
+    int class_temp;
 
     if (!_this) {
         return SDL_FALSE;
     }
 
-    enumerate = _this->syms.udev_enumerate_new(_this->udev);
-    if (!enumerate) {
-        SDL_SetError("udev_enumerate_new() failed");
+    if (stat(device_path, &statbuf) == -1) {
         return SDL_FALSE;
     }
 
-    _this->syms.udev_enumerate_scan_devices(enumerate);
-    devs = _this->syms.udev_enumerate_get_list_entry(enumerate);
-    for (item = devs; item && !found; item = _this->syms.udev_list_entry_get_next(item)) {
-        const char *path = _this->syms.udev_list_entry_get_name(item);
-        struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path);
-        if (dev) {
-            const char *val = NULL;
-            const char *existing_path;
+    if (S_ISBLK(statbuf.st_mode)) {
+        type = 'b';
+    }
+    else if (S_ISCHR(statbuf.st_mode)) {
+        type = 'c';
+    }
+    else {
+        return SDL_FALSE;
+    }
 
-            existing_path = _this->syms.udev_device_get_devnode(dev);
-            if (existing_path && SDL_strcmp(device_path, existing_path) == 0) {
-                int class_temp;
-                found = SDL_TRUE;
+    dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev);
 
-                val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");
-                if (val) {
-                    *vendor = (Uint16)SDL_strtol(val, NULL, 16);
-                }
+    if (!dev) {
+        return SDL_FALSE;
+    }
 
-                val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID");
-                if (val) {
-                    *product = (Uint16)SDL_strtol(val, NULL, 16);
-                }
+    val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");
+    if (val) {
+        *vendor = (Uint16)SDL_strtol(val, NULL, 16);
+    }
 
-                val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION");
-                if (val) {
-                    *version = (Uint16)SDL_strtol(val, NULL, 16);
-                }
+    val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID");
+    if (val) {
+        *product = (Uint16)SDL_strtol(val, NULL, 16);
+    }
 
-                class_temp = device_class(dev);
-                if (class_temp) {
-                    *class = class_temp;
-                }
-            }
-            _this->syms.udev_device_unref(dev);
-        }
+    val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION");
+    if (val) {
+        *version = (Uint16)SDL_strtol(val, NULL, 16);
     }
-    _this->syms.udev_enumerate_unref(enumerate);
 
-    return found;
+    class_temp = device_class(dev);
+    if (class_temp) {
+        *class = class_temp;
+    }
+
+    _this->syms.udev_device_unref(dev);
+
+    return SDL_TRUE;
 }
 
 void SDL_UDEV_UnloadLibrary(void)