/*
 * Memory allocation routines using mmap().
 *
 * Derived from mm-1.1.3 by Gunnar Ritter, Freiburg i. Br., Germany,
 * September 2001.
 */

/*	Sccsid @(#)mapmalloc.c	1.12 (gritter) 1/28/03	*/

/* ====================================================================
 * Copyright (c) 1999-2000 Ralf S. Engelschall. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by
 *     Ralf S. Engelschall <rse@engelschall.com>."
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by
 *     Ralf S. Engelschall <rse@engelschall.com>."
 *
 * THIS SOFTWARE IS PROVIDED BY RALF S. ENGELSCHALL ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL RALF S. ENGELSCHALL OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"
#include "ex.h"
#include "ex_proto.h"

#ifdef	LANGMSG
#include <nl_types.h>
extern	nl_catd	catd;
#else
#define	catgets(a, b, c, d)	(d)
#endif

#ifndef	min_of
#define	min_of(a, b)	((a) < (b) ? (a) : (b))
#endif
#ifndef	max_of
#define	max_of(a, b)	((a) > (b) ? (a) : (b))
#endif

#ifndef	MAP_ANON
#ifdef	MAP_ANONYMOUS
#define MAP_ANON	MAP_ANONYMOUS
#endif
#endif

#ifndef	MAP_FAILED
#define MAP_FAILED	((char *)-1)
#endif

#define MM_ALLOC_MINSIZE         8192

#ifndef	__P
#ifdef	__STDC__
#define	__P(a)	a
#else
#define	__P(a)	()
#endif
#endif

/*
 * Define a union with types which are likely to have the longest
 * *relevant* CPU-specific memory word alignment restrictions...
 */
union mem_word {
	char		(*mw_fp) __P((int));
	char		*mw_cp;
	long		mw_l;
	double		mw_d;
	intptr_t	mw_it;
};

typedef	union mem_word	mem_word;

#define	SIZEOF_mem_word	(sizeof(mem_word))

/*
 * Define the structure used for memory chunks
 */
union mem_chunk_mc_u {
	struct mem_chunk	*mc_next;	/* really used when it's free */
	mem_word		mc_base;/* virtually used when it's allocated */
};

struct mem_chunk {
	size_t			mc_size;	/* physical size */
	size_t			mc_usize;	/* user known size */
	struct mem_pool		*mc_pool;
	union mem_chunk_mc_u	mc_u;
};

typedef struct mem_chunk mem_chunk;

#define SIZEOF_mem_chunk (sizeof(mem_chunk)-sizeof(union mem_chunk_mc_u))

/*
 * Define the structure describing a memory pool
 */
struct mem_pool {
	struct mem_pool	*mp_prev;
	struct mem_pool	*mp_next;
	size_t		mp_size;
	size_t		mp_offset;
	int		mp_users;
	mem_chunk	mp_freechunks;
	mem_word	mp_base;
};

typedef struct mem_pool mem_pool;

#define SIZEOF_mem_pool (sizeof(mem_pool)-SIZEOF_mem_word)

/*
 * Define the structure describing a shared memory core area
 * (the actual contents depends on the shared memory and
 * semaphore/mutex type and is stripped down to a minimum
 * required)
 */
struct mem_core {
	size_t		mo_size;
	size_t		mo_usize;
	mem_word	mo_base;
};

typedef struct mem_core mem_core;

#define SIZEOF_mem_core (sizeof(mem_core)-SIZEOF_mem_word)

typedef mem_pool MM;

static MM	*mm_global = NULL;
#ifndef	MAP_ANON
static int	zerofd = -1;
#endif

/*
 * Determine memory page size of OS
 */
static size_t
mm_core_pagesize()
{
	static int pagesize = 0;

	if (pagesize == 0)
#ifdef	_SC_PAGESIZE
		pagesize = sysconf(_SC_PAGESIZE);
#else
		pagesize = 8192;
#endif
	return pagesize;
}

/*
 * Align a size to the next page or word boundary
 */
static size_t
mm_core_align2page(size_t size)
{
	int psize = mm_core_pagesize();
	return ((size) % (psize) > 0 ?
		((size) / (psize) + 1) * (psize) : (size));
}

static size_t
mm_core_align2word(size_t size)
{
	return ((1 + ((size - 1) / SIZEOF_mem_word)) * SIZEOF_mem_word);
}

/*
 * Create a shared memory area
 */
static char
*mm_core_create(size_t usersize)
{
	mem_core *mo;
	char *area = ((char *)-1);
	size_t size;

	if (usersize <= 0) {
		errno = EINVAL;
		return NULL;
	}
	size = mm_core_align2page(usersize + SIZEOF_mem_core);
#ifdef	MAP_ANON
	if ((area = (char *)mmap(NULL, size, PROT_READ | PROT_WRITE,
			MAP_ANON | MAP_PRIVATE, -1, 0)) == (char *)MAP_FAILED)
		error(catgets(catd, 1, 243,
		"failed to memory map anonymous area"));
#else	/* !MAP_ANON */
	if (zerofd == -1 && ((zerofd = open("/dev/zero", O_RDWR)) == -1
#ifdef	F_SETFD
#ifndef	FD_CLOEXEC
#define	FD_CLOEXEC	1
#endif	/* !FD_CLOEXEC */
			|| fcntl(zerofd, F_SETFD, FD_CLOEXEC) == -1
#endif	/* F_SETFD */
			    ))
		error(catgets(catd, 1, 244,
			"failed to open /dev/zero"));
	if ((area = (char *)mmap(NULL, size, PROT_READ | PROT_WRITE,
			MAP_PRIVATE, zerofd, (off_t)0)) == (char *)MAP_FAILED)
		error(catgets(catd, 1, 245,
			"failed to memory map /dev/zero"));
#endif	/* !MAP_ANON */
	/*
	 * Configure the memory core parameters
	 */
	mo = (mem_core *)area;
	mo->mo_size = size;
	mo->mo_usize = usersize;
	/*
	 * Return successfully established core
	 */
	return ((char *)&(mo->mo_base.mw_cp));
}

/*
 * Create a memory pool
 */
static MM *
mm_create(MM *mmPrev, size_t usize)
{
	MM *mm = NULL;
	char *core;
	size_t size;

	/*
	 * defaults 
	 */
	if (usize < MM_ALLOC_MINSIZE)
		usize = MM_ALLOC_MINSIZE;
	/*
	 * determine size 
	 */
	size = usize + SIZEOF_mem_pool;
	/*
	 * get a shared memory area 
	 */
	if ((core = mm_core_create(size)) == NULL)
		return NULL;
	/*
	 * fill in the memory pool structure 
	 */
	mm = (MM *)core;
	mm->mp_prev = mmPrev;
	mm->mp_next = NULL;
	mm->mp_size = size;
	mm->mp_offset = SIZEOF_mem_pool;
	mm->mp_users = 0;
	/*
	 * first element of list of free chunks counts existing chunks 
	 */
	mm->mp_freechunks.mc_size = 0;	/* has to be 0 forever */
	mm->mp_freechunks.mc_usize = 0;	/* counts chunks */
	mm->mp_freechunks.mc_u.mc_next = NULL;
	return mm;
}

/*
 * Insert a chunk to the list of free chunks. Algorithm used is:
 * Insert in sorted manner to the list and merge with previous
 * and/or next chunk when possible to form larger chunks out of
 * smaller ones.
 */
static void
mm_insert_chunk(MM *mm, mem_chunk *mcInsert)
{
	mem_chunk *mc;
	mem_chunk *mcPrev;
	mem_chunk *mcNext;

	mc = &(mm->mp_freechunks);
	while (mc->mc_u.mc_next != NULL &&
			(char *)(mc->mc_u.mc_next) < (char *)mcInsert)
		mc = mc->mc_u.mc_next;
	mcPrev = mc;
	mcNext = mc->mc_u.mc_next;
	if (mcPrev == mcInsert || mcNext == mcInsert)
		error(catgets(catd, 1, 246,
		"chunk of memory already in free list"));
	if ((char *)mcPrev + (mcPrev->mc_size) == (char *)mcInsert &&
			(mcNext != NULL &&
			 (char *)mcInsert + (mcInsert->mc_size) ==
			 (char *)mcNext)) {
		/*
		 * merge with previous and next chunk 
		 */
		mcPrev->mc_size += mcInsert->mc_size + mcNext->mc_size;
		mcPrev->mc_u.mc_next = mcNext->mc_u.mc_next;
		mm->mp_freechunks.mc_usize -= 1;
	} else if ((char *)mcPrev + (mcPrev->mc_size) ==
		   (char *)mcInsert) {
		/*
		 * merge with previous chunk 
		 */
		mcPrev->mc_size += mcInsert->mc_size;
	} else if (mcNext != NULL &&
			(char *)mcInsert + (mcInsert->mc_size) ==
			(char *)mcNext) {
		/*
		 * merge with next chunk 
		 */
		mcInsert->mc_size += mcNext->mc_size;
		mcInsert->mc_u.mc_next = mcNext->mc_u.mc_next;
		mcPrev->mc_u.mc_next = mcInsert;
	} else {
		/*
		 * no merging possible, so insert as new chunk 
		 */
		mcInsert->mc_u.mc_next = mcNext;
		mcPrev->mc_u.mc_next = mcInsert;
		mm->mp_freechunks.mc_usize += 1;
	}
}

/*
 * Retrieve a chunk from the list of free chunks.  Algorithm used
 * is: Search for minimal-sized chunk which is larger or equal
 * than the request size. But when the retrieved chunk is still a
 * lot larger than the requested size, split out the requested
 * size to not waste memory.
 */
static mem_chunk *
mm_retrieve_chunk(MM *mm, size_t size)
{
	mem_chunk *mc;
	mem_chunk **pmcMin;
	mem_chunk *mcRes;
	size_t sMin;
	size_t s;

	if (size == 0)
		return NULL;
	if (mm->mp_freechunks.mc_usize == 0)
		return NULL;
	/*
	 * find best-fitting chunk 
	 */
	pmcMin = NULL;
	sMin = mm->mp_size;	/* initialize with maximum possible */
	mc = &(mm->mp_freechunks);
	while (mc->mc_u.mc_next != NULL) {
		s = mc->mc_u.mc_next->mc_size;
		if (s >= size && s < sMin) {
			pmcMin = &(mc->mc_u.mc_next);
			sMin = s;
			if (s == size)
				break;
		}
		mc = mc->mc_u.mc_next;
	}
	/*
	 * create result chunk 
	 */
	if (pmcMin == NULL)
		mcRes = NULL;
	else {
		mcRes = *pmcMin;
		if (mcRes->mc_size >= (size + min_of(2 * size, 128))) {
			/*
			 * split out in part 
			 */
			s = mcRes->mc_size - size;
			mcRes->mc_size = size;
			/*
			 * add back remaining chunk part as new chunk 
			 */
			mc = (mem_chunk *)((char *)mcRes + size);
			mc->mc_size = s;
			mc->mc_u.mc_next = mcRes->mc_u.mc_next;
			*pmcMin = mc;
		} else {
			/*
			 * split out as a whole 
			 */
			*pmcMin = mcRes->mc_u.mc_next;
			mm->mp_freechunks.mc_usize--;
		}
	}
	return mcRes;
}

/*
 * Allocate a chunk of memory
 */
void *
malloc(size_t usize)
{
	MM *mm;
	mem_chunk *mc;
	size_t size;

	size = mm_core_align2word(SIZEOF_mem_chunk + usize);
	if (mm_global == NULL && (mm_global = mm_create(NULL, size)) == NULL)
		return NULL;
	mm = mm_global;
nextpool:
	if ((mc = mm_retrieve_chunk(mm, size)) != NULL) {
		mc->mc_usize = usize;
		mc->mc_pool = mm;
		mm->mp_users++;
		return (char *)&(mc->mc_u.mc_base.mw_cp);
	}
	if ((mm->mp_size - mm->mp_offset) < size) {
		if (mm->mp_next != NULL ||
				(mm->mp_next = mm_create(mm, size)) != NULL) {
			mm = mm->mp_next;
			goto nextpool;
		}
		error(catgets(catd, 1, 247, "out of memory"));
		errno = ENOMEM;
		return NULL;
	}
	mc = (mem_chunk *)((char *)mm + mm->mp_offset);
	mc->mc_size = size;
	mc->mc_usize = usize;
	mc->mc_pool = mm;
	mm->mp_offset += size;
	mm->mp_users++;
	return (char *)&(mc->mc_u.mc_base.mw_cp);
}

/*
 * Free a chunk of memory
 */
void
free(void *ptrV)
{
	MM *mm;
	mem_chunk *mc;
	char *ptr = ptrV;

	if (mm_global == NULL || ptr == NULL)
		return;
	mc = (mem_chunk *)((char *)ptr - SIZEOF_mem_chunk);
	mm = mc->mc_pool;
	mm_insert_chunk(mm, mc);
	if (--mm->mp_users == 0) {
		mem_core *mo = (mem_core *)((char *)mm - SIZEOF_mem_core);
		if (mm == mm_global)
			mm_global = mm->mp_next;
		if (mm->mp_prev != NULL)
			mm->mp_prev->mp_next = mm->mp_next;
		if (mm->mp_next != NULL)
			mm->mp_next->mp_prev = mm->mp_prev;
		munmap((char *)mo, mo->mo_size);
	}
}

/*
 * Reallocate a chunk of memory
 */
void *
realloc(void *ptrV, size_t usize)
{
	size_t size;
	mem_chunk *mc;
	char *vp;
	char *ptr = ptrV;

	if (ptr == NULL)
		return malloc(usize);	/* POSIX.1 semantics */
	mc = (mem_chunk *)((char *)ptr - SIZEOF_mem_chunk);
	if (usize <= mc->mc_usize) {
		mc->mc_usize = usize;
		return ptr;
	}
	size = mm_core_align2word(SIZEOF_mem_chunk + usize);
	if (size <= mc->mc_size) {
		mc->mc_usize = usize;
		return ptr;
	}
	if ((vp = malloc(usize)) == NULL)
		return NULL;
	memcpy(vp, ptr, usize);
	free(ptr);
	return vp;
}

/*
 * Allocate and initialize a chunk of memory
 */
void *
calloc(size_t number, size_t usize)
{
	char *vp;

	if ((vp = malloc(number * usize)) == NULL)
		return NULL;
	memset(vp, 0, number * usize);
	return vp;
}

#ifndef __NetBSD__ /* signature does not agree with netbsd's libcompat */
/*ARGSUSED*/
void cfree(p, num, size)
char *p;
size_t num, size;
{
	free(p);
}
#endif


/*
 * Not all systems have the following in libc, so avoid compiler warnings
 * by inserting extra global declarations.
 */
char *memalign(size_t alignment, size_t size);
void *valloc(size_t size);
char *mallinfo(void);
int mallopt(void);
char *poolsbrk(intptr_t val);


/*ARGSUSED*/
char *
memalign(alignment, size)
size_t alignment, size;
{
	(void)alignment;
	(void)size;
	return NULL;
}

/*ARGSUSED*/
void *
valloc(size)
size_t size;
{
	(void)size;
	return NULL;
}

char *
mallinfo(void)
{
	return NULL;
}

int
mallopt(void)
{
	return -1;
}

/*ARGSUSED*/
char *
poolsbrk(intptr_t val)
{
	(void)val;
	return NULL;
}
