blob: 2f399319a899f6bdb471995ef5b8fbf8be7ade5e [file] [log] [blame]
/*
This file is part of libmicrohttpd
Copyright (C) 2007, 2009, 2010 Daniel Pittman and Christian Grothoff
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file memorypool.c
* @brief memory pool
* @author Christian Grothoff
*/
#include "memorypool.h"
/* define MAP_ANONYMOUS for Mac OS X */
#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
#define MAP_ANONYMOUS MAP_ANON
#endif
#ifndef MAP_FAILED
#define MAP_FAILED ((void*)-1)
#endif
/**
* Align to 2x word size (as GNU libc does).
*/
#define ALIGN_SIZE (2 * sizeof(void*))
/**
* Round up 'n' to a multiple of ALIGN_SIZE.
*/
#define ROUND_TO_ALIGN(n) ((n+(ALIGN_SIZE-1)) & (~(ALIGN_SIZE-1)))
/**
* Handle for a memory pool. Pools are not reentrant and must not be
* used by multiple threads.
*/
struct MemoryPool
{
/**
* Pointer to the pool's memory
*/
char *memory;
/**
* Size of the pool.
*/
size_t size;
/**
* Offset of the first unallocated byte.
*/
size_t pos;
/**
* Offset of the last unallocated byte.
*/
size_t end;
/**
* #MHD_NO if pool was malloc'ed, #MHD_YES if mmapped (VirtualAlloc'ed for W32).
*/
int is_mmap;
};
/**
* Create a memory pool.
*
* @param max maximum size of the pool
* @return NULL on error
*/
struct MemoryPool *
MHD_pool_create (size_t max)
{
struct MemoryPool *pool;
pool = malloc (sizeof (struct MemoryPool));
if (NULL == pool)
return NULL;
#if defined(MAP_ANONYMOUS) || defined(_WIN32)
if (max <= 32 * 1024)
pool->memory = MAP_FAILED;
else
#if defined(MAP_ANONYMOUS) && !defined(_WIN32)
pool->memory = mmap (NULL, max, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
#elif defined(_WIN32)
pool->memory = VirtualAlloc(NULL, max, MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
#endif
#else
pool->memory = MAP_FAILED;
#endif
if ((pool->memory == MAP_FAILED) || (pool->memory == NULL))
{
pool->memory = malloc (max);
if (pool->memory == NULL)
{
free (pool);
return NULL;
}
pool->is_mmap = MHD_NO;
}
else
{
pool->is_mmap = MHD_YES;
}
pool->pos = 0;
pool->end = max;
pool->size = max;
return pool;
}
/**
* Destroy a memory pool.
*
* @param pool memory pool to destroy
*/
void
MHD_pool_destroy (struct MemoryPool *pool)
{
if (pool == NULL)
return;
if (pool->is_mmap == MHD_NO)
free (pool->memory);
else
#if defined(MAP_ANONYMOUS) && !defined(_WIN32)
munmap (pool->memory, pool->size);
#elif defined(_WIN32)
VirtualFree(pool->memory, 0, MEM_RELEASE);
#else
abort();
#endif
free (pool);
}
/**
* Allocate size bytes from the pool.
*
* @param pool memory pool to use for the operation
* @param size number of bytes to allocate
* @param from_end allocate from end of pool (set to #MHD_YES);
* use this for small, persistent allocations that
* will never be reallocated
* @return NULL if the pool cannot support size more
* bytes
*/
void *
MHD_pool_allocate (struct MemoryPool *pool,
size_t size, int from_end)
{
void *ret;
size_t asize;
asize = ROUND_TO_ALIGN (size);
if ( (0 == asize) && (0 != size) )
return NULL; /* size too close to SIZE_MAX */
if ((pool->pos + asize > pool->end) || (pool->pos + asize < pool->pos))
return NULL;
if (from_end == MHD_YES)
{
ret = &pool->memory[pool->end - asize];
pool->end -= asize;
}
else
{
ret = &pool->memory[pool->pos];
pool->pos += asize;
}
return ret;
}
/**
* Reallocate a block of memory obtained from the pool.
* This is particularly efficient when growing or
* shrinking the block that was last (re)allocated.
* If the given block is not the most recenlty
* (re)allocated block, the memory of the previous
* allocation may be leaked until the pool is
* destroyed (and copying the data maybe required).
*
* @param pool memory pool to use for the operation
* @param old the existing block
* @param old_size the size of the existing block
* @param new_size the new size of the block
* @return new address of the block, or
* NULL if the pool cannot support @a new_size
* bytes (old continues to be valid for @a old_size)
*/
void *
MHD_pool_reallocate (struct MemoryPool *pool,
void *old,
size_t old_size,
size_t new_size)
{
void *ret;
size_t asize;
asize = ROUND_TO_ALIGN (new_size);
if ( (0 == asize) && (0 != new_size) )
return NULL; /* new_size too close to SIZE_MAX */
if ((pool->end < old_size) || (pool->end < asize))
return NULL; /* unsatisfiable or bogus request */
if ((pool->pos >= old_size) && (&pool->memory[pool->pos - old_size] == old))
{
/* was the previous allocation - optimize! */
if (pool->pos + asize - old_size <= pool->end)
{
/* fits */
pool->pos += asize - old_size;
if (asize < old_size) /* shrinking - zero again! */
memset (&pool->memory[pool->pos], 0, old_size - asize);
return old;
}
/* does not fit */
return NULL;
}
if (asize <= old_size)
return old; /* cannot shrink, no need to move */
if ((pool->pos + asize >= pool->pos) &&
(pool->pos + asize <= pool->end))
{
/* fits */
ret = &pool->memory[pool->pos];
memcpy (ret, old, old_size);
pool->pos += asize;
return ret;
}
/* does not fit */
return NULL;
}
/**
* Clear all entries from the memory pool except
* for @a keep of the given @a size.
*
* @param pool memory pool to use for the operation
* @param keep pointer to the entry to keep (maybe NULL)
* @param size how many bytes need to be kept at this address
* @return addr new address of @a keep (if it had to change)
*/
void *
MHD_pool_reset (struct MemoryPool *pool,
void *keep,
size_t size)
{
if (NULL != keep)
{
if (keep != pool->memory)
{
memmove (pool->memory, keep, size);
keep = pool->memory;
}
}
pool->end = pool->size;
memset (&pool->memory[size],
0,
pool->size - size);
if (NULL != keep)
pool->pos = ROUND_TO_ALIGN(size);
return keep;
}
/* end of memorypool.c */