filesystem: SDL_EnumerateDirectory() gives dirs with path seperators appended.

Fixes #11065.
Fixes #11427.
This commit is contained in:
Ryan C. Gordon
2025-01-15 17:03:01 -05:00
parent e98ee9bb04
commit 87e1b0eb89
5 changed files with 54 additions and 29 deletions

View File

@@ -313,6 +313,9 @@ typedef enum SDL_EnumerationResult
* terminate the enumeration early, and dictate the return value of the * terminate the enumeration early, and dictate the return value of the
* enumeration function itself. * 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 userdata an app-controlled pointer that is passed to the callback.
* \param dirname the directory that is being enumerated. * \param dirname the directory that is being enumerated.
* \param fname the next entry in the enumeration. * \param fname the next entry in the enumeration.

View File

@@ -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. // !!! FIXME: and only casefold the new pieces instead of allocating and folding full paths for all of this.
char *fullpath = NULL; 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; return SDL_ENUM_FAILURE;
} }
@@ -417,7 +417,8 @@ char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_Glob
data.enumerator = enumerator; data.enumerator = enumerator;
data.getpathinfo = getpathinfo; data.getpathinfo = getpathinfo;
data.fsuserdata = userdata; 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; char **result = NULL;
if (data.enumerator(path, GlobDirectoryCallback, &data, data.fsuserdata)) { if (data.enumerator(path, GlobDirectoryCallback, &data, data.fsuserdata)) {

View File

@@ -37,25 +37,42 @@
bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) 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;
}
DIR *dir = opendir(path); // 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(pathwithsep);
if (!dir) { if (!dir) {
SDL_free(pathwithsep);
return SDL_SetError("Can't open directory: %s", strerror(errno)); 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; 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; const char *name = ent->d_name;
if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) { if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) {
continue; continue;
} }
result = cb(userdata, path, name); result = cb(userdata, pathwithsep, name);
} }
closedir(dir); closedir(dir);
SDL_free(pathwithsep);
return (result != SDL_ENUM_FAILURE); return (result != SDL_ENUM_FAILURE);
} }

View File

@@ -34,35 +34,45 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback
SDL_EnumerationResult result = SDL_ENUM_CONTINUE; SDL_EnumerationResult result = SDL_ENUM_CONTINUE;
if (*path == '\0') { // if empty (completely at the root), we need to enumerate drive letters. if (*path == '\0') { // if empty (completely at the root), we need to enumerate drive letters.
const DWORD drives = GetLogicalDrives(); const DWORD drives = GetLogicalDrives();
char name[3] = { 0, ':', '\0' }; char name[] = { 0, ':', '\\', '\0' };
for (int i = 'A'; (result == SDL_ENUM_CONTINUE) && (i <= 'Z'); i++) { for (int i = 'A'; (result == SDL_ENUM_CONTINUE) && (i <= 'Z'); i++) {
if (drives & (1 << (i - 'A'))) { if (drives & (1 << (i - 'A'))) {
name[0] = (char) i; name[0] = (char) i;
result = cb(userdata, path, name); result = cb(userdata, "", name);
} }
} }
} else { } 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 // 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 // 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. // 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.
WCHAR *wpattern = WIN_UTF8ToStringW(pattern); if ((patternlen == -1) || (!pattern)) {
SDL_free(pattern);
if (!wpattern) {
return false; 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);
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; WIN32_FIND_DATAW entw;
HANDLE dir = FindFirstFileExW(wpattern, FindExInfoStandard, &entw, FindExSearchNameMatch, NULL, 0); HANDLE dir = FindFirstFileExW(wpattern, FindExInfoStandard, &entw, FindExSearchNameMatch, NULL, 0);
SDL_free(wpattern); SDL_free(wpattern);
if (dir == INVALID_HANDLE_VALUE) { if (dir == INVALID_HANDLE_VALUE) {
SDL_free(pattern);
return WIN_SetError("Failed to enumerate directory"); return WIN_SetError("Failed to enumerate directory");
} }
@@ -79,12 +89,13 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback
if (!utf8fn) { if (!utf8fn) {
result = SDL_ENUM_FAILURE; result = SDL_ENUM_FAILURE;
} else { } else {
result = cb(userdata, path, utf8fn); result = cb(userdata, pattern, utf8fn);
SDL_free(utf8fn); SDL_free(utf8fn);
} }
} while ((result == SDL_ENUM_CONTINUE) && (FindNextFileW(dir, &entw) != 0)); } while ((result == SDL_ENUM_CONTINUE) && (FindNextFileW(dir, &entw) != 0));
FindClose(dir); FindClose(dir);
SDL_free(pattern);
} }
return (result != SDL_ENUM_FAILURE); return (result != SDL_ENUM_FAILURE);

View File

@@ -20,14 +20,7 @@ static SDL_EnumerationResult SDLCALL enum_callback(void *userdata, const char *o
SDL_PathInfo info; SDL_PathInfo info;
char *fullpath = NULL; char *fullpath = NULL;
/* you can use '/' for a path separator on Windows, but to make the log output look correct, we'll #ifdef this... */ if (SDL_asprintf(&fullpath, "%s%s", origdir, fname) < 0) {
#ifdef SDL_PLATFORM_WINDOWS
const char *pathsep = "\\";
#else
const char *pathsep = "/";
#endif
if (SDL_asprintf(&fullpath, "%s%s%s", origdir, *origdir ? pathsep : "", fname) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!"); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!");
return SDL_ENUM_FAILURE; return SDL_ENUM_FAILURE;
} }