Atlas - SDL_filesystem.c
Home / ext / SDL / src / filesystem Lines: 1 | Size: 17326 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21 22#include "SDL_internal.h" 23 24#include "SDL_filesystem_c.h" 25#include "SDL_sysfilesystem.h" 26#include "../stdlib/SDL_sysstdlib.h" 27 28bool SDL_RemovePath(const char *path) 29{ 30 CHECK_PARAM(!path) { 31 return SDL_InvalidParamError("path"); 32 } 33 return SDL_SYS_RemovePath(path); 34} 35 36bool SDL_RenamePath(const char *oldpath, const char *newpath) 37{ 38 CHECK_PARAM(!oldpath) { 39 return SDL_InvalidParamError("oldpath"); 40 } 41 CHECK_PARAM(!newpath) { 42 return SDL_InvalidParamError("newpath"); 43 } 44 return SDL_SYS_RenamePath(oldpath, newpath); 45} 46 47bool SDL_CopyFile(const char *oldpath, const char *newpath) 48{ 49 CHECK_PARAM(!oldpath) { 50 return SDL_InvalidParamError("oldpath"); 51 } 52 CHECK_PARAM(!newpath) { 53 return SDL_InvalidParamError("newpath"); 54 } 55 return SDL_SYS_CopyFile(oldpath, newpath); 56} 57 58bool SDL_CreateDirectory(const char *path) 59{ 60 CHECK_PARAM(!path) { 61 return SDL_InvalidParamError("path"); 62 } 63 64 bool retval = SDL_SYS_CreateDirectory(path); 65 if (!retval && *path) { // maybe we're missing parent directories? 66 char *parents = SDL_strdup(path); 67 if (!parents) { 68 return false; // oh well. 69 } 70 71 // in case there was a separator at the end of the path and it was 72 // upsetting something, chop it off. 73 const size_t slen = SDL_strlen(parents); 74 #ifdef SDL_PLATFORM_WINDOWS 75 if ((parents[slen - 1] == '/') || (parents[slen - 1] == '\\')) 76 #else 77 if (parents[slen - 1] == '/') 78 #endif 79 { 80 parents[slen - 1] = '\0'; 81 retval = SDL_SYS_CreateDirectory(parents); 82 } 83 84 if (!retval) { 85 for (char *ptr = parents; *ptr; ptr++) { 86 const char ch = *ptr; 87 #ifdef SDL_PLATFORM_WINDOWS 88 const bool issep = (ch == '/') || (ch == '\\'); 89 if (issep && ((ptr - parents) == 2) && (parents[1] == ':')) { 90 continue; // it's just the drive letter, skip it. 91 } 92 #else 93 const bool issep = (ch == '/'); 94 if (issep && ((ptr - parents) == 0)) { 95 continue; // it's just the root directory, skip it. 96 } 97 #endif 98 99 if (issep) { 100 *ptr = '\0'; 101 // (this does not fail if the path already exists as a directory.) 102 retval = SDL_SYS_CreateDirectory(parents); 103 if (!retval) { // still failing when making parents? Give up. 104 break; 105 } 106 *ptr = ch; 107 } 108 } 109 110 // last chance: did it work this time? 111 retval = SDL_SYS_CreateDirectory(parents); 112 } 113 114 SDL_free(parents); 115 } 116 return retval; 117} 118 119bool SDL_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata) 120{ 121 CHECK_PARAM(!path) { 122 return SDL_InvalidParamError("path"); 123 } 124 CHECK_PARAM(!callback) { 125 return SDL_InvalidParamError("callback"); 126 } 127 return SDL_SYS_EnumerateDirectory(path, callback, userdata); 128} 129 130bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info) 131{ 132 SDL_PathInfo dummy; 133 134 if (!info) { 135 info = &dummy; 136 } 137 SDL_zerop(info); 138 139 CHECK_PARAM(!path) { 140 return SDL_InvalidParamError("path"); 141 } 142 return SDL_SYS_GetPathInfo(path, info); 143} 144 145static bool EverythingMatch(const char *pattern, const char *str, bool *matched_to_dir) 146{ 147 SDL_assert(pattern == NULL); 148 SDL_assert(str != NULL); 149 SDL_assert(matched_to_dir != NULL); 150 151 *matched_to_dir = true; 152 return true; // everything matches! 153} 154 155// this is just '*' and '?', with '/' matching nothing. 156static bool WildcardMatch(const char *pattern, const char *str, bool *matched_to_dir) 157{ 158 SDL_assert(pattern != NULL); 159 SDL_assert(str != NULL); 160 SDL_assert(matched_to_dir != NULL); 161 162 const char *str_backtrack = NULL; 163 const char *pattern_backtrack = NULL; 164 char sch_backtrack = 0; 165 char sch = *str; 166 char pch = *pattern; 167 168 while (sch) { 169 if (pch == '*') { 170 str_backtrack = str; 171 pattern_backtrack = ++pattern; 172 sch_backtrack = sch; 173 pch = *pattern; 174 } else if (pch == sch) { 175 if (pch == '/') { 176 str_backtrack = pattern_backtrack = NULL; 177 } 178 sch = *(++str); 179 pch = *(++pattern); 180 } else if ((pch == '?') && (sch != '/')) { // end of string (checked at `while`) or path separator do not match '?'. 181 sch = *(++str); 182 pch = *(++pattern); 183 } else if (!pattern_backtrack || (sch_backtrack == '/')) { // we didn't have a match. Are we in a '*' and NOT on a path separator? Keep going. Otherwise, fail. 184 *matched_to_dir = false; 185 return false; 186 } else { // still here? Wasn't a match, but we're definitely in a '*' pattern. 187 str = ++str_backtrack; 188 pattern = pattern_backtrack; 189 sch_backtrack = sch; 190 sch = *str; 191 pch = *pattern; 192 } 193 194 #ifdef SDL_PLATFORM_WINDOWS 195 if (sch == '\\') { 196 sch = '/'; 197 } 198 #endif 199 } 200 201 // '*' at the end can be ignored, they are allowed to match nothing. 202 while (pch == '*') { 203 pch = *(++pattern); 204 } 205 206 *matched_to_dir = ((pch == '/') || (pch == '\0')); // end of string and the pattern is complete or failed at a '/'? We should descend into this directory. 207 208 return (pch == '\0'); // survived the whole pattern? That's a match! 209} 210 211 212// Note that this will currently encode illegal codepoints: UTF-16 surrogates, 0xFFFE, and 0xFFFF. 213// and a codepoint > 0x10FFFF will fail the same as if there wasn't enough memory. 214// clean this up if you want to move this to SDL_string.c. 215static size_t EncodeCodepointToUtf8(char *ptr, Uint32 cp, size_t remaining) 216{ 217 if (cp < 0x80) { // fits in a single UTF-8 byte. 218 if (remaining) { 219 *ptr = (char) cp; 220 return 1; 221 } 222 } else if (cp < 0x800) { // fits in 2 bytes. 223 if (remaining >= 2) { 224 ptr[0] = (char) ((cp >> 6) | 128 | 64); 225 ptr[1] = (char) (cp & 0x3F) | 128; 226 return 2; 227 } 228 } else if (cp < 0x10000) { // fits in 3 bytes. 229 if (remaining >= 3) { 230 ptr[0] = (char) ((cp >> 12) | 128 | 64 | 32); 231 ptr[1] = (char) ((cp >> 6) & 0x3F) | 128; 232 ptr[2] = (char) (cp & 0x3F) | 128; 233 return 3; 234 } 235 } else if (cp <= 0x10FFFF) { // fits in 4 bytes. 236 if (remaining >= 4) { 237 ptr[0] = (char) ((cp >> 18) | 128 | 64 | 32 | 16); 238 ptr[1] = (char) ((cp >> 12) & 0x3F) | 128; 239 ptr[2] = (char) ((cp >> 6) & 0x3F) | 128; 240 ptr[3] = (char) (cp & 0x3F) | 128; 241 return 4; 242 } 243 } 244 245 return 0; 246} 247 248static char *CaseFoldUtf8String(const char *fname) 249{ 250 SDL_assert(fname != NULL); 251 const size_t allocation = (SDL_strlen(fname) + 1) * 3 * 4; 252 char *result = (char *) SDL_malloc(allocation); // lazy: just allocating the max needed. 253 if (!result) { 254 return NULL; 255 } 256 257 Uint32 codepoint; 258 char *ptr = result; 259 size_t remaining = allocation; 260 while ((codepoint = SDL_StepUTF8(&fname, NULL)) != 0) { 261 Uint32 folded[3]; 262 const int num_folded = SDL_CaseFoldUnicode(codepoint, folded); 263 SDL_assert(num_folded > 0); 264 SDL_assert(num_folded <= SDL_arraysize(folded)); 265 for (int i = 0; i < num_folded; i++) { 266 SDL_assert(remaining > 0); 267 const size_t rc = EncodeCodepointToUtf8(ptr, folded[i], remaining); 268 SDL_assert(rc > 0); 269 SDL_assert(rc < remaining); 270 remaining -= rc; 271 ptr += rc; 272 } 273 } 274 275 SDL_assert(remaining > 0); 276 remaining--; 277 *ptr = '\0'; 278 279 if (remaining > 0) { 280 SDL_assert(allocation > remaining); 281 ptr = (char *)SDL_realloc(result, allocation - remaining); // shrink it down. 282 if (ptr) { // shouldn't fail, but if it does, `result` is still valid. 283 result = ptr; 284 } 285 } 286 287 return result; 288} 289 290 291typedef struct GlobDirCallbackData 292{ 293 bool (*matcher)(const char *pattern, const char *str, bool *matched_to_dir); 294 const char *pattern; 295 int num_entries; 296 SDL_GlobFlags flags; 297 SDL_GlobEnumeratorFunc enumerator; 298 SDL_GlobGetPathInfoFunc getpathinfo; 299 void *fsuserdata; 300 size_t basedirlen; 301 SDL_IOStream *string_stream; 302} GlobDirCallbackData; 303 304static SDL_EnumerationResult SDLCALL GlobDirectoryCallback(void *userdata, const char *dirname, const char *fname) 305{ 306 SDL_assert(userdata != NULL); 307 SDL_assert(dirname != NULL); 308 SDL_assert(fname != NULL); 309 310 //SDL_Log("GlobDirectoryCallback('%s', '%s')", dirname, fname); 311 312 GlobDirCallbackData *data = (GlobDirCallbackData *) userdata; 313 314 // !!! FIXME: if we're careful, we can keep a single buffer in `data` that we push and pop paths off the end of as we walk the tree, 315 // !!! FIXME: and only casefold the new pieces instead of allocating and folding full paths for all of this. 316 317 char *fullpath = NULL; 318 if (SDL_asprintf(&fullpath, "%s%s", dirname, fname) < 0) { 319 return SDL_ENUM_FAILURE; 320 } 321 322 char *folded = NULL; 323 if (data->flags & SDL_GLOB_CASEINSENSITIVE) { 324 folded = CaseFoldUtf8String(fullpath); 325 if (!folded) { 326 return SDL_ENUM_FAILURE; 327 } 328 } 329 330 bool matched_to_dir = false; 331 const bool matched = data->matcher(data->pattern, (folded ? folded : fullpath) + data->basedirlen, &matched_to_dir); 332 //SDL_Log("GlobDirectoryCallback: Considered %spath='%s' vs pattern='%s': %smatched (matched_to_dir=%s)", folded ? "(folded) " : "", (folded ? folded : fullpath) + data->basedirlen, data->pattern, matched ? "" : "NOT ", matched_to_dir ? "TRUE" : "FALSE"); 333 SDL_free(folded); 334 335 if (matched) { 336 const char *subpath = fullpath + data->basedirlen; 337 const size_t slen = SDL_strlen(subpath) + 1; 338 if (SDL_WriteIO(data->string_stream, subpath, slen) != slen) { 339 SDL_free(fullpath); 340 return SDL_ENUM_FAILURE; // stop enumerating, return failure to the app. 341 } 342 data->num_entries++; 343 } 344 345 SDL_EnumerationResult result = SDL_ENUM_CONTINUE; // keep enumerating by default. 346 if (matched_to_dir) { 347 SDL_PathInfo info; 348 if (data->getpathinfo(fullpath, &info, data->fsuserdata) && (info.type == SDL_PATHTYPE_DIRECTORY)) { 349 //SDL_Log("GlobDirectoryCallback: Descending into subdir '%s'", fname); 350 if (!data->enumerator(fullpath, GlobDirectoryCallback, data, data->fsuserdata)) { 351 result = SDL_ENUM_FAILURE; 352 } 353 } 354 } 355 356 SDL_free(fullpath); 357 358 return result; 359} 360 361char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_GlobFlags flags, int *count, SDL_GlobEnumeratorFunc enumerator, SDL_GlobGetPathInfoFunc getpathinfo, void *userdata) 362{ 363 int dummycount; 364 if (!count) { 365 count = &dummycount; 366 } 367 *count = 0; 368 369 CHECK_PARAM(!path) { 370 SDL_InvalidParamError("path"); 371 return NULL; 372 } 373 374 // if path ends with any slash, chop them off, so we don't confuse the pattern matcher later. 375 char *pathcpy = NULL; 376 size_t pathlen = SDL_strlen(path); 377 if ((pathlen > 1) && ((path[pathlen-1] == '/') || (path[pathlen-1] == '\\'))) { 378 pathcpy = SDL_strdup(path); 379 if (!pathcpy) { 380 return NULL; 381 } 382 char *ptr = &pathcpy[pathlen-1]; 383 while ((ptr > pathcpy) && ((*ptr == '/') || (*ptr == '\\'))) { 384 *(ptr--) = '\0'; 385 --pathlen; 386 } 387 path = pathcpy; 388 } 389 390 if (!pattern) { 391 flags &= ~SDL_GLOB_CASEINSENSITIVE; // avoid some unnecessary allocations and work later. 392 } 393 394 char *folded = NULL; 395 if (flags & SDL_GLOB_CASEINSENSITIVE) { 396 SDL_assert(pattern != NULL); 397 folded = CaseFoldUtf8String(pattern); 398 if (!folded) { 399 SDL_free(pathcpy); 400 return NULL; 401 } 402 } 403 404 GlobDirCallbackData data; 405 SDL_zero(data); 406 data.string_stream = SDL_IOFromDynamicMem(); 407 if (!data.string_stream) { 408 SDL_free(folded); 409 SDL_free(pathcpy); 410 return NULL; 411 } 412 413 if (!pattern) { 414 data.matcher = EverythingMatch; // no pattern? Everything matches. 415 416 // !!! FIXME 417 //} else if (flags & SDL_GLOB_GITIGNORE) { 418 // data.matcher = GitIgnoreMatch; 419 420 } else { 421 data.matcher = WildcardMatch; 422 } 423 424 data.pattern = folded ? folded : pattern; 425 data.flags = flags; 426 data.enumerator = enumerator; 427 data.getpathinfo = getpathinfo; 428 data.fsuserdata = userdata; 429 430 data.basedirlen = 0; 431 if (*path) { 432 if (SDL_strcmp(path, "/") == 0 || SDL_strcmp(path, "\\") == 0) { 433 data.basedirlen = 1; 434 } else { 435 data.basedirlen = pathlen + 1; // +1 for the '/' we'll be adding. 436 } 437 } 438 439 char **result = NULL; 440 if (data.enumerator(path, GlobDirectoryCallback, &data, data.fsuserdata)) { 441 const size_t streamlen = (size_t) SDL_GetIOSize(data.string_stream); 442 const size_t buflen = streamlen + ((data.num_entries + 1) * sizeof (char *)); // +1 for NULL terminator at end of array. 443 result = (char **) SDL_malloc(buflen); 444 if (result) { 445 if (data.num_entries > 0) { 446 Sint64 iorc = SDL_SeekIO(data.string_stream, 0, SDL_IO_SEEK_SET); 447 SDL_assert(iorc == 0); // this should never fail for a memory stream! 448 char *ptr = (char *) (result + (data.num_entries + 1)); 449 iorc = SDL_ReadIO(data.string_stream, ptr, streamlen); 450 SDL_assert(iorc == (Sint64) streamlen); // this should never fail for a memory stream! 451 for (int i = 0; i < data.num_entries; i++) { 452 result[i] = ptr; 453 ptr += SDL_strlen(ptr) + 1; 454 } 455 } 456 result[data.num_entries] = NULL; // NULL terminate the list. 457 *count = data.num_entries; 458 } 459 } 460 461 SDL_CloseIO(data.string_stream); 462 SDL_free(folded); 463 SDL_free(pathcpy); 464 465 return result; 466} 467 468static bool GlobDirectoryGetPathInfo(const char *path, SDL_PathInfo *info, void *userdata) 469{ 470 return SDL_GetPathInfo(path, info); 471} 472 473static bool GlobDirectoryEnumerator(const char *path, SDL_EnumerateDirectoryCallback cb, void *cbuserdata, void *userdata) 474{ 475 return SDL_EnumerateDirectory(path, cb, cbuserdata); 476} 477 478char **SDL_GlobDirectory(const char *path, const char *pattern, SDL_GlobFlags flags, int *count) 479{ 480 //SDL_Log("SDL_GlobDirectory('%s', '%s') ...", path, pattern); 481 return SDL_InternalGlobDirectory(path, pattern, flags, count, GlobDirectoryEnumerator, GlobDirectoryGetPathInfo, NULL); 482} 483 484 485static char *CachedBasePath = NULL; 486 487const char *SDL_GetBasePath(void) 488{ 489 if (!CachedBasePath) { 490 CachedBasePath = SDL_SYS_GetBasePath(); 491 } 492 return CachedBasePath; 493} 494 495 496static char *CachedUserFolders[SDL_FOLDER_COUNT]; 497 498const char *SDL_GetUserFolder(SDL_Folder folder) 499{ 500 const int idx = (int) folder; 501 502 CHECK_PARAM((idx < 0) || (idx >= SDL_arraysize(CachedUserFolders))) { 503 SDL_InvalidParamError("folder"); 504 return NULL; 505 } 506 507 if (!CachedUserFolders[idx]) { 508 CachedUserFolders[idx] = SDL_SYS_GetUserFolder(folder); 509 } 510 return CachedUserFolders[idx]; 511} 512 513 514char *SDL_GetPrefPath(const char *org, const char *app) 515{ 516 CHECK_PARAM(!app) { 517 SDL_InvalidParamError("app"); 518 return NULL; 519 } 520 521 // if org is NULL, just make it "" so backends don't have to check both. 522 if (!org) { 523 org = ""; 524 } 525 526 return SDL_SYS_GetPrefPath(org, app); 527} 528 529char *SDL_GetCurrentDirectory(void) 530{ 531 return SDL_SYS_GetCurrentDirectory(); 532} 533 534void SDL_InitFilesystem(void) 535{ 536} 537 538void SDL_QuitFilesystem(void) 539{ 540 if (CachedBasePath) { 541 SDL_free(CachedBasePath); 542 CachedBasePath = NULL; 543 } 544 for (int i = 0; i < SDL_arraysize(CachedUserFolders); i++) { 545 if (CachedUserFolders[i]) { 546 SDL_free(CachedUserFolders[i]); 547 CachedUserFolders[i] = NULL; 548 } 549 } 550} 551 552[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.