2 * PhysicsFS; a portable, flexible file i/o abstraction.
4 * Documentation is in physfs.h. It's verbose, honest. :)
6 * Please see the file LICENSE.txt in the source's root directory.
8 * This file written by Ryan C. Gordon.
16 #define __PHYSICSFS_INTERNAL__
17 #include "physfs_internal.h"
20 typedef struct __PHYSFS_DIRHANDLE__
22 void *opaque; /* Instance data unique to the archiver. */
23 char *dirName; /* Path to archive in platform-dependent notation. */
24 char *mountPoint; /* Mountpoint in virtual file tree. */
25 const PHYSFS_Archiver *funcs; /* Ptr to archiver info for this handle. */
26 struct __PHYSFS_DIRHANDLE__ *next; /* linked list stuff. */
30 typedef struct __PHYSFS_FILEHANDLE__
32 void *opaque; /* Instance data unique to the archiver for this file. */
33 PHYSFS_uint8 forReading; /* Non-zero if reading, zero if write/append */
34 const DirHandle *dirHandle; /* Archiver instance that created this */
35 const PHYSFS_Archiver *funcs; /* Ptr to archiver info for this handle. */
36 PHYSFS_uint8 *buffer; /* Buffer, if set (NULL otherwise). Don't touch! */
37 PHYSFS_uint32 bufsize; /* Bufsize, if set (0 otherwise). Don't touch! */
38 PHYSFS_uint32 buffill; /* Buffer fill size. Don't touch! */
39 PHYSFS_uint32 bufpos; /* Buffer position. Don't touch! */
40 struct __PHYSFS_FILEHANDLE__ *next; /* linked list stuff. */
44 typedef struct __PHYSFS_ERRMSGTYPE__
49 struct __PHYSFS_ERRMSGTYPE__ *next;
53 /* The various i/o drivers...some of these may not be compiled in. */
54 extern const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_ZIP;
55 extern const PHYSFS_Archiver __PHYSFS_Archiver_ZIP;
56 extern const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_LZMA;
57 extern const PHYSFS_Archiver __PHYSFS_Archiver_LZMA;
58 extern const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_GRP;
59 extern const PHYSFS_Archiver __PHYSFS_Archiver_GRP;
60 extern const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_QPAK;
61 extern const PHYSFS_Archiver __PHYSFS_Archiver_QPAK;
62 extern const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_HOG;
63 extern const PHYSFS_Archiver __PHYSFS_Archiver_HOG;
64 extern const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_MVL;
65 extern const PHYSFS_Archiver __PHYSFS_Archiver_MVL;
66 extern const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_WAD;
67 extern const PHYSFS_Archiver __PHYSFS_Archiver_WAD;
68 extern const PHYSFS_Archiver __PHYSFS_Archiver_DIR;
71 static const PHYSFS_ArchiveInfo *supported_types[] =
73 #if (defined PHYSFS_SUPPORTS_ZIP)
74 &__PHYSFS_ArchiveInfo_ZIP,
76 #if (defined PHYSFS_SUPPORTS_7Z)
77 &__PHYSFS_ArchiveInfo_LZMA,
79 #if (defined PHYSFS_SUPPORTS_GRP)
80 &__PHYSFS_ArchiveInfo_GRP,
82 #if (defined PHYSFS_SUPPORTS_QPAK)
83 &__PHYSFS_ArchiveInfo_QPAK,
85 #if (defined PHYSFS_SUPPORTS_HOG)
86 &__PHYSFS_ArchiveInfo_HOG,
88 #if (defined PHYSFS_SUPPORTS_MVL)
89 &__PHYSFS_ArchiveInfo_MVL,
91 #if (defined PHYSFS_SUPPORTS_WAD)
92 &__PHYSFS_ArchiveInfo_WAD,
97 static const PHYSFS_Archiver *archivers[] =
99 &__PHYSFS_Archiver_DIR,
100 #if (defined PHYSFS_SUPPORTS_ZIP)
101 &__PHYSFS_Archiver_ZIP,
103 #if (defined PHYSFS_SUPPORTS_7Z)
104 &__PHYSFS_Archiver_LZMA,
106 #if (defined PHYSFS_SUPPORTS_GRP)
107 &__PHYSFS_Archiver_GRP,
109 #if (defined PHYSFS_SUPPORTS_QPAK)
110 &__PHYSFS_Archiver_QPAK,
112 #if (defined PHYSFS_SUPPORTS_HOG)
113 &__PHYSFS_Archiver_HOG,
115 #if (defined PHYSFS_SUPPORTS_MVL)
116 &__PHYSFS_Archiver_MVL,
118 #if (defined PHYSFS_SUPPORTS_WAD)
119 &__PHYSFS_Archiver_WAD,
126 /* General PhysicsFS state ... */
127 static int initialized = 0;
128 static ErrMsg *errorMessages = NULL;
129 static DirHandle *searchPath = NULL;
130 static DirHandle *writeDir = NULL;
131 static FileHandle *openWriteList = NULL;
132 static FileHandle *openReadList = NULL;
133 static char *baseDir = NULL;
134 static char *userDir = NULL;
135 static int allowSymLinks = 0;
138 static void *errorLock = NULL; /* protects error message list. */
139 static void *stateLock = NULL; /* protects other PhysFS static state. */
142 static int externalAllocator = 0;
143 PHYSFS_Allocator allocator;
152 const char *errorstr;
153 } EnumStringListCallbackData;
155 static void enumStringListCallback(void *data, const char *str)
159 EnumStringListCallbackData *pecd = (EnumStringListCallbackData *) data;
164 ptr = allocator.Realloc(pecd->list, (pecd->size + 2) * sizeof (char *));
165 newstr = (char *) allocator.Malloc(strlen(str) + 1);
167 pecd->list = (char **) ptr;
169 if ((ptr == NULL) || (newstr == NULL))
171 pecd->errorstr = ERR_OUT_OF_MEMORY;
172 pecd->list[pecd->size] = NULL;
173 PHYSFS_freeList(pecd->list);
178 pecd->list[pecd->size] = newstr;
180 } /* enumStringListCallback */
183 static char **doEnumStringList(void (*func)(PHYSFS_StringCallback, void *))
185 EnumStringListCallbackData ecd;
186 memset(&ecd, '\0', sizeof (ecd));
187 ecd.list = (char **) allocator.Malloc(sizeof (char *));
188 BAIL_IF_MACRO(ecd.list == NULL, ERR_OUT_OF_MEMORY, NULL);
189 func(enumStringListCallback, &ecd);
190 BAIL_IF_MACRO(ecd.errorstr != NULL, ecd.errorstr, NULL);
191 ecd.list[ecd.size] = NULL;
193 } /* doEnumStringList */
196 static void __PHYSFS_bubble_sort(void *a, PHYSFS_uint32 lo, PHYSFS_uint32 hi,
197 int (*cmpfn)(void *, PHYSFS_uint32, PHYSFS_uint32),
198 void (*swapfn)(void *, PHYSFS_uint32, PHYSFS_uint32))
206 for (i = lo; i < hi; i++)
208 if (cmpfn(a, i, i + 1) > 0)
215 } /* __PHYSFS_bubble_sort */
218 static void __PHYSFS_quick_sort(void *a, PHYSFS_uint32 lo, PHYSFS_uint32 hi,
219 int (*cmpfn)(void *, PHYSFS_uint32, PHYSFS_uint32),
220 void (*swapfn)(void *, PHYSFS_uint32, PHYSFS_uint32))
226 if ((hi - lo) <= PHYSFS_QUICKSORT_THRESHOLD)
227 __PHYSFS_bubble_sort(a, lo, hi, cmpfn, swapfn);
232 if (cmpfn(a, lo, i) > 0) swapfn(a, lo, i);
233 if (cmpfn(a, lo, hi) > 0) swapfn(a, lo, hi);
234 if (cmpfn(a, i, hi) > 0) swapfn(a, i, hi);
242 while(cmpfn(a, ++i, v) < 0) { /* do nothing */ }
243 while(cmpfn(a, --j, v) > 0) { /* do nothing */ }
250 __PHYSFS_quick_sort(a, lo, j, cmpfn, swapfn);
251 __PHYSFS_quick_sort(a, i+1, hi, cmpfn, swapfn);
253 } /* __PHYSFS_quick_sort */
256 void __PHYSFS_sort(void *entries, PHYSFS_uint32 max,
257 int (*cmpfn)(void *, PHYSFS_uint32, PHYSFS_uint32),
258 void (*swapfn)(void *, PHYSFS_uint32, PHYSFS_uint32))
261 * Quicksort w/ Bubblesort fallback algorithm inspired by code from here:
262 * http://www.cs.ubc.ca/spider/harrison/Java/sorting-demo.html
264 __PHYSFS_quick_sort(entries, 0, max - 1, cmpfn, swapfn);
265 } /* __PHYSFS_sort */
268 static ErrMsg *findErrorForCurrentThread(void)
273 if (errorLock != NULL)
274 __PHYSFS_platformGrabMutex(errorLock);
276 if (errorMessages != NULL)
278 tid = __PHYSFS_platformGetThreadID();
280 for (i = errorMessages; i != NULL; i = i->next)
284 if (errorLock != NULL)
285 __PHYSFS_platformReleaseMutex(errorLock);
291 if (errorLock != NULL)
292 __PHYSFS_platformReleaseMutex(errorLock);
294 return(NULL); /* no error available. */
295 } /* findErrorForCurrentThread */
298 void __PHYSFS_setError(const char *str)
305 err = findErrorForCurrentThread();
309 err = (ErrMsg *) allocator.Malloc(sizeof (ErrMsg));
311 return; /* uhh...? */
313 memset((void *) err, '\0', sizeof (ErrMsg));
314 err->tid = __PHYSFS_platformGetThreadID();
316 if (errorLock != NULL)
317 __PHYSFS_platformGrabMutex(errorLock);
319 err->next = errorMessages;
322 if (errorLock != NULL)
323 __PHYSFS_platformReleaseMutex(errorLock);
326 err->errorAvailable = 1;
327 strncpy(err->errorString, str, sizeof (err->errorString));
328 err->errorString[sizeof (err->errorString) - 1] = '\0';
329 } /* __PHYSFS_setError */
332 const char *PHYSFS_getLastError(void)
334 ErrMsg *err = findErrorForCurrentThread();
336 if ((err == NULL) || (!err->errorAvailable))
339 err->errorAvailable = 0;
340 return(err->errorString);
341 } /* PHYSFS_getLastError */
344 /* MAKE SURE that errorLock is held before calling this! */
345 static void freeErrorMessages(void)
350 for (i = errorMessages; i != NULL; i = next)
356 errorMessages = NULL;
357 } /* freeErrorMessages */
360 void PHYSFS_getLinkedVersion(PHYSFS_Version *ver)
364 ver->major = PHYSFS_VER_MAJOR;
365 ver->minor = PHYSFS_VER_MINOR;
366 ver->patch = PHYSFS_VER_PATCH;
368 } /* PHYSFS_getLinkedVersion */
371 static const char *find_filename_extension(const char *fname)
373 const char *retval = strchr(fname, '.');
374 const char *p = retval;
378 p = strchr(p + 1, '.');
384 retval++; /* skip '.' */
387 } /* find_filename_extension */
390 static DirHandle *tryOpenDir(const PHYSFS_Archiver *funcs,
391 const char *d, int forWriting)
393 DirHandle *retval = NULL;
394 if (funcs->isArchive(d, forWriting))
396 void *opaque = funcs->openArchive(d, forWriting);
399 retval = (DirHandle *) allocator.Malloc(sizeof (DirHandle));
401 funcs->dirClose(opaque);
404 memset(retval, '\0', sizeof (DirHandle));
405 retval->mountPoint = NULL;
406 retval->funcs = funcs;
407 retval->opaque = opaque;
416 static DirHandle *openDirectory(const char *d, int forWriting)
418 DirHandle *retval = NULL;
419 const PHYSFS_Archiver **i;
422 BAIL_IF_MACRO(!__PHYSFS_platformExists(d), ERR_NO_SUCH_FILE, NULL);
424 ext = find_filename_extension(d);
427 /* Look for archivers with matching file extensions first... */
428 for (i = archivers; (*i != NULL) && (retval == NULL); i++)
430 if (__PHYSFS_stricmpASCII(ext, (*i)->info->extension) == 0)
431 retval = tryOpenDir(*i, d, forWriting);
434 /* failing an exact file extension match, try all the others... */
435 for (i = archivers; (*i != NULL) && (retval == NULL); i++)
437 if (__PHYSFS_stricmpASCII(ext, (*i)->info->extension) != 0)
438 retval = tryOpenDir(*i, d, forWriting);
442 else /* no extension? Try them all. */
444 for (i = archivers; (*i != NULL) && (retval == NULL); i++)
445 retval = tryOpenDir(*i, d, forWriting);
448 BAIL_IF_MACRO(retval == NULL, ERR_UNSUPPORTED_ARCHIVE, NULL);
450 } /* openDirectory */
454 * Make a platform-independent path string sane. Doesn't actually check the
455 * file hierarchy, it just cleans up the string.
456 * (dst) must be a buffer at least as big as (src), as this is where the
457 * cleaned up string is deposited.
458 * If there are illegal bits in the path (".." entries, etc) then we
459 * return zero and (dst) is undefined. Non-zero if the path was sanitized.
461 static int sanitizePlatformIndependentPath(const char *src, char *dst)
466 while (*src == '/') /* skip initial '/' chars... */
474 if ((ch == ':') || (ch == '\\')) /* illegal chars in a physfs path. */
475 BAIL_MACRO(ERR_INSECURE_FNAME, 0);
477 if (ch == '/') /* path separator. */
479 *dst = '\0'; /* "." and ".." are illegal pathnames. */
480 if ((strcmp(prev, ".") == 0) || (strcmp(prev, "..") == 0))
481 BAIL_MACRO(ERR_INSECURE_FNAME, 0);
483 while (*src == '/') /* chop out doubles... */
486 if (*src == '\0') /* ends with a pathsep? */
487 break; /* we're done, don't add final pathsep to dst. */
493 } while (ch != '\0');
496 } /* sanitizePlatformIndependentPath */
500 * Figure out if (fname) is part of (h)'s mountpoint. (fname) must be an
501 * output from sanitizePlatformIndependentPath(), so that it is in a known
504 * This only finds legitimate segments of a mountpoint. If the mountpoint is
505 * "/a/b/c" and (fname) is "/a/b/c", "/", or "/a/b/c/d", then the results are
506 * all zero. "/a/b" will succeed, though.
508 static int partOfMountPoint(DirHandle *h, char *fname)
510 /* !!! FIXME: This code feels gross. */
512 size_t len, mntpntlen;
514 if (h->mountPoint == NULL)
516 else if (*fname == '\0')
520 mntpntlen = strlen(h->mountPoint);
521 if (len > mntpntlen) /* can't be a subset of mountpoint. */
524 /* if true, must be not a match or a complete match, but not a subset. */
525 if ((len + 1) == mntpntlen)
528 rc = strncmp(fname, h->mountPoint, len); /* !!! FIXME: case insensitive? */
530 return(0); /* not a match. */
532 /* make sure /a/b matches /a/b/ and not /a/bc ... */
533 return(h->mountPoint[len] == '/');
534 } /* partOfMountPoint */
537 static DirHandle *createDirHandle(const char *newDir,
538 const char *mountPoint,
541 DirHandle *dirHandle = NULL;
542 char *tmpmntpnt = NULL;
544 GOTO_IF_MACRO(!newDir, ERR_INVALID_ARGUMENT, badDirHandle);
545 if (mountPoint != NULL)
547 const size_t len = strlen(mountPoint) + 1;
548 tmpmntpnt = (char *) __PHYSFS_smallAlloc(len);
549 GOTO_IF_MACRO(!tmpmntpnt, ERR_OUT_OF_MEMORY, badDirHandle);
550 if (!sanitizePlatformIndependentPath(mountPoint, tmpmntpnt))
552 mountPoint = tmpmntpnt; /* sanitized version. */
555 dirHandle = openDirectory(newDir, forWriting);
556 GOTO_IF_MACRO(!dirHandle, NULL, badDirHandle);
558 dirHandle->dirName = (char *) allocator.Malloc(strlen(newDir) + 1);
559 GOTO_IF_MACRO(!dirHandle->dirName, ERR_OUT_OF_MEMORY, badDirHandle);
560 strcpy(dirHandle->dirName, newDir);
562 if ((mountPoint != NULL) && (*mountPoint != '\0'))
564 dirHandle->mountPoint = (char *)allocator.Malloc(strlen(mountPoint)+2);
565 GOTO_IF_MACRO(!dirHandle->mountPoint, ERR_OUT_OF_MEMORY, badDirHandle);
566 strcpy(dirHandle->mountPoint, mountPoint);
567 strcat(dirHandle->mountPoint, "/");
570 __PHYSFS_smallFree(tmpmntpnt);
574 if (dirHandle != NULL)
576 dirHandle->funcs->dirClose(dirHandle->opaque);
577 allocator.Free(dirHandle->dirName);
578 allocator.Free(dirHandle->mountPoint);
579 allocator.Free(dirHandle);
582 __PHYSFS_smallFree(tmpmntpnt);
584 } /* createDirHandle */
587 /* MAKE SURE you've got the stateLock held before calling this! */
588 static int freeDirHandle(DirHandle *dh, FileHandle *openList)
595 for (i = openList; i != NULL; i = i->next)
596 BAIL_IF_MACRO(i->dirHandle == dh, ERR_FILES_STILL_OPEN, 0);
598 dh->funcs->dirClose(dh->opaque);
599 allocator.Free(dh->dirName);
600 allocator.Free(dh->mountPoint);
603 } /* freeDirHandle */
606 static char *calculateUserDir(void)
608 char *retval = __PHYSFS_platformGetUserDir();
611 /* make sure it really exists and is normalized. */
612 char *ptr = __PHYSFS_platformRealPath(retval);
613 allocator.Free(retval);
619 const char *dirsep = PHYSFS_getDirSeparator();
620 const char *uname = __PHYSFS_platformGetUserName();
621 const char *str = (uname != NULL) ? uname : "default";
623 retval = (char *) allocator.Malloc(strlen(baseDir) + strlen(str) +
627 __PHYSFS_setError(ERR_OUT_OF_MEMORY);
629 sprintf(retval, "%susers%s%s", baseDir, dirsep, str);
631 allocator.Free((void *) uname);
635 } /* calculateUserDir */
638 static int appendDirSep(char **dir)
640 const char *dirsep = PHYSFS_getDirSeparator();
643 if (strcmp((*dir + strlen(*dir)) - strlen(dirsep), dirsep) == 0)
646 ptr = (char *) allocator.Realloc(*dir, strlen(*dir) + strlen(dirsep) + 1);
649 allocator.Free(*dir);
659 static char *calculateBaseDir(const char *argv0)
662 const char *dirsep = NULL;
665 /* Give the platform layer first shot at this. */
666 retval = __PHYSFS_platformCalcBaseDir(argv0);
670 /* We need argv0 to go on. */
671 BAIL_IF_MACRO(argv0 == NULL, ERR_ARGV0_IS_NULL, NULL);
673 dirsep = PHYSFS_getDirSeparator();
674 if (strlen(dirsep) == 1) /* fast path. */
675 ptr = strrchr(argv0, *dirsep);
678 ptr = strstr(argv0, dirsep);
685 p = strstr(p + 1, dirsep);
692 size_t size = (size_t) (ptr - argv0);
693 retval = (char *) allocator.Malloc(size + 1);
694 BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
695 memcpy(retval, argv0, size);
700 /* argv0 wasn't helpful. */
701 BAIL_MACRO(ERR_INVALID_ARGUMENT, NULL);
703 } /* calculateBaseDir */
706 static int initializeMutexes(void)
708 errorLock = __PHYSFS_platformCreateMutex();
709 if (errorLock == NULL)
710 goto initializeMutexes_failed;
712 stateLock = __PHYSFS_platformCreateMutex();
713 if (stateLock == NULL)
714 goto initializeMutexes_failed;
716 return(1); /* success. */
718 initializeMutexes_failed:
719 if (errorLock != NULL)
720 __PHYSFS_platformDestroyMutex(errorLock);
722 if (stateLock != NULL)
723 __PHYSFS_platformDestroyMutex(stateLock);
725 errorLock = stateLock = NULL;
726 return(0); /* failed. */
727 } /* initializeMutexes */
730 static void setDefaultAllocator(void);
732 int PHYSFS_init(const char *argv0)
736 BAIL_IF_MACRO(initialized, ERR_IS_INITIALIZED, 0);
738 if (!externalAllocator)
739 setDefaultAllocator();
741 if (allocator.Init != NULL)
742 BAIL_IF_MACRO(!allocator.Init(), NULL, 0);
744 BAIL_IF_MACRO(!__PHYSFS_platformInit(), NULL, 0);
746 BAIL_IF_MACRO(!initializeMutexes(), NULL, 0);
748 baseDir = calculateBaseDir(argv0);
749 BAIL_IF_MACRO(baseDir == NULL, NULL, 0);
751 /* !!! FIXME: only call this if we got this from argv0 (unreliable). */
752 ptr = __PHYSFS_platformRealPath(baseDir);
753 allocator.Free(baseDir);
754 BAIL_IF_MACRO(ptr == NULL, NULL, 0);
757 BAIL_IF_MACRO(!appendDirSep(&baseDir), NULL, 0);
759 userDir = calculateUserDir();
760 if ((userDir == NULL) || (!appendDirSep(&userDir)))
762 allocator.Free(baseDir);
769 /* This makes sure that the error subsystem is initialized. */
770 __PHYSFS_setError(PHYSFS_getLastError());
776 /* MAKE SURE you hold stateLock before calling this! */
777 static int closeFileHandleList(FileHandle **list)
780 FileHandle *next = NULL;
782 for (i = *list; i != NULL; i = next)
785 if (!i->funcs->fileClose(i->opaque))
796 } /* closeFileHandleList */
799 /* MAKE SURE you hold the stateLock before calling this! */
800 static void freeSearchPath(void)
803 DirHandle *next = NULL;
805 closeFileHandleList(&openReadList);
807 if (searchPath != NULL)
809 for (i = searchPath; i != NULL; i = next)
812 freeDirHandle(i, openReadList);
816 } /* freeSearchPath */
819 int PHYSFS_deinit(void)
821 BAIL_IF_MACRO(!initialized, ERR_NOT_INITIALIZED, 0);
822 BAIL_IF_MACRO(!__PHYSFS_platformDeinit(), NULL, 0);
824 closeFileHandleList(&openWriteList);
825 BAIL_IF_MACRO(!PHYSFS_setWriteDir(NULL), ERR_FILES_STILL_OPEN, 0);
832 allocator.Free(baseDir);
838 allocator.Free(userDir);
845 __PHYSFS_platformDestroyMutex(errorLock);
846 __PHYSFS_platformDestroyMutex(stateLock);
848 if (allocator.Deinit != NULL)
851 errorLock = stateLock = NULL;
853 } /* PHYSFS_deinit */
856 int PHYSFS_isInit(void)
859 } /* PHYSFS_isInit */
862 const PHYSFS_ArchiveInfo **PHYSFS_supportedArchiveTypes(void)
864 return(supported_types);
865 } /* PHYSFS_supportedArchiveTypes */
868 void PHYSFS_freeList(void *list)
871 for (i = (void **) list; *i != NULL; i++)
874 allocator.Free(list);
875 } /* PHYSFS_freeList */
878 const char *PHYSFS_getDirSeparator(void)
880 return(__PHYSFS_platformDirSeparator);
881 } /* PHYSFS_getDirSeparator */
884 char **PHYSFS_getCdRomDirs(void)
886 return(doEnumStringList(__PHYSFS_platformDetectAvailableCDs));
887 } /* PHYSFS_getCdRomDirs */
890 void PHYSFS_getCdRomDirsCallback(PHYSFS_StringCallback callback, void *data)
892 __PHYSFS_platformDetectAvailableCDs(callback, data);
893 } /* PHYSFS_getCdRomDirsCallback */
896 const char *PHYSFS_getBaseDir(void)
898 return(baseDir); /* this is calculated in PHYSFS_init()... */
899 } /* PHYSFS_getBaseDir */
902 const char *PHYSFS_getUserDir(void)
904 return(userDir); /* this is calculated in PHYSFS_init()... */
905 } /* PHYSFS_getUserDir */
908 const char *PHYSFS_getWriteDir(void)
910 const char *retval = NULL;
912 __PHYSFS_platformGrabMutex(stateLock);
913 if (writeDir != NULL)
914 retval = writeDir->dirName;
915 __PHYSFS_platformReleaseMutex(stateLock);
918 } /* PHYSFS_getWriteDir */
921 int PHYSFS_setWriteDir(const char *newDir)
925 __PHYSFS_platformGrabMutex(stateLock);
927 if (writeDir != NULL)
929 BAIL_IF_MACRO_MUTEX(!freeDirHandle(writeDir, openWriteList), NULL,
936 writeDir = createDirHandle(newDir, NULL, 1);
937 retval = (writeDir != NULL);
940 __PHYSFS_platformReleaseMutex(stateLock);
943 } /* PHYSFS_setWriteDir */
946 int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath)
949 DirHandle *prev = NULL;
952 BAIL_IF_MACRO(newDir == NULL, ERR_INVALID_ARGUMENT, 0);
954 if (mountPoint == NULL)
957 __PHYSFS_platformGrabMutex(stateLock);
959 for (i = searchPath; i != NULL; i = i->next)
961 /* already in search path? */
962 BAIL_IF_MACRO_MUTEX(strcmp(newDir, i->dirName)==0, NULL, stateLock, 1);
966 dh = createDirHandle(newDir, mountPoint, 0);
967 BAIL_IF_MACRO_MUTEX(dh == NULL, NULL, stateLock, 0);
978 dh->next = searchPath;
982 __PHYSFS_platformReleaseMutex(stateLock);
987 int PHYSFS_addToSearchPath(const char *newDir, int appendToPath)
989 return(PHYSFS_mount(newDir, NULL, appendToPath));
990 } /* PHYSFS_addToSearchPath */
993 int PHYSFS_removeFromSearchPath(const char *oldDir)
996 DirHandle *prev = NULL;
997 DirHandle *next = NULL;
999 BAIL_IF_MACRO(oldDir == NULL, ERR_INVALID_ARGUMENT, 0);
1001 __PHYSFS_platformGrabMutex(stateLock);
1002 for (i = searchPath; i != NULL; i = i->next)
1004 if (strcmp(i->dirName, oldDir) == 0)
1007 BAIL_IF_MACRO_MUTEX(!freeDirHandle(i, openReadList), NULL,
1015 BAIL_MACRO_MUTEX(NULL, stateLock, 1);
1020 BAIL_MACRO_MUTEX(ERR_NOT_IN_SEARCH_PATH, stateLock, 0);
1021 } /* PHYSFS_removeFromSearchPath */
1024 char **PHYSFS_getSearchPath(void)
1026 return(doEnumStringList(PHYSFS_getSearchPathCallback));
1027 } /* PHYSFS_getSearchPath */
1030 const char *PHYSFS_getMountPoint(const char *dir)
1033 __PHYSFS_platformGrabMutex(stateLock);
1034 for (i = searchPath; i != NULL; i = i->next)
1036 if (strcmp(i->dirName, dir) == 0)
1038 const char *retval = ((i->mountPoint) ? i->mountPoint : "/");
1039 __PHYSFS_platformReleaseMutex(stateLock);
1043 __PHYSFS_platformReleaseMutex(stateLock);
1045 BAIL_MACRO(ERR_NOT_IN_SEARCH_PATH, NULL);
1046 } /* PHYSFS_getMountPoint */
1049 void PHYSFS_getSearchPathCallback(PHYSFS_StringCallback callback, void *data)
1053 __PHYSFS_platformGrabMutex(stateLock);
1055 for (i = searchPath; i != NULL; i = i->next)
1056 callback(data, i->dirName);
1058 __PHYSFS_platformReleaseMutex(stateLock);
1059 } /* PHYSFS_getSearchPathCallback */
1062 /* Split out to avoid stack allocation in a loop. */
1063 static void setSaneCfgAddPath(const char *i, const size_t l, const char *dirsep,
1066 const char *d = PHYSFS_getRealDir(i);
1067 const size_t allocsize = strlen(d) + strlen(dirsep) + l + 1;
1068 char *str = (char *) __PHYSFS_smallAlloc(allocsize);
1071 sprintf(str, "%s%s%s", d, dirsep, i);
1072 PHYSFS_addToSearchPath(str, archivesFirst == 0);
1073 __PHYSFS_smallFree(str);
1075 } /* setSaneCfgAddPath */
1078 int PHYSFS_setSaneConfig(const char *organization, const char *appName,
1079 const char *archiveExt, int includeCdRoms,
1082 const char *basedir = PHYSFS_getBaseDir();
1083 const char *userdir = PHYSFS_getUserDir();
1084 const char *dirsep = PHYSFS_getDirSeparator();
1085 PHYSFS_uint64 len = 0;
1088 BAIL_IF_MACRO(!initialized, ERR_NOT_INITIALIZED, 0);
1090 /* set write dir... */
1091 len = (strlen(userdir) + (strlen(organization) * 2) +
1092 (strlen(appName) * 2) + (strlen(dirsep) * 3) + 2);
1094 str = (char *) __PHYSFS_smallAlloc(len);
1096 BAIL_IF_MACRO(str == NULL, ERR_OUT_OF_MEMORY, 0);
1097 sprintf(str, "%s.%s%s%s", userdir, organization, dirsep, appName);
1099 if (!PHYSFS_setWriteDir(str))
1102 sprintf(str, ".%s/%s", organization, appName);
1103 if ( (PHYSFS_setWriteDir(userdir)) &&
1104 (PHYSFS_mkdir(str)) )
1106 sprintf(str, "%s.%s%s%s", userdir, organization, dirsep, appName);
1107 if (!PHYSFS_setWriteDir(str))
1117 PHYSFS_setWriteDir(NULL); /* just in case. */
1118 __PHYSFS_smallFree(str);
1119 BAIL_MACRO(ERR_CANT_SET_WRITE_DIR, 0);
1123 /* Put write dir first in search path... */
1124 PHYSFS_addToSearchPath(str, 0);
1125 __PHYSFS_smallFree(str);
1127 /* Put base path on search path... */
1128 PHYSFS_addToSearchPath(basedir, 1);
1130 /* handle CD-ROMs... */
1133 char **cds = PHYSFS_getCdRomDirs();
1135 for (i = cds; *i != NULL; i++)
1136 PHYSFS_addToSearchPath(*i, 1);
1138 PHYSFS_freeList(cds);
1141 /* Root out archives, and add them to search path... */
1142 if (archiveExt != NULL)
1144 char **rc = PHYSFS_enumerateFiles("/");
1146 size_t extlen = strlen(archiveExt);
1149 for (i = rc; *i != NULL; i++)
1151 size_t l = strlen(*i);
1152 if ((l > extlen) && ((*i)[l - extlen - 1] == '.'))
1154 ext = (*i) + (l - extlen);
1155 if (__PHYSFS_stricmpASCII(ext, archiveExt) == 0)
1156 setSaneCfgAddPath(*i, l, dirsep, archivesFirst);
1160 PHYSFS_freeList(rc);
1164 } /* PHYSFS_setSaneConfig */
1167 void PHYSFS_permitSymbolicLinks(int allow)
1169 allowSymLinks = allow;
1170 } /* PHYSFS_permitSymbolicLinks */
1173 int PHYSFS_symbolicLinksPermitted(void)
1175 return(allowSymLinks);
1176 } /* PHYSFS_symbolicLinksPermitted */
1179 /* string manipulation in C makes my ass itch. */
1180 char *__PHYSFS_convertToDependent(const char *prepend,
1181 const char *dirName,
1184 const char *dirsep = __PHYSFS_platformDirSeparator;
1185 size_t sepsize = strlen(dirsep);
1191 while (*dirName == '/') /* !!! FIXME: pass through sanitize function. */
1194 allocSize = strlen(dirName) + 1;
1195 if (prepend != NULL)
1196 allocSize += strlen(prepend) + sepsize;
1198 allocSize += strlen(append) + sepsize;
1200 /* make sure there's enough space if the dir separator is bigger. */
1203 str = (char *) dirName;
1206 str = strchr(str, '/');
1209 allocSize += (sepsize - 1);
1212 } while (str != NULL);
1215 str = (char *) allocator.Malloc(allocSize);
1216 BAIL_IF_MACRO(str == NULL, ERR_OUT_OF_MEMORY, NULL);
1218 if (prepend == NULL)
1222 strcpy(str, prepend);
1223 strcat(str, dirsep);
1226 for (i1 = (char *) dirName, i2 = str + strlen(str); *i1; i1++, i2++)
1242 strcat(str, dirsep);
1243 strcat(str, append);
1247 } /* __PHYSFS_convertToDependent */
1251 * Verify that (fname) (in platform-independent notation), in relation
1252 * to (h) is secure. That means that each element of fname is checked
1253 * for symlinks (if they aren't permitted). This also allows for quick
1254 * rejection of files that exist outside an archive's mountpoint.
1256 * With some exceptions (like PHYSFS_mkdir(), which builds multiple subdirs
1257 * at a time), you should always pass zero for "allowMissing" for efficiency.
1259 * (fname) must point to an output from sanitizePlatformIndependentPath(),
1260 * since it will make sure that path names are in the right format for
1261 * passing certain checks. It will also do checks for "insecure" pathnames
1262 * like ".." which should be done once instead of once per archive. This also
1263 * gives us license to treat (fname) as scratch space in this function.
1265 * Returns non-zero if string is safe, zero if there's a security issue.
1266 * PHYSFS_getLastError() will specify what was wrong. (*fname) will be
1267 * updated to point past any mount point elements so it is prepared to
1268 * be used with the archiver directly.
1270 static int verifyPath(DirHandle *h, char **_fname, int allowMissing)
1272 char *fname = *_fname;
1277 if (*fname == '\0') /* quick rejection. */
1280 /* !!! FIXME: This codeblock sucks. */
1281 if (h->mountPoint != NULL) /* NULL mountpoint means "/". */
1283 size_t mntpntlen = strlen(h->mountPoint);
1284 size_t len = strlen(fname);
1285 assert(mntpntlen > 1); /* root mount points should be NULL. */
1286 /* not under the mountpoint, so skip this archive. */
1287 BAIL_IF_MACRO(len < mntpntlen-1, ERR_NO_SUCH_PATH, 0);
1288 /* !!! FIXME: Case insensitive? */
1289 retval = strncmp(h->mountPoint, fname, mntpntlen-1);
1290 BAIL_IF_MACRO(retval != 0, ERR_NO_SUCH_PATH, 0);
1291 if (len > mntpntlen-1) /* corner case... */
1292 BAIL_IF_MACRO(fname[mntpntlen-1] != '/', ERR_NO_SUCH_PATH, 0);
1293 fname += mntpntlen-1; /* move to start of actual archive path. */
1296 *_fname = fname; /* skip mountpoint for later use. */
1297 retval = 1; /* may be reset, below. */
1306 end = strchr(start, '/');
1308 if (end != NULL) *end = '\0';
1309 rc = h->funcs->isSymLink(h->opaque, fname, &retval);
1310 if (end != NULL) *end = '/';
1312 BAIL_IF_MACRO(rc, ERR_SYMLINK_DISALLOWED, 0); /* insecure. */
1314 /* break out early if path element is missing. */
1318 * We need to clear it if it's the last element of the path,
1319 * since this might be a non-existant file we're opening
1322 if ((end == NULL) || (allowMissing))
1338 static int doMkdir(const char *_dname, char *dname)
1344 int exists = 1; /* force existance check on first path element. */
1346 BAIL_IF_MACRO(!sanitizePlatformIndependentPath(_dname, dname), NULL, 0);
1348 __PHYSFS_platformGrabMutex(stateLock);
1349 BAIL_IF_MACRO_MUTEX(writeDir == NULL, ERR_NO_WRITE_DIR, stateLock, 0);
1351 BAIL_IF_MACRO_MUTEX(!verifyPath(h, &dname, 1), NULL, stateLock, 0);
1356 end = strchr(start, '/');
1360 /* only check for existance if all parent dirs existed, too... */
1362 retval = h->funcs->isDirectory(h->opaque, dname, &exists);
1365 retval = h->funcs->mkdir(h->opaque, dname);
1377 __PHYSFS_platformReleaseMutex(stateLock);
1382 int PHYSFS_mkdir(const char *_dname)
1388 BAIL_IF_MACRO(_dname == NULL, ERR_INVALID_ARGUMENT, 0);
1389 len = strlen(_dname) + 1;
1390 dname = (char *) __PHYSFS_smallAlloc(len);
1391 BAIL_IF_MACRO(dname == NULL, ERR_OUT_OF_MEMORY, 0);
1392 retval = doMkdir(_dname, dname);
1393 __PHYSFS_smallFree(dname);
1395 } /* PHYSFS_mkdir */
1398 static int doDelete(const char *_fname, char *fname)
1402 BAIL_IF_MACRO(!sanitizePlatformIndependentPath(_fname, fname), NULL, 0);
1404 __PHYSFS_platformGrabMutex(stateLock);
1406 BAIL_IF_MACRO_MUTEX(writeDir == NULL, ERR_NO_WRITE_DIR, stateLock, 0);
1408 BAIL_IF_MACRO_MUTEX(!verifyPath(h, &fname, 0), NULL, stateLock, 0);
1409 retval = h->funcs->remove(h->opaque, fname);
1411 __PHYSFS_platformReleaseMutex(stateLock);
1416 int PHYSFS_delete(const char *_fname)
1422 BAIL_IF_MACRO(_fname == NULL, ERR_INVALID_ARGUMENT, 0);
1423 len = strlen(_fname) + 1;
1424 fname = (char *) __PHYSFS_smallAlloc(len);
1425 BAIL_IF_MACRO(fname == NULL, ERR_OUT_OF_MEMORY, 0);
1426 retval = doDelete(_fname, fname);
1427 __PHYSFS_smallFree(fname);
1429 } /* PHYSFS_delete */
1432 const char *PHYSFS_getRealDir(const char *_fname)
1434 const char *retval = NULL;
1438 BAIL_IF_MACRO(_fname == NULL, ERR_INVALID_ARGUMENT, NULL);
1439 len = strlen(_fname) + 1;
1440 fname = __PHYSFS_smallAlloc(len);
1441 BAIL_IF_MACRO(fname == NULL, ERR_OUT_OF_MEMORY, NULL);
1442 if (sanitizePlatformIndependentPath(_fname, fname))
1445 __PHYSFS_platformGrabMutex(stateLock);
1446 for (i = searchPath; ((i != NULL) && (retval == NULL)); i = i->next)
1448 char *arcfname = fname;
1449 if (partOfMountPoint(i, arcfname))
1450 retval = i->dirName;
1451 else if (verifyPath(i, &arcfname, 0))
1453 if (i->funcs->exists(i->opaque, arcfname))
1454 retval = i->dirName;
1457 __PHYSFS_platformReleaseMutex(stateLock);
1460 __PHYSFS_smallFree(fname);
1462 } /* PHYSFS_getRealDir */
1465 static int locateInStringList(const char *str,
1469 PHYSFS_uint32 len = *pos;
1470 PHYSFS_uint32 half_len;
1471 PHYSFS_uint32 lo = 0;
1472 PHYSFS_uint32 middle;
1477 half_len = len >> 1;
1478 middle = lo + half_len;
1479 cmp = strcmp(list[middle], str);
1481 if (cmp == 0) /* it's in the list already. */
1488 len -= half_len + 1;
1494 } /* locateInStringList */
1497 static void enumFilesCallback(void *data, const char *origdir, const char *str)
1502 EnumStringListCallbackData *pecd = (EnumStringListCallbackData *) data;
1505 * See if file is in the list already, and if not, insert it in there
1509 if (locateInStringList(str, pecd->list, &pos))
1510 return; /* already in the list. */
1512 ptr = allocator.Realloc(pecd->list, (pecd->size + 2) * sizeof (char *));
1513 newstr = (char *) allocator.Malloc(strlen(str) + 1);
1515 pecd->list = (char **) ptr;
1517 if ((ptr == NULL) || (newstr == NULL))
1518 return; /* better luck next time. */
1520 strcpy(newstr, str);
1522 if (pos != pecd->size)
1524 memmove(&pecd->list[pos+1], &pecd->list[pos],
1525 sizeof (char *) * ((pecd->size) - pos));
1528 pecd->list[pos] = newstr;
1530 } /* enumFilesCallback */
1533 char **PHYSFS_enumerateFiles(const char *path)
1535 EnumStringListCallbackData ecd;
1536 memset(&ecd, '\0', sizeof (ecd));
1537 ecd.list = (char **) allocator.Malloc(sizeof (char *));
1538 BAIL_IF_MACRO(ecd.list == NULL, ERR_OUT_OF_MEMORY, NULL);
1539 PHYSFS_enumerateFilesCallback(path, enumFilesCallback, &ecd);
1540 ecd.list[ecd.size] = NULL;
1542 } /* PHYSFS_enumerateFiles */
1546 * Broke out to seperate function so we can use stack allocation gratuitously.
1548 static void enumerateFromMountPoint(DirHandle *i, const char *arcfname,
1549 PHYSFS_EnumFilesCallback callback,
1550 const char *_fname, void *data)
1552 const size_t len = strlen(arcfname);
1555 const size_t slen = strlen(i->mountPoint) + 1;
1556 char *mountPoint = (char *) __PHYSFS_smallAlloc(slen);
1558 if (mountPoint == NULL)
1559 return; /* oh well. */
1561 strcpy(mountPoint, i->mountPoint);
1562 ptr = mountPoint + ((len) ? len + 1 : 0);
1563 end = strchr(ptr, '/');
1564 assert(end); /* should always find a terminating '/'. */
1566 callback(data, _fname, ptr);
1567 __PHYSFS_smallFree(mountPoint);
1568 } /* enumerateFromMountPoint */
1571 /* !!! FIXME: this should report error conditions. */
1572 void PHYSFS_enumerateFilesCallback(const char *_fname,
1573 PHYSFS_EnumFilesCallback callback,
1579 BAIL_IF_MACRO(_fname == NULL, ERR_INVALID_ARGUMENT, ) /*0*/;
1580 BAIL_IF_MACRO(callback == NULL, ERR_INVALID_ARGUMENT, ) /*0*/;
1582 len = strlen(_fname) + 1;
1583 fname = (char *) __PHYSFS_smallAlloc(len);
1584 BAIL_IF_MACRO(fname == NULL, ERR_OUT_OF_MEMORY, ) /*0*/;
1586 if (sanitizePlatformIndependentPath(_fname, fname))
1591 __PHYSFS_platformGrabMutex(stateLock);
1592 noSyms = !allowSymLinks;
1593 for (i = searchPath; i != NULL; i = i->next)
1595 char *arcfname = fname;
1596 if (partOfMountPoint(i, arcfname))
1597 enumerateFromMountPoint(i, arcfname, callback, _fname, data);
1599 else if (verifyPath(i, &arcfname, 0))
1601 i->funcs->enumerateFiles(i->opaque, arcfname, noSyms,
1602 callback, _fname, data);
1605 __PHYSFS_platformReleaseMutex(stateLock);
1608 __PHYSFS_smallFree(fname);
1609 } /* PHYSFS_enumerateFilesCallback */
1612 int PHYSFS_exists(const char *fname)
1614 return(PHYSFS_getRealDir(fname) != NULL);
1615 } /* PHYSFS_exists */
1618 PHYSFS_sint64 PHYSFS_getLastModTime(const char *_fname)
1620 PHYSFS_sint64 retval = -1;
1624 BAIL_IF_MACRO(_fname == NULL, ERR_INVALID_ARGUMENT, -1);
1625 len = strlen(_fname) + 1;
1626 fname = (char *) __PHYSFS_smallAlloc(len);
1627 BAIL_IF_MACRO(fname == NULL, ERR_OUT_OF_MEMORY, -1);
1629 if (sanitizePlatformIndependentPath(_fname, fname))
1631 if (*fname == '\0') /* eh...punt if it's the root dir. */
1632 retval = 1; /* !!! FIXME: Maybe this should be an error? */
1637 __PHYSFS_platformGrabMutex(stateLock);
1638 for (i = searchPath; ((i != NULL) && (!exists)); i = i->next)
1640 char *arcfname = fname;
1641 exists = partOfMountPoint(i, arcfname);
1643 retval = 1; /* !!! FIXME: What's the right value? */
1644 else if (verifyPath(i, &arcfname, 0))
1646 retval = i->funcs->getLastModTime(i->opaque, arcfname,
1650 __PHYSFS_platformReleaseMutex(stateLock);
1654 __PHYSFS_smallFree(fname);
1656 } /* PHYSFS_getLastModTime */
1659 int PHYSFS_isDirectory(const char *_fname)
1665 BAIL_IF_MACRO(_fname == NULL, ERR_INVALID_ARGUMENT, 0);
1666 len = strlen(_fname) + 1;
1667 fname = (char *) __PHYSFS_smallAlloc(len);
1668 BAIL_IF_MACRO(fname == NULL, ERR_OUT_OF_MEMORY, 0);
1670 if (!sanitizePlatformIndependentPath(_fname, fname))
1673 else if (*fname == '\0')
1674 retval = 1; /* Root is always a dir. :) */
1681 __PHYSFS_platformGrabMutex(stateLock);
1682 for (i = searchPath; ((i != NULL) && (!exists)); i = i->next)
1684 char *arcfname = fname;
1685 if ((exists = partOfMountPoint(i, arcfname)) != 0)
1687 else if (verifyPath(i, &arcfname, 0))
1688 retval = i->funcs->isDirectory(i->opaque, arcfname, &exists);
1690 __PHYSFS_platformReleaseMutex(stateLock);
1693 __PHYSFS_smallFree(fname);
1695 } /* PHYSFS_isDirectory */
1698 int PHYSFS_isSymbolicLink(const char *_fname)
1704 BAIL_IF_MACRO(!allowSymLinks, ERR_SYMLINK_DISALLOWED, 0);
1706 BAIL_IF_MACRO(_fname == NULL, ERR_INVALID_ARGUMENT, 0);
1707 len = strlen(_fname) + 1;
1708 fname = (char *) __PHYSFS_smallAlloc(len);
1709 BAIL_IF_MACRO(fname == NULL, ERR_OUT_OF_MEMORY, 0);
1711 if (!sanitizePlatformIndependentPath(_fname, fname))
1714 else if (*fname == '\0')
1715 retval = 1; /* Root is never a symlink. */
1722 __PHYSFS_platformGrabMutex(stateLock);
1723 for (i = searchPath; ((i != NULL) && (!fileExists)); i = i->next)
1725 char *arcfname = fname;
1726 if ((fileExists = partOfMountPoint(i, arcfname)) != 0)
1727 retval = 0; /* virtual dir...not a symlink. */
1728 else if (verifyPath(i, &arcfname, 0))
1729 retval = i->funcs->isSymLink(i->opaque, arcfname, &fileExists);
1731 __PHYSFS_platformReleaseMutex(stateLock);
1734 __PHYSFS_smallFree(fname);
1736 } /* PHYSFS_isSymbolicLink */
1739 static PHYSFS_File *doOpenWrite(const char *_fname, int appending)
1741 FileHandle *fh = NULL;
1745 BAIL_IF_MACRO(_fname == NULL, ERR_INVALID_ARGUMENT, 0);
1746 len = strlen(_fname) + 1;
1747 fname = (char *) __PHYSFS_smallAlloc(len);
1748 BAIL_IF_MACRO(fname == NULL, ERR_OUT_OF_MEMORY, 0);
1750 if (sanitizePlatformIndependentPath(_fname, fname))
1752 void *opaque = NULL;
1753 DirHandle *h = NULL;
1754 const PHYSFS_Archiver *f;
1756 __PHYSFS_platformGrabMutex(stateLock);
1758 GOTO_IF_MACRO(!writeDir, ERR_NO_WRITE_DIR, doOpenWriteEnd);
1761 GOTO_IF_MACRO(!verifyPath(h, &fname, 0), NULL, doOpenWriteEnd);
1765 opaque = f->openAppend(h->opaque, fname);
1767 opaque = f->openWrite(h->opaque, fname);
1769 GOTO_IF_MACRO(opaque == NULL, NULL, doOpenWriteEnd);
1771 fh = (FileHandle *) allocator.Malloc(sizeof (FileHandle));
1774 f->fileClose(opaque);
1775 GOTO_MACRO(ERR_OUT_OF_MEMORY, doOpenWriteEnd);
1779 memset(fh, '\0', sizeof (FileHandle));
1780 fh->opaque = opaque;
1782 fh->funcs = h->funcs;
1783 fh->next = openWriteList;
1788 __PHYSFS_platformReleaseMutex(stateLock);
1791 __PHYSFS_smallFree(fname);
1792 return((PHYSFS_File *) fh);
1796 PHYSFS_File *PHYSFS_openWrite(const char *filename)
1798 return(doOpenWrite(filename, 0));
1799 } /* PHYSFS_openWrite */
1802 PHYSFS_File *PHYSFS_openAppend(const char *filename)
1804 return(doOpenWrite(filename, 1));
1805 } /* PHYSFS_openAppend */
1808 PHYSFS_File *PHYSFS_openRead(const char *_fname)
1810 FileHandle *fh = NULL;
1814 BAIL_IF_MACRO(_fname == NULL, ERR_INVALID_ARGUMENT, 0);
1815 len = strlen(_fname) + 1;
1816 fname = (char *) __PHYSFS_smallAlloc(len);
1817 BAIL_IF_MACRO(fname == NULL, ERR_OUT_OF_MEMORY, 0);
1819 if (sanitizePlatformIndependentPath(_fname, fname))
1822 DirHandle *i = NULL;
1823 fvoid *opaque = NULL;
1825 __PHYSFS_platformGrabMutex(stateLock);
1827 GOTO_IF_MACRO(!searchPath, ERR_NO_SUCH_PATH, openReadEnd);
1829 /* !!! FIXME: Why aren't we using a for loop here? */
1834 char *arcfname = fname;
1835 if (verifyPath(i, &arcfname, 0))
1837 opaque = i->funcs->openRead(i->opaque, arcfname, &fileExists);
1842 } while ((i != NULL) && (!fileExists));
1844 /* !!! FIXME: may not set an error if openRead didn't fail. */
1845 GOTO_IF_MACRO(opaque == NULL, NULL, openReadEnd);
1847 fh = (FileHandle *) allocator.Malloc(sizeof (FileHandle));
1850 i->funcs->fileClose(opaque);
1851 GOTO_MACRO(ERR_OUT_OF_MEMORY, openReadEnd);
1854 memset(fh, '\0', sizeof (FileHandle));
1855 fh->opaque = opaque;
1858 fh->funcs = i->funcs;
1859 fh->next = openReadList;
1863 __PHYSFS_platformReleaseMutex(stateLock);
1866 __PHYSFS_smallFree(fname);
1867 return((PHYSFS_File *) fh);
1868 } /* PHYSFS_openRead */
1871 static int closeHandleInOpenList(FileHandle **list, FileHandle *handle)
1873 FileHandle *prev = NULL;
1877 for (i = *list; i != NULL; i = i->next)
1879 if (i == handle) /* handle is in this list? */
1881 PHYSFS_uint8 *tmp = handle->buffer;
1882 rc = PHYSFS_flush((PHYSFS_File *) handle);
1884 rc = handle->funcs->fileClose(handle->opaque);
1888 if (tmp != NULL) /* free any associated buffer. */
1889 allocator.Free(tmp);
1892 *list = handle->next;
1894 prev->next = handle->next;
1896 allocator.Free(handle);
1903 } /* closeHandleInOpenList */
1906 int PHYSFS_close(PHYSFS_File *_handle)
1908 FileHandle *handle = (FileHandle *) _handle;
1911 __PHYSFS_platformGrabMutex(stateLock);
1913 /* -1 == close failure. 0 == not found. 1 == success. */
1914 rc = closeHandleInOpenList(&openReadList, handle);
1915 BAIL_IF_MACRO_MUTEX(rc == -1, NULL, stateLock, 0);
1918 rc = closeHandleInOpenList(&openWriteList, handle);
1919 BAIL_IF_MACRO_MUTEX(rc == -1, NULL, stateLock, 0);
1922 __PHYSFS_platformReleaseMutex(stateLock);
1923 BAIL_IF_MACRO(!rc, ERR_NOT_A_HANDLE, 0);
1925 } /* PHYSFS_close */
1928 static PHYSFS_sint64 doBufferedRead(FileHandle *fh, void *buffer,
1929 PHYSFS_uint32 objSize,
1930 PHYSFS_uint32 objCount)
1932 PHYSFS_sint64 retval = 0;
1933 PHYSFS_uint32 remainder = 0;
1935 while (objCount > 0)
1937 PHYSFS_uint32 buffered = fh->buffill - fh->bufpos;
1938 PHYSFS_uint64 mustread = (objSize * objCount) - remainder;
1939 PHYSFS_uint32 copied;
1941 if (buffered == 0) /* need to refill buffer? */
1943 PHYSFS_sint64 rc = fh->funcs->read(fh->opaque, fh->buffer,
1947 fh->bufpos -= remainder;
1948 return(((rc == -1) && (retval == 0)) ? -1 : retval);
1951 buffered = fh->buffill = (PHYSFS_uint32) rc;
1955 if (buffered > mustread)
1956 buffered = (PHYSFS_uint32) mustread;
1958 memcpy(buffer, fh->buffer + fh->bufpos, (size_t) buffered);
1959 buffer = ((PHYSFS_uint8 *) buffer) + buffered;
1960 fh->bufpos += buffered;
1961 buffered += remainder; /* take remainder into account. */
1962 copied = (buffered / objSize);
1963 remainder = (buffered % objSize);
1969 } /* doBufferedRead */
1972 PHYSFS_sint64 PHYSFS_read(PHYSFS_File *handle, void *buffer,
1973 PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
1975 FileHandle *fh = (FileHandle *) handle;
1977 BAIL_IF_MACRO(!fh->forReading, ERR_FILE_ALREADY_OPEN_W, -1);
1978 BAIL_IF_MACRO(objSize == 0, NULL, 0);
1979 BAIL_IF_MACRO(objCount == 0, NULL, 0);
1980 if (fh->buffer != NULL)
1981 return(doBufferedRead(fh, buffer, objSize, objCount));
1983 return(fh->funcs->read(fh->opaque, buffer, objSize, objCount));
1987 static PHYSFS_sint64 doBufferedWrite(PHYSFS_File *handle, const void *buffer,
1988 PHYSFS_uint32 objSize,
1989 PHYSFS_uint32 objCount)
1991 FileHandle *fh = (FileHandle *) handle;
1993 /* whole thing fits in the buffer? */
1994 if (fh->buffill + (objSize * objCount) < fh->bufsize)
1996 memcpy(fh->buffer + fh->buffill, buffer, objSize * objCount);
1997 fh->buffill += (objSize * objCount);
2001 /* would overflow buffer. Flush and then write the new objects, too. */
2002 BAIL_IF_MACRO(!PHYSFS_flush(handle), NULL, -1);
2003 return(fh->funcs->write(fh->opaque, buffer, objSize, objCount));
2004 } /* doBufferedWrite */
2007 PHYSFS_sint64 PHYSFS_write(PHYSFS_File *handle, const void *buffer,
2008 PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
2010 FileHandle *fh = (FileHandle *) handle;
2012 BAIL_IF_MACRO(fh->forReading, ERR_FILE_ALREADY_OPEN_R, -1);
2013 BAIL_IF_MACRO(objSize == 0, NULL, 0);
2014 BAIL_IF_MACRO(objCount == 0, NULL, 0);
2015 if (fh->buffer != NULL)
2016 return(doBufferedWrite(handle, buffer, objSize, objCount));
2018 return(fh->funcs->write(fh->opaque, buffer, objSize, objCount));
2019 } /* PHYSFS_write */
2022 int PHYSFS_eof(PHYSFS_File *handle)
2024 FileHandle *fh = (FileHandle *) handle;
2026 if (!fh->forReading) /* never EOF on files opened for write/append. */
2029 /* eof if buffer is empty and archiver says so. */
2030 return((fh->bufpos == fh->buffill) && (fh->funcs->eof(fh->opaque)));
2034 PHYSFS_sint64 PHYSFS_tell(PHYSFS_File *handle)
2036 FileHandle *fh = (FileHandle *) handle;
2037 PHYSFS_sint64 pos = fh->funcs->tell(fh->opaque);
2038 PHYSFS_sint64 retval = fh->forReading ?
2039 (pos - fh->buffill) + fh->bufpos :
2040 (pos + fh->buffill);
2045 int PHYSFS_seek(PHYSFS_File *handle, PHYSFS_uint64 pos)
2047 FileHandle *fh = (FileHandle *) handle;
2048 BAIL_IF_MACRO(!PHYSFS_flush(handle), NULL, 0);
2050 if (fh->buffer && fh->forReading)
2052 /* avoid throwing away our precious buffer if seeking within it. */
2053 PHYSFS_sint64 offset = pos - PHYSFS_tell(handle);
2054 if ( /* seeking within the already-buffered range? */
2055 ((offset >= 0) && (offset <= fh->buffill - fh->bufpos)) /* fwd */
2056 || ((offset < 0) && (-offset <= fh->bufpos)) /* backward */ )
2058 fh->bufpos += (PHYSFS_uint32) offset;
2059 return(1); /* successful seek */
2063 /* we have to fall back to a 'raw' seek. */
2064 fh->buffill = fh->bufpos = 0;
2065 return(fh->funcs->seek(fh->opaque, pos));
2069 PHYSFS_sint64 PHYSFS_fileLength(PHYSFS_File *handle)
2071 FileHandle *fh = (FileHandle *) handle;
2072 return(fh->funcs->fileLength(fh->opaque));
2073 } /* PHYSFS_filelength */
2076 int PHYSFS_setBuffer(PHYSFS_File *handle, PHYSFS_uint64 _bufsize)
2078 FileHandle *fh = (FileHandle *) handle;
2079 PHYSFS_uint32 bufsize;
2081 /* !!! FIXME: Unlocalized string. */
2082 BAIL_IF_MACRO(_bufsize > 0xFFFFFFFF, "buffer must fit in 32-bits", 0);
2083 bufsize = (PHYSFS_uint32) _bufsize;
2085 BAIL_IF_MACRO(!PHYSFS_flush(handle), NULL, 0);
2088 * For reads, we need to move the file pointer to where it would be
2089 * if we weren't buffering, so that the next read will get the
2090 * right chunk of stuff from the file. PHYSFS_flush() handles writes.
2092 if ((fh->forReading) && (fh->buffill != fh->bufpos))
2095 PHYSFS_sint64 curpos = fh->funcs->tell(fh->opaque);
2096 BAIL_IF_MACRO(curpos == -1, NULL, 0);
2097 pos = ((curpos - fh->buffill) + fh->bufpos);
2098 BAIL_IF_MACRO(!fh->funcs->seek(fh->opaque, pos), NULL, 0);
2101 if (bufsize == 0) /* delete existing buffer. */
2103 if (fh->buffer != NULL)
2105 allocator.Free(fh->buffer);
2112 PHYSFS_uint8 *newbuf;
2113 newbuf = (PHYSFS_uint8 *) allocator.Realloc(fh->buffer, bufsize);
2114 BAIL_IF_MACRO(newbuf == NULL, ERR_OUT_OF_MEMORY, 0);
2115 fh->buffer = newbuf;
2118 fh->bufsize = bufsize;
2119 fh->buffill = fh->bufpos = 0;
2121 } /* PHYSFS_setBuffer */
2124 int PHYSFS_flush(PHYSFS_File *handle)
2126 FileHandle *fh = (FileHandle *) handle;
2129 if ((fh->forReading) || (fh->bufpos == fh->buffill))
2130 return(1); /* open for read or buffer empty are successful no-ops. */
2132 /* dump buffer to disk. */
2133 rc = fh->funcs->write(fh->opaque, fh->buffer + fh->bufpos,
2134 fh->buffill - fh->bufpos, 1);
2135 BAIL_IF_MACRO(rc <= 0, NULL, 0);
2136 fh->bufpos = fh->buffill = 0;
2138 } /* PHYSFS_flush */
2141 int PHYSFS_setAllocator(const PHYSFS_Allocator *a)
2143 BAIL_IF_MACRO(initialized, ERR_IS_INITIALIZED, 0);
2144 externalAllocator = (a != NULL);
2145 if (externalAllocator)
2146 memcpy(&allocator, a, sizeof (PHYSFS_Allocator));
2149 } /* PHYSFS_setAllocator */
2152 static void *mallocAllocatorMalloc(PHYSFS_uint64 s)
2154 BAIL_IF_MACRO(__PHYSFS_ui64FitsAddressSpace(s), ERR_OUT_OF_MEMORY, NULL);
2156 return(malloc((size_t) s));
2157 } /* mallocAllocatorMalloc */
2160 static void *mallocAllocatorRealloc(void *ptr, PHYSFS_uint64 s)
2162 BAIL_IF_MACRO(__PHYSFS_ui64FitsAddressSpace(s), ERR_OUT_OF_MEMORY, NULL);
2164 return(realloc(ptr, (size_t) s));
2165 } /* mallocAllocatorRealloc */
2168 static void mallocAllocatorFree(void *ptr)
2172 } /* mallocAllocatorFree */
2175 static void setDefaultAllocator(void)
2177 assert(!externalAllocator);
2178 if (!__PHYSFS_platformSetDefaultAllocator(&allocator))
2180 allocator.Init = NULL;
2181 allocator.Deinit = NULL;
2182 allocator.Malloc = mallocAllocatorMalloc;
2183 allocator.Realloc = mallocAllocatorRealloc;
2184 allocator.Free = mallocAllocatorFree;
2186 } /* setDefaultAllocator */
2189 void *__PHYSFS_initSmallAlloc(void *ptr, PHYSFS_uint64 len)
2191 const char useHeap = ((ptr == NULL) ? 1 : 0);
2192 if (useHeap) /* too large for stack allocation or alloca() failed. */
2193 ptr = allocator.Malloc(len+1);
2197 char *retval = (char *) ptr;
2198 /*printf("%s alloc'd (%d) bytes at (%p).\n",
2199 useHeap ? "heap" : "stack", (int) len, ptr);*/
2204 return(NULL); /* allocation failed. */
2205 } /* __PHYSFS_initSmallAlloc */
2208 void __PHYSFS_smallFree(void *ptr)
2212 char *block = ((char *) ptr) - 1;
2213 const char useHeap = *block;
2215 allocator.Free(block);
2216 /*printf("%s free'd (%p).\n", useHeap ? "heap" : "stack", block);*/
2218 } /* __PHYSFS_smallFree */
2220 /* end of physfs.c ... */