Skip to content

Commit

Permalink
Added PHYSFS_mountMemory().
Browse files Browse the repository at this point in the history
  • Loading branch information
icculus committed Aug 30, 2010
1 parent 8cd320b commit bb9f5e5
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 2 deletions.
209 changes: 209 additions & 0 deletions src/physfs.c
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
47 changes: 47 additions & 0 deletions src/physfs.h
Expand Up @@ -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. */


Expand Down
8 changes: 8 additions & 0 deletions src/physfs_internal.h
Expand Up @@ -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 *));


/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
Expand Down
67 changes: 65 additions & 2 deletions test/test_physfs.c
Expand Up @@ -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 == '\"')
{
Expand Down Expand Up @@ -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 == '\"')
Expand Down Expand Up @@ -1023,6 +1085,7 @@ static const command_info commands[] =
{ "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>" },
Expand Down

0 comments on commit bb9f5e5

Please sign in to comment.