From bb9f5e504928d5b9f31850dfeb870824497f709f Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 30 Aug 2010 02:39:11 -0400 Subject: [PATCH] Added PHYSFS_mountMemory(). --- src/physfs.c | 209 ++++++++++++++++++++++++++++++++++++++++++ src/physfs.h | 47 ++++++++++ src/physfs_internal.h | 8 ++ test/test_physfs.c | 67 +++++++++++++- 4 files changed, 329 insertions(+), 2 deletions(-) diff --git a/src/physfs.c b/src/physfs.c index e0508c6c..e05447e0 100644 --- a/src/physfs.c +++ b/src/physfs.c @@ -267,6 +267,191 @@ PHYSFS_Io *__PHYSFS_createNativeIo(const char *path, const int mode) } /* __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 @@ int PHYSFS_mountIo(PHYSFS_Io *io, const char *fname, } /* 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); diff --git a/src/physfs.h b/src/physfs.h index f2e1944f..ace2b636 100644 --- a/src/physfs.h +++ b/src/physfs.h @@ -2937,6 +2937,53 @@ typedef struct PHYSFS_Io 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. */ diff --git a/src/physfs_internal.h b/src/physfs_internal.h index ec5107df..461509df 100644 --- a/src/physfs_internal.h +++ b/src/physfs_internal.h @@ -1033,6 +1033,14 @@ extern PHYSFS_Allocator __PHYSFS_AllocatorHooks; */ 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 *)); + /*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/ diff --git a/test/test_physfs.c b/test/test_physfs.c index e744d40b..b0902a12 100644 --- a/test/test_physfs.c +++ b/test/test_physfs.c @@ -128,11 +128,19 @@ static int cmd_addarchive(char *args) } /* 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 @@ static int cmd_mount(char *args) /*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 @@ static const command_info commands[] = { "deinit", cmd_deinit, 0, NULL }, { "addarchive", cmd_addarchive, 2, " " }, { "mount", cmd_mount, 3, " " }, + { "mountmem", cmd_mount_mem, 3, " " }, { "removearchive", cmd_removearchive, 1, "" }, { "unmount", cmd_removearchive, 1, "" }, { "enumerate", cmd_enumerate, 1, "" },