SDL: joystick: Split out Linux opening code for reuse by querying code.

From 5ae0dd4b5223a2002950b831caeb652a44340517 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sat, 24 Jul 2021 17:44:35 -0400
Subject: [PATCH] joystick: Split out Linux opening code for reuse by querying
 code.

This prevents an assertion whem LINUX_JoystickGetGamepadMapping tried to
open the stick temporarily and messed with global state by doing so. Now
the global state is only set in LINUX_JoystickOpen, but the common code
is shared by both interfaces.

Fixes #4198.
---
 src/joystick/linux/SDL_sysjoystick.c | 79 +++++++++++++++++++---------
 1 file changed, 53 insertions(+), 26 deletions(-)

diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c
index f0c2d5ce5..3e267e38f 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -965,26 +965,14 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
 }
 
 
-/* Function to open a joystick for use.
-   The joystick to open is specified by the device index.
-   This should fill the nbuttons and naxes fields of the joystick structure.
-   It returns 0, or -1 if there is an error.
- */
+/* This is used to do the heavy lifting for LINUX_JoystickOpen and
+   also LINUX_JoystickGetGamepadMapping, so we can query the hardware
+   without adding an opened SDL_Joystick object to the system.
+   This expects `joystick->hwdata` to be allocated and will not free it
+   on error. Returns -1 on error, 0 on success. */
 static int
-LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index)
+PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item)
 {
-    SDL_joylist_item *item = JoystickByDevIndex(device_index);
-
-    if (item == NULL) {
-        return SDL_SetError("No such device");
-    }
-
-    joystick->instance_id = item->device_instance;
-    joystick->hwdata = (struct joystick_hwdata *)
-        SDL_calloc(1, sizeof(*joystick->hwdata));
-    if (joystick->hwdata == NULL) {
-        return SDL_OutOfMemory();
-    }
     joystick->hwdata->item = item;
     joystick->hwdata->guid = item->guid;
     joystick->hwdata->effect.id = -1;
@@ -997,18 +985,14 @@ LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index)
                                      &joystick->naxes,
                                      &joystick->nhats);
     } else {
-        int fd = open(item->path, O_RDWR, 0);
+        const int fd = open(item->path, O_RDWR, 0);
         if (fd < 0) {
-            SDL_free(joystick->hwdata);
-            joystick->hwdata = NULL;
             return SDL_SetError("Unable to open %s", item->path);
         }
 
         joystick->hwdata->fd = fd;
         joystick->hwdata->fname = SDL_strdup(item->path);
         if (joystick->hwdata->fname == NULL) {
-            SDL_free(joystick->hwdata);
-            joystick->hwdata = NULL;
             close(fd);
             return SDL_OutOfMemory();
         }
@@ -1019,6 +1003,35 @@ LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index)
         /* Get the number of buttons and axes on the joystick */
         ConfigJoystick(joystick, fd);
     }
+}
+
+
+/* Function to open a joystick for use.
+   The joystick to open is specified by the device index.
+   This should fill the nbuttons and naxes fields of the joystick structure.
+   It returns 0, or -1 if there is an error.
+ */
+static int
+LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index)
+{
+    SDL_joylist_item *item = JoystickByDevIndex(device_index);
+
+    if (item == NULL) {
+        return SDL_SetError("No such device");
+    }
+
+    joystick->instance_id = item->device_instance;
+    joystick->hwdata = (struct joystick_hwdata *)
+        SDL_calloc(1, sizeof(*joystick->hwdata));
+    if (joystick->hwdata == NULL) {
+        return SDL_OutOfMemory();
+    }
+
+    if (PrepareJoystickHwdata(joystick, item) == -1) {
+        SDL_free(joystick->hwdata);
+        joystick->hwdata = NULL;
+        return -1;  /* SDL_SetError will already have been called */
+    }
 
     SDL_assert(item->hwdata == NULL);
     item->hwdata = joystick->hwdata;
@@ -1026,7 +1039,7 @@ LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index)
     /* mark joystick as fresh and ready */
     joystick->hwdata->fresh = SDL_TRUE;
 
-    return (0);
+    return 0;
 }
 
 static int
@@ -1417,18 +1430,32 @@ LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
         return SDL_TRUE;
     }
 
+    /* We temporarily open the device to check how it's configured. Make
+       a fake SDL_Joystick object to do so. */
     joystick = (SDL_Joystick *) SDL_calloc(sizeof(*joystick), 1);
     if (joystick == NULL) {
         SDL_OutOfMemory();
         return SDL_FALSE;
     }
 
-    /* We temporarily open the device to check how it's configured. */
-    if (LINUX_JoystickOpen(joystick, device_index) < 0) {
+    joystick->hwdata = (struct joystick_hwdata *)
+        SDL_calloc(1, sizeof(*joystick->hwdata));
+    if (joystick->hwdata == NULL) {
         SDL_free(joystick);
+        SDL_OutOfMemory();
         return SDL_FALSE;
     }
 
+    if (PrepareJoystickHwdata(joystick, item) == -1) {
+        SDL_free(joystick->hwdata);
+        SDL_free(joystick);
+        return SDL_FALSE;  /* SDL_SetError will already have been called */
+    }
+
+    /* don't assign `item->hwdata` so it's not in any global state. */
+
+    /* it is now safe to call LINUX_JoystickClose on this fake joystick. */
+
     if (!joystick->hwdata->has_key[BTN_GAMEPAD]) {
         /* Not a gamepad according to the specs. */
         LINUX_JoystickClose(joystick);