From 87e1b0eb89823156551c6244ed365f063a3b1dcf Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 15 Jan 2025 17:03:01 -0500
Subject: [PATCH] filesystem: SDL_EnumerateDirectory() gives dirs with path
seperators appended.
Fixes #11065.
Fixes #11427.
---
include/SDL3/SDL_filesystem.h | 3 +++
src/filesystem/SDL_filesystem.c | 5 ++--
src/filesystem/posix/SDL_sysfsops.c | 27 ++++++++++++++++++----
src/filesystem/windows/SDL_sysfsops.c | 33 ++++++++++++++++++---------
test/testfilesystem.c | 9 +-------
5 files changed, 51 insertions(+), 26 deletions(-)
diff --git a/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h
index c228a0382f4bb..8baa254441952 100644
--- a/include/SDL3/SDL_filesystem.h
+++ b/include/SDL3/SDL_filesystem.h
@@ -313,6 +313,9 @@ typedef enum SDL_EnumerationResult
* terminate the enumeration early, and dictate the return value of the
* enumeration function itself.
*
+ * `dirname` is guaranteed to end with a path separator ('\\' on
+ * Windows, '/' on most other platforms).
+ *
* \param userdata an app-controlled pointer that is passed to the callback.
* \param dirname the directory that is being enumerated.
* \param fname the next entry in the enumeration.
diff --git a/src/filesystem/SDL_filesystem.c b/src/filesystem/SDL_filesystem.c
index 6bb905a0b851e..28388517c4978 100644
--- a/src/filesystem/SDL_filesystem.c
+++ b/src/filesystem/SDL_filesystem.c
@@ -307,7 +307,7 @@ static SDL_EnumerationResult SDLCALL GlobDirectoryCallback(void *userdata, const
// !!! FIXME: and only casefold the new pieces instead of allocating and folding full paths for all of this.
char *fullpath = NULL;
- if (SDL_asprintf(&fullpath, "%s/%s", dirname, fname) < 0) {
+ if (SDL_asprintf(&fullpath, "%s%s", dirname, fname) < 0) {
return SDL_ENUM_FAILURE;
}
@@ -417,7 +417,8 @@ char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_Glob
data.enumerator = enumerator;
data.getpathinfo = getpathinfo;
data.fsuserdata = userdata;
- data.basedirlen = SDL_strlen(path) + 1; // +1 for the '/' we'll be adding.
+ data.basedirlen = *path ? (SDL_strlen(path) + 1) : 0; // +1 for the '/' we'll be adding.
+
char **result = NULL;
if (data.enumerator(path, GlobDirectoryCallback, &data, data.fsuserdata)) {
diff --git a/src/filesystem/posix/SDL_sysfsops.c b/src/filesystem/posix/SDL_sysfsops.c
index 0658b8f1554d9..c562c2aed2461 100644
--- a/src/filesystem/posix/SDL_sysfsops.c
+++ b/src/filesystem/posix/SDL_sysfsops.c
@@ -37,25 +37,42 @@
bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata)
{
- SDL_EnumerationResult result = SDL_ENUM_CONTINUE;
+ char *pathwithsep = NULL;
+ int pathwithseplen = SDL_asprintf(&pathwithsep, "%s/", path);
+ if ((pathwithseplen == -1) || (!pathwithsep)) {
+ return false;
+ }
+
+ // trim down to a single path separator at the end, in case the caller added one or more.
+ pathwithseplen--;
+ while ((pathwithseplen >= 0) && (pathwithsep[pathwithseplen] == '/')) {
+ pathwithsep[pathwithseplen--] = '\0';
+ }
- DIR *dir = opendir(path);
+ DIR *dir = opendir(pathwithsep);
if (!dir) {
+ SDL_free(pathwithsep);
return SDL_SetError("Can't open directory: %s", strerror(errno));
}
+ // make sure there's a path separator at the end now for the actual callback.
+ pathwithsep[++pathwithseplen] = '/';
+ pathwithsep[++pathwithseplen] = '\0';
+
+ SDL_EnumerationResult result = SDL_ENUM_CONTINUE;
struct dirent *ent;
- while ((result == SDL_ENUM_CONTINUE) && ((ent = readdir(dir)) != NULL))
- {
+ while ((result == SDL_ENUM_CONTINUE) && ((ent = readdir(dir)) != NULL)) {
const char *name = ent->d_name;
if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) {
continue;
}
- result = cb(userdata, path, name);
+ result = cb(userdata, pathwithsep, name);
}
closedir(dir);
+ SDL_free(pathwithsep);
+
return (result != SDL_ENUM_FAILURE);
}
diff --git a/src/filesystem/windows/SDL_sysfsops.c b/src/filesystem/windows/SDL_sysfsops.c
index 89961c6a8fa8e..9c48ba957b599 100644
--- a/src/filesystem/windows/SDL_sysfsops.c
+++ b/src/filesystem/windows/SDL_sysfsops.c
@@ -34,35 +34,45 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback
SDL_EnumerationResult result = SDL_ENUM_CONTINUE;
if (*path == '\0') { // if empty (completely at the root), we need to enumerate drive letters.
const DWORD drives = GetLogicalDrives();
- char name[3] = { 0, ':', '\0' };
+ char name[] = { 0, ':', '\\', '\0' };
for (int i = 'A'; (result == SDL_ENUM_CONTINUE) && (i <= 'Z'); i++) {
if (drives & (1 << (i - 'A'))) {
name[0] = (char) i;
- result = cb(userdata, path, name);
+ result = cb(userdata, "", name);
}
}
} else {
- const size_t patternlen = SDL_strlen(path) + 3;
- char *pattern = (char *) SDL_malloc(patternlen);
- if (!pattern) {
- return false;
- }
-
// you need a wildcard to enumerate through FindFirstFileEx(), but the wildcard is only checked in the
// filename element at the end of the path string, so always tack on a "\\*" to get everything, and
// also prevent any wildcards inserted by the app from being respected.
- SDL_snprintf(pattern, patternlen, "%s\\*", path);
+ char *pattern = NULL;
+ int patternlen = SDL_asprintf(&pattern, "%s\\\\", path); // we'll replace that second '\\' in the trimdown.
+ if ((patternlen == -1) || (!pattern)) {
+ return false;
+ }
+
+ // trim down to a single path separator at the end, in case the caller added one or more.
+ patternlen--;
+ while ((patternlen >= 0) && ((pattern[patternlen] == '\\') || (pattern[patternlen] == '/'))) {
+ pattern[patternlen--] ='\0';
+ }
+ pattern[++patternlen] = '\\';
+ pattern[++patternlen] = '*';
+ pattern[++patternlen] = '\0';
WCHAR *wpattern = WIN_UTF8ToStringW(pattern);
- SDL_free(pattern);
if (!wpattern) {
+ SDL_free(pattern);
return false;
}
+ pattern[--patternlen] = '\0'; // chop off the '*' so we just have the dirname with a path separator.
+
WIN32_FIND_DATAW entw;
HANDLE dir = FindFirstFileExW(wpattern, FindExInfoStandard, &entw, FindExSearchNameMatch, NULL, 0);
SDL_free(wpattern);
if (dir == INVALID_HANDLE_VALUE) {
+ SDL_free(pattern);
return WIN_SetError("Failed to enumerate directory");
}
@@ -79,12 +89,13 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback
if (!utf8fn) {
result = SDL_ENUM_FAILURE;
} else {
- result = cb(userdata, path, utf8fn);
+ result = cb(userdata, pattern, utf8fn);
SDL_free(utf8fn);
}
} while ((result == SDL_ENUM_CONTINUE) && (FindNextFileW(dir, &entw) != 0));
FindClose(dir);
+ SDL_free(pattern);
}
return (result != SDL_ENUM_FAILURE);
diff --git a/test/testfilesystem.c b/test/testfilesystem.c
index 5618413730bcf..9fe2cdb82383e 100644
--- a/test/testfilesystem.c
+++ b/test/testfilesystem.c
@@ -20,14 +20,7 @@ static SDL_EnumerationResult SDLCALL enum_callback(void *userdata, const char *o
SDL_PathInfo info;
char *fullpath = NULL;
- /* you can use '/' for a path separator on Windows, but to make the log output look correct, we'll #ifdef this... */
- #ifdef SDL_PLATFORM_WINDOWS
- const char *pathsep = "\\";
- #else
- const char *pathsep = "/";
- #endif
-
- if (SDL_asprintf(&fullpath, "%s%s%s", origdir, *origdir ? pathsep : "", fname) < 0) {
+ if (SDL_asprintf(&fullpath, "%s%s", origdir, fname) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!");
return SDL_ENUM_FAILURE;
}