/*
     This file is part of libmicrohttpd
     Copyright (C) 2007, 2009, 2010, 2018 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;

  /**
   * false if pool was malloc'ed, true if mmapped (VirtualAlloc'ed for W32).
   */
  bool is_mmap;
};


/**
 * Free the memory given by @a ptr. Calls "free(ptr)".  This function
 * should be used to free the username returned by
 * #MHD_digest_auth_get_username().
 * @note Since v0.9.56
 *
 * @param ptr pointer to free.
 */
_MHD_EXTERN void
MHD_free (void *ptr)
{
  free (ptr);
}


/**
 * 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 ( (MAP_FAILED == pool->memory) ||
       (NULL == pool->memory))
  {
    pool->memory = malloc (max);
    if (NULL == pool->memory)
    {
      free (pool);
      return NULL;
    }
    pool->is_mmap = false;
  }
  else
  {
    pool->is_mmap = true;
  }
  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 (NULL == pool)
    return;
  if (! pool->is_mmap)
    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);
}


/**
 * Check how much memory is left in the @a pool
 *
 * @param pool pool to check
 * @return number of bytes still available in @a pool
 */
size_t
MHD_pool_get_free (struct MemoryPool *pool)
{
  return (pool->end - pool->pos);
}


/**
 * 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 recently
 * (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];
    if (0 != old_size)
      memmove (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. The pointer
 * returned should be a buffer of @a new_size where
 * the first @a copy_bytes are from @a keep.
 *
 * @param pool memory pool to use for the operation
 * @param keep pointer to the entry to keep (maybe NULL)
 * @param copy_bytes how many bytes need to be kept at this address
 * @param new_size how many bytes should the allocation we return have?
 *                 (should be larger or equal to @a copy_bytes)
 * @return addr new address of @a keep (if it had to change)
 */
void *
MHD_pool_reset (struct MemoryPool *pool,
                void *keep,
                size_t copy_bytes,
                size_t new_size)
{
  if ( (NULL != keep) &&
       (keep != pool->memory) )
  {
    if (0 != copy_bytes)
      memmove (pool->memory,
               keep,
               copy_bytes);
    keep = pool->memory;
  }
  pool->end = pool->size;
  /* technically not needed, but safer to zero out */
  if (pool->size > copy_bytes)
    memset (&pool->memory[copy_bytes],
            0,
            pool->size - copy_bytes);
  if (NULL != keep)
    pool->pos = ROUND_TO_ALIGN (new_size);
  return keep;
}


/* end of memorypool.c */
