Reworked enumeration to be more powerful.
authorRyan C. Gordon <icculus@icculus.org>
Sat, 12 Aug 2017 02:19:22 -0400
changeset 1559 aaee2b83291f
parent 1558 f910eacfda41
child 1560 6db57d6bff9a
Reworked enumeration to be more powerful. Now callbacks can stop further enumeration and report errors, if they had a catastrophic issue or just found what they needed and don't need to process any more items. Also, the actual enumerators can report errors instead of silently dropping items. This led to several other fixes as all these implementations got audited and reworked. The original, non-callback enumerator now returns NULL if it can't produce a complete list instead of dropping items and returning a partial list.
src/physfs.c
src/physfs.h
src/physfs_archiver_7z.c
src/physfs_archiver_dir.c
src/physfs_archiver_grp.c
src/physfs_archiver_hog.c
src/physfs_archiver_iso9660.c
src/physfs_archiver_mvl.c
src/physfs_archiver_qpak.c
src/physfs_archiver_slb.c
src/physfs_archiver_vdf.c
src/physfs_archiver_wad.c
src/physfs_archiver_zip.c
src/physfs_internal.h
src/physfs_platform_os2.c
src/physfs_platform_posix.c
src/physfs_platform_windows.c
--- a/src/physfs.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs.c	Sat Aug 12 02:19:22 2017 -0400
@@ -742,6 +742,7 @@
         case PHYSFS_ERR_OS_ERROR: return "OS reported an error";
         case PHYSFS_ERR_DUPLICATE: return "duplicate resource";
         case PHYSFS_ERR_BAD_PASSWORD: return "bad password";
+        case PHYSFS_ERR_APP_CALLBACK: return "app callback reported error";
     } /* switch */
 
     return NULL;  /* don't know this error code. */
@@ -1430,7 +1431,7 @@
     BAIL_IF(!_archiver->info.author, PHYSFS_ERR_INVALID_ARGUMENT, 0);
     BAIL_IF(!_archiver->info.url, PHYSFS_ERR_INVALID_ARGUMENT, 0);
     BAIL_IF(!_archiver->openArchive, PHYSFS_ERR_INVALID_ARGUMENT, 0);
-    BAIL_IF(!_archiver->enumerateFiles, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+    BAIL_IF(!_archiver->enumerate, PHYSFS_ERR_INVALID_ARGUMENT, 0);
     BAIL_IF(!_archiver->openRead, PHYSFS_ERR_INVALID_ARGUMENT, 0);
     BAIL_IF(!_archiver->openWrite, PHYSFS_ERR_INVALID_ARGUMENT, 0);
     BAIL_IF(!_archiver->openAppend, PHYSFS_ERR_INVALID_ARGUMENT, 0);
@@ -1910,6 +1911,7 @@
     /* Root out archives, and add them to search path... */
     if (archiveExt != NULL)
     {
+        /* !!! FIXME-3.0: turn this into a callback */
         char **rc = PHYSFS_enumerateFiles("/");
         char **i;
         size_t extlen = strlen(archiveExt);
@@ -2212,7 +2214,7 @@
 } /* locateInStringList */
 
 
-static void enumFilesCallback(void *data, const char *origdir, const char *str)
+static int enumFilesCallback(void *data, const char *origdir, const char *str)
 {
     PHYSFS_uint32 pos;
     void *ptr;
@@ -2225,7 +2227,7 @@
      */
     pos = pecd->size;
     if (locateInStringList(str, pecd->list, &pos))
-        return;  /* already in the list. */
+        return 1;  /* already in the list, but keep going. */
 
     ptr = allocator.Realloc(pecd->list, (pecd->size + 2) * sizeof (char *));
     newstr = (char *) allocator.Malloc(strlen(str) + 1);
@@ -2233,7 +2235,13 @@
         pecd->list = (char **) ptr;
 
     if ((ptr == NULL) || (newstr == NULL))
-        return;  /* better luck next time. */
+    {
+        if (newstr)
+            allocator.Free(newstr);
+
+        pecd->errcode = PHYSFS_ERR_OUT_OF_MEMORY;
+        return -1;  /* better luck next time. */
+    } /* if */
 
     strcpy(newstr, str);
 
@@ -2245,6 +2253,8 @@
 
     pecd->list[pos] = newstr;
     pecd->size++;
+
+    return 1;
 } /* enumFilesCallback */
 
 
@@ -2254,7 +2264,17 @@
     memset(&ecd, '\0', sizeof (ecd));
     ecd.list = (char **) allocator.Malloc(sizeof (char *));
     BAIL_IF(!ecd.list, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
-    PHYSFS_enumerateFilesCallback(path, enumFilesCallback, &ecd);
+    if (!PHYSFS_enumerate(path, enumFilesCallback, &ecd))
+    {
+        const PHYSFS_ErrorCode errcode = currentErrorCode();
+        PHYSFS_uint32 i;
+        for (i = 0; i < ecd.size; i++)
+            allocator.Free(ecd.list[i]);
+        allocator.Free(ecd.list);
+        BAIL_IF(errcode == PHYSFS_ERR_APP_CALLBACK, ecd.errcode, NULL);
+        return NULL;
+    } /* if */
+
     ecd.list[ecd.size] = NULL;
     return ecd.list;
 } /* PHYSFS_enumerateFiles */
@@ -2263,8 +2283,8 @@
 /*
  * Broke out to seperate function so we can use stack allocation gratuitously.
  */
-static void enumerateFromMountPoint(DirHandle *i, const char *arcfname,
-                                    PHYSFS_EnumFilesCallback callback,
+static int enumerateFromMountPoint(DirHandle *i, const char *arcfname,
+                                    PHYSFS_EnumerateCallback callback,
                                     const char *_fname, void *data)
 {
     const size_t len = strlen(arcfname);
@@ -2272,70 +2292,89 @@
     char *end = NULL;
     const size_t slen = strlen(i->mountPoint) + 1;
     char *mountPoint = (char *) __PHYSFS_smallAlloc(slen);
-
-    if (mountPoint == NULL)
-        return;  /* oh well. */
+    int rc;
+
+    BAIL_IF(!mountPoint, PHYSFS_ERR_OUT_OF_MEMORY, -1);
 
     strcpy(mountPoint, i->mountPoint);
     ptr = mountPoint + ((len) ? len + 1 : 0);
     end = strchr(ptr, '/');
     assert(end);  /* should always find a terminating '/'. */
     *end = '\0';
-    callback(data, _fname, ptr);
+    rc = callback(data, _fname, ptr);
     __PHYSFS_smallFree(mountPoint);
+
+    BAIL_IF(rc == -1, PHYSFS_ERR_APP_CALLBACK, -1);
+    return rc;
 } /* enumerateFromMountPoint */
 
 
 typedef struct SymlinkFilterData
 {
-    PHYSFS_EnumFilesCallback callback;
+    PHYSFS_EnumerateCallback callback;
     void *callbackData;
     DirHandle *dirhandle;
+    PHYSFS_ErrorCode errcode;
 } SymlinkFilterData;
 
 /* !!! FIXME-3.0: broken if in a virtual mountpoint (stat call fails). */
-static void enumCallbackFilterSymLinks(void *_data, const char *origdir,
-                                       const char *fname)
+static int enumCallbackFilterSymLinks(void *_data, const char *origdir,
+                                      const char *fname)
 {
+    SymlinkFilterData *data = (SymlinkFilterData *) _data;
+    const DirHandle *dh = data->dirhandle;
+    PHYSFS_Stat statbuf;
     const char *trimmedDir = (*origdir == '/') ? (origdir+1) : origdir;
     const size_t slen = strlen(trimmedDir) + strlen(fname) + 2;
     char *path = (char *) __PHYSFS_smallAlloc(slen);
-
-    if (path != NULL)
+    int retval = 1;
+
+    if (path == NULL)
     {
-        SymlinkFilterData *data = (SymlinkFilterData *) _data;
-        const DirHandle *dh = data->dirhandle;
-        PHYSFS_Stat statbuf;
-
-        snprintf(path, slen, "%s%s%s", trimmedDir, *trimmedDir ? "/" : "", fname);
-        if (dh->funcs->stat(dh->opaque, path, &statbuf))
+        data->errcode = PHYSFS_ERR_OUT_OF_MEMORY;
+        return -1;
+    } /* if */
+
+    snprintf(path, slen, "%s%s%s", trimmedDir, *trimmedDir ? "/" : "", fname);
+
+    if (!dh->funcs->stat(dh->opaque, path, &statbuf))
+    {
+        data->errcode = currentErrorCode();
+        retval = -1;
+    } /* if */
+    else
+    {
+        /* Pass it on to the application if it's not a symlink. */
+        if (statbuf.filetype != PHYSFS_FILETYPE_SYMLINK)
         {
-            /* Pass it on to the application if it's not a symlink. */
-            if (statbuf.filetype != PHYSFS_FILETYPE_SYMLINK)
-                data->callback(data->callbackData, origdir, fname);
+            retval = data->callback(data->callbackData, origdir, fname);
+            if (retval == -1)
+                data->errcode = PHYSFS_ERR_APP_CALLBACK;
         } /* if */
-
-        __PHYSFS_smallFree(path);
-    } /* if */
+    } /* else */
+
+    __PHYSFS_smallFree(path);
+
+    return retval;
 } /* enumCallbackFilterSymLinks */
 
 
-/* !!! FIXME-3.0: this should report error conditions. */
-void PHYSFS_enumerateFilesCallback(const char *_fname,
-                                   PHYSFS_EnumFilesCallback callback,
-                                   void *data)
+int PHYSFS_enumerate(const char *_fn, PHYSFS_EnumerateCallback cb, void *data)
 {
+    int retval = 1;
     size_t len;
     char *fname;
 
-    BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, ) /*0*/;
-    BAIL_IF(!callback, PHYSFS_ERR_INVALID_ARGUMENT, ) /*0*/;
-
-    len = strlen(_fname) + 1;
+    BAIL_IF(!_fn, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+    BAIL_IF(!cb, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+    len = strlen(_fn) + 1;
     fname = (char *) __PHYSFS_smallAlloc(len);
-    BAIL_IF(!fname, PHYSFS_ERR_OUT_OF_MEMORY, ) /*0*/;
-
-    if (sanitizePlatformIndependentPath(_fname, fname))
+    BAIL_IF(!fname, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+
+    if (!sanitizePlatformIndependentPath(_fn, fname))
+        retval = 0;
+    else
     {
         DirHandle *i;
         SymlinkFilterData filterdata;
@@ -2345,36 +2384,70 @@
         if (!allowSymLinks)
         {
             memset(&filterdata, '\0', sizeof (filterdata));
-            filterdata.callback = callback;
+            filterdata.callback = cb;
             filterdata.callbackData = data;
         } /* if */
 
-        for (i = searchPath; i != NULL; i = i->next)
+        for (i = searchPath; (retval > 0) && (i != NULL); i = i->next)
         {
             char *arcfname = fname;
+
             if (partOfMountPoint(i, arcfname))
-                enumerateFromMountPoint(i, arcfname, callback, _fname, data);
+                retval = enumerateFromMountPoint(i, arcfname, cb, _fn, data);
 
             else if (verifyPath(i, &arcfname, 0))
             {
                 if ((!allowSymLinks) && (i->funcs->info.supportsSymlinks))
                 {
                     filterdata.dirhandle = i;
-                    i->funcs->enumerateFiles(i->opaque, arcfname,
-                                             enumCallbackFilterSymLinks,
-                                             _fname, &filterdata);
+                    filterdata.errcode = PHYSFS_ERR_OK;
+                    retval = i->funcs->enumerate(i->opaque, arcfname,
+                                                 enumCallbackFilterSymLinks,
+                                                 _fn, &filterdata);
+                    if (retval == -1)
+                    {
+                        if (currentErrorCode() == PHYSFS_ERR_APP_CALLBACK)
+                            PHYSFS_setErrorCode(filterdata.errcode);
+                    } /* if */
                 } /* if */
                 else
                 {
-                    i->funcs->enumerateFiles(i->opaque, arcfname,
-                                             callback, _fname, data);
+                    retval = i->funcs->enumerate(i->opaque, arcfname,
+                                                 cb, _fn, data);
                 } /* else */
             } /* else if */
         } /* for */
+
         __PHYSFS_platformReleaseMutex(stateLock);
     } /* if */
 
     __PHYSFS_smallFree(fname);
+
+    return (retval < 0) ? 0 : 1;
+} /* PHYSFS_enumerate */
+
+
+typedef struct
+{
+    /* can't use the typedef because it might trigger deprecation warnings. */
+    void (*callback)(void *data, const char *origdir, const char *fname);
+    void *data;
+} LegacyEnumFilesCallbackData;
+
+static int enumFilesCallbackAlwaysSucceed(void *data, const char *origdir,
+                                          const char *fname)
+{
+    LegacyEnumFilesCallbackData *cbdata = (LegacyEnumFilesCallbackData *) data;
+    cbdata->callback(cbdata->data, origdir, fname);
+    return 1;
+} /* enumFilesCallbackAlwaysSucceed */
+
+void PHYSFS_enumerateFilesCallback(const char *fname,
+                                   PHYSFS_EnumFilesCallback callback,
+                                   void *data)
+{
+    LegacyEnumFilesCallbackData cbdata = { callback, data };
+    (void) PHYSFS_enumerate(fname, enumFilesCallbackAlwaysSucceed, &cbdata);
 } /* PHYSFS_enumerateFilesCallback */
 
 
@@ -3138,21 +3211,27 @@
     BAIL(PHYSFS_ERR_NOT_FOUND, NULL);
 } /* __PHYSFS_DirTreeFind */
 
-void __PHYSFS_DirTreeEnumerateFiles(void *opaque, const char *dname,
-                                    PHYSFS_EnumFilesCallback cb,
-                                    const char *origdir, void *callbackdata)
+int __PHYSFS_DirTreeEnumerate(void *opaque, const char *dname,
+                              PHYSFS_EnumerateCallback cb,
+                              const char *origdir, void *callbackdata)
 {
-    __PHYSFS_DirTree *tree = ((__PHYSFS_DirTree *) opaque);
+    __PHYSFS_DirTree *tree = (__PHYSFS_DirTree *) opaque;
     const __PHYSFS_DirTreeEntry *entry = __PHYSFS_DirTreeFind(tree, dname);
     if (entry && entry->isdir)
     {
         for (entry = entry->children; entry; entry = entry->sibling)
         {
-            const char *ptr = strrchr(entry->name, '/');
-            cb(callbackdata, origdir, ptr ? ptr + 1 : entry->name);
+            const char *name = entry->name;
+            const char *ptr = strrchr(name, '/');
+            const int rc = cb(callbackdata, origdir, ptr ? ptr + 1 : name);
+            BAIL_IF(rc == -1, PHYSFS_ERR_APP_CALLBACK, -1);
+            if (rc == 0)
+                return 0;
         } /* for */
     } /* if */
-} /* __PHYSFS_DirTreeEnumerateFiles */
+
+    return 1;
+} /* __PHYSFS_DirTreeEnumerate */
 
 
 void __PHYSFS_DirTreeDeinit(__PHYSFS_DirTree *dt)
--- a/src/physfs.h	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs.h	Sat Aug 12 02:19:22 2017 -0400
@@ -1066,6 +1066,17 @@
  * \fn char **PHYSFS_enumerateFiles(const char *dir)
  * \brief Get a file listing of a search path's directory.
  *
+ * \warning In PhysicsFS versions prior to 2.1, this function would return
+ *          as many items as it could in the face of a failure condition
+ *          (out of memory, disk i/o error, etc). Since this meant apps
+ *          couldn't distinguish between complete success and partial failure,
+ *          and since the function could always return NULL to report
+ *          catastrophic failures anyway, in PhysicsFS 2.1 this function's
+ *          policy changed: it will either return a list of complete results
+ *          or it will return NULL for any failure of any kind, so we can
+ *          guarantee that the enumeration ran to completion and has no gaps
+ *          in its results.
+ *
  * Matching directories are interpolated. That is, if "C:\mydir" is in the
  *  search path and contains a directory "savegames" that contains "x.sav",
  *  "y.sav", and "z.sav", and there is also a "C:\userdir" in the search path
@@ -1097,9 +1108,10 @@
  *  function when you are done with it.
  *
  *    \param dir directory in platform-independent notation to enumerate.
- *   \return Null-terminated array of null-terminated strings.
- *
- * \sa PHYSFS_enumerateFilesCallback
+ *   \return Null-terminated array of null-terminated strings, or NULL for
+ *           failure cases.
+ *
+ * \sa PHYSFS_enumerate
  */
 PHYSFS_DECL char **PHYSFS_enumerateFiles(const char *dir);
 
@@ -2240,6 +2252,9 @@
  * \typedef PHYSFS_EnumFilesCallback
  * \brief Function signature for callbacks that enumerate files.
  *
+ * \deprecated As of PhysicsFS 2.1, Use PHYSFS_EnumerateCallback with
+ *  PHYSFS_enumerate() instead; it gives you more control over the process.
+ *
  * These are used to report a list of directory entries to an original caller,
  *  one file/dir/symlink per callback. All strings are UTF-8 encoded.
  *  Functions should not try to modify or free any string's memory.
@@ -2249,9 +2264,10 @@
  *  PHYSFS_freeList(). The callback means that the library doesn't need to
  *  allocate an entire list and all the strings up front.
  *
- * Be aware that promises data ordering in the list versions are not
+ * Be aware that promised data ordering in the list versions are not
  *  necessarily so in the callback versions. Check the documentation on
- *  specific APIs, but strings may not be sorted as you expect.
+ *  specific APIs, but strings may not be sorted as you expect and you might
+ *  get duplicate strings.
  *
  *    \param data User-defined data pointer, passed through from the API
  *                that eventually called the callback.
@@ -2268,7 +2284,7 @@
  * \sa PHYSFS_enumerateFilesCallback
  */
 typedef void (*PHYSFS_EnumFilesCallback)(void *data, const char *origdir,
-                                         const char *fname);
+                                         const char *fname) PHYSFS_DEPRECATED;
 
 
 /**
@@ -2345,48 +2361,22 @@
  * \fn void PHYSFS_enumerateFilesCallback(const char *dir, PHYSFS_EnumFilesCallback c, void *d)
  * \brief Get a file listing of a search path's directory, using an application-defined callback.
  *
- * Internally, PHYSFS_enumerateFiles() just calls this function and then builds
- *  a list before returning to the application, so functionality is identical
- *  except for how the information is represented to the application.
- *
- * Unlike PHYSFS_enumerateFiles(), this function does not return an array.
- *  Rather, it calls a function specified by the application once per
- *  element of the search path:
- *
- * \code
- *
- * static void printDir(void *data, const char *origdir, const char *fname)
- * {
- *     printf(" * We've got [%s] in [%s].\n", fname, origdir);
- * }
- *
- * // ...
- * PHYSFS_enumerateFilesCallback("/some/path", printDir, NULL);
- * \endcode
- *
- * !!! FIXME-3.0: enumerateFiles() does not promise alphabetical sorting by
- * !!! FIXME:  case-sensitivity in the code, and doesn't promise sorting at
- * !!! FIXME:  all in the above docs.
- *
- * Items sent to the callback are not guaranteed to be in any order whatsoever.
- *  There is no sorting done at this level, and if you need that, you should
- *  probably use PHYSFS_enumerateFiles() instead, which guarantees
- *  alphabetical sorting. This form reports whatever is discovered in each
- *  archive before moving on to the next. Even within one archive, we can't
- *  guarantee what order it will discover data. <em>Any sorting you find in
- *  these callbacks is just pure luck. Do not rely on it.</em> As this walks
- *  the entire list of archives, you may receive duplicate filenames.
- *
- *    \param dir Directory, in platform-independent notation, to enumerate.
- *    \param c Callback function to notify about search path elements.
- *    \param d Application-defined data passed to callback. Can be NULL.
- *
+ * \deprecated As of PhysicsFS 2.1, use PHYSFS_enumerate() instead. This
+ *  function has no way to report errors (or to have the callback signal an
+ *  error or request a stop), so if data will be lost, your callback has no
+ *  way to direct the process, and your calling app has no way to know.
+ *
+ * As of PhysicsFS 2.1, this function just wraps PHYSFS_enumerate() and
+ *  ignores errors. Consider using PHYSFS_enumerate() or
+ *  PHYSFS_enumerateFiles() instead.
+ *
+ * \sa PHYSFS_enumerate
+ * \sa PHYSFS_enumerateFiles
  * \sa PHYSFS_EnumFilesCallback
- * \sa PHYSFS_enumerateFiles
  */
 PHYSFS_DECL void PHYSFS_enumerateFilesCallback(const char *dir,
                                                PHYSFS_EnumFilesCallback c,
-                                               void *d);
+                                               void *d) PHYSFS_DEPRECATED;
 
 /**
  * \fn void PHYSFS_utf8FromUcs4(const PHYSFS_uint32 *src, char *dst, PHYSFS_uint64 len)
@@ -2544,6 +2534,99 @@
  */
 PHYSFS_DECL int PHYSFS_utf8stricmp(const char *str1, const char *str2);
 
+/**
+ * \typedef PHYSFS_EnumerateCallback
+ * \brief Function signature for callbacks that enumerate and return results.
+ *
+ * This is the same thing as PHYSFS_EnumFilesCallback from PhysicsFS 2.0,
+ *  except it can return a result from the callback: namely: if you're looking
+ *  for something specific, once you find it, you can tell PhysicsFS to stop
+ *  enumerating further. This is used with PHYSFS_enumerate(), which we
+ *  hopefully got right this time.  :)
+ *
+ *    \param data User-defined data pointer, passed through from the API
+ *                that eventually called the callback.
+ *    \param origdir A string containing the full path, in platform-independent
+ *                   notation, of the directory containing this file. In most
+ *                   cases, this is the directory on which you requested
+ *                   enumeration, passed in the callback for your convenience.
+ *    \param fname The filename that is being enumerated. It may not be in
+ *                 alphabetical order compared to other callbacks that have
+ *                 fired, and it will not contain the full path. You can
+ *                 recreate the fullpath with $origdir/$fname ... The file
+ *                 can be a subdirectory, a file, a symlink, etc.
+ *   \return 1 to keep enumerating, 0 to stop (no error), -1 to stop (error).
+ *           All other values are (currently) undefined; don't use them.
+ *
+ * \sa PHYSFS_enumerate
+ */
+typedef int (*PHYSFS_EnumerateCallback)(void *data, const char *origdir,
+                                         const char *fname);
+
+/**
+ * \fn int PHYSFS_enumerate(const char *dir, PHYSFS_EnumerateCallback c, void *d)
+ * \brief Get a file listing of a search path's directory, using an application-defined callback, with errors reported.
+ *
+ * Internally, PHYSFS_enumerateFiles() just calls this function and then builds
+ *  a list before returning to the application, so functionality is identical
+ *  except for how the information is represented to the application.
+ *
+ * Unlike PHYSFS_enumerateFiles(), this function does not return an array.
+ *  Rather, it calls a function specified by the application once per
+ *  element of the search path:
+ *
+ * \code
+ *
+ * static int printDir(void *data, const char *origdir, const char *fname)
+ * {
+ *     printf(" * We've got [%s] in [%s].\n", fname, origdir);
+ *     return 1;  // give me more data, please.
+ * }
+ *
+ * // ...
+ * PHYSFS_enumerate("/some/path", printDir, NULL);
+ * \endcode
+ *
+ * !!! FIXME-3.0: enumerateFiles() does not promise alphabetical sorting by
+ * !!! FIXME:  case-sensitivity in the code, and doesn't promise sorting at
+ * !!! FIXME:  all in the above docs.
+ *
+ * Items sent to the callback are not guaranteed to be in any order whatsoever.
+ *  There is no sorting done at this level, and if you need that, you should
+ *  probably use PHYSFS_enumerateFiles() instead, which guarantees
+ *  alphabetical sorting. This form reports whatever is discovered in each
+ *  archive before moving on to the next. Even within one archive, we can't
+ *  guarantee what order it will discover data. <em>Any sorting you find in
+ *  these callbacks is just pure luck. Do not rely on it.</em> As this walks
+ *  the entire list of archives, you may receive duplicate filenames.
+ *
+ * This API and the callbacks themselves are capable of reporting errors.
+ *  Prior to this API, callbacks had to accept every enumerated item, even if
+ *  they were only looking for a specific thing and wanted to stop after that,
+ *  or had a serious error and couldn't alert anyone. Furthermore, if
+ *  PhysicsFS itself had a problem (disk error or whatnot), it couldn't report
+ *  it to the calling app, it would just have to skip items or stop
+ *  enumerating outright, and the caller wouldn't know it had lost some data
+ *  along the way.
+ *
+ * Now the caller can be sure it got a complete data set, and its callback has
+ *  control if it wants enumeration to stop early. See the documentation for
+ *  PHYSFS_EnumerateCallback for details on how your callback should behave.
+ *
+ *    \param dir Directory, in platform-independent notation, to enumerate.
+ *    \param c Callback function to notify about search path elements.
+ *    \param d Application-defined data passed to callback. Can be NULL.
+ *   \return non-zero on success, zero on failure. Specifics of the error can
+ *           be gleaned from PHYSFS_getLastError(). If the callback returns
+ *           zero to stop early, this will considered success. Callbacks
+ *           returning -1 will result in PHYSFS_ERR_APP_CALLBACK.
+ *
+ * \sa PHYSFS_EnumerateCallback
+ * \sa PHYSFS_enumerateFiles
+ */
+PHYSFS_DECL int PHYSFS_enumerate(const char *dir, PHYSFS_EnumerateCallback c,
+                                 void *d);
+
 
 /**
  * \fn int PHYSFS_unmount(const char *oldDir)
@@ -3157,7 +3240,8 @@
     PHYSFS_ERR_DIR_NOT_EMPTY,    /**< Tried to delete dir with files in it. */
     PHYSFS_ERR_OS_ERROR,         /**< Unspecified OS-level error.           */
     PHYSFS_ERR_DUPLICATE,        /**< Duplicate entry.                      */
-    PHYSFS_ERR_BAD_PASSWORD      /**< Bad password.                         */
+    PHYSFS_ERR_BAD_PASSWORD,     /**< Bad password.                         */
+    PHYSFS_ERR_APP_CALLBACK      /**< Application callback reported error.  */
 } PHYSFS_ErrorCode;
 
 
@@ -3417,14 +3501,28 @@
 
     /**
      * List all files in (dirname). Each file is passed to (cb),
-     *  where a copy is made if appropriate, so you should dispose of
-     *  it properly upon return from the callback.
-     * If you have a failure, report as much as you can.
+     *  where a copy is made if appropriate, so you can dispose of
+     *  it, if appropriate, upon return from the callback.
      *  (dirname) is in platform-independent notation.
+     * If you have a failure, call PHYSFS_SetErrorCode() with whatever code
+     *  seem appropriate and return -1.
+     * If the callback returns -1, please call
+     *  PHYSFS_SetErrorCode(PHYSFS_ERR_APP_CALLBACK) and then return -1.
+     * If the callback returns 0, stop enumerating and return 0. Don't call
+     *  the callback again in any circumstances. Don't set an error code in
+     *  this case.
+     * Callbacks are (currently) only supposed to return -1, 0, or 1. Any
+     *  other result has undefined behavior.
+     * As long as the callback returned 1 and you haven't experienced any
+     *  errors of your own, keep enumerating until you're done and then return
+     *  1 without setting an error code.
+     *
+     * \warning PHYSFS_enumerate returns zero or non-zero (success or failure),
+     *          so be aware this function pointer returns different values!
      */
-    void (*enumerateFiles)(void *opaque, const char *dirname,
-                           PHYSFS_EnumFilesCallback cb,
-                           const char *origdir, void *callbackdata);
+    int (*enumerate)(void *opaque, const char *dirname,
+                     PHYSFS_EnumerateCallback cb,
+                     const char *origdir, void *callbackdata);
 
     /**
      * Open file for reading.
--- a/src/physfs_archiver_7z.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_7z.c	Sat Aug 12 02:19:22 2017 -0400
@@ -398,7 +398,7 @@
         0,  /* supportsSymlinks */
     },
     SZIP_openArchive,
-    __PHYSFS_DirTreeEnumerateFiles,
+    __PHYSFS_DirTreeEnumerate,
     SZIP_openRead,
     SZIP_openWrite,
     SZIP_openAppend,
--- a/src/physfs_archiver_dir.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_dir.c	Sat Aug 12 02:19:22 2017 -0400
@@ -66,19 +66,18 @@
 } /* DIR_openArchive */
 
 
-static void DIR_enumerateFiles(void *opaque, const char *dname,
-                               PHYSFS_EnumFilesCallback cb,
-                               const char *origdir, void *callbackdata)
+static int DIR_enumerate(void *opaque, const char *dname,
+                         PHYSFS_EnumerateCallback cb,
+                         const char *origdir, void *callbackdata)
 {
     char *d;
-
+    int retval;
     CVT_TO_DEPENDENT(d, opaque, dname);
-    if (d != NULL)
-    {
-        __PHYSFS_platformEnumerateFiles(d, cb, origdir, callbackdata);
-        __PHYSFS_smallFree(d);
-    } /* if */
-} /* DIR_enumerateFiles */
+    BAIL_IF_ERRPASS(!d, -1);
+    retval = __PHYSFS_platformEnumerate(d, cb, origdir, callbackdata);
+    __PHYSFS_smallFree(d);
+    return retval;
+} /* DIR_enumerate */
 
 
 static PHYSFS_Io *doOpen(void *opaque, const char *name, const int mode)
@@ -178,7 +177,7 @@
         1,  /* supportsSymlinks */
     },
     DIR_openArchive,
-    DIR_enumerateFiles,
+    DIR_enumerate,
     DIR_openRead,
     DIR_openWrite,
     DIR_openAppend,
--- a/src/physfs_archiver_grp.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_grp.c	Sat Aug 12 02:19:22 2017 -0400
@@ -97,7 +97,7 @@
         0,  /* supportsSymlinks */
     },
     GRP_openArchive,
-    UNPK_enumerateFiles,
+    UNPK_enumerate,
     UNPK_openRead,
     UNPK_openWrite,
     UNPK_openAppend,
--- a/src/physfs_archiver_hog.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_hog.c	Sat Aug 12 02:19:22 2017 -0400
@@ -95,7 +95,7 @@
         0,  /* supportsSymlinks */
     },
     HOG_openArchive,
-    UNPK_enumerateFiles,
+    UNPK_enumerate,
     UNPK_openRead,
     UNPK_openWrite,
     UNPK_openAppend,
--- a/src/physfs_archiver_iso9660.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_iso9660.c	Sat Aug 12 02:19:22 2017 -0400
@@ -349,7 +349,7 @@
         0,  /* supportsSymlinks */
     },
     ISO9660_openArchive,
-    UNPK_enumerateFiles,
+    UNPK_enumerate,
     UNPK_openRead,
     UNPK_openWrite,
     UNPK_openAppend,
--- a/src/physfs_archiver_mvl.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_mvl.c	Sat Aug 12 02:19:22 2017 -0400
@@ -91,7 +91,7 @@
         0,  /* supportsSymlinks */
     },
     MVL_openArchive,
-    UNPK_enumerateFiles,
+    UNPK_enumerate,
     UNPK_openRead,
     UNPK_openWrite,
     UNPK_openAppend,
--- a/src/physfs_archiver_qpak.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_qpak.c	Sat Aug 12 02:19:22 2017 -0400
@@ -107,7 +107,7 @@
         0,  /* supportsSymlinks */
     },
     QPAK_openArchive,
-    UNPK_enumerateFiles,
+    UNPK_enumerate,
     UNPK_openRead,
     UNPK_openWrite,
     UNPK_openAppend,
--- a/src/physfs_archiver_slb.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_slb.c	Sat Aug 12 02:19:22 2017 -0400
@@ -117,7 +117,7 @@
         0,  /* supportsSymlinks */
     },
     SLB_openArchive,
-    UNPK_enumerateFiles,
+    UNPK_enumerate,
     UNPK_openRead,
     UNPK_openWrite,
     UNPK_openAppend,
--- a/src/physfs_archiver_vdf.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_vdf.c	Sat Aug 12 02:19:22 2017 -0400
@@ -144,7 +144,7 @@
         0,  /* supportsSymlinks */
     },
     VDF_openArchive,
-    UNPK_enumerateFiles,
+    UNPK_enumerate,
     UNPK_openRead,
     UNPK_openWrite,
     UNPK_openAppend,
--- a/src/physfs_archiver_wad.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_wad.c	Sat Aug 12 02:19:22 2017 -0400
@@ -116,7 +116,7 @@
         0,  /* supportsSymlinks */
     },
     WAD_openArchive,
-    UNPK_enumerateFiles,
+    UNPK_enumerate,
     UNPK_openRead,
     UNPK_openWrite,
     UNPK_openAppend,
--- a/src/physfs_archiver_zip.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_archiver_zip.c	Sat Aug 12 02:19:22 2017 -0400
@@ -1678,7 +1678,7 @@
         1,  /* supportsSymlinks */
     },
     ZIP_openArchive,
-    __PHYSFS_DirTreeEnumerateFiles,
+    __PHYSFS_DirTreeEnumerate,
     ZIP_openRead,
     ZIP_openWrite,
     ZIP_openAppend,
--- a/src/physfs_internal.h	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_internal.h	Sat Aug 12 02:19:22 2017 -0400
@@ -346,7 +346,7 @@
 int UNPK_remove(void *opaque, const char *name);
 int UNPK_mkdir(void *opaque, const char *name);
 int UNPK_stat(void *opaque, const char *fn, PHYSFS_Stat *st);
-#define UNPK_enumerateFiles __PHYSFS_DirTreeEnumerateFiles
+#define UNPK_enumerate __PHYSFS_DirTreeEnumerate
 
 
 
@@ -374,9 +374,9 @@
 int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen);
 void *__PHYSFS_DirTreeAdd(__PHYSFS_DirTree *dt, char *name, const int isdir);
 void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path);
-void __PHYSFS_DirTreeEnumerateFiles(void *opaque, const char *dname,
-                                    PHYSFS_EnumFilesCallback cb,
-                                    const char *origdir, void *callbackdata);
+int __PHYSFS_DirTreeEnumerate(void *opaque, const char *dname,
+                              PHYSFS_EnumerateCallback cb,
+                              const char *origdir, void *callbackdata);
 void __PHYSFS_DirTreeDeinit(__PHYSFS_DirTree *dt);
 
 
@@ -615,16 +615,15 @@
 
 /*
  * Enumerate a directory of files. This follows the rules for the
- *  PHYSFS_Archiver::enumerateFiles() method, except that the
- *  (dirName) that is passed to this function is converted to
- *  platform-DEPENDENT notation by the caller. The PHYSFS_Archiver version
- *  uses platform-independent notation. Note that ".", "..", and other
- *  meta-entries should always be ignored.
+ *  PHYSFS_Archiver::enumerate() method, except that the (dirName) that is
+ *  passed to this function is converted to platform-DEPENDENT notation by
+ *  the caller. The PHYSFS_Archiver version uses platform-independent
+ *  notation. Note that ".", "..", and other meta-entries should always
+ *  be ignored.
  */
-void __PHYSFS_platformEnumerateFiles(const char *dirname,
-                                     PHYSFS_EnumFilesCallback callback,
-                                     const char *origdir,
-                                     void *callbackdata);
+int __PHYSFS_platformEnumerate(const char *dirname,
+                               PHYSFS_EnumerateCallback callback,
+                               const char *origdir, void *callbackdata);
 
 /*
  * Make a directory in the actual filesystem. (path) is specified in
--- a/src/physfs_platform_os2.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_platform_os2.c	Sat Aug 12 02:19:22 2017 -0400
@@ -390,10 +390,9 @@
     return __PHYSFS_platformCalcBaseDir(NULL);  /* !!! FIXME-3.0: ? */
 } /* __PHYSFS_platformCalcPrefDir */
 
-void __PHYSFS_platformEnumerateFiles(const char *dirname,
-                                     PHYSFS_EnumFilesCallback callback,
-                                     const char *origdir,
-                                     void *callbackdata)
+int __PHYSFS_platformEnumerate(const char *dirname,
+                               PHYSFS_EnumerateCallback callback,
+                               const char *origdir, void *callbackdata)
 {                                        
     size_t utf8len = strlen(dirname);
     char *utf8 = (char *) __PHYSFS_smallAlloc(utf8len + 5);
@@ -402,8 +401,10 @@
     HDIR hdir = HDIR_CREATE;
     ULONG count = 1;
     APIRET rc;
+    int cbrc;
+    int retval = 1;
 
-    BAIL_IF(!utf8, PHYSFS_ERR_OUT_OF_MEMORY,);
+    BAIL_IF(!utf8, PHYSFS_ERR_OUT_OF_MEMORY, -1);
 
     strcpy(utf8, dirname);
     if (utf8[utf8len - 1] != '\\')
@@ -413,8 +414,7 @@
 
     cpspec = cvtUtf8ToCodepage(utf8);
     __PHYSFS_smallFree(utf8);
-    if (!cpspec)
-        return;
+    BAIL_IF_ERRPASS(!cpspec, -1);
 
     rc = DosFindFirst((unsigned char *) cpspec, &hdir,
                       FILE_DIRECTORY | FILE_ARCHIVED |
@@ -422,24 +422,34 @@
                       &fb, sizeof (fb), &count, FIL_STANDARD);
     allocator.Free(cpspec);
 
-    BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc),);
+    BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), -1);
 
     while (count == 1)
     {
         if ((strcmp(fb.achName, ".") != 0) && (strcmp(fb.achName, "..") != 0))
         {
             utf8 = cvtCodepageToUtf8(fb.achName);
-            if (utf8)
+            if (!utf8)
+                retval = -1;
+            else
             {
-                callback(callbackdata, origdir, utf8);
+                retval = callback(callbackdata, origdir, utf8);
                 allocator.Free(utf8);
-            } /* if */
+                if (retval == -1)
+                    PHYSFS_SetErrorCode(PHYSFS_ERR_APP_CALLBACK);
+            } /* else */
         } /* if */
+
+        if (retval != 1)
+            break;
+
         DosFindNext(hdir, &fb, sizeof (fb), &count);
     } /* while */
 
     DosFindClose(hdir);
-} /* __PHYSFS_platformEnumerateFiles */
+
+    return retval;
+} /* __PHYSFS_platformEnumerate */
 
 
 char *__PHYSFS_platformCurrentDir(void)
--- a/src/physfs_platform_posix.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_platform_posix.c	Sat Aug 12 02:19:22 2017 -0400
@@ -118,36 +118,35 @@
 } /* __PHYSFS_platformCalcUserDir */
 
 
-void __PHYSFS_platformEnumerateFiles(const char *dirname,
-                                     PHYSFS_EnumFilesCallback callback,
-                                     const char *origdir,
-                                     void *callbackdata)
+int __PHYSFS_platformEnumerate(const char *dirname,
+                               PHYSFS_EnumerateCallback callback,
+                               const char *origdir, void *callbackdata)
 {
     DIR *dir;
     struct dirent *ent;
-    char *buf = NULL;
+    int retval = 1;
 
-    errno = 0;
     dir = opendir(dirname);
-    if (dir == NULL)
+    BAIL_IF(dir == NULL, errcodeFromErrno(), -1);
+
+    while ((retval == 1) && ((ent = readdir(dir)) != NULL))
     {
-        allocator.Free(buf);
-        return;
-    } /* if */
+        const char *name = ent->d_name;
+        if (name[0] == '.')  /* ignore "." and ".." */
+        {
+            if ((name[1] == '\0') || ((name[1] == '.') && (name[2] == '\0')))
+                continue;
+        } /* if */
 
-    while ((ent = readdir(dir)) != NULL)
-    {
-        if (strcmp(ent->d_name, ".") == 0)
-            continue;
-        else if (strcmp(ent->d_name, "..") == 0)
-            continue;
-
-        callback(callbackdata, origdir, ent->d_name);
+        retval = callback(callbackdata, origdir, name);
+        if (retval == -1)
+            PHYSFS_setErrorCode(PHYSFS_ERR_APP_CALLBACK);
     } /* while */
 
-    allocator.Free(buf);
     closedir(dir);
-} /* __PHYSFS_platformEnumerateFiles */
+
+    return retval;
+} /* __PHYSFS_platformEnumerate */
 
 
 int __PHYSFS_platformMkDir(const char *path)
--- a/src/physfs_platform_windows.c	Fri Aug 11 01:45:29 2017 -0400
+++ b/src/physfs_platform_windows.c	Sat Aug 12 02:19:22 2017 -0400
@@ -621,21 +621,20 @@
 } /* __PHYSFS_platformGetThreadID */
 
 
-void __PHYSFS_platformEnumerateFiles(const char *dirname,
-                                     PHYSFS_EnumFilesCallback callback,
-                                     const char *origdir,
-                                     void *callbackdata)
+void __PHYSFS_platformEnumerate(const char *dirname,
+                                PHYSFS_EnumerateCallback callback,
+                                const char *origdir, void *callbackdata)
 {
     HANDLE dir = INVALID_HANDLE_VALUE;
     WIN32_FIND_DATAW entw;
     size_t len = strlen(dirname);
     char *searchPath = NULL;
     WCHAR *wSearchPath = NULL;
+    int retval = 1;
 
     /* Allocate a new string for path, maybe '\\', "*", and NULL terminator */
     searchPath = (char *) __PHYSFS_smallAlloc(len + 3);
-    if (searchPath == NULL)
-        return;
+    BAIL_IF(!searchPath, PHYSFS_ERR_OUT_OF_MEMORY, -1);
 
     /* Copy current dirname */
     strcpy(searchPath, dirname);
@@ -651,36 +650,40 @@
     strcat(searchPath, "*");
 
     UTF8_TO_UNICODE_STACK(wSearchPath, searchPath);
-    if (!wSearchPath)
-        return;  /* oh well. */
+    __PHYSFS_smallFree(searchPath);
+    BAIL_IF_ERRPASS(!wSearchPath, -1);
 
     dir = winFindFirstFileW(wSearchPath, &entw);
-
     __PHYSFS_smallFree(wSearchPath);
-    __PHYSFS_smallFree(searchPath);
-    if (dir == INVALID_HANDLE_VALUE)
-        return;
+    BAIL_IF(dir == INVALID_HANDLE_VALUE, errcodeFromWinApi(), -1);
 
     do
     {
         const WCHAR *fn = entw.cFileName;
         char *utf8;
 
-        if ((fn[0] == '.') && (fn[1] == '\0'))
-            continue;
-        if ((fn[0] == '.') && (fn[1] == '.') && (fn[2] == '\0'))
-            continue;
+        if (fn[0] == '.')  /* ignore "." and ".." */
+        {
+            if ((fn[1] == '\0') || ((fn[1] == '.') && (fn[2] == '\0')))
+                continue;
+        } /* if */
 
         utf8 = unicodeToUtf8Heap(fn);
-        if (utf8 != NULL)
+        if (utf8 == NULL)
+            retval = -1;
+        else
         {
-            callback(callbackdata, origdir, utf8);
+            retval = callback(callbackdata, origdir, utf8);
             allocator.Free(utf8);
-        } /* if */
-    } while (FindNextFileW(dir, &entw) != 0);
+            if (retval == -1)
+                PHYSFS_SetErrorCode(PHYSFS_ERR_APP_CALLBACK);
+        } /* else */
+    } while ((retval == 1) && (FindNextFileW(dir, &entw) != 0));
 
     FindClose(dir);
-} /* __PHYSFS_platformEnumerateFiles */
+
+    return retval;
+} /* __PHYSFS_platformEnumerate */
 
 
 int __PHYSFS_platformMkDir(const char *path)