From a2cc4617278c97d10d896d805a7eec5159190b52 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 14 Mar 2005 07:15:40 +0000 Subject: [PATCH] More mountpoint work and other cleanups. --- CHANGELOG | 1 + physfs.c | 157 +++++++++++++++++++++++++++++++++++++++++++----------- physfs.h | 139 ++++++++++++++++++++++++++++++----------------- 3 files changed, 219 insertions(+), 78 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index cb175e84..3a64ecde 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ * CHANGELOG. */ +03132005 - More mount work, added PHYSFS_getMountPoint() and more cleanups. 03122005 - Added evil GOTO_*_MACRO_* macros. Fixed unix.c to compile again on MacOS X. Added PHYSFS_mount() (thanks, Philip!). Cleaned up the INSTALL and CREDITS files a little. Split off start of diff --git a/physfs.c b/physfs.c index 24632f92..320724a1 100644 --- a/physfs.c +++ b/physfs.c @@ -535,6 +535,44 @@ static int sanitizePlatformIndependentPath(const char *src, char *dst) } /* sanitizePlatformIndependentPath */ +/* + * Figure out if (fname) is part of (h)'s mountpoint. (fname) must be an + * output from sanitizePlatformIndependentPath(), so that it is in a known + * state. + * + * This only finds legitimate segments of a mountpoint. If the mountpoint is + * "/a/b/c" and (fname) is "/a/b/c", "/", or "/a/b/c/d", then the results are + * all zero. "/a/b" will succeed, though. + */ +static int partOfMountPoint(DirHandle *h, char *fname) +{ + /* !!! FIXME: This code feels gross. */ + int rc; + size_t len, mntpntlen; + + if (h->mountPoint == NULL) + return(0); + else if (*fname == '\0') + return(1); + + len = strlen(fname); + mntpntlen = strlen(h->mountPoint); + if (len > mntpntlen) /* can't be a subset of mountpoint. */ + return(0); + + /* if true, must be not a match or a complete match, but not a subset. */ + if ((len + 1) == mntpntlen) + return(0); + + rc = strncmp(fname, h->mountPoint, len); /* !!! FIXME: case insensitive? */ + if (rc != 0) + return(0); /* not a match. */ + + /* make sure /a/b matches /a/b/ and not /a/bc ... */ + return(h->mountPoint[len] == '/'); +} /* partOfMountPoint */ + + static DirHandle *createDirHandle(const char *newDir, const char *mountPoint, int forWriting) @@ -594,6 +632,7 @@ static int freeDirHandle(DirHandle *dh, FileHandle *openList) dh->funcs->dirClose(dh->opaque); free(dh->dirName); + free(dh->mountPoint); free(dh); return(1); } /* freeDirHandle */ @@ -1023,6 +1062,25 @@ char **PHYSFS_getSearchPath(void) } /* PHYSFS_getSearchPath */ +const char *PHYSFS_getMountPoint(const char *dir) +{ + DirHandle *i; + __PHYSFS_platformGrabMutex(stateLock); + for (i = searchPath; i != NULL; i = i->next) + { + if (strcmp(i->dirName, dir) == 0) + { + const char *retval = ((i->mountPoint) ? i->mountPoint : "/"); + __PHYSFS_platformReleaseMutex(stateLock); + return(retval); + } /* if */ + } /* for */ + __PHYSFS_platformReleaseMutex(stateLock); + + BAIL_MACRO(ERR_NOT_IN_SEARCH_PATH, NULL); +} /* PHYSFS_getMountPoint */ + + void PHYSFS_getSearchPathCallback(PHYSFS_StringCallback callback, void *data) { DirHandle *i; @@ -1216,17 +1274,20 @@ char * __PHYSFS_convertToDependent(const char *prepend, * With some exceptions (like PHYSFS_mkdir(), which builds multiple subdirs * at a time), you should always pass zero for "allowMissing" for efficiency. * - * (fname) must be an output fromsanitizePlatformIndependentPath(), since it - * will make sure that path names are in the right format for passing certain - * checks. It will also do checks for "insecure" pathnames like ".." which - * should be done once instead of once per archive. This also gives us - * license to treat (fname) as scratch space in this function. + * (fname) must point to an output from sanitizePlatformIndependentPath(), + * since it will make sure that path names are in the right format for + * passing certain checks. It will also do checks for "insecure" pathnames + * like ".." which should be done once instead of once per archive. This also + * gives us license to treat (fname) as scratch space in this function. * * Returns non-zero if string is safe, zero if there's a security issue. - * PHYSFS_getLastError() will specify what was wrong. + * PHYSFS_getLastError() will specify what was wrong. (*fname) will be + * updated to point past any mount point elements so it is prepared to + * be used with the archiver directly. */ -int __PHYSFS_verifySecurity(DirHandle *h, char *fname, int allowMissing) +static int verifyPath(DirHandle *h, char **_fname, int allowMissing) { + char *fname = *_fname; int retval = 1; char *start; char *end; @@ -1234,18 +1295,24 @@ int __PHYSFS_verifySecurity(DirHandle *h, char *fname, int allowMissing) if (*fname == '\0') /* quick rejection. */ return(1); + /* !!! FIXME: This codeblock sucks. */ 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. */ size_t len = strlen(fname); /* not under the mountpoint, so skip this archive. */ - BAIL_IF_MACRO(len < mntpntlen, ERR_NO_SUCH_PATH, 0); - retval = strncmp(h->mountPoint, fname, mntpntlen); + BAIL_IF_MACRO(len < mntpntlen-1, ERR_NO_SUCH_PATH, 0); + /* !!! FIXME: Case insensitive? */ + retval = strncmp(h->mountPoint, fname, mntpntlen-1); BAIL_IF_MACRO(retval != 0, ERR_NO_SUCH_PATH, 0); - fname += mntpntlen; /* move to start of actual archive path. */ - retval = 1; + if (len > mntpntlen-1) /* corner case... */ + BAIL_IF_MACRO(fname[mntpntlen-1] != '/', ERR_NO_SUCH_PATH, 0); + fname += mntpntlen-1; /* move to start of actual archive path. */ + if (*fname == '/') + fname++; + *_fname = fname; /* skip mountpoint for later use. */ + retval = 1; /* may be reset, below. */ } /* if */ start = fname; @@ -1282,7 +1349,7 @@ int __PHYSFS_verifySecurity(DirHandle *h, char *fname, int allowMissing) } /* if */ return(retval); -} /* __PHYSFS_verifySecurity */ +} /* verifyPath */ int PHYSFS_mkdir(const char *_dname) @@ -1301,9 +1368,9 @@ int PHYSFS_mkdir(const char *_dname) __PHYSFS_platformGrabMutex(stateLock); BAIL_IF_MACRO_MUTEX(writeDir == NULL, ERR_NO_WRITE_DIR, stateLock, 0); h = writeDir; - BAIL_IF_MACRO_MUTEX(!__PHYSFS_verifySecurity(h,dname,1),NULL,stateLock,0); - start = dname; + BAIL_IF_MACRO_MUTEX(!verifyPath(h, &dname, 1), NULL, stateLock, 0); + start = dname; while (1) { end = strchr(start, '/'); @@ -1345,7 +1412,7 @@ int PHYSFS_delete(const char *_fname) BAIL_IF_MACRO_MUTEX(writeDir == NULL, ERR_NO_WRITE_DIR, stateLock, 0); h = writeDir; - BAIL_IF_MACRO_MUTEX(!__PHYSFS_verifySecurity(h,fname,0),NULL,stateLock,0); + BAIL_IF_MACRO_MUTEX(!verifyPath(h, &fname, 0), NULL, stateLock, 0); retval = h->funcs->remove(h->opaque, fname); __PHYSFS_platformReleaseMutex(stateLock); @@ -1365,9 +1432,12 @@ const char *PHYSFS_getRealDir(const char *_fname) __PHYSFS_platformGrabMutex(stateLock); for (i = searchPath; ((i != NULL) && (retval == NULL)); i = i->next) { - if (__PHYSFS_verifySecurity(i, fname, 0)) + char *arcfname = fname; + if (partOfMountPoint(i, arcfname)) + retval = i->dirName; + else if (verifyPath(i, &arcfname, 0)) { - if (i->funcs->exists(i->opaque, fname)) + if (i->funcs->exists(i->opaque, arcfname)) retval = i->dirName; } /* if */ } /* for */ @@ -1484,8 +1554,22 @@ void PHYSFS_enumerateFilesCallback(const char *_fname, noSyms = !allowSymLinks; for (i = searchPath; i != NULL; i = i->next) { - if (__PHYSFS_verifySecurity(i, fname, 0)) - i->funcs->enumerateFiles(i->opaque, fname, noSyms, callback, data); + char *arcfname = fname; + if (partOfMountPoint(i, arcfname)) + { + size_t len = strlen(arcfname); + char *ptr = i->mountPoint + ((len) ? len + 1 : 0); + char *end = strchr(ptr, '/'); + assert(end); /* should always find a terminating '/'. */ + *end = '\0'; /* !!! FIXME: not safe in a callback... */ + callback(data, ptr); + *end = '/'; /* !!! FIXME: not safe in a callback... */ + } /* if */ + + else if (verifyPath(i, &arcfname, 0)) + { + i->funcs->enumerateFiles(i->opaque,arcfname,noSyms,callback,data); + } /* else if */ } /* for */ __PHYSFS_platformReleaseMutex(stateLock); } /* PHYSFS_enumerateFilesCallback */ @@ -1513,8 +1597,12 @@ PHYSFS_sint64 PHYSFS_getLastModTime(const char *_fname) __PHYSFS_platformGrabMutex(stateLock); for (i = searchPath; ((i != NULL) && (!fileExists)); i = i->next) { - if (__PHYSFS_verifySecurity(i, fname, 0)) - retval = i->funcs->getLastModTime(i->opaque, fname, &fileExists); + char *arcfname = fname; + fileExists = partOfMountPoint(i, arcfname); + if (fileExists) + retval = 1; /* !!! FIXME: What's the right value? */ + else if (verifyPath(i, &arcfname, 0)) + retval = i->funcs->getLastModTime(i->opaque,arcfname,&fileExists); } /* for */ __PHYSFS_platformReleaseMutex(stateLock); @@ -1536,8 +1624,11 @@ int PHYSFS_isDirectory(const char *_fname) __PHYSFS_platformGrabMutex(stateLock); for (i = searchPath; ((i != NULL) && (!fileExists)); i = i->next) { - if (__PHYSFS_verifySecurity(i, fname, 0)) - retval = i->funcs->isDirectory(i->opaque, fname, &fileExists); + char *arcfname = fname; + if ((fileExists = partOfMountPoint(i, arcfname)) != 0) + retval = 1; + else if (verifyPath(i, &arcfname, 0)) + retval = i->funcs->isDirectory(i->opaque, arcfname, &fileExists); } /* for */ __PHYSFS_platformReleaseMutex(stateLock); @@ -1562,8 +1653,11 @@ int PHYSFS_isSymbolicLink(const char *_fname) __PHYSFS_platformGrabMutex(stateLock); for (i = searchPath; ((i != NULL) && (!fileExists)); i = i->next) { - if (__PHYSFS_verifySecurity(i, fname, 0)) - retval = i->funcs->isSymLink(i->opaque, fname, &fileExists); + char *arcfname = fname; + if ((fileExists = partOfMountPoint(i, arcfname)) != 0) + retval = 0; /* virtual dir...not a symlink. */ + else if (verifyPath(i, &arcfname, 0)) + retval = i->funcs->isSymLink(i->opaque, arcfname, &fileExists); } /* for */ __PHYSFS_platformReleaseMutex(stateLock); @@ -1586,8 +1680,7 @@ static PHYSFS_File *doOpenWrite(const char *_fname, int appending) BAIL_IF_MACRO_MUTEX(!writeDir, ERR_NO_WRITE_DIR, stateLock, NULL); h = writeDir; - BAIL_IF_MACRO_MUTEX(!__PHYSFS_verifySecurity(h, fname, 0), NULL, - stateLock, NULL); + BAIL_IF_MACRO_MUTEX(!verifyPath(h, &fname, 0), NULL, stateLock, NULL); f = h->funcs; if (appending) @@ -1642,15 +1735,19 @@ PHYSFS_File *PHYSFS_openRead(const char *_fname) BAIL_IF_MACRO(!sanitizePlatformIndependentPath(_fname, fname), NULL, 0); __PHYSFS_platformGrabMutex(stateLock); + + /* !!! FIXME: should probably be ERR_PATH_NOT_FOUND */ BAIL_IF_MACRO_MUTEX(!searchPath, ERR_NOT_IN_SEARCH_PATH, stateLock, NULL); + /* !!! FIXME: Why aren't we using a for loop here? */ i = searchPath; do { - if (__PHYSFS_verifySecurity(i, fname, 0)) + char *arcfname = fname; + if (verifyPath(i, &arcfname, 0)) { - opaque = i->funcs->openRead(i->opaque, fname, &fileExists); + opaque = i->funcs->openRead(i->opaque, arcfname, &fileExists); if (opaque) break; } /* if */ diff --git a/physfs.h b/physfs.h index 71925cb2..9602b9ad 100644 --- a/physfs.h +++ b/physfs.h @@ -493,13 +493,17 @@ __EXPORT__ void PHYSFS_freeList(void *listVar); * \fn const char *PHYSFS_getLastError(void) * \brief Get human-readable error information. * - * Get the last PhysicsFS error message as a null-terminated string. - * This will be NULL if there's been no error since the last call to this - * function. The pointer returned by this call points to an internal buffer. - * Each thread has a unique error state associated with it, but each time - * a new error message is set, it will overwrite the previous one associated - * with that thread. It is safe to call this function at anytime, even - * before PHYSFS_init(). + * Get the last PhysicsFS error message as a human-readable, null-terminated + * string. This will be NULL if there's been no error since the last call to + * this function. The pointer returned by this call points to an internal + * buffer. Each thread has a unique error state associated with it, but each + * time a new error message is set, it will overwrite the previous one + * associated with that thread. It is safe to call this function at anytime, + * even before PHYSFS_init(). + * + * It is not wise to expect a specific string of characters here, since the + * error message may be localized into an unfamiliar language. These strings + * are meant to be passed on directly to the user. * * \return READ ONLY string of last error message. */ @@ -584,6 +588,8 @@ __EXPORT__ void PHYSFS_permitSymbolicLinks(int allow); * resources by calling PHYSFS_freeList() with the returned pointer. * * \return Null-terminated array of null-terminated strings. + * + * \sa PHYSFS_getCdRomDirsCallback */ __EXPORT__ char **PHYSFS_getCdRomDirs(void); @@ -668,54 +674,17 @@ __EXPORT__ const char *PHYSFS_getWriteDir(void); __EXPORT__ int PHYSFS_setWriteDir(const char *newDir); -/** - * \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. 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. - * NULL or "" is equivalent to "/". - * \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: + * This is a legacy call in PhysicsFS 2.0, equivalent to: * PHYSFS_mount(newDir, NULL, appendToPath); * + * You must use this and not PHYSFS_mount if binary compatibility with + * PhysicsFS 1.0 is important (which it may not be for many people). + * * \sa PHYSFS_mount - * \sa PHYSFS_unmount * \sa PHYSFS_removeFromSearchPath * \sa PHYSFS_getSearchPath */ @@ -764,6 +733,7 @@ __EXPORT__ int PHYSFS_removeFromSearchPath(const char *oldDir); * \return Null-terminated array of null-terminated strings. NULL if there * was a problem (read: OUT OF MEMORY). * + * \sa PHYSFS_getSearchPathCallback * \sa PHYSFS_addToSearchPath * \sa PHYSFS_removeFromSearchPath */ @@ -910,6 +880,10 @@ __EXPORT__ int PHYSFS_delete(const char *filename); * permitted symlinks, then it will be ignored, and the search for a match * will continue. * + * If you specify a fake directory that only exists as a mount point, it'll + * be associated with the first archive mounted there, even though that + * directory isn't necessarily contained in a real archive. + * * \param filename file to look for. * \return READ ONLY string of element of search path containing the * the file in question. NULL if not found. @@ -952,6 +926,8 @@ __EXPORT__ const char *PHYSFS_getRealDir(const char *filename); * * \param dir directory in platform-independent notation to enumerate. * \return Null-terminated array of null-terminated strings. + * + * \sa PHYSFS_enumerateFilesCallback */ __EXPORT__ char **PHYSFS_enumerateFiles(const char *dir); @@ -1921,6 +1897,73 @@ typedef struct __EXPORT__ int PHYSFS_setAllocator(PHYSFS_Allocator *allocator); +/** + * \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. 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. + * NULL or "" is equivalent to "/". + * \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(). + * + * \sa PHYSFS_removeFromSearchPath + * \sa PHYSFS_getSearchPath + * \sa PHYSFS_getMountPoint + */ +__EXPORT__ int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath); + +/** + * \fn int PHYSFS_getMountPoint(const char *dir); + * \brief Determine a mounted archive's mountpoint. + * + * You give this function the name of an archive or dir you successfully + * added to the search path, and it reports the location in the interpolated + * tree where it is mounted. Files mounted with a NULL mountpoint or through + * PHYSFS_addToSearchPath() will report "/". The return value is READ ONLY + * and valid until the archive is removed from the search path. + * + * \param dir directory or archive previously added to the path, in + * platform-dependent notation. This must match the string + * used when adding, even if your string would also reference + * the same file with a different string of characters. + * \return READ-ONLY string of mount point if added to path, NULL on failure + * (bogus archive, etc) Specifics of the error can be gleaned from + * PHYSFS_getLastError(). + * + * \sa PHYSFS_removeFromSearchPath + * \sa PHYSFS_getSearchPath + * \sa PHYSFS_getMountPoint + */ +__EXPORT__ const char *PHYSFS_getMountPoint(const char *dir); + + /* * it is not safe to call physfs functions in these callbacks, as they may * be holding non recursive mutexes.