Added PHYSFS_mountMemory().
authorRyan C. Gordon <icculus@icculus.org>
Mon, 30 Aug 2010 02:39:11 -0400
changeset 1120 67e5e23425a0
parent 1119 028559924db4
child 1121 63800b6cf054
Added PHYSFS_mountMemory().
src/physfs.c
src/physfs.h
src/physfs_internal.h
test/test_physfs.c
--- a/src/physfs.c	Sun Aug 29 20:56:35 2010 -0400
+++ b/src/physfs.c	Mon Aug 30 02:39:11 2010 -0400
@@ -267,6 +267,191 @@
 } /* __PHYSFS_createNativeIo */
 
 
+/* PHYSFS_Io implementation for i/o to a memory buffer... */
+
+typedef struct __PHYSFS_MemoryIoInfo
+{
+    const PHYSFS_uint8 *buf;
+    PHYSFS_uint64 len;
+    PHYSFS_uint64 pos;
+    PHYSFS_Io *parent;
+    volatile PHYSFS_uint32 refcount;
+    void (*destruct)(void *);
+} MemoryIoInfo;
+
+static PHYSFS_sint64 memoryIo_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len)
+{
+    MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+    const PHYSFS_uint64 avail = info->len - info->pos;
+    assert(avail <= info->len);
+
+    if (avail == 0)
+        return 0;  /* we're at EOF; nothing to do. */
+
+    if (len > avail)
+        len = avail;
+
+    memcpy(buf, info->buf + info->pos, len);
+    info->pos += len;
+    return len;
+} /* memoryIo_read */
+
+static PHYSFS_sint64 memoryIo_write(PHYSFS_Io *io, const void *buffer,
+                                    PHYSFS_uint64 len)
+{
+    BAIL_MACRO(ERR_NOT_SUPPORTED, -1);
+} /* memoryIo_write */
+
+static int memoryIo_seek(PHYSFS_Io *io, PHYSFS_uint64 offset)
+{
+    MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+    BAIL_IF_MACRO(offset > info->len, ERR_PAST_EOF, 0);
+    info->pos = offset;
+    return 1;
+} /* memoryIo_seek */
+
+static PHYSFS_sint64 memoryIo_tell(PHYSFS_Io *io)
+{
+    const MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+    return (PHYSFS_sint64) info->pos;
+} /* memoryIo_tell */
+
+static PHYSFS_sint64 memoryIo_length(PHYSFS_Io *io)
+{
+    const MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+    return (PHYSFS_sint64) info->len;
+} /* memoryIo_length */
+
+static PHYSFS_Io *memoryIo_duplicate(PHYSFS_Io *io)
+{
+    MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+    MemoryIoInfo *newinfo = NULL;
+    PHYSFS_Io *parent = info->parent;
+    PHYSFS_Io *retval = NULL;
+
+    /* avoid deep copies. */
+    assert((!parent) || (!((MemoryIoInfo *) parent->opaque)->parent) );
+
+    /* share the buffer between duplicates. */
+    if (parent != NULL)  /* dup the parent, increment its refcount. */
+        return parent->duplicate(parent);
+
+    /* we're the parent. */
+
+    retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+    BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
+    newinfo = (MemoryIoInfo *) allocator.Malloc(sizeof (MemoryIoInfo));
+    if (!newinfo)
+    {
+        allocator.Free(retval);
+        BAIL_MACRO(ERR_OUT_OF_MEMORY, NULL);
+    } /* if */
+
+    /* !!! FIXME: want lockless atomic increment. */
+    __PHYSFS_platformGrabMutex(stateLock);
+    info->refcount++;
+    __PHYSFS_platformReleaseMutex(stateLock);
+
+    memset(newinfo, '\0', sizeof (*info));
+    newinfo->buf = info->buf;
+    newinfo->len = info->len;
+    newinfo->pos = 0;
+    newinfo->parent = io;
+    newinfo->refcount = 0;
+    newinfo->destruct = NULL;
+
+    memcpy(retval, io, sizeof (*retval));
+    retval->opaque = newinfo;
+    return retval;
+} /* memoryIo_duplicate */
+
+static int memoryIo_flush(PHYSFS_Io *io) { return 1;  /* it's read-only. */ }
+
+static void memoryIo_destroy(PHYSFS_Io *io)
+{
+    MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+    PHYSFS_Io *parent = info->parent;
+    int should_die = 0;
+
+    if (parent != NULL)
+    {
+        assert(info->buf == ((MemoryIoInfo *) info->parent->opaque)->buf);
+        assert(info->len == ((MemoryIoInfo *) info->parent->opaque)->len);
+        assert(info->refcount == 0);
+        assert(info->destruct == NULL);
+        allocator.Free(info);
+        allocator.Free(io);
+        parent->destroy(parent);  /* decrements refcount. */
+        return;
+    } /* if */
+
+    /* we _are_ the parent. */
+    assert(info->refcount > 0);  /* even in a race, we hold a reference. */
+
+    /* !!! FIXME: want lockless atomic decrement. */
+    __PHYSFS_platformGrabMutex(stateLock);
+    info->refcount--;
+    should_die = (info->refcount == 0);
+    __PHYSFS_platformReleaseMutex(stateLock);
+
+    if (should_die)
+    {
+        void (*destruct)(void *) = info->destruct;
+        void *buf = (void *) info->buf;
+        io->opaque = NULL;  /* kill this here in case of race. */
+        destruct = info->destruct;
+        allocator.Free(info);
+        allocator.Free(io);
+        if (destruct != NULL)
+        destruct(buf);
+    } /* if */
+} /* memoryIo_destroy */
+
+
+static const PHYSFS_Io __PHYSFS_memoryIoInterface =
+{
+    memoryIo_read,
+    memoryIo_write,
+    memoryIo_seek,
+    memoryIo_tell,
+    memoryIo_length,
+    memoryIo_duplicate,
+    memoryIo_flush,
+    memoryIo_destroy,
+    NULL
+};
+
+PHYSFS_Io *__PHYSFS_createMemoryIo(const void *buf, PHYSFS_uint64 len,
+                                   void (*destruct)(void *))
+{
+    PHYSFS_Io *io = NULL;
+    MemoryIoInfo *info = NULL;
+
+    io = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+    GOTO_IF_MACRO(io == NULL, ERR_OUT_OF_MEMORY, createMemoryIo_failed);
+    info = (MemoryIoInfo *) allocator.Malloc(sizeof (MemoryIoInfo));
+    GOTO_IF_MACRO(info == NULL, ERR_OUT_OF_MEMORY, createMemoryIo_failed);
+
+    memset(info, '\0', sizeof (*info));
+    info->buf = (const PHYSFS_uint8 *) buf;
+    info->len = len;
+    info->pos = 0;
+    info->parent = NULL;
+    info->refcount = 1;
+    info->destruct = destruct;
+
+    memcpy(io, &__PHYSFS_memoryIoInterface, sizeof (*io));
+    io->opaque = info;
+    return io;
+
+createMemoryIo_failed:
+    if (info != NULL) allocator.Free(info);
+    if (io != NULL) allocator.Free(io);
+    return NULL;
+} /* __PHYSFS_createMemoryIo */
+
+
+
 /* functions ... */
 
 typedef struct
@@ -1147,6 +1332,30 @@
 } /* PHYSFS_mountIo */
 
 
+int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len, void (*del)(void *),
+                       const char *fname, const char *mountPoint,
+                       int appendToPath)
+{
+    int retval = 0;
+    PHYSFS_Io *io = NULL;
+
+    BAIL_IF_MACRO(buf == NULL, ERR_INVALID_ARGUMENT, 0);
+
+    io = __PHYSFS_createMemoryIo(buf, len, del);
+    BAIL_IF_MACRO(io == NULL, NULL, 0);
+    retval = doMount(io, fname, mountPoint, appendToPath);
+    if (!retval)
+    {
+        /* docs say not to call (del) in case of failure, so cheat. */
+        MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+        info->destruct = NULL;
+        io->destroy(io);
+    } /* if */
+
+    return retval;
+} /* PHYSFS_mountMemory */
+
+
 int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath)
 {
     BAIL_IF_MACRO(newDir == NULL, ERR_INVALID_ARGUMENT, 0);
--- a/src/physfs.h	Sun Aug 29 20:56:35 2010 -0400
+++ b/src/physfs.h	Mon Aug 30 02:39:11 2010 -0400
@@ -2937,6 +2937,53 @@
 PHYSFS_DECL int PHYSFS_mountIo(PHYSFS_Io *io, const char *fname,
                                const char *mountPoint, int appendToPath);
 
+
+/**
+ * \fn int PHYSFS_mountMemory(const void *ptr, PHYSFS_uint64 len, void (*del)(void *), const char *fname, const char *mountPoint, int appendToPath)
+ * \brief Add an archive, contained in a memory buffer, to the search path.
+ *
+ * \warning Unless you have some special, low-level need, you should be using
+ *          PHYSFS_mount() instead of this.
+ *
+ * This function operates just like PHYSFS_mount(), but takes a memory buffer
+ *  instead of a pathname. This buffer contains all the data of the archive,
+ *  and is used instead of a real file in the physical filesystem.
+ *
+ * (filename) is only used here to optimize archiver selection (if you name it
+ *  XXXXX.zip, we might try the ZIP archiver first, for example). It doesn't
+ *  need to refer to a real file at all, and can even be NULL. If the filename
+ *  isn't helpful, the system will try every archiver until one works or none
+ *  of them do.
+ *
+ * (ptr) must remain until the archive is unmounted. When the archive is
+ *  unmounted, the system will call (del)(ptr), which will notify you that
+ *  the system is done with the buffer, and give you a chance to free your
+ *  resources. (del) can be NULL, in which case the system will make no
+ *  attempt to free the buffer.
+ *
+ * If this function fails, (del) is not called.
+ *
+ *   \param ptr Address of the memory buffer containing the archive data.
+ *   \param len Size of memory buffer, in bytes.
+ *   \param del A callback that triggers upon unmount. Can be NULL.
+ *   \param fname Filename that can represent this stream. Can be NULL.
+ *   \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, etc).
+ *                  Specifics of the error can be gleaned from
+ *                  PHYSFS_getLastError().
+ *
+ * \sa PHYSFS_unmount
+ * \sa PHYSFS_getSearchPath
+ * \sa PHYSFS_getMountPoint
+ */
+PHYSFS_DECL int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len,
+                                   void (*del)(void *), const char *fname,
+                                   const char *mountPoint, int appendToPath);
+
+
 /* Everything above this line is part of the PhysicsFS 2.1 API. */
 
 
--- a/src/physfs_internal.h	Sun Aug 29 20:56:35 2010 -0400
+++ b/src/physfs_internal.h	Mon Aug 30 02:39:11 2010 -0400
@@ -1033,6 +1033,14 @@
  */
 PHYSFS_Io *__PHYSFS_createNativeIo(const char *path, const int mode);
 
+/*
+ * Create a PHYSFS_Io for a buffer of memory (READ-ONLY). If you already
+ *  have one of these, just use its duplicate() method, and it'll increment
+ *  its refcount without allocating a copy of the buffer.
+ */
+PHYSFS_Io *__PHYSFS_createMemoryIo(const void *buf, PHYSFS_uint64 len,
+                                   void (*destruct)(void *));
+
 
 /*--------------------------------------------------------------------------*/
 /*--------------------------------------------------------------------------*/
--- a/test/test_physfs.c	Sun Aug 29 20:56:35 2010 -0400
+++ b/test/test_physfs.c	Mon Aug 30 02:39:11 2010 -0400
@@ -128,11 +128,19 @@
 } /* cmd_addarchive */
 
 
-static int cmd_mount(char *args)
+/* wrap free() to avoid calling convention wankery. */
+static void freeBuf(void *buf)
+{
+    free(buf);
+} /* freeBuf */
+
+
+static int cmd_mount_internal(char *args, const int fromMem)
 {
     char *ptr;
     char *mntpoint = NULL;
     int appending = 0;
+    int rc = 0;
 
     if (*args == '\"')
     {
@@ -172,15 +180,69 @@
 
     /*printf("[%s], [%s], [%d]\n", args, mntpoint, appending);*/
 
-    if (PHYSFS_mount(args, mntpoint, appending))
+    if (!fromMem)
+        rc = PHYSFS_mount(args, mntpoint, appending);
+    else
+    {
+        FILE *in = fopen(args, "rb");
+        void *buf = NULL;
+        long len = 0;
+
+        if (in == NULL)
+        {
+            printf("Failed to open %s to read into memory: %s.\n", args, strerror(errno));
+            return 1;
+        } /* if */
+
+        if ( (fseek(in, 0, SEEK_END) != 0) || ((len = ftell(in)) < 0) )
+        {
+            printf("Failed to find size of %s to read into memory: %s.\n", args, strerror(errno));
+            fclose(in);
+            return 1;
+        } /* if */
+
+        buf = malloc(len);
+        if (buf == NULL)
+        {
+            printf("Failed to allocate space to read %s into memory: %s.\n", args, strerror(errno));
+            fclose(in);
+            return 1;
+        } /* if */
+
+        if ((fseek(in, 0, SEEK_SET) != 0) || (fread(buf, len, 1, in) != 1))
+        {
+            printf("Failed to read %s into memory: %s.\n", args, strerror(errno));
+            fclose(in);
+            free(buf);
+            return 1;
+        } /* if */
+
+        fclose(in);
+
+        rc = PHYSFS_mountMemory(buf, len, freeBuf, args, mntpoint, appending);
+    } /* else */
+
+    if (rc)
         printf("Successful.\n");
     else
         printf("Failure. reason: %s.\n", PHYSFS_getLastError());
 
     return 1;
+} /* cmd_mount_internal */
+
+
+static int cmd_mount(char *args)
+{
+    return cmd_mount_internal(args, 0);
 } /* cmd_mount */
 
 
+static int cmd_mount_mem(char *args)
+{
+    return cmd_mount_internal(args, 1);
+} /* cmd_mount_mem */
+
+
 static int cmd_removearchive(char *args)
 {
     if (*args == '\"')
@@ -1023,6 +1085,7 @@
     { "deinit",         cmd_deinit,         0, NULL                         },
     { "addarchive",     cmd_addarchive,     2, "<archiveLocation> <append>" },
     { "mount",          cmd_mount,          3, "<archiveLocation> <mntpoint> <append>" },
+    { "mountmem",       cmd_mount_mem,      3, "<archiveLocation> <mntpoint> <append>" },
     { "removearchive",  cmd_removearchive,  1, "<archiveLocation>"          },
     { "unmount",        cmd_removearchive,  1, "<archiveLocation>"          },
     { "enumerate",      cmd_enumerate,      1, "<dirToEnumerate>"           },