Added buffering API.
authorRyan C. Gordon <icculus@icculus.org>
Sun, 01 Dec 2002 11:21:27 +0000
changeset 508 0e75524a96af
parent 507 e13baf8efaaa
child 509 80c834815397
Added buffering API.
physfs.c
physfs.h
physfs_internal.h
test/test_physfs.c
--- a/physfs.c	Sun Dec 01 11:17:56 2002 +0000
+++ b/physfs.c	Sun Dec 01 11:21:27 2002 +0000
@@ -1227,12 +1227,22 @@
             if (h->funcs->isSymLink(h, str, &retval))
             {
                 __PHYSFS_setError(ERR_SYMLINK_DISALLOWED);
-                retval = 0;
+                free(str);
+                return(0); /* insecure. */
             } /* if */
 
-            /* break out early if path element is missing or it's a symlink. */
+            /* break out early if path element is missing. */
             if (!retval)
+            {
+                /*
+                 * We need to clear it if it's the last element of the path,
+                 *  since this might be a non-existant file we're opening
+                 *  for writing...
+                 */
+                if (end == NULL)
+                    retval = 1;
                 break;
+            } /* if */
         } /* if */
 
         if (end == NULL)
@@ -1569,6 +1579,9 @@
         free(list);
     else
     {
+        rc->buffer = NULL;  /* just in case. */
+        rc->buffill = rc->bufpos = rc->bufsize = 0;  /* just in case. */
+        rc->forReading = 0;
         list->handle.opaque = (void *) rc;
         list->next = openWriteList;
         openWriteList = list;
@@ -1615,13 +1628,17 @@
     BAIL_IF_MACRO_MUTEX(rc == NULL, NULL, stateLock, NULL);
 
     list = (FileHandleList *) malloc(sizeof (FileHandleList));
-    BAIL_IF_MACRO(!list, ERR_OUT_OF_MEMORY, NULL);
+    BAIL_IF_MACRO_MUTEX(!list, ERR_OUT_OF_MEMORY, stateLock, NULL);
     list->handle.opaque = (void *) rc;
     list->next = openReadList;
     openReadList = list;
     retval = &(list->handle);
+    __PHYSFS_platformReleaseMutex(stateLock);
 
-    __PHYSFS_platformReleaseMutex(stateLock);
+    rc->buffer = NULL;  /* just in case. */
+    rc->buffill = rc->bufpos = rc->bufsize = 0;  /* just in case. */
+    rc->forReading = 1;
+
     return(retval);
 } /* PHYSFS_openRead */
 
@@ -1631,16 +1648,22 @@
     FileHandle *h = (FileHandle *) handle->opaque;
     FileHandleList *prev = NULL;
     FileHandleList *i;
-    int rc;
+    int rc = 1;
 
     for (i = *list; i != NULL; i = i->next)
     {
         if (&i->handle == handle)  /* handle is in this list? */
         {
-            rc = h->funcs->fileClose(h);
+            PHYSFS_uint8 *tmp = h->buffer;
+            rc = PHYSFS_flush(handle);
+            if (rc)
+                rc = h->funcs->fileClose(h);
             if (!rc)
                 return(-1);
 
+            if (tmp != NULL)  /* free any associated buffer. */
+                free(tmp);
+
             if (prev == NULL)
                 *list = i->next;
             else
@@ -1677,18 +1700,92 @@
 } /* PHYSFS_close */
 
 
+static PHYSFS_sint64 doBufferedRead(PHYSFS_file *handle, void *buffer,
+                                    PHYSFS_uint32 objSize,
+                                    PHYSFS_uint32 objCount)
+{
+    FileHandle *h = (FileHandle *) handle->opaque;
+    PHYSFS_sint64 retval = 0;
+    PHYSFS_uint32 remainder = 0;
+
+    while (objCount > 0)
+    {
+        PHYSFS_uint64 buffered = h->buffill - h->bufpos;
+        PHYSFS_uint64 mustread = (objSize * objCount) - remainder;
+        PHYSFS_uint32 copied;
+
+        if (buffered == 0) /* need to refill buffer? */
+        {
+            PHYSFS_sint64 rc = h->funcs->read(h, h->buffer, 1, h->bufsize);
+            if (rc <= 0)
+            {
+                h->bufpos -= remainder;
+                return(((rc == -1) && (retval == 0)) ? -1 : retval);
+            } /* if */
+
+            buffered = h->buffill = rc;
+            h->bufpos = 0;
+        } /* if */
+
+        if (buffered > mustread)
+            buffered = mustread;
+
+        memcpy(buffer, h->buffer + h->bufpos, (size_t) buffered);
+        buffer = ((PHYSFS_uint8 *) buffer) + buffered;
+        h->bufpos += buffered;
+        buffered += remainder;  /* take remainder into account. */
+        copied = (buffered / objSize);
+        remainder = (buffered % objSize);
+        retval += copied;
+        objCount -= copied;
+    } /* while */
+
+    return(retval);
+} /* doBufferedRead */
+
+
 PHYSFS_sint64 PHYSFS_read(PHYSFS_file *handle, void *buffer,
                           PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
 {
     FileHandle *h = (FileHandle *) handle->opaque;
+
+    BAIL_IF_MACRO(!h->forReading, ERR_FILE_ALREADY_OPEN_W, -1);
+    if (h->buffer != NULL)
+        return(doBufferedRead(handle, buffer, objSize, objCount));
+
     return(h->funcs->read(h, buffer, objSize, objCount));
 } /* PHYSFS_read */
 
 
+static PHYSFS_sint64 doBufferedWrite(PHYSFS_file *handle, const void *buffer,
+                                     PHYSFS_uint32 objSize,
+                                     PHYSFS_uint32 objCount)
+{
+    FileHandle *h = (FileHandle *) handle->opaque;
+
+    /* whole thing fits in the buffer? */
+    if (h->buffill + (objSize * objCount) < h->bufsize)
+    {
+        memcpy(h->buffer + h->buffill, buffer, objSize * objCount);
+        h->buffill += (objSize * objCount);
+        return(objCount);
+    } /* if */
+
+    /* would overflow buffer. Flush and then write the new objects, too. */
+    BAIL_IF_MACRO(!PHYSFS_flush(handle), NULL, -1);
+    return(h->funcs->write(h, buffer, objSize, objCount));
+} /* doBufferedWrite */
+
+
 PHYSFS_sint64 PHYSFS_write(PHYSFS_file *handle, const void *buffer,
                            PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
 {
     FileHandle *h = (FileHandle *) handle->opaque;
+
+    BAIL_IF_MACRO(h->forReading, ERR_FILE_ALREADY_OPEN_R, -1);
+    if (h->buffer != NULL)
+        return(doBufferedWrite(handle, buffer, objSize, objCount));
+
     return(h->funcs->write(h, buffer, objSize, objCount));
 } /* PHYSFS_write */
 
@@ -1696,20 +1793,29 @@
 int PHYSFS_eof(PHYSFS_file *handle)
 {
     FileHandle *h = (FileHandle *) handle->opaque;
-    return(h->funcs->eof(h));
+
+    if (!h->forReading)  /* never EOF on files opened for write/append. */
+        return(0);
+
+    /* eof if buffer is empty and archiver says so. */
+    return((h->bufpos == h->buffill) && (h->funcs->eof(h)));
 } /* PHYSFS_eof */
 
 
 PHYSFS_sint64 PHYSFS_tell(PHYSFS_file *handle)
 {
     FileHandle *h = (FileHandle *) handle->opaque;
-    return(h->funcs->tell(h));
+    PHYSFS_sint64 retval = h->forReading ?
+                            (h->funcs->tell(h) - h->buffill) + h->bufpos :
+                            (h->funcs->tell(h) + h->buffill);
+    return(retval);
 } /* PHYSFS_tell */
 
 
 int PHYSFS_seek(PHYSFS_file *handle, PHYSFS_uint64 pos)
 {
     FileHandle *h = (FileHandle *) handle->opaque;
+    BAIL_IF_MACRO(!PHYSFS_flush(handle), NULL, 0);
     return(h->funcs->seek(h, pos));
 } /* PHYSFS_seek */
 
@@ -1721,6 +1827,65 @@
 } /* PHYSFS_filelength */
 
 
+int PHYSFS_setBuffer(PHYSFS_file *handle, PHYSFS_uint64 bufsize)
+{
+    FileHandle *h = (FileHandle *) handle->opaque;
+
+    BAIL_IF_MACRO(!PHYSFS_flush(handle), NULL, 0);
+
+    /*
+     * For reads, we need to move the file pointer to where it would be
+     *  if we weren't buffering, so that the next read will get the
+     *  right chunk of stuff from the file. PHYSFS_flush() handles writes.
+     */
+    if ((h->forReading) && (h->buffill != h->bufpos))
+    {
+        PHYSFS_uint64 pos;
+        PHYSFS_sint64 curpos = h->funcs->tell(h);
+        BAIL_IF_MACRO(curpos == -1, NULL, 0);
+        pos = ((curpos - h->buffill) + h->bufpos);
+        BAIL_IF_MACRO(!h->funcs->seek(h, pos), NULL, 0);
+    } /* if */
+
+    if (bufsize == 0)  /* delete existing buffer. */
+    {
+        if (h->buffer != NULL)
+        {
+            free(h->buffer);
+            h->buffer = NULL;
+        } /* if */
+    } /* if */
+
+    else
+    {
+        PHYSFS_uint8 *newbuf = realloc(h->buffer, bufsize);
+        BAIL_IF_MACRO(newbuf == NULL, ERR_OUT_OF_MEMORY, 0);
+        h->buffer = newbuf;
+    } /* else */
+
+    h->bufsize = bufsize;
+    h->buffill = h->bufpos = 0;
+    return(1);
+} /* PHYSFS_setBuffer */
+
+
+int PHYSFS_flush(PHYSFS_file *handle)
+{
+    FileHandle *h = (FileHandle *) handle->opaque;
+    PHYSFS_sint64 rc;
+
+    if ((h->forReading) || (h->bufpos == h->buffill))
+        return(1);  /* open for read or buffer empty are successful no-ops. */
+
+    /* dump buffer to disk. */
+    rc = h->funcs->write(h, h->buffer + h->bufpos, h->buffill - h->bufpos, 1);
+    BAIL_IF_MACRO(rc <= 0, NULL, 0);
+
+    h->bufpos = h->buffill = 0;
+    return(1);
+} /* PHYSFS_flush */
+
+
 LinkedStringList *__PHYSFS_addToLinkedStringList(LinkedStringList *retval,
                                                  LinkedStringList **prev,
                                                  const char *str,
--- a/physfs.h	Sun Dec 01 11:17:56 2002 +0000
+++ b/physfs.h	Sun Dec 01 11:21:27 2002 +0000
@@ -258,6 +258,8 @@
  * \sa PHYSFS_seek
  * \sa PHYSFS_tell
  * \sa PHYSFS_eof
+ * \sa PHYSFS_setBuffer
+ * \sa PHYSFS_flush
  */
 typedef struct
 {
@@ -289,6 +291,7 @@
     const char *url;         /**< URL related to this archive */
 } PHYSFS_ArchiveInfo;
 
+
 /**
  * \struct PHYSFS_Version
  * \brief Information the version of PhysicsFS in use.
@@ -315,6 +318,9 @@
 #define PHYSFS_VER_PATCH 7
 #endif  /* DOXYGEN_SHOULD_IGNORE_THIS */
 
+
+/* PhysicsFS state stuff ... */
+
 /**
  * \def PHYSFS_VERSION(x)
  * \brief Macro to determine PhysicsFS version program was compiled against.
@@ -775,6 +781,8 @@
                                     int archivesFirst);
 
 
+/* Directory management stuff ... */
+
 /**
  * \fn int PHYSFS_mkdir(const char *dirName)
  * \brief Create a directory.
@@ -856,7 +864,6 @@
 __EXPORT__ const char *PHYSFS_getRealDir(const char *filename);
 
 
-
 /**
  * \fn char **PHYSFS_enumerateFiles(const char *dir)
  * \brief Get a file listing of a search path's directory.
@@ -957,6 +964,24 @@
 
 
 /**
+ * \fn PHYSFS_sint64 PHYSFS_getLastModTime(const char *filename)
+ * \brief Get the last modification time of a file.
+ *
+ * The modtime is returned as a number of seconds since the epoch
+ *  (Jan 1, 1970). The exact derivation and accuracy of this time depends on
+ *  the particular archiver. If there is no reasonable way to obtain this
+ *  information for a particular archiver, or there was some sort of error,
+ *  this function returns (-1).
+ *
+ *   \param filename filename to check, in platform-independent notation.
+ *  \return last modified time of the file. -1 if it can't be determined.
+ */
+__EXPORT__ PHYSFS_sint64 PHYSFS_getLastModTime(const char *filename);
+
+
+/* i/o stuff... */
+
+/**
  * \fn PHYSFS_file *PHYSFS_openWrite(const char *filename)
  * \brief Open a file for writing.
  *
@@ -1054,22 +1079,6 @@
 
 
 /**
- * \fn PHYSFS_sint64 PHYSFS_getLastModTime(const char *filename)
- * \brief Get the last modification time of a file.
- *
- * The modtime is returned as a number of seconds since the epoch
- *  (Jan 1, 1970). The exact derivation and accuracy of this time depends on
- *  the particular archiver. If there is no reasonable way to obtain this
- *  information for a particular archiver, or there was some sort of error,
- *  this function returns (-1).
- *
- *   \param filename filename to check, in platform-independent notation.
- *  \return last modified time of the file. -1 if it can't be determined.
- */
-__EXPORT__ PHYSFS_sint64 PHYSFS_getLastModTime(const char *filename);
-
-
-/**
  * \fn PHYSFS_sint64 PHYSFS_read(PHYSFS_file *handle, void *buffer, PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
  * \brief Read data from a PhysicsFS filehandle
  *
@@ -1108,6 +1117,9 @@
                                       PHYSFS_uint32 objSize,
                                       PHYSFS_uint32 objCount);
 
+
+/* File position stuff... */
+
 /**
  * \fn int PHYSFS_eof(PHYSFS_file *handle)
  * \brief Check for end-of-file state on a PhysicsFS filehandle.
@@ -1172,6 +1184,70 @@
 __EXPORT__ PHYSFS_sint64 PHYSFS_fileLength(PHYSFS_file *handle);
 
 
+/* Buffering stuff... */
+
+/**
+ * \fn int PHYSFS_setBuffer(PHYSFS_file *handle, PHYSFS_uint64 bufsize)
+ * \brief Set up buffering for a PhysicsFS file handle.
+ *
+ * Define an i/o buffer for a file handle. A memory block of (bufsize) bytes
+ *  will be allocated and associated with (handle).
+ *
+ * For files opened for reading, up to (bufsize) bytes are read from (handle)
+ *  and stored in the internal buffer. Calls to PHYSFS_read() will pull
+ *  from this buffer until it is empty, and then refill it for more reading.
+ *  Note that compressed files, like ZIP archives, will decompress while
+ *  buffering, so this can be handy for offsetting CPU-intensive operations.
+ *  The buffer isn't filled until you do your next read.
+ *
+ * For files opened for writing, data will be buffered to memory until the
+ *  buffer is full or the buffer is flushed. Closing a handle implicitly
+ *  causes a flush...check your return values!
+ *
+ * Seeking, etc transparently accounts for buffering.
+ *
+ * You can resize an existing buffer by calling this function more than once
+ *  on the same file. Setting the buffer size to zero will free an existing
+ *  buffer.
+ *
+ * PhysicsFS file handles are unbuffered by default.
+ *
+ * Please check the return value of this function! Failures can include
+ *  not being able to seek backwards in a read-only file when removing the
+ *  buffer, not being able to allocate the buffer, and not being able to
+ *  flush the buffer to disk, among other unexpected problems.
+ *
+ *   \param handle handle returned from PHYSFS_open*().
+ *   \param bufsize size, in bytes, of buffer to allocate.
+ *  \return nonzero if successful, zero on error.
+ *
+ * \sa PHYSFS_flush
+ * \sa PHYSFS_read
+ * \sa PHYSFS_write
+ * \sa PHYSFS_close
+ */
+__EXPORT__ int PHYSFS_setBuffer(PHYSFS_file *handle, PHYSFS_uint64 bufsize);
+
+
+/**
+ * \fn int PHYSFS_flush(PHYSFS_file *handle, PHYSFS_uint64 bufsize)
+ * \brief Flush a buffered PhysicsFS file handle.
+ *
+ * For buffered files opened for writing, this will put the current contents
+ *  of the buffer to disk and flag the buffer as empty if possible.
+ *
+ * For buffered files opened for reading or unbuffered files, this is a safe
+ *  no-op, and will report success.
+ *
+ *   \param handle handle returned from PHYSFS_open*().
+ *  \return nonzero if successful, zero on error.
+ *
+ * \sa PHYSFS_setBuffer
+ * \sa PHYSFS_close
+ */
+__EXPORT__ int PHYSFS_flush(PHYSFS_file *handle);
+
+
 /* Byteorder stuff... */
 
 /**
--- a/physfs_internal.h	Sun Dec 01 11:17:56 2002 +0000
+++ b/physfs_internal.h	Sun Dec 01 11:21:27 2002 +0000
@@ -725,6 +725,31 @@
     void *opaque;
 
         /*
+         * Non-zero if file opened for reading, zero if write/append.
+         */
+    PHYSFS_uint8 forReading;
+
+        /*
+         * This is the buffer, if one is set (NULL otherwise). Don't touch.
+         */
+    PHYSFS_uint8 *buffer;
+
+        /*
+         * This is the buffer size, if one is set (0 otherwise). Don't touch.
+         */
+    PHYSFS_uint64 bufsize;
+
+        /*
+         * This is the buffer fill size. Don't touch.
+         */
+    PHYSFS_uint64 buffill;
+
+        /*
+         * This is the buffer position. Don't touch.
+         */
+    PHYSFS_uint64 bufpos;
+
+        /*
          * This should be the DirHandle that created this FileHandle.
          */
     const struct __PHYSFS_DIRHANDLE__ *dirHandle;
--- a/test/test_physfs.c	Sun Dec 01 11:17:56 2002 +0000
+++ b/test/test_physfs.c	Sun Dec 01 11:21:27 2002 +0000
@@ -34,6 +34,7 @@
 #define TEST_VERSION_PATCH  7
 
 static FILE *history_file = NULL;
+static PHYSFS_uint32 do_buffer_size = 0;
 
 static void output_versions(void)
 {
@@ -288,6 +289,230 @@
 } /* cmd_permitsyms */
 
 
+static int cmd_setbuffer(char *args)
+{
+    if (*args == '\"')
+    {
+        args++;
+        args[strlen(args) - 1] = '\0';
+    } /* if */
+
+    do_buffer_size = atoi(args);
+    if (do_buffer_size)
+    {
+        printf("Further tests will set a (%lu) size buffer.\n",
+                (unsigned long) do_buffer_size);
+    } /* if */
+
+    else
+    {
+        printf("Further tests will NOT use a buffer.\n");
+    } /* else */
+
+    return(1);
+} /* cmd_setbuffer */
+
+
+static int cmd_stressbuffer(char *args)
+{
+    int num;
+
+    if (*args == '\"')
+    {
+        args++;
+        args[strlen(args) - 1] = '\0';
+    } /* if */
+
+    num = atoi(args);
+    if (num < 0)
+        printf("buffer must be greater than or equal to zero.\n");
+    else
+    {
+        PHYSFS_file *f;
+        int rndnum;
+
+        printf("Stress testing with (%d) byte buffer...\n", num);
+        f = PHYSFS_openWrite("test.txt");
+        if (f == NULL)
+            printf("Couldn't open test.txt for writing: %s.\n", PHYSFS_getLastError());
+        else
+        {
+            int i, j;
+            char buf[37];
+            char buf2[37];
+
+            if (!PHYSFS_setBuffer(f, num))
+            {
+                printf("PHYSFS_setBuffer() failed: %s.\n", PHYSFS_getLastError());
+                PHYSFS_close(f);
+                PHYSFS_delete("test.txt");
+                return(1);
+            } /* if */
+
+            strcpy(buf, "abcdefghijklmnopqrstuvwxyz0123456789");
+            srand(time(NULL));
+
+            for (i = 0; i < 10; i++)
+            {
+                for (j = 0; j < 10000; j++)
+                {
+                    int right = 1 + (int) (35.0 * rand() / (RAND_MAX + 1.0));
+                    int left = 36 - right;
+                    if (PHYSFS_write(f, buf, left, 1) != 1)
+                    {
+                        printf("PHYSFS_write() failed: %s.\n", PHYSFS_getLastError());
+                        PHYSFS_close(f);
+                        return(1);
+                    } /* if */
+
+                    rndnum = 1 + (int) (1000.0 * rand() / (RAND_MAX + 1.0));
+                    if (rndnum == 42)
+                    {
+                        if (!PHYSFS_flush(f))
+                        {
+                            printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+                            PHYSFS_close(f);
+                            return(1);
+                        } /* if */
+                    } /* if */
+
+                    if (PHYSFS_write(f, buf + left, 1, right) != right)
+                    {
+                        printf("PHYSFS_write() failed: %s.\n", PHYSFS_getLastError());
+                        PHYSFS_close(f);
+                        return(1);
+                    } /* if */
+
+                    rndnum = 1 + (int) (1000.0 * rand() / (RAND_MAX + 1.0));
+                    if (rndnum == 42)
+                    {
+                        if (!PHYSFS_flush(f))
+                        {
+                            printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+                            PHYSFS_close(f);
+                            return(1);
+                        } /* if */
+                    } /* if */
+                } /* for */
+
+                if (!PHYSFS_flush(f))
+                {
+                    printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+                    PHYSFS_close(f);
+                    return(1);
+                } /* if */
+
+            } /* for */
+
+            if (!PHYSFS_close(f))
+            {
+                printf("PHYSFS_close() failed: %s.\n", PHYSFS_getLastError());
+                return(1);  /* oh well. */
+            } /* if */
+
+            printf(" ... test file written ...\n");
+            f = PHYSFS_openRead("test.txt");
+            if (f == NULL)
+            {
+                printf("Failed to reopen stress file for reading: %s.\n", PHYSFS_getLastError());
+                return(1);
+            } /* if */
+
+            if (!PHYSFS_setBuffer(f, num))
+            {
+                printf("PHYSFS_setBuffer() failed: %s.\n", PHYSFS_getLastError());
+                PHYSFS_close(f);
+                return(1);
+            } /* if */
+
+            for (i = 0; i < 10; i++)
+            {
+                for (j = 0; j < 10000; j++)
+                {
+                    int right = 1 + (int) (35.0 * rand() / (RAND_MAX + 1.0));
+                    int left = 36 - right;
+                    if (PHYSFS_read(f, buf2, left, 1) != 1)
+                    {
+                        printf("PHYSFS_read() failed: %s.\n", PHYSFS_getLastError());
+                        PHYSFS_close(f);
+                        return(1);
+                    } /* if */
+
+                    rndnum = 1 + (int) (1000.0 * rand() / (RAND_MAX + 1.0));
+                    if (rndnum == 42)
+                    {
+                        if (!PHYSFS_flush(f))
+                        {
+                            printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+                            PHYSFS_close(f);
+                            return(1);
+                        } /* if */
+                    } /* if */
+
+                    if (PHYSFS_read(f, buf2 + left, 1, right) != right)
+                    {
+                        printf("PHYSFS_read() failed: %s.\n", PHYSFS_getLastError());
+                        PHYSFS_close(f);
+                        return(1);
+                    } /* if */
+
+                    rndnum = 1 + (int) (1000.0 * rand() / (RAND_MAX + 1.0));
+                    if (rndnum == 42)
+                    {
+                        if (!PHYSFS_flush(f))
+                        {
+                            printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+                            PHYSFS_close(f);
+                            return(1);
+                        } /* if */
+                    } /* if */
+
+                    if (memcmp(buf, buf2, 36) != 0)
+                    {
+                        printf("readback is mismatched on iterations (%d, %d).\n", i, j);
+                        printf("wanted: [");
+                        for (i = 0; i < 36; i++)
+                            printf("%c", buf[i]);
+                        printf("]\n");
+
+                        printf("   got: [");
+                        for (i = 0; i < 36; i++)
+                            printf("%c", buf2[i]);
+                        printf("]\n");
+                        PHYSFS_close(f);
+                        return(1);
+                    } /* if */
+                } /* for */
+
+                if (!PHYSFS_flush(f))
+                {
+                    printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+                    PHYSFS_close(f);
+                    return(1);
+                } /* if */
+
+            } /* for */
+
+            printf(" ... test file read ...\n");
+
+            if (!PHYSFS_eof(f))
+                printf("PHYSFS_eof() returned true! That's wrong.\n");
+
+            if (!PHYSFS_close(f))
+            {
+                printf("PHYSFS_close() failed: %s.\n", PHYSFS_getLastError());
+                return(1);  /* oh well. */
+            } /* if */
+
+            PHYSFS_delete("test.txt");
+            printf("stress test completed successfully.\n");
+        } /* else */
+    } /* else */
+
+    return(1);
+} /* cmd_stressbuffer */
+
+
 static int cmd_setsaneconfig(char *args)
 {
     char *org;
@@ -433,6 +658,17 @@
         printf("failed to open. Reason: [%s].\n", PHYSFS_getLastError());
     else
     {
+        if (do_buffer_size)
+        {
+            if (!PHYSFS_setBuffer(f, do_buffer_size))
+            {
+                printf("failed to set file buffer. Reason: [%s].\n",
+                        PHYSFS_getLastError());
+                PHYSFS_close(f);
+                return(1);
+            } /* if */
+        } /* if */
+
         while (1)
         {
             char buffer[128];
@@ -506,8 +742,22 @@
         printf("failed to open. Reason: [%s].\n", PHYSFS_getLastError());
     else
     {
-        size_t bw = strlen(WRITESTR);
-        PHYSFS_sint64 rc = PHYSFS_write(f, WRITESTR, 1, bw);
+        size_t bw;
+        PHYSFS_sint64 rc;
+
+        if (do_buffer_size)
+        {
+            if (!PHYSFS_setBuffer(f, do_buffer_size))
+            {
+                printf("failed to set file buffer. Reason: [%s].\n",
+                        PHYSFS_getLastError());
+                PHYSFS_close(f);
+                return(1);
+            } /* if */
+        } /* if */
+
+        bw = strlen(WRITESTR);
+        rc = PHYSFS_write(f, WRITESTR, 1, bw);
         if (rc != bw)
         {
             printf("Wrote (%d) of (%d) bytes. Reason: [%s].\n",
@@ -540,8 +790,22 @@
         printf("failed to open. Reason: [%s].\n", PHYSFS_getLastError());
     else
     {
-        size_t bw = strlen(WRITESTR);
-        PHYSFS_sint64 rc = PHYSFS_write(f, WRITESTR, 1, bw);
+        size_t bw;
+        PHYSFS_sint64 rc;
+
+        if (do_buffer_size)
+        {
+            if (!PHYSFS_setBuffer(f, do_buffer_size))
+            {
+                printf("failed to set file buffer. Reason: [%s].\n",
+                        PHYSFS_getLastError());
+                PHYSFS_close(f);
+                return(1);
+            } /* if */
+        } /* if */
+
+        bw = strlen(WRITESTR);
+        rc = PHYSFS_write(f, WRITESTR, 1, bw);
         if (rc != bw)
         {
             printf("Wrote (%d) of (%d) bytes. Reason: [%s].\n",
@@ -648,6 +912,8 @@
     { "append",         cmd_append,         1, "<fileToAppend>"             },
     { "write",          cmd_write,          1, "<fileToCreateOrTrash>"      },
     { "getlastmodtime", cmd_getlastmodtime, 1, "<fileToExamine>"            },
+    { "setbuffer",      cmd_setbuffer,      1, "<bufferSize>"               },
+    { "stressbuffer",   cmd_stressbuffer,   1, "<bufferSize>"               },
     { NULL,             NULL,              -1, NULL                         }
 };