Atlas - SDL_filesystem.c

Home / ext / SDL / src / filesystem Lines: 1 | Size: 17139 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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 } 386 path = pathcpy; 387 } 388 389 if (!pattern) { 390 flags &= ~SDL_GLOB_CASEINSENSITIVE; // avoid some unnecessary allocations and work later. 391 } 392 393 char *folded = NULL; 394 if (flags & SDL_GLOB_CASEINSENSITIVE) { 395 SDL_assert(pattern != NULL); 396 folded = CaseFoldUtf8String(pattern); 397 if (!folded) { 398 SDL_free(pathcpy); 399 return NULL; 400 } 401 } 402 403 GlobDirCallbackData data; 404 SDL_zero(data); 405 data.string_stream = SDL_IOFromDynamicMem(); 406 if (!data.string_stream) { 407 SDL_free(folded); 408 SDL_free(pathcpy); 409 return NULL; 410 } 411 412 if (!pattern) { 413 data.matcher = EverythingMatch; // no pattern? Everything matches. 414 415 // !!! FIXME 416 //} else if (flags & SDL_GLOB_GITIGNORE) { 417 // data.matcher = GitIgnoreMatch; 418 419 } else { 420 data.matcher = WildcardMatch; 421 } 422 423 data.pattern = folded ? folded : pattern; 424 data.flags = flags; 425 data.enumerator = enumerator; 426 data.getpathinfo = getpathinfo; 427 data.fsuserdata = userdata; 428 data.basedirlen = *path ? (SDL_strlen(path) + 1) : 0; // +1 for the '/' we'll be adding. 429 430 431 char **result = NULL; 432 if (data.enumerator(path, GlobDirectoryCallback, &data, data.fsuserdata)) { 433 const size_t streamlen = (size_t) SDL_GetIOSize(data.string_stream); 434 const size_t buflen = streamlen + ((data.num_entries + 1) * sizeof (char *)); // +1 for NULL terminator at end of array. 435 result = (char **) SDL_malloc(buflen); 436 if (result) { 437 if (data.num_entries > 0) { 438 Sint64 iorc = SDL_SeekIO(data.string_stream, 0, SDL_IO_SEEK_SET); 439 SDL_assert(iorc == 0); // this should never fail for a memory stream! 440 char *ptr = (char *) (result + (data.num_entries + 1)); 441 iorc = SDL_ReadIO(data.string_stream, ptr, streamlen); 442 SDL_assert(iorc == (Sint64) streamlen); // this should never fail for a memory stream! 443 for (int i = 0; i < data.num_entries; i++) { 444 result[i] = ptr; 445 ptr += SDL_strlen(ptr) + 1; 446 } 447 } 448 result[data.num_entries] = NULL; // NULL terminate the list. 449 *count = data.num_entries; 450 } 451 } 452 453 SDL_CloseIO(data.string_stream); 454 SDL_free(folded); 455 SDL_free(pathcpy); 456 457 return result; 458} 459 460static bool GlobDirectoryGetPathInfo(const char *path, SDL_PathInfo *info, void *userdata) 461{ 462 return SDL_GetPathInfo(path, info); 463} 464 465static bool GlobDirectoryEnumerator(const char *path, SDL_EnumerateDirectoryCallback cb, void *cbuserdata, void *userdata) 466{ 467 return SDL_EnumerateDirectory(path, cb, cbuserdata); 468} 469 470char **SDL_GlobDirectory(const char *path, const char *pattern, SDL_GlobFlags flags, int *count) 471{ 472 //SDL_Log("SDL_GlobDirectory('%s', '%s') ...", path, pattern); 473 return SDL_InternalGlobDirectory(path, pattern, flags, count, GlobDirectoryEnumerator, GlobDirectoryGetPathInfo, NULL); 474} 475 476 477static char *CachedBasePath = NULL; 478 479const char *SDL_GetBasePath(void) 480{ 481 if (!CachedBasePath) { 482 CachedBasePath = SDL_SYS_GetBasePath(); 483 } 484 return CachedBasePath; 485} 486 487 488static char *CachedUserFolders[SDL_FOLDER_COUNT]; 489 490const char *SDL_GetUserFolder(SDL_Folder folder) 491{ 492 const int idx = (int) folder; 493 494 CHECK_PARAM((idx < 0) || (idx >= SDL_arraysize(CachedUserFolders))) { 495 SDL_InvalidParamError("folder"); 496 return NULL; 497 } 498 499 if (!CachedUserFolders[idx]) { 500 CachedUserFolders[idx] = SDL_SYS_GetUserFolder(folder); 501 } 502 return CachedUserFolders[idx]; 503} 504 505 506char *SDL_GetPrefPath(const char *org, const char *app) 507{ 508 CHECK_PARAM(!app) { 509 SDL_InvalidParamError("app"); 510 return NULL; 511 } 512 513 // if org is NULL, just make it "" so backends don't have to check both. 514 if (!org) { 515 org = ""; 516 } 517 518 return SDL_SYS_GetPrefPath(org, app); 519} 520 521char *SDL_GetCurrentDirectory(void) 522{ 523 return SDL_SYS_GetCurrentDirectory(); 524} 525 526void SDL_InitFilesystem(void) 527{ 528} 529 530void SDL_QuitFilesystem(void) 531{ 532 if (CachedBasePath) { 533 SDL_free(CachedBasePath); 534 CachedBasePath = NULL; 535 } 536 for (int i = 0; i < SDL_arraysize(CachedUserFolders); i++) { 537 if (CachedUserFolders[i]) { 538 SDL_free(CachedUserFolders[i]); 539 CachedUserFolders[i] = NULL; 540 } 541 } 542} 543 544
[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.