From 93adbf1d88885bed0d5d2b25918ddbe997124ada Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sun, 13 Mar 2005 09:09:26 +0000 Subject: [PATCH] First chunk of PHYSFS_mount() implementation. Incomplete! --- TODO | 2 ++ physfs.c | 65 +++++++++++++++++++++++++++++++++++++++++----------- physfs.h | 70 ++++++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 112 insertions(+), 25 deletions(-) diff --git a/TODO b/TODO index 3eb1afd..9c3998e 100644 --- a/TODO +++ b/TODO @@ -45,6 +45,8 @@ Some might be dupes, some might be done already. - Should file enumeration return an error or set error state? - Ryanify pocketpc.c ... - Update internal zlib? +- Split verifySecurity() off into sanitizePath() and hook it into mount point + initialization. - Get svn hooks working. - maybe other stuff. diff --git a/physfs.c b/physfs.c index e38424a..e5625f3 100644 --- a/physfs.c +++ b/physfs.c @@ -25,6 +25,7 @@ typedef struct __PHYSFS_DIRHANDLE__ { void *opaque; /* Instance data unique to the archiver. */ char *dirName; /* Path to archive in platform-dependent notation. */ + char *mountPoint; /* Mountpoint in virtual file tree. */ const PHYSFS_Archiver *funcs; /* Ptr to archiver info for this handle. */ struct __PHYSFS_DIRHANDLE__ *next; /* linked list stuff. */ } DirHandle; @@ -440,6 +441,7 @@ static DirHandle *tryOpenDir(const PHYSFS_Archiver *funcs, else { memset(retval, '\0', sizeof (DirHandle)); + retval->mountPoint = NULL; retval->funcs = funcs; retval->opaque = opaque; } /* else */ @@ -487,25 +489,41 @@ static DirHandle *openDirectory(const char *d, int forWriting) } /* openDirectory */ -static DirHandle *createDirHandle(const char *newDir, int forWriting) +static DirHandle *createDirHandle(const char *newDir, + const char *mountPoint, + int forWriting) { DirHandle *dirHandle = NULL; - - BAIL_IF_MACRO(newDir == NULL, ERR_INVALID_ARGUMENT, NULL); + GOTO_IF_MACRO(!newDir, ERR_INVALID_ARGUMENT, badDirHandle); dirHandle = openDirectory(newDir, forWriting); - BAIL_IF_MACRO(dirHandle == NULL, NULL, NULL); + GOTO_IF_MACRO(!dirHandle, NULL, badDirHandle); dirHandle->dirName = (char *) malloc(strlen(newDir) + 1); - if (dirHandle->dirName == NULL) + GOTO_IF_MACRO(!dirHandle->dirName, ERR_OUT_OF_MEMORY, badDirHandle); + strcpy(dirHandle->dirName, newDir); + + if ((mountPoint != NULL) && (*mountPoint != '\0')) { - dirHandle->funcs->dirClose(dirHandle->opaque); - free(dirHandle); - BAIL_MACRO(ERR_OUT_OF_MEMORY, NULL); + /* !!! FIXME: Sanitize the string here. */ + dirHandle->mountPoint = (char *) malloc(strlen(mountPoint) + 2); + GOTO_IF_MACRO(!dirHandle->mountPoint, ERR_OUT_OF_MEMORY, badDirHandle); + strcpy(dirHandle->mountPoint, mountPoint); + strcat(dirHandle->mountPoint, "/"); } /* if */ - strcpy(dirHandle->dirName, newDir); return(dirHandle); + +badDirHandle: + if (dirHandle != NULL) + { + dirHandle->funcs->dirClose(dirHandle->opaque); + free(dirHandle->dirName); + free(dirHandle->mountPoint); + free(dirHandle); + } /* if */ + + return(NULL); } /* createDirHandle */ @@ -859,7 +877,7 @@ int PHYSFS_setWriteDir(const char *newDir) if (newDir != NULL) { - writeDir = createDirHandle(newDir, 1); + writeDir = createDirHandle(newDir, NULL, 1); retval = (writeDir != NULL); } /* if */ @@ -869,12 +887,15 @@ int PHYSFS_setWriteDir(const char *newDir) } /* PHYSFS_setWriteDir */ -int PHYSFS_addToSearchPath(const char *newDir, int appendToPath) +int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath) { DirHandle *dh; DirHandle *prev = NULL; DirHandle *i; + BAIL_IF_MACRO(newDir == NULL, ERR_INVALID_ARGUMENT, 0); + BAIL_IF_MACRO(mountPoint == NULL, ERR_INVALID_ARGUMENT, 0); + __PHYSFS_platformGrabMutex(stateLock); for (i = searchPath; i != NULL; i = i->next) @@ -884,7 +905,7 @@ int PHYSFS_addToSearchPath(const char *newDir, int appendToPath) prev = i; } /* for */ - dh = createDirHandle(newDir, 0); + dh = createDirHandle(newDir, mountPoint, 0); BAIL_IF_MACRO_MUTEX(dh == NULL, NULL, stateLock, 0); if (appendToPath) @@ -902,6 +923,12 @@ int PHYSFS_addToSearchPath(const char *newDir, int appendToPath) __PHYSFS_platformReleaseMutex(stateLock); return(1); +} /* PHYSFS_mount */ + + +int PHYSFS_addToSearchPath(const char *newDir, int appendToPath) +{ + return(PHYSFS_mount(newDir, "/", appendToPath)); } /* PHYSFS_addToSearchPath */ @@ -1130,7 +1157,8 @@ char * __PHYSFS_convertToDependent(const char *prepend, * Verify that (fname) (in platform-independent notation), in relation * to (h) is secure. That means that each element of fname is checked * for symlinks (if they aren't permitted). Also, elements such as - * ".", "..", or ":" are flagged. + * ".", "..", or ":" are flagged. This also allows for quick rejection of + * files that exist outside an archive's mountpoint. * * With some exceptions (like PHYSFS_mkdir(), which builds multiple subdirs * at a time), you should always pass zero for "allowMissing" for efficiency. @@ -1148,6 +1176,17 @@ int __PHYSFS_verifySecurity(DirHandle *h, const char *fname, int allowMissing) if (*fname == '\0') /* quick rejection. */ return(1); + if (h->mountPoint != NULL) /* NULL mountpoint means "/". */ + { + /* !!! FIXME: Case insensitive? */ + size_t mntpntlen = strlen(h->mountPoint); + assert(mntpntlen > 1); /* root mount points should be NULL. */ + if (strncmp(h->mountPoint, fname, mntpntlen) != 0) + return(0); /* not under the mountpoint, so skip this archive. */ + + fname += mntpntlen; /* move to start of actual archive path. */ + } /* if */ + /* !!! FIXME: Can we ditch this malloc()? */ start = str = malloc(strlen(fname) + 1); BAIL_IF_MACRO(str == NULL, ERR_OUT_OF_MEMORY, 0); diff --git a/physfs.h b/physfs.h index 81fb13a..2924afa 100644 --- a/physfs.h +++ b/physfs.h @@ -78,13 +78,13 @@ * it correctly. * * Files opened through PhysicsFS may NOT contain "." or ".." or ":" as dir - * elements. Not only are these meaningless on MacOS and/or Unix, they are a - * security hole. Also, symbolic links (which can be found in some archive - * types and directly in the filesystem on Unix platforms) are NOT followed - * until you call PHYSFS_permitSymbolicLinks(). That's left to your own - * discretion, as following a symlink can allow for access outside the write - * dir and search paths. There is no mechanism for creating new symlinks in - * PhysicsFS. + * elements. Not only are these meaningless on MacOS Classic and/or Unix, + * they are a security hole. Also, symbolic links (which can be found in + * some archive types and directly in the filesystem on Unix platforms) are + * NOT followed until you call PHYSFS_permitSymbolicLinks(). That's left to + * your own discretion, as following a symlink can allow for access outside + * the write dir and search paths. For portability, there is no mechanism for + * creating new symlinks in PhysicsFS. * * The write dir is not included in the search path unless you specifically * add it. While you CAN change the write dir as many times as you like, @@ -110,6 +110,18 @@ * PHYSFS_getBaseDir(), and PHYSFS_getUserDir() for info on what those * are and how they can help you determine an optimal search path. * + * PhysicsFS 2.0 adds the concept of "mounting" archives to arbitrary points + * in the search path. If a zipfile contains "maps/level.map" and you mount + * that archive at "mods/mymod", then you would have to open + * "mods/mymod/maps/level.map" to access the file, even though "mods/mymod" + * isn't actually specified in the .zip file. Unlike the Unix mentality of + * mounting a filesystem, "mods/mymod" doesn't actually have to exist when + * mounting the zipfile. It's a "virtual" directory. The mounting mechanism + * allows the developer to seperate archives in the tree and avoid trampling + * over files when added new archives, such as including mod support in a + * game...keeping external content on a tight leash in this manner can be of + * utmost importance to some applications. + * * PhysicsFS is mostly thread safe. The error messages returned by * PHYSFS_getLastError are unique by thread, and library-state-setting * functions are mutex'd. For efficiency, individual file accesses are @@ -124,7 +136,7 @@ * Note that archives need not be named as such: if you have a ZIP file and * rename it with a .PKG extension, the file will still be recognized as a * ZIP archive by PhysicsFS; the file's contents are used to determine its - * type. + * type where possible. * * Currently supported archive types: * - .ZIP (pkZip/WinZip/Info-ZIP compatible) @@ -133,12 +145,13 @@ * - .HOG (Descent I/II HOG file archives) * - .MVL (Descent II movielib archives) * - .WAD (DOOM engine archives) + * - .MIX (Older Westwood games archives) * * Please see the file LICENSE in the source's root directory for licensing * and redistribution rights. * - * Please see the file CREDITS in the source's root directory for a complete - * list of who's responsible for this. + * Please see the file CREDITS in the source's root directory for a more or + * less complete list of who's responsible for this. * * \author Ryan C. Gordon. */ @@ -656,19 +669,52 @@ __EXPORT__ int PHYSFS_setWriteDir(const char *newDir); /** - * \fn int PHYSFS_addToSearchPath(const char *newDir, int appendToPath) + * \fn int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath); * \brief Add an archive or directory to the search path. * * If this is a duplicate, the entry is not added again, even though the - * function succeeds. + * function succeeds. You may not add the same archive to two different + * mountpoints: duplicate checking is done against the archive and not the + * mountpoint. + * + * When you mount an archive, it is added to a virtual file system...all files + * in all of the archives are interpolated into a single hierachical file + * tree. Two archives mounted at the same place (or an archive with files + * overlapping another mountpoint) may have overlapping files: in such a case, + * the file earliest in the search path is selected, and the other files are + * inaccessible to the application. This allows archives to be used to + * override previous revisions; you can use the mounting mechanism to place + * archives at a specific point in the file tree and prevent overlap; this + * is useful for downloadable mods that might trample over application data + * or each other, for example. + * + * The mountpoint does not need to exist prior to mounting, which is different + * than those familiar with the Unix concept of "mounting" may not expect. + * As well, more than one archive can be mounted to the same mountpoint, or + * mountpoints and archive contents can overlap...the interpolation mechanism + * still functions as usual. * * \param newDir directory or archive to add to the path, in * platform-dependent notation. + * \param mountPoint Location in the interpolated tree that this archive + * will be "mounted", in platform-independent notation. * \param appendToPath nonzero to append to search path, zero to prepend. * \return nonzero if added to path, zero on failure (bogus archive, dir * missing, etc). Specifics of the error can be * gleaned from PHYSFS_getLastError(). + */ +__EXPORT__ int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath); + + +/** + * \fn int PHYSFS_addToSearchPath(const char *newDir, int appendToPath) + * \brief Add an archive or directory to the search path. * + * This is a legacy call, equivalent to: + * PHYSFS_mount(newDir, "/", appendToPath); + * + * \sa PHYSFS_mount + * \sa PHYSFS_unmount * \sa PHYSFS_removeFromSearchPath * \sa PHYSFS_getSearchPath */