SDL: Re-added SDL_getenv() as a thread-safe getenv() implementation

From 28b94c4758563eee6a7193d22e0380cc85f7b5bf Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 15 Sep 2024 09:16:30 -0700
Subject: [PATCH] Re-added SDL_getenv() as a thread-safe getenv()
 implementation

---
 docs/README-migration.md                      |   2 +
 include/SDL3/SDL_stdinc.h                     |  21 +-
 src/SDL_hints.c                               |   8 +-
 src/audio/SDL_audiodev.c                      |   2 +-
 src/core/linux/SDL_ibus.c                     |  10 +-
 src/core/linux/SDL_ime.c                      |   4 +-
 src/core/linux/SDL_sandbox.c                  |   6 +-
 src/dynapi/SDL_dynapi.sym                     |   3 +-
 src/dynapi/SDL_dynapi_overrides.h             |   3 +-
 src/dynapi/SDL_dynapi_procs.h                 |   3 +-
 src/filesystem/cocoa/SDL_sysfilesystem.m      |   2 +-
 src/filesystem/emscripten/SDL_sysfilesystem.c |   2 +-
 src/filesystem/haiku/SDL_sysfilesystem.cc     |   4 +-
 src/filesystem/unix/SDL_sysfilesystem.c       |  16 +-
 src/haptic/SDL_haptic.c                       |   2 +-
 src/locale/unix/SDL_syslocale.c               |   4 +-
 src/stdlib/SDL_getenv.c                       | 554 +++++++++---------
 src/stdlib/SDL_getenv_c.h                     |  23 -
 src/stdlib/SDL_iconv.c                        |   8 +-
 src/video/SDL_egl.c                           |   4 +-
 src/video/wayland/SDL_waylandevents.c         |   6 +-
 src/video/wayland/SDL_waylandmessagebox.c     |   4 +-
 src/video/wayland/SDL_waylandmouse.c          |   4 +-
 src/video/wayland/SDL_waylandshmbuffer.c      |   2 +-
 src/video/wayland/SDL_waylandvideo.c          |   4 +-
 src/video/wayland/SDL_waylandwindow.c         |   2 +-
 src/video/x11/SDL_x11keyboard.c               |   2 +-
 src/video/x11/SDL_x11modes.c                  |   2 +-
 28 files changed, 361 insertions(+), 346 deletions(-)
 delete mode 100644 src/stdlib/SDL_getenv_c.h

diff --git a/docs/README-migration.md b/docs/README-migration.md
index 171823a014ccc..daa521af42a13 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -1743,6 +1743,8 @@ Please note that the case-folding technique used by SDL3 will not produce correc
 
 SDL_strtoll(), SDL_strtoull(), SDL_lltoa(), and SDL_ulltoa() use long long values instead of 64-bit values, to match their C runtime counterparts.
 
+SDL_setenv() is not thread-safe and has been renamed SDL_setenv_unsafe().
+
 The following macros have been removed:
 * SDL_TABLESIZE() - use SDL_arraysize() instead
 
diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h
index e7ca1cc7cda36..924c5399b47e5 100644
--- a/include/SDL3/SDL_stdinc.h
+++ b/include/SDL3/SDL_stdinc.h
@@ -1156,16 +1156,33 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyEnvironment(SDL_Environment *env);
 /**
  * Get the value of a variable in the environment.
  *
+ * This function uses SDL's cached copy of the environment and is thread-safe.
+ *
+ * \param name the name of the variable to get.
+ * \returns a pointer to the value of the variable or NULL if it can't be
+ *          found.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_getenv(const char *name);
+
+/**
+ * Get the value of a variable in the environment.
+ *
+ * This function bypasses SDL's cached copy of the environment and is not thread-safe.
+ *
  * \param name the name of the variable to get.
  * \returns a pointer to the value of the variable or NULL if it can't be
  *          found.
  *
  * \threadsafety This function is not thread safe, consider using
- *               SDL_GetEnvironmentVariable() instead.
+ *               SDL_getenv() instead.
  *
  * \since This function is available since SDL 3.0.0.
  *
- * \sa SDL_GetEnvironmentVariable
+ * \sa SDL_getenv
  */
 extern SDL_DECLSPEC const char * SDLCALL SDL_getenv_unsafe(const char *name);
 
diff --git a/src/SDL_hints.c b/src/SDL_hints.c
index a91877bbaad38..8dd9c551ccb09 100644
--- a/src/SDL_hints.c
+++ b/src/SDL_hints.c
@@ -72,7 +72,7 @@ SDL_bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPr
         return SDL_InvalidParamError("name");
     }
 
-    const char *env = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), name);
+    const char *env = SDL_getenv(name);
     if (env && (priority < SDL_HINT_OVERRIDE)) {
         return SDL_SetError("An environment variable is taking priority");
     }
@@ -126,7 +126,7 @@ SDL_bool SDL_ResetHint(const char *name)
         return SDL_InvalidParamError("name");
     }
 
-    const char *env = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), name);
+    const char *env = SDL_getenv(name);
 
     const SDL_PropertiesID hints = GetHintProperties(false);
     if (!hints) {
@@ -165,7 +165,7 @@ static void SDLCALL ResetHintsCallback(void *userdata, SDL_PropertiesID hints, c
         return;  // uh...okay.
     }
 
-    const char *env = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), name);
+    const char *env = SDL_getenv(name);
     if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
         SDL_HintWatch *entry = hint->callbacks;
         while (entry) {
@@ -196,7 +196,7 @@ const char *SDL_GetHint(const char *name)
         return NULL;
     }
 
-    const char *result = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), name);
+    const char *result = SDL_getenv(name);
 
     const SDL_PropertiesID hints = GetHintProperties(false);
     if (hints) {
diff --git a/src/audio/SDL_audiodev.c b/src/audio/SDL_audiodev.c
index f5dc4b20e95f3..f48a5b25d0bb5 100644
--- a/src/audio/SDL_audiodev.c
+++ b/src/audio/SDL_audiodev.c
@@ -87,7 +87,7 @@ static void SDL_EnumUnixAudioDevices_Internal(const bool recording, const bool c
     }
 
     // Figure out what our audio device is
-    audiodev = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "AUDIODEV");
+    audiodev = SDL_getenv("AUDIODEV");
     if (!audiodev) {
         if (classic) {
             audiodev = SDL_PATH_DEV_AUDIO;
diff --git a/src/core/linux/SDL_ibus.c b/src/core/linux/SDL_ibus.c
index 62daaf3a5fb0f..dbbc3e1740e41 100644
--- a/src/core/linux/SDL_ibus.c
+++ b/src/core/linux/SDL_ibus.c
@@ -331,14 +331,14 @@ static char *IBus_GetDBusAddressFilename(void)
     }
 
     // Use this environment variable if it exists.
-    addr = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "IBUS_ADDRESS");
+    addr = SDL_getenv("IBUS_ADDRESS");
     if (addr && *addr) {
         return SDL_strdup(addr);
     }
 
     /* Otherwise, we have to get the hostname, display, machine id, config dir
        and look up the address from a filepath using all those bits, eek. */
-    disp_env = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "DISPLAY");
+    disp_env = SDL_getenv("DISPLAY");
 
     if (!disp_env || !*disp_env) {
         display = SDL_strdup(":0.0");
@@ -363,7 +363,7 @@ static char *IBus_GetDBusAddressFilename(void)
     }
 
     if (!*host) {
-        const char *session = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "XDG_SESSION_TYPE");
+        const char *session = SDL_getenv("XDG_SESSION_TYPE");
         if (session && SDL_strcmp(session, "wayland") == 0) {
             host = "unix-wayland";
         } else {
@@ -373,11 +373,11 @@ static char *IBus_GetDBusAddressFilename(void)
 
     SDL_memset(config_dir, 0, sizeof(config_dir));
 
-    conf_env = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "XDG_CONFIG_HOME");
+    conf_env = SDL_getenv("XDG_CONFIG_HOME");
     if (conf_env && *conf_env) {
         SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
     } else {
-        const char *home_env = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "HOME");
+        const char *home_env = SDL_getenv("HOME");
         if (!home_env || !*home_env) {
             SDL_free(display);
             return NULL;
diff --git a/src/core/linux/SDL_ime.c b/src/core/linux/SDL_ime.c
index a371e658e1ca6..a853618bb41c7 100644
--- a/src/core/linux/SDL_ime.c
+++ b/src/core/linux/SDL_ime.c
@@ -44,8 +44,8 @@ static void InitIME(void)
 {
     static bool inited = false;
 #ifdef HAVE_FCITX
-    const char *im_module = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "SDL_IM_MODULE");
-    const char *xmodifiers = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "XMODIFIERS");
+    const char *im_module = SDL_getenv("SDL_IM_MODULE");
+    const char *xmodifiers = SDL_getenv("XMODIFIERS");
 #endif
 
     if (inited == true) {
diff --git a/src/core/linux/SDL_sandbox.c b/src/core/linux/SDL_sandbox.c
index 797d36c1ef95e..5a8df84871459 100644
--- a/src/core/linux/SDL_sandbox.c
+++ b/src/core/linux/SDL_sandbox.c
@@ -33,9 +33,9 @@ SDL_Sandbox SDL_DetectSandbox(void)
 
     /* For Snap, we check multiple variables because they might be set for
      * unrelated reasons. This is the same thing WebKitGTK does. */
-    if (SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "SNAP") != NULL &&
-        SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "SNAP_NAME") != NULL &&
-        SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "SNAP_REVISION") != NULL) {
+    if (SDL_getenv("SNAP") != NULL &&
+        SDL_getenv("SNAP_NAME") != NULL &&
+        SDL_getenv("SNAP_REVISION") != NULL) {
         return SDL_SANDBOX_SNAP;
     }
 
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 6b382567539eb..01f6f506d0602 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -239,7 +239,6 @@ SDL3_0.0.0 {
     SDL_GetBasePath;
     SDL_GetBooleanProperty;
     SDL_GetCPUCacheLineSize;
-    SDL_GetNumLogicalCPUCores;
     SDL_GetCameraDriver;
     SDL_GetCameraFormat;
     SDL_GetCameraID;
@@ -414,6 +413,7 @@ SDL3_0.0.0 {
     SDL_GetNumJoystickBalls;
     SDL_GetNumJoystickButtons;
     SDL_GetNumJoystickHats;
+    SDL_GetNumLogicalCPUCores;
     SDL_GetNumRenderDrivers;
     SDL_GetNumVideoDrivers;
     SDL_GetNumberProperty;
@@ -1029,6 +1029,7 @@ SDL3_0.0.0 {
     SDL_fmod;
     SDL_fmodf;
     SDL_free;
+    SDL_getenv;
     SDL_getenv_unsafe;
     SDL_hid_ble_scan;
     SDL_hid_close;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 3221271800163..e0d1be09216d5 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -264,7 +264,6 @@
 #define SDL_GetBasePath SDL_GetBasePath_REAL
 #define SDL_GetBooleanProperty SDL_GetBooleanProperty_REAL
 #define SDL_GetCPUCacheLineSize SDL_GetCPUCacheLineSize_REAL
-#define SDL_GetNumLogicalCPUCores SDL_GetNumLogicalCPUCores_REAL
 #define SDL_GetCameraDriver SDL_GetCameraDriver_REAL
 #define SDL_GetCameraFormat SDL_GetCameraFormat_REAL
 #define SDL_GetCameraID SDL_GetCameraID_REAL
@@ -439,6 +438,7 @@
 #define SDL_GetNumJoystickBalls SDL_GetNumJoystickBalls_REAL
 #define SDL_GetNumJoystickButtons SDL_GetNumJoystickButtons_REAL
 #define SDL_GetNumJoystickHats SDL_GetNumJoystickHats_REAL
+#define SDL_GetNumLogicalCPUCores SDL_GetNumLogicalCPUCores_REAL
 #define SDL_GetNumRenderDrivers SDL_GetNumRenderDrivers_REAL
 #define SDL_GetNumVideoDrivers SDL_GetNumVideoDrivers_REAL
 #define SDL_GetNumberProperty SDL_GetNumberProperty_REAL
@@ -1054,6 +1054,7 @@
 #define SDL_fmod SDL_fmod_REAL
 #define SDL_fmodf SDL_fmodf_REAL
 #define SDL_free SDL_free_REAL
+#define SDL_getenv SDL_getenv_REAL
 #define SDL_getenv_unsafe SDL_getenv_unsafe_REAL
 #define SDL_hid_ble_scan SDL_hid_ble_scan_REAL
 #define SDL_hid_close SDL_hid_close_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index ab406b4f587ee..8c3c10a05a0bf 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -284,7 +284,6 @@ SDL_DYNAPI_PROC(int,SDL_GetAudioStreamQueued,(SDL_AudioStream *a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetBasePath,(void),(),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_GetBooleanProperty,(SDL_PropertiesID a, const char *b, SDL_bool c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_GetCPUCacheLineSize,(void),(),return)
-SDL_DYNAPI_PROC(int,SDL_GetNumLogicalCPUCores,(void),(),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetCameraDriver,(int a),(a),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_GetCameraFormat,(SDL_Camera *a, SDL_CameraSpec *b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_CameraID,SDL_GetCameraID,(SDL_Camera *a),(a),return)
@@ -459,6 +458,7 @@ SDL_DYNAPI_PROC(int,SDL_GetNumJoystickAxes,(SDL_Joystick *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetNumJoystickBalls,(SDL_Joystick *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetNumJoystickButtons,(SDL_Joystick *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetNumJoystickHats,(SDL_Joystick *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_GetNumLogicalCPUCores,(void),(),return)
 SDL_DYNAPI_PROC(int,SDL_GetNumRenderDrivers,(void),(),return)
 SDL_DYNAPI_PROC(int,SDL_GetNumVideoDrivers,(void),(),return)
 SDL_DYNAPI_PROC(Sint64,SDL_GetNumberProperty,(SDL_PropertiesID a, const char *b, Sint64 c),(a,b,c),return)
@@ -1063,6 +1063,7 @@ SDL_DYNAPI_PROC(float,SDL_floorf,(float a),(a),return)
 SDL_DYNAPI_PROC(double,SDL_fmod,(double a, double b),(a,b),return)
 SDL_DYNAPI_PROC(float,SDL_fmodf,(float a, float b),(a,b),return)
 SDL_DYNAPI_PROC(void,SDL_free,(void *a),(a),)
+SDL_DYNAPI_PROC(const char*,SDL_getenv,(const char *a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_getenv_unsafe,(const char *a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_hid_ble_scan,(SDL_bool a),(a),)
 SDL_DYNAPI_PROC(int,SDL_hid_close,(SDL_hid_device *a),(a),return)
diff --git a/src/filesystem/cocoa/SDL_sysfilesystem.m b/src/filesystem/cocoa/SDL_sysfilesystem.m
index c88ad1c1ade7d..07a75ff74e4e6 100644
--- a/src/filesystem/cocoa/SDL_sysfilesystem.m
+++ b/src/filesystem/cocoa/SDL_sysfilesystem.m
@@ -144,7 +144,7 @@
 
         switch (folder) {
         case SDL_FOLDER_HOME:
-            base = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "HOME");
+            base = SDL_getenv("HOME");
 
             if (!base) {
                 SDL_SetError("No $HOME environment variable available");
diff --git a/src/filesystem/emscripten/SDL_sysfilesystem.c b/src/filesystem/emscripten/SDL_sysfilesystem.c
index 8e228203c4c88..e10081c775193 100644
--- a/src/filesystem/emscripten/SDL_sysfilesystem.c
+++ b/src/filesystem/emscripten/SDL_sysfilesystem.c
@@ -93,7 +93,7 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder)
         return NULL;
     }
 
-    home = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "HOME");
+    home = SDL_getenv("HOME");
     if (!home) {
         SDL_SetError("No $HOME environment variable available");
         return NULL;
diff --git a/src/filesystem/haiku/SDL_sysfilesystem.cc b/src/filesystem/haiku/SDL_sysfilesystem.cc
index e8f70715591e1..60e7d5b09de5e 100644
--- a/src/filesystem/haiku/SDL_sysfilesystem.cc
+++ b/src/filesystem/haiku/SDL_sysfilesystem.cc
@@ -68,7 +68,7 @@ char *SDL_SYS_GetBasePath(void)
 char *SDL_SYS_GetPrefPath(const char *org, const char *app)
 {
     // !!! FIXME: is there a better way to do this?
-    const char *home = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "HOME");
+    const char *home = SDL_getenv("HOME");
     const char *append = "/config/settings/";
     size_t len = SDL_strlen(home);
 
@@ -102,7 +102,7 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder)
     const char *home = NULL;
     char *result;
 
-    home = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "HOME");
+    home = SDL_getenv("HOME");
     if (!home) {
         SDL_SetError("No $HOME environment variable available");
         return NULL;
diff --git a/src/filesystem/unix/SDL_sysfilesystem.c b/src/filesystem/unix/SDL_sysfilesystem.c
index 80938c47c5f07..6f46a5b3fdd52 100644
--- a/src/filesystem/unix/SDL_sysfilesystem.c
+++ b/src/filesystem/unix/SDL_sysfilesystem.c
@@ -74,7 +74,7 @@ static char *readSymLink(const char *path)
 #ifdef SDL_PLATFORM_OPENBSD
 static char *search_path_for_binary(const char *bin)
 {
-    const char *envr_real = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "PATH");
+    const char *envr_real = SDL_getenv("PATH");
     char *envr;
     size_t alloc_size;
     char *exe = NULL;
@@ -163,7 +163,7 @@ char *SDL_SYS_GetBasePath(void)
             exe = search_path_for_binary(cmdline[0]);
         } else {
             if (exe && *exe == '.') {
-                const char *pwd = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "PWD");
+                const char *pwd = SDL_getenv("PWD");
                 if (pwd && *pwd) {
                     SDL_asprintf(&pwddst, "%s/%s", pwd, exe);
                 }
@@ -265,7 +265,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app)
      *
      * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
      */
-    const char *envr = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "XDG_DATA_HOME");
+    const char *envr = SDL_getenv("XDG_DATA_HOME");
     const char *append;
     char *result = NULL;
     char *ptr = NULL;
@@ -281,7 +281,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app)
 
     if (!envr) {
         // You end up with "$HOME/.local/share/Game Name 2"
-        envr = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "HOME");
+        envr = SDL_getenv("HOME");
         if (!envr) {
             // we could take heroic measures with /etc/passwd, but oh well.
             SDL_SetError("neither XDG_DATA_HOME nor HOME environment is set");
@@ -368,12 +368,12 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa
   int relative;
   size_t l;
 
-  home_dir = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "HOME");
+  home_dir = SDL_getenv("HOME");
 
   if (!home_dir)
     goto error;
 
-  config_home = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "XDG_CONFIG_HOME");
+  config_home = SDL_getenv("XDG_CONFIG_HOME");
   if (!config_home || config_home[0] == 0)
     {
       l = SDL_strlen (home_dir) + SDL_strlen ("/.config/user-dirs.dirs") + 1;
@@ -495,7 +495,7 @@ static char *xdg_user_dir_lookup (const char *type)
     if (dir)
         return dir;
 
-    home_dir = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "HOME");
+    home_dir = SDL_getenv("HOME");
 
     if (!home_dir)
         return NULL;
@@ -533,7 +533,7 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder)
     */
     switch(folder) {
     case SDL_FOLDER_HOME:
-        param = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "HOME");
+        param = SDL_getenv("HOME");
 
         if (!param) {
             SDL_SetError("No $HOME environment variable available");
diff --git a/src/haptic/SDL_haptic.c b/src/haptic/SDL_haptic.c
index c40afacbf28f2..2f6ef78ea129e 100644
--- a/src/haptic/SDL_haptic.c
+++ b/src/haptic/SDL_haptic.c
@@ -537,7 +537,7 @@ SDL_bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain)
     }
 
     // The user can use an environment variable to override the max gain.
-    env = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "SDL_HAPTIC_GAIN_MAX");
+    env = SDL_getenv("SDL_HAPTIC_GAIN_MAX");
     if (env) {
         max_gain = SDL_atoi(env);
 
diff --git a/src/locale/unix/SDL_syslocale.c b/src/locale/unix/SDL_syslocale.c
index ac4301dc1a933..e945da5c86da6 100644
--- a/src/locale/unix/SDL_syslocale.c
+++ b/src/locale/unix/SDL_syslocale.c
@@ -78,13 +78,13 @@ bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen)
     *tmp = '\0';
 
     // LANG is the primary locale (maybe)
-    envr = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "LANG");
+    envr = SDL_getenv("LANG");
     if (envr) {
         SDL_strlcpy(tmp, envr, buflen);
     }
 
     // fallback languages
-    envr = SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "LANGUAGE");
+    envr = SDL_getenv("LANGUAGE");
     if (envr) {
         if (*tmp) {
             SDL_strlcat(tmp, ":", buflen);
diff --git a/src/stdlib/SDL_getenv.c b/src/stdlib/SDL_getenv.c
index c29fc3149cffe..0243b9bf92446 100644
--- a/src/stdlib/SDL_getenv.c
+++ b/src/stdlib/SDL_getenv.c
@@ -20,7 +20,6 @@
 */
 #include "SDL_internal.h"
 
-#include "SDL_getenv_c.h"
 #include "../SDL_hashtable.h"
 
 #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)
@@ -37,11 +36,6 @@
       (defined(HAVE_SETENV) || defined(HAVE_PUTENV)) && \
       (defined(HAVE_UNSETENV) || defined(HAVE_PUTENV))
 #define HAVE_LIBC_ENVIRONMENT
-#else
-#define HAVE_LOCAL_ENVIRONMENT
-#endif
-
-#if !defined(SDL_PLATFORM_WINDOWS)
 #if defined(SDL_PLATFORM_MACOS)
 #include <crt_externs.h>
 #define environ (*_NSGetEnviron())
@@ -51,7 +45,253 @@
 #else
 extern char **environ;
 #endif
-#endif // !SDL_PLATFORM_WINDOWS
+#else
+#define HAVE_LOCAL_ENVIRONMENT
+static char **environ;
+#endif
+
+
+struct SDL_Environment
+{
+    SDL_Mutex *lock;
+    SDL_HashTable *strings;
+};
+static SDL_Environment *SDL_environment;
+
+SDL_Environment *SDL_GetEnvironment(void)
+{
+    if (!SDL_environment) {
+        SDL_environment = SDL_CreateEnvironment(true);
+    }
+    return SDL_environment;
+}
+
+void SDL_CleanupEnvironment(void)
+{
+    SDL_Environment *env = SDL_environment;
+
+    if (env) {
+        SDL_environment = NULL;
+        SDL_DestroyEnvironment(env);
+    }
+}
+
+SDL_Environment *SDL_CreateEnvironment(SDL_bool populated)
+{
+    SDL_Environment *env = SDL_calloc(1, sizeof(*env));
+    if (!env) {
+        return NULL;
+    }
+
+    env->strings = SDL_CreateHashTable(NULL, 16, SDL_HashString, SDL_KeyMatchString, SDL_NukeFreeKey, false);
+    if (!env->strings) {
+        SDL_free(env);
+        return NULL;
+    }
+
+    // Don't fail if we can't create a mutex (e.g. on a single-thread environment)
+    env->lock = SDL_CreateMutex();
+
+    if (populated) {
+#ifdef SDL_PLATFORM_WINDOWS
+        LPWCH strings = GetEnvironmentStringsW();
+        if (strings) {
+            for (LPWCH string = strings; *string; string += SDL_wcslen(string) + 1) {
+                char *variable = WIN_StringToUTF8W(string);
+                if (!variable) {
+                    continue;
+                }
+
+                char *value = SDL_strchr(variable, '=');
+                if (!value || value == variable) {
+                    SDL_free(variable);
+                    continue;
+                }
+                *value++ = '\0';
+
+                SDL_InsertIntoHashTable(env->strings, variable, value);
+            }
+            FreeEnvironmentStringsW(strings);
+        }
+#else
+#ifdef SDL_PLATFORM_ANDROID
+        // Make sure variables from the application manifest are available
+        Android_JNI_GetManifestEnvironmentVariables();
+#endif
+        char **strings = environ;
+        if (strings) {
+            for (int i = 0; strings[i]; ++i) {
+                char *variable = SDL_strdup(strings[i]);
+                if (!variable) {
+                    continue;
+                }
+
+                char *value = SDL_strchr(variable, '=');
+                if (!value || value == variable) {
+                    SDL_free(variable);
+                    continue;
+                }
+                *value++ = '\0';
+
+                SDL_InsertIntoHashTable(env->strings, variable, value);
+            }
+        }
+#endif // SDL_PLATFORM_WINDOWS
+    }
+
+    return env;
+}
+
+const char *SDL_GetEnvironmentVariable(SDL_Environment *env, const char *name)
+{
+    const char *result = NULL;
+
+    if (!env) {
+        return NULL;
+    } else if (!name || *name == '\0') {
+        return NULL;
+    }
+
+    SDL_LockMutex(env->lock);
+    {
+        const char *value;
+
+        if (SDL_FindInHashTable(env->strings, name, (const void **)&value)) {
+            result = SDL_GetPersistentString(value);
+        }
+    }
+    SDL_UnlockMutex(env->lock);
+
+    return result;
+}
+
+char **SDL_GetEnvironmentVariables(SDL_Environment *env)
+{
+    char **result = NULL;
+
+    if (!env) {
+        SDL_InvalidParamError("env");
+        return NULL;
+    }
+
+    SDL_LockMutex(env->lock);
+    {
+        size_t count, length = 0;
+        void *iter;
+        const char *key, *value;
+
+        // First pass, get the size we need for all the strings
+        count = 0;
+        iter = NULL;
+        while (SDL_IterateHashTable(env->strings, (const void **)&key, (const void **)&value, &iter)) {
+            length += SDL_strlen(key) + 1 + SDL_strlen(value) + 1;
+            ++count;
+        }
+
+        // Allocate memory for the strings
+        result = (char **)SDL_malloc((count + 1) * sizeof(*result) + length);
+        char *string = (char *)(result + count + 1);
+
+        // Second pass, copy the strings
+        count = 0;
+        iter = NULL;
+        while (SDL_IterateHashTable(env->strings, (const void **)&key, (const void **)&value, &iter)) {
+            size_t len;
+
+            result[count] = string;
+            len = SDL_strlen(key);
+            SDL_memcpy(string, key, len);
+            string += len;
+            *string++ = '=';
+            len = SDL_strlen(value);
+            SDL_memcpy(string, value, len);
+            string += len;
+            *string++ = '\0';
+            ++count;
+        }
+        result[count] = NULL;
+    }
+    SDL_UnlockMutex(env->lock);
+
+    return result;
+}
+
+SDL_bool SDL_SetEnvironmentVariable(SDL_Environment *env, const char *name, const char *value, SDL_bool overwrite)
+{
+    bool result = false;
+
+    if (!env) {
+        return SDL_InvalidParamError("env");
+    } else if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {
+        return SDL_InvalidParamError("name");
+    } else if (!value) {
+        return SDL_InvalidParamError("value");
+    }
+
+    SDL_LockMutex(env->lock);
+    {
+        const void *existing_value;
+        bool insert = true;
+
+        if (SDL_FindInHashTable(env->strings, name, &existing_value)) {
+            if (!overwrite) {
+                result = true;
+                insert = false;
+            } else {
+                SDL_RemoveFromHashTable(env->strings, name);
+            }
+        }
+
+        if (insert) {
+            char *string = NULL;
+            if (SDL_asprintf(&string, "%s=%s", name, value) > 0) {
+                size_t len = SDL_strlen(name);
+                string[len] = '\0';
+                name = string;
+                value = string + len + 1;
+                result = SDL_InsertIntoHashTable(env->strings, name, value);
+            }
+        }
+    }
+    SDL_UnlockMutex(env->lock);
+
+    return result;
+}
+
+SDL_bool SDL_UnsetEnvironmentVariable(SDL_Environment *env, const char *name)
+{
+    bool result = false;
+
+    if (!env) {
+        return SDL_InvalidParamError("env");
+    } else if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {
+        return SDL_InvalidParamError("name");
+    }
+
+    SDL_LockMutex(env->lock);
+    {
+        const void *value;
+        if (SDL_FindInHashTable(env->strings, name, &value)) {
+            result = SDL_RemoveFromHashTable(env->strings, name);
+        } else {
+            result = true;
+        }
+    }
+    SDL_UnlockMutex(env->lock);
+
+    return result;
+}
+
+void SDL_DestroyEnvironment(SDL_Environment *env)
+{
+    if (!env || env == SDL_environment) {
+        return;
+    }
+
+    SDL_DestroyMutex(env->lock);
+    SDL_DestroyHashTable(env->strings);
+    SDL_free(env);
+}
 
 // Put a variable into the environment
 // Note: Name may not contain a '=' character. (Reference: http://www.unix.com/man-page/Linux/3/setenv/)
@@ -64,6 +304,8 @@ int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
         return -1;
     }
 
+    SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));
+
     return setenv(name, value, overwrite);
 }
 // We have a real environment table, but no real setenv? Fake it w/ putenv.
@@ -77,6 +319,8 @@ int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
         return -1;
     }
 
+    SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));
+
     if (getenv(name) != NULL) {
         if (!overwrite) {
             return 0; // leave the existing one there.
@@ -99,6 +343,8 @@ int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
         return -1;
     }
 
+    SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));
+
     if (!overwrite) {
         if (GetEnvironmentVariableA(name, NULL, 0) > 0) {
             return 0; // asked not to overwrite existing value.
@@ -111,9 +357,6 @@ int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
 }
 #else // roll our own
 
-// We'll leak this, as environment variables are intended to persist past SDL_Quit()
-static char **SDL_env;
-
 int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
 {
     int added;
@@ -131,6 +374,8 @@ int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
         return 0;
     }
 
+    SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));
+
     // Allocate memory for the variable
     len = SDL_strlen(name) + SDL_strlen(value) + 2;
     new_variable = (char *)SDL_malloc(len);
@@ -145,14 +390,14 @@ int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
     // Actually put it into the environment
     added = 0;
     i = 0;
-    if (SDL_env) {
+    if (environ) {
         // Check to see if it's already there...
         len = (value - name);
-        for (; SDL_env[i]; ++i) {
-            if (SDL_strncmp(SDL_env[i], name, len) == 0) {
+        for (; environ[i]; ++i) {
+            if (SDL_strncmp(environ[i], name, len) == 0) {
                 // If we found it, just replace the entry
-                SDL_free(SDL_env[i]);
-                SDL_env[i] = new_variable;
+                SDL_free(environ[i]);
+                environ[i] = new_variable;
                 added = 1;
                 break;
             }
@@ -161,11 +406,11 @@ int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
 
     // Didn't find it in the environment, expand and add
     if (!added) {
-        new_env = SDL_realloc(SDL_env, (i + 2) * sizeof(char *));
+        new_env = SDL_realloc(environ, (i + 2) * sizeof(char *));
         if (new_env) {
-            SDL_env = new_env;
-            SDL_env[i++] = new_variable;
-            SDL_env[i++] = (char *)0;
+            environ = new_env;
+            environ[i++] = new_variable;
+            environ[i++] = (char *)0;
             added = 1;
         } else {
             SDL_free(new_variable);
@@ -184,6 +429,8 @@ int SDL_unsetenv_unsafe(const char *name)
         return -1;
     }
 
+    SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);
+
     return unsetenv(name);
 }
 // We have a real environment table, but no unsetenv? Fake it w/ putenv.
@@ -195,6 +442,8 @@ int SDL_unsetenv_unsafe(const char *name)
         return -1;
     }
 
+    SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);
+
     // Hope this environment uses the non-standard extension of removing the environment variable if it has no '='
     return putenv(name);
 }
@@ -207,6 +456,8 @@ int SDL_unsetenv_unsafe(const char *name)
         return -1;
     }
 
+    SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);
+
     if (!SetEnvironmentVariableA(name, NULL)) {
         return -1;
     }
@@ -222,13 +473,15 @@ int SDL_unsetenv_unsafe(const char *name)
         return -1;
     }
 
-    if (SDL_env) {
+    SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);
+
+    if (environ) {
         len = SDL_strlen(name);
-        for (i = 0; SDL_env[i]; ++i) {
-            if ((SDL_strncmp(SDL_env[i], name, len) == 0) &&
-                (SDL_env[i][len] == '=')) {
+        for (i = 0; environ[i]; ++i) {
+            if ((SDL_strncmp(environ[i], name, len) == 0) &&
+                (environ[i][len] == '=')

(Patch may be truncated, please check the link at the top of this post.)