SDL: filesystem: Added SDL_GetCurrentDirectory().

From f8520383849e3f9c014adcf7039d6be37d5f29b6 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 27 Nov 2024 19:41:37 -0500
Subject: [PATCH] filesystem: Added SDL_GetCurrentDirectory().

Fixes #11531.
---
 include/SDL3/SDL_filesystem.h              | 16 +++++++++++
 src/dynapi/SDL_dynapi.sym                  |  1 +
 src/dynapi/SDL_dynapi_overrides.h          |  1 +
 src/dynapi/SDL_dynapi_procs.h              |  1 +
 src/filesystem/SDL_filesystem.c            |  7 +++--
 src/filesystem/SDL_sysfilesystem.h         |  1 +
 src/filesystem/dummy/SDL_sysfilesystem.c   |  6 ++++
 src/filesystem/posix/SDL_sysfsops.c        | 32 ++++++++++++++++++++++
 src/filesystem/windows/SDL_sysfilesystem.c | 28 +++++++++++++++++++
 test/testfilesystem.c                      | 10 +++++++
 10 files changed, 101 insertions(+), 2 deletions(-)

diff --git a/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h
index bf403e28ddf01..11a57015770de 100644
--- a/include/SDL3/SDL_filesystem.h
+++ b/include/SDL3/SDL_filesystem.h
@@ -447,6 +447,22 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetPathInfo(const char *path, SDL_PathInfo
  */
 extern SDL_DECLSPEC char ** SDLCALL SDL_GlobDirectory(const char *path, const char *pattern, SDL_GlobFlags flags, int *count);
 
+/**
+ * Get what the system believes is the "current working directory."
+ *
+ * For systems without a concept of a current working directory, this will
+ * still attempt to provide something reasonable.
+ *
+ * SDL does not provide a means to _change_ the current working directory;
+ * for platforms without this concept, this would cause surprises with file
+ * access outside of SDL.
+ *
+ * \returns a UTF-8 string of the current working directory in
+ *          platform-dependent notation. NULL if there's a problem. This
+ *          should be freed with SDL_free() when it is no longer needed.
+ */
+extern SDL_DECLSPEC char * SDLCALL SDL_GetCurrentDirectory(void);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index e786d37002e76..c750acc421634 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1186,6 +1186,7 @@ SDL3_0.0.0 {
     SDL_CancelGPUCommandBuffer;
     SDL_SaveFile_IO;
     SDL_SaveFile;
+    SDL_GetCurrentDirectory;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 215f471510348..cac3f0b7dddd8 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1211,3 +1211,4 @@
 #define SDL_CancelGPUCommandBuffer SDL_CancelGPUCommandBuffer_REAL
 #define SDL_SaveFile_IO SDL_SaveFile_IO_REAL
 #define SDL_SaveFile SDL_SaveFile_REAL
+#define SDL_GetCurrentDirectory SDL_GetCurrentDirectory_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 05849f6ec3e33..36a8ad0cf8255 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1217,3 +1217,4 @@ SDL_DYNAPI_PROC(SDL_Sandbox,SDL_GetSandbox,(void),(),return)
 SDL_DYNAPI_PROC(bool,SDL_CancelGPUCommandBuffer,(SDL_GPUCommandBuffer *a),(a),return)
 SDL_DYNAPI_PROC(bool,SDL_SaveFile_IO,(SDL_IOStream *a,const void *b,size_t c,bool d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(bool,SDL_SaveFile,(const char *a,const void *b,size_t c),(a,b,c),return)
+SDL_DYNAPI_PROC(char*,SDL_GetCurrentDirectory,(void),(),return)
diff --git a/src/filesystem/SDL_filesystem.c b/src/filesystem/SDL_filesystem.c
index d13b6ea8d93b2..46cb74b7f8277 100644
--- a/src/filesystem/SDL_filesystem.c
+++ b/src/filesystem/SDL_filesystem.c
@@ -495,10 +495,13 @@ const char *SDL_GetUserFolder(SDL_Folder folder)
 
 char *SDL_GetPrefPath(const char *org, const char *app)
 {
-    char *path = SDL_SYS_GetPrefPath(org, app);
-    return path;
+    return SDL_SYS_GetPrefPath(org, app);
 }
 
+char *SDL_GetCurrentDirectory(void)
+{
+    return SDL_SYS_GetCurrentDirectory();
+}
 
 void SDL_InitFilesystem(void)
 {
diff --git a/src/filesystem/SDL_sysfilesystem.h b/src/filesystem/SDL_sysfilesystem.h
index e0fe9c604bb97..cec031f4c8c7d 100644
--- a/src/filesystem/SDL_sysfilesystem.h
+++ b/src/filesystem/SDL_sysfilesystem.h
@@ -26,6 +26,7 @@
 extern char *SDL_SYS_GetBasePath(void);
 extern char *SDL_SYS_GetPrefPath(const char *org, const char *app);
 extern char *SDL_SYS_GetUserFolder(SDL_Folder folder);
+extern char *SDL_SYS_GetCurrentDirectory(void);
 
 extern bool SDL_SYS_EnumerateDirectory(const char *path, const char *dirname, SDL_EnumerateDirectoryCallback cb, void *userdata);
 extern bool SDL_SYS_RemovePath(const char *path);
diff --git a/src/filesystem/dummy/SDL_sysfilesystem.c b/src/filesystem/dummy/SDL_sysfilesystem.c
index 0c74b5986b41d..4aebe95881913 100644
--- a/src/filesystem/dummy/SDL_sysfilesystem.c
+++ b/src/filesystem/dummy/SDL_sysfilesystem.c
@@ -45,4 +45,10 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder)
     return NULL;
 }
 
+char *SDL_SYS_GetCurrentDirectory(void)
+{
+    SDL_Unsupported();
+    return NULL;
+}
+
 #endif // SDL_FILESYSTEM_DUMMY || SDL_FILESYSTEM_DISABLED
diff --git a/src/filesystem/posix/SDL_sysfsops.c b/src/filesystem/posix/SDL_sysfsops.c
index db197ed05af72..ec2cc15c6f78a 100644
--- a/src/filesystem/posix/SDL_sysfsops.c
+++ b/src/filesystem/posix/SDL_sysfsops.c
@@ -33,6 +33,7 @@
 #include <errno.h>
 #include <dirent.h>
 #include <sys/stat.h>
+#include <unistd.h>
 
 bool SDL_SYS_EnumerateDirectory(const char *path, const char *dirname, SDL_EnumerateDirectoryCallback cb, void *userdata)
 {
@@ -185,5 +186,36 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info)
     return true;
 }
 
+// Note that this isn't actually part of filesystem, not fsops, but everything that uses posix fsops uses this implementation, even with separate filesystem code.
+char *SDL_SYS_GetCurrentDirectory(void)
+{
+    size_t buflen = 64;
+    char *buf = NULL;
+
+    while (true) {
+        void *ptr = SDL_realloc(buf, buflen);
+        if (!ptr) {
+            SDL_free(buf);
+            return NULL;
+        }
+        buf = (char *) ptr;
+
+        if (getcwd(buf, buflen) != NULL) {
+            break;  // we got it!
+        }
+
+        if (errno == ERANGE) {
+            buflen *= 2;  // try again with a bigger buffer.
+            continue;
+        }
+
+        SDL_free(buf);
+        SDL_SetError("getcwd failed: %s", strerror(errno));
+        return NULL;
+    }
+
+    return buf;
+}
+
 #endif // SDL_FSOPS_POSIX
 
diff --git a/src/filesystem/windows/SDL_sysfilesystem.c b/src/filesystem/windows/SDL_sysfilesystem.c
index 6db77d455f4b2..3b627adad874c 100644
--- a/src/filesystem/windows/SDL_sysfilesystem.c
+++ b/src/filesystem/windows/SDL_sysfilesystem.c
@@ -345,4 +345,32 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder)
     }
     return result;
 }
+
+char *SDL_SYS_GetCurrentDirectory(void)
+{
+    WCHAR *wstr = NULL;
+    DWORD buflen = 0;
+    while (true) {
+        const DWORD bw = GetCurrentDirectoryW(buflen, wstr);
+        if (bw == 0) {
+            WIN_SetError("GetCurrentDirectoryW failed");
+            return NULL;
+        } else if (bw < buflen) {
+            break;  // we got it!
+        }
+
+        void *ptr = SDL_realloc(wstr, bw * sizeof (WCHAR));
+        if (!ptr) {
+            SDL_free(wstr);
+            return NULL;
+        }
+        wstr = (WCHAR *) ptr;
+        buflen = bw;
+    }
+
+    char *retval = WIN_StringToUTF8W(wstr);
+    SDL_free(wstr);
+    return retval;
+}
+
 #endif // SDL_FILESYSTEM_WINDOWS
diff --git a/test/testfilesystem.c b/test/testfilesystem.c
index 3a068a0f2b863..76087998bb6ac 100644
--- a/test/testfilesystem.c
+++ b/test/testfilesystem.c
@@ -62,6 +62,7 @@ int main(int argc, char *argv[])
 {
     SDLTest_CommonState *state;
     char *pref_path;
+    char *curdir;
     const char *base_path;
 
     /* Initialize test framework */
@@ -106,6 +107,15 @@ int main(int argc, char *argv[])
     }
     SDL_free(pref_path);
 
+    curdir = SDL_GetCurrentDirectory();
+    if (!curdir) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't find current directory: %s\n",
+                     SDL_GetError());
+    } else {
+        SDL_Log("current directory: '%s'\n", curdir);
+    }
+    SDL_free(curdir);
+
     if (base_path) {
         char **globlist;
         SDL_IOStream *stream;