More mountpoint work and other cleanups.
authorRyan C. Gordon <icculus@icculus.org>
Mon, 14 Mar 2005 07:15:40 +0000
changeset 687 f76dffa43fa2
parent 686 4c8bc5f86c94
child 688 067a55bf46ba
More mountpoint work and other cleanups.
CHANGELOG
physfs.c
physfs.h
--- a/CHANGELOG	Mon Mar 14 07:14:58 2005 +0000
+++ b/CHANGELOG	Mon Mar 14 07:15:40 2005 +0000
@@ -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
--- a/physfs.c	Mon Mar 14 07:14:58 2005 +0000
+++ b/physfs.c	Mon Mar 14 07:15:40 2005 +0000
@@ -535,6 +535,44 @@
 } /* 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 @@
     
     dh->funcs->dirClose(dh->opaque);
     free(dh->dirName);
+    free(dh->mountPoint);
     free(dh);
     return(1);
 } /* freeDirHandle */
@@ -1023,6 +1062,25 @@
 } /* 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 @@
  * 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 @@
     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 @@
     } /* if */
 
     return(retval);
-} /* __PHYSFS_verifySecurity */
+} /* verifyPath */
 
 
 int PHYSFS_mkdir(const char *_dname)
@@ -1301,9 +1368,9 @@
     __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);
+    BAIL_IF_MACRO_MUTEX(!verifyPath(h, &dname, 1), NULL, stateLock, 0);
+
     start = dname;
-
     while (1)
     {
         end = strchr(start, '/');
@@ -1345,7 +1412,7 @@
 
     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 @@
     __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 @@
     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_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 @@
     __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 @@
     __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 @@
     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 @@
     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 */
--- a/physfs.h	Mon Mar 14 07:14:58 2005 +0000
+++ b/physfs.h	Mon Mar 14 07:15:40 2005 +0000
@@ -493,13 +493,17 @@
  * \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 @@
  *  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);
 
@@ -669,53 +675,16 @@
 
 
 /**
- * \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 @@
  *   \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 @@
  *  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 @@
  *
  *    \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 @@
 __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.